Files
smartDriveEE/src/main/java/com/rj/service/impl/LbUserCouponServiceImpl.java
2026-07-01 14:53:41 +08:00

590 lines
24 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
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)
.ge(LbUser::getCoupon, 100)
.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 pageSize = ctx.pageLimit() > 0 ? ctx.pageLimit() : 10;
// 总共需要获取的条数
int totalNeeded = recentDays > 0 ? recentDays : 90;
List<JsonNode> allItems = new ArrayList<>();
int page = 1;
int fetched = 0;
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(60))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
while (fetched < totalNeeded) {
int limit = Math.min(pageSize, totalNeeded - fetched);
int cate = 2;
int type = 1;
String uri = couponApiBaseUrl + (couponApiBaseUrl.contains("?") ? "&" : "?")
+ "cate=" + cate + "&type=" + type + "&page=" + page + "&limit=" + limit;
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = randomNoncestr();
Map<String, Object> signParams = new LinkedHashMap<>();
signParams.put("cate", cate);
signParams.put("type", type);
signParams.put("page", page);
signParams.put("limit", limit);
signParams.put("timestamp", timestamp);
signParams.put("noncestr", noncestr);
String sign = HxrGoodsSignUtil.computeSign(signParams, ctx.appStr().trim());
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));
break;
}
JsonNode root = JSON.readTree(response.body());
int code = root.path("code").asInt(-1);
if (code != 0) {
log.warn("优惠券API返回错误 code={} msg={}", code, root.path("msg").asText(""));
break;
}
JsonNode listNode = root.path("data").path("list");
if (listNode.isMissingNode() || !listNode.isArray() || listNode.size() == 0) {
log.info("优惠券API返回空列表 page={}", page);
break;
}
for (JsonNode item : listNode) {
if (fetched >= totalNeeded) {
break;
}
allItems.add(item);
fetched++;
}
// 如果返回的数据少于请求的limit说明没有更多数据了
if (listNode.size() < limit) {
break;
}
page++;
}
if (allItems.isEmpty()) {
return null;
}
// 构建合并后的JSON
ObjectNode resultNode = JSON.createObjectNode();
resultNode.put("code", 0);
ObjectNode dataNode = resultNode.putObject("data");
ArrayNode listArray = dataNode.putArray("list");
for (JsonNode item : allItems) {
listArray.add(item);
}
return JSON.writeValueAsString(resultNode);
}
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;
}
}
}