优惠劵清理逻辑开发
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_LOGIN_API_PATH = "/api/user/login";
|
||||||
public static final String DEFAULT_PAY_BANK_API_PATH = "/api/bank/index";
|
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_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_ORDER_PAGE_LIMIT = 90;
|
||||||
public static final int DEFAULT_BUYER_ORDER_LIST_LIMIT = 90;
|
public static final int DEFAULT_BUYER_ORDER_LIST_LIMIT = 90;
|
||||||
|
|||||||
@@ -104,6 +104,21 @@ public class LbUserCouponController {
|
|||||||
return ResponseEntity.badRequest().body(result);
|
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")
|
@PostMapping("/monthlyStatistic")
|
||||||
@Operation(summary = "按月统计用户优惠券金额")
|
@Operation(summary = "按月统计用户优惠券金额")
|
||||||
public ResponseEntity<Map<String, Object>> monthlyStatistic(
|
public ResponseEntity<Map<String, Object>> monthlyStatistic(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package com.rj.dto.hxr;
|
|||||||
*/
|
*/
|
||||||
public record HxrMoneyCouponApiContext(
|
public record HxrMoneyCouponApiContext(
|
||||||
String couponApiBaseUrl,
|
String couponApiBaseUrl,
|
||||||
|
String couponUpdateUrl,
|
||||||
int pageLimit,
|
int pageLimit,
|
||||||
String origin,
|
String origin,
|
||||||
String referer,
|
String referer,
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ public class LbThirdIntegrationConfig implements Serializable {
|
|||||||
@Schema(description = "用户优惠券列表分页 limit")
|
@Schema(description = "用户优惠券列表分页 limit")
|
||||||
private Integer moneyCouponListLimit;
|
private Integer moneyCouponListLimit;
|
||||||
|
|
||||||
|
@TableField("coupon_update_path")
|
||||||
|
@Schema(description = "优惠券更新 API 路径")
|
||||||
|
private String couponUpdatePath;
|
||||||
|
|
||||||
@TableField("order_referer")
|
@TableField("order_referer")
|
||||||
@Schema(description = "订单 Referer")
|
@Schema(description = "订单 Referer")
|
||||||
private String orderReferer;
|
private String orderReferer;
|
||||||
|
|||||||
@@ -37,6 +37,16 @@ public interface ILbUserCouponService extends IService<LbUserCoupon> {
|
|||||||
*/
|
*/
|
||||||
Map<String, Object> syncUserCoupons(String tenantId, Integer recentDays);
|
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 {
|
try {
|
||||||
return Optional.of(new HxrMoneyCouponApiContext(
|
return Optional.of(new HxrMoneyCouponApiContext(
|
||||||
LbThirdIntegrationConfigUtil.resolveMoneyCouponApiBaseUrl(config),
|
LbThirdIntegrationConfigUtil.resolveMoneyCouponApiBaseUrl(config),
|
||||||
|
LbThirdIntegrationConfigUtil.resolveCouponUpdateUrl(config),
|
||||||
LbThirdIntegrationConfigUtil.resolveMoneyCouponListLimit(config),
|
LbThirdIntegrationConfigUtil.resolveMoneyCouponListLimit(config),
|
||||||
LbThirdIntegrationConfigUtil.resolveGoodsApiOrigin(config),
|
LbThirdIntegrationConfigUtil.resolveGoodsApiOrigin(config),
|
||||||
LbThirdIntegrationConfigUtil.resolveGoodsApiReferer(config),
|
LbThirdIntegrationConfigUtil.resolveGoodsApiReferer(config),
|
||||||
@@ -715,6 +716,9 @@ public class LbThirdIntegrationConfigServiceImpl
|
|||||||
if (!StringUtils.hasText(entity.getPayZhifubaoApiPath())) {
|
if (!StringUtils.hasText(entity.getPayZhifubaoApiPath())) {
|
||||||
entity.setPayZhifubaoApiPath(LbThirdIntegrationConstants.DEFAULT_PAY_ZHIFUBAO_API_PATH);
|
entity.setPayZhifubaoApiPath(LbThirdIntegrationConstants.DEFAULT_PAY_ZHIFUBAO_API_PATH);
|
||||||
}
|
}
|
||||||
|
if (!StringUtils.hasText(entity.getCouponUpdatePath())) {
|
||||||
|
entity.setCouponUpdatePath(LbThirdIntegrationConstants.DEFAULT_COUPON_UPDATE_PATH);
|
||||||
|
}
|
||||||
if (entity.getOrderPageLimit() == null) {
|
if (entity.getOrderPageLimit() == null) {
|
||||||
entity.setOrderPageLimit(LbThirdIntegrationConstants.DEFAULT_ORDER_PAGE_LIMIT);
|
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 {
|
private String fetchMoneyCouponList(HxrMoneyCouponApiContext ctx, int recentDays) throws Exception {
|
||||||
String couponApiBaseUrl = ctx.couponApiBaseUrl();
|
String couponApiBaseUrl = ctx.couponApiBaseUrl();
|
||||||
if (!StringUtils.hasText(couponApiBaseUrl)) {
|
if (!StringUtils.hasText(couponApiBaseUrl)) {
|
||||||
|
|||||||
@@ -274,6 +274,19 @@ public final class LbThirdIntegrationConfigUtil {
|
|||||||
return limit;
|
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) {
|
private static Map<String, String> parseQueryString(String query) {
|
||||||
Map<String, String> params = new HashMap<>();
|
Map<String, String> params = new HashMap<>();
|
||||||
if (query == null || query.isBlank()) {
|
if (query == null || query.isBlank()) {
|
||||||
|
|||||||
@@ -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',
|
`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_origin` VARCHAR(256) DEFAULT NULL COMMENT '货品 Origin;空则 web_base_url',
|
||||||
`goods_api_referer` VARCHAR(256) DEFAULT NULL COMMENT '货品 Referer;空则 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 鉴权类型',
|
`auth_type` VARCHAR(32) NOT NULL DEFAULT 'COOKIE_PHPSID' COMMENT 'Admin 鉴权类型',
|
||||||
`cookie` VARCHAR(1024) DEFAULT NULL COMMENT '完整 Cookie 明文(优先使用)',
|
`cookie` VARCHAR(1024) DEFAULT NULL COMMENT '完整 Cookie 明文(优先使用)',
|
||||||
|
|||||||
Reference in New Issue
Block a user