peng
昨天 7361bd1d9740c3cd9cb3fb9fc5425e11048cd4e6
framework/src/main/java/cn/lili/modules/lmk/service/impl/StoreCouponClaimRecordServiceImpl.java
@@ -1,16 +1,27 @@
package cn.lili.modules.lmk.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.modules.lmk.domain.entity.StoreCoupon;
import cn.lili.modules.lmk.domain.entity.StoreCouponSingle;
import cn.lili.modules.lmk.enums.general.PrizeStatusEnum;
import cn.lili.modules.lmk.enums.general.StoreCouponStausEnum;
import cn.lili.modules.lmk.service.StoreCouponService;
import cn.lili.modules.lmk.service.StoreCouponSingleService;
import cn.lili.modules.order.order.entity.dto.CouponExportDetailDTO;
import cn.lili.modules.order.order.entity.dto.StoreCouponClaimRecordDTO;
import cn.lili.modules.order.order.entity.enums.ClaimStatusEnum;
import cn.lili.modules.promotion.entity.dos.MemberCoupon;
import cn.lili.modules.promotion.entity.dto.search.MemberCouponSearchParams;
import cn.lili.modules.promotion.entity.vos.MemberCouponVO;
import cn.lili.modules.promotion.service.MemberCouponService;
import cn.lili.rocketmq.RocketmqSendCallbackBuilder;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.lili.modules.lmk.domain.entity.StoreCouponClaimRecord;
import cn.lili.modules.lmk.mapper.StoreCouponClaimRecordMapper;
@@ -21,6 +32,10 @@
import cn.lili.modules.lmk.domain.form.StoreCouponClaimRecordForm;
import cn.lili.modules.lmk.domain.vo.StoreCouponClaimRecordVO;
import cn.lili.modules.lmk.domain.query.StoreCouponClaimRecordQuery;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
@@ -32,7 +47,12 @@
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -50,6 +70,7 @@
    private static final String STORE_COUPON_CLAIM = "store_coupon_claim:";
    private final MemberCouponService memberCouponService;
    private final StoreCouponService storeCouponService;
    private final StoreCouponSingleService storeCouponSingleService;
    /**
     * 添加
     * @param form
@@ -147,33 +168,66 @@
        String userId = currentUser.getId();
        String nickName = currentUser.getNickName();
        //锁住店铺某一个优惠卷
        //锁住礼品码id
        RLock redissonLock = redissonClient.getLock(STORE_COUPON_CLAIM + id);
        try {
            redissonLock.lock();
            StoreCoupon storeCoupon = storeCouponService.getById(id);
            if (storeCoupon == null) {
                throw new ServiceException("当前店铺不存在优惠卷");
            LambdaQueryWrapper<StoreCouponSingle> forUpdate = Wrappers.<StoreCouponSingle>lambdaQuery()
                    .eq(StoreCouponSingle::getId, id).last("FOR UPDATE");
            StoreCouponSingle storeCouponSingle = storeCouponSingleService.getOne(forUpdate);
            if (storeCouponSingle == null) {
                throw new ServiceException("当前礼品码不存在");
            }
            if (!ClaimStatusEnum.NOT_CLAIM.name().equals(storeCouponSingle.getClaimStatus())) {
                throw new ServiceException("当前礼品码状态异常");
            }
            LambdaQueryWrapper<StoreCouponSingle> claimListQuery = Wrappers.<StoreCouponSingle>lambdaQuery()
                    .eq(StoreCouponSingle::getClaimUserId, userId)
                    .eq(StoreCouponSingle::getStoreCoupRef, storeCouponSingle.getStoreCoupRef())
                    .eq(StoreCouponSingle::getClaimStatus, ClaimStatusEnum.CLAIM.name());
            List<StoreCouponSingle> claimList = storeCouponSingleService.list(claimListQuery);
            if (!claimList.isEmpty()) {
                throw new ServiceException("已经领取过该类型的礼品码无法领取");
            }
            //处理幂等问题限制一个用户只能该店铺领取一种优惠卷
            LambdaQueryWrapper<StoreCouponClaimRecord> memCoupon = Wrappers.<StoreCouponClaimRecord>lambdaQuery()
                    .eq(StoreCouponClaimRecord::getUserId, userId)
                    .eq(StoreCouponClaimRecord::getCouponId, storeCoupon.getCouponId());
                    .eq(StoreCouponClaimRecord::getCouponId, storeCouponSingle.getCouponId());
            List<StoreCouponClaimRecord> list = this.list(memCoupon);
            if (!list.isEmpty()){
                throw new ServiceException("当前用户已经领取过了无法再次领取");
            }
            //更新单品被领取的记录
            storeCouponSingle.setClaimStatus(ClaimStatusEnum.CLAIM.name());
            storeCouponSingle.setClaimUserId(userId);
            storeCouponSingle.setClaimUserName(nickName);
            //校验是否在单品卷类领取过
            LambdaQueryWrapper<StoreCoupon> storeCoupQuery = Wrappers.<StoreCoupon>lambdaQuery()
                    .eq(StoreCoupon::getId, storeCouponSingle.getStoreCoupRef()).last("FOR UPDATE");
            StoreCoupon storeCoupon = storeCouponService.getOne(storeCoupQuery);
            if (storeCoupon == null) {
                throw new ServiceException("当前店铺优惠卷不存在");
            }
            if (!StoreCouponStausEnum.ENABLE.name().equals(storeCoupon.getStatus())) {
                throw new ServiceException("当前店铺优惠卷状态异常");
            }
            //领取对应的优惠卷写入记录
            memberCouponService.receiveCoupon(storeCoupon.getCouponId(),userId , nickName);
            StoreCouponClaimRecord storeCouponClaimRecord = new StoreCouponClaimRecord();
            storeCouponClaimRecord.setCouponId(storeCoupon.getCouponId());
            storeCouponClaimRecord.setCouponName(storeCoupon.getCouponName());
            storeCouponClaimRecord.setStoreId(storeCoupon.getStoreId());
            storeCouponClaimRecord.setStoreName(storeCoupon.getStoreName());
            storeCouponClaimRecord.setUserId(userId);
            MemberCoupon memberCoupon = memberCouponService.receiveCoupon(storeCouponSingle.getCouponId(), userId, nickName);
            String memberCouponId = memberCoupon.getId();
            storeCouponSingle.setMemberCouponId(memberCouponId);
            storeCouponSingleService.updateById(storeCouponSingle);
            StoreCouponClaimRecord storeCouponClaimRecord = getStoreCouponClaimRecord(storeCouponSingle, userId);
            storeCouponClaimRecord.setMemberCouponId(memberCouponId);
            this.save(storeCouponClaimRecord);
            LambdaUpdateWrapper<StoreCoupon> updateStoreCoupon = Wrappers.<StoreCoupon>lambdaUpdate().eq(StoreCoupon::getId, storeCoupon.getId())
                    .set(StoreCoupon::getCouponClaimNum, storeCoupon.getCouponClaimNum() + 1)
                    .ge(StoreCoupon::getCouponNum, storeCoupon.getCouponClaimNum() + 1);
            boolean update = storeCouponService.update(updateStoreCoupon);
            if (!update) {
                throw new ServiceException("更新失败");
            }
            //领取成功返回优惠卷id用于跳转购物使用
            return Result.ok().data(storeCoupon.getCouponId());
            return Result.ok().data(storeCouponSingle.getCouponId());
        } finally {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@@ -183,8 +237,177 @@
                        redissonLock.unlock();
                    }
                }
                @Override
                public void afterCompletion(int status) {
                    // 确保即使在事务回滚的情况下也能释放锁
                    if (redissonLock.isHeldByCurrentThread()) {
                        redissonLock.unlock();
                    }
                }
            });
        }
    }
    private XSSFWorkbook initCouponExportData(List<StoreCouponClaimRecordVO> list) {
        // 转换VO为DTO(如果DTO与VO字段一致,可直接使用VO简化代码)
        List<StoreCouponClaimRecordDTO> dtos = new ArrayList<>();
        for (StoreCouponClaimRecordVO vo : list) {
            StoreCouponClaimRecordDTO dto = new StoreCouponClaimRecordDTO();
            BeanUtil.copyProperties(vo, dto);
            dtos.add(dto);
        }
        System.out.println("-----------------------");
        System.out.println(dtos);
        XSSFWorkbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("优惠券领取记录");
        // 创建表头
        Row header = sheet.createRow(0);
        String[] headers = {
                "会员名称", "优惠券名称", "发布店铺", "面额/折扣",
                 "获取方式", "会员优惠券状态", "优惠券类型",
                 "使用起始时间", "截止时间"
        };
        for (int i = 0; i < headers.length; i++) {
            Cell cell = header.createCell(i);
            cell.setCellValue(headers[i]);
        }
        // 填充数据(增加空值处理,避免NPE)
        for (int i = 0; i < dtos.size(); i++) {
            StoreCouponClaimRecordDTO dto = dtos.get(i);
            Row row = sheet.createRow(i + 1);
            // 1. 会员名称(可能为null)
            row.createCell(0).setCellValue(Objects.nonNull(dto.getMemberName()) ? dto.getMemberName() : "");
            // 2. 优惠券名称(可能为null)
            row.createCell(1).setCellValue(Objects.nonNull(dto.getCouponName()) ? dto.getCouponName() : "");
            // 3. 发布店铺(处理platform特殊值,默认空字符串)
            String storeName = dto.getStoreName();
            if ("platform".equals(storeName)) {
                row.createCell(2).setCellValue("平台");
            } else {
                row.createCell(2).setCellValue(Objects.nonNull(storeName) ? storeName : "");
            }
            // 4. 面额/折扣(优先显示折扣,其次显示面额,避免覆盖)
            Cell amountCell = row.createCell(3);
            if (Objects.nonNull(dto.getDiscount())) {
                amountCell.setCellValue(dto.getDiscount() + "折");
            } else if (Objects.nonNull(dto.getPrice())) {
                amountCell.setCellValue(dto.getPrice() + "元"); // 统一用"元"更规范
            } else {
                amountCell.setCellValue(""); // 均为空时显示空
            }
            // 4. 获取方式(补充默认未知状态,覆盖所有枚举值)
            String getType = dto.getGetType();
            String getTypeDesc;
            switch (getType) {
                case "FREE":
                    getTypeDesc = "免费获取";
                    break;
                case "ACTIVITY":
                    getTypeDesc = "活动获取";
                    break;
                case "INSIDE": // 注意:原代码lime是颜色,实际枚举应为INSIDE
                    getTypeDesc = "内购";
                    break;
                default:
                    getTypeDesc = "未知";
            }
            row.createCell(4).setCellValue(getTypeDesc);
            // 5. 会员优惠券状态(覆盖所有可能状态)
            String status = dto.getMemberCouponStatus();
            String statusDesc;
            switch (status) {
                case "NEW":
                    statusDesc = "已领取";
                    break;
                case "USED":
                    statusDesc = "已使用";
                    break;
                case "EXPIRE":
                    statusDesc = "已过期";
                    break;
                case "CLOSED":
                    statusDesc = "已作废";
                    break;
                default:
                    statusDesc = "未知状态";
            }
            row.createCell(5).setCellValue(statusDesc);
            // 6. 优惠券类型(补充默认处理)
            String couponType = dto.getCouponType();
            String couponTypeDesc;
            if ("DISCOUNT".equals(couponType)) {
                couponTypeDesc = "打折";
            } else if ("PRICE".equals(couponType)) {
                couponTypeDesc = "减免现金";
            } else {
                couponTypeDesc = "未知类型";
            }
            row.createCell(6).setCellValue(couponTypeDesc);
            // 10. 使用起始时间(处理null,格式化时间)
            Cell startTimeCell = row.createCell(7);
            if (Objects.nonNull(dto.getStartTime())) {
                startTimeCell.setCellValue(DateUtil.formatDateTime(dto.getStartTime()));
            } else {
                startTimeCell.setCellValue("");
            }
            // 11. 截止时间(同上)
            Cell endTimeCell = row.createCell(8);
            if (Objects.nonNull(dto.getEndTime())) {
                endTimeCell.setCellValue(DateUtil.formatDateTime(dto.getEndTime()));
            } else {
                endTimeCell.setCellValue("");
            }
        }
        return workbook;
    }
    @Override
    public void queryExportCoupon(HttpServletResponse response, StoreCouponClaimRecordQuery query) {
        List<StoreCouponClaimRecordVO> exportData = baseMapper.getExportData(query);
        XSSFWorkbook workbook = initCouponExportData(exportData);
        try {
            // 设置响应头
            String fileName = URLEncoder.encode("优惠券领取记录", "UTF-8");
            response.setContentType("application/vnd.ms-excel;charset=UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename=" + fileName + ".xlsx");
            ServletOutputStream out = response.getOutputStream();
            workbook.write(out);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                workbook.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    private static StoreCouponClaimRecord getStoreCouponClaimRecord(StoreCouponSingle storeCouponSingle, String userId) {
        StoreCouponClaimRecord storeCouponClaimRecord = new StoreCouponClaimRecord();
        storeCouponClaimRecord.setCouponId(storeCouponSingle.getCouponId());
        storeCouponClaimRecord.setCouponName(storeCouponSingle.getCouponName());
        storeCouponClaimRecord.setStoreId(storeCouponSingle.getStoreId());
        storeCouponClaimRecord.setStoreName(storeCouponSingle.getStoreName());
        storeCouponClaimRecord.setUserId(userId);
        storeCouponClaimRecord.setStoreCouponId(storeCouponSingle.getStoreCoupRef());
        return storeCouponClaimRecord;
    }
}