package com.xiaopeng.account.service.impl; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; 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.google.common.collect.Lists; import com.xiaopeng.account.mapper.XpAppUserCouponMapper; import com.xiaopeng.account.model.dto.ReceiveCouponDto; import com.xiaopeng.account.model.dto.SendCouponDto; import com.xiaopeng.account.model.query.XpUserCouponListQuery; import com.xiaopeng.account.model.vo.XpAppUserCouponListVo; import com.xiaopeng.account.model.vo.XpAppUserCouponTotalVo; import com.xiaopeng.account.model.vo.XpAppUserCouponVo; import com.xiaopeng.account.service.IXpAppUserCouponService; import com.xiaopeng.account.util.holiday.HolidayUtil; import com.xiaopeng.api.dto.account.NavigationCouponPickerRequest; import com.xiaopeng.api.dto.account.UcUserInfo; import com.xiaopeng.api.enums.NavigationCouponBubbleType; import com.xiaopeng.api.vo.account.NavigationCouponItemVo; import com.xiaopeng.api.vo.account.NavigationCouponPickerVo; import com.xiaopeng.api.feignClient.account.UserResourcesClient; import com.xiaopeng.api.feignClient.operation.XpCouponClient; import com.xiaopeng.api.feignClient.operation.XpWhiteListClient; import com.xiaopeng.api.model.account.XpAppUserCoupon; import com.xiaopeng.api.model.operation.XpCoupon; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; /** *

* 用户优惠券 服务实现类 *

* * @author mitao * @since 2026-02-03 */ @Service public class XpAppUserCouponServiceImpl extends ServiceImpl implements IXpAppUserCouponService { @Autowired private HolidayUtil holidayUtil; @Autowired private XpCouponClient xpCouponClient; @Autowired private XpWhiteListClient xpWhiteListClient; @Autowired private UserResourcesClient userResourcesClient; /** * 获取用户优惠券列表 * * @param query * @param uid * @return */ @Override public XpAppUserCouponVo userCouponList(XpUserCouponListQuery query, String uid) { Page page = new Page<>(query.getPageNum(), query.getPageSize()); Page list = baseMapper.userCouponList(page, query, Long.valueOf(uid)); XpAppUserCouponVo vo = new XpAppUserCouponVo(); vo.setTotal((int) page.getTotal()); vo.setList(list.getRecords()); return vo; } /** * 获取用户优惠券数量total * * @param uid * @return */ @Override public XpAppUserCouponTotalVo userCouponTotal(String uid) { List couponList = baseMapper.selectList(new LambdaQueryWrapper().eq(XpAppUserCoupon::getAppUserId, uid)); XpAppUserCouponTotalVo vo = new XpAppUserCouponTotalVo(); // vo.setAwaitReceiveNum((int) couponList.stream().filter(e -> e.getStatus() == 0).count()); vo.setAvailableNum((int) couponList.stream().filter(e -> e.getStatus() == 1).count()); vo.setUsedNum((int) couponList.stream().filter(e -> e.getStatus() == 2).count()); vo.setExpiredNum((int) couponList.stream().filter(e -> e.getStatus() == 3).count()); return vo; } /** * 领取优惠券 * * @param dto * @param uid * @return */ @Override public boolean receive(ReceiveCouponDto dto, String uid) { XpAppUserCoupon coupon = baseMapper.selectById(dto.getCouponId()); if (coupon.getAppUserId().toString().equals(uid)) { coupon.setStatus(1); return updateById(coupon); } return false; } /** * 时间规则 * * @param couponList * @return */ @Override public List timeRule(List couponList) { List xpAppUserCoupons = new ArrayList<>(); for (XpAppUserCoupon coupon : couponList) { String timeJson = coupon.getTimeJson(); Boolean inOperationTime = isInOperationTime(timeJson); if (inOperationTime) { xpAppUserCoupons.add(coupon); } } return xpAppUserCoupons; } /** * 发送优惠券 * * @param dto * @return */ @Override @Async("couponSendExecutor") public R send(SendCouponDto dto) { R coupon = xpCouponClient.getCouponById(dto.getCouponId()); if (coupon != null) { XpCoupon xpCoupon = coupon.getData(); if (dto.getPhone() == null || dto.getPhone().isEmpty()) { // 全部发放 R> allWhiteList = xpWhiteListClient.getAllWhiteList(); if (allWhiteList != null) { List phoneList = allWhiteList.getData(); dto.setPhone(phoneList); } } for (String phone : dto.getPhone()) { UcUserInfo userByMobile = userResourcesClient.getUserByMobile(phone); if (userByMobile != null) { Long uid = userByMobile.getUid(); XpAppUserCoupon xpAppUserCoupon = new XpAppUserCoupon(); xpAppUserCoupon.setFullMoney(xpCoupon.getConditionalAmount()); xpAppUserCoupon.setMoney(xpCoupon.getAmount()); xpAppUserCoupon.setCouponId(dto.getCouponId()); xpAppUserCoupon.setAppUserId(uid); xpAppUserCoupon.setExpirationTime(LocalDateTime.now().plusDays(7)); xpAppUserCoupon.setStatus(1); xpAppUserCoupon.setCouponType(xpCoupon.getCouponType()); xpAppUserCoupon.setPhone(phone); xpAppUserCoupon.setCouponName(xpCoupon.getCouponName()); xpAppUserCoupon.setOpenCityId("0"); xpAppUserCoupon.setTimeJson("[{\"time\": [\"00:00-23:59\"], \"type\": 3, \"typeStr\": 0}]"); baseMapper.insert(xpAppUserCoupon); } } } return null; } @Override public Integer getUserCouponNum(String uid) { if (uid == null) { return 0; } Long num = baseMapper.selectCount(new LambdaQueryWrapper().eq(XpAppUserCoupon::getAppUserId, uid).eq(XpAppUserCoupon::getStatus, 1).lt(XpAppUserCoupon::getExpirationTime, LocalDateTime.now())); return num.intValue(); } /** * 是否在运营内 * * @param timeJson 规则json字符串 * @return */ private Boolean isInOperationTime(String timeJson) { JSONArray jsonArray = JSONArray.parseArray(timeJson); Integer holidayToday = holidayUtil.isHolidayToday(); for (Object o : jsonArray) { JSONObject jsonObject = JSONObject.parseObject(o.toString()); String type = jsonObject.getString("type"); if (type.equals(holidayToday.toString())) { JSONArray time = jsonObject.getJSONArray("time"); // 判断当前时间 是否在time时间段中 for (Object object : time) { String[] timeArray = object.toString().split("-"); String startTime = timeArray[0]; String endTime = timeArray[1]; boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime); if (currentTimeInRange) { return true; } } } else if (type.equals("3")) { // 判断是什么类型 0每天 1-7 想起 String typeStr = jsonObject.getString("typeStr"); // 查看今天周几 Integer week = DateUtils.getWeek(); // 包含每天 或者 包含当天 if (typeStr.contains("0") || typeStr.contains(week.toString())) { JSONArray time = jsonObject.getJSONArray("time"); // 判断当前时间 是否在time时间段中 for (Object object : time) { String[] timeArray = object.toString().split("-"); String startTime = timeArray[0]; String endTime = timeArray[1]; boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime); if (currentTimeInRange) { return true; } } } } } return false; } @Override public NavigationCouponPickerVo navigationCouponPicker(Long userId, NavigationCouponPickerRequest req) { NavigationCouponPickerVo vo = new NavigationCouponPickerVo(); double orderAmount = req.getOrderAmount() == null ? 0d : req.getOrderAmount(); List couponList = baseMapper.selectList(new LambdaQueryWrapper() .eq(XpAppUserCoupon::getStatus, 1) .eq(XpAppUserCoupon::getAppUserId, userId) .apply("(FIND_IN_SET({0}, open_city_id) or open_city_id='0')", req.getOpenCityId())); couponList = timeRule(couponList); Long bestId = null; double bestEff = -1d; LocalDateTime bestExp = null; for (XpAppUserCoupon c : couponList) { if (!couponAmountAvailable(c, orderAmount)) { continue; } double eff = effectiveDiscountAmount(c, orderAmount); if (eff <= 0) { continue; } LocalDateTime exp = c.getExpirationTime(); if (bestId == null || eff > bestEff + 1e-6 || (Math.abs(eff - bestEff) < 1e-6 && exp != null && bestExp != null && exp.isBefore(bestExp)) || (Math.abs(eff - bestEff) < 1e-6 && bestExp == null && exp != null)) { bestId = c.getId(); bestEff = eff; bestExp = exp; } } vo.setRecommendedUserCouponId(bestId); Long selected = req.getSelectedUserCouponId(); Long applied; if (selected != null && selected == 0L) { applied = null; } else if (selected != null && selected > 0L) { XpAppUserCoupon chosen = couponList.stream().filter(c -> selected.equals(c.getId())).findFirst().orElse(null); if (chosen != null && couponAmountAvailable(chosen, orderAmount)) { applied = selected; } else { applied = null; } } else { applied = bestId; } vo.setAppliedUserCouponId(applied); long availCount = couponList.stream().filter(c -> couponAmountAvailable(c, orderAmount)).count(); if (availCount == 0) { vo.setBubbleType(NavigationCouponBubbleType.HIDDEN); List hiddenItems = buildNavigationItems(couponList, orderAmount, bestId, applied); vo.setItems(hiddenItems); vo.setListTotalCount(hiddenItems.size()); return vo; } if (applied == null) { vo.setBubbleType(NavigationCouponBubbleType.MULTI_AVAILABLE); vo.setAvailableCouponCount((int) availCount); } else { vo.setBubbleType(NavigationCouponBubbleType.SELECTED); XpAppUserCoupon ap = couponList.stream().filter(c -> applied.equals(c.getId())).findFirst().orElse(null); vo.setSelectedDiscountAmount(ap != null ? effectiveDiscountAmount(ap, orderAmount) : 0d); } List items = buildNavigationItems(couponList, orderAmount, bestId, applied); vo.setItems(items); vo.setListTotalCount(items.size()); return vo; } private List buildNavigationItems(List couponList, double orderAmount, Long bestId, Long appliedId) { List raw = new ArrayList<>(); for (XpAppUserCoupon c : couponList) { NavigationCouponItemVo it = new NavigationCouponItemVo(); it.setUserCouponId(c.getId()); it.setCouponName(c.getCouponName()); it.setCouponType(c.getCouponType()); it.setDiscountMoney(c.getMoney() != null ? c.getMoney().doubleValue() : 0d); it.setFullMoney(c.getFullMoney() != null ? c.getFullMoney().doubleValue() : null); it.setExpireTime(c.getExpirationTime()); boolean ok = couponAmountAvailable(c, orderAmount); it.setAvailable(ok); if (c.getCouponType() != null && c.getCouponType() == 2) { it.setThresholdLabel("满" + moneyStr(c.getFullMoney()) + "元可用"); if (!ok) { it.setUnavailableReason("未满足使用门槛"); } } else { it.setThresholdLabel("无门槛"); } it.setBestOffer(bestId != null && bestId.equals(c.getId())); it.setSelected(appliedId != null && appliedId.equals(c.getId())); raw.add(it); } List available = raw.stream().filter(x -> Boolean.TRUE.equals(x.getAvailable())) .sorted(Comparator .comparing((NavigationCouponItemVo x) -> !Boolean.TRUE.equals(x.getBestOffer())) .thenComparing((NavigationCouponItemVo x) -> -effectiveDiscountAmount(findCoupon(couponList, x.getUserCouponId()), orderAmount)) .thenComparing(NavigationCouponItemVo::getExpireTime, Comparator.nullsLast(Comparator.naturalOrder()))) .collect(Collectors.toList()); List unavailable = raw.stream().filter(x -> !Boolean.TRUE.equals(x.getAvailable())) .sorted(Comparator.comparing(NavigationCouponItemVo::getExpireTime, Comparator.nullsLast(Comparator.naturalOrder()))) .collect(Collectors.toList()); available.addAll(unavailable); return available; } private static XpAppUserCoupon findCoupon(List list, Long id) { return list.stream().filter(c -> Objects.equals(c.getId(), id)).findFirst().orElse(null); } private static String moneyStr(BigDecimal b) { if (b == null) { return "0"; } return b.stripTrailingZeros().toPlainString(); } private static boolean couponAmountAvailable(XpAppUserCoupon c, double orderAmount) { if (c.getCouponType() == null || c.getCouponType() == 1) { return true; } if (c.getCouponType() == 2) { return c.getFullMoney() != null && orderAmount >= c.getFullMoney().doubleValue(); } return true; } private static double effectiveDiscountAmount(XpAppUserCoupon c, double orderAmount) { if (c == null || c.getMoney() == null) { return 0d; } double m = c.getMoney().doubleValue(); if (!couponAmountAvailable(c, orderAmount)) { return 0d; } return Math.min(orderAmount, m); } }