Pārlūkot izejas kodu

add:签到抽奖

ct 6 dienas atpakaļ
vecāks
revīzija
db9e1b4ff2
64 mainītis faili ar 6006 papildinājumiem un 16 dzēšanām
  1. 97 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseRewardController.java
  2. 123 0
      fs-admin/src/main/java/com/fs/course/controller/FsCourseRewardRoundController.java
  3. 80 0
      fs-admin/src/main/java/com/fs/reward/controller/FsRewardGoodsController.java
  4. 107 0
      fs-admin/src/main/java/com/fs/reward/controller/FsRewardGoodsOrderController.java
  5. 3 0
      fs-common/src/main/java/com/fs/common/constant/FsConstants.java
  6. 37 0
      fs-common/src/main/java/com/fs/common/utils/luckyDraw/LotteryUtil.java
  7. 87 0
      fs-common/src/main/java/com/fs/common/utils/luckyDraw/Prize.java
  8. 14 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  9. 71 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseRedPacketRetry.java
  10. 63 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseReward.java
  11. 90 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseRewardRound.java
  12. 47 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseRewardVideoRelation.java
  13. 28 0
      fs-service/src/main/java/com/fs/course/enums/FsCourseRewardTypeEnum.java
  14. 62 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketRetryMapper.java
  15. 68 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardMapper.java
  16. 80 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardRoundMapper.java
  17. 87 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardVideoRelationMapper.java
  18. 29 0
      fs-service/src/main/java/com/fs/course/param/FsCourseActualRewardsParam.java
  19. 54 0
      fs-service/src/main/java/com/fs/course/param/FsCourseRewardVideoRelationParam.java
  20. 1 0
      fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java
  21. 104 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseRewardRoundService.java
  22. 67 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseRewardService.java
  23. 65 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseRewardVideoRelationService.java
  24. 11 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  25. 748 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRewardRoundServiceImpl.java
  26. 224 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRewardServiceImpl.java
  27. 162 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseRewardVideoRelationServiceImpl.java
  28. 305 10
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  29. 5 0
      fs-service/src/main/java/com/fs/erp/domain/ErpOrder.java
  30. 2 0
      fs-service/src/main/java/com/fs/his/config/IntegralConfig.java
  31. 10 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  32. 9 3
      fs-service/src/main/java/com/fs/his/mapper/FsUserSignMapper.java
  33. 5 0
      fs-service/src/main/java/com/fs/his/service/IFsUserSignService.java
  34. 7 1
      fs-service/src/main/java/com/fs/his/service/impl/FsUserSignServiceImpl.java
  35. 26 0
      fs-service/src/main/java/com/fs/pay/constant/PaymentTypeConstant.java
  36. 110 0
      fs-service/src/main/java/com/fs/reward/domain/FsRewardGoods.java
  37. 155 0
      fs-service/src/main/java/com/fs/reward/domain/FsRewardGoodsOrder.java
  38. 32 0
      fs-service/src/main/java/com/fs/reward/mapper/FsRewardGoodsMapper.java
  39. 39 0
      fs-service/src/main/java/com/fs/reward/mapper/FsRewardGoodsOrderMapper.java
  40. 68 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsAddParam.java
  41. 20 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsEditParam.java
  42. 23 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsListParam.java
  43. 24 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderAddParam.java
  44. 56 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderListParam.java
  45. 25 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderPayParam.java
  46. 71 0
      fs-service/src/main/java/com/fs/reward/service/IFsRewardGoodsOrderService.java
  47. 38 0
      fs-service/src/main/java/com/fs/reward/service/IFsRewardGoodsService.java
  48. 1234 0
      fs-service/src/main/java/com/fs/reward/service/impl/FsRewardGoodsOrderServiceImpl.java
  49. 105 0
      fs-service/src/main/java/com/fs/reward/service/impl/FsRewardGoodsServiceImpl.java
  50. 168 0
      fs-service/src/main/java/com/fs/reward/vo/FsRewardGoodsOrderVO.java
  51. 113 0
      fs-service/src/main/java/com/fs/reward/vo/FsRewardGoodsVO.java
  52. 36 0
      fs-service/src/main/java/com/fs/reward/vo/FsRewardListVO.java
  53. 1 0
      fs-service/src/main/java/com/fs/tzBankPay/TzBankService/TzBankService.java
  54. 30 0
      fs-service/src/main/java/com/fs/tzBankPay/TzBankService/TzBankServiceImpl/TzBankServiceImpl.java
  55. 1 0
      fs-service/src/main/java/com/fs/tzBankPay/doman/PayCreateOrder.java
  56. 4 1
      fs-service/src/main/java/com/fs/tzBankPay/doman/PayType.java
  57. 19 0
      fs-service/src/main/java/com/fs/utils/Range.java
  58. 121 0
      fs-service/src/main/resources/mapper/course/FsCourseRedPacketRetryMapper.xml
  59. 116 0
      fs-service/src/main/resources/mapper/course/RewardMapper.xml
  60. 173 0
      fs-service/src/main/resources/mapper/course/RewardRoundMapper.xml
  61. 52 0
      fs-service/src/main/resources/mapper/reward/FsRewardGoodsMapper.xml
  62. 93 0
      fs-service/src/main/resources/mapper/reward/FsRewardGoodsOrderMapper.xml
  63. 11 1
      fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java
  64. 90 0
      fs-user-app/src/main/java/com/fs/app/controller/RewardGoodsController.java

+ 97 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseRewardController.java

@@ -0,0 +1,97 @@
+package com.fs.course.controller;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsCourseReward;
+import com.fs.course.service.IFsCourseRewardService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 奖励配置Controller
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+@RestController
+@RequestMapping("/course/reward")
+public class FsCourseRewardController extends BaseController
+{
+    @Autowired
+    private IFsCourseRewardService rewardService;
+
+    /**
+     * 查询奖励配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:reward:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseReward reward)
+    {
+        startPage();
+        List<FsCourseReward> list = rewardService.selectFsCourseRewardList(reward);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出奖励配置列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:reward:export')")
+    @Log(title = "奖励配置", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseReward reward)
+    {
+        List<FsCourseReward> list = rewardService.selectFsCourseRewardList(reward);
+        ExcelUtil<FsCourseReward> util = new ExcelUtil<FsCourseReward>(FsCourseReward.class);
+        return util.exportExcel(list, "奖励配置数据");
+    }
+
+    /**
+     * 获取奖励配置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:reward:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(rewardService.selectFsCourseRewardById(id));
+    }
+
+    /**
+     * 新增奖励配置
+     */
+    @PreAuthorize("@ss.hasPermi('course:reward:add')")
+    @Log(title = "奖励配置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseReward reward)
+    {
+        return toAjax(rewardService.insertFsCourseReward(reward));
+    }
+
+    /**
+     * 修改奖励配置
+     */
+    @PreAuthorize("@ss.hasPermi('course:reward:edit')")
+    @Log(title = "奖励配置", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseReward reward)
+    {
+        return toAjax(rewardService.updateFsCourseReward(reward));
+    }
+
+    /**
+     * 删除奖励配置
+     */
+    @PreAuthorize("@ss.hasPermi('course:reward:remove')")
+    @Log(title = "奖励配置", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(rewardService.deleteFsCourseRewardByIds(ids));
+    }
+}

+ 123 - 0
fs-admin/src/main/java/com/fs/course/controller/FsCourseRewardRoundController.java

@@ -0,0 +1,123 @@
+package com.fs.course.controller;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.company.domain.Company;
+import com.fs.company.service.ICompanyService;
+import com.fs.course.domain.FsCourseRewardRound;
+import com.fs.course.service.IFsCourseRewardRoundService;
+import com.fs.his.domain.FsUser;
+import com.fs.his.service.IFsUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 奖励领取记录Controller
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+@RestController
+@RequestMapping("/course/rewardRound")
+public class FsCourseRewardRoundController extends BaseController
+{
+    @Autowired
+    private IFsCourseRewardRoundService rewardRoundService;
+
+    @Autowired
+    private IFsUserService fsUserService;
+    @Autowired
+    private ICompanyService companyService;
+
+    /**
+     * 查询奖励领取记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:rewardRound:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseRewardRound rewardRound)
+    {
+        startPage();
+        List<FsCourseRewardRound> list = rewardRoundService.selectFsCourseRewardRoundList(rewardRound);
+        for (FsCourseRewardRound fsCourseRewardRound : list) {
+            // 检查userId是否为空
+            if (fsCourseRewardRound.getUserId() != null) {
+                FsUser fsUser = fsUserService.selectFsUserByUserId(fsCourseRewardRound.getUserId());
+                if (fsUser != null) {
+                    fsCourseRewardRound.setUserIdByName(fsUser.getUserId()+"_"+fsUser.getNickName());
+                }
+            }
+
+            // 检查companyId是否为空
+            if (fsCourseRewardRound.getCompanyId() != null) {
+                Company company = companyService.selectCompanyById(fsCourseRewardRound.getCompanyId());
+                if (company != null) {
+                    fsCourseRewardRound.setCompanyName(company.getCompanyName());
+                }
+            }
+        }
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出奖励领取记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('course:rewardRound:export')")
+    @Log(title = "奖励领取记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseRewardRound rewardRound)
+    {
+        List<FsCourseRewardRound> list = rewardRoundService.selectFsCourseRewardRoundList(rewardRound);
+        ExcelUtil<FsCourseRewardRound> util = new ExcelUtil<FsCourseRewardRound>(FsCourseRewardRound.class);
+        return util.exportExcel(list, "奖励领取记录数据");
+    }
+
+    /**
+     * 获取奖励领取记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('course:rewardRound:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(rewardRoundService.selectFsCourseRewardRoundById(id));
+    }
+
+    /**
+     * 新增奖励领取记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:rewardRound:add')")
+    @Log(title = "奖励领取记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseRewardRound rewardRound)
+    {
+        return toAjax(rewardRoundService.insertFsCourseRewardRound(rewardRound));
+    }
+
+    /**
+     * 修改奖励领取记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:rewardRound:edit')")
+    @Log(title = "奖励领取记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseRewardRound rewardRound)
+    {
+        return toAjax(rewardRoundService.updateFsCourseRewardRound(rewardRound));
+    }
+
+    /**
+     * 删除奖励领取记录
+     */
+    @PreAuthorize("@ss.hasPermi('course:rewardRound:remove')")
+    @Log(title = "奖励领取记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(rewardRoundService.deleteFsCourseRewardRoundByIds(ids));
+    }
+}

+ 80 - 0
fs-admin/src/main/java/com/fs/reward/controller/FsRewardGoodsController.java

@@ -0,0 +1,80 @@
+package com.fs.reward.controller;
+
+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.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.reward.param.FsRewardGoodsAddParam;
+import com.fs.reward.param.FsRewardGoodsEditParam;
+import com.fs.reward.param.FsRewardGoodsListParam;
+import com.fs.reward.service.IFsRewardGoodsService;
+import com.fs.reward.vo.FsRewardGoodsVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Slf4j
+@Api("奖励商品管理")
+@RestController
+@RequestMapping(value="/reward/rewardGoods")
+@AllArgsConstructor
+public class FsRewardGoodsController extends BaseController {
+
+    private final IFsRewardGoodsService rewardGoodsService;
+
+    @ApiOperation("奖励商品列表")
+    @PreAuthorize("@ss.hasPermi('reward:rewardGoods:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsRewardGoodsListParam param) {
+        startPage();
+        List<FsRewardGoodsVO> list = rewardGoodsService.selectFsRewardGoodsVOList(param);
+        return getDataTable(list);
+    }
+
+    @ApiOperation("奖励商品详情")
+    @PreAuthorize("@ss.hasPermi('reward:rewardGoods:query')")
+    @GetMapping("/{goodsId}")
+    public AjaxResult getInfo(@PathVariable Long goodsId) {
+        return AjaxResult.success(rewardGoodsService.selectFsRewardGoodsVOById(goodsId));
+    }
+
+    @ApiOperation("添加奖励商品")
+    @PreAuthorize("@ss.hasPermi('reward:rewardGoods:add')")
+    @Log(title = "奖励商品", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@Validated @RequestBody FsRewardGoodsAddParam param) {
+        param.setCreateBy(SecurityUtils.getUsername());
+        return toAjax(rewardGoodsService.insertFsRewardGoods(param));
+    }
+
+    @ApiOperation("修改奖励商品")
+    @PreAuthorize("@ss.hasPermi('reward:rewardGoods:edit')")
+    @Log(title = "奖励商品", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@Validated @RequestBody FsRewardGoodsEditParam param) {
+        param.setUpdateBy(SecurityUtils.getUsername());
+        return toAjax(rewardGoodsService.updateFsRewardGoods(param));
+    }
+
+    @ApiOperation("删除奖励商品")
+    @PreAuthorize("@ss.hasPermi('reward:rewardGoods:remove')")
+    @Log(title = "奖励商品", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{goodsIds}")
+    public AjaxResult remove(@PathVariable Long[] goodsIds) {
+        return toAjax(rewardGoodsService.logicDeleteFsRewardGoods(goodsIds));
+    }
+
+    @ApiOperation("根据ID查询奖励商品")
+    @GetMapping(value = "/getByIds")
+    public AjaxResult getByIds(@RequestParam List<Long> ids) {
+        return AjaxResult.success(rewardGoodsService.listByIds(ids));
+    }
+}

+ 107 - 0
fs-admin/src/main/java/com/fs/reward/controller/FsRewardGoodsOrderController.java

@@ -0,0 +1,107 @@
+package com.fs.reward.controller;
+
+import cn.hutool.core.util.StrUtil;
+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.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.dto.ExpressInfoDTO;
+import com.fs.his.enums.ShipperCodeEnum;
+import com.fs.his.service.IFsExpressService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.reward.domain.FsRewardGoodsOrder;
+import com.fs.reward.param.FsRewardGoodsOrderListParam;
+import com.fs.reward.service.IFsRewardGoodsOrderService;
+import com.fs.reward.vo.FsRewardGoodsOrderVO;
+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.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Objects;
+
+import static com.fs.his.utils.PhoneUtil.decryptPhone;
+
+@Slf4j
+@Api("奖励商品订单管理")
+@RestController
+@RequestMapping(value="/reward/rewardGoodsOrder")
+public class FsRewardGoodsOrderController extends BaseController {
+
+    @Autowired
+    private IFsRewardGoodsOrderService rewardGoodsOrderService;
+    @Autowired
+    private IFsExpressService expressService;
+
+    @ApiOperation("奖励商品订单列表")
+    @PreAuthorize("@ss.hasPermi('reward:rewardGoodsOrder:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsRewardGoodsOrderListParam param) {
+        if (param != null && StringUtils.isNotBlank(param.getReceiveUserPhone())) {
+            param.setReceiveUserPhone(PhoneUtil.encryptPhone(param.getReceiveUserPhone()));
+        }
+        if (param != null && StringUtils.isNotBlank(param.getReceiveUserName())) {
+            param.setReceiveUserName(param.getReceiveUserName().trim());
+        }
+
+        startPage();
+        List<FsRewardGoodsOrderVO> list = rewardGoodsOrderService.selectFsRewardGoodsOrderVOList(param);
+        return getDataTable(list);
+    }
+
+    @ApiOperation("奖励商品订单详情")
+    @PreAuthorize("@ss.hasPermi('reward:rewardGoodsOrder:query')")
+    @GetMapping("/{orderId}")
+    public AjaxResult getInfo(@PathVariable Long orderId) {
+        return AjaxResult.success(rewardGoodsOrderService.selectFsRewardGoodsOrderVOById(orderId));
+    }
+
+    @ApiOperation("支付信息")
+    @GetMapping("/payment/{orderSn}")
+    public AjaxResult getPayInfo(@PathVariable String orderSn) {
+        return AjaxResult.success(rewardGoodsOrderService.getPayInfo(orderSn));
+    }
+
+    @ApiOperation("物流信息")
+    @GetMapping(value = "/getExpress/{orderId}")
+    public R getExpress(@PathVariable Long orderId) {
+        FsRewardGoodsOrder order = rewardGoodsOrderService.getById(orderId);
+        if (Objects.isNull(order)) {
+            return R.error("订单不存在");
+        }
+
+        ExpressInfoDTO expressInfoDTO=null;
+        if(StringUtils.isNotEmpty(order.getDeliverySn())){
+            String lastFourNumber = "";
+            if (order.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
+                lastFourNumber = order.getMobile().length() > 11 ? decryptPhone(order.getMobile()) : order.getMobile();
+                lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+            }
+            expressInfoDTO = expressService.getExpressInfo(order.getOrderSn(),order.getDeliveryCode(),order.getDeliverySn(),lastFourNumber);
+
+            if(expressInfoDTO != null && (expressInfoDTO.getStateEx()!=null&&expressInfoDTO.getStateEx().equals("0"))&&(expressInfoDTO.getState()!=null&&expressInfoDTO.getState().equals("0"))){
+                lastFourNumber = "19923690275";
+                if (order.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
+                    lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+                }
+
+                expressInfoDTO = expressService.getExpressInfo(order.getOrderSn(),order.getDeliveryCode(),order.getDeliverySn(),lastFourNumber);
+
+            }
+        }
+        return R.ok().put("data", expressInfoDTO);
+    }
+
+    @Log(title = "奖励商品订单", businessType = BusinessType.UPDATE)
+    @PreAuthorize("@ss.hasPermi('reward:rewardGoodsOrder:cancelOrder')")
+    @PutMapping("/cancelOrder/{orderId}")
+    public AjaxResult cancelOrder(@PathVariable Long orderId) {
+        return toAjax(rewardGoodsOrderService.cancelOrder(orderId));
+    }
+}

+ 3 - 0
fs-common/src/main/java/com/fs/common/constant/FsConstants.java

@@ -19,4 +19,7 @@ public interface FsConstants {
     String COMPANY_MONEY_LOCK = "company_money_lock:";
     // 看客统计  按公司分组 按TimeType 0-今天,1-昨天,2-本周,3-本月,4-上月;
     String WATCH_COURSE_STATISTICS_GROUP_COMPANY = "watch_course_statistics:group_company:";
+
+    // 抽奖大礼品缓存key
+    String REDIS_GRAND_GIFT = "grand:gift:";
 }

+ 37 - 0
fs-common/src/main/java/com/fs/common/utils/luckyDraw/LotteryUtil.java

@@ -0,0 +1,37 @@
+package com.fs.common.utils.luckyDraw;
+
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class LotteryUtil {
+
+    /**
+     * 根据奖品配置抽奖
+     *
+     * @param prizes 奖品列表
+     * @return 中奖的奖品
+     */
+    public static Prize draw(List<Prize> prizes) {
+        if (prizes == null || prizes.isEmpty()) {
+            return null;
+        }
+
+        // 计算总概率和
+        double total = prizes.stream().mapToDouble(Prize::getProbability).sum();
+
+        // [0, total) 之间取随机数
+//        double random = ThreadLocalRandom.current().nextDouble() * total;
+
+//        // 逐步累加找到落点
+//        double sum = 0;
+//        for (Prize prize : prizes) {
+//            sum += prize.getProbability();
+//            if (random < sum) {
+//                return prize;
+//            }
+//        }
+
+        // 理论上不会到这里
+        return prizes.get(prizes.size() - 1);
+    }
+}

+ 87 - 0
fs-common/src/main/java/com/fs/common/utils/luckyDraw/Prize.java

@@ -0,0 +1,87 @@
+package com.fs.common.utils.luckyDraw;
+
+
+public class Prize {
+
+    private Integer type;
+    private String amount;
+    private String code;
+    private String codeName;
+    private double probability;
+    private String couponId;
+    private String goodsId;
+
+    public Prize(Integer type, String amount, String code, double probability, String couponId, String goodsId, String codeName) {
+        this.type = type;
+        this.amount = amount;
+        this.code = code;
+        this.probability = probability;
+        this.couponId = couponId;
+        this.goodsId = goodsId;
+        this.codeName = codeName;
+    }
+
+    public Prize(Integer type, String amount, String code, double probability, String couponId) {
+        this.type = type;
+        this.amount = amount;
+        this.code = code;
+        this.probability = probability;
+        this.couponId = couponId;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public String getAmount() {
+        return amount;
+    }
+
+    public void setAmount(String amount) {
+        this.amount = amount;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public double getProbability() {
+        return probability;
+    }
+
+    public void setProbability(double probability) {
+        this.probability = probability;
+    }
+
+    public String getCouponId() {
+        return couponId;
+    }
+
+    public void setCouponId(String couponId) {
+        this.couponId = couponId;
+    }
+
+    public String getGoodsId() {
+        return goodsId;
+    }
+
+    public void setGoodsId(String goodsId) {
+        this.goodsId = goodsId;
+    }
+
+    public String getCodeName() {
+        return codeName;
+    }
+
+    public void setCodeName(String codeName) {
+        this.codeName = codeName;
+    }
+}

+ 14 - 0
fs-service/src/main/java/com/fs/course/config/CourseConfig.java

@@ -107,4 +107,18 @@ public class CourseConfig implements Serializable {
         private LocalTime endDisabledTime;
     }
 
+    /**
+     * 第一个百分比
+     */
+    private String silverPercent;
+
+    /**
+     * 第二个百分比
+     */
+    private String goldPercent;
+    /**
+     * 第一个宝箱
+     */
+    private String silverBox;
+
 }

+ 71 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseRedPacketRetry.java

@@ -0,0 +1,71 @@
+package com.fs.course.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/**
+ * 课程红包失败重试记录对象 fs_course_red_packet_retry
+ *
+ * @author fs
+ * @date 2025-03-18
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsCourseRedPacketRetry extends BaseEntity{
+
+    /** 日志Id */
+    @TableId(type = IdType.AUTO)
+    private Long logId;
+
+    /** 批次单号 */
+    @Excel(name = "批次单号")
+    private String outBatchNo;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 用户id */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /** 小节id */
+    @Excel(name = "小节id")
+    private Long videoId;
+
+    /** 公司员工id */
+    @Excel(name = "公司员工id")
+    private Long companyUserId;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 转帐金额 */
+    @Excel(name = "转帐金额")
+    private BigDecimal amount;
+
+    /** 状态 0 发送中  1  已发送 */
+    @Excel(name = "状态 0 发送中  1  已发送")
+    private Integer status;
+
+    /** 企微分享userId */
+    @Excel(name = "企微分享userId")
+    private String qwUserId;
+
+    /** 观看记录id */
+    @Excel(name = "观看记录id")
+    private Long watchLogId;
+
+    private String sendOpenId;
+
+    private Integer source;
+
+
+}

+ 63 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseReward.java

@@ -0,0 +1,63 @@
+package com.fs.course.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 奖励配置对象 fs_course_reward
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsCourseReward extends BaseEntity{
+
+    /** 主键ID */
+    private Long id;
+
+    /** 奖励名称 */
+    @Excel(name = "奖励名称")
+    private String name;
+
+    /** 奖励描述 */
+    @Excel(name = "奖励描述")
+    private String description;
+
+    /** 奖励类型 (1:宝箱, 2:红包, 3:积分, 4:转盘, 5:保底转盘, 6:大礼品) */
+    @Excel(name = "奖励类型 (1:宝箱, 2:红包, 3:积分, 4:转盘, 5:保底转盘, 6:大礼品)")
+    private Long rewardType;
+
+    /** 状态 (0:禁用, 1:启用) */
+    @Excel(name = "状态 (0:禁用, 1:启用)")
+    private Long status;
+
+    /** 期望值 */
+    @Excel(name = "期望值")
+    private String expectedValue;
+
+    /** 创建人ID */
+    @Excel(name = "创建人ID")
+    private Long createId;
+
+    /** 实际获得的奖励内容 */
+    @Excel(name = "实际获得的奖励内容")
+    private String actualRewards;
+
+    /**
+     * 关闭宝箱url
+     */
+    private String closeChestUrl;
+
+    /**
+     * 开启宝箱url
+     */
+    private String openChestUrl;
+
+    /**
+     * 奖励id
+     */
+    private Long [] rewardIds;
+}

+ 90 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseRewardRound.java

@@ -0,0 +1,90 @@
+package com.fs.course.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/**
+ * 奖励领取记录对象 reward_round
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsCourseRewardRound extends BaseEntity{
+
+    /** 主键ID */
+    private Long id;
+
+    /** 奖励ID */
+    @Excel(name = "奖励ID")
+    private Long rewardId;
+
+    /** 领取用户ID */
+    @Excel(name = "领取用户ID")
+    private Long userId;
+    private String userIdByName;
+
+    /** 奖励类型 */
+    @Excel(name = "奖励类型")
+    private Long rewardType;
+
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+    private String companyName;
+
+    /** 实际领取到的奖励 */
+    @Excel(name = "实际领取到的奖励")
+    private String actualRewards;
+
+    /** 创建人ID */
+    @Excel(name = "创建人ID")
+    private Long createId;
+
+    /** 奖励状态 (0:已作废, 1:已领取, 2:已过期) */
+    @Excel(name = "奖励状态 (0:已作废, 1:已领取, 2:已过期)")
+    private Long status;
+
+    /** 规则id */
+    @Excel(name = "规则id")
+    private String ruleId;
+
+    /**
+     * 规则名称
+     */
+    private String ruleName;
+
+    /** 看课记录id */
+    @Excel(name = "看课记录id")
+    private Long watchId;
+
+    /** 奖励规则关联id */
+    @Excel(name = "奖励规则关联id")
+    private Long rewardVideoRelationId;
+    /**
+     * 时长
+     */
+    private String second;
+    /**
+     * 小节id
+     */
+    private Long videoId;
+    private String qwUserId;
+    private Long qwExternalId;
+
+    private Integer linkType;
+
+    /**
+     * 奖励商品ID
+     */
+    private Long goodsId;
+    /**
+     * 奖励商品价格
+     */
+    private BigDecimal goodsPrice;
+}

+ 47 - 0
fs-service/src/main/java/com/fs/course/domain/FsCourseRewardVideoRelation.java

@@ -0,0 +1,47 @@
+package com.fs.course.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 奖励与视频小节关联关系对象 fs_course_reward_video_relation
+ *
+ * @author fs
+ * @date 2025-09-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsCourseRewardVideoRelation extends BaseEntity{
+
+    /** 关联关系ID */
+    private Long id;
+
+    /** 关联reward_.id */
+    @Excel(name = "关联reward_.id")
+    private Long rewardId;
+
+    /** 关联视频小节ID */
+    @Excel(name = "关联视频小节ID")
+    private Long videoSectionId;
+
+    /** 标志百分比 */
+    @Excel(name = "标志百分比")
+    private String mark;
+
+
+    /** 创建人ID */
+    @Excel(name = "创建人ID")
+    private Long createId;
+
+    /** 创建人ID */
+    @Excel(name = "创建人ID")
+    private Long updateId;
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+
+    private Integer type;
+
+}

+ 28 - 0
fs-service/src/main/java/com/fs/course/enums/FsCourseRewardTypeEnum.java

@@ -0,0 +1,28 @@
+package com.fs.course.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+import java.util.Arrays;
+
+@Getter
+@NoArgsConstructor
+@AllArgsConstructor
+public enum FsCourseRewardTypeEnum {
+    TYPE_1(1L,"宝箱"),
+    TYPE_2(2L,"红包"),
+    TYPE_3(3L,"积分"),
+    TYPE_4(4L,"转盘"),
+    TYPE_5(5L,"保底转盘"),
+    TYPE_6(6L,"大礼品"),
+    ;
+    private Long value;
+    private String desc;
+
+    public static FsCourseRewardTypeEnum getByValue(Long value) {
+        if (value == null)
+            return null;
+        return Arrays.stream(values()).filter(e -> e.value.equals(value)).findFirst().orElse(null);
+    }
+}

+ 62 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseRedPacketRetryMapper.java

@@ -0,0 +1,62 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsCourseRedPacketRetry;
+
+import java.util.List;
+
+/**
+ * 课程红包失败重试记录Mapper接口
+ * 
+ * @author fs
+ * @date 2025-03-18
+ */
+public interface FsCourseRedPacketRetryMapper extends BaseMapper<FsCourseRedPacketRetry>{
+    /**
+     * 查询课程红包失败重试记录
+     * 
+     * @param logId 课程红包失败重试记录主键
+     * @return 课程红包失败重试记录
+     */
+    FsCourseRedPacketRetry selectFsCourseRedPacketRetryByLogId(Long logId);
+
+    /**
+     * 查询课程红包失败重试记录列表
+     * 
+     * @param fsCourseRedPacketRetry 课程红包失败重试记录
+     * @return 课程红包失败重试记录集合
+     */
+    List<FsCourseRedPacketRetry> selectFsCourseRedPacketRetryList(FsCourseRedPacketRetry fsCourseRedPacketRetry);
+
+    /**
+     * 新增课程红包失败重试记录
+     * 
+     * @param fsCourseRedPacketRetry 课程红包失败重试记录
+     * @return 结果
+     */
+    int insertFsCourseRedPacketRetry(FsCourseRedPacketRetry fsCourseRedPacketRetry);
+
+    /**
+     * 修改课程红包失败重试记录
+     * 
+     * @param fsCourseRedPacketRetry 课程红包失败重试记录
+     * @return 结果
+     */
+    int updateFsCourseRedPacketRetry(FsCourseRedPacketRetry fsCourseRedPacketRetry);
+
+    /**
+     * 删除课程红包失败重试记录
+     * 
+     * @param logId 课程红包失败重试记录主键
+     * @return 结果
+     */
+    int deleteFsCourseRedPacketRetryByLogId(Long logId);
+
+    /**
+     * 批量删除课程红包失败重试记录
+     * 
+     * @param logIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsCourseRedPacketRetryByLogIds(Long[] logIds);
+}

+ 68 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardMapper.java

@@ -0,0 +1,68 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsCourseReward;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 奖励配置Mapper接口
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+public interface FsCourseRewardMapper extends BaseMapper<FsCourseReward>{
+    /**
+     * 查询奖励配置
+     *
+     * @param id 奖励配置主键
+     * @return 奖励配置
+     */
+    FsCourseReward selectFsCourseRewardById(Long id);
+
+    /**
+     * 查询奖励配置列表
+     *
+     * @param reward 奖励配置
+     * @return 奖励配置集合
+     */
+    List<FsCourseReward> selectFsCourseRewardList(FsCourseReward reward);
+
+    /**
+     * 新增奖励配置
+     *
+     * @param reward 奖励配置
+     * @return 结果
+     */
+    int insertFsCourseReward(FsCourseReward reward);
+
+    /**
+     * 修改奖励配置
+     *
+     * @param reward 奖励配置
+     * @return 结果
+     */
+    int updateFsCourseReward(FsCourseReward reward);
+
+    /**
+     * 删除奖励配置
+     *
+     * @param id 奖励配置主键
+     * @return 结果
+     */
+    int deleteFsCourseRewardById(Long id);
+
+    /**
+     * 批量删除奖励配置
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsCourseRewardByIds(Long[] ids);
+
+    /**
+     * 根据id集合查询奖励配置
+     */
+    List<FsCourseReward> selectFsCourseRewardByIds(@Param("ids") List<Long> ids);
+}

+ 80 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardRoundMapper.java

@@ -0,0 +1,80 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.vo.RedPacketMoneyVO;
+import com.fs.course.domain.FsCourseRewardRound;
+import com.fs.reward.vo.FsRewardListVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 奖励领取记录Mapper接口
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+public interface FsCourseRewardRoundMapper extends BaseMapper<FsCourseRewardRound>{
+    /**
+     * 查询奖励领取记录
+     *
+     * @param id 奖励领取记录主键
+     * @return 奖励领取记录
+     */
+    FsCourseRewardRound selectFsCourseRewardRoundById(Long id);
+
+    /**
+     * 查询奖励领取记录列表
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 奖励领取记录集合
+     */
+    List<FsCourseRewardRound> selectFsCourseRewardRoundList(FsCourseRewardRound rewardRound);
+
+    /**
+     * 新增奖励领取记录
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 结果
+     */
+    int insertFsCourseRewardRound(FsCourseRewardRound rewardRound);
+
+    /**
+     * 修改奖励领取记录
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 结果
+     */
+    int updateFsCourseRewardRound(FsCourseRewardRound rewardRound);
+
+    /**
+     * 删除奖励领取记录
+     *
+     * @param id 奖励领取记录主键
+     * @return 结果
+     */
+    int deleteFsCourseRewardRoundById(Long id);
+
+    /**
+     * 批量删除奖励领取记录
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsCourseRewardRoundByIds(Long[] ids);
+
+    List<RedPacketMoneyVO> selectFsCourseRewardRoundByAmount(@Param("start") String start,@Param("end") String end);
+
+    List<RedPacketMoneyVO> selectFsCourseRewardRoundByAmountForLuckyBag(@Param("start") String start,@Param("end") String end);
+
+    /**
+     * 查询1000条指定状态且小于指定时间的数据
+     */
+    List<FsCourseRewardRound> get1kByStatusAndLtDate(@Param("status") int status, @Param("endTime") LocalDate endTime);
+
+    /**
+     * 查询用户中奖记录
+     */
+    List<FsRewardListVO> getRewardListByUserId(@Param("userId") Long userId);
+}

+ 87 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardVideoRelationMapper.java

@@ -0,0 +1,87 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.course.domain.FsCourseReward;
+import com.fs.course.domain.FsCourseRewardVideoRelation;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+
+/**
+ * 奖励与视频小节关联关系Mapper接口
+ *
+ * @author fs
+ * @date 2025-09-03
+ */
+public interface FsCourseRewardVideoRelationMapper extends BaseMapper<FsCourseRewardVideoRelation>{
+    /**
+     * 查询奖励与视频小节关联关系
+     *
+     * @param id 奖励与视频小节关联关系主键
+     * @return 奖励与视频小节关联关系
+     */
+    FsCourseRewardVideoRelation selectFsCourseRewardVideoRelationById(Long id);
+
+    /**
+     * 查询奖励与视频小节关联关系列表
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 奖励与视频小节关联关系集合
+     */
+    List<FsCourseRewardVideoRelation> selectFsCourseRewardVideoRelationList(FsCourseRewardVideoRelation fsCourseRewardVideoRelation);
+
+    /**
+     * 新增奖励与视频小节关联关系
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 结果
+     */
+    int insertFsCourseRewardVideoRelation(FsCourseRewardVideoRelation fsCourseRewardVideoRelation);
+
+    /**
+     * 修改奖励与视频小节关联关系
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 结果
+     */
+    int updateFsCourseRewardVideoRelation(FsCourseRewardVideoRelation fsCourseRewardVideoRelation);
+
+    /**
+     * 删除奖励与视频小节关联关系
+     *
+     * @param id 奖励与视频小节关联关系主键
+     * @return 结果
+     */
+    int deleteFsCourseRewardVideoRelationById(Long id);
+
+    /**
+     * 批量删除奖励与视频小节关联关系
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsCourseRewardVideoRelationByIds(Long[] ids);
+
+    /**
+     * 根据公司ID、小节ID和类型删除关系
+     */
+    void deleteByTypes(@Param("videoId") Long videoId, @Param("companyId") Long companyId, @Param("types") List<Integer> types);
+
+    /**
+     * 根据公司ID、小节ID和类型查询奖励ID
+     */
+    Long getRewardIdByCompanyIdAndVideoIdAndType(@Param("companyId") Long companyId, @Param("videoId") Long videoId, @Param("type") int type);
+
+    /**
+     * 根据公司ID、小节ID和类型查询奖励配置
+     */
+    FsCourseReward getRewardByCompanyIdAndVideoIdAndType(@Param("companyId") Long companyId, @Param("videoId") Long videoId, @Param("type") Integer type);
+
+    List<FsCourseRewardVideoRelation> selectFsCourseRewardVideoRelationListByType(FsCourseRewardVideoRelation fsCourseRewardVideoRelation);
+
+    /**
+     * 根据公司ID、小节ID和奖励ID查询配置关系
+     */
+    FsCourseRewardVideoRelation selectByCompanyIdAndVideoIdAndRewardId(@Param("companyId") Long companyId, @Param("videoId") Long videoId, @Param("rewardId") Long rewardId);
+}

+ 29 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseActualRewardsParam.java

@@ -0,0 +1,29 @@
+package com.fs.course.param;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@Data
+public class FsCourseActualRewardsParam {
+
+    @JsonProperty("type")
+    private Integer type;
+    @JsonProperty("jltype")
+    private Integer jltype;
+    @JsonProperty("name")
+    private String name;
+    @JsonProperty("amount")
+    private String amount;
+    @JsonProperty("probability")
+    private String probability;
+    @JsonProperty("code")
+    private String code;
+    @JsonProperty("iconUrl")
+    private String iconUrl;
+    @JsonProperty("isGuarantee")
+    private Integer isGuarantee;
+    @JsonProperty("couponId")
+    private String couponId;
+}

+ 54 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseRewardVideoRelationParam.java

@@ -0,0 +1,54 @@
+package com.fs.course.param;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 奖励与视频小节关联关系对象 fs_course_reward_video_relation
+ *
+ * @author fs
+ * @date 2025-09-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsCourseRewardVideoRelationParam extends BaseEntity{
+
+    /** 关联关系ID */
+    private Long id;
+
+    /** 关联reward_.id */
+    @Excel(name = "关联reward_.id")
+    private Long rewardId;
+
+    /** 关联视频小节ID */
+    @Excel(name = "关联视频小节ID")
+    private Long videoSectionId;
+
+    /** 标志百分比 */
+    @Excel(name = "标志百分比")
+    private String mark;
+
+    /** 状态 (0:禁用, 1:启用) */
+    @Excel(name = "状态 (0:禁用, 1:启用)")
+    private Long status;
+
+    /** 创建人ID */
+    @Excel(name = "创建人ID")
+    private Long createId;
+
+    /** 创建人ID */
+    @Excel(name = "创建人ID")
+    private Long updateId;
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+    /**
+     * 奖励id
+     */
+    private Long [] rewardIds;
+    private Long [] rewardIdList;
+
+
+}

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

@@ -39,5 +39,6 @@ public class FsCourseSendRewardUParam implements Serializable
 
     private String code;
     private Integer isAuto;
+    private Integer rewardType; //奖励类型 1红包 2积分 3随机转盘 4保底转盘 5大礼品
 
 }

+ 104 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseRewardRoundService.java

@@ -0,0 +1,104 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.domain.R;
+import com.fs.company.vo.RedPacketMoneyVO;
+import com.fs.course.domain.FsCourseRewardRound;
+import com.fs.reward.vo.FsRewardListVO;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 奖励领取记录Service接口
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+public interface IFsCourseRewardRoundService extends IService<FsCourseRewardRound>{
+    /**
+     * 查询奖励领取记录
+     *
+     * @param id 奖励领取记录主键
+     * @return 奖励领取记录
+     */
+    FsCourseRewardRound selectFsCourseRewardRoundById(Long id);
+
+    /**
+     * 查询奖励领取记录列表
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 奖励领取记录集合
+     */
+    List<FsCourseRewardRound> selectFsCourseRewardRoundList(FsCourseRewardRound rewardRound);
+
+    /**
+     * 新增奖励领取记录
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 结果
+     */
+    int insertFsCourseRewardRound(FsCourseRewardRound rewardRound);
+
+    /**
+     * 修改奖励领取记录
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 结果
+     */
+    int updateFsCourseRewardRound(FsCourseRewardRound rewardRound);
+
+    /**
+     * 批量删除奖励领取记录
+     *
+     * @param ids 需要删除的奖励领取记录主键集合
+     * @return 结果
+     */
+    int deleteFsCourseRewardRoundByIds(Long[] ids);
+
+    /**
+     * 删除奖励领取记录信息
+     *
+     * @param id 奖励领取记录主键
+     * @return 结果
+     */
+    int deleteFsCourseRewardRoundById(Long id);
+
+    /**
+     * 领取奖励
+     * @param rewardRound
+     * @return
+     */
+    R claim(FsCourseRewardRound rewardRound);
+
+    /**
+     * 校验是否能领取奖励
+     *
+     * @param rewardRound
+     * @return
+     */
+    R isClaim(FsCourseRewardRound rewardRound);
+
+    /**
+     * 同步扣除企业金额
+     */
+    List<RedPacketMoneyVO> syncUpdatedCompanyAmount(Integer status);
+
+    /**
+     * 同步扣除企业金额
+     */
+    List<RedPacketMoneyVO> syncUpdatedCompanyAmountForLuckyBag(Integer status);
+
+    // 福袋过期 每天同步更新状态
+    void syncLuckyBagExpiry();
+
+    /**
+     * 查询1w条指定状态且小于指定时间的数据
+     */
+    List<FsCourseRewardRound> get1kByStatusAndLtDate(int status, LocalDate endTime);
+
+    /**
+     * 查询用户中奖记录
+     */
+    List<FsRewardListVO> getRewardListByUserId(Long userId);
+}

+ 67 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseRewardService.java

@@ -0,0 +1,67 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsCourseReward;
+
+import java.util.List;
+
+/**
+ * 奖励配置Service接口
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+public interface IFsCourseRewardService extends IService<FsCourseReward>{
+    /**
+     * 查询奖励配置
+     *
+     * @param id 奖励配置主键
+     * @return 奖励配置
+     */
+    FsCourseReward selectFsCourseRewardById(Long id);
+
+    /**
+     * 查询奖励配置列表
+     *
+     * @param reward 奖励配置
+     * @return 奖励配置集合
+     */
+    List<FsCourseReward> selectFsCourseRewardList(FsCourseReward reward);
+
+    /**
+     * 新增奖励配置
+     *
+     * @param reward 奖励配置
+     * @return 结果
+     */
+    int insertFsCourseReward(FsCourseReward reward);
+
+    /**
+     * 修改奖励配置
+     *
+     * @param reward 奖励配置
+     * @return 结果
+     */
+    int updateFsCourseReward(FsCourseReward reward);
+
+    /**
+     * 批量删除奖励配置
+     *
+     * @param ids 需要删除的奖励配置主键集合
+     * @return 结果
+     */
+    int deleteFsCourseRewardByIds(Long[] ids);
+
+    /**
+     * 删除奖励配置信息
+     *
+     * @param id 奖励配置主键
+     * @return 结果
+     */
+    int deleteFsCourseRewardById(Long id);
+
+    /**
+     * 根据id集合查询奖励配置
+     */
+    List<FsCourseReward> selectFsCourseRewardByIds(List<Long> ids);
+}

+ 65 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseRewardVideoRelationService.java

@@ -0,0 +1,65 @@
+package com.fs.course.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.course.domain.FsCourseRewardVideoRelation;
+import com.fs.course.param.FsCourseRewardVideoRelationParam;
+
+import java.util.List;
+
+/**
+ * 奖励与视频小节关联关系Service接口
+ *
+ * @author fs
+ * @date 2025-09-03
+ */
+public interface IFsCourseRewardVideoRelationService extends IService<FsCourseRewardVideoRelation>{
+    /**
+     * 查询奖励与视频小节关联关系
+     *
+     * @param id 奖励与视频小节关联关系主键
+     * @return 奖励与视频小节关联关系
+     */
+    FsCourseRewardVideoRelation selectFsCourseRewardVideoRelationById(Long id);
+
+    /**
+     * 查询奖励与视频小节关联关系列表
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 奖励与视频小节关联关系集合
+     */
+    List<FsCourseRewardVideoRelation> selectFsCourseRewardVideoRelationList(FsCourseRewardVideoRelation fsCourseRewardVideoRelation);
+
+    /**
+     * 新增奖励与视频小节关联关系
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 结果
+     */
+    int insertFsCourseRewardVideoRelation(FsCourseRewardVideoRelation fsCourseRewardVideoRelation);
+
+    /**
+     * 修改奖励与视频小节关联关系
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 结果
+     */
+    int updateFsCourseRewardVideoRelation(FsCourseRewardVideoRelationParam fsCourseRewardVideoRelation);
+
+    /**
+     * 批量删除奖励与视频小节关联关系
+     *
+     * @param ids 需要删除的奖励与视频小节关联关系主键集合
+     * @return 结果
+     */
+    int deleteFsCourseRewardVideoRelationByIds(Long[] ids);
+
+    /**
+     * 删除奖励与视频小节关联关系信息
+     *
+     * @param id 奖励与视频小节关联关系主键
+     * @return 结果
+     */
+    int deleteFsCourseRewardVideoRelationById(Long id);
+
+    List<FsCourseRewardVideoRelation> selectFsCourseRewardVideoRelationListByType(FsCourseRewardVideoRelation fsCourseRewardVideoRelation);
+}

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

@@ -1,5 +1,6 @@
 package com.fs.course.service;
 
+import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
@@ -254,4 +255,14 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
     R registerQwFsUserFinish(FsUserCourseVideoAddKfUParam param);
 
     R registerQwFsUser(FsUserCourseVideoAddKfUParam param);
+
+    /**
+     * 获取签到大礼品选项
+     */
+    JSONArray getSignGrandGiftRules(Long userId);
+
+    /**
+     * 领取签到大礼品奖品
+     */
+    Map<String, Object> claimSignReward(Long userId);
 }

+ 748 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRewardRoundServiceImpl.java

@@ -0,0 +1,748 @@
+package com.fs.course.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.vo.RedPacketMoneyVO;
+import com.fs.course.domain.*;
+import com.fs.course.mapper.*;
+import com.fs.course.param.FsCourseSendRewardUParam;
+import com.fs.course.service.IFsCourseRewardRoundService;
+import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserIntegralLogs;
+import com.fs.his.domain.FsUserWx;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.param.WxSendRedPacketParam;
+import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.service.IFsUserIntegralLogsService;
+import com.fs.his.service.IFsUserWxService;
+import com.fs.qw.mapper.LuckyBagCollectRecordMapper;
+import com.fs.reward.vo.FsRewardListVO;
+import com.fs.system.service.ISysConfigService;
+import com.fs.utils.Range;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+/**
+ * 奖励领取记录Service业务层处理
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+@Service
+public class FsCourseRewardRoundServiceImpl extends ServiceImpl<FsCourseRewardRoundMapper, FsCourseRewardRound> implements IFsCourseRewardRoundService {
+    @Autowired
+    private IFsUserCourseVideoService courseVideoService;
+    private static final Logger logger = LoggerFactory.getLogger(FsUserCourseVideoServiceImpl.class);
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private IFsUserWxService fsUserWxService;
+    @Autowired
+    private FsUserCourseVideoMapper fsUserCourseVideoMapper;
+    @Autowired
+    private FsCourseWatchLogMapper courseWatchLogMapper;
+    @Autowired
+    private ISysConfigService configService;
+    @Autowired
+    private FsUserCourseVideoRedPackageMapper fsUserCourseVideoRedPackageMapper;
+    @Autowired
+    private IFsUserIntegralLogsService iFsUserIntegralLogsService;
+    @Autowired
+    private FsUserMapper fsUserMapper;
+    @Autowired
+    private IFsStorePaymentService paymentService;
+    @Autowired
+    private FsCourseRedPacketLogMapper redPacketLogMapper;
+    @Autowired
+    private FsCourseRedPacketRetryMapper redPacketRetryMapper;
+    @Autowired
+    private FsCourseRewardMapper fsCourseRewardMapper;
+    @Autowired
+    private FsCourseRewardVideoRelationMapper fsCourseRewardVideoRelationMapper;
+
+    @Autowired
+    private LuckyBagCollectRecordMapper luckyBagCollectRecordMapper;
+    /**
+     * 查询奖励领取记录
+     *
+     * @param id 奖励领取记录主键
+     * @return 奖励领取记录
+     */
+    @Override
+    public FsCourseRewardRound selectFsCourseRewardRoundById(Long id)
+    {
+        return baseMapper.selectFsCourseRewardRoundById(id);
+    }
+
+    /**
+     * 查询奖励领取记录列表
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 奖励领取记录
+     */
+    @Override
+    public List<FsCourseRewardRound> selectFsCourseRewardRoundList(FsCourseRewardRound rewardRound)
+    {
+        return baseMapper.selectFsCourseRewardRoundList(rewardRound);
+    }
+
+    /**
+     * 新增奖励领取记录
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseRewardRound(FsCourseRewardRound rewardRound)
+    {
+        FsCourseWatchLog fsCourseWatchLog = courseWatchLogMapper.getWatchCourseVideo(
+                rewardRound.getUserId(), rewardRound.getVideoId(),rewardRound.getQwUserId(),rewardRound.getQwExternalId());
+        if (ObjectUtil.isNotEmpty(fsCourseWatchLog)){
+            rewardRound.setWatchId(fsCourseWatchLog.getLogId());
+            rewardRound.setCompanyId(fsCourseWatchLog.getCompanyId());
+        }
+        rewardRound.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsCourseRewardRound(rewardRound);
+    }
+
+    /**
+     * 修改奖励领取记录
+     *
+     * @param rewardRound 奖励领取记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseRewardRound(FsCourseRewardRound rewardRound)
+    {
+        rewardRound.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsCourseRewardRound(rewardRound);
+    }
+
+    /**
+     * 批量删除奖励领取记录
+     *
+     * @param ids 需要删除的奖励领取记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseRewardRoundByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsCourseRewardRoundByIds(ids);
+    }
+
+    /**
+     * 删除奖励领取记录信息
+     *
+     * @param id 奖励领取记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseRewardRoundById(Long id)
+    {
+        return baseMapper.deleteFsCourseRewardRoundById(id);
+    }
+
+    @Override
+    public R claim(FsCourseRewardRound rewardRound) {
+        Double inputSecond = Double.parseDouble(rewardRound.getSecond());
+        if (inputSecond == null) {
+            return R.error(400, "请输入有效时长");
+        }
+        FsCourseWatchLog fsCourseWatchLog = courseWatchLogMapper.getWatchCourseVideo(
+                rewardRound.getUserId(), rewardRound.getVideoId(),rewardRound.getQwUserId(),rewardRound.getQwExternalId());
+        if (fsCourseWatchLog == null) {
+            return R.error(502, "未找到观看记录");
+        }
+        String redisKey = "h5user:watch:duration:" + fsCourseWatchLog.getQwUserId()+ ":" + fsCourseWatchLog.getQwExternalContactId() + ":" + fsCourseWatchLog.getVideoId();
+        String videoRedisKey = "h5user:video:duration:" + fsCourseWatchLog.getVideoId();
+        String redisSecondStr = redisCache.getCacheObject(redisKey);
+        Long videoRedisKeyStr = redisCache.getCacheObject(videoRedisKey);
+//        String redisSecondStr = "304";
+//        String videoRedisKeyStr = "1000";
+        if (ObjectUtil.isEmpty(redisSecondStr) || ObjectUtil.isEmpty(videoRedisKeyStr)) {
+
+            return R.error(500, "系统配置错误,请稍后再试");
+        }
+        double range = 60.0;
+        double lowerBound = Long.parseLong(redisSecondStr) - range;
+        double upperBound = Long.parseLong(redisSecondStr) + range;
+        if (inputSecond < lowerBound || inputSecond > upperBound) {
+            return R.error(501, "您的观看时长不符合领取条件");
+        }
+        FsCourseRewardRound fsCourseRewardRound = new FsCourseRewardRound();
+        fsCourseRewardRound.setWatchId(fsCourseWatchLog.getLogId());
+        fsCourseRewardRound.setUserId(rewardRound.getUserId());
+        List<FsCourseRewardRound> fsCourseRewardRounds = baseMapper.selectFsCourseRewardRoundList(fsCourseRewardRound);
+
+        FsCourseRewardVideoRelation fsCourseRewardVideoRelation = new FsCourseRewardVideoRelation();
+        fsCourseRewardVideoRelation.setVideoSectionId(rewardRound.getVideoId());
+        fsCourseRewardVideoRelation.setCompanyId(rewardRound.getCompanyId());
+//        fsCourseRewardVideoRelation.setRewardId(rewardRound.getRewardId());
+        List<FsCourseRewardVideoRelation> fsCourseRewardVideoRelationList = fsCourseRewardVideoRelationMapper.selectFsCourseRewardVideoRelationList(fsCourseRewardVideoRelation);
+
+        if (CollectionUtils.isEmpty(fsCourseRewardVideoRelationList)){
+            return R.error(504, "没有配置领取规则,请联系客服!");
+        }
+        if (fsCourseRewardVideoRelationList.size()>1){
+            if (CollectionUtils.isNotEmpty(fsCourseRewardRounds) && fsCourseRewardRounds.size() >= 2) {
+                return R.error(504, "已经领取过两次了,不能领取了");
+            }
+        }else {
+            if (CollectionUtils.isNotEmpty(fsCourseRewardRounds) && fsCourseRewardRounds.size() >= 1) {
+                return R.error(504, "已经领取过了,不能领取了");
+            }
+        }
+
+
+        double actualPercentage = (inputSecond / videoRedisKeyStr) * 100;
+
+        String targetPercentage = findClosestPercentage(actualPercentage);
+        if (targetPercentage == null) {
+            return R.error(503, "您的观看时长不在任何奖励区间内");
+        }
+        FsCourseRewardVideoRelation fsCourseRewardVideoRelation1 = new FsCourseRewardVideoRelation();
+        fsCourseRewardVideoRelation1.setVideoSectionId(rewardRound.getVideoId());
+        fsCourseRewardVideoRelation1.setCompanyId(rewardRound.getCompanyId());
+        fsCourseRewardVideoRelation1.setRewardId(rewardRound.getRewardId());
+        fsCourseRewardVideoRelation1.setMark(targetPercentage+"%");
+        System.out.println("参数:"+fsCourseRewardVideoRelation1);
+        List<FsCourseRewardVideoRelation> fsCourseRewardVideoRelationList1 = fsCourseRewardVideoRelationMapper
+                .selectFsCourseRewardVideoRelationList(fsCourseRewardVideoRelation1);
+        FsCourseRewardVideoRelation fsCourseRewardVideoRelation2 = new FsCourseRewardVideoRelation();
+        if (CollectionUtils.isNotEmpty(fsCourseRewardVideoRelationList1)){
+            fsCourseRewardVideoRelation2 = fsCourseRewardVideoRelationList1.get(0);
+        }else {
+            return R.error(503, "当前时间段不能领取!");
+        }
+        FsCourseReward fsCourseReward = fsCourseRewardMapper.selectFsCourseRewardById(rewardRound.getRewardId());
+
+        rewardRound.setRewardType(fsCourseReward.getRewardType());
+        rewardRound.setRewardVideoRelationId(fsCourseRewardVideoRelation2.getId());
+        List<FsCourseRewardRound> rewardRounds = selectFsCourseRewardRoundList(rewardRound);
+        if (CollectionUtils.isNotEmpty(rewardRounds)){
+            return R.error(503, "已经领取过奖励了,不能在领取了");
+        }
+        if (ObjectUtil.isNotEmpty(rewardRound.getStatus())&&(!rewardRound.getStatus().equals(1L))){
+            rewardRound.setActualRewards("0");
+            insertFsCourseRewardRound(rewardRound);
+            return R.ok();
+        }
+        FsCourseSendRewardUParam param = new FsCourseSendRewardUParam();
+//        param.setCourseId(rewardRound.getCourseId());
+        param.setUserId(rewardRound.getUserId());
+        param.setVideoId(rewardRound.getVideoId());
+        param.setLinkType(rewardRound.getLinkType());
+        param.setRewardType(2);
+        param.setQwUserId(rewardRound.getQwUserId());
+        param.setQwExternalId(rewardRound.getQwExternalId());
+        Integer auctual = findIntegral(fsCourseReward.getActualRewards());
+        R result = sendReward(param,auctual);
+        rewardRound.setActualRewards(String.valueOf(auctual));
+        if (result.get("code").equals(200)){
+            insertFsCourseRewardRound(rewardRound);
+            return R.ok("芳华币+"+auctual);
+        }else {
+            return result;
+        }
+    }
+
+
+    /**
+     * 随机宝箱奖励数
+     *
+     * @return
+     */
+    private Integer findIntegral(String listString) {
+        List<Map> items = JSONObject.parseArray(listString, Map.class);
+
+        // 根据probability概率随机选择一个项
+        Map<String, Object> selectedItem = new HashMap<>();
+
+        // 1. 提取并转换概率值
+        List<Double> probabilities = new ArrayList<>();
+        double totalProbability = 0.0;
+
+        for (Map item : items) {
+            String probStr = (String) item.get("probability");
+            // 移除百分号并转换为小数
+            double prob = Double.parseDouble(probStr.replace("%", "")) / 100.0;
+            probabilities.add(prob);
+            totalProbability += prob;
+        }
+
+        // 2. 验证概率总和(应该是1.0,即100%)
+        if (Math.abs(totalProbability - 1.0) > 0.0001) {
+            for (int i = 0; i < probabilities.size(); i++) {
+                probabilities.set(i, probabilities.get(i) / totalProbability);
+            }
+        }
+
+        // 3. 生成随机数并选择
+        double random = Math.random();
+        double cumulativeProbability = 0.0;
+
+        for (int i = 0; i < probabilities.size(); i++) {
+            cumulativeProbability += probabilities.get(i);
+            if (random <= cumulativeProbability) {
+                selectedItem = items.get(i);
+                break;
+            }
+        }
+
+        Integer amount = (Integer) selectedItem.get("amount");
+        return amount;
+    }
+
+    /**
+     * 发放奖励
+     * @param param
+     * @return
+     */
+    private R sendReward(FsCourseSendRewardUParam param,Integer integral) {
+        FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        if (user.getStatus()==0){
+            return R.error("会员被停用,无权限,请联系客服!");
+        }
+        FsCourseWatchLog log = new FsCourseWatchLog();
+
+        //判断链接类型
+        if (param.getLinkType()!=null&&param.getLinkType()==1){
+            FsCourseRedPacketLog packetLog = redPacketLogMapper.selectFsCourseRedPacketLogByTemporary(param.getVideoId(),param.getUserId());
+            if (packetLog!=null){
+                return R.error("奖励已发放");
+            }
+        }else {
+            log = courseWatchLogMapper.getWatchCourseVideo(param.getUserId(),param.getVideoId(),param.getQwUserId(),param.getQwExternalId());
+            if (log==null){
+                return R.error("无记录");
+            }
+            if (log.getRewardType()!=null){
+                return R.error("奖励已发放");
+            }
+        }
+
+
+
+        FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
+        //发放奖励
+        switch (param.getRewardType()){
+            //红包奖励
+            case 1:
+                BigDecimal amount = BigDecimal.ZERO;
+                FsUserCourseVideoRedPackage redPackage = fsUserCourseVideoRedPackageMapper.selectRedPacketByCompanyId(param.getVideoId(), param.getCompanyId(),param.getPeriodId());
+                WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
+
+                if (redPackage!=null){
+                    amount = redPackage.getRedPacketMoney();
+                }else if (video!=null){
+                    amount = video.getRedPacketMoney();
+                }
+                packetParam.setOpenId(user.getMpOpenId());
+                //来源是小程序切换openId
+                if (param.getSource()==2){
+                    FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
+                    if (fsUserWx ==null || fsUserWx.getOpenId()==null){
+                        packetParam.setOpenId(user.getCourseMaOpenId());
+                        try {
+                            handleFsUserWx(user,param.getAppId());
+                        }catch (Exception e){
+                            logger.error("zyp \n 【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId());
+                        }
+
+                    }else {
+                        packetParam.setOpenId(fsUserWx.getOpenId());
+                    }
+//                    packetParam.setOpenId(user.getCourseMaOpenId());
+                }
+                if (param.getAppId()!=null){
+                    packetParam.setAppId(param.getAppId());
+                }
+                packetParam.setAmount(amount);
+                packetParam.setSource(param.getSource());
+                R sendRedPacket = paymentService.sendRedPacket(packetParam);
+                //更改发送状态
+                if (param.getLinkType()==null || param.getLinkType()==0 || param.getLinkType()==3 || param.getLinkType()==5){
+                    log.setRewardType(param.getRewardType());
+                    courseWatchLogMapper.updateFsCourseWatchLog(log);
+                }
+                if (sendRedPacket.get("code").equals(200)){
+                    //添加红包记录
+                    FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+                    redPacketLog.setCourseId(param.getCourseId());
+                    redPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                    redPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+                    redPacketLog.setCompanyId(param.getCompanyId());
+                    redPacketLog.setUserId(param.getUserId());
+                    redPacketLog.setVideoId(param.getVideoId());
+                    redPacketLog.setStatus(0);
+                    redPacketLog.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null );
+                    redPacketLog.setCompanyUserId(param.getCompanyUserId());
+                    redPacketLog.setCreateTime(new Date());
+                    redPacketLog.setAmount(amount);
+                    redPacketLog.setWatchLogId(log.getLogId() !=null ? log.getLogId() : null);
+                    redPacketLog.setAppId(param.getAppId());
+                    redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+                    return R.ok("奖励发放成功");
+                }
+                else {
+                    logger.info("添加重试补发:{}",packetParam);
+                    FsCourseRedPacketRetry retry = new FsCourseRedPacketRetry();
+                    retry.setCourseId(param.getCourseId());
+                    retry.setCompanyId(param.getCompanyId());
+                    retry.setUserId(param.getUserId());
+                    retry.setVideoId(param.getVideoId());
+                    retry.setStatus(0);
+                    retry.setQwUserId(param.getQwUserId() != null ? param.getQwUserId() : null );
+                    retry.setCompanyUserId(param.getCompanyUserId());
+                    retry.setCreateTime(new Date());
+                    retry.setAmount(amount);
+                    retry.setWatchLogId(log.getLogId() !=null ? log.getLogId() : null);
+                    retry.setSendOpenId(packetParam.getOpenId());
+                    retry.setSource(param.getSource());
+                    redPacketRetryMapper.insertFsCourseRedPacketRetry(retry);
+                    return R.ok("奖励发放成功");
+                }
+                //积分奖励
+            case 2:
+                //增加积分
+                FsUser userMap=new FsUser();
+                userMap.setUserId(user.getUserId());
+                userMap.setIntegral(user.getIntegral()+integral);
+                // 增加可提现积分
+                userMap.setWithdrawIntegral(user.getWithdrawIntegral() + integral);
+                fsUserMapper.updateFsUser(userMap);
+                FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+                integralLogs.setIntegral(integral.longValue());
+                integralLogs.setUserId(user.getUserId());
+                integralLogs.setBalance(userMap.getIntegral());
+                integralLogs.setLogType(22);
+                integralLogs.setBusinessId(StringUtils.isNotEmpty(log.getLogId().toString()) ? log.getLogId().toString() : null);
+                integralLogs.setCreateTime(new Date());
+                integralLogs.setNickName(user.getNickName());
+                integralLogs.setPhone(user.getPhone());
+                //integralLogs.setId(integralLogsService.getFsUserIntegralLogsInsertId());
+//                fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLogs);
+                iFsUserIntegralLogsService.insertFsUserIntegralLogs(integralLogs);
+
+                return R.ok("奖励发放成功");
+            default:
+                return R.error("参数错误!");
+        }
+    }
+
+    /**
+     * 处理用户与小程序的绑定
+     */
+    private void handleFsUserWx(FsUser user,String appId) {
+        FsUserWx fsUserWx = new FsUserWx();
+        fsUserWx.setType(1);
+        fsUserWx.setFsUserId(user.getUserId());
+        fsUserWx.setAppId(appId);
+        fsUserWx.setOpenId(user.getCourseMaOpenId());
+        fsUserWx.setUnionId(user.getUnionId());
+        fsUserWx.setCreateTime(new Date());
+        fsUserWx.setUpdateTime(new Date());
+        fsUserWxService.saveOrUpdateByUniqueKey(fsUserWx);
+
+        logger.info("zyp \n 【更新或插入用户与小程序{}的绑定关系】:{}", appId, user.getUserId());
+
+    }
+
+
+    @Override
+    public R isClaim(FsCourseRewardRound rewardRound) {
+        Double inputSecond = Double.parseDouble(rewardRound.getSecond());
+        if (inputSecond == null) {
+            return R.error(400, "请输入有效时长");
+        }
+        FsCourseWatchLog fsCourseWatchLog = courseWatchLogMapper.getWatchCourseVideo(
+                rewardRound.getUserId(), rewardRound.getVideoId(),rewardRound.getQwUserId(),rewardRound.getQwExternalId());
+        if (fsCourseWatchLog == null) {
+            return R.error(502, "未找到观看记录");
+        }
+        String redisKey = "h5user:watch:duration:" + fsCourseWatchLog.getQwUserId()+ ":" + fsCourseWatchLog.getQwExternalContactId() + ":" + fsCourseWatchLog.getVideoId();
+        String videoRedisKey = "h5user:video:duration:" + fsCourseWatchLog.getVideoId();
+        String redisSecondStr = redisCache.getCacheObject(redisKey);
+        Long videoRedisKeyStr = redisCache.getCacheObject(videoRedisKey);
+//        String redisSecondStr = "304";
+//        String videoRedisKeyStr = "1000";
+        if (ObjectUtil.isEmpty(redisSecondStr) || ObjectUtil.isEmpty(videoRedisKeyStr)) {
+
+            return R.error(500, "系统配置错误,请稍后再试");
+        }
+        double range = 60.0;
+        double lowerBound = Long.parseLong(redisSecondStr) - range;
+        double upperBound = Long.parseLong(redisSecondStr) + range;
+        if (inputSecond < lowerBound || inputSecond > upperBound) {
+            return R.error(501, "您的观看时长不符合领取条件");
+        }
+        FsCourseRewardRound fsCourseRewardRound = new FsCourseRewardRound();
+        fsCourseRewardRound.setWatchId(fsCourseWatchLog.getLogId());
+        fsCourseRewardRound.setUserId(rewardRound.getUserId());
+        List<FsCourseRewardRound> fsCourseRewardRounds = baseMapper.selectFsCourseRewardRoundList(fsCourseRewardRound);
+
+        FsCourseRewardVideoRelation fsCourseRewardVideoRelation = new FsCourseRewardVideoRelation();
+        fsCourseRewardVideoRelation.setVideoSectionId(rewardRound.getVideoId());
+        fsCourseRewardVideoRelation.setCompanyId(rewardRound.getCompanyId());
+//        fsCourseRewardVideoRelation.setRewardId(rewardRound.getRewardId());
+        List<FsCourseRewardVideoRelation> fsCourseRewardVideoRelationList = fsCourseRewardVideoRelationMapper.selectFsCourseRewardVideoRelationList(fsCourseRewardVideoRelation);
+
+        if (CollectionUtils.isEmpty(fsCourseRewardVideoRelationList)){
+            return R.error(504, "没有配置领取规则,请联系客服!");
+        }
+        if (fsCourseRewardVideoRelationList.size()>1){
+            if (CollectionUtils.isNotEmpty(fsCourseRewardRounds) && fsCourseRewardRounds.size() >= 2) {
+                return R.error(504, "已经领取过两次了,不能领取了");
+            }
+        }else {
+            if (CollectionUtils.isNotEmpty(fsCourseRewardRounds) && fsCourseRewardRounds.size() >= 1) {
+                return R.error(504, "已经领取过了,不能领取了");
+            }
+        }
+
+
+        double actualPercentage = (inputSecond / videoRedisKeyStr) * 100;
+
+        String targetPercentage = findClosestPercentage(actualPercentage);
+        if (targetPercentage == null) {
+            return R.error(503, "您的观看时长不在任何奖励区间内");
+        }
+        FsCourseRewardVideoRelation fsCourseRewardVideoRelation1 = new FsCourseRewardVideoRelation();
+        fsCourseRewardVideoRelation1.setVideoSectionId(rewardRound.getVideoId());
+        fsCourseRewardVideoRelation1.setCompanyId(rewardRound.getCompanyId());
+        fsCourseRewardVideoRelation1.setRewardId(rewardRound.getRewardId());
+        fsCourseRewardVideoRelation1.setMark(targetPercentage+"%");
+        System.out.println("参数:"+fsCourseRewardVideoRelation1);
+        List<FsCourseRewardVideoRelation> fsCourseRewardVideoRelationList1 = fsCourseRewardVideoRelationMapper
+                .selectFsCourseRewardVideoRelationList(fsCourseRewardVideoRelation1);
+        FsCourseRewardVideoRelation fsCourseRewardVideoRelation2 = new FsCourseRewardVideoRelation();
+        if (CollectionUtils.isNotEmpty(fsCourseRewardVideoRelationList1)){
+            fsCourseRewardVideoRelation2 = fsCourseRewardVideoRelationList1.get(0);
+        }else {
+            return R.error(503, "当前时间段不能领取!");
+        }
+        FsCourseReward fsCourseReward = fsCourseRewardMapper.selectFsCourseRewardById(rewardRound.getRewardId());
+
+        rewardRound.setRewardType(fsCourseReward.getRewardType());
+        rewardRound.setRewardVideoRelationId(fsCourseRewardVideoRelation2.getId());
+        List<FsCourseRewardRound> rewardRounds = selectFsCourseRewardRoundList(rewardRound);
+        if (CollectionUtils.isNotEmpty(rewardRounds)){
+            return R.error(503, "已经领取过奖励了,不能在领取了");
+        }
+        return R.ok();
+    }
+
+
+    // 辅助方法:查找最接近的配置百分比
+    private String findClosestPercentage(double actualPercentage) {
+        Map<String, Range<Double>> percentageRanges = new LinkedHashMap<>();
+        percentageRanges.put("10", Range.between(5.0, 15.0));
+        percentageRanges.put("20", Range.between(15.0, 25.0));
+        percentageRanges.put("30", Range.between(25.0, 35.0));
+        percentageRanges.put("40", Range.between(35.0, 45.0));
+        percentageRanges.put("50", Range.between(45.0, 55.0));
+        percentageRanges.put("70", Range.between(65.0, 75.0));
+        percentageRanges.put("80", Range.between(75.0, 85.0));
+
+        for (Map.Entry<String, Range<Double>> entry : percentageRanges.entrySet()) {
+            if (entry.getValue().contains(actualPercentage)) {
+                return entry.getKey();
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * 同步公司奖励金额(按状态区分初始化与每日更新)
+     *
+     * @param status 0=初始化统计(统计截止昨日23:59:59之前所有数据)
+     *               1=每日统计(统计昨日00:00:00 - 昨日23:59:59的数据)
+     * @return 按公司统计的红包金额列表
+     */
+    @Override
+    public List<RedPacketMoneyVO> syncUpdatedCompanyAmount(Integer status) {
+        // 定义时间变量
+        LocalDateTime startTime = null;
+        LocalDateTime endTime;
+        String start = null;
+        String end = null;
+
+        // 获取Redis缓存标识
+        Long kcdate = redisCache.getCacheObject("syncUpdatedCompanyAmount");
+
+        // 定义日期格式化器
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+        // 校验入参
+        if (status == null) {
+            logger.warn("【syncUpdatedCompanyAmount】status参数为空,默认执行每日统计逻辑。");
+            status = 1;
+        }
+
+        try {
+            if (status.equals(0)
+                    && ObjectUtil.isEmpty(kcdate)
+            ) {
+                // 初始化逻辑:统计截止到昨天23:59:59前的所有数据
+                LocalDate yesterdayEnd = LocalDate.now().minusDays(1);
+                endTime = yesterdayEnd.atTime(23, 59, 59);
+                end = endTime.format(formatter);
+
+                logger.info("【初始化公司奖励金额统计】截止时间:{}", end);
+
+                List<RedPacketMoneyVO> resultList = baseMapper.selectFsCourseRewardRoundByAmount(null, end);
+                logger.info("【初始化公司奖励金额统计】共统计公司数:{}",
+                        resultList != null ? resultList.size() : 0);
+                return Optional.ofNullable(resultList).orElse(Collections.emptyList());
+
+            } else {
+                // 每日统计逻辑:统计昨天整天的数据
+                LocalDate yesterday = LocalDate.now().minusDays(1);
+                startTime = yesterday.atStartOfDay();
+                endTime = yesterday.atTime(23, 59, 59);
+
+                start = startTime.format(formatter);
+                end = endTime.format(formatter);
+
+                logger.info("【每日公司奖励金额统计】统计时间范围:{} - {}", start, end);
+
+                List<RedPacketMoneyVO> resultList = baseMapper.selectFsCourseRewardRoundByAmount(start, end);
+                logger.info("【每日公司奖励金额统计】共统计公司数:{}",
+                        resultList != null ? resultList.size() : 0);
+                return Optional.ofNullable(resultList).orElse(Collections.emptyList());
+            }
+        } catch (Exception e) {
+            logger.error("【公司奖励金额统计异常】status={}, start={}, end={}, error={}",
+                    status, start, end, e.getMessage(), e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * 同步公司奖励金额(按状态区分初始化与每日更新) 福袋
+     *
+     * @param status 0=初始化统计(统计截止昨日23:59:59之前所有数据)
+     *               1=每日统计(统计昨日00:00:00 - 昨日23:59:59的数据)
+     * @return 按公司统计的红包金额列表
+     */
+    @Override
+    public List<RedPacketMoneyVO> syncUpdatedCompanyAmountForLuckyBag(Integer status) {
+        // 定义时间变量
+        LocalDateTime startTime = null;
+        LocalDateTime endTime;
+        String start = null;
+        String end = null;
+
+        // 获取Redis缓存标识
+        Long kcdate = redisCache.getCacheObject("syncUpdatedCompanyAmountForLuckyBag");
+
+        // 定义日期格式化器
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+        // 校验入参
+        if (status == null) {
+            logger.warn("【syncUpdatedCompanyAmountForLuckyBag】status参数为空,默认执行每日统计逻辑。");
+            status = 1;
+        }
+
+        try {
+            if (status.equals(0)
+                    && ObjectUtil.isEmpty(kcdate)
+            ) {
+                // 初始化逻辑:统计截止到昨天23:59:59前的所有数据
+                LocalDate yesterdayEnd = LocalDate.now().minusDays(1);
+                endTime = yesterdayEnd.atTime(23, 59, 59);
+                end = endTime.format(formatter);
+
+                logger.info("【初始化公司福袋奖励金额统计】截止时间:{}", end);
+
+                List<RedPacketMoneyVO> resultList = baseMapper.selectFsCourseRewardRoundByAmountForLuckyBag(null, end);
+                logger.info("【初始化公司福袋奖励金额统计】共统计公司数:{}",
+                        resultList != null ? resultList.size() : 0);
+                return Optional.ofNullable(resultList).orElse(Collections.emptyList());
+
+            } else {
+                // 每日统计逻辑:统计昨天整天的数据
+                LocalDate yesterday = LocalDate.now().minusDays(1);
+                startTime = yesterday.atStartOfDay();
+                endTime = yesterday.atTime(23, 59, 59);
+
+                start = startTime.format(formatter);
+                end = endTime.format(formatter);
+
+                logger.info("【每日公司福袋奖励金额统计】统计时间范围:{} - {}", start, end);
+
+                List<RedPacketMoneyVO> resultList = baseMapper.selectFsCourseRewardRoundByAmountForLuckyBag(start, end);
+                logger.info("【每日公司福袋奖励金额统计】共统计公司数:{}",
+                        resultList != null ? resultList.size() : 0);
+                return Optional.ofNullable(resultList).orElse(Collections.emptyList());
+            }
+        } catch (Exception e) {
+            logger.error("【公司奖励福袋金额统计异常】status={}, start={}, end={}, error={}",
+                    status, start, end, e.getMessage(), e);
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public void syncLuckyBagExpiry() {
+        int updatedRows = luckyBagCollectRecordMapper.updateLuckyBagExpiryStatus();
+        if (updatedRows >= 0) {
+            logger.info("【更新福袋领取状态】更新成功,共更新 {} 条记录", updatedRows);
+        } else {
+            logger.error("【更新福袋领取状态】更新失败");
+        }
+    }
+
+    /**
+     * 查询1w条指定状态且小于指定时间的数据
+     */
+    @Override
+    public List<FsCourseRewardRound> get1kByStatusAndLtDate(int status, LocalDate endTime) {
+        return baseMapper.get1kByStatusAndLtDate(status, endTime);
+    }
+
+    /**
+     * 查询用户中奖记录
+     */
+    @Override
+    public List<FsRewardListVO> getRewardListByUserId(Long userId) {
+        List<FsRewardListVO> rewardList = baseMapper.getRewardListByUserId(userId);
+        rewardList.forEach(reward -> {
+            if (StringUtils.isNotBlank(reward.getCodeName())) {
+                reward.setContent(reward.getCodeName());
+                return;
+            }
+            String content = "-";
+            try {
+                JSONArray array = JSONObject.parseArray(reward.getContent());
+                for (Object item : array) {
+                    JSONObject json = (JSONObject) item;
+                    if (json.getString("code").equals(reward.getCode())) {
+                        content = json.getString("name");
+                    }
+                }
+            } catch (Exception ignore) {}
+            reward.setContent(content);
+        });
+        return rewardList;
+    }
+}

+ 224 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRewardServiceImpl.java

@@ -0,0 +1,224 @@
+package com.fs.course.service.impl;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.course.domain.FsCourseReward;
+import com.fs.course.enums.FsCourseRewardTypeEnum;
+import com.fs.course.mapper.FsCourseRewardMapper;
+import com.fs.course.param.FsCourseActualRewardsParam;
+import com.fs.course.service.IFsCourseRewardService;
+import com.fs.his.domain.FsCoupon;
+import com.fs.his.mapper.FsCouponMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 奖励配置Service业务层处理
+ *
+ * @author 杨衍生
+ * @date 2025-09-02
+ */
+@Service
+public class FsCourseRewardServiceImpl extends ServiceImpl<FsCourseRewardMapper, FsCourseReward> implements IFsCourseRewardService {
+
+    @Autowired
+    private FsCouponMapper fsCouponMapper;
+
+    /**
+     * 查询奖励配置
+     *
+     * @param id 奖励配置主键
+     * @return 奖励配置
+     */
+    @Override
+    public FsCourseReward selectFsCourseRewardById(Long id)
+    {
+        return baseMapper.selectFsCourseRewardById(id);
+    }
+
+    /**
+     * 查询奖励配置列表
+     *
+     * @param fsCourseReward 奖励配置
+     * @return 奖励配置
+     */
+    @Override
+    public List<FsCourseReward> selectFsCourseRewardList(FsCourseReward fsCourseReward)
+    {
+        return baseMapper.selectFsCourseRewardList(fsCourseReward);
+    }
+
+    /**
+     * 新增奖励配置
+     *
+     * @param fsCourseReward 奖励配置
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseReward(FsCourseReward fsCourseReward)
+    {
+        checkParam(fsCourseReward);
+        fsCourseReward.setCreateTime(DateUtils.getNowDate());
+
+        return baseMapper.insertFsCourseReward(fsCourseReward);
+    }
+
+    /**
+     * 计算成本
+     */
+    private void checkParam(FsCourseReward fsCourseReward) {
+        FsCourseRewardTypeEnum rewardTypeEnum = FsCourseRewardTypeEnum.getByValue(fsCourseReward.getRewardType());
+        JSONObject jsonObject;
+        switch (rewardTypeEnum) {
+            case TYPE_1:
+            case TYPE_4:
+            case TYPE_5:
+            case TYPE_6:
+                List<FsCourseActualRewardsParam> actualRewardsParams = JSONArray.parseArray(fsCourseReward.getActualRewards(), FsCourseActualRewardsParam.class);
+                if (actualRewardsParams.isEmpty()) {
+                    throw new ServiceException("配置不能为空");
+                }
+
+                // 检查宝箱图片
+                if (rewardTypeEnum == FsCourseRewardTypeEnum.TYPE_1 &&
+                        (StringUtils.isBlank(fsCourseReward.getOpenChestUrl()) || StringUtils.isBlank(fsCourseReward.getCloseChestUrl()))) {
+                    throw new ServiceException("配置错误,宝箱开启或关闭图片不能为空");
+                }
+
+                int guaranteeSum = 0;
+                for (FsCourseActualRewardsParam actualRewardsParam : actualRewardsParams) {
+                    // 检查百分比配置
+                    if (!isValidPercentage(actualRewardsParam.getProbability())) {
+                        throw new ServiceException("配置百分比概率错误或格式不正确");
+                    }
+
+                    // 检查icon和name
+                    if (rewardTypeEnum != FsCourseRewardTypeEnum.TYPE_1 &&
+                            (StringUtils.isBlank(actualRewardsParam.getIconUrl()) || StringUtils.isBlank(actualRewardsParam.getName()))) {
+                        throw new ServiceException("配置错误,icon或name不能为空");
+                    }
+
+                    // 奖励数量检查
+                    if (rewardTypeEnum != FsCourseRewardTypeEnum.TYPE_1 &&
+                            actualRewardsParam.getType() != 3 && StringUtils.isBlank(actualRewardsParam.getAmount())) {
+                        throw new ServiceException("配置错误,奖励数量不能为空");
+                    }
+
+                    // 优惠券检查
+                    if (Objects.nonNull(actualRewardsParam.getType()) && actualRewardsParam.getType() == 4 ||
+                            Objects.nonNull(actualRewardsParam.getJltype()) && actualRewardsParam.getJltype() == 3) {
+
+                        if (StringUtils.isBlank(actualRewardsParam.getCouponId())) {
+                            throw new ServiceException("配置错误,优惠券不能为空");
+                        }
+
+                        FsCoupon coupon = fsCouponMapper.selectFsCouponByCouponId(Long.parseLong(actualRewardsParam.getCouponId()));
+                        //不存在
+                        if (coupon == null) {
+                            throw new ServiceException("配置错误,优惠券不存在");
+                        }
+
+                        //停用
+                        if (coupon.getStatus()==0) {
+                            throw new ServiceException("配置错误,优惠券不存在");
+                        }
+                    }
+
+                    guaranteeSum += Objects.isNull(actualRewardsParam.getIsGuarantee()) ? 0 : actualRewardsParam.getIsGuarantee();
+                }
+
+                // 检查保底
+                if (rewardTypeEnum == FsCourseRewardTypeEnum.TYPE_5 && guaranteeSum > 1) {
+                    throw new ServiceException("配置错误,保底配置不正确");
+                }
+                break;
+            case TYPE_2:
+                jsonObject = JSONObject.parseObject(fsCourseReward.getActualRewards());
+                BigDecimal amount = jsonObject.getBigDecimal("amount");
+                if (Objects.isNull(amount)) {
+                    throw new ServiceException("红包金额不能为空");
+                }
+                break;
+            case TYPE_3:
+                jsonObject = JSONObject.parseObject(fsCourseReward.getActualRewards());
+                BigDecimal points = jsonObject.getBigDecimal("points");
+                if (points == null) {
+                    throw new ServiceException("积分数量不能为空");
+                }
+                break;
+            default:
+                throw new ServiceException("类型错误");
+        }
+    }
+
+    /**
+     * 百分比校验
+     */
+    public boolean isValidPercentage(String percentage) {
+        if (percentage == null) {
+            return false;
+        }
+
+        try {
+            String value = percentage.trim().replace("%", "");
+            BigDecimal number = new BigDecimal(value);
+            return number.compareTo(BigDecimal.ZERO) >= 0;
+        } catch (NumberFormatException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 修改奖励配置
+     *
+     * @param fsCourseReward 奖励配置
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseReward(FsCourseReward fsCourseReward)
+    {
+        checkParam(fsCourseReward);
+        fsCourseReward.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsCourseReward(fsCourseReward);
+    }
+
+    /**
+     * 批量删除奖励配置
+     *
+     * @param ids 需要删除的奖励配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseRewardByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsCourseRewardByIds(ids);
+    }
+
+    /**
+     * 删除奖励配置信息
+     *
+     * @param id 奖励配置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseRewardById(Long id)
+    {
+        return baseMapper.deleteFsCourseRewardById(id);
+    }
+
+    /**
+     * 根据id集合查询奖励配置
+     */
+    @Override
+    public List<FsCourseReward> selectFsCourseRewardByIds(List<Long> ids) {
+        return baseMapper.selectFsCourseRewardByIds(ids);
+    }
+}

+ 162 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseRewardVideoRelationServiceImpl.java

@@ -0,0 +1,162 @@
+package com.fs.course.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.course.config.CourseConfig;
+import com.fs.course.domain.FsCourseReward;
+import com.fs.course.domain.FsCourseRewardVideoRelation;
+import com.fs.course.mapper.FsCourseRewardMapper;
+import com.fs.course.mapper.FsCourseRewardVideoRelationMapper;
+import com.fs.course.param.FsCourseRewardVideoRelationParam;
+import com.fs.course.service.IFsCourseRewardVideoRelationService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+/**
+ * 奖励与视频小节关联关系Service业务层处理
+ *
+ * @author fs
+ * @date 2025-09-03
+ */
+@Service
+public class FsCourseRewardVideoRelationServiceImpl extends ServiceImpl<FsCourseRewardVideoRelationMapper, FsCourseRewardVideoRelation> implements IFsCourseRewardVideoRelationService {
+
+    @Autowired
+    private FsCourseRewardMapper fsCourseRewardMapper;
+
+    @Autowired
+    private SysConfigMapper configMapper;
+    /**
+     * 查询奖励与视频小节关联关系
+     *
+     * @param id 奖励与视频小节关联关系主键
+     * @return 奖励与视频小节关联关系
+     */
+    @Override
+    public FsCourseRewardVideoRelation selectFsCourseRewardVideoRelationById(Long id)
+    {
+        return baseMapper.selectFsCourseRewardVideoRelationById(id);
+    }
+
+    /**
+     * 查询奖励与视频小节关联关系列表
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 奖励与视频小节关联关系
+     */
+    @Override
+    public List<FsCourseRewardVideoRelation> selectFsCourseRewardVideoRelationList(FsCourseRewardVideoRelation fsCourseRewardVideoRelation)
+    {
+        return baseMapper.selectFsCourseRewardVideoRelationList(fsCourseRewardVideoRelation);
+    }
+
+    /**
+     * 新增奖励与视频小节关联关系
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 结果
+     */
+    @Override
+    public int insertFsCourseRewardVideoRelation(FsCourseRewardVideoRelation fsCourseRewardVideoRelation)
+    {
+        fsCourseRewardVideoRelation.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsCourseRewardVideoRelation(fsCourseRewardVideoRelation);
+    }
+
+    /**
+     * 修改奖励与视频小节关联关系
+     *
+     * @param fsCourseRewardVideoRelation 奖励与视频小节关联关系
+     * @return 结果
+     */
+    @Override
+    public int updateFsCourseRewardVideoRelation(FsCourseRewardVideoRelationParam fsCourseRewardVideoRelation) {
+        SysConfig sysConfig = configMapper.selectConfigByConfigKey("course.config");
+        CourseConfig config = JSONUtil.toBean(sysConfig.getConfigValue(), CourseConfig.class);
+        // 1. 删除现有的关联关系
+        List<Integer> list = new ArrayList<>();
+        for (Long rewardId : fsCourseRewardVideoRelation.getRewardIdList()) {
+            FsCourseReward fsCourseReward = fsCourseRewardMapper.selectFsCourseRewardById(rewardId);
+            list.add(fsCourseReward.getRewardType().intValue());
+        }
+        if (CollectionUtils.isNotEmpty(list)){
+            baseMapper.deleteByTypes(fsCourseRewardVideoRelation.getVideoSectionId(), fsCourseRewardVideoRelation.getCompanyId(), list);
+        }
+
+        // 2. 获取并排序奖励
+        FsCourseReward fsCourseReward = new FsCourseReward();
+        fsCourseReward.setRewardIds(fsCourseRewardVideoRelation.getRewardIds());
+        List<FsCourseReward> rewards = fsCourseRewardMapper.selectFsCourseRewardList(fsCourseReward);
+
+        List<FsCourseReward> sortedRewards = rewards.stream()
+                .sorted(Comparator.comparing(FsCourseReward::getExpectedValue))
+                .collect(Collectors.toList());
+
+        // 3. 根据奖励数量设置不同的 mark 值
+        int code = 0;
+        int rewardCount = sortedRewards.size();
+
+        for (int i = 0; i < rewardCount; i++) {
+            FsCourseReward sortedReward = sortedRewards.get(i);
+            FsCourseRewardVideoRelation fsCourseRewardVideoRelation2 = new FsCourseRewardVideoRelation();
+            fsCourseRewardVideoRelation2.setRewardId(sortedReward.getId());
+            fsCourseRewardVideoRelation2.setCompanyId(fsCourseRewardVideoRelation.getCompanyId());
+            fsCourseRewardVideoRelation2.setVideoSectionId(fsCourseRewardVideoRelation.getVideoSectionId());
+            fsCourseRewardVideoRelation2.setCreateTime(DateUtils.getNowDate());
+            fsCourseRewardVideoRelation2.setUpdateTime(DateUtils.getNowDate());
+
+            if (rewardCount == 1) {
+                fsCourseRewardVideoRelation2.setMark(config.getGoldPercent()+"%");
+            } else if (rewardCount == 2) {
+                if (i == 0) {
+                    fsCourseRewardVideoRelation2.setMark(config.getSilverPercent()+"%");
+                } else {
+                    fsCourseRewardVideoRelation2.setMark(config.getGoldPercent()+"%");
+                }
+            }
+
+            code = baseMapper.insertFsCourseRewardVideoRelation(fsCourseRewardVideoRelation2);
+        }
+
+        return code;
+    }
+
+    /**
+     * 批量删除奖励与视频小节关联关系
+     *
+     * @param ids 需要删除的奖励与视频小节关联关系主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseRewardVideoRelationByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsCourseRewardVideoRelationByIds(ids);
+    }
+
+    /**
+     * 删除奖励与视频小节关联关系信息
+     *
+     * @param id 奖励与视频小节关联关系主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsCourseRewardVideoRelationById(Long id)
+    {
+        return baseMapper.deleteFsCourseRewardVideoRelationById(id);
+    }
+
+    @Override
+    public List<FsCourseRewardVideoRelation> selectFsCourseRewardVideoRelationListByType(FsCourseRewardVideoRelation fsCourseRewardVideoRelation) {
+        return baseMapper.selectFsCourseRewardVideoRelationListByType(fsCourseRewardVideoRelation);
+    }
+}

+ 305 - 10
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1,5 +1,6 @@
 package com.fs.course.service.impl;
 
+import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
@@ -25,6 +26,8 @@ import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.DateUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.common.utils.luckyDraw.LotteryUtil;
+import com.fs.common.utils.luckyDraw.Prize;
 import com.fs.company.constant.CompanyTrafficConstants;
 import com.fs.company.domain.Company;
 import com.fs.company.domain.CompanyCompanyFsuser;
@@ -53,17 +56,11 @@ import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.*;
 import com.fs.his.config.AppConfig;
-import com.fs.his.domain.FsUser;
-import com.fs.his.domain.FsUserIntegralLogs;
-import com.fs.his.domain.FsUserWx;
-import com.fs.his.mapper.FsPackageMapper;
-import com.fs.his.mapper.FsUserIntegralLogsMapper;
-import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.config.IntegralConfig;
+import com.fs.his.domain.*;
+import com.fs.his.mapper.*;
 import com.fs.his.param.WxSendRedPacketParam;
-import com.fs.his.service.IFsStorePaymentService;
-import com.fs.his.service.IFsUserIntegralLogsService;
-import com.fs.his.service.IFsUserService;
-import com.fs.his.service.IFsUserWxService;
+import com.fs.his.service.*;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.his.vo.FsPackageAndTypeListVO;
 import com.fs.his.vo.FsPackageListVO;
@@ -93,6 +90,8 @@ import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qwApi.Result.QwAddContactWayResult;
 import com.fs.qwApi.param.QwAddContactWayParam;
 import com.fs.qwApi.service.QwApiService;
+import com.fs.reward.domain.FsRewardGoods;
+import com.fs.reward.mapper.FsRewardGoodsMapper;
 import com.fs.sop.domain.SopUserLogsInfo;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.service.ISopUserLogsInfoService;
@@ -153,6 +152,10 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     private OpenIMService openIMService;
     @Autowired
     private CompanyCompanyFsuserMapper companyCompanyFsuserMapper;
+    @Autowired
+    private FsCourseRewardMapper courseRewardMapper;
+    @Autowired
+    private FsRewardGoodsMapper rewardGoodsMapper;
     private static final Logger logger = LoggerFactory.getLogger(FsUserCourseVideoServiceImpl.class);
 
     private static final String miniappRealLink = "/pages_course/video.html?course=";
@@ -282,6 +285,18 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     @Autowired
     private FsPackageMapper fsPackageMapper;
 
+    @Autowired
+    private FsCourseRewardRoundMapper roundMapper;
+
+    @Autowired
+    private FsUserCouponMapper fsUserCouponMapper;
+
+    @Autowired
+    private FsCouponMapper fsCouponMapper;
+
+    @Autowired
+    private FsUserSignMapper fsUserSignMapper;
+
 
     /**
      * 查询课堂视频
@@ -4753,6 +4768,91 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         return handleExternalContactLogic(param, fsUser);
     }
 
+    @Override
+    public JSONArray getSignGrandGiftRules(Long userId) {
+        String json = configService.selectConfigByKey("his.integral");
+        IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+
+        if (config != null && config.getDefaultGrandGift() != null) {
+            FsCourseReward rewardConfig = courseRewardMapper.selectFsCourseRewardById(config.getDefaultGrandGift());
+            if (rewardConfig != null && StringUtils.isNotBlank(rewardConfig.getActualRewards())) {
+                JSONArray jsonArray = JSON.parseArray(rewardConfig.getActualRewards());
+                return buildGrandGiftArray(jsonArray, userId);
+            }
+        }
+        return new JSONArray();
+    }
+
+    @Override
+    public Map<String, Object> claimSignReward(Long userId) {
+        // 检查是否已领取过了
+        LocalDate today = LocalDate.now();
+        Wrapper<FsCourseRewardRound> wrapper = Wrappers.<FsCourseRewardRound>lambdaQuery()
+                .eq(FsCourseRewardRound::getUserId, userId)
+                .eq(FsCourseRewardRound::getWatchId, 0L)
+                .ge(FsCourseRewardRound::getCreateTime, today.atStartOfDay())
+                .lt(FsCourseRewardRound::getCreateTime, today.plusDays(1).atStartOfDay());;
+        if (roundMapper.selectCount(wrapper) > 0) {
+            throw new CustomException("请勿重复领取!");
+        }
+
+        String prizeStr = redisCache.getCacheObject(FsConstants.REDIS_GRAND_GIFT + userId);
+        if (StringUtils.isBlank(prizeStr)) {
+            throw new CustomException("连接超时,请刷新后重试");
+        }
+
+        String json = configService.selectConfigByKey("his.integral");
+        IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+        if (config == null || config.getDefaultGrandGift() == null) {
+            throw new CustomException("非法操作!");
+        }
+
+        FsCourseReward rewardConfig = courseRewardMapper.selectFsCourseRewardById(config.getDefaultGrandGift());
+        if (rewardConfig == null || StringUtils.isBlank(rewardConfig.getActualRewards())) {
+            throw new CustomException("配置错误,请联系管理员#1");
+        }
+
+        Prize prize = JSONObject.parseObject(prizeStr, Prize.class);
+        FsCourseRewardRound rewardRound = new FsCourseRewardRound();
+        rewardRound.setRewardId(rewardConfig.getId());
+        rewardRound.setUserId(userId);
+        rewardRound.setRewardType(rewardConfig.getRewardType());
+        rewardRound.setCompanyId(null);
+        rewardRound.setActualRewards(prize.getAmount());
+        rewardRound.setCreateTime(new Date());
+        rewardRound.setStatus(1L);
+        rewardRound.setWatchId(0L);
+        rewardRound.setRuleId(prize.getCode());
+        rewardRound.setRuleName(prize.getCodeName());
+        rewardRound.setRewardVideoRelationId(null);
+
+        // 抽中奖励商品
+        if (prize.getType() == 5) {
+            FsRewardGoods fsRewardGoods = rewardGoodsMapper.selectById(Long.parseLong(prize.getGoodsId()));
+            rewardRound.setGoodsId(fsRewardGoods.getGoodsId());
+            rewardRound.setGoodsPrice(fsRewardGoods.getPrice());
+        }
+
+        roundMapper.insertFsCourseRewardRound(rewardRound);
+
+        switch (prize.getType()) {
+            case 2:
+                return sendSignPoints(userId, new BigDecimal(prize.getAmount())).put("data", prize.getCode());
+            case 3:
+                return R.ok().put("data", prize.getCode());
+            case 4:
+                return sendCoupon(userId, prize.getCouponId(), Integer.parseInt(prize.getAmount())).put("data", prize.getCode());
+            case 5:
+                Map<String, Object> result = new HashMap<>();
+                result.put("roundId", rewardRound.getId());
+                result.put("goodsId", prize.getGoodsId());
+                result.put("data", prize.getCode());
+                return R.ok(result);
+            default:
+                return R.error("配置错误,请联系管理员#6");
+        }
+    }
+
 
     public void uploadSingleTaskWithRetry(FsVideoResource videoResource,Integer type) {
         int maxRetry = 3;
@@ -4775,5 +4875,200 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
             }
         }
     }
+
+
+    /**
+     * 构建大礼品奖品列表
+     */
+    private JSONArray buildGrandGiftArray(JSONArray old, Long userId) {
+        JSONArray array = new JSONArray(25);
+        Map<String, JSONObject> prizeMap = old.stream()
+                .map(o -> (JSONObject) o)
+                .collect(Collectors.toMap(
+                        o -> o.getString("code"),
+                        o -> o,
+                        (v1, v2) -> v1 // 如果 code 重复,保留第一个
+                ));
+        List<Prize> realPrizes = buildPrizes(old, true);
+        Prize luckyPrize = LotteryUtil.draw(realPrizes);
+        if (Objects.isNull(luckyPrize)) {
+            throw new CustomException("大礼品配置错误,请联系管理员#1");
+        }
+
+        // 缓存中奖记录
+        redisCache.setCacheObject(FsConstants.REDIS_GRAND_GIFT + userId, JSON.toJSONString(luckyPrize), 30, TimeUnit.MINUTES);
+        JSONObject luckyJson = prizeMap.get(luckyPrize.getCode());
+        if (luckyJson != null) {
+            array.add(luckyJson);
+        }
+
+        List<Prize> giftPrizes = buildPrizes(old, false);
+        for (int i = 0; i < 24; i++) {
+            Prize giftPrize = LotteryUtil.draw(giftPrizes);
+            if (Objects.isNull(giftPrize)) {
+                throw new CustomException("大礼品配置错误,请联系管理员#2");
+            }
+            JSONObject giftJson = prizeMap.get(giftPrize.getCode());
+            if (giftJson != null) {
+                array.add(giftJson);
+            }
+        }
+
+        Collections.shuffle(array);
+
+        return array;
+    }
+
+    /**
+     * 构建奖品列表
+     */
+    private List<Prize> buildPrizes(JSONArray jsonArray, boolean isReal) {
+        List<Prize> prizes = new ArrayList<>();
+
+        // 收集所有需要查询的商品ID,批量查询避免N+1问题
+        Set<Long> goodsIds = new HashSet<>();
+        for (Object o : jsonArray) {
+            try {
+                JSONObject json = (JSONObject) o;
+                String goodsId = json.getString("goodsId");
+                if (StringUtils.isNotBlank(goodsId)) {
+                    goodsIds.add(Long.parseLong(goodsId));
+                }
+            } catch (Exception e) {
+                // 忽略解析错误,在后续循环中处理
+            }
+        }
+
+        // 批量查询商品信息
+        Map<Long, FsRewardGoods> goodsMap = new HashMap<>();
+        if (!goodsIds.isEmpty()) {
+            List<FsRewardGoods> goodsList = rewardGoodsMapper.selectBatchIds(goodsIds);
+            goodsMap = goodsList.stream().collect(Collectors.toMap(FsRewardGoods::getGoodsId, g -> g));
+        }
+
+        for (Object o : jsonArray) {
+            try {
+                JSONObject json = (JSONObject) o;
+
+                String name = json.getString("name");
+                Integer type = json.getInteger("type");
+                String amount = json.getString("amount");
+                String code = json.getString("code");
+                String couponId = json.getString("couponId");
+                String goodsId = json.getString("goodsId");
+
+                // 跳过现金红包
+                if (type == 1) {
+                    continue;
+                }
+
+                if (StringUtils.isNotBlank(goodsId)) {
+                    FsRewardGoods fsRewardGoods = goodsMap.get(Long.parseLong(goodsId));
+                    if (Objects.isNull(fsRewardGoods) || fsRewardGoods.getStatus() == 0 || fsRewardGoods.getIsDel() == 1) {
+                        continue;
+                    }
+
+                    if (fsRewardGoods.getStock() <= 0 && isReal) {
+                        continue;
+                    }
+                }
+
+                // 安全解析概率
+                double probability = parseProbability(json.getString("probability"));
+                if (probability <= 0) {
+                    log.warn("奖品概率配置错误,跳过: {}", json);
+                    continue;
+                }
+
+                prizes.add(new Prize(type, amount, code, isReal ? probability : 1, couponId, goodsId, name));
+
+            } catch (Exception e) {
+                log.warn("解析奖品配置失败,跳过: {}", JSON.toJSONString(o), e);
+            }
+        }
+
+        return prizes;
+    }
+    /**
+     * 安全解析概率值
+     */
+    private double parseProbability(String probabilityStr) {
+        try {
+            if (StringUtils.isBlank(probabilityStr)) {
+                return -1;
+            }
+
+            // 移除百分号并转换
+            String cleanStr = probabilityStr.replace("%", "").trim();
+            return Double.parseDouble(cleanStr);
+        } catch (NumberFormatException e) {
+            log.error("概率格式错误: {}", probabilityStr, e);
+            return -1;
+        }
+    }
+
+    /**
+     * 发送积分
+     */
+    private R sendSignPoints(Long userId, BigDecimal points) {
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(userId);
+
+        // 获取签到ID
+        Long signId = fsUserSignMapper.selectTodaySignId(fsUser.getUserId());
+
+        // 添加记录
+        FsUserIntegralLogs logs = new FsUserIntegralLogs();
+        logs.setIntegral(points.longValue());
+        logs.setUserId(fsUser.getUserId());
+        logs.setBalance(NumberUtil.add(fsUser.getIntegral(),points).longValue());
+        logs.setLogType(1);
+        logs.setBusinessId(signId != null ? signId.toString() : null);
+        logs.setCreateTime(new Date());
+        logs.setNickName(fsUser.getNickName());
+        logs.setPhone(fsUser.getPhone());
+        iFsUserIntegralLogsService.insertFsUserIntegralLogs(logs);
+
+        //用户积分增加
+        FsUser userMap =new  FsUser();
+        userMap.setIntegral(fsUser.getIntegral()+points.longValue());
+        userMap.setUserId(fsUser.getUserId());
+        fsUserMapper.updateFsUser(userMap);
+
+        return R.ok("奖励发放成功");
+    }
+
+    /**
+     * 发送优惠券
+     */
+    private R sendCoupon(Long userId, String couponId, Integer num) {
+        log.debug("发送优惠券 userId: {}, couponId: {}, num: {}", userId, couponId, num);
+
+        FsCoupon coupon = fsCouponMapper.selectFsCouponByCouponId(Long.parseLong(couponId));
+        //不存在
+        if (coupon == null) {
+            return R.error("优惠券不存在");
+        }
+        //停用
+        if (coupon.getStatus()==0) {
+            return R.error("优惠券不存在");
+        }
+
+        FsUserCoupon fsUserCoupon = new FsUserCoupon();
+        fsUserCoupon.setCouponId(coupon.getCouponId());
+        fsUserCoupon.setCouponCode("C"+System.currentTimeMillis());
+        fsUserCoupon.setUserId(userId);
+        fsUserCoupon.setCreateTime(DateUtils.getNowDate());
+        if (coupon.getLimitType() == 2){
+            long limitDay = coupon.getLimitDay().longValue() * 24 * 60 * 60 * 1000;
+            long time = new Date().getTime();
+            fsUserCoupon.setLimitTime(new Date(limitDay+time));
+        }else {
+            fsUserCoupon.setLimitTime(coupon.getLimitTime());
+        }
+        fsUserCoupon.setStatus(0);
+        fsUserCouponMapper.insertFsUserCoupon(fsUserCoupon);
+        return R.ok("奖励发放成功");
+    }
+
 }
 

+ 5 - 0
fs-service/src/main/java/com/fs/erp/domain/ErpOrder.java

@@ -35,4 +35,9 @@ public class ErpOrder {
 
     String buyer_account;
     Boolean isIntegralOrder;
+
+    //卖家备注
+    String remark;
+    String parent_code;
+    Integer orderNum = 0;
 }

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

@@ -30,4 +30,6 @@ public class IntegralConfig implements Serializable {
     private Integer integralPlayGame; // 首次下载app获取积分
     private Integer integralArticle; // 观看文章(图文)一篇
     private Integer integralArticleTime; // 观看文章(图文)一篇  多少秒算已看完
+
+    private Long defaultGrandGift; // 签到大礼品配置
 }

+ 10 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -249,4 +249,14 @@ public class FsUser extends BaseEntity
     public void setNickname(String nickname) {
         this.nickname = nickname;
     }
+
+    // 可提现积分
+    private Long withdrawIntegral;
+
+    // 可提现佣金
+    private BigDecimal mayWithdraw;
+    // 累计佣金
+    private BigDecimal totalCommission;
+    // 已提现佣金
+    private BigDecimal withdrawFinish;
 }

+ 9 - 3
fs-service/src/main/java/com/fs/his/mapper/FsUserSignMapper.java

@@ -20,7 +20,7 @@ public interface FsUserSignMapper
     /**
      * 查询签到记录
      *
-     * @param int 签到记录ID
+     * @param id 签到记录ID
      * @return 签到记录
      */
     public FsUserSign selectFsUserSignById(Long id);
@@ -53,7 +53,7 @@ public interface FsUserSignMapper
     /**
      * 删除签到记录
      *
-     * @param int 签到记录ID
+     * @param id 签到记录ID
      * @return 结果
      */
     public int deleteFsUserSignById(Long id);
@@ -61,7 +61,7 @@ public interface FsUserSignMapper
     /**
      * 批量删除签到记录
      *
-     * @param ints 需要删除的数据ID
+     * @param ids 需要删除的数据ID
      * @return 结果
      */
     public int deleteFsUserSignByIds(Long[] ids);
@@ -78,4 +78,10 @@ public interface FsUserSignMapper
             " order by id desc "+
             "</script>"})
     List<FsUserSign> selectFsUserSignListQuery(@Param("maps") FsUserSignQueryParam param);
+
+    /**
+     * 获取用户当日签到记录ID
+     */
+    @Select("select id from fs_user_sign where user_id=#{userId} and DATE_FORMAT(create_time, '%Y-%m-%d') = DATE_FORMAT(NOW(), '%Y-%m-%d') and is_del=0 order by create_time desc limit 1")
+    Long selectTodaySignId(Long userId);
 }

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

@@ -69,4 +69,9 @@ public interface IFsUserSignService
     Long getSign(FsUser user);
 
     Boolean isDaySign(FsUser user);
+
+    /**
+     * 获取用户当日签到记录ID
+     */
+    Long selectTodaySignId(Long userId);
 }

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

@@ -222,7 +222,13 @@ public class FsUserSignServiceImpl implements IFsUserSignService
         return isDaySign;
     }
 
-
+    /**
+     * 获取用户当日签到记录ID
+     */
+    @Override
+    public Long selectTodaySignId(Long userId) {
+        return fsUserSignMapper.selectTodaySignId(userId);
+    }
 
 
     /**

+ 26 - 0
fs-service/src/main/java/com/fs/pay/constant/PaymentTypeConstant.java

@@ -0,0 +1,26 @@
+package com.fs.pay.constant;
+
+/**
+ * 支付类型常量
+ */
+public interface PaymentTypeConstant {
+    /**
+     * 微信支付方式
+     */
+    public static final String WX = "wx";
+
+    /**
+     * 易宝支付
+     */
+    public static final String YB = "yb";
+
+    /**
+     * 台州银行
+     */
+    public static final String TZ_BANK = "tz";
+
+    /**
+     * 汇付
+     */
+    public static final String HF = "hf";
+}

+ 110 - 0
fs-service/src/main/java/com/fs/reward/domain/FsRewardGoods.java

@@ -0,0 +1,110 @@
+package com.fs.reward.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@TableName("fs_reward_goods")
+@Data
+public class FsRewardGoods {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long goodsId;
+
+    /**
+     * 商品名称
+     */
+    private String goodsName;
+
+    /**
+     * 封面
+     */
+    private String goodsImg;
+
+    /**
+     * 轮播图
+     */
+    private String goodsImages;
+
+    /**
+     * 商品介绍
+     */
+    private String goodsIntroduce;
+
+    /**
+     * 商品描述
+     */
+    private String goodsDesc;
+
+    /**
+     * 店铺ID
+     */
+    private Long storeId;
+
+    /**
+     * 店铺商品编码
+     */
+    private String storeGoodsSn;
+
+    /**
+     * 状态 1上架 0下架
+     */
+    private Integer status;
+
+    /**
+     * 单价
+     */
+    private BigDecimal price;
+
+    /**
+     * 原价
+     */
+    private BigDecimal opPrice;
+
+    /**
+     * 库存
+     */
+    private Integer stock;
+
+    /**
+     * 序号
+     */
+    private Integer sort;
+
+    /**
+     * 0未删除 1已删除
+     */
+    private Integer isDel;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+}

+ 155 - 0
fs-service/src/main/java/com/fs/reward/domain/FsRewardGoodsOrder.java

@@ -0,0 +1,155 @@
+package com.fs.reward.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@TableName("fs_reward_goods_order")
+@Data
+public class FsRewardGoodsOrder {
+
+    /**
+     * 主键ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long orderId;
+
+    /**
+     * 订单号
+     */
+    private String orderSn;
+
+    /**
+     * 外部订单号
+     */
+    private String extendOrderSn;
+
+    /**
+     * 店铺ID
+     */
+    private Long storeId;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 收货人
+     */
+    private String userName;
+
+    /**
+     * 收货手机号
+     */
+    private String mobile;
+
+    /**
+     * 收货地址
+     */
+    private String address;
+
+    /**
+     * 订单金额
+     */
+    private BigDecimal orderMoney;
+
+    /**
+     * 支付金额
+     */
+    private BigDecimal payMoney;
+
+    /**
+     * 支付类型
+     */
+    private Integer payType;
+
+    /**
+     * 支付时间
+     */
+    private LocalDateTime payTime;
+
+    /**
+     * 快递公司编号
+     */
+    private String deliveryCode;
+
+    /**
+     * 快递名称
+     */
+    private String deliveryName;
+
+    /**
+     * 快递单号
+     */
+    private String deliverySn;
+
+    /**
+     * 中奖记录ID
+     */
+    private Long roundId;
+
+    /**
+     * 商品ID
+     */
+    private Long goodsId;
+
+    /**
+     * 商品信息
+     */
+    private String goodsJson;
+
+    /**
+     * 状态 1待支付 2已支付 3已发货 4已完成 -1已取消
+     */
+    private Integer status;
+
+    /**
+     * 发货时间
+     */
+    private LocalDateTime deliveryTime;
+
+    /**
+     * 完成时间
+     */
+    private LocalDateTime finishTime;
+
+    /**
+     * 取消时间
+     */
+    private LocalDateTime cancelTime;
+
+    /**
+     * 过期时间
+     */
+    private LocalDateTime expiredTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 修改时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 32 - 0
fs-service/src/main/java/com/fs/reward/mapper/FsRewardGoodsMapper.java

@@ -0,0 +1,32 @@
+package com.fs.reward.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.reward.domain.FsRewardGoods;
+import com.fs.reward.param.FsRewardGoodsListParam;
+import com.fs.reward.vo.FsRewardGoodsVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface FsRewardGoodsMapper extends BaseMapper<FsRewardGoods> {
+
+    /**
+     * 查询奖励商品列表
+     */
+    List<FsRewardGoodsVO> selectFsRewardGoodsVOList(FsRewardGoodsListParam param);
+
+    /**
+     * 查询奖励商品详情
+     */
+    FsRewardGoodsVO selectFsRewardGoodsVOById(@Param("goodsId") Long goodsId);
+
+    /**
+     * 减少库存
+     */
+    int reduceStock(@Param("goodsId") Long goodsId);
+
+    /**
+     * 增加库存(订单取消时回滚)
+     */
+    int addStock(@Param("goodsId") Long goodsId);
+}

+ 39 - 0
fs-service/src/main/java/com/fs/reward/mapper/FsRewardGoodsOrderMapper.java

@@ -0,0 +1,39 @@
+package com.fs.reward.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.reward.domain.FsRewardGoodsOrder;
+import com.fs.reward.param.FsRewardGoodsOrderListParam;
+import com.fs.reward.vo.FsRewardGoodsOrderVO;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+public interface FsRewardGoodsOrderMapper extends BaseMapper<FsRewardGoodsOrder> {
+
+    /**
+     * 查询奖励商品订单列表
+     */
+    List<FsRewardGoodsOrderVO> selectFsRewardGoodsOrderVOList(FsRewardGoodsOrderListParam param);
+
+    /**
+     * 根据id查询奖励商品订单详情
+     */
+    FsRewardGoodsOrderVO selectFsRewardGoodsOrderVOById(@Param("orderId") Long orderId);
+
+    /**
+     * 根据用户id、商品id和轮次id查询奖励商品订单
+     */
+    @Select("select * from fs_reward_goods_order where user_id = #{userId} and goods_id = #{goodsId} and round_id = #{roundId}")
+    FsRewardGoodsOrder selectByUserIdAndGoodsIdAndRoundId(@Param("userId") Long userId, @Param("goodsId") Long goodsId, @Param("roundId") Long roundId);
+
+    /**
+     * 查询未推送订单列表
+     */
+    List<FsRewardGoodsOrder> selectNoPushOrders();
+
+    /**
+     * 查询已推送订单列表
+     */
+    List<Long> selectPushOrders();
+}

+ 68 - 0
fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsAddParam.java

@@ -0,0 +1,68 @@
+package com.fs.reward.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.hibernate.validator.constraints.Range;
+
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+import java.math.BigDecimal;
+
+@Data
+public class FsRewardGoodsAddParam {
+
+    @NotBlank(message = "商品名称不能为空")
+    @ApiModelProperty("商品名称")
+    private String goodsName;
+
+    @NotBlank(message = "商品封面不能为空")
+    @ApiModelProperty("商品封面")
+    private String goodsImg;
+
+    @NotBlank(message = "商品轮播图不能为空")
+    @ApiModelProperty("商品轮播图")
+    private String goodsImages;
+
+    @NotBlank(message = "商品介绍不能为空")
+    @ApiModelProperty("商品介绍")
+    private String goodsIntroduce;
+
+    @ApiModelProperty("商品描述")
+    private String goodsDesc;
+
+    @NotNull(message = "店铺不能为空")
+    @ApiModelProperty("店铺ID")
+    private Long storeId;
+
+    @NotBlank(message = "商品编码不能为空")
+    @ApiModelProperty("店铺商品编码")
+    private String storeGoodsSn;
+
+    @NotNull(message = "状态不能为空")
+    @Range(min = 0, max = 1, message = "状态值不正确")
+    @ApiModelProperty("状态 1上架 0下架")
+    private Integer status;
+
+    @NotNull(message = "商品单价不能为空")
+    @ApiModelProperty("单价")
+    private BigDecimal price;
+
+    @NotNull(message = "商品原价不能为空")
+    @ApiModelProperty("原价")
+    private BigDecimal opPrice;
+
+    @NotNull(message = "库存不能为空")
+    @Min(value = 0, message = "库不能小于0")
+    @ApiModelProperty("库存")
+    private Integer stock;
+
+    @ApiModelProperty("序号")
+    private Integer sort = 1;
+
+    @ApiModelProperty("备注")
+    private String remark;
+
+    @ApiModelProperty("创建人")
+    private String createBy;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsEditParam.java

@@ -0,0 +1,20 @@
+package com.fs.reward.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import javax.validation.constraints.NotNull;
+
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class FsRewardGoodsEditParam extends FsRewardGoodsAddParam {
+
+    @NotNull(message = "商品ID不能为空")
+    @ApiModelProperty("商品ID")
+    private Long goodsId;
+
+    @ApiModelProperty("修改人")
+    private String updateBy;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsListParam.java

@@ -0,0 +1,23 @@
+package com.fs.reward.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+public class FsRewardGoodsListParam {
+
+    @ApiModelProperty("商品ID")
+    private Long goodsId;
+
+    @ApiModelProperty("商品名称")
+    private String goodsName;
+
+    @ApiModelProperty("店铺ID")
+    private Long storeId;
+
+    @ApiModelProperty("商品编码")
+    private String storeGoodsSn;
+
+    @ApiModelProperty("商品状态")
+    private Integer status;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderAddParam.java

@@ -0,0 +1,24 @@
+package com.fs.reward.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@ApiModel("中奖商品下单参数")
+@Data
+public class FsRewardGoodsOrderAddParam {
+
+    @NotNull(message = "商品不能为空")
+    @ApiModelProperty("商品ID")
+    private Long goodsId;
+
+    @NotNull(message = "中奖记录不能为空")
+    @ApiModelProperty("中奖记录ID")
+    private Long roundId;
+
+    @NotNull(message = "收货地址不能为空")
+    @ApiModelProperty("收货地址ID")
+    private Long addressId;
+}

+ 56 - 0
fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderListParam.java

@@ -0,0 +1,56 @@
+package com.fs.reward.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+@Data
+public class FsRewardGoodsOrderListParam {
+
+    @ApiModelProperty("店铺ID")
+    private Long storeId;
+
+    @ApiModelProperty("用户ID")
+    private Long userId;
+
+    @ApiModelProperty("收货人")
+    private String receiveUserName;
+
+    @ApiModelProperty("收货人电话号码")
+    private String receiveUserPhone;
+
+    @ApiModelProperty("订单编码")
+    private String orderSn;
+
+    @ApiModelProperty("状态 1待支付 2已支付 3已发货 4已完成 -1已取消")
+    private Integer status;
+
+    @ApiModelProperty("快递单号")
+    private String deliverySn;
+
+    @ApiModelProperty("创建时间-start")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime sCreateTime;
+
+    @ApiModelProperty("创建时间-end")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime eCreateTime;
+
+    @ApiModelProperty("支付时间-start")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime sPayTime;
+
+    @ApiModelProperty("支付时间-end")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime ePayTime;
+
+    @ApiModelProperty("发货时间-start")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime sDeliveryTime;
+
+    @ApiModelProperty("发货时间-end")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime eDeliveryTime;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderPayParam.java

@@ -0,0 +1,25 @@
+package com.fs.reward.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+@ApiModel("支付参数")
+@Data
+public class FsRewardGoodsOrderPayParam implements Serializable {
+
+    @ApiModelProperty("订单号")
+    @NotNull(message = "订单号不能为空")
+    private Long orderId;
+
+    @ApiModelProperty("支付类型 1-微信 2-微信H5 3-微信APP 4-支付宝 5-支付宝H5")
+    @NotNull(message = "支付类型不能为空")
+    private Integer payType;
+
+    @ApiModelProperty("微信APPID")
+    private String appId;
+
+}

+ 71 - 0
fs-service/src/main/java/com/fs/reward/service/IFsRewardGoodsOrderService.java

@@ -0,0 +1,71 @@
+package com.fs.reward.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.FsStorePayment;
+import com.fs.reward.domain.FsRewardGoodsOrder;
+import com.fs.reward.param.FsRewardGoodsOrderAddParam;
+import com.fs.reward.param.FsRewardGoodsOrderListParam;
+import com.fs.reward.param.FsRewardGoodsOrderPayParam;
+import com.fs.reward.vo.FsRewardGoodsOrderVO;
+
+import java.util.List;
+import java.util.Map;
+
+public interface IFsRewardGoodsOrderService extends IService<FsRewardGoodsOrder> {
+
+    /**
+     * 查询奖励商品订单列表
+     */
+    List<FsRewardGoodsOrderVO> selectFsRewardGoodsOrderVOList(FsRewardGoodsOrderListParam param);
+
+    /**
+     * 根据id查询奖励商品订单详情
+     */
+    FsRewardGoodsOrderVO selectFsRewardGoodsOrderVOById(Long orderId);
+
+    /**
+     * 创建奖励商品订单
+     */
+    FsRewardGoodsOrder createOrder(Long userId, FsRewardGoodsOrderAddParam param);
+
+    /**
+     * 支付订单
+     */
+    Map<String, Object> payment(Long userId, FsRewardGoodsOrderPayParam param);
+
+    /**
+     * 支付回调
+     */
+    R payConfirm(String orderSn, String payCode, String tradeNo, String payType, Integer type);
+
+    /**
+     * 推送erp
+     */
+    void pushErp() throws InterruptedException;
+
+    /**
+     * 同步erp信息
+     */
+    void syncErpData() throws InterruptedException;
+
+    /**
+     * 获取订单支付信息
+     */
+    List<FsStorePayment> getPayInfo(String orderSn);
+
+    /**
+     * 取消过期未支付订单
+     */
+    void cancelExpireRewardOrder();
+
+    /**
+     * 完成订单
+     */
+    void completeOrder();
+
+    /**
+     * 取消订单
+     */
+    int cancelOrder(Long orderId);
+}

+ 38 - 0
fs-service/src/main/java/com/fs/reward/service/IFsRewardGoodsService.java

@@ -0,0 +1,38 @@
+package com.fs.reward.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.reward.domain.FsRewardGoods;
+import com.fs.reward.param.FsRewardGoodsAddParam;
+import com.fs.reward.param.FsRewardGoodsEditParam;
+import com.fs.reward.param.FsRewardGoodsListParam;
+import com.fs.reward.vo.FsRewardGoodsVO;
+
+import java.util.List;
+
+public interface IFsRewardGoodsService extends IService<FsRewardGoods> {
+
+    /**
+     * 查询奖励商品列表
+     */
+    List<FsRewardGoodsVO> selectFsRewardGoodsVOList(FsRewardGoodsListParam param);
+
+    /**
+     * 查询奖励商品详情
+     */
+    FsRewardGoodsVO selectFsRewardGoodsVOById(Long goodsId);
+
+    /**
+     * 添加奖励商品
+     */
+    int insertFsRewardGoods(FsRewardGoodsAddParam param);
+
+    /**
+     * 修改奖励商品
+     */
+    int updateFsRewardGoods(FsRewardGoodsEditParam param);
+
+    /**
+     * 逻辑删除商品
+     */
+    int logicDeleteFsRewardGoods(Long[] goodsIds);
+}

+ 1234 - 0
fs-service/src/main/java/com/fs/reward/service/impl/FsRewardGoodsOrderServiceImpl.java

@@ -0,0 +1,1234 @@
+package com.fs.reward.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.core.utils.OrderCodeUtils;
+import com.fs.course.domain.FsCourseRewardRound;
+import com.fs.course.mapper.FsCourseRewardRoundMapper;
+import com.fs.erp.domain.*;
+import com.fs.erp.dto.BaseResponse;
+import com.fs.erp.dto.ErpOrderQueryRequert;
+import com.fs.erp.dto.ErpOrderQueryResponse;
+import com.fs.erp.dto.ErpRefundUpdateRequest;
+import com.fs.his.domain.*;
+import com.fs.his.dto.ErpRemarkDTO;
+import com.fs.his.dto.PayConfigDTO;
+import com.fs.his.enums.ShipperCodeEnum;
+import com.fs.his.mapper.FsStoreMapper;
+import com.fs.his.mapper.FsUserAddressMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.mapper.FsUserWxMapper;
+import com.fs.his.service.IFsExpressService;
+import com.fs.his.service.IFsStorePaymentService;
+import com.fs.his.service.IFsUserAddressService;
+import com.fs.his.utils.PhoneUtil;
+import com.fs.huifuPay.domain.HuiFuCreateOrder;
+import com.fs.huifuPay.domain.HuiFuRefundResult;
+import com.fs.huifuPay.domain.HuifuCreateOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayRefundRequest;
+import com.fs.huifuPay.service.HuiFuService;
+import com.fs.pay.constant.PaymentTypeConstant;
+import com.fs.pay.domain.PaymentMiniProgramConfig;
+import com.fs.reward.domain.FsRewardGoods;
+import com.fs.reward.domain.FsRewardGoodsOrder;
+import com.fs.reward.mapper.FsRewardGoodsMapper;
+import com.fs.reward.mapper.FsRewardGoodsOrderMapper;
+import com.fs.reward.param.FsRewardGoodsOrderAddParam;
+import com.fs.reward.param.FsRewardGoodsOrderListParam;
+import com.fs.reward.param.FsRewardGoodsOrderPayParam;
+import com.fs.reward.service.IFsRewardGoodsOrderService;
+import com.fs.reward.vo.FsRewardGoodsOrderVO;
+import com.fs.system.service.ISysConfigService;
+import com.fs.tzBankPay.TzBankService.TzBankService;
+import com.fs.tzBankPay.doman.*;
+import com.fs.wx.order.domain.FsWxExpressTask;
+import com.fs.wx.order.mapper.FsWxExpressTaskMapper;
+import com.fs.ybPay.domain.CreateWxOrderResult;
+import com.fs.ybPay.domain.OrderResult;
+import com.fs.ybPay.domain.RefundResult;
+import com.fs.ybPay.dto.OrderQueryDTO;
+import com.fs.ybPay.dto.RefundDTO;
+import com.fs.ybPay.dto.WxJspayDTO;
+import com.fs.ybPay.service.IPayService;
+import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
+import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryResult;
+import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.google.common.collect.Lists;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static com.fs.his.utils.PhoneUtil.decryptPhone;
+
+@Slf4j
+@Service
+public class FsRewardGoodsOrderServiceImpl extends ServiceImpl<FsRewardGoodsOrderMapper, FsRewardGoodsOrder> implements IFsRewardGoodsOrderService {
+
+    @Autowired
+    private FsRewardGoodsMapper fsRewardGoodsMapper;
+    @Autowired
+    private FsUserAddressMapper fsUserAddressMapper;
+    @Autowired
+    private FsCourseRewardRoundMapper fsCourseRewardRoundMapper;
+    @Autowired
+    private FsUserMapper fsUserMapper;
+    @Autowired
+    private FsStoreMapper fsStoreMapper;
+    @Autowired
+    private IFsStorePaymentService storePaymentService;
+    @Autowired
+    private WxPayService wxPayService;
+    @Autowired
+    private IPayService payService;
+    @Autowired
+    private TzBankService tzBankService;
+    @Autowired
+    private HuiFuService huiFuService;
+    @Autowired
+    private ISysConfigService configService;
+    @Autowired
+    private FsUserWxMapper fsUserWxMapper;
+    @Autowired
+    private IFsExpressService expressService;
+    @Autowired
+    private IFsUserAddressService fsUserAddressService;
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private FsWxExpressTaskMapper fsWxExpressTaskMapper;
+
+    @Value("${jst.selfShopCode:''}")
+    private String selfShopCode;
+
+    /**
+     * 查询奖励商品订单列表
+     */
+    @Override
+    public List<FsRewardGoodsOrderVO> selectFsRewardGoodsOrderVOList(FsRewardGoodsOrderListParam param) {
+        return baseMapper.selectFsRewardGoodsOrderVOList(param);
+    }
+
+    /**
+     * 根据id查询奖励商品订单详情
+     */
+    @Override
+    public FsRewardGoodsOrderVO selectFsRewardGoodsOrderVOById(Long orderId) {
+        return baseMapper.selectFsRewardGoodsOrderVOById(orderId);
+    }
+
+    /**
+     * 创建奖励商品订单
+     */
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public FsRewardGoodsOrder createOrder(Long userId, FsRewardGoodsOrderAddParam param) {
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(userId);
+        if (Objects.isNull(fsUser)) {
+            throw new CustomException("用户不存在!");
+        }
+
+        FsCourseRewardRound rewardRound = fsCourseRewardRoundMapper.selectFsCourseRewardRoundById(param.getRoundId());
+        if (Objects.isNull(rewardRound)
+                || !Objects.equals(rewardRound.getGoodsId(), param.getGoodsId())
+                || !Objects.equals(rewardRound.getUserId(), userId)) {
+            throw new CustomException("中奖记录不存在!");
+        }
+
+        LocalDateTime lastTime = rewardRound.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().plusDays(30);
+        if (LocalDateTime.now().isAfter(lastTime)) {
+            throw new CustomException("中奖记录已过期!");
+        }
+
+        FsRewardGoodsOrder fsRewardGoodsOrder = baseMapper.selectByUserIdAndGoodsIdAndRoundId(userId, param.getGoodsId(), param.getRoundId());
+        if (Objects.nonNull(fsRewardGoodsOrder)) {
+            throw new CustomException("请勿重复下单!");
+        }
+
+        FsRewardGoods fsRewardGoods = fsRewardGoodsMapper.selectById(param.getGoodsId());
+        if (Objects.isNull(fsRewardGoods) || fsRewardGoods.getIsDel() == 1) {
+            throw new CustomException("商品不存在!");
+        }
+
+        if (fsRewardGoods.getStatus() == 0) {
+            throw new CustomException("商品已下架!");
+        }
+
+        if (fsRewardGoods.getStock() <= 0) {
+            throw new CustomException("商品库存不足!");
+        }
+
+        FsUserAddress fsUserAddress = fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
+        if (Objects.isNull(fsUserAddress)) {
+            throw new CustomException("收货地址不存在!");
+        }
+
+        String orderSn = "reward"+OrderCodeUtils.getOrderSn();
+        if (StringUtils.isEmpty(orderSn)) {
+            throw new CustomException("订单号生成失败,请重试");
+        }
+
+        // 减库存
+        int cnt = fsRewardGoodsMapper.reduceStock(fsRewardGoods.getGoodsId());
+        if (cnt <= 0) {
+            throw new CustomException("商品库存不足!");
+        }
+
+        // 抽中时的价格
+        if (Objects.nonNull(rewardRound.getGoodsPrice())) {
+            fsRewardGoods.setPrice(rewardRound.getGoodsPrice());
+        }
+
+        fsRewardGoodsOrder = new FsRewardGoodsOrder();
+        fsRewardGoodsOrder.setOrderSn(orderSn);
+        fsRewardGoodsOrder.setStoreId(fsRewardGoods.getStoreId());
+        fsRewardGoodsOrder.setUserId(userId);
+        fsRewardGoodsOrder.setUserName(fsUserAddress.getRealName().trim());
+        fsRewardGoodsOrder.setMobile(fsUserAddress.getPhone().trim());
+        fsRewardGoodsOrder.setAddress(fsUserAddress.getProvince() + " " + fsUserAddress.getCity() + " " + fsUserAddress.getDistrict() + " " + fsUserAddress.getDetail());
+        fsRewardGoodsOrder.setOrderMoney(fsRewardGoods.getPrice());
+        fsRewardGoodsOrder.setPayMoney(fsRewardGoods.getPrice());
+        fsRewardGoodsOrder.setRoundId(param.getRoundId());
+        fsRewardGoodsOrder.setGoodsId(param.getGoodsId());
+        fsRewardGoodsOrder.setGoodsJson(JSON.toJSONString(fsRewardGoods));
+        fsRewardGoodsOrder.setStatus(1);
+        fsRewardGoodsOrder.setExpiredTime(lastTime);
+        fsRewardGoodsOrder.setCreateTime(LocalDateTime.now());
+        fsRewardGoodsOrder.setCreateBy(fsUser.getNickName());
+        fsRewardGoodsOrder.setRemark("实物抽奖");
+        baseMapper.insert(fsRewardGoodsOrder);
+
+        return fsRewardGoodsOrder;
+    }
+
+    /**
+     * 支付订单
+     */
+    @Override
+    public Map<String, Object> payment(Long userId, FsRewardGoodsOrderPayParam param) {
+        // 查询订单
+        FsRewardGoodsOrder order = baseMapper.selectById(param.getOrderId());
+        if (Objects.isNull(order)) {
+            throw new CustomException("订单不存在!");
+        }
+        
+        if (!order.getUserId().equals(userId)) {
+            throw new CustomException("非法操作!");
+        }
+        
+        if (!order.getStatus().equals(1)) {
+            throw new CustomException("订单状态不正确!");
+        }
+
+        if (order.getExpiredTime().isBefore(LocalDateTime.now())) {
+            throw new CustomException("订单已过期!");
+        }
+        
+        // 查询用户
+        FsUser user = fsUserMapper.selectFsUserByUserId(userId);
+        if (Objects.isNull(user)) {
+            throw new CustomException("用户不存在!");
+        }
+
+        order.setPayType(param.getPayType());
+        baseMapper.updateById(order);
+        
+        // 如果金额为0,直接完成支付
+        if (order.getPayMoney().compareTo(BigDecimal.ZERO) == 0) {
+            this.payConfirm(order.getOrderSn(), "", "", "", 2);
+
+            Map<String, Object> result = new HashMap<>();
+            result.put("isPay", 1);
+            result.put("message", "支付成功");
+            return result;
+        }
+        
+        // 根据支付类型进行支付
+        switch (param.getPayType()) {
+            case 1:
+                return handleWxPayment(order, user, param);
+            case 2:
+                return handleWxH5Payment(order, user, param);
+            case 3:
+                return handleWxAppPayment(order, user, param);
+            case 4:
+                return handleAlipayPayment(order, user, param);
+            case 5:
+                return handleAlipayH5Payment(order, user, param);
+            default:
+                throw new CustomException("不支持的支付类型!");
+        }
+    }
+
+    /**
+     * 处理微信App支付
+     */
+    private Map<String, Object> handleWxAppPayment(FsRewardGoodsOrder order, FsUser user, FsRewardGoodsOrderPayParam param) {
+        if (StringUtils.isEmpty(user.getMpOpenId())) {
+            throw new CustomException("用户OpenID不存在!");
+        }
+
+        // 获取配置
+        String json = configService.selectConfigByKey("his.pay");
+        PayConfigDTO payConfigDTO = JSONUtil.toBean(json, PayConfigDTO.class);
+
+        // 创建支付记录
+        FsStorePayment storePayment = createPaymentRecord(order, user.getUserId(), payConfigDTO.getType(), "微信", null, user.getMpOpenId());
+
+//        switch (payConfigDTO.getType()) {
+//            case PaymentTypeConstant.YB:
+//                throw new CustomException("支付暂不可用!");
+//            case PaymentTypeConstant.TZ_BANK:
+//                return tzPay(order, user, param, storePayment);
+//            case PaymentTypeConstant.HF:
+//                return hfPay(user, param, storePayment);
+//        }
+
+        throw new CustomException("支付配置错误!");
+    }
+
+    /**
+     * 处理微信h5支付
+     */
+    private Map<String, Object> handleWxH5Payment(FsRewardGoodsOrder order, FsUser user, FsRewardGoodsOrderPayParam param) {
+        if (StringUtils.isEmpty(user.getMpOpenId())) {
+            throw new CustomException("用户OpenID不存在!");
+        }
+
+        // 获取配置
+        String json = configService.selectConfigByKey("his.pay");
+        PayConfigDTO payConfigDTO = JSONUtil.toBean(json, PayConfigDTO.class);
+
+        // 创建支付记录
+        FsStorePayment storePayment = createPaymentRecord(order, user.getUserId(), payConfigDTO.getType(), "微信", null, user.getMpOpenId());
+
+        switch (payConfigDTO.getType()) {
+//            case PaymentTypeConstant.YB:
+//                throw new CustomException("支付暂不可用!");
+//            case PaymentTypeConstant.TZ_BANK:
+//                return tzPay(order, user, param, storePayment);
+//            case PaymentTypeConstant.HF:
+//                return hfPay(user, param, storePayment);
+        }
+
+        throw new CustomException("支付配置错误!");
+    }
+
+    /**
+     * 处理微信支付
+     */
+    private Map<String, Object> handleWxPayment(FsRewardGoodsOrder order, FsUser user, FsRewardGoodsOrderPayParam param) {
+        if (StringUtils.isEmpty(user.getMaOpenId())) {
+            throw new CustomException("用户OpenID不存在!");
+        }
+        
+//        // 获取支付配置
+//        PaymentMiniProgramConfig paymentConfig = paymentConfigService.selectPaymentConfigByAppId(param.getAppId());
+//        if (Objects.isNull(paymentConfig)) {
+//            throw new CustomException("当前小程序没有配置可用的支付方式!");
+//        }
+//
+//        // 创建支付记录
+//        FsStorePayment storePayment = createPaymentRecord(order, user.getUserId(), paymentConfig.getPayType(), "weixin", param.getAppId(), user.getMaOpenId());
+//
+//        switch (paymentConfig.getPayType()) {
+//            case PaymentTypeConstant.WX:
+//                return wxPay(user, paymentConfig, storePayment);
+//            case PaymentTypeConstant.YB:
+//                return ybPay(user, param, storePayment);
+//            case PaymentTypeConstant.TZ_BANK:
+//                return tzPay(order, user, param, storePayment);
+//            case PaymentTypeConstant.HF:
+//                return hfPay(user, param, storePayment);
+//        }
+        
+        throw new CustomException("支付配置错误!");
+    }
+
+    /**
+     * 汇付支付
+     */
+    private Map<String, Object> hfPay(FsUser user, FsRewardGoodsOrderPayParam param, FsStorePayment storePayment) {
+        log.info("创建汇付订单");
+        HuiFuCreateOrder o = new HuiFuCreateOrder();
+
+        if (param.getPayType() == 1) {
+            o.setTradeType("T_MINIAPP");
+            o.setAppId(param.getAppId());
+            o.setOpenid(user.getMaOpenId());
+        } else if (param.getPayType() == 2) {
+            o.setTradeType("T_JSAPI");
+            o.setOpenid(user.getMpOpenId());
+        } else if (param.getPayType() == 3) {
+            o.setTradeType("T_JSAPI");
+            o.setOpenid(user.getMpOpenId());
+        } else if (param.getPayType() == 4){
+            o.setTradeType("A_NATIVE");
+        } else if (param.getPayType() == 5) {
+            o.setTradeType("A_NATIVE");
+        }
+
+        o.setReqSeqId("reward-" + storePayment.getPayCode());
+        o.setTransAmt(storePayment.getPayMoney().toString());
+        o.setGoodsDesc("奖励商品订单支付");
+        HuifuCreateOrderResult huiResult = huiFuService.createOrder(o);
+
+        log.info("创建汇付支付:{}", huiResult);
+        FsStorePayment mt = new FsStorePayment();
+        mt.setPaymentId(storePayment.getPaymentId());
+        mt.setTradeNo(huiResult.getHf_seq_id());
+        storePaymentService.updateFsStorePayment(mt);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("data", huiResult);
+//        result.put("type", PaymentTypeConstant.HF);
+        result.put("isPay", 0);
+        return result;
+    }
+
+    /**
+     * 台州银行支付
+     */
+    private Map<String, Object> tzPay(FsRewardGoodsOrder order, FsUser user, FsRewardGoodsOrderPayParam param, FsStorePayment storePayment) {
+        PayCreateOrder o = new PayCreateOrder();
+        o.setOrderNo("reward" + storePayment.getPayCode()); // 业务系统订单号
+        o.setTrxAmt(storePayment.getPayMoney().doubleValue()); // 交易金额
+        o.setBusinessCstNo(order.getUserId().toString()); // 业务平台客户号
+
+        String phone = "";
+        if (user.getPhone() != null && user.getPhone().length() > 4) {
+            phone = user.getPhone().substring(user.getPhone().length() - 4);
+        }
+
+        if (param.getPayType() == 1) {
+            if (user.getPhone().length() > 11) {
+                o.setPayerMobileNo(PhoneUtil.decryptPhone(user.getPhone()));
+            } else {
+                o.setPayerMobileNo(user.getPhone());
+            }
+            String openId = user.getMaOpenId();
+            if (param.getAppId() != null){
+                Wrapper<FsUserWx> queryWrapper = Wrappers.<FsUserWx>lambdaQuery()
+                        .eq(FsUserWx::getFsUserId, user.getUserId())
+                        .eq(FsUserWx::getAppId, param.getAppId());
+                FsUserWx fsUserWx = fsUserWxMapper.selectOne(queryWrapper);
+                if (Objects.nonNull(fsUserWx)){
+                    openId = fsUserWx.getOpenId();
+                }
+            }
+
+            o.setOpenId(openId);
+            o.setAppid(param.getAppId());
+//        } else if (param.getPayType() == 2) {
+//            o.setOpenId(user.getMpOpenId());
+//            o.setPayType(Collections.singletonList(PayType.微信公众号.getCode()));
+//        } else if (param.getPayType() == 3) {
+//            o.setPayType(Collections.singletonList("11"));
+//        } else if (param.getPayType() == 4){
+//            o.setPayType(Collections.singletonList(PayType.支付宝条码支付.getCode()));
+//        } else if (param.getPayType() == 5) {
+//            o.setOpenId(user.getMpOpenId());
+//            o.setPayType(Collections.singletonList(PayType.支付宝条码支付.getCode()));
+        }
+
+        o.setPayerName("微信用户" + phone);
+        o.setGoodsInfo("奖励商品订单支付"); // 订单信息
+        o.setOrderType(10);
+        o.setOrderId(order.getOrderId().toString());
+
+        TzBankResult<PayCreateOrderResult> tzBankResult;
+        if (param.getPayType() == 3) {
+            tzBankResult = tzBankService.createAppOrder(o);
+        } else {
+            tzBankResult = tzBankService.createOrder(o);
+        }
+
+        FsStorePayment mt = new FsStorePayment();
+        mt.setPaymentId(storePayment.getPaymentId());
+        mt.setTradeNo(tzBankResult.getBody().getOrderFlowNo());
+        storePaymentService.updateFsStorePayment(mt);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("data", tzBankResult);
+        result.put("type", PaymentTypeConstant.TZ_BANK);
+        result.put("isPay", 0);
+        return result;
+    }
+
+    /**
+     * 易宝支付
+     */
+    private Map<String, Object> ybPay(FsUser user, FsRewardGoodsOrderPayParam param, FsStorePayment storePayment) {
+        WxJspayDTO p = new WxJspayDTO();
+        p.setPayMoney(storePayment.getPayMoney().toString());
+        p.setLowOrderId("reward-" + storePayment.getPayCode());
+        p.setBody("奖励商品订单支付");
+        p.setIsMinipg("1");
+        p.setOpenId(user.getMaOpenId());
+        p.setAttach("");
+        p.setStoreid("0");
+        p.setAppId(param.getAppId());
+
+        CreateWxOrderResult wxOrder = payService.createWxOrder(p);
+        log.info("易宝返回: {}", wxOrder);
+        if (wxOrder.getStatus().equals("100")) {
+            FsStorePayment mt = new FsStorePayment();
+            mt.setPaymentId(storePayment.getPaymentId());
+            mt.setTradeNo(wxOrder.getUpOrderId());
+            storePaymentService.updateFsStorePayment(mt);
+
+            Map<String, Object> result = new HashMap<>();
+            result.put("data", wxOrder);
+            result.put("type", PaymentTypeConstant.YB);
+            result.put("isPay", 0);
+            return result;
+        } else {
+            throw new CustomException("支付失败");
+        }
+    }
+
+    /**
+     * 微信支付
+     */
+    private Map<String, Object> wxPay(FsUser user, PaymentMiniProgramConfig paymentConfig, FsStorePayment storePayment) {
+        WxPayConfig payConfig = new WxPayConfig();
+        payConfig.setAppId(paymentConfig.getAppid());
+        payConfig.setMchId(paymentConfig.getWxMerchantNo());
+        payConfig.setMchKey(paymentConfig.getWxKey());
+        payConfig.setSubAppId(StringUtils.trimToNull(null));
+        payConfig.setSubMchId(StringUtils.trimToNull(null));
+        payConfig.setKeyPath(null);
+        payConfig.setNotifyUrl(paymentConfig.getWxNotifyUrl());
+        wxPayService.setConfig(payConfig);
+
+        WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
+        orderRequest.setOpenid(user.getMaOpenId());
+        orderRequest.setBody("奖励商品订单支付");
+        orderRequest.setOutTradeNo("reward-" + storePayment.getPayCode());
+        orderRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(storePayment.getPayMoney().toString()));
+        orderRequest.setTradeType("JSAPI");
+        orderRequest.setSpbillCreateIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
+
+        try {
+            WxPayMpOrderResult orderResult = wxPayService.createOrder(orderRequest);
+
+            Map<String, Object> result = new HashMap<>();
+            result.put("data", orderResult);
+            result.put("type", PaymentTypeConstant.WX);
+            result.put("isPay", 0);
+            return result;
+        } catch (WxPayException e) {
+            log.error("微信支付失败", e);
+            throw new CustomException("支付失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理支付宝支付
+     */
+    private Map<String, Object> handleAlipayPayment(FsRewardGoodsOrder order, FsUser user, FsRewardGoodsOrderPayParam param) {
+        // 获取配置
+        String json = configService.selectConfigByKey("his.pay");
+        PayConfigDTO payConfigDTO = JSONUtil.toBean(json, PayConfigDTO.class);
+
+        // 创建支付记录
+        FsStorePayment storePayment = createPaymentRecord(order, user.getUserId(), payConfigDTO.getType(), "alipay", null, null);
+
+        switch (payConfigDTO.getType()) {
+            case PaymentTypeConstant.YB:
+                throw new CustomException("支付暂不可用!");
+            case PaymentTypeConstant.TZ_BANK:
+                return tzPay(order, user, param, storePayment);
+            case PaymentTypeConstant.HF:
+                return hfPay(user, param, storePayment);
+        }
+
+        throw new CustomException("支付配置错误!");
+    }
+
+
+    /**
+     * 支付宝H5支付
+     */
+    private Map<String, Object> handleAlipayH5Payment(FsRewardGoodsOrder order, FsUser user, FsRewardGoodsOrderPayParam param) {
+        // 获取配置
+        String json = configService.selectConfigByKey("his.pay");
+        PayConfigDTO payConfigDTO = JSONUtil.toBean(json, PayConfigDTO.class);
+
+        // 创建支付记录
+        FsStorePayment storePayment = createPaymentRecord(order, user.getUserId(), payConfigDTO.getType(), "alipay", null, user.getMpOpenId());
+
+        switch (payConfigDTO.getType()) {
+            case PaymentTypeConstant.YB:
+                throw new CustomException("支付暂不可用!");
+            case PaymentTypeConstant.TZ_BANK:
+                return tzPay(order, user, param, storePayment);
+            case PaymentTypeConstant.HF:
+                return hfPay(user, param, storePayment);
+        }
+
+        throw new CustomException("支付配置错误!");
+    }
+    
+    /**
+     * 创建支付记录
+     */
+    private FsStorePayment createPaymentRecord(FsRewardGoodsOrder order, Long userId, String payType, String payTypeCode, String appId, String openId) {
+        String payCode = OrderCodeUtils.getOrderSn();
+        if (StringUtils.isEmpty(payCode)) {
+            throw new CustomException("支付码生成失败,请重试");
+        }
+        
+        FsStorePayment storePayment = new FsStorePayment();
+        storePayment.setStatus(0);
+        storePayment.setPayMode(payType);
+        storePayment.setBusinessCode(order.getOrderSn());
+        storePayment.setPayCode(payCode);
+        storePayment.setPayMoney(order.getPayMoney());
+        storePayment.setCreateTime(new Date());
+        storePayment.setPayTypeCode(payTypeCode);
+        storePayment.setBusinessType(10); // 奖励商品订单
+        storePayment.setRemark("奖励商品订单支付");
+        storePayment.setOpenId(openId);
+        storePayment.setUserId(userId);
+        storePayment.setStoreId(order.getStoreId());
+        storePayment.setBusinessId(order.getOrderId().toString());
+        storePayment.setAppId(appId);
+        
+        if (storePaymentService.insertFsStorePayment(storePayment) > 0) {
+            return storePayment;
+        }
+        
+        throw new CustomException("创建支付记录失败!");
+    }
+
+    /**
+     * 支付回调
+     */
+    @Override
+    public R payConfirm(String orderSn, String payCode, String tradeNo, String payType, Integer type) {
+        FsRewardGoodsOrder order;
+        if (type == 1) {
+            FsStorePayment storePayment = storePaymentService.selectFsStorePaymentByPaymentCode(payCode);
+            if (storePayment != null) {
+                if (storePayment.getStatus().equals(0)) {
+                    FsStorePayment paymentMap = new FsStorePayment();
+                    paymentMap.setPaymentId(storePayment.getPaymentId());
+                    paymentMap.setStatus(1);
+                    paymentMap.setPayTime(new Date());
+                    paymentMap.setTradeNo(tradeNo);
+                    if (payType.equals(PayType.WECHAT_MINI_PROGRAM_PAYMENT.getCode())) {
+                        paymentMap.setPayTypeCode(PayType.WECHAT_MINI_PROGRAM_PAYMENT.name());
+                    } else if (payType.equals(PayType.ALIPAY_BARCODE_PAYMENT.getCode())) {
+                        paymentMap.setPayTypeCode(PayType.ALIPAY_BARCODE_PAYMENT.name());
+                    }
+                    if (storePayment.getPayMode().equals("yb")) {
+                        OrderQueryDTO orderQueryDTO = new OrderQueryDTO();
+                        orderQueryDTO.setUpOrderId(tradeNo);
+                        OrderResult orderResult = payService.getOrder(orderQueryDTO);
+                        paymentMap.setBankSerialNo(orderResult.getBankOrderId());
+                        paymentMap.setBankTransactionId(orderResult.getBankTrxId());
+                    }
+                    log.info("更新支付记录");
+                    storePaymentService.updateFsStorePayment(paymentMap);
+                }
+                order = getById(Long.parseLong(storePayment.getBusinessId()));
+            } else {
+                log.info("支付单号不存在: {}", payCode);
+                return R.error("支付单号不存在");
+            }
+        } else if (type == 2) {
+            order = lambdaQuery().eq(FsRewardGoodsOrder::getOrderSn, orderSn).one();
+        } else {
+            return R.error("type类型错误");
+        }
+
+        order.setStatus(2); // 已支付
+        order.setPayTime(LocalDateTime.now());
+        order.setUpdateTime(LocalDateTime.now());
+        order.setUpdateBy("回调");
+        baseMapper.updateById(order);
+        return R.ok();
+    }
+
+    /**
+     * 推送erp
+     */
+    @Override
+    public void pushErp() throws InterruptedException {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+        List<FsRewardGoodsOrder> noPushOrder = baseMapper.selectNoPushOrders();
+        List<ErpOrder> erpOrders = noPushOrder.stream().map(order -> {
+            ErpOrder erpOrder = new ErpOrder();
+            erpOrder.setVip_code(order.getUserId().toString());
+            erpOrder.setPlatform_code(order.getOrderSn());
+            erpOrder.setShop_code(selfShopCode);
+            erpOrder.setSeller_memo(order.getRemark());
+            erpOrder.setBuyer_account(order.getUserName());
+            erpOrder.setRemark(order.getRemark());
+
+            List<ErpOrderPayment> payments = new ArrayList<>();
+            ErpOrderPayment payment = new ErpOrderPayment();
+            payment.setPay_type_code("weixin");
+            payment.setPayment(order.getPayMoney().doubleValue());
+            payment.setPaytime(Timestamp.valueOf(order.getPayTime()));
+            payments.add(payment);
+            erpOrder.setPayments(payments);
+
+            //大于100发发顺丰云配
+            if (order.getPayMoney().compareTo(new BigDecimal(100)) > 0) {
+                //发互联网医院SF.0235488558_241101
+                FsExpress express = expressService.selectFsExpressByOmsCode("SF.0235488558_241101");
+                erpOrder.setExpress_code(express.getOmsCode());
+                order.setDeliveryName(express.getName());
+                order.setDeliveryCode(express.getCode());
+            } else {
+                //发ztpdd
+                FsExpress express = expressService.selectFsExpressByOmsCode("CDYJFYD.400011111705_241230");
+                erpOrder.setExpress_code(express.getOmsCode());
+                order.setDeliveryName(express.getName());
+                order.setDeliveryCode(express.getCode());
+            }
+
+            ErpRemarkDTO remarkDTO = new ErpRemarkDTO();
+            remarkDTO.setTotalPrice(order.getOrderMoney());
+            remarkDTO.setPayPrice(order.getPayMoney());
+            remarkDTO.setDeliveryMoney(BigDecimal.ZERO);
+            remarkDTO.setPayMoney(order.getPayMoney());
+            remarkDTO.setCouponMoney(BigDecimal.ZERO);
+            remarkDTO.setOrderId(order.getOrderSn());
+            remarkDTO.setYdMoney(BigDecimal.ZERO);
+            remarkDTO.setPayTime(order.getPayTime().format(formatter));
+
+            erpOrder.setSeller_memo(erpOrder.getSeller_memo() + JSONUtil.toJsonStr(remarkDTO));
+            erpOrder.setRemark(erpOrder.getRemark() + JSONUtil.toJsonStr(remarkDTO));
+            erpOrder.setOrder_type_code("order");
+            erpOrder.setDeal_datetime(LocalDateTime.now().format(formatter));
+
+            List<ErpOrderItem> details = new ArrayList<>();
+            FsRewardGoods fsRewardGoods = JSON.parseObject(order.getGoodsJson(), FsRewardGoods.class);
+            ErpOrderItem item = new ErpOrderItem();
+            item.setItem_code(fsRewardGoods.getStoreGoodsSn().trim());
+            item.setPrice(fsRewardGoods.getPrice().toString());
+            item.setQty(1);
+            item.setRefund(0);
+            details.add(item);
+
+            erpOrder.setDetails(details);
+            erpOrder.setReceiver_name(order.getUserName().replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9]", ""));
+
+            String userPhone = order.getMobile().length() > 11 ? decryptPhone(order.getMobile()) : order.getMobile();
+            erpOrder.setReceiver_mobile(userPhone);
+
+            String[] address = order.getAddress().split(" ");
+            if (address.length < 3) {
+                String kdnAddress = fsUserAddressService.getKdnAddress(order.getAddress());
+                Map<String, Object> addDAta = (Map<String, Object>) JSON.parse(kdnAddress);
+                Map<String, String> add = (Map<String, String>) addDAta.get("Data");
+                erpOrder.setReceiver_province(add.get("ProvinceName"));
+                erpOrder.setReceiver_city(add.get("CityName"));
+                erpOrder.setReceiver_district(add.get("ExpAreaName"));
+                erpOrder.setReceiver_address(add.get("StreetName") + add.get("Address"));
+            } else {
+                erpOrder.setReceiver_province(address[0]);
+                erpOrder.setReceiver_city(address[1]);
+                erpOrder.setReceiver_district(address[2]);
+                //处理地址多空隔问题
+                if (address.length > 3) {
+                    StringBuilder addrs = new StringBuilder();
+                    for (int i = 3; i < address.length; i++) {
+                        addrs.append(address[i]);
+                    }
+                    erpOrder.setReceiver_address(addrs.toString());
+                } else {
+                    erpOrder.setReceiver_address(address[2]);
+                }
+            }
+            erpOrder.setReceiver_address(erpOrder.getReceiver_address().replace("+", "加"));
+            erpOrder.setReceiver_address(erpOrder.getReceiver_address().replace("\n", ""));
+
+            baseMapper.updateById(order);
+
+            return erpOrder;
+        }).collect(Collectors.toList());
+
+        if (!erpOrders.isEmpty()) {
+//            jstErpOrderService.batchAddRewardOrder(erpOrders);
+        }
+    }
+
+    /**
+     * 同步erp信息
+     */
+    @Override
+    public void syncErpData() throws InterruptedException {
+        List<Long> extendOrderSnList = baseMapper.selectPushOrders();
+
+        if (!extendOrderSnList.isEmpty()) {
+            List<List<Long>> batches = Lists.partition(extendOrderSnList, 100);
+            for (List<Long> batch : batches) {
+                int pageIndex = 1;
+                boolean has_next = false;
+                do {
+                    Thread.sleep(300);
+                    ErpOrderQueryRequert request = new ErpOrderQueryRequert();
+                    request.setPage_size(100);
+//                    request.setPage_index(pageIndex);
+//                    request.setShop_id(selfShopCode);
+//                    request.setO_ids(batch); // 设置当前批次
+
+//                    ErpOrderQueryResponse response = jstErpOrderService.getOrder(request, "reward");
+//                    has_next = response.hasNextSafe();
+
+//                    if (response.getOrders() != null) {
+//                        for(ErpOrderQuery orderQuery : response.getOrders()){
+//                            if(orderQuery.getDeliverys()!=null&& !orderQuery.getDeliverys().isEmpty()){
+//                                for(ErpDeliverys delivery:orderQuery.getDeliverys()){
+//                                    if(delivery.getDelivery()&& StringUtils.isNotEmpty(delivery.getMail_no())){
+//                                        deliveryOrder(orderQuery.getCode(),delivery.getMail_no(),delivery.getExpress_code(),delivery.getExpress_name());
+//                                        redisCache.deleteObject("delivery"+":"+orderQuery.getO_id());
+//                                    }
+//                                }
+//
+//                            }
+//                        }
+//                    }
+                    pageIndex++;
+                } while (has_next);
+            }
+        }
+    }
+
+    /**
+     * 发货
+     */
+    private void deliveryOrder(String orderCode, String deliveryId, String deliverCode, String deliverName) {
+        FsRewardGoodsOrder order = lambdaQuery().eq(FsRewardGoodsOrder::getOrderSn, orderCode).one();
+        if (order == null || order.getStatus() != 2) {
+            return;
+        }
+
+        FsExpress express = expressService.selectFsExpressByOmsCode(deliverCode);
+        if (express != null) {
+            order.setDeliveryName(deliverName);
+            order.setDeliveryCode(express.getCode());
+        }
+        order.setStatus(3);
+        order.setDeliverySn(deliveryId);
+        order.setDeliveryTime(LocalDateTime.now());
+        baseMapper.updateById(order);
+
+        FsStore store = fsStoreMapper.selectFsStoreByStoreId(order.getStoreId());
+        //订阅物流回调
+        String lastFourNumber = "";
+        if (order.getDeliveryCode().equals(ShipperCodeEnum.SF.getValue())) {
+            if (store != null && store.getSendPhone() != null) {
+                lastFourNumber = store.getSendPhone();
+            } else {
+                lastFourNumber = order.getMobile().length() > 11 ?
+                        decryptPhone(order.getMobile()) :
+                        order.getMobile();
+            }
+            if (lastFourNumber.length() == 11) {
+                lastFourNumber = StrUtil.sub(lastFourNumber, lastFourNumber.length(), -4);
+            }
+        }
+
+        expressService.subscribeEspress(order.getOrderSn(), order.getDeliveryCode(), order.getDeliverySn(), lastFourNumber);
+
+        FsStorePayment params = new FsStorePayment();
+        params.setBusinessCode(order.getOrderSn());
+        params.setBusinessType(10);
+        List<FsStorePayment> fsStorePayments = storePaymentService.selectFsStorePaymentList(params);
+        if(CollectionUtils.isNotEmpty(fsStorePayments)){
+            FsStorePayment fsStorePayment = fsStorePayments.get(0);
+            FsWxExpressTask fsWxExpressTask = new FsWxExpressTask();
+            fsWxExpressTask.setUserId(order.getUserId());
+            fsWxExpressTask.setStatus(0);
+            fsWxExpressTask.setRetryCount(0);
+            fsWxExpressTask.setCreateTime(LocalDateTime.now());
+            fsWxExpressTask.setUpdateTime(LocalDateTime.now());
+            fsWxExpressTask.setOrderCode(order.getOrderSn());
+            fsWxExpressTask.setExpressCompany(Objects.nonNull(express) ? express.getCode() : "");
+            fsWxExpressTask.setExpressNo(deliveryId);
+            fsWxExpressTask.setAppid(fsStorePayment.getAppId());
+            fsWxExpressTaskMapper.insert(fsWxExpressTask);
+        }
+
+    }
+
+    /**
+     * 获取订单支付信息
+     */
+    @Override
+    public List<FsStorePayment> getPayInfo(String orderSn) {
+//        return storePaymentService.selectByBusinessTypeAndBusinessCode(10, orderSn);
+        return null;
+    }
+
+    /**
+     * 取消过期未支付订单
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cancelExpireRewardOrder() {
+        // 查询需要取消的订单
+        List<FsRewardGoodsOrder> expiredOrders = lambdaQuery()
+                .eq(FsRewardGoodsOrder::getStatus, 1)
+                .le(FsRewardGoodsOrder::getExpiredTime, LocalDateTime.now())
+                .list();
+        
+        if (CollectionUtils.isEmpty(expiredOrders)) {
+            return;
+        }
+        
+        // 批量更新订单状态
+        List<Long> orderIds = expiredOrders.stream()
+                .map(FsRewardGoodsOrder::getOrderId)
+                .collect(Collectors.toList());
+        
+        lambdaUpdate()
+                .set(FsRewardGoodsOrder::getStatus, -1)
+                .in(FsRewardGoodsOrder::getOrderId, orderIds)
+                .update();
+        
+        // 回滚库存
+        for (FsRewardGoodsOrder order : expiredOrders) {
+            fsRewardGoodsMapper.addStock(order.getGoodsId());
+        }
+        
+        log.info("取消过期奖励商品订单数量: {}, 回滚库存完成", orderIds.size());
+    }
+
+    /**
+     * 完成订单
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void completeOrder() {
+        // 自动完成发货时间超过7天且处于已发货状态的订单
+        LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7);
+        
+        // 查询需要完成的订单:状态为3(已发货)且发货时间超过7天
+        List<FsRewardGoodsOrder> ordersToComplete = lambdaQuery()
+                .eq(FsRewardGoodsOrder::getStatus, 3)
+                .le(FsRewardGoodsOrder::getDeliveryTime, sevenDaysAgo)
+                .list();
+        
+        if (CollectionUtils.isEmpty(ordersToComplete)) {
+            log.info("没有需要自动完成的订单");
+            return;
+        }
+        
+        // 批量更新订单状态为已完成
+        List<Long> orderIds = ordersToComplete.stream()
+                .map(FsRewardGoodsOrder::getOrderId)
+                .collect(Collectors.toList());
+        
+        lambdaUpdate()
+                .set(FsRewardGoodsOrder::getStatus, 4)
+                .set(FsRewardGoodsOrder::getFinishTime, LocalDateTime.now())
+                .set(FsRewardGoodsOrder::getUpdateTime, LocalDateTime.now())
+                .set(FsRewardGoodsOrder::getUpdateBy, "系统自动完成")
+                .in(FsRewardGoodsOrder::getOrderId, orderIds)
+                .update();
+        
+        log.info("自动完成奖励商品订单数量: {}", orderIds.size());
+    }
+
+    /**
+     * 取消订单
+     */
+    @Override
+    public int cancelOrder(Long orderId) {
+        // 查询订单信息
+        FsRewardGoodsOrder order = baseMapper.selectById(orderId);
+        if (order == null) {
+            throw new CustomException("订单不存在");
+        }
+        
+        // 订单状态:1待支付 2已支付 3已发货 4已完成 -1已取消
+        Integer status = order.getStatus();
+        
+        // 已取消状态,直接返回成功
+        if (status == -1) {
+            return 1;
+        }
+        
+        // 已完成状态,提示不可取消
+        if (status == 4) {
+            throw new CustomException("订单已完成,不可取消");
+        }
+        
+        // 待支付状态,直接修改订单状态
+        if (status == 1) {
+            return lambdaUpdate()
+                    .set(FsRewardGoodsOrder::getStatus, -1)
+                    .set(FsRewardGoodsOrder::getCancelTime, LocalDateTime.now())
+                    .set(FsRewardGoodsOrder::getUpdateTime, LocalDateTime.now())
+                    .set(FsRewardGoodsOrder::getUpdateBy, "系统取消")
+                    .eq(FsRewardGoodsOrder::getOrderId, orderId)
+                    .update() ? 1 : 0;
+        }
+        
+        // 已支付、已发货状态,先进行聚水潭订单取消,取消成功后再修改订单状态并退款
+        if (status == 2 || status == 3) {
+            return processShippedOrderCancellation(order);
+        }
+        
+        throw new CustomException("订单状态异常,无法取消");
+    }
+
+    /**
+     * 处理已发货订单的取消
+     */
+    private int processShippedOrderCancellation(FsRewardGoodsOrder order) {
+        try {
+            // 先取消聚水潭订单
+            if (StringUtils.isNotEmpty(order.getExtendOrderSn())) {
+                cancelJstOrder(order);
+            }
+            
+            // 执行退款
+            processRefund(order);
+            
+            // 修改订单状态
+            return lambdaUpdate()
+                    .set(FsRewardGoodsOrder::getStatus, -1)
+                    .set(FsRewardGoodsOrder::getCancelTime, LocalDateTime.now())
+                    .set(FsRewardGoodsOrder::getUpdateTime, LocalDateTime.now())
+                    .set(FsRewardGoodsOrder::getUpdateBy, "系统取消")
+                    .eq(FsRewardGoodsOrder::getOrderId, order.getOrderId())
+                    .update() ? 1 : 0;
+                    
+        } catch (Exception e) {
+            log.error("已发货订单取消失败,订单号:{}", order.getOrderSn(), e);
+            throw new CustomException("订单取消失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 取消聚水潭订单
+     */
+    private void cancelJstOrder(FsRewardGoodsOrder order) {
+        try {
+//            ErpRefundUpdateRequest requestDTO = new ErpRefundUpdateRequest();
+//            requestDTO.setTid(order.getOrderSn());
+//            requestDTO.setOid(order.getExtendOrderSn());
+//            BaseResponse response = jstErpOrderService.cancelRewardOrder(requestDTO);
+//
+//            if (response == null || !response.getSuccess()) {
+//                throw new CustomException("聚水潭订单取消失败");
+//            }
+            
+            log.info("聚水潭订单取消成功,订单号:{},外部订单号:{}", order.getOrderSn(), order.getExtendOrderSn());
+            
+        } catch (CustomException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("聚水潭订单取消失败,订单号:{}", order.getOrderSn(), e);
+            throw new CustomException("聚水潭订单取消失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 处理退款
+     */
+    private void processRefund(FsRewardGoodsOrder order) {
+        if (order.getPayMoney() == null || order.getPayMoney().compareTo(BigDecimal.ZERO) <= 0) {
+            return; // 无需退款
+        }
+        
+        String orderType = "reward";
+        
+        try {
+            // 查询支付记录
+            FsStorePayment params = new FsStorePayment();
+            params.setBusinessType(10);
+            params.setBusinessCode(order.getOrderSn());
+            params.setStatus(1);
+            List<FsStorePayment> payments = storePaymentService.selectFsStorePaymentList(params);
+            
+            if (payments != null && !payments.isEmpty()) {
+                FsStorePayment payment = payments.get(0);
+                
+                // 根据支付方式处理退款
+                switch (payment.getPayMode()) {
+                    case PaymentTypeConstant.WX:
+                        processWxRefund(payment, orderType);
+                        break;
+                    case PaymentTypeConstant.YB:
+                        processYbRefund(payment, orderType);
+                        break;
+                    case PaymentTypeConstant.TZ_BANK:
+                        processTzBankRefund(payment, orderType);
+                        break;
+                    case PaymentTypeConstant.HF:
+                        processHfRefund(payment, orderType);
+                        break;
+                    default:
+                        throw new CustomException("不支持的支付方式:" + payment.getPayMode());
+                }
+            } else {
+                throw new CustomException("未找到支付明细");
+            }
+            
+            log.info("退款处理成功,订单号:{},退款金额:{}", order.getOrderSn(), order.getPayMoney());
+                    
+        } catch (Exception e) {
+            log.error("退款处理失败,订单号:{}", order.getOrderSn(), e);
+            throw new CustomException("退款处理失败:" + e.getMessage());
+        }
+    }
+    
+    /**
+     * 微信支付退款
+     */
+    private void processWxRefund(FsStorePayment payment, String orderType) {
+//        PaymentMiniProgramConfig paymentConfig = paymentConfigService.selectPaymentConfigByAppId(payment.getAppid());
+//        if (Objects.isNull(paymentConfig)) {
+//            log.error("当前小程序 {} 没有配置可用的支付方式!", payment.getAppid());
+//            throw new CustomException("当前小程序没有配置可用的支付方式!");
+//        }
+
+        WxPayConfig payConfig = new WxPayConfig();
+//        payConfig.setAppId(paymentConfig.getAppid());
+//        payConfig.setMchId(paymentConfig.getWxMerchantNo());
+//        payConfig.setMchKey(paymentConfig.getWxKey());
+//        payConfig.setKeyPath(paymentConfig.getWxKeyPath());
+        payConfig.setSubAppId(StringUtils.trimToNull(null));
+        payConfig.setSubMchId(StringUtils.trimToNull(null));
+        wxPayService.setConfig(payConfig);
+        
+        WxPayRefundRequest refundRequest = new WxPayRefundRequest();
+        refundRequest.setOutTradeNo(orderType + "-" + payment.getPayCode());
+        refundRequest.setOutRefundNo(orderType + "-" + payment.getPayCode());
+        refundRequest.setTotalFee(WxPayUnifiedOrderRequest.yuanToFen(payment.getPayMoney().toString()));
+        refundRequest.setRefundFee(WxPayUnifiedOrderRequest.yuanToFen(payment.getPayMoney().toString()));
+        
+        try {
+            WxPayRefundResult refundResult = wxPayService.refund(refundRequest);
+            WxPayRefundQueryResult refundQueryResult = wxPayService.refundQuery("", refundResult.getOutTradeNo(), refundResult.getOutRefundNo(), refundResult.getRefundId());
+            if (refundQueryResult != null && refundQueryResult.getResultCode().equals("SUCCESS")) {
+                // 更新支付记录状态
+                updatePaymentRefundStatus(payment);
+            } else {
+                String msg = Objects.nonNull(refundQueryResult) ? refundQueryResult.getReturnMsg() : "";
+                throw new CustomException("退款请求失败" + msg);
+            }
+        } catch (WxPayException e) {
+            throw new CustomException("退款请求失败" + e.getReturnMsg());
+        }
+    }
+    
+    /**
+     * 易宝支付退款
+     */
+    private void processYbRefund(FsStorePayment payment, String orderType) {
+        RefundDTO refundDTO = new RefundDTO();
+        refundDTO.setRefundMoney(payment.getPayMoney().toString());
+        refundDTO.setLowRefundNo(orderType + "-" + payment.getPayCode());
+        refundDTO.setUpOrderId(payment.getTradeNo());
+//        refundDTO.setAppid(payment.getAppid());
+
+        RefundResult result = payService.refund(refundDTO);
+        log.info("订单退款返回结果: {}", result);
+        if (result.getState().equals("5")) {
+            // 更新支付记录状态
+            updatePaymentRefundStatus(payment);
+        } else {
+            throw new CustomException("退款请求失败" + result.getMessage());
+        }
+    }
+    
+    /**
+     * 台州银行退款
+     */
+    private void processTzBankRefund(FsStorePayment payment, String orderType) {
+        RefundParam tzBankResult = new RefundParam();
+        // 商户原支付订单号
+        tzBankResult.setOldPayOutOrderNo(orderType + payment.getPayCode());
+        // 商户退款订单号 对接平台系统里自己生成的退款订单号
+        tzBankResult.setRefundOrderNo(orderType + payment.getPayCode());
+        // 交易发送时间 yyyyMMddHHmmss
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+        tzBankResult.setTrxSendTime(sdf.format(new Date()));
+        // 退款金额
+        tzBankResult.setRefundOrdTransAmt(payment.getPayMoney().doubleValue());
+//        tzBankResult.setAppid(payment.getAppid());
+
+        TzBankResult<com.fs.tzBankPay.doman.RefundResult> result = tzBankService.refund(tzBankResult);
+        log.info("订单退款返回结果: {}", result);
+        if (result.getBody().getRefundOrdStatus().equals("90") || result.getBody().getRefundOrdStatus().equals("60")) {
+            // 更新支付记录状态
+            updatePaymentRefundStatus(payment);
+        } else {
+            log.error("退款请求失败: {}", result.getRetMsg());
+            throw new CustomException("退款请求失败" + result.getRetMsg());
+        }
+    }
+    
+    /**
+     * 汇付支付退款
+     */
+    private void processHfRefund(FsStorePayment payment, String orderType) {
+        V2TradePaymentScanpayRefundRequest request = new V2TradePaymentScanpayRefundRequest();
+        request.setOrdAmt(payment.getPayMoney().toString());
+        request.setOrgReqDate(new SimpleDateFormat("yyyyMMdd").format(payment.getCreateTime()));
+        request.setReqSeqId("refund-" + payment.getPayCode());
+        Map<String, Object> extendInfoMap = new HashMap<>();
+        extendInfoMap.put("org_req_seq_id", orderType + "-" + payment.getPayCode());
+        request.setExtendInfo(extendInfoMap);
+        request.setAppId(payment.getAppId());
+        
+        HuiFuRefundResult refund = huiFuService.refund(request);
+        log.info("订单退款返回结果: {}" +  refund);
+        if ((refund.getResp_code().equals("00000000") || refund.getResp_code().equals("00000100")) && 
+            (refund.getTrans_stat().equals("S") || refund.getTrans_stat().equals("P"))) {
+            // 更新支付记录状态
+            updatePaymentRefundStatus(payment);
+        } else {
+            throw new CustomException("退款请求失败" + refund.getResp_desc());
+        }
+    }
+    
+    /**
+     * 更新支付记录退款状态
+     */
+    private void updatePaymentRefundStatus(FsStorePayment payment) {
+        FsStorePayment paymentMap = new FsStorePayment();
+        paymentMap.setPaymentId(payment.getPaymentId());
+        paymentMap.setStatus(-1);
+        paymentMap.setRefundTime(DateUtils.getNowDate());
+        paymentMap.setRefundMoney(payment.getPayMoney());
+        storePaymentService.updateFsStorePayment(paymentMap);
+    }
+}

+ 105 - 0
fs-service/src/main/java/com/fs/reward/service/impl/FsRewardGoodsServiceImpl.java

@@ -0,0 +1,105 @@
+package com.fs.reward.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.bean.BeanUtils;
+import com.fs.reward.domain.FsRewardGoods;
+import com.fs.reward.mapper.FsRewardGoodsMapper;
+import com.fs.reward.param.FsRewardGoodsAddParam;
+import com.fs.reward.param.FsRewardGoodsEditParam;
+import com.fs.reward.param.FsRewardGoodsListParam;
+import com.fs.reward.service.IFsRewardGoodsService;
+import com.fs.reward.vo.FsRewardGoodsVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+@Slf4j
+@Service
+public class FsRewardGoodsServiceImpl extends ServiceImpl<FsRewardGoodsMapper, FsRewardGoods> implements IFsRewardGoodsService {
+
+    /**
+     * 查询奖励商品列表
+     */
+    @Override
+    public List<FsRewardGoodsVO> selectFsRewardGoodsVOList(FsRewardGoodsListParam param) {
+        return baseMapper.selectFsRewardGoodsVOList(param);
+    }
+
+    /**
+     * 查询奖励商品详情
+     */
+    @Override
+    public FsRewardGoodsVO selectFsRewardGoodsVOById(Long goodsId) {
+        return baseMapper.selectFsRewardGoodsVOById(goodsId);
+    }
+
+    /**
+     * 添加奖励商品
+     */
+    @Override
+    public int insertFsRewardGoods(FsRewardGoodsAddParam param) {
+        FsRewardGoods goods = new FsRewardGoods();
+        BeanUtils.copyProperties(param, goods, "goodsId");
+
+        if (isNameUsedByOthers(null, param.getGoodsName())) {
+            throw new CustomException("商品名称已存在");
+        }
+
+        goods.setIsDel(0);
+        goods.setCreateTime(LocalDateTime.now());
+        return baseMapper.insert(goods);
+    }
+
+    /**
+     * 商品名称是否已存在
+     */
+    private boolean isNameUsedByOthers(Long goodsId, String goodsName) {
+        FsRewardGoods rewardGoods = lambdaQuery()
+                .eq(FsRewardGoods::getGoodsName, goodsName)
+                .eq(FsRewardGoods::getIsDel, 0)
+                .last("limit 1")
+                .one();
+
+        if (rewardGoods == null) {
+            return false;
+        }
+
+        return !Objects.equals(rewardGoods.getGoodsId(), goodsId);
+    }
+
+    /**
+     * 修改奖励商品
+     */
+    @Override
+    public int updateFsRewardGoods(FsRewardGoodsEditParam param) {
+        FsRewardGoods goods = getById(param.getGoodsId());
+        if (goods == null) {
+            throw new CustomException("商品不存在");
+        }
+
+        if (isNameUsedByOthers(param.getGoodsId(), param.getGoodsName())) {
+            throw new CustomException("商品名称已存在");
+        }
+
+        BeanUtils.copyProperties(param, goods, "goodsId", "createBy");
+        return baseMapper.updateById(goods);
+    }
+
+    /**
+     * 逻辑删除商品
+     */
+    @Override
+    public int logicDeleteFsRewardGoods(Long[] goodsIds) {
+        lambdaUpdate()
+            .set(FsRewardGoods::getIsDel, 1)
+            .set(FsRewardGoods::getUpdateTime, LocalDateTime.now())
+            .in(FsRewardGoods::getGoodsId, Arrays.asList(goodsIds))
+            .update();
+        return 1;
+    }
+}

+ 168 - 0
fs-service/src/main/java/com/fs/reward/vo/FsRewardGoodsOrderVO.java

@@ -0,0 +1,168 @@
+package com.fs.reward.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+public class FsRewardGoodsOrderVO {
+
+    /**
+     * 主键ID
+     */
+    private Long orderId;
+
+    /**
+     * 订单号
+     */
+    private String orderSn;
+
+    /**
+     * 外部订单号
+     */
+    private String extendOrderSn;
+
+    /**
+     * 店铺ID
+     */
+    private Long storeId;
+
+    /**
+     * 店铺名称
+     */
+    private String storeName;
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 用户名称
+     */
+    private String nickName;
+
+    /**
+     * 收货人
+     */
+    private String userName;
+
+    /**
+     * 收货手机号
+     */
+    private String mobile;
+
+    /**
+     * 收货地址
+     */
+    private String address;
+
+    /**
+     * 订单金额
+     */
+    private BigDecimal orderMoney;
+
+    /**
+     * 支付金额
+     */
+    private BigDecimal payMoney;
+
+    /**
+     * 支付类型
+     */
+    private Integer payType;
+
+    /**
+     * 支付时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime payTime;
+
+    /**
+     * 快递公司编号
+     */
+    private String deliveryCode;
+
+    /**
+     * 快递名称
+     */
+    private String deliveryName;
+
+    /**
+     * 快递单号
+     */
+    private String deliverySn;
+
+    /**
+     * 中奖记录ID
+     */
+    private Long roundId;
+
+    /**
+     * 商品ID
+     */
+    private Long goodsId;
+
+    /**
+     * 商品信息
+     */
+    private String goodsJson;
+
+    /**
+     * 状态 1待支付 2已支付 3已发货 4已完成 -1已取消
+     */
+    private Integer status;
+
+    /**
+     * 发货时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime deliveryTime;
+
+    /**
+     * 完成时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime finishTime;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    /**
+     * 取消时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime cancelTime;
+
+    /**
+     * 过期时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime expiredTime;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 修改时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime updateTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+
+    /**
+     * 备注
+     */
+    private String remark;
+}

+ 113 - 0
fs-service/src/main/java/com/fs/reward/vo/FsRewardGoodsVO.java

@@ -0,0 +1,113 @@
+package com.fs.reward.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+public class FsRewardGoodsVO {
+
+    /**
+     * 主键ID
+     */
+    private Long goodsId;
+
+    /**
+     * 商品名称
+     */
+    private String goodsName;
+
+    /**
+     * 封面
+     */
+    private String goodsImg;
+
+    /**
+     * 轮播图
+     */
+    private String goodsImages;
+
+    /**
+     * 商品介绍
+     */
+    private String goodsIntroduce;
+
+    /**
+     * 商品描述
+     */
+    private String goodsDesc;
+
+    /**
+     * 店铺ID
+     */
+    private Long storeId;
+
+    /**
+     * 店铺名称
+     */
+    private String storeName;
+
+    /**
+     * 店铺商品编码
+     */
+    private String storeGoodsSn;
+
+    /**
+     * 状态 1上架 0下架
+     */
+    private Integer status;
+
+    /**
+     * 单价
+     */
+    private BigDecimal price;
+
+    /**
+     * 原价
+     */
+    private BigDecimal opPrice;
+
+    /**
+     * 库存
+     */
+    private Integer stock;
+
+    /**
+     * 序号
+     */
+    private Integer sort;
+
+    /**
+     * 0未删除 1已删除
+     */
+    private Integer isDel;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    /**
+     * 创建人
+     */
+    private String createBy;
+
+    /**
+     * 修改时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime updateTime;
+
+    /**
+     * 修改人
+     */
+    private String updateBy;
+}

+ 36 - 0
fs-service/src/main/java/com/fs/reward/vo/FsRewardListVO.java

@@ -0,0 +1,36 @@
+package com.fs.reward.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@ApiModel("中奖列表VO")
+@Data
+public class FsRewardListVO {
+
+    @ApiModelProperty("中奖记录ID")
+    private Long roundId;
+
+    @ApiModelProperty("中奖码")
+    private String code;
+
+    @ApiModelProperty("中奖名称")
+    private String codeName;
+
+    @ApiModelProperty("中奖时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty("中奖内容")
+    private String content;
+
+    @ApiModelProperty("商品ID")
+    private Long goodsId;
+
+    @ApiModelProperty("订单ID")
+    private Long orderId;
+
+}

+ 1 - 0
fs-service/src/main/java/com/fs/tzBankPay/TzBankService/TzBankService.java

@@ -18,4 +18,5 @@ public interface TzBankService {
     /**分账查询**/
     TzBankResult<orderShareQueryResult> orderShareQuery(orderShareQueryParam orderShareParam);
 
+    TzBankResult<PayCreateOrderResult> createAppOrder(PayCreateOrder o);
 }

+ 30 - 0
fs-service/src/main/java/com/fs/tzBankPay/TzBankService/TzBankServiceImpl/TzBankServiceImpl.java

@@ -132,4 +132,34 @@ public class TzBankServiceImpl implements TzBankService {
         TzBankResult<orderShareQueryResult> messageInfo = new Gson().fromJson(send, new TypeToken<TzBankResult<orderShareQueryResult>>() {}.getType());
         return messageInfo;
     }
+
+    @Override
+    public TzBankResult<PayCreateOrderResult> createAppOrder(PayCreateOrder order) {
+        String platMerCstNo,notifyUrl,appid;
+        SysConfigMapper sysConfigMapper= SpringUtils.getBean(SysConfigMapper.class);
+        SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.pay");
+        FsPayConfig fsPayConfig = new Gson().fromJson(sysConfig.getConfigValue(), FsPayConfig.class);
+        platMerCstNo = fsPayConfig.getTzPlatMerCstNo();
+        notifyUrl = fsPayConfig.getTzPayDecrypt();
+        order.setOrderSource("01"); // 场景号
+        order.setCurrency("CNY"); // 币种
+        order.setTransType("2001"); // 交易类型
+        order.setTradeMerCstNo(platMerCstNo); // 交易商户号
+        order.setPlatMerCstNo(platMerCstNo); // 平台商户号
+        order.setNeedLedger("00"); // 是否需要分账
+        // 获取当前时间戳
+        Instant currentTimestamp = Instant.now();
+        // 增加一个小时
+        Instant oneHourLater = currentTimestamp.plusSeconds(3600);
+        order.setExpiredTime(oneHourLater.toEpochMilli()+""); // 订单过期时间
+        List<String> payType = Arrays.asList(PayType.APP_WX_PAYMENT.getCode());
+        if(order.getPayType()==null){
+            order.setPayType(payType);
+        }
+        order.setPayNotifyUrl(notifyUrl);
+        order.setPayReturnUrl(TzBankConfig.payReturnUrl);
+        String send = tzBankSendUtil.send(order,TzBankConfig.payCreateOrder);
+        TzBankResult<PayCreateOrderResult> messageInfo = new Gson().fromJson(send, new TypeToken<TzBankResult<PayCreateOrderResult>>() {}.getType());
+        return messageInfo;
+    }
 }

+ 1 - 0
fs-service/src/main/java/com/fs/tzBankPay/doman/PayCreateOrder.java

@@ -34,4 +34,5 @@ public class PayCreateOrder {
     private String openId;
     private String orderId;
     private Integer orderType;//1问诊 2药品 3套餐包 4课程订单 5开通会员 6积分商城
+    private String appid;
 }

+ 4 - 1
fs-service/src/main/java/com/fs/tzBankPay/doman/PayType.java

@@ -21,7 +21,10 @@ public enum PayType {
     SUPPLY_CHAIN_CREDIT_PAYMENT("51"),
     UNIONPAY_PAYMENT("53"),
     UNIONPAY_CONTRACT_PAYMENT("55"),
-    PAYMENT_ON_BEHALF("99");
+    PAYMENT_ON_BEHALF("99"),
+    APP_WX_PAYMENT("11"), //APP微信支付
+    APPLE_PAYMENT("29")//苹果内部支付
+    ;
 
     private final String code;
 

+ 19 - 0
fs-service/src/main/java/com/fs/utils/Range.java

@@ -0,0 +1,19 @@
+package com.fs.utils;
+
+public class Range<T extends Comparable<T>> {
+    private final T min;
+    private final T max;
+
+    public Range(T min, T max) {
+        this.min = min;
+        this.max = max;
+    }
+
+    public boolean contains(T value) {
+        return value.compareTo(min) >= 0 && value.compareTo(max) <= 0;
+    }
+
+    public static <T extends Comparable<T>> Range<T> between(T min, T max) {
+        return new Range<>(min, max);
+    }
+}

+ 121 - 0
fs-service/src/main/resources/mapper/course/FsCourseRedPacketRetryMapper.xml

@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.course.mapper.FsCourseRedPacketRetryMapper">
+
+    <resultMap type="FsCourseRedPacketRetry" id="FsCourseRedPacketRetryResult">
+        <result property="logId"    column="log_id"    />
+        <result property="outBatchNo"    column="out_batch_no"    />
+        <result property="courseId"    column="course_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="videoId"    column="video_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="amount"    column="amount"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="status"    column="status"    />
+        <result property="qwUserId"    column="qw_user_id"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="watchLogId"    column="watch_log_id"    />
+        <result property="remark"    column="remark"    />
+        <result property="sendOpenId"    column="send_open_id"    />
+        <result property="source"    column="source"    />
+    </resultMap>
+
+    <sql id="selectFsCourseRedPacketRetryVo">
+        select log_id, out_batch_no, course_id,source,send_open_id, user_id, video_id, company_user_id, company_id, amount, create_time, status, qw_user_id, update_time, watch_log_id, remark from fs_course_red_packet_retry
+    </sql>
+
+    <select id="selectFsCourseRedPacketRetryList" parameterType="FsCourseRedPacketRetry" resultMap="FsCourseRedPacketRetryResult">
+        <include refid="selectFsCourseRedPacketRetryVo"/>
+        <where>
+            <if test="outBatchNo != null  and outBatchNo != ''"> and out_batch_no = #{outBatchNo}</if>
+            <if test="courseId != null "> and course_id = #{courseId}</if>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="videoId != null "> and video_id = #{videoId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="amount != null "> and amount = #{amount}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="qwUserId != null "> and qw_user_id = #{qwUserId}</if>
+            <if test="watchLogId != null "> and watch_log_id = #{watchLogId}</if>
+        </where>
+    </select>
+
+    <select id="selectFsCourseRedPacketRetryByLogId" parameterType="Long" resultMap="FsCourseRedPacketRetryResult">
+        <include refid="selectFsCourseRedPacketRetryVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <insert id="insertFsCourseRedPacketRetry" parameterType="FsCourseRedPacketRetry" useGeneratedKeys="true" keyProperty="logId">
+        insert into fs_course_red_packet_retry
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="outBatchNo != null">out_batch_no,</if>
+            <if test="courseId != null">course_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="amount != null">amount,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="status != null">status,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="watchLogId != null">watch_log_id,</if>
+            <if test="remark != null">remark,</if>
+            <if test="sendOpenId != null">send_open_id,</if>
+            <if test="source != null">source,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="outBatchNo != null">#{outBatchNo},</if>
+            <if test="courseId != null">#{courseId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="amount != null">#{amount},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="status != null">#{status},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="watchLogId != null">#{watchLogId},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="sendOpenId != null">#{sendOpenId},</if>
+            <if test="source != null">#{source},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsCourseRedPacketRetry" parameterType="FsCourseRedPacketRetry">
+        update fs_course_red_packet_retry
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="outBatchNo != null">out_batch_no = #{outBatchNo},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="amount != null">amount = #{amount},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="qwUserId != null">qw_user_id = #{qwUserId},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="watchLogId != null">watch_log_id = #{watchLogId},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="sendOpenId != null">send_open_id = #{sendOpenId},</if>
+            <if test="source != null">source = #{source},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <delete id="deleteFsCourseRedPacketRetryByLogId" parameterType="Long">
+        delete from fs_course_red_packet_retry where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteFsCourseRedPacketRetryByLogIds" parameterType="String">
+        delete from fs_course_red_packet_retry where log_id in
+        <foreach item="logId" collection="array" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+</mapper>

+ 116 - 0
fs-service/src/main/resources/mapper/course/RewardMapper.xml

@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.course.mapper.FsCourseRewardMapper">
+
+    <resultMap type="FsCourseReward" id="FsCourseRewardResult">
+        <result property="id"    column="id"    />
+        <result property="name"    column="name"    />
+        <result property="description"    column="description"    />
+        <result property="rewardType"    column="reward_type"    />
+        <result property="status"    column="status"    />
+        <result property="expectedValue"    column="expected_value"    />
+        <result property="createId"    column="create_id"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="actualRewards"    column="actual_rewards"    />
+        <result property="closeChestUrl"    column="close_chest_url"    />
+        <result property="openChestUrl"    column="open_chest_url"    />
+    </resultMap>
+
+    <sql id="selectFsCourseRewardVo">
+        select id, name, description,open_chest_url,close_chest_url, reward_type, status, expected_value, create_id, create_time, update_time, actual_rewards from fs_course_reward
+    </sql>
+
+    <select id="selectFsCourseRewardList" parameterType="FsCourseReward" resultMap="FsCourseRewardResult">
+        <include refid="selectFsCourseRewardVo"/>
+        <where>
+            <if test="name != null  and name != ''"> and name like concat('%', #{name}, '%')</if>
+            <if test="description != null  and description != ''"> and description = #{description}</if>
+            <if test="rewardType != null "> and reward_type = #{rewardType}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="expectedValue != null  and expectedValue != ''"> and expected_value = #{expectedValue}</if>
+            <if test="createId != null "> and create_id = #{createId}</if>
+            <if test="rewardIds != null">
+               and id in
+                <foreach item="item" collection="rewardIds" open="(" separator="," close=")">
+                    #{item}
+                </foreach>
+            </if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectFsCourseRewardById" parameterType="Long" resultMap="FsCourseRewardResult">
+        <include refid="selectFsCourseRewardVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFsCourseReward" parameterType="FsCourseReward" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_course_reward
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="name != null and name != ''">name,</if>
+            <if test="description != null">description,</if>
+            <if test="rewardType != null">reward_type,</if>
+            <if test="status != null">status,</if>
+            <if test="expectedValue != null and expectedValue != ''">expected_value,</if>
+            <if test="createId != null">create_id,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="actualRewards != null and actualRewards != ''">actual_rewards,</if>
+            <if test="closeChestUrl != null and closeChestUrl != ''">close_chest_url,</if>
+            <if test="openChestUrl != null and openChestUrl != ''">open_chest_url,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="name != null and name != ''">#{name},</if>
+            <if test="description != null">#{description},</if>
+            <if test="rewardType != null">#{rewardType},</if>
+            <if test="status != null">#{status},</if>
+            <if test="expectedValue != null and expectedValue != ''">#{expectedValue},</if>
+            <if test="createId != null">#{createId},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="actualRewards != null and actualRewards != ''">#{actualRewards},</if>
+            <if test="closeChestUrl != null and closeChestUrl != ''">#{closeChestUrl},</if>
+            <if test="openChestUrl != null and openChestUrl != ''">#{openChestUrl},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsCourseReward" parameterType="FsCourseReward">
+        update fs_course_reward
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="name != null and name != ''">name = #{name},</if>
+            <if test="description != null">description = #{description},</if>
+            <if test="rewardType != null">reward_type = #{rewardType},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="expectedValue != null and expectedValue != ''">expected_value = #{expectedValue},</if>
+            <if test="createId != null">create_id = #{createId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="actualRewards != null and actualRewards != ''">actual_rewards = #{actualRewards},</if>
+            <if test="closeChestUrl != null and closeChestUrl != ''">close_chest_url = #{closeChestUrl},</if>
+            <if test="openChestUrl != null and openChestUrl != ''">open_chest_url = #{openChestUrl},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsCourseRewardById" parameterType="Long">
+        delete from fs_course_reward where id = #{id}
+    </delete>
+
+    <delete id="deleteFsCourseRewardByIds" parameterType="String">
+        delete from fs_course_reward where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="selectFsCourseRewardByIds" resultType="com.fs.course.domain.FsCourseReward">
+        <include refid="selectFsCourseRewardVo"/>
+        where id in
+        <foreach item="id" collection="ids" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </select>
+</mapper>

+ 173 - 0
fs-service/src/main/resources/mapper/course/RewardRoundMapper.xml

@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.course.mapper.FsCourseRewardRoundMapper">
+
+    <resultMap type="FsCourseRewardRound" id="FsCourseRewardRoundResult">
+        <result property="id"    column="id"    />
+        <result property="rewardId"    column="reward_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="rewardType"    column="reward_type"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="actualRewards"    column="actual_rewards"    />
+        <result property="createId"    column="create_id"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="status"    column="status"    />
+        <result property="rewardVideoRelationId"    column="reward_video_relation_id"    />
+        <result property="watchId"    column="watch_id"    />
+        <result property="ruleId"    column="rule_id"    />
+        <result property="ruleName"    column="rule_name"    />
+        <result property="goodsId"    column="goods_id"    />
+        <result property="goodsPrice"    column="goods_price"    />
+    </resultMap>
+
+    <sql id="selectFsCourseRewardRoundVo">
+        select id, reward_id, user_id, reward_type, company_id, actual_rewards, create_id, create_time, update_time,
+               status,  watch_id, rule_id, reward_video_relation_id, goods_id, goods_price, rule_name
+        from fs_course_reward_round
+    </sql>
+
+    <select id="selectFsCourseRewardRoundList" parameterType="FsCourseRewardRound" resultMap="FsCourseRewardRoundResult">
+        <include refid="selectFsCourseRewardRoundVo"/>
+        <where>
+            status &lt;&gt; 0
+            <if test="rewardId != null "> and reward_id = #{rewardId}</if>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="rewardType != null "> and reward_type = #{rewardType}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="actualRewards != null  and actualRewards != ''"> and actual_rewards = #{actualRewards}</if>
+            <if test="createId != null "> and create_id = #{createId}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="rewardVideoRelationId != null "> and reward_video_relation_id = #{rewardVideoRelationId}</if>
+            <if test="watchId != null "> and watch_id = #{watchId}</if>
+            <if test="ruleId != null "> and rule_id = #{ruleId}</if>
+            <if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
+                and date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
+            </if>
+            <if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
+                and date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
+            </if>
+        </where>
+        order by id desc
+    </select>
+
+    <select id="selectFsCourseRewardRoundById" parameterType="Long" resultMap="FsCourseRewardRoundResult">
+        <include refid="selectFsCourseRewardRoundVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFsCourseRewardRound" parameterType="FsCourseRewardRound" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_course_reward_round
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="rewardId != null">reward_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="rewardType != null">reward_type,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="actualRewards != null and actualRewards != ''">actual_rewards,</if>
+            <if test="createId != null">create_id,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="status != null">status,</if>
+            <if test="rewardVideoRelationId != null">reward_video_relation_id,</if>
+            <if test="watchId != null">watch_id,</if>
+            <if test="ruleId != null">rule_id,</if>
+            <if test="goodsId != null">goods_id,</if>
+            <if test="goodsPrice != null">goods_price,</if>
+            <if test="ruleName != null">rule_name,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="rewardId != null">#{rewardId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="rewardType != null">#{rewardType},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="actualRewards != null and actualRewards != ''">#{actualRewards},</if>
+            <if test="createId != null">#{createId},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="status != null">#{status},</if>
+            <if test="rewardVideoRelationId != null">#{rewardVideoRelationId},</if>
+            <if test="watchId != null">#{watchId},</if>
+            <if test="ruleId != null">#{ruleId},</if>
+            <if test="goodsId != null">#{goodsId},</if>
+            <if test="goodsPrice != null">#{goodsPrice},</if>
+            <if test="ruleName != null">#{ruleName},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsCourseRewardRound" parameterType="FsCourseRewardRound">
+        update fs_course_reward_round
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="rewardId != null">reward_id = #{rewardId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="rewardType != null">reward_type = #{rewardType},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="actualRewards != null and actualRewards != ''">actual_rewards = #{actualRewards},</if>
+            <if test="createId != null">create_id = #{createId},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="rewardVideoRelationId != null">reward_video_relation_id = #{rewardVideoRelationId},</if>
+            <if test="watchId != null">watch_id = #{watchId},</if>
+            <if test="ruleId != null">rule_id = #{ruleId},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsCourseRewardRoundById" parameterType="Long">
+        delete from fs_course_reward_round where id = #{id}
+    </delete>
+
+    <delete id="deleteFsCourseRewardRoundByIds" parameterType="String">
+        delete from fs_course_reward_round where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+
+    <select id="selectFsCourseRewardRoundByAmount" resultType="com.fs.company.vo.RedPacketMoneyVO">
+        select company_id ,sum(actual_rewards)/1000 as money from fs_course_reward_round
+        <where>
+            status =1 and reward_type=1
+            <if test="start != null "> and create_time &gt;= #{start} </if>
+            <if test="end != null "> and create_time &lt;= #{end}</if>
+        </where>
+        group by company_id
+    </select>
+
+
+    <select id="selectFsCourseRewardRoundByAmountForLuckyBag" resultType="com.fs.company.vo.RedPacketMoneyVO">
+        select company_id ,sum(nullif(coin_amount,0) )/1000 as money from lucky_bag_collect_record
+        <where>
+            collect_type =1
+            <if test="start != null "> and collect_time &gt;= #{start} </if>
+            <if test="end != null "> and collect_time &lt;= #{end}</if>
+        </where>
+        group by company_id
+    </select>
+
+    <select id="get1kByStatusAndLtDate" resultMap="FsCourseRewardRoundResult">
+        <include refid="selectFsCourseRewardRoundVo"/>
+        where status = #{status} and create_time &lt; #{endTime}
+        order by id asc
+        limit 1000
+    </select>
+
+    <select id="getRewardListByUserId" resultType="com.fs.reward.vo.FsRewardListVO">
+        select
+            round.id                as  roundId,
+            round.rule_id           as  code,
+            round.create_time       as  createTime,
+            reward.actual_rewards   as  content,
+            round.goods_id          as  goodsId,
+            o.order_id              as  orderId,
+            round.rule_name         as  codeName
+        from fs_course_reward_round round
+        left join fs_course_reward reward on round.reward_id = reward.id
+        left join fs_reward_goods_order o on round.id = o.round_id
+        where round.user_id = #{userId} and round.reward_type in (4,5,6)
+        order by round.create_time desc
+    </select>
+</mapper>

+ 52 - 0
fs-service/src/main/resources/mapper/reward/FsRewardGoodsMapper.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.reward.mapper.FsRewardGoodsMapper">
+
+    <select id="selectFsRewardGoodsVOList" resultType="com.fs.reward.vo.FsRewardGoodsVO">
+        select
+            rg.*,
+            s.store_name
+        from fs_reward_goods rg
+        left join fs_store s on rg.store_id = s.store_id
+        where rg.is_del = 0
+        <if test="goodsId != null">
+            and rg.goods_id = #{goodsId}
+        </if>
+        <if test="goodsName != null and goodsName != ''">
+            and rg.goods_name like concat('%', #{goodsName}, '%')
+        </if>
+        <if test="storeId != null">
+            and rg.store_id = #{storeId}
+        </if>
+        <if test="storeGoodsSn != null and storeGoodsSn != ''">
+            and rg.store_goods_sn = #{storeGoodsSn}
+        </if>
+        <if test="status != null">
+            and rg.status = #{status}
+        </if>
+        order by rg.sort, rg.goods_id
+    </select>
+
+    <select id="selectFsRewardGoodsVOById" resultType="com.fs.reward.vo.FsRewardGoodsVO">
+        select
+            rg.*,
+            s.store_name
+        from fs_reward_goods rg
+        left join fs_store s on rg.store_id = s.store_id
+        where goods_id = #{goodsId}
+    </select>
+
+    <update id="reduceStock">
+        update fs_reward_goods 
+        set stock = stock - 1 
+        where goods_id = #{goodsId} and stock > 0
+    </update>
+
+    <update id="addStock">
+        update fs_reward_goods 
+        set stock = stock + 1 
+        where goods_id = #{goodsId}
+    </update>
+</mapper>

+ 93 - 0
fs-service/src/main/resources/mapper/reward/FsRewardGoodsOrderMapper.xml

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.reward.mapper.FsRewardGoodsOrderMapper">
+
+    <select id="selectFsRewardGoodsOrderVOList" resultType="com.fs.reward.vo.FsRewardGoodsOrderVO">
+        select
+            rgo.*,
+            u.nick_name,
+            s.store_name
+        from fs_reward_goods_order rgo
+        left join fs_user u on rgo.user_id = u.user_id
+        left join fs_store s on s.store_id = rgo.store_id
+        <where>
+            <if test="storeId != null">
+                and rgo.store_id = #{storeId}
+            </if>
+            <if test="userId != null">
+                and rgo.user_id = #{userId}
+            </if>
+            <if test="receiveUserName != null">
+                and rgo.user_name = #{receiveUserName}
+            </if>
+            <if test="receiveUserPhone != null">
+                and rgo.mobile = #{receiveUserPhone}
+            </if>
+            <if test="orderSn != null and orderSn != ''">
+                and rgo.order_sn = #{orderSn}
+            </if>
+            <if test="deliverySn != null and deliverySn != ''">
+                and rgo.delivery_id = #{deliverySn}
+            </if>
+            <if test="status != null">
+                and rgo.status = #{status}
+            </if>
+            <if test="sCreateTime != null">
+                and rgo.create_time >= #{sCreateTime}
+            </if>
+            <if test="eCreateTime != null">
+                <![CDATA[
+                and rgo.create_time < date_add(#{eCreateTime}, interval 1 day)
+                ]]>
+            </if>
+            <if test="sPayTime != null">
+                and rgo.pay_time >= #{sPayTime}
+            </if>
+            <if test="ePayTime != null">
+                <![CDATA[
+                and rgo.pay_time < date_add(#{ePayTime}, interval 1 day)
+                ]]>
+            </if>
+            <if test="sDeliveryTime != null">
+                and rgo.delivery_time >= #{sDeliveryTime}
+            </if>
+            <if test="eDeliveryTime != null">
+                <![CDATA[
+                and rgo.delivery_time < date_add(#{eDeliveryTime}, interval 1 day)
+                ]]>
+            </if>
+        </where>
+        order by rgo.create_time desc, rgo.order_id desc
+    </select>
+
+    <select id="selectFsRewardGoodsOrderVOById" resultType="com.fs.reward.vo.FsRewardGoodsOrderVO">
+        select
+            rgo.*,
+            u.nick_name,
+            s.store_name
+        from fs_reward_goods_order rgo
+        left join fs_user u on rgo.user_id = u.user_id
+        left join fs_store s on s.store_id = rgo.store_id
+        where rgo.order_id = #{orderId}
+    </select>
+
+    <select id="selectNoPushOrders" resultType="com.fs.reward.domain.FsRewardGoodsOrder">
+        select
+            o.*
+        from fs_reward_goods_order o
+        left join fs_store s on o.store_id = s.store_id
+        where o.status = 2 and s.delivery_type = 4 and o.extend_order_sn is null
+        limit 1000
+    </select>
+
+    <select id="selectPushOrders" resultType="java.lang.Long">
+        select
+            o.extend_order_sn
+        from fs_reward_goods_order o
+        left join fs_store s on o.store_id = s.store_id
+        where o.status = 2 and s.delivery_type = 4 and o.extend_order_sn is not null
+    </select>
+
+</mapper>

+ 11 - 1
fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java

@@ -9,6 +9,7 @@ import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.StringUtils;
+import com.fs.course.service.IFsUserCourseVideoService;
 import com.fs.his.domain.*;
 import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.enums.PaymentMethodEnum;
@@ -50,6 +51,8 @@ public class IntegralController extends  AppBaseController {
     private IFsIntegralCartService cartService;
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private IFsUserCourseVideoService fsUserCourseVideoService;
     @ApiOperation("获取积分商品列表")
     @GetMapping("/getIntegralGoodsList")
     @Cacheable(value = "getIntegralGoodsList", key = "#param")
@@ -216,7 +219,14 @@ public class IntegralController extends  AppBaseController {
     public R sign(HttpServletRequest request){
         FsUser user=userService.selectFsUserByUserId(Long.parseLong(getUserId()));
         Long integral=userSignService.sign(user);
-        return R.ok("签到获得" + integral + "积分");
+        return R.ok("签到获得" + integral + "积分").put("data", fsUserCourseVideoService.getSignGrandGiftRules(user.getUserId()));
+    }
+
+    @Login
+    @ApiOperation("领取签到大礼品")
+    @PostMapping("/claimSignReward")
+    public R claimSignReward() {
+        return R.ok(fsUserCourseVideoService.claimSignReward(Long.parseLong(getUserId())));
     }
 
     @Login

+ 90 - 0
fs-user-app/src/main/java/com/fs/app/controller/RewardGoodsController.java

@@ -0,0 +1,90 @@
+package com.fs.app.controller;
+
+import com.fs.app.annotation.Login;
+import com.fs.common.annotation.RepeatSubmit;
+import com.fs.common.core.domain.R;
+import com.fs.course.domain.FsCourseRewardRound;
+import com.fs.course.service.IFsCourseRewardRoundService;
+import com.fs.reward.param.FsRewardGoodsOrderAddParam;
+import com.fs.reward.param.FsRewardGoodsOrderPayParam;
+import com.fs.reward.service.IFsRewardGoodsOrderService;
+import com.fs.reward.service.IFsRewardGoodsService;
+import com.fs.reward.vo.FsRewardGoodsOrderVO;
+import com.fs.reward.vo.FsRewardGoodsVO;
+import com.fs.reward.vo.FsRewardListVO;
+import com.github.pagehelper.PageInfo;
+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.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Objects;
+
+
+@Slf4j
+@Api("中奖处理接口")
+@RestController
+@RequestMapping(value="/app/reward")
+public class RewardGoodsController extends AppBaseController {
+
+    @Autowired
+    private IFsRewardGoodsService rewardGoodsService;
+    @Autowired
+    private IFsRewardGoodsOrderService rewardGoodsOrderService;
+    @Autowired
+    private IFsCourseRewardRoundService courseRewardRoundService;
+
+    @Login
+    @ApiOperation("商品详情接口")
+    @GetMapping("/goodsDetail")
+    public R goodsDetail(@RequestParam Long goodsId, @RequestParam Long roundId) {
+        FsRewardGoodsVO fsRewardGoodsVO = rewardGoodsService.selectFsRewardGoodsVOById(goodsId);
+        FsCourseRewardRound rewardRound = courseRewardRoundService.selectFsCourseRewardRoundById(roundId);
+        if (Objects.nonNull(fsRewardGoodsVO) && Objects.nonNull(rewardRound) && Objects.nonNull(rewardRound.getGoodsPrice())) {
+            fsRewardGoodsVO.setPrice(rewardRound.getGoodsPrice());
+        }
+        return R.ok().put("data", fsRewardGoodsVO);
+    }
+
+    @RepeatSubmit
+    @Login
+    @ApiOperation("下单接口")
+    @PostMapping("/createOrder")
+    public R createOrder(@Validated @RequestBody FsRewardGoodsOrderAddParam param) {
+        return R.ok().put("data", rewardGoodsOrderService.createOrder(Long.parseLong(getUserId()), param));
+    }
+
+    @RepeatSubmit
+    @Login
+    @ApiOperation("通用支付接口")
+    @PostMapping("/payment")
+    public R payment(@Validated @RequestBody FsRewardGoodsOrderPayParam param) {
+        return R.ok(rewardGoodsOrderService.payment(Long.parseLong(getUserId()), param));
+    }
+
+    @Login
+    @ApiOperation("订单详情接口")
+    @GetMapping("/orderDetail")
+    public R getOrderDetail(@RequestParam Long orderId) {
+        Long userId = Long.parseLong(getUserId());
+        FsRewardGoodsOrderVO fsRewardGoodsOrderVO = rewardGoodsOrderService.selectFsRewardGoodsOrderVOById(orderId);
+        if (Objects.isNull(fsRewardGoodsOrderVO) || !fsRewardGoodsOrderVO.getUserId().equals(userId)) {
+            return R.error("订单不存在");
+        }
+        return R.ok().put("data", fsRewardGoodsOrderVO);
+    }
+
+    @Login
+    @ApiOperation("中奖列表接口")
+    @GetMapping("/rewardList")
+    public R rewardList() {
+        Long userId = Long.parseLong(getUserId());
+        startPage();
+        List<FsRewardListVO> rewardList = courseRewardRoundService.getRewardListByUserId(userId);
+        PageInfo<FsRewardListVO> pageInfo = new PageInfo<>(rewardList);
+        return R.ok().put("data", pageInfo);
+    }
+}