优惠劵清理逻辑开发
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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\": \"<lb_user.coupon>\" }")
|
||||
public ResponseEntity<Map<String, Object>> updateCoupon(
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@RequestParam String tenantId) {
|
||||
Map<String, Object> 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<Map<String, Object>> monthlyStatistic(
|
||||
|
||||
@@ -5,6 +5,7 @@ package com.rj.dto.hxr;
|
||||
*/
|
||||
public record HxrMoneyCouponApiContext(
|
||||
String couponApiBaseUrl,
|
||||
String couponUpdateUrl,
|
||||
int pageLimit,
|
||||
String origin,
|
||||
String referer,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -37,6 +37,16 @@ public interface ILbUserCouponService extends IService<LbUserCoupon> {
|
||||
*/
|
||||
Map<String, Object> syncUserCoupons(String tenantId, Integer recentDays);
|
||||
|
||||
/**
|
||||
* 更新用户优惠券数值
|
||||
* 读取 lb_user 表用户,模拟登录获取 token,调用第三方 coupon_update_path 接口修改优惠券数值;
|
||||
* 请求体 { "cate": 2, "type": 2, "money": "<lb_user.coupon>" }。
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @return 更新结果
|
||||
*/
|
||||
Map<String, Object> updateCoupon(String tenantId);
|
||||
|
||||
/**
|
||||
* 按月统计用户优惠券金额
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -234,6 +234,204 @@ public class LbUserCouponServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> updateCoupon(String tenantId) {
|
||||
Map<String, Object> 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<LbThirdIntegrationConfig>()
|
||||
.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<HxrUserLoginApiContext> loginCtxOpt =
|
||||
lbThirdIntegrationConfigService.resolveUserLoginApiContext(tid);
|
||||
if (loginCtxOpt.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "未找到登录API配置");
|
||||
return result;
|
||||
}
|
||||
HxrUserLoginApiContext loginCtx = loginCtxOpt.get();
|
||||
|
||||
// 读取 lb_user 表:有优惠券金额且手机号可用的用户
|
||||
List<LbUser> allUsers = lbUserService.list(
|
||||
new LambdaQueryWrapper<LbUser>()
|
||||
.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<String, LbUser> uniqueUserMap = new LinkedHashMap<>();
|
||||
for (LbUser user : allUsers) {
|
||||
String key = user.getMobile() + "|" + (user.getNickname() != null ? user.getNickname() : "");
|
||||
uniqueUserMap.put(key, user);
|
||||
}
|
||||
List<LbUser> 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<HxrMoneyCouponApiContext> 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": "<lb_user.coupon>" }
|
||||
*/
|
||||
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<String, Object> 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<String, Object> 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<String> 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)) {
|
||||
|
||||
@@ -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<String, String> parseQueryString(String query) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (query == null || query.isBlank()) {
|
||||
|
||||
Reference in New Issue
Block a user