yfh 3 недель назад
Родитель
Сommit
b4aa844d6a

+ 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));
+    }
+}

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

@@ -0,0 +1,113 @@
+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) {
+            FsUser fsUser = fsUserService.selectFsUserByUserId(fsCourseRewardRound.getUserId());
+            fsCourseRewardRound.setUserIdByName(fsUser.getUserId()+"_"+fsUser.getNickName());
+
+            Company company = companyService.selectCompanyById(fsCourseRewardRound.getCompanyId());
+            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));
+    }
+}

+ 57 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseRewardController.java

@@ -0,0 +1,57 @@
+package com.fs.company.controller.course;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+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.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+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:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(rewardService.selectFsCourseRewardById(id));
+    }
+
+    @GetMapping("/listByIds/{ids}")
+    public AjaxResult listByIds(@PathVariable List<Long> ids) {
+        return AjaxResult.success(rewardService.selectFsCourseRewardByIds(ids));
+    }
+
+}

+ 106 - 0
fs-company/src/main/java/com/fs/company/controller/course/FsCourseRewardVideoRelationController.java

@@ -0,0 +1,106 @@
+package com.fs.company.controller.course;
+
+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.ServletUtils;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.course.domain.FsCourseRewardVideoRelation;
+import com.fs.course.param.FsCourseRewardVideoRelationParam;
+import com.fs.course.service.IFsCourseRewardVideoRelationService;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
+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 fs
+ * @date 2025-09-03
+ */
+@RestController
+@RequestMapping("/course/relation")
+public class FsCourseRewardVideoRelationController extends BaseController
+{
+    @Autowired
+    private IFsCourseRewardVideoRelationService fsCourseRewardVideoRelationService;
+    @Autowired
+    private TokenService tokenService;
+    /**
+     * 查询奖励与视频小节关联关系列表
+     */
+//    @PreAuthorize("@ss.hasPermi('course:relation:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsCourseRewardVideoRelation fsCourseRewardVideoRelation)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        fsCourseRewardVideoRelation.setCompanyId(loginUser.getCompany().getCompanyId());
+        startPage();
+        List<FsCourseRewardVideoRelation> list = fsCourseRewardVideoRelationService.selectFsCourseRewardVideoRelationListByType(fsCourseRewardVideoRelation);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出奖励与视频小节关联关系列表
+     */
+//    @PreAuthorize("@ss.hasPermi('course:relation:export')")
+    @Log(title = "奖励与视频小节关联关系", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsCourseRewardVideoRelation fsCourseRewardVideoRelation)
+    {
+        List<FsCourseRewardVideoRelation> list = fsCourseRewardVideoRelationService.selectFsCourseRewardVideoRelationList(fsCourseRewardVideoRelation);
+        ExcelUtil<FsCourseRewardVideoRelation> util = new ExcelUtil<FsCourseRewardVideoRelation>(FsCourseRewardVideoRelation.class);
+        return util.exportExcel(list, "奖励与视频小节关联关系数据");
+    }
+
+    /**
+     * 获取奖励与视频小节关联关系详细信息
+     */
+//    @PreAuthorize("@ss.hasPermi('course:relation:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsCourseRewardVideoRelationService.selectFsCourseRewardVideoRelationById(id));
+    }
+
+    /**
+     * 新增奖励与视频小节关联关系
+     */
+//    @PreAuthorize("@ss.hasPermi('course:relation:add')")
+    @Log(title = "奖励与视频小节关联关系", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsCourseRewardVideoRelation fsCourseRewardVideoRelation)
+    {
+        return toAjax(fsCourseRewardVideoRelationService.insertFsCourseRewardVideoRelation(fsCourseRewardVideoRelation));
+    }
+
+    /**
+     * 修改奖励与视频小节关联关系
+     */
+//    @PreAuthorize("@ss.hasPermi('course:relation:edit')")
+    @Log(title = "奖励与视频小节关联关系", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsCourseRewardVideoRelationParam fsCourseRewardVideoRelation)
+    {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        fsCourseRewardVideoRelation.setCompanyId( loginUser.getCompany().getCompanyId());
+        return toAjax(fsCourseRewardVideoRelationService.updateFsCourseRewardVideoRelation(fsCourseRewardVideoRelation));
+    }
+
+    /**
+     * 删除奖励与视频小节关联关系
+     */
+    @PreAuthorize("@ss.hasPermi('course:relation:remove')")
+    @Log(title = "奖励与视频小节关联关系", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsCourseRewardVideoRelationService.deleteFsCourseRewardVideoRelationByIds(ids));
+    }
+}

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

@@ -43,7 +43,25 @@ public class CourseConfig implements Serializable {
     private Integer isNegative;//是否为负数 0、不允许,1、允许
 
     private Integer isOpen;
+    /**
+     * 第一个百分比
+     */
+    private String silverPercent;
+
+    /**
+     * 第二个百分比
+     */
+    private String goldPercent;
 
+    /**
+     * 第一个宝箱
+     */
+    private String silverBox;
+
+    /**
+     * 第二个宝箱
+     */
+    private String goldBox;
     /**
      * 侧边栏是否仅展示当天课程
      */

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

@@ -0,0 +1,26 @@
+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,"保底转盘");
+    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);
+    }
+}

+ 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;
+
+
+}

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

@@ -0,0 +1,99 @@
+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 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);
+
+}

+ 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);
+}

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

@@ -0,0 +1,642 @@
+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.course.utils.Range;
+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.system.service.ISysConfigService;
+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 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 2:
+                //增加积分
+                FsUser userMap=new FsUser();
+                userMap.setUserId(user.getUserId());
+                userMap.setIntegral(user.getIntegral()+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);
+    }
+
+}

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

@@ -0,0 +1,223 @@
+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:
+                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 && number.compareTo(BigDecimal.valueOf(100)) <= 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);
+    }
+}

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

@@ -0,0 +1,19 @@
+package com.fs.course.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);
+    }
+}