Procházet zdrojové kódy

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

caoliqin před 4 dny
rodič
revize
4852d74c98
100 změnil soubory, kde provedl 5084 přidání a 97 odebrání
  1. 70 0
      fs-admin/src/main/java/com/fs/decoration/controller/DecorationComponentController.java
  2. 28 0
      fs-admin/src/main/java/com/fs/decoration/controller/DecorationComponentTypeController.java
  3. 53 0
      fs-admin/src/main/java/com/fs/decoration/controller/DecorationTemplateController.java
  4. 124 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductActivityController.java
  5. 153 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductDiscountController.java
  6. 152 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductFlashSaleController.java
  7. 60 0
      fs-admin/src/main/java/com/fs/hisStore/task/ActivityExpireTask.java
  8. 33 0
      fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java
  9. 1 2
      fs-admin/src/main/java/com/fs/xiaoshouyi/controller/XiaoShouYiController.java
  10. 18 1
      fs-common/src/main/java/com/fs/common/config/FSSysConfig.java
  11. 14 0
      fs-common/src/main/java/com/fs/common/core/redis/service/ActivityStockData.java
  12. 22 0
      fs-common/src/main/java/com/fs/common/core/redis/service/ActivityStockDataProvider.java
  13. 711 0
      fs-common/src/main/java/com/fs/common/core/redis/service/ActivityStockService.java
  14. 26 0
      fs-common/src/main/java/com/fs/common/core/redis/service/ActivityValidateResult.java
  15. 14 0
      fs-common/src/main/java/com/fs/common/utils/PhoneUtils.java
  16. 1 2
      fs-company/src/main/java/com/fs/xiaoshouyi/controller/XiaoShouYiController.java
  17. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecMapper.java
  18. 6 0
      fs-service/src/main/java/com/fs/company/service/CompanyWorkflowEngine.java
  19. 8 7
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java
  20. 64 5
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowEngineImpl.java
  21. 15 15
      fs-service/src/main/java/com/fs/company/service/impl/CompanyWxServiceImpl.java
  22. 15 2
      fs-service/src/main/java/com/fs/company/service/impl/call/node/AbstractWorkflowNode.java
  23. 7 1
      fs-service/src/main/java/com/fs/company/service/impl/call/node/AiAddWxTaskNewNode.java
  24. 2 0
      fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java
  25. 2 2
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  26. 28 5
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  27. 41 0
      fs-service/src/main/java/com/fs/decoration/domain/DecorationComponent.java
  28. 32 0
      fs-service/src/main/java/com/fs/decoration/domain/DecorationComponentType.java
  29. 38 0
      fs-service/src/main/java/com/fs/decoration/domain/DecorationTemplate.java
  30. 19 0
      fs-service/src/main/java/com/fs/decoration/dto/DecorationComponentQuery.java
  31. 34 0
      fs-service/src/main/java/com/fs/decoration/dto/DecorationComponentSaveReq.java
  32. 16 0
      fs-service/src/main/java/com/fs/decoration/dto/DecorationComponentStatusReq.java
  33. 31 0
      fs-service/src/main/java/com/fs/decoration/dto/DecorationTemplateSaveReq.java
  34. 43 0
      fs-service/src/main/java/com/fs/decoration/mapper/DecorationComponentMapper.java
  35. 15 0
      fs-service/src/main/java/com/fs/decoration/mapper/DecorationComponentTypeMapper.java
  36. 37 0
      fs-service/src/main/java/com/fs/decoration/mapper/DecorationTemplateMapper.java
  37. 38 0
      fs-service/src/main/java/com/fs/decoration/service/IDecorationComponentService.java
  38. 16 0
      fs-service/src/main/java/com/fs/decoration/service/IDecorationComponentTypeService.java
  39. 32 0
      fs-service/src/main/java/com/fs/decoration/service/IDecorationTemplateService.java
  40. 117 0
      fs-service/src/main/java/com/fs/decoration/service/impl/DecorationComponentServiceImpl.java
  41. 29 0
      fs-service/src/main/java/com/fs/decoration/service/impl/DecorationComponentTypeServiceImpl.java
  42. 95 0
      fs-service/src/main/java/com/fs/decoration/service/impl/DecorationTemplateServiceImpl.java
  43. 18 0
      fs-service/src/main/java/com/fs/decoration/vo/OptionVO.java
  44. 37 0
      fs-service/src/main/java/com/fs/decoration/vo/TemplateContentVO.java
  45. 4 0
      fs-service/src/main/java/com/fs/his/mapper/FsAdvMapper.java
  46. 5 0
      fs-service/src/main/java/com/fs/his/mapper/FsArticleCateMapper.java
  47. 22 0
      fs-service/src/main/java/com/fs/his/mapper/FsArticleMapper.java
  48. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsCityMapper.java
  49. 5 0
      fs-service/src/main/java/com/fs/his/mapper/FsPrescribeMapper.java
  50. 7 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreProductMapper.java
  51. 6 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserAddressMapper.java
  52. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  53. 4 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserWxMapper.java
  54. 6 0
      fs-service/src/main/java/com/fs/his/param/FsPrescribeParam.java
  55. 3 0
      fs-service/src/main/java/com/fs/his/service/IFsAdvService.java
  56. 2 0
      fs-service/src/main/java/com/fs/his/service/IFsArticleCateService.java
  57. 5 0
      fs-service/src/main/java/com/fs/his/service/IFsArticleService.java
  58. 1 0
      fs-service/src/main/java/com/fs/his/service/IFsCityService.java
  59. 3 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreProductService.java
  60. 3 0
      fs-service/src/main/java/com/fs/his/service/IFsUserAddressService.java
  61. 8 0
      fs-service/src/main/java/com/fs/his/service/impl/FsAdvServiceImpl.java
  62. 7 0
      fs-service/src/main/java/com/fs/his/service/impl/FsArticleCateServiceImpl.java
  63. 17 0
      fs-service/src/main/java/com/fs/his/service/impl/FsArticleServiceImpl.java
  64. 11 0
      fs-service/src/main/java/com/fs/his/service/impl/FsCityServiceImpl.java
  65. 23 4
      fs-service/src/main/java/com/fs/his/service/impl/FsPrescribeServiceImpl.java
  66. 7 1
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreProductServiceImpl.java
  67. 13 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserAddressServiceImpl.java
  68. 18 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java
  69. 154 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductActivity.java
  70. 128 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductDiscount.java
  71. 123 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductFlashSale.java
  72. 12 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductScrm.java
  73. 8 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java
  74. 131 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductActivityMapper.java
  75. 109 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductDiscountMapper.java
  76. 109 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductFlashSaleMapper.java
  77. 10 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java
  78. 15 8
      fs-service/src/main/java/com/fs/hisStore/mapper/FsWechatTemplateScrmMapper.java
  79. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreConfirmOrderParam.java
  80. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreOrderCreateParam.java
  81. 3 0
      fs-service/src/main/java/com/fs/hisStore/param/FsStoreProductQueryParam.java
  82. 8 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java
  83. 99 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductActivityService.java
  84. 94 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductDiscountService.java
  85. 94 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductFlashSaleService.java
  86. 10 0
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductScrmService.java
  87. 119 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/ActivityStockDataProviderImpl.java
  88. 55 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  89. 553 1
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  90. 244 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductActivityServiceImpl.java
  91. 144 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductDiscountServiceImpl.java
  92. 144 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductFlashSaleServiceImpl.java
  93. 84 26
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java
  94. 7 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductListQueryVO.java
  95. 10 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductQueryVO.java
  96. 20 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsUnsyncOrderVO.java
  97. 2 14
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  98. 21 0
      fs-service/src/main/java/com/fs/store/bean/Drug.java
  99. 23 0
      fs-service/src/main/java/com/fs/store/bean/DrugV2.java
  100. 44 0
      fs-service/src/main/java/com/fs/store/bean/Prescribe.java

+ 70 - 0
fs-admin/src/main/java/com/fs/decoration/controller/DecorationComponentController.java

@@ -0,0 +1,70 @@
+package com.fs.decoration.controller;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.decoration.dto.DecorationComponentQuery;
+import com.fs.decoration.dto.DecorationComponentSaveReq;
+import com.fs.decoration.dto.DecorationComponentStatusReq;
+import com.fs.decoration.service.IDecorationComponentService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 组件定义控制器
+ */
+@RestController
+@RequestMapping("/decoration/component")
+public class DecorationComponentController {
+
+    @Resource
+    private IDecorationComponentService decorationComponentService;
+
+    /**
+     * 查询组件列表
+     */
+    @GetMapping("/list")
+    public AjaxResult list(DecorationComponentQuery query) {
+        return AjaxResult.success(decorationComponentService.selectList(query));
+    }
+
+    /**
+     * 查询组件详情
+     */
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        return AjaxResult.success(decorationComponentService.selectById(id));
+    }
+
+    /**
+     * 新增/修改组件
+     */
+    @PostMapping("/save")
+    public AjaxResult save(@RequestBody DecorationComponentSaveReq req) {
+        Long id = decorationComponentService.saveComponent(req);
+        return AjaxResult.success("保存成功", id);
+    }
+
+    /**
+     * 删除组件
+     */
+    @DeleteMapping("/{id}")
+    public AjaxResult remove(@PathVariable Long id) {
+        decorationComponentService.deleteById(id);
+        return AjaxResult.success("删除成功");
+    }
+
+    /**
+     * 修改组件状态
+     */
+    @PostMapping("/changeStatus")
+    public AjaxResult changeStatus(@RequestBody DecorationComponentStatusReq req) {
+        if (req.getId() == null) {
+            return AjaxResult.error("组件ID不能为空");
+        }
+        if (req.getStatus() == null) {
+            return AjaxResult.error("状态不能为空");
+        }
+        decorationComponentService.updateStatus(req.getId(), req.getStatus());
+        return AjaxResult.success("状态修改成功");
+    }
+}

+ 28 - 0
fs-admin/src/main/java/com/fs/decoration/controller/DecorationComponentTypeController.java

@@ -0,0 +1,28 @@
+package com.fs.decoration.controller;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.decoration.service.IDecorationComponentTypeService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * 组件类型控制器
+ */
+@RestController
+@RequestMapping("/decoration/componentType")
+public class DecorationComponentTypeController {
+
+    @Resource
+    private IDecorationComponentTypeService decorationComponentTypeService;
+
+    /**
+     * 查询组件类型下拉
+     */
+    @GetMapping("/options")
+    public AjaxResult options() {
+        return AjaxResult.success(decorationComponentTypeService.listOptions());
+    }
+}

+ 53 - 0
fs-admin/src/main/java/com/fs/decoration/controller/DecorationTemplateController.java

@@ -0,0 +1,53 @@
+package com.fs.decoration.controller;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.decoration.dto.DecorationTemplateSaveReq;
+import com.fs.decoration.service.IDecorationTemplateService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 模板控制器
+ */
+@RestController
+@RequestMapping("/decoration/template")
+public class DecorationTemplateController {
+
+    @Resource
+    private IDecorationTemplateService decorationTemplateService;
+
+    /**
+     * 查询模板列表
+     */
+    @GetMapping("/list")
+    public AjaxResult list() {
+        return AjaxResult.success(decorationTemplateService.selectList());
+    }
+
+    /**
+     * 查询模板详情
+     */
+    @GetMapping("/{id}")
+    public AjaxResult getInfo(@PathVariable Long id) {
+        return AjaxResult.success(decorationTemplateService.selectById(id));
+    }
+
+    /**
+     * 新增/修改模板
+     */
+    @PostMapping("/save")
+    public AjaxResult save(@RequestBody DecorationTemplateSaveReq req) {
+        Long id = decorationTemplateService.saveTemplate(req);
+        return AjaxResult.success("保存成功", id);
+    }
+
+    /**
+     * 删除模板
+     */
+    @DeleteMapping("/{id}")
+    public AjaxResult remove(@PathVariable Long id) {
+        decorationTemplateService.deleteById(id);
+        return AjaxResult.success("删除成功");
+    }
+}

+ 124 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductActivityController.java

@@ -0,0 +1,124 @@
+package com.fs.hisStore.controller;
+
+import java.util.Date;
+import java.util.List;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.hisStore.domain.FsStoreProductActivity;
+import com.fs.hisStore.domain.FsStoreProductScrm;
+import com.fs.hisStore.service.IFsStoreProductActivityService;
+import com.fs.hisStore.service.IFsStoreProductScrmService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 商品活动中间表Controller(仅提供商品管理页面弹窗接口)
+ *
+ * @author fs
+ * @date 2026-04-16
+ */
+@Slf4j
+@Api("商品活动设置(秒杀/折扣)")
+@RestController
+@RequestMapping("/store/store/productActivity")
+public class FsStoreProductActivityController extends BaseController {
+
+    @Autowired
+    private IFsStoreProductActivityService activityService;
+
+    @Autowired
+    private IFsStoreProductScrmService storeProductService;
+
+    /**
+     * 根据商品ID查询活动设置(含活动状态、是否进行中)
+     */
+    @ApiOperation("根据商品ID查询活动设置")
+    @GetMapping("/getByProductId/{productId}")
+    public AjaxResult getByProductId(@PathVariable("productId") Long productId) {
+        List<FsStoreProductActivity> list = activityService.selectByProductId(productId);
+
+        // 判断是否有正在进行中的活动
+        FsStoreProductActivity ongoing = activityService.selectOngoingActivity(productId);
+        boolean isOngoing = ongoing != null;
+
+        long now = System.currentTimeMillis();
+        for (FsStoreProductActivity item : list) {
+            // 计算活动状态
+            if (item.getStartTime() != null && item.getEndTime() != null) {
+                long startMs = item.getStartTime().getTime();
+                long endMs = item.getEndTime().getTime();
+                if (now < startMs) {
+                    item.setActivityStatus("not_started");
+                    item.setCountdown((startMs - now) / 1000);
+                } else if (now > endMs) {
+                    item.setActivityStatus("ended");
+                } else {
+                    item.setActivityStatus("ongoing");
+                    item.setCountdown((endMs - now) / 1000);
+                }
+            }
+            item.setIsOngoing(isOngoing);
+        }
+
+        AjaxResult ajax = AjaxResult.success(list);
+        ajax.put("isOngoing", isOngoing);
+        if (isOngoing) {
+            ajax.put("ongoingActivityType", ongoing.getActivityType());
+        }
+        return ajax;
+    }
+
+    /**
+     * 保存活动设置(批量保存/更新)
+     * 请求体: { productId, activityType, activityList: [{specId, originalPrice, flashPrice, discount, discountPrice, stock, startTime, endTime}] }
+     */
+    @ApiOperation("保存活动设置")
+    @PostMapping("/save")
+    public AjaxResult saveActivity(@RequestBody FsStoreProductActivitySaveRequest request) {
+        try {
+            // 校验商品审核状态
+            if (request.getProductId() != null && request.getActivityType() != null && request.getActivityType() != 0) {
+                FsStoreProductScrm product = storeProductService.selectFsStoreProductById(request.getProductId());
+                if (product == null) {
+                    return AjaxResult.error("商品不存在");
+                }
+                if (product.getIsAudit() == null || !"1".equals(product.getIsAudit())) {
+                    return AjaxResult.error("商品审核通过后才能设置活动");
+                }
+            }
+            activityService.saveActivity(request.getProductId(), request.getActivityType(), request.getActivityList());
+            return AjaxResult.success();
+        } catch (RuntimeException e) {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 删除活动设置
+     */
+    @ApiOperation("删除活动设置")
+    @DeleteMapping("/remove/{productId}")
+    public AjaxResult remove(@PathVariable("productId") Long productId) {
+        try {
+            // 删除 = 设置activityType=0
+            activityService.saveActivity(productId, 0, null);
+            return AjaxResult.success();
+        } catch (RuntimeException e) {
+            return AjaxResult.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 保存活动设置请求体
+     */
+    @lombok.Data
+    public static class FsStoreProductActivitySaveRequest {
+        private Long productId;
+        /** 活动类型:0=无 6=秒杀 7=限时折扣 */
+        private Integer activityType;
+        private List<FsStoreProductActivity> activityList;
+    }
+}

+ 153 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductDiscountController.java

@@ -0,0 +1,153 @@
+package com.fs.hisStore.controller;
+
+import java.util.Date;
+import java.util.List;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.core.redis.service.ActivityStockService;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.framework.web.service.TokenService;
+import com.fs.hisStore.domain.FsStoreProductDiscount;
+import com.fs.hisStore.service.IFsStoreProductDiscountService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 限时折扣商品Controller
+ *
+ * @author fs
+ * @date 2026-04-03
+ */
+@RestController
+@RequestMapping("/store/store/productDiscount")
+public class FsStoreProductDiscountController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductDiscountService fsStoreProductDiscountService;
+
+    @Autowired
+    private ActivityStockService activityStockService;
+
+    /**
+     * 查询限时折扣商品列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:productDiscount:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductDiscount fsStoreProductDiscount)
+    {
+        startPage();
+        List<FsStoreProductDiscount> list = fsStoreProductDiscountService.selectFsStoreProductDiscountList(fsStoreProductDiscount);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出限时折扣商品列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:productDiscount:export')")
+    @Log(title = "限时折扣商品", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductDiscount fsStoreProductDiscount)
+    {
+        List<FsStoreProductDiscount> list = fsStoreProductDiscountService.selectFsStoreProductDiscountList(fsStoreProductDiscount);
+        ExcelUtil<FsStoreProductDiscount> util = new ExcelUtil<>(FsStoreProductDiscount.class);
+        return util.exportExcel(list, "限时折扣商品数据");
+    }
+
+    /**
+     * 获取限时折扣商品详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:productDiscount:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreProductDiscountService.selectFsStoreProductDiscountById(id));
+    }
+
+    /**
+     * 新增限时折扣商品
+     */
+    @PreAuthorize("@ss.hasPermi('store:productDiscount:add')")
+    @Log(title = "限时折扣商品", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductDiscount fsStoreProductDiscount)
+    {
+        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
+        if(loginUser == null){
+            throw new ServiceException("用户信息不存在!");
+        }
+        fsStoreProductDiscount.setCreateBy(loginUser.getUserId().toString());
+        int result = fsStoreProductDiscountService.insertFsStoreProductDiscount(fsStoreProductDiscount);
+        if (result > 0 && fsStoreProductDiscount.getId() != null) {
+            activityStockService.initDiscountStock(fsStoreProductDiscount.getId(), fsStoreProductDiscount.getStock());
+            if (fsStoreProductDiscount.getStartTime() != null && fsStoreProductDiscount.getEndTime() != null) {
+                activityStockService.initDiscountInfo(
+                        fsStoreProductDiscount.getId(),
+                        fsStoreProductDiscount.getStatus(),
+                        fsStoreProductDiscount.getStartTime().getTime(),
+                        fsStoreProductDiscount.getEndTime().getTime(),
+                        fsStoreProductDiscount.getProductId(),
+                        fsStoreProductDiscount.getStock()
+                );
+            }
+        }
+        return toAjax(result);
+    }
+
+    /**
+     * 修改限时折扣商品
+     */
+    @PreAuthorize("@ss.hasPermi('store:productDiscount:edit')")
+    @Log(title = "限时折扣商品", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductDiscount fsStoreProductDiscount)
+    {
+        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
+        if(loginUser == null){
+            throw new ServiceException("用户信息不存在!");
+        }
+        fsStoreProductDiscount.setUpdateBy(loginUser.getUserId().toString());
+        int result = fsStoreProductDiscountService.updateFsStoreProductDiscount(fsStoreProductDiscount);
+        if (result > 0 && fsStoreProductDiscount.getId() != null) {
+            // 活动信息始终更新(状态、时间等)
+            if (fsStoreProductDiscount.getStartTime() != null && fsStoreProductDiscount.getEndTime() != null) {
+                activityStockService.initDiscountInfo(
+                        fsStoreProductDiscount.getId(),
+                        fsStoreProductDiscount.getStatus(),
+                        fsStoreProductDiscount.getStartTime().getTime(),
+                        fsStoreProductDiscount.getEndTime().getTime(),
+                        fsStoreProductDiscount.getProductId(),
+                        fsStoreProductDiscount.getStock()
+                );
+            }
+            // 库存只在明确修改时才重新初始化Redis,避免覆盖已扣减的库存
+            if (fsStoreProductDiscount.getStock() != null) {
+                Integer currentRedisStock = activityStockService.getStock(7, fsStoreProductDiscount.getId());
+                // 只有当请求的库存值与Redis当前库存不一致时才重新初始化(说明管理员确实要改库存)
+                if (!fsStoreProductDiscount.getStock().equals(currentRedisStock.longValue())) {
+                    activityStockService.initDiscountStock(fsStoreProductDiscount.getId(), fsStoreProductDiscount.getStock());
+                }
+            }
+        }
+        return toAjax(result);
+    }
+
+    /**
+     * 删除限时折扣商品
+     */
+    @PreAuthorize("@ss.hasPermi('store:productDiscount:remove')")
+    @Log(title = "限时折扣商品", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreProductDiscountService.deleteFsStoreProductDiscountByIds(ids));
+    }
+}

+ 152 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreProductFlashSaleController.java

@@ -0,0 +1,152 @@
+package com.fs.hisStore.controller;
+
+import java.util.List;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.core.redis.service.ActivityStockService;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.framework.web.service.TokenService;
+import com.fs.hisStore.domain.FsStoreProductFlashSale;
+import com.fs.hisStore.service.IFsStoreProductFlashSaleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 秒杀商品Controller
+ *
+ * @author fs
+ * @date 2026-04-08
+ */
+@RestController
+@RequestMapping("/store/store/productFlashSale")
+public class FsStoreProductFlashSaleController extends BaseController
+{
+    @Autowired
+    private IFsStoreProductFlashSaleService fsStoreProductFlashSaleService;
+
+    @Autowired
+    private ActivityStockService activityStockService;
+
+    /**
+     * 查询秒杀商品列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:productFlashSale:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsStoreProductFlashSale fsStoreProductFlashSale)
+    {
+        startPage();
+        List<FsStoreProductFlashSale> list = fsStoreProductFlashSaleService.selectFsStoreProductFlashSaleList(fsStoreProductFlashSale);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出秒杀商品列表
+     */
+    @PreAuthorize("@ss.hasPermi('store:productFlashSale:export')")
+    @Log(title = "秒杀商品", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsStoreProductFlashSale fsStoreProductFlashSale)
+    {
+        List<FsStoreProductFlashSale> list = fsStoreProductFlashSaleService.selectFsStoreProductFlashSaleList(fsStoreProductFlashSale);
+        ExcelUtil<FsStoreProductFlashSale> util = new ExcelUtil<>(FsStoreProductFlashSale.class);
+        return util.exportExcel(list, "秒杀商品数据");
+    }
+
+    /**
+     * 获取秒杀商品详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('store:productFlashSale:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsStoreProductFlashSaleService.selectFsStoreProductFlashSaleById(id));
+    }
+
+    /**
+     * 新增秒杀商品
+     */
+    @PreAuthorize("@ss.hasPermi('store:productFlashSale:add')")
+    @Log(title = "秒杀商品", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsStoreProductFlashSale fsStoreProductFlashSale)
+    {
+        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
+        if(loginUser == null){
+            throw new ServiceException("用户信息不存在!");
+        }
+        fsStoreProductFlashSale.setCreateBy(loginUser.getUserId().toString());
+        int result = fsStoreProductFlashSaleService.insertFsStoreProductFlashSale(fsStoreProductFlashSale);
+        if (result > 0 && fsStoreProductFlashSale.getId() != null) {
+            activityStockService.initFlashSaleStock(fsStoreProductFlashSale.getId(), fsStoreProductFlashSale.getStock());
+            if (fsStoreProductFlashSale.getStartTime() != null && fsStoreProductFlashSale.getEndTime() != null) {
+                activityStockService.initFlashSaleInfo(
+                        fsStoreProductFlashSale.getId(),
+                        fsStoreProductFlashSale.getStatus(),
+                        fsStoreProductFlashSale.getStartTime().getTime(),
+                        fsStoreProductFlashSale.getEndTime().getTime(),
+                        fsStoreProductFlashSale.getProductId(),
+                        fsStoreProductFlashSale.getStock()
+                );
+            }
+        }
+        return toAjax(result);
+    }
+
+    /**
+     * 修改秒杀商品
+     */
+    @PreAuthorize("@ss.hasPermi('store:productFlashSale:edit')")
+    @Log(title = "秒杀商品", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsStoreProductFlashSale fsStoreProductFlashSale)
+    {
+        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
+        if(loginUser == null){
+            throw new ServiceException("用户信息不存在!");
+        }
+        fsStoreProductFlashSale.setUpdateBy(loginUser.getUserId().toString());
+        int result = fsStoreProductFlashSaleService.updateFsStoreProductFlashSale(fsStoreProductFlashSale);
+        if (result > 0 && fsStoreProductFlashSale.getId() != null) {
+            // 活动信息始终更新(状态、时间等)
+            if (fsStoreProductFlashSale.getStartTime() != null && fsStoreProductFlashSale.getEndTime() != null) {
+                activityStockService.initFlashSaleInfo(
+                        fsStoreProductFlashSale.getId(),
+                        fsStoreProductFlashSale.getStatus(),
+                        fsStoreProductFlashSale.getStartTime().getTime(),
+                        fsStoreProductFlashSale.getEndTime().getTime(),
+                        fsStoreProductFlashSale.getProductId(),
+                        fsStoreProductFlashSale.getStock()
+                );
+            }
+            // 库存只在明确修改时才重新初始化Redis,避免覆盖已扣减的库存
+            if (fsStoreProductFlashSale.getStock() != null) {
+                Integer currentRedisStock = activityStockService.getStock(6, fsStoreProductFlashSale.getId());
+                // 只有当请求的库存值与Redis当前库存不一致时才重新初始化(说明管理员确实要改库存)
+                if (!fsStoreProductFlashSale.getStock().equals(currentRedisStock.longValue())) {
+                    activityStockService.initFlashSaleStock(fsStoreProductFlashSale.getId(), fsStoreProductFlashSale.getStock());
+                }
+            }
+        }
+        return toAjax(result);
+    }
+
+    /**
+     * 删除秒杀商品
+     */
+    @PreAuthorize("@ss.hasPermi('store:productFlashSale:remove')")
+    @Log(title = "秒杀商品", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsStoreProductFlashSaleService.deleteFsStoreProductFlashSaleByIds(ids));
+    }
+}

+ 60 - 0
fs-admin/src/main/java/com/fs/hisStore/task/ActivityExpireTask.java

@@ -0,0 +1,60 @@
+package com.fs.hisStore.task;
+
+import com.fs.hisStore.domain.FsStoreProductActivity;
+import com.fs.hisStore.service.IFsStoreProductActivityService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 活动过期兜底定时任务
+ * 每1分钟扫描一次过期活动,自动重置 activity_type 恢复为普通商品
+ *
+ * @author fs
+ * @date 2026-04-22
+ */
+@Slf4j
+@Component("activityExpireTask")
+public class ActivityExpireTask {
+
+    @Autowired
+    private IFsStoreProductActivityService activityService;
+
+    /**
+     * 每1分钟执行一次,处理过期活动
+     */
+//    @Scheduled(fixedRate = 60000)
+    public void handleExpiredActivities() {
+        try {
+            // 1. 查询所有已过期但未重置的活动
+            List<FsStoreProductActivity> expiredList = activityService.selectExpiredList();
+            if (expiredList == null || expiredList.isEmpty()) {
+                return;
+            }
+
+            log.info("[ActivityExpireTask] 发现{}个过期活动,开始处理", expiredList.size());
+
+            // 2. 按商品ID分组处理
+            Map<Long, List<FsStoreProductActivity>> groupByProduct = expiredList.stream()
+                    .collect(Collectors.groupingBy(FsStoreProductActivity::getProductId));
+
+            for (Map.Entry<Long, List<FsStoreProductActivity>> entry : groupByProduct.entrySet()) {
+                try {
+                    activityService.handleExpiredActivity(entry.getKey(), entry.getValue());
+                    log.info("[ActivityExpireTask] 商品{}的过期活动处理完成", entry.getKey());
+                } catch (Exception e) {
+                    log.error("[ActivityExpireTask] 商品{}的过期活动处理失败", entry.getKey(), e);
+                }
+            }
+
+            log.info("[ActivityExpireTask] 本轮过期活动处理完成,共处理{}个商品", groupByProduct.size());
+        } catch (Exception e) {
+            log.error("[ActivityExpireTask] 活动过期定时任务执行异常", e);
+        }
+    }
+}

+ 33 - 0
fs-admin/src/main/java/com/fs/hisStore/task/MallStoreTask.java

@@ -387,6 +387,39 @@ public class MallStoreTask
         }
     }
 
+    /**
+     * 超时未支付自动取消订单(含活动库存回滚)
+     * 建议配置为每分钟执行一次的定时任务
+     */
+    public void cancelUnpayOrder()
+    {
+        try {
+            String json = configService.selectConfigByKey("store.config");
+            if (StringUtils.isEmpty(json)) return;
+            StoreConfig config = JSONUtil.toBean(json, StoreConfig.class);
+            Integer unPayTime = config.getUnPayTime(); // 分钟
+            if (unPayTime == null || unPayTime <= 0) return;
+
+            // 查询超时未支付的SCRM订单
+            List<FsStoreOrderScrm> orderList = fsStoreOrderMapper.selectUnpayTimeoutOrderList(unPayTime);
+            if (orderList == null || orderList.isEmpty()) return;
+
+            log.info("[cancelUnpayOrder] 发现{}个超时未支付订单,开始取消", orderList.size());
+            for (FsStoreOrderScrm order : orderList) {
+                try {
+                    // cancelOrder内部已处理活动库存回滚(refundStock -> refundActivityStock)
+                    orderService.cancelOrder(order.getId());
+                    log.info("[cancelUnpayOrder] 超时订单取消成功,orderId={}, orderType={}, associatedId={}",
+                            order.getId(), order.getOrderType(), order.getAssociatedId());
+                } catch (Exception e) {
+                    log.error("[cancelUnpayOrder] 超时订单取消失败,orderId={}", order.getId(), e);
+                }
+            }
+        } catch (Exception e) {
+            log.error("[cancelUnpayOrder] 超时取消订单任务异常", e);
+        }
+    }
+
 
     //每天执行一次
     public void userMoneyOp()

+ 1 - 2
fs-admin/src/main/java/com/fs/xiaoshouyi/controller/XiaoShouYiController.java

@@ -72,8 +72,7 @@ public class XiaoShouYiController {
 
             GenerateLinkResponse response = materialService.generateMaterialTrackLink(
                                                                     companyUserId,
-                                                                    request.getMaterialIds(),
-                                                                    request.getSendUserId());
+                                                                    request.getMaterialIds());
 
             return response.isSuccess()
                     ? AjaxResult.success(response)

+ 18 - 1
fs-common/src/main/java/com/fs/common/config/FSSysConfig.java

@@ -49,9 +49,26 @@ public class FSSysConfig
 //    private String cwarehouseCode;
 //    private String cwarehouseName;
 
+    String commonApi;
 
+    String accessKeyID;
+    String accessKeySecret;
 
-    String commonApi;
+    public String getAccessKeyID() {
+        return accessKeyID;
+    }
+
+    public void setAccessKeyID(String accessKeyID) {
+        this.accessKeyID = accessKeyID;
+    }
+
+    public String getAccessKeySecret() {
+        return accessKeySecret;
+    }
+
+    public void setAccessKeySecret(String accessKeySecret) {
+        this.accessKeySecret = accessKeySecret;
+    }
 
     public String getKdnId() {
         return kdnId;

+ 14 - 0
fs-common/src/main/java/com/fs/common/core/redis/service/ActivityStockData.java

@@ -0,0 +1,14 @@
+package com.fs.common.core.redis.service;
+
+import lombok.Data;
+
+@Data
+public class ActivityStockData {
+    private Long id;
+    private Long productId;
+    private Long specId;
+    private Long stock;
+    private Integer status;
+    private Long startTime;
+    private Long endTime;
+}

+ 22 - 0
fs-common/src/main/java/com/fs/common/core/redis/service/ActivityStockDataProvider.java

@@ -0,0 +1,22 @@
+package com.fs.common.core.redis.service;
+
+import java.util.List;
+
+public interface ActivityStockDataProvider {
+
+    ActivityStockData loadActivityData(Integer orderType, Long associatedId);
+
+    void updateStockToDb(Integer orderType, Long associatedId, Long redisStock);
+
+    List<ActivityStockData> loadAllActiveActivities(Integer orderType);
+
+    /**
+     * 加载商品规格库存到Redis
+     */
+    void loadProductSpecStock(Long specId);
+
+    /**
+     * 将Redis中的商品规格库存同步回数据库
+     */
+    void updateProductSpecStockToDb(Long specId, long redisStock);
+}

+ 711 - 0
fs-common/src/main/java/com/fs/common/core/redis/service/ActivityStockService.java

@@ -0,0 +1,711 @@
+package com.fs.common.core.redis.service;
+
+import com.fs.common.core.redis.RedisCache;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Service
+public class ActivityStockService {
+
+    public final RedisTemplate<String, Object> redisTemplate;
+    public final RedisCache redisCache;
+
+    /** 统一活动信息key前缀 */
+    private static final String ACTIVITY_INFO_KEY = "activity:info:";
+    /** 原商品规格库存key前缀 */
+    private static final String PRODUCT_SPEC_STOCK_KEY = "product:spec:stock:";
+
+    /** 扣减规格库存 Lua脚本(活动不再有独立库存,直接扣规格库存) */
+    private static final DefaultRedisScript<Long> ACTIVITY_STOCK_DEDUCT_SCRIPT;
+
+    static {
+        ACTIVITY_STOCK_DEDUCT_SCRIPT = new DefaultRedisScript<>();
+        // KEYS[1] = 原商品规格库存key(product:spec:stock:{specId})
+        // ARGV[1] = 扣减数量
+        // 返回: >=0 成功(规格剩余库存), -1 库存不足, -2 key不存在, -3 值非数字, -4 扣减数量无效
+        ACTIVITY_STOCK_DEDUCT_SCRIPT.setScriptText(
+                "local deductNum = tonumber(ARGV[1]); " +
+                "if deductNum == nil or deductNum <= 0 then return -4; end " +
+                "if redis.call('exists', KEYS[1]) ~= 1 then return -2; end " +
+                "local stock = tonumber(redis.call('get', KEYS[1])); " +
+                "if stock == nil then return -3; end " +
+                "if stock < deductNum then return -1; end " +
+                "return redis.call('decrby', KEYS[1], deductNum);"
+        );
+        ACTIVITY_STOCK_DEDUCT_SCRIPT.setResultType(Long.class);
+    }
+
+    /** 回滚规格库存 Lua脚本 */
+    private static final DefaultRedisScript<Long> ACTIVITY_STOCK_ROLLBACK_SCRIPT;
+
+    static {
+        ACTIVITY_STOCK_ROLLBACK_SCRIPT = new DefaultRedisScript<>();
+        // KEYS[1] = 原商品规格库存key(product:spec:stock:{specId})
+        // ARGV[1] = 回滚数量
+        ACTIVITY_STOCK_ROLLBACK_SCRIPT.setScriptText(
+                "local rollbackNum = tonumber(ARGV[1]); " +
+                "if rollbackNum == nil or rollbackNum <= 0 then return -4; end " +
+                "if redis.call('exists', KEYS[1]) == 1 then " +
+                "  return redis.call('incrby', KEYS[1], rollbackNum); " +
+                "end " +
+                "return 0;"
+        );
+        ACTIVITY_STOCK_ROLLBACK_SCRIPT.setResultType(Long.class);
+    }
+
+    /** 单独扣减活动库存(无specId时不扣商品库存) */
+    private static final DefaultRedisScript<Long> SINGLE_DEDUCT_SCRIPT;
+
+    static {
+        SINGLE_DEDUCT_SCRIPT = new DefaultRedisScript<>();
+        SINGLE_DEDUCT_SCRIPT.setScriptText(
+                "if redis.call('exists', KEYS[1]) ~= 1 then return -2; end " +
+                "local stock_str = redis.call('get', KEYS[1]); " +
+                "local stock = tonumber(stock_str); " +
+                "if stock == nil then return -3; end " +
+                "local deductNum_str = ARGV[1]; " +
+                "local deductNum = tonumber(deductNum_str); " +
+                "if deductNum == nil or deductNum <= 0 then return -4; end " +
+                "if stock >= deductNum then " +
+                "  return redis.call('decrby', KEYS[1], deductNum); " +
+                "else " +
+                "  return -1; " +
+                "end"
+        );
+        SINGLE_DEDUCT_SCRIPT.setResultType(Long.class);
+    }
+
+    private ActivityStockDataProvider dataProvider;
+
+    public ActivityStockService(RedisTemplate<String, Object> redisTemplate, RedisCache redisCache) {
+        this.redisTemplate = redisTemplate;
+        this.redisCache = redisCache;
+    }
+
+    public void setDataProvider(ActivityStockDataProvider dataProvider) {
+        this.dataProvider = dataProvider;
+    }
+
+    // ==================== 初始化方法 ====================
+
+    /**
+     * 初始化活动信息到Redis(活动不再有独立库存,只存活动信息用于校验)
+     */
+    public void initActivityInfo(Long activityId, Integer status, Long startTime, Long endTime, Long productId, Long specId, Long stock) {
+        String infoKey = ACTIVITY_INFO_KEY + activityId;
+        Map<String, Object> activityInfo = new HashMap<>();
+        activityInfo.put("status", status);
+        activityInfo.put("startTime", startTime);
+        activityInfo.put("endTime", endTime);
+        activityInfo.put("productId", productId);
+        activityInfo.put("specId", specId);
+        redisCache.setCacheMap(infoKey, activityInfo);
+        // 设置过期时间:活动结束后1小时自动清理缓存
+        long ttlMs = endTime - System.currentTimeMillis() + 3600000;
+        if (ttlMs > 0) {
+            redisTemplate.expire(infoKey, ttlMs, TimeUnit.MILLISECONDS);
+        }
+        log.info("活动商品{}活动信息初始化完成,specId={}", activityId, specId);
+    }
+
+    /**
+     * 初始化原商品规格库存到Redis
+     */
+    public void initProductSpecStock(Long specId, Integer stock) {
+        if (specId == null) return;
+        String stockKey = PRODUCT_SPEC_STOCK_KEY + specId;
+        if (!redisCache.hasKey(stockKey)) {
+            redisTemplate.opsForValue().set(stockKey, stock);
+            log.info("商品规格{}库存初始化到Redis完成,库存:{}", specId, stock);
+        }
+    }
+
+    // ==================== 兼容旧接口(已废弃,活动无独立库存) ====================
+
+    /**
+     * @deprecated 活动无独立库存,规格库存用initProductSpecStock初始化
+     */
+    @Deprecated
+    public void initFlashSaleStock(Long flashSaleId, Long stock) {
+        // 不再初始化独立活动库存
+    }
+
+    /**
+     * @deprecated 活动无独立库存,规格库存用initProductSpecStock初始化
+     */
+    @Deprecated
+    public void initDiscountStock(Long discountId, Long stock) {
+        // 不再初始化独立活动库存
+    }
+
+    public void initFlashSaleInfo(Long flashSaleId, Integer status, Long startTime, Long endTime, Long productId, Long stock) {
+        initActivityInfo(flashSaleId, status, startTime, endTime, productId, null, stock);
+    }
+
+    public void initDiscountInfo(Long discountId, Integer status, Long startTime, Long endTime, Long productId, Long stock) {
+        initActivityInfo(discountId, status, startTime, endTime, productId, null, stock);
+    }
+
+    public Integer getFlashSaleStock(Long flashSaleId) {
+        return getStock(6, flashSaleId);
+    }
+
+    public Integer getDiscountStock(Long discountId) {
+        return getStock(7, discountId);
+    }
+
+    // ==================== 核心方法 ====================
+
+    public int getStock(Integer orderType, Long associatedId) {
+        // 活动无独立库存,从活动信息中获取specId查规格库存
+        String infoKey = ACTIVITY_INFO_KEY + associatedId;
+        Map<String, Object> activityInfo = redisCache.getCacheMap(infoKey);
+        Long specId = null;
+        if (activityInfo != null) {
+            specId = parseLong(activityInfo.get("specId"));
+        }
+        if (specId == null) {
+            loadActivityFromDb(orderType, associatedId);
+            activityInfo = redisCache.getCacheMap(infoKey);
+            if (activityInfo != null) {
+                specId = parseLong(activityInfo.get("specId"));
+            }
+        }
+        if (specId == null) return 0;
+        String specStockKey = PRODUCT_SPEC_STOCK_KEY + specId;
+        Object stockObj = redisTemplate.opsForValue().get(specStockKey);
+        return parseStockValue(stockObj);
+    }
+
+    /**
+     * Redis Pipeline 批量获取库存
+     * 先从 ACTIVITY_INFO_KEY 获取各活动的 specId,再用 PRODUCT_SPEC_STOCK_KEY Pipeline 批量查规格库存
+     */
+    public Map<Long, Integer> batchGetStock(Integer orderType, List<Long> associatedIds) {
+        if (associatedIds == null || associatedIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        Map<Long, Integer> result = new LinkedHashMap<>();
+        try {
+            //Redis缓存读取每个活动的specId映射
+            Map<Long, Long> specIdMap = new LinkedHashMap<>();
+            for (Long id : associatedIds) {
+                String infoKey = ACTIVITY_INFO_KEY + id;
+                Map<String, Object> activityInfo = redisCache.getCacheMap(infoKey);
+                if (activityInfo != null) {
+                    Long specId = parseLong(activityInfo.get("specId"));
+                    if (specId != null) {
+                        specIdMap.put(id, specId);
+                    }
+                }
+            }
+
+            // 若specIdMap 为空(活动信息不在Redis),批量从DB加载后逐个getStock降级
+            if (specIdMap.isEmpty()) {
+                log.warn("batchGetStock: 活动信息全部不在Redis,降级逐个加载,orderType={}, size={}", orderType, associatedIds.size());
+                for (Long id : associatedIds) {
+                    loadActivityFromDb(orderType, id);
+                    result.put(id, getStock(orderType, id));
+                }
+                return result;
+            }
+
+            //associatedId 顺序构建 specKey 列表(未命中的记录为 null)
+            List<Long> pipelineIds = new ArrayList<>();
+            List<String> specKeys = new ArrayList<>();
+            for (Long id : associatedIds) {
+                Long specId = specIdMap.get(id);
+                if (specId != null) {
+                    pipelineIds.add(id);
+                    specKeys.add(PRODUCT_SPEC_STOCK_KEY + specId);
+                }
+            }
+
+            //规格库存
+            final List<String> finalSpecKeys = specKeys;
+            List<Object> stockValues = redisTemplate.executePipelined(
+                    (org.springframework.data.redis.core.RedisCallback<Object>) connection -> {
+                        for (String key : finalSpecKeys) {
+                            connection.get(key.getBytes(java.nio.charset.StandardCharsets.UTF_8));
+                        }
+                        return null;
+                    }
+            );
+
+            //结果映射回 associatedId
+            Map<Long, Integer> pipelineResult = new LinkedHashMap<>();
+            List<Long> needFallbackIds = new ArrayList<>();  // Pipeline结果为null需要降级的ID
+            for (int i = 0; i < pipelineIds.size(); i++) {
+                Long id = pipelineIds.get(i);
+                Object value = (i < stockValues.size()) ? stockValues.get(i) : null;
+                if (value == null) {
+                    // product:spec:stock:{specId} key不存在,需要降级从DB加载规格库存
+                    needFallbackIds.add(id);
+                    pipelineResult.put(id, 0);
+                } else {
+                    pipelineResult.put(id, parseStockValue(value));
+                }
+            }
+
+            //合并结果;specIdMap 中没有的 id(缓存缺失)降级逐个查
+            for (Long id : associatedIds) {
+                if (pipelineResult.containsKey(id)) {
+                    result.put(id, pipelineResult.get(id));
+                } else {
+                    //specId,降级加载
+                    loadActivityFromDb(orderType, id);
+                    result.put(id, getStock(orderType, id));
+                }
+            }
+
+            // 对Pipeline返回null的记录,降级从DB加载规格库存并重新获取
+            if (!needFallbackIds.isEmpty()) {
+                log.info("batchGetStock: {}个活动规格库存key不存在,降级加载", needFallbackIds.size());
+                for (Long id : needFallbackIds) {
+                    Long specId = specIdMap.get(id);
+                    if (specId != null && dataProvider != null) {
+                        dataProvider.loadProductSpecStock(specId);
+                    }
+                    // 重新读取规格库存
+                    String specStockKey = PRODUCT_SPEC_STOCK_KEY + specIdMap.get(id);
+                    Object stockObj = redisTemplate.opsForValue().get(specStockKey);
+                    result.put(id, parseStockValue(stockObj));
+                }
+            }
+        } catch (Exception e) {
+            log.error("Pipeline批量获取库存异常,orderType={}", orderType, e);
+            for (Long id : associatedIds) {
+                result.put(id, getStock(orderType, id));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 验证活动是否有效(上架且在活动时间内)
+     */
+    public ActivityValidateResult validateActivityWithDetail(Integer orderType, Long associatedId) {
+        String infoKey = ACTIVITY_INFO_KEY + associatedId;
+        Map<String, Object> activityInfo = redisCache.getCacheMap(infoKey);
+        if (activityInfo == null || activityInfo.isEmpty()) {
+            log.info("活动信息不存在于Redis,尝试从数据库加载。orderType={}, associatedId={}", orderType, associatedId);
+            boolean loaded = loadActivityFromDb(orderType, associatedId);
+            if (!loaded) {
+                return ActivityValidateResult.fail("活动不存在");
+            }
+            activityInfo = redisCache.getCacheMap(infoKey);
+            if (activityInfo == null || activityInfo.isEmpty()) {
+                return ActivityValidateResult.fail("活动信息加载失败");
+            }
+        }
+        return doValidateActivity(activityInfo);
+    }
+
+    public boolean validateActivity(Integer orderType, Long associatedId) {
+        return validateActivityWithDetail(orderType, associatedId).isValid();
+    }
+
+    private ActivityValidateResult doValidateActivity(Map<String, Object> activityInfo) {
+        Integer status = parseInteger(activityInfo.get("status"));
+        if (status == null || status != 1) {
+            return ActivityValidateResult.fail("活动已下架");
+        }
+        Long startTime = parseLong(activityInfo.get("startTime"));
+        Long endTime = parseLong(activityInfo.get("endTime"));
+        if (startTime == null || endTime == null) {
+            return ActivityValidateResult.fail("活动时间信息不完整");
+        }
+        long now = System.currentTimeMillis();
+        if (now < startTime) {
+            return ActivityValidateResult.fail("活动尚未开始");
+        }
+        if (now > endTime) {
+            return ActivityValidateResult.fail("活动已结束");
+        }
+        return ActivityValidateResult.success();
+    }
+
+    /**
+     * 扣减活动商品库存(直接扣规格库存,活动无独立库存)
+     * @param orderType 活动类型 6=秒杀 7=折扣
+     * @param associatedId 中间表记录ID
+     * @param deductNum 扣减数量
+     * @return 是否成功
+     */
+    public boolean deductStock(Integer orderType, Long associatedId, Integer deductNum) {
+        Integer num = Optional.ofNullable(deductNum).orElse(1);
+
+        if (!validateActivity(orderType, associatedId)) {
+            log.warn("活动校验失败,可能已下架或不在活动时间内。orderType={}, associatedId={}", orderType, associatedId);
+            return false;
+        }
+
+        try {
+            // 从活动信息中获取specId
+            String infoKey = ACTIVITY_INFO_KEY + associatedId;
+            Map<String, Object> activityInfo = redisCache.getCacheMap(infoKey);
+            Long specId = null;
+            if (activityInfo != null) {
+                specId = parseLong(activityInfo.get("specId"));
+            }
+            if (specId == null || specId <= 0) {
+                log.error("活动规格ID不存在,无法扣减库存。associatedId={}", associatedId);
+                return false;
+            }
+
+            String specStockKey = PRODUCT_SPEC_STOCK_KEY + specId;
+            if (!redisCache.hasKey(specStockKey)) {
+                if (dataProvider != null) {
+                    dataProvider.loadProductSpecStock(specId);
+                }
+            }
+
+            Long remainingStock = redisTemplate.execute(
+                    ACTIVITY_STOCK_DEDUCT_SCRIPT,
+                    Collections.singletonList(specStockKey),
+                    num
+            );
+
+            log.info("Lua脚本返回值:{}", remainingStock);
+
+            if (remainingStock == null) {
+                log.error("Lua脚本返回null,库存扣减结果未知,orderType={},associatedId={}", orderType, associatedId);
+                return false;
+            }
+
+            if (remainingStock >= 0) {
+                log.info("活动商品库存扣减成功,orderType={},associatedId={},规格剩余库存:{}", orderType, associatedId, remainingStock);
+                // 库存归零时异步标记,通知DB同步
+                if (remainingStock == 0) {
+                    final Long finalAssociatedId = associatedId;
+                    final Integer finalOrderType = orderType;
+                    CompletableFuture.runAsync(() -> {
+                        try {
+                            if (finalOrderType == 6) {
+                                syncFlashSaleStockToDb(finalAssociatedId, 0L);
+                            } else if (finalOrderType == 7) {
+                                syncDiscountStockToDb(finalAssociatedId, 0L);
+                            }
+                        } catch (Exception ex) {
+                            log.error("库存归零异步同步DB异常,orderType={},associatedId={}", finalOrderType, finalAssociatedId, ex);
+                        }
+                    });
+                }
+                return true;
+            } else {
+                String errorMsg = parseErrorCode(remainingStock);
+                log.warn("活动商品扣减失败:orderType={},associatedId={},原因:{}", orderType, associatedId, errorMsg);
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("活动库存扣减异常,orderType={},associatedId={}", orderType, associatedId, e);
+            return false;
+        }
+    }
+
+    public CompletableFuture<Boolean> deductStockAsync(Integer orderType, Long associatedId, Integer deductNum) {
+        return CompletableFuture.supplyAsync(() -> deductStock(orderType, associatedId, deductNum));
+    }
+
+    /**
+     * 回滚活动商品库存(直接回滚规格库存,活动无独立库存)
+     */
+    public boolean rollbackStock(Integer orderType, Long associatedId, Integer quantity) {
+        if (orderType == null || associatedId == null || quantity == null || quantity <= 0) {
+            return false;
+        }
+        try {
+            // 从活动信息中获取specId
+            String infoKey = ACTIVITY_INFO_KEY + associatedId;
+            Map<String, Object> activityInfo = redisCache.getCacheMap(infoKey);
+            Long specId = null;
+            if (activityInfo != null) {
+                specId = parseLong(activityInfo.get("specId"));
+            }
+            // 活动信息key可能因TTL过期被清理,从DB降级加载
+            if (specId == null || specId <= 0) {
+                log.info("rollbackStock: 活动信息不在Redis,尝试从DB加载,orderType={}, associatedId={}", orderType, associatedId);
+                boolean loaded = loadActivityFromDb(orderType, associatedId);
+                if (loaded) {
+                    activityInfo = redisCache.getCacheMap(infoKey);
+                    if (activityInfo != null) {
+                        specId = parseLong(activityInfo.get("specId"));
+                    }
+                }
+            }
+            if (specId == null || specId <= 0) {
+                log.error("rollbackStock: 活动规格ID无法获取,Redis库存回滚失败!orderType={}, associatedId={}, quantity={},需人工处理",
+                        orderType, associatedId, quantity);
+                return false;
+            }
+
+            // 如果规格库存key不存在,先从DB加载初始化
+            String specStockKey = PRODUCT_SPEC_STOCK_KEY + specId;
+            if (!redisCache.hasKey(specStockKey)) {
+                log.info("rollbackStock: 规格库存key不存在,尝试从DB加载,specId={}", specId);
+                if (dataProvider != null) {
+                    dataProvider.loadProductSpecStock(specId);
+                }
+            }
+
+            Long result = redisTemplate.execute(
+                    ACTIVITY_STOCK_ROLLBACK_SCRIPT,
+                    Collections.singletonList(specStockKey),
+                    quantity
+            );
+
+            if (result != null && result >= 0) {
+                log.info("活动规格库存回滚成功,orderType={},associatedId={},specId={},回滚数量={}", orderType, associatedId, specId, quantity);
+                return true;
+            } else {
+                log.warn("活动规格库存回滚Lua失败,降级为incrby,associatedId={},specId={}", associatedId, specId);
+                redisCache.incr(specStockKey, Long.valueOf(quantity));
+                return true;
+            }
+        } catch (Exception e) {
+            log.error("活动库存回滚异常,orderType={}, associatedId={}, quantity={}", orderType, associatedId, quantity, e);
+            return false;
+        }
+    }
+
+    // ==================== 同步方法 ====================
+
+    public void syncFlashSaleStockToDb(Long flashSaleId, Long dbStock) {
+        syncActivityStockToDb(flashSaleId, dbStock);
+    }
+
+    public void syncDiscountStockToDb(Long discountId, Long dbStock) {
+        syncActivityStockToDb(discountId, dbStock);
+    }
+
+    private void syncActivityStockToDb(Long activityId, Long dbStock) {
+        try {
+            // 从活动info获取specId
+            String infoKey = ACTIVITY_INFO_KEY + activityId;
+            Map<String, Object> activityInfo = redisCache.getCacheMap(infoKey);
+            if (activityInfo == null || activityInfo.isEmpty()) {
+                log.warn("活动{} 信息不存在,无法同步规格库存到DB", activityId);
+                return;
+            }
+            Long specId = parseLong(activityInfo.get("specId"));
+            if (specId == null || specId <= 0) {
+                log.warn("活动{} 未配置规格ID,无法同步规格库存到DB", activityId);
+                return;
+            }
+
+            // 读取真实的规格库存
+            String specStockKey = PRODUCT_SPEC_STOCK_KEY + specId;
+            Object stockObj = redisTemplate.opsForValue().get(specStockKey);
+            if (stockObj == null) {
+                log.warn("规格{} Redis库存不存在,跳过同步到DB", specId);
+                return;
+            }
+            long redisStock = parseLongFromObject(stockObj);
+
+            // 比较并同步
+            if (dbStock == null || redisStock != dbStock.longValue()) {
+                if (dataProvider != null) {
+                    dataProvider.updateProductSpecStockToDb(specId, redisStock);
+                    log.info("活动{} 规格{} 库存同步到DB成功,redisStock={}", activityId, specId, redisStock);
+                } else {
+                    log.warn("dataProvider未注入,无法同步活动{}规格{}库存到DB", activityId, specId);
+                }
+            } else {
+                log.debug("活动{} 规格{} 库存一致(={}),无需同步", activityId, specId, redisStock);
+            }
+        } catch (Exception e) {
+            log.error("活动{} 同步规格库存到DB异常", activityId, e);
+        }
+    }
+
+    /**
+     * 同步指定规格的Redis库存到数据库(活动过期时调用)
+     */
+    public void syncProductSpecStockToDbBySpecId(Long specId) {
+        if (specId == null || dataProvider == null) {
+            return;
+        }
+        try {
+            String specStockKey = PRODUCT_SPEC_STOCK_KEY + specId;
+            Object stockObj = redisTemplate.opsForValue().get(specStockKey);
+            if (stockObj != null) {
+                long redisStock = parseLongFromObject(stockObj);
+                dataProvider.updateProductSpecStockToDb(specId, redisStock);
+                log.info("活动过期同步规格库存到DB,specId={}, redisStock={}", specId, redisStock);
+            } else {
+                log.info("活动过期规格库存key不存在,跳过同步,specId={}", specId);
+            }
+        } catch (Exception e) {
+            log.error("同步规格库存到数据库异常,specId={}", specId, e);
+        }
+    }
+
+    /**
+     * 定时对账:将Redis规格库存同步到数据库(兤底,防止Redis回滚失败导致不一致)
+     * 活动中间表已无stock字段,只同步规格库存
+     */
+    public void syncAllStockToDb() {
+        if (dataProvider == null) {
+            log.warn("ActivityStockDataProvider 未设置,无法同步库存到数据库");
+            return;
+        }
+        try {
+            // 同步商品规格库存到数据库(核心对账逻辑)
+            syncProductSpecStockToDb();
+        } catch (Exception e) {
+            log.error("全量库存同步到数据库异常", e);
+        }
+    }
+
+    /**
+     * 同步Redis中的商品规格库存到数据库
+     */
+    private void syncProductSpecStockToDb() {
+        try {
+            Set<String> specKeys = scanKeys(PRODUCT_SPEC_STOCK_KEY + "*");
+            if (specKeys == null || specKeys.isEmpty()) {
+                return;
+            }
+            for (String specKey : specKeys) {
+                try {
+                    Long specId = Long.parseLong(specKey.substring(PRODUCT_SPEC_STOCK_KEY.length()));
+                    Object stockObj = redisTemplate.opsForValue().get(specKey);
+                    if (stockObj != null) {
+                        long redisStock = parseLongFromObject(stockObj);
+                        if (dataProvider != null) {
+                            dataProvider.updateProductSpecStockToDb(specId, redisStock);
+                        }
+                    }
+                } catch (Exception e) {
+                    log.error("商品规格库存同步异常,key={}", specKey, e);
+                }
+            }
+            log.info("商品规格库存同步到数据库完成,key数={}", specKeys.size());
+        } catch (Exception e) {
+            log.error("商品规格库存同步到数据库异常", e);
+        }
+    }
+
+    // ==================== 内部工具方法 ====================
+
+    private boolean loadActivityFromDb(Integer orderType, Long associatedId) {
+        if (dataProvider == null) return false;
+        try {
+            ActivityStockData data = dataProvider.loadActivityData(orderType, associatedId);
+            if (data == null) return false;
+            String infoKey = ACTIVITY_INFO_KEY + associatedId;
+            Map<String, Object> activityInfo = new HashMap<>();
+            activityInfo.put("status", data.getStatus());
+            activityInfo.put("startTime", data.getStartTime());
+            activityInfo.put("endTime", data.getEndTime());
+            activityInfo.put("productId", data.getProductId());
+            activityInfo.put("specId", data.getSpecId());
+            redisCache.setCacheMap(infoKey, activityInfo);
+            if (data.getEndTime() != null) {
+                long ttlMs = data.getEndTime() - System.currentTimeMillis() + 3600000;
+                if (ttlMs > 0) {
+                    redisTemplate.expire(infoKey, ttlMs, TimeUnit.MILLISECONDS);
+                }
+            }
+            // 初始化规格库存到Redis
+            if (data.getSpecId() != null) {
+                dataProvider.loadProductSpecStock(data.getSpecId());
+            }
+            log.info("从数据库加载活动信息到Redis成功,associatedId={}, specId={}", associatedId, data.getSpecId());
+            return true;
+        } catch (Exception e) {
+            log.error("从数据库加载活动信息异常,orderType={}, associatedId={}", orderType, associatedId, e);
+            return false;
+        }
+    }
+
+    private Set<String> scanKeys(String pattern) {
+        Set<String> keys = new HashSet<>();
+        try {
+            org.springframework.data.redis.core.ScanOptions options =
+                    org.springframework.data.redis.core.ScanOptions.scanOptions()
+                            .match(pattern)
+                            .count(100)
+                            .build();
+            redisTemplate.execute((org.springframework.data.redis.core.RedisCallback<Void>) connection -> {
+                org.springframework.data.redis.core.Cursor<byte[]> cursor = connection.scan(options);
+                try {
+                    while (cursor.hasNext()) {
+                        keys.add(new String(cursor.next(), java.nio.charset.StandardCharsets.UTF_8));
+                    }
+                } finally {
+                    try { cursor.close(); } catch (java.io.IOException e) { log.warn("关闭Redis cursor失败", e); }
+                }
+                return null;
+            });
+        } catch (Exception e) {
+            log.error("SCAN扫描key异常,pattern={}", pattern, e);
+        }
+        return keys;
+    }
+
+    private int parseStockValue(Object stockObj) {
+        if (stockObj == null) return 0;
+        if (stockObj instanceof Integer) return (Integer) stockObj;
+        if (stockObj instanceof Long) return ((Long) stockObj).intValue();
+        if (stockObj instanceof String) {
+            try { return Integer.parseInt((String) stockObj); }
+            catch (NumberFormatException e) { return 0; }
+        }
+        return 0;
+    }
+
+    private long parseLongFromObject(Object obj) {
+        if (obj instanceof Integer) return ((Integer) obj).longValue();
+        if (obj instanceof Long) return (Long) obj;
+        if (obj instanceof String) return Long.parseLong((String) obj);
+        return 0;
+    }
+
+    private Integer parseInteger(Object obj) {
+        if (obj instanceof Integer) return (Integer) obj;
+        if (obj instanceof String) {
+            try { return Integer.parseInt((String) obj); } catch (NumberFormatException e) { return null; }
+        }
+        return null;
+    }
+
+    private Long parseLong(Object obj) {
+        if (obj instanceof Long) return (Long) obj;
+        if (obj instanceof Integer) return ((Integer) obj).longValue();
+        if (obj instanceof String) {
+            try { return Long.parseLong((String) obj); } catch (NumberFormatException e) { return null; }
+        }
+        return null;
+    }
+
+    private String parseErrorCode(Long code) {
+        if (code == null) return "Lua脚本返回null";
+        switch (code.intValue()) {
+            case -1: return "活动库存不足";
+            case -2: return "活动库存Key不存在";
+            case -3: return "库存值非数字";
+            case -4: return "扣减数量无效";
+            case -5: return "商品规格库存Key不存在";
+            case -6: return "商品规格库存不足";
+            default: return "未知错误,错误码:" + code;
+        }
+    }
+
+    // ==================== 兼容旧接口 ====================
+
+    public CompletableFuture<Integer> getFlashSaleStockAsync(Long flashSaleId) {
+        return CompletableFuture.supplyAsync(() -> getFlashSaleStock(flashSaleId));
+    }
+
+    public CompletableFuture<Integer> getDiscountStockAsync(Long discountId) {
+        return CompletableFuture.supplyAsync(() -> getDiscountStock(discountId));
+    }
+}

+ 26 - 0
fs-common/src/main/java/com/fs/common/core/redis/service/ActivityValidateResult.java

@@ -0,0 +1,26 @@
+package com.fs.common.core.redis.service;
+
+import lombok.Data;
+
+/**
+ * 活动校验结果
+ */
+@Data
+public class ActivityValidateResult {
+
+    private boolean valid;
+    private String message;
+
+    private ActivityValidateResult(boolean valid, String message) {
+        this.valid = valid;
+        this.message = message;
+    }
+
+    public static ActivityValidateResult success() {
+        return new ActivityValidateResult(true, "校验通过");
+    }
+
+    public static ActivityValidateResult fail(String message) {
+        return new ActivityValidateResult(false, message);
+    }
+}

+ 14 - 0
fs-common/src/main/java/com/fs/common/utils/PhoneUtils.java

@@ -0,0 +1,14 @@
+package com.fs.common.utils;
+
+import cn.hutool.core.util.StrUtil;
+
+public class PhoneUtils {
+    public static String getLastFourNum(String phone) {
+
+            String lastFourNumber = phone;
+            if (lastFourNumber.length() == 11) {
+                lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+            }
+            return lastFourNumber;
+    }
+}

+ 1 - 2
fs-company/src/main/java/com/fs/xiaoshouyi/controller/XiaoShouYiController.java

@@ -72,8 +72,7 @@ public class XiaoShouYiController {
 
             GenerateLinkResponse response = materialService.generateMaterialTrackLink(
                                                                     companyUserId,
-                                                                    request.getMaterialIds(),
-                                                                    request.getSendUserId());
+                                                                    request.getMaterialIds());
 
             return response.isSuccess()
                     ? AjaxResult.success(response)

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecMapper.java

@@ -114,4 +114,6 @@ public interface CompanyAiWorkflowExecMapper extends BaseMapper<CompanyAiWorkflo
     );
 
     WxContact selectWxContectByWorkflowInstanceId(@Param("workflowInstanceId") String workflowInstanceId);
+
+    Long selectWxClientIdByWorkflowInstanceId(@Param("workflowInstanceId") String workflowInstanceId);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/company/service/CompanyWorkflowEngine.java

@@ -1,5 +1,6 @@
 package com.fs.company.service;
 
+import com.alibaba.fastjson.JSONObject;
 import com.fs.company.domain.CompanyAiWorkflowExecLog;
 import com.fs.company.vo.ExecutionResult;
 
@@ -69,4 +70,9 @@ public interface CompanyWorkflowEngine {
      * @param workFlowId
      */
     Long createSipTask(Long roboticId,Long workFlowId);
+    /**
+     * 添加微信成功
+     * @param object
+     */
+    void addWxSuccess(JSONObject object);
 }

+ 8 - 7
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -225,6 +225,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             client.setRoboticId(companyVoiceRobotic.getId());
             client.setCustomerId(Long.parseLong(e));
             client.setIsWeCom(isWeCom);
+            client.setCreateTime(new Date());
             return client;
         }).collect(Collectors.toList());
         companyWxClientServiceImpl.saveBatch(clients);
@@ -845,11 +846,11 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     @Override
     @Async("cidWorkFlowExecutor")
     public void callerResult4EasyCall(CdrDetailVo result) {
-//        try {
-//            Thread.sleep(3000L);
-//        } catch (InterruptedException e) {
-//            throw new RuntimeException(e);
-//        }
+        try {
+            Thread.sleep(5000L);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
 //        EASYCALL
         log.info("进入easyCall外呼结果回调:{}", JSON.toJSONString(result));
         if (result == null || StringUtils.isBlank(result.getUuid())) return;
@@ -894,7 +895,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
 //        redisCache2.deleteObject(EASYCALL_INTENT_RETRY_KEY + result.getUuid());
 
         // dialogue(对话内容)由对方异步写入,回调时可能尚未赋值,进入延迟重试队列等待
-        if (isDialogueEmpty(callPhoneRes.getDialogue())) {
+        if (isDialogueEmpty(callPhoneRes.getDialogue()) && !"未接通".equals(callPhoneRes.getIntent())) {
             String retryKey = EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid();
             Integer retryCount = redisCache2.getCacheObject(retryKey);
             if (retryCount == null) {
@@ -936,7 +937,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             log.error("easyCall dialogue重试时仍未查询到外呼结果, uuid={}", result.getUuid());
             return;
         }
-        if (isDialogueEmpty(callPhoneRes.getDialogue())) {
+        if (isDialogueEmpty(callPhoneRes.getDialogue()) && !"未接通".equals(callPhoneRes.getIntent())) {
             // dialogue 仍为空,继续判断是否还有剩余重试次数
             String retryKey = EASYCALL_DIALOGUE_RETRY_KEY + result.getUuid();
             Integer retryCount = redisCache2.getCacheObject(retryKey);

+ 64 - 5
fs-service/src/main/java/com/fs/company/service/impl/CompanyWorkflowEngineImpl.java

@@ -71,6 +71,9 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
     @Autowired
     CompanyWxAccountMapper companyWxAccountMapper;
 
+    @Autowired
+    CompanyWxClientMapper companyWxClientMapper;
+
     /**
      * 初始化工作流
      * 创建工作流实例并保存初始状态
@@ -616,13 +619,15 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
 
     /**
      * 加微成功后流程唤醒操作
-     * @param workflowInstanceId
-     * @param nodeKey
-     * @param accountId
-     * @param remark
+     * @param object
      */
     @Async("cidWorkFlowExecutor")
-    public void addWxSuccess(String workflowInstanceId, String nodeKey,Long accountId,String remark){
+    public void addWxSuccess(JSONObject object){
+
+        String workflowInstanceId = object.getString("instanceId");
+        String nodeKey = object.getString("nodeKey");
+        Long accountId = object.getLong("accountId");
+        String remark = object.getString("remark");
         if(StringUtils.isBlank(remark) || StringUtils.isBlank(workflowInstanceId) || StringUtils.isBlank(nodeKey) || accountId == null){
             log.error("addWxSuccess: 参数错误,workflowInstanceId:{},nodeKey:{},accountId:{},remark:{}", workflowInstanceId, nodeKey, accountId, remark);
             return;
@@ -632,6 +637,60 @@ public class CompanyWorkflowEngineImpl implements CompanyWorkflowEngine {
             companyWxAccount.setRemark( remark);
             companyWxAccountMapper.updateCompanyWxAccount(companyWxAccount);
         }
+
+        // 加载当前执行记录
+        CompanyAiWorkflowExec currentExec = currentExecutionMapper.selectByWorkflowInstanceId(workflowInstanceId);
+
+        if (currentExec == null) {
+            throw new CustomException("工作流实例不存在: " + workflowInstanceId);
+        }
+        //更新加微结果到wxClient表
+        updateWxClientSuccessByWorkflowInstanceId(workflowInstanceId);
+        // 验证当前节点是否匹配
+        if (!nodeKey.equals(currentExec.getCurrentNodeKey())) {
+            log.error("节点不匹配 - 期望: {}, 实际: {}", nodeKey, currentExec.getCurrentNodeKey());
+            return;
+        }
+
+        // 检查当前工作流是否处于暂停状态
+        if (!Integer.valueOf(ExecutionStatusEnum.PAUSED.getValue()).equals(currentExec.getStatus()) &&
+                !Integer.valueOf(ExecutionStatusEnum.WAITING.getValue()).equals(currentExec.getStatus())) {
+            log.error("工作流未处于暂停状态,无法唤醒::{} " , workflowInstanceId);
+        }
+
+        // 反序列化执行上下文并合并新的输入数据
+        ExecutionContext context = deserializeContext(currentExec);
+
+        // 加载工作流定义
+        CompanyWorkflow definition = loadCompanyWorkflow(currentExec.getWorkflowId());
+
+        // 创建节点实例
+        IWorkflowNode node = createNode(definition, nodeKey);
+        if (node == null) {
+            throw new CustomException("节点不存在: " + nodeKey);
+        }
+
+        // 继续执行节点逻辑
+        ExecutionResult result = node.continueExecute(context);
+
+    }
+
+    /**
+     * 更新加微结果到wxClient表
+     * @param workflowInstanceId
+     */
+    public void updateWxClientSuccessByWorkflowInstanceId(String workflowInstanceId){
+        Long id = currentExecutionMapper.selectWxClientIdByWorkflowInstanceId(workflowInstanceId);
+        CompanyWxClient client = companyWxClientMapper.selectCompanyWxClientById(id);
+        if(null != client && !Integer.valueOf(1).equals(client.getIsAdd())){
+            //更新wxClient表
+            CompanyWxClient companyWxClient = new CompanyWxClient();
+            companyWxClient.setId(id);
+            companyWxClient.setIsAdd(1);
+            companyWxClient.setSuccessAddTime(LocalDateTime.now());
+            companyWxClientMapper.updateCompanyWxClient(companyWxClient);
+        }
+
     }
 
 }

+ 15 - 15
fs-service/src/main/java/com/fs/company/service/impl/CompanyWxServiceImpl.java

@@ -587,19 +587,19 @@ public class CompanyWxServiceImpl extends ServiceImpl<CompanyWxAccountMapper, Co
     public void triggerWorkflowOnAddWxSuccess(Long wxClientId) {
         try {
             // 先查老类型的等待中工作流实例
-            CompanyAiWorkflowExec waitingExec = companyAiWorkflowExecMapper.selectWaitingAddWxWorkflowByWxClientId(
-                    wxClientId,
-                    ExecutionStatusEnum.WAITING.getValue(),
-                    NodeTypeEnum.AI_ADD_WX_TASK.getValue());
-            boolean isNewNodeType = false;
-            // 老类型未找到,再查新类型
-            if (waitingExec == null) {
-                waitingExec = companyAiWorkflowExecMapper.selectWaitingAddWxWorkflowByWxClientId(
+//            CompanyAiWorkflowExec waitingExec = companyAiWorkflowExecMapper.selectWaitingAddWxWorkflowByWxClientId(
+//                    wxClientId,
+//                    ExecutionStatusEnum.WAITING.getValue(),
+//                    NodeTypeEnum.AI_ADD_WX_TASK.getValue());
+//            boolean isNewNodeType = false;
+//            // 老类型未找到,再查新类型
+//            if (waitingExec == null) {
+            CompanyAiWorkflowExec  waitingExec = companyAiWorkflowExecMapper.selectWaitingAddWxWorkflowByWxClientId(
                         wxClientId,
                         ExecutionStatusEnum.WAITING.getValue(),
                         NodeTypeEnum.AI_ADD_WX_TASK_NEW.getValue());
-                isNewNodeType = true;
-            }
+//                isNewNodeType = true;
+//            }
 
             if (waitingExec == null) {
                 log.info("未找到等待中的加微工作流实例 - wxClientId: {}", wxClientId);
@@ -609,7 +609,7 @@ public class CompanyWxServiceImpl extends ServiceImpl<CompanyWxAccountMapper, Co
             //查询工作流加微执行日志是否未更新状态
             CompanyAiWorkflowExecLog queryP = new CompanyAiWorkflowExecLog();
             queryP.setWorkflowInstanceId(waitingExec.getWorkflowInstanceId());
-            queryP.setNodeType(isNewNodeType ? NodeTypeEnum.AI_ADD_WX_TASK_NEW.getValue() : NodeTypeEnum.AI_ADD_WX_TASK.getValue());
+            queryP.setNodeType(NodeTypeEnum.AI_ADD_WX_TASK_NEW.getValue());
             queryP.setStatus(ExecutionStatusEnum.WAITING.getValue());
             List<CompanyAiWorkflowExecLog> companyAiWorkflowExecLogs = companyAiWorkflowExecLogMapper.selectCompanyAiWorkflowExecLogList(queryP);
             companyAiWorkflowExecLogs.forEach(log -> {
@@ -626,11 +626,11 @@ public class CompanyWxServiceImpl extends ServiceImpl<CompanyWxAccountMapper, Co
 
             // 互斥检查:根据节点类型使用对应的互斥方法
             boolean canExecute;
-            if (isNewNodeType) {
+//            if (isNewNodeType) {
                 canExecute = AiAddWxTaskNewNode.tryMarkAsExecuted(workflowInstanceId, wxClientId);
-            } else {
-                canExecute = AiAddWxTaskNode.tryMarkAsExecuted(workflowInstanceId, wxClientId);
-            }
+//            } else {
+//                canExecute = AiAddWxTaskNode.tryMarkAsExecuted(workflowInstanceId, wxClientId);
+//            }
             if (!canExecute) {
                 log.info("工作流已被其他路径执行,跳过 - workflowInstanceId: {}, wxClientId: {}",
                         workflowInstanceId, wxClientId);

+ 15 - 2
fs-service/src/main/java/com/fs/company/service/impl/call/node/AbstractWorkflowNode.java

@@ -205,7 +205,8 @@ public abstract class AbstractWorkflowNode implements IWorkflowNode {
             fExecLog.setStatus(status.getValue());
             fExecLog.setEndTime(new Date());
             long durationInMillis = fExecLog.getEndTime().getTime() - fExecLog.getStartTime().getTime();
-            fExecLog.setDuration(durationInMillis);
+            // 兜底防护:确保duration不为负数;当计算结果<=0时设为1毫秒以显得更加真实
+            fExecLog.setDuration(durationInMillis > 0 ? durationInMillis : 1);
             companyAiWorkflowExecLogMapper.updateById(fExecLog);
         }else{
             log.error("未更新到节点状态:context:{},findS:{},targetS:{}",context,findStatus,status);
@@ -364,14 +365,21 @@ public abstract class AbstractWorkflowNode implements IWorkflowNode {
             logEntry.setErrorMessage(result.getErrorMessage());
             logEntry.setCreatedTime(new Date());
             Long startTime = context.getVariable("node_start_time_" + nodeKey, Long.class);
+            Long endTime = context.getVariable("node_end_time_" + nodeKey, Long.class);
+            // 当startTime为null但endTime不为null时(如获取锁失败、preExecute未执行),
+            // 不能用new Date()作为startTime的fallback,因为此时new Date()可能晚于endTime,
+            // 导致duration计算为负数。应将startTime对齐到endTime,表示节点未能正常启动
             if (null != startTime) {
                 logEntry.setStartTime(new Date(startTime));
+            } else if (null != endTime) {
+                logEntry.setStartTime(new Date(endTime));
             } else {
                 logEntry.setStartTime(new Date());
             }
-            Long endTime = context.getVariable("node_end_time_" + nodeKey, Long.class);
             if (null != endTime) {
                 logEntry.setEndTime(new Date(endTime));
+            } else if (null != startTime) {
+                logEntry.setEndTime(new Date(startTime));
             } else {
                 logEntry.setEndTime(new Date());
             }
@@ -379,6 +387,11 @@ public abstract class AbstractWorkflowNode implements IWorkflowNode {
             if (null != startTime && null != endTime) {
                 duration = endTime - startTime;
             }
+            // 兜底防护:确保duration不为负数;当使用fallback值(startTime或endTime为null)时,
+            // 最小耗时设为1毫秒以显得更加真实
+            if (duration <= 0) {
+                duration = (null != startTime && null != endTime) ? 0 : 1;
+            }
             logEntry.setDuration(duration);
             return logEntry;
         } catch (JsonProcessingException e) {

+ 7 - 1
fs-service/src/main/java/com/fs/company/service/impl/call/node/AiAddWxTaskNewNode.java

@@ -1,5 +1,6 @@
 package com.fs.company.service.impl.call.node;
 
+import cn.hutool.core.util.RandomUtil;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCacheT;
@@ -94,6 +95,7 @@ public class AiAddWxTaskNewNode extends AbstractWorkflowNode {
             }
 
             WxContact wxQuery = companyAiWorkflowExecMapper.selectWxContectByWorkflowInstanceId(context.getWorkflowInstanceId());
+            wxQuery.setRemark(wxQuery.getRemark() + RandomUtil.randomNumbers(10));
             wxQuery.setNickName(wxQuery.getRemark());
             wxQuery.setFriends(0);
             wxContactMapper.insert(wxQuery);
@@ -313,6 +315,10 @@ public class AiAddWxTaskNewNode extends AbstractWorkflowNode {
     @Override
     protected void postExecute(ExecutionContext context, ExecutionResult result) {
         super.postExecute(context, result);
-        doneAddwx(context.getWorkflowInstanceId());
+        // 仅当节点成功进入PAUSED状态时才执行doneAddwx,
+        // 避免在result为null(执行异常)或FAILURE(加微准备失败)时仍继续流转流程
+        if (result != null && result.getStatus() == ExecutionStatusEnum.PAUSED) {
+            doneAddwx(context.getWorkflowInstanceId());
+        }
     }
 }

+ 2 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java

@@ -54,4 +54,6 @@ public class FsCourseSendRewardUParam implements Serializable
 
     private String code;
 
+    private Long watchLogId;
+
 }

+ 2 - 2
fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java

@@ -288,7 +288,7 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
     R createAppFd(LuckyBagCollectRecord param);
 
     /**
-    * app发红包
+    * app发红包 1 自动发课 2 手动发课
     */
-    R withdrawal(FsCourseSendRewardUParam param);
+    R withdrawal(FsCourseSendRewardUParam param,Integer type);
 }

+ 28 - 5
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1607,7 +1607,13 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             switch (config.getRewardType()) {
                 // 红包奖励
                 case 1:
-                    return sendRedPacketReward(param, user, watchLog, video, config);
+                    if (param.getSource() == 3){
+                        param.setWatchLogId(watchLog.getLogId());
+                        return withdrawal(param,1);
+                    } else {
+                        return sendRedPacketReward(param, user, watchLog, video, config);
+                    }
+//                    return sendRedPacketReward(param, user, watchLog, video, config);
                 // 积分奖励
                 case 2:
                     return sendIntegralReward(param, user, watchLog, config);
@@ -4891,7 +4897,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
      */
     @Override
     @Transactional
-    public R withdrawal(FsCourseSendRewardUParam param) {
+    public R withdrawal(FsCourseSendRewardUParam param,Integer type) {
         Long userId = param.getUserId();
         // 生成锁的key,基于用户ID和视频ID确保同一用户同一视频的请求被锁定
         String lockKey = "reward_red_lock:user:" + userId;
@@ -4906,7 +4912,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             }
 
             logger.info("成功获取锁,开始处理奖励发放,用户ID:{}", userId);
-            return executeWithdrawal(param);
+            return executeWithdrawal(param,type);
 
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
@@ -5028,7 +5034,7 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     }
 
 
-    private R executeWithdrawal(FsCourseSendRewardUParam param) {
+    private R executeWithdrawal(FsCourseSendRewardUParam param, Integer type) {
         log.info("进入用户判断");
         FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
         if (user == null) {
@@ -5097,8 +5103,12 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
                 }
                 packetParam.setOpenId(openId);
                 BeanUtils.copyProperties(param, packetParam);
+                if (type==1){
+                    return sendAppRedPacketAuto(packetParam, log,video, config);
+                }else if (type==2){
+                    return sendAppRedPacket(packetParam, log,video, config);
+                }
 
-                return sendAppRedPacket(packetParam, log,video, config);
             // 积分奖励
             case 2:
                 return sendIntegralReward(param, user, log, config);
@@ -5334,6 +5344,9 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     }
 
 
+    /**
+    * app手动看课的
+    */
     private R sendAppRedPacket(WxSendRedPacketParam packetParam,FsCourseWatchLog log,FsUserCourseVideo video,CourseConfig config) {
         FsUserCoursePeriodDays periodDays = new FsUserCoursePeriodDays();
         periodDays.setVideoId(log.getVideoId());
@@ -5347,6 +5360,14 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             return R.error(403, "已超过领取红包时间");
         }
 
+        // 手动的就多一个查询手动营期的时间 ,所以这里直接复用
+        return sendAppRedPacketAuto(packetParam,log,video,config);
+    }
+
+    /**
+    * app自动看课的
+    */
+    private R sendAppRedPacketAuto(WxSendRedPacketParam packetParam,FsCourseWatchLog log,FsUserCourseVideo video,CourseConfig config) {
 
         // 确定红包金额
         BigDecimal amount = BigDecimal.ZERO;
@@ -5568,6 +5589,8 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         }
     }
 
+
+
     /**
      * 获取用户openId
      */

+ 41 - 0
fs-service/src/main/java/com/fs/decoration/domain/DecorationComponent.java

@@ -0,0 +1,41 @@
+package com.fs.decoration.domain;
+
+import lombok.Data;
+import java.util.Date;
+
+/**
+ * 组件定义实体
+ */
+@Data
+public class DecorationComponent {
+
+    /** 主键ID */
+    private Long id;
+
+    /** 组件编码,例如 banner、goodsList */
+    private String componentCode;
+
+    /** 组件名称,例如 顶部轮播 */
+    private String componentName;
+
+    /** 组件类型编码,例如 basic、goods */
+    private String componentTypeCode;
+
+    /** 图标 */
+    private String icon;
+
+    /** 缩略图 */
+    private String previewImage;
+
+    /** 状态:1启用,0停用 */
+    private Integer status;
+
+    /** 备注 */
+    private String remark;
+
+    /** 创建时间 */
+    private Date createTime;
+
+    /** 更新时间 */
+    private Date updateTime;
+}

+ 32 - 0
fs-service/src/main/java/com/fs/decoration/domain/DecorationComponentType.java

@@ -0,0 +1,32 @@
+package com.fs.decoration.domain;
+
+import lombok.Data;
+import java.util.Date;
+
+/**
+ * 组件类型实体
+ */
+@Data
+public class DecorationComponentType {
+
+    /** 主键ID */
+    private Long id;
+
+    /** 类型编码,例如 basic */
+    private String typeCode;
+
+    /** 类型名称,例如 基础组件 */
+    private String typeName;
+
+    /** 状态:1启用,0停用 */
+    private Integer status;
+
+    /** 备注 */
+    private String remark;
+
+    /** 创建时间 */
+    private Date createTime;
+
+    /** 更新时间 */
+    private Date updateTime;
+}

+ 38 - 0
fs-service/src/main/java/com/fs/decoration/domain/DecorationTemplate.java

@@ -0,0 +1,38 @@
+package com.fs.decoration.domain;
+
+import lombok.Data;
+import java.util.Date;
+
+/**
+ * 模板实体
+ */
+@Data
+public class DecorationTemplate {
+
+    /** 主键ID */
+    private Long id;
+
+    /** 模板名称 */
+    private String templateName;
+
+    /** 模板类型 */
+    private String templateType;
+
+    /** 模板封面 */
+    private String coverUrl;
+
+    /** 模板JSON */
+    private String templateData;
+
+    /** 状态:1启用,0停用 */
+    private Integer status;
+
+    /** 备注 */
+    private String remark;
+
+    /** 创建时间 */
+    private Date createTime;
+
+    /** 更新时间 */
+    private Date updateTime;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/decoration/dto/DecorationComponentQuery.java

@@ -0,0 +1,19 @@
+package com.fs.decoration.dto;
+
+import lombok.Data;
+
+/**
+ * 组件查询参数
+ */
+@Data
+public class DecorationComponentQuery {
+
+    /** 组件名称,模糊查询 */
+    private String componentName;
+
+    /** 组件类型编码 */
+    private String componentTypeCode;
+
+    /** 状态 */
+    private Integer status;
+}

+ 34 - 0
fs-service/src/main/java/com/fs/decoration/dto/DecorationComponentSaveReq.java

@@ -0,0 +1,34 @@
+package com.fs.decoration.dto;
+
+import lombok.Data;
+
+/**
+ * 组件新增/修改参数
+ */
+@Data
+public class DecorationComponentSaveReq {
+
+    /** 主键ID,新增时为空 */
+    private Long id;
+
+    /** 组件编码 */
+    private String componentCode;
+
+    /** 组件名称 */
+    private String componentName;
+
+    /** 组件类型编码 */
+    private String componentTypeCode;
+
+    /** 图标 */
+    private String icon;
+
+    /** 缩略图 */
+    private String previewImage;
+
+    /** 状态 */
+    private Integer status;
+
+    /** 备注 */
+    private String remark;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/decoration/dto/DecorationComponentStatusReq.java

@@ -0,0 +1,16 @@
+package com.fs.decoration.dto;
+
+import lombok.Data;
+
+/**
+ * 组件状态修改参数
+ */
+@Data
+public class DecorationComponentStatusReq {
+
+    /** 组件ID */
+    private Long id;
+
+    /** 状态:1启用,0停用 */
+    private Integer status;
+}

+ 31 - 0
fs-service/src/main/java/com/fs/decoration/dto/DecorationTemplateSaveReq.java

@@ -0,0 +1,31 @@
+package com.fs.decoration.dto;
+
+import lombok.Data;
+
+/**
+ * 模板保存参数
+ */
+@Data
+public class DecorationTemplateSaveReq {
+
+    /** 主键ID,新增时为空 */
+    private Long id;
+
+    /** 模板名称 */
+    private String templateName;
+
+    /** 模板类型 */
+    private String templateType;
+
+    /** 模板封面 */
+    private String coverUrl;
+
+    /** 模板JSON */
+    private String templateData;
+
+    /** 状态 */
+    private Integer status;
+
+    /** 备注 */
+    private String remark;
+}

+ 43 - 0
fs-service/src/main/java/com/fs/decoration/mapper/DecorationComponentMapper.java

@@ -0,0 +1,43 @@
+package com.fs.decoration.mapper;
+
+import com.fs.decoration.domain.DecorationComponent;
+import com.fs.decoration.dto.DecorationComponentQuery;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 组件定义 Mapper
+ */
+public interface DecorationComponentMapper {
+
+    /**
+     * 查询组件列表
+     */
+    List<DecorationComponent> selectList(DecorationComponentQuery query);
+
+    /**
+     * 根据ID查询组件
+     */
+    DecorationComponent selectById(@Param("id") Long id);
+
+    /**
+     * 根据组件编码查询组件
+     */
+    DecorationComponent selectByComponentCode(@Param("componentCode") String componentCode);
+
+    /**
+     * 新增组件
+     */
+    int insert(DecorationComponent entity);
+
+    /**
+     * 修改组件
+     */
+    int updateById(DecorationComponent entity);
+
+    /**
+     * 删除组件
+     */
+    int deleteById(@Param("id") Long id);
+}

+ 15 - 0
fs-service/src/main/java/com/fs/decoration/mapper/DecorationComponentTypeMapper.java

@@ -0,0 +1,15 @@
+package com.fs.decoration.mapper;
+
+import com.fs.decoration.domain.DecorationComponentType;
+import java.util.List;
+
+/**
+ * 组件类型 Mapper
+ */
+public interface DecorationComponentTypeMapper {
+
+    /**
+     * 查询启用的组件类型列表
+     */
+    List<DecorationComponentType> selectList();
+}

+ 37 - 0
fs-service/src/main/java/com/fs/decoration/mapper/DecorationTemplateMapper.java

@@ -0,0 +1,37 @@
+package com.fs.decoration.mapper;
+
+import com.fs.decoration.domain.DecorationTemplate;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 模板 Mapper
+ */
+public interface DecorationTemplateMapper {
+
+    /**
+     * 查询模板列表
+     */
+    List<DecorationTemplate> selectList();
+
+    /**
+     * 根据ID查询模板
+     */
+    DecorationTemplate selectById(@Param("id") Long id);
+
+    /**
+     * 新增模板
+     */
+    int insert(DecorationTemplate entity);
+
+    /**
+     * 修改模板
+     */
+    int updateById(DecorationTemplate entity);
+
+    /**
+     * 删除模板
+     */
+    int deleteById(@Param("id") Long id);
+}

+ 38 - 0
fs-service/src/main/java/com/fs/decoration/service/IDecorationComponentService.java

@@ -0,0 +1,38 @@
+package com.fs.decoration.service;
+
+import com.fs.decoration.domain.DecorationComponent;
+import com.fs.decoration.dto.DecorationComponentQuery;
+import com.fs.decoration.dto.DecorationComponentSaveReq;
+
+import java.util.List;
+
+/**
+ * 组件定义 Service
+ */
+public interface IDecorationComponentService {
+
+    /**
+     * 查询组件列表
+     */
+    List<DecorationComponent> selectList(DecorationComponentQuery query);
+
+    /**
+     * 查询组件详情
+     */
+    DecorationComponent selectById(Long id);
+
+    /**
+     * 新增/修改组件
+     */
+    Long saveComponent(DecorationComponentSaveReq req);
+
+    /**
+     * 删除组件
+     */
+    int deleteById(Long id);
+
+    /**
+     * 修改状态
+     */
+    int updateStatus(Long id, Integer status);
+}

+ 16 - 0
fs-service/src/main/java/com/fs/decoration/service/IDecorationComponentTypeService.java

@@ -0,0 +1,16 @@
+package com.fs.decoration.service;
+
+import com.fs.decoration.vo.OptionVO;
+
+import java.util.List;
+
+/**
+ * 组件类型 Service
+ */
+public interface IDecorationComponentTypeService {
+
+    /**
+     * 查询组件类型下拉
+     */
+    List<OptionVO> listOptions();
+}

+ 32 - 0
fs-service/src/main/java/com/fs/decoration/service/IDecorationTemplateService.java

@@ -0,0 +1,32 @@
+package com.fs.decoration.service;
+
+import com.fs.decoration.domain.DecorationTemplate;
+import com.fs.decoration.dto.DecorationTemplateSaveReq;
+
+import java.util.List;
+
+/**
+ * 模板 Service
+ */
+public interface IDecorationTemplateService {
+
+    /**
+     * 查询模板列表
+     */
+    List<DecorationTemplate> selectList();
+
+    /**
+     * 查询模板详情
+     */
+    DecorationTemplate selectById(Long id);
+
+    /**
+     * 新增/修改模板
+     */
+    Long saveTemplate(DecorationTemplateSaveReq req);
+
+    /**
+     * 删除模板
+     */
+    int deleteById(Long id);
+}

+ 117 - 0
fs-service/src/main/java/com/fs/decoration/service/impl/DecorationComponentServiceImpl.java

@@ -0,0 +1,117 @@
+package com.fs.decoration.service.impl;
+
+import com.fs.decoration.domain.DecorationComponent;
+import com.fs.decoration.dto.DecorationComponentQuery;
+import com.fs.decoration.dto.DecorationComponentSaveReq;
+import com.fs.decoration.mapper.DecorationComponentMapper;
+import com.fs.decoration.service.IDecorationComponentService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 组件定义 Service 实现
+ */
+@Service
+public class DecorationComponentServiceImpl implements IDecorationComponentService {
+
+    @Resource
+    private DecorationComponentMapper decorationComponentMapper;
+
+    @Override
+    public List<DecorationComponent> selectList(DecorationComponentQuery query) {
+        return decorationComponentMapper.selectList(query);
+    }
+
+    @Override
+    public DecorationComponent selectById(Long id) {
+        return decorationComponentMapper.selectById(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long saveComponent(DecorationComponentSaveReq req) {
+        // 基础校验
+        if (req.getComponentCode() == null || req.getComponentCode().trim().isEmpty()) {
+            throw new RuntimeException("组件编码不能为空");
+        }
+        if (req.getComponentName() == null || req.getComponentName().trim().isEmpty()) {
+            throw new RuntimeException("组件名称不能为空");
+        }
+        if (req.getComponentTypeCode() == null || req.getComponentTypeCode().trim().isEmpty()) {
+            throw new RuntimeException("组件类型不能为空");
+        }
+
+        // 根据组件编码检查是否已存在
+        DecorationComponent exist = decorationComponentMapper.selectByComponentCode(req.getComponentCode());
+
+        // 新增
+        if (req.getId() == null) {
+            if (exist != null) {
+                throw new RuntimeException("组件编码已存在");
+            }
+
+            DecorationComponent entity = new DecorationComponent();
+            entity.setComponentCode(req.getComponentCode());
+            entity.setComponentName(req.getComponentName());
+            entity.setComponentTypeCode(req.getComponentTypeCode());
+            entity.setIcon(req.getIcon());
+            entity.setPreviewImage(req.getPreviewImage());
+            entity.setStatus(req.getStatus() == null ? 1 : req.getStatus());
+            entity.setRemark(req.getRemark());
+
+            decorationComponentMapper.insert(entity);
+            return entity.getId();
+        }
+
+        // 修改
+        DecorationComponent old = decorationComponentMapper.selectById(req.getId());
+        if (old == null) {
+            throw new RuntimeException("组件不存在");
+        }
+
+        // 如果存在同编码记录,但不是自己,说明编码冲突
+        if (exist != null && !exist.getId().equals(req.getId())) {
+            throw new RuntimeException("组件编码已存在");
+        }
+
+        DecorationComponent entity = new DecorationComponent();
+        entity.setId(req.getId());
+        entity.setComponentCode(req.getComponentCode());
+        entity.setComponentName(req.getComponentName());
+        entity.setComponentTypeCode(req.getComponentTypeCode());
+        entity.setIcon(req.getIcon());
+        entity.setPreviewImage(req.getPreviewImage());
+        entity.setStatus(req.getStatus() == null ? old.getStatus() : req.getStatus());
+        entity.setRemark(req.getRemark());
+
+        decorationComponentMapper.updateById(entity);
+        return entity.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteById(Long id) {
+        DecorationComponent old = decorationComponentMapper.selectById(id);
+        if (old == null) {
+            throw new RuntimeException("组件不存在");
+        }
+        return decorationComponentMapper.deleteById(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int updateStatus(Long id, Integer status) {
+        DecorationComponent old = decorationComponentMapper.selectById(id);
+        if (old == null) {
+            throw new RuntimeException("组件不存在");
+        }
+
+        DecorationComponent entity = new DecorationComponent();
+        entity.setId(id);
+        entity.setStatus(status);
+        return decorationComponentMapper.updateById(entity);
+    }
+}

+ 29 - 0
fs-service/src/main/java/com/fs/decoration/service/impl/DecorationComponentTypeServiceImpl.java

@@ -0,0 +1,29 @@
+package com.fs.decoration.service.impl;
+
+import com.fs.decoration.domain.DecorationComponentType;
+import com.fs.decoration.mapper.DecorationComponentTypeMapper;
+import com.fs.decoration.service.IDecorationComponentTypeService;
+import com.fs.decoration.vo.OptionVO;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 组件类型 Service 实现
+ */
+@Service
+public class DecorationComponentTypeServiceImpl implements IDecorationComponentTypeService {
+
+    @Resource
+    private DecorationComponentTypeMapper decorationComponentTypeMapper;
+
+    @Override
+    public List<OptionVO> listOptions() {
+        List<DecorationComponentType> list = decorationComponentTypeMapper.selectList();
+        return list.stream()
+                .map(item -> new OptionVO(item.getTypeCode(), item.getTypeName()))
+                .collect(Collectors.toList());
+    }
+}

+ 95 - 0
fs-service/src/main/java/com/fs/decoration/service/impl/DecorationTemplateServiceImpl.java

@@ -0,0 +1,95 @@
+package com.fs.decoration.service.impl;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.decoration.domain.DecorationTemplate;
+import com.fs.decoration.dto.DecorationTemplateSaveReq;
+import com.fs.decoration.mapper.DecorationTemplateMapper;
+import com.fs.decoration.service.IDecorationTemplateService;
+import com.fs.decoration.vo.TemplateContentVO;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 模板 Service 实现
+ */
+@Service
+public class DecorationTemplateServiceImpl implements IDecorationTemplateService {
+
+    @Resource
+    private DecorationTemplateMapper decorationTemplateMapper;
+
+    /** 用于校验 templateData JSON 结构 */
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    public List<DecorationTemplate> selectList() {
+        return decorationTemplateMapper.selectList();
+    }
+
+    @Override
+    public DecorationTemplate selectById(Long id) {
+        return decorationTemplateMapper.selectById(id);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long saveTemplate(DecorationTemplateSaveReq req) {
+        if (req.getTemplateName() == null || req.getTemplateName().trim().isEmpty()) {
+            throw new RuntimeException("模板名称不能为空");
+        }
+        if (req.getTemplateData() == null || req.getTemplateData().trim().isEmpty()) {
+            throw new RuntimeException("模板数据不能为空");
+        }
+
+        // 校验模板 JSON 格式是否正确
+        validateTemplateData(req.getTemplateData());
+
+        DecorationTemplate entity = new DecorationTemplate();
+        entity.setId(req.getId());
+        entity.setTemplateName(req.getTemplateName());
+        entity.setTemplateType(req.getTemplateType());
+        entity.setCoverUrl(req.getCoverUrl());
+        entity.setTemplateData(req.getTemplateData());
+        entity.setStatus(req.getStatus() == null ? 1 : req.getStatus());
+        entity.setRemark(req.getRemark());
+
+        if (req.getId() == null) {
+            decorationTemplateMapper.insert(entity);
+        } else {
+            DecorationTemplate old = decorationTemplateMapper.selectById(req.getId());
+            if (old == null) {
+                throw new RuntimeException("模板不存在");
+            }
+            decorationTemplateMapper.updateById(entity);
+        }
+
+        return entity.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteById(Long id) {
+        DecorationTemplate old = decorationTemplateMapper.selectById(id);
+        if (old == null) {
+            throw new RuntimeException("模板不存在");
+        }
+        return decorationTemplateMapper.deleteById(id);
+    }
+
+    /**
+     * 校验模板 JSON 格式
+     */
+    private void validateTemplateData(String templateData) {
+        try {
+            TemplateContentVO content = objectMapper.readValue(templateData, TemplateContentVO.class);
+            if (content == null) {
+                throw new RuntimeException("模板JSON不能为空");
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("模板JSON格式错误:" + e.getMessage());
+        }
+    }
+}

+ 18 - 0
fs-service/src/main/java/com/fs/decoration/vo/OptionVO.java

@@ -0,0 +1,18 @@
+package com.fs.decoration.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * 下拉选项VO
+ */
+@Data
+@AllArgsConstructor
+public class OptionVO {
+
+    /** value 值 */
+    private String value;
+
+    /** label 文本 */
+    private String label;
+}

+ 37 - 0
fs-service/src/main/java/com/fs/decoration/vo/TemplateContentVO.java

@@ -0,0 +1,37 @@
+package com.fs.decoration.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 模板JSON结构校验VO
+ */
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class TemplateContentVO {
+
+    /** 页面配置 */
+    private PageConfigVO pageConfig;
+
+    /** 页面组件列表 */
+    private List<ComponentItemVO> components;
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class PageConfigVO {
+        /** 模板名称 */
+        private String templateName;
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class ComponentItemVO {
+        /** 页面中组件实例ID */
+        private String id;
+
+        /** 组件编码 */
+        private String componentCode;
+    }
+}

+ 4 - 0
fs-service/src/main/java/com/fs/his/mapper/FsAdvMapper.java

@@ -3,6 +3,7 @@ package com.fs.his.mapper;
 import java.util.List;
 import com.fs.his.domain.FsAdv;
 import com.fs.his.param.FsAdvUParam;
+import com.fs.hisStore.vo.FsAdvListQueryVO;
 import org.apache.ibatis.annotations.Select;
 
 /**
@@ -76,4 +77,7 @@ public interface FsAdvMapper
             "    order by status desc,sort,adv_id desc "+
             "</script>"})
     List<FsAdv> selectFsAdvListUVO(FsAdvUParam map);
+
+    @Select("select * from fs_adv where adv_type=#{advType} and status=1 order by sort desc")
+    List<FsAdvListQueryVO> selectFsAdvListQuery(Integer advType);
 }

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

@@ -5,6 +5,7 @@ import java.util.Map;
 
 import com.fs.his.domain.FsArticleCate;
 import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.vo.FsArticleCateListQueryVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
@@ -68,4 +69,8 @@ public interface FsArticleCateMapper
     List<OptionsVO> selectFsArticleCateAllList();
 
     List<FsArticleCate> selectFsArticleCateAllListVO(@Param("params") Map<String, Object> params);
+
+    @Select("select * from fs_article_cate where status=1 and is_del=0 order by sort asc " )
+    List<FsArticleCateListQueryVO> selectFsArticleCateListQuery();
+
 }

+ 22 - 0
fs-service/src/main/java/com/fs/his/mapper/FsArticleMapper.java

@@ -6,6 +6,8 @@ import com.fs.his.param.FsArticleListUParam;
 import com.fs.his.vo.FsArticleListUVO;
 import com.fs.his.vo.FsArticleListVO;
 import com.fs.his.vo.FsArticleVO;
+import com.fs.store.param.FsArticleQueryParam;
+import com.fs.store.vo.FsArticleListQueryVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -90,4 +92,24 @@ public interface FsArticleMapper
             "</script>"})
 
     List<FsArticleListVO> selectFsArticleListVO(FsArticle fsArticle);
+
+
+    @Select({"<script> " +
+            "select a.* from fs_article a  " +
+            "where 1=1 " +
+            "<if test = 'maps.isTui != null    '> " +
+            "and a.is_tui =#{maps.isTui} " +
+            "</if>" +
+            "<if test = 'maps.cateId != null and maps.cateId != 0    '> " +
+            "and a.cate_id =#{maps.cateId} " +
+            "</if>" +
+            "<if test = 'maps.keyword != null and maps.keyword != \"\"    '> " +
+            "and a.title like concat('%', #{maps.keyword}, '%') " +
+            "</if>" +
+            " order by a.sort desc "+
+            "<if test = 'maps.limit != null    '> " +
+            "limit #{maps.limit} " +
+            "</if>" +
+            "</script>"})
+    List<FsArticleListQueryVO> selectFsArticleListQuery(@Param("maps") FsArticleQueryParam param);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/his/mapper/FsCityMapper.java

@@ -73,4 +73,6 @@ public interface FsCityMapper
     @Select("SELECT city_id,city_name FROM fs_city where parent_id=0 ")
     List<CitysAreaVO> getCitysArea();
 
+    @Select("select * from fs_city where is_show=1")
+    List<FsCity> selectFsCitys();
 }

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

@@ -243,8 +243,13 @@ public interface FsPrescribeMapper
             "OR drug_doctor_sign_url LIKE '%htj-1258038825.cos.ap-beijing.myqcloud.com%' \n" +
             "order by  prescribe_id desc ")
     List<FsPrescribe> selectFsPrescribeByPrescribeByimg();
+
     @Select("select prescribe_code from fs_prescribe where store_order_id=#{orderId}")
     String selectFsPrescribeCodeByOrderId(String  orderId);
+
+    @Select("select * from fs_prescribe where store_order_id=#{orderId} order by prescribe_id desc  limit 1 ")
+    FsPrescribe selectFsPrescribeByOrderId(Long orderId);
+
     @Select("select prescribe_code from fs_prescribe where store_order_id=(select order_id from fs_store_order where order_code=#{businessCode} )")
     String selectFsPrescribeCodeByOrderCode(String businessCode);
     @Select({"<script> " +

+ 7 - 0
fs-service/src/main/java/com/fs/his/mapper/FsStoreProductMapper.java

@@ -6,6 +6,7 @@ import com.fs.his.domain.FsStoreProductRule;
 import com.fs.his.param.FsProductAttrValueParam;
 import com.fs.his.param.FsStoreProductListSParam;
 import com.fs.his.vo.*;
+import com.fs.store.vo.FsStoreProductActivityListVO;
 import com.fs.hisStore.vo.FsStoreProductListVO;
 import com.fs.live.domain.LiveGoods;
 import org.apache.ibatis.annotations.Param;
@@ -283,4 +284,10 @@ public interface FsStoreProductMapper {
     List<FsStoreProductListVO> liveList(@Param("maps") LiveGoods maps);
 
     FsStoreProduct selectFsStoreProductByBarCode(@Param("barCode") String barCode);
+
+    @Select({"<script> " +
+            "select * from fs_store_product where find_in_set(product_id,#{ids})  " +
+            "</script>"})
+    List<FsStoreProductActivityListVO> selectFsStoreProductByIds(@Param("ids")String productIds);
+
 }

+ 6 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserAddressMapper.java

@@ -91,4 +91,10 @@ public interface FsUserAddressMapper
             "            <if test=\"isDefault != null \"> and is_default = #{isDefault}</if>" +
             "</script>"})
     Long selectFsUserAddressListCount(FsUserAddress fsUserAddress);
+
+    @Select("select city_id from fs_city where name=#{city} and level = 1 limit 1 ")
+    Long selectFsUserAddressByCityName(String city);
+
+    @Update(" update fs_user_address set is_del=1 where user_id = #{userId}")
+    Integer delAllAddress(Long userId);
 }

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

@@ -506,4 +506,6 @@ public interface FsUserMapper
     List<AppSalesCourseStatisticsVO> selectAppSalesUserCountVO(FsCourseWatchLogStatisticsListParam param);
 
     List<AppSalesCourseStatisticsVO> selectAppSalesNewUserCountVO(FsCourseWatchLogStatisticsListParam param);
+
+    int updateMpOpenIdByUserId(@Param("userId") Long userId, @Param("openId") String openId);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserWxMapper.java

@@ -3,6 +3,7 @@ package com.fs.his.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.his.domain.FsUserWx;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 
 public interface FsUserWxMapper extends BaseMapper<FsUserWx> {
 
@@ -13,4 +14,7 @@ public interface FsUserWxMapper extends BaseMapper<FsUserWx> {
     void insertOrUpdateByUniqueKey(FsUserWx wx);
 
     FsUserWx getFsUserWcByUserIdAndAppId(@Param("fsUserId") Long userId,@Param("appId") String appId );
+
+    @Select("select * from fs_user_wx where app_id=#{appid} and fs_user_id=#{userId} limit 1")
+    FsUserWx selectByAppidAndUserId(@Param("appid") String appid, @Param("userId") Long userId);
 }

+ 6 - 0
fs-service/src/main/java/com/fs/his/param/FsPrescribeParam.java

@@ -11,6 +11,9 @@ public class FsPrescribeParam {
     /** id */
     private Long prescribeId;
 
+    private Long orderId;
+
+
     /** 处方类型 1问诊 2订单 */
     @Excel(name = "处方类型 1西药 2中药")
     private Integer prescribeType;
@@ -50,6 +53,9 @@ public class FsPrescribeParam {
     @Excel(name = "是否有过敏史", readConverterExp = "传=值:是/否")
     private String isHistoryAllergic;
 
+    private String nowIllness;//现病史
+    private String historyIllness;//既往史
+
     /** 过敏史 */
     @Excel(name = "过敏史")
     private String historyAllergic;

+ 3 - 0
fs-service/src/main/java/com/fs/his/service/IFsAdvService.java

@@ -4,6 +4,7 @@ import java.util.List;
 import com.fs.his.domain.FsAdv;
 import com.fs.his.param.FsAdvUParam;
 import com.fs.his.vo.FsAdvUVO;
+import com.fs.hisStore.vo.FsAdvListQueryVO;
 
 /**
  * 广告Service接口
@@ -65,4 +66,6 @@ public interface IFsAdvService
 
     List<FsAdv> selectFsAdvListUVO(FsAdvUParam map);
 
+    List<FsAdvListQueryVO> selectFsAdvListQuery(Integer advType);
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/his/service/IFsArticleCateService.java

@@ -5,6 +5,7 @@ import java.util.Map;
 
 import com.fs.his.domain.FsArticleCate;
 import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.vo.FsArticleCateListQueryVO;
 
 /**
  * 文章分类Service接口
@@ -66,4 +67,5 @@ public interface IFsArticleCateService
 
     List<FsArticleCate> selectFsArticleCateAllListVO(Map<String, Object> params);
 
+    List<FsArticleCateListQueryVO> selectFsArticleCateListQuery();
 }

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

@@ -6,6 +6,8 @@ import com.fs.his.param.FsArticleListUParam;
 import com.fs.his.vo.FsArticleListUVO;
 import com.fs.his.vo.FsArticleListVO;
 import com.fs.his.vo.FsArticleVO;
+import com.fs.store.param.FsArticleQueryParam;
+import com.fs.store.vo.FsArticleListQueryVO;
 
 /**
  * 文章Service接口
@@ -68,4 +70,7 @@ public interface IFsArticleService
     int updateFsArticleViews(Long articleId);
 
     List<FsArticleListVO> selectFsArticleListVO(FsArticle fsArticle);
+
+
+    List<FsArticleListQueryVO> selectFsArticleListQuery(FsArticleQueryParam param);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/his/service/IFsCityService.java

@@ -67,4 +67,5 @@ public interface IFsCityService
 
     List<CitysAreaVO> getCitysArea();
 
+    List<FsCity> selectFsCitys();
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/service/IFsStoreProductService.java

@@ -12,6 +12,7 @@ import com.fs.his.param.FsStoreProductListSParam;
 import com.fs.his.vo.*;
 import com.fs.hisStore.vo.FsStoreProductListVO;
 import com.fs.live.domain.LiveGoods;
+import com.fs.store.vo.FsStoreProductActivityListVO;
 
 /**
  * 商品Service接口
@@ -108,4 +109,6 @@ public interface IFsStoreProductService
     List<FsStoreProduct> getStoreProductInProductIds(List<Long> productIds);
 
     List<FsStoreProductListVO> liveList(LiveGoods liveId);
+
+    List<FsStoreProductActivityListVO> selectFsStoreProductByIds(String productIds);
 }

+ 3 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserAddressService.java

@@ -71,4 +71,7 @@ public interface IFsUserAddressService
 
     Long selectFsUserAddressListCount(FsUserAddress fsUserAddress);
 
+    Long selectFsUserAddressByCityName(String city);
+
+    Integer delAllAddress(Long userId);
 }

+ 8 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsAdvServiceImpl.java

@@ -4,6 +4,7 @@ import java.util.Collections;
 import java.util.List;
 import com.fs.common.utils.DateUtils;
 import com.fs.his.param.FsAdvUParam;
+import com.fs.hisStore.vo.FsAdvListQueryVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.his.mapper.FsAdvMapper;
@@ -106,4 +107,11 @@ public class FsAdvServiceImpl implements IFsAdvService
     public List<FsAdv> selectFsAdvListUVO(FsAdvUParam map) {
         return fsAdvMapper.selectFsAdvListUVO(map);
     }
+
+    @Override
+    public List<FsAdvListQueryVO> selectFsAdvListQuery(Integer advType) {
+        return fsAdvMapper.selectFsAdvListQuery(advType);
+    }
+
+
 }

+ 7 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsArticleCateServiceImpl.java

@@ -4,6 +4,7 @@ import java.util.List;
 import java.util.Map;
 
 import com.fs.his.vo.OptionsVO;
+import com.fs.hisStore.vo.FsArticleCateListQueryVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.his.mapper.FsArticleCateMapper;
@@ -104,4 +105,10 @@ public class FsArticleCateServiceImpl implements IFsArticleCateService
 
        return fsArticleCateMapper.selectFsArticleCateAllListVO(params);
     }
+
+
+    @Override
+    public List<FsArticleCateListQueryVO> selectFsArticleCateListQuery() {
+        return fsArticleCateMapper.selectFsArticleCateListQuery();
+    }
 }

+ 17 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsArticleServiceImpl.java

@@ -6,6 +6,10 @@ import com.fs.his.param.FsArticleListUParam;
 import com.fs.his.vo.FsArticleListUVO;
 import com.fs.his.vo.FsArticleListVO;
 import com.fs.his.vo.FsArticleVO;
+import com.fs.hisStore.mapper.FsArticleViewsScrmMapper;
+import com.fs.hisStore.vo.FsArticleViewListQueryVO;
+import com.fs.store.param.FsArticleQueryParam;
+import com.fs.store.vo.FsArticleListQueryVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.his.mapper.FsArticleMapper;
@@ -24,6 +28,9 @@ public class FsArticleServiceImpl implements IFsArticleService
     @Autowired
     private FsArticleMapper fsArticleMapper;
 
+    @Autowired
+    private FsArticleViewsScrmMapper viewsMapper;
+
     /**
      * 查询文章
      *
@@ -112,4 +119,14 @@ public class FsArticleServiceImpl implements IFsArticleService
     public List<FsArticleListVO> selectFsArticleListVO(FsArticle fsArticle) {
         return fsArticleMapper.selectFsArticleListVO(fsArticle);
     }
+
+    @Override
+    public List<FsArticleListQueryVO> selectFsArticleListQuery(FsArticleQueryParam param) {
+        List<FsArticleListQueryVO> list=fsArticleMapper.selectFsArticleListQuery(param);
+        for(FsArticleListQueryVO vo:list){
+            List<FsArticleViewListQueryVO> viewsList=viewsMapper.selectFsArticleViewsTopByArticleId(vo.getArticleId());
+            vo.setViewsList(viewsList);
+        }
+        return list;
+    }
 }

+ 11 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsCityServiceImpl.java

@@ -118,5 +118,16 @@ public class FsCityServiceImpl implements IFsCityService
         return list;
     }
 
+    @Override
+    public List<FsCity> selectFsCitys() {
+        List<FsCity> redisList = redisCache.getCacheObject("city");
+        if(redisList != null){
+            return redisList;
+        }
+        List<FsCity> list = fsCityMapper.selectFsCitys();
+        redisCache.setCacheObject("city", list);
+        return list;
+    }
+
 
 }

+ 23 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsPrescribeServiceImpl.java

@@ -3,12 +3,14 @@ package com.fs.his.service.impl;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.fs.common.config.FSSysConfig;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.util.OrderUtils;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.*;
 import com.fs.his.dto.FsPackagePatientDTO;
@@ -16,10 +18,7 @@ import com.fs.his.dto.FsPackagePruductDTO;
 import com.fs.his.dto.FsPrescribeUsageDTO;
 import com.fs.his.mapper.*;
 import com.fs.his.param.*;
-import com.fs.his.service.IFsDoctorService;
-import com.fs.his.service.IFsPrescribeDrugService;
-import com.fs.his.service.IFsPrescribeService;
-import com.fs.his.service.IFsStoreOrderService;
+import com.fs.his.service.*;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.utils.IdCardUtil;
 import com.fs.his.utils.PhoneUtil;
@@ -30,6 +29,9 @@ import com.fs.im.dto.MsgDTO;
 import com.fs.im.dto.MsgDataDTO;
 import com.fs.im.dto.MsgDataFormatDTO;
 import com.fs.im.service.IImService;
+import com.fs.store.bean.DrugV2;
+import com.fs.store.bean.PrescribeV2;
+import com.fs.store.param.PrescribeV2Param;
 import com.fs.system.oss.CloudStorageService;
 import com.fs.system.oss.OSSFactory;
 import com.google.zxing.WriterException;
@@ -82,6 +84,22 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
     ConfigUtil configUtil;
     @Autowired
     FsExportTaskMapper fsExportTaskMapper;
+
+    @Autowired
+    IFsStoreOrderItemService orderItemService;
+
+    @Autowired
+    IFsPatientService patientService;
+
+    @Autowired
+    IFsUserService userService;
+
+    @Autowired
+    FSSysConfig fsSysConfig;
+
+    @Autowired
+    IFsStoreProductService productService;
+
     /**
      * 查询处方
      *
@@ -992,4 +1010,5 @@ public class FsPrescribeServiceImpl implements IFsPrescribeService
         return fsPrescribeMapper.selectByOrderId(orderId);
     }
 
+
 }

+ 7 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsStoreProductServiceImpl.java

@@ -34,6 +34,7 @@ import com.fs.his.param.FsStoreProductListSParam;
 import com.fs.his.service.IFsPackageService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.*;
+import com.fs.store.vo.FsStoreProductActivityListVO;
 import com.fs.hisStore.vo.FsStoreProductListVO;
 import com.fs.live.domain.LiveGoods;
 import org.apache.commons.lang3.StringUtils;
@@ -529,7 +530,7 @@ public class FsStoreProductServiceImpl implements IFsStoreProductService {
                 } else if (erpType == 5){
                     //聚水潭商品同步
                     params.setProductIdList(Arrays.asList(product.getProductId()));
-                    
+
                     jSTErpGoodsService.addGoods(params);
                 }
             }
@@ -878,4 +879,9 @@ public class FsStoreProductServiceImpl implements IFsStoreProductService {
     public List<FsStoreProductListVO> liveList(LiveGoods liveId) {
         return fsStoreProductMapper.liveList(liveId);
     }
+
+    @Override
+    public List<FsStoreProductActivityListVO> selectFsStoreProductByIds(String productIds) {
+        return fsStoreProductMapper.selectFsStoreProductByIds(productIds);
+    }
 }

+ 13 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserAddressServiceImpl.java

@@ -181,6 +181,9 @@ public class FsUserAddressServiceImpl implements IFsUserAddressService
     public Long selectFsUserAddressListCount(FsUserAddress fsUserAddress) {
         return fsUserAddressMapper.selectFsUserAddressListCount(fsUserAddress);
     }
+
+
+
     private String encrypt(String content, String keyValue, String charset) {
         if (keyValue != null) {
             content = content + keyValue;
@@ -208,4 +211,14 @@ public class FsUserAddressServiceImpl implements IFsUserAddressService
         }
         return sb.toString().toLowerCase();
     }
+
+    @Override
+    public Long selectFsUserAddressByCityName(String city) {
+        return fsUserAddressMapper.selectFsUserAddressByCityName(city);
+    }
+
+    @Override
+    public Integer delAllAddress(Long userId) {
+        return fsUserAddressMapper.delAllAddress(userId);
+    }
 }

+ 18 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreOrderScrm.java

@@ -392,4 +392,22 @@ public class FsStoreOrderScrm extends BaseEntity
 
     //虚拟手机号
     private String virtualPhone;
+
+    //关联id根据订单类型+associatedId唯一数据
+    private Long associatedId;
+
+    //是否同步库存 0-否 1-是
+    private Integer isSyncInventory;
+
+    @TableField(exist = false)
+    private String reasonValue1;
+
+    @TableField(exist = false)
+    private String reasonValue2;
+
+    @TableField(exist = false)
+    private String auditRemark;
+
+    @TableField(exist = false)
+    private String auditReasonName;
 }

+ 154 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductActivity.java

@@ -0,0 +1,154 @@
+package com.fs.hisStore.domain;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 商品活动中间表对象 fs_store_product_activity
+ * 合并秒杀(6)和限时折扣(7),一个商品+一个规格=一条记录
+ *
+ * @author fs
+ * @date 2026-04-16
+ */
+@Data
+public class FsStoreProductActivity implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 商品ID */
+    @Excel(name = "商品ID")
+    private Long productId;
+
+    /** 活动类型:6=秒杀 7=限时折扣 */
+    @Excel(name = "活动类型", readConverterExp = "6=秒杀,7=限时折扣")
+    private Integer activityType;
+
+    /** 规格ID(fs_store_product_attr_value.id) */
+    @Excel(name = "规格ID")
+    private Long specId;
+
+    /** 原价 */
+    @Excel(name = "原价")
+    private BigDecimal originalPrice;
+
+    /** 秒杀价(activity_type=6时) */
+    @Excel(name = "秒杀价")
+    private BigDecimal flashPrice;
+
+    /** 折扣如0.8(activity_type=7时) */
+    @Excel(name = "折扣")
+    private BigDecimal discount;
+
+    /** 折扣价(activity_type=7时) */
+    @Excel(name = "折扣价")
+    private BigDecimal discountPrice;
+
+    /** 开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    /** 结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+
+    /** 状态:0=下架 1=上架 */
+    @Excel(name = "状态", readConverterExp = "0=下架,1=上架")
+    private Integer status;
+
+    /** 删除标志:0=正常 1=删除 */
+    private Integer delFlag;
+
+    /** 商品名称(关联查询) */
+    @Excel(name = "商品名称")
+    private String productName;
+
+    /** 商品图片(关联查询) */
+    private String productImage;
+
+    /** 商品售价(关联查询) */
+    private BigDecimal price;
+
+    /** 商品原价(关联查询) */
+    private BigDecimal otPrice;
+
+    /** 商品销量(关联查询) */
+    private Integer sales;
+
+    /** 商品库存(关联查询) */
+    private Integer productStock;
+
+    /** 商品分类名称(关联查询) */
+    private String cateName;
+
+    /** 商品编号(关联查询) */
+    private String barCode;
+
+    /** 商品详情(关联查询) */
+    @TableField(exist = false)
+    private String productInfo;
+
+    /** 商品轮播图(关联查询) */
+    @TableField(exist = false)
+    private String sliderImage;
+
+    /** 规格名称/SKU(关联查询) */
+    @TableField(exist = false)
+    private String specName;
+
+    /** 规格库存(关联查询) */
+    @TableField(exist = false)
+    private Integer specStock;
+
+    /** Redis实时库存(非数据库字段) */
+    @TableField(exist = false)
+    private Integer remainStock;
+
+    /** 图片 */
+    @TableField(exist = false)
+    private String image;
+
+    /** 活动状态(非数据库字段): not_started/ongoing/sold_out/ended */
+    @TableField(exist = false)
+    private String activityStatus;
+
+    /** 倒计时秒数(非数据库字段) */
+    @TableField(exist = false)
+    private Long countdown;
+
+    /** 是否正在进行中(非数据库字段,前端控制只读) */
+    @TableField(exist = false)
+    private Boolean isOngoing;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+}

+ 128 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductDiscount.java

@@ -0,0 +1,128 @@
+package com.fs.hisStore.domain;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 限时折扣商品对象 fs_store_product_discount
+ *
+ * @author fs
+ * @date 2026-04-03
+ */
+@Data
+public class FsStoreProductDiscount implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 关联商品ID(fs_store_product_scrm.product_id) */
+    @Excel(name = "商品ID")
+    private Long productId;
+
+    /** 折扣库存 */
+    @Excel(name = "折扣库存")
+    private Long stock;
+
+    /** 折扣开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "折扣开始时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    /** 折扣结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "折扣结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+
+    /** 原价 */
+    @Excel(name = "原价")
+    private BigDecimal originalPrice;
+
+    /** 折扣(如0.8表示8折) */
+    @Excel(name = "折扣")
+    private BigDecimal discount;
+
+    /** 折扣支付价格 */
+    @Excel(name = "折扣价格")
+    private BigDecimal discountPrice;
+
+    /** 状态(0:下架,1:上架) */
+    @Excel(name = "状态", readConverterExp = "0=下架,1=上架")
+    private Integer status;
+
+    /** 删除标志(0:正常,1:删除) */
+    private Integer delFlag;
+
+    /** 商品名称(关联查询) */
+    @Excel(name = "商品名称")
+    private String productName;
+
+    /** 商品图片(关联查询) */
+    private String productImage;
+
+    /** 商品售价(关联查询) */
+    private BigDecimal price;
+
+    /** 商品原价(关联查询) */
+    private BigDecimal otPrice;
+
+    /** 商品销量(关联查询) */
+    private Integer sales;
+
+    /** 商品库存(关联查询) */
+    private Integer productStock;
+
+    /** 商品分类名称(关联查询) */
+    private String cateName;
+
+    /** 商品编号(关联查询) */
+    private String barCode;
+
+    /** 商品简介(关联查询) */
+    private String productInfo;
+
+    /** 轮播图(关联查询) */
+    private String sliderImage;
+
+    /** Redis实时库存(非数据库字段) */
+    @TableField(exist = false)
+    private Integer remainStock;
+
+    /** 活动状态(非数据库字段): not_started/ongoing/sold_out/ended */
+    @TableField(exist = false)
+    private String activityStatus;
+
+    /** 倒计时秒数(非数据库字段) */
+    @TableField(exist = false)
+    private Long countdown;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+}

+ 123 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreProductFlashSale.java

@@ -0,0 +1,123 @@
+package com.fs.hisStore.domain;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 秒杀商品对象 fs_store_product_flash_sale
+ *
+ * @author fs
+ * @date 2026-04-08
+ */
+@Data
+public class FsStoreProductFlashSale implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 关联商品ID(fs_store_product_scrm.product_id) */
+    @Excel(name = "商品ID")
+    private Long productId;
+
+    /** 原价 */
+    @Excel(name = "原价")
+    private BigDecimal originalPrice;
+
+    /** 秒杀价 */
+    @Excel(name = "秒杀价")
+    private BigDecimal flashPrice;
+
+    /** 秒杀库存 */
+    @Excel(name = "库存")
+    private Long stock;
+
+    /** 秒杀开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    /** 秒杀结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+
+    /** 状态(0:下架,1:上架) */
+    @Excel(name = "状态", readConverterExp = "0=下架,1=上架")
+    private Integer status;
+
+    /** 删除标志(0:正常,1:删除) */
+    private Integer delFlag;
+
+    /** 商品名称(关联查询) */
+    @Excel(name = "商品名称")
+    private String productName;
+
+    /** 商品图片(关联查询) */
+    private String productImage;
+
+    /** 商品售价(关联查询) */
+    private BigDecimal price;
+
+    /** 商品原价(关联查询) */
+    private BigDecimal otPrice;
+
+    /** 商品销量(关联查询) */
+    private Integer sales;
+
+    /** 商品库存(关联查询) */
+    private Integer productStock;
+
+    /** 商品分类名称(关联查询) */
+    private String cateName;
+
+    /** 商品编号(关联查询) */
+    private String barCode;
+
+    /** 商品简介(关联查询) */
+    private String productInfo;
+
+    /** 轮播图(关联查询) */
+    private String sliderImage;
+
+    /** Redis实时库存(非数据库字段) */
+    @TableField(exist = false)
+    private Integer remainStock;
+
+    /** 活动状态(非数据库字段): not_started/ongoing/sold_out/ended */
+    @TableField(exist = false)
+    private String activityStatus;
+
+    /** 倒计时秒数(非数据库字段) */
+    @TableField(exist = false)
+    private Long countdown;
+
+    /** 创建者 */
+    @TableField(fill = FieldFill.INSERT)
+    private String createBy;
+
+    /** 创建时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 更新者 */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private String updateBy;
+
+    /** 更新时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /** 备注 */
+    private String remark;
+}

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

@@ -354,6 +354,18 @@ public class FsStoreProductScrm extends BaseEntity
     @Excel(name = "单次购买数量")
     private Integer singlePurchaseLimit;
 
+    /** 活动类型:0=无 6=秒杀 7=限时折扣 */
+    @Excel(name = "活动类型", readConverterExp = "0=无,6=秒杀,7=限时折扣")
+    private Integer activityType;
+
+    /** 活动开始时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date activityStartTime;
+
+    /** 活动结束时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date activityEndTime;
+
     @TableField(exist = false)
     private String onShelfTime;
 

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreOrderScrmMapper.java

@@ -1526,4 +1526,12 @@ public interface FsStoreOrderScrmMapper
 
 
     List<FsMyStoreOrderListQueryVO> selectFsStoreOrderListBySidebarVO(@Param("maps") FsStoreOrderScrmSidebarVO param);
+
+    /**
+     * 查询超时未支付的SCRM订单(用于定时任务自动取消)
+     * @param unPayTime 超时时间(分钟)
+     * @return 超时未支付订单列表
+     */
+    @Select("SELECT * FROM fs_store_order_scrm WHERE status = 0 AND paid = 0 AND create_time < DATE_SUB(NOW(), INTERVAL #{unPayTime} MINUTE)")
+    List<FsStoreOrderScrm> selectUnpayTimeoutOrderList(@Param("unPayTime") Integer unPayTime);
 }

+ 131 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductActivityMapper.java

@@ -0,0 +1,131 @@
+package com.fs.hisStore.mapper;
+
+import java.util.List;
+import com.fs.hisStore.domain.FsStoreProductActivity;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 商品活动中间表Mapper接口
+ *
+ * @author fs
+ * @date 2026-04-16
+ */
+public interface FsStoreProductActivityMapper {
+
+    /**
+     * 查询活动记录
+     */
+    public FsStoreProductActivity selectFsStoreProductActivityById(Long id);
+
+    /**
+     * 查询活动列表
+     */
+    public List<FsStoreProductActivity> selectFsStoreProductActivityList(FsStoreProductActivity query);
+
+    /**
+     * 根据商品ID查询活动记录(未删除)
+     */
+    public List<FsStoreProductActivity> selectByProductId(@Param("productId") Long productId);
+
+    /**
+     * 根据商品ID和规格ID查询
+     */
+    public FsStoreProductActivity selectByProductIdAndSpecId(@Param("productId") Long productId, @Param("specId") Long specId);
+
+    /**
+     * 查询即将开始的活动(1小时内开始+未过期)
+     */
+    public List<FsStoreProductActivity> selectUpcomingList(@Param("activityType") Integer activityType);
+
+    /**
+     * 查询已过期的活动(status=1且end_time<now)
+     */
+    public List<FsStoreProductActivity> selectExpiredList();
+
+    /**
+     * 查询即将开始的活动(status=0且start_time-1h<=now且end_time>now且stock>0)
+     */
+    public List<FsStoreProductActivity> selectStartingList();
+
+    /**
+     * 新增活动记录
+     */
+    public int insertFsStoreProductActivity(FsStoreProductActivity activity);
+
+    /**
+     * 批量新增活动记录
+     */
+    public int batchInsertActivity(@Param("list") List<FsStoreProductActivity> list);
+
+    /**
+     * 修改活动记录
+     */
+    public int updateFsStoreProductActivity(FsStoreProductActivity activity);
+
+    /**
+     * 逻辑删除活动记录
+     */
+    public int deleteFsStoreProductActivityById(Long id);
+
+    /**
+     * 根据商品ID逻辑删除所有活动记录
+     */
+    public int deleteByProductId(@Param("productId") Long productId);
+
+    /**
+     * 更新状态
+     */
+    public int updateStatus(@Param("id") Long id, @Param("status") Integer status);
+
+    /**
+     * 查询商品正在进行中的活动(校验用)
+     */
+    public FsStoreProductActivity selectOngoingActivity(@Param("productId") Long productId);
+
+    /**
+     * 更新商品表activity_type及活动时间
+     */
+    public int updateProductActivityType(@Param("productId") Long productId, @Param("activityType") Integer activityType,
+                                         @Param("activityStartTime") java.util.Date activityStartTime,
+                                         @Param("activityEndTime") java.util.Date activityEndTime);
+
+    // ===== 小程序端查询方法 =====
+
+    /**
+     * 查询秒杀活动列表(1小时内即将开抢+未过期)
+     */
+    List<FsStoreProductActivity> selectUpcomingFlashSaleActivityList();
+
+    /**
+     * 查询折扣活动列表(1小时内即将开抢+未过期)
+     */
+    List<FsStoreProductActivity> selectUpcomingDiscountActivityList();
+
+    /**
+     * 按活动ID查询详情(JOIN商品表获取完整信息)
+     */
+    FsStoreProductActivity selectActivityDetailById(@Param("id") Long id);
+
+    /**
+     * 按商品ID查询当前进行中的秒杀活动
+     */
+    List<FsStoreProductActivity> selectFlashSaleActivityByProductId(@Param("productId") Long productId);
+
+    /**
+     * 按商品ID查询当前进行中的折扣活动
+     */
+    List<FsStoreProductActivity> selectDiscountActivityByProductId(@Param("productId") Long productId);
+
+    /**
+     * 按商品ID和活动类型查询所有参与活动的规格(用于详情页返回规格数组)
+     */
+    List<FsStoreProductActivity> selectActivitySpecsByProductIdAndType(
+            @Param("productId") Long productId,
+            @Param("activityType") Integer activityType);
+    /**
+     * 按商品ID和规格ID查询活动记录
+     * @param productId 商品id
+     * @param specId 规格id
+     */
+    FsStoreProductActivity selectActivityByProductIdAndSpecId(@Param("productId") Long productId,@Param("specId") Long specId);
+}

+ 109 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductDiscountMapper.java

@@ -0,0 +1,109 @@
+package com.fs.hisStore.mapper;
+
+import java.util.List;
+
+import com.fs.hisStore.domain.FsStoreProductDiscount;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 限时折扣商品Mapper接口
+ *
+ * @author fs
+ * @date 2026-04-03
+ */
+public interface FsStoreProductDiscountMapper
+{
+    /**
+     * 查询限时折扣商品
+     *
+     * @param id 限时折扣商品主键
+     * @return 限时折扣商品
+     */
+    public FsStoreProductDiscount selectFsStoreProductDiscountById(Long id);
+
+    /**
+     * 查询限时折扣商品列表
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 限时折扣商品集合
+     */
+    public List<FsStoreProductDiscount> selectFsStoreProductDiscountList(FsStoreProductDiscount fsStoreProductDiscount);
+
+    /**
+     * 新增限时折扣商品
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 结果
+     */
+    public int insertFsStoreProductDiscount(FsStoreProductDiscount fsStoreProductDiscount);
+
+    /**
+     * 修改限时折扣商品
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 结果
+     */
+    public int updateFsStoreProductDiscount(FsStoreProductDiscount fsStoreProductDiscount);
+
+    /**
+     * 删除限时折扣商品
+     *
+     * @param id 限时折扣商品主键
+     * @return 结果
+     */
+    public int deleteFsStoreProductDiscountById(Long id);
+
+    /**
+     * 批量删除限时折扣商品
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteFsStoreProductDiscountByIds(Long[] ids);
+
+    /**
+     * 查询当前有效的限时折扣商品列表(小程序端使用)
+     *
+     * @return 限时折扣商品集合
+     */
+    public List<FsStoreProductDiscount> selectActiveDiscountList();
+
+    /**
+     * 根据商品ID查询限时折扣信息
+     *
+     * @param productId 商品ID
+     * @return 限时折扣商品
+     */
+    public FsStoreProductDiscount selectDiscountByProductId(@Param("productId") Long productId);
+
+    public List<FsStoreProductDiscount> selectUpcomingDiscountList();
+
+    /**
+     * 获取则扣商品信息
+     * @param ids 折扣id
+     * @return list
+     * **/
+    public List<FsStoreProductDiscount> getProductDiscountInfoByIds(@Param("ids") List<Long> ids);
+
+    int batchUpdateStock(@Param("list") List<FsStoreProductDiscount> list);
+
+    /**
+     * 查询已过期的折扣商品(已上架且结束时间已过)
+     */
+    List<FsStoreProductDiscount> selectExpiredDiscountList();
+
+    /**
+     * 查询即将开始的折扣商品(开始时间在当前时间之前或等于当前时间)
+     */
+    List<FsStoreProductDiscount> selectStartingDiscountList();
+
+    /**
+     * 更新折扣商品状态
+     */
+    int updateStatus(@Param("id") Long id, @Param("status") Integer status);
+
+    /**
+     * 原子性增加库存(退款回滚用)
+     */
+    int increaseStock(@Param("id") Long id, @Param("num") Long num);
+}

+ 109 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductFlashSaleMapper.java

@@ -0,0 +1,109 @@
+package com.fs.hisStore.mapper;
+
+import java.util.List;
+
+import com.fs.hisStore.domain.FsStoreProductFlashSale;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * 秒杀商品Mapper接口
+ *
+ * @author fs
+ * @date 2026-04-08
+ */
+public interface FsStoreProductFlashSaleMapper
+{
+    /**
+     * 查询秒杀商品
+     *
+     * @param id 秒杀商品主键
+     * @return 秒杀商品
+     */
+    public FsStoreProductFlashSale selectFsStoreProductFlashSaleById(Long id);
+
+    /**
+     * 查询秒杀商品列表
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 秒杀商品集合
+     */
+    public List<FsStoreProductFlashSale> selectFsStoreProductFlashSaleList(FsStoreProductFlashSale fsStoreProductFlashSale);
+
+    /**
+     * 新增秒杀商品
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 结果
+     */
+    public int insertFsStoreProductFlashSale(FsStoreProductFlashSale fsStoreProductFlashSale);
+
+    /**
+     * 修改秒杀商品
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 结果
+     */
+    public int updateFsStoreProductFlashSale(FsStoreProductFlashSale fsStoreProductFlashSale);
+
+    /**
+     * 删除秒杀商品
+     *
+     * @param id 秒杀商品主键
+     * @return 结果
+     */
+    public int deleteFsStoreProductFlashSaleById(Long id);
+
+    /**
+     * 批量删除秒杀商品
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteFsStoreProductFlashSaleByIds(Long[] ids);
+
+    /**
+     * 查询当前有效的秒杀商品列表(小程序端使用)
+     *
+     * @return 秒杀商品集合
+     */
+    public List<FsStoreProductFlashSale> selectActiveFlashSaleList();
+
+    /**
+     * 根据商品ID查询秒杀信息
+     *
+     * @param productId 商品ID
+     * @return 秒杀商品
+     */
+    public FsStoreProductFlashSale selectFlashSaleByProductId(@Param("productId") Long productId);
+
+    public List<FsStoreProductFlashSale> selectUpcomingFlashSaleList();
+
+    /**
+     * 获取商品信息
+     * @param ids 秒杀id
+     * @return 秒杀数据
+     */
+    public List<FsStoreProductFlashSale> getProductFlashSaleInfoByIds(@Param("ids") List<Long> ids);
+
+    int batchUpdateStock(@Param("list") List<FsStoreProductFlashSale> list);
+
+    /**
+     * 查询已过期的秒杀商品(已上架且结束时间已过)
+     */
+    List<FsStoreProductFlashSale> selectExpiredFlashSaleList();
+
+    /**
+     * 查询即将开始的秒杀商品(开始时间在当前时间之前或等于当前时间)
+     */
+    List<FsStoreProductFlashSale> selectStartingFlashSaleList();
+
+    /**
+     * 更新秒杀商品状态
+     */
+    int updateStatus(@Param("id") Long id, @Param("status") Integer status);
+
+    /**
+     * 原子性增加库存(退款回滚用)
+     */
+    int increaseStock(@Param("id") Long id, @Param("num") Long num);
+}

+ 10 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsStoreProductScrmMapper.java

@@ -256,6 +256,9 @@ public interface FsStoreProductScrmMapper
             "<if test = 'maps.newOrder != null and maps.newOrder==\"desc\" '> " +
             "and p.is_new =1 order by p.create_time desc  " +
             "</if>" +
+            "<if test = 'maps.activityType != null and maps.activityType != 0 '> " +
+            "and p.activity_type =#{maps.activityType}  " +
+            "</if>" +
             "</script>"})
     List<FsStoreProductListQueryVO> selectFsStoreProductListQuery(@Param("maps")FsStoreProductQueryParam param);
     @Select({"<script> " +
@@ -487,4 +490,11 @@ public interface FsStoreProductScrmMapper
             "</script>"})
     List<FsStoreProductListQueryVO> selectFsStoreProductSidebarListQuery(@Param("maps")FsStoreOrderScrmSidebarVO param);
 
+
+    @Select("select * from fs_store_product where is_del=0 and is_show=1 and  is_new=1 and is_display=1 order by sort desc, product_id DESC limit #{count}")
+    List<FsStoreProductListQueryVO> selectFsStoreProductNewQueryCount(int count);
+
+    @Select("select * from fs_store_product where is_del=0 and is_show=1 and  is_hot=1 and is_display=1 order by sort desc, product_id DESC limit #{count}")
+    List<FsStoreProductListQueryVO> selectFsStoreProductHotQueryCount(int count);
+
 }

+ 15 - 8
fs-service/src/main/java/com/fs/hisStore/mapper/FsWechatTemplateScrmMapper.java

@@ -1,20 +1,23 @@
 package com.fs.hisStore.mapper;
 
 import java.util.List;
+import java.util.Set;
+
 import com.fs.hisStore.domain.FsWechatTemplateScrm;
+import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 /**
  * 微信模板Mapper接口
- * 
+ *
  * @author fs
  * @date 2022-03-15
  */
-public interface FsWechatTemplateScrmMapper 
+public interface FsWechatTemplateScrmMapper
 {
     /**
      * 查询微信模板
-     * 
+     *
      * @param id 微信模板ID
      * @return 微信模板
      */
@@ -22,7 +25,7 @@ public interface FsWechatTemplateScrmMapper
 
     /**
      * 查询微信模板列表
-     * 
+     *
      * @param fsWechatTemplate 微信模板
      * @return 微信模板集合
      */
@@ -30,7 +33,7 @@ public interface FsWechatTemplateScrmMapper
 
     /**
      * 新增微信模板
-     * 
+     *
      * @param fsWechatTemplate 微信模板
      * @return 结果
      */
@@ -38,7 +41,7 @@ public interface FsWechatTemplateScrmMapper
 
     /**
      * 修改微信模板
-     * 
+     *
      * @param fsWechatTemplate 微信模板
      * @return 结果
      */
@@ -46,7 +49,7 @@ public interface FsWechatTemplateScrmMapper
 
     /**
      * 删除微信模板
-     * 
+     *
      * @param id 微信模板ID
      * @return 结果
      */
@@ -54,7 +57,7 @@ public interface FsWechatTemplateScrmMapper
 
     /**
      * 批量删除微信模板
-     * 
+     *
      * @param ids 需要删除的数据ID
      * @return 结果
      */
@@ -62,8 +65,12 @@ public interface FsWechatTemplateScrmMapper
 
     @Select("select * from fs_wechat_template where tempkey=#{key}")
     FsWechatTemplateScrm selectFsWechatTemplateByKey(String key);
+
     @Select("select temp_id from fs_wechat_template where status=1")
     List<String> selectFsWechatTemplateIds();
+
     @Select("select temp_id from fs_wechat_template where find_in_set(tempkey,#{key})")
     List<String> selectFsWechatTemplateIdsByKeys(String key);
+
+    List<String> selectFsWechatTemplateIdsByKeySet(@Param("items") Set<String> key);
 }

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

@@ -14,4 +14,7 @@ public class FsStoreConfirmOrderParam {
     @NotBlank(message = "购买类型不能为空")
     @ApiModelProperty(value = "buy cart")
     private String type;
+
+    @ApiModelProperty(value = "商品类型6秒杀/7折扣")
+    private Integer productType;
 }

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

@@ -65,4 +65,7 @@ public class FsStoreOrderCreateParam implements Serializable
     private Integer projectId;
     //营期ID
     private Integer periodId;
+
+    //关联Id
+    private Long associatedId;
 }

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

@@ -43,4 +43,7 @@ public class FsStoreProductQueryParam extends BaseQueryParam implements Serializ
 
     @ApiModelProperty(value = "当前的appid")
     private String appId;
+
+    @ApiModelProperty(value = "活动类型 0=普通 6=秒杀 7=限时折扣")
+    private Integer activityType;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreOrderScrmService.java

@@ -12,6 +12,7 @@ import com.fs.common.core.domain.R;
 import com.fs.company.domain.CompanyUser;
 import com.fs.course.dto.FsOrderDeliveryNoteDTO;
 import com.fs.erp.domain.ErpOrder;
+import com.fs.his.domain.FsStoreOrder;
 import com.fs.his.dto.FsStoreOrderAmountScrmStatsQueryDto;
 import com.fs.his.enums.PaymentMethodEnum;
 import com.fs.his.param.FsIntegralOrderDoPayParam;
@@ -108,6 +109,11 @@ public interface IFsStoreOrderScrmService
 
     R createOrder(long userId, FsStoreOrderCreateParam param);
 
+    /**
+     * 创建秒杀/限时折扣活动订单(独立接口)
+     */
+    R createActivityOrder(long userId, FsStoreOrderCreateParam param);
+
     R createOrderByPrescribe(Long prescribeId);
 
     void cancelOrder(Long orderId);
@@ -402,4 +408,6 @@ public interface IFsStoreOrderScrmService
     R payment(FsIntegralOrderDoPayParam param, PaymentMethodEnum paymentMethodEnum);
 
     R zfbPayment(FsStoreOrderDoPayParam param);
+
+//    R getExpressMulti(FsStoreOrder order);
 }

+ 99 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductActivityService.java

@@ -0,0 +1,99 @@
+package com.fs.hisStore.service;
+
+import java.util.List;
+import com.fs.hisStore.domain.FsStoreProductActivity;
+
+/**
+ * 商品活动中间表Service接口
+ *
+ * @author fs
+ * @date 2026-04-16
+ */
+public interface IFsStoreProductActivityService {
+
+    /**
+     * 查询活动记录
+     */
+    public FsStoreProductActivity selectFsStoreProductActivityById(Long id);
+
+    /**
+     * 查询活动列表
+     */
+    public List<FsStoreProductActivity> selectFsStoreProductActivityList(FsStoreProductActivity query);
+
+    /**
+     * 根据商品ID查询活动记录
+     */
+    public List<FsStoreProductActivity> selectByProductId(Long productId);
+
+    /**
+     * 保存活动设置(批量:先删旧再插新+更新商品表activity_type)
+     * @param productId 商品ID
+     * @param activityType 活动类型 0=无 6=秒杀 7=限时折扣
+     * @param activityList 规格活动列表(activityType!=0时)
+     * @return 结果
+     */
+    public int saveActivity(Long productId, Integer activityType, List<FsStoreProductActivity> activityList);
+
+    /**
+     * 查询商品正在进行中的活动
+     */
+    public FsStoreProductActivity selectOngoingActivity(Long productId);
+
+    /**
+     * 查询即将开始的活动
+     */
+    public List<FsStoreProductActivity> selectUpcomingList(Integer activityType);
+
+    /**
+     * 查询已过期的活动
+     */
+    public List<FsStoreProductActivity> selectExpiredList();
+
+    /**
+     * 查询即将开始需要上架的活动
+     */
+    public List<FsStoreProductActivity> selectStartingList();
+
+    /**
+     * 更新状态
+     */
+    public int updateStatus(Long id, Integer status);
+
+    /**
+     * 处理过期活动:同步Redis库存到DB + 重置商品activity_type=0
+     */
+    public void handleExpiredActivity(Long productId, List<FsStoreProductActivity> activityList);
+
+    // ===== 小程序端查询方法 =====
+
+    /**
+     * 查询秒杀活动列表(1小时内即将开抢+未过期)
+     */
+    List<FsStoreProductActivity> selectUpcomingFlashSaleActivityList();
+
+    /**
+     * 查询折扣活动列表(1小时内即将开抢+未过期)
+     */
+    List<FsStoreProductActivity> selectUpcomingDiscountActivityList();
+
+    /**
+     * 按活动ID查询详情(JOIN商品表获取完整信息)
+     */
+    FsStoreProductActivity selectActivityDetailById(Long id);
+
+    /**
+     * 按商品ID查询当前进行中的秒杀活动
+     */
+    List<FsStoreProductActivity> selectFlashSaleActivityByProductId(Long productId);
+
+    /**
+     * 按商品ID查询当前进行中的折扣活动
+     */
+    List<FsStoreProductActivity> selectDiscountActivityByProductId(Long productId);
+
+    /**
+     * 按商品ID和活动类型查询所有参与活动的规格(用于详情页返回规格数组)
+     */
+    List<FsStoreProductActivity> selectActivitySpecsByProductIdAndType(Long productId, Integer activityType);
+}

+ 94 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductDiscountService.java

@@ -0,0 +1,94 @@
+package com.fs.hisStore.service;
+
+import java.util.List;
+
+import com.fs.hisStore.domain.FsStoreProductDiscount;
+
+/**
+ * 限时折扣商品Service接口
+ *
+ * @author fs
+ * @date 2026-04-03
+ */
+public interface IFsStoreProductDiscountService
+{
+    /**
+     * 查询限时折扣商品
+     *
+     * @param id 限时折扣商品主键
+     * @return 限时折扣商品
+     */
+    public FsStoreProductDiscount selectFsStoreProductDiscountById(Long id);
+
+    /**
+     * 查询限时折扣商品列表
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 限时折扣商品集合
+     */
+    public List<FsStoreProductDiscount> selectFsStoreProductDiscountList(FsStoreProductDiscount fsStoreProductDiscount);
+
+    /**
+     * 新增限时折扣商品
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 结果
+     */
+    public int insertFsStoreProductDiscount(FsStoreProductDiscount fsStoreProductDiscount);
+
+    /**
+     * 修改限时折扣商品
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 结果
+     */
+    public int updateFsStoreProductDiscount(FsStoreProductDiscount fsStoreProductDiscount);
+
+    /**
+     * 批量删除限时折扣商品
+     *
+     * @param ids 需要删除的主键集合
+     * @return 结果
+     */
+    public int deleteFsStoreProductDiscountByIds(Long[] ids);
+
+    /**
+     * 删除限时折扣商品信息
+     *
+     * @param id 限时折扣商品主键
+     * @return 结果
+     */
+    public int deleteFsStoreProductDiscountById(Long id);
+
+    /**
+     * 查询当前有效的限时折扣商品列表(小程序端使用)
+     *
+     * @return 限时折扣商品集合
+     */
+    public List<FsStoreProductDiscount> selectActiveDiscountList();
+
+    /**
+     * 根据商品ID查询限时折扣信息
+     *
+     * @param productId 商品ID
+     * @return 限时折扣商品
+     */
+    public FsStoreProductDiscount selectDiscountByProductId(Long productId);
+
+    /**
+     * 查询即将开抢和未过期的折扣商品列表(小程序端使用)
+     * 展示1小时内即将开抢和当前进行中的商品
+     *
+     * @return 折扣商品集合
+     */
+    public List<FsStoreProductDiscount> selectUpcomingDiscountList();
+
+    /**
+     * 更新折扣商品库存
+     *
+     * @param id 折扣商品ID
+     * @param stock 库存数量
+     * @return 结果
+     */
+    public int updateDiscountStock(Long id, Long stock);
+}

+ 94 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductFlashSaleService.java

@@ -0,0 +1,94 @@
+package com.fs.hisStore.service;
+
+import java.util.List;
+
+import com.fs.hisStore.domain.FsStoreProductFlashSale;
+
+/**
+ * 秒杀商品Service接口
+ *
+ * @author fs
+ * @date 2026-04-08
+ */
+public interface IFsStoreProductFlashSaleService
+{
+    /**
+     * 查询秒杀商品
+     *
+     * @param id 秒杀商品主键
+     * @return 秒杀商品
+     */
+    public FsStoreProductFlashSale selectFsStoreProductFlashSaleById(Long id);
+
+    /**
+     * 查询秒杀商品列表
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 秒杀商品集合
+     */
+    public List<FsStoreProductFlashSale> selectFsStoreProductFlashSaleList(FsStoreProductFlashSale fsStoreProductFlashSale);
+
+    /**
+     * 新增秒杀商品
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 结果
+     */
+    public int insertFsStoreProductFlashSale(FsStoreProductFlashSale fsStoreProductFlashSale);
+
+    /**
+     * 修改秒杀商品
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 结果
+     */
+    public int updateFsStoreProductFlashSale(FsStoreProductFlashSale fsStoreProductFlashSale);
+
+    /**
+     * 批量删除秒杀商品
+     *
+     * @param ids 需要删除的主键集合
+     * @return 结果
+     */
+    public int deleteFsStoreProductFlashSaleByIds(Long[] ids);
+
+    /**
+     * 删除秒杀商品信息
+     *
+     * @param id 秒杀商品主键
+     * @return 结果
+     */
+    public int deleteFsStoreProductFlashSaleById(Long id);
+
+    /**
+     * 查询当前有效的秒杀商品列表(小程序端使用)
+     *
+     * @return 秒杀商品集合
+     */
+    public List<FsStoreProductFlashSale> selectActiveFlashSaleList();
+
+    /**
+     * 根据商品ID查询秒杀信息
+     *
+     * @param productId 商品ID
+     * @return 秒杀商品
+     */
+    public FsStoreProductFlashSale selectFlashSaleByProductId(Long productId);
+
+    /**
+     * 查询即将开抢和未过期的秒杀商品列表(小程序端使用)
+     * 展示1小时内即将开抢和当前进行中的商品
+     *
+     * @return 秒杀商品集合
+     */
+    public List<FsStoreProductFlashSale> selectUpcomingFlashSaleList();
+
+    /**
+     * 更新秒杀商品库存
+     *
+     * @param id 秒杀商品ID
+     * @param stock 库存数量
+     * @return 结果
+     */
+    public int updateFlashSaleStock(Long id, Long stock);
+}

+ 10 - 0
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreProductScrmService.java

@@ -36,6 +36,14 @@ public interface IFsStoreProductScrmService
      */
     public FsStoreProductScrm selectFsStoreProductById(Long productId);
 
+    /**
+     * 查询商品
+     *
+     * @param productId 商品ID
+     * @return 商品
+     */
+    public FsStoreProductScrm selectFsStoreRedisProductById(Long productId);
+
     /**
      * 查询商品列表
      *
@@ -101,8 +109,10 @@ public interface IFsStoreProductScrmService
 
 
     List<FsStoreProductListQueryVO> selectFsStoreProductNewQuery(int count, String appId);
+    List<FsStoreProductListQueryVO> selectFsStoreProductNewQueryCount(int count);
 
     List<FsStoreProductListQueryVO> selectFsStoreProductHotQuery(int count, String appId);
+    List<FsStoreProductListQueryVO> selectFsStoreProductHotQueryCount(int count);
 
     /** 绿色有机分页(与 selectFsStoreProductNewQuery 条件一致,支持分页) */
     List<FsStoreProductListQueryVO> selectFsStoreProductNewQueryPage(int pageNum, int pageSize, String appId, String keyword);

+ 119 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/ActivityStockDataProviderImpl.java

@@ -0,0 +1,119 @@
+package com.fs.hisStore.service.impl;
+
+import com.fs.common.core.redis.service.ActivityStockData;
+import com.fs.common.core.redis.service.ActivityStockDataProvider;
+import com.fs.common.core.redis.service.ActivityStockService;
+import com.fs.hisStore.domain.FsStoreProductActivity;
+import com.fs.hisStore.mapper.FsStoreProductActivityMapper;
+import com.fs.hisStore.mapper.FsStoreProductAttrValueScrmMapper;
+import com.fs.hisStore.domain.FsStoreProductAttrValueScrm;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+@Component
+public class ActivityStockDataProviderImpl implements ActivityStockDataProvider {
+
+    @Autowired
+    private FsStoreProductActivityMapper activityMapper;
+
+    @Autowired
+    private FsStoreProductAttrValueScrmMapper attrValueMapper;
+
+    @Autowired
+    private ActivityStockService activityStockService;
+
+    @PostConstruct
+    public void init() {
+        activityStockService.setDataProvider(this);
+        log.info("ActivityStockDataProvider 注册完成");
+    }
+
+    @Override
+    public ActivityStockData loadActivityData(Integer orderType, Long associatedId) {
+        FsStoreProductActivity activity = activityMapper.selectFsStoreProductActivityById(associatedId);
+        if (activity == null || activity.getDelFlag() == 1) {
+            return null;
+        }
+        ActivityStockData data = new ActivityStockData();
+        data.setId(activity.getId());
+        data.setProductId(activity.getProductId());
+        data.setSpecId(activity.getSpecId());
+        data.setStock(activity.getSpecStock() != null ? activity.getSpecStock().longValue() : null);
+        data.setStatus(activity.getStatus());
+        if (activity.getStartTime() != null) {
+            data.setStartTime(activity.getStartTime().getTime());
+        }
+        if (activity.getEndTime() != null) {
+            data.setEndTime(activity.getEndTime().getTime());
+        }
+        return data;
+    }
+
+    @Override
+    public void updateStockToDb(Integer orderType, Long associatedId, Long redisStock) {
+        // 活动中间表已无stock字段,库存复用规格库存,下单/退款实时同步DB,无需此操作
+        log.info("活动商品{}库存同步跳过,库存复用规格库存实时同步", associatedId);
+    }
+
+    @Override
+    public List<ActivityStockData> loadAllActiveActivities(Integer orderType) {
+        List<ActivityStockData> result = new ArrayList<>();
+        FsStoreProductActivity query = new FsStoreProductActivity();
+        query.setStatus(1);
+        if (orderType != null) {
+            query.setActivityType(orderType);
+        }
+        List<FsStoreProductActivity> list = activityMapper.selectFsStoreProductActivityList(query);
+        if (list != null) {
+            for (FsStoreProductActivity item : list) {
+                ActivityStockData data = new ActivityStockData();
+                data.setId(item.getId());
+                data.setProductId(item.getProductId());
+                data.setSpecId(item.getSpecId());
+                data.setStock(item.getSpecStock() != null ? item.getSpecStock().longValue() : null);
+                data.setStatus(item.getStatus());
+                if (item.getStartTime() != null) {
+                    data.setStartTime(item.getStartTime().getTime());
+                }
+                if (item.getEndTime() != null) {
+                    data.setEndTime(item.getEndTime().getTime());
+                }
+                result.add(data);
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public void loadProductSpecStock(Long specId) {
+        if (specId == null) return;
+        try {
+            FsStoreProductAttrValueScrm attrValue = attrValueMapper.selectFsStoreProductAttrValueById(specId);
+            if (attrValue != null && attrValue.getStock() != null) {
+                activityStockService.initProductSpecStock(specId, attrValue.getStock());
+            }
+        } catch (Exception e) {
+            log.error("加载商品规格{}库存到Redis失败", specId, e);
+        }
+    }
+
+    @Override
+    public void updateProductSpecStockToDb(Long specId, long redisStock) {
+        if (specId == null) return;
+        try {
+            FsStoreProductAttrValueScrm attrValue = new FsStoreProductAttrValueScrm();
+            attrValue.setId(specId);
+            attrValue.setStock((int) redisStock);
+            attrValueMapper.updateFsStoreProductAttrValue(attrValue);
+            log.info("商品规格{}库存同步到数据库,库存={}", specId, redisStock);
+        } catch (Exception e) {
+            log.error("商品规格{}库存同步到数据库失败", specId, e);
+        }
+    }
+}

+ 55 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -222,6 +222,9 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
     @Autowired
     private FsRefundReasonMapper fsRefundReasonMapper;
 
+    @Autowired
+    private com.fs.common.core.redis.service.ActivityStockService activityStockService;
+
     /**
      * 查询售后记录
      *
@@ -908,12 +911,15 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         //获取订单下的商品
         List<FsStoreOrderItemVO> orderItemVOS=fsStoreOrderItemMapper.selectFsStoreOrderItemListByOrderId(order.getId());
 
+        // 活动订单:先回滚Redis规格库存(确保Redis和DB库存一致)
+        rollbackActivityStockIfNeeded(order, orderItemVOS);
+
         // 获取售后商品
         FsStoreAfterSalesItemScrm params = new FsStoreAfterSalesItemScrm();
         params.setStoreAfterSalesId(storeAfterSales.getId());
         List<FsStoreAfterSalesItemScrm> fsStoreAfterSalesItems = afterSalesItemService.selectFsStoreAfterSalesItemList(params);
 
-        // 退库存
+        // 退DB库存
         for (FsStoreAfterSalesItemScrm item : fsStoreAfterSalesItems) {
             FsStoreOrderItemVO itemVO = orderItemVOS.stream().filter(i -> i.getProductId().equals(item.getProductId())).findFirst().orElse(null);
             if(Objects.nonNull(itemVO) && itemVO.getIsAfterSales() == 1 && Objects.nonNull(item.getNum())){
@@ -1866,4 +1872,52 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
     public FsStoreAfterSalesScrm selectFsStoreAfterSalesByOrderCode(String orderCode) {
         return fsStoreAfterSalesMapper.selectFsStoreAfterSalesByOrderCode(orderCode);
     }
+
+    /**
+     * 判断是否为活动订单(秒杀 orderType=6 或限时折扣 orderType=7)
+     */
+    private boolean isActivityOrder(FsStoreOrderScrm order) {
+        return order != null
+                && order.getOrderType() != null
+                && (order.getOrderType() == 6 || order.getOrderType() == 7)
+                && order.getAssociatedId() != null
+                && order.getAssociatedId() > 0;
+    }
+
+    /**
+     * 活动订单退款时回滚Redis规格库存。
+     * 异常不中断退款主流程,但记录错误日志以便人工排查。
+     */
+    private void rollbackActivityStockIfNeeded(FsStoreOrderScrm order, List<FsStoreOrderItemVO> items) {
+        if (!isActivityOrder(order)) {
+            return;
+        }
+        try {
+            int totalRefundNum = 0;
+            if (items != null) {
+                for (FsStoreOrderItemVO vo : items) {
+                    if (vo.getNum() != null) {
+                        totalRefundNum += vo.getNum();
+                    }
+                }
+            }
+            if (totalRefundNum > 0) {
+                boolean success = activityStockService.rollbackStock(
+                        order.getOrderType(),
+                        order.getAssociatedId(),
+                        totalRefundNum);
+                if (success) {
+                    logger.info("售后退款Redis活动库存回滚成功,orderId={},orderType={},associatedId={},quantity={}",
+                            order.getId(), order.getOrderType(), order.getAssociatedId(), totalRefundNum);
+                } else {
+                    logger.error("售后退款Redis活动库存回滚失败!orderId={},orderType={},associatedId={},需人工处理",
+                            order.getId(), order.getOrderType(), order.getAssociatedId());
+                }
+            }
+        } catch (Exception e) {
+            logger.error("售后退款Redis活动库存回滚异常!orderId={},orderType={},associatedId={},需人工处理",
+                    order.getId(), order.getOrderType(), order.getAssociatedId(), e);
+            // 不中断退款流程
+        }
+    }
 }

+ 553 - 1
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -113,6 +113,7 @@ import com.fs.hisStore.constants.StoreConstants;
 import com.fs.hisStore.domain.*;
 import com.fs.hisStore.enums.*;
 import com.fs.hisStore.service.*;
+import com.fs.store.domain.FsStoreDelivers;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysDictTypeService;
@@ -180,6 +181,7 @@ import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeParseException;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -219,6 +221,8 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private FsStoreCartScrmMapper cartMapper;
 
+//    @Autowired
+//    private FsStoreDeliversMapper fsStoreDeliversMapper;
 
     @Autowired
     private FsUserCompanyUserMapper fsUserCompanyUserMapper;
@@ -244,6 +248,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private RedissonClient redissonClient;
 
+    @Autowired
+    private com.fs.common.core.redis.service.ActivityStockService activityStockService;
+
+    @Autowired
+    private IFsStoreProductActivityService activityService;
+
     @Autowired
     private IFsStoreCartScrmService cartService;
 
@@ -446,6 +456,15 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     @Autowired
     private FsUserCompanyPackageScrmMapper fsUserCompanyPackageScrmMapper;
 
+    @Autowired
+    private FsStoreProductDiscountMapper fsStoreProductDiscountMapper;
+
+    @Autowired
+    private FsStoreProductFlashSaleMapper fsStoreProductFlashSaleMapper;
+
+    @Autowired
+    private FsStoreProductActivityMapper activityMapper;
+
     @Value("${cloud_host.company_name}")
     private String companyName;
     @PostConstruct
@@ -781,6 +800,24 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
                 cartMapper.updateFsStoreCart(fsStoreCart);
             }
         }
+
+        if(cartParam.getProductType() != null && (cartParam.getProductType() == 6 || cartParam.getProductType() == 7) ){//更新金额
+            for (FsStoreCartQueryVO c : carts){
+                //获取对应商品金额
+                FsStoreProductActivity activity = activityMapper.selectActivityByProductIdAndSpecId(c.getProductId(), c.getProductAttrValueId());
+                if(activity == null){
+                    return R.error("操作失败,对应活动未找到或已过期!");
+                }
+                //更新购物车信息
+                FsStoreCartScrm cartScrm = new FsStoreCartScrm();
+                cartScrm.setId(c.getId());
+                BigDecimal price = cartParam.getProductType() == 7?activity.getDiscountPrice():activity.getFlashPrice();
+                cartScrm.setChangePrice(price);
+                c.setPrice(price);
+                c.setChangePrice(price);
+                cartMapper.updateFsStoreCart(cartScrm);
+            }
+        }
         String uuid = IdUtil.randomUUID();
         redisCache.setCacheObject("orderKey:" + uuid, cartParam.getCartIds(), 300, TimeUnit.SECONDS);
         redisCache.setCacheObject("orderCarts:" + uuid, carts, 300, TimeUnit.SECONDS);
@@ -933,6 +970,22 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         if (cartIds != null) {
             //获取购物车列表
             List<FsStoreCartQueryVO> carts = redisCache.getCacheObject("orderCarts:" + param.getOrderKey());
+
+            // 校验:购物车商品是否在活动期间,活动期间不允许走普通下单
+            if (carts != null) {
+                long now = System.currentTimeMillis();
+                for (FsStoreCartQueryVO cart : carts) {
+                    FsStoreProductScrm product = productService.selectFsStoreRedisProductById(cart.getProductId());
+                    if (product != null && product.getActivityType() != null && product.getActivityType() != 0
+                            && product.getActivityStartTime() != null && product.getActivityEndTime() != null) {
+                        if (now >= product.getActivityStartTime().getTime() && now <= product.getActivityEndTime().getTime()) {
+                            String typeName = product.getActivityType() == 6 ? "秒杀" : "限时折扣";
+                            return R.error("商品【" + product.getProductName() + "】正在" + typeName + "活动中,请从活动专区购买");
+                        }
+                    }
+                }
+            }
+
             //获取地址
             FsUserAddressScrm address = userAddressMapper.selectFsUserAddressById(param.getAddressId());
             //生成分布式唯一值
@@ -1236,6 +1289,422 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
     }
 
+    /**
+     * 创建秒杀/限时折扣活动订单(独立接口,流程与createOrder一致)
+     */
+    @Override
+    @Transactional
+    public R createActivityOrder(long userId, FsStoreOrderCreateParam param) {
+        Long associatedId = null;
+        Integer orderType = param.getOrderType();
+    
+        // 1. 活动参数校验
+        if (orderType == null || (orderType != 6 && orderType != 7)) {
+            return R.error("无效的活动类型");
+        }
+        if (param.getAssociatedId() == null || param.getAssociatedId() <= 0) {
+            return R.error("活动ID不能为空");
+        }
+    
+        // 2. 校验活动时间和状态
+        com.fs.common.core.redis.service.ActivityValidateResult validateResult =
+                activityStockService.validateActivityWithDetail(orderType, param.getAssociatedId());
+        if (!validateResult.isValid()) {
+            return R.error(validateResult.getMessage());
+        }
+    
+        // 3. 确保Redis活动信息和规格库存已初始化(活动无独立库存,用规格库存)
+        FsStoreProductActivity activityInfo = activityService.selectFsStoreProductActivityById(param.getAssociatedId());
+        if (activityInfo == null) {
+            return R.error("活动信息不存在");
+        }
+        activityStockService.initActivityInfo(
+                param.getAssociatedId(), 1,
+                activityInfo.getStartTime().getTime(), activityInfo.getEndTime().getTime(),
+                activityInfo.getProductId(), activityInfo.getSpecId(), null
+        );
+        if (activityInfo.getSpecId() != null && activityInfo.getSpecStock() != null) {
+            activityStockService.initProductSpecStock(activityInfo.getSpecId(), activityInfo.getSpecStock());
+        }
+
+        // 3.5 提前获取购物车,计算活动商品总件数,确保Lua扣减数量与DB扣减数量一致
+        String preCartIds = redisCache.getCacheObject("orderKey:" + param.getOrderKey());
+        List<FsStoreCartQueryVO> preCarts = null;
+        int activityDeductNum = 1; // 默认扣减1件
+        if (preCartIds != null) {
+            preCarts = redisCache.getCacheObject("orderCarts:" + param.getOrderKey());
+            if (preCarts != null) {
+                activityDeductNum = 0;
+                for (FsStoreCartQueryVO cart : preCarts) {
+                    if (cart.getCartNum() != null) {
+                        activityDeductNum += cart.getCartNum();
+                    }
+                }
+                if (activityDeductNum <= 0) {
+                    activityDeductNum = 1;
+                }
+            }
+        }
+
+        // 4. Lua原子扣减活动库存(已移除 getStock() 预检查——先查再扣存在竞态窗口,Lua脚本本身会原子判断库存是否充足)
+        boolean deductSuccess = activityStockService.deductStock(orderType, param.getAssociatedId(), activityDeductNum);
+        if (!deductSuccess) {
+            return R.error("活动商品已售罄,请稍后重试");
+        }
+        associatedId = param.getAssociatedId();
+
+        try {
+            FsUserCompanyUser fsUserCompanyUser = fsUserCompanyUserMapper.selectFsUserCompanyUserByUserId(userId);
+            if (ObjectUtil.isNotEmpty(fsUserCompanyUser) && param.getVideoId()!=null && param.getCompanyId() == null){
+                param.setCompanyId(fsUserCompanyUser.getCompanyId());
+                param.setCompanyUserId(fsUserCompanyUser.getCompanyUserId());
+            }
+    
+            if (!CloudHostUtils.hasCloudHostName("鹤颜堂")){
+                if (ObjectUtil.isEmpty(param.getAddressId())){
+                    return R.error("地址不能为空!");
+                }
+            }
+    
+            FsStoreOrderComputedParam computedParam = new FsStoreOrderComputedParam();
+            BeanUtils.copyProperties(param, computedParam);
+            //计算金额
+            FsStoreOrderComputeDTO dto;
+            try {
+                dto = this.computedOrder(userId, computedParam);
+            } catch (ServiceException e) {
+                if ("偏远地区暂不可购买".equals(e.getMessage())) {
+                    return R.error("偏远地区暂不可购买");
+                }
+                throw e;
+            }
+            String cartIds = redisCache.getCacheObject("orderKey:" + param.getOrderKey());
+            Integer payType = redisCache.getCacheObject("createOrderPayType:" + param.getCreateOrderKey());
+            if (payType != null) {
+                param.setPayType(payType.toString());
+            }
+            BigDecimal integral = BigDecimal.ZERO;
+            if (cartIds != null) {
+                //获取购物车列表
+                List<FsStoreCartQueryVO> carts = redisCache.getCacheObject("orderCarts:" + param.getOrderKey());
+                //获取地址
+                FsUserAddressScrm address = userAddressMapper.selectFsUserAddressById(param.getAddressId());
+                //生成分布式唯一值
+                String orderSn = SnowflakeUtil.nextIdStr();
+                //是否使用积分
+                Boolean isIntegral = false;
+                //组合数据
+                FsStoreOrderScrm storeOrder = new FsStoreOrderScrm();
+                storeOrder.setStoreHouseCode("CK01");
+                if(param.getCompanyId()!=null){
+                    storeOrder.setCompanyId(param.getCompanyId());
+                }
+                if(param.getCompanyUserId()!=null){
+                    storeOrder.setCompanyUserId(param.getCompanyUserId());
+                }
+                if ("北京卓美".equals(companyName) && param.getVideoId()!=null){
+                    storeOrder.setVideoId(param.getVideoId());
+                    storeOrder.setCourseId(param.getCourseId());
+                    storeOrder.setPeriodId(param.getPeriodId());
+                    storeOrder.setProjectId(param.getProjectId());
+                }
+                String json = configService.selectConfigByKey("store.config");
+                StoreConfig config= JSONUtil.toBean(json, StoreConfig.class);
+    
+                //绑定销售
+                if("北京卓美".equals(companyName) && param.getVideoId()!=null && storeOrder.getCompanyId() == null || !("北京卓美").equals(companyName)){
+                    FsUserScrm fsuser= userService.selectFsUserById(userId);
+                    if(ObjectUtil.isEmpty(config.getOrderAttribution())
+                            ||!config.getOrderAttribution().equals(1)){
+                        if(param.getCompanyUserId()!=null){
+                            if (ObjectUtil.isNotEmpty(fsuser.getCompanyUserId())&&!fsuser.getCompanyUserId().equals(param.getCompanyUserId())){
+                                CompanyUser companyUser=companyUserService.selectCompanyUserById(fsuser.getCompanyUserId());
+                                return R.error(String.format("请联系【%s】销售进行购买商品!",companyUser.getUserName()));
+                            }else {
+                                fsuser.setCompanyUserId(param.getCompanyUserId());
+                                userService.updateFsUser(fsuser);
+                            }
+                            CompanyUser companyUser=companyUserService.selectCompanyUserById(param.getCompanyUserId());
+                            if(companyUser!=null){
+                                storeOrder.setDeptId(companyUser.getDeptId());
+                            }
+                        }else {
+                            storeOrder.setCompanyUserId(fsuser.getCompanyUserId());
+                        }
+                    }
+                }
+    
+                CompanyUserUser map=new CompanyUserUser();
+                map.setCompanyUserId(param.getCompanyUserId());
+                map.setUserId(userId);
+                List<CompanyUserUser> list= companyUserUserMapper.selectCompanyUserUserList(map);
+                if(list==null||list.size()==0){
+                    CompanyUser companyUser=companyUserService.selectCompanyUserById(param.getCompanyUserId());
+                    if(companyUser!=null&&companyUser.getStatus().equals("0")){
+                        map.setCompanyId(companyUser.getCompanyId());
+                        companyUserUserMapper.insertCompanyUserUser(map);
+                    }
+                }
+    
+                storeOrder.setUserId(userId);
+                storeOrder.setOrderCode(orderSn);
+                if (ObjectUtil.isNotEmpty(address)){
+                    storeOrder.setRealName(address.getRealName());
+                    storeOrder.setUserPhone(address.getPhone());
+                    storeOrder.setUserAddress(address.getProvince() + " " + address.getCity() +
+                            " " + address.getDistrict() + " " + address.getDetail().trim());
+                }
+                storeOrder.setCartId(cartIds);
+                storeOrder.setTotalNum(Long.parseLong(String.valueOf(carts.size())));
+                storeOrder.setTotalPrice(dto.getTotalPrice());
+                storeOrder.setTotalPostage(dto.getPayPostage());
+    
+                //优惠券处理
+                if (param.getCouponUserId() != null) {
+                    FsStoreCouponUserScrm couponUser = couponUserService.selectFsStoreCouponUserById(param.getCouponUserId());
+                    if (couponUser != null && couponUser.getStatus() == 0) {
+                        storeOrder.setCouponId(couponUser.getId());
+                        storeOrder.setCouponPrice(couponUser.getCouponPrice());
+                        couponUser.setStatus(1);
+                        couponUser.setUseTime(new Date());
+                        couponUserService.updateFsStoreCouponUser(couponUser);
+                    }
+                }
+                //处理推荐人
+                FsUserScrm user = userService.selectFsUserById(storeOrder.getUserId());
+                if (user.getSpreadUserId() != null && user.getSpreadUserId() > 0) {
+                    storeOrder.setTuiUserId(user.getSpreadUserId());
+                } else {
+                    if (param.getTuiUserId() != null) {
+                        FsUserScrm tuiUser = userService.selectFsUserById(param.getTuiUserId());
+                        if (tuiUser != null && tuiUser.getIsPromoter() == 1) {
+                            storeOrder.setTuiUserId(param.getTuiUserId());
+                        }
+                    }
+                }
+                storeOrder.setPayPostage(dto.getPayPostage());
+                storeOrder.setDeductionPrice(dto.getDeductionPrice());
+                storeOrder.setPaid(0);
+                storeOrder.setPayType(param.getPayType());
+                if (isIntegral) {
+                    storeOrder.setPayIntegral(integral);
+                }
+                storeOrder.setUseIntegral(BigDecimal.valueOf(dto.getUsedIntegral()));
+                storeOrder.setBackIntegral(BigDecimal.ZERO);
+                storeOrder.setGainIntegral(BigDecimal.ZERO);
+                storeOrder.setMark(param.getMark());
+                //获取成本价
+                BigDecimal costPrice = this.getOrderSumPrice(carts, "costPrice");
+                storeOrder.setCost(costPrice);
+                storeOrder.setIsChannel(1);
+                storeOrder.setShippingType(1);
+                storeOrder.setCreateTime(new Date());
+    
+                if (config.getServiceFee() != null) {
+                    storeOrder.setServiceFee(config.getServiceFee());
+                }
+    
+                //后台制单处理
+                if (param.getPayPrice() != null && param.getPayPrice().compareTo(BigDecimal.ZERO) > 0) {
+                    if (param.getPayPrice().compareTo(dto.getTotalPrice()) > 0) {
+                        return R.error("改价价格不能大于商品总价");
+                    }
+                    storeOrder.setPayPrice(param.getPayPrice());
+                } else {
+                    storeOrder.setPayPrice(dto.getPayPrice());
+                }
+    
+                //付款方式
+                if (param.getPayType().equals("1") || param.getPayType().equals("99")) {
+                    storeOrder.setStatus(0);
+                    if("广州郑多燕".equals(cloudHostProper.getCompanyName())){
+                        BigDecimal amount = redisCache.getCacheObject("createOrderAmount:" + param.getCreateOrderKey());
+                        storeOrder.setPayDelivery(amount != null ? amount : BigDecimal.ZERO);
+                    }
+                }
+                else if (param.getPayType().equals("2")) {
+                    storeOrder.setStatus(1);
+                }
+                else if (param.getPayType().equals("3")) {
+                    BigDecimal amount = param.getAmount();
+                    if (amount != null && amount.compareTo(BigDecimal.ZERO) > 0) {
+                        storeOrder.setStatus(0);
+                        storeOrder.setPayMoney(amount);
+                        storeOrder.setPayDelivery(storeOrder.getPayPrice().subtract(amount));
+                    } else {
+                        storeOrder.setStatus(1);
+                    }
+                }
+                Boolean isPay = true;
+                if (param.getOrderCreateType() != null && param.getOrderCreateType() == 3 && param.getCompanyId() != null) {
+                    Company company = companyMapper.selectCompanyById(param.getCompanyId());
+                    if (company != null) {
+                        if (company.getIsPay() != null && company.getIsPay() == 0 && param.getIsUserApp()) {
+                            storeOrder.setStatus(1);
+                            isPay = false;
+                        }
+                    }
+                }
+                storeOrder.setOrderCreateType(param.getOrderCreateType());
+                Long prescribe = carts.stream().filter(item -> item.getProductType() != null && item.getProductType() == 2).count();
+                if (prescribe > 0) {
+                    storeOrder.setIsPrescribe(1);
+                } else {
+                    storeOrder.setIsPrescribe(0);
+                }
+                if (storeOrder.getCustomerId() == null) {
+                    storeOrder.setCustomerId(param.getCustomerId());
+                }
+                FsStoreOrderScrm tempOrder = redisCache.getCacheObject("orderInfo:" + param.getCreateOrderKey());
+                if (tempOrder != null) {
+                    storeOrder.setOrderType(tempOrder.getOrderType());
+                    storeOrder.setOrderMedium(tempOrder.getOrderMedium());
+                    redisCache.deleteObject("orderInfo:" + param.getCreateOrderKey());
+                } else {
+                    storeOrder.setOrderType(param.getOrderType());
+                    storeOrder.setOrderMedium(param.getOrderMedium());
+                }
+    
+                //活动商品只扣Redis(已在上文扣减),DB延后扣减
+                // storeOrder.setAssociatedId
+                storeOrder.setAssociatedId(associatedId);
+                Integer flag = fsStoreOrderMapper.insertFsStoreOrder(storeOrder);
+                if (flag == 0) {
+                    return R.error("订单创建失败");
+                }
+    
+                // 活动商品:订单创建成功后,同步扣减DB中的商品规格库存和销量(与普通下单一致)
+                this.deStockIncSale(carts);
+    
+                if (!isPay && storeOrder.getCompanyId() != null) {
+                    addOrderAudit(storeOrder);
+                }
+                //使用了积分扣积分
+                if (dto.getUsedIntegral() > 0) {
+                    this.decIntegral(userId, dto.getUsedIntegral(), dto.getDeductionPrice(), storeOrder.getId().toString());
+                }
+    
+                //保存OrderItem
+                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());
+                    fsStoreCartDTO.setSku(vo.getProductAttrName());
+                    fsStoreCartDTO.setProductName(vo.getProductName());
+                    fsStoreCartDTO.setNum(vo.getCartNum());
+                    fsStoreCartDTO.setBarCode(vo.getBarCode());
+                    fsStoreCartDTO.setGroupBarCode(vo.getGroupBarCode());
+                    fsStoreCartDTO.setBrokerage(vo.getBrokerage());
+                    fsStoreCartDTO.setBrokerageTwo(vo.getBrokerageTwo());
+                    fsStoreCartDTO.setBrokerageThree(vo.getBrokerageThree());
+                    if (StringUtils.isEmpty(vo.getProductAttrImage())) {
+                        fsStoreCartDTO.setImage(vo.getProductImage());
+                    } else {
+                        fsStoreCartDTO.setImage(vo.getProductAttrImage());
+                    }
+                    FsStoreOrderItemScrm item = new FsStoreOrderItemScrm();
+                    item.setOrderId(storeOrder.getId());
+                    item.setOrderCode(orderSn);
+                    item.setCartId(vo.getId());
+                    item.setProductId(vo.getProductId());
+                    item.setJsonInfo(JSONUtil.toJsonStr(fsStoreCartDTO));
+                    item.setNum(vo.getCartNum());
+                    item.setIsAfterSales(0);
+                    if (vo.getProductType().equals(2)) {
+                        item.setIsPrescribe(1);
+                    }
+                    fsStoreOrderItemMapper.insertFsStoreOrderItem(item);
+                    listOrderItem.add(item);
+                }
+                if (listOrderItem.size() > 0) {
+                    String itemJson = JSONUtil.toJsonStr(listOrderItem);
+                    storeOrder.setItemJson(itemJson);
+                    fsStoreOrderMapper.updateFsStoreOrder(storeOrder);
+                }
+                //购物车状态修改
+                cartMapper.updateIsPay(cartIds);
+    
+                //删除缓存
+                redisCache.deleteObject("orderKey:" + param.getOrderKey());
+                if (config.getIsBrushOrders() == null || !(config.getIsBrushOrders() && param.getCompanyUserId() != null)) {
+                    redisCache.deleteObject("orderCarts:" + param.getOrderKey());
+                }
+    
+                //添加记录
+                orderStatusService.create(storeOrder.getId(), OrderLogEnum.CREATE_ORDER.getValue(),
+                        OrderLogEnum.CREATE_ORDER.getDesc());
+    
+                //加入redis,24小时自动取消
+                String redisKey = String.valueOf(StrUtil.format("{}{}",
+                        StoreConstants.REDIS_ORDER_OUTTIME_UNPAY, storeOrder.getId()));
+                if (config.getUnPayTime() != null && config.getUnPayTime() > 0) {
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(), config.getUnPayTime(), TimeUnit.MINUTES);
+                } else {
+                    redisCache.setCacheObject(redisKey, storeOrder.getId(), 30, TimeUnit.MINUTES);
+                }
+                //添加支付到期时间
+                Calendar calendar = Calendar.getInstance();
+                calendar.setTime(storeOrder.getCreateTime());
+                if (config.getUnPayTime() != null) {
+                    calendar.add(Calendar.MINUTE, config.getUnPayTime());
+                }
+                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                String payLimitTime = format.format(calendar.getTime());
+                redisCache.setCacheObject("orderAmount:" + storeOrder.getId(), storeOrder.getPayMoney(), 24, TimeUnit.HOURS);
+                //删除推荐订单KEY
+                String createOrderKey = param.getCreateOrderKey();
+                if("鸿森堂".equals(cloudHostProper.getCompanyName())){
+                    BigDecimal amount = redisCache.getCacheObject("createOrderAmount:" + createOrderKey);
+                    redisCache.setCacheObject("orderAmount:" + storeOrder.getId(), amount, 24, TimeUnit.HOURS);
+                }else {
+                    if (StringUtils.isNotEmpty(createOrderKey)) {
+                        if (config.getIsBrushOrders() == null || !(config.getIsBrushOrders() && param.getCompanyUserId() != null)) {
+                            redisCache.deleteObject("createOrderKey:" + createOrderKey);
+                            redisCache.deleteObject("orderCarts:" + createOrderKey);
+                            redisCache.deleteObject("createOrderMoney:" + createOrderKey);
+                        }
+                        BigDecimal amount = redisCache.getCacheObject("createOrderAmount:" + createOrderKey);
+                        redisCache.deleteObject("createOrderAmount:" + createOrderKey);
+                    }
+                }
+                // 订单创建成功后,异步同步活动中间表库存到DB(最终一致性兜底)
+                final Long finalAssociatedId = param.getAssociatedId();
+                final Integer finalOrderType = orderType;
+                CompletableFuture.runAsync(() -> {
+                    try {
+                        if (finalOrderType == 6) {
+                            activityStockService.syncFlashSaleStockToDb(finalAssociatedId,
+                                    (long) activityStockService.getStock(finalOrderType, finalAssociatedId));
+                        } else if (finalOrderType == 7) {
+                            activityStockService.syncDiscountStockToDb(finalAssociatedId,
+                                    (long) activityStockService.getStock(finalOrderType, finalAssociatedId));
+                        }
+                    } catch (Exception e) {
+                        log.error("活动中间表库存异步同步失败,associatedId={}, orderType={}", finalAssociatedId, finalOrderType, e);
+                    }
+                });
+
+                return R.ok().put("order", storeOrder).put("payLimitTime", payLimitTime);
+            } else {
+                return R.error("订单已过期");
+            }
+        } catch (Exception e) {
+            // Redis已扣减成功,订单创建过程异常,必须回滚Redis库存
+            try {
+                activityStockService.rollbackStock(orderType, param.getAssociatedId(), activityDeductNum);
+                log.info("订单创建异常,Redis库存已回滚,associatedId={}, orderType={}, 回滚数量={}",
+                        param.getAssociatedId(), orderType, activityDeductNum);
+            } catch (Exception rollbackEx) {
+                log.error("订单创建异常后Redis库存回滚失败!associatedId={},orderType={},需要人工处理",
+                        param.getAssociatedId(), orderType, rollbackEx);
+            }
+            log.error("活动订单创建异常,orderType={}, associatedId={}", orderType, param.getAssociatedId(), e);
+            throw e;
+        }
+    }
+
     @Override
     public R createOrderByPrescribe(Long prescribeId) {
         FsPrescribe prescribe = prescribeMapper.selectFsPrescribeByPrescribeId(prescribeId);
@@ -1356,6 +1825,14 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         }
     }
 
+    /**
+     * @deprecated 已改用deStockIncSale统一扣减
+     */
+    @Deprecated
+    private void decActivityProductStock(List<FsStoreCartQueryVO> cartInfo) {
+        deStockIncSale(cartInfo);
+    }
+
     //未支付取消订单
     @Override
     public void cancelOrder(Long orderId) {
@@ -2646,6 +3123,12 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         //退库存
         //获取订单下的商品
         List<FsStoreOrderItemVO> orderItemVOS = fsStoreOrderItemMapper.selectFsStoreOrderItemListByOrderId(order.getId());
+
+        // 活动订单:先回滚Redis规格库存
+        if (isActivityOrder(order)) {
+            refundActivityStock(order, orderItemVOS);
+        }
+
         for (FsStoreOrderItemVO vo : orderItemVOS) {
             if (vo.getIsAfterSales() == 1) {
                 productService.incProductStock(vo.getNum(), vo.getProductId(), vo.getProductAttrValueId());
@@ -3464,6 +3947,13 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
     private void refundStock(FsStoreOrderScrm order) {
         //获取订单下的商品
         List<FsStoreOrderItemVO> orderItemVOS = fsStoreOrderItemMapper.selectFsStoreOrderItemListByOrderId(order.getId());
+
+        // 活动订单:先回滚Redis规格库存
+        if (isActivityOrder(order)) {
+            refundActivityStock(order, orderItemVOS);
+        }
+
+        // DB规格库存回滚
         for (FsStoreOrderItemVO vo : orderItemVOS) {
             productService.incProductStock(vo.getNum(), vo.getProductId()
                     , vo.getProductAttrValueId());
@@ -3471,6 +3961,46 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
 
     }
 
+    /**
+     * 判断是否为活动订单(秒杀 orderType=6 或限时折扣 orderType=7)
+     */
+    private boolean isActivityOrder(FsStoreOrderScrm order) {
+        return order != null
+                && order.getOrderType() != null
+                && (order.getOrderType() == 6 || order.getOrderType() == 7)
+                && order.getAssociatedId() != null
+                && order.getAssociatedId() > 0;
+    }
+
+    /**
+     * 活动订单退款时回滚Redis规格库存(Lua原子操作)。
+     * 异常不中断退款主流程,但记录错误日志以便人工排查。
+     */
+    private void refundActivityStock(FsStoreOrderScrm order, List<FsStoreOrderItemVO> items) {
+        try {
+            int totalRefundNum = 0;
+            if (items != null) {
+                for (FsStoreOrderItemVO vo : items) {
+                    if (vo.getNum() != null) {
+                        totalRefundNum += vo.getNum();
+                    }
+                }
+            }
+            if (totalRefundNum > 0) {
+                activityStockService.rollbackStock(
+                        order.getOrderType(),
+                        order.getAssociatedId(),
+                        totalRefundNum);
+                log.info("活动订单退款Redis库存回滚成功,orderId={},orderType={},associatedId={},quantity={}",
+                        order.getId(), order.getOrderType(), order.getAssociatedId(), totalRefundNum);
+            }
+        } catch (Exception e) {
+            log.error("活动订单退款Redis库存回滚失败!orderId={},orderType={},associatedId={},需人工处理",
+                    order.getId(), order.getOrderType(), order.getAssociatedId(), e);
+            // 不中断退款流程
+        }
+    }
+
 
     /**
      * 获取订单价格
@@ -6431,10 +6961,32 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
         FsStoreOrderPriceDTO priceGroup = this.getOrderPriceGroup(carts, userAddress);
         BigDecimal payPostage = priceGroup.getStorePostage();
         BigDecimal badCode = BigDecimal.valueOf(-1);
-        // 检查运费计算结果,如果是 -1 表示偏远地区不可购买
         if (payPostage.compareTo(badCode) == 0) {
             throw new ServiceException("偏远地区暂不可购买");
         }
         return payPostage;
     }
+
+//    @Override
+//    public R getExpressMulti(FsStoreOrder order) {
+//        //顺丰轨迹查询处理
+//        String lastFourNumber = PhoneUtils.getLastFourNum(order.getUserPhone());
+//        // 获取该订单关联的物流信息
+//        List<FsStoreDelivers> fsStoreDelivers = fsStoreDeliversMapper.findByOrderId(order.getId());
+//        if(CollectionUtils.isEmpty(fsStoreDelivers)){
+//            return R.ok("当前订单号暂无物流信息");
+//        }
+//
+//        List<ExpressInfoDTO> result = new ArrayList<>();
+//        for (FsStoreDelivers fsStoreDeliver : fsStoreDelivers) {
+//            ExpressInfoDTO dto=expressService.getExpressInfo(order.getOrderCode(),
+//                    fsStoreDeliver.getDeliverSn(),
+//                    fsStoreDeliver.getDeliverId(),
+//                    lastFourNumber);
+//            result.add(dto);
+//        }
+//
+//        return R.ok().put("data",result);
+//    }
+
 }

+ 244 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductActivityServiceImpl.java

@@ -0,0 +1,244 @@
+package com.fs.hisStore.service.impl;
+
+import java.util.List;
+
+import com.fs.common.core.redis.RedisCache;
+import com.fs.hisStore.domain.FsStoreProductActivity;
+import com.fs.hisStore.mapper.FsStoreProductActivityMapper;
+import com.fs.hisStore.service.IFsStoreProductActivityService;
+import com.fs.common.core.redis.service.ActivityStockService;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 商品活动中间表Service实现
+ *
+ * @author fs
+ * @date 2026-04-16
+ */
+@Slf4j
+@Service
+public class FsStoreProductActivityServiceImpl implements IFsStoreProductActivityService {
+
+    @Autowired
+    private FsStoreProductActivityMapper activityMapper;
+
+    @Autowired
+    private ActivityStockService activityStockService;
+
+    @Autowired
+    public RedisCache redisCache;
+
+    @Autowired
+    private RedissonClient redissonClient;
+
+    private static final String EXPIRED_LOCK_PREFIX = "lock:activity:expired:";
+
+
+    /** 统一活动信息key前缀 */
+    private static final String ACTIVITY_INFO_KEY = "activity:info:";
+    /** 原商品规格库存key前缀 */
+    private static final String PRODUCT_SPEC_STOCK_KEY = "product:spec:stock:";
+
+    @Override
+    public FsStoreProductActivity selectFsStoreProductActivityById(Long id) {
+        return activityMapper.selectFsStoreProductActivityById(id);
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectFsStoreProductActivityList(FsStoreProductActivity query) {
+        return activityMapper.selectFsStoreProductActivityList(query);
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectByProductId(Long productId) {
+        return activityMapper.selectByProductId(productId);
+    }
+
+    @Override
+    @Transactional
+    public int saveActivity(Long productId, Integer activityType, List<FsStoreProductActivity> activityList) {
+        //校验:如果活动正在进行中则不允许修改
+        if (activityType != null && activityType != 0) {
+            FsStoreProductActivity ongoing = activityMapper.selectOngoingActivity(productId);
+            if (ongoing != null) {
+                throw new RuntimeException("当前商品正在" + (ongoing.getActivityType() == 6 ? "秒杀" : "限时折扣") + "活动中,活动进行期间无法修改活动设置");
+            }
+        }
+
+        //逻辑删除该商品旧的活动记录
+        activityMapper.deleteByProductId(productId);
+        int result = 0;
+        java.util.Date activityStartTime = null;
+        java.util.Date activityEndTime = null;
+        if (activityType != null && activityType != 0 && activityList != null && !activityList.isEmpty()) {
+            //批量插入新的活动记录
+            for (FsStoreProductActivity item : activityList) {
+                item.setProductId(productId);
+                item.setActivityType(activityType);
+                item.setStatus(1); // 直接设为上架,依赖时间范围过滤控制展示
+                item.setDelFlag(0);
+                // 取活动时间(所有规格共用同一时间)
+                if (item.getStartTime() != null) activityStartTime = item.getStartTime();
+                if (item.getEndTime() != null) activityEndTime = item.getEndTime();
+            }
+            result = activityMapper.batchInsertActivity(activityList);
+        }
+
+        //同步更新商品表的activity_type及活动时间
+        activityMapper.updateProductActivityType(productId, activityType != null ? activityType : 0, activityStartTime, activityEndTime);
+
+        //初始化Redis活动信息和规格库存(确保小程序接口能立即查到库存)
+        if (activityType != null && activityType != 0 && activityList != null && !activityList.isEmpty()) {
+            for (FsStoreProductActivity item : activityList) {
+                //清理对应的redis数据
+                redisCache.deleteObject(PRODUCT_SPEC_STOCK_KEY + item.getSpecId());
+                redisCache.deleteObject(ACTIVITY_INFO_KEY + item.getId());
+                if (item.getId() != null) {
+                    Long st = item.getStartTime() != null ? item.getStartTime().getTime() : null;
+                    Long et = item.getEndTime() != null ? item.getEndTime().getTime() : null;
+                    activityStockService.initActivityInfo(
+                            item.getId(), item.getStatus(),
+                            st, et,
+                            item.getProductId(), item.getSpecId(), null
+                    );
+                    // specStock是关联查询字段,batchInsert后item中无此值,需从规格表读取
+                    if (item.getSpecId() != null) {
+                        if (item.getSpecStock() != null) {
+                            activityStockService.initProductSpecStock(item.getSpecId(), item.getSpecStock());
+                        } else {
+                            // 从活动中间表重新JOIN查询获取specStock
+                            FsStoreProductActivity dbItem = activityMapper.selectFsStoreProductActivityById(item.getId());
+                            if (dbItem != null && dbItem.getSpecStock() != null) {
+                                activityStockService.initProductSpecStock(item.getSpecId(), dbItem.getSpecStock());
+                            }
+                        }
+                    }
+                    log.info("保存活动后初始化Redis,activityId={}, specId={}",
+                            item.getId(), item.getSpecId());
+                }
+            }
+        }
+        redisCache.deleteObject("fs:product:id:" + productId);//删除缓存预防创建订单无法校验
+        log.info("保存商品{}活动设置完成,activityType={}", productId, activityType);
+        return result;
+    }
+
+    @Override
+    public FsStoreProductActivity selectOngoingActivity(Long productId) {
+        return activityMapper.selectOngoingActivity(productId);
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectUpcomingList(Integer activityType) {
+        return activityMapper.selectUpcomingList(activityType);
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectExpiredList() {
+        return activityMapper.selectExpiredList();
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectStartingList() {
+        return activityMapper.selectStartingList();
+    }
+
+    @Override
+    public int updateStatus(Long id, Integer status) {
+        return activityMapper.updateStatus(id, status);
+    }
+
+
+    @Override
+    public void handleExpiredActivity(Long productId, List<FsStoreProductActivity> activityList) {
+        // 分布式锁防并发:同一商品只允许一个请求执行同步+重置
+        String lockKey = EXPIRED_LOCK_PREFIX + productId;
+        RLock lock = redissonClient.getLock(lockKey);
+        try {
+            boolean locked = lock.tryLock(3, 30, TimeUnit.SECONDS);
+            if (!locked) {
+                log.info("活动过期处理锁竞争失败,其他请求正在处理, productId={}", productId);
+                return;
+            }
+            try {
+                // 更新活动状态为已结束,并同步Redis缓存
+                if (activityList != null) {
+                    for (FsStoreProductActivity item : activityList) {
+                        activityMapper.updateStatus(item.getId(), 0);
+                        // 更新Redis中的活动状态为已结束
+                        if (item.getStartTime() != null && item.getEndTime() != null) {
+                            activityStockService.initActivityInfo(
+                                    item.getId(), 0,
+                                    item.getStartTime().getTime(), item.getEndTime().getTime(),
+                                    item.getProductId(), item.getSpecId(), null
+                            );
+                        }
+                        // 活动过期时,同步Redis规格库存到DB,并清理Redis残留key
+                        // 避免新活动复用同一规格时使用残留的错误库存值
+                        if (item.getSpecId() != null) {
+                            try {
+                                activityStockService.syncProductSpecStockToDbBySpecId(item.getSpecId());
+                                // 清理Redis中的规格库存key,下次访问时会从DB重新加载
+                                redisCache.deleteObject(PRODUCT_SPEC_STOCK_KEY + item.getSpecId());
+                                log.info("活动过期清理Redis规格库存key,specId={}", item.getSpecId());
+                            } catch (Exception e) {
+                                log.error("活动过期同步/清理规格库存异常,specId={}", item.getSpecId(), e);
+                            }
+                        }
+                        // 清理Redis中的活动信息key
+                        redisCache.deleteObject(ACTIVITY_INFO_KEY + item.getId());
+                    }
+                }
+
+                activityMapper.updateProductActivityType(productId, 0, null, null);
+                log.info("活动过期即时处理完成, productId={}, activity_type已重置为0", productId);
+            } finally {
+                if (lock.isHeldByCurrentThread()) {
+                    lock.unlock();
+                }
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("活动过期处理获取锁中断, productId={}", productId, e);
+        }
+    }
+
+    // ===== 小程序端查询方法 =====
+
+    @Override
+    public List<FsStoreProductActivity> selectUpcomingFlashSaleActivityList() {
+        return activityMapper.selectUpcomingFlashSaleActivityList();
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectUpcomingDiscountActivityList() {
+        return activityMapper.selectUpcomingDiscountActivityList();
+    }
+
+    @Override
+    public FsStoreProductActivity selectActivityDetailById(Long id) {
+        return activityMapper.selectActivityDetailById(id);
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectFlashSaleActivityByProductId(Long productId) {
+        return activityMapper.selectFlashSaleActivityByProductId(productId);
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectDiscountActivityByProductId(Long productId) {
+        return activityMapper.selectDiscountActivityByProductId(productId);
+    }
+
+    @Override
+    public List<FsStoreProductActivity> selectActivitySpecsByProductIdAndType(Long productId, Integer activityType) {
+        return activityMapper.selectActivitySpecsByProductIdAndType(productId, activityType);
+    }
+}

+ 144 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductDiscountServiceImpl.java

@@ -0,0 +1,144 @@
+package com.fs.hisStore.service.impl;
+
+import java.util.List;
+
+import com.fs.hisStore.domain.FsStoreProductDiscount;
+import com.fs.hisStore.mapper.FsStoreProductDiscountMapper;
+import com.fs.hisStore.service.IFsStoreProductDiscountService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 限时折扣商品Service业务层处理
+ *
+ * @author fs
+ * @date 2026-04-03
+ */
+@Service
+public class FsStoreProductDiscountServiceImpl implements IFsStoreProductDiscountService
+{
+    @Autowired
+    private FsStoreProductDiscountMapper fsStoreProductDiscountMapper;
+
+    /**
+     * 查询限时折扣商品
+     *
+     * @param id 限时折扣商品主键
+     * @return 限时折扣商品
+     */
+    @Override
+    public FsStoreProductDiscount selectFsStoreProductDiscountById(Long id)
+    {
+        return fsStoreProductDiscountMapper.selectFsStoreProductDiscountById(id);
+    }
+
+    /**
+     * 查询限时折扣商品列表
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 限时折扣商品
+     */
+    @Override
+    public List<FsStoreProductDiscount> selectFsStoreProductDiscountList(FsStoreProductDiscount fsStoreProductDiscount)
+    {
+        return fsStoreProductDiscountMapper.selectFsStoreProductDiscountList(fsStoreProductDiscount);
+    }
+
+    /**
+     * 新增限时折扣商品
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 结果
+     */
+    @Override
+    public int insertFsStoreProductDiscount(FsStoreProductDiscount fsStoreProductDiscount)
+    {
+        return fsStoreProductDiscountMapper.insertFsStoreProductDiscount(fsStoreProductDiscount);
+    }
+
+    /**
+     * 修改限时折扣商品
+     *
+     * @param fsStoreProductDiscount 限时折扣商品
+     * @return 结果
+     */
+    @Override
+    public int updateFsStoreProductDiscount(FsStoreProductDiscount fsStoreProductDiscount)
+    {
+        return fsStoreProductDiscountMapper.updateFsStoreProductDiscount(fsStoreProductDiscount);
+    }
+
+    /**
+     * 批量删除限时折扣商品
+     *
+     * @param ids 需要删除的主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsStoreProductDiscountByIds(Long[] ids)
+    {
+        return fsStoreProductDiscountMapper.deleteFsStoreProductDiscountByIds(ids);
+    }
+
+    /**
+     * 删除限时折扣商品信息
+     *
+     * @param id 限时折扣商品主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsStoreProductDiscountById(Long id)
+    {
+        return fsStoreProductDiscountMapper.deleteFsStoreProductDiscountById(id);
+    }
+
+    /**
+     * 查询当前有效的限时折扣商品列表(小程序端使用)
+     *
+     * @return 限时折扣商品集合
+     */
+    @Override
+    public List<FsStoreProductDiscount> selectActiveDiscountList()
+    {
+        return fsStoreProductDiscountMapper.selectActiveDiscountList();
+    }
+
+    /**
+     * 根据商品ID查询限时折扣信息
+     *
+     * @param productId 商品ID
+     * @return 限时折扣商品
+     */
+    @Override
+    public FsStoreProductDiscount selectDiscountByProductId(Long productId)
+    {
+        return fsStoreProductDiscountMapper.selectDiscountByProductId(productId);
+    }
+
+    /**
+     * 查询即将开抢和未过期的折扣商品列表(小程序端使用)
+     *
+     * @return 折扣商品集合
+     */
+    @Override
+    public List<FsStoreProductDiscount> selectUpcomingDiscountList()
+    {
+        return fsStoreProductDiscountMapper.selectUpcomingDiscountList();
+    }
+
+    /**
+     * 更新折扣商品库存
+     *
+     * @param id 折扣商品ID
+     * @param stock 库存数量
+     * @return 结果
+     */
+    @Override
+    public int updateDiscountStock(Long id, Long stock)
+    {
+        FsStoreProductDiscount update = new FsStoreProductDiscount();
+        update.setId(id);
+        update.setStock(stock);
+        return fsStoreProductDiscountMapper.updateFsStoreProductDiscount(update);
+    }
+}

+ 144 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductFlashSaleServiceImpl.java

@@ -0,0 +1,144 @@
+package com.fs.hisStore.service.impl;
+
+import java.util.List;
+
+import com.fs.hisStore.domain.FsStoreProductFlashSale;
+import com.fs.hisStore.mapper.FsStoreProductFlashSaleMapper;
+import com.fs.hisStore.service.IFsStoreProductFlashSaleService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 秒杀商品Service业务层处理
+ *
+ * @author fs
+ * @date 2026-04-08
+ */
+@Service
+public class FsStoreProductFlashSaleServiceImpl implements IFsStoreProductFlashSaleService
+{
+    @Autowired
+    private FsStoreProductFlashSaleMapper fsStoreProductFlashSaleMapper;
+
+    /**
+     * 查询秒杀商品
+     *
+     * @param id 秒杀商品主键
+     * @return 秒杀商品
+     */
+    @Override
+    public FsStoreProductFlashSale selectFsStoreProductFlashSaleById(Long id)
+    {
+        return fsStoreProductFlashSaleMapper.selectFsStoreProductFlashSaleById(id);
+    }
+
+    /**
+     * 查询秒杀商品列表
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 秒杀商品
+     */
+    @Override
+    public List<FsStoreProductFlashSale> selectFsStoreProductFlashSaleList(FsStoreProductFlashSale fsStoreProductFlashSale)
+    {
+        return fsStoreProductFlashSaleMapper.selectFsStoreProductFlashSaleList(fsStoreProductFlashSale);
+    }
+
+    /**
+     * 新增秒杀商品
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 结果
+     */
+    @Override
+    public int insertFsStoreProductFlashSale(FsStoreProductFlashSale fsStoreProductFlashSale)
+    {
+        return fsStoreProductFlashSaleMapper.insertFsStoreProductFlashSale(fsStoreProductFlashSale);
+    }
+
+    /**
+     * 修改秒杀商品
+     *
+     * @param fsStoreProductFlashSale 秒杀商品
+     * @return 结果
+     */
+    @Override
+    public int updateFsStoreProductFlashSale(FsStoreProductFlashSale fsStoreProductFlashSale)
+    {
+        return fsStoreProductFlashSaleMapper.updateFsStoreProductFlashSale(fsStoreProductFlashSale);
+    }
+
+    /**
+     * 批量删除秒杀商品
+     *
+     * @param ids 需要删除的主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsStoreProductFlashSaleByIds(Long[] ids)
+    {
+        return fsStoreProductFlashSaleMapper.deleteFsStoreProductFlashSaleByIds(ids);
+    }
+
+    /**
+     * 删除秒杀商品信息
+     *
+     * @param id 秒杀商品主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsStoreProductFlashSaleById(Long id)
+    {
+        return fsStoreProductFlashSaleMapper.deleteFsStoreProductFlashSaleById(id);
+    }
+
+    /**
+     * 查询当前有效的秒杀商品列表(小程序端使用)
+     *
+     * @return 秒杀商品集合
+     */
+    @Override
+    public List<FsStoreProductFlashSale> selectActiveFlashSaleList()
+    {
+        return fsStoreProductFlashSaleMapper.selectActiveFlashSaleList();
+    }
+
+    /**
+     * 根据商品ID查询秒杀信息
+     *
+     * @param productId 商品ID
+     * @return 秒杀商品
+     */
+    @Override
+    public FsStoreProductFlashSale selectFlashSaleByProductId(Long productId)
+    {
+        return fsStoreProductFlashSaleMapper.selectFlashSaleByProductId(productId);
+    }
+
+    /**
+     * 查询即将开抢和未过期的秒杀商品列表(小程序端使用)
+     *
+     * @return 秒杀商品集合
+     */
+    @Override
+    public List<FsStoreProductFlashSale> selectUpcomingFlashSaleList()
+    {
+        return fsStoreProductFlashSaleMapper.selectUpcomingFlashSaleList();
+    }
+
+    /**
+     * 更新秒杀商品库存
+     *
+     * @param id 秒杀商品ID
+     * @param stock 库存数量
+     * @return 结果
+     */
+    @Override
+    public int updateFlashSaleStock(Long id, Long stock)
+    {
+        FsStoreProductFlashSale update = new FsStoreProductFlashSale();
+        update.setId(id);
+        update.setStock(stock);
+        return fsStoreProductFlashSaleMapper.updateFsStoreProductFlashSale(update);
+    }
+}

+ 84 - 26
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreProductScrmServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.hisStore.service.impl;
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import cn.hutool.core.collection.ListUtil;
@@ -127,6 +128,9 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
     @Autowired
     private FsStoreProductCategoryScrmMapper fsStoreProductCategoryScrmMapper;
 
+    @Autowired
+    private FsStoreProductActivityMapper activityMapper;
+
     @Autowired
     private FsStoreProductUserEndCategoryMapper fsStoreProductUserEndCategoryMapper;
     @Autowired
@@ -188,13 +192,47 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
      */
     @Override
     public FsStoreProductScrm selectFsStoreProductById(Long productId){
+        return fsStoreProductMapper.selectFsStoreProductById(productId);
+    }
+
+    /**
+     * 查询商品缓存
+     *
+     * @param productId 商品ID
+     * @return 商品
+     */
+    @Override
+    public FsStoreProductScrm selectFsStoreRedisProductById(Long productId){
         String key = "fs:product:id:" + productId;
         FsStoreProductScrm scrm = redisCacheT.getCacheObject(key);
         if(scrm != null){
+            // 活动已过期但缓存中还保留旧的活动时间,需要清除缓存重新从DB加载
+            if (scrm.getActivityEndTime() != null && scrm.getActivityEndTime().before(new Date())) {
+                log.info("商品{}的活动已过期但缓存未失效,清除缓存重新加载,activityEndTime={}", productId, scrm.getActivityEndTime());
+                redisCacheT.deleteObject(key);
+                // 重新从DB加载
+                scrm = fsStoreProductMapper.selectFsStoreProductById(productId);
+                if (scrm != null) {
+                    redisCacheT.setCacheObject(key, scrm, 60L, TimeUnit.SECONDS);
+                }
+                return scrm;
+            }
             return scrm;
         }
         scrm = fsStoreProductMapper.selectFsStoreProductById(productId);
-        redisCacheT.setCacheObject(key, scrm);
+
+        // 计算过期时间
+        long expireSeconds = 60L;
+        if (scrm != null && scrm.getActivityEndTime() != null) {
+            Date now = new Date();
+            Date activityEndTime = scrm.getActivityEndTime();
+            if (activityEndTime.after(now)) {
+                expireSeconds = (activityEndTime.getTime() - now.getTime()) / 1000;
+                expireSeconds = Math.max(expireSeconds, 1L);
+            }
+        }
+
+        redisCacheT.setCacheObject(key, scrm, expireSeconds, TimeUnit.SECONDS);
         return scrm;
     }
 
@@ -240,6 +278,13 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         if(1 == fsStoreProduct.getIsShow() && "1".equals(fsStoreProduct.getIsAudit())){
             fsStoreProduct.setIsAudit("0");
         }
+        // 活动进行中的商品不允许修改库存
+        if (fsStoreProduct.getProductId() != null && fsStoreProduct.getActivityType() != null && fsStoreProduct.getActivityType() != 0) {
+            FsStoreProductActivity ongoing = activityMapper.selectOngoingActivity(fsStoreProduct.getProductId());
+            if (ongoing != null) {
+                throw new CustomException("商品正在活动中,不允许修改,请先结束活动");
+            }
+        }
         int result = fsStoreProductMapper.updateFsStoreProduct(fsStoreProduct);
         // 清除缓存
         clearProductDetailCache(fsStoreProduct.getProductId());
@@ -705,34 +750,37 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         } else {
             product.setSinglePurchaseLimit(0);
         }
-        //校验店铺资质信息
-        if (!CompanyEnum.contains(cloudHostProper.getCompanyName())) {
-            //获取店铺
-            FsStoreScrm store = fsStoreScrmService.selectFsStoreByStoreId(product.getStoreId());
-            if(store == null || 1 != store.getStatus()){
-                return R.error("店铺不存在或未启用");
-            }else{
-                //验证资质
-                switch (product.getProductType()){
-                    case 1://非处方
-                        break;
-                    case 2://处方
+
+        if(product.getStoreId() != null){
+            //校验店铺资质信息
+            if (!CompanyEnum.contains(cloudHostProper.getCompanyName())) {
+                //获取店铺
+                FsStoreScrm store = fsStoreScrmService.selectFsStoreByStoreId(product.getStoreId());
+                if(store == null || 1 != store.getStatus()){
+                    return R.error("店铺不存在或未启用");
+                }else{
+                    //验证资质
+                    switch (product.getProductType()){
+                        case 1://非处方
+                            break;
+                        case 2://处方
 //                        if("".equals(store.getDrugLicense()) ||  LocalDate.now().isBefore(store.getDrugLicenseExpiryEnd())){
 //                            return R.error("店铺药品资质为空或已过期,请完善后再添加");
 //                        }
-                        break;
-                    case 3://食品
-                        if("".equals(store.getFoodLicense()) ||  LocalDate.now().isBefore(store.getFoodLicenseExpiryEnd())){
-                            return R.error("店铺食品资质为空或已过期,请完善后再添加");
-                        }
-                        break;
-                    case 4://器械
-                        if("".equals(store.getMedicalDevice3()) ||  LocalDate.now().isBefore(store.getMedicalDevice3ExpiryEnd())){
-                            return R.error("店铺器械资质为空或已过期,请完善后再添加");
-                        }
-                        break;
-                    default:
-                        return R.error("商品类型错误");
+                            break;
+                        case 3://食品
+                            if("".equals(store.getFoodLicense()) ||  LocalDate.now().isBefore(store.getFoodLicenseExpiryEnd())){
+                                return R.error("店铺食品资质为空或已过期,请完善后再添加");
+                            }
+                            break;
+                        case 4://器械
+                            if("".equals(store.getMedicalDevice3()) ||  LocalDate.now().isBefore(store.getMedicalDevice3ExpiryEnd())){
+                                return R.error("店铺器械资质为空或已过期,请完善后再添加");
+                            }
+                            break;
+                        default:
+                            return R.error("商品类型错误");
+                    }
                 }
             }
         }
@@ -1181,6 +1229,11 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         return fsStoreProductMapper.selectFsStoreProductNewQuery(map);
     }
 
+    @Override
+    public List<FsStoreProductListQueryVO> selectFsStoreProductNewQueryCount(int count) {
+        return fsStoreProductMapper.selectFsStoreProductNewQueryCount(count);
+    }
+
     @Override
     public List<FsStoreProductListQueryVO> selectFsStoreProductHotQuery(int count, String appId) {
         HashMap<String, Object> map = new HashMap<>();
@@ -1190,6 +1243,11 @@ public class FsStoreProductScrmServiceImpl implements IFsStoreProductScrmService
         return fsStoreProductMapper.selectFsStoreProductHotQuery(map);
     }
 
+    @Override
+    public List<FsStoreProductListQueryVO> selectFsStoreProductHotQueryCount(int count) {
+        return fsStoreProductMapper.selectFsStoreProductHotQueryCount(count);
+    }
+
     @Override
     public List<FsStoreProductListQueryVO> selectFsStoreProductNewQueryPage(int pageNum, int pageSize, String appId, String keyword) {
         HashMap<String, Object> map = new HashMap<>();

+ 7 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreProductListQueryVO.java

@@ -71,6 +71,13 @@ public class FsStoreProductListQueryVO implements Serializable
 
     private List<FsStoreProductAttrValueScrm> attrValueList = new ArrayList<>();
 
+    /** 活动类型 0=普通 6=秒杀 7=限时折扣 */
+    private Integer activityType;
 
+    /** 活动开始时间 */
+    private java.util.Date activityStartTime;
+
+    /** 活动结束时间 */
+    private java.util.Date activityEndTime;
 
 }

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

@@ -4,6 +4,7 @@ import lombok.Data;
 
 import java.io.Serializable;
 import java.math.BigDecimal;
+import java.util.Date;
 
 @Data
 public class FsStoreProductQueryVO implements Serializable
@@ -145,4 +146,13 @@ public class FsStoreProductQueryVO implements Serializable
     /** 单次购买数量上限 */
     private Integer singlePurchaseLimit;
 
+    /** 活动类型:0=无 6=秒杀 7=限时折扣 */
+    private Integer activityType;
+
+    /** 活动开始时间 */
+    private Date activityStartTime;
+
+    /** 活动结束时间 */
+    private Date activityEndTime;
+
 }

+ 20 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsUnsyncOrderVO.java

@@ -0,0 +1,20 @@
+package com.fs.hisStore.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class FsUnsyncOrderVO implements Serializable {
+    //订单ID
+    private Long id;
+
+    //关联ID
+    private Long associatedId;
+
+    //订单商品总数
+    private Long totalNum;
+
+    //订单类型6:秒杀、7折扣
+    private Integer orderType;
+}

+ 2 - 14
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -2086,11 +2086,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
                         String link = createH5GjLinkByMiniApp(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
                                 String.valueOf(qwUser.getId()), companyUserId, companyId, item.getExternalId(), config);;
-                        String cacheKey = buildXsyLinkCacheKey(param.getCorpId(),param.getCourseId(),param.getVideoId(),companyUserId,companyId);
-                        //从redis中查询收否已经存在追踪链接
-                        String xsyTrackUrl = redisCache.getCacheObject(cacheKey).toString();
-                        if (StringUtils.isBlank(xsyTrackUrl)){
-                            // 失败不影响主流程
+//
                             try {
                                 if (StringUtils.isEmpty(link)) {
                                     log.warn("销售易素材处理跳过,原因:生成原始链接为空。sopId={}, userId={}, externalId={}",
@@ -2131,7 +2127,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     materialIdList.add(materialWithUpload.getMaterialId());
 
                                     GenerateLinkResponse response =
-                                            xiaoShouYiMaterialService.generateMaterialTrackLink(Long.valueOf(companyUserId),materialIdList, 4256378833539950L);
+                                            xiaoShouYiMaterialService.generateMaterialTrackLink(Long.valueOf(companyUserId),materialIdList);
 
                                     if (response == null) {
                                         throw new RuntimeException("generateMaterialTrackLink返回为空");
@@ -2154,10 +2150,6 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     st.setXsyLinkUrl(xsyRedirectUrl);
                                     st.setLinkUrl(xsyRedirectUrl);
 
-                                    //将追踪链接存入redis
-                                    redisCache.setCacheObject(cacheKey, xsyRedirectUrl, 10, TimeUnit.DAYS);
-                                    log.info("销售易追踪链接写入缓存成功,cacheKey={}, xsyRedirectUrl={}", cacheKey, xsyRedirectUrl);
-
                                     log.info("销售易素材创建并生成追踪链接成功。sopId={}, userId={}, externalId={}, materialId={}, redirectUrl={}",
                                             item.getSopId(), item.getFsUserId(), item.getExternalId(),
                                             materialWithUpload.getMaterialId(), xsyRedirectUrl);
@@ -2172,10 +2164,6 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     st.setXsyLinkUrl(link);
                                 }
                             }
-                        }
-
-                        st.setXsyLinkUrl(xsyTrackUrl);
-                        st.setLinkUrl(xsyTrackUrl);
 
 
                         if (StringUtils.isNotEmpty(link)) {

+ 21 - 0
fs-service/src/main/java/com/fs/store/bean/Drug.java

@@ -0,0 +1,21 @@
+package com.fs.store.bean;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class Drug implements Serializable {
+    String drug_common_name;//药品名称
+    String drug_specification;//药品规格
+    String usage_method;//使⽤⽅法
+    String usage_frequency_unit;//药品频次
+    String usage_per_use_count;//每次⽤药数量
+    String usage_per_use_unit;//每次⽤药单位
+    String usage_days;//天数
+    String sale_amount;//药品数量
+    String sale_unit;//药品数量单位
+    String instructions;//药品说明书
+    String approval_number;//药品产品批准⽂号⽤药单位
+
+}

+ 23 - 0
fs-service/src/main/java/com/fs/store/bean/DrugV2.java

@@ -0,0 +1,23 @@
+package com.fs.store.bean;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class DrugV2 implements Serializable {
+    Long drugCode;//药品名称
+    String drugCommonName;//药品名称
+    String drugSpecification;//药品规格
+    String usageMethod;//使⽤⽅法
+    String frequencyUnit;//药品频次
+    String frequencyNum;//频次数量,例:1 等
+    String dosaNum;//每次⽤药数量
+    String dosaUnit;//每次⽤药单位
+    String usageDays;//天数
+    String saleAmount;//药品数量
+    String saleUnit;//药品数量单位
+    String instructions;//药品说明书
+    String approvalNumber;//药品产品批准⽂号⽤药单位
+
+}

+ 44 - 0
fs-service/src/main/java/com/fs/store/bean/Prescribe.java

@@ -0,0 +1,44 @@
+package com.fs.store.bean;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class Prescribe implements Serializable {
+    String rp_type;//药品类型:01⻄药 02中药
+    String rp_id;//处⽅单ID(唯⼀ID)
+    String pharmacy_code;//⻔店ID
+    String pharmacy_name;//⻔店名称
+    String chief_complaint;//病情描述(主诉)
+    String now_illness;//现病史
+    String history_illness;//既往史
+    Integer patient_age;//患者年龄
+    String patient_name;//患者姓名
+    Integer weight;//体重
+    String icd_name;//对应icd名称
+    String icd_name2;//对应icd名称2
+    String is_history_allergic;//是否有过敏史(传值:是/否)
+    String history_allergic;
+    String liver_unusual;//肝功能是否异常(传值:是/否)
+    String renal_unusual;//肾功能是否异常(传值:是/否)
+    String lactation_flag;//是否是备孕/怀孕/哺乳期(传值:是/否)
+    String patient_tel;//患者⼿机号(通过订单号去订单中⼼查)
+    String patient_gender;//患者性别(传数字,1男 2⼥)
+    String[] record_pic;//复诊凭证
+    String rp_create_time;//处⽅开具时间(来互联⽹医院开⽅的时间)
+    String rp_url_type;//处⽅⽂件格式(传值:png/jpg)
+    String pharmacist_id;//药师ID
+    String pharmacist_name;//药师名称
+    String pharmacist_autograph;//药师签名地址
+    String dispensing_pharmacist_id;//发药药师ID
+    String dispensing_pharmacist_name;//发药药师名称
+    String dispensing_pharmacist_autograph;//发药药师签名地址
+    String deployment_pharmacist_id;//配药药师ID
+    String deployment_pharmacist_name;//配药药师名称
+    String deployment_pharmacist_autograph;//配药药师签名地址
+    Integer is_pharmacist_audit;//是否提供药师审核,0-不使⽤⼀线平台的药师审核,1-使⽤⼀线平台的药师审核
+    String callback_url;//回调地址,如果填写,审核完处⽅后⾃动回调到第三⽅药房服务接⼝
+    List<Drug> drug_list;
+}

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů