从第三方获取用户优惠券
This commit is contained in:
101
src/main/java/com/rj/controller/LbUserCouponController.java
Normal file
101
src/main/java/com/rj/controller/LbUserCouponController.java
Normal file
@@ -0,0 +1,101 @@
|
||||
package com.rj.controller;
|
||||
|
||||
import com.rj.entity.LbUserCoupon;
|
||||
import com.rj.service.ILbUserCouponService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/lbUserCoupon")
|
||||
@Tag(name = "用户优惠券", description = "lb_user_coupon 增删改查与分页")
|
||||
public class LbUserCouponController {
|
||||
|
||||
@Autowired
|
||||
private ILbUserCouponService lbUserCouponService;
|
||||
|
||||
@PostMapping("/add")
|
||||
@Operation(summary = "新增")
|
||||
public ResponseEntity<Map<String, Object>> add(
|
||||
@Parameter(description = "实体(id 为空时自动生成 UUID)", required = true)
|
||||
@RequestBody LbUserCoupon entity) {
|
||||
Map<String, Object> result = lbUserCouponService.add(entity);
|
||||
Boolean success = (Boolean) result.get("success");
|
||||
if (success != null && success) {
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
@PutMapping("/update")
|
||||
@Operation(summary = "编辑")
|
||||
public ResponseEntity<Map<String, Object>> update(
|
||||
@Parameter(description = "实体", required = true) @RequestBody LbUserCoupon entity) {
|
||||
Map<String, Object> result = lbUserCouponService.update(entity);
|
||||
Boolean success = (Boolean) result.get("success");
|
||||
if (success != null && success) {
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete/{id}")
|
||||
@Operation(summary = "删除")
|
||||
public ResponseEntity<Map<String, Object>> delete(
|
||||
@Parameter(description = "主键ID(UUID)", required = true) @PathVariable String id) {
|
||||
Map<String, Object> result = lbUserCouponService.deleteById(id);
|
||||
Boolean success = (Boolean) result.get("success");
|
||||
if (success != null && success) {
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
@GetMapping("/list")
|
||||
@Operation(summary = "分页查询")
|
||||
public ResponseEntity<Map<String, Object>> list(
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "10") Integer size,
|
||||
@RequestParam(required = false) String tenantId,
|
||||
@RequestParam(required = false) String userId,
|
||||
@RequestParam(required = false) String userPhone,
|
||||
@RequestParam(required = false) String userNickname,
|
||||
@RequestParam(required = false) String parentPhone,
|
||||
@RequestParam(required = false) String parentNickname,
|
||||
@Parameter(description = "优惠日期起,格式 yyyy-MM-dd")
|
||||
@RequestParam(required = false) String couponDateStart,
|
||||
@Parameter(description = "优惠日期止,格式 yyyy-MM-dd")
|
||||
@RequestParam(required = false) String couponDateEnd) {
|
||||
Map<String, Object> result = lbUserCouponService.pageQuery(
|
||||
current, size, tenantId, userId, userPhone, userNickname,
|
||||
parentPhone, parentNickname, couponDateStart, couponDateEnd
|
||||
);
|
||||
Boolean success = (Boolean) result.get("success");
|
||||
if (success != null && success) {
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
return ResponseEntity.internalServerError().body(result);
|
||||
}
|
||||
|
||||
@PostMapping("/syncCoupons")
|
||||
@Operation(summary = "同步用户优惠券")
|
||||
public ResponseEntity<Map<String, Object>> syncCoupons(
|
||||
@Parameter(description = "租户ID", required = true)
|
||||
@RequestParam String tenantId,
|
||||
@Parameter(description = "最近天数", required = true)
|
||||
@RequestParam Integer recentDays) {
|
||||
Map<String, Object> result = lbUserCouponService.syncUserCoupons(tenantId, recentDays);
|
||||
Boolean success = (Boolean) result.get("success");
|
||||
if (success != null && success) {
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
}
|
||||
13
src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java
Normal file
13
src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package com.rj.dto.hxr;
|
||||
|
||||
/**
|
||||
* 调用 hxrd 优惠券列表 API 所需的运行时配置(由 {@code lb_third_integration_config} 解析而来)。
|
||||
*/
|
||||
public record HxrMoneyCouponApiContext(
|
||||
String couponApiBaseUrl,
|
||||
int pageLimit,
|
||||
String origin,
|
||||
String referer,
|
||||
String token,
|
||||
String appStr) {
|
||||
}
|
||||
@@ -100,6 +100,14 @@ public class LbThirdIntegrationConfig implements Serializable {
|
||||
@Schema(description = "粉丝分页 limit")
|
||||
private Integer fansPageLimit;
|
||||
|
||||
@TableField("money_coupon_list_path")
|
||||
@Schema(description = "用户优惠券列表 API 路径")
|
||||
private String moneyCouponListPath;
|
||||
|
||||
@TableField("money_coupon_list_limit")
|
||||
@Schema(description = "用户优惠券列表分页 limit")
|
||||
private Integer moneyCouponListLimit;
|
||||
|
||||
@TableField("order_referer")
|
||||
@Schema(description = "订单 Referer")
|
||||
private String orderReferer;
|
||||
|
||||
66
src/main/java/com/rj/entity/LbUserCoupon.java
Normal file
66
src/main/java/com/rj/entity/LbUserCoupon.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package com.rj.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("lb_user_coupon")
|
||||
@Schema(description = "用户优惠券明细表")
|
||||
public class LbUserCoupon implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId("id")
|
||||
@Schema(description = "UUID")
|
||||
private String id;
|
||||
|
||||
@TableField("tenant_id")
|
||||
@Schema(description = "租户ID")
|
||||
private String tenantId;
|
||||
|
||||
@TableField("user_id")
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
|
||||
@TableField("user_phone")
|
||||
@Schema(description = "用户手机号")
|
||||
private String userPhone;
|
||||
|
||||
@TableField("user_nickname")
|
||||
@Schema(description = "用户昵称")
|
||||
private String userNickname;
|
||||
|
||||
@TableField("parent_phone")
|
||||
@Schema(description = "用户上级手机号")
|
||||
private String parentPhone;
|
||||
|
||||
@TableField("parent_nickname")
|
||||
@Schema(description = "上级昵称")
|
||||
private String parentNickname;
|
||||
|
||||
@TableField("coupon_amount")
|
||||
@Schema(description = "优惠券金额")
|
||||
private BigDecimal couponAmount;
|
||||
|
||||
@TableField("coupon_date")
|
||||
@Schema(description = "优惠日期")
|
||||
private LocalDate couponDate;
|
||||
|
||||
@TableField("create_time")
|
||||
@Schema(description = "创建日期")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@TableField("update_time")
|
||||
@Schema(description = "修改日期")
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
9
src/main/java/com/rj/mapper/LbUserCouponMapper.java
Normal file
9
src/main/java/com/rj/mapper/LbUserCouponMapper.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package com.rj.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.rj.entity.LbUserCoupon;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface LbUserCouponMapper extends BaseMapper<LbUserCoupon> {
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.rj.dto.hxr.HxrAdminUserApiContext;
|
||||
import com.rj.dto.hxr.HxrBuyerOrderApiContext;
|
||||
import com.rj.dto.hxr.HxrFansApiContext;
|
||||
import com.rj.dto.hxr.HxrGoodsApiContext;
|
||||
import com.rj.dto.hxr.HxrMoneyCouponApiContext;
|
||||
import com.rj.dto.hxr.HxrUserLoginApiContext;
|
||||
import com.rj.entity.LbThirdIntegrationConfig;
|
||||
|
||||
@@ -79,4 +80,10 @@ public interface ILbThirdIntegrationConfigService extends IService<LbThirdIntegr
|
||||
* {@code token} 为登录用户 token,必填。
|
||||
*/
|
||||
Optional<HxrBuyerOrderApiContext> resolveBuyerOrderApiContext(String tenantId, String token);
|
||||
|
||||
/**
|
||||
* 按租户 id 解析用户优惠券列表 API 运行时配置(money_coupon_list_path、money_coupon_list_limit、Origin、Referer、appStr);
|
||||
* {@code token} 为登录用户 token,必填。
|
||||
*/
|
||||
Optional<HxrMoneyCouponApiContext> resolveMoneyCouponApiContext(String tenantId, String token);
|
||||
}
|
||||
|
||||
35
src/main/java/com/rj/service/ILbUserCouponService.java
Normal file
35
src/main/java/com/rj/service/ILbUserCouponService.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package com.rj.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.rj.entity.LbUserCoupon;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface ILbUserCouponService extends IService<LbUserCoupon> {
|
||||
|
||||
Map<String, Object> add(LbUserCoupon entity);
|
||||
|
||||
Map<String, Object> update(LbUserCoupon entity);
|
||||
|
||||
Map<String, Object> deleteById(String id);
|
||||
|
||||
Map<String, Object> pageQuery(Integer current,
|
||||
Integer size,
|
||||
String tenantId,
|
||||
String userId,
|
||||
String userPhone,
|
||||
String userNickname,
|
||||
String parentPhone,
|
||||
String parentNickname,
|
||||
String couponDateStart,
|
||||
String couponDateEnd);
|
||||
|
||||
/**
|
||||
* 同步用户优惠券
|
||||
*
|
||||
* @param tenantId 租户ID
|
||||
* @param recentDays 最近天数
|
||||
* @return 同步结果
|
||||
*/
|
||||
Map<String, Object> syncUserCoupons(String tenantId, Integer recentDays);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.rj.dto.hxr.HxrAdminUserApiContext;
|
||||
import com.rj.dto.hxr.HxrBuyerOrderApiContext;
|
||||
import com.rj.dto.hxr.HxrFansApiContext;
|
||||
import com.rj.dto.hxr.HxrGoodsApiContext;
|
||||
import com.rj.dto.hxr.HxrMoneyCouponApiContext;
|
||||
import com.rj.dto.hxr.HxrUserLoginApiContext;
|
||||
import com.rj.entity.LbThirdIntegrationConfig;
|
||||
import com.rj.mapper.LbThirdIntegrationConfigMapper;
|
||||
@@ -466,6 +467,37 @@ public class LbThirdIntegrationConfigServiceImpl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<HxrMoneyCouponApiContext> resolveMoneyCouponApiContext(String tenantId, String token) {
|
||||
if (!StringUtils.hasText(tenantId) || !StringUtils.hasText(token)) {
|
||||
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 appStr = config.getGoodsApiAppStr();
|
||||
if (!StringUtils.hasText(appStr)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(new HxrMoneyCouponApiContext(
|
||||
LbThirdIntegrationConfigUtil.resolveMoneyCouponApiBaseUrl(config),
|
||||
LbThirdIntegrationConfigUtil.resolveMoneyCouponListLimit(config),
|
||||
LbThirdIntegrationConfigUtil.resolveGoodsApiOrigin(config),
|
||||
LbThirdIntegrationConfigUtil.resolveGoodsApiReferer(config),
|
||||
token.trim(),
|
||||
appStr.trim()));
|
||||
} catch (IllegalStateException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<HxrAdminOrderApiContext> resolveAdminOrderApiContext(String tenantId) {
|
||||
return resolveAdminOrderApiContext(tenantId, false);
|
||||
|
||||
526
src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java
Normal file
526
src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java
Normal file
@@ -0,0 +1,526 @@
|
||||
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.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.rj.dto.hxr.HxrMoneyCouponApiContext;
|
||||
import com.rj.dto.hxr.HxrUserLoginApiContext;
|
||||
import com.rj.entity.LbThirdIntegrationConfig;
|
||||
import com.rj.entity.LbUser;
|
||||
import com.rj.entity.LbUserCoupon;
|
||||
import com.rj.mapper.LbUserCouponMapper;
|
||||
import com.rj.service.HxrAdminUserLoginService;
|
||||
import com.rj.service.ILbThirdIntegrationConfigService;
|
||||
import com.rj.service.ILbUserCouponService;
|
||||
import com.rj.service.ILbUserService;
|
||||
import com.rj.util.HxrGoodsSignUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LbUserCouponServiceImpl
|
||||
extends ServiceImpl<LbUserCouponMapper, LbUserCoupon>
|
||||
implements ILbUserCouponService {
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private static final String DEFAULT_SIMULATE_LOGIN_PASSWORD = "123456";
|
||||
private static final String 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";
|
||||
private static final String HEADER_TOKEN = "token";
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
private static final ObjectMapper JSON = new ObjectMapper()
|
||||
.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
private final ILbUserService lbUserService;
|
||||
private final ILbThirdIntegrationConfigService lbThirdIntegrationConfigService;
|
||||
private final HxrAdminUserLoginService hxrAdminUserLoginService;
|
||||
|
||||
public LbUserCouponServiceImpl(
|
||||
ILbUserService lbUserService,
|
||||
ILbThirdIntegrationConfigService lbThirdIntegrationConfigService,
|
||||
HxrAdminUserLoginService hxrAdminUserLoginService) {
|
||||
this.lbUserService = lbUserService;
|
||||
this.lbThirdIntegrationConfigService = lbThirdIntegrationConfigService;
|
||||
this.hxrAdminUserLoginService = hxrAdminUserLoginService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> syncUserCoupons(String tenantId, Integer recentDays) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (tenantId == null || tenantId.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "tenantId不能为空");
|
||||
return result;
|
||||
}
|
||||
if (recentDays == null || recentDays < 1) {
|
||||
result.put("success", false);
|
||||
result.put("message", "recentDays必须大于0");
|
||||
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;
|
||||
}
|
||||
|
||||
// 获取登录上下文
|
||||
Optional<HxrUserLoginApiContext> loginCtxOpt =
|
||||
lbThirdIntegrationConfigService.resolveUserLoginApiContext(tid);
|
||||
if (loginCtxOpt.isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "未找到登录API配置");
|
||||
return result;
|
||||
}
|
||||
HxrUserLoginApiContext loginCtx = loginCtxOpt.get();
|
||||
|
||||
// 计算日期范围:现在 - recentDays 天
|
||||
LocalDateTime startDate = LocalDateTime.now().minusDays(recentDays);
|
||||
|
||||
// 查询用户列表,根据 updated_at 过滤,mobile 和 nickname 去重
|
||||
List<LbUser> allUsers = lbUserService.list(
|
||||
new LambdaQueryWrapper<LbUser>()
|
||||
.eq(LbUser::getTenantId, tid)
|
||||
.ge(LbUser::getUpdatedAt, startDate)
|
||||
.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={}, recentDays={}, 过滤后用户数={}", tid, recentDays, userList.size());
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
|
||||
for (LbUser user : userList) {
|
||||
String mobile = user.getMobile();
|
||||
if (!StringUtils.hasText(mobile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// 调用登录接口
|
||||
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;
|
||||
}
|
||||
|
||||
// 提取 token
|
||||
String token = HxrAdminUserLoginService.extractToken(loginResult.parsed());
|
||||
if (!StringUtils.hasText(token)) {
|
||||
log.warn("登录成功但无法提取token mobile={}", mobile);
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取优惠券API上下文
|
||||
Optional<HxrMoneyCouponApiContext> couponCtxOpt =
|
||||
lbThirdIntegrationConfigService.resolveMoneyCouponApiContext(tid, token);
|
||||
if (couponCtxOpt.isEmpty()) {
|
||||
log.warn("无法获取优惠券API上下文 mobile={}", mobile);
|
||||
failCount++;
|
||||
continue;
|
||||
}
|
||||
HxrMoneyCouponApiContext couponCtx = couponCtxOpt.get();
|
||||
|
||||
// 调用优惠券列表API
|
||||
String couponJson = fetchMoneyCouponList(couponCtx, recentDays);
|
||||
if (couponJson == null || couponJson.isBlank()) {
|
||||
log.warn("优惠券列表为空 mobile={}", mobile);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 解析并保存优惠券
|
||||
int saved = parseAndSaveCoupons(couponJson, user, tid);
|
||||
successCount += saved;
|
||||
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
private String fetchMoneyCouponList(HxrMoneyCouponApiContext ctx, int recentDays) throws Exception {
|
||||
String couponApiBaseUrl = ctx.couponApiBaseUrl();
|
||||
if (!StringUtils.hasText(couponApiBaseUrl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int pageLimit = recentDays > 0 ? recentDays : 90;
|
||||
String uri = couponApiBaseUrl + (couponApiBaseUrl.contains("?") ? "&" : "?") + "limit=" + pageLimit;
|
||||
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
String noncestr = randomNoncestr();
|
||||
Map<String, Object> signParams = new LinkedHashMap<>();
|
||||
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(uri))
|
||||
.timeout(Duration.ofSeconds(120))
|
||||
.header("Accept", "application/json,*/*")
|
||||
.header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
||||
.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)
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
int status = response.statusCode();
|
||||
if (status < 200 || status >= 300) {
|
||||
log.warn("优惠券API HTTP {} bodyPrefix={}", status, abbreviate(response.body(), 400));
|
||||
return null;
|
||||
}
|
||||
return response.body();
|
||||
}
|
||||
|
||||
private int parseAndSaveCoupons(String couponJson, LbUser user, String tenantId) throws Exception {
|
||||
JsonNode root = JSON.readTree(couponJson);
|
||||
int code = root.path("code").asInt(-1);
|
||||
if (code != 0) {
|
||||
log.warn("优惠券API返回错误 code={} msg={}", code, root.path("msg").asText(""));
|
||||
return 0;
|
||||
}
|
||||
|
||||
JsonNode listNode = root.path("data").path("list");
|
||||
if (listNode.isMissingNode() || !listNode.isArray()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int savedCount = 0;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
for (JsonNode item : listNode) {
|
||||
try {
|
||||
LbUserCoupon coupon = new LbUserCoupon();
|
||||
coupon.setId(UUID.randomUUID().toString());
|
||||
coupon.setTenantId(tenantId);
|
||||
coupon.setUserId(String.valueOf(user.getId()));
|
||||
coupon.setUserPhone(user.getMobile());
|
||||
coupon.setUserNickname(user.getNickname());
|
||||
coupon.setParentPhone(user.getPmobile());
|
||||
coupon.setParentNickname(user.getPname());
|
||||
|
||||
// 从JSON中解析金额和日期
|
||||
String moneyStr = item.path("money").asText("0");
|
||||
coupon.setCouponAmount(new BigDecimal(moneyStr));
|
||||
|
||||
String createdAtStr = item.path("created_at").asText(null);
|
||||
if (createdAtStr != null && !createdAtStr.isBlank()) {
|
||||
try {
|
||||
LocalDate couponDate = LocalDate.parse(createdAtStr.substring(0, 10), DATE_FORMATTER);
|
||||
coupon.setCouponDate(couponDate);
|
||||
} catch (Exception e) {
|
||||
log.warn("日期解析失败 created_at={}", createdAtStr);
|
||||
}
|
||||
}
|
||||
|
||||
coupon.setCreateTime(now);
|
||||
coupon.setUpdateTime(now);
|
||||
|
||||
this.save(coupon);
|
||||
savedCount++;
|
||||
} catch (Exception e) {
|
||||
log.warn("保存优惠券记录异常 item={}", item, e);
|
||||
}
|
||||
}
|
||||
|
||||
return savedCount;
|
||||
}
|
||||
|
||||
private static String randomNoncestr() {
|
||||
String base36 = Long.toUnsignedString(Math.abs(RANDOM.nextLong()), 36);
|
||||
if (base36.length() >= 5) {
|
||||
return base36.substring(base36.length() - 5);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(base36);
|
||||
while (sb.length() < 5) {
|
||||
sb.append(Integer.toString(RANDOM.nextInt(36), 36));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String abbreviate(String s, int maxLen) {
|
||||
if (s == null) {
|
||||
return "";
|
||||
}
|
||||
return s.length() <= maxLen ? s : s.substring(0, maxLen) + "...";
|
||||
}
|
||||
|
||||
private LocalDate parseDate(String dateStr) {
|
||||
if (dateStr == null || dateStr.trim().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(dateStr.trim(), DATE_FORMATTER);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> add(LbUserCoupon entity) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (entity.getTenantId() == null || entity.getTenantId().trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "tenantId不能为空");
|
||||
return result;
|
||||
}
|
||||
entity.setTenantId(entity.getTenantId().trim());
|
||||
if (entity.getUserPhone() != null) {
|
||||
entity.setUserPhone(entity.getUserPhone().trim());
|
||||
}
|
||||
if (entity.getUserNickname() != null) {
|
||||
entity.setUserNickname(entity.getUserNickname().trim());
|
||||
}
|
||||
if (entity.getParentPhone() != null) {
|
||||
entity.setParentPhone(entity.getParentPhone().trim());
|
||||
}
|
||||
if (entity.getParentNickname() != null) {
|
||||
entity.setParentNickname(entity.getParentNickname().trim());
|
||||
}
|
||||
if (entity.getCouponAmount() == null) {
|
||||
entity.setCouponAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
|
||||
entity.setId(UUID.randomUUID().toString());
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (entity.getCreateTime() == null) {
|
||||
entity.setCreateTime(now);
|
||||
}
|
||||
entity.setUpdateTime(now);
|
||||
|
||||
boolean ok = this.save(entity);
|
||||
result.put("success", ok);
|
||||
result.put("message", ok ? "新增成功" : "新增失败");
|
||||
if (ok) {
|
||||
result.put("data", entity);
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("用户优惠券新增异常", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "新增异常:" + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> update(LbUserCoupon entity) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (entity.getId() == null || entity.getId().trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "id不能为空");
|
||||
return result;
|
||||
}
|
||||
if (entity.getTenantId() != null) {
|
||||
entity.setTenantId(entity.getTenantId().trim());
|
||||
}
|
||||
if (entity.getUserPhone() != null) {
|
||||
entity.setUserPhone(entity.getUserPhone().trim());
|
||||
}
|
||||
if (entity.getUserNickname() != null) {
|
||||
entity.setUserNickname(entity.getUserNickname().trim());
|
||||
}
|
||||
if (entity.getParentPhone() != null) {
|
||||
entity.setParentPhone(entity.getParentPhone().trim());
|
||||
}
|
||||
if (entity.getParentNickname() != null) {
|
||||
entity.setParentNickname(entity.getParentNickname().trim());
|
||||
}
|
||||
entity.setUpdateTime(LocalDateTime.now());
|
||||
boolean ok = this.updateById(entity);
|
||||
result.put("success", ok);
|
||||
result.put("message", ok ? "编辑成功" : "编辑失败");
|
||||
if (ok) {
|
||||
result.put("data", this.getById(entity.getId()));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("用户优惠券编辑异常", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "编辑异常:" + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> deleteById(String id) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (id == null || id.trim().isEmpty()) {
|
||||
result.put("success", false);
|
||||
result.put("message", "id不能为空");
|
||||
return result;
|
||||
}
|
||||
boolean ok = this.removeById(id.trim());
|
||||
result.put("success", ok);
|
||||
result.put("message", ok ? "删除成功" : "删除失败");
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("用户优惠券删除异常", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "删除异常:" + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> pageQuery(Integer current,
|
||||
Integer size,
|
||||
String tenantId,
|
||||
String userId,
|
||||
String userPhone,
|
||||
String userNickname,
|
||||
String parentPhone,
|
||||
String parentNickname,
|
||||
String couponDateStart,
|
||||
String couponDateEnd) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
if (current == null || current < 1) {
|
||||
current = 1;
|
||||
}
|
||||
if (size == null || size < 1) {
|
||||
size = 10;
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<LbUserCoupon> queryWrapper = new LambdaQueryWrapper<>();
|
||||
if (tenantId != null && !tenantId.trim().isEmpty()) {
|
||||
queryWrapper.eq(LbUserCoupon::getTenantId, tenantId.trim());
|
||||
}
|
||||
if (userId != null && !userId.trim().isEmpty()) {
|
||||
queryWrapper.eq(LbUserCoupon::getUserId, userId.trim());
|
||||
}
|
||||
if (userPhone != null && !userPhone.trim().isEmpty()) {
|
||||
queryWrapper.like(LbUserCoupon::getUserPhone, userPhone.trim());
|
||||
}
|
||||
if (userNickname != null && !userNickname.trim().isEmpty()) {
|
||||
queryWrapper.like(LbUserCoupon::getUserNickname, userNickname.trim());
|
||||
}
|
||||
if (parentPhone != null && !parentPhone.trim().isEmpty()) {
|
||||
queryWrapper.like(LbUserCoupon::getParentPhone, parentPhone.trim());
|
||||
}
|
||||
if (parentNickname != null && !parentNickname.trim().isEmpty()) {
|
||||
queryWrapper.like(LbUserCoupon::getParentNickname, parentNickname.trim());
|
||||
}
|
||||
|
||||
LocalDate start = parseDate(couponDateStart);
|
||||
if (couponDateStart != null && !couponDateStart.trim().isEmpty() && start == null) {
|
||||
result.put("success", false);
|
||||
result.put("message", "couponDateStart 格式错误,请使用 yyyy-MM-dd");
|
||||
return result;
|
||||
}
|
||||
LocalDate end = parseDate(couponDateEnd);
|
||||
if (couponDateEnd != null && !couponDateEnd.trim().isEmpty() && end == null) {
|
||||
result.put("success", false);
|
||||
result.put("message", "couponDateEnd 格式错误,请使用 yyyy-MM-dd");
|
||||
return result;
|
||||
}
|
||||
if (start != null) {
|
||||
queryWrapper.ge(LbUserCoupon::getCouponDate, start);
|
||||
}
|
||||
if (end != null) {
|
||||
queryWrapper.le(LbUserCoupon::getCouponDate, end);
|
||||
}
|
||||
|
||||
queryWrapper.orderByDesc(LbUserCoupon::getUpdateTime)
|
||||
.orderByDesc(LbUserCoupon::getCreateTime);
|
||||
|
||||
Page<LbUserCoupon> page = this.page(new Page<>(current, size), queryWrapper);
|
||||
result.put("success", true);
|
||||
result.put("message", "查询成功");
|
||||
result.put("data", page.getRecords());
|
||||
result.put("total", page.getTotal());
|
||||
result.put("current", page.getCurrent());
|
||||
result.put("size", page.getSize());
|
||||
result.put("pages", page.getPages());
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("用户优惠券分页查询异常", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "查询异常:" + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -227,6 +227,27 @@ public final class LbThirdIntegrationConfigUtil {
|
||||
return limit;
|
||||
}
|
||||
|
||||
public static String resolveMoneyCouponApiBaseUrl(LbThirdIntegrationConfig config) {
|
||||
String base = trimTrailingSlash(requireText(config.getAdminBaseUrl(), "adminBaseUrl"));
|
||||
String path = config.getMoneyCouponListPath();
|
||||
if (!StringUtils.hasText(path)) {
|
||||
path = "";
|
||||
}
|
||||
path = path.trim();
|
||||
if (!path.startsWith("/")) {
|
||||
path = "/" + path;
|
||||
}
|
||||
return base + path;
|
||||
}
|
||||
|
||||
public static int resolveMoneyCouponListLimit(LbThirdIntegrationConfig config) {
|
||||
Integer limit = config.getMoneyCouponListLimit();
|
||||
if (limit == null || limit < 1) {
|
||||
return 90;
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
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