Просмотр исходного кода

购物卡、购物卡订单代码提交

wangzhijun 1 неделя назад
Родитель
Сommit
40a0f3d602

+ 37 - 2
nightFragrance-massage/src/main/java/com/ylx/giftCard/controller/GiftCardController.java

@@ -1,13 +1,48 @@
 package com.ylx.giftCard.controller;
 
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ylx.common.core.domain.R;
+import com.ylx.giftCard.domain.GiftCard;
+import com.ylx.giftCard.domain.dto.GiftCardPurchaseDTO;
+import com.ylx.giftCard.domain.vo.GiftCardDetailVO;
+import com.ylx.giftCard.domain.vo.GiftCardVO;
+import com.ylx.giftCard.service.IGiftCardService;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
 
 @RestController
 @RequestMapping("/gift/card")
 @Api(tags = {"购物卡"})
 @Slf4j
 public class GiftCardController {
+
+    @Resource
+    private IGiftCardService giftCardService;
+
+    @GetMapping("/page")
+    @ApiOperation("购物卡分页列表接口")
+    public R<Page<GiftCardVO>> getGiftCardPage(Page<GiftCard> page) {
+        Page<GiftCardVO> pageData = giftCardService.getGiftCardPage(page);
+        return R.ok(pageData);
+    }
+
+    @PostMapping("/purchase")
+    @ApiOperation("购物卡购买接口")
+    public R<String> purchaseGiftCard(@Validated @RequestBody GiftCardPurchaseDTO dto) {
+        boolean success = giftCardService.purchaseGiftCard(dto);
+        return success ? R.ok("购买成功") : R.fail("购买失败,库存不足或商品不存在");
+    }
+
+    @GetMapping("/{id}/detail")
+    @ApiOperation("获取购物卡详情")
+    public R<GiftCardDetailVO> getGiftCardDetail(@PathVariable Long id) {
+        GiftCardDetailVO detail = giftCardService.getGiftCardDetail(id);
+        return ObjectUtil.isNotNull(detail) ? R.ok(detail) : R.fail("购物卡不存在或已被删除");
+    }
 }

+ 28 - 2
nightFragrance-massage/src/main/java/com/ylx/giftCard/domain/GiftCard.java

@@ -1,12 +1,13 @@
 package com.ylx.giftCard.domain;
 
-import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
 import com.ylx.common.core.domain.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.math.BigDecimal;
-import java.time.LocalDateTime;
+import java.time.LocalDate;
 
 /**
  * 购物卡信息表
@@ -37,6 +38,26 @@ public class GiftCard extends BaseEntity {
      */
     private BigDecimal amount;
 
+    /**
+     * 库存数量,0表示无库存
+     */
+    private Integer stock;
+
+    /**
+     * 累计销量
+     */
+    private Integer sales;
+
+    /**
+     * 有效期开始日期
+     */
+    private LocalDate validStartDate;
+
+    /**
+     * 有效期结束日期
+     */
+    private LocalDate validEndDate;
+
     /**
      * 商户提成比例,如 1.00 表示 1%
      */
@@ -57,4 +78,9 @@ public class GiftCard extends BaseEntity {
      */
     private String description;
 
+    /**
+     * 是否删除:0=否,1=是。
+     */
+    private Integer isDelete;
+
 }

+ 25 - 0
nightFragrance-massage/src/main/java/com/ylx/giftCard/domain/dto/GiftCardPurchaseDTO.java

@@ -0,0 +1,25 @@
+package com.ylx.giftCard.domain.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import javax.validation.constraints.*;
+
+@ApiModel("购物卡购买DTO")
+@Data
+public class GiftCardPurchaseDTO {
+
+    @NotNull(message = "购物卡ID不能为空")
+    @Positive(message = "购物卡ID必须为正整数")
+    @ApiModelProperty("购物卡ID")
+    private Long id;
+
+    @NotNull(message = "购买数量不能为空")
+    @Min(value = 1, message = "购买数量必须大于0")
+    @ApiModelProperty("购买数量")
+    private Integer quantity;
+
+    @ApiModelProperty("商户id")
+    private String merchantId;
+
+}

+ 43 - 0
nightFragrance-massage/src/main/java/com/ylx/giftCard/domain/vo/GiftCardDetailVO.java

@@ -0,0 +1,43 @@
+package com.ylx.giftCard.domain.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@ApiModel("购物卡详情DTO")
+@Data
+public class GiftCardDetailVO {
+
+    @ApiModelProperty("购物卡ID")
+    private Long id;
+
+    @ApiModelProperty("购物卡名称")
+    private String name;
+
+    @ApiModelProperty("价格(单位:元,保留两位小数)")
+    private BigDecimal price; // 建议用 BigDecimal 避免浮点误差
+
+    @ApiModelProperty("适用范围描述")
+    private String scopeDesc = "全平台可用";
+
+    @ApiModelProperty("有效期开始日期")
+    private LocalDate validStartDate;
+
+    @ApiModelProperty("有效期结束日期")
+    private LocalDate validEndDate;
+
+    @ApiModelProperty("服务商户描述")
+    private String merchantDesc = "全平台商户可用";
+
+    @ApiModelProperty("库存数量")
+    private Integer stock;
+
+    @ApiModelProperty("销量")
+    private Integer sales;
+
+    @ApiModelProperty("封面图URL")
+    private String coverUrl;
+}

+ 53 - 0
nightFragrance-massage/src/main/java/com/ylx/giftCard/domain/vo/GiftCardVO.java

@@ -0,0 +1,53 @@
+package com.ylx.giftCard.domain.vo;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.ylx.giftCard.domain.GiftCard;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 礼品卡视图对象(Value Object)类
+ * 用于封装礼品卡相关的数据,提供前端展示使用
+ */
+@Data
+public class GiftCardVO {
+
+    @ApiModelProperty("礼品卡ID")
+    private Long id;
+
+    @ApiModelProperty("礼品卡名称")
+    private String name;
+
+    @ApiModelProperty("礼品卡金额")
+    private BigDecimal amount;
+
+    @ApiModelProperty("礼品卡图片URL")
+    private String imageUrl;
+
+    @ApiModelProperty("销售数量")
+    private Integer sales;
+
+    @ApiModelProperty("库存数量")
+    private Integer stock;
+
+    @ApiModelProperty("售罄状态")
+    private Boolean isSoldOut;
+
+    /**
+     * 构造函数,用于将实体对象转换为视图对象
+     *
+     * @param entity 礼品卡实体对象
+     */
+    public GiftCardVO(GiftCard entity) {
+        this.id = entity.getId();
+        this.name = entity.getName();
+        this.amount = entity.getAmount();
+        this.imageUrl = entity.getImageUrl();
+        this.sales = entity.getSales();
+        this.stock = entity.getStock();
+        // 判断库存是否为null或小于等于0,若是则标记为售罄
+        this.isSoldOut = ObjectUtil.isNull(entity.getStock()) || entity.getStock() <= 0;
+    }
+}

+ 3 - 0
nightFragrance-massage/src/main/java/com/ylx/giftCard/service/IGiftCardOrderService.java

@@ -1,7 +1,10 @@
 package com.ylx.giftCard.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ylx.giftCard.domain.GiftCard;
 import com.ylx.giftCard.domain.GiftCardOrder;
 
 public interface IGiftCardOrderService extends IService<GiftCardOrder> {
+
+    void buildOrders(GiftCard card, Integer quantity, String merchantId);
 }

+ 12 - 0
nightFragrance-massage/src/main/java/com/ylx/giftCard/service/IGiftCardService.java

@@ -1,4 +1,16 @@
 package com.ylx.giftCard.service;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ylx.giftCard.domain.GiftCard;
+import com.ylx.giftCard.domain.dto.GiftCardPurchaseDTO;
+import com.ylx.giftCard.domain.vo.GiftCardDetailVO;
+import com.ylx.giftCard.domain.vo.GiftCardVO;
+
 public interface IGiftCardService {
+
+    Page<GiftCardVO> getGiftCardPage(Page<GiftCard> page);
+
+    boolean purchaseGiftCard(GiftCardPurchaseDTO dto);
+
+    GiftCardDetailVO getGiftCardDetail(Long id);
 }

+ 153 - 1
nightFragrance-massage/src/main/java/com/ylx/giftCard/service/impl/GiftCardOrderServiceImpl.java

@@ -1,12 +1,164 @@
 package com.ylx.giftCard.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
 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.giftCard.domain.GiftCard;
 import com.ylx.giftCard.domain.GiftCardOrder;
 import com.ylx.giftCard.mapper.GiftCardOrderMapper;
 import com.ylx.giftCard.service.IGiftCardOrderService;
+import com.ylx.massage.domain.TJs;
+import com.ylx.massage.service.TJsService;
+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.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
 @Service
 public class GiftCardOrderServiceImpl extends ServiceImpl<GiftCardOrderMapper, GiftCardOrder> implements IGiftCardOrderService {
 
-}
+    @Resource
+    private TJsService jsService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void buildOrders(GiftCard card, Integer quantity, String merchantId) {
+        // 1. 参数校验
+        if (ObjectUtil.isNull(card)) {
+            throw new IllegalArgumentException("购物卡信息不能为空");
+        }
+        if (quantity == null || quantity <= 0) {
+            throw new IllegalArgumentException("购买数量必须大于0");
+        }
+
+        // 2. 获取用户信息
+        WxLoginUser wxLoginUser = SecurityUtils.getWxLoginUser();
+        if (ObjectUtil.isNull(wxLoginUser)) {
+            log.warn("用户未登录,无法创建订单");
+            throw new ServiceException("用户未登录");
+        }
+
+        // 3. 查询商户信息(只查询一次,避免重复查询)
+        TJs merchant = null;
+        if (StrUtil.isNotEmpty(merchantId)) {
+            merchant = this.jsService.getById(merchantId);
+            if (ObjectUtil.isNull(merchant)) {
+                log.warn("商户信息不存在,ID: {}", merchantId);
+            }
+        }
+
+        // 4. 批量创建订单
+        List<GiftCardOrder> orders = new ArrayList<>(quantity);
+        for (int i = 0; i < quantity; i++) {
+            GiftCardOrder order = createSingleOrder(card, wxLoginUser, merchant, i + 1, quantity);
+            orders.add(order);
+        }
+
+        // 5. 批量插入订单
+        boolean insertResult = this.saveBatch(orders);
+        if (!insertResult) {
+            log.warn("批量创建购物卡订单失败,购物卡ID: {},下单人ID: {},总数量: {}", card.getId(), wxLoginUser.getId(), quantity);
+            throw new ServiceException("批量创建订单失败");
+        }
+
+        log.info("批量创建购物卡订单成功,购物卡ID: {},下单人ID: {},总数量: {}", card.getId(), wxLoginUser.getId(), quantity);
+    }
+
+    /**
+     * 创建单个订单
+     */
+    private GiftCardOrder createSingleOrder(GiftCard card, WxLoginUser wxLoginUser, TJs merchant, int currentSeq, int totalQuantity) {
+        GiftCardOrder order = new GiftCardOrder();
+
+        // 生成唯一订单号
+        String orderNo = generateUniqueOrderNo(currentSeq);
+        order.setOrderNo(orderNo);
+
+        // 设置购物卡信息
+        setGiftCardInfo(order, card);
+
+        // 设置用户信息
+        setUserInfo(order, wxLoginUser);
+
+        // 设置商户信息
+        setMerchantInfo(order, merchant);
+
+        // 计算金额(每张卡的金额)
+        calculateAmountPerCard(order, card);
+
+        // 设置订单状态和时间
+        order.setStatus(1); // 已支付
+        order.setCreateTime(DateUtils.getNowDate());
+        order.setUpdateTime(order.getCreateTime());
+
+        return order;
+    }
+
+    /**
+     * 生成唯一订单号(带序列号)
+     */
+    private String generateUniqueOrderNo(int sequence) {
+        long timestamp = System.currentTimeMillis();
+        String randomNum = RandomUtil.randomNumbers(6);
+        // 可以加入序列号信息,便于区分同批订单中的不同卡
+        return "GC" + timestamp + randomNum + String.format("%02d", sequence); // 序列号补零
+    }
+
+    /**
+     * 设置购物卡信息
+     */
+    private void setGiftCardInfo(GiftCardOrder order, GiftCard card) {
+        order.setGiftCardId(card.getId());
+        order.setGiftCardName(card.getName());
+        order.setGiftCardAmount(card.getAmount());
+        order.setCommissionRate(card.getCommissionRate());
+    }
+
+    /**
+     * 设置用户信息
+     */
+    private void setUserInfo(GiftCardOrder order, WxLoginUser wxLoginUser) {
+        order.setUserId(wxLoginUser.getId());
+        order.setUserName(wxLoginUser.getUsername());
+        order.setUserPhone(wxLoginUser.getCPhone());
+    }
+
+    /**
+     * 设置商户信息
+     */
+    private void setMerchantInfo(GiftCardOrder order, TJs merchant) {
+        if (ObjectUtil.isNotNull(merchant)) {
+            order.setMerchantId(merchant.getId());
+            order.setMerchantName(merchant.getcName());
+            order.setMerchantNickName(merchant.getcNickName());
+            // order.setMerchantAccount(merchant.getAccount());
+        }
+    }
+
+    /**
+     * 计算单张卡的金额(每张卡的金额相同)
+     */
+    private void calculateAmountPerCard(GiftCardOrder order, GiftCard card) {
+        // 单张卡的支付金额 = 购物卡面额
+        BigDecimal payAmount = card.getAmount().setScale(2, RoundingMode.HALF_UP);
+        order.setPayAmount(payAmount);
+
+        // 单张卡的佣金金额 = 面额 * 佣金率 / 100
+        BigDecimal commissionAmount = payAmount
+                .multiply(card.getCommissionRate())
+                .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
+        order.setCommissionAmount(commissionAmount);
+    }
+}

+ 123 - 0
nightFragrance-massage/src/main/java/com/ylx/giftCard/service/impl/GiftCardServiceImpl.java

@@ -1,11 +1,134 @@
 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 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.giftCard.domain.GiftCard;
+import com.ylx.giftCard.domain.GiftCardOrder;
+import com.ylx.giftCard.domain.dto.GiftCardPurchaseDTO;
+import com.ylx.giftCard.domain.vo.GiftCardDetailVO;
+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.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+@Slf4j
 @Service
 public class GiftCardServiceImpl extends ServiceImpl<GiftCardMapper, GiftCard> implements IGiftCardService {
+
+    @Resource
+    private IGiftCardOrderService giftCardOrderService;
+
+    private static final int NOT_DELETE = 0;
+    private static final int PUBLISHED = 1;
+
+    @Override
+    public Page<GiftCardVO> getGiftCardPage(Page<GiftCard> page) {
+
+        LambdaQueryWrapper<GiftCard> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GiftCard::getIsDelete, NOT_DELETE);
+        wrapper.eq(GiftCard::getIsPublished, PUBLISHED);
+        wrapper.orderByDesc(GiftCard::getCreateTime);
+
+        Page<GiftCard> giftCardPage = this.baseMapper.selectPage(page, wrapper);
+        Page<GiftCardVO> pageData = new Page<>(
+                giftCardPage.getCurrent(),
+                giftCardPage.getSize(),
+                giftCardPage.getTotal()
+        );
+
+        if (CollectionUtil.isNotEmpty(giftCardPage.getRecords())) {
+            List<GiftCardVO> voList = giftCardPage.getRecords().stream()
+                    .map(GiftCardVO::new)
+                    .collect(Collectors.toList());
+            pageData.setRecords(voList);
+        }
+
+        return pageData;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean purchaseGiftCard(GiftCardPurchaseDTO dto) {
+        Long id = dto.getId();
+        Integer quantity = dto.getQuantity();
+        String merchantId = dto.getMerchantId();
+
+        GiftCard card = this.getById(id);
+        if (ObjectUtil.isNull(card)) {
+            log.warn("购买失败,购物卡不存在,ID: {}", id);
+            return false;
+        }
+
+        // 1. 扣减库存(使用乐观锁防止超卖)
+        // 使用逗号拼接多个字段的原子更新操作,避免使用 String.format
+        String updateExpr = "stock = stock - " + quantity + ", sales = sales + " + quantity;
+
+        LambdaUpdateWrapper<GiftCard> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(GiftCard::getId, id)
+                .eq(GiftCard::getIsDelete, NOT_DELETE) // 确保商品未被删除
+                .eq(GiftCard::getIsPublished, PUBLISHED) // 确保商品已上架
+                .ge(GiftCard::getStock, quantity) // 核心:库存必须大于等于购买数量
+                .setSql(updateExpr);
+
+        int rowsAffected = this.baseMapper.update(null, updateWrapper);
+
+        if (rowsAffected <= 0) {
+            log.warn("购买失败,库存不足或商品不存在,购物卡ID: {}", id);
+            return false;
+        }
+
+        log.info("购买成功,购物卡ID: {}, 数量: {}", id, quantity);
+
+        // 异步创建订单
+        createOrderAsync(card, quantity, merchantId);
+
+        return true;
+    }
+
+    @Override
+    public GiftCardDetailVO getGiftCardDetail(Long id) {
+
+        LambdaQueryWrapper<GiftCard> 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;
+    }
+
+    /**
+     * 异步创建订单
+     */
+    @Async
+    public void createOrderAsync(GiftCard card, Integer quantity, String merchantId) {
+        try {
+            this.giftCardOrderService.buildOrders(card, quantity, merchantId);
+            log.info("购物卡订单创建成功");
+        } catch (Exception e) {
+            log.error("异步创建订单失败,订单数据: cardId={}, quantity={}, merchantId={}", card.getId(), quantity, merchantId, e);
+            // 可以考虑重试机制或告警
+        }
+    }
+
 }