package com.ylx.giftCard.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ylx.common.core.domain.model.WxLoginUser; import com.ylx.common.exception.ServiceException; import com.ylx.common.utils.DateUtils; import com.ylx.common.utils.SecurityUtils; import com.ylx.common.weixinPay.enums.WxPayTypeEnum; import com.ylx.common.weixinPay.service.WxPayV3Service; import com.ylx.giftCard.domain.GiftCard; import com.ylx.giftCard.domain.GiftCardOrder; import com.ylx.giftCard.domain.dto.GiftCardManageQueryDTO; import com.ylx.giftCard.domain.dto.GiftCardManageSaveDTO; import com.ylx.giftCard.domain.dto.GiftCardManageUpdateDTO; import com.ylx.giftCard.domain.dto.GiftCardPublishStatusDTO; import com.ylx.giftCard.domain.dto.GiftCardPurchaseDTO; import com.ylx.giftCard.domain.vo.GiftCardDetailVO; import com.ylx.giftCard.domain.vo.GiftCardManageDetailVO; import com.ylx.giftCard.domain.vo.GiftCardManageExportVO; import com.ylx.giftCard.domain.vo.GiftCardManagePageVO; import com.ylx.giftCard.domain.vo.GiftCardVO; import com.ylx.giftCard.mapper.GiftCardMapper; import com.ylx.giftCard.service.IGiftCardOrderService; import com.ylx.giftCard.service.IGiftCardService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; import java.time.LocalDate; import java.time.ZoneId; import java.util.Date; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Slf4j @Service public class GiftCardServiceImpl extends ServiceImpl implements IGiftCardService { @Resource private IGiftCardOrderService giftCardOrderService; @Resource private WxPayV3Service wxPayV3Service; private static final int NOT_DELETE = 0; private static final int PUBLISHED = 1; private static final int UNPUBLISHED = 0; private static final int DELETE = 1; @Override public Page getGiftCardPage(Page page) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(GiftCard::getIsDelete, NOT_DELETE); wrapper.eq(GiftCard::getIsPublished, PUBLISHED); wrapper.orderByDesc(GiftCard::getCreateTime); Page giftCardPage = this.baseMapper.selectPage(page, wrapper); Page pageData = new Page<>( giftCardPage.getCurrent(), giftCardPage.getSize(), giftCardPage.getTotal() ); if (CollectionUtil.isNotEmpty(giftCardPage.getRecords())) { List voList = giftCardPage.getRecords().stream() .map(GiftCardVO::new) .collect(Collectors.toList()); pageData.setRecords(voList); } return pageData; } @Override @Transactional(rollbackFor = Exception.class) public Map purchaseGiftCard(GiftCardPurchaseDTO dto) { // 1. 获取当前用户 WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser(); if (ObjectUtil.isNull(wxLoginUser)) { log.warn("用户未登录,无法创建订单"); throw new ServiceException("用户未登录"); } Long id = dto.getId(); Integer quantity = dto.getQuantity(); String merchantId = dto.getMerchantId(); // 2. 查询并校验购物卡 GiftCard card = this.getById(id); validateGiftCard(card, quantity); // 3. 乐观锁扣减库存(增加状态校验,防止无效更新) int rowsAffected = deductStockOptimisticLock(card.getId(), dto.getQuantity()); if (rowsAffected <= 0) { log.warn("购买失败,库存不足或商品不存在,购物卡ID: {}", id); throw new ServiceException("库存不足或商品状态异常"); } log.info("购买成功,购物卡ID: {}, 数量: {}", id, quantity); // 4. 创建订单 GiftCardOrder order = this.giftCardOrderService.buildOrder(card, quantity, merchantId, wxLoginUser); if(ObjectUtil.isNull(order)){ log.warn("购物卡订单创建失败,购物卡ID: {},下单人ID: {}", card.getId(), wxLoginUser.getId()); throw new ServiceException("购物卡订单创建失败"); } log.info("购物卡订单创建,购物卡ID: {}, 订单编号: {}", id, order.getOrderNo()); // 5. 调用微信支付 return createWxPayOrder(order, wxLoginUser); } @Override public GiftCardDetailVO getGiftCardDetail(Long id) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(GiftCard::getId, id) .eq(GiftCard::getIsDelete, NOT_DELETE) .eq(GiftCard::getIsPublished, PUBLISHED); GiftCard card = this.getOne(wrapper); if (ObjectUtil.isNull(card)) { return null; } GiftCardDetailVO vo = new GiftCardDetailVO(); BeanUtil.copyProperties(card, vo); return vo; } @Override public Page getManagePage(Page page, GiftCardManageQueryDTO dto) { Page pageParam = ObjectUtil.isNull(page) ? new Page<>(1, 10) : page; LambdaQueryWrapper wrapper = buildManageQueryWrapper(dto); Page entityPage = this.baseMapper.selectPage(pageParam, wrapper); Page pageData = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal()); pageData.setPages(entityPage.getPages()); if (CollectionUtil.isNotEmpty(entityPage.getRecords())) { pageData.setRecords(entityPage.getRecords().stream() .map(this::toManagePageVO) .collect(Collectors.toList())); } return pageData; } @Override public List getManageExportList(GiftCardManageQueryDTO dto) { GiftCardManageQueryDTO query = ObjectUtil.isNull(dto) ? new GiftCardManageQueryDTO() : dto; checkCreateDateRange(query.getBeginCreateDate(), query.getEndCreateDate()); if (StrUtil.isNotBlank(query.getName())) { query.setName(StrUtil.trim(query.getName())); } List entityList = this.baseMapper.selectManageList( query, toStartDate(query.getBeginCreateDate()), toEndDate(query.getEndCreateDate()) ); return entityList.stream().map(this::toManageExportVO).collect(Collectors.toList()); } @Override public GiftCardManageDetailVO getManageDetail(Long id) { GiftCard card = getActiveGiftCard(id); GiftCardManageDetailVO vo = new GiftCardManageDetailVO(); BeanUtil.copyProperties(card, vo); return vo; } @Override @Transactional(rollbackFor = Exception.class) public void addGiftCard(GiftCardManageSaveDTO dto) { checkSaveParam(dto); GiftCard entity = new GiftCard(); fillGiftCard(entity, dto, normalizePublishStatus(dto.getIsPublished(), UNPUBLISHED)); entity.setId(null); entity.setSales(0); entity.setCreateBy(SecurityUtils.getUsername()); entity.setCreateTime(DateUtils.getNowDate()); entity.setUpdateBy(SecurityUtils.getUsername()); entity.setUpdateTime(DateUtils.getNowDate()); int insertResult = this.baseMapper.insert(entity); if (insertResult <= 0) { throw new ServiceException("新增购物卡失败"); } } @Override @Transactional(rollbackFor = Exception.class) public void updateGiftCard(GiftCardManageUpdateDTO dto) { if (ObjectUtil.isNull(dto) || ObjectUtil.isNull(dto.getId())) { throw new ServiceException("购物卡ID不能为空"); } checkSaveParam(dto); GiftCard entity = getActiveGiftCard(dto.getId()); Integer publishStatus = normalizePublishStatus(dto.getIsPublished(), entity.getIsPublished()); fillGiftCard(entity, dto, publishStatus); entity.setUpdateBy(SecurityUtils.getUsername()); entity.setUpdateTime(DateUtils.getNowDate()); int updateResult = this.baseMapper.updateById(entity); if (updateResult <= 0) { throw new ServiceException("编辑购物卡失败"); } } @Override @Transactional(rollbackFor = Exception.class) public void updatePublishStatus(GiftCardPublishStatusDTO dto) { if (ObjectUtil.isNull(dto) || ObjectUtil.isNull(dto.getId())) { throw new ServiceException("购物卡ID不能为空"); } Integer publishStatus = normalizePublishStatus(dto.getIsPublished(), null); GiftCard entity = getActiveGiftCard(dto.getId()); entity.setIsPublished(publishStatus); entity.setUpdateBy(SecurityUtils.getUsername()); entity.setUpdateTime(DateUtils.getNowDate()); int updateResult = this.baseMapper.updateById(entity); if (updateResult <= 0) { throw new ServiceException("修改购物卡上架状态失败"); } } @Override @Transactional(rollbackFor = Exception.class) public void deleteGiftCard(Long id) { int deleteResult = this.baseMapper.deleteById(id); if (deleteResult <= 0) { throw new ServiceException("删除购物卡失败"); } } /** * 校验购物卡有效性 */ private void validateGiftCard(GiftCard card, Integer quantity) { if (ObjectUtil.isNull(card)) { throw new ServiceException("商品不存在"); } if (card.getIsDelete() != NOT_DELETE) { log.warn("购买失败,购物卡已删除,ID: {}", card.getId()); throw new ServiceException("购物卡已删除"); } if (card.getIsPublished() != PUBLISHED) { log.warn("购买失败,购物卡未上架,ID: {}", card.getId()); throw new ServiceException("购物卡未上架"); } if (card.getStock() < quantity) { log.warn("购买失败,库存不足,ID: {},库存: {},需求数量: {}", card.getId(), card.getStock(), quantity); throw new ServiceException("库存不足"); } } /** * 乐观锁扣减库存(增加状态条件,确保只更新有效记录) */ private int deductStockOptimisticLock(Long cardId, Integer quantity) { LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(GiftCard::getId, cardId) .eq(GiftCard::getIsDelete, NOT_DELETE) .eq(GiftCard::getIsPublished, PUBLISHED) .ge(GiftCard::getStock, quantity) // 库存充足时才更新 .setSql("stock = stock - " + quantity + ", sales = sales + " + quantity); return baseMapper.update(null, wrapper); } /** * 补偿回滚库存 */ private void recoverStock(Long cardId, Integer num) { GiftCard updateEntity = new GiftCard(); LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(GiftCard::getId, cardId) .eq(GiftCard::getIsDelete, NOT_DELETE); updateWrapper.setSql("stock = stock + #{num}, sales = sales - #{num}"); // 数据放到实体里 updateEntity.setStock(num); baseMapper.update(updateEntity, updateWrapper); } /** * 创建微信支付订单(事务外执行,减少事务时长) */ private Map createWxPayOrder(GiftCardOrder order, WxLoginUser wxLoginUser) { try { return wxPayV3Service.createV3JsapiOrder( order.getOrderNo(), order.getPayAmount(), "购物卡购买", wxLoginUser.getCOpenid(), WxPayTypeEnum.GIFT_CARD.getCode() ); } catch (Exception e) { log.error("微信支付下单失败,订单号: {}", order.getOrderNo(), e); // 支付失败:恢复库存、修改订单为取消 recoverStock(order.getGiftCardId(), order.getPurchaseQuantity()); this.giftCardOrderService.cancelOrder(order.getId()); throw new ServiceException("支付服务异常,请稍后重试"); } } private GiftCard getActiveGiftCard(Long id) { if (ObjectUtil.isNull(id)) { throw new ServiceException("购物卡ID不能为空"); } if (id <= 0) { throw new ServiceException("购物卡ID不正确"); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(GiftCard::getId, id).eq(GiftCard::getIsDelete, NOT_DELETE); GiftCard entity = this.baseMapper.selectOne(wrapper); if (ObjectUtil.isNull(entity)) { throw new ServiceException("购物卡不存在或已删除"); } return entity; } private void checkSaveParam(GiftCardManageSaveDTO dto) { if (ObjectUtil.isNull(dto)) { throw new ServiceException("购物卡参数不能为空"); } if (StrUtil.isBlank(dto.getName())) { throw new ServiceException("购物卡名称不能为空"); } if (StrUtil.length(StrUtil.trim(dto.getName())) > 20) { throw new ServiceException("购物卡名称不能超过20个字符"); } if (StrUtil.isBlank(dto.getImageUrl())) { throw new ServiceException("图片不能为空"); } if (ObjectUtil.isNull(dto.getAmount()) || dto.getAmount().signum() <= 0) { throw new ServiceException("购物卡金额必须大于0"); } if (ObjectUtil.isNull(dto.getCommissionRate()) || dto.getCommissionRate().signum() < 0 || dto.getCommissionRate().compareTo(new BigDecimal("100")) > 0) { throw new ServiceException("商户提成比例必须在0到100之间"); } if (ObjectUtil.isNull(dto.getStock()) || dto.getStock() < 0) { throw new ServiceException("库存不能小于0"); } if (ObjectUtil.isNull(dto.getValidStartDate()) || ObjectUtil.isNull(dto.getValidEndDate())) { throw new ServiceException("有效期不能为空"); } if (dto.getValidStartDate().isAfter(dto.getValidEndDate())) { throw new ServiceException("有效期开始日期不能晚于结束日期"); } } private void checkCreateDateRange(LocalDate beginDate, LocalDate endDate) { if (ObjectUtil.isNotNull(beginDate) && ObjectUtil.isNotNull(endDate) && beginDate.isAfter(endDate)) { throw new ServiceException("创建开始日期不能晚于结束日期"); } } private LambdaQueryWrapper buildManageQueryWrapper(GiftCardManageQueryDTO dto) { GiftCardManageQueryDTO query = ObjectUtil.isNull(dto) ? new GiftCardManageQueryDTO() : dto; checkCreateDateRange(query.getBeginCreateDate(), query.getEndCreateDate()); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(GiftCard::getIsDelete, NOT_DELETE) .like(StrUtil.isNotBlank(query.getName()), GiftCard::getName, StrUtil.trim(query.getName())) .eq(ObjectUtil.isNotNull(query.getIsPublished()), GiftCard::getIsPublished, query.getIsPublished()) .ge(ObjectUtil.isNotNull(query.getBeginCreateDate()), GiftCard::getCreateTime, toStartDate(query.getBeginCreateDate())) .le(ObjectUtil.isNotNull(query.getEndCreateDate()), GiftCard::getCreateTime, toEndDate(query.getEndCreateDate())) .orderByDesc(GiftCard::getCreateTime) .orderByDesc(GiftCard::getId); return wrapper; } /** * 填充购物卡实体 * @param entity * @param dto * @param publishStatus */ private void fillGiftCard(GiftCard entity, GiftCardManageSaveDTO dto, Integer publishStatus) { entity.setMerchantId(StrUtil.trim(dto.getMerchantId())); entity.setName(StrUtil.trim(dto.getName())); entity.setImageUrl(StrUtil.trim(dto.getImageUrl())); entity.setAmount(dto.getAmount()); entity.setCommissionRate(dto.getCommissionRate()); entity.setStock(dto.getStock()); entity.setValidStartDate(dto.getValidStartDate()); entity.setValidEndDate(dto.getValidEndDate()); entity.setDescription(dto.getDescription()); entity.setIsPublished(publishStatus); } /** * 规范化上架状态值 * @param value * @param defaultValue * @return Integer */ private Integer normalizePublishStatus(Integer value, Integer defaultValue) { if (ObjectUtil.isNull(value)) { if (ObjectUtil.isNull(defaultValue)) { throw new ServiceException("上架状态不能为空"); } return defaultValue; } if (value == UNPUBLISHED || value == PUBLISHED) { return value; } throw new ServiceException("上架状态值不正确"); } private GiftCardManagePageVO toManagePageVO(GiftCard entity) { GiftCardManagePageVO vo = new GiftCardManagePageVO(); BeanUtil.copyProperties(entity, vo); return vo; } private GiftCardManageExportVO toManageExportVO(GiftCard entity) { GiftCardManageExportVO vo = new GiftCardManageExportVO(); BeanUtil.copyProperties(entity, vo); return vo; } private Date toStartDate(LocalDate date) { if (ObjectUtil.isNull(date)) { return null; } return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()); } private Date toEndDate(LocalDate date) { if (ObjectUtil.isNull(date)) { return null; } return Date.from(date.plusDays(1).atStartOfDay(ZoneId.systemDefault()).minusNanos(1).toInstant()); } }