yuhongqi 4 dní pred
rodič
commit
fa933a7bbe
21 zmenil súbory, kde vykonal 1079 pridanie a 57 odobranie
  1. 186 0
      fs-admin/src/main/java/com/fs/user/controller/FsUserIntegralController.java
  2. 5 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserIntegralLogsMapper.java
  3. 5 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  4. 40 22
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  5. 45 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductPurchaseLimitScrm.java
  6. 4 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java
  7. 72 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductPurchaseLimitScrmMapper.java
  8. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductAddEditParam.java
  9. 91 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductPurchaseLimitScrmService.java
  10. 95 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreCartScrmServiceImpl.java
  11. 72 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  12. 163 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductPurchaseLimitScrmServiceImpl.java
  13. 6 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  14. 3 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductQueryVO.java
  15. 105 9
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  16. 9 0
      fs-service/src/main/resources/mapper/his/FsUserIntegralLogsMapper.xml
  17. 15 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  18. 81 0
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductPurchaseLimitScrmMapper.xml
  19. 19 15
      fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml
  20. 28 9
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveGoodsController.java
  21. 32 1
      fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java

+ 186 - 0
fs-admin/src/main/java/com/fs/user/controller/FsUserIntegralController.java

@@ -0,0 +1,186 @@
+package com.fs.user.controller;
+
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserIntegralLogs;
+import com.fs.his.mapper.FsUserIntegralLogsMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.service.IFsUserIntegralLogsService;
+import com.fs.his.service.IFsUserService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 添加积分参数
+ */
+class AddIntegralParam {
+    private Long userId;
+    private Integer logType;
+    private Long integral;
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public Integer getLogType() {
+        return logType;
+    }
+
+    public void setLogType(Integer logType) {
+        this.logType = logType;
+    }
+
+    public Long getIntegral() {
+        return integral;
+    }
+
+    public void setIntegral(Long integral) {
+        this.integral = integral;
+    }
+}
+
+/**
+ * 用户积分管理Controller 提供给卓美 完成直播积分发放
+ *
+ * @author fs
+ * @date 2025-01-01
+ */
+@Api("用户积分管理")
+@RestController
+@RequestMapping("/user/integral")
+public class FsUserIntegralController {
+
+    @Autowired
+    private IFsUserService userService;
+
+    @Autowired
+    private IFsUserIntegralLogsService integralLogsService;
+
+    @Autowired
+    private FsUserMapper userMapper;
+
+    @Autowired
+    private FsUserIntegralLogsMapper integralLogsMapper;
+
+    /**
+     * 查询用户列表(支持手机号和昵称筛选)
+     */
+    @ApiOperation("查询用户列表")
+    @GetMapping("/list")
+    public TableDataInfo list(@RequestParam(required = false) String phone,
+                              @RequestParam(required = false) String nickName,
+                              @RequestParam(defaultValue = "1") Integer pageNum,
+                              @RequestParam(defaultValue = "10") Integer pageSize) {
+        PageHelper.startPage(pageNum, pageSize);
+        
+        // 构建查询条件
+        FsUser queryUser = new FsUser();
+        if (StringUtils.isNotEmpty(phone)) {
+            queryUser.setPhone(phone);
+        }
+        if (StringUtils.isNotEmpty(nickName)) {
+            queryUser.setNickName(nickName);
+            queryUser.setNickname(nickName);
+        }
+        
+        // 查询用户列表(只返回需要的字段)
+        List<FsUser> userList = userMapper.selectFsUserListForIntegral(queryUser);
+        
+        PageInfo<FsUser> pageInfo = new PageInfo<>(userList);
+        TableDataInfo dataTable = new TableDataInfo();
+        dataTable.setCode(200);
+        dataTable.setMsg("查询成功");
+        dataTable.setRows(pageInfo.getList());
+        dataTable.setTotal(pageInfo.getTotal());
+        return dataTable;
+    }
+
+    /**
+     * 查询用户积分明细
+     */
+    @ApiOperation("查询用户积分明细")
+    @GetMapping("/logs/{userId}")
+    public R getIntegralLogs(@PathVariable Long userId,
+                             @RequestParam(defaultValue = "1") Integer pageNum,
+                             @RequestParam(defaultValue = "10") Integer pageSize,
+                             @RequestParam(required = false) Integer logType) {
+        PageHelper.startPage(pageNum, pageSize);
+        FsUserIntegralLogs queryLogs = new FsUserIntegralLogs();
+        queryLogs.setUserId(userId);
+        if (logType != null) {
+            queryLogs.setLogType(logType);
+        }
+        // 按创建时间倒序查询(Mapper XML 中已添加排序)
+        List<FsUserIntegralLogs> logsList = integralLogsService.selectFsUserIntegralLogsList(queryLogs);
+        PageInfo<FsUserIntegralLogs> pageInfo = new PageInfo<>(logsList);
+        return R.ok().put("data", pageInfo.getList()).put("total", pageInfo.getTotal());
+    }
+
+    /**
+     * 添加积分
+     */
+    @ApiOperation("添加积分")
+    @PostMapping("/add")
+    public R addIntegral(@RequestBody AddIntegralParam param) {
+        Long userId = param.getUserId();
+        Integer logType = param.getLogType();
+        Long integral = param.getIntegral();
+        // 查询用户信息
+        FsUser user = userService.selectFsUserByUserId(userId);
+        if (user == null) {
+            return R.error("用户不存在");
+        }
+
+        // 查询用户最新的积分记录
+        FsUserIntegralLogs latestLog = integralLogsMapper.selectLatestIntegralLogByUserId(userId);
+        
+        // 计算新的积分余额
+        Long currentIntegral = user.getIntegral() != null ? user.getIntegral() : 0L;
+        Long newIntegral = currentIntegral + integral;
+        
+        // 创建时间默认往后移动2个小时,使用整点的创建时间和更新时间
+        Date now = new Date();
+        LocalDateTime localDateTime = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());
+        // 往后移动2小时
+        localDateTime = localDateTime.plusHours(2);
+        // 设置为整点(分钟和秒都设为0)
+        localDateTime = localDateTime.withMinute(0).withSecond(0).withNano(0);
+        Date createTime = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
+        Date updateTime = createTime;
+
+        // 更新用户积分
+        user.setIntegral(newIntegral);
+
+        userService.increaseIntegral(Collections.singletonList(user.getUserId()),integral);
+
+        // 创建积分记录
+        FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+        integralLogs.setUserId(userId);
+        integralLogs.setLogType(logType);
+        integralLogs.setIntegral(integral);
+        integralLogs.setBalance(newIntegral);
+        integralLogs.setCreateTime(createTime);
+        integralLogs.setUpdateTime(updateTime);
+        integralLogsService.insertFsUserIntegralLogs(integralLogs);
+
+        return R.ok("添加积分成功");
+    }
+}

+ 5 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserIntegralLogsMapper.java

@@ -139,4 +139,9 @@ public interface FsUserIntegralLogsMapper
     Long selectH5VideoIntegralCount(@Param("userId") Long userId,@Param("videoId") Long videoId);
 
     List<FsUserIntegralLogs> selectFsUserIntegralLogsByUserIdAndLogType(@Param("userId") Long userId, @Param("logType") Integer logType, @Param("date") LocalDate date);
+
+    /**
+     * 查询用户最新的积分记录
+     */
+    FsUserIntegralLogs selectLatestIntegralLogByUserId(@Param("userId") Long userId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -471,4 +471,9 @@ public interface FsUserMapper
     List<FsUser> selectFsUserListByPhone(String phone);
 
     void updatePasswordByPhone(@Param("password")String password, @Param("encryptPhone")String encryptPhone);
+
+    /**
+     * 查询用户列表(用于积分管理,支持手机号和昵称模糊查询)
+     */
+    List<FsUser> selectFsUserListForIntegral(FsUser fsUser);
 }

+ 40 - 22
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -1647,34 +1647,52 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
     @Override
     @Transactional
     public String payConfirm(String payCode, String tradeNo, String bankTransactionId, String bankSerialNo) {
+        // 使用 Redis setNx 加分布式锁,基于支付单号
+        String lockKey = "payConfirm:lock:" + payCode;
+        boolean lockAcquired = false;
         try {
-            //更新订单状态
-            FsStorePayment storePayment=fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
-            if(!storePayment.getStatus().equals(0)){
-                return "";
+            // 尝试获取锁,锁过期时间设置为30秒
+            lockAcquired = redisCache.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
+            if (!lockAcquired) {
+                // 如果获取锁失败,说明有其他线程正在处理该支付单,直接返回
+                logger.info("支付确认处理中,支付单号: {}", payCode);
+                return "SUCCESS";
             }
-            storePayment.setStatus(1);
-            storePayment.setPayTime(new Date());
-            storePayment.setTradeNo(tradeNo);
-            storePayment.setBankSerialNo(bankSerialNo);
-            storePayment.setBankTransactionId(bankTransactionId);
-            fsStorePaymentMapper.updateFsStorePayment(storePayment);
-            //增加佣金
+
+            try {
+                //更新订单状态
+                FsStorePayment storePayment=fsStorePaymentMapper.selectFsStorePaymentByPaymentCode(payCode);
+                if(!storePayment.getStatus().equals(0)){
+                    return "";
+                }
+                storePayment.setStatus(1);
+                storePayment.setPayTime(new Date());
+                storePayment.setTradeNo(tradeNo);
+                storePayment.setBankSerialNo(bankSerialNo);
+                storePayment.setBankTransactionId(bankTransactionId);
+                fsStorePaymentMapper.updateFsStorePayment(storePayment);
+                //增加佣金
 //        if(storePayment.getCompanyId()!=null&&storePayment.getCompanyId()>0){
 //            companyService.addCompanyPaymentMoney(storePayment);
 //        }
-        } catch (Exception e) {
-            //更新订单状态
-            FsStorePaymentScrm storePayment=fsStorePaymentScrmMapper.selectFsStorePaymentByPaymentCode(payCode);
-            if(!storePayment.getStatus().equals(0)){
-                return "";
+            } catch (Exception e) {
+                //更新订单状态
+                FsStorePaymentScrm storePayment=fsStorePaymentScrmMapper.selectFsStorePaymentByPaymentCode(payCode);
+                if(!storePayment.getStatus().equals(0)){
+                    return "";
+                }
+                storePayment.setStatus(1);
+                storePayment.setPayTime(new Date());
+                storePayment.setTradeNo(tradeNo);
+                storePayment.setBankSerialNo(bankSerialNo);
+                storePayment.setBankTransactionId(bankTransactionId);
+                fsStorePaymentScrmMapper.updateFsStorePayment(storePayment);
+            }
+        } finally {
+            // 释放锁
+            if (lockAcquired) {
+                redisCache.deleteObject(lockKey);
             }
-            storePayment.setStatus(1);
-            storePayment.setPayTime(new Date());
-            storePayment.setTradeNo(tradeNo);
-            storePayment.setBankSerialNo(bankSerialNo);
-            storePayment.setBankTransactionId(bankTransactionId);
-            fsStorePaymentScrmMapper.updateFsStorePayment(storePayment);
         }
         return "SUCCESS";
     }

+ 45 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductPurchaseLimitScrm.java

@@ -0,0 +1,45 @@
+package com.fs.hisStore.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 商品限购对象 fs_store_product_purchase_limit
+ *
+ * @author fs
+ * @date 2024-01-01
+ */
+@Data
+public class FsStoreProductPurchaseLimitScrm implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /** 限购ID */
+    private Long id;
+
+    /** 商品ID */
+    @Excel(name = "商品ID")
+    private Long productId;
+
+    /** 用户ID */
+    @Excel(name = "用户ID")
+    private Long userId;
+
+    /** 已购买数量 */
+    @Excel(name = "已购买数量")
+    private Integer num;
+
+    /** 创建时间 */
+    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /** 更新时间 */
+    @Excel(name = "更新时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+}
+

+ 4 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java

@@ -343,4 +343,8 @@ public class FsStoreProductScrm extends BaseEntity
     @Excel(name = "所属小程序app_id")
     private String appIds;
 
+    /** 限购数量 */
+    @Excel(name = "限购数量")
+    private Integer purchaseLimit;
+
 }

+ 72 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductPurchaseLimitScrmMapper.java

@@ -0,0 +1,72 @@
+package com.fs.hisStore.mapper;
+
+import java.util.List;
+import com.fs.hisStore.domain.FsStoreProductPurchaseLimitScrm;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 商品限购Mapper接口
+ *
+ * @author fs
+ * @date 2024-01-01
+ */
+public interface FsStoreProductPurchaseLimitScrmMapper
+{
+    /**
+     * 查询商品限购
+     *
+     * @param id 商品限购ID
+     * @return 商品限购
+     */
+    public FsStoreProductPurchaseLimitScrm selectFsStoreProductPurchaseLimitById(Long id);
+
+    /**
+     * 查询商品限购列表
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 商品限购集合
+     */
+    public List<FsStoreProductPurchaseLimitScrm> selectFsStoreProductPurchaseLimitList(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit);
+
+    /**
+     * 根据商品ID和用户ID查询限购记录
+     *
+     * @param productId 商品ID
+     * @param userId 用户ID
+     * @return 商品限购
+     */
+    public FsStoreProductPurchaseLimitScrm selectByProductIdAndUserId(@Param("productId") Long productId, @Param("userId") Long userId);
+
+    /**
+     * 新增商品限购
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 结果
+     */
+    public int insertFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit);
+
+    /**
+     * 修改商品限购
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 结果
+     */
+    public int updateFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit);
+
+    /**
+     * 删除商品限购
+     *
+     * @param id 商品限购ID
+     * @return 结果
+     */
+    public int deleteFsStoreProductPurchaseLimitById(Long id);
+
+    /**
+     * 批量删除商品限购
+     *
+     * @param ids 需要删除的数据ID
+     * @return 结果
+     */
+    public int deleteFsStoreProductPurchaseLimitByIds(Long[] ids);
+}
+

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductAddEditParam.java

@@ -278,6 +278,9 @@ public class FsStoreProductAddEditParam implements Serializable
         // 指定企业
     private String companyIds;
 
+    /** 限购数量 */
+    private Integer purchaseLimit;
+
 
     /** 原产地 */
     @Excel(name = "原产地")

+ 91 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductPurchaseLimitScrmService.java

@@ -0,0 +1,91 @@
+package com.fs.hisStore.service;
+
+import java.util.List;
+import com.fs.hisStore.domain.FsStoreProductPurchaseLimitScrm;
+
+/**
+ * 商品限购Service接口
+ *
+ * @author fs
+ * @date 2024-01-01
+ */
+public interface IFsStoreProductPurchaseLimitScrmService
+{
+    /**
+     * 查询商品限购
+     *
+     * @param id 商品限购ID
+     * @return 商品限购
+     */
+    public FsStoreProductPurchaseLimitScrm selectFsStoreProductPurchaseLimitById(Long id);
+
+    /**
+     * 查询商品限购列表
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 商品限购集合
+     */
+    public List<FsStoreProductPurchaseLimitScrm> selectFsStoreProductPurchaseLimitList(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit);
+
+    /**
+     * 根据商品ID和用户ID查询限购记录
+     *
+     * @param productId 商品ID
+     * @param userId 用户ID
+     * @return 商品限购
+     */
+    public FsStoreProductPurchaseLimitScrm selectByProductIdAndUserId(Long productId, Long userId);
+
+    /**
+     * 新增商品限购
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 结果
+     */
+    public int insertFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit);
+
+    /**
+     * 修改商品限购
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 结果
+     */
+    public int updateFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit);
+
+    /**
+     * 批量删除商品限购
+     *
+     * @param ids 需要删除的商品限购ID
+     * @return 结果
+     */
+    public int deleteFsStoreProductPurchaseLimitByIds(Long[] ids);
+
+    /**
+     * 删除商品限购信息
+     *
+     * @param id 商品限购ID
+     * @return 结果
+     */
+    public int deleteFsStoreProductPurchaseLimitById(Long id);
+
+    /**
+     * 增加用户限购数量
+     *
+     * @param productId 商品ID
+     * @param userId 用户ID
+     * @param num 增加的数量
+     * @return 结果
+     */
+    public int increasePurchaseLimit(Long productId, Long userId, Integer num);
+
+    /**
+     * 减少用户限购数量
+     *
+     * @param productId 商品ID
+     * @param userId 用户ID
+     * @param num 减少的数量
+     * @return 结果
+     */
+    public int decreasePurchaseLimit(Long productId, Long userId, Integer num);
+}
+

+ 95 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreCartScrmServiceImpl.java

@@ -25,6 +25,10 @@ import com.fs.hisStore.param.FsStoreCartDelParam;
 import com.fs.hisStore.param.FsStoreCartNumParam;
 import com.fs.hisStore.param.FsStoreCartParam;
 import com.fs.hisStore.service.IFsStoreCartScrmService;
+import com.fs.hisStore.service.IFsStoreProductPurchaseLimitScrmService;
+import com.fs.hisStore.service.IFsStoreProductScrmService;
+import com.fs.hisStore.domain.FsStoreProductPurchaseLimitScrm;
+import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.vo.FsStoreCartVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -69,6 +73,12 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
     @Autowired
     private MedicalMallConfig medicalMallConfig;
 
+    @Autowired
+    private IFsStoreProductPurchaseLimitScrmService purchaseLimitService;
+
+    @Autowired
+    private IFsStoreProductScrmService productService;
+
 
 
     /**
@@ -147,6 +157,10 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
 
     @Override
     public R addCart(long uid, FsStoreCartParam cartParam) {
+        // 检查并调整限购数量
+        Integer adjustedNum = adjustPurchaseLimit(uid, cartParam.getProductId(), cartParam.getCartNum());
+        cartParam.setCartNum(adjustedNum);
+        
         //如果是直接购买,直接写入记录
         if(cartParam.getIsBuy()==1){
             FsStoreCartScrm storeCart = FsStoreCartScrm.builder()
@@ -192,7 +206,10 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
             }
             else{
                 storeCart=cart.get(0);
-                storeCart.setCartNum(cartParam.getCartNum() + cart.get(0).getCartNum());
+                int newCartNum = cartParam.getCartNum() + cart.get(0).getCartNum();
+                // 检查并调整限购数量(需要检查新的总数量)
+                Integer adjustedNewNum = adjustPurchaseLimit(uid, cartParam.getProductId(), newCartNum);
+                storeCart.setCartNum(adjustedNewNum);
                 storeCart.setUpdateTime(new Date());
                 checkProductStock(cartParam.getProductId(),storeCart.getProductAttrValueId());
                 fsStoreCartMapper.updateFsStoreCart(storeCart);
@@ -216,6 +233,8 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
     @Override
     public R changeNum(long userId, FsStoreCartNumParam cartParam) {
         FsStoreCartScrm cart=fsStoreCartMapper.selectFsStoreCartById(cartParam.getId());
+        // 检查限购
+        checkPurchaseLimit(userId, cart.getProductId(), cartParam.getNumber());
         checkProductStock(cart.getProductId(),cart.getProductAttrValueId());
         cart.setCartNum(cartParam.getNumber());
         cart.setUpdateTime(new Date());
@@ -223,6 +242,81 @@ public class FsStoreCartScrmServiceImpl implements IFsStoreCartScrmService
         return R.ok();
     }
 
+    /**
+     * 检查限购(用于修改数量时,超过限购则抛出异常)
+     * @param userId 用户ID
+     * @param productId 商品ID
+     * @param num 要购买的数量
+     */
+    private void checkPurchaseLimit(Long userId, Long productId, Integer num) {
+        // 查询商品信息
+        FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
+        if (product == null) {
+            return;
+        }
+        
+        // 如果商品没有设置限购,直接返回
+        if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
+            return;
+        }
+        
+        // 查询用户已购买的数量
+        FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(productId, userId);
+        int purchasedNum = 0;
+        if (purchaseLimit != null) {
+            purchasedNum = purchaseLimit.getNum();
+        }
+        
+        // 检查是否超过限购数量
+        if (purchasedNum + num > product.getPurchaseLimit()) {
+            int productTotalNum = purchasedNum + num;
+            int maxAllowed = product.getPurchaseLimit() - (productTotalNum);
+            if (maxAllowed <= 0) {
+                throw new CustomException("该商品已达到限购数量,无法继续购买");
+            }
+            throw new CustomException("该商品限购" + product.getPurchaseLimit() + "件,您已购买" + productTotalNum + "件,最多还能购买" + maxAllowed + "件");
+        }
+    }
+
+    /**
+     * 调整限购数量(用于添加购物车时,超过限购则调整为最大可购买数量)
+     * @param userId 用户ID
+     * @param productId 商品ID
+     * @param num 要购买的数量
+     * @return 调整后的数量
+     */
+    private Integer adjustPurchaseLimit(Long userId, Long productId, Integer num) {
+        // 查询商品信息
+        FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
+        if (product == null) {
+            return num;
+        }
+        
+        // 如果商品没有设置限购,直接返回原数量
+        if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
+            return num;
+        }
+        
+        // 查询用户已购买的数量
+        FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(productId, userId);
+        int purchasedNum = 0;
+        if (purchaseLimit != null) {
+            purchasedNum = purchaseLimit.getNum();
+        }
+        
+        // 检查是否超过限购数量
+        if (purchasedNum + num > product.getPurchaseLimit()) {
+            // 如果超过限购,设置数量为限购的最大数量
+            int maxAllowed = product.getPurchaseLimit() - purchasedNum;
+            if (maxAllowed <= 0) {
+                return 0; // 已达到限购,返回0
+            }
+            return maxAllowed; // 返回最大可购买数量
+        }
+        
+        return num; // 未超过限购,返回原数量
+    }
+
     @Override
     public void checkProductStock(Long productId, Long productAttrValueId) {
         IErpGoodsService goodsService = getErpService();

+ 72 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -347,6 +347,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     //fsStoreMapper
     private FsStoreScrmMapper fsStoreMapper;
 
+    @Autowired
+    private IFsStoreProductPurchaseLimitScrmService purchaseLimitService;
+
     @Autowired
     private IFsUserWatchService fsUserWatchService;
 
@@ -1059,6 +1062,9 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             List<FsStoreOrderItemScrm> listOrderItem = new ArrayList<>();
             //保存购物车商品信息
             for (FsStoreCartQueryVO vo : carts) {
+                // 检查限购
+                checkAndRecordPurchaseLimit(userId, vo.getProductId(), vo.getCartNum());
+                
                 FsStoreCartDTO fsStoreCartDTO = new FsStoreCartDTO();
                 fsStoreCartDTO.setProductId(vo.getProductId());
                 fsStoreCartDTO.setPrice(vo.getPrice());
@@ -1090,6 +1096,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 }
                 fsStoreOrderItemMapper.insertFsStoreOrderItem(item);
                 listOrderItem.add(item);
+                
+                // 记录限购数量(订单创建成功后记录)
+                FsStoreProductScrm product = productService.selectFsStoreProductById(vo.getProductId());
+                if (product != null && product.getPurchaseLimit() != null && product.getPurchaseLimit() > 0) {
+                    purchaseLimitService.increasePurchaseLimit(vo.getProductId(), userId, vo.getCartNum());
+                }
             }
             if (listOrderItem.size() > 0) {
                 String itemJson = JSONUtil.toJsonStr(listOrderItem);
@@ -1285,6 +1297,8 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             this.refundCoupon(order);
             //退回库存
             this.refundStock(order);
+            // 删除限购记录
+            this.deletePurchaseLimitRecords(order);
             fsStoreOrderMapper.cancelOrder(orderId);
             //添加记录
             orderStatusService.create(order.getId(), OrderLogEnum.CANCEL_ORDER.getValue(),
@@ -3145,6 +3159,64 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     /**
      * 退回库存
      */
+    /**
+     * 检查并记录限购
+     * @param userId 用户ID
+     * @param productId 商品ID
+     * @param num 购买数量
+     */
+    private void checkAndRecordPurchaseLimit(Long userId, Long productId, Integer num) {
+        // 查询商品信息
+        FsStoreProductScrm product = productService.selectFsStoreProductById(productId);
+        if (product == null) {
+            return;
+        }
+        
+        // 如果商品没有设置限购,直接返回
+        if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
+            return;
+        }
+        
+        // 查询用户已购买的数量
+        FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(productId, userId);
+        int purchasedNum = 0;
+        if (purchaseLimit != null) {
+            purchasedNum = purchaseLimit.getNum();
+        }
+        
+        // 检查是否超过限购数量
+        if (purchasedNum + num > product.getPurchaseLimit()) {
+            int productTotalNum = purchasedNum + num;
+            throw new CustomException("该商品限购" + product.getPurchaseLimit() + "件,您已购买" + productTotalNum + "件,无法继续购买");
+        }
+        
+        // 记录限购数量(在订单创建成功后记录,这里先检查)
+    }
+
+    /**
+     * 删除限购记录
+     * @param order 订单
+     */
+    private void deletePurchaseLimitRecords(FsStoreOrderScrm order) {
+        // 获取订单下的商品
+        List<FsStoreOrderItemVO> orderItemVOS = fsStoreOrderItemMapper.selectFsStoreOrderItemListByOrderId(order.getId());
+        for (FsStoreOrderItemVO vo : orderItemVOS) {
+            // 查询商品信息
+            FsStoreProductScrm product = productService.selectFsStoreProductById(vo.getProductId());
+            if (product == null) {
+                continue;
+            }
+            
+            // 如果商品没有设置限购,跳过
+            if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
+                continue;
+            }
+            
+            // 减少限购数量
+            purchaseLimitService.decreasePurchaseLimit(vo.getProductId(), order.getUserId(), Math.toIntExact(vo.getNum()));
+        }
+    }
+
     private void refundStock(FsStoreOrderScrm order) {
         //获取订单下的商品
         List<FsStoreOrderItemVO> orderItemVOS = fsStoreOrderItemMapper.selectFsStoreOrderItemListByOrderId(order.getId());

+ 163 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductPurchaseLimitScrmServiceImpl.java

@@ -0,0 +1,163 @@
+package com.fs.hisStore.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.fs.hisStore.domain.FsStoreProductPurchaseLimitScrm;
+import com.fs.hisStore.mapper.FsStoreProductPurchaseLimitScrmMapper;
+import com.fs.hisStore.service.IFsStoreProductPurchaseLimitScrmService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 商品限购Service业务层处理
+ *
+ * @author fs
+ * @date 2024-01-01
+ */
+@Service
+public class FsStoreProductPurchaseLimitScrmServiceImpl implements IFsStoreProductPurchaseLimitScrmService
+{
+    @Autowired
+    private FsStoreProductPurchaseLimitScrmMapper fsStoreProductPurchaseLimitMapper;
+
+    /**
+     * 查询商品限购
+     *
+     * @param id 商品限购ID
+     * @return 商品限购
+     */
+    @Override
+    public FsStoreProductPurchaseLimitScrm selectFsStoreProductPurchaseLimitById(Long id)
+    {
+        return fsStoreProductPurchaseLimitMapper.selectFsStoreProductPurchaseLimitById(id);
+    }
+
+    /**
+     * 查询商品限购列表
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 商品限购
+     */
+    @Override
+    public List<FsStoreProductPurchaseLimitScrm> selectFsStoreProductPurchaseLimitList(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
+    {
+        return fsStoreProductPurchaseLimitMapper.selectFsStoreProductPurchaseLimitList(fsStoreProductPurchaseLimit);
+    }
+
+    /**
+     * 根据商品ID和用户ID查询限购记录
+     *
+     * @param productId 商品ID
+     * @param userId 用户ID
+     * @return 商品限购
+     */
+    @Override
+    public FsStoreProductPurchaseLimitScrm selectByProductIdAndUserId(Long productId, Long userId)
+    {
+        return fsStoreProductPurchaseLimitMapper.selectByProductIdAndUserId(productId, userId);
+    }
+
+    /**
+     * 新增商品限购
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 结果
+     */
+    @Override
+    public int insertFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
+    {
+        fsStoreProductPurchaseLimit.setCreateTime(DateUtils.getNowDate());
+        return fsStoreProductPurchaseLimitMapper.insertFsStoreProductPurchaseLimit(fsStoreProductPurchaseLimit);
+    }
+
+    /**
+     * 修改商品限购
+     *
+     * @param fsStoreProductPurchaseLimit 商品限购
+     * @return 结果
+     */
+    @Override
+    public int updateFsStoreProductPurchaseLimit(FsStoreProductPurchaseLimitScrm fsStoreProductPurchaseLimit)
+    {
+        fsStoreProductPurchaseLimit.setUpdateTime(DateUtils.getNowDate());
+        return fsStoreProductPurchaseLimitMapper.updateFsStoreProductPurchaseLimit(fsStoreProductPurchaseLimit);
+    }
+
+    /**
+     * 批量删除商品限购
+     *
+     * @param ids 需要删除的商品限购ID
+     * @return 结果
+     */
+    @Override
+    public int deleteFsStoreProductPurchaseLimitByIds(Long[] ids)
+    {
+        return fsStoreProductPurchaseLimitMapper.deleteFsStoreProductPurchaseLimitByIds(ids);
+    }
+
+    /**
+     * 删除商品限购信息
+     *
+     * @param id 商品限购ID
+     * @return 结果
+     */
+    @Override
+    public int deleteFsStoreProductPurchaseLimitById(Long id)
+    {
+        return fsStoreProductPurchaseLimitMapper.deleteFsStoreProductPurchaseLimitById(id);
+    }
+
+    /**
+     * 增加用户限购数量
+     *
+     * @param productId 商品ID
+     * @param userId 用户ID
+     * @param num 增加的数量
+     * @return 结果
+     */
+    @Override
+    public int increasePurchaseLimit(Long productId, Long userId, Integer num)
+    {
+        FsStoreProductPurchaseLimitScrm limit = selectByProductIdAndUserId(productId, userId);
+        if (limit == null) {
+            // 创建新记录
+            limit = new FsStoreProductPurchaseLimitScrm();
+            limit.setProductId(productId);
+            limit.setUserId(userId);
+            limit.setNum(num);
+            return insertFsStoreProductPurchaseLimit(limit);
+        } else {
+            // 更新现有记录
+            limit.setNum(limit.getNum() + num);
+            return updateFsStoreProductPurchaseLimit(limit);
+        }
+    }
+
+    /**
+     * 减少用户限购数量
+     *
+     * @param productId 商品ID
+     * @param userId 用户ID
+     * @param num 减少的数量
+     * @return 结果
+     */
+    @Override
+    public int decreasePurchaseLimit(Long productId, Long userId, Integer num)
+    {
+        FsStoreProductPurchaseLimitScrm limit = selectByProductIdAndUserId(productId, userId);
+        if (limit != null) {
+            int newNum = limit.getNum() - num;
+            if (newNum <= 0) {
+                // 如果数量为0或负数,删除记录
+                return deleteFsStoreProductPurchaseLimitById(limit.getId());
+            } else {
+                // 更新数量
+                limit.setNum(newNum);
+                return updateFsStoreProductPurchaseLimit(limit);
+            }
+        }
+        return 0;
+    }
+}
+

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -661,6 +661,12 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         product.setVideo(param.getVideo());
         product.setStoreId(param.getStoreId());
         product.setIsDrug(param.getIsDrug().toString());
+        // 处理限购字段:如果不为null并且大于0,保存限购数字
+        if (param.getPurchaseLimit() != null && param.getPurchaseLimit() > 0) {
+            product.setPurchaseLimit(param.getPurchaseLimit());
+        } else {
+            product.setPurchaseLimit(0);
+        }
         //校验店铺资质信息
         if (!CompanyEnum.contains(cloudHostProper.getCompanyName())) {
             //获取店铺

+ 3 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductQueryVO.java

@@ -139,4 +139,7 @@ public class FsStoreProductQueryVO implements Serializable
 
     private String storeId;
 
+    /** 限购数量 */
+    private Integer purchaseLimit;
+
 }

+ 105 - 9
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -80,6 +80,8 @@ import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
 import com.fs.hisStore.mapper.FsUserScrmMapper;
 import com.fs.hisStore.param.*;
 import com.fs.hisStore.service.*;
+import com.fs.hisStore.service.IFsStoreProductPurchaseLimitScrmService;
+import com.fs.hisStore.domain.FsStoreProductPurchaseLimitScrm;
 import com.fs.hisStore.vo.*;
 import com.fs.huifuPay.domain.HuiFuCreateOrder;
 import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
@@ -203,6 +205,9 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     @Autowired
     private LiveUserLotteryRecordMapper liveUserLotteryRecordMapper;
 
+    @Autowired
+    private IFsStoreProductPurchaseLimitScrmService purchaseLimitService;
+
     @Autowired
     ICompanyUserService companyUserService;
 
@@ -697,8 +702,27 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     @Override
     @Transactional
     public String payConfirm(Integer type,Long orderId,String payCode,String tradeNo,String bankTransactionId,String bankSerialNo) {
+        // 使用 Redis setNx 加分布式锁,基于订单ID或支付单号
+        String lockKey;
+        if (type.equals(1) && StringUtils.isNotEmpty(payCode)) {
+            lockKey = "livePayConfirm:lock:" + payCode;
+        } else if (orderId != null) {
+            lockKey = "livePayConfirm:lock:" + orderId;
+        } else {
+            lockKey = "livePayConfirm:lock:" + System.currentTimeMillis();
+        }
+        
+        boolean lockAcquired = false;
         Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
         try {
+            // 尝试获取锁,锁过期时间设置为30秒
+            lockAcquired = redisCache.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
+            if (!lockAcquired) {
+                // 如果获取锁失败,说明有其他线程正在处理该订单,直接返回
+                log.info("支付确认处理中,订单ID: {}, 支付单号: {}", orderId, payCode);
+                return "SUCCESS";
+            }
+
             LiveOrder order=null;
             if(type.equals(1)){
                 LiveOrderPayment storePayment = liveOrderPaymentMapper.selectLiveOrderPaymentByPaymentCode(payCode);
@@ -777,6 +801,11 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             err.setMsg("支付错误:"+e.getMessage());
             err.setCreateTime(DateUtils.getNowDate());
             liveOrderPaymentErrorMapper.insertLiveOrderPaymentError(err);
+        } finally {
+            // 释放锁
+            if (lockAcquired) {
+                redisCache.deleteObject(lockKey);
+            }
         }
         return "SUCCESS";
     }
@@ -1161,15 +1190,15 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
 
 
             //模板消息支付成功发布事件
-            TemplateBean templateBean = TemplateBean.builder()
-                    .orderId(order.getOrderId().toString())
-                    .orderCode(order.getOrderCode().toString())
-                    .remark("您的订单已签收成功")
-                    .finishTime(order.getFinishTime())
-                    .userId(Long.valueOf(order.getUserId()))
-                    .templateType(TemplateListenEnum.TYPE_3.getValue())
-                    .build();
-            publisher.publishEvent(new TemplateEvent(this, templateBean));
+//            TemplateBean templateBean = TemplateBean.builder()
+//                    .orderId(order.getOrderId().toString())
+//                    .orderCode(order.getOrderCode().toString())
+//                    .remark("您的订单已签收成功")
+//                    .finishTime(order.getFinishTime())
+//                    .userId(Long.valueOf(order.getUserId()))
+//                    .templateType(TemplateListenEnum.TYPE_3.getValue())
+//                    .build();
+//            publisher.publishEvent(new TemplateEvent(this, templateBean));
             return R.ok("操作成功");
         } else {
             return R.error("非法操作");
@@ -3659,6 +3688,11 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if(goods.getStock() == null) return R.error("直播间商品库存不足");
         if(fsStoreProduct.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || goods.getStock() < Integer.parseInt(liveOrder.getTotalNum())) return R.error("抱歉,这款商品已被抢光,暂时无库存~");
 
+        // 检查限购
+        Long userId = Long.parseLong(liveOrder.getUserId());
+        Integer purchaseNum = Integer.parseInt(liveOrder.getTotalNum());
+        checkPurchaseLimitForLiveOrder(userId, liveOrder.getProductId(), purchaseNum);
+
         FsStoreProductAttrValueScrm attrValue = null;
         if (!Objects.isNull(liveOrder.getAttrValueId())) {
             attrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(liveOrder.getAttrValueId());
@@ -3773,6 +3807,12 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 liveOrderItem.setNum(Long.valueOf(liveOrder.getTotalNum()));
                 liveOrderItem.setJsonInfo(JSON.toJSONString(dto));
                 liveOrderItemMapper.insertLiveOrderItem(liveOrderItem);
+                
+                // 记录限购数量(订单创建成功后记录)
+                if (fsStoreProduct.getPurchaseLimit() != null && fsStoreProduct.getPurchaseLimit() > 0) {
+                    purchaseLimitService.increasePurchaseLimit(liveOrder.getProductId(), userId, purchaseNum);
+                }
+                
                 redisCache.deleteObject("orderKey:" + liveOrder.getOrderKey());
                 //添加支付到期时间
                 Calendar calendar = Calendar.getInstance();
@@ -3943,6 +3983,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             goods.setStock(goods.getStock()+Long.parseLong(liveOrder.getTotalNum()));
             // 更新商品库存
             liveGoodsMapper.updateLiveGoods(goods);
+            // 删除限购记录
+            deletePurchaseLimitRecordsForLiveOrder(liveOrder);
             // 退券
             this.refundCoupon(order);
             TemplateBean templateBean = TemplateBean.builder()
@@ -3960,6 +4002,60 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
     }
 
+    /**
+     * 检查限购(用于直播订单)
+     * @param userId 用户ID
+     * @param productId 商品ID
+     * @param num 购买数量
+     */
+    private void checkPurchaseLimitForLiveOrder(Long userId, Long productId, Integer num) {
+        // 查询商品信息
+        FsStoreProductScrm product = fsStoreProductService.selectFsStoreProductById(productId);
+        if (product == null) {
+            return;
+        }
+        
+        // 如果商品没有设置限购,直接返回
+        if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
+            return;
+        }
+        
+        // 查询用户已购买的数量
+        FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(productId, userId);
+        int purchasedNum = 0;
+        if (purchaseLimit != null) {
+            purchasedNum = purchaseLimit.getNum();
+        }
+        
+        // 检查是否超过限购数量
+        if (purchasedNum + num > product.getPurchaseLimit()) {
+            int productTotalNum = purchasedNum + num;
+            throw new CustomException("该商品限购" + product.getPurchaseLimit() + "件,您已购买" + productTotalNum + "件,无法继续购买");
+        }
+    }
+
+    /**
+     * 删除限购记录(用于直播订单)
+     * @param liveOrder 订单
+     */
+    private void deletePurchaseLimitRecordsForLiveOrder(LiveOrder liveOrder) {
+        // 查询商品信息
+        FsStoreProductScrm product = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
+        if (product == null) {
+            return;
+        }
+        
+        // 如果商品没有设置限购,跳过
+        if (product.getPurchaseLimit() == null || product.getPurchaseLimit() <= 0) {
+            return;
+        }
+        
+        // 减少限购数量
+        Long userId = Long.parseLong(liveOrder.getUserId());
+        Integer num = Integer.parseInt(liveOrder.getTotalNum());
+        purchaseLimitService.decreasePurchaseLimit(liveOrder.getProductId(), userId, num);
+    }
+
     private void refundCoupon(LiveOrder order) {
         if(order.getCouponUserId()!=null){
             LiveCouponUser couponUser=liveCouponUserService.selectLiveCouponUserById(order.getUserCouponId());

+ 9 - 0
fs-service/src/main/resources/mapper/his/FsUserIntegralLogsMapper.xml

@@ -31,6 +31,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null "> and create_time = #{createTime}</if>
             <if test="businessType != null "> and business_type = #{businessType}</if>
         </where>
+        order by create_time desc
     </select>
 
     <select id="selectFsUserIntegralLogsById" parameterType="Long" resultMap="FsUserIntegralLogsResult">
@@ -98,4 +99,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{id}
         </foreach>
     </delete>
+
+    <!-- 查询用户最新的积分记录 -->
+    <select id="selectLatestIntegralLogByUserId" parameterType="Long" resultMap="FsUserIntegralLogsResult">
+        <include refid="selectFsUserIntegralLogsVo"/>
+        where user_id = #{userId}
+        order by create_time desc, id desc
+        limit 1
+    </select>
 </mapper>

+ 15 - 0
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -2443,4 +2443,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </update>
 
+    <!-- 查询用户列表(用于积分管理) -->
+    <select id="selectFsUserListForIntegral" parameterType="FsUser" resultMap="FsUserResult">
+        select user_id, nick_name, phone, integral from fs_user
+        <where>
+            and is_del = 0
+            <if test="phone != null and phone != ''">
+                and phone like concat('%', #{phone}, '%')
+            </if>
+            <if test="nickName != null and nickName != ''">
+                and (nick_name like concat('%', #{nickName}, '%') or nickname like concat('%', #{nickName}, '%'))
+            </if>
+        </where>
+        order by user_id desc
+    </select>
+
 </mapper>

+ 81 - 0
fs-service/src/main/resources/mapper/hisStore/FsStoreProductPurchaseLimitScrmMapper.xml

@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.hisStore.mapper.FsStoreProductPurchaseLimitScrmMapper">
+
+    <resultMap type="FsStoreProductPurchaseLimitScrm" id="FsStoreProductPurchaseLimitResult">
+        <result property="id"    column="id"    />
+        <result property="productId"    column="product_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="num"    column="num"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectFsStoreProductPurchaseLimitVo">
+        select id, product_id, user_id, num, create_time, update_time from fs_store_product_purchase_limit_scrm
+    </sql>
+
+    <select id="selectFsStoreProductPurchaseLimitList" parameterType="FsStoreProductPurchaseLimitScrm" resultMap="FsStoreProductPurchaseLimitResult">
+        <include refid="selectFsStoreProductPurchaseLimitVo"/>
+        <where>
+            <if test="productId != null "> and product_id = #{productId}</if>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="num != null "> and num = #{num}</if>
+        </where>
+    </select>
+
+    <select id="selectFsStoreProductPurchaseLimitById" parameterType="Long" resultMap="FsStoreProductPurchaseLimitResult">
+        <include refid="selectFsStoreProductPurchaseLimitVo"/>
+        where id = #{id}
+    </select>
+
+    <select id="selectByProductIdAndUserId" resultMap="FsStoreProductPurchaseLimitResult">
+        <include refid="selectFsStoreProductPurchaseLimitVo"/>
+        where product_id = #{productId} and user_id = #{userId}
+    </select>
+
+    <insert id="insertFsStoreProductPurchaseLimit" parameterType="FsStoreProductPurchaseLimitScrm" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_store_product_purchase_limit_scrm
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="productId != null">product_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="num != null">num,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="productId != null">#{productId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="num != null">#{num},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsStoreProductPurchaseLimit" parameterType="FsStoreProductPurchaseLimitScrm">
+        update fs_store_product_purchase_limit_scrm
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="productId != null">product_id = #{productId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="num != null">num = #{num},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsStoreProductPurchaseLimitById" parameterType="Long">
+        delete from fs_store_product_purchase_limit_scrm where id = #{id}
+    </delete>
+
+    <delete id="deleteFsStoreProductPurchaseLimitByIds" parameterType="String">
+        delete from fs_store_product_purchase_limit_scrm where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+</mapper>
+

+ 19 - 15
fs-service/src/main/resources/mapper/hisStore/FsStoreProductScrmMapper.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.hisStore.mapper.FsStoreProductScrmMapper">
 
     <resultMap type="FsStoreProductScrm" id="FsStoreProductResult">
@@ -76,6 +76,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="shelfLife"    column="shelf_life"    />
         <result property="domesticImported"    column="domestic_imported"    />
         <result property="appIds"    column="app_ids"    />
+        <result property="purchaseLimit"    column="purchase_limit"    />
     </resultMap>
 
     <sql id="selectFsStoreProductVo">
@@ -87,8 +88,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                is_display,tui_cate_id,company_ids,is_drug,drug_image,drug_reg_cert_no,common_name,dosage_form,
                unit_price,batch_number,mah,mah_address,manufacturer,manufacturer_address,indications,dosage,
                adverse_reactions,contraindications,precautions,is_audit,store_id,return_address,brand,food_production_license_code,
-               origin_place,net_content,shelf_life,domestic_imported,app_ids
-               from fs_store_product_scrm
+               origin_place,net_content,shelf_life,domestic_imported,app_ids,purchase_limit
+        from fs_store_product_scrm
     </sql>
 
     <sql id="selectFsStoreProductPVo">
@@ -100,7 +101,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                p.is_display,p.tui_cate_id,p.company_ids,p.is_drug,p.drug_image,p.drug_reg_cert_no,p.common_name,p.dosage_form,
                p.unit_price,p.batch_number,p.mah,p.mah_address,p.manufacturer,p.manufacturer_address,p.indications,p.dosage,
                p.adverse_reactions,p.contraindications,p.precautions,p.is_audit,p.store_id,p.return_address,p.brand,p.food_production_license_code,
-               p.origin_place,p.net_content,p.shelf_life,p.domestic_imported,app_ids
+               p.origin_place,p.net_content,p.shelf_life,p.domestic_imported,app_ids,p.purchase_limit
         from fs_store_product_scrm p
     </sql>
 
@@ -278,7 +279,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="shelfLife != null">shelf_life,</if>
             <if test="domesticImported != null and domesticImported != ''">domestic_imported,</if>
             <if test="appIds != null and appIds != ''">app_ids, </if>
-         </trim>
+            <if test="purchaseLimit != null">purchase_limit,</if>
+        </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="image != null and image != ''">#{image},</if>
             <if test="video != null and video != ''">#{video},</if>
@@ -350,7 +352,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="shelfLife != null">#{shelfLife},</if>
             <if test="domesticImported != null and domesticImported != ''">#{domesticImported},</if>
             <if test="appIds != null and appIds != ''">#{appIds}, </if>
-         </trim>
+            <if test="purchaseLimit != null">#{purchaseLimit},</if>
+        </trim>
     </insert>
 
     <update id="updateFsStoreProduct" parameterType="FsStoreProductScrm">
@@ -426,6 +429,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="shelfLife != null">shelf_life = #{shelfLife},</if>
             <if test="domesticImported != null">domestic_imported = #{domesticImported},</if>
             <if test="appIds != null and appIds != ''">app_ids = #{appIds}, </if>
+            <if test="purchaseLimit != null">purchase_limit = #{purchaseLimit},</if>
         </trim>
         where product_id = #{productId}
     </update>
@@ -480,14 +484,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             inner join fs_store_scrm fs on fs.store_id = fsp.store_id
         </if>
         <if test='config.isAudit == "1" '>
-        and fs.is_audit = '1'
+            and fs.is_audit = '1'
         </if>
         where fsp.is_del=0 and fsp.is_show=1
         <if test='config.isAudit == "1" '>
-        and fsp.is_audit = '1'
+            and fsp.is_audit = '1'
         </if>
         <if test = 'param.appId != null and param.appId != ""'>
-        and ((FIND_IN_SET(#{param.appId}, fsp.app_ids) > 0))
+            and ((FIND_IN_SET(#{param.appId}, fsp.app_ids) > 0))
         </if>
         and fsp.is_best=1 and fsp.is_display=1 order by fsp.sort desc,fsp.product_id desc
     </select>
@@ -521,11 +525,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectFsStoreProductHotQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
         <if test='config.isAudit == "1" '>
-        inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
+            inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
         <if test='config.isAudit == "1" '>
-        and p.is_audit = '1'
+            and p.is_audit = '1'
         </if>
         <if test='appId != null and appId = "" '>
             and ((FIND_IN_SET(#{appId}, p.app_ids) > 0))
@@ -535,14 +539,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectFsStoreProductGoodListQuery" resultType="com.fs.hisStore.vo.FsStoreProductListQueryVO">
         select p.* from fs_store_product_scrm p
         <if test='config.isAudit == "1" '>
-        inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
+            inner join fs_store_scrm fs on fs.store_id = p.store_id and fs.is_audit = 1
         </if>
         where p.is_del=0 and p.is_show=1
         <if test='config.isAudit == "1" '>
-        and p.is_audit = '1'
+            and p.is_audit = '1'
         </if>
         <if test = 'param.appId != null and param.appId != ""'>
-        and ((FIND_IN_SET(#{param.appId}, p.app_ids) > 0))
+            and ((FIND_IN_SET(#{param.appId}, p.app_ids) > 0))
         </if>
         and  p.is_good=1 and p.is_display=1 order by p.sort desc
     </select>

+ 28 - 9
fs-user-app/src/main/java/com/fs/app/controller/live/LiveGoodsController.java

@@ -10,14 +10,8 @@ import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.his.service.IFsStoreProductService;
-import com.fs.hisStore.domain.FsStoreProductAttrScrm;
-import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
-import com.fs.hisStore.domain.FsStoreProductRelationScrm;
-import com.fs.hisStore.domain.FsStoreProductScrm;
-import com.fs.hisStore.service.IFsStoreProductAttrScrmService;
-import com.fs.hisStore.service.IFsStoreProductAttrValueScrmService;
-import com.fs.hisStore.service.IFsStoreProductRelationScrmService;
-import com.fs.hisStore.service.IFsStoreProductScrmService;
+import com.fs.hisStore.domain.*;
+import com.fs.hisStore.service.*;
 import com.fs.live.domain.LiveGoods;
 import com.fs.live.service.ILiveGoodsService;
 import com.github.pagehelper.PageHelper;
@@ -56,6 +50,8 @@ public class LiveGoodsController extends AppBaseController
 
     @Autowired
     private IFsStoreProductAttrValueScrmService attrValueService;
+    @Autowired
+    private IFsStoreProductPurchaseLimitScrmService purchaseLimitService;
 
     /**
      * 查询直播商品列表
@@ -167,6 +163,7 @@ public class LiveGoodsController extends AppBaseController
             cacheData.put("productValues", productValues);
             redisCache.setCacheObject(cacheKey, cacheData, LiveKeysConstant.PRODUCT_DETAIL_CACHE_EXPIRE, TimeUnit.SECONDS);
         }
+
         
         // 获取用户的TOKEN写入足迹
         String userId=getUserId();
@@ -194,7 +191,29 @@ public class LiveGoodsController extends AppBaseController
                 productRelationService.insertFsStoreProductRelation(relation);
             }
         }
-        return R.ok().put("product",product).put("productAttr",productAttr).put("productValues",productValues);
+
+        // 查询限购信息
+        Integer remainingPurchaseLimit = null; // 剩余可购买数量
+        Integer purchasedNum = 0; // 已购买数量
+        if (product.getPurchaseLimit() != null && product.getPurchaseLimit() > 0) {
+            // 商品有限购,查询用户是否购买过
+            if (userId != null) {
+                FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(
+                        product.getProductId(), Long.parseLong(userId));
+                if (purchaseLimit != null) {
+                    purchasedNum = purchaseLimit.getNum();
+                }
+                // 计算剩余可购买数量
+                remainingPurchaseLimit = product.getPurchaseLimit() - purchasedNum;
+                if (remainingPurchaseLimit < 0) {
+                    remainingPurchaseLimit = 0;
+                }
+            } else {
+                // 未登录用户,剩余可购买数量等于限购数量
+                remainingPurchaseLimit = product.getPurchaseLimit();
+            }
+        }
+        return R.ok().put("remainingPurchaseLimit", remainingPurchaseLimit).put("product",product).put("productAttr",productAttr).put("productValues",productValues);
     }
 
     /**

+ 32 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/ProductScrmController.java

@@ -54,6 +54,9 @@ public class ProductScrmController extends AppBaseController {
 
     @Autowired
     private IFsStoreScrmService storeScrmService;
+
+    @Autowired
+    private IFsStoreProductPurchaseLimitScrmService purchaseLimitService;
     /**
      * 获取用户信息
      * @param storeId
@@ -205,7 +208,35 @@ public class ProductScrmController extends AppBaseController {
                 productRelationService.insertFsStoreProductRelation(relation);
             }
         }
-        return R.ok().put("product",product).put("productAttr",productAttr).put("productValues",productValues).put("store",fsStoreScrm);
+
+        // 查询限购信息
+        Integer remainingPurchaseLimit = null; // 剩余可购买数量
+        Integer purchasedNum = 0; // 已购买数量
+        if (product.getPurchaseLimit() != null && product.getPurchaseLimit() > 0) {
+            // 商品有限购,查询用户是否购买过
+            if (userId != null) {
+                FsStoreProductPurchaseLimitScrm purchaseLimit = purchaseLimitService.selectByProductIdAndUserId(
+                        product.getProductId(), Long.parseLong(userId));
+                if (purchaseLimit != null) {
+                    purchasedNum = purchaseLimit.getNum();
+                }
+                // 计算剩余可购买数量
+                remainingPurchaseLimit = product.getPurchaseLimit() - purchasedNum;
+                if (remainingPurchaseLimit < 0) {
+                    remainingPurchaseLimit = 0;
+                }
+            } else {
+                // 未登录用户,剩余可购买数量等于限购数量
+                remainingPurchaseLimit = product.getPurchaseLimit();
+            }
+        }
+
+        return R.ok().put("product",product)
+                .put("productAttr",productAttr)
+                .put("productValues",productValues)
+                .put("store",fsStoreScrm)
+                .put("remainingPurchaseLimit", remainingPurchaseLimit) // 剩余可购买数量
+                .put("purchasedNum", purchasedNum); // 已购买数量
     }
 
     @Login