From 9e4f32a47c4e0114e94ace1415580b2e9269f780 Mon Sep 17 00:00:00 2001 From: cst61 Date: Sat, 20 Jun 2026 08:42:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=8E=E7=AC=AC=E4=B8=89=E6=96=B9=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=94=A8=E6=88=B7=E4=BC=98=E6=83=A0=E5=88=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rj/controller/LbUserCouponController.java | 101 ++++ .../rj/dto/hxr/HxrMoneyCouponApiContext.java | 13 + .../rj/entity/LbThirdIntegrationConfig.java | 8 + src/main/java/com/rj/entity/LbUserCoupon.java | 66 +++ .../com/rj/mapper/LbUserCouponMapper.java | 9 + .../ILbThirdIntegrationConfigService.java | 7 + .../com/rj/service/ILbUserCouponService.java | 35 ++ .../LbThirdIntegrationConfigServiceImpl.java | 32 ++ .../service/impl/LbUserCouponServiceImpl.java | 526 ++++++++++++++++++ .../rj/util/LbThirdIntegrationConfigUtil.java | 21 + 10 files changed, 818 insertions(+) create mode 100644 src/main/java/com/rj/controller/LbUserCouponController.java create mode 100644 src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java create mode 100644 src/main/java/com/rj/entity/LbUserCoupon.java create mode 100644 src/main/java/com/rj/mapper/LbUserCouponMapper.java create mode 100644 src/main/java/com/rj/service/ILbUserCouponService.java create mode 100644 src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java diff --git a/src/main/java/com/rj/controller/LbUserCouponController.java b/src/main/java/com/rj/controller/LbUserCouponController.java new file mode 100644 index 0000000..141b50c --- /dev/null +++ b/src/main/java/com/rj/controller/LbUserCouponController.java @@ -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> add( + @Parameter(description = "实体(id 为空时自动生成 UUID)", required = true) + @RequestBody LbUserCoupon entity) { + Map 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> update( + @Parameter(description = "实体", required = true) @RequestBody LbUserCoupon entity) { + Map 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> delete( + @Parameter(description = "主键ID(UUID)", required = true) @PathVariable String id) { + Map 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> 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 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> syncCoupons( + @Parameter(description = "租户ID", required = true) + @RequestParam String tenantId, + @Parameter(description = "最近天数", required = true) + @RequestParam Integer recentDays) { + Map result = lbUserCouponService.syncUserCoupons(tenantId, recentDays); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + return ResponseEntity.badRequest().body(result); + } +} diff --git a/src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java b/src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java new file mode 100644 index 0000000..fd9f734 --- /dev/null +++ b/src/main/java/com/rj/dto/hxr/HxrMoneyCouponApiContext.java @@ -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) { +} \ No newline at end of file diff --git a/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java b/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java index 79431b1..19cffa1 100644 --- a/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java +++ b/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java @@ -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; diff --git a/src/main/java/com/rj/entity/LbUserCoupon.java b/src/main/java/com/rj/entity/LbUserCoupon.java new file mode 100644 index 0000000..4775b87 --- /dev/null +++ b/src/main/java/com/rj/entity/LbUserCoupon.java @@ -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; +} diff --git a/src/main/java/com/rj/mapper/LbUserCouponMapper.java b/src/main/java/com/rj/mapper/LbUserCouponMapper.java new file mode 100644 index 0000000..be3cbfe --- /dev/null +++ b/src/main/java/com/rj/mapper/LbUserCouponMapper.java @@ -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 { +} diff --git a/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java b/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java index e5c97c5..f09852e 100644 --- a/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java +++ b/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java @@ -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 resolveBuyerOrderApiContext(String tenantId, String token); + + /** + * 按租户 id 解析用户优惠券列表 API 运行时配置(money_coupon_list_path、money_coupon_list_limit、Origin、Referer、appStr); + * {@code token} 为登录用户 token,必填。 + */ + Optional resolveMoneyCouponApiContext(String tenantId, String token); } diff --git a/src/main/java/com/rj/service/ILbUserCouponService.java b/src/main/java/com/rj/service/ILbUserCouponService.java new file mode 100644 index 0000000..9bc8d6e --- /dev/null +++ b/src/main/java/com/rj/service/ILbUserCouponService.java @@ -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 { + + Map add(LbUserCoupon entity); + + Map update(LbUserCoupon entity); + + Map deleteById(String id); + + Map 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 syncUserCoupons(String tenantId, Integer recentDays); +} diff --git a/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java b/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java index dc57832..344b9b6 100644 --- a/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java @@ -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 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 resolveAdminOrderApiContext(String tenantId) { return resolveAdminOrderApiContext(tenantId, false); diff --git a/src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java b/src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java new file mode 100644 index 0000000..5253aaa --- /dev/null +++ b/src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java @@ -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 + 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 syncUserCoupons(String tenantId, Integer recentDays) { + Map 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() + .eq(LbThirdIntegrationConfig::getTenantId, tid) + .eq(LbThirdIntegrationConfig::getEnabled, 1) + .last("LIMIT 1")); + if (config == null) { + result.put("success", false); + result.put("message", "未找到该租户的第三方集成配置,或配置未启用"); + return result; + } + + // 获取登录上下文 + Optional 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 allUsers = lbUserService.list( + new LambdaQueryWrapper() + .eq(LbUser::getTenantId, tid) + .ge(LbUser::getUpdatedAt, startDate) + .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={}, 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 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 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 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 add(LbUserCoupon entity) { + Map 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 update(LbUserCoupon entity) { + Map 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 deleteById(String id) { + Map 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 pageQuery(Integer current, + Integer size, + String tenantId, + String userId, + String userPhone, + String userNickname, + String parentPhone, + String parentNickname, + String couponDateStart, + String couponDateEnd) { + Map result = new HashMap<>(); + try { + if (current == null || current < 1) { + current = 1; + } + if (size == null || size < 1) { + size = 10; + } + + LambdaQueryWrapper 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 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; + } + } +} diff --git a/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java b/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java index 7129939..c619f7e 100644 --- a/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java +++ b/src/main/java/com/rj/util/LbThirdIntegrationConfigUtil.java @@ -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 parseQueryString(String query) { Map params = new HashMap<>(); if (query == null || query.isBlank()) {