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;
|
import cn.lili.modules.lmk.service.StoreCouponClaimRecordService;
|
import cn.lili.base.Result;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
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;
|
import lombok.RequiredArgsConstructor;
|
import cn.lili.utils.PageUtil;
|
import org.springframework.beans.BeanUtils;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
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;
|
|
/**
|
* 店铺优惠卷领取记录 服务实现类
|
*
|
* @author peng
|
* @since 2025-09-25
|
*/
|
@Service
|
@RequiredArgsConstructor
|
public class StoreCouponClaimRecordServiceImpl extends ServiceImpl<StoreCouponClaimRecordMapper, StoreCouponClaimRecord> implements StoreCouponClaimRecordService {
|
|
private final StoreCouponClaimRecordMapper storeCouponClaimRecordMapper;
|
private final RedissonClient redissonClient;
|
private static final String STORE_COUPON_CLAIM = "store_coupon_claim:";
|
private final MemberCouponService memberCouponService;
|
private final StoreCouponService storeCouponService;
|
private final StoreCouponSingleService storeCouponSingleService;
|
/**
|
* 添加
|
* @param form
|
* @return
|
*/
|
@Override
|
public Result add(StoreCouponClaimRecordForm form) {
|
StoreCouponClaimRecord entity = StoreCouponClaimRecordForm.getEntityByForm(form, null);
|
baseMapper.insert(entity);
|
return Result.ok("添加成功");
|
}
|
|
/**
|
* 修改
|
* @param form
|
* @return
|
*/
|
@Override
|
public Result update(StoreCouponClaimRecordForm form) {
|
StoreCouponClaimRecord entity = baseMapper.selectById(form.getId());
|
|
// 为空抛IllegalArgumentException,做全局异常处理
|
Assert.notNull(entity, "记录不存在");
|
BeanUtils.copyProperties(form, entity);
|
baseMapper.updateById(entity);
|
return Result.ok("修改成功");
|
}
|
|
/**
|
* 批量删除
|
* @param ids
|
* @return
|
*/
|
@Override
|
public Result remove(List<String> ids) {
|
baseMapper.deleteBatchIds(ids);
|
return Result.ok("删除成功");
|
}
|
|
/**
|
* id删除
|
* @param id
|
* @return
|
*/
|
@Override
|
public Result removeById(String id) {
|
baseMapper.deleteById(id);
|
return Result.ok("删除成功");
|
}
|
|
/**
|
* 分页查询
|
* @param query
|
* @return
|
*/
|
@Override
|
public Result page(StoreCouponClaimRecordQuery query) {
|
IPage<StoreCouponClaimRecordVO> page = PageUtil.getPage(query, StoreCouponClaimRecordVO.class);
|
baseMapper.getPage(page, query);
|
return Result.ok().data(page.getRecords()).total(page.getTotal());
|
}
|
|
/**
|
* 根据id查找
|
* @param id
|
* @return
|
*/
|
@Override
|
public Result detail(String id) {
|
StoreCouponClaimRecordVO vo = baseMapper.getById(id);
|
Assert.notNull(vo, "记录不存在");
|
return Result.ok().data(vo);
|
}
|
|
/**
|
* 列表
|
* @return
|
*/
|
@Override
|
public Result all() {
|
List<StoreCouponClaimRecord> entities = baseMapper.selectList(null);
|
List<StoreCouponClaimRecordVO> vos = entities.stream()
|
.map(entity -> StoreCouponClaimRecordVO.getVoByEntity(entity, null))
|
.collect(Collectors.toList());
|
return Result.ok().data(vos);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public Result claimCoupon(String id) {
|
AuthUser currentUser = UserContext.getCurrentUser();
|
if (currentUser == null) {
|
throw new ServiceException("当前用户没有登录无法领取");
|
}
|
String userId = currentUser.getId();
|
String nickName = currentUser.getNickName();
|
|
//锁住礼品码id
|
RLock redissonLock = redissonClient.getLock(STORE_COUPON_CLAIM + id);
|
try {
|
redissonLock.lock();
|
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, 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("当前店铺优惠卷状态异常");
|
}
|
//领取对应的优惠卷写入记录
|
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(storeCouponSingle.getCouponId());
|
|
} finally {
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
@Override
|
public void afterCommit() {
|
if (redissonLock.isHeldByCurrentThread()) {
|
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;
|
}
|
}
|