GiftCardServiceImpl.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. package com.ylx.giftCard.service.impl;
  2. import cn.hutool.core.bean.BeanUtil;
  3. import cn.hutool.core.collection.CollectionUtil;
  4. import cn.hutool.core.util.ObjectUtil;
  5. import cn.hutool.core.util.StrUtil;
  6. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  7. import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  8. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  10. import com.ylx.common.core.domain.model.WxLoginUser;
  11. import com.ylx.common.exception.ServiceException;
  12. import com.ylx.common.utils.DateUtils;
  13. import com.ylx.common.utils.SecurityUtils;
  14. import com.ylx.common.weixinPay.enums.WxPayTypeEnum;
  15. import com.ylx.common.weixinPay.service.WxPayV3Service;
  16. import com.ylx.giftCard.domain.GiftCard;
  17. import com.ylx.giftCard.domain.GiftCardOrder;
  18. import com.ylx.giftCard.domain.dto.GiftCardManageQueryDTO;
  19. import com.ylx.giftCard.domain.dto.GiftCardManageSaveDTO;
  20. import com.ylx.giftCard.domain.dto.GiftCardManageUpdateDTO;
  21. import com.ylx.giftCard.domain.dto.GiftCardPublishStatusDTO;
  22. import com.ylx.giftCard.domain.dto.GiftCardPurchaseDTO;
  23. import com.ylx.giftCard.domain.vo.GiftCardDetailVO;
  24. import com.ylx.giftCard.domain.vo.GiftCardManageDetailVO;
  25. import com.ylx.giftCard.domain.vo.GiftCardManageExportVO;
  26. import com.ylx.giftCard.domain.vo.GiftCardManagePageVO;
  27. import com.ylx.giftCard.domain.vo.GiftCardVO;
  28. import com.ylx.giftCard.mapper.GiftCardMapper;
  29. import com.ylx.giftCard.service.IGiftCardOrderService;
  30. import com.ylx.giftCard.service.IGiftCardService;
  31. import lombok.extern.slf4j.Slf4j;
  32. import org.springframework.stereotype.Service;
  33. import org.springframework.transaction.annotation.Transactional;
  34. import javax.annotation.Resource;
  35. import java.math.BigDecimal;
  36. import java.time.LocalDate;
  37. import java.time.ZoneId;
  38. import java.util.Date;
  39. import java.util.List;
  40. import java.util.Map;
  41. import java.util.stream.Collectors;
  42. @Slf4j
  43. @Service
  44. public class GiftCardServiceImpl extends ServiceImpl<GiftCardMapper, GiftCard> implements IGiftCardService {
  45. @Resource
  46. private IGiftCardOrderService giftCardOrderService;
  47. @Resource
  48. private WxPayV3Service wxPayV3Service;
  49. private static final int NOT_DELETE = 0;
  50. private static final int PUBLISHED = 1;
  51. private static final int UNPUBLISHED = 0;
  52. private static final int DELETE = 1;
  53. @Override
  54. public Page<GiftCardVO> getGiftCardPage(Page<GiftCard> page) {
  55. LambdaQueryWrapper<GiftCard> wrapper = new LambdaQueryWrapper<>();
  56. wrapper.eq(GiftCard::getIsDelete, NOT_DELETE);
  57. wrapper.eq(GiftCard::getIsPublished, PUBLISHED);
  58. wrapper.orderByDesc(GiftCard::getCreateTime);
  59. Page<GiftCard> giftCardPage = this.baseMapper.selectPage(page, wrapper);
  60. Page<GiftCardVO> pageData = new Page<>(
  61. giftCardPage.getCurrent(),
  62. giftCardPage.getSize(),
  63. giftCardPage.getTotal()
  64. );
  65. if (CollectionUtil.isNotEmpty(giftCardPage.getRecords())) {
  66. List<GiftCardVO> voList = giftCardPage.getRecords().stream()
  67. .map(GiftCardVO::new)
  68. .collect(Collectors.toList());
  69. pageData.setRecords(voList);
  70. }
  71. return pageData;
  72. }
  73. @Override
  74. @Transactional(rollbackFor = Exception.class)
  75. public Map<String, Object> purchaseGiftCard(GiftCardPurchaseDTO dto) {
  76. // 1. 获取当前用户
  77. WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
  78. if (ObjectUtil.isNull(wxLoginUser)) {
  79. log.warn("用户未登录,无法创建订单");
  80. throw new ServiceException("用户未登录");
  81. }
  82. Long id = dto.getId();
  83. Integer quantity = dto.getQuantity();
  84. String merchantId = dto.getMerchantId();
  85. // 2. 查询并校验购物卡
  86. GiftCard card = this.getById(id);
  87. validateGiftCard(card, quantity);
  88. // 3. 乐观锁扣减库存(增加状态校验,防止无效更新)
  89. int rowsAffected = deductStockOptimisticLock(card.getId(), dto.getQuantity());
  90. if (rowsAffected <= 0) {
  91. log.warn("购买失败,库存不足或商品不存在,购物卡ID: {}", id);
  92. throw new ServiceException("库存不足或商品状态异常");
  93. }
  94. log.info("购买成功,购物卡ID: {}, 数量: {}", id, quantity);
  95. // 4. 创建订单
  96. GiftCardOrder order = this.giftCardOrderService.buildOrder(card, quantity, merchantId, wxLoginUser);
  97. if(ObjectUtil.isNull(order)){
  98. log.warn("购物卡订单创建失败,购物卡ID: {},下单人ID: {}", card.getId(), wxLoginUser.getId());
  99. throw new ServiceException("购物卡订单创建失败");
  100. }
  101. log.info("购物卡订单创建,购物卡ID: {}, 订单编号: {}", id, order.getOrderNo());
  102. // 5. 调用微信支付
  103. return createWxPayOrder(order, wxLoginUser);
  104. }
  105. @Override
  106. public GiftCardDetailVO getGiftCardDetail(Long id) {
  107. LambdaQueryWrapper<GiftCard> wrapper = new LambdaQueryWrapper<>();
  108. wrapper.eq(GiftCard::getId, id)
  109. .eq(GiftCard::getIsDelete, NOT_DELETE)
  110. .eq(GiftCard::getIsPublished, PUBLISHED);
  111. GiftCard card = this.getOne(wrapper);
  112. if (ObjectUtil.isNull(card)) {
  113. return null;
  114. }
  115. GiftCardDetailVO vo = new GiftCardDetailVO();
  116. BeanUtil.copyProperties(card, vo);
  117. return vo;
  118. }
  119. @Override
  120. public Page<GiftCardManagePageVO> getManagePage(Page<GiftCard> page, GiftCardManageQueryDTO dto) {
  121. Page<GiftCard> pageParam = ObjectUtil.isNull(page) ? new Page<>(1, 10) : page;
  122. LambdaQueryWrapper<GiftCard> wrapper = buildManageQueryWrapper(dto);
  123. Page<GiftCard> entityPage = this.baseMapper.selectPage(pageParam, wrapper);
  124. Page<GiftCardManagePageVO> pageData = new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
  125. pageData.setPages(entityPage.getPages());
  126. if (CollectionUtil.isNotEmpty(entityPage.getRecords())) {
  127. pageData.setRecords(entityPage.getRecords().stream()
  128. .map(this::toManagePageVO)
  129. .collect(Collectors.toList()));
  130. }
  131. return pageData;
  132. }
  133. @Override
  134. public List<GiftCardManageExportVO> getManageExportList(GiftCardManageQueryDTO dto) {
  135. GiftCardManageQueryDTO query = ObjectUtil.isNull(dto) ? new GiftCardManageQueryDTO() : dto;
  136. checkCreateDateRange(query.getBeginCreateDate(), query.getEndCreateDate());
  137. if (StrUtil.isNotBlank(query.getName())) {
  138. query.setName(StrUtil.trim(query.getName()));
  139. }
  140. List<GiftCard> entityList = this.baseMapper.selectManageList(
  141. query,
  142. toStartDate(query.getBeginCreateDate()),
  143. toEndDate(query.getEndCreateDate())
  144. );
  145. return entityList.stream().map(this::toManageExportVO).collect(Collectors.toList());
  146. }
  147. @Override
  148. public GiftCardManageDetailVO getManageDetail(Long id) {
  149. GiftCard card = getActiveGiftCard(id);
  150. GiftCardManageDetailVO vo = new GiftCardManageDetailVO();
  151. BeanUtil.copyProperties(card, vo);
  152. return vo;
  153. }
  154. @Override
  155. @Transactional(rollbackFor = Exception.class)
  156. public void addGiftCard(GiftCardManageSaveDTO dto) {
  157. checkSaveParam(dto);
  158. GiftCard entity = new GiftCard();
  159. fillGiftCard(entity, dto, normalizePublishStatus(dto.getIsPublished(), UNPUBLISHED));
  160. entity.setId(null);
  161. entity.setSales(0);
  162. entity.setCreateBy(SecurityUtils.getUsername());
  163. entity.setCreateTime(DateUtils.getNowDate());
  164. entity.setUpdateBy(SecurityUtils.getUsername());
  165. entity.setUpdateTime(DateUtils.getNowDate());
  166. int insertResult = this.baseMapper.insert(entity);
  167. if (insertResult <= 0) {
  168. throw new ServiceException("新增购物卡失败");
  169. }
  170. }
  171. @Override
  172. @Transactional(rollbackFor = Exception.class)
  173. public void updateGiftCard(GiftCardManageUpdateDTO dto) {
  174. if (ObjectUtil.isNull(dto) || ObjectUtil.isNull(dto.getId())) {
  175. throw new ServiceException("购物卡ID不能为空");
  176. }
  177. checkSaveParam(dto);
  178. GiftCard entity = getActiveGiftCard(dto.getId());
  179. Integer publishStatus = normalizePublishStatus(dto.getIsPublished(), entity.getIsPublished());
  180. fillGiftCard(entity, dto, publishStatus);
  181. entity.setUpdateBy(SecurityUtils.getUsername());
  182. entity.setUpdateTime(DateUtils.getNowDate());
  183. int updateResult = this.baseMapper.updateById(entity);
  184. if (updateResult <= 0) {
  185. throw new ServiceException("编辑购物卡失败");
  186. }
  187. }
  188. @Override
  189. @Transactional(rollbackFor = Exception.class)
  190. public void updatePublishStatus(GiftCardPublishStatusDTO dto) {
  191. if (ObjectUtil.isNull(dto) || ObjectUtil.isNull(dto.getId())) {
  192. throw new ServiceException("购物卡ID不能为空");
  193. }
  194. Integer publishStatus = normalizePublishStatus(dto.getIsPublished(), null);
  195. GiftCard entity = getActiveGiftCard(dto.getId());
  196. entity.setIsPublished(publishStatus);
  197. entity.setUpdateBy(SecurityUtils.getUsername());
  198. entity.setUpdateTime(DateUtils.getNowDate());
  199. int updateResult = this.baseMapper.updateById(entity);
  200. if (updateResult <= 0) {
  201. throw new ServiceException("修改购物卡上架状态失败");
  202. }
  203. }
  204. @Override
  205. @Transactional(rollbackFor = Exception.class)
  206. public void deleteGiftCard(Long id) {
  207. int deleteResult = this.baseMapper.deleteById(id);
  208. if (deleteResult <= 0) {
  209. throw new ServiceException("删除购物卡失败");
  210. }
  211. }
  212. /**
  213. * 校验购物卡有效性
  214. */
  215. private void validateGiftCard(GiftCard card, Integer quantity) {
  216. if (ObjectUtil.isNull(card)) {
  217. throw new ServiceException("商品不存在");
  218. }
  219. if (card.getIsDelete() != NOT_DELETE) {
  220. log.warn("购买失败,购物卡已删除,ID: {}", card.getId());
  221. throw new ServiceException("购物卡已删除");
  222. }
  223. if (card.getIsPublished() != PUBLISHED) {
  224. log.warn("购买失败,购物卡未上架,ID: {}", card.getId());
  225. throw new ServiceException("购物卡未上架");
  226. }
  227. if (card.getStock() < quantity) {
  228. log.warn("购买失败,库存不足,ID: {},库存: {},需求数量: {}", card.getId(), card.getStock(), quantity);
  229. throw new ServiceException("库存不足");
  230. }
  231. }
  232. /**
  233. * 乐观锁扣减库存(增加状态条件,确保只更新有效记录)
  234. */
  235. private int deductStockOptimisticLock(Long cardId, Integer quantity) {
  236. LambdaUpdateWrapper<GiftCard> wrapper = new LambdaUpdateWrapper<>();
  237. wrapper.eq(GiftCard::getId, cardId)
  238. .eq(GiftCard::getIsDelete, NOT_DELETE)
  239. .eq(GiftCard::getIsPublished, PUBLISHED)
  240. .ge(GiftCard::getStock, quantity) // 库存充足时才更新
  241. .setSql("stock = stock - " + quantity + ", sales = sales + " + quantity);
  242. return baseMapper.update(null, wrapper);
  243. }
  244. /**
  245. * 补偿回滚库存
  246. */
  247. private void recoverStock(Long cardId, Integer num) {
  248. GiftCard updateEntity = new GiftCard();
  249. LambdaUpdateWrapper<GiftCard> updateWrapper = new LambdaUpdateWrapper<>();
  250. updateWrapper.eq(GiftCard::getId, cardId)
  251. .eq(GiftCard::getIsDelete, NOT_DELETE);
  252. updateWrapper.setSql("stock = stock + #{num}, sales = sales - #{num}");
  253. // 数据放到实体里
  254. updateEntity.setStock(num);
  255. baseMapper.update(updateEntity, updateWrapper);
  256. }
  257. /**
  258. * 创建微信支付订单(事务外执行,减少事务时长)
  259. */
  260. private Map<String, Object> createWxPayOrder(GiftCardOrder order, WxLoginUser wxLoginUser) {
  261. try {
  262. return wxPayV3Service.createV3JsapiOrder(
  263. order.getOrderNo(),
  264. order.getPayAmount(),
  265. "购物卡购买",
  266. wxLoginUser.getCOpenid(),
  267. WxPayTypeEnum.GIFT_CARD.getCode()
  268. );
  269. } catch (Exception e) {
  270. log.error("微信支付下单失败,订单号: {}", order.getOrderNo(), e);
  271. // 支付失败:恢复库存、修改订单为取消
  272. recoverStock(order.getGiftCardId(), order.getPurchaseQuantity());
  273. this.giftCardOrderService.cancelOrder(order.getId());
  274. throw new ServiceException("支付服务异常,请稍后重试");
  275. }
  276. }
  277. private GiftCard getActiveGiftCard(Long id) {
  278. if (ObjectUtil.isNull(id)) {
  279. throw new ServiceException("购物卡ID不能为空");
  280. }
  281. if (id <= 0) {
  282. throw new ServiceException("购物卡ID不正确");
  283. }
  284. LambdaQueryWrapper<GiftCard> wrapper = new LambdaQueryWrapper<>();
  285. wrapper.eq(GiftCard::getId, id).eq(GiftCard::getIsDelete, NOT_DELETE);
  286. GiftCard entity = this.baseMapper.selectOne(wrapper);
  287. if (ObjectUtil.isNull(entity)) {
  288. throw new ServiceException("购物卡不存在或已删除");
  289. }
  290. return entity;
  291. }
  292. private void checkSaveParam(GiftCardManageSaveDTO dto) {
  293. if (ObjectUtil.isNull(dto)) {
  294. throw new ServiceException("购物卡参数不能为空");
  295. }
  296. if (StrUtil.isBlank(dto.getName())) {
  297. throw new ServiceException("购物卡名称不能为空");
  298. }
  299. if (StrUtil.length(StrUtil.trim(dto.getName())) > 20) {
  300. throw new ServiceException("购物卡名称不能超过20个字符");
  301. }
  302. if (StrUtil.isBlank(dto.getImageUrl())) {
  303. throw new ServiceException("图片不能为空");
  304. }
  305. if (ObjectUtil.isNull(dto.getAmount()) || dto.getAmount().signum() <= 0) {
  306. throw new ServiceException("购物卡金额必须大于0");
  307. }
  308. if (ObjectUtil.isNull(dto.getCommissionRate())
  309. || dto.getCommissionRate().signum() < 0
  310. || dto.getCommissionRate().compareTo(new BigDecimal("100")) > 0) {
  311. throw new ServiceException("商户提成比例必须在0到100之间");
  312. }
  313. if (ObjectUtil.isNull(dto.getStock()) || dto.getStock() < 0) {
  314. throw new ServiceException("库存不能小于0");
  315. }
  316. if (ObjectUtil.isNull(dto.getValidStartDate()) || ObjectUtil.isNull(dto.getValidEndDate())) {
  317. throw new ServiceException("有效期不能为空");
  318. }
  319. if (dto.getValidStartDate().isAfter(dto.getValidEndDate())) {
  320. throw new ServiceException("有效期开始日期不能晚于结束日期");
  321. }
  322. }
  323. private void checkCreateDateRange(LocalDate beginDate, LocalDate endDate) {
  324. if (ObjectUtil.isNotNull(beginDate) && ObjectUtil.isNotNull(endDate) && beginDate.isAfter(endDate)) {
  325. throw new ServiceException("创建开始日期不能晚于结束日期");
  326. }
  327. }
  328. private LambdaQueryWrapper<GiftCard> buildManageQueryWrapper(GiftCardManageQueryDTO dto) {
  329. GiftCardManageQueryDTO query = ObjectUtil.isNull(dto) ? new GiftCardManageQueryDTO() : dto;
  330. checkCreateDateRange(query.getBeginCreateDate(), query.getEndCreateDate());
  331. LambdaQueryWrapper<GiftCard> wrapper = new LambdaQueryWrapper<>();
  332. wrapper.eq(GiftCard::getIsDelete, NOT_DELETE)
  333. .like(StrUtil.isNotBlank(query.getName()), GiftCard::getName, StrUtil.trim(query.getName()))
  334. .eq(ObjectUtil.isNotNull(query.getIsPublished()), GiftCard::getIsPublished, query.getIsPublished())
  335. .ge(ObjectUtil.isNotNull(query.getBeginCreateDate()), GiftCard::getCreateTime, toStartDate(query.getBeginCreateDate()))
  336. .le(ObjectUtil.isNotNull(query.getEndCreateDate()), GiftCard::getCreateTime, toEndDate(query.getEndCreateDate()))
  337. .orderByDesc(GiftCard::getCreateTime)
  338. .orderByDesc(GiftCard::getId);
  339. return wrapper;
  340. }
  341. /**
  342. * 填充购物卡实体
  343. * @param entity
  344. * @param dto
  345. * @param publishStatus
  346. */
  347. private void fillGiftCard(GiftCard entity, GiftCardManageSaveDTO dto, Integer publishStatus) {
  348. entity.setMerchantId(StrUtil.trim(dto.getMerchantId()));
  349. entity.setName(StrUtil.trim(dto.getName()));
  350. entity.setImageUrl(StrUtil.trim(dto.getImageUrl()));
  351. entity.setAmount(dto.getAmount());
  352. entity.setCommissionRate(dto.getCommissionRate());
  353. entity.setStock(dto.getStock());
  354. entity.setValidStartDate(dto.getValidStartDate());
  355. entity.setValidEndDate(dto.getValidEndDate());
  356. entity.setDescription(dto.getDescription());
  357. entity.setIsPublished(publishStatus);
  358. }
  359. /**
  360. * 规范化上架状态值
  361. * @param value
  362. * @param defaultValue
  363. * @return Integer
  364. */
  365. private Integer normalizePublishStatus(Integer value, Integer defaultValue) {
  366. if (ObjectUtil.isNull(value)) {
  367. if (ObjectUtil.isNull(defaultValue)) {
  368. throw new ServiceException("上架状态不能为空");
  369. }
  370. return defaultValue;
  371. }
  372. if (value == UNPUBLISHED || value == PUBLISHED) {
  373. return value;
  374. }
  375. throw new ServiceException("上架状态值不正确");
  376. }
  377. private GiftCardManagePageVO toManagePageVO(GiftCard entity) {
  378. GiftCardManagePageVO vo = new GiftCardManagePageVO();
  379. BeanUtil.copyProperties(entity, vo);
  380. return vo;
  381. }
  382. private GiftCardManageExportVO toManageExportVO(GiftCard entity) {
  383. GiftCardManageExportVO vo = new GiftCardManageExportVO();
  384. BeanUtil.copyProperties(entity, vo);
  385. return vo;
  386. }
  387. private Date toStartDate(LocalDate date) {
  388. if (ObjectUtil.isNull(date)) {
  389. return null;
  390. }
  391. return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
  392. }
  393. private Date toEndDate(LocalDate date) {
  394. if (ObjectUtil.isNull(date)) {
  395. return null;
  396. }
  397. return Date.from(date.plusDays(1).atStartOfDay(ZoneId.systemDefault()).minusNanos(1).toInstant());
  398. }
  399. }