From 8aa1a619b99ead40655bc7857258a67403ba201a Mon Sep 17 00:00:00 2001 From: cst61 Date: Sat, 30 May 2026 23:32:23 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=BF=81=E7=A7=BB=E5=88=B0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E8=A1=A8=E9=87=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rj/controller/LbOrderRowController.java | 3 +- .../LbThirdIntegrationConfigController.java | 12 +- .../com/rj/controller/LbUserController.java | 4 +- ...irdIntegrationCredentialUpdateRequest.java | 10 +- .../rj/entity/LbThirdIntegrationConfig.java | 50 ++--- .../service/HxrAdminOrderSelectService.java | 88 ++++++++- .../com/rj/service/HxrAdminUserService.java | 56 +++++- .../ILbThirdIntegrationConfigService.java | 12 ++ .../service/impl/LbOrderRowServiceImpl.java | 38 +++- .../LbThirdIntegrationConfigServiceImpl.java | 179 ++++++++++-------- .../rj/service/impl/LbUserServiceImpl.java | 27 ++- .../rj/util/LbThirdIntegrationConfigUtil.java | 81 ++++++++ src/main/sql/lb_third_integration_config.sql | 8 +- 13 files changed, 423 insertions(+), 145 deletions(-) diff --git a/src/main/java/com/rj/controller/LbOrderRowController.java b/src/main/java/com/rj/controller/LbOrderRowController.java index 3f81052..0650989 100644 --- a/src/main/java/com/rj/controller/LbOrderRowController.java +++ b/src/main/java/com/rj/controller/LbOrderRowController.java @@ -97,7 +97,8 @@ public class LbOrderRowController { @PostMapping("/sync-order-from-third") @Operation( summary = "从 外部系统 同步订单", - description = "按购买时间区间、租户 id、转卖状态调用后台 order/select,将结果写入 lb_order_row(按 tenant_id + 订单 id upsert)") + description = "按购买时间区间、租户 id、转卖状态调用后台 order/select,将结果写入 lb_order_row(按 tenant_id + 订单 id upsert);" + + "cookie、URL、Referer 等从 lb_third_integration_config 按 tenantId 读取") public ResponseEntity> syncFromHxr( @Parameter(description = "同步条件", required = true) @RequestBody LbOrderRowSyncFromHxrRequest request) { Map result = diff --git a/src/main/java/com/rj/controller/LbThirdIntegrationConfigController.java b/src/main/java/com/rj/controller/LbThirdIntegrationConfigController.java index 9f3d5d8..1096a91 100644 --- a/src/main/java/com/rj/controller/LbThirdIntegrationConfigController.java +++ b/src/main/java/com/rj/controller/LbThirdIntegrationConfigController.java @@ -28,7 +28,7 @@ public class LbThirdIntegrationConfigController { @PostMapping("/add") @Operation(summary = "新增") public ResponseEntity> add( - @Parameter(description = "配置实体(凭证请传 *Plain 明文字段)", required = true) + @Parameter(description = "配置实体(凭证请传 cookie、phpsid、goodsApiToken、goodsApiAppStr 明文字段)", required = true) @RequestBody LbThirdIntegrationConfig entity) { Map result = lbThirdIntegrationConfigService.add(entity); Boolean success = (Boolean) result.get("success"); @@ -41,7 +41,7 @@ public class LbThirdIntegrationConfigController { @PutMapping("/update") @Operation(summary = "更新") public ResponseEntity> update( - @Parameter(description = "配置实体(仅传需修改字段;凭证 *Plain 非空时才轮换)", required = true) + @Parameter(description = "配置实体(仅传需修改字段;凭证字段非空时才更新)", required = true) @RequestBody LbThirdIntegrationConfig entity) { Map result = lbThirdIntegrationConfigService.update(entity); Boolean success = (Boolean) result.get("success"); @@ -97,8 +97,8 @@ public class LbThirdIntegrationConfigController { @GetMapping("/credential/{tenantId}") @Operation( - summary = "查询租户凭证(含明文)", - description = "按 tenantId 解密返回 cookie、phpsid、goodsApiToken、goodsApiAppStr 明文,便于管理查看") + summary = "查询租户凭证", + description = "按 tenantId 返回 cookie、phpsid、goodsApiToken、goodsApiAppStr 明文") public ResponseEntity> getCredentialStatus( @Parameter(description = "租户 id", required = true) @PathVariable String tenantId) { Map result = lbThirdIntegrationConfigService.getCredentialStatusByTenantId(tenantId); @@ -117,8 +117,8 @@ public class LbThirdIntegrationConfigController { summary = "更新租户凭证", description = "仅更新 cookie/phpsid/goodsApiToken/goodsApiAppStr 凭证列;" - + "传明文则加密入库并 credential_version+1;" - + "传 clear* 为 true 则清除对应密文;至少操作一项") + + "传明文则直接入库并 credential_version+1;" + + "传 clear* 为 true 则清除对应凭证;至少操作一项") public ResponseEntity> updateCredential( @Parameter(description = "租户 id", required = true) @PathVariable String tenantId, @Parameter(description = "凭证更新请求", required = true) diff --git a/src/main/java/com/rj/controller/LbUserController.java b/src/main/java/com/rj/controller/LbUserController.java index c7b5dd9..b6dcb62 100644 --- a/src/main/java/com/rj/controller/LbUserController.java +++ b/src/main/java/com/rj/controller/LbUserController.java @@ -124,8 +124,8 @@ public class LbUserController { @Operation( summary = "从 hxrd 后台同步用户", description = - "分页调用 /app/admin/user/select(每页 limit=90),将 data 解析为 LbUser 后立即 upsert 到 lb_user;" - + "需配置 hxr.admin.cookie 或 phpsid") + "分页调用 /app/admin/user/select,将 data 解析为 LbUser 后立即 upsert 到 lb_user;" + + "cookie、URL、Referer 等从 lb_third_integration_config 按 tenantId 读取") public ResponseEntity> syncFromHxr( @Parameter(description = "租户 id", required = true) @RequestParam String tenantId) { Map result = lbUserService.syncFromHxrAdmin(tenantId); diff --git a/src/main/java/com/rj/dto/LbThirdIntegrationCredentialUpdateRequest.java b/src/main/java/com/rj/dto/LbThirdIntegrationCredentialUpdateRequest.java index 97fc8e7..cbcb03e 100644 --- a/src/main/java/com/rj/dto/LbThirdIntegrationCredentialUpdateRequest.java +++ b/src/main/java/com/rj/dto/LbThirdIntegrationCredentialUpdateRequest.java @@ -7,7 +7,7 @@ import lombok.Data; import java.time.LocalDateTime; /** - * 更新 {@code lb_third_integration_config} 凭证列的请求体(明文仅用于写入,响应不回显)。 + * 更新 {@code lb_third_integration_config} 凭证列的请求体。 */ @Data @Schema(description = "租户第三方集成凭证更新请求") @@ -32,15 +32,15 @@ public class LbThirdIntegrationCredentialUpdateRequest { @Schema(description = "操作人(可选,写入 updated_by)") private String updatedBy; - @Schema(description = "为 true 时清除 cookie_cipher") + @Schema(description = "为 true 时清除 cookie") private Boolean clearCookie; - @Schema(description = "为 true 时清除 phpsid_cipher") + @Schema(description = "为 true 时清除 phpsid") private Boolean clearPhpsid; - @Schema(description = "为 true 时清除 goods_api_token_cipher") + @Schema(description = "为 true 时清除 goods_api_token") private Boolean clearGoodsApiToken; - @Schema(description = "为 true 时清除 goods_api_app_str_cipher") + @Schema(description = "为 true 时清除 goods_api_app_str") private Boolean clearGoodsApiAppStr; } diff --git a/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java b/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java index ba390b4..9b3b768 100644 --- a/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java +++ b/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java @@ -100,21 +100,21 @@ public class LbThirdIntegrationConfig implements Serializable { @Schema(description = "Admin 鉴权类型") private String authType; - @TableField("cookie_cipher") - @Schema(description = "完整 Cookie 密文(存储字段,接口不回显)") - private byte[] cookieCipher; + @TableField("cookie") + @Schema(description = "完整 Cookie 明文") + private String cookie; - @TableField("phpsid_cipher") - @Schema(description = "PHPSID 密文(存储字段,接口不回显)") - private byte[] phpsidCipher; + @TableField("phpsid") + @Schema(description = "PHPSID 明文") + private String phpsid; - @TableField("goods_api_token_cipher") - @Schema(description = "货品/抢购 token 密文(存储字段,接口不回显)") - private byte[] goodsApiTokenCipher; + @TableField("goods_api_token") + @Schema(description = "货品/抢购 token 明文") + private String goodsApiToken; - @TableField("goods_api_app_str_cipher") - @Schema(description = "签名密钥 appStr 密文(存储字段,接口不回显)") - private byte[] goodsApiAppStrCipher; + @TableField("goods_api_app_str") + @Schema(description = "签名密钥 appStr 明文") + private String goodsApiAppStr; @TableField("credential_version") @Schema(description = "凭证版本号") @@ -176,37 +176,19 @@ public class LbThirdIntegrationConfig implements Serializable { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; - // ---------- 明文凭证(仅请求入参,不落库)---------- - @TableField(exist = false) - @Schema(description = "完整 Cookie 明文(保存时加密入库)") - private String cookiePlain; - - @TableField(exist = false) - @Schema(description = "PHPSID 明文(保存时加密入库)") - private String phpsidPlain; - - @TableField(exist = false) - @Schema(description = "货品/抢购 token 明文(保存时加密入库)") - private String goodsApiTokenPlain; - - @TableField(exist = false) - @Schema(description = "签名密钥 appStr 明文(保存时加密入库)") - private String goodsApiAppStrPlain; - - @TableField(exist = false) - @Schema(description = "是否已配置 Cookie 密文") + @Schema(description = "是否已配置 Cookie") private Boolean cookieConfigured; @TableField(exist = false) - @Schema(description = "是否已配置 PHPSID 密文") + @Schema(description = "是否已配置 PHPSID") private Boolean phpsidConfigured; @TableField(exist = false) - @Schema(description = "是否已配置货品 token 密文") + @Schema(description = "是否已配置货品 token") private Boolean goodsApiTokenConfigured; @TableField(exist = false) - @Schema(description = "是否已配置 appStr 密文") + @Schema(description = "是否已配置 appStr") private Boolean goodsApiAppStrConfigured; } diff --git a/src/main/java/com/rj/service/HxrAdminOrderSelectService.java b/src/main/java/com/rj/service/HxrAdminOrderSelectService.java index 4c91f82..ee73c6b 100644 --- a/src/main/java/com/rj/service/HxrAdminOrderSelectService.java +++ b/src/main/java/com/rj/service/HxrAdminOrderSelectService.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.rj.config.HxrAdminProperties; +import com.rj.dto.hxr.HxrAdminOrderApiContext; import com.rj.dto.hxr.HxrOrderRow; import com.rj.dto.hxr.HxrOrderSelectResponse; import lombok.RequiredArgsConstructor; @@ -87,12 +88,75 @@ public class HxrAdminOrderSelectService { buildStatusOrderSelectUrl(properties.getOrderSelectPaidUrl(), buyTimeStart, buyTimeEnd, page)); } + /** + * 按购买时间区间、转卖状态及页码拉取订单列表(使用租户 {@code lb_third_integration_config} 解析出的运行时配置)。 + */ + public Optional fetchOrderSelect( + String buyTimeStart, + String buyTimeEnd, + Integer isResell, + int page, + HxrAdminOrderApiContext ctx) + throws Exception { + return fetchOrderSelectInternal( + buildOrderSelectUrl(ctx, buyTimeStart, buyTimeEnd, isResell, null, page), + ctx); + } + + /** + * 按购买时间区间及页码拉取未支付订单({@code status=0})。 + */ + public Optional fetchUnpaidOrderSelect( + String buyTimeStart, String buyTimeEnd, int page, HxrAdminOrderApiContext ctx) + throws Exception { + return fetchOrderSelectInternal( + buildOrderSelectUrl(ctx, buyTimeStart, buyTimeEnd, null, 0, page), ctx); + } + + /** + * 按购买时间区间及页码拉取已支付订单({@code status=1})。 + */ + public Optional fetchPaidOrderSelect( + String buyTimeStart, String buyTimeEnd, int page, HxrAdminOrderApiContext ctx) + throws Exception { + return fetchOrderSelectInternal( + buildOrderSelectUrl(ctx, buyTimeStart, buyTimeEnd, null, 1, page), ctx); + } + private Optional fetchOrderSelectInternal(String requestUri) throws Exception { String cookieHeader = properties.resolveCookieHeader(); if (cookieHeader == null || cookieHeader.isBlank()) { log.warn("hxr.admin 未配置 cookie 或 phpsid,跳过拉取"); return Optional.empty(); } + return fetchOrderSelectInternal(requestUri, cookieHeader, null); + } + + private Optional fetchOrderSelectInternal( + String requestUri, HxrAdminOrderApiContext ctx) throws Exception { + if (ctx == null) { + log.warn("订单 API 配置为空,跳过 order/select"); + return Optional.empty(); + } + String cookieHeader = ctx.cookieHeader(); + if (cookieHeader == null || cookieHeader.isBlank()) { + log.warn("未配置 cookie 或 phpsid,跳过 order/select"); + return Optional.empty(); + } + log.info("requestUri: {}", requestUri); + return fetchOrderSelectInternal(requestUri, cookieHeader, ctx.referer()); + } + + private Optional fetchOrderSelectInternal( + String requestUri, String cookieHeader, String referer) throws Exception { + if (cookieHeader == null || cookieHeader.isBlank()) { + log.warn("未配置 cookie 或 phpsid,跳过 order/select"); + return Optional.empty(); + } + String refererHeader = referer; + if (refererHeader == null || refererHeader.isBlank()) { + refererHeader = "https://hxrdhoutai.hxrdsm.cn/app/admin/order/index"; + } HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(35)) @@ -106,7 +170,7 @@ public class HxrAdminOrderSelectService { .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") .header("Cookie", cookieHeader) .header("Priority", "u=1, i") - .header("Referer", "https://hxrdhoutai.hxrdsm.cn/app/admin/order/index") + .header("Referer", refererHeader) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0") .GET() .build(); @@ -126,6 +190,28 @@ public class HxrAdminOrderSelectService { return Optional.of(body); } + private String buildOrderSelectUrl( + HxrAdminOrderApiContext ctx, + String buyTimeStart, + String buyTimeEnd, + Integer isResell, + Integer status, + int page) { + int pageLimit = ctx.pageLimit() > 0 ? ctx.pageLimit() : 90; + UriComponentsBuilder b = UriComponentsBuilder.fromUriString(ctx.orderSelectBaseUrl()); + b.replaceQueryParam("page", Math.max(1, page)); + b.replaceQueryParam("limit", pageLimit); + b.replaceQueryParam("buy_time[0]", buyTimeStart == null ? "" : buyTimeStart); + b.replaceQueryParam("buy_time[1]", buyTimeEnd == null ? "" : buyTimeEnd); + if (isResell != null) { + b.replaceQueryParam("is_resell", isResell); + } + if (status != null) { + b.replaceQueryParam("status", status); + } + return b.build().encode().toUriString(); + } + /** * 在 {@link HxrAdminProperties#getOrderSelectUrl()} 上覆盖 {@code buy_time[0]}、{@code buy_time[1]}、{@code is_resell}。 */ diff --git a/src/main/java/com/rj/service/HxrAdminUserService.java b/src/main/java/com/rj/service/HxrAdminUserService.java index 773f76d..7007669 100644 --- a/src/main/java/com/rj/service/HxrAdminUserService.java +++ b/src/main/java/com/rj/service/HxrAdminUserService.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.rj.config.HxrAdminProperties; +import com.rj.dto.hxr.HxrAdminUserApiContext; import com.rj.dto.hxr.HxrLbUserSelectResponse; import com.rj.dto.hxr.HxrSimpleApiResponse; import com.rj.dto.hxr.HxrUserRow; @@ -102,7 +103,7 @@ public class HxrAdminUserService { } /** - * 分页拉取用户列表,{@code data} 解析为 {@link }(与 lb_user 字段对应)。 + * 分页拉取用户列表(使用 {@code hxr.admin} 配置文件,兼容旧调用)。 * * @param page 页码,从 1 开始 */ @@ -112,10 +113,41 @@ public class HxrAdminUserService { log.warn("hxr.admin 未配置 cookie 或 phpsid,跳过 user/select"); return Optional.empty(); } + HxrAdminUserApiContext ctx = new HxrAdminUserApiContext( + stripQuery(properties.getUserSelectUrl()), + USER_SELECT_PAGE_SIZE, + cookieHeader, + "https://hxrdhoutai.hxrdsm.cn/app/admin/user/index"); + return fetchUserSelectPage(page, ctx); + } - String uri = UriComponentsBuilder.fromUriString(properties.getUserSelectUrl()) - .replaceQueryParam("page", Math.max(1, page)) - .replaceQueryParam("limit", USER_SELECT_PAGE_SIZE) + /** + * 分页拉取用户列表(使用租户 {@code lb_third_integration_config} 解析出的运行时配置)。 + * + * @param page 页码,从 1 开始 + */ + public Optional fetchUserSelectPage(int page, HxrAdminUserApiContext ctx) + throws Exception { + if (ctx == null) { + log.warn("用户 API 配置为空,跳过 user/select"); + return Optional.empty(); + } + String cookieHeader = ctx.cookieHeader(); + if (cookieHeader == null || cookieHeader.isBlank()) { + log.warn("未配置 cookie 或 phpsid,跳过 user/select"); + return Optional.empty(); + } + String userSelectBaseUrl = ctx.userSelectBaseUrl(); + if (userSelectBaseUrl == null || userSelectBaseUrl.isBlank()) { + log.warn("未配置 userSelectBaseUrl,跳过 user/select"); + return Optional.empty(); + } + + int pageLimit = ctx.pageLimit() > 0 ? ctx.pageLimit() : USER_SELECT_PAGE_SIZE; + int safePage = Math.max(1, page); + String uri = UriComponentsBuilder.fromUriString(userSelectBaseUrl) + .replaceQueryParam("page", safePage) + .replaceQueryParam("limit", pageLimit) .replaceQueryParam("mobile", "") .replaceQueryParam("id", "") .replaceQueryParam("pid", "") @@ -123,6 +155,12 @@ public class HxrAdminUserService { .encode() .toUriString(); + String referer = ctx.referer(); + if (referer == null || referer.isBlank()) { + log.warn("未配置 referer ,跳过 user/select"); + return Optional.empty(); + } + HttpClient client = HttpClient.newBuilder() .connectTimeout(java.time.Duration.ofSeconds(35)) .followRedirects(HttpClient.Redirect.NORMAL) @@ -135,7 +173,7 @@ public class HxrAdminUserService { .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") .header("Cookie", cookieHeader) .header("Priority", "u=1, i") - .header("Referer", "https://hxrdhoutai.hxrdsm.cn/app/admin/user/index") + .header("Referer", referer) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36 Edg/148.0.0.0") .GET() .build(); @@ -302,6 +340,14 @@ public class HxrAdminUserService { return form.toString(); } + private static String stripQuery(String url) { + if (url == null) { + return ""; + } + int q = url.indexOf('?'); + return q >= 0 ? url.substring(0, q) : url; + } + private static String abbreviate(String s, int maxLen) { if (s == null) { return ""; diff --git a/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java b/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java index 7c08e2d..05efdc0 100644 --- a/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java +++ b/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java @@ -2,6 +2,8 @@ package com.rj.service; import com.baomidou.mybatisplus.extension.service.IService; import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest; +import com.rj.dto.hxr.HxrAdminOrderApiContext; +import com.rj.dto.hxr.HxrAdminUserApiContext; import com.rj.dto.hxr.HxrGoodsApiContext; import com.rj.entity.LbThirdIntegrationConfig; @@ -42,4 +44,14 @@ public interface ILbThirdIntegrationConfigService extends IService resolveGoodsApiContext(String tenantId, String tokenOverride); + + /** + * 按租户 id 解析后台用户 API 运行时配置(含 cookie/phpsid、URL、Referer)。 + */ + Optional resolveAdminUserApiContext(String tenantId); + + /** + * 按租户 id 解析后台订单 API 运行时配置(含 cookie/phpsid、URL、Referer)。 + */ + Optional resolveAdminOrderApiContext(String tenantId); } diff --git a/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java b/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java index 24120be..2dd6428 100644 --- a/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java @@ -3,6 +3,7 @@ package com.rj.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.rj.dto.hxr.HxrAdminOrderApiContext; import com.rj.dto.hxr.HxrOrderRow; import com.rj.dto.hxr.HxrOrderSelectResponse; import com.rj.entity.LbDepartmentUser; @@ -35,9 +36,6 @@ import java.util.stream.Collectors; @Service public class LbOrderRowServiceImpl extends ServiceImpl implements ILbOrderRowService { - /** 与 hxr.admin.order-select-url 中 {@code limit} 一致,用于判断是否还有下一页 */ - private static final int HXR_ORDER_PAGE_SIZE = 90; - /** 从第三方同步的订单明细默认 data_type */ private static final String SYNC_DATA_TYPE_DETAIL = "detail_data"; @@ -59,6 +57,9 @@ public class LbOrderRowServiceImpl extends ServiceImpl apiContextOpt = + lbThirdIntegrationConfigService.resolveAdminOrderApiContext(tid); + if (apiContextOpt.isEmpty()) { + result.put("success", false); + result.put("message", + "未找到该租户的第三方集成配置,或配置未启用、URL/凭证不完整(请检查 lb_third_integration_config)"); + result.put("synced", 0); + return result; + } + HxrAdminOrderApiContext apiContext = apiContextOpt.get(); + int orderPageLimit = apiContext.pageLimit() > 0 ? apiContext.pageLimit() : 90; + String previousTenantId = TenantContextHolder.getTenantId(); TenantContextHolder.setTenantId(tid); try { @@ -298,11 +311,12 @@ public class LbOrderRowServiceImpl extends ServiceImpl opt = - fetchHxrOrderPage(buyTimeStart, buyTimeEnd, isResell, hxrOrderStatus, page); + fetchHxrOrderPage(buyTimeStart, buyTimeEnd, isResell, hxrOrderStatus, page, apiContext); if (opt.isEmpty()) { if (page == 1) { result.put("success", false); - result.put("message", "未拉取到订单(请检查 hxr.admin cookie/phpsid、网络或后台返回)"); + result.put("message", + "未拉取到订单(请检查 lb_third_integration_config 中 cookie/phpsid、URL 或网络)"); result.put("synced", 0); return result; } @@ -346,7 +360,7 @@ public class LbOrderRowServiceImpl extends ServiceImpl resolveAdminUserApiContext(String tenantId) { + if (!StringUtils.hasText(tenantId)) { + return Optional.empty(); + } + LbThirdIntegrationConfig config = getByTenantIdOrNull(tenantId.trim()); + if (config == null) { + return Optional.empty(); + } + if (config.getEnabled() == null || config.getEnabled() != 1) { + return Optional.empty(); + } + + String cookieHeader = LbThirdIntegrationConfigUtil.resolveCookieHeader(config); + if (!StringUtils.hasText(cookieHeader)) { + return Optional.empty(); + } + + try { + return Optional.of(new HxrAdminUserApiContext( + LbThirdIntegrationConfigUtil.resolveUserSelectBaseUrl(config), + LbThirdIntegrationConfigUtil.resolveUserPageLimit(config), + cookieHeader.trim(), + LbThirdIntegrationConfigUtil.resolveUserReferer(config))); + } catch (IllegalStateException e) { + return Optional.empty(); + } + } + + @Override + public Optional resolveAdminOrderApiContext(String tenantId) { + if (!StringUtils.hasText(tenantId)) { + return Optional.empty(); + } + LbThirdIntegrationConfig config = getByTenantIdOrNull(tenantId.trim()); + if (config == null) { + return Optional.empty(); + } + if (config.getEnabled() == null || config.getEnabled() != 1) { + return Optional.empty(); + } + + String cookieHeader = LbThirdIntegrationConfigUtil.resolveCookieHeader(config); + if (!StringUtils.hasText(cookieHeader)) { + return Optional.empty(); + } + + try { + return Optional.of(new HxrAdminOrderApiContext( + LbThirdIntegrationConfigUtil.resolveOrderSelectBaseUrl(config), + LbThirdIntegrationConfigUtil.resolveOrderPageLimit(config), + cookieHeader.trim(), + LbThirdIntegrationConfigUtil.resolveOrderReferer(config))); + } catch (IllegalStateException e) { + return Optional.empty(); + } + } + private LbThirdIntegrationConfig getByTenantIdOrNull(String tenantId) { return this.getOne(new LambdaQueryWrapper() .eq(LbThirdIntegrationConfig::getTenantId, tenantId) @@ -359,7 +414,7 @@ public class LbThirdIntegrationConfigServiceImpl || Boolean.TRUE.equals(request.getClearGoodsApiToken()) || Boolean.TRUE.equals(request.getClearGoodsApiAppStr()); if (!hasUpdate && !hasClear) { - return "请至少提供一项凭证明文,或指定一项 clear* 清除操作"; + return "请至少提供一项凭证,或指定一项 clear* 清除操作"; } if (Boolean.TRUE.equals(request.getClearCookie()) && StringUtils.hasText(request.getCookie())) { return "clearCookie 与 cookie 不能同时传"; @@ -380,27 +435,27 @@ public class LbThirdIntegrationConfigServiceImpl LambdaUpdateWrapper uw, LbThirdIntegrationCredentialUpdateRequest request) { if (Boolean.TRUE.equals(request.getClearCookie())) { - uw.set(LbThirdIntegrationConfig::getCookieCipher, null); + uw.set(LbThirdIntegrationConfig::getCookie, null); } else if (StringUtils.hasText(request.getCookie())) { - uw.set(LbThirdIntegrationConfig::getCookieCipher, encryptToBytes(request.getCookie())); + uw.set(LbThirdIntegrationConfig::getCookie, request.getCookie().trim()); } if (Boolean.TRUE.equals(request.getClearPhpsid())) { - uw.set(LbThirdIntegrationConfig::getPhpsidCipher, null); + uw.set(LbThirdIntegrationConfig::getPhpsid, null); } else if (StringUtils.hasText(request.getPhpsid())) { - uw.set(LbThirdIntegrationConfig::getPhpsidCipher, encryptToBytes(request.getPhpsid())); + uw.set(LbThirdIntegrationConfig::getPhpsid, request.getPhpsid().trim()); } if (Boolean.TRUE.equals(request.getClearGoodsApiToken())) { - uw.set(LbThirdIntegrationConfig::getGoodsApiTokenCipher, null); + uw.set(LbThirdIntegrationConfig::getGoodsApiToken, null); } else if (StringUtils.hasText(request.getGoodsApiToken())) { - uw.set(LbThirdIntegrationConfig::getGoodsApiTokenCipher, encryptToBytes(request.getGoodsApiToken())); + uw.set(LbThirdIntegrationConfig::getGoodsApiToken, request.getGoodsApiToken().trim()); } if (Boolean.TRUE.equals(request.getClearGoodsApiAppStr())) { - uw.set(LbThirdIntegrationConfig::getGoodsApiAppStrCipher, null); + uw.set(LbThirdIntegrationConfig::getGoodsApiAppStr, null); } else if (StringUtils.hasText(request.getGoodsApiAppStr())) { - uw.set(LbThirdIntegrationConfig::getGoodsApiAppStrCipher, encryptToBytes(request.getGoodsApiAppStr())); + uw.set(LbThirdIntegrationConfig::getGoodsApiAppStr, request.getGoodsApiAppStr().trim()); } } @@ -410,20 +465,20 @@ public class LbThirdIntegrationConfigServiceImpl data.put("tenantId", config.getTenantId()); data.put("providerCode", config.getProviderCode()); data.put("authType", config.getAuthType()); - data.put("cookieConfigured", hasCipher(config.getCookieCipher())); - data.put("phpsidConfigured", hasCipher(config.getPhpsidCipher())); - data.put("goodsApiTokenConfigured", hasCipher(config.getGoodsApiTokenCipher())); - data.put("goodsApiAppStrConfigured", hasCipher(config.getGoodsApiAppStrCipher())); + data.put("cookieConfigured", isConfigured(config.getCookie())); + data.put("phpsidConfigured", isConfigured(config.getPhpsid())); + data.put("goodsApiTokenConfigured", isConfigured(config.getGoodsApiToken())); + data.put("goodsApiAppStrConfigured", isConfigured(config.getGoodsApiAppStr())); data.put("credentialVersion", config.getCredentialVersion()); data.put("credentialExpireTime", config.getCredentialExpireTime()); data.put("lastVerifiedTime", config.getLastVerifiedTime()); data.put("lastVerifiedOk", config.getLastVerifiedOk()); data.put("updateTime", config.getUpdateTime()); if (includePlaintext) { - data.put("cookie", decryptFromBytes(config.getCookieCipher())); - data.put("phpsid", decryptFromBytes(config.getPhpsidCipher())); - data.put("goodsApiToken", decryptFromBytes(config.getGoodsApiTokenCipher())); - data.put("goodsApiAppStr", decryptFromBytes(config.getGoodsApiAppStrCipher())); + data.put("cookie", config.getCookie()); + data.put("phpsid", config.getPhpsid()); + data.put("goodsApiToken", config.getGoodsApiToken()); + data.put("goodsApiAppStr", config.getGoodsApiAppStr()); } return data; } @@ -496,85 +551,61 @@ public class LbThirdIntegrationConfigServiceImpl } /** - * 将明文凭证加密为 VARBINARY;未传明文则保留已有密文(更新场景)。 + * 更新场景下保留未传凭证字段的已有值。 * * @return 是否有任一凭证字段被更新 */ - private boolean applyCredentialEncryption(LbThirdIntegrationConfig entity, LbThirdIntegrationConfig existing) { + private boolean applyCredentials(LbThirdIntegrationConfig entity, LbThirdIntegrationConfig existing) { boolean updated = false; - if (StringUtils.hasText(entity.getCookiePlain())) { - entity.setCookieCipher(encryptToBytes(entity.getCookiePlain())); + if (StringUtils.hasText(entity.getCookie())) { + entity.setCookie(entity.getCookie().trim()); updated = true; } else if (existing != null) { - entity.setCookieCipher(existing.getCookieCipher()); + entity.setCookie(existing.getCookie()); } - if (StringUtils.hasText(entity.getPhpsidPlain())) { - entity.setPhpsidCipher(encryptToBytes(entity.getPhpsidPlain())); + if (StringUtils.hasText(entity.getPhpsid())) { + entity.setPhpsid(entity.getPhpsid().trim()); updated = true; } else if (existing != null) { - entity.setPhpsidCipher(existing.getPhpsidCipher()); + entity.setPhpsid(existing.getPhpsid()); } - if (StringUtils.hasText(entity.getGoodsApiTokenPlain())) { - entity.setGoodsApiTokenCipher(encryptToBytes(entity.getGoodsApiTokenPlain())); + if (StringUtils.hasText(entity.getGoodsApiToken())) { + entity.setGoodsApiToken(entity.getGoodsApiToken().trim()); updated = true; } else if (existing != null) { - entity.setGoodsApiTokenCipher(existing.getGoodsApiTokenCipher()); + entity.setGoodsApiToken(existing.getGoodsApiToken()); } - if (StringUtils.hasText(entity.getGoodsApiAppStrPlain())) { - entity.setGoodsApiAppStrCipher(encryptToBytes(entity.getGoodsApiAppStrPlain())); + if (StringUtils.hasText(entity.getGoodsApiAppStr())) { + entity.setGoodsApiAppStr(entity.getGoodsApiAppStr().trim()); updated = true; } else if (existing != null) { - entity.setGoodsApiAppStrCipher(existing.getGoodsApiAppStrCipher()); + entity.setGoodsApiAppStr(existing.getGoodsApiAppStr()); } - entity.setCookiePlain(null); - entity.setPhpsidPlain(null); - entity.setGoodsApiTokenPlain(null); - entity.setGoodsApiAppStrPlain(null); return updated; } - private static byte[] encryptToBytes(String plainText) { - String encrypted = PasswordUtil.encryptReversibleWithDefaultSalt(plainText); - return encrypted.getBytes(StandardCharsets.UTF_8); - } - - /** - * 供业务层读取凭证明文(内部使用) - */ - public static String decryptFromBytes(byte[] cipherBytes) { - if (cipherBytes == null || cipherBytes.length == 0) { - return null; - } - String stored = new String(cipherBytes, StandardCharsets.UTF_8); - return PasswordUtil.decryptReversibleWithDefaultSalt(stored); - } - private LbThirdIntegrationConfig maskForResponse(LbThirdIntegrationConfig entity) { if (entity == null) { return null; } - entity.setCookieConfigured(hasCipher(entity.getCookieCipher())); - entity.setPhpsidConfigured(hasCipher(entity.getPhpsidCipher())); - entity.setGoodsApiTokenConfigured(hasCipher(entity.getGoodsApiTokenCipher())); - entity.setGoodsApiAppStrConfigured(hasCipher(entity.getGoodsApiAppStrCipher())); + entity.setCookieConfigured(isConfigured(entity.getCookie())); + entity.setPhpsidConfigured(isConfigured(entity.getPhpsid())); + entity.setGoodsApiTokenConfigured(isConfigured(entity.getGoodsApiToken())); + entity.setGoodsApiAppStrConfigured(isConfigured(entity.getGoodsApiAppStr())); - entity.setCookieCipher(null); - entity.setPhpsidCipher(null); - entity.setGoodsApiTokenCipher(null); - entity.setGoodsApiAppStrCipher(null); - entity.setCookiePlain(null); - entity.setPhpsidPlain(null); - entity.setGoodsApiTokenPlain(null); - entity.setGoodsApiAppStrPlain(null); + entity.setCookie(null); + entity.setPhpsid(null); + entity.setGoodsApiToken(null); + entity.setGoodsApiAppStr(null); return entity; } - private static boolean hasCipher(byte[] cipher) { - return cipher != null && cipher.length > 0; + private static boolean isConfigured(String value) { + return StringUtils.hasText(value); } } diff --git a/src/main/java/com/rj/service/impl/LbUserServiceImpl.java b/src/main/java/com/rj/service/impl/LbUserServiceImpl.java index 39cec65..8035c1c 100644 --- a/src/main/java/com/rj/service/impl/LbUserServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbUserServiceImpl.java @@ -3,10 +3,12 @@ package com.rj.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.rj.dto.hxr.HxrAdminUserApiContext; import com.rj.dto.hxr.HxrLbUserSelectResponse; import com.rj.entity.LbUser; import com.rj.mapper.LbUserMapper; import com.rj.service.HxrAdminUserService; +import com.rj.service.ILbThirdIntegrationConfigService; import com.rj.service.ILbUserService; import com.rj.tenant.TenantContextHolder; import lombok.extern.slf4j.Slf4j; @@ -34,6 +36,9 @@ public class LbUserServiceImpl extends ServiceImpl impleme @Autowired private HxrAdminUserService hxrAdminUserService; + @Autowired + private ILbThirdIntegrationConfigService lbThirdIntegrationConfigService; + @Override public Map add(LbUser entity) { Map result = new HashMap<>(); @@ -313,6 +318,20 @@ public class LbUserServiceImpl extends ServiceImpl impleme } String tid = tenantId.trim(); + Optional apiContextOpt = + lbThirdIntegrationConfigService.resolveAdminUserApiContext(tid); + if (apiContextOpt.isEmpty()) { + result.put("success", false); + result.put("message", + "未找到该租户的第三方集成配置,或配置未启用、URL/凭证不完整(请检查 lb_third_integration_config)"); + result.put("synced", 0); + return result; + } + HxrAdminUserApiContext apiContext = apiContextOpt.get(); + int userPageLimit = apiContext.pageLimit() > 0 + ? apiContext.pageLimit() + : HxrAdminUserService.USER_SELECT_PAGE_SIZE; + String previousTenantId = TenantContextHolder.getTenantId(); TenantContextHolder.setTenantId(tid); try { @@ -320,11 +339,13 @@ public class LbUserServiceImpl extends ServiceImpl impleme int page = 1; int totalSynced = 0; while (true) { - Optional opt = hxrAdminUserService.fetchUserSelectPage(page); + Optional opt = + hxrAdminUserService.fetchUserSelectPage(page, apiContext); if (opt.isEmpty()) { if (page == 1) { result.put("success", false); - result.put("message", "未拉取到用户(请检查 hxr.admin cookie/phpsid、网络或后台返回)"); + result.put("message", + "未拉取到用户(请检查 lb_third_integration_config 中 cookie/phpsid、URL 或网络)"); result.put("synced", 0); return result; } @@ -370,7 +391,7 @@ public class LbUserServiceImpl extends ServiceImpl impleme } totalSynced += upserted; - if (rows.size() < HxrAdminUserService.USER_SELECT_PAGE_SIZE) { + if (rows.size() < userPageLimit) { break; } page++; diff --git a/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java b/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java index 62651ea..3f8c409 100644 --- a/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java +++ b/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java @@ -47,6 +47,87 @@ public final class LbThirdIntegrationConfigUtil { return limit; } + public static String resolveUserSelectBaseUrl(LbThirdIntegrationConfig config) { + String base = trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl")); + String path = config.getUserSelectPath(); + if (!StringUtils.hasText(path)) { + path = LbThirdIntegrationConstants.DEFAULT_USER_SELECT_PATH; + } + path = path.trim(); + if (!path.startsWith("/")) { + path = "/" + path; + } + return base + path; + } + + public static String resolveUserReferer(LbThirdIntegrationConfig config) { + if (StringUtils.hasText(config.getUserReferer())) { + return config.getUserReferer().trim(); + } + return trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl")) + + "/app/admin/user/index"; + } + + public static int resolveUserPageLimit(LbThirdIntegrationConfig config) { + Integer limit = config.getUserPageLimit(); + if (limit == null || limit < 1) { + return LbThirdIntegrationConstants.DEFAULT_USER_PAGE_LIMIT; + } + return limit; + } + + public static String resolveOrderSelectBaseUrl(LbThirdIntegrationConfig config) { + String base = trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl")); + String path = config.getOrderSelectPath(); + if (!StringUtils.hasText(path)) { + path = LbThirdIntegrationConstants.DEFAULT_ORDER_SELECT_PATH; + } + path = path.trim(); + if (!path.startsWith("/")) { + path = "/" + path; + } + return base + path; + } + + public static String resolveOrderReferer(LbThirdIntegrationConfig config) { + if (StringUtils.hasText(config.getOrderReferer())) { + return config.getOrderReferer().trim(); + } + return trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl")) + + "/app/admin/order/index"; + } + + public static int resolveOrderPageLimit(LbThirdIntegrationConfig config) { + Integer limit = config.getOrderPageLimit(); + if (limit == null || limit < 1) { + return LbThirdIntegrationConstants.DEFAULT_ORDER_PAGE_LIMIT; + } + return limit; + } + + /** + * 组装 Cookie 请求头:优先完整 cookie,否则 {@code PHPSID=} + phpsid。 + */ + public static String resolveCookieHeader(LbThirdIntegrationConfig config) { + String c = firstNonBlank(config.getCookie()); + if (c != null) { + return c; + } + String id = firstNonBlank(config.getPhpsid()); + if (id != null) { + return "PHPSID=" + id; + } + return ""; + } + + private static String firstNonBlank(String s) { + if (s == null) { + return null; + } + String t = s.trim(); + return t.isEmpty() ? null : t; + } + private static String requireText(String value, String fieldName) { if (!StringUtils.hasText(value)) { throw new IllegalStateException(fieldName + " 未配置"); diff --git a/src/main/sql/lb_third_integration_config.sql b/src/main/sql/lb_third_integration_config.sql index 1b25f4a..431f119 100644 --- a/src/main/sql/lb_third_integration_config.sql +++ b/src/main/sql/lb_third_integration_config.sql @@ -26,10 +26,10 @@ CREATE TABLE `lb_third_integration_config` ( `goods_api_referer` VARCHAR(256) DEFAULT NULL COMMENT '货品 Referer;空则 web_base_url/', `auth_type` VARCHAR(32) NOT NULL DEFAULT 'COOKIE_PHPSID' COMMENT 'Admin 鉴权类型', - `cookie_cipher` VARBINARY(1024) DEFAULT NULL COMMENT '完整 Cookie 密文(优先使用)', - `phpsid_cipher` VARBINARY(512) DEFAULT NULL COMMENT 'PHPSID 值密文', - `goods_api_token_cipher` VARBINARY(512) DEFAULT NULL COMMENT '货品/抢购 token 密文', - `goods_api_app_str_cipher` VARBINARY(512) DEFAULT NULL COMMENT '签名密钥 appStr 密文', + `cookie` VARCHAR(1024) DEFAULT NULL COMMENT '完整 Cookie 明文(优先使用)', + `phpsid` VARCHAR(512) DEFAULT NULL COMMENT 'PHPSID 值明文', + `goods_api_token` VARCHAR(512) DEFAULT NULL COMMENT '货品/抢购 token 明文', + `goods_api_app_str` VARCHAR(512) DEFAULT NULL COMMENT '签名密钥 appStr 明文', `credential_version` INT NOT NULL DEFAULT 1 COMMENT '凭证版本号,轮换时递增', `credential_expire_time` DATETIME DEFAULT NULL COMMENT '凭证预计过期时间',