Pārlūkot izejas kodu

feat:APP独立发课、看课

caoliqin 2 nedēļas atpakaļ
vecāks
revīzija
87b927efa9
78 mainītis faili ar 5291 papildinājumiem un 16 dzēšanām
  1. 1 1
      fs-app-task/src/main/resources/application.yml
  2. 5 0
      fs-common/src/main/java/com/fs/common/constant/FsConstants.java
  3. 11 0
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  4. 3 0
      fs-service/src/main/java/com/fs/app/cusrole/domain/AppCustomerRole.java
  5. 22 0
      fs-service/src/main/java/com/fs/course/config/CourseConfig.java
  6. 30 0
      fs-service/src/main/java/com/fs/course/constant/CourseConstant.java
  7. 6 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseRewardRound.java
  8. 71 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardMapper.java
  9. 12 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardRoundMapper.java
  10. 16 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseRewardVideoRelationMapper.java
  11. 32 0
      fs-service/src/main/java/com/fs/course/param/FsCourseAnswerRewardParam.java
  12. 1 0
      fs-service/src/main/java/com/fs/course/param/FsCourseQuestionAnswerUParam.java
  13. 32 0
      fs-service/src/main/java/com/fs/course/param/FsCourseRewardRuleParam.java
  14. 2 0
      fs-service/src/main/java/com/fs/course/param/FsCourseSendRewardUParam.java
  15. 2 1
      fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoFinishUParam.java
  16. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseQuestionBankService.java
  17. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  18. 19 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseVideoService.java
  19. 105 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java
  20. 93 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  21. 787 4
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  22. 19 0
      fs-service/src/main/java/com/fs/course/utils/luckyDraw/Prize.java
  23. 40 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseAnswerRewardVO.java
  24. 15 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseRewardTypeVO.java
  25. 1 0
      fs-service/src/main/java/com/fs/erp/domain/ErpOrder.java
  26. 2 0
      fs-service/src/main/java/com/fs/erp/domain/ErpOrderQuery.java
  27. 4 0
      fs-service/src/main/java/com/fs/erp/dto/ErpOrderQueryRequert.java
  28. 6 0
      fs-service/src/main/java/com/fs/erp/dto/ErpOrderQueryResponse.java
  29. 2 0
      fs-service/src/main/java/com/fs/erp/http/JstErpHttpService.java
  30. 10 0
      fs-service/src/main/java/com/fs/erp/http/JstErpHttpServiceImpl.java
  31. 45 0
      fs-service/src/main/java/com/fs/erp/service/IJSTErpOrderService.java
  32. 325 0
      fs-service/src/main/java/com/fs/erp/service/impl/JSTErpRewardOrderServiceImpl.java
  33. 7 0
      fs-service/src/main/java/com/fs/his/config/IntegralConfig.java
  34. 8 0
      fs-service/src/main/java/com/fs/his/domain/FsStorePayment.java
  35. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsStorePaymentMapper.java
  36. 9 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserIntegralLogsMapper.java
  37. 3 0
      fs-service/src/main/java/com/fs/his/service/IFsUserIntegralLogsService.java
  38. 5 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  39. 9 0
      fs-service/src/main/java/com/fs/his/service/impl/FsUserIntegralLogsServiceImpl.java
  40. 29 8
      fs-service/src/main/java/com/fs/huifuPay/domain/HuiFuCreateOrder.java
  41. 8 0
      fs-service/src/main/java/com/fs/huifuPay/sdk/opps/core/request/V2TradePaymentScanpayRefundRequest.java
  42. 26 0
      fs-service/src/main/java/com/fs/pay/constant/PaymentTypeConstant.java
  43. 70 0
      fs-service/src/main/java/com/fs/pay/service/IPaymentMiniProgramConfigService.java
  44. 187 0
      fs-service/src/main/java/com/fs/pay/service/impl/PaymentMiniProgramConfigServiceImpl.java
  45. 110 0
      fs-service/src/main/java/com/fs/reward/domain/FsRewardGoods.java
  46. 155 0
      fs-service/src/main/java/com/fs/reward/domain/FsRewardGoodsOrder.java
  47. 32 0
      fs-service/src/main/java/com/fs/reward/mapper/FsRewardGoodsMapper.java
  48. 50 0
      fs-service/src/main/java/com/fs/reward/mapper/FsRewardGoodsOrderMapper.java
  49. 24 0
      fs-service/src/main/java/com/fs/reward/param/FsAppRewardGoodsOrderAddParam.java
  50. 68 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsAddParam.java
  51. 20 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsEditParam.java
  52. 23 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsListParam.java
  53. 24 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderAddParam.java
  54. 71 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderListParam.java
  55. 25 0
      fs-service/src/main/java/com/fs/reward/param/FsRewardGoodsOrderPayParam.java
  56. 89 0
      fs-service/src/main/java/com/fs/reward/service/IFsRewardGoodsOrderService.java
  57. 38 0
      fs-service/src/main/java/com/fs/reward/service/IFsRewardGoodsService.java
  58. 1399 0
      fs-service/src/main/java/com/fs/reward/service/impl/FsRewardGoodsOrderServiceImpl.java
  59. 105 0
      fs-service/src/main/java/com/fs/reward/service/impl/FsRewardGoodsServiceImpl.java
  60. 159 0
      fs-service/src/main/java/com/fs/reward/vo/FsRewardGoodsOrderExcelVO.java
  61. 168 0
      fs-service/src/main/java/com/fs/reward/vo/FsRewardGoodsOrderVO.java
  62. 113 0
      fs-service/src/main/java/com/fs/reward/vo/FsRewardGoodsVO.java
  63. 38 0
      fs-service/src/main/java/com/fs/reward/vo/FsRewardListVO.java
  64. 2 0
      fs-service/src/main/java/com/fs/tzBankPay/TzBankService/TzBankService.java
  65. 22 0
      fs-service/src/main/java/com/fs/tzBankPay/TzBankService/TzBankServiceImpl/TzBankServiceImpl.java
  66. 1 0
      fs-service/src/main/java/com/fs/tzBankPay/doman/PayCreateOrder.java
  67. 23 0
      fs-service/src/main/java/com/fs/tzBankPay/doman/PayType.java
  68. 5 0
      fs-service/src/main/java/com/fs/tzBankPay/doman/RefundParam.java
  69. 5 0
      fs-service/src/main/java/com/fs/ybPay/dto/RefundDTO.java
  70. 39 0
      fs-service/src/main/resources/application-config-dev.yml
  71. 1 1
      fs-service/src/main/resources/application-config-druid-hst.yml
  72. 1 1
      fs-service/src/main/resources/mapper/app/AppWorkTaskMapper.xml
  73. 88 0
      fs-service/src/main/resources/mapper/course/FsCourseRewardVideoRelationMapper.xml
  74. 125 0
      fs-service/src/main/resources/mapper/course/RewardMapper.xml
  75. 87 0
      fs-service/src/main/resources/mapper/course/RewardRoundMapper.xml
  76. 5 0
      fs-service/src/main/resources/mapper/his/FsStorePaymentMapper.xml
  77. 89 0
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java
  78. 1 0
      pom.xml

+ 1 - 1
fs-app-task/src/main/resources/application.yml

@@ -1,6 +1,6 @@
 server:
   # 服务器的HTTP端口,默认为8080
-  port: 8888
+  port: 9889
 # Spring配置
 spring:
   profiles:

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

@@ -12,6 +12,11 @@ public interface FsConstants {
     String FRIEND_WELCOME_VIDEO_KEY = "friend:welcome:";
     String REDIS_INTEGRAL_ORDER_UNPAY = "integral:order:unpay:";
 
+    // 抽奖大礼品缓存key
+    String REDIS_GRAND_GIFT = "grand:gift";
+
+    String REDIS_GRAND_GIFT_LISTS = "grand:gift:lists";
+
     // 公司余额redis key "company:money:" + company.getCompanyId()
     String COMPANY_MONEY_KEY = "company:money:";
     String COMPANY_MONEY_DATE_KEY = "company:money:date:";

+ 11 - 0
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -540,4 +540,15 @@ public class RedisCache
     public boolean hasKey(String key) {
         return Boolean.TRUE.equals(redisTemplate.hasKey(key));
     }
+
+    public void addToSetRaw(String setKey, String... values) {
+        redisTemplate.execute((RedisCallback<Void>) connection -> {
+            byte[] rawKey = redisTemplate.getStringSerializer().serialize(setKey);
+            for (String value : values) {
+                byte[] rawValue = redisTemplate.getStringSerializer().serialize(value);
+                connection.sAdd(rawKey, rawValue);
+            }
+            return null;
+        });
+    }
 }

+ 3 - 0
fs-service/src/main/java/com/fs/app/cusrole/domain/AppCustomerRole.java

@@ -3,6 +3,7 @@ package com.fs.app.cusrole.domain;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.time.LocalDateTime;
@@ -22,8 +23,10 @@ public class AppCustomerRole {
 
     private String avatar;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime updateTime;
 
     private Long appFastgptRoleId;

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

@@ -55,6 +55,28 @@ public class CourseConfig implements Serializable {
     private Integer isOpen;
     private Boolean completionCountdown;
 
+    private Integer appAnswerIntegralSU; //app答题积分
+    private Integer appAnswerIntegralS; //app答题积分
+    private Integer appAnswerIntegralA; //app答题积分
+    private Integer appAnswerIntegralB; //app答题积分
+    private Integer appAnswerIntegralC; //app答题积分
+    private Integer appAnswerIntegralD; //app答题积分
+    private Integer appAnswerIntegralE; //app答题积
+    private Integer appAnswerIntegralF; //app答题积
+
+    // 默认转盘奖励
+    private Long defaultSpinReward;
+    // 默认大礼品
+    private Long defaultGrandGift;
+    private Long defaultGrandGiftSU;
+    private Long defaultGrandGiftS;
+    private Long defaultGrandGiftA;
+    private Long defaultGrandGiftB;
+    private Long defaultGrandGiftC;
+    private Long defaultGrandGiftD;
+    private Long defaultGrandGiftE;
+    private Long defaultGrandGiftF;
+
     /**
      * 外网通用看课链接域名
      */

+ 30 - 0
fs-service/src/main/java/com/fs/course/constant/CourseConstant.java

@@ -0,0 +1,30 @@
+package com.fs.course.constant;
+
+public class CourseConstant {
+
+    public static final String FEI_SHU_WATCH_HEART = "h5feishu:watch:heartbeat:";
+    public static final String FEI_SHU_WATCH_DURATION = "h5feishu:watch:duration:";
+
+    public static final String WATCH_DURATION_ACTIVE_KEYS = "watch:duration:active_keys";
+    public static final String WATCH_HEARTBEAT_ACTIVE_KEYS = "watch:heartbeat:active_keys";
+    public static final String WATCH_DURATION_APP_ACTIVE_KEYS = "watch:duration:app:active_keys";
+    public static final String WATCH_HEARTBEAT_APP_ACTIVE_KEYS = "watch:heartbeat:app:active_keys";
+    public static final String FEI_SHU_WATCH_DURATION_ACTIVE_KEYS = "watch:duration:feishu:active_keys";
+    public static final String FEI_SHU_WATCH_HEARTBEAT_ACTIVE_KEYS = "watch:heartbeat:feishu:active_keys";
+
+    public static final String VIDEO_LIKE_ACTIVE_KEYS = "video:like:active_keys";
+    public static final String VIDEO_UNLIKE_ACTIVE_KEYS = "video:unlike:active_keys";
+    public static final String VIDEO_FAVORITE_ACTIVE_KEYS = "video:favorite:active_keys";
+    public static final String VIDEO_NOFAVORITE_ACTIVE_KEYS = "video:nofavorite:active_keys";
+    public static final String VIDEO_COMMENT_COUNT_ACTIVE_KEYS = "video:comment_count:active_keys";
+    public static final String VIDEO_COMMENT_LIST_ACTIVE_KEYS = "video:comment_list:active_keys";
+    public static final String VIDEO_REPLY_LIST_ACTIVE_KEYS = "video:reply_list:active_keys";
+
+    public static String getFeiShuWatchHeartKey(Long userId, Long videoId, Long companyUserId) {
+        return String.format(FEI_SHU_WATCH_HEART + "%s:%s:%s", userId, videoId, companyUserId);
+    }
+
+    public static String getFeiShuWatchDurationKey(Long userId, Long videoId, Long companyUserId) {
+        return String.format(FEI_SHU_WATCH_DURATION + "%s:%s:%s", userId, videoId, companyUserId);
+    }
+}

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

@@ -5,6 +5,8 @@ import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
+import java.math.BigDecimal;
+
 /**
  * 奖励领取记录对象 reward_round
  *
@@ -76,4 +78,8 @@ public class FsCourseRewardRound extends BaseEntity{
      * 奖励商品ID
      */
     private Long goodsId;
+    /**
+     * 奖励商品价格
+     */
+    private BigDecimal goodsPrice;
 }

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

@@ -0,0 +1,71 @@
+package com.fs.course.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.app.cusrole.domain.AppCustomerRole;
+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);
+
+    List<AppCustomerRole> getAppCustomerList();
+}

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

@@ -1,6 +1,7 @@
 package com.fs.course.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.app.course.domain.FsAppCourseRewardRound;
 import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.domain.FsCourseRewardRound;
 import org.apache.ibatis.annotations.Param;
@@ -72,4 +73,15 @@ public interface FsCourseRewardRoundMapper extends BaseMapper<FsCourseRewardRoun
      */
     List<FsCourseRewardRound> get1kByStatusAndLtDate(@Param("status") int status, @Param("endTime") LocalDate endTime);
 
+
+    int insertFsCourseRewardRoundApp(FsAppCourseRewardRound rewardRound);
+
+    List<FsAppCourseRewardRound> selectFsAppCourseRewardRoundList(FsAppCourseRewardRound rewardRound);
+
+    FsAppCourseRewardRound selectFsAppCourseRewardRoundById(@Param("id") Long id);
+
+    Integer selectTodayTotalReward(@Param("userId") Long userId);
+
+    Integer selectTodayTotalRewardByStatus(Long userId);
+
 }

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

@@ -1,6 +1,7 @@
 package com.fs.course.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.app.course.domain.FsAppCourseRewardVideoRelation;
 import com.fs.course.domain.FsCourseReward;
 import com.fs.course.domain.FsCourseRewardVideoRelation;
 import org.apache.ibatis.annotations.Param;
@@ -68,11 +69,15 @@ public interface FsCourseRewardVideoRelationMapper extends BaseMapper<FsCourseRe
      */
     void deleteByTypes(@Param("videoId") Long videoId, @Param("companyId") Long companyId, @Param("types") List<Integer> types);
 
+    void deleteByTypesApp(@Param("videoId") Long videoId, @Param("appCustomerId") Long appCustomerId, @Param("types") List<Integer> types);
+
     /**
      * 根据公司ID、小节ID和类型查询奖励ID
      */
     Long getRewardIdByCompanyIdAndVideoIdAndType(@Param("companyId") Long companyId, @Param("videoId") Long videoId, @Param("type") int type);
 
+    Long getRewardIdByCompanyIdAndVideoIdAndTypeApp(@Param("companyId") Long companyId, @Param("videoId") Long videoId, @Param("type") int type);
+
     /**
      * 根据公司ID、小节ID和类型查询奖励配置
      */
@@ -84,4 +89,15 @@ public interface FsCourseRewardVideoRelationMapper extends BaseMapper<FsCourseRe
      * 根据公司ID、小节ID和奖励ID查询配置关系
      */
     FsCourseRewardVideoRelation selectByCompanyIdAndVideoIdAndRewardId(@Param("companyId") Long companyId, @Param("videoId") Long videoId, @Param("rewardId") Long rewardId);
+
+    FsCourseReward getRewardByCompanyIdAndVideoIdAndTypeApp(@Param("appCustomerId") Long appCustomerId,@Param("videoId") Long videoId,@Param("type") Integer type);
+
+    FsAppCourseRewardVideoRelation selectByCompanyIdAndVideoIdAndRewardIdApp(@Param("companyId") Long appCustomerId,@Param("videoId") Long videoId,@Param("rewardId") Long id);
+
+    List<FsAppCourseRewardVideoRelation> selectFsAppCourseRewardVideoRelationListByType(FsAppCourseRewardVideoRelation fsCourseRewardVideoRelation);
+
+    int insertFsCourseRewardVideoRelationApp(FsAppCourseRewardVideoRelation relation);
+
+    List<FsAppCourseRewardVideoRelation> selectFsAppCourseRewardVideoRelationList(FsAppCourseRewardVideoRelation fsCourseRewardVideoRelation);
+
 }

+ 32 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseAnswerRewardParam.java

@@ -0,0 +1,32 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+public class FsCourseAnswerRewardParam {
+
+    @NotNull(message = "视频ID不能为空")
+    @ApiModelProperty("视频小节ID")
+    private Long videoId;
+
+    @ApiModelProperty("红包配置")
+    private Long redPacketRuleId;
+
+    @ApiModelProperty("积分配置")
+    private Long pointRuleId;
+
+    @ApiModelProperty("转盘配置")
+    private Long turntableRuleId;
+
+    @ApiModelProperty("保底转盘配置")
+    private Long turntableGuaranteeRuleId;
+
+    @ApiModelProperty("大礼品配置")
+    private Long grandGiftRuleId;
+
+    @ApiModelProperty("客服id")
+    private Long appCustomerId;
+}

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

@@ -32,5 +32,6 @@ public class FsCourseQuestionAnswerUParam implements Serializable
     private List<FsCourseQuestionBank> questions;
     private Long periodId;
     private Integer isOpen;
+    private String appCustomerId;//客服id
 
 }

+ 32 - 0
fs-service/src/main/java/com/fs/course/param/FsCourseRewardRuleParam.java

@@ -0,0 +1,32 @@
+package com.fs.course.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+@Data
+public class FsCourseRewardRuleParam {
+
+    @NotNull(message = "奖励类型不能为空")
+    @ApiModelProperty("奖励类型 3:转盘 4:保底转盘 5:大礼品")
+    private Integer type;
+
+    @NotNull(message = "视频ID不能为空")
+    @ApiModelProperty("视频ID")
+    private Long videoId;
+
+    @NotBlank(message = "公司ID不能为空")
+    @ApiModelProperty("公司ID")
+    private Long companyId;
+
+    @ApiModelProperty("用户ID")
+    private Long userId;
+
+    @ApiModelProperty("来源类型 1:H5 2:小程序 3:APP")
+    private Integer source = 1;
+
+    @ApiModelProperty("客服ID")
+    private Long appCustomerId;
+}

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

@@ -56,4 +56,6 @@ public class FsCourseSendRewardUParam implements Serializable
 
     private Long watchLogId;
 
+    private String appCustomerId;//客服id
+
 }

+ 2 - 1
fs-service/src/main/java/com/fs/course/param/FsUserCourseVideoFinishUParam.java

@@ -11,7 +11,7 @@ public class FsUserCourseVideoFinishUParam implements Serializable {
     private Long duration; //时长
     private Long videoId;
     private Long userId;
-    private String qwUserId;
+    private String qwUserId;//app传的是app_customer_id
     private String corpId;
     private Long companyUserId;
     private Long companyId;
@@ -24,4 +24,5 @@ public class FsUserCourseVideoFinishUParam implements Serializable {
     private Integer isOpen;//是否公开课
     private Long periodId;
     private Integer projectId;
+    private String link;
 }

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

@@ -120,4 +120,6 @@ public interface IFsCourseQuestionBankService
      * 提交今日问题作答(仅记录答题结果,不发券)
      */
     LiveCompletionCouponAnswerResult submitAnswers(LiveCompletionCouponAnswerParam param, Long userId,LiveWatchConfig config);
+
+    R courseAnswerApp(FsCourseQuestionAnswerUParam param);
 }

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

@@ -151,6 +151,8 @@ public interface IFsUserCourseService {
      */
     void editConfig(Long id, String configJson);
 
+    R getCourseVideoDetailsApp(FsUserCourseVideoFinishUParam param);
+
     /**
      * 获取课程选项列表
      */

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

@@ -1,6 +1,8 @@
 package com.fs.course.service;
 
+import com.alibaba.fastjson.JSONArray;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.app.course.param.AppCourseRewardTypeParam;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.course.domain.FsUserCourseVideo;
@@ -98,6 +100,23 @@ public interface IFsUserCourseVideoService extends IService<FsUserCourseVideo> {
 
     R isAddKf(FsUserCourseVideoAddKfUParam param);
 
+    R updateWatchDurationApp(FsUserCourseVideoFinishUParam param);
+
+    R isAddKfApp(FsUserCourseVideoAddKfUParam param);
+
+    R sendRewardV2App(FsCourseSendRewardUParam param);
+
+    R getInternetTrafficApp(FsUserCourseVideoFinishUParam param);
+
+    List<FsCourseRewardTypeVO> getVideoRewardTypesApp(AppCourseRewardTypeParam param);
+
+    JSONArray getVideoRewardRulesApp(FsCourseRewardRuleParam param);
+
+    FsCourseAnswerRewardVO getAnswerRewardConfigApp(Long videoId, Long l);
+
+    void changeAnswerRewardConfigApp(FsCourseAnswerRewardParam param, Long appCustomerId);
+
+
     List<FsUserCourseVideoVO> selectFsUserCourseVideoListByCourseIdAndCompany(FsUserCourseVideoParam fsUserCourseVideo);
 
     R updateFsUserCourseVideoCompanyRedPacket(FsUserCourseVideoParam fsUserCourseVideo);

+ 105 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseQuestionBankServiceImpl.java

@@ -4,6 +4,9 @@ import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.app.watchlog.domain.AppCourseWatchLog;
+import com.fs.app.watchlog.mapper.AppCourseWatchLogMapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.ServiceException;
@@ -81,6 +84,8 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
     @Autowired
     private FsCourseWatchLogMapper courseWatchLogMapper;
     @Autowired
+    private AppCourseWatchLogMapper appCourseWatchLogMapper;
+    @Autowired
     private LiveCourseQuestionRelMapper liveCourseQuestionRelMapper;
     @Autowired
     private FsUserCourseCategoryMapper courseCategoryMapper;
@@ -1577,4 +1582,104 @@ public class FsCourseQuestionBankServiceImpl implements IFsCourseQuestionBankSer
         return trimmed.split(",");
     }
 
+    @Override
+    public R courseAnswerApp(FsCourseQuestionAnswerUParam param) {
+        //获取配置参数
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        //本次答题正确个数
+        int thisRightCount = 0;
+        //用户答错次数
+        int errorCount = 0;
+        List<FsCourseQuestionBank> incorrectQuestions = new ArrayList<>();
+
+        AppCourseWatchLog log = appCourseWatchLogMapper.selectOne(new LambdaQueryWrapper<AppCourseWatchLog>().eq(AppCourseWatchLog::getUserId, param.getUserId())
+                .eq(AppCourseWatchLog::getVideoId, param.getVideoId())
+                .eq(AppCourseWatchLog::getAppCustomerId, param.getAppCustomerId()));
+        if (log==null){
+            return R.error("无记录");
+        }
+        FsUserCourseVideo video = courseVideoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
+        if (video.getIsFirst() == null || video.getIsFirst()==0){
+            if (log.getLogType()!=2){
+                String durationStr = redisCache.getCacheObject("h5user:watch:duration:app:" + param.getQwUserId() + ":" + param.getUserId() + ":" + param.getVideoId());
+                boolean redisFinished = false;
+                if (StringUtils.isNotEmpty(durationStr) && video.getDuration() != null && video.getDuration() > 0) {
+                    long redisDuration = Long.parseLong(durationStr);
+                    long percentage = redisDuration * 100 / video.getDuration();
+                    redisFinished = percentage >= config.getAnswerRate();
+                }
+                if (!redisFinished) {
+                    return R.error("未完课");
+                }
+            }
+        }
+        //日志id
+        Long logId = log.getLogId();
+        FsCourseAnswerLogs rightLog = courseAnswerLogsMapper.selectRightLogByCourseVideo(param.getVideoId(), param.getUserId(), param.getQwUserId());
+        if (rightLog != null) {
+            if (log.getRewardType() != null) {
+                return R.error("该课程已答题完成,不可重复答题");
+            } else {
+                return R.ok("答题成功");
+            }
+        }
+        errorCount = courseAnswerLogsMapper.selectErrorCountByCourseVideo(param.getVideoId(), param.getUserId(),param.getQwUserId(),null);
+
+        if (errorCount >= config.getAnswerErrorCount()) {
+            return R.error("该课题到达答错次数限制");
+        }
+        int remainCount = config.getAnswerErrorCount()-errorCount-1;
+
+        // 一次性获取所有问题的正确答案
+        Map<Long, FsCourseQuestionBank> correctAnswersMap = fsCourseQuestionBankMapper.selectFsCourseQuestionBankByIds(
+                param.getQuestions().stream().map(FsCourseQuestionBank::getId).collect(Collectors.toList())
+        ).stream().collect(Collectors.toMap(FsCourseQuestionBank::getId, question -> question));
+
+        for (FsCourseQuestionBank questionBank : param.getQuestions()) {
+            FsCourseQuestionBank correctAnswer = correctAnswersMap.get(questionBank.getId());
+            if (correctAnswer.getType() == 1) {
+                if (questionBank.getAnswer().equals(correctAnswer.getAnswer())) {
+                    thisRightCount++;
+                } else {
+                    correctAnswer.setAnswer(null);
+                    incorrectQuestions.add(correctAnswer);
+                }
+            } else if (correctAnswer.getType() == 2) {
+                String[] userAnswers = convertStringToArray(questionBank.getAnswer());
+                String[] correctAnswers = convertStringToArray(correctAnswer.getAnswer());
+
+                Arrays.sort(userAnswers);
+                Arrays.sort(correctAnswers);
+
+                if (Arrays.equals(userAnswers, correctAnswers)) {
+                    thisRightCount++;
+                } else {
+                    correctAnswer.setAnswer(null);
+                    incorrectQuestions.add(correctAnswer);
+                }
+            }
+        }
+
+        FsCourseAnswerLogs logs = new FsCourseAnswerLogs();
+        logs.setWatchLogId(logId);
+        logs.setUserId(param.getUserId());
+        logs.setVideoId(param.getVideoId());
+        logs.setCourseId(param.getCourseId());
+        logs.setQwUserId(param.getQwUserId());
+        logs.setQuestionJson(JSONObject.toJSONString(param.getQuestions()));
+        logs.setCreateTime(new Date());
+
+        if (thisRightCount == param.getQuestions().size()) {
+            logs.setIsRight(1);
+            courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
+            return R.ok("答题成功");
+        } else {
+            logs.setIsRight(0);
+            courseAnswerLogsMapper.insertFsCourseAnswerLogs(logs);
+            return R.ok("答题失败").put("incorrectQuestions", incorrectQuestions).put("remain",remainCount);
+        }
+    }
+
 }

+ 93 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -10,10 +10,14 @@ import java.net.URLConnection;
 import java.net.URLEncoder;
 import java.util.*;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.app.watchlog.domain.AppCourseWatchLog;
+import com.fs.app.watchlog.mapper.AppCourseWatchLogMapper;
 import com.fs.common.utils.RedisUtil;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.common.core.domain.R;
@@ -94,6 +98,10 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     @Autowired
     private FsCourseLinkMapper fsCourseLinkMapper;
     @Autowired
+    private FsCourseQuestionBankMapper fsCourseQuestionBankMapper;
+    @Autowired
+    private AppCourseWatchLogMapper appCourseWatchLogMapper;
+    @Autowired
     private FsCourseAnswerLogsMapper fsCourseAnswerLogsMapper;
     @Autowired
     private FsCourseWatchLogMapper fsCourseWatchLogMapper;
@@ -896,6 +904,91 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         fsUserCourseMapper.editConfig(id, configJson);
     }
 
+    @Override
+    public R getCourseVideoDetailsApp(FsUserCourseVideoFinishUParam param) {
+        fsUserMapper.selectFsUserByUserId(param.getUserId());
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        FsUserCourseVideoH5DVO course = null;
+        try {
+            course = redisCache.getCacheObject("cache:video1:" + param.getVideoId());
+            if (course == null) {
+                course = selectFsUserCourseVideoH5DVOByVideoId(param.getVideoId());
+                redisCache.setCacheObject("cache:video1:" + param.getVideoId(), course, 10, TimeUnit.MINUTES);
+            }
+        } catch (Exception e) {
+            // ignore cache error
+        }
+        List<FsUserCourseVideoQuestionVO> questionVOList = null;
+        try {
+            questionVOList = redisCache.getCacheObject("cache:question:" + param.getVideoId());
+            if (questionVOList == null || questionVOList.size() == 0) {
+                questionVOList = new ArrayList<>();
+                if (course != null && StringUtils.isNotEmpty(course.getQuestionBankId())) {
+                    String[] questionIds = course.getQuestionBankId().split(",");
+                    for (String questionId : questionIds) {
+                        FsUserCourseVideoQuestionVO vo = new FsUserCourseVideoQuestionVO();
+                        FsCourseQuestionBank questionBank = fsCourseQuestionBankMapper.selectFsCourseQuestionBankById(Long.parseLong(questionId));
+                        if (questionBank != null && questionBank.getStatus() != 0) {
+                            BeanUtils.copyProperties(questionBank, vo);
+                            questionVOList.add(vo);
+                        }
+                    }
+                }
+                redisCache.setCacheObject("cache:question:" + param.getVideoId(), questionVOList, 10, TimeUnit.MINUTES);
+            }
+        } catch (Exception e) {
+            // ignore cache error
+        }
+        Long duration = 0L;
+        long tipsTime = 0L;
+        int isFinish = 0;
+
+        if (param.getLinkType() != null && param.getLinkType() == 1) {
+            return R.ok().put("course", course)
+                    .put("questions", questionVOList)
+                    .put("config", config).put("playDuration", duration).put("tipsTime", tipsTime).put("maxBufferLength", config.getMaxBufferLength());
+        }
+        FsCourseLink fsCourseLink = fsCourseLinkMapper.selectFsCourseLinkByLink(param.getLink());
+        if (fsCourseLink == null) {
+            return R.error("链接不存在");
+        }
+        param.setQwUserId(String.valueOf(fsCourseLink.getQwUserId()));
+        String redisKey = "h5user:watch:duration:app:" + param.getQwUserId() + ":" + param.getUserId() + ":" + param.getVideoId();
+        String durationStr = redisCache.getCacheObject(redisKey);
+
+        AppCourseWatchLog log = appCourseWatchLogMapper.selectOne(new LambdaQueryWrapper<AppCourseWatchLog>().eq(AppCourseWatchLog::getUserId, param.getUserId())
+                .eq(AppCourseWatchLog::getAppCustomerId, fsCourseLink.getQwUserId()).eq(AppCourseWatchLog::getVideoId, param.getVideoId()));
+        if (log == null) {
+            return R.error("请联系客服先注册!");
+        }
+        if (durationStr != null) {
+            duration = Long.parseLong(durationStr);
+        } else {
+            duration = log.getDuration();
+        }
+        if (course != null && course.getDuration() != null) {
+            tipsTime = course.getDuration() / 2;
+        }
+        if (log.getLogType() == 2) {
+            isFinish = 1;
+        }
+        String videoRedisKey = "h5user:video:duration:app:" + param.getVideoId();
+        Long videoDuration = redisCache.getCacheObject(videoRedisKey);
+        if (videoDuration == null && course != null) {
+            redisCache.setCacheObject(videoRedisKey, course.getDuration());
+        }
+
+        return R.ok()
+                .put("course", course)
+                .put("questions", questionVOList)
+                .put("config", config)
+                .put("playDuration", duration)
+                .put("tipsTime", tipsTime)
+                .put("maxBufferLength", config.getMaxBufferLength())
+                .put("isFinish", isFinish);
+    }
+
     @Override
     public List<OptionsVO> selectCourseOptionsList() {
         return fsUserCourseMapper.selectCourseOptionsList();

+ 787 - 4
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -1,11 +1,14 @@
 package com.fs.course.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -13,6 +16,15 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.app.course.domain.FsAppCourseRewardRound;
+import com.fs.app.course.domain.FsAppCourseRewardVideoRelation;
+import com.fs.app.course.domain.FsAppCourseTrafficLog;
+import com.fs.app.course.mapper.FsAppCourseTrafficLogMapper;
+import com.fs.app.course.param.AppCourseRewardTypeParam;
+import com.fs.app.user.domain.FsUserPortrait;
+import com.fs.app.user.service.IFsUserPortraitService;
+import com.fs.app.watchlog.domain.AppCourseWatchLog;
+import com.fs.app.watchlog.mapper.AppCourseWatchLogMapper;
 import com.fs.common.BeanCopyUtils;
 import com.fs.common.constant.FsConstants;
 import com.fs.common.core.domain.R;
@@ -37,6 +49,7 @@ import com.fs.config.cloud.CloudHostProper;
 import com.fs.course.config.CourseConfig;
 import com.fs.course.config.RandomRedPacketConfig;
 import com.fs.course.config.RandomRedPacketRule;
+import com.fs.course.constant.CourseConstant;
 import com.fs.course.domain.*;
 import com.fs.course.dto.CoursePackageDTO;
 import com.fs.course.mapper.*;
@@ -45,16 +58,14 @@ import com.fs.course.param.newfs.FsUserCourseAddCompanyUserParam;
 import com.fs.course.param.newfs.FsUserCourseVideoLinkParam;
 import com.fs.course.param.newfs.FsUserCourseVideoUParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
-import com.fs.course.service.IFsCourseLinkService;
-import com.fs.course.service.IFsUserCompanyBindService;
-import com.fs.course.service.IFsUserCompanyUserService;
-import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.service.*;
 import com.fs.course.utils.luckyDraw.LotteryUtil;
 import com.fs.course.utils.luckyDraw.Prize;
 import com.fs.course.vo.*;
 import com.fs.course.vo.newfs.*;
 import com.fs.enums.ExceptionCodeEnum;
 import com.fs.his.config.AppConfig;
+import com.fs.his.config.IntegralConfig;
 import com.fs.his.domain.*;
 import com.fs.his.mapper.FsCouponMapper;
 import com.fs.his.mapper.FsUserCouponMapper;
@@ -81,6 +92,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;
@@ -204,6 +217,23 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     @Autowired
     private FsCourseRedPacketLogMapper redPacketLogMapper;
 
+    @Autowired
+    private IFsCoursePlaySourceConfigService fsCoursePlaySourceConfigService;
+
+    @Autowired
+    private AppCourseWatchLogMapper appCourseWatchLogMapper;
+    @Autowired
+    private FsAppCourseTrafficLogMapper fsAppCourseTrafficLogMapper;
+
+    @Autowired
+    private FsRewardGoodsMapper fsRewardGoodsMapper;
+
+    @Autowired
+    private IFsUserPortraitService fsUserPortraitService;
+
+    @Autowired
+    private FsCourseRewardMapper courseRewardMapper;
+
     @Autowired
     private FsCourseLinkMapper courseLinkMapper;
     @Autowired
@@ -1272,6 +1302,688 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
     }
 
 
+
+    @Override
+    public R updateWatchDurationApp(FsUserCourseVideoFinishUParam param) {
+        log.info("debugger node 1----------------------------------------");
+        //临时短链不做记录
+        if (param.getLinkType()!=null && param.getLinkType()==1){
+            return R.ok();
+        }
+        FsCourseLink fsCourseLink = courseLinkMapper.selectFsCourseLinkByLink(param.getLink());
+        if (fsCourseLink == null)return R.error("未查询到短链记录");
+
+        log.info("debugger node 0,{}", fsCourseLink);///后续可调整debug级别日志
+        log.info("debugger node 1,{}", param);
+
+        // 从Redis中获取观看时长
+        String redisKey = "h5user:watch:duration:app:" + fsCourseLink.getQwUserId()+ ":" + param.getUserId() + ":" + param.getVideoId();
+        try {
+            String durationStr = redisCache.getCacheObject(redisKey);
+            Long duration = StringUtils.isNotEmpty(durationStr)   ? Long.parseLong(durationStr) : 0L;
+            // 更新Redis中的观看时长
+            if (param.getDuration() != null && param.getDuration() > duration) {
+                //24小时过期
+                redisCache.setCacheObject(redisKey, param.getDuration().toString(),1,TimeUnit.HOURS);
+                redisCache.addToSetRaw(CourseConstant.WATCH_DURATION_APP_ACTIVE_KEYS, redisKey);
+                redisCache.expire(CourseConstant.WATCH_DURATION_APP_ACTIVE_KEYS, 3, TimeUnit.HOURS);
+            }
+            updateHeartbeatApp(String.valueOf(fsCourseLink.getQwUserId()), param);
+            return R.ok();
+        }catch (Exception e){
+            e.printStackTrace();
+            logger.error("更新时长失败:{}",redisKey,e.getMessage());
+            return R.error();
+        }
+
+    }
+
+    public void updateHeartbeatApp(String customerId, FsUserCourseVideoFinishUParam param) {
+        String redisKey = "h5user:watch:heartbeat:app:" + customerId + ":" + param.getUserId() + ":" + param.getVideoId();
+        redisCache.setCacheObject(redisKey, LocalDateTime.now().toString());
+        redisCache.expire(redisKey, 120, TimeUnit.SECONDS);
+        redisCache.addToSetRaw(CourseConstant.WATCH_HEARTBEAT_APP_ACTIVE_KEYS, redisKey);
+        redisCache.expire(CourseConstant.WATCH_HEARTBEAT_APP_ACTIVE_KEYS, 3, TimeUnit.HOURS);
+    }
+
+    @Override
+    public R isAddKfApp(FsUserCourseVideoAddKfUParam param) {
+        logger.info("zyp \n【判断添加客服】:{}", param);
+        //查询用户
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        //用户不存在唤起重新授权
+        if (fsUser == null) {
+            return R.error(401, "会员不存在");
+        }
+        if (fsUser.getStatus() == 0) {
+            return R.error("会员被停用,无权限,请联系客服!");
+        }
+
+        // 从数据库中查找短链对应的真实链接
+        FsCourseLink courseLink = courseLinkMapper.selectFsCourseLinkByLink(param.getLink());
+        if (courseLink != null) {
+            // 获取当前时间
+            Date currentTime = new Date();
+            // 获取链接的更新时间(假设这个字段代表过期时间)
+            Date updateTime = courseLink.getUpdateTime();
+
+            // 判断是否过期
+            if (currentTime.after(updateTime)) {
+                // 链接已过期
+                return R.error("链接已过期");
+            } else {
+                return R.ok();
+            }
+        }
+
+//        else if (param.getLinkType()!=null&&param.getLinkType()==4){
+//        }
+        return R.error("未添加客服");
+    }
+
+    @Override
+    public R sendRewardV2App(FsCourseSendRewardUParam param) {
+        log.debug("sendRewardV2 param: {}", JSON.toJSONString(param));
+
+        FsUser user = fsUserMapper.selectFsUserByUserId(param.getUserId());
+        if (user.getStatus()==0){
+            return R.error("会员被停用,无权限,请联系客服!");
+        }
+
+        // 小程序和app都不能领取红包
+        if (param.getRewardType() == 1) {
+            return R.error("暂不支持红包奖励!");
+        }
+
+        // 小程序领取奖励选项仅展示积分,app领取奖励选项根据课程配置展示积分、转盘、保底转盘
+        if (param.getSource() != 3 && param.getRewardType() != 2) {
+            return R.error("不支持的奖励类型!");
+        }
+
+        FsCoursePlaySourceConfig fsCoursePlaySourceConfig = fsCoursePlaySourceConfigService.selectCoursePlaySourceConfigByAppId(param.getAppId());
+
+        if (ObjectUtil.isNotEmpty(fsCoursePlaySourceConfig)) {
+            param.setSource(fsCoursePlaySourceConfig.getType().equals(1)?2:1);
+        }
+        if (param.getSource()==1&&StringUtils.isEmpty(user.getMpOpenId())){
+            return R.error(401,"请重新授权!");
+        }
+        AppCourseWatchLog watchLog = appCourseWatchLogMapper.selectOne(new LambdaQueryWrapper<AppCourseWatchLog>().eq(AppCourseWatchLog::getUserId, param.getUserId()).eq(AppCourseWatchLog::getVideoId, param.getVideoId())
+                .eq(AppCourseWatchLog::getAppCustomerId, param.getAppCustomerId()));
+//        FsCourseWatchLog watchLog = appCourseWatchLogMapper.getWatchCourseVideo(param.getUserId(),param.getVideoId(),param.getQwUserId(),param.getQwExternalId());
+        if (Objects.isNull(watchLog)) {
+            return R.error("无记录");
+        }
+        if (Objects.nonNull(watchLog.getRewardType())){
+            return R.error("奖励已发放,请勿重复提交");
+        }
+
+        String jsonIn = configService.selectConfigByKey("his.integral");
+        IntegralConfig integralConfig = JSONUtil.toBean(jsonIn, IntegralConfig.class);
+        Integer todayTotalIntegral = fsUserIntegralLogsMapper.selectTodayTotalIntegralByType(33, user.getUserId());
+        Integer todayTotalReward = roundMapper.selectTodayTotalRewardByStatus(user.getUserId());
+        if (integralConfig.getWatchTeamPopularizationFangHuaRestrictions() != null && todayTotalIntegral >= integralConfig.getWatchTeamPopularizationFangHuaRestrictions()) {
+            throw new CustomException("您真棒,今天的观看团队科普选择直接获得积分领取已达到上限,明天再来吧");
+        }else if(integralConfig.getWatchTeamPopularizationRestrictions() != null && todayTotalReward >= integralConfig.getWatchTeamPopularizationRestrictions()) {
+            throw new CustomException("您真棒,今天的观看团队科普选择大礼品抽奖领取已达到上限,明天再来吧");
+        }
+
+        if(param.getRewardType()!=null&&param.getRewardType()>=1&&param.getRewardType()<=5){
+            watchLog.setRewardType(Long.valueOf(param.getRewardType()));
+            appCourseWatchLogMapper.updateById(watchLog);
+        }
+
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        switch (param.getRewardType()){
+            // 红包
+//            case 1:
+//                return sendRedPacket(param, user, getAmount(config, watchLog), watchLog);
+            // 积分
+            case 2:
+                return sendPointsApp(param, user, getIntegralApp(config, watchLog, param), watchLog);//todo 答题抽奖
+            // 转盘
+//            case 3:
+//                return drawApp(param, user, watchLog,4, config);
+//            // 保底转盘
+//            case 4:
+//                return drawApp(param, user, watchLog, 5,config);
+
+            case 5:
+                return drawApp(param, user, watchLog,6, config);
+            default:
+                return R.error("参数错误!");
+        }
+    }
+
+    /**
+     * app发送积分
+     */
+    private R sendPointsApp(FsCourseSendRewardUParam param, FsUser user, BigDecimal integral, AppCourseWatchLog watchLog) {
+        log.debug("发送积分 param: {}, user: {}, integral: {}, watchLog: {}",
+                JSON.toJSONString(param), JSON.toJSONString(user), integral, watchLog);
+        log.info("发送积分 userId:{}, watchLogId: {}, integral: {}", watchLog.getUserId(), watchLog.getLogId(), integral);
+
+        //增加积分
+        FsUser userMap=new FsUser();
+        userMap.setUserId(user.getUserId());
+        userMap.setIntegral(user.getIntegral() + integral.intValue());
+        // 增加可提现积分
+        userMap.setWithdrawIntegral(user.getWithdrawIntegral() + integral.intValue());
+        fsUserMapper.updateFsUser(userMap);
+        CompletableFuture.runAsync(() -> {
+            FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+            integralLogs.setIntegral(integral.longValue());
+            integralLogs.setUserId(user.getUserId());
+            integralLogs.setBalance(userMap.getIntegral());
+            integralLogs.setLogType(33);
+            integralLogs.setBusinessId(watchLog.getLogId().toString());
+            integralLogs.setCreateTime(new Date());
+            integralLogs.setNickName(user.getNickName());
+            integralLogs.setPhone(user.getPhone());
+            //asyncAddIntegralLogs.saveLogAsync(integralLogs);
+            iFsUserIntegralLogsService.insertFsUserIntegralLogsMySql(integralLogs);
+
+//            //转换红包app答题不发红包
+            FsCourseRedPacketLog redPacketLog = new FsCourseRedPacketLog();
+            redPacketLog.setCourseId(param.getCourseId());
+            redPacketLog.setOutBatchNo(integralLogs.getId().toString());
+            redPacketLog.setCompanyId(param.getCompanyId());
+            redPacketLog.setUserId(param.getUserId());
+            redPacketLog.setVideoId(param.getVideoId());
+            redPacketLog.setStatus(1);
+            redPacketLog.setQwUserId(param.getQwUserId());
+            redPacketLog.setCompanyUserId(param.getCompanyUserId());
+            redPacketLog.setCreateTime(new Date());
+            redPacketLog.setAmount(integral.divide(BigDecimal.valueOf(1000), 2, RoundingMode.DOWN));
+            redPacketLog.setRemark("点播答题领取积分转");
+            redPacketLog.setWatchLogId(watchLog.getLogId());
+            redPacketLogMapper.insertFsCourseRedPacketLog(redPacketLog);
+        });
+
+
+        return R.ok("奖励发放成功");
+    }
+
+    /**
+     * 获取发送积分数app
+     */
+    private BigDecimal getIntegralApp(CourseConfig config, AppCourseWatchLog watchLog, FsCourseSendRewardUParam param) {
+        BigDecimal integral;
+
+        // 小程序答题奖励选择芳华币强制领取芳华币为300,app答题奖励选择芳华币默认领取芳华币为500(可根据课程配置改变)
+        if (param.getSource() != 3) {
+            integral = BigDecimal.valueOf(config.getAnswerIntegral());
+        } else {
+            // 查询销售公司是否单独配置
+            FsCourseReward videoReward = videoRelationMapper.getRewardByCompanyIdAndVideoIdAndTypeApp(watchLog.getAppCustomerId(), watchLog.getVideoId(), 3);
+            log.debug("发送积分 videoReward: {}", JSON.toJSONString(videoReward));
+            if (Objects.nonNull(videoReward) && StringUtils.isNotBlank(videoReward.getActualRewards())) {
+                JSONObject jsonObject = JSON.parseObject(videoReward.getActualRewards());
+                integral = jsonObject.getBigDecimal("points");
+            } else {
+                integral = BigDecimal.valueOf(getAppAnswerIntegral(param.getUserId(),config));
+            }
+        }
+
+        return integral;
+    }
+
+    private Integer getAppAnswerIntegral(Long userId, CourseConfig config) {
+        FsUserPortrait user = fsUserPortraitService.getOneByUserId(String.valueOf(userId));
+        Integer defaultId = null;
+        if (user != null && StrUtil.isNotEmpty(user.getConsumptionLevel())){
+            String activityLevel = user.getConsumptionLevel();
+            if ("S+".equals(activityLevel)){
+                defaultId = config.getAppAnswerIntegralSU();
+            }else if ("S".equals(activityLevel)){
+                defaultId = config.getAppAnswerIntegralS();
+            }else if ("A".equals(activityLevel)){
+                defaultId = config.getAppAnswerIntegralA();
+            }else if ("B".equals(activityLevel)){
+                defaultId = config.getAppAnswerIntegralB();
+            }else if ("C".equals(activityLevel)){
+                defaultId = config.getAppAnswerIntegralC();
+            }else if ("D".equals(activityLevel)){
+                defaultId = config.getAppAnswerIntegralD();
+            }else if ("E".equals(activityLevel)){
+                defaultId = config.getAppAnswerIntegralE();
+            }else if ("F".equals(activityLevel)){
+                defaultId = config.getAppAnswerIntegralF();
+            }
+        }
+        if (defaultId == null){
+            log.error("用户消费水平等级为空:{} {}",userId,user);
+            defaultId = config.getAppAnswerIntegral();
+        }
+        return defaultId;
+    }
+
+    /**
+     * 抽奖
+     */
+    private R drawApp(FsCourseSendRewardUParam param, FsUser user, AppCourseWatchLog watchLog, Integer drawType, CourseConfig config) {
+        FsCourseReward rewardConfig = videoRelationMapper.getRewardByCompanyIdAndVideoIdAndTypeApp(watchLog.getAppCustomerId(), watchLog.getVideoId(), drawType);
+        String typeName = drawType == 4 ? "转盘" :
+                drawType ==  5 ? "保底转盘" : "大礼品";
+
+//        // 默认转盘
+//        if (drawType == 4 && Objects.isNull(rewardConfig)) {
+//            rewardConfig = courseRewardMapper.selectFsCourseRewardById(config.getDefaultSpinReward());
+//        }
+
+        // 默认大礼品
+        if (drawType == 6 && Objects.isNull(rewardConfig)) {
+            rewardConfig = courseRewardMapper.selectFsCourseRewardById(getDefaultGrandGiftId(param.getUserId(),config));
+        }
+
+        if (Objects.isNull(rewardConfig)) {
+            throw new CustomException("客服未配置"+ typeName +",请选择其他奖励类型");
+        }
+
+        if (StringUtils.isBlank(rewardConfig.getActualRewards())) {
+            throw new CustomException(typeName + "配置错误,请联系管理员#1");
+        }
+
+        Prize prize = null;
+        if (drawType == 6) {
+            String prizeStr = redisCache.getCacheObject(FsConstants.REDIS_GRAND_GIFT + user.getUserId());
+            if (StringUtils.isBlank(prizeStr)) {
+                throw new CustomException("连接超时,请刷新后重试");
+            }
+            prize = JSONObject.parseObject(prizeStr, Prize.class);
+        }
+//        else {
+//            // 解析JSON配置
+//            JSONArray jsonArray;
+//            try {
+//                jsonArray = JSON.parseArray(rewardConfig.getActualRewards());
+//            } catch (Exception e) {
+//                log.error("解析{}配置JSON失败", typeName, e);
+//                throw new CustomException(typeName + "配置错误,请联系管理员#2");
+//            }
+//
+//            if (jsonArray == null || jsonArray.isEmpty()) {
+//                throw new CustomException(typeName + "配置错误,请联系管理员#3");
+//            }
+//
+//            List<Prize> prizes = buildPrizesApp(jsonArray, param, rewardConfig.getId(), drawType);
+//            prize = LotteryUtil.draw(prizes);
+//            if (Objects.isNull(prize)) {
+//                throw new CustomException(typeName + "配置错误,请联系管理员#4");
+//            }
+//        }
+
+        // 配置关系
+        FsAppCourseRewardVideoRelation relation = videoRelationMapper.selectByCompanyIdAndVideoIdAndRewardIdApp(watchLog.getAppCustomerId(), watchLog.getVideoId(), rewardConfig.getId());
+
+        FsAppCourseRewardRound rewardRound = new FsAppCourseRewardRound();
+        rewardRound.setId(IdUtil.getSnowflake(0, 0).nextId());
+        rewardRound.setRewardId(rewardConfig.getId());
+        rewardRound.setUserId(user.getUserId());
+        rewardRound.setRewardType(rewardConfig.getRewardType());
+        rewardRound.setAppCustomerRoleId(watchLog.getAppCustomerId());
+        rewardRound.setActualRewards(prize.getAmount());
+        rewardRound.setCreateTime(new Date());
+        rewardRound.setStatus(1L);
+        rewardRound.setWatchId(watchLog.getLogId());
+        rewardRound.setRuleId(prize.getCode());
+        rewardRound.setRuleName(prize.getCodeName());
+        rewardRound.setRewardVideoRelationId(Objects.nonNull(relation) ? relation.getId() : null);
+
+        // 抽中奖励商品
+        if (prize.getType() == 5) {
+            FsRewardGoods fsRewardGoods = fsRewardGoodsMapper.selectById(Long.parseLong(prize.getGoodsId()));
+            rewardRound.setGoodsId(fsRewardGoods.getGoodsId());
+            rewardRound.setGoodsPrice(fsRewardGoods.getPrice());
+            // 减库存
+            int cnt = fsRewardGoodsMapper.reduceStock(fsRewardGoods.getGoodsId());
+            if (cnt <= 0) {
+                throw new CustomException("商品库存不足!");
+            }
+        }
+
+        roundMapper.insertFsCourseRewardRoundApp(rewardRound);
+
+        switch (prize.getType()) {
+//            case 1:
+//                return sendRedPacket(param, user, new BigDecimal(prize.getAmount()), watchLog).put("data", prize.getCode());
+            case 2:
+                return sendPointsApp(param, user, new BigDecimal(prize.getAmount()), watchLog).put("data", prize.getCode());
+            case 3:
+                return R.ok().put("data", prize.getCode());
+//            case 4:
+//                return sendCoupon(user.getUserId(), prize.getCouponId(), Integer.parseInt(prize.getAmount())).put("data", prize.getCode());
+            case 5:
+                Map<String, Object> result = new HashMap<>();
+                result.put("roundId", String.valueOf(rewardRound.getId()));
+                result.put("goodsId", prize.getGoodsId());
+                result.put("data", prize.getCode());
+                return R.ok(result);
+            default:
+                return R.error(typeName + "配置错误,请联系管理员#5");
+        }
+    }
+
+
+    @Override
+    public R getInternetTrafficApp(FsUserCourseVideoFinishUParam param) {
+        try {
+            FsAppCourseTrafficLog trafficLog = new FsAppCourseTrafficLog();
+//            FsCourseTrafficLog trafficLog = new FsCourseTrafficLog();
+            FsCourseLink fsCourseLink = fsCourseLinkMapper.selectFsCourseLinkByLink(param.getLink());
+            if (fsCourseLink == null){
+                return R.error("链接不存在");
+            }
+            trafficLog.setAppCustomerId(fsCourseLink.getQwUserId());
+            trafficLog.setCreateTime(new Date());
+            BeanUtils.copyProperties(param, trafficLog);
+
+            FsUserCourseVideo video = fsUserCourseVideoMapper.selectFsUserCourseVideoByVideoId(param.getVideoId());
+            if (video == null) {
+                return R.error("视频不存在");
+            }
+
+            // 限制bufferRate不超过100
+            BigDecimal bufferRate = param.getBufferRate();
+            if (bufferRate.compareTo(new BigDecimal("100")) > 0) {
+                bufferRate = new BigDecimal("100");
+            }
+
+            // 实际流量传输消耗是大于文件的,添加倍数计算流量消耗 配置 course.data.usage.multiple
+            BigDecimal multiple = new BigDecimal("1"); // 默认一倍
+            String config=configService.selectConfigByKey("course.data.usage.multiple");
+            if(StringUtils.isNotEmpty(config)){
+                multiple=new BigDecimal(config);
+            }
+
+            // 计算流量
+            BigDecimal result = bufferRate.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
+            BigDecimal longAsBigDecimal = BigDecimal.valueOf(video.getFileSize());
+            long roundedResult = result.multiply(longAsBigDecimal).multiply(multiple).setScale(0, RoundingMode.HALF_UP).longValue();
+            trafficLog.setInternetTraffic(roundedResult);
+
+            // 处理 UUID 为空的情况
+            if (StringUtils.isNotEmpty(trafficLog.getUuId())) {
+                // 直接插入或更新
+//                logger.error("zyp \n【插入或更新流量】:{}",trafficLog);
+                fsAppCourseTrafficLogMapper.insertOrUpdate(trafficLog);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            // 打印参数param和异常信息
+            logger.error("zyp \n【插入或更新流量失败】参数: {}, 错误信息:{}", param, e.getMessage(), e);
+            return R.error();
+        }
+        return R.ok();
+    }
+
+    private Long getDefaultGrandGiftId(Long userId,CourseConfig config) {
+        FsUserPortrait user = fsUserPortraitService.getOneByUserId(String.valueOf(userId));
+        Long defaultId = null;
+        if (user != null && StrUtil.isNotEmpty(user.getConsumptionLevel())){
+            String activityLevel = user.getConsumptionLevel();
+            if ("S+".equals(activityLevel)){
+                defaultId = config.getDefaultGrandGiftSU();
+            }else if ("S".equals(activityLevel)){
+                defaultId = config.getDefaultGrandGiftS();
+            }else if ("A".equals(activityLevel)){
+                defaultId = config.getDefaultGrandGiftA();
+            }else if ("B".equals(activityLevel)){
+                defaultId = config.getDefaultGrandGiftB();
+            }else if ("C".equals(activityLevel)){
+                defaultId = config.getDefaultGrandGiftC();
+            }else if ("D".equals(activityLevel)){
+                defaultId = config.getDefaultGrandGiftD();
+            }else if ("E".equals(activityLevel)){
+                defaultId = config.getDefaultGrandGiftE();
+            }else if ("F".equals(activityLevel)){
+                defaultId = config.getDefaultGrandGiftF();
+            }
+        }
+        if (defaultId == null){
+            defaultId = config.getDefaultGrandGift();
+        }
+        return defaultId;
+
+    }
+
+    @Override
+    public List<FsCourseRewardTypeVO> getVideoRewardTypesApp(AppCourseRewardTypeParam param) {
+        log.debug("获取课程可领取奖励类型 param: {}", JSON.toJSONString(param));
+
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        List<FsCourseRewardTypeVO> result = new ArrayList<>();
+
+        // 判断是否为APP端
+        boolean isApp = Objects.nonNull(param.getSource()) && param.getSource() == 3;
+
+        // 获取视频关联的积分奖励配置(仅APP端需要)
+//        FsCourseReward pointReward = isApp
+//                ? videoRelationMapper.getRewardByCompanyIdAndVideoIdAndTypeApp(param.getAppCustomerRoleId(), param.getVideoId(), 3)
+//                : null;
+//
+//        // 小程序答题奖励选择积分强制领取积分为300,app答题奖励选择积分默认领取积分为500(可根据课程配置改变)
+//        int integral = isApp
+//                ? Optional.ofNullable(config.getAppAnswerIntegral()).orElse(500)
+//                : Optional.ofNullable(config.getAnswerIntegral()).orElse(300);
+//        BigDecimal pointsExpected = integral > 0
+//                ? new BigDecimal(integral).divide(BigDecimal.valueOf(1000), 4, RoundingMode.DOWN)
+//                : BigDecimal.ZERO;
+//        String pointsText = "";
+//        if (Objects.nonNull(pointReward)) {
+//            pointsExpected = new BigDecimal(pointReward.getExpectedValue());
+//            pointsText = pointReward.getDescription();
+//        }
+//        result.add(new FsCourseRewardTypeVO("积分奖励", 2, pointsExpected, pointsText));
+
+        // 小程序仅展示积分奖励
+        if (!isApp) {
+            return result;
+        }
+
+
+        // 大礼品配置
+        FsCourseReward giftReward = Optional.ofNullable(
+                videoRelationMapper.getRewardByCompanyIdAndVideoIdAndTypeApp(param.getAppCustomerRoleId(), param.getVideoId(), 6)
+        ).orElseGet(() ->
+                Optional.ofNullable(getDefaultGrandGiftId(param.getUserId(),config))
+                        .map(courseRewardMapper::selectFsCourseRewardById)
+                        .orElse(null)
+        );
+        // 添加大礼品奖励
+        Optional.ofNullable(giftReward)
+                .ifPresent(r ->
+                        result.add(new FsCourseRewardTypeVO("开启好运气", 5, new BigDecimal(r.getExpectedValue()), r.getDescription()))
+                );
+
+        // 降序
+        result.sort(Comparator.comparing(FsCourseRewardTypeVO::getExpectedValue).reversed());
+
+        log.debug("获取课程可领取奖励类型返回 result: {}", JSON.toJSONString(result));
+        return result;
+    }
+
+    /**
+     * 构建大礼品奖品列表
+     */
+    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;
+    }
+
+    @Override
+    public JSONArray getVideoRewardRulesApp(FsCourseRewardRuleParam param) {
+        log.debug("获取课程奖励类型对应规则 param: {}", JSON.toJSONString(param));
+
+        List<Integer> types = Arrays.asList(
+//                3, 4,
+                5);
+        if (!types.contains(param.getType())) {
+            throw new CustomException("类型不正确");
+        }
+
+        FsCourseReward rewardConfig = videoRelationMapper.getRewardByCompanyIdAndVideoIdAndTypeApp(param.getAppCustomerId(), param.getVideoId(), param.getType() + 1);
+
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+//        // 默认转盘
+//        if (param.getType() == 3 && Objects.nonNull(config.getDefaultSpinReward()) && Objects.isNull(rewardConfig)) {
+//            rewardConfig = courseRewardMapper.selectFsCourseRewardById(config.getDefaultSpinReward());
+//        }
+
+        // 默认大礼品
+        if (param.getType() == 5 && Objects.nonNull(config.getDefaultGrandGift()) && Objects.isNull(rewardConfig)) {
+            rewardConfig = courseRewardMapper.selectFsCourseRewardById(getDefaultGrandGiftId(param.getUserId(),config));
+        }
+
+        if (Objects.isNull(rewardConfig) || StringUtils.isBlank(rewardConfig.getActualRewards())) {
+            throw new CustomException("配置错误,请联系管理员");
+        }
+
+        JSONArray jsonArray = JSON.parseArray(rewardConfig.getActualRewards());
+//        // 保底转盘特殊处理
+//        if (param.getType() == 4 && !jsonArray.isEmpty()) {
+//            // 一次性查出用户的所有已领取记录
+//            FsCourseRewardRound query = new FsCourseRewardRound();
+//            query.setUserId(param.getUserId());
+//            query.setCompanyId(param.getCompanyId());
+//            query.setRewardId(rewardConfig.getId());
+//            List<FsCourseRewardRound> rounds = roundMapper.selectFsCourseRewardRoundList(query);
+//
+//            // 把 ruleId 放到 Set 里方便判断
+//            Set<String> receivedCodes = rounds.stream()
+//                    .map(FsCourseRewardRound::getRuleId)
+//                    .collect(Collectors.toSet());
+//
+//            // 标记 jsonArray
+//            for (Object o : jsonArray) {
+//                JSONObject jsonObject = (JSONObject) o;
+//                String code = jsonObject.getString("code");
+//                jsonObject.put("isReceive", receivedCodes.contains(code));
+//            }
+//        }
+
+        // 大礼品
+        if (param.getType() == 5 && !jsonArray.isEmpty()) {
+            jsonArray = buildGrandGiftArray(jsonArray, param.getUserId());
+        }
+
+        // APP过滤现金奖励
+        if (Objects.nonNull(param.getSource()) && param.getSource() == 3) {
+            jsonArray.removeIf(o -> {
+                JSONObject jsonObject = (JSONObject) o;
+                return Objects.equals(jsonObject.getInteger("type"), 1);
+            });
+        }
+
+        log.debug("获取课程奖励类型对应规则返回 jsonArray: {}", JSON.toJSONString(jsonArray));
+        return jsonArray;
+    }
+
+    @Override
+    public FsCourseAnswerRewardVO getAnswerRewardConfigApp(Long videoId, Long appCustomerId) {
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+
+        FsCourseAnswerRewardVO vo = new FsCourseAnswerRewardVO();
+//        vo.setRedPacketRuleId(videoRelationMapper.getRewardIdByCompanyIdAndVideoIdAndType(appCustomerId, videoId, 2));
+//        vo.setPointRuleId(videoRelationMapper.getRewardIdByCompanyIdAndVideoIdAndType(appCustomerId, videoId, 3));
+//        vo.setTurntableRuleId(videoRelationMapper.getRewardIdByCompanyIdAndVideoIdAndType(appCustomerId, videoId, 4));
+//        vo.setTurntableGuaranteeRuleId(videoRelationMapper.getRewardIdByCompanyIdAndVideoIdAndType(appCustomerId, videoId, 5));
+        vo.setGrandGiftRuleId(videoRelationMapper.getRewardIdByCompanyIdAndVideoIdAndTypeApp(appCustomerId, videoId, 6));
+        vo.setDefaultRedPacketAmount(config.getRedPackageMoney());
+        vo.setDefaultPointNum(config.getAnswerIntegral());
+        vo.setDefaultAppPointNum(config.getAppAnswerIntegral());
+        vo.setDefaultTurntableRuleId(config.getDefaultSpinReward());
+        vo.setDefaultGrandGiftRuleId(config.getDefaultGrandGift());
+        return vo;
+    }
+
+    @Override
+    public void changeAnswerRewardConfigApp(FsCourseAnswerRewardParam param, Long appCustomerId) {
+        videoRelationMapper.deleteByTypesApp(param.getVideoId(), appCustomerId, Arrays.asList(2,3,4,5,6));
+
+//        if (Objects.nonNull(param.getRedPacketRuleId())) {
+//            FsCourseRewardVideoRelation relation = new FsCourseRewardVideoRelation();
+//            relation.setVideoSectionId(param.getVideoId());
+//            relation.setRewardId(param.getRedPacketRuleId());
+//            relation.setCompanyId(companyId);
+//            videoRelationMapper.insertFsCourseRewardVideoRelation(relation);
+//        }
+//
+//        if (Objects.nonNull(param.getPointRuleId())) {
+//            FsCourseRewardVideoRelation relation = new FsCourseRewardVideoRelation();
+//            relation.setVideoSectionId(param.getVideoId());
+//            relation.setRewardId(param.getPointRuleId());
+//            relation.setCompanyId(companyId);
+//            videoRelationMapper.insertFsCourseRewardVideoRelation(relation);
+//        }
+//
+//        if (Objects.nonNull(param.getTurntableRuleId())) {
+//            FsCourseRewardVideoRelation relation = new FsCourseRewardVideoRelation();
+//            relation.setVideoSectionId(param.getVideoId());
+//            relation.setRewardId(param.getTurntableRuleId());
+//            relation.setCompanyId(companyId);
+//            videoRelationMapper.insertFsCourseRewardVideoRelation(relation);
+//        }
+//
+//        if (Objects.nonNull(param.getTurntableGuaranteeRuleId())) {
+//            FsCourseRewardVideoRelation relation = new FsCourseRewardVideoRelation();
+//            relation.setVideoSectionId(param.getVideoId());
+//            relation.setRewardId(param.getTurntableGuaranteeRuleId());
+//            relation.setCompanyId(companyId);
+//            videoRelationMapper.insertFsCourseRewardVideoRelation(relation);
+//        }
+
+        if (Objects.nonNull(param.getGrandGiftRuleId())) {
+            FsAppCourseRewardVideoRelation relation = new FsAppCourseRewardVideoRelation();
+            relation.setVideoSectionId(param.getVideoId());
+            relation.setRewardId(param.getGrandGiftRuleId());
+            relation.setAppCustomerRoleId(appCustomerId);
+            videoRelationMapper.insertFsCourseRewardVideoRelationApp(relation);
+        }
+    }
+
+
     @Override
     public List<FsUserCourseVideoVO> selectFsUserCourseVideoListByCourseIdAndCompany(FsUserCourseVideoParam fsUserCourseVideo) {
         return fsUserCourseVideoMapper.selectFsUserCourseVideoListByCourseIdAndCompany(fsUserCourseVideo);
@@ -5395,6 +6107,77 @@ public class FsUserCourseVideoServiceImpl extends ServiceImpl<FsUserCourseVideoM
         }
     }
 
+    /**
+     * 构建奖品列表
+     */
+    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 = fsRewardGoodsMapper.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;
+    }
+
 
     /**
     * app手动看课的

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

@@ -6,10 +6,21 @@ 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, String goodsId) {
         this.type = type;
         this.amount = amount;
@@ -74,4 +85,12 @@ public class Prize {
     public void setGoodsId(String goodsId) {
         this.goodsId = goodsId;
     }
+
+    public String getCodeName() {
+        return codeName;
+    }
+
+    public void setCodeName(String codeName) {
+        this.codeName = codeName;
+    }
 }

+ 40 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseAnswerRewardVO.java

@@ -0,0 +1,40 @@
+package com.fs.course.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class FsCourseAnswerRewardVO {
+
+    @ApiModelProperty("红包配置")
+    private Long redPacketRuleId;
+
+    @ApiModelProperty("积分配置")
+    private Long pointRuleId;
+
+    @ApiModelProperty("转盘配置")
+    private Long turntableRuleId;
+
+    @ApiModelProperty("保底转盘配置")
+    private Long turntableGuaranteeRuleId;
+
+    @ApiModelProperty("大礼品配置")
+    private Long grandGiftRuleId;
+
+    @ApiModelProperty("默认红包金额")
+    private BigDecimal defaultRedPacketAmount;
+
+    @ApiModelProperty("默认积分数量")
+    private Integer defaultPointNum;
+
+    @ApiModelProperty("默认App积分数量")
+    private Integer defaultAppPointNum;
+
+    @ApiModelProperty("默认转盘")
+    private Long defaultTurntableRuleId;
+
+    @ApiModelProperty("默认大礼品")
+    private Long defaultGrandGiftRuleId;
+}

+ 15 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseRewardTypeVO.java

@@ -0,0 +1,15 @@
+package com.fs.course.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+@AllArgsConstructor
+public class FsCourseRewardTypeVO {
+    private String name;
+    private Integer value;
+    private BigDecimal expectedValue;
+    private String text;
+}

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

@@ -39,4 +39,5 @@ public class ErpOrder {
     String shopNo;
     String buyer_account;
     Boolean isIntegralOrder;
+    String remark;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/erp/domain/ErpOrderQuery.java

@@ -27,6 +27,8 @@ public class ErpOrderQuery {
     String receiver_mobile;
     String receiver_address;
     Integer delivery_state;
+    Integer o_id;
+    String so_id;
 
     // 发货时间
     Date consignTime;

+ 4 - 0
fs-service/src/main/java/com/fs/erp/dto/ErpOrderQueryRequert.java

@@ -3,6 +3,7 @@ package com.fs.erp.dto;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.util.List;
 
 @Data
 public class ErpOrderQueryRequert implements Serializable {
@@ -10,4 +11,7 @@ public class ErpOrderQueryRequert implements Serializable {
     Integer page_no;
     Integer page_size;
     String platform_code;
+    String shop_id;
+    Integer page_index;
+    List<Long> o_ids;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/erp/dto/ErpOrderQueryResponse.java

@@ -1,5 +1,6 @@
 package com.fs.erp.dto;
 
+import cn.hutool.core.annotation.Alias;
 import com.fs.erp.domain.ErpOrderQuery;
 import lombok.Data;
 
@@ -8,5 +9,10 @@ import java.util.List;
 @Data
 public class ErpOrderQueryResponse extends BaseResponse {
     List<ErpOrderQuery> orders;
+    @Alias("has_next")
+    private Boolean hasNext;
 
+    public boolean hasNextSafe() {
+        return Boolean.TRUE.equals(hasNext);
+    }
 }

+ 2 - 0
fs-service/src/main/java/com/fs/erp/http/JstErpHttpService.java

@@ -49,6 +49,8 @@ public interface JstErpHttpService {
      */
     ErpOrderResponseDTO upload(ShopOrderDTO dto);
 
+    ErpOrderResponseDTO batchUpload(java.util.List<ShopOrderDTO> dtos);
+
     /**
      * 订单查询(自有商城、跨境线下)
      * @param dto 请求参数

+ 10 - 0
fs-service/src/main/java/com/fs/erp/http/JstErpHttpServiceImpl.java

@@ -23,6 +23,7 @@ import org.springframework.stereotype.Service;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 @Slf4j
@@ -108,6 +109,15 @@ public class JstErpHttpServiceImpl implements JstErpHttpService {
         return parseResponse(response, new TypeReference<CommonResponse<ErpOrderResponseDTO>>() {});
     }
 
+    @Override
+    public ErpOrderResponseDTO batchUpload(List<ShopOrderDTO> dtos) {
+        String url = BASE_URL + "open/jushuitan/orders/upload";
+        log.info("批量上传订单信息 - URL: {}, 请求体: {}", url, JSON.toJSONString(dtos));
+
+        HttpResponse response = executeJsonPost(url, dtos);
+        return parseResponse(response, new TypeReference<CommonResponse<ErpOrderResponseDTO>>() {});
+    }
+
     @Override
     public OrderQueryResponseDTO query(OrderQueryRequestDTO dto) {
         String url = BASE_URL + "open/orders/single/query";

+ 45 - 0
fs-service/src/main/java/com/fs/erp/service/IJSTErpOrderService.java

@@ -0,0 +1,45 @@
+package com.fs.erp.service;
+
+import com.fs.erp.domain.ErpOrder;
+import com.fs.erp.domain.ErpRefundOrder;
+import com.fs.erp.dto.*;
+import com.fs.his.domain.FsStoreOrder;
+
+import java.util.List;
+
+public interface IJSTErpOrderService {
+    ErpOrderResponse addOrder(ErpOrder order);
+
+
+    ErpOrderResponse batchAddOrder(List<ErpOrder> orders) throws InterruptedException;
+
+    //用户发起退款,然后后台审核通过后,提交退款单
+    ErpOrderResponse refundOrder(ErpRefundOrder order);
+    ErpOrderResponse refundOrderScrm(ErpRefundOrder order);
+    ErpOrderResponse refundLiveOrder(ErpRefundOrder order);
+    ErpDeliverysResponse getDeliver(ErpDeliverysRequest param);
+    ErpOrderQueryResponse getOrder(ErpOrderQueryRequert param,String type);
+    BaseResponse refundUpdate(ErpRefundUpdateRequest param);
+
+    BaseResponse refundUpdateBySplitOrder(ErpRefundUpdateRequest param);
+
+    ErpOrderResponse finishOrder(ErpOrder order);
+    //代服管家查物流状态
+    void getOrderDeliveryStatus(FsStoreOrder order);
+
+    ErpOrderResponse batchAddIntegralOrder(List<ErpOrder> erpOrders) throws InterruptedException;
+    //购物车多个商品推送
+    ErpOrderResponse batchAddIntegralCartOrder(List<ErpOrder> erpOrders) throws InterruptedException;
+
+    BaseResponse refundIntegralOrderUpdate(ErpRefundUpdateRequest param);
+
+    /**
+     * 推送奖品订单
+     */
+    ErpOrderResponse batchAddRewardOrder(List<ErpOrder> erpOrders) throws InterruptedException;
+
+    /**
+     * 取消奖品订单
+     */
+    BaseResponse cancelRewardOrder(ErpRefundUpdateRequest param);
+}

+ 325 - 0
fs-service/src/main/java/com/fs/erp/service/impl/JSTErpRewardOrderServiceImpl.java

@@ -0,0 +1,325 @@
+package com.fs.erp.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+import com.fs.erp.constant.ErpQueryOrderStatusEnum;
+import com.fs.erp.constant.OrderStatusEnum;
+import com.fs.erp.domain.*;
+import com.fs.erp.dto.*;
+import com.fs.erp.http.JstErpHttpService;
+import com.fs.erp.service.IJSTErpOrderService;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.reward.domain.FsRewardGoods;
+import com.fs.reward.domain.FsRewardGoodsOrder;
+import com.fs.reward.mapper.FsRewardGoodsOrderMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.util.Asserts;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 奖品订单等 JST ERP 能力实现,委托现有 {@link JSTErpOrderServiceImpl} 处理通用订单逻辑。
+ */
+@Slf4j
+@Service
+public class JSTErpRewardOrderServiceImpl implements IJSTErpOrderService {
+
+    @Autowired
+    private JSTErpOrderServiceImpl jstErpOrderService;
+
+    @Autowired
+    private JstErpHttpService jstErpHttpService;
+
+    @Autowired
+    private FsRewardGoodsOrderMapper fsRewardGoodsOrderMapper;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Override
+    public ErpOrderResponse addOrder(ErpOrder order) {
+        return jstErpOrderService.addOrder(order);
+    }
+
+    @Override
+    public ErpOrderResponse batchAddOrder(List<ErpOrder> orders) throws InterruptedException {
+        throw new UnsupportedOperationException("batchAddOrder 暂未在当前项目实现");
+    }
+
+    @Override
+    public ErpOrderResponse refundOrder(ErpRefundOrder order) {
+        return jstErpOrderService.refundOrder(order);
+    }
+
+    @Override
+    public ErpOrderResponse refundOrderScrm(ErpRefundOrder order) {
+        return jstErpOrderService.refundOrderScrm(order);
+    }
+
+    @Override
+    public ErpOrderResponse refundLiveOrder(ErpRefundOrder order) {
+        return jstErpOrderService.refundLiveOrder(order);
+    }
+
+    @Override
+    public ErpDeliverysResponse getDeliver(ErpDeliverysRequest param) {
+        return jstErpOrderService.getDeliver(param);
+    }
+
+    @Override
+    public ErpOrderQueryResponse getOrder(ErpOrderQueryRequert param, String type) {
+        OrderQueryRequestDTO requestDTO = new OrderQueryRequestDTO();
+        if (StringUtils.isNotEmpty(param.getCode())) {
+            requestDTO.setOIds(Collections.singletonList(Long.valueOf(param.getCode())));
+        } else {
+            requestDTO.setOIds(param.getO_ids());
+            requestDTO.setShopId(Integer.parseInt(param.getShop_id()));
+            requestDTO.setPageIndex(param.getPage_index());
+            requestDTO.setPageSize(param.getPage_size());
+        }
+
+        OrderQueryResponseDTO query = jstErpHttpService.query(requestDTO);
+        ErpOrderQueryResponse response = new ErpOrderQueryResponse();
+
+        if (query.getOrders() != null && !query.getOrders().isEmpty()) {
+            List<ErpOrderQuery> erpOrders;
+            if ("reward".equals(type)) {
+                erpOrders = query.getOrders().stream()
+                        .map(this::convertToErpRewardOrderQuery)
+                        .collect(Collectors.toList());
+            } else {
+                throw new UnsupportedOperationException("暂不支持的订单查询类型: " + type);
+            }
+            response.setOrders(erpOrders);
+            response.setHasNext(query.getHasNext());
+        } else {
+            response.setOrders(Collections.emptyList());
+        }
+
+        return response;
+    }
+
+    @Override
+    public BaseResponse refundUpdate(ErpRefundUpdateRequest param) {
+        return jstErpOrderService.refundUpdate(param);
+    }
+
+    @Override
+    public BaseResponse refundUpdateBySplitOrder(ErpRefundUpdateRequest param) {
+        throw new UnsupportedOperationException("refundUpdateBySplitOrder 暂未在当前项目实现");
+    }
+
+    @Override
+    public ErpOrderResponse finishOrder(ErpOrder order) {
+        return jstErpOrderService.finishOrder(order);
+    }
+
+    @Override
+    public void getOrderDeliveryStatus(FsStoreOrder order) {
+        jstErpOrderService.getOrderDeliveryStatus(order);
+    }
+
+    @Override
+    public ErpOrderResponse batchAddIntegralOrder(List<ErpOrder> erpOrders) throws InterruptedException {
+        throw new UnsupportedOperationException("batchAddIntegralOrder 暂未在当前项目实现");
+    }
+
+    @Override
+    public ErpOrderResponse batchAddIntegralCartOrder(List<ErpOrder> erpOrders) throws InterruptedException {
+        throw new UnsupportedOperationException("batchAddIntegralCartOrder 暂未在当前项目实现");
+    }
+
+    @Override
+    public BaseResponse refundIntegralOrderUpdate(ErpRefundUpdateRequest param) {
+        throw new UnsupportedOperationException("refundIntegralOrderUpdate 暂未在当前项目实现");
+    }
+
+    @Override
+    public ErpOrderResponse batchAddRewardOrder(List<ErpOrder> erpOrders) throws InterruptedException {
+        List<ShopOrderDTO> normalOrders = new ArrayList<>();
+        for (ErpOrder order : erpOrders) {
+            Wrapper<FsRewardGoodsOrder> queryWrapper = Wrappers.<FsRewardGoodsOrder>lambdaQuery()
+                    .eq(FsRewardGoodsOrder::getOrderSn, order.getPlatform_code());
+            FsRewardGoodsOrder rewardGoodsOrder = fsRewardGoodsOrderMapper.selectOne(queryWrapper);
+
+            ShopOrderDTO shopOrderDTO = new ShopOrderDTO();
+            shopOrderDTO.setShopId(Long.valueOf(order.getShop_code()));
+            shopOrderDTO.setSoId(order.getPlatform_code());
+            shopOrderDTO.setOrderDate(order.getDeal_datetime());
+            shopOrderDTO.setShopStatus(OrderStatusEnum.WAIT_SELLER_SEND_GOODS.name());
+            shopOrderDTO.setShopBuyerId(order.getBuyer_account());
+            shopOrderDTO.setReceiverState(order.getReceiver_province());
+            shopOrderDTO.setReceiverCity(order.getReceiver_city());
+            shopOrderDTO.setReceiverDistrict(order.getReceiver_district());
+            shopOrderDTO.setReceiverAddress(order.getReceiver_address());
+            shopOrderDTO.setReceiverName(order.getReceiver_name());
+            shopOrderDTO.setReceiverPhone(order.getReceiver_mobile());
+            shopOrderDTO.setPayAmount(rewardGoodsOrder.getPayMoney().doubleValue());
+            shopOrderDTO.setRemark(order.getRemark());
+            shopOrderDTO.setFreight(0.0);
+            shopOrderDTO.setBuyerMessage(order.getBuyer_memo());
+
+            FsRewardGoods fsRewardGoods = JSON.parseObject(rewardGoodsOrder.getGoodsJson(), FsRewardGoods.class);
+            List<OrderItemDTO> itemDTOList = new ArrayList<>();
+            OrderItemDTO orderItemDTO = new OrderItemDTO();
+            orderItemDTO.setSkuId(fsRewardGoods.getStoreGoodsSn());
+            orderItemDTO.setShopSkuId(fsRewardGoods.getStoreGoodsSn());
+            orderItemDTO.setName(fsRewardGoods.getGoodsName());
+            orderItemDTO.setShopIId(orderItemDTO.getSkuId());
+            orderItemDTO.setPropertiesValue("默认");
+            orderItemDTO.setAmount(fsRewardGoods.getPrice());
+            orderItemDTO.setPic(fsRewardGoods.getGoodsImg());
+            orderItemDTO.setPrice(fsRewardGoods.getPrice());
+            orderItemDTO.setQty(1);
+            orderItemDTO.setOuterOiId(String.format("%s%s", rewardGoodsOrder.getOrderSn(), rewardGoodsOrder.getRoundId()));
+            itemDTOList.add(orderItemDTO);
+            shopOrderDTO.setItems(itemDTOList);
+
+            PaymentDTO paymentDTO = new PaymentDTO();
+            paymentDTO.setAmount(rewardGoodsOrder.getPayMoney().doubleValue());
+            paymentDTO.setOuterPayId(order.getPlatform_code());
+            paymentDTO.setPayDate(order.getDeal_datetime());
+            paymentDTO.setPayment("微信支付");
+            paymentDTO.setBuyerAccount(order.getBuyer_account());
+            paymentDTO.setSellerAccount("平台销售");
+            shopOrderDTO.setPay(paymentDTO);
+
+            normalOrders.add(shopOrderDTO);
+        }
+
+        int batchSize = 50;
+        ErpOrderResponseDTO lastResponse = null;
+        for (int i = 0; i < normalOrders.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, normalOrders.size());
+            List<ShopOrderDTO> batch = new ArrayList<>(normalOrders.subList(i, endIndex));
+            lastResponse = jstErpHttpService.batchUpload(batch);
+            List<ErpOrderResponseDTO.OrderData> datas = lastResponse.getDatas();
+            for (ErpOrderResponseDTO.OrderData data : datas) {
+                if (data != null && data.getIssuccess()) {
+                    Wrapper<FsRewardGoodsOrder> queryWrapper = Wrappers.<FsRewardGoodsOrder>lambdaQuery()
+                            .eq(FsRewardGoodsOrder::getOrderSn, data.getSoId());
+                    FsRewardGoodsOrder rewardGoodsOrder = fsRewardGoodsOrderMapper.selectOne(queryWrapper);
+                    if (rewardGoodsOrder != null && StringUtils.isEmpty(rewardGoodsOrder.getExtendOrderSn())) {
+                        redisCache.setCacheObject("delivery" + ":" + data.getOId(), rewardGoodsOrder.getOrderSn());
+                        rewardGoodsOrder.setExtendOrderSn(data.getOId().toString());
+                        rewardGoodsOrder.setUpdateTime(LocalDateTime.now());
+                        rewardGoodsOrder.setUpdateBy("ERP Push");
+                        fsRewardGoodsOrderMapper.updateById(rewardGoodsOrder);
+                    }
+                }
+            }
+            log.info("批量推送奖品订单,数量: {}", batch.size());
+            Thread.sleep(600);
+        }
+
+        if (lastResponse != null && CollectionUtils.isEmpty(lastResponse.getDatas())) {
+            log.info("推送ERP返回结果: {}", lastResponse);
+            throw new IllegalArgumentException("推送ERP返回数不应该为0");
+        }
+
+        if (lastResponse == null) {
+            lastResponse = new ErpOrderResponseDTO();
+            ErpOrderResponseDTO.OrderData data = new ErpOrderResponseDTO.OrderData();
+            data.setOId(0);
+            lastResponse.setDatas(Collections.singletonList(data));
+        }
+
+        ErpOrderResponseDTO.OrderData orderData = lastResponse.getDatas().get(0);
+
+        ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
+        erpOrderResponse.setSuccess(true);
+        erpOrderResponse.setCode(String.valueOf(orderData.getOId()));
+        return erpOrderResponse;
+    }
+
+    @Override
+    public BaseResponse cancelRewardOrder(ErpRefundUpdateRequest param) {
+        OrderQueryRequestDTO queryRequestDTO = new OrderQueryRequestDTO();
+        queryRequestDTO.setSoIds(Collections.singletonList(String.valueOf(param.getTid())));
+        OrderQueryResponseDTO query = jstErpHttpService.query(queryRequestDTO);
+        if (query != null && query.getOrders().get(0).getStatus().equals("Cancelled")) {
+            BaseResponse baseResponse = new BaseResponse();
+            baseResponse.setSuccess(true);
+            return baseResponse;
+        }
+
+        OrderCancelRequestDTO requestDTO = new OrderCancelRequestDTO();
+        requestDTO.setOIds(Collections.singletonList(Integer.valueOf(param.getOid())));
+        requestDTO.setCancelType("后台取消");
+        requestDTO.setRemark("后台取消");
+        CommonResponse cancel = jstErpHttpService.cancel(requestDTO);
+
+        BaseResponse baseResponse = new BaseResponse();
+        baseResponse.setSuccess(cancel.getCode() == 0);
+        return baseResponse;
+    }
+
+    private ErpOrderQuery convertToErpRewardOrderQuery(OrderQueryResponseDTO.Order order) {
+        ErpOrderQuery erpOrder = new ErpOrderQuery();
+
+        Wrapper<FsRewardGoodsOrder> queryWrapper = Wrappers.<FsRewardGoodsOrder>lambdaQuery()
+                .eq(FsRewardGoodsOrder::getOrderSn, order.getSoId());
+        FsRewardGoodsOrder rewardGoodsOrder = fsRewardGoodsOrderMapper.selectOne(queryWrapper);
+        Asserts.notNull(rewardGoodsOrder, "该订单号没有找到!");
+
+        erpOrder.setCode(order.getSoId());
+
+        if (order.getItems() != null && !order.getItems().isEmpty()) {
+            int totalQty = order.getItems().stream()
+                    .mapToInt(OrderQueryResponseDTO.OrderItem::getQty)
+                    .sum();
+            erpOrder.setQty(totalQty);
+        }
+        erpOrder.setAmount(order.getAmount() != null ? order.getAmount().doubleValue() : null);
+        erpOrder.setPayment(order.getPaidAmount() != null ? order.getPaidAmount().doubleValue() : null);
+        erpOrder.setCod(order.getIsCod());
+        erpOrder.setPlatform_code(order.getOrderFrom());
+
+        try {
+            if (order.getCreated() != null) {
+                SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+                erpOrder.setCreatetime(formatter.parse(order.getCreated()));
+            }
+        } catch (Exception e) {
+            // ignore parse error
+        }
+
+        erpOrder.setShop_name(order.getShopName());
+        erpOrder.setShop_code(String.valueOf(order.getShopId()));
+        erpOrder.setExpress_name(order.getLogisticsCompany());
+        erpOrder.setExpress_code(order.getLId());
+        erpOrder.setReceiver_name(rewardGoodsOrder.getUserName());
+        erpOrder.setReceiver_phone(rewardGoodsOrder.getMobile());
+        erpOrder.setReceiver_mobile(rewardGoodsOrder.getMobile());
+        erpOrder.setReceiver_address(rewardGoodsOrder.getAddress());
+
+        if (ObjectUtil.equal(order.getStatus(), ErpQueryOrderStatusEnum.SENT.getCode())) {
+            List<ErpDeliverys> deliverysList = new ArrayList<>();
+            ErpDeliverys delivery = new ErpDeliverys();
+            delivery.setMail_no(order.getLId());
+            delivery.setExpress_code(order.getLcId());
+            delivery.setExpress_name(order.getLogisticsCompany());
+            delivery.setDelivery(true);
+            delivery.setCode(order.getSoId());
+            deliverysList.add(delivery);
+            erpOrder.setDeliverys(deliverysList);
+            erpOrder.setDelivery_state(1);
+        } else {
+            erpOrder.setDelivery_state(0);
+        }
+
+        return erpOrder;
+    }
+}

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

@@ -54,4 +54,11 @@ public class IntegralConfig implements Serializable {
 
     /** 积分流水类型展示端配置,key=logType(dict_value),value=app / mini / app,mini */
     private Map<String, String> logTypeDisplayScope;
+
+    //观看团队科普选择直接获得芳华币限制数量 33
+    private Integer WatchTeamPopularizationFangHuaRestrictions;
+
+    //观看团队科普选择大礼品抽奖限制次数
+    private Integer WatchTeamPopularizationRestrictions;
+
 }

+ 8 - 0
fs-service/src/main/java/com/fs/his/domain/FsStorePayment.java

@@ -100,6 +100,14 @@ public class FsStorePayment extends BaseEntity
     //小程序appId
     private String appId;
 
+    public String getAppid() {
+        return appId;
+    }
+
+    public void setAppid(String appid) {
+        this.appId = appid;
+    }
+
     private Long merConfigId;
 
     @JsonFormat(pattern = "yyyy-MM-dd")

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

@@ -346,4 +346,6 @@ public interface FsStorePaymentMapper
      */
     @Select("SELECT * FROM fs_store_payment WHERE `status` = 1 AND DATE(share_time) = CURDATE() AND is_share = 1 AND share_status = 0")
     List<FsStorePayment> selectSharePaymentList();
+
+    List<FsStorePayment> selectByBusinessTypeAndBusinessCode(@Param("businessType") int businessType, @Param("businessCode") String businessCode);
 }

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

@@ -176,4 +176,13 @@ public interface FsUserIntegralLogsMapper
             "  AND create_time < CURDATE() " +
             "GROUP BY log_type, DATE(create_time)")
     List<FsIntegralCount> selectYesterdayIntegralGroupByLogType();
+
+    @Select({"<script> " +
+            "SELECT COALESCE(SUM(integral), 0) AS todayTotalIntegral FROM fs_user_integral_logs " +
+            "WHERE user_id = #{userId} " +
+            "AND log_type = #{logType} " +
+            "AND create_time <![CDATA[>=]]> CURDATE() " +
+            "AND create_time <![CDATA[<]]> DATE_ADD(CURDATE(), INTERVAL 1 DAY) " +
+            "</script>"})
+    Integer selectTodayTotalIntegralByType(@Param("logType") Integer logType, @Param("userId") Long userId);
 }

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

@@ -89,4 +89,7 @@ public interface IFsUserIntegralLogsService
     R getNewcomerBenefits(Long userId);
 
     List<FsIntegralCount> selectYesterdayIntegralGroupByLogType();
+
+    int insertFsUserIntegralLogsMySql(FsUserIntegralLogs fsUserIntegralLogs);
+
 }

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

@@ -2374,4 +2374,9 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
         }
     }
 
+    @Override
+    public List<FsStorePayment> selectByBusinessTypeAndBusinessCode(int businessType, String businessCode) {
+        return fsStorePaymentMapper.selectByBusinessTypeAndBusinessCode(businessType, businessCode);
+    }
+
 }

+ 9 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsUserIntegralLogsServiceImpl.java

@@ -636,4 +636,13 @@ public class FsUserIntegralLogsServiceImpl implements IFsUserIntegralLogsService
     public List<FsIntegralCount> selectYesterdayIntegralGroupByLogType() {
         return fsUserIntegralLogsMapper.selectYesterdayIntegralGroupByLogType();
     }
+
+    @Override
+    public int insertFsUserIntegralLogsMySql(FsUserIntegralLogs fsUserIntegralLogs) {
+        fsUserIntegralLogs.setCreateTime(DateUtils.getNowDate());
+        int i = fsUserIntegralLogsMapper.insertFsUserIntegralLogs(fsUserIntegralLogs);
+//        fsUserIntegralLogsMapper.insertFsUserIntegralLogsClickHouse(fsUserIntegralLogs);
+        return i;
+    }
+
 }

+ 29 - 8
fs-service/src/main/java/com/fs/huifuPay/domain/HuiFuCreateOrder.java

@@ -1,6 +1,9 @@
 package com.fs.huifuPay.domain;
 
+import lombok.AccessLevel;
 import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
 
 @Data
 public class HuiFuCreateOrder {
@@ -14,12 +17,30 @@ public class HuiFuCreateOrder {
 //    T_H5:微信直连H5支付
 //    T_APP:微信APP支付(只支持直连)
 //    T_NATIVE:微信正扫(只支持直连)
-    String tradeType;
-    String reqSeqId;
-    String transAmt;
-    String goodsDesc;
-    String openid;
-    String appId; //多小程序支付
-    String acctSplitBunch; //公司分账参数
-    String delayAcctFlag; //延时交易 延迟分账需要
+    private String tradeType;
+    private String reqSeqId;
+    private String transAmt;
+    private String goodsDesc;
+    private String openid;
+    @Getter(AccessLevel.NONE)
+    @Setter(AccessLevel.NONE)
+    private String appId;
+    private String acctSplitBunch;
+    private String delayAcctFlag;
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getAppid() {
+        return appId;
+    }
+
+    public void setAppid(String appid) {
+        this.appId = appid;
+    }
 }

+ 8 - 0
fs-service/src/main/java/com/fs/huifuPay/sdk/opps/core/request/V2TradePaymentScanpayRefundRequest.java

@@ -139,4 +139,12 @@ public class V2TradePaymentScanpayRefundRequest extends BaseRequest {
     public void setAppId(String appId) {
         this.appId = appId;
     }
+
+    public String getAppid() {
+        return appId;
+    }
+
+    public void setAppid(String appid) {
+        this.appId = appid;
+    }
 }

+ 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 {
+    /**
+     * 微信支付方式
+     */
+    String WX = "wx";
+
+    /**
+     * 易宝支付
+     */
+    String YB = "yb";
+
+    /**
+     * 台州银行
+     */
+    String TZ_BANK = "tz";
+
+    /**
+     * 汇付
+     */
+    String HF = "hf";
+}

+ 70 - 0
fs-service/src/main/java/com/fs/pay/service/IPaymentMiniProgramConfigService.java

@@ -0,0 +1,70 @@
+package com.fs.pay.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.pay.domain.PaymentMiniProgramConfig;
+
+/**
+ * 多小程序支付配置Service接口
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+public interface IPaymentMiniProgramConfigService extends IService<PaymentMiniProgramConfig>{
+    /**
+     * 查询多小程序支付配置
+     *
+     * @param id 多小程序支付配置主键
+     * @return 多小程序支付配置
+     */
+    PaymentMiniProgramConfig selectPaymentMiniProgramConfigById(String id);
+
+    /**
+     * 通过appid查询支付配置
+     * @param appId 小程序appid
+     * @return PaymentMiniProgramConfig
+     */
+    PaymentMiniProgramConfig selectPaymentConfigByAppId(String appId);
+
+    /**
+     * 查询多小程序支付配置列表
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 多小程序支付配置集合
+     */
+    List<PaymentMiniProgramConfig> selectPaymentMiniProgramConfigList(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 新增多小程序支付配置
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 结果
+     */
+    int insertPaymentMiniProgramConfig(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 修改多小程序支付配置
+     *
+     * @param paymentMiniProgramConfig 多小程序支付配置
+     * @return 结果
+     */
+    int updatePaymentMiniProgramConfig(PaymentMiniProgramConfig paymentMiniProgramConfig);
+
+    /**
+     * 批量删除多小程序支付配置
+     *
+     * @param ids 需要删除的多小程序支付配置主键集合
+     * @return 结果
+     */
+    int deletePaymentMiniProgramConfigByIds(String[] ids);
+
+    /**
+     * 删除多小程序支付配置信息
+     *
+     * @param id 多小程序支付配置主键
+     * @return 结果
+     */
+    int deletePaymentMiniProgramConfigById(String id);
+
+
+}

+ 187 - 0
fs-service/src/main/java/com/fs/pay/service/impl/PaymentMiniProgramConfigServiceImpl.java

@@ -0,0 +1,187 @@
+package com.fs.pay.service.impl;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.pay.domain.PaymentMiniProgramConfig;
+import com.fs.pay.mapper.PaymentMiniProgramConfigMapper;
+import com.fs.pay.service.IPaymentMiniProgramConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 多小程序支付配置Service业务层处理
+ *
+ * @author fs
+ * @date 2025-11-05
+ */
+@Service
+public class PaymentMiniProgramConfigServiceImpl extends ServiceImpl<PaymentMiniProgramConfigMapper, PaymentMiniProgramConfig> implements IPaymentMiniProgramConfigService {
+
+    private static final Logger logger = LoggerFactory.getLogger(PaymentMiniProgramConfigServiceImpl.class);
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /** 支付小程序配置缓存key前缀 */
+    private static final String PAYMENT_MINI_CONFIG_CACHE_PREFIX = "payment:mini:config:appid:";
+
+    /** 缓存过期时间:24小时 */
+    private static final long CACHE_EXPIRE_TIME = 24 * 60 * 60;
+
+    private String getCacheKey(String appId) {
+        return PAYMENT_MINI_CONFIG_CACHE_PREFIX + appId;
+    }
+
+    private PaymentMiniProgramConfig getFromCache(String appId) {
+        try {
+            String cacheKey = getCacheKey(appId);
+            Object cacheValue = redisTemplate.opsForValue().get(cacheKey);
+            if (cacheValue != null) {
+                if (cacheValue instanceof PaymentMiniProgramConfig) {
+                    return (PaymentMiniProgramConfig) cacheValue;
+                }
+            }
+            return null;
+        } catch (Exception e) {
+            logger.error("从缓存获取支付配置异常,appId: {}", appId, e);
+            return null;
+        }
+    }
+
+    private void setCache(String appId, PaymentMiniProgramConfig config) {
+        try {
+            if (config != null) {
+                String cacheKey = getCacheKey(appId);
+                redisTemplate.opsForValue().set(cacheKey, config, CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
+            }
+        } catch (Exception e) {
+            logger.error("设置支付配置缓存异常,appId: {}", appId, e);
+        }
+    }
+
+    private void deleteCache(String appId) {
+        try {
+            String cacheKey = getCacheKey(appId);
+            redisTemplate.delete(cacheKey);
+        } catch (Exception e) {
+            logger.error("删除支付配置缓存异常,appId: {}", appId, e);
+        }
+    }
+
+    @Override
+    public PaymentMiniProgramConfig selectPaymentMiniProgramConfigById(String id) {
+        return baseMapper.selectPaymentMiniProgramConfigById(id);
+    }
+
+    @Override
+    public PaymentMiniProgramConfig selectPaymentConfigByAppId(String appId) {
+        if (appId == null || appId.trim().isEmpty()) {
+            return null;
+        }
+
+        PaymentMiniProgramConfig config = getFromCache(appId);
+        if (config != null) {
+            logger.debug("从缓存获取支付配置,appId: {}", appId);
+            if(ObjectUtil.equal(config.getStatus(),0)){
+                throw new UnsupportedOperationException("当前支付方式已停用!");
+            }
+            return config;
+        }
+
+        config = baseMapper.selectPaymentConfigByAppId(appId);
+        if(ObjectUtil.isNull(config)){
+            throw new IllegalArgumentException(String.format("当前小程序 %s 支付方式未找到!",appId));
+        }
+        if (config != null) {
+            setCache(appId, config);
+            logger.debug("支付配置已缓存,appId: {}", appId);
+        }
+
+        if(ObjectUtil.equal(config.getStatus(),0)){
+            throw new UnsupportedOperationException("当前支付方式已停用!");
+        }
+        return config;
+    }
+
+    @Override
+    public List<PaymentMiniProgramConfig> selectPaymentMiniProgramConfigList(PaymentMiniProgramConfig paymentMiniProgramConfig) {
+        return baseMapper.selectPaymentMiniProgramConfigList(paymentMiniProgramConfig);
+    }
+
+    @Override
+    public int insertPaymentMiniProgramConfig(PaymentMiniProgramConfig paymentMiniProgramConfig) {
+        paymentMiniProgramConfig.setCreateTime(DateUtils.getNowDate());
+        int result = baseMapper.insertPaymentMiniProgramConfig(paymentMiniProgramConfig);
+
+        if (result > 0 && paymentMiniProgramConfig.getAppid() != null) {
+            logger.info("新增支付配置,appId: {}", paymentMiniProgramConfig.getAppid());
+        }
+
+        return result;
+    }
+
+    @Override
+    public int updatePaymentMiniProgramConfig(PaymentMiniProgramConfig paymentMiniProgramConfig) {
+        paymentMiniProgramConfig.setUpdateTime(DateUtils.getNowDate());
+        int result = baseMapper.updatePaymentMiniProgramConfig(paymentMiniProgramConfig);
+
+        if (result > 0 && paymentMiniProgramConfig.getAppid() != null) {
+            deleteCache(paymentMiniProgramConfig.getAppid());
+            logger.info("修改支付配置并清除缓存,appId: {}", paymentMiniProgramConfig.getAppid());
+        }
+
+        return result;
+    }
+
+    @Override
+    public int deletePaymentMiniProgramConfigByIds(String[] ids) {
+        if (ids == null || ids.length == 0) {
+            return 0;
+        }
+
+        for (String id : ids) {
+            try {
+                PaymentMiniProgramConfig config = baseMapper.selectPaymentMiniProgramConfigById(id);
+                if (config != null && config.getAppid() != null) {
+                    deleteCache(config.getAppid());
+                }
+            } catch (Exception e) {
+                logger.error("批量删除时查询配置异常,id: {}", id, e);
+            }
+        }
+
+        int result = baseMapper.deletePaymentMiniProgramConfigByIds(ids);
+
+        if (result > 0) {
+            logger.info("批量删除支付配置,共 {} 条", ids.length);
+        }
+
+        return result;
+    }
+
+    @Override
+    public int deletePaymentMiniProgramConfigById(String id) {
+        PaymentMiniProgramConfig config = null;
+        try {
+            config = baseMapper.selectPaymentMiniProgramConfigById(id);
+        } catch (Exception e) {
+            logger.error("删除前查询配置异常,id: {}", id, e);
+        }
+
+        int result = baseMapper.deletePaymentMiniProgramConfigById(id);
+
+        if (result > 0 && config != null && config.getAppid() != null) {
+            deleteCache(config.getAppid());
+            logger.info("删除支付配置并清除缓存,id: {}, appId: {}", id, config.getAppid());
+        }
+
+        return result;
+    }
+}

+ 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;
+
+    /**
+     * 支付类型 1-微信 2-微信H5 3-微信APP 4-支付宝 5-支付宝H5
+     */
+    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);
+}

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

@@ -0,0 +1,50 @@
+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.FsRewardGoodsOrderExcelVO;
+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();
+
+    /**
+     * 查询导出订单列表
+     */
+    List<FsRewardGoodsOrderExcelVO> selectFsRewardGoodsOrderExcelListVO(FsRewardGoodsOrderListParam param);
+
+    /**
+     * 查询导出订单数量
+     */
+    Long selectFsRewardGoodsOrderExcelListVOCount(FsRewardGoodsOrderListParam param);
+}

+ 24 - 0
fs-service/src/main/java/com/fs/reward/param/FsAppRewardGoodsOrderAddParam.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 FsAppRewardGoodsOrderAddParam {
+
+    @NotNull(message = "商品不能为空")
+    @ApiModelProperty("商品ID")
+    private Long goodsId;
+
+    @NotNull(message = "中奖记录不能为空")
+    @ApiModelProperty("中奖记录ID")
+    private String roundId;
+
+    @NotNull(message = "收货地址不能为空")
+    @ApiModelProperty("收货地址ID")
+    private Long addressId;
+}

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

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

@@ -0,0 +1,71 @@
+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;
+
+    /**
+     * 任务ID(导出用)
+     */
+    private Long taskId;
+
+    /**
+     * 限制SQL(导出用)
+     */
+    private Integer limitSize;
+
+    /**
+     * 偏移量(导出分页用)
+     */
+    private Integer offset;
+}

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

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

@@ -0,0 +1,89 @@
+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.FsAppRewardGoodsOrderAddParam;
+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);
+
+    FsRewardGoodsOrder createOrderApp(Long userId, FsAppRewardGoodsOrderAddParam param);
+
+    /**
+     * 查询导出订单数量
+     */
+    Long selectFsRewardGoodsOrderExcelListVOCount(FsRewardGoodsOrderListParam param);
+
+    /**
+     * 导出数据
+     */
+    void exportData(FsRewardGoodsOrderListParam param, Long count);
+
+    /**
+     * 判断查询参数是否为空
+     */
+    Boolean isEntityNull(FsRewardGoodsOrderListParam param);
+}

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

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

@@ -0,0 +1,1399 @@
+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.app.course.domain.FsAppCourseRewardRound;
+import com.fs.common.core.domain.AjaxResult;
+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.common.utils.poi.ExcelUtil;
+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.erp.service.IJSTErpOrderService;
+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.IFsExportTaskService;
+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.pay.service.IPaymentMiniProgramConfigService;
+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.FsAppRewardGoodsOrderAddParam;
+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.FsRewardGoodsOrderExcelVO;
+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.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.lang.reflect.Field;
+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 IPaymentMiniProgramConfigService paymentConfigService;
+    @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 IJSTErpOrderService jstErpOrderService;
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private FsWxExpressTaskMapper fsWxExpressTaskMapper;
+    @Autowired
+    private IFsExportTaskService exportTaskService;
+
+    @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("商品已下架!");
+        }
+
+        FsUserAddress fsUserAddress = fsUserAddressMapper.selectFsUserAddressByAddressId(param.getAddressId());
+        if (Objects.isNull(fsUserAddress)) {
+            throw new CustomException("收货地址不存在!");
+        }
+
+        String orderSn = OrderCodeUtils.getOrderSn();
+        if (StringUtils.isEmpty(orderSn)) {
+            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.微信小程序支付.getCode())) {
+                        paymentMap.setPayTypeCode(PayType.微信小程序支付.name());
+                    } else if (payType.equals(PayType.支付宝条码支付.getCode())) {
+                        paymentMap.setPayTypeCode(PayType.支付宝条码支付.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);
+            String userName = order.getUserName();
+            String receiverName = "";
+            if (StringUtils.isNotEmpty(userName)) {
+                receiverName = userName.replaceAll("[^\\u4e00-\\u9fa5a-zA-Z0-9]", "");
+            }
+            if (StringUtils.isEmpty(receiverName)) {
+                receiverName = "特殊符号";
+            }
+            erpOrder.setReceiver_name(receiverName);
+
+            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);
+    }
+
+    /**
+     * 取消过期未支付订单
+     */
+    @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
+    @Transactional(rollbackFor = Exception.class)
+    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("订单已完成,不可取消");
+        }
+
+        // 还原库存
+        fsRewardGoodsMapper.addStock(order.getGoodsId());
+
+        // 待支付状态,直接修改订单状态
+        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("订单状态异常,无法取消");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public FsRewardGoodsOrder createOrderApp(Long userId, FsAppRewardGoodsOrderAddParam param) {
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(userId);
+        if (Objects.isNull(fsUser)) {
+            throw new CustomException("用户不存在!");
+        }
+
+        FsAppCourseRewardRound rewardRound = fsCourseRewardRoundMapper.selectFsAppCourseRewardRoundById(Long.valueOf(param.getRoundId()));
+        if (Objects.isNull(rewardRound)
+                || !Objects.equals(rewardRound.getGoodsId(), param.getGoodsId())
+                || !Objects.equals(rewardRound.getUserId(), userId)) {
+            throw new CustomException("App中奖记录不存在!");
+        }
+        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(), Long.valueOf(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 = 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(Long.valueOf(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("App实物抽奖");
+        baseMapper.insert(fsRewardGoodsOrder);
+
+        return fsRewardGoodsOrder;
+    }
+
+    /**
+     * 处理已发货订单的取消
+     */
+    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);
+    }
+
+    @Override
+    public Long selectFsRewardGoodsOrderExcelListVOCount(FsRewardGoodsOrderListParam param) {
+        return baseMapper.selectFsRewardGoodsOrderExcelListVOCount(param);
+    }
+
+    @Async
+    @Override
+    public void exportData(FsRewardGoodsOrderListParam param, Long count) {
+        List<FsRewardGoodsOrderExcelVO> exportList = new ArrayList<>();
+        Long fileSize = 1000L;
+        Long totalPage = count % fileSize == 0 ? count / fileSize : count / fileSize + 1;
+        for (int i = 0; i < totalPage; i++) {
+            param.setLimitSize(fileSize.intValue());
+            param.setOffset(i * fileSize.intValue());
+            List<FsRewardGoodsOrderExcelVO> itemList = baseMapper.selectFsRewardGoodsOrderExcelListVO(param);
+            param.setLimitSize(null);
+            param.setOffset(null);
+            if (null != itemList && !itemList.isEmpty()) {
+                exportList.addAll(itemList);
+            }
+        }
+//
+//        exportList.forEach(exportVO -> {
+//            // 解密手机号
+//            if (StringUtils.isNotEmpty(exportVO.getMobile())) {
+//                try {
+//                    exportVO.setMobile(PhoneUtil.decryptPhone(exportVO.getMobile()));
+//                } catch (Exception e) {
+//                    log.warn("手机号解密失败: {}, 原因: {}", exportVO.getMobile(), e.getMessage());
+//                    // 解密失败时保持原值
+//                }
+//            }
+//        });
+
+        ExcelUtil<FsRewardGoodsOrderExcelVO> util = new ExcelUtil<>(FsRewardGoodsOrderExcelVO.class);
+        AjaxResult result = util.exportExcel(exportList, "奖励商品订单导出");
+        FsExportTask task = exportTaskService.selectFsExportTaskByTaskId(param.getTaskId());
+        task.setFinishTime(new Date());
+        task.setStatus(1);
+        task.setFileUrl(result.get("msg").toString());
+        exportTaskService.updateFsExportTask(task);
+    }
+
+    @Override
+    public Boolean isEntityNull(FsRewardGoodsOrderListParam param) {
+        if (param == null) {
+            return true;
+        }
+        Integer status = param.getStatus();
+        param.setStatus(null);
+        Class<?> entityClass = param.getClass();
+        Field[] fields = entityClass.getDeclaredFields();
+        try {
+            for (Field field : fields) {
+                field.setAccessible(true);
+                Object value = field.get(param);
+                if (value != null) {
+                    param.setStatus(status);
+                    return false;
+                }
+            }
+        } catch (IllegalAccessException e) {
+            log.error("反射访问字段失败: {}", e.getMessage(), e);
+        }
+        param.setStatus(status);
+        return true;
+    }
+}

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

+ 159 - 0
fs-service/src/main/java/com/fs/reward/vo/FsRewardGoodsOrderExcelVO.java

@@ -0,0 +1,159 @@
+package com.fs.reward.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 奖励商品订单导出VO
+ */
+@Data
+public class FsRewardGoodsOrderExcelVO {
+
+    /**
+     * 订单ID
+     */
+    @Excel(name = "订单ID")
+    private Long orderId;
+
+    /**
+     * 订单号
+     */
+    @Excel(name = "订单编号")
+    private String orderSn;
+
+    /**
+     * 店铺名称
+     */
+    @Excel(name = "所属店铺")
+    private String storeName;
+
+    /**
+     * 用户ID
+     */
+    @Excel(name = "下单用户")
+    private Long userId;
+
+    /**
+     * 用户昵称
+     */
+    @Excel(name = "下单用户昵称")
+    private String nickName;
+
+
+    /**
+     * 商品ID
+     */
+    @Excel(name = "商品ID")
+    private Long goodsId;
+
+    /**
+     * 商品名称
+     */
+    @Excel(name = "商品名称")
+    private String goodsName;
+
+    /**
+     * 收货人
+     */
+    @Excel(name = "收货人")
+    private String userName;
+
+    /**
+     * 收货手机号
+     */
+//    @Excel(name = "收货手机号")
+//    private String mobile;
+
+    /**
+     * 收货地址
+     */
+    @Excel(name = "收货地址")
+    private String address;
+
+    /**
+     * 订单金额
+     */
+    @Excel(name = "订单金额")
+    private BigDecimal orderMoney;
+
+    /**
+     * 支付金额
+     */
+    @Excel(name = "支付金额")
+    private BigDecimal payMoney;
+
+    /**
+     * 支付类型
+     */
+    @Excel(name = "支付类型", readConverterExp = "1=微信,2=微信H5,3=微信APP,4=支付宝,5=支付宝H5")
+    private Integer payType;
+
+    /**
+     * 创建时间
+     */
+    @Excel(name = "下单时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     * 支付时间
+     */
+    @Excel(name = "支付时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date payTime;
+
+    /**
+     * 快递公司编号
+     */
+    @Excel(name = "快递公司编号")
+    private String deliveryCode;
+
+    /**
+     * 快递名称
+     */
+    @Excel(name = "快递名称")
+    private String deliveryName;
+
+    /**
+     * 快递单号
+     */
+    @Excel(name = "快递单号")
+    private String deliverySn;
+
+    /**
+     * 状态 1待支付 2已支付 3已发货 4已完成 -1已取消
+     */
+    @Excel(name = "订单状态", dictType = "sys_reward_order_status")
+    private Integer status;
+
+    /**
+     * 发货时间
+     */
+    @Excel(name = "发货时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date deliveryTime;
+
+    /**
+     * 完成时间
+     */
+    @Excel(name = "完成时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date finishTime;
+
+    /**
+     * 取消时间
+     */
+    @Excel(name = "取消时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date cancelTime;
+
+    /**
+     * 备注
+     */
+    @Excel(name = "备注")
+    private String remark;
+}

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

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

@@ -0,0 +1,38 @@
+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 String 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;
+
+    @ApiModelProperty("订单类型1企微2app")
+    private Long orderType;
+}

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

@@ -6,6 +6,8 @@ public interface TzBankService {
     /**创建订单**/
      TzBankResult<PayCreateOrderResult> createOrder(PayCreateOrder order);
 
+    TzBankResult<PayCreateOrderResult> createAppOrder(PayCreateOrder order);
+
     /**查询订单**/
      TzBankResult<QueryOrderResult> queryOrder(payQueryOrder payQueryOrder);
     /**退款**/

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

@@ -62,6 +62,28 @@ public class TzBankServiceImpl implements TzBankService {
         }
         return messageInfo;
     }
+
+    @Override
+    public TzBankResult<PayCreateOrderResult> createAppOrder(PayCreateOrder order) {
+        SysConfigMapper sysConfigMapper = SpringUtils.getBean(SysConfigMapper.class);
+        SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("his.pay");
+        FsPayConfig fsPayConfig = new Gson().fromJson(sysConfig.getConfigValue(), FsPayConfig.class);
+        String platMerCstNo = fsPayConfig.getTzPlatMerCstNo();
+        order.setOrderSource("01");
+        order.setCurrency("CNY");
+        order.setTransType("2001");
+        order.setTradeMerCstNo(platMerCstNo);
+        order.setPlatMerCstNo(platMerCstNo);
+        order.setNeedLedger("00");
+        Instant oneHourLater = Instant.now().plusSeconds(3600);
+        order.setExpiredTime(oneHourLater.toEpochMilli() + "");
+        order.setPayType(Arrays.asList(PayType.微信二维码支付.getCode()));
+        order.setPayNotifyUrl(fsPayConfig.getTzPayDecrypt());
+        order.setPayReturnUrl(TzBankConfig.payReturnUrl);
+        String send = tzBankSendUtil.send(order, TzBankConfig.payCreateOrder);
+        return new Gson().fromJson(send, new TypeToken<TzBankResult<PayCreateOrderResult>>() {}.getType());
+    }
+
     //查询订单
     public TzBankResult<QueryOrderResult> queryOrder(payQueryOrder payQueryOrder) {
         SysConfigMapper sysConfigMapper= SpringUtils.getBean(SysConfigMapper.class);

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

@@ -33,5 +33,6 @@ public class PayCreateOrder {
     private String identityName; // 用户真实名称 银联签约支付时需要上送
     private String openId;
     private String orderId;
+    private String appid;
     private Integer orderType;//1问诊 2药品 3套餐包 4课程订单 5开通会员 6积分商城
 }

+ 23 - 0
fs-service/src/main/java/com/fs/tzBankPay/doman/PayType.java

@@ -1,6 +1,29 @@
 package com.fs.tzBankPay.doman;
 
 public enum PayType {
+    账户支付("01"),
+    B2C网关("02"),
+    B2B网关("03"),
+    快捷支付("04"),
+    企业白条支付("06"),
+    e购贷支付("08"),
+    数字人民币支付("09"),
+    app微信支付("11"),
+    微信小程序支付("14"),
+    微信公众号("15"),
+    微信二维码支付("16"),
+    支付宝条码支付("27"),
+    苹果商店内购支付("29"),
+    云闪付("44"),
+    台行及村镇卡支付("45"),
+    B2B网银("46"),
+    B2C网银("47"),
+    钱包支付("48"),
+    台行及村镇企业账户支付("49"),
+    供应链白条支付("51"),
+    银联支付("53"),
+    银联签约支付("55"),
+    代付("99"),
     ACCOUNT_PAYMENT("01"),
     B2C_GATEWAY("02"),
     B2B_GATEWAY("03"),

+ 5 - 0
fs-service/src/main/java/com/fs/tzBankPay/doman/RefundParam.java

@@ -15,6 +15,11 @@ public class RefundParam {
     private String notifyURL; //由企业提供,退款申请后通知业务系统退款结果信息,一般为后端接口地址。因部分网络运营商存在域名解析失败的问题,建议上送IP加端口的形式,该地址需台行开通网络防火墙
     private List<sharers> sharers;
 
+    /**
+     * 小程序APPID
+     */
+    private String appid;
+
     @Data
     public static class sharers {
         private BigDecimal shareAmt;    // 分账金额

+ 5 - 0
fs-service/src/main/java/com/fs/ybPay/dto/RefundDTO.java

@@ -12,6 +12,11 @@ public class RefundDTO {
     private String upOrderId;   //交易订单号
     private String refundMoney; //退款金额
 
+    /**
+     * 小程序appid
+     */
+    private String appid;
+
 
     public Map<String, String> toMap() {
         Map<String, String> map = new HashMap<>();

+ 39 - 0
fs-service/src/main/resources/application-config-dev.yml

@@ -148,3 +148,42 @@ weizou:
 #  customer:
 #    ai:
 #      key:
+
+# ----   以下是app发课相关配置 -----
+config:
+  debug: false # 部分地方需要通过配置区分是否调试模式,原逻辑无需调整
+  receiver: 1 # 调试课程接收人
+  sender-prefix: C # IM发送消息,发送人前缀
+  receiver-prefix: U # IM发送消息,接收人前缀
+  batch-size: 500 # 批量处理数据大小
+  load: # 自定义条件加载配置
+    include: "sendSopLogs,generateSopLogs,consumer,generateUrgentClass" # 包含需要加载的配置条件,*则是加载全部,如果需要指定加载组件,只需要和组件配置的名称一致即可,多个用英文逗号隔开,不需要用yml数组格式
+#    exclude: "*" # 排除需要加载的配置条件,*则是排除全部,如果需要指定排除组件,只需要和组件配置的名称一致即可,多个用英文逗号隔开,不需要用yml数组格式,优先级高于include
+
+task:
+  enabled: true # 开启定时任务开关
+  mode: consumer # 区分是生产任务-producer,还是消费任务-consumer
+  producer:
+    gen-user-logs-delay: 60000 # 生成营期任务的间隔时间,单位毫秒
+    gen-user-logs-info-fragmentation-cron: 0 5 * * * * # 营期分片频率
+    gen-sop-logs-delay: 10000 #根据营期分片生成待执行记录的间隔时长
+    sync-watchlog-to-urgent-class-cron: 0 0 0/2 * * * # 同步看课记录到催课看板的频率
+    handle-repeat-data-delay: 1200000 # 处理待发记录重复数据的频次 20min = 20 * 60 * 1000
+    node: 0
+  consumer:
+    node: 0 # 消费节点编号,为消费者时必须设置且不可重复
+    future-timeout-seconds: 30 # future 等待时长
+    compensate-fixed-delay: 10000 # 消费补偿的间隔频率(10s)
+    consume-max-handle-minutes: 60 # 针对单条数据处理最大时长,超过则算作超时,单位分钟(60min)
+    consume-bind-tag-fixed-delay: 5000 # 消费绑定标签的间隔频率(5s)
+    consume-unbind-tag-fixed-delay: 5000 # 消费解绑标签的间隔频率(5s)
+    consume-bind-customer-fixed-delay: 5000 # 消费绑定客服的间隔频率(5s)
+    consume-unbind-customer-fixed-delay: 5000 # 消费解绑客服的间隔频率(5s)
+    consume-send-welcome-fixed-delay: 5000 # 消费发送欢迎语消息的间隔频率(5s)
+    consume-clear-app-sop-logs-cron: "0 0 1 * * *"
+    consume-clear-app-welcome-send-logs-cron: "0 5 1 * * *"
+    consume-auto-remind-ai-fixed-delay: 30000 # 自动 提醒/触达 ai的间隔频率(10s)
+    thread-pool:
+      core-pool-size: 30
+      max-pool-size: 50
+      thread-name-prefix: app-sop-send-thread-

+ 1 - 1
fs-service/src/main/resources/application-config-druid-hst.yml

@@ -105,7 +105,7 @@ wx_miniapp_temp:
 enableRedPackAccount: 1
 
 config:
-  debug: true # 部分地方需要通过配置区分是否调试模式,原逻辑无需调整
+  debug: false # 部分地方需要通过配置区分是否调试模式,原逻辑无需调整
   receiver: 10180 # 调试课程接收人
   sender-prefix: C # IM发送消息,发送人前缀
   receiver-prefix: U # IM发送消息,接收人前缀

+ 1 - 1
fs-service/src/main/resources/mapper/app/AppWorkTaskMapper.xml

@@ -52,7 +52,7 @@
         FROM
             app_work_task awt
         LEFT JOIN fs_user fu ON fu.is_del = 0 AND fu.history_app IS NOT NULL AND fu.user_id = awt.fs_user_id
-        LEFT JOIN app_customer_role_member acr ON acr.id = awt.customer_role_id
+        LEFT JOIN app_customer_role_member acr ON acr.app_customer_id = awt.customer_role_id
         <where>
             awt.is_delete = 0
             <if test="fsUserId != null">

+ 88 - 0
fs-service/src/main/resources/mapper/course/FsCourseRewardVideoRelationMapper.xml

@@ -76,6 +76,30 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
          </trim>
     </insert>
 
+    <insert id="insertFsCourseRewardVideoRelationApp">
+        insert into fs_app_course_reward_video_relation
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="rewardId != null and rewardId != ''">reward_id,</if>
+            <if test="videoSectionId != null and videoSectionId != ''">video_section_id,</if>
+            <if test="mark != null">mark,</if>
+            <if test="createId != null">create_id,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateId != null">update_id,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="appCustomerRoleId != null">app_customer_role_id,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="rewardId != null and rewardId != ''">#{rewardId},</if>
+            <if test="videoSectionId != null and videoSectionId != ''">#{videoSectionId},</if>
+            <if test="mark != null">#{mark},</if>
+            <if test="createId != null">#{createId},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateId != null">#{updateId},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="appCustomerRoleId != null">#{appCustomerRoleId},</if>
+        </trim>
+    </insert>
+
     <update id="updateFsCourseRewardVideoRelation" parameterType="FsCourseRewardVideoRelation">
         update fs_course_reward_video_relation
         <trim prefix="SET" suffixOverrides=",">
@@ -112,6 +136,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </delete>
 
+    <delete id="deleteByTypesApp">
+        delete crvr from fs_app_course_reward_video_relation crvr
+        inner join fs_course_reward cr on cr.id = crvr.reward_id
+        where crvr.video_section_id = #{videoId} and crvr.app_customer_role_id = #{appCustomerId}
+        and cr.reward_type in
+        <foreach item="type" collection="types" open="(" separator="," close=")">
+            #{type}
+        </foreach>
+    </delete>
+
 
     <select id="selectFsCourseRewardVideoRelationListByType" resultMap="FsCourseRewardVideoRelationResult">
         select crvr.id, crvr.reward_id, crvr.company_id,crvr.video_section_id, crvr.mark, crvr.create_id, crvr.create_time, crvr.update_id, crvr.update_time
@@ -135,4 +169,58 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         where company_id = #{companyId} and video_section_id = #{videoId} and reward_id = #{rewardId}
     </select>
 
+    <select id="getRewardByCompanyIdAndVideoIdAndTypeApp" resultType="com.fs.course.domain.FsCourseReward">
+        select cr.*
+        from fs_course_reward cr
+                 inner join fs_app_course_reward_video_relation crvr on cr.id = crvr.reward_id
+        where crvr.app_customer_role_id = #{appCustomerId} and crvr.video_section_id = #{videoId} and cr.reward_type = #{type} and cr.status = 1
+            limit 1
+    </select>
+
+    <select id="selectByCompanyIdAndVideoIdAndRewardIdApp"
+            resultType="com.fs.app.course.domain.FsAppCourseRewardVideoRelation">
+        select
+            id,reward_id,video_section_id,mark,create_id,create_time,update_id,update_time,app_customer_role_id
+        from fs_app_course_reward_video_relation
+        where app_customer_role_id = #{companyId} and video_section_id = #{videoId} and reward_id = #{rewardId}
+    </select>
+
+    <select id="selectFsAppCourseRewardVideoRelationListByType"
+            resultType="com.fs.app.course.domain.FsAppCourseRewardVideoRelation">
+        select crvr.id, crvr.reward_id, crvr.app_customer_role_id,crvr.video_section_id, crvr.mark, crvr.create_id, crvr.create_time, crvr.update_id, crvr.update_time
+        from fs_app_course_reward_video_relation crvr
+        inner join fs_course_reward cr on cr.id = crvr.reward_id
+        <where>
+            <if test="rewardId != null  and rewardId != ''"> and crvr.reward_id = #{rewardId}</if>
+            <if test="type != null  and type != ''">   and cr.reward_type = #{type}</if>
+            <if test="videoSectionId != null  and videoSectionId != ''"> and video_section_id = #{videoSectionId}</if>
+            <if test="mark != null "> and crvr.mark = #{mark}</if>
+            <if test="appCustomerRoleId != null ">and crvr.app_customer_role_id = #{appCustomerRoleId}</if>
+            <if test="createId != null "> and crvr.create_id = #{createId}</if>
+            <if test="updateId != null "> and crvr.update_id = #{updateId}</if>
+        </where>
+    </select>
+
+    <select id="getRewardIdByCompanyIdAndVideoIdAndTypeApp" resultType="java.lang.Long">
+        select cr.id
+        from fs_app_course_reward_video_relation crvr
+                 inner join fs_course_reward cr on cr.id = crvr.reward_id and cr.reward_type = #{type}
+        where crvr.app_customer_role_id = #{companyId} and crvr.video_section_id = #{videoId} and cr.status = 1
+            limit 1
+    </select>
+
+    <select id="selectFsAppCourseRewardVideoRelationList"
+            resultType="com.fs.app.course.domain.FsAppCourseRewardVideoRelation">
+        select
+        id,reward_id,video_section_id,mark,create_id,create_time,update_id,update_time,app_customer_role_id
+        from fs_app_course_reward_video_relation
+        <where>
+            <if test="rewardId != null  and rewardId != ''"> and reward_id = #{rewardId}</if>
+            <if test="videoSectionId != null  and videoSectionId != ''"> and video_section_id = #{videoSectionId}</if>
+            <if test="mark != null "> and mark = #{mark}</if>
+            <if test="createId != null "> and create_id = #{createId}</if>
+            <if test="updateId != null "> and update_id = #{updateId}</if>
+        </where>
+    </select>
+
 </mapper>

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

@@ -0,0 +1,125 @@
+<?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>
+    <select id="getAppCustomerList" resultType="com.fs.app.cusrole.domain.AppCustomerRole">
+        select
+            id, role_name, avatar, remark, create_time, update_time, app_fastgpt_role_id
+        from
+            app_customer_role
+        WHERE
+            is_delete = 0
+        ORDER BY create_time DESC;
+    </select>
+</mapper>

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

@@ -149,5 +149,92 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         limit 1000
     </select>
 
+    <insert id="insertFsCourseRewardRoundApp">
+        insert into fs_app_course_reward_round
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="rewardId != null">reward_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="rewardType != null">reward_type,</if>
+            <if test="appCustomerRoleId != null">app_customer_role_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="id != null">#{id},</if>
+            <if test="rewardId != null">#{rewardId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="rewardType != null">#{rewardType},</if>
+            <if test="appCustomerRoleId != null">#{appCustomerRoleId},</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>
+
+    <select id="selectFsAppCourseRewardRoundList" resultType="com.fs.app.course.domain.FsAppCourseRewardRound">
+        select id, reward_id, user_id, reward_type, app_customer_role_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_app_course_reward_round
+        <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="appCustomerRoleId != null "> and app_customer_role_id = #{appCustomerRoleId}</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="selectFsAppCourseRewardRoundById" resultType="com.fs.app.course.domain.FsAppCourseRewardRound">
+        select id, reward_id, user_id, reward_type, app_customer_role_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_app_course_reward_round
+        where id = #{id}
+    </select>
+
+    <select id="selectTodayTotalReward" resultType="java.lang.Integer">
+        SELECT COUNT(*) AS  todayTotalReward FROM fs_course_reward_round
+        where watch_id = 88888888
+          AND user_id=#{userId}
+          AND create_time <![CDATA[>=]]> CURDATE()
+          AND create_time <![CDATA[<]]> DATE_ADD(CURDATE(), INTERVAL 1 DAY)
+    </select>
+    <select id="selectTodayTotalRewardByStatus" resultType="java.lang.Integer">
+        SELECT COUNT(*) AS todayTotalReward FROM fs_course_reward_round
+        where status = 1
+          AND user_id=#{userId}
+          AND create_time <![CDATA[>=]]> CURDATE()
+          AND create_time <![CDATA[<]]> DATE_ADD(CURDATE(), INTERVAL 1 DAY)
+    </select>
 
 </mapper>

+ 5 - 0
fs-service/src/main/resources/mapper/his/FsStorePaymentMapper.xml

@@ -285,4 +285,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectLastByBusinessCode" resultType="com.fs.his.domain.FsStorePayment">
         select * from fs_store_payment where business_code like #{orderSn} order by create_time desc limit 1
     </select>
+    <select id="selectByBusinessTypeAndBusinessCode" resultType="com.fs.his.domain.FsStorePayment">
+        select * from fs_store_payment
+        where business_type = #{businessType} and business_code = #{businessCode}
+        order by create_time desc
+    </select>
 </mapper>

+ 89 - 0
fs-user-app/src/main/java/com/fs/app/controller/course/CourseQwController.java

@@ -9,6 +9,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fs.app.annotation.Login;
 import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.controller.AppBaseController;
+import com.fs.app.course.param.AppCourseRewardTypeParam;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.CloudHostUtils;
@@ -43,6 +44,7 @@ import io.jsonwebtoken.Claims;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
+import lombok.extern.slf4j.Slf4j;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
@@ -61,6 +63,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
+@Slf4j
 @Api("企微看课接口")
 @RestController
 @RequestMapping(value="/app/course")
@@ -373,6 +376,92 @@ public class CourseQwController extends AppBaseController {
         return r;
     }
 
+    @Login
+    @ApiOperation("h5课程详情加问答")
+    @GetMapping("/getCourseVideoDetailsApp")
+    public R getCourseVideoDetailsApp(FsUserCourseVideoFinishUParam param)
+    {
+        param.setUserId(Long.parseLong(getUserId()));
+        return courseService.getCourseVideoDetailsApp(param);
+    }
+
+    /**
+     * 是否添加客服app
+     */
+    @Login
+    @ApiOperation("是否添加客服")
+    @PostMapping("/isAddKfApp")
+    public R isAddKfApp(@RequestBody FsUserCourseVideoAddKfUParam param) {
+        Long userId = Long.parseLong(getUserId());
+        param.setUserId(userId);
+        return courseVideoService.isAddKfApp(param);
+    }
+
+    @ApiOperation("App答题")
+    @PostMapping("/courseAnswerApp")
+    @Login
+    public R courseAnswerApp(@RequestBody FsCourseQuestionAnswerUParam param)
+    {
+        logger.info(" \n【App答题】:{}",param.getQuestions());
+        if (param.getDuration()==null){
+            logger.info(" \n【未识别到时长】:{}",param.getUserId());
+        }
+        param.setUserId(Long.parseLong(getUserId()));
+        return questionBankService.courseAnswerApp(param);
+    }
+
+    @Login
+    @ApiOperation("发放奖励")
+    @PostMapping("/sendRewardApp")
+    @RepeatSubmit
+    public R sendRewardApp(@RequestBody FsCourseSendRewardUParam param)
+    {
+        param.setUserId(Long.parseLong(getUserId()));
+        logger.info("zyp \n【发放奖励】:{}",param);
+        return courseVideoService.sendRewardV2App(param);
+    }
+
+    @Login
+    @ApiOperation("获取课程可领取奖励类型App")
+    @PostMapping("/getVideoRewardTypesApp")
+    public R getVideoRewardTypesApp(@RequestBody AppCourseRewardTypeParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return R.ok().put("data", courseVideoService.getVideoRewardTypesApp(param));
+    }
+
+    @Login
+    @ApiOperation("获取课程奖励类型对应规则")
+    @PostMapping("/getVideoRewardRulesApp")
+    public R getVideoRewardRulesApp(@RequestBody FsCourseRewardRuleParam param) {
+        param.setUserId(Long.parseLong(getUserId()));
+        return R.ok().put("data", courseVideoService.getVideoRewardRulesApp(param));
+    }
+
+    /**
+     * app更新看课时长(不走企微参数)
+     * @param param
+     * @return
+     */
+    @Login
+    @ApiOperation("更新看课时长")
+    @PostMapping("/updateWatchDurationApp")
+    public R updateWatchDurationApp(@RequestBody FsUserCourseVideoFinishUParam param)
+    {
+        log.info("debugger node 0000----------------------------------------, {}", param);
+        param.setUserId(Long.parseLong(getUserId()));
+        return courseVideoService.updateWatchDurationApp(param);
+    }
+
+    @Login
+    @ApiOperation("获取缓冲流量 ps:前端定时调")
+    @PostMapping("/getInternetTrafficApp")
+    public R getInternetTrafficApp(@RequestBody FsUserCourseVideoFinishUParam param) {
+        Long userId = Long.parseLong(getUserId());
+        param.setUserId(userId);
+        return courseVideoService.getInternetTrafficApp(param);
+    }
+
+
 
 //    @Login
 //    @ApiOperation("H5看课绑定手机号")

+ 1 - 0
pom.xml

@@ -309,6 +309,7 @@
         <module>fs-qw-api</module>
         <module>fs-qw-task</module>
         <module>fs-app-task</module>
+        <module>fs-app-task-course</module>
         <module>fs-redis</module>
         <module>fs-watch</module>
         <module>fs-common-api</module>