From bf685417e7942b18cf27f04dce9ef1f6261ac31c Mon Sep 17 00:00:00 2001 From: cst61 Date: Sat, 30 May 2026 22:03:52 +0800 Subject: [PATCH] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E8=BF=81=E7=A7=BB=E5=88=B0?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/rj/controller/LbGoodsController.java | 9 +-- .../com/rj/dto/hxr/HxrGoodsApiContext.java | 13 ++++ .../com/rj/service/HxrAdminGoodsService.java | 56 ++++++++++++++-- .../ILbThirdIntegrationConfigService.java | 7 ++ .../rj/service/impl/LbGoodsServiceImpl.java | 20 +++++- .../LbThirdIntegrationConfigServiceImpl.java | 37 +++++++++++ .../rj/util/LbThirdIntegrationConfigUtil.java | 64 +++++++++++++++++++ 7 files changed, 194 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/rj/dto/hxr/HxrGoodsApiContext.java create mode 100644 src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java diff --git a/src/main/java/com/rj/controller/LbGoodsController.java b/src/main/java/com/rj/controller/LbGoodsController.java index ce96b8a..147a3ba 100644 --- a/src/main/java/com/rj/controller/LbGoodsController.java +++ b/src/main/java/com/rj/controller/LbGoodsController.java @@ -118,12 +118,13 @@ public class LbGoodsController { @Operation( summary = "从 hxrd 第三方同步货品", description = - "分页调用 GET /api/order/goods(每页 limit=20,自动计算 S/T/N 签名)," - + "解析 data.list 后 upsert 到 lb_goods;需配置 hxr.admin.goods-api-token、goods-api-app-str;" - + "token 可选,传入时替代配置中的 goods-api-token") + "分页调用 GET /api/order/goods(自动计算 S/T/N 签名)," + + "解析 data.list 后 upsert 到 lb_goods;" + + "token、URL、appStr 等从 lb_third_integration_config 按 tenantId 读取;" + + "token 可选,传入时覆盖库中 goodsApiToken") public ResponseEntity> syncFromHxr( @Parameter(description = "租户 id", required = true) @RequestParam String tenantId, - @Parameter(description = "hxrd 接口请求头 token;未传时使用 hxr.admin.goods-api-token") + @Parameter(description = "hxrd 接口请求头 token;未传时使用 lb_third_integration_config 中的 goodsApiToken") @RequestParam(required = false) String token) { Map result = lbGoodsService.syncFromHxrGoods(tenantId, token); diff --git a/src/main/java/com/rj/dto/hxr/HxrGoodsApiContext.java b/src/main/java/com/rj/dto/hxr/HxrGoodsApiContext.java new file mode 100644 index 0000000..972137b --- /dev/null +++ b/src/main/java/com/rj/dto/hxr/HxrGoodsApiContext.java @@ -0,0 +1,13 @@ +package com.rj.dto.hxr; + +/** + * 调用 hxrd 货品/抢购 API 所需的运行时配置(由 {@code lb_third_integration_config} 解析而来)。 + */ +public record HxrGoodsApiContext( + String goodsApiBaseUrl, + int pageLimit, + String origin, + String referer, + String token, + String appStr) { +} diff --git a/src/main/java/com/rj/service/HxrAdminGoodsService.java b/src/main/java/com/rj/service/HxrAdminGoodsService.java index 59d5c9a..b3fe803 100644 --- a/src/main/java/com/rj/service/HxrAdminGoodsService.java +++ b/src/main/java/com/rj/service/HxrAdminGoodsService.java @@ -7,6 +7,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.HxrGoodsApiContext; import com.rj.dto.hxr.HxrLbGoodsSelectResponse; import com.rj.util.HxrGoodsSignUtil; import lombok.RequiredArgsConstructor; @@ -49,7 +50,7 @@ public class HxrAdminGoodsService { private final HxrAdminProperties properties; /** - * 分页拉取货品列表。 + * 分页拉取货品列表(使用 {@code hxr.admin} 配置文件,兼容旧调用)。 * * @param page 页码,从 1 开始 * @param token 可选;非空时作为请求头 token,否则使用 {@code hxr.admin.goods-api-token} @@ -65,11 +66,45 @@ public class HxrAdminGoodsService { log.warn("hxr.admin 未配置 goods-api-app-str,跳过 /api/order/goods"); return Optional.empty(); } + HxrGoodsApiContext ctx = new HxrGoodsApiContext( + stripQuery(properties.getGoodsApiUrl()), + GOODS_PAGE_SIZE, + properties.getGoodsApiOrigin(), + properties.getGoodsApiReferer(), + resolvedToken, + appStr.trim()); + return fetchGoodsPage(page, ctx); + } + /** + * 分页拉取货品列表(使用租户 {@code lb_third_integration_config} 解析出的运行时配置)。 + */ + public Optional fetchGoodsPage(int page, HxrGoodsApiContext ctx) throws Exception { + if (ctx == null) { + log.warn("货品 API 配置为空,跳过 /api/order/goods"); + return Optional.empty(); + } + String resolvedToken = ctx.token(); + if (resolvedToken == null || resolvedToken.isBlank()) { + log.warn("未提供 token,跳过 /api/order/goods"); + return Optional.empty(); + } + String appStr = ctx.appStr(); + if (appStr == null || appStr.isBlank()) { + log.warn("未配置 goodsApiAppStr,跳过 /api/order/goods"); + return Optional.empty(); + } + String goodsApiBaseUrl = ctx.goodsApiBaseUrl(); + if (goodsApiBaseUrl == null || goodsApiBaseUrl.isBlank()) { + log.warn("未配置 goodsApiBaseUrl,跳过 /api/order/goods"); + return Optional.empty(); + } + + int pageLimit = ctx.pageLimit() > 0 ? ctx.pageLimit() : GOODS_PAGE_SIZE; int safePage = Math.max(1, page); - String uri = UriComponentsBuilder.fromUriString(properties.getGoodsApiUrl()) + String uri = UriComponentsBuilder.fromUriString(goodsApiBaseUrl) .replaceQueryParam("page", safePage) - .replaceQueryParam("limit", GOODS_PAGE_SIZE) + .replaceQueryParam("limit", pageLimit) .build() .encode() .toUriString(); @@ -78,7 +113,7 @@ public class HxrAdminGoodsService { String noncestr = randomNoncestr(); Map signParams = new LinkedHashMap<>(); signParams.put("page", safePage); - signParams.put("limit", GOODS_PAGE_SIZE); + signParams.put("limit", pageLimit); signParams.put("timestamp", timestamp); signParams.put("noncestr", noncestr); String sign = HxrGoodsSignUtil.computeSign(signParams, appStr.trim()); @@ -93,8 +128,8 @@ public class HxrAdminGoodsService { .timeout(Duration.ofSeconds(120)) .header("Accept", "application/json,*/*") .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") - .header("Origin", properties.getGoodsApiOrigin()) - .header("Referer", properties.getGoodsApiReferer()) + .header("Origin", ctx.origin()) + .header("Referer", ctx.referer()) .header("User-Agent", USER_AGENT) .header("token", resolvedToken) .header("S", sign) @@ -136,6 +171,15 @@ public class HxrAdminGoodsService { return Optional.of(body); } + /** 从完整 URL 中去掉 query,供配置文件回退路径使用。 */ + private static String stripQuery(String url) { + if (url == null || url.isBlank()) { + return url; + } + int q = url.indexOf('?'); + return q >= 0 ? url.substring(0, q) : url; + } + /** 优先使用入参 token,否则回退到配置 {@code goods-api-token}。 */ private String resolveToken(String token) { if (token != null && !token.isBlank()) { diff --git a/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java b/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java index bf48b7b..7c08e2d 100644 --- a/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java +++ b/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java @@ -2,9 +2,11 @@ package com.rj.service; import com.baomidou.mybatisplus.extension.service.IService; import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest; +import com.rj.dto.hxr.HxrGoodsApiContext; import com.rj.entity.LbThirdIntegrationConfig; import java.util.Map; +import java.util.Optional; /** * 租户第三方集成配置服务 @@ -35,4 +37,9 @@ public interface ILbThirdIntegrationConfigService extends IService updateCredentialByTenantId( String tenantId, LbThirdIntegrationCredentialUpdateRequest request); + + /** + * 按租户 id 解析货品 API 运行时配置(含解密凭证);{@code tokenOverride} 非空时优先于库中 token。 + */ + Optional resolveGoodsApiContext(String tenantId, String tokenOverride); } diff --git a/src/main/java/com/rj/service/impl/LbGoodsServiceImpl.java b/src/main/java/com/rj/service/impl/LbGoodsServiceImpl.java index 198d32d..eeef53d 100644 --- a/src/main/java/com/rj/service/impl/LbGoodsServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbGoodsServiceImpl.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.HxrGoodsApiContext; import com.rj.dto.hxr.HxrLbGoodsPageData; import com.rj.dto.hxr.HxrLbGoodsSelectResponse; import com.rj.entity.LbGoods; @@ -10,6 +11,7 @@ import com.rj.mapper.LbGoodsMapper; import com.rj.service.HxrAdminBuyService; import com.rj.service.HxrAdminGoodsService; import com.rj.service.ILbGoodsService; +import com.rj.service.ILbThirdIntegrationConfigService; import com.rj.tenant.TenantContextHolder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -40,6 +42,8 @@ public class LbGoodsServiceImpl extends ServiceImpl impl private final HxrAdminBuyService hxrAdminBuyService; + private final ILbThirdIntegrationConfigService lbThirdIntegrationConfigService; + @Override public Map add(LbGoods entity) { Map result = new HashMap<>(); @@ -260,6 +264,17 @@ public class LbGoodsServiceImpl extends ServiceImpl impl } String tid = tenantId.trim(); + Optional apiContextOpt = + lbThirdIntegrationConfigService.resolveGoodsApiContext(tid, token); + if (apiContextOpt.isEmpty()) { + result.put("success", false); + result.put("message", + "未找到该租户的第三方集成配置,或配置未启用、URL/凭证不完整(请检查 lb_third_integration_config)"); + result.put("synced", 0); + return result; + } + HxrGoodsApiContext apiContext = apiContextOpt.get(); + String previousTenantId = TenantContextHolder.getTenantId(); TenantContextHolder.setTenantId(tid); try { @@ -268,11 +283,12 @@ public class LbGoodsServiceImpl extends ServiceImpl impl int totalSynced = 0; while (true) { Optional opt = - hxrAdminGoodsService.fetchGoodsPage(page, token); + hxrAdminGoodsService.fetchGoodsPage(page, apiContext); if (opt.isEmpty()) { if (page == 1) { result.put("success", false); - result.put("message", "未拉取到货品(请检查 hxr.admin.goods-api-token、goods-api-app-str 或网络)"); + result.put("message", + "未拉取到货品(请检查 lb_third_integration_config 中 token、appStr、URL 或网络)"); result.put("synced", 0); return result; } diff --git a/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java b/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java index 70c9cc5..2ba8a5e 100644 --- a/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java @@ -7,9 +7,11 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.rj.common.LbThirdIntegrationConstants; import com.rj.common.PasswordUtil; import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest; +import com.rj.dto.hxr.HxrGoodsApiContext; import com.rj.entity.LbThirdIntegrationConfig; import com.rj.mapper.LbThirdIntegrationConfigMapper; import com.rj.service.ILbThirdIntegrationConfigService; +import com.rj.util.LbThirdIntegrationConfigUtil; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -18,6 +20,7 @@ import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; /** @@ -306,6 +309,40 @@ public class LbThirdIntegrationConfigServiceImpl } } + @Override + public Optional resolveGoodsApiContext(String tenantId, String tokenOverride) { + 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 token = StringUtils.hasText(tokenOverride) + ? tokenOverride.trim() + : decryptFromBytes(config.getGoodsApiTokenCipher()); + String appStr = decryptFromBytes(config.getGoodsApiAppStrCipher()); + if (!StringUtils.hasText(token) || !StringUtils.hasText(appStr)) { + return Optional.empty(); + } + + try { + return Optional.of(new HxrGoodsApiContext( + LbThirdIntegrationConfigUtil.resolveGoodsApiBaseUrl(config), + LbThirdIntegrationConfigUtil.resolveGoodsPageLimit(config), + LbThirdIntegrationConfigUtil.resolveGoodsApiOrigin(config), + LbThirdIntegrationConfigUtil.resolveGoodsApiReferer(config), + token.trim(), + appStr.trim())); + } catch (IllegalStateException e) { + return Optional.empty(); + } + } + private LbThirdIntegrationConfig getByTenantIdOrNull(String tenantId) { return this.getOne(new LambdaQueryWrapper() .eq(LbThirdIntegrationConfig::getTenantId, tenantId) diff --git a/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java b/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java new file mode 100644 index 0000000..62651ea --- /dev/null +++ b/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java @@ -0,0 +1,64 @@ +package com.rj.util; + +import com.rj.common.LbThirdIntegrationConstants; +import com.rj.entity.LbThirdIntegrationConfig; +import org.springframework.util.StringUtils; + +/** + * 从 {@link LbThirdIntegrationConfig} 解析第三方 API 地址与请求头。 + */ +public final class LbThirdIntegrationConfigUtil { + + private LbThirdIntegrationConfigUtil() { + } + + public static String resolveGoodsApiBaseUrl(LbThirdIntegrationConfig config) { + String base = trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl")); + String path = config.getGoodsApiPath(); + if (!StringUtils.hasText(path)) { + path = LbThirdIntegrationConstants.DEFAULT_GOODS_API_PATH; + } + path = path.trim(); + if (!path.startsWith("/")) { + path = "/" + path; + } + return base + path; + } + + public static String resolveGoodsApiOrigin(LbThirdIntegrationConfig config) { + if (StringUtils.hasText(config.getGoodsApiOrigin())) { + return config.getGoodsApiOrigin().trim(); + } + return trimTrailingSlash(requireText(config.getWebBaseUrl(), "webBaseUrl")); + } + + public static String resolveGoodsApiReferer(LbThirdIntegrationConfig config) { + if (StringUtils.hasText(config.getGoodsApiReferer())) { + return config.getGoodsApiReferer().trim(); + } + return trimTrailingSlash(requireText(config.getWebBaseUrl(), "webBaseUrl")) + "/"; + } + + public static int resolveGoodsPageLimit(LbThirdIntegrationConfig config) { + Integer limit = config.getGoodsPageLimit(); + if (limit == null || limit < 1) { + return LbThirdIntegrationConstants.DEFAULT_GOODS_PAGE_LIMIT; + } + return limit; + } + + private static String requireText(String value, String fieldName) { + if (!StringUtils.hasText(value)) { + throw new IllegalStateException(fieldName + " 未配置"); + } + return value.trim(); + } + + private static String trimTrailingSlash(String url) { + String trimmed = url.trim(); + while (trimmed.endsWith("/")) { + trimmed = trimmed.substring(0, trimmed.length() - 1); + } + return trimmed; + } +}