From 25002e004aeeb5239549347cf25ea7a735ac28d1 Mon Sep 17 00:00:00 2001 From: cst61 Date: Mon, 3 Aug 2026 11:31:06 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E6=83=A0=E5=8A=B5=E6=B8=85=E7=90=86?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/LbThirdIntegrationConstants.java | 1 + .../rj/controller/LbUserCouponController.java | 15 ++ .../rj/dto/hxr/HxrMoneyCouponApiContext.java | 1 + .../rj/entity/LbThirdIntegrationConfig.java | 4 + .../com/rj/service/ILbUserCouponService.java | 10 + .../LbThirdIntegrationConfigServiceImpl.java | 4 + .../service/impl/LbUserCouponServiceImpl.java | 198 ++++++++++++++++++ .../rj/util/LbThirdIntegrationConfigUtil.java | 13 ++ src/main/sql/lb_third_integration_config.sql | 1 + 9 files changed, 247 insertions(+) diff --git a/src/main/java/com/rj/common/LbThirdIntegrationConstants.java b/src/main/java/com/rj/common/LbThirdIntegrationConstants.java index 76c2bff..9052a9b 100644 --- a/src/main/java/com/rj/common/LbThirdIntegrationConstants.java +++ b/src/main/java/com/rj/common/LbThirdIntegrationConstants.java @@ -23,6 +23,7 @@ public final class LbThirdIntegrationConstants { public static final String DEFAULT_LOGIN_API_PATH = "/api/user/login"; public static final String DEFAULT_PAY_BANK_API_PATH = "/api/bank/index"; public static final String DEFAULT_PAY_ZHIFUBAO_API_PATH = "/api/pay/zhifubao"; + public static final String DEFAULT_COUPON_UPDATE_PATH = "/app/admin/coupon/update"; public static final int DEFAULT_ORDER_PAGE_LIMIT = 90; public static final int DEFAULT_BUYER_ORDER_LIST_LIMIT = 90; diff --git a/src/main/java/com/rj/controller/LbUserCouponController.java b/src/main/java/com/rj/controller/LbUserCouponController.java index 06f8945..43ea12b 100644 --- a/src/main/java/com/rj/controller/LbUserCouponController.java +++ b/src/main/java/com/rj/controller/LbUserCouponController.java @@ -104,6 +104,21 @@ public class LbUserCouponController { return ResponseEntity.badRequest().body(result); } + @PostMapping("/updateCoupon") + @Operation(summary = "更新用户优惠券数值", + description = "读取 lb_user 表用户,模拟登录获取 token,调用第三方 coupon_update_path 接口修改优惠券数值," + + "请求体 { \"cate\": 2, \"type\": 2, \"money\": \"\" }") + public ResponseEntity> updateCoupon( + @Parameter(description = "租户ID", required = true) + @RequestParam String tenantId) { + Map result = lbUserCouponService.updateCoupon(tenantId); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + return ResponseEntity.badRequest().body(result); + } + @PostMapping("/monthlyStatistic") @Operation(summary = "按月统计用户优惠券金额") public ResponseEntity> monthlyStatistic( diff --git a/src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java b/src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java index fd9f734..3c945e1 100644 --- a/src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java +++ b/src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java @@ -5,6 +5,7 @@ package com.rj.dto.hxr; */ public record HxrMoneyCouponApiContext( String couponApiBaseUrl, + String couponUpdateUrl, int pageLimit, String origin, String referer, diff --git a/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java b/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java index 56ef27c..d5e8efa 100644 --- a/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java +++ b/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java @@ -116,6 +116,10 @@ public class LbThirdIntegrationConfig implements Serializable { @Schema(description = "用户优惠券列表分页 limit") private Integer moneyCouponListLimit; + @TableField("coupon_update_path") + @Schema(description = "优惠券更新 API 路径") + private String couponUpdatePath; + @TableField("order_referer") @Schema(description = "订单 Referer") private String orderReferer; diff --git a/src/main/java/com/rj/service/ILbUserCouponService.java b/src/main/java/com/rj/service/ILbUserCouponService.java index e12a0fe..07258b8 100644 --- a/src/main/java/com/rj/service/ILbUserCouponService.java +++ b/src/main/java/com/rj/service/ILbUserCouponService.java @@ -37,6 +37,16 @@ public interface ILbUserCouponService extends IService { */ Map syncUserCoupons(String tenantId, Integer recentDays); + /** + * 更新用户优惠券数值 + * 读取 lb_user 表用户,模拟登录获取 token,调用第三方 coupon_update_path 接口修改优惠券数值; + * 请求体 { "cate": 2, "type": 2, "money": "" }。 + * + * @param tenantId 租户ID + * @return 更新结果 + */ + Map updateCoupon(String tenantId); + /** * 按月统计用户优惠券金额 * diff --git a/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java b/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java index 96d031b..e5dc061 100644 --- a/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java @@ -489,6 +489,7 @@ public class LbThirdIntegrationConfigServiceImpl try { return Optional.of(new HxrMoneyCouponApiContext( LbThirdIntegrationConfigUtil.resolveMoneyCouponApiBaseUrl(config), + LbThirdIntegrationConfigUtil.resolveCouponUpdateUrl(config), LbThirdIntegrationConfigUtil.resolveMoneyCouponListLimit(config), LbThirdIntegrationConfigUtil.resolveGoodsApiOrigin(config), LbThirdIntegrationConfigUtil.resolveGoodsApiReferer(config), @@ -715,6 +716,9 @@ public class LbThirdIntegrationConfigServiceImpl if (!StringUtils.hasText(entity.getPayZhifubaoApiPath())) { entity.setPayZhifubaoApiPath(LbThirdIntegrationConstants.DEFAULT_PAY_ZHIFUBAO_API_PATH); } + if (!StringUtils.hasText(entity.getCouponUpdatePath())) { + entity.setCouponUpdatePath(LbThirdIntegrationConstants.DEFAULT_COUPON_UPDATE_PATH); + } if (entity.getOrderPageLimit() == null) { entity.setOrderPageLimit(LbThirdIntegrationConstants.DEFAULT_ORDER_PAGE_LIMIT); } diff --git a/src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java b/src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java index 573ad56..4869a3e 100644 --- a/src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java @@ -234,6 +234,204 @@ public class LbUserCouponServiceImpl } } + @Override + public Map updateCoupon(String tenantId) { + Map result = new HashMap<>(); + try { + if (tenantId == null || tenantId.trim().isEmpty()) { + result.put("success", false); + result.put("message", "tenantId不能为空"); + return result; + } + + String tid = tenantId.trim(); + + // 获取第三方集成配置 + LbThirdIntegrationConfig config = lbThirdIntegrationConfigService.getOne( + new LambdaQueryWrapper() + .eq(LbThirdIntegrationConfig::getTenantId, tid) + .eq(LbThirdIntegrationConfig::getEnabled, 1) + .last("LIMIT 1")); + if (config == null) { + result.put("success", false); + result.put("message", "未找到该租户的第三方集成配置,或配置未启用"); + return result; + } + + // 校验 coupon_update_path 已配置 + if (!StringUtils.hasText(config.getCouponUpdatePath())) { + result.put("success", false); + result.put("message", "未配置 coupon_update_path"); + return result; + } + + // 获取登录上下文 + Optional loginCtxOpt = + lbThirdIntegrationConfigService.resolveUserLoginApiContext(tid); + if (loginCtxOpt.isEmpty()) { + result.put("success", false); + result.put("message", "未找到登录API配置"); + return result; + } + HxrUserLoginApiContext loginCtx = loginCtxOpt.get(); + + // 读取 lb_user 表:有优惠券金额且手机号可用的用户 + List allUsers = lbUserService.list( + new LambdaQueryWrapper() + .eq(LbUser::getTenantId, tid) + .isNotNull(LbUser::getCoupon) + .gt(LbUser::getCoupon, BigDecimal.ZERO) + .isNotNull(LbUser::getMobile) + .ne(LbUser::getMobile, "") + .orderByDesc(LbUser::getUpdatedAt) + .last("LIMIT 5000")); + + // 根据 mobile 和 nickname 去重 + Map uniqueUserMap = new LinkedHashMap<>(); + for (LbUser user : allUsers) { + String key = user.getMobile() + "|" + (user.getNickname() != null ? user.getNickname() : ""); + uniqueUserMap.put(key, user); + } + List userList = new ArrayList<>(uniqueUserMap.values()); + + log.info("更新用户优惠券数值:tenantId={}, 过滤后用户数={}", tid, userList.size()); + + int successCount = 0; + int failCount = 0; + + for (LbUser user : userList) { + String mobile = user.getMobile(); + if (!StringUtils.hasText(mobile)) { + continue; + } + + try { + // 模拟登录获取 token + HxrAdminUserLoginService.LoginApiResult loginResult = + hxrAdminUserLoginService.login(mobile, DEFAULT_SIMULATE_LOGIN_PASSWORD, loginCtx); + if (!loginResult.success()) { + log.warn("用户登录失败 mobile={} apiCode={} apiMsg={}", mobile, loginResult.apiCode(), loginResult.apiMsg()); + failCount++; + continue; + } + + String token = HxrAdminUserLoginService.extractToken(loginResult.parsed()); + if (!StringUtils.hasText(token)) { + log.warn("登录成功但无法提取token mobile={}", mobile); + failCount++; + continue; + } + + // 获取优惠券API上下文(含 couponUpdateUrl) + Optional couponCtxOpt = + lbThirdIntegrationConfigService.resolveMoneyCouponApiContext(tid, token); + if (couponCtxOpt.isEmpty()) { + log.warn("无法获取优惠券API上下文 mobile={}", mobile); + failCount++; + continue; + } + HxrMoneyCouponApiContext couponCtx = couponCtxOpt.get(); + + // 调用第三方接口修改优惠券数值 + boolean ok = postCouponUpdate(couponCtx, user); + if (ok) { + successCount++; + } else { + failCount++; + } + } catch (Exception e) { + log.error("处理用户优惠券更新异常 mobile={}", mobile, e); + failCount++; + } + } + + result.put("success", true); + result.put("message", "更新完成"); + result.put("totalUsers", userList.size()); + result.put("successCount", successCount); + result.put("failCount", failCount); + return result; + + } catch (Exception e) { + log.error("更新用户优惠券数值异常", e); + result.put("success", false); + result.put("message", "更新异常:" + e.getMessage()); + return result; + } + } + + /** + * 调用第三方 coupon_update_path 接口修改优惠券数值。 + * 请求体:{ "cate": 2, "type": 2, "money": "" } + */ + private boolean postCouponUpdate(HxrMoneyCouponApiContext ctx, LbUser user) throws Exception { + String couponUpdateUrl = ctx.couponUpdateUrl(); + if (!StringUtils.hasText(couponUpdateUrl)) { + log.warn("优惠券更新URL为空 mobile={}", user.getMobile()); + return false; + } + + int cate = 2; + int type = 2; + String moneyStr = user.getCoupon() != null ? user.getCoupon().toPlainString() : "0"; + + Map body = new LinkedHashMap<>(); + body.put("cate", cate); + body.put("type", type); + body.put("money", moneyStr); + String bodyJson = JSON.writeValueAsString(body); + + long timestamp = System.currentTimeMillis() / 1000; + String noncestr = randomNoncestr(); + Map signParams = new LinkedHashMap<>(); + signParams.put("cate", cate); + signParams.put("type", type); + signParams.put("money", moneyStr); + signParams.put("timestamp", timestamp); + signParams.put("noncestr", noncestr); + String sign = HxrGoodsSignUtil.computeSign(signParams, ctx.appStr().trim()); + + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(60)) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(couponUpdateUrl.trim())) + .timeout(Duration.ofSeconds(120)) + .header("Accept", "application/json,*/*") + .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") + .header("Content-Type", "application/json") + .header("Origin", ctx.origin()) + .header("Referer", ctx.referer()) + .header("User-Agent", USER_AGENT) + .header(HEADER_TOKEN, ctx.token()) + .header("S", sign) + .header("T", String.valueOf(timestamp)) + .header("N", noncestr) + .POST(HttpRequest.BodyPublishers.ofString(bodyJson, StandardCharsets.UTF_8)) + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + int status = response.statusCode(); + if (status < 200 || status >= 300) { + log.warn("优惠券更新API HTTP {} mobile={} bodyPrefix={}", + status, user.getMobile(), abbreviate(response.body(), 400)); + return false; + } + + JsonNode root = JSON.readTree(response.body()); + int code = root.path("code").asInt(-1); + if (code != 0) { + log.warn("优惠券更新API返回错误 code={} msg={} mobile={} money={}", + code, root.path("msg").asText(""), user.getMobile(), moneyStr); + return false; + } + + log.info("优惠券更新成功 mobile={} money={}", user.getMobile(), moneyStr); + return true; + } + private String fetchMoneyCouponList(HxrMoneyCouponApiContext ctx, int recentDays) throws Exception { String couponApiBaseUrl = ctx.couponApiBaseUrl(); if (!StringUtils.hasText(couponApiBaseUrl)) { diff --git a/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java b/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java index af23e4a..e40ba24 100644 --- a/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java +++ b/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java @@ -274,6 +274,19 @@ public final class LbThirdIntegrationConfigUtil { return limit; } + public static String resolveCouponUpdateUrl(LbThirdIntegrationConfig config) { + String base = trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl")); + String path = config.getCouponUpdatePath(); + if (!StringUtils.hasText(path)) { + path = LbThirdIntegrationConstants.DEFAULT_COUPON_UPDATE_PATH; + } + path = path.trim(); + if (!path.startsWith("/")) { + path = "/" + path; + } + return base + path; + } + private static Map parseQueryString(String query) { Map params = new HashMap<>(); if (query == null || query.isBlank()) { diff --git a/src/main/sql/lb_third_integration_config.sql b/src/main/sql/lb_third_integration_config.sql index 86a44dd..eca62b7 100644 --- a/src/main/sql/lb_third_integration_config.sql +++ b/src/main/sql/lb_third_integration_config.sql @@ -31,6 +31,7 @@ CREATE TABLE `lb_third_integration_config` ( `user_referer` VARCHAR(512) DEFAULT NULL COMMENT '用户 Referer;空则 {admin_base_url}/app/admin/user/index', `goods_api_origin` VARCHAR(256) DEFAULT NULL COMMENT '货品 Origin;空则 web_base_url', `goods_api_referer` VARCHAR(256) DEFAULT NULL COMMENT '货品 Referer;空则 web_base_url/', + `coupon_update_path` VARCHAR(128) NOT NULL DEFAULT '/app/admin/coupon/update' COMMENT '优惠券更新 API 路径', `auth_type` VARCHAR(32) NOT NULL DEFAULT 'COOKIE_PHPSID' COMMENT 'Admin 鉴权类型', `cookie` VARCHAR(1024) DEFAULT NULL COMMENT '完整 Cookie 明文(优先使用)',