ソースを参照

红德堂-广告联盟及相关代码同步

Long 1 週間 前
コミット
6b991ab8ed
61 ファイル変更4579 行追加40 行削除
  1. 138 0
      fs-admin/src/main/java/com/fs/his/controller/AdProfitDetailController.java
  2. 99 0
      fs-admin/src/main/java/com/fs/his/controller/FsConsecutiveWithdrawRecordController.java
  3. 69 0
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralRedPacketLogController.java
  4. 15 1
      fs-admin/src/main/java/com/fs/his/controller/FsUserController.java
  5. 20 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  6. 1 1
      fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  7. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  8. 62 0
      fs-service/src/main/java/com/fs/his/config/IntegralConfig.java
  9. 96 0
      fs-service/src/main/java/com/fs/his/domain/AdProfitDetail.java
  10. 56 0
      fs-service/src/main/java/com/fs/his/domain/FsConsecutiveWithdrawRecord.java
  11. 42 0
      fs-service/src/main/java/com/fs/his/domain/FsIntegralExchange.java
  12. 69 0
      fs-service/src/main/java/com/fs/his/domain/FsIntegralRedPacketLog.java
  13. 10 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  14. 99 0
      fs-service/src/main/java/com/fs/his/dto/AdProfitDetailDto.java
  15. 2 0
      fs-service/src/main/java/com/fs/his/enums/FsUserIntegralLogTypeEnum.java
  16. 73 0
      fs-service/src/main/java/com/fs/his/mapper/AdProfitDetailMapper.java
  17. 81 0
      fs-service/src/main/java/com/fs/his/mapper/FsConsecutiveWithdrawRecordMapper.java
  18. 62 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralExchangeMapper.java
  19. 103 0
      fs-service/src/main/java/com/fs/his/mapper/FsIntegralRedPacketLogMapper.java
  20. 3 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserIntegralLogsMapper.java
  21. 11 4
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  22. 12 0
      fs-service/src/main/java/com/fs/his/param/AdProfitDetailStatisticsParam.java
  23. 9 0
      fs-service/src/main/java/com/fs/his/param/FsConsecutiveWithdrawRecordParam.java
  24. 13 0
      fs-service/src/main/java/com/fs/his/param/FsIntegralRedPacketLogParam.java
  25. 19 0
      fs-service/src/main/java/com/fs/his/param/FsIntegralWithdrawalParam.java
  26. 4 1
      fs-service/src/main/java/com/fs/his/param/FsUserAddIntegralParam.java
  27. 11 0
      fs-service/src/main/java/com/fs/his/param/FsUserDisabledUsersParam.java
  28. 70 0
      fs-service/src/main/java/com/fs/his/service/IAdProfitDetailService.java
  29. 66 0
      fs-service/src/main/java/com/fs/his/service/IFsConsecutiveWithdrawRecordService.java
  30. 62 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralExchangeService.java
  31. 74 0
      fs-service/src/main/java/com/fs/his/service/IFsIntegralRedPacketLogService.java
  32. 10 0
      fs-service/src/main/java/com/fs/his/service/IFsStorePaymentService.java
  33. 3 4
      fs-service/src/main/java/com/fs/his/service/IFsUserIntegralLogsService.java
  34. 37 0
      fs-service/src/main/java/com/fs/his/service/IFsUserService.java
  35. 199 0
      fs-service/src/main/java/com/fs/his/service/impl/AdProfitDetailServiceImpl.java
  36. 404 0
      fs-service/src/main/java/com/fs/his/service/impl/FsConsecutiveWithdrawRecordServiceImpl.java
  37. 93 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralExchangeServiceImpl.java
  38. 13 1
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java
  39. 453 0
      fs-service/src/main/java/com/fs/his/service/impl/FsIntegralRedPacketLogServiceImpl.java
  40. 184 4
      fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java
  41. 208 4
      fs-service/src/main/java/com/fs/his/service/impl/FsUserIntegralLogsServiceImpl.java
  42. 415 15
      fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java
  43. 76 0
      fs-service/src/main/java/com/fs/his/utils/ProfitShareUtils.java
  44. 30 0
      fs-service/src/main/java/com/fs/his/utils/UniAdSignUtils.java
  45. 14 0
      fs-service/src/main/java/com/fs/his/vo/AdProfitDetailStatisticsVo.java
  46. 23 0
      fs-service/src/main/java/com/fs/his/vo/ExchangeDetailVo.java
  47. 12 0
      fs-service/src/main/java/com/fs/his/vo/FsConsecutiveWithdrawRecordVo.java
  48. 13 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralRedPacketLogVo.java
  49. 22 0
      fs-service/src/main/java/com/fs/his/vo/IntegralExchangeVo.java
  50. 1 1
      fs-service/src/main/java/com/fs/live/domain/LiveUserRedRecord.java
  51. 1 1
      fs-service/src/main/java/com/fs/live/service/impl/LiveRedConfServiceImpl.java
  52. 253 0
      fs-service/src/main/resources/mapper/his/AdProfitDetailMapper.xml
  53. 137 0
      fs-service/src/main/resources/mapper/his/FsConsecutiveWithdrawRecordMapper.xml
  54. 70 0
      fs-service/src/main/resources/mapper/his/FsIntegralExchangeMapper.xml
  55. 266 0
      fs-service/src/main/resources/mapper/his/FsIntegralRedPacketLogMapper.xml
  56. 24 1
      fs-service/src/main/resources/mapper/his/FsUserIntegralLogsMapper.xml
  57. 38 1
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  58. 40 0
      fs-user-app/src/main/java/com/fs/app/controller/CommonController.java
  59. 77 0
      fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java
  60. 7 0
      fs-user-app/src/main/java/com/fs/app/controller/UserController.java
  61. 4 0
      fs-user-app/src/main/java/com/fs/app/controller/WxPayController.java

+ 138 - 0
fs-admin/src/main/java/com/fs/his/controller/AdProfitDetailController.java

@@ -0,0 +1,138 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.AdProfitDetail;
+import com.fs.his.dto.AdProfitDetailDto;
+import com.fs.his.param.AdProfitDetailStatisticsParam;
+import com.fs.his.service.IAdProfitDetailService;
+import com.fs.his.vo.AdProfitDetailStatisticsVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 广告分佣Controller
+ * 
+ * @author fs
+ * @date 2025-11-27
+ */
+@RestController
+@RequestMapping("/adv/profit")
+public class AdProfitDetailController extends BaseController
+{
+    @Autowired
+    private IAdProfitDetailService adProfitDetailService;
+
+    /**
+     * 查询广告分佣列表
+     */
+    @PreAuthorize("@ss.hasPermi('adv:profit:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(AdProfitDetailDto adProfitDetailDto)
+    {
+        startPage();
+        List<AdProfitDetailDto> list = adProfitDetailService.selectAdProfitDetailList(adProfitDetailDto);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出广告分佣列表
+     */
+    @PreAuthorize("@ss.hasPermi('adv:profit:export')")
+    @Log(title = "广告分佣", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(AdProfitDetailDto adProfitDetailDto)
+    {
+        List<AdProfitDetailDto> list = adProfitDetailService.selectAdProfitDetailList(adProfitDetailDto);
+        ExcelUtil<AdProfitDetailDto> util = new ExcelUtil<AdProfitDetailDto>(AdProfitDetailDto.class);
+        return util.exportExcel(list, "广告分佣数据");
+    }
+
+    /**
+     * 获取广告分佣详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('adv:profit:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(adProfitDetailService.selectAdProfitDetailById(id));
+    }
+
+    /**
+     * 新增广告分佣
+     */
+    @PreAuthorize("@ss.hasPermi('adv:profit:add')")
+    @Log(title = "广告分佣", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody AdProfitDetail adProfitDetail)
+    {
+        return toAjax(adProfitDetailService.insertAdProfitDetail(adProfitDetail));
+    }
+
+    /**
+     * 修改广告分佣
+     */
+    @PreAuthorize("@ss.hasPermi('adv:profit:edit')")
+    @Log(title = "广告分佣", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody AdProfitDetail adProfitDetail)
+    {
+        return toAjax(adProfitDetailService.updateAdProfitDetail(adProfitDetail));
+    }
+
+    /**
+     * 删除广告分佣
+     */
+    @PreAuthorize("@ss.hasPermi('adv:profit:remove')")
+    @Log(title = "广告分佣", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(adProfitDetailService.deleteAdProfitDetailByIds(ids));
+    }
+
+    /**
+     * 查询用户可提现 和 已提现总金额
+     */
+//    @PreAuthorize("@ss.hasPermi('adv:profit:remove')")
+//    @Log(title = "广告分佣", businessType = BusinessType.DELETE)
+    @GetMapping("/getWithFinishAndMayWithdraw")
+    public R getWithFinishAndMayWithdraw()
+    {
+        return adProfitDetailService.getWithFinishAndMayWithdraw();
+    }
+
+    /**
+     * 统计
+     */
+    @PreAuthorize("@ss.hasPermi('his:statistics:commission')")
+    @GetMapping("/statistics")
+    public R statistics(AdProfitDetailStatisticsParam param)
+    {
+        List<AdProfitDetailStatisticsVo> list = adProfitDetailService.statisticsList(param);
+        return R.ok().put("list",list);
+    }
+
+    /**
+     * 导出统计
+     */
+    @PreAuthorize("@ss.hasPermi('his:statistics:commissionExport')")
+    @GetMapping("/exportCommission")
+    @Log(title = "广告分佣统计导出", businessType = BusinessType.EXPORT)
+    public AjaxResult exportCommission(AdProfitDetailStatisticsParam param)
+    {
+        List<AdProfitDetailStatisticsVo> list = adProfitDetailService.statisticsList(param);
+
+        ExcelUtil<AdProfitDetailStatisticsVo> util = new ExcelUtil<AdProfitDetailStatisticsVo>(AdProfitDetailStatisticsVo.class);
+        return util.exportExcel(list, "广告分佣统计数据");
+    }
+
+}

+ 99 - 0
fs-admin/src/main/java/com/fs/his/controller/FsConsecutiveWithdrawRecordController.java

@@ -0,0 +1,99 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.domain.FsConsecutiveWithdrawRecord;
+import com.fs.his.param.FsConsecutiveWithdrawRecordParam;
+import com.fs.his.service.IFsConsecutiveWithdrawRecordService;
+import com.fs.his.vo.FsConsecutiveWithdrawRecordVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 连续提现记录Controller
+ * 
+ * @author fs
+ * @date 2026-02-04
+ */
+@RestController
+@RequestMapping("/his/consecutiveWithdrawRecord")
+public class FsConsecutiveWithdrawRecordController extends BaseController
+{
+    @Autowired
+    private IFsConsecutiveWithdrawRecordService fsConsecutiveWithdrawRecordService;
+
+    /**
+     * 查询连续提现记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:consecutiveWithdrawRecord:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(FsConsecutiveWithdrawRecordParam param)
+    {
+        startPage();
+        List<FsConsecutiveWithdrawRecordVo> list = fsConsecutiveWithdrawRecordService.selectFsConsecutiveWithdrawRecordList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出连续提现记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:consecutiveWithdrawRecord:export')")
+    @Log(title = "连续提现记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsConsecutiveWithdrawRecordParam fsConsecutiveWithdrawRecord)
+    {
+        List<FsConsecutiveWithdrawRecordVo> list = fsConsecutiveWithdrawRecordService.selectFsConsecutiveWithdrawRecordList(fsConsecutiveWithdrawRecord);
+        ExcelUtil<FsConsecutiveWithdrawRecordVo> util = new ExcelUtil<FsConsecutiveWithdrawRecordVo>(FsConsecutiveWithdrawRecordVo.class);
+        return util.exportExcel(list, "连续提现记录数据");
+    }
+
+    /**
+     * 获取连续提现记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:consecutiveWithdrawRecord:query')")
+    @GetMapping(value = "/{id}")
+    public AjaxResult getInfo(@PathVariable("id") Long id)
+    {
+        return AjaxResult.success(fsConsecutiveWithdrawRecordService.selectFsConsecutiveWithdrawRecordById(id));
+    }
+
+    /**
+     * 新增连续提现记录
+     */
+    @PreAuthorize("@ss.hasPermi('his:consecutiveWithdrawRecord:add')")
+    @Log(title = "连续提现记录", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody FsConsecutiveWithdrawRecord fsConsecutiveWithdrawRecord)
+    {
+        return toAjax(fsConsecutiveWithdrawRecordService.insertFsConsecutiveWithdrawRecord(fsConsecutiveWithdrawRecord));
+    }
+
+    /**
+     * 修改连续提现记录
+     */
+    @PreAuthorize("@ss.hasPermi('his:consecutiveWithdrawRecord:edit')")
+    @Log(title = "连续提现记录", businessType = BusinessType.UPDATE)
+    @PutMapping
+    public AjaxResult edit(@RequestBody FsConsecutiveWithdrawRecord fsConsecutiveWithdrawRecord)
+    {
+        return toAjax(fsConsecutiveWithdrawRecordService.updateFsConsecutiveWithdrawRecord(fsConsecutiveWithdrawRecord));
+    }
+
+    /**
+     * 删除连续提现记录
+     */
+    @PreAuthorize("@ss.hasPermi('his:consecutiveWithdrawRecord:remove')")
+    @Log(title = "连续提现记录", businessType = BusinessType.DELETE)
+	@DeleteMapping("/{ids}")
+    public AjaxResult remove(@PathVariable Long[] ids)
+    {
+        return toAjax(fsConsecutiveWithdrawRecordService.deleteFsConsecutiveWithdrawRecordByIds(ids));
+    }
+}

+ 69 - 0
fs-admin/src/main/java/com/fs/his/controller/FsIntegralRedPacketLogController.java

@@ -0,0 +1,69 @@
+package com.fs.his.controller;
+
+import com.fs.common.annotation.Log;
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.enums.BusinessType;
+import com.fs.common.utils.poi.ExcelUtil;
+import com.fs.his.param.FsIntegralRedPacketLogParam;
+import com.fs.his.service.IFsIntegralRedPacketLogService;
+import com.fs.his.vo.FsIntegralRedPacketLogVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 积分佣金红包记录Controller
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+@RestController
+@RequestMapping("/his/integralRedPacketLog")
+public class FsIntegralRedPacketLogController extends BaseController
+{
+    @Autowired
+    private IFsIntegralRedPacketLogService fsIntegralRedPacketLogService;
+
+    /**
+     * 查询积分佣金红包记录列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(FsIntegralRedPacketLogParam fsIntegralRedPacketLog)
+    {
+        startPage();
+        List<FsIntegralRedPacketLogVo> list = fsIntegralRedPacketLogService.getList(fsIntegralRedPacketLog);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出积分佣金红包记录列表
+     */
+    @PreAuthorize("@ss.hasPermi('his:integralRedPacketLog:export')")
+    @Log(title = "积分佣金红包记录", businessType = BusinessType.EXPORT)
+    @GetMapping("/export")
+    public AjaxResult export(FsIntegralRedPacketLogParam fsIntegralRedPacketLog)
+    {
+        List<FsIntegralRedPacketLogVo> list = fsIntegralRedPacketLogService.getList(fsIntegralRedPacketLog);
+        ExcelUtil<FsIntegralRedPacketLogVo> util = new ExcelUtil<FsIntegralRedPacketLogVo>(FsIntegralRedPacketLogVo.class);
+        return util.exportExcel(list, "积分佣金红包记录数据");
+    }
+
+    /**
+     * 获取积分佣金红包记录详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('his:integralRedPacketLog:query')")
+    @GetMapping(value = "/{logId}")
+    public AjaxResult getInfo(@PathVariable("logId") Long logId)
+    {
+        return AjaxResult.success(fsIntegralRedPacketLogService.selectFsIntegralRedPacketLogByLogId(logId));
+    }
+
+
+}

+ 15 - 1
fs-admin/src/main/java/com/fs/his/controller/FsUserController.java

@@ -1,6 +1,8 @@
 package com.fs.his.controller;
 
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.*;
 
 import com.alibaba.fastjson.JSON;
@@ -20,6 +22,7 @@ import com.fs.his.dto.FsUserDTO;
 import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.param.FsUserAddIntegralTemplateParam;
 import com.fs.his.param.FsUserAddPointsParam;
+import com.fs.his.param.FsUserDisabledUsersParam;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.dto.FsUserTransferImportDTO;
 import com.fs.his.service.IFsUserCompanyUserTransferTaskService;
@@ -113,7 +116,7 @@ public class FsUserController extends BaseController
                     }
                 }
             }
-
+            fsUserVO.setMayWithdraw(fsUserVO.getMayWithdraw().divide(new BigDecimal("100"),2, RoundingMode.DOWN));
         }
         return getDataTable(list);
     }
@@ -337,6 +340,17 @@ public class FsUserController extends BaseController
         return toAjax(fsUserService.updateFsUser(fsUser));
     }
 
+    /**
+     * 批量禁用用户
+     */
+    @PreAuthorize("@ss.hasPermi('his:user:disabledUsers')")
+    @Log(title = "批量禁用用户", businessType = BusinessType.UPDATE)
+    @PostMapping("/disabledUsers")
+    public AjaxResult disabledUsers(@RequestBody FsUserDisabledUsersParam param)
+    {
+        return toAjax(fsUserService.disabledUsers(param));
+    }
+
     /**
      * 删除用户
      */

+ 20 - 0
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -1828,4 +1828,24 @@ public class Task {
         }
     }
 
+    @Autowired
+    private IFsIntegralRedPacketLogService fsIntegralRedPacketLogService;
+
+    /**
+     * 同步微信商家转账状态
+     */
+    public void synchronizationWxMerchantPayStatus(){
+        fsIntegralRedPacketLogService.synchronizationWxMerchantPayStatus();
+    }
+
+    @Autowired
+    private IFsConsecutiveWithdrawRecordService fsConsecutiveWithdrawRecordService;
+
+    /**
+     * 风控连续提现用户 每天执行一次
+     */
+    public void checkConsecutiveWithdrawUsers(){
+        fsConsecutiveWithdrawRecordService.checkConsecutiveWithdrawUsers();
+    }
+
 }

+ 1 - 1
fs-live-app/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -693,7 +693,7 @@ public class WebSocketServer {
         sendMsgVo.setUserId(userId);
         sendMsgVo.setUserType(0L);
         sendMsgVo.setCmd("Integral");
-        sendMsgVo.setMsg("恭喜你成功获得观看奖励:" + scoreAmount + "芳华币");
+        sendMsgVo.setMsg("恭喜你成功获得观看奖励:" + scoreAmount + "积分");
         sendMsgVo.setData(String.valueOf(scoreAmount));
 
         if(Objects.isNull( session)) return;

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

@@ -382,7 +382,7 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
             todayTotalIntegral=0;
         }
         if (todayTotalIntegral>=config.getIntegralByOneDay()){
-            return R.error("当天芳华币已达限额");
+            return R.error("当天积分已达限额");
         }
         int rate = (int)((double)param.getDuration() / video.getDuration() * 100);
         if (rate>=90){

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

@@ -3,6 +3,7 @@ package com.fs.his.config;
 import lombok.Data;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
 
 @Data
 public class IntegralConfig implements Serializable {
@@ -26,4 +27,65 @@ public class IntegralConfig implements Serializable {
     private Integer integralAddPatient;//新用户完善就诊人获得积分
     private Integer integralAddUserAddress;//新用户填写收货地址获取积分
     private Integer integralSubscriptCourse;//付费课程订阅积分比例
+
+    private BigDecimal integralUserRatio;// 广告联盟 用户分润比例
+    private BigDecimal integralCompanyRatio;// 广告联盟 公司分润比例
+    private BigDecimal integralHuYiRatio;// 广告联盟 互医分润比例
+    private BigDecimal integralYunLianRatio;// 广告联盟 云联分润比例
+    private Integer minimumIntegral; // 广告联盟 用户保底收益
+
+    //积分提现商户配置
+    private Integer isNew;//0:老商户 商家转账到零钱 1:新商户 商家转账
+
+    /**
+     * 商户号.
+     */
+    private String mchId;
+    /**
+     * 商户密钥.
+     */
+    private String mchKey;
+
+    /**
+     * p12证书文件的绝对路径或者以classpath:开头的类路径.
+     */
+    private String keyPath;
+
+    /**
+     * apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     */
+    private String privateKeyPath;
+
+    /**
+     * apiclient_cert.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     */
+    private String privateCertPath;
+
+    /**
+     * apiV3 秘钥值.
+     */
+    private String apiV3Key;
+    /**
+     * 公钥ID
+     */
+    private String publicKeyId;
+
+    /**
+     * pub_key.pem证书文件的绝对路径或者以classpath:开头的类路径.
+     */
+    private String publicKeyPath;
+
+    private String notifyUrl;
+
+    //一次允许提现最大金额(元)
+    private BigDecimal maxApplicationAmount;
+
+    //一天允提现次数
+    private Integer withdrawNum;
+
+    //连续提现几天封控
+    private Integer limitDayNum;
+
+    //连续提现几天封控
+    private BigDecimal limitAmount;
 }

+ 96 - 0
fs-service/src/main/java/com/fs/his/domain/AdProfitDetail.java

@@ -0,0 +1,96 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fs.common.annotation.Excel;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+* <p>
+* ad_profit_detail 实体类
+* </p>
+*/
+@Getter
+@Setter
+@TableName("ad_profit_detail")
+public class AdProfitDetail implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+    * 主键
+    */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+    * 广告回调唯一交易ID
+    */
+    @Excel(name = "广告回调唯一交易ID")
+    private String adTransId;
+
+    /**
+    * 广告任务id
+    */
+    @Excel(name = "广告任务id")
+    private String adTaskId;
+
+    /**
+    * 广告平台结算总金额
+    */
+    @Excel(name = "广告平台结算总金额")
+    private BigDecimal sumMoney;
+
+    /**
+    * 用户id
+    */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /**
+    * 用户分润
+    */
+    @Excel(name = "用户分润")
+    private BigDecimal userMoney;
+
+    /**
+    * 公司id
+    */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /**
+    * 公司分润
+    */
+    @Excel(name = "公司分润")
+    private BigDecimal companyMoney;
+
+    /**
+    * 互医分润
+    */
+    @Excel(name = "互医分润")
+    private BigDecimal huyiMoney;
+
+    /**
+    * 云联分润
+    */
+    @Excel(name = "云联分润")
+    private BigDecimal yunlianMoney;
+
+    /**
+     * 广告原始回调参数
+     */
+    private String originParam;
+
+    /**
+    * 创建时间
+    */
+    private LocalDateTime createTime;
+
+
+}

+ 56 - 0
fs-service/src/main/java/com/fs/his/domain/FsConsecutiveWithdrawRecord.java

@@ -0,0 +1,56 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 连续提现记录对象 fs_consecutive_withdraw_record
+ *
+ * @author fs
+ * @date 2026-02-04
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsConsecutiveWithdrawRecord extends BaseEntity{
+
+    @TableId
+    private Long id;
+
+    /** 用户ID */
+    @Excel(name = "用户ID",sort = 1)
+    private Long userId;
+
+    /** 连续天数 */
+    @Excel(name = "连续天数")
+    private Integer consecutiveDays;
+
+    /** 连续开始日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "连续开始日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date startDate;
+
+    /** 连续结束日期 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "连续结束日期", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date endDate;
+
+    /** 连续期间提现次数 */
+    @Excel(name = "连续期间提现次数")
+    private Integer withdrawCount;
+
+    /** 连续期间提现总金额 */
+    @Excel(name = "连续期间提现总金额")
+    private BigDecimal totalAmount;
+
+    /** 状态:1-有效 0-无效 */
+    private Long status;
+
+
+}

+ 42 - 0
fs-service/src/main/java/com/fs/his/domain/FsIntegralExchange.java

@@ -0,0 +1,42 @@
+package com.fs.his.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 用户积分提现佣金记录对象 fs_integral_exchange
+ *
+ * @author fs
+ * @date 2026-01-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsIntegralExchange extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** $column.columnComment */
+    @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()")
+    private Long userId;
+
+    /** 积分 正数表示增加积分,负数表示减少积分 */
+    @Excel(name = "积分 正数表示增加积分,负数表示减少积分")
+    private Long integral;
+
+    /** $column.columnComment */
+    @Excel(name = "积分 正数表示增加积分,负数表示减少积分")
+    private Integer status;
+
+    /** $column.columnComment */
+    @Excel(name = "积分 正数表示增加积分,负数表示减少积分")
+    private String phone;
+
+    /** $column.columnComment */
+    @Excel(name = "积分 正数表示增加积分,负数表示减少积分")
+    private String nickName;
+
+
+}

+ 69 - 0
fs-service/src/main/java/com/fs/his/domain/FsIntegralRedPacketLog.java

@@ -0,0 +1,69 @@
+package com.fs.his.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.math.BigDecimal;
+
+/**
+ * 积分佣金红包记录对象 fs_integral_red_packet_log
+ *
+ * @author fs
+ * @date 2026-01-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FsIntegralRedPacketLog extends BaseEntity{
+
+    /** 日志Id */
+    @TableId
+    private Long logId;
+
+    /** 用户id */
+    @Excel(name = "会员ID",sort = 1)
+    private Long userId;
+
+    /** 转帐金额 */
+    @Excel(name = "转帐金额")
+    private BigDecimal amount;
+
+    /** 批次单号 */
+    @Excel(name = "批次单号")
+    private String outBatchNo;
+
+    /** 微信批次单号 */
+    @Excel(name = "微信批次单号")
+    private String batchId;
+
+    /** 状态
+     * -3取消中
+     * -2取消
+     * -1失败
+     * 0 发送中
+     * 1 已发送
+     * 2 转账已受理
+     * 3 转账处理中
+     * 4 待收款用户确认,可拉起微信收款确认页面进行收款确认
+     * 5 转账结果尚未明确,可拉起微信收款确认页面再次重试确认收款
+     */
+    @Excel(name = "状态",dictType = "wx_merchant_pay_status")
+    private String status;
+
+    /** appId */
+    private String appId;
+
+    /** 唤起收款参数 */
+    private String packageInfo;
+
+    /** 商户号 */
+    private String mchId;
+
+    /** 佣金退回状态 0:否 1:是 */
+    @Excel(name = "退佣金状态  0:否 1:是")
+    private Integer returnedStatus;
+
+
+}

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

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

+ 99 - 0
fs-service/src/main/java/com/fs/his/dto/AdProfitDetailDto.java

@@ -0,0 +1,99 @@
+package com.fs.his.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+@Getter
+@Setter
+public class AdProfitDetailDto implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键
+     */
+    private Long id;
+
+    /**
+     * 广告回调唯一交易ID
+     */
+    @Excel(name = "回调ID")
+    private String adTransId;
+
+    /**
+     * 广告任务id
+     */
+    private String adTaskId;
+
+    /**
+     * 广告平台结算总金额
+     */
+    @Excel(name = "结算总金额")
+    private BigDecimal sumMoney;
+
+    /**
+     * 用户id
+     */
+    @Excel(name = "用户id")
+    private Long userId;
+
+    /**
+     * 用户分润
+     */
+    @Excel(name = "用户分润")
+    private BigDecimal userMoney;
+
+    /**
+     * 公司id
+     */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /**
+     * 公司分润
+     */
+    @Excel(name = "公司分润")
+    private BigDecimal companyMoney;
+
+    /**
+     * 互医分润
+     */
+    @Excel(name = "互医分润")
+    private BigDecimal huyiMoney;
+
+    /**
+     * 云联分润
+     */
+    @Excel(name = "云联分润")
+    private BigDecimal yunlianMoney;
+
+    /**
+     * 创建时间
+     */
+    @Excel(name = "创建时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    /**
+     * 公司名称
+     */
+    @Excel(name = "公司名称")
+    private String companyName;
+
+    /**
+     * 用户昵称
+     */
+    @Excel(name = "用户昵称")
+    private String nickName;
+
+    @JsonFormat(pattern = "yyyy-MM-dd ")
+    private Date sTime;
+    @JsonFormat(pattern = "yyyy-MM-dd ")
+    private Date eTime;
+}

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

@@ -32,6 +32,8 @@ public enum FsUserIntegralLogTypeEnum {
     TYPE_22(22,"首次完成积分商城下单"),
     TYPE_23(23,"管理员添加"),
     TYPE_24(24, "付费课程订阅"),
+    TYPE_31(31,"广告积分"),
+    TYPE_32(32,"积分兑换佣金"),
     ;
 
 

+ 73 - 0
fs-service/src/main/java/com/fs/his/mapper/AdProfitDetailMapper.java

@@ -0,0 +1,73 @@
+package com.fs.his.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.AdProfitDetail;
+import com.fs.his.dto.AdProfitDetailDto;
+import com.fs.his.param.AdProfitDetailStatisticsParam;
+import com.fs.his.vo.AdProfitDetailStatisticsVo;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+* <p>
+* ad_profit_detail Mapper 接口
+* </p>
+*/
+public interface AdProfitDetailMapper extends BaseMapper<AdProfitDetail>  {
+    /**
+     * 查询广告分佣
+     *
+     * @param id 广告分佣主键
+     * @return 广告分佣
+     */
+    AdProfitDetail selectAdProfitDetailById(Long id);
+
+    /**
+     * 查询广告分佣列表
+     *
+     * @param adProfitDetailDto 广告分佣
+     * @return 广告分佣集合
+     */
+    List<AdProfitDetailDto> selectAdProfitDetailList(AdProfitDetailDto adProfitDetailDto);
+
+    /**
+     * 新增广告分佣
+     *
+     * @param adProfitDetail 广告分佣
+     * @return 结果
+     */
+    int insertAdProfitDetail(AdProfitDetail adProfitDetail);
+
+    /**
+     * 修改广告分佣
+     *
+     * @param adProfitDetail 广告分佣
+     * @return 结果
+     */
+    int updateAdProfitDetail(AdProfitDetail adProfitDetail);
+
+    /**
+     * 删除广告分佣
+     *
+     * @param id 广告分佣主键
+     * @return 结果
+     */
+    int deleteAdProfitDetailById(Long id);
+
+    /**
+     * 批量删除广告分佣
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteAdProfitDetailByIds(Long[] ids);
+
+    String getCompanyByUserId(String userId);
+
+    Map<String, Object> getWithFinishAndMayWithdraw();
+
+    List<AdProfitDetailStatisticsVo> statisticsList(@Param("param")AdProfitDetailStatisticsParam param);
+}

+ 81 - 0
fs-service/src/main/java/com/fs/his/mapper/FsConsecutiveWithdrawRecordMapper.java

@@ -0,0 +1,81 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsConsecutiveWithdrawRecord;
+import com.fs.his.param.FsConsecutiveWithdrawRecordParam;
+import com.fs.his.vo.FsConsecutiveWithdrawRecordVo;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * 连续提现记录Mapper接口
+ * 
+ * @author fs
+ * @date 2026-02-04
+ */
+public interface FsConsecutiveWithdrawRecordMapper extends BaseMapper<FsConsecutiveWithdrawRecord>{
+    /**
+     * 查询连续提现记录
+     * 
+     * @param id 连续提现记录主键
+     * @return 连续提现记录
+     */
+    FsConsecutiveWithdrawRecord selectFsConsecutiveWithdrawRecordById(Long id);
+
+    /**
+     * 查询连续提现记录列表
+     * 
+     * @param param 连续提现记录
+     * @return 连续提现记录集合
+     */
+    List<FsConsecutiveWithdrawRecordVo> selectFsConsecutiveWithdrawRecordList(FsConsecutiveWithdrawRecordParam param);
+
+    /**
+     * 新增连续提现记录
+     * 
+     * @param fsConsecutiveWithdrawRecord 连续提现记录
+     * @return 结果
+     */
+    int insertFsConsecutiveWithdrawRecord(FsConsecutiveWithdrawRecord fsConsecutiveWithdrawRecord);
+
+    /**
+     * 修改连续提现记录
+     * 
+     * @param fsConsecutiveWithdrawRecord 连续提现记录
+     * @return 结果
+     */
+    int updateFsConsecutiveWithdrawRecord(FsConsecutiveWithdrawRecord fsConsecutiveWithdrawRecord);
+
+    /**
+     * 删除连续提现记录
+     * 
+     * @param id 连续提现记录主键
+     * @return 结果
+     */
+    int deleteFsConsecutiveWithdrawRecordById(Long id);
+
+    /**
+     * 批量删除连续提现记录
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsConsecutiveWithdrawRecordByIds(Long[] ids);
+
+    /**
+     * 检查用户在某时间段是否已记录连续提现
+     */
+    boolean existsRecordInPeriod(@Param("userId") Long userId,
+                                 @Param("startDate") LocalDate startDate,
+                                 @Param("endDate") LocalDate endDate,
+                                 @Param("thresholdDays") int thresholdDays);
+
+    Integer countByUserAndPeriod(@Param("userId") Long userId,
+                                 @Param("startDate") LocalDate startDate,
+                                 @Param("endDate") LocalDate endDate);
+
+    FsConsecutiveWithdrawRecord selectCountByUserIdAndStartTime(@Param("userId") Long userId,
+                                                                @Param("startDate") LocalDate startDate);
+}

+ 62 - 0
fs-service/src/main/java/com/fs/his/mapper/FsIntegralExchangeMapper.java

@@ -0,0 +1,62 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsIntegralExchange;
+
+import java.util.List;
+
+/**
+ * 用户积分提现佣金记录Mapper接口
+ * 
+ * @author fs
+ * @date 2026-01-09
+ */
+public interface FsIntegralExchangeMapper extends BaseMapper<FsIntegralExchange>{
+    /**
+     * 查询用户积分提现佣金记录
+     * 
+     * @param id 用户积分提现佣金记录主键
+     * @return 用户积分提现佣金记录
+     */
+    FsIntegralExchange selectFsIntegralExchangeById(Long id);
+
+    /**
+     * 查询用户积分提现佣金记录列表
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 用户积分提现佣金记录集合
+     */
+    List<FsIntegralExchange> selectFsIntegralExchangeList(FsIntegralExchange fsIntegralExchange);
+
+    /**
+     * 新增用户积分提现佣金记录
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 结果
+     */
+    int insertFsIntegralExchange(FsIntegralExchange fsIntegralExchange);
+
+    /**
+     * 修改用户积分提现佣金记录
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 结果
+     */
+    int updateFsIntegralExchange(FsIntegralExchange fsIntegralExchange);
+
+    /**
+     * 删除用户积分提现佣金记录
+     * 
+     * @param id 用户积分提现佣金记录主键
+     * @return 结果
+     */
+    int deleteFsIntegralExchangeById(Long id);
+
+    /**
+     * 批量删除用户积分提现佣金记录
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsIntegralExchangeByIds(Long[] ids);
+}

+ 103 - 0
fs-service/src/main/java/com/fs/his/mapper/FsIntegralRedPacketLogMapper.java

@@ -0,0 +1,103 @@
+package com.fs.his.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.his.domain.FsIntegralRedPacketLog;
+import com.fs.his.param.FsIntegralRedPacketLogParam;
+import com.fs.his.vo.FsIntegralRedPacketLogVo;
+import org.apache.ibatis.annotations.Param;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 积分佣金红包记录Mapper接口
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+public interface FsIntegralRedPacketLogMapper extends BaseMapper<FsIntegralRedPacketLog>{
+    /**
+     * 查询积分佣金红包记录
+     * 
+     * @param logId 积分佣金红包记录主键
+     * @return 积分佣金红包记录
+     */
+    FsIntegralRedPacketLog selectFsIntegralRedPacketLogByLogId(Long logId);
+
+    /**
+     * 查询积分佣金红包记录列表
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 积分佣金红包记录集合
+     */
+    List<FsIntegralRedPacketLog> selectFsIntegralRedPacketLogList(FsIntegralRedPacketLog fsIntegralRedPacketLog);
+
+    /**
+     * 新增积分佣金红包记录
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 结果
+     */
+    int insertFsIntegralRedPacketLog(FsIntegralRedPacketLog fsIntegralRedPacketLog);
+
+    /**
+     * 修改积分佣金红包记录
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 结果
+     */
+    int updateFsIntegralRedPacketLog(FsIntegralRedPacketLog fsIntegralRedPacketLog);
+
+    /**
+     * 删除积分佣金红包记录
+     * 
+     * @param logId 积分佣金红包记录主键
+     * @return 结果
+     */
+    int deleteFsIntegralRedPacketLogByLogId(Long logId);
+
+    /**
+     * 批量删除积分佣金红包记录
+     * 
+     * @param logIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsIntegralRedPacketLogByLogIds(Long[] logIds);
+
+    BigDecimal sumMoneyByUserId(@Param("userId") Long userId);
+
+    FsIntegralRedPacketLog selectFsIntegralRedPacketLogByBatchNo(@Param("outBatchNo") String outBatchNo);
+
+    void batchUpdate(@Param("list")List<FsIntegralRedPacketLog> list);
+
+    List<FsIntegralRedPacketLogVo> getList(FsIntegralRedPacketLogParam param);
+
+    Long countLogsByToday(@Param("userId") Long userId);
+
+    /**
+     * 查询时间段内有提现的用户
+     */
+
+    List<Long> selectUsersWithWithdrawInPeriod(@Param("startDate") LocalDate startDate,
+                                               @Param("endDate") LocalDate endDate,
+                                               @Param("limitAmount") BigDecimal limitAmount);
+
+    /**
+     * 查询用户提现日期列表
+     */
+    List<LocalDate> selectWithdrawDatesByUser(@Param("userId") Long userId,
+                                              @Param("startDate") LocalDate startDate,
+                                              @Param("endDate") LocalDate endDate);
+
+    /**
+     * 查询用户时间段内提现统计
+     */
+    Map<String, Object> selectWithdrawStatsByUserAndPeriod(
+            @Param("userId") Long userId,
+            @Param("startDate") LocalDate startDate,
+            @Param("endDate") LocalDate endDate);
+
+    BigDecimal sumMoneyByStatus(@Param("status")int status);
+}

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

@@ -3,6 +3,7 @@ package com.fs.his.mapper;
 import com.fs.his.domain.FsUserIntegralLogs;
 import com.fs.his.param.FsUserIntegralLogsListUParam;
 import com.fs.his.param.FsUserIntegralLogsParam;
+import com.fs.his.vo.ExchangeDetailVo;
 import com.fs.his.vo.FsUserIntegralLogsListUVO;
 import com.fs.his.vo.FsUserIntegralLogsListVO;
 import com.fs.his.vo.SubIntegralVO;
@@ -139,4 +140,6 @@ public interface FsUserIntegralLogsMapper
     Long selectH5VideoIntegralCount(@Param("userId") Long userId,@Param("videoId") Long videoId);
 
     List<FsUserIntegralLogs> selectFsUserIntegralLogsByUserIdAndLogType(@Param("userId") Long userId, @Param("logType") Integer logType, @Param("date") LocalDate date);
+
+    List<ExchangeDetailVo> getExchangDetailList(ExchangeDetailVo detailVo);
 }

+ 11 - 4
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -14,11 +14,9 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.dto.AppUserCompanyDTO;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.param.FindUserByParam;
+import com.fs.his.param.FsUserDisabledUsersParam;
 import com.fs.his.param.FsUserParam;
-import com.fs.his.vo.FsUserVO;
-import com.fs.his.vo.FsUserExportListVO;
-import com.fs.his.vo.OptionsVO;
-import com.fs.his.vo.UserOpenIdVO;
+import com.fs.his.vo.*;
 import com.fs.hisStore.vo.FsCompanyUserListQueryVO;
 import com.fs.qw.dto.FsUserTransferParamDTO;
 import com.fs.qw.param.QwFsUserParam;
@@ -557,4 +555,13 @@ public interface FsUserMapper
             "</if> " +
             "</script>"})
     List<AppUserCompanyDTO> selectAppUserListForActiveCount(@Param("param")FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 获取用户钱包明细
+     */
+    IntegralExchangeVo getWallet(@Param("userId") Long userId);
+
+    int addIntegral(@Param("userId") Long userId,@Param("integral") Long integral);
+
+    int disabledUsers(@Param("param") FsUserDisabledUsersParam param);
 }

+ 12 - 0
fs-service/src/main/java/com/fs/his/param/AdProfitDetailStatisticsParam.java

@@ -0,0 +1,12 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+@Data
+public class AdProfitDetailStatisticsParam {
+    Integer type;//"类型 1今天 2昨天 3 本周 4 上周 5本月 6上月 7本季度 8上季度 9本年 10去年
+    String companyIds;//员工IDS 多个用,分割
+    Long[] companies;
+    String startTime;
+    String endTime;
+}

+ 9 - 0
fs-service/src/main/java/com/fs/his/param/FsConsecutiveWithdrawRecordParam.java

@@ -0,0 +1,9 @@
+package com.fs.his.param;
+
+import com.fs.his.domain.FsConsecutiveWithdrawRecord;
+import lombok.Data;
+
+@Data
+public class FsConsecutiveWithdrawRecordParam extends FsConsecutiveWithdrawRecord {
+    private String nickName;
+}

+ 13 - 0
fs-service/src/main/java/com/fs/his/param/FsIntegralRedPacketLogParam.java

@@ -0,0 +1,13 @@
+package com.fs.his.param;
+
+import com.fs.his.domain.FsIntegralRedPacketLog;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class FsIntegralRedPacketLogParam extends FsIntegralRedPacketLog {
+    private String nickName;
+    private BigDecimal minAmount;
+    private BigDecimal maxAmount;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/his/param/FsIntegralWithdrawalParam.java

@@ -0,0 +1,19 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class FsIntegralWithdrawalParam {
+    private Long userId;
+
+    //申请提现金额
+    private BigDecimal applicationAmount;
+
+    private String appId;
+
+    private Integer source;//来源 1:h5 2:彩虹汇医小程序 3:APP
+
+
+}

+ 4 - 1
fs-service/src/main/java/com/fs/his/param/FsUserAddIntegralParam.java

@@ -13,6 +13,9 @@ public class FsUserAddIntegralParam implements Serializable {
 
     private Long userId;
 
-    private Integer type; //类型1浏览商品 2刷视频 3邀请奖励 4被邀请奖励
+    private Integer type; //类型1浏览商品 2刷视频 3邀请奖励 4被邀请奖励 5广告积分
+
+    // 广告积分使用
+    private String extra;
 
 }

+ 11 - 0
fs-service/src/main/java/com/fs/his/param/FsUserDisabledUsersParam.java

@@ -0,0 +1,11 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FsUserDisabledUsersParam {
+    private List<Long> userIds;
+    private String remark;
+}

+ 70 - 0
fs-service/src/main/java/com/fs/his/service/IAdProfitDetailService.java

@@ -0,0 +1,70 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.AdProfitDetail;
+import com.fs.his.dto.AdProfitDetailDto;
+import com.fs.his.param.AdProfitDetailStatisticsParam;
+import com.fs.his.vo.AdProfitDetailStatisticsVo;
+
+import java.util.List;
+
+/**
+ * 广告分佣Service接口
+ * 
+ * @author fs
+ * @date 2025-11-27
+ */
+public interface IAdProfitDetailService extends IService<AdProfitDetail>{
+    /**
+     * 查询广告分佣
+     * 
+     * @param id 广告分佣主键
+     * @return 广告分佣
+     */
+    AdProfitDetail selectAdProfitDetailById(Long id);
+
+    /**
+     * 查询广告分佣列表
+     * 
+     * @param adProfitDetailDto 广告分佣
+     * @return 广告分佣集合
+     */
+    List<AdProfitDetailDto> selectAdProfitDetailList(AdProfitDetailDto adProfitDetailDto);
+
+    /**
+     * 新增广告分佣
+     * 
+     * @param adProfitDetail 广告分佣
+     * @return 结果
+     */
+    int insertAdProfitDetail(AdProfitDetail adProfitDetail);
+
+    /**
+     * 修改广告分佣
+     * 
+     * @param adProfitDetail 广告分佣
+     * @return 结果
+     */
+    int updateAdProfitDetail(AdProfitDetail adProfitDetail);
+
+    /**
+     * 批量删除广告分佣
+     * 
+     * @param ids 需要删除的广告分佣主键集合
+     * @return 结果
+     */
+    int deleteAdProfitDetailByIds(Long[] ids);
+
+    /**
+     * 删除广告分佣信息
+     * 
+     * @param id 广告分佣主键
+     * @return 结果
+     */
+    int deleteAdProfitDetailById(Long id);
+
+    R getWithFinishAndMayWithdraw();
+
+    List<AdProfitDetailStatisticsVo> statisticsList(AdProfitDetailStatisticsParam param);
+}

+ 66 - 0
fs-service/src/main/java/com/fs/his/service/IFsConsecutiveWithdrawRecordService.java

@@ -0,0 +1,66 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsConsecutiveWithdrawRecord;
+import com.fs.his.param.FsConsecutiveWithdrawRecordParam;
+import com.fs.his.vo.FsConsecutiveWithdrawRecordVo;
+
+import java.util.List;
+
+/**
+ * 连续提现记录Service接口
+ * 
+ * @author fs
+ * @date 2026-02-04
+ */
+public interface IFsConsecutiveWithdrawRecordService extends IService<FsConsecutiveWithdrawRecord>{
+    /**
+     * 查询连续提现记录
+     * 
+     * @param id 连续提现记录主键
+     * @return 连续提现记录
+     */
+    FsConsecutiveWithdrawRecord selectFsConsecutiveWithdrawRecordById(Long id);
+
+    /**
+     * 查询连续提现记录列表
+     * 
+     * @param fsConsecutiveWithdrawRecord 连续提现记录
+     * @return 连续提现记录集合
+     */
+    List<FsConsecutiveWithdrawRecordVo> selectFsConsecutiveWithdrawRecordList(FsConsecutiveWithdrawRecordParam fsConsecutiveWithdrawRecord);
+
+    /**
+     * 新增连续提现记录
+     * 
+     * @param fsConsecutiveWithdrawRecord 连续提现记录
+     * @return 结果
+     */
+    int insertFsConsecutiveWithdrawRecord(FsConsecutiveWithdrawRecord fsConsecutiveWithdrawRecord);
+
+    /**
+     * 修改连续提现记录
+     * 
+     * @param fsConsecutiveWithdrawRecord 连续提现记录
+     * @return 结果
+     */
+    int updateFsConsecutiveWithdrawRecord(FsConsecutiveWithdrawRecord fsConsecutiveWithdrawRecord);
+
+    /**
+     * 批量删除连续提现记录
+     * 
+     * @param ids 需要删除的连续提现记录主键集合
+     * @return 结果
+     */
+    int deleteFsConsecutiveWithdrawRecordByIds(Long[] ids);
+
+    /**
+     * 删除连续提现记录信息
+     * 
+     * @param id 连续提现记录主键
+     * @return 结果
+     */
+    int deleteFsConsecutiveWithdrawRecordById(Long id);
+
+    void checkConsecutiveWithdrawUsers();
+}

+ 62 - 0
fs-service/src/main/java/com/fs/his/service/IFsIntegralExchangeService.java

@@ -0,0 +1,62 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.his.domain.FsIntegralExchange;
+
+import java.util.List;
+
+/**
+ * 用户积分提现佣金记录Service接口
+ * 
+ * @author fs
+ * @date 2026-01-09
+ */
+public interface IFsIntegralExchangeService extends IService<FsIntegralExchange>{
+    /**
+     * 查询用户积分提现佣金记录
+     * 
+     * @param id 用户积分提现佣金记录主键
+     * @return 用户积分提现佣金记录
+     */
+    FsIntegralExchange selectFsIntegralExchangeById(Long id);
+
+    /**
+     * 查询用户积分提现佣金记录列表
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 用户积分提现佣金记录集合
+     */
+    List<FsIntegralExchange> selectFsIntegralExchangeList(FsIntegralExchange fsIntegralExchange);
+
+    /**
+     * 新增用户积分提现佣金记录
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 结果
+     */
+    int insertFsIntegralExchange(FsIntegralExchange fsIntegralExchange);
+
+    /**
+     * 修改用户积分提现佣金记录
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 结果
+     */
+    int updateFsIntegralExchange(FsIntegralExchange fsIntegralExchange);
+
+    /**
+     * 批量删除用户积分提现佣金记录
+     * 
+     * @param ids 需要删除的用户积分提现佣金记录主键集合
+     * @return 结果
+     */
+    int deleteFsIntegralExchangeByIds(Long[] ids);
+
+    /**
+     * 删除用户积分提现佣金记录信息
+     * 
+     * @param id 用户积分提现佣金记录主键
+     * @return 结果
+     */
+    int deleteFsIntegralExchangeById(Long id);
+}

+ 74 - 0
fs-service/src/main/java/com/fs/his/service/IFsIntegralRedPacketLogService.java

@@ -0,0 +1,74 @@
+package com.fs.his.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.FsIntegralRedPacketLog;
+import com.fs.his.param.FsIntegralRedPacketLogParam;
+import com.fs.his.vo.FsIntegralRedPacketLogVo;
+import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
+
+import java.util.List;
+
+/**
+ * 积分佣金红包记录Service接口
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+public interface IFsIntegralRedPacketLogService extends IService<FsIntegralRedPacketLog>{
+    /**
+     * 查询积分佣金红包记录
+     * 
+     * @param logId 积分佣金红包记录主键
+     * @return 积分佣金红包记录
+     */
+    FsIntegralRedPacketLog selectFsIntegralRedPacketLogByLogId(Long logId);
+
+    /**
+     * 查询积分佣金红包记录列表
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 积分佣金红包记录集合
+     */
+    List<FsIntegralRedPacketLog> selectFsIntegralRedPacketLogList(FsIntegralRedPacketLog fsIntegralRedPacketLog);
+    List<FsIntegralRedPacketLogVo> getList(FsIntegralRedPacketLogParam param);
+
+    /**
+     * 新增积分佣金红包记录
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 结果
+     */
+    int insertFsIntegralRedPacketLog(FsIntegralRedPacketLog fsIntegralRedPacketLog);
+
+    /**
+     * 修改积分佣金红包记录
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 结果
+     */
+    int updateFsIntegralRedPacketLog(FsIntegralRedPacketLog fsIntegralRedPacketLog);
+
+    /**
+     * 批量删除积分佣金红包记录
+     * 
+     * @param logIds 需要删除的积分佣金红包记录主键集合
+     * @return 结果
+     */
+    int deleteFsIntegralRedPacketLogByLogIds(Long[] logIds);
+
+    /**
+     * 删除积分佣金红包记录信息
+     * 
+     * @param logId 积分佣金红包记录主键
+     * @return 结果
+     */
+    int deleteFsIntegralRedPacketLogByLogId(Long logId);
+
+    R syncRedPacket(String outBatchNo, String batchId);
+    R syncErrorRedPacket(TransferBillsNotifyResult.DecryptNotifyResult result);
+
+    FsIntegralRedPacketLog getRedPacketLogByCode(String orderCode);
+
+    void synchronizationWxMerchantPayStatus();
+}

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

@@ -137,4 +137,14 @@ public interface IFsStorePaymentService
     String payConfirm(String payCode,String tradeNo,String bankTransactionId,String bankSerialNo);
 
     void synchronizePayStatus();
+
+    /**
+     * 积分提现
+     */
+    R sendIntegralRedPacket(WxSendRedPacketParam packetParam);
+
+    /**
+     * 提现回调
+     */
+    String integralV3TransferNotify(String notifyData, HttpServletRequest request);
 }

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

@@ -2,10 +2,7 @@ package com.fs.his.service;
 
 import com.fs.common.core.domain.R;
 import com.fs.his.domain.FsUserIntegralLogs;
-import com.fs.his.param.FsUserAddIntegralParam;
-import com.fs.his.param.FsUserAddIntegralTemplateParam;
-import com.fs.his.param.FsUserIntegralLogsListUParam;
-import com.fs.his.param.FsUserIntegralLogsParam;
+import com.fs.his.param.*;
 import com.fs.his.vo.FsUserIntegralLogsListUVO;
 import com.fs.his.vo.FsUserIntegralLogsListVO;
 
@@ -86,4 +83,6 @@ public interface IFsUserIntegralLogsService
 
     //app获取新人福利完成情况
     R getNewcomerBenefits(Long userId);
+
+    Long sumIntegralByLogTypeAndCreateTime(Integer logType, AdProfitDetailStatisticsParam param);
 }

+ 37 - 0
fs-service/src/main/java/com/fs/his/service/IFsUserService.java

@@ -16,6 +16,8 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserAddress;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.param.FindUserByParam;
+import com.fs.his.param.FsIntegralWithdrawalParam;
+import com.fs.his.param.FsUserDisabledUsersParam;
 import com.fs.his.param.FsUserParam;
 import com.fs.his.vo.*;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
@@ -277,4 +279,39 @@ public interface IFsUserService
 
     //首页app用户统计
     AppUserCountVO getAppUserCount();
+
+    /**
+     * 接收 UniApp 激励广告回调
+     */
+    R uniCallBack(Map<String, Object> params);
+
+    /**
+     * uniapp广告 返回一个广告id
+     */
+    R createLogs(Long userId);
+
+    /**
+     * 获取用户钱包明细
+     */
+    R getWallet(Long userId);
+
+    /**
+     * 用户钱包 积分兑换佣金
+     */
+    R integralExchange(Long userId);
+
+    /**
+     * 用户钱包 获取兑换明细
+     */
+    R exchangDetail(Map<String, Object> params);
+
+    /**
+     * 用户提现
+     */
+    R withdrawal(FsIntegralWithdrawalParam param);
+
+    /**
+     * 批量禁用用户
+     */
+    int disabledUsers(FsUserDisabledUsersParam param);
 }

+ 199 - 0
fs-service/src/main/java/com/fs/his/service/impl/AdProfitDetailServiceImpl.java

@@ -0,0 +1,199 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.core.domain.R;
+import com.fs.his.domain.AdProfitDetail;
+import com.fs.his.dto.AdProfitDetailDto;
+import com.fs.his.enums.FsUserIntegralLogTypeEnum;
+import com.fs.his.mapper.AdProfitDetailMapper;
+import com.fs.his.mapper.FsIntegralRedPacketLogMapper;
+import com.fs.his.param.AdProfitDetailStatisticsParam;
+import com.fs.his.service.IAdProfitDetailService;
+import com.fs.his.service.IFsUserIntegralLogsService;
+import com.fs.his.vo.AdProfitDetailStatisticsVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * 广告分佣Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-11-27
+ */
+@Service
+@Slf4j
+public class AdProfitDetailServiceImpl extends ServiceImpl<AdProfitDetailMapper, AdProfitDetail> implements IAdProfitDetailService {
+    @Autowired
+    private FsIntegralRedPacketLogMapper fsIntegralRedPacketLogMapper;
+    @Autowired
+    private IFsUserIntegralLogsService fsUserIntegralLogsService;
+    @Autowired
+    @Qualifier("threadPoolTaskExecutor")
+    private ThreadPoolTaskExecutor executor;
+
+    /**
+     * 查询广告分佣
+     * 
+     * @param id 广告分佣主键
+     * @return 广告分佣
+     */
+    @Override
+    public AdProfitDetail selectAdProfitDetailById(Long id)
+    {
+        return baseMapper.selectAdProfitDetailById(id);
+    }
+
+    /**
+     * 查询广告分佣列表
+     * 
+     * @param adProfitDetailDto 广告分佣
+     * @return 广告分佣
+     */
+    @Override
+    public List<AdProfitDetailDto> selectAdProfitDetailList(AdProfitDetailDto adProfitDetailDto)
+    {
+        List<AdProfitDetailDto> list = baseMapper.selectAdProfitDetailList(adProfitDetailDto);
+        list.stream().forEach(item -> {
+            item.setSumMoney(item.getSumMoney() != null ? item.getSumMoney().divide(BigDecimal.valueOf(100)) : null);
+            item.setUserMoney(item.getUserMoney() != null ? item.getUserMoney().divide(BigDecimal.valueOf(100)) : null);
+            item.setCompanyMoney(item.getCompanyMoney() != null ? item.getCompanyMoney().divide(BigDecimal.valueOf(100)) : null);
+            item.setYunlianMoney(item.getYunlianMoney() != null ? item.getYunlianMoney().divide(BigDecimal.valueOf(100)) : null);
+            item.setHuyiMoney(item.getHuyiMoney() != null ? item.getHuyiMoney().divide(BigDecimal.valueOf(100)) : null);
+        });
+        return list;
+    }
+
+    /**
+     * 新增广告分佣
+     * 
+     * @param adProfitDetail 广告分佣
+     * @return 结果
+     */
+    @Override
+    public int insertAdProfitDetail(AdProfitDetail adProfitDetail)
+    {
+        adProfitDetail.setCreateTime(LocalDateTime.now());
+        return baseMapper.insertAdProfitDetail(adProfitDetail);
+    }
+
+    /**
+     * 修改广告分佣
+     * 
+     * @param adProfitDetail 广告分佣
+     * @return 结果
+     */
+    @Override
+    public int updateAdProfitDetail(AdProfitDetail adProfitDetail)
+    {
+        return baseMapper.updateAdProfitDetail(adProfitDetail);
+    }
+
+    /**
+     * 批量删除广告分佣
+     * 
+     * @param ids 需要删除的广告分佣主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAdProfitDetailByIds(Long[] ids)
+    {
+        return baseMapper.deleteAdProfitDetailByIds(ids);
+    }
+
+    /**
+     * 删除广告分佣信息
+     * 
+     * @param id 广告分佣主键
+     * @return 结果
+     */
+    @Override
+    public int deleteAdProfitDetailById(Long id)
+    {
+        return baseMapper.deleteAdProfitDetailById(id);
+    }
+
+    @Override
+    public R getWithFinishAndMayWithdraw() {
+        Map<String,Object> map = baseMapper.getWithFinishAndMayWithdraw();
+        if (map == null) {
+            Map<String,Object> widthdrawMap = new HashMap<>();
+            widthdrawMap.put("totalMayWithdraw", BigDecimal.ZERO);
+            widthdrawMap.put("totalWithdrawFinish", BigDecimal.ZERO);
+            widthdrawMap.put("withdrawMoney", BigDecimal.ZERO);
+            return R.ok(widthdrawMap);
+        }
+        // 可提现总金额
+        BigDecimal totalMayWithdraw = new BigDecimal(map.get("totalMayWithdraw").toString()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN);
+        // 已提现总金额
+        BigDecimal totalWithdrawFinish = new BigDecimal(map.get("totalWithdrawFinish").toString()).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN);
+        // 提现中金额
+        BigDecimal withdrawMoney = fsIntegralRedPacketLogMapper.sumMoneyByStatus(0);
+        if (withdrawMoney != null) {
+            totalMayWithdraw = totalMayWithdraw.subtract(withdrawMoney);
+            totalWithdrawFinish = totalWithdrawFinish.subtract(withdrawMoney);
+            withdrawMoney = withdrawMoney.setScale(2, RoundingMode.UP);
+        } else {
+            withdrawMoney = BigDecimal.ZERO;
+        }
+        Map<String,Object> widthdrawMap = new HashMap<>();
+        widthdrawMap.put("totalMayWithdraw",totalMayWithdraw);
+        widthdrawMap.put("totalWithdrawFinish",totalWithdrawFinish);
+        widthdrawMap.put("withdrawMoney",withdrawMoney);
+        return R.ok(widthdrawMap);
+    }
+
+    @Override
+    public List<AdProfitDetailStatisticsVo> statisticsList(AdProfitDetailStatisticsParam param) {
+        // 并行执行两个查询
+        CompletableFuture<List<AdProfitDetailStatisticsVo>> vosFuture = CompletableFuture
+                .supplyAsync(() -> baseMapper.statisticsList(param), executor);
+
+        CompletableFuture<BigDecimal> integralFuture = CompletableFuture
+                .supplyAsync(() -> {
+                    Long integral = fsUserIntegralLogsService.sumIntegralByLogTypeAndCreateTime(
+                            FsUserIntegralLogTypeEnum.TYPE_31.getValue(), param
+                    );
+                    return integral != null && integral > 0
+                            ? new BigDecimal(integral).divide(BigDecimal.valueOf(1000), 2, RoundingMode.HALF_UP)
+                            : BigDecimal.ZERO;
+                }, executor);
+
+        try {
+            // 等待两个都完成
+            CompletableFuture.allOf(vosFuture, integralFuture)
+                    .get(60, TimeUnit.SECONDS);
+
+            List<AdProfitDetailStatisticsVo> vos = vosFuture.getNow(null);
+            BigDecimal totalIntegral = integralFuture.getNow(BigDecimal.ZERO);
+
+            if (vos != null && !vos.isEmpty()) {
+                vos.get(0).setAmountYuan(totalIntegral);
+            }
+
+            return vos != null ? vos : Collections.emptyList();
+
+        } catch (TimeoutException e) {
+            log.error("查询超时", e);
+            vosFuture.cancel(true);
+            integralFuture.cancel(true);
+            throw new RuntimeException("查询超时,请稍后重试");
+        } catch (Exception e) {
+            log.error("查询异常", e);
+            throw new RuntimeException("查询执行异常", e);
+        }
+    }
+}

+ 404 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsConsecutiveWithdrawRecordServiceImpl.java

@@ -0,0 +1,404 @@
+package com.fs.his.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.config.IntegralConfig;
+import com.fs.his.domain.FsConsecutiveWithdrawRecord;
+import com.fs.his.mapper.FsConsecutiveWithdrawRecordMapper;
+import com.fs.his.mapper.FsIntegralRedPacketLogMapper;
+import com.fs.his.param.FsConsecutiveWithdrawRecordParam;
+import com.fs.his.service.IFsConsecutiveWithdrawRecordService;
+import com.fs.his.vo.FsConsecutiveWithdrawRecordVo;
+import com.fs.system.service.ISysConfigService;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 连续提现记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-02-04
+ */
+@Service
+@Slf4j
+public class FsConsecutiveWithdrawRecordServiceImpl extends ServiceImpl<FsConsecutiveWithdrawRecordMapper, FsConsecutiveWithdrawRecord> implements IFsConsecutiveWithdrawRecordService {
+    @Autowired
+    private FsIntegralRedPacketLogMapper redPacketLogMapper;
+
+    @Autowired
+    private RedissonClient redissonClient;
+
+    @Autowired
+    private ISysConfigService configService;
+
+    /**
+     * 查询连续提现记录
+     * 
+     * @param id 连续提现记录主键
+     * @return 连续提现记录
+     */
+    @Override
+    public FsConsecutiveWithdrawRecord selectFsConsecutiveWithdrawRecordById(Long id)
+    {
+        return baseMapper.selectFsConsecutiveWithdrawRecordById(id);
+    }
+
+    /**
+     * 查询连续提现记录列表
+     * 
+     * @param param 连续提现记录
+     * @return 连续提现记录
+     */
+    @Override
+    public List<FsConsecutiveWithdrawRecordVo> selectFsConsecutiveWithdrawRecordList(FsConsecutiveWithdrawRecordParam param)
+    {
+        return baseMapper.selectFsConsecutiveWithdrawRecordList(param);
+    }
+
+    /**
+     * 新增连续提现记录
+     * 
+     * @param fsConsecutiveWithdrawRecord 连续提现记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsConsecutiveWithdrawRecord(FsConsecutiveWithdrawRecord fsConsecutiveWithdrawRecord)
+    {
+        fsConsecutiveWithdrawRecord.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsConsecutiveWithdrawRecord(fsConsecutiveWithdrawRecord);
+    }
+
+    /**
+     * 修改连续提现记录
+     * 
+     * @param fsConsecutiveWithdrawRecord 连续提现记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsConsecutiveWithdrawRecord(FsConsecutiveWithdrawRecord fsConsecutiveWithdrawRecord)
+    {
+        fsConsecutiveWithdrawRecord.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsConsecutiveWithdrawRecord(fsConsecutiveWithdrawRecord);
+    }
+
+    /**
+     * 批量删除连续提现记录
+     * 
+     * @param ids 需要删除的连续提现记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsConsecutiveWithdrawRecordByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsConsecutiveWithdrawRecordByIds(ids);
+    }
+
+    /**
+     * 删除连续提现记录信息
+     * 
+     * @param id 连续提现记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsConsecutiveWithdrawRecordById(Long id)
+    {
+        return baseMapper.deleteFsConsecutiveWithdrawRecordById(id);
+    }
+    // 缓存已处理的用户和日期,避免重复查询
+    private final Set<String> processedUserPeriodCache = ConcurrentHashMap.newKeySet();
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void checkConsecutiveWithdrawUsers() {
+        if (!tryLockTask()) {
+            log.info("连续提现检查任务已在其他节点执行中,本次跳过");
+            return;
+        }
+        try {
+            log.info("========== 开始执行连续提现检查任务 ==========");
+            // 清空缓存
+            processedUserPeriodCache.clear();
+            executeCheck();
+            log.info("========== 连续提现检查任务执行完成 ==========");
+        } catch (Exception e) {
+            log.error("连续提现检查任务执行失败", e);
+            throw e; // 抛出异常让事务回滚
+        } finally {
+            unlockTask();
+        }
+    }
+
+
+    /**
+     * 查找连续提现期间
+     */
+    private List<ConsecutivePeriod> findConsecutivePeriods(List<LocalDate> dates) {
+        List<ConsecutivePeriod> periods = new ArrayList<>();
+
+        if (dates.isEmpty()) {
+            return periods;
+        }
+
+        // 去重并排序
+        Set<LocalDate> dateSet = new TreeSet<>(dates);
+        List<LocalDate> sortedDates = new ArrayList<>(dateSet);
+
+        LocalDate currentStart = sortedDates.get(0);
+        LocalDate previousDate = currentStart;
+        int currentStreak = 1;
+
+        for (int i = 1; i < sortedDates.size(); i++) {
+            LocalDate currentDate = sortedDates.get(i);
+            // 计算天数差
+            if (previousDate.plusDays(1).equals(currentDate)) {
+                // 连续
+                currentStreak++;
+            } else {
+                // 不连续,结束当前连续期间
+                if (currentStreak > 1) {
+                    periods.add(new ConsecutivePeriod(
+                            currentStart, previousDate, currentStreak));
+                }
+                // 开始新的连续期间
+                currentStart = currentDate;
+                currentStreak = 1;
+            }
+
+            previousDate = currentDate;
+        }
+
+        // 添加最后一个连续期间
+        if (currentStreak > 1) {
+            periods.add(new ConsecutivePeriod(
+                    currentStart, previousDate, currentStreak));
+        }
+
+        return periods;
+    }
+
+    /**
+     * 记录连续提现
+     */
+    private Boolean recordConsecutiveWithdraw(Long userId, ConsecutivePeriod period,BigDecimal limitAmount) {
+        // 1. 检查是否已记录
+        boolean exists = checkRecordExists(userId, period.getStartDate(), period.getEndDate());
+        if (exists) {
+            log.debug("记录已存在 - 用户: {}, 期间: {} 到 {}",
+                    userId, period.getStartDate(), period.getEndDate());
+            return false;
+        }
+        //查询开始时间是否一致 一致则更新
+        FsConsecutiveWithdrawRecord oldRecord = baseMapper.selectCountByUserIdAndStartTime(userId, period.getStartDate());
+
+        // 查询连续期间提现详情
+        Map<String, Object> withdrawStats = redPacketLogMapper.selectWithdrawStatsByUserAndPeriod(
+                userId, period.getStartDate(), period.getEndDate());
+
+        FsConsecutiveWithdrawRecord record = new FsConsecutiveWithdrawRecord();
+
+        record.setConsecutiveDays(period.getConsecutiveDays());
+
+        record.setEndDate(Date.from(period.getEndDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
+        record.setWithdrawCount(((Number) withdrawStats.get("count")).intValue());
+        Object totalAmount = withdrawStats.get("total_amount");
+        if (totalAmount == null || ((BigDecimal)totalAmount).compareTo(limitAmount)<0) {
+            log.info("用户UserId:{},提现:{},未达到阈值:{}",userId,totalAmount,limitAmount);
+            return false;
+        }
+        record.setTotalAmount((BigDecimal) withdrawStats.get("total_amount"));
+
+        int i = 0;
+        if (oldRecord != null) {
+            //更新
+            record.setId(oldRecord.getId());
+            record.setUpdateTime(DateUtils.getNowDate());
+            i = baseMapper.updateFsConsecutiveWithdrawRecord(record);
+        } else {
+            //新增
+            record.setUserId(userId);
+            record.setStartDate(Date.from(period.getStartDate().atStartOfDay(ZoneId.systemDefault()).toInstant()));
+            record.setCreateTime(DateUtils.getNowDate());
+            i = baseMapper.insertFsConsecutiveWithdrawRecord(record);
+        }
+        log.info("记录用户 {} 连续提现: {}天 ({} 到 {})",
+                userId, period.getConsecutiveDays(),
+                period.getStartDate(), period.getEndDate());
+        return i > 0;
+    }
+    /**
+     * 检查记录是否已存在
+     */
+    private boolean checkRecordExists(Long userId, LocalDate startDate, LocalDate endDate) {
+        Integer count = baseMapper.countByUserAndPeriod(userId,startDate,endDate);
+        return count != null && count > 0;
+    }
+
+
+    /**
+     * 连续期间内部类
+     */
+    @Data
+    @AllArgsConstructor
+    private static class ConsecutivePeriod {
+        private LocalDate startDate;
+        private LocalDate endDate;
+        private int consecutiveDays;
+    }
+
+    /**
+     * 执行连续提现检查
+     */
+    private void executeCheck() {
+
+        // 1.获取动态配置
+        String json = configService.selectConfigByKey("his.integral");
+        IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+        int checkRangeDays = 30;//检查时间范围的天数
+        Integer thresholdDays = config.getLimitDayNum();
+        BigDecimal limitAmount = config.getLimitAmount();
+        if (thresholdDays == null || thresholdDays <=0){
+            log.error("连续天数阈值配置无效: {}", thresholdDays);
+            return;
+        }
+        if (limitAmount == null || limitAmount.compareTo(BigDecimal.ZERO) <= 0){
+            log.error("连续累计金额阈值配置无效: {}", thresholdDays);
+            return;
+        }
+        log.info("当前配置: 连续天数阈值={}天, 检查范围={}天,累计金额阈值={}", thresholdDays, checkRangeDays, limitAmount);
+        // 2. 计算日期范围
+        LocalDate endDate = LocalDate.now().minusDays(1);  // 昨天
+//        LocalDate endDate = LocalDate.now();  // 今天
+        LocalDate startDate = endDate.minusDays(checkRangeDays - 1);
+        // 3. 查询最近有提现且累计金额大于大于阈值的用户
+        List<Long> recentWithdrawUsers = redPacketLogMapper.selectUsersWithWithdrawInPeriod(
+                startDate, endDate,limitAmount);
+        if (recentWithdrawUsers.isEmpty()) {
+            log.info("近期没有累计金额达到阈值用户提现");
+            return;
+        }
+        log.info("近期提现用户数量: {}", recentWithdrawUsers.size());
+        // 4. 批量检查每个用户
+        int batchSize = 100;
+        int qualifiedCount = 0;
+        int recordedCount = 0;
+
+        for (int i = 0; i < recentWithdrawUsers.size(); i += batchSize) {
+            int endIndex = Math.min(i + batchSize, recentWithdrawUsers.size());
+            List<Long> batchUsers = recentWithdrawUsers.subList(i, endIndex);
+
+            // 处理本批次用户
+            for (Long userId : batchUsers) {
+                try {
+                    int batchRecorded = processUserWithdrawRecord(
+                            userId, thresholdDays, startDate, endDate,limitAmount);
+                    if (batchRecorded > 0) {
+                        qualifiedCount++;
+                        recordedCount += batchRecorded;
+                    }
+                } catch (Exception e) {
+                    log.error("检查用户 {} 连续提现记录失败", userId, e);
+                }
+            }
+
+            log.info("已处理 {}/{} 个用户", endIndex, recentWithdrawUsers.size());
+        }
+        log.info("检查完成: 达标用户={}, 新记录数={}", qualifiedCount, recordedCount);
+    }
+
+    /**
+     * 处理单个用户提现记录
+     */
+    private int processUserWithdrawRecord(Long userId, int thresholdDays,
+                                          LocalDate startDate, LocalDate endDate,BigDecimal limitAmount) {
+        // 使用缓存键检查是否已处理过
+        String cacheKey = String.format("%s_%s_%s", userId, startDate, endDate);
+        if (processedUserPeriodCache.contains(cacheKey)) {
+            return 0;
+        }
+        // 1. 查询用户提现日期列表
+        List<LocalDate> withdrawDates = redPacketLogMapper.selectWithdrawDatesByUser(
+                userId, startDate, endDate);
+
+        if (withdrawDates.isEmpty() || withdrawDates.size() < thresholdDays) {
+            return 0;
+        }
+
+        // 2. 查找连续提现期间
+        List<ConsecutivePeriod> consecutivePeriods = findConsecutivePeriods(withdrawDates);
+
+        // 3. 检查是否有达到阈值的连续期间
+        int recordedCount = 0;
+        for (ConsecutivePeriod period : consecutivePeriods) {
+            if (period.getConsecutiveDays() >= thresholdDays) {
+                // 4. 记录到数据库
+                boolean success = recordConsecutiveWithdraw(userId, period,limitAmount);
+                if (success) {
+                    recordedCount++;
+                }
+            }
+        }
+        // 添加到缓存
+        if (!withdrawDates.isEmpty()) {
+            processedUserPeriodCache.add(cacheKey);
+        }
+        return recordedCount;
+    }
+
+    private static final String TASK_LOCK_KEY = "consecutive_withdraw_task_lock";
+    /**
+     * 获取分布式锁
+     */
+    private boolean tryLockTask() {
+        RLock lock = redissonClient.getLock(TASK_LOCK_KEY);
+
+        try {
+            // 尝试获取锁,最多等待5秒,锁持有时间30分钟
+            // 参数说明:
+            // waitTime: 等待锁的时间(秒)
+            // leaseTime: 锁自动释放时间(秒)
+            // unit: 时间单位
+            return lock.tryLock(5, 1800, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.warn("获取分布式锁时被中断", e);
+            return false;
+        } catch (Exception e) {
+            log.error("获取分布式锁失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 释放锁
+     */
+    private void unlockTask() {
+        RLock lock = redissonClient.getLock(TASK_LOCK_KEY);
+
+        try {
+            // 检查当前线程是否持有锁
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                log.debug("分布式锁释放成功: {}", TASK_LOCK_KEY);
+            } else {
+                log.debug("当前线程未持有锁,无需释放: {}", TASK_LOCK_KEY);
+            }
+        } catch (IllegalMonitorStateException e) {
+            log.warn("锁状态异常,可能已自动释放: {}", TASK_LOCK_KEY);
+        } catch (Exception e) {
+            log.error("释放分布式锁失败: {}", TASK_LOCK_KEY, e);
+        }
+    }
+}

+ 93 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralExchangeServiceImpl.java

@@ -0,0 +1,93 @@
+package com.fs.his.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsIntegralExchange;
+import com.fs.his.mapper.FsIntegralExchangeMapper;
+import com.fs.his.service.IFsIntegralExchangeService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 用户积分提现佣金记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-01-09
+ */
+@Service
+public class FsIntegralExchangeServiceImpl extends ServiceImpl<FsIntegralExchangeMapper, FsIntegralExchange> implements IFsIntegralExchangeService {
+
+    /**
+     * 查询用户积分提现佣金记录
+     * 
+     * @param id 用户积分提现佣金记录主键
+     * @return 用户积分提现佣金记录
+     */
+    @Override
+    public FsIntegralExchange selectFsIntegralExchangeById(Long id)
+    {
+        return baseMapper.selectFsIntegralExchangeById(id);
+    }
+
+    /**
+     * 查询用户积分提现佣金记录列表
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 用户积分提现佣金记录
+     */
+    @Override
+    public List<FsIntegralExchange> selectFsIntegralExchangeList(FsIntegralExchange fsIntegralExchange)
+    {
+        return baseMapper.selectFsIntegralExchangeList(fsIntegralExchange);
+    }
+
+    /**
+     * 新增用户积分提现佣金记录
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsIntegralExchange(FsIntegralExchange fsIntegralExchange)
+    {
+        fsIntegralExchange.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsIntegralExchange(fsIntegralExchange);
+    }
+
+    /**
+     * 修改用户积分提现佣金记录
+     * 
+     * @param fsIntegralExchange 用户积分提现佣金记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsIntegralExchange(FsIntegralExchange fsIntegralExchange)
+    {
+        return baseMapper.updateFsIntegralExchange(fsIntegralExchange);
+    }
+
+    /**
+     * 批量删除用户积分提现佣金记录
+     * 
+     * @param ids 需要删除的用户积分提现佣金记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsIntegralExchangeByIds(Long[] ids)
+    {
+        return baseMapper.deleteFsIntegralExchangeByIds(ids);
+    }
+
+    /**
+     * 删除用户积分提现佣金记录信息
+     * 
+     * @param id 用户积分提现佣金记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsIntegralExchangeById(Long id)
+    {
+        return baseMapper.deleteFsIntegralExchangeById(id);
+    }
+}

+ 13 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralOrderServiceImpl.java

@@ -432,7 +432,19 @@ public class FsIntegralOrderServiceImpl implements IFsIntegralOrderService
                 //写入日志
                 FsUser userMap=new FsUser();
                 userMap.setUserId(user.getUserId());
-                userMap.setIntegral(user.getIntegral()-totalIntegral);
+
+                // 可消费积分
+                long consumer = user.getIntegral() - user.getWithdrawIntegral();
+                if (consumer < totalIntegral) {
+                    // 扣除完可消费积分后,剩余的积分
+                    long extra = totalIntegral - consumer;
+                    // 可提现积分扣除 剩余积分
+                    Long withdrawIntegral = user.getWithdrawIntegral()- extra;
+                    userMap.setIntegral(withdrawIntegral);
+                    userMap.setWithdrawIntegral(withdrawIntegral);
+                } else {
+                    userMap.setIntegral(user.getIntegral() - totalIntegral);
+                }
                 fsUserMapper.updateFsUser(userMap);
                 FsUserIntegralLogs logs = new FsUserIntegralLogs();
                 logs.setIntegral(-totalIntegral);

+ 453 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsIntegralRedPacketLogServiceImpl.java

@@ -0,0 +1,453 @@
+package com.fs.his.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.core.domain.R;
+import com.fs.common.utils.DateUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.config.IntegralConfig;
+import com.fs.his.domain.FsIntegralRedPacketLog;
+import com.fs.his.domain.FsUser;
+import com.fs.his.mapper.FsIntegralRedPacketLogMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.param.FsIntegralRedPacketLogParam;
+import com.fs.his.service.IFsIntegralRedPacketLogService;
+import com.fs.his.vo.FsIntegralRedPacketLogVo;
+import com.fs.system.service.ISysConfigService;
+import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
+import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.TransferService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+
+/**
+ * 积分佣金红包记录Service业务层处理
+ * 
+ * @author fs
+ * @date 2026-01-22
+ */
+@Service
+@Slf4j
+public class FsIntegralRedPacketLogServiceImpl extends ServiceImpl<FsIntegralRedPacketLogMapper, FsIntegralRedPacketLog> implements IFsIntegralRedPacketLogService {
+
+    @Autowired
+    private ISysConfigService configService;
+    @Autowired
+    private FsUserMapper fsUserMapper;
+    @Autowired
+    @Qualifier("threadPoolTaskExecutor")
+    private ThreadPoolTaskExecutor taskExecutor;
+
+    /**
+     * 查询积分佣金红包记录
+     * 
+     * @param logId 积分佣金红包记录主键
+     * @return 积分佣金红包记录
+     */
+    @Override
+    public FsIntegralRedPacketLog selectFsIntegralRedPacketLogByLogId(Long logId)
+    {
+        FsIntegralRedPacketLog log = baseMapper.selectFsIntegralRedPacketLogByLogId(logId);
+        if(log!=null && log.getOutBatchNo()!=null){
+            return getRedPacketLogByCode(log.getOutBatchNo());
+        }
+
+        return log;
+    }
+
+    /**
+     * 查询积分佣金红包记录列表
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 积分佣金红包记录
+     */
+    @Override
+    public List<FsIntegralRedPacketLog> selectFsIntegralRedPacketLogList(FsIntegralRedPacketLog fsIntegralRedPacketLog)
+    {
+        //1.
+        List<FsIntegralRedPacketLog> list = baseMapper.selectFsIntegralRedPacketLogList(fsIntegralRedPacketLog);
+        if(list!=null && !list.isEmpty()){
+            String json = configService.selectConfigByKey("his.integral");
+            IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+
+            WxPayConfig payConfig = new WxPayConfig();
+            BeanUtils.copyProperties(config, payConfig);
+            WxPayService wxPayService = new WxPayServiceImpl();
+            wxPayService.setConfig(payConfig);
+            TransferService transferService = wxPayService.getTransferService();
+            Date oneMonthAgo = DateUtils.addMonths(new Date(), -1);
+            for (FsIntegralRedPacketLog redPacketLog : list) {
+                String outBatchNo = redPacketLog.getOutBatchNo();
+                if( "0".equals(redPacketLog.getStatus()) &&
+                        StringUtils.isNotBlank(outBatchNo) &&
+                        redPacketLog.getCreateTime().after(oneMonthAgo)){
+                    try {
+                        TransferBillsGetResult result = transferService.getBillsByOutBillNo(outBatchNo);
+                        if (result != null) {
+                            // 直接修改内存中的对象(前端能立即看到最新状态)
+                            syncLogToMemory(redPacketLog, result);
+                        }
+                    } catch (WxPayException e) {
+                        log.error("查询红包状态失败, outBatchNo: {}", outBatchNo, e);
+                    }
+                }
+            }
+        }
+
+        return list;
+    }
+
+
+    @Override
+    public List<FsIntegralRedPacketLogVo> getList(FsIntegralRedPacketLogParam param) {
+        return baseMapper.getList(param);
+    }
+
+    /**
+     * 同步状态到内存对象(即时返回给前端)
+     * 保留原remark,添加最新状态备注
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void syncLogToMemory(FsIntegralRedPacketLog redPacketLog, TransferBillsGetResult result) {
+        String state = result.getState();
+        Date now = new Date();
+
+        switch (state) {
+            case "SUCCESS":
+                redPacketLog.setStatus("1");
+                redPacketLog.setBatchId(result.getTransferBillNo());
+                redPacketLog.setRemark("转账成功");
+                redPacketLog.setUpdateTime(now);
+                baseMapper.updateFsIntegralRedPacketLog(redPacketLog);
+                break;
+            case "FAIL":
+                redPacketLog.setStatus("-1");
+                redPacketLog.setRemark("失败原因:" + result.getFailReason());
+                redPacketLog.setUpdateTime(now);
+                updateUser(redPacketLog); // 回滚用户金额
+                redPacketLog.setReturnedStatus(1);
+                baseMapper.updateFsIntegralRedPacketLog(redPacketLog);
+                break;
+            case "CANCELLED":
+                redPacketLog.setStatus("-2");
+                redPacketLog.setRemark("转账撤销完成");
+                redPacketLog.setUpdateTime(now);
+                updateUser(redPacketLog); // 回滚用户金额
+                redPacketLog.setReturnedStatus(1);
+                baseMapper.updateFsIntegralRedPacketLog(redPacketLog);
+                break;
+            case "ACCEPTED":
+                redPacketLog.setStatus("2");
+                redPacketLog.setRemark("转账已受理");
+                break;
+            case "PROCESSING":
+                redPacketLog.setStatus("3");
+                redPacketLog.setRemark("转账处理中");
+                break;
+            case "WAIT_USER_CONFIRM":
+                redPacketLog.setStatus("4");
+                redPacketLog.setRemark("待收款用户确认,可拉起微信收款确认页面进行收款确认");
+                break;
+            case "TRANSFERING":
+                redPacketLog.setStatus("5");
+                redPacketLog.setRemark("转账结果尚未明确,可拉起微信收款确认页面再次重试确认收款");
+                break;
+            case "CANCELING":
+                redPacketLog.setStatus("-3");
+                redPacketLog.setRemark("商户撤销请求受理成功,该笔转账正在撤销中");
+                break;
+            default:
+                break;
+        }
+    }
+
+
+    /**
+     * 构建数据库更新对象(仅终态,且不覆盖原remark,只添加状态标记)
+     */
+    private FsIntegralRedPacketLog buildDBUpdateLog(FsIntegralRedPacketLog log, TransferBillsGetResult result) {
+        String state = result.getState();
+        Date now = new Date();
+        FsIntegralRedPacketLog updateLog = new FsIntegralRedPacketLog();
+        updateLog.setLogId(log.getLogId());
+        updateLog.setUpdateTime(now);
+
+        // 数据库中只保存简洁的状态标记,不保存完整的流程备注
+        switch (state) {
+            case "SUCCESS":
+                updateLog.setStatus("1");
+                updateLog.setBatchId(result.getTransferBillNo());
+                // 数据库remark保持原样或置空,不保存"转账成功"字样(避免重复)
+                break;
+            case "FAIL":
+                updateLog.setStatus("-1");
+                // 数据库只保存失败原因,不叠加历史备注
+                updateLog.setRemark("失败:" + result.getFailReason());
+                break;
+            case "CANCELLED":
+                updateLog.setStatus("-2");
+                updateLog.setRemark("转账已撤销");
+                break;
+            default:
+                return null;
+        }
+        return updateLog;
+    }
+
+    private boolean isTerminalStatus(String state) {
+        return "SUCCESS".equals(state) || "FAIL".equals(state) || "CANCELLED".equals(state);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public void updateUser(FsIntegralRedPacketLog redPacketLog) {
+        Long userId = redPacketLog.getUserId();
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(userId);
+        if (fsUser == null) {
+            log.error("用户不存在, userId:{}", userId);
+            return;
+        }
+        FsUser updatefsUser = new FsUser();
+        updatefsUser.setUserId(userId);
+        BigDecimal fenWithdraw = redPacketLog.getAmount().multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_DOWN);
+        BigDecimal mayWithdraw = fsUser.getMayWithdraw();
+        mayWithdraw = mayWithdraw.add(fenWithdraw);
+        updatefsUser.setMayWithdraw(mayWithdraw);
+        updatefsUser.setWithdrawFinish(fsUser.getWithdrawFinish().subtract(fenWithdraw));
+        fsUserMapper.updateFsUser(updatefsUser);
+        log.info("提现失败,回滚佣金,logId:{},参数:{}",redPacketLog.getLogId(), JSON.toJSONString(redPacketLog));
+    }
+
+    /**
+     * 新增积分佣金红包记录
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 结果
+     */
+    @Override
+    public int insertFsIntegralRedPacketLog(FsIntegralRedPacketLog fsIntegralRedPacketLog)
+    {
+        fsIntegralRedPacketLog.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsIntegralRedPacketLog(fsIntegralRedPacketLog);
+    }
+
+    /**
+     * 修改积分佣金红包记录
+     * 
+     * @param fsIntegralRedPacketLog 积分佣金红包记录
+     * @return 结果
+     */
+    @Override
+    public int updateFsIntegralRedPacketLog(FsIntegralRedPacketLog fsIntegralRedPacketLog)
+    {
+        fsIntegralRedPacketLog.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsIntegralRedPacketLog(fsIntegralRedPacketLog);
+    }
+
+    /**
+     * 批量删除积分佣金红包记录
+     * 
+     * @param logIds 需要删除的积分佣金红包记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsIntegralRedPacketLogByLogIds(Long[] logIds)
+    {
+        return baseMapper.deleteFsIntegralRedPacketLogByLogIds(logIds);
+    }
+
+    /**
+     * 删除积分佣金红包记录信息
+     * 
+     * @param logId 积分佣金红包记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsIntegralRedPacketLogByLogId(Long logId)
+    {
+        return baseMapper.deleteFsIntegralRedPacketLogByLogId(logId);
+    }
+
+    @Override
+    public R syncRedPacket(String outBatchNo, String batchId) {
+        FsIntegralRedPacketLog log = baseMapper.selectFsIntegralRedPacketLogByBatchNo(outBatchNo);
+        if (log!=null){
+            if ("1".equals(log.getStatus()) && batchId.equals(log.getBatchId())){
+                return R.ok();
+            }
+            log.setStatus("1");
+            log.setUpdateTime(new Date());
+            log.setBatchId(batchId);
+            baseMapper.updateFsIntegralRedPacketLog(log);
+            return R.ok();
+        }
+        return R.error("批次不存在");
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R syncErrorRedPacket(TransferBillsNotifyResult.DecryptNotifyResult result) {
+        String state = result.getState();
+        if ("FAIL".equals(state) || "CANCELLED".equals(state)) {
+            String outBillNo = result.getOutBillNo();
+            if (StringUtils.isNotBlank(outBillNo)) {
+                FsIntegralRedPacketLog log = baseMapper.selectFsIntegralRedPacketLogByBatchNo(outBillNo);
+                if (log!=null){
+                    if ("-1".equals(log.getStatus()) || "-2".equals(log.getStatus())){
+                        return R.ok();
+                    }
+                    log.setStatus("FAIL".equals(state)?"-1":"-2");
+                    log.setUpdateTime(new Date());
+                    log.setRemark("失败原因:" +result.getFailReason());
+                    if (StringUtils.isNotBlank(log.getBatchId())){
+                        log.setBatchId(log.getBatchId());
+                    }
+                    updateUser(log);
+                    log.setReturnedStatus(1);
+                    baseMapper.updateFsIntegralRedPacketLog(log);
+                    return R.ok();
+                }
+                return R.error("批次不存在");
+            }
+        }
+        return R.error();
+    }
+
+    @Override
+    public FsIntegralRedPacketLog getRedPacketLogByCode(String orderCode) {
+        FsIntegralRedPacketLog redPacketLog = baseMapper.selectFsIntegralRedPacketLogByBatchNo(orderCode);
+        if (redPacketLog != null && "0".equals(redPacketLog.getStatus())) {
+            // 3. 获取微信配置
+            String json = configService.selectConfigByKey("his.integral");
+            IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+
+            WxPayConfig payConfig = new WxPayConfig();
+            BeanUtils.copyProperties(config, payConfig);
+            WxPayService wxPayService = new WxPayServiceImpl();
+            wxPayService.setConfig(payConfig);
+            TransferService transferService = wxPayService.getTransferService();
+            try {
+                redPacketLog = doExecute(transferService, redPacketLog);
+            } catch (Exception e) {
+                log.error("查询红包状态失败, outBatchNo: {}", redPacketLog.getOutBatchNo(), e);
+            }
+        }
+        return redPacketLog;
+    }
+
+    @Override
+    public void synchronizationWxMerchantPayStatus() {
+        FsIntegralRedPacketLogParam queryParam = new FsIntegralRedPacketLogParam();
+        Date oneMonthAgo = DateUtils.addMonths(new Date(), -1);
+        queryParam.setBeginTime(DateUtils.parseDateToStr("yyyy-MM-dd HH:mm:ss",oneMonthAgo));
+        queryParam.setStatus("0");
+        List<FsIntegralRedPacketLog> list = baseMapper.selectFsIntegralRedPacketLogList(queryParam);
+        if (list!=null&& !list.isEmpty()){
+            String json = configService.selectConfigByKey("his.integral");
+            IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+
+            WxPayConfig payConfig = new WxPayConfig();
+            BeanUtils.copyProperties(config, payConfig);
+            WxPayService wxPayService = new WxPayServiceImpl();
+            wxPayService.setConfig(payConfig);
+            TransferService transferService = wxPayService.getTransferService();
+
+            List<CompletableFuture<Void>> futures = list.stream()
+                    .map(redPacketLog -> CompletableFuture.runAsync(() -> {
+                        try {
+                            doExecute(transferService,redPacketLog);
+                        } catch (Exception e) {
+                            log.error("定时查询红包状态失败, redPacketLog: {}", JSONUtil.toJsonStr(redPacketLog), e);
+                        }
+                    }, taskExecutor))
+                    .collect(Collectors.toList());
+            // 6. 等待所有查询完成
+            try {
+                CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
+            } catch (Exception e) {
+                log.warn("定时查询微信状态超时,返回当前已获取的数据,剩余任务后台继续");
+            }
+        }
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public FsIntegralRedPacketLog doExecute(TransferService transferService, FsIntegralRedPacketLog redPacketLog) {
+        TransferBillsGetResult result = null;
+        try {
+            String outBatchNo = redPacketLog.getOutBatchNo();
+            if (StringUtils.isBlank(outBatchNo)) {
+                result = new TransferBillsGetResult();
+                result.setState("FAIL");
+                result.setFailReason("未生成订单号");
+            } else {
+                result = transferService.getBillsByOutBillNo(outBatchNo);
+            }
+
+            if (result != null) {
+                String state = result.getState();
+                Date now = new Date();
+                switch (state) {
+                    case "SUCCESS":
+                        redPacketLog.setStatus("1");
+                        redPacketLog.setBatchId(result.getTransferBillNo());
+                        redPacketLog.setRemark("转账成功");
+                        redPacketLog.setUpdateTime(now);
+                        baseMapper.updateFsIntegralRedPacketLog(redPacketLog);
+                        break;
+                    case "FAIL":
+                        redPacketLog.setStatus("-1");
+                        redPacketLog.setRemark("失败原因:" + result.getFailReason());
+                        redPacketLog.setUpdateTime(now);
+                        updateUser(redPacketLog); // 回滚用户金额
+                        redPacketLog.setReturnedStatus(1);
+                        baseMapper.updateFsIntegralRedPacketLog(redPacketLog);
+                        break;
+                    case "CANCELLED":
+                        redPacketLog.setStatus("-2");
+                        redPacketLog.setRemark("失败原因:转账撤销完成");
+                        redPacketLog.setUpdateTime(now);
+                        updateUser(redPacketLog); // 回滚用户金额
+                        redPacketLog.setReturnedStatus(1);
+                        baseMapper.updateFsIntegralRedPacketLog(redPacketLog);
+                    break;
+                    case "ACCEPTED":
+                        redPacketLog.setStatus("0");
+                        break;
+                    case "PROCESSING":
+                        redPacketLog.setStatus("0");
+                        break;
+                    case "WAIT_USER_CONFIRM":
+                        redPacketLog.setStatus("4");
+                        break;
+                    case "TRANSFERING":
+                        redPacketLog.setStatus("5");
+                        break;
+                    case "CANCELING":
+                        redPacketLog.setStatus("-3");
+                        break;
+                    default:
+                        break;
+                }
+            }
+        } catch (WxPayException e) {
+            throw new RuntimeException(e);
+        }
+        return redPacketLog;
+    }
+}

+ 184 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsStorePaymentServiceImpl.java

@@ -46,6 +46,7 @@ import com.fs.course.mapper.FsCoursePlaySourceConfigMapper;
 import com.fs.course.service.IFsCourseRedPacketLogService;
 import com.fs.course.service.IFsUserCourseOrderService;
 import com.fs.course.service.IFsUserVipOrderService;
+import com.fs.his.config.IntegralConfig;
 import com.fs.his.domain.*;
 import com.fs.his.dto.PayConfigDTO;
 import com.fs.his.enums.PaymentMethodEnum;
@@ -53,13 +54,10 @@ import com.fs.his.mapper.*;
 import com.fs.his.param.FsStorePaymentParam;
 import com.fs.his.param.PayOrderParam;
 import com.fs.his.param.WxSendRedPacketParam;
-import com.fs.his.service.IFsInquiryOrderService;
+import com.fs.his.service.*;
 
 import com.fs.his.param.WxSendRedPacketParam;
 import com.fs.his.service.IFsInquiryOrderService;
-import com.fs.his.service.IFsPackageOrderService;
-import com.fs.his.service.IFsStoreOrderService;
-import com.fs.his.service.IFsStorePaymentService;
 import com.fs.his.utils.PhoneUtil;
 import com.fs.his.vo.FsStorePaymentExcelVO;
 import com.fs.his.vo.FsStorePaymentVO;
@@ -1786,4 +1784,186 @@ public class FsStorePaymentServiceImpl implements IFsStorePaymentService {
 
     }
 
+    /**
+     * 积分提现
+     */
+    @Override
+    public R sendIntegralRedPacket(WxSendRedPacketParam param) {
+        //组合返回参数
+        R result = new R();
+        String json = configService.selectConfigByKey("his.integral");
+        IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+        if (config.getIsNew() != null && config.getIsNew() == 1) {
+            result = sendRedPacketV3Internal(param, config);
+        } else {
+            result= sendRedPacketLegacyInternal(param, config);
+        }
+
+        result.put("mchId", config.getMchId());
+        result.put("isNew",config.getIsNew());
+        logger.info("积分提现返回:{}",result);
+        return result;
+    }
+
+    // 内部方法:处理新版本的发红包逻辑
+    private R sendRedPacketV3Internal(WxSendRedPacketParam param,IntegralConfig config) {
+
+        WxPayConfig payConfig = new WxPayConfig();
+        BeanUtils.copyProperties(config, payConfig);
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+        TransferService transferService = wxPayService.getTransferService();
+
+        TransferBillsRequest request = new TransferBillsRequest();
+        request.setAppid(param.getAppId());
+        request.setOpenid(param.getOpenId());
+
+        String code =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(code)){
+            return R.error("订单生成失败,请重试");
+        }
+//        String code = String.valueOf(IdUtil.getSnowflake(0, 0).nextId());
+        request.setOutBillNo("fsIntegral" + code);
+        if (param.getAmount() == null) {
+            return R.error();
+        }
+        Integer amount = WxPayUnifiedOrderRequest.yuanToFen(param.getAmount().toString());
+        request.setTransferAmount(amount);
+        request.setTransferRemark("积分提现");
+        request.setUserRecvPerception("活动奖励");
+        request.setNotifyUrl(config.getNotifyUrl());
+        request.setTransferSceneId("1000");
+
+        // 设置场景信息
+        List<TransferBillsRequest.TransferSceneReportInfo> transferSceneReportInfos = new ArrayList<>();
+        TransferBillsRequest.TransferSceneReportInfo info1 = new TransferBillsRequest.TransferSceneReportInfo();
+        info1.setInfoType("活动名称");
+        info1.setInfoContent("积分提现");
+        transferSceneReportInfos.add(info1);
+
+        TransferBillsRequest.TransferSceneReportInfo info2 = new TransferBillsRequest.TransferSceneReportInfo();
+        info2.setInfoType("奖励说明");
+        info2.setInfoContent("积分提现");
+        transferSceneReportInfos.add(info2);
+        request.setTransferSceneReportInfos(transferSceneReportInfos);
+
+
+        try {
+            TransferBillsResult transferBillsResult = transferService.transferBills(request);
+            logger.info("商家转账支付完成:[msg:{}]", transferBillsResult);
+            return R.ok("发送红包成功").put("data", transferBillsResult).put("mchId", config.getMchId())
+                    .put("package",transferBillsResult.getPackageInfo())
+                    .put("appId",param.getAppId())
+                    .put("orderCode",request.getOutBillNo());
+        } catch (Exception e) {
+            logger.error("商家转账支付失败:参数: {} :原因: {}", com.alibaba.fastjson.JSON.toJSONString(param), e.getMessage(),e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private R sendRedPacketLegacyInternal(WxSendRedPacketParam param, IntegralConfig config) {
+        //如果服务号的配置存在,小程序红包接口可以使用服务号来发红包,重新赋值
+        //仅老商户支持
+        if (param.getOpenId()!=null && StringUtils.isNotEmpty(param.getAppId())){
+//            config.setAppId(param.getAppId());
+            param.setOpenId(param.getOpenId());
+        }
+        WxPayConfig payConfig = new WxPayConfig();
+        BeanUtils.copyProperties(config, payConfig);
+        WxPayService wxPayService = new WxPayServiceImpl();
+        wxPayService.setConfig(payConfig);
+        TransferService transferService = wxPayService.getTransferService();
+
+        TransferBatchesRequest request = new TransferBatchesRequest();
+//        request.setAppid(config.getAppId());
+
+
+        // todo 如果未配置负载均衡请还原原本的单号方式
+//        String code = IdUtil.getSnowflake(0, 0).nextIdStr();
+        String code =  OrderCodeUtils.getOrderSn();
+        if(StringUtils.isEmpty(code)){
+            return R.error("红包单号生成失败,请重试");
+        }
+        request.setOutBatchNo("fsIntegral" + code);
+        request.setBatchRemark("积分提现");
+        request.setBatchName("积分提现");
+        Integer amount = WxPayUnifiedOrderRequest.yuanToFen(param.getAmount().toString());
+        request.setTotalAmount(amount);
+        request.setTotalNum(1);
+        request.setNotifyUrl(config.getNotifyUrl());
+
+        ArrayList<TransferBatchesRequest.TransferDetail> transferDetailList = new ArrayList<>();
+        TransferBatchesRequest.TransferDetail transferDetail = new TransferBatchesRequest.TransferDetail();
+        transferDetail.setOpenid(param.getOpenId());
+        String code1 = IdUtil.getSnowflake(0, 0).nextIdStr();
+        transferDetail.setOutDetailNo("fsCourse" + code1);
+        transferDetail.setTransferAmount(amount);
+        transferDetail.setTransferRemark("积分提现成功!");
+        transferDetailList.add(transferDetail);
+        request.setTransferDetailList(transferDetailList);
+
+        try {
+            TransferBatchesResult transferBatchesResult = transferService.transferBatches(request);
+            return R.ok("积分提现成功").put("orderCode", transferBatchesResult.getOutBatchNo()).put("batchId", transferBatchesResult.getBatchId()).put("mchId", config.getMchId());
+        } catch (Exception e) {
+            logger.error("商家转账支付失败:参数: {} :原因: {}", com.alibaba.fastjson.JSON.toJSONString(param), e.getMessage(),e);
+            if (e instanceof WxPayException) {
+//            if (e instanceof WxPayException && "济南联志健康".equals(signProjectName)) {
+                WxPayException wxPayException = (WxPayException) e;
+                String customErrorMsg = wxPayException.getCustomErrorMsg();
+                if (null != customErrorMsg && customErrorMsg.startsWith("商户运营账户资金不足")) {
+                    return R.error("[积分提现] 账户余额不足,请联系管理员!");
+                }
+            }
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Autowired
+    private IFsIntegralRedPacketLogService integralRedPacketLogService;
+
+    /**
+     * 提现回调
+     */
+    @Override
+    public String integralV3TransferNotify(String notifyData, HttpServletRequest request) {
+        logger.info("zyp \n【收到转账回调V3】:{}",notifyData);
+        try {
+            String json = configService.selectConfigByKey("his.integral");
+            IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+            //创建微信订单
+            WxPayConfig payConfig = new WxPayConfig();
+            BeanUtils.copyProperties(config,payConfig);
+            WxPayService wxPayService = new WxPayServiceImpl();
+            wxPayService.setConfig(payConfig);
+            SignatureHeader signatureHeader = new SignatureHeader();
+            signatureHeader.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
+            signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
+            signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));
+            signatureHeader.setSignature(request.getHeader("Wechatpay-Signature"));
+            TransferBillsNotifyResult result = wxPayService.parseTransferBillsNotifyV3Result(notifyData,signatureHeader);
+            logger.info("积分到零钱回调1:{}",result.getResult());
+            if (result == null){
+                return WxPayNotifyResponse.fail("");
+            }
+            if (result.getResult().getState().equals("SUCCESS")) {
+                R r = integralRedPacketLogService.syncRedPacket(result.getResult().getOutBillNo(),result.getResult().getTransferBillNo());
+                logger.info("result:{}",r);
+                if (r.get("code").equals(200)){
+                    return WxPayNotifyResponse.success("处理成功");
+                }else {
+                    return WxPayNotifyResponse.fail("");
+                }
+            }else {
+                //失败 退回佣金
+                integralRedPacketLogService.syncErrorRedPacket(result.getResult());
+                return WxPayNotifyResponse.success("处理成功");
+            }
+        } catch (WxPayException e) {
+            e.printStackTrace();
+            logger.error("zyp \n【转账回调异常】:{}", e.getReturnMsg());
+            return WxPayNotifyResponse.fail(e.getMessage());
+        }
+    }
+
 }

+ 208 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsUserIntegralLogsServiceImpl.java

@@ -1,20 +1,22 @@
 package com.fs.his.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.DateUtils;
 import com.fs.his.config.IntegralConfig;
+import com.fs.his.domain.AdProfitDetail;
 import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserIntegralLogs;
 import com.fs.his.domain.FsUserNewTask;
 import com.fs.his.enums.FsUserIntegralLogTypeEnum;
+import com.fs.his.mapper.AdProfitDetailMapper;
 import com.fs.his.mapper.FsUserIntegralLogsMapper;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.mapper.FsUserNewTaskMapper;
-import com.fs.his.param.FsUserAddIntegralParam;
-import com.fs.his.param.FsUserAddIntegralTemplateParam;
-import com.fs.his.param.FsUserIntegralLogsListUParam;
-import com.fs.his.param.FsUserIntegralLogsParam;
+import com.fs.his.param.*;
 import com.fs.his.service.IFsUserIntegralLogsService;
 import com.fs.his.vo.FsUserIntegralLogsListUVO;
 import com.fs.his.vo.FsUserIntegralLogsListVO;
@@ -23,11 +25,19 @@ import com.fs.system.service.ISysConfigService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
 
+import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * 积分记录Service业务层处理
@@ -47,6 +57,13 @@ public class FsUserIntegralLogsServiceImpl implements IFsUserIntegralLogsService
     private ISysConfigService configService;
     @Autowired
     private FsUserNewTaskMapper fsUserNewTaskMapper;
+    @Autowired
+    private AdProfitDetailMapper adProfitDetailMapper;
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+    @Autowired
+    @Qualifier("threadPoolTaskExecutor")
+    private ThreadPoolTaskExecutor executor;
     /**
      * 查询积分记录
      *
@@ -209,11 +226,74 @@ public class FsUserIntegralLogsServiceImpl implements IFsUserIntegralLogsService
                     return addProductIntegral(config.getIntegralProduct(),user.getUserId(),user.getIntegral());
                 case 2:
                     return addVideoIntegral(config.getIntegralFirstVideo(),config.getIntegralFinishVideo(),user.getUserId(),user.getIntegral());
+                case 5:
+                    return addAdvIntegral(param,user);
             }
         }
         return R.error("用户信息不存在");
     }
 
+    /**
+     * 添加广告积分
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public R addAdvIntegral(FsUserAddIntegralParam param, FsUser user) {
+        try {
+            // 查询用户详细分润
+            AdProfitDetail adProfitDetail = adProfitDetailMapper.selectOne(new LambdaQueryWrapper<AdProfitDetail>()
+                    .eq(AdProfitDetail::getUserId, param.getUserId())
+                    .eq(AdProfitDetail::getAdTaskId, param.getExtra()));
+            if (BeanUtil.isEmpty(adProfitDetail)) {
+                return R.error("用户没有分润信息");
+            }
+
+            // 客户分润转积分  1元=1000积分   库里存储的是单位是分: * 10
+            BigDecimal customerPoints = adProfitDetail.getUserMoney()
+                    .multiply(new BigDecimal("10"))
+                    .setScale(0, BigDecimal.ROUND_DOWN);
+
+            if (customerPoints.compareTo(BigDecimal.ZERO) <= 0) {
+                return R.error("积分为0,无法添加");
+            }
+
+            // 更新用户积分
+            FsUser fsUser = new FsUser();
+            fsUser.setUserId(param.getUserId());
+//            long newIntegral = Objects.nonNull(user.getIntegral())
+//                    ? user.getIntegral() + customerPoints.longValue()
+//                    : customerPoints.longValue();
+//            long newWithdrawIntegral = Objects.nonNull(user.getWithdrawIntegral())
+//                    ? user.getWithdrawIntegral() + customerPoints.longValue()
+//                    : customerPoints.longValue();
+//            fsUser.setIntegral(newIntegral);
+//            fsUser.setWithdrawIntegral(newWithdrawIntegral);
+//            fsUser.setUpdateTime(new Date());
+//            fsUserMapper.updateFsUser(fsUser);
+
+            // 原子增加积分
+            fsUserMapper.addIntegral(fsUser.getUserId(),customerPoints.longValue());
+
+            FsUser newFsUser = fsUserMapper.selectFsUserByUserId(fsUser.getUserId());
+            // 添加积分记录
+            FsUserIntegralLogs integralLogs = new FsUserIntegralLogs();
+            integralLogs.setLogType(FsUserIntegralLogTypeEnum.TYPE_31.getValue());
+            integralLogs.setUserId(param.getUserId());
+            integralLogs.setIntegral(customerPoints.longValue());
+            integralLogs.setBalance(newFsUser.getIntegral()); // 用计算后的新积分作为余额,避免重复计算
+            integralLogs.setCreateTime(new Date());
+            integralLogs.setNickName(user.getNickName());
+            integralLogs.setPhone(user.getPhone());
+//            fsUserIntegralLogsMapper.insertFsUserIntegralLogs(integralLogs);
+            insertFsUserIntegralLogs(integralLogs);
+            return R.ok("广告任务完成,获得" + customerPoints + "积分").put("integral", customerPoints);
+
+        } catch (Exception e) {
+            log.error("用户[{}]积分添加失败,参数:{}", param.getUserId(), JSON.toJSONString(param), e);
+            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+            return R.error("获取积分失败,请稍后重试");
+        }
+    }
+
     /**
      * 更新积分通用
      * @param param
@@ -543,4 +623,128 @@ public class FsUserIntegralLogsServiceImpl implements IFsUserIntegralLogsService
         map.put("isFinishFirstOrderPoint", isFinishFirstOrderPoint);
         return R.ok().put("data",map).put("isNewUser",isNewUser).put("createTime",createTime);
     }
+
+    @Override
+    public Long sumIntegralByLogTypeAndCreateTime(Integer logType, AdProfitDetailStatisticsParam param) {
+        List<String> tables = getAllTableNames();
+        if (tables == null || tables.isEmpty()) {
+            return 0L;
+        }
+
+        // 并行查询每个分表
+        List<CompletableFuture<Long>> futures = tables.stream()
+                .map(table -> CompletableFuture.supplyAsync(
+                        () -> querySingleTableSumSafe(table, logType, param),
+                        executor
+                ))
+                .collect(Collectors.toList());
+
+        // 等待所有完成(不阻塞主线程太久)
+        CompletableFuture<Void> allDone = CompletableFuture.allOf(
+                futures.toArray(new CompletableFuture[0])
+        );
+
+        try {
+            // 等待最多60秒
+            allDone.get(60, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            log.error("等待分表查询超时", e);
+        }
+
+        // 汇总结果(已完成的直接取,未完成的返回0)
+        return futures.stream()
+                .mapToLong(future -> future.isDone() ? future.getNow(0L) : 0L)
+                .sum();
+    }
+
+    public List<String> getAllTableNames() {
+        String sql = "SELECT table_name FROM information_schema.tables " +
+                "WHERE table_schema = DATABASE() " +
+                "AND (table_name LIKE 'fs_user_integral_logs_%' " +
+                "     OR table_name = 'fs_user_integral_logs') " +
+                "AND table_name NOT LIKE '%copy%' " +
+                "ORDER BY table_name";
+        return jdbcTemplate.queryForList(sql, String.class);
+    }
+
+    /**
+     * 使用参数化查询(更安全)
+     */
+    private Long querySingleTableSumSafe(String tableName, int logType,
+                                         AdProfitDetailStatisticsParam param) {
+        try {
+            StringBuilder sql = new StringBuilder();
+            List<Object> params = new ArrayList<>();
+
+            sql.append("SELECT COALESCE(SUM(integral), 0) FROM ")
+                    .append(tableName)
+                    .append(" WHERE log_type = ?");
+            params.add(logType);
+
+            // 添加时间条件
+            appendTimeCondition(sql, params, param);
+
+            return jdbcTemplate.queryForObject(sql.toString(), Long.class, params.toArray());
+
+        } catch (Exception e) {
+            log.error("查询表{}失败: {}", tableName, e.getMessage());
+            return 0L;
+        }
+    }
+
+    private void appendTimeCondition(StringBuilder sql, List<Object> params,
+                                     AdProfitDetailStatisticsParam param) {
+        Integer type = param.getType();
+
+        if (type == null) {
+            // 使用startTime和endTime
+            sql.append(" AND create_time >= ? AND create_time < DATE_ADD(?, INTERVAL 1 DAY)");
+            params.add(param.getStartTime());
+            params.add(param.getEndTime());
+            return;
+        }
+
+        switch (type) {
+            case 1: // 今天
+                sql.append(" AND create_time >= CURDATE() AND create_time < DATE_ADD(CURDATE(), INTERVAL 1 DAY)");
+                break;
+            case 2: // 昨天
+                sql.append(" AND create_time >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) AND create_time < CURDATE()");
+                break;
+            case 3: // 本周
+                sql.append(" AND create_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY) " +
+                        "AND create_time < DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY)");
+                break;
+            case 4: // 上周
+                sql.append(" AND create_time >= DATE_SUB(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY) " +
+                        "AND create_time < DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)");
+                break;
+            case 5: // 本月
+                sql.append(" AND create_time >= DATE_FORMAT(CURDATE(), '%Y-%m-01') " +
+                        "AND create_time < DATE_ADD(DATE_FORMAT(CURDATE(), '%Y-%m-01'), INTERVAL 1 MONTH)");
+                break;
+            case 6: // 上月
+                sql.append(" AND create_time >= DATE_SUB(DATE_FORMAT(CURDATE(), '%Y-%m-01'), INTERVAL 1 MONTH) " +
+                        "AND create_time < DATE_FORMAT(CURDATE(), '%Y-%m-01')");
+                break;
+            case 7: // 本季度
+                sql.append(" AND create_time >= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', QUARTER(CURDATE())*3-2, '-01'), '%Y-%m-%d') " +
+                        "AND create_time < DATE_ADD(STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', QUARTER(CURDATE())*3-2, '-01'), '%Y-%m-%d'), INTERVAL 3 MONTH)");
+                break;
+            case 8: // 上季度
+                sql.append(" AND create_time >= DATE_SUB(STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', QUARTER(CURDATE())*3-2, '-01'), '%Y-%m-%d'), INTERVAL 3 MONTH) " +
+                        "AND create_time < STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', QUARTER(CURDATE())*3-2, '-01'), '%Y-%m-%d')");
+                break;
+            case 9: // 本年
+                sql.append(" AND create_time >= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d') " +
+                        "AND create_time < DATE_ADD(STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d'), INTERVAL 1 YEAR)");
+                break;
+            case 10: // 去年
+                sql.append(" AND create_time >= DATE_SUB(STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d'), INTERVAL 1 YEAR) " +
+                        "AND create_time < STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d')");
+                break;
+            default:
+                throw new IllegalArgumentException("不支持的时间类型: " + type);
+        }
+    }
 }

+ 415 - 15
fs-service/src/main/java/com/fs/his/service/impl/FsUserServiceImpl.java

@@ -9,13 +9,18 @@ import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.util.IdUtil;
 import cn.hutool.json.JSONUtil;
+import com.alibaba.fastjson.JSON;
 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.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.constant.HttpStatus;
@@ -58,13 +63,11 @@ import com.fs.his.domain.FsUserIntegralLogs;
 import com.fs.his.dto.FindUsersByDTO;
 import com.fs.his.enums.FsUserIntegralLogTypeEnum;
 import com.fs.his.mapper.*;
-import com.fs.his.param.FindUserByParam;
-import com.fs.his.param.FsUserAddIntegralTemplateParam;
-import com.fs.his.param.FsUserParam;
-import com.fs.his.service.IFsUserIntegralLogsService;
-import com.fs.his.service.IFsUserProjectTagService;
-import com.fs.his.service.IFsUserWxService;
+import com.fs.his.param.*;
+import com.fs.his.service.*;
 import com.fs.his.utils.PhoneUtil;
+import com.fs.his.utils.ProfitShareUtils;
+import com.fs.his.utils.UniAdSignUtils;
 import com.fs.his.vo.*;
 import com.fs.im.config.ImTypeConfig;
 import com.fs.im.service.OpenIMService;
@@ -94,8 +97,10 @@ import com.fs.system.service.ISysConfigService;
 import com.fs.watch.domain.WatchUser;
 import com.fs.watch.domain.vo.FsUserAndCompanyAndDoctorVo;
 import com.fs.watch.service.WatchUserService;
+import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.http.client.ClientProtocolException;
 import org.apache.http.client.methods.CloseableHttpResponse;
@@ -105,12 +110,13 @@ import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.Asserts;
 import org.apache.http.util.EntityUtils;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import com.fs.his.service.IFsUserService;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -124,6 +130,7 @@ import static com.fs.hisStore.enums.BillDetailEnum.CATEGORY_3;
  * @author fs
  * @date 2023-06-07
  */
+@Slf4j
 @Service
 public class FsUserServiceImpl implements IFsUserService {
     Logger logger = LoggerFactory.getLogger(getClass());
@@ -173,14 +180,6 @@ public class FsUserServiceImpl implements IFsUserService {
     @Autowired
     private IFsUserCompanyUserService userCompanyUserService;
     @Autowired
-    private IQwExternalContactCacheService qwExternalContactCacheService;
-
-    @Autowired
-    private CompanyRoleMapper companyRoleMapper;
-
-    @Autowired
-    private RedisCache redisCache;
-    @Autowired
     private IFsUserProjectTagService userProjectTagService;
 
     @Autowired
@@ -207,6 +206,20 @@ public class FsUserServiceImpl implements IFsUserService {
 
     @Autowired
     private CompanyUserUserMapper companyUserUserMapper;
+    @Autowired
+    private AdProfitDetailMapper adProfitDetailMapper;
+    @Autowired
+    private IFsIntegralExchangeService fsIntegralExchangeService;
+    @Autowired
+    private IFsUserIntegralLogsService fsUserIntegralLogsService;
+    @Autowired
+    private FsUserIntegralLogsMapper fsUserIntegralLogsMapper;
+    @Autowired
+    private FsIntegralRedPacketLogMapper integralRedPacketLogMapper;
+    @Autowired
+    private IFsStorePaymentService paymentService;
+    @Autowired
+    private RedissonClient redissonClient;
 
 
     /**
@@ -2053,4 +2066,391 @@ public class FsUserServiceImpl implements IFsUserService {
         }
     }
 
+
+    /**
+     * 接收 UniApp 激励广告回调
+     */
+    @Override
+    public R uniCallBack(Map<String, Object> params) {
+        log.info("接收到uniapp激励广告回调参数:{}", params);
+
+        try {
+            // 交易id
+            String transId = params.get("trans_id").toString();
+            String sign = params.get("sign").toString();
+
+            // 签名验证
+            if (!UniAdSignUtils.verifySign(transId, sign)) {
+                log.error("签名验证失败:trans_id={}, sign={}", transId, sign);
+                return R.ok().put("isValid", false);
+            }
+
+            // 防止重复回调
+            int count = adProfitDetailMapper.selectCount(new LambdaQueryWrapper<AdProfitDetail>().eq(AdProfitDetail::getAdTransId, transId));
+            if (count > 0) {
+                return R.ok().put("isValid", false);
+            }
+
+            if (!params.containsKey("cpm")){
+                AdProfitDetail adProfitDetail = new AdProfitDetail();
+                adProfitDetail.setAdTransId(transId);
+                adProfitDetail.setAdTaskId(params.get("extra").toString());
+                adProfitDetail.setUserId(Long.valueOf(params.get("user_id").toString()));
+                adProfitDetail.setCreateTime(LocalDateTime.now());
+                adProfitDetail.setOriginParam(JSON.toJSONString(params));
+                int result = adProfitDetailMapper.insert(adProfitDetail);
+                log.error("uniapp广告回调cpm为空,回调参数={}", params);
+                return R.ok().put("isValid", false);
+            }
+
+            // 广告收益(元)
+            BigDecimal singleProfit = new BigDecimal(params.get("cpm").toString())
+                    .divide(new BigDecimal("1000"), 6, RoundingMode.HALF_UP);
+
+            // 获取分润比例
+            String json = configService.selectConfigByKey("his.integral");
+            IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+
+            List<BigDecimal> ratios = Arrays.asList(
+                    config.getIntegralUserRatio(),
+                    config.getIntegralCompanyRatio(),
+                    config.getIntegralHuYiRatio(),
+                    config.getIntegralYunLianRatio()
+            );
+
+            // 使用银行级分配算法
+            Map<Integer, BigDecimal> profitMap = ProfitShareUtils.allocate(singleProfit, ratios);
+
+            BigDecimal customerProfit = profitMap.get(0);
+            BigDecimal salesProfit    = profitMap.get(1);
+            BigDecimal huyiProfit     = profitMap.get(2);
+            BigDecimal yunlianProfit  = profitMap.get(3);
+
+            // 计算用户本次广告积分
+            BigDecimal userIntegra = customerProfit.multiply(new BigDecimal("10")).setScale(0, RoundingMode.DOWN);
+            // 配置的保底收益
+            Integer minimumIntegral = config.getMinimumIntegral();
+            boolean resultCom = userIntegra.compareTo(BigDecimal.valueOf(minimumIntegral)) < 0;
+
+            // 查询用户的销售公司
+            String companyId = adProfitDetailMapper.getCompanyByUserId(params.get("user_id").toString());
+
+            // 入库
+            AdProfitDetail adProfitDetail = new AdProfitDetail();
+            adProfitDetail.setAdTransId(transId);
+            adProfitDetail.setAdTaskId(params.get("extra").toString());
+            adProfitDetail.setSumMoney(singleProfit);
+            adProfitDetail.setUserId(Long.valueOf(params.get("user_id").toString()));
+            // 如果用户收益小于保底收益,直接给用户保底 并且不进行分润
+            if (resultCom) {
+                adProfitDetail.setUserMoney(BigDecimal.valueOf(minimumIntegral).divide(new BigDecimal("10"),2,RoundingMode.DOWN));
+            } else {
+                adProfitDetail.setUserMoney(customerProfit);
+                adProfitDetail.setCompanyMoney(salesProfit);
+                adProfitDetail.setHuyiMoney(huyiProfit);
+                adProfitDetail.setYunlianMoney(yunlianProfit);
+            }
+
+
+            adProfitDetail.setCompanyId(
+                    StringUtils.isNotEmpty(companyId) ? Long.valueOf(companyId) : null
+            );
+
+            adProfitDetail.setCreateTime(LocalDateTime.now());
+            adProfitDetail.setOriginParam(JSON.toJSONString(params));
+
+            int result = adProfitDetailMapper.insert(adProfitDetail);
+
+            return R.ok().put("isValid", result > 0);
+        } catch (Exception e) {
+            log.error(
+                    "【广告回调 异常】,原始参数={}", params, e);
+            return R.ok().put("isValid", false);
+        }
+    }
+
+    /**
+     * uniapp广告 返回一个广告id
+     */
+    @Override
+    public R createLogs(Long userId) {
+        String adTaskId = IdUtil.fastSimpleUUID() + userId;
+        return R.ok().put("extra", adTaskId);
+    }
+
+    /**
+     * 获取用户钱包明细
+     */
+    @Override
+    public R getWallet(Long userId) {
+        IntegralExchangeVo vo = fsUserMapper.getWallet(userId);
+        vo.setMayWithdraw(vo.getMayWithdraw().divide(BigDecimal.valueOf(100)));
+        vo.setTotalCommission(vo.getTotalCommission().divide(BigDecimal.valueOf(100)));
+        vo.setWithdrawFinish(vo.getWithdrawFinish().divide(BigDecimal.valueOf(100)));
+        vo.setWithdrawCash(new BigDecimal(vo.getWithdrawIntegral()).divide(BigDecimal.valueOf(1000),2,RoundingMode.DOWN));
+        return R.ok().put("data", vo);
+    }
+
+    /**
+     * 用户钱包 积分兑换佣金
+     */
+    @Override
+    public R integralExchange(Long userId) {
+        // 查询用户钱包明细
+        FsUser fsUser = fsUserMapper.selectFsUserByUserId(userId);
+        if (BeanUtil.isEmpty(fsUser)) {
+            return R.error("用户不存在");
+        }
+
+        Long withdrawIntegral = fsUser.getWithdrawIntegral();
+        if (withdrawIntegral <= 0){
+            return R.error("积分不足");
+        }
+
+        // 积分转金额  1元=1000积分   库里存储的是单位是分:÷10
+        BigDecimal commission = new BigDecimal(withdrawIntegral ).divide(BigDecimal.valueOf(10),2,RoundingMode.DOWN);
+        // 更新用户积分 佣金
+        FsUser user = new FsUser();
+        user.setUserId(userId);
+        user.setIntegral(fsUser.getIntegral() - withdrawIntegral);
+        user.setWithdrawIntegral(0L);
+        user.setTotalCommission(fsUser.getTotalCommission().add(commission));
+        user.setUpdateTime(new Date());
+        user.setMayWithdraw(fsUser.getMayWithdraw().add(commission));
+        fsUserMapper.updateFsUser(user);
+        FsIntegralExchange fsIntegralExchange = new FsIntegralExchange();
+        fsIntegralExchange.setUserId(userId);
+        fsIntegralExchange.setNickName(fsUser.getNickName());
+        fsIntegralExchange.setPhone(fsUser.getPhone());
+        fsIntegralExchange.setCreateTime(new Date());
+        fsIntegralExchange.setIntegral(-withdrawIntegral);
+        fsIntegralExchangeService.insertFsIntegralExchange(fsIntegralExchange);
+        //添加积分记录
+        FsUserIntegralLogs logs = new FsUserIntegralLogs();
+        logs.setUserId(userId);
+        if(fsIntegralExchange.getId() != null){
+            logs.setBusinessId(fsIntegralExchange.getId().toString());
+        }
+        logs.setLogType(FsUserIntegralLogTypeEnum.TYPE_32.getValue());
+        logs.setIntegral(-withdrawIntegral);
+        logs.setBalance(fsUser.getIntegral() - withdrawIntegral);
+        logs.setCreateTime(new Date());
+        logs.setNickName(StringUtils.isNotEmpty(fsUser.getNickName()) ? fsUser.getNickName() : null);
+        logs.setPhone(fsUser.getPhone());
+        fsUserIntegralLogsService.insertFsUserIntegralLogs(logs);
+        return R.ok("兑换成功");
+    }
+
+    /**
+     * 用户钱包 获取兑换明细
+     */
+    @Override
+    public R exchangDetail(Map<String, Object> params) {
+        PageHelper.startPage(Integer.parseInt(params.get("page").toString()), Integer.parseInt(params.get("limit").toString()));
+        ExchangeDetailVo detailVo = new ExchangeDetailVo();
+        detailVo.setUserId(Long.valueOf(params.get("userId").toString()));
+        detailVo.setLogType(FsUserIntegralLogTypeEnum.TYPE_32.getValue());
+        // 查询积分记录
+        List<ExchangeDetailVo> logsList =fsUserIntegralLogsMapper.getExchangDetailList(detailVo);
+        PageInfo<ExchangeDetailVo> logsPageInfo = new PageInfo<>(logsList);
+        Map<String,Object> rMap = new HashMap<>();
+        rMap.put("rows", logsPageInfo.getList());
+        rMap.put("total", logsPageInfo.getTotal());
+        return R.ok().put("data", rMap);
+    }
+
+    /**
+     * 用户提现
+     */
+    @Override
+    public R withdrawal(FsIntegralWithdrawalParam param) {
+        Long userId = param.getUserId();
+        // 生成锁的key,基于用户ID和视频ID确保同一用户同一视频的请求被锁定
+        String lockKey = "reward_integral_lock:user:" + userId;
+        RLock lock = redissonClient.getLock(lockKey);
+
+        try {
+            // 尝试获取锁,等待时间5秒,锁过期时间30秒
+            boolean isLocked = lock.tryLock(5, 300, TimeUnit.SECONDS);
+            if (!isLocked) {
+                logger.warn("获取锁失败,用户ID:{}", userId);
+                return R.error("操作频繁,请稍后再试!");
+            }
+
+            logger.info("成功获取锁,开始处理奖励发放,用户ID:{}", userId);
+            return executeWithdrawal(param);
+
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            logger.error("获取锁被中断,用户ID:{}", userId, e);
+            return R.error("系统繁忙,请重试!");
+        } finally {
+            // 释放锁
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                logger.info("释放锁成功,用户ID:{}", userId);
+            }
+        }
+
+    }
+
+    private R executeWithdrawal(FsIntegralWithdrawalParam param){
+        //查询可提现
+        Long userId = param.getUserId();
+        FsUser fsUser = selectFsUserByUserId(userId);
+        if (fsUser == null) {
+            return R.error("用户不存在");
+        }
+        //拉黑用户不能提现
+        if (fsUser.getStatus() == 0){
+            return R.error("暂无法提现!");
+        }
+        BigDecimal mayWithdraw = fsUser.getMayWithdraw();
+        if (mayWithdraw == null || mayWithdraw.compareTo(BigDecimal.ZERO) <= 0) {
+            return R.error("用户无可提现金额");
+        }
+        BigDecimal applicationAmount = param.getApplicationAmount();
+        if (applicationAmount == null || applicationAmount.compareTo(BigDecimal.ZERO) <= 0) {
+            return R.error("用户提现金额错误");
+        }
+        String json = configService.selectConfigByKey("his.integral");
+        IntegralConfig config = JSONUtil.toBean(json, IntegralConfig.class);
+        BigDecimal maxApplicationAmount = config.getMaxApplicationAmount();
+        if (maxApplicationAmount != null && maxApplicationAmount.compareTo(BigDecimal.ZERO) > 0 && applicationAmount.compareTo(maxApplicationAmount) > 0) {
+            return R.error("一次最多提现 "+ maxApplicationAmount + " 元");
+        }
+        if (mayWithdraw.compareTo(applicationAmount.multiply(BigDecimal.valueOf(100))) < 0) {
+            return R.error("用户可提现金额不足");
+        }
+        Integer withdrawNum = config.getWithdrawNum();
+        if (withdrawNum != null) {
+            Long count = integralRedPacketLogMapper.countLogsByToday(userId);
+            if (count != null && count >= withdrawNum) {
+                return R.error("当天最多领取 " + withdrawNum + "次 ,未领取成功的,可点击详情重新领取!");
+            }
+        }
+
+        // 保存log
+        FsIntegralRedPacketLog fsIntegralRedPacketLog = saveRedPacketLog(userId, applicationAmount,param.getAppId());
+        //来源是小程序切换openId
+        WxSendRedPacketParam packetParam = new WxSendRedPacketParam();
+        String openId = getOpenId(param, fsUser);
+        if (StringUtils.isBlank(openId)) {
+            fsIntegralRedPacketLog.setStatus("-1");
+            fsIntegralRedPacketLog.setRemark("失败原因:未使用微信登录");
+            fsIntegralRedPacketLog.setReturnedStatus(1);
+            integralRedPacketLogMapper.updateFsIntegralRedPacketLog(fsIntegralRedPacketLog);
+            return R.error("请重新使用微信登录");
+        }
+        packetParam.setOpenId(openId);
+        packetParam.setAmount(applicationAmount);
+        packetParam.setSource(param.getSource());
+        packetParam.setAppId(param.getAppId());
+        try {
+            //发送红包
+            R sendRedPacket = paymentService.sendIntegralRedPacket(packetParam);
+
+            if (sendRedPacket.get("code").equals(200)) {
+                TransferBillsResult transferBillsResult;
+                if (sendRedPacket.get("isNew").equals(1)) {
+                    transferBillsResult = (TransferBillsResult) sendRedPacket.get("data");
+                    fsIntegralRedPacketLog.setOutBatchNo(transferBillsResult.getOutBillNo());
+                    fsIntegralRedPacketLog.setBatchId(transferBillsResult.getTransferBillNo());
+                } else {
+                    fsIntegralRedPacketLog.setOutBatchNo(sendRedPacket.get("orderCode").toString());
+                    fsIntegralRedPacketLog.setBatchId(sendRedPacket.get("batchId").toString());
+                }
+                fsIntegralRedPacketLog.setUpdateTime(new Date());
+                Object aPackage = sendRedPacket.get("package");
+                if (aPackage != null) {
+                    fsIntegralRedPacketLog.setPackageInfo(aPackage.toString());
+                }
+                Object mchIdObj = sendRedPacket.get("mchId");
+                if (mchIdObj != null) {
+                    fsIntegralRedPacketLog.setMchId(mchIdObj.toString());
+                }
+                // 更新观看记录的奖励类型
+                integralRedPacketLogMapper.updateFsIntegralRedPacketLog(fsIntegralRedPacketLog);
+                //减少可兑换佣金
+                BigDecimal fenWithdraw = fsIntegralRedPacketLog.getAmount().multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP);
+                mayWithdraw = mayWithdraw.subtract(fenWithdraw);
+                BigDecimal withdrawFinish = fsUser.getWithdrawFinish().add(fenWithdraw);
+                fsUser.setMayWithdraw(mayWithdraw);
+                fsUser.setWithdrawFinish(withdrawFinish);
+                log.info("佣金提现修改mayWithdraw:{},withdrawFinish:{}", mayWithdraw, fsUser.getWithdrawFinish());
+                fsUserMapper.updateFsUser(fsUser);
+                return sendRedPacket;
+            }
+        } catch (Exception e) {
+            return R.error("提现失败!请联系管理员!" + e.getMessage());
+        }
+        return R.ok("发放成功");
+    }
+
+    private FsIntegralRedPacketLog saveRedPacketLog(Long userId, BigDecimal applicationAmount,String appId) {
+        FsIntegralRedPacketLog redPacketLog = new FsIntegralRedPacketLog();
+        redPacketLog.setUserId(userId);
+        redPacketLog.setAmount(applicationAmount);
+        redPacketLog.setStatus("0");
+        redPacketLog.setAppId(appId);
+        redPacketLog.setCreateTime(new Date());
+        integralRedPacketLogMapper.insertFsIntegralRedPacketLog(redPacketLog);
+        return redPacketLog;
+    }
+
+    /**
+     * 获取用户openId
+     */
+    private String getOpenId(FsIntegralWithdrawalParam param, FsUser user) {
+        if (param.getSource()==2){
+            FsUserWx fsUserWx = fsUserWxService.selectByAppIdAndUserId(param.getAppId(),user.getUserId(),1);
+            if (Objects.nonNull(fsUserWx) && StringUtils.isNotBlank(fsUserWx.getOpenId())) {
+                return fsUserWx.getOpenId();
+            }
+
+            if (StringUtils.isNotBlank(user.getCourseMaOpenId())) {
+                try {
+                    handleFsUserWx(user,param.getAppId());
+                } catch (Exception e){
+                    log.error("【更新或插入用户与小程序的绑定关系失败】:{}", user.getUserId(), e);
+                }
+                return user.getCourseMaOpenId();
+            }
+        } else if (param.getSource()==3){
+            return user.getAppOpenId();
+        }
+
+        return user.getMpOpenId();
+    }
+
+    /**
+     * 处理用户与小程序的绑定
+     */
+    private void handleFsUserWx(FsUser user,String appId) {
+        FsUserWx fsUserWx = new FsUserWx();
+        fsUserWx.setType(1);
+        fsUserWx.setFsUserId(user.getUserId());
+        fsUserWx.setAppId(appId);
+        fsUserWx.setOpenId(user.getCourseMaOpenId());
+        fsUserWx.setUnionId(user.getUnionId());
+        fsUserWx.setCreateTime(new Date());
+        fsUserWx.setUpdateTime(new Date());
+        fsUserWxService.saveOrUpdateByUniqueKey(fsUserWx);
+
+        logger.info("zyp \n 【更新或插入用户与小程序{}的绑定关系】:{}", appId, user.getUserId());
+
+    }
+
+    /**
+     * 批量禁用用户
+     */
+    @Override
+    public int disabledUsers(FsUserDisabledUsersParam param) {
+        if (param.getUserIds() == null || param.getUserIds().isEmpty()) {
+            return -1;
+        } else {
+            return fsUserMapper.disabledUsers(param);
+        }
+    }
+
 }

+ 76 - 0
fs-service/src/main/java/com/fs/his/utils/ProfitShareUtils.java

@@ -0,0 +1,76 @@
+package com.fs.his.utils;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ProfitShareUtils {
+
+    /**
+     * 分润项内部结构(用于排序与临时计算)
+     */
+    private static class CalcItem {
+        int index;              // 下标(保持原顺序)
+        int integerPart;        // 截断后的整数部分(分)
+        BigDecimal decimalPart; // 小数部分
+        CalcItem(int index, int integerPart, BigDecimal decimalPart) {
+            this.index = index;
+            this.integerPart = integerPart;
+            this.decimalPart = decimalPart;
+        }
+    }
+
+    /**
+     * 分润核心算法:银行级最小单位分配法
+     *
+     * @param total 总金额(元)
+     * @param ratios 比例列表(合计建议为 1)
+     * @return Map<Integer, BigDecimal>   key = 下标,value = 金额(元)
+     */
+    public static Map<Integer, BigDecimal> allocate(BigDecimal total, List<BigDecimal> ratios) {
+
+        if (total == null || ratios == null || ratios.isEmpty()) {
+            throw new IllegalArgumentException("金额或比例不能为空");
+        }
+
+        // 金额换算成分(总金额必须是合法金额,能够 *100 得到整数)
+        BigDecimal totalCentBD = total.multiply(new BigDecimal("100"));
+        int totalCent = totalCentBD.intValue();
+
+        List<CalcItem> calcs = new ArrayList<>();
+        int sumInteger = 0;
+
+        // 1) 计算每项理论金额(单位:分)
+        for (int i = 0; i < ratios.size(); i++) {
+
+            BigDecimal raw = totalCentBD.multiply(ratios.get(i)); // 理论分
+            int integerPart = raw.setScale(0, BigDecimal.ROUND_DOWN).intValue();
+            BigDecimal decimalPart = raw.subtract(new BigDecimal(integerPart)); // 小数部分
+
+            sumInteger += integerPart;
+            calcs.add(new CalcItem(i, integerPart, decimalPart));
+        }
+
+        // 2) 计算剩余分
+        int remain = totalCent - sumInteger;
+
+        // 3) 按小数从大到小分配剩余分
+        calcs.sort((a, b) -> b.decimalPart.compareTo(a.decimalPart));
+
+        for (int i = 0; i < remain; i++) {
+            calcs.get(i).integerPart += 1;
+        }
+
+        // 4) 输出结果(元)
+        Map<Integer, BigDecimal> result = new HashMap<>();
+        for (CalcItem c : calcs) {
+            BigDecimal yuan = new BigDecimal(c.integerPart)
+                    .divide(new BigDecimal("100"), 2, BigDecimal.ROUND_DOWN);
+            result.put(c.index, yuan);
+        }
+
+        return result;
+    }
+}

+ 30 - 0
fs-service/src/main/java/com/fs/his/utils/UniAdSignUtils.java

@@ -0,0 +1,30 @@
+package com.fs.his.utils;
+
+import cn.hutool.crypto.digest.DigestAlgorithm;
+import cn.hutool.crypto.digest.Digester;
+
+/**
+ * UniApp激励广告回调签名验证工具类
+ */
+public class UniAdSignUtils {
+    // 广告位secret 红德堂密钥
+    private static final String UNI_AD_SECRET = "bf50976c7b78e20ba08f8dde1a1afcfb0e0abbba9d6c7ad1f8566a22f04e6c82";
+
+    /**
+     * 验证UniApp广告回调签名
+     * 签名规则:sign = sha256(secret + trans_id)
+     * @param transId      回调参数中的trans_id(唯一交易ID)
+     * @param receivedSign 回调参数中收到的sign
+     * @return 签名是否有效
+     */
+    public static boolean verifySign(String transId, String receivedSign) {
+        // 校验参数合法性
+        if (transId == null || receivedSign == null) {
+            return false;
+        }
+
+        Digester sha256 = new Digester(DigestAlgorithm.SHA256);
+        String sign = sha256.digestHex(UNI_AD_SECRET + ":" + transId);
+        return sign.equals(receivedSign);
+    }
+}

+ 14 - 0
fs-service/src/main/java/com/fs/his/vo/AdProfitDetailStatisticsVo.java

@@ -0,0 +1,14 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class AdProfitDetailStatisticsVo {
+    @Excel(name = "公司名称")
+    String name;
+    @Excel(name = "分佣金额")
+    BigDecimal amountYuan;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/his/vo/ExchangeDetailVo.java

@@ -0,0 +1,23 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 积分兑换佣金明细
+ */
+
+@Data
+public class ExchangeDetailVo {
+    private  Long id;
+    private  Long  userId;
+    private Integer logType;
+    private Long integral;
+    private Long balance;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createTime;
+    private BigDecimal commission;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/his/vo/FsConsecutiveWithdrawRecordVo.java

@@ -0,0 +1,12 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import com.fs.his.domain.FsConsecutiveWithdrawRecord;
+import lombok.Data;
+
+@Data
+public class FsConsecutiveWithdrawRecordVo extends FsConsecutiveWithdrawRecord {
+    //用户昵称
+    @Excel(name = "用户昵称",sort = 2)
+    private String nickName;
+}

+ 13 - 0
fs-service/src/main/java/com/fs/his/vo/FsIntegralRedPacketLogVo.java

@@ -0,0 +1,13 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import com.fs.his.domain.FsIntegralRedPacketLog;
+import lombok.Data;
+
+@Data
+public class FsIntegralRedPacketLogVo extends FsIntegralRedPacketLog {
+    @Excel(name = "用户昵称",sort = 2)
+    private String nickName;
+
+
+}

+ 22 - 0
fs-service/src/main/java/com/fs/his/vo/IntegralExchangeVo.java

@@ -0,0 +1,22 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class IntegralExchangeVo {
+    private Long userId;
+    // 总积分
+    private Long integral;
+    // 可提现佣金
+    private BigDecimal mayWithdraw;
+    // 可兑换积分
+    private Long withdrawIntegral;
+    // 累计佣金
+    private BigDecimal totalCommission;
+    // 已提现佣金
+    private BigDecimal withdrawFinish;
+    // 可兑换现金
+    private BigDecimal withdrawCash;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/live/domain/LiveUserRedRecord.java

@@ -31,7 +31,7 @@ public class LiveUserRedRecord extends BaseEntity{
     private Long userId;
 
     /** 芳华币数量 */
-    @Excel(name = "芳华币数量")
+    @Excel(name = "积分数量")
     private Long integral;
 
 

+ 1 - 1
fs-service/src/main/java/com/fs/live/service/impl/LiveRedConfServiceImpl.java

@@ -274,7 +274,7 @@ public class LiveRedConfServiceImpl implements ILiveRedConfService {
         //String msg = String.format("用户 %d 抢到了红包 %d,获得 %d 芳华币", userId, redId, integral);
         //WebSocketServer.notifyUsers(msg);
         redisCache.hashPut(String.format(LiveKeysConstant.LIVE_HOME_PAGE_CONFIG_RED, red.getLiveId(), red.getRedId()), String.valueOf(red.getUserId()), JSONUtil.toJsonStr(record));
-        return R.ok("恭喜您成功抢到" + integral + "芳华币");
+        return R.ok("恭喜您成功抢到" + integral + "积分");
 /*        } catch (Exception e) {
             e.printStackTrace();
             log.error("抢红包异常:" + e.getMessage());

+ 253 - 0
fs-service/src/main/resources/mapper/his/AdProfitDetailMapper.xml

@@ -0,0 +1,253 @@
+<?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.his.mapper.AdProfitDetailMapper">
+    <resultMap type="AdProfitDetail" id="AdProfitDetailResult">
+        <result property="id"    column="id"    />
+        <result property="adTransId"    column="ad_trans_id"    />
+        <result property="adTaskId"    column="ad_task_id"    />
+        <result property="sumMoney"    column="sum_money"    />
+        <result property="userId"    column="user_id"    />
+        <result property="userMoney"    column="user_money"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyMoney"    column="company_money"    />
+        <result property="huyiMoney"    column="huyi_money"    />
+        <result property="yunlianMoney"    column="yunlian_money"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectAdProfitDetailVo">
+        select id, ad_trans_id, ad_task_id, sum_money, user_id, user_money, company_id, company_money, huyi_money, yunlian_money, create_time from ad_profit_detail
+    </sql>
+
+
+
+    <select id="selectAdProfitDetailById" parameterType="Long" resultMap="AdProfitDetailResult">
+        <include refid="selectAdProfitDetailVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertAdProfitDetail" parameterType="AdProfitDetail" useGeneratedKeys="true" keyProperty="id">
+        insert into ad_profit_detail
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="adTransId != null and adTransId != ''">ad_trans_id,</if>
+            <if test="adTaskId != null">ad_task_id,</if>
+            <if test="sumMoney != null">sum_money,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="userMoney != null">user_money,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyMoney != null">company_money,</if>
+            <if test="huyiMoney != null">huyi_money,</if>
+            <if test="yunlianMoney != null">yunlian_money,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="adTransId != null and adTransId != ''">#{adTransId},</if>
+            <if test="adTaskId != null">#{adTaskId},</if>
+            <if test="sumMoney != null">#{sumMoney},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="userMoney != null">#{userMoney},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyMoney != null">#{companyMoney},</if>
+            <if test="huyiMoney != null">#{huyiMoney},</if>
+            <if test="yunlianMoney != null">#{yunlianMoney},</if>
+            <if test="createTime != null">#{createTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateAdProfitDetail" parameterType="AdProfitDetail">
+        update ad_profit_detail
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="adTransId != null and adTransId != ''">ad_trans_id = #{adTransId},</if>
+            <if test="adTaskId != null">ad_task_id = #{adTaskId},</if>
+            <if test="sumMoney != null">sum_money = #{sumMoney},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="userMoney != null">user_money = #{userMoney},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyMoney != null">company_money = #{companyMoney},</if>
+            <if test="huyiMoney != null">huyi_money = #{huyiMoney},</if>
+            <if test="yunlianMoney != null">yunlian_money = #{yunlianMoney},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteAdProfitDetailById" parameterType="Long">
+        delete from ad_profit_detail where id = #{id}
+    </delete>
+
+    <delete id="deleteAdProfitDetailByIds" parameterType="String">
+        delete from ad_profit_detail where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <select id="getCompanyByUserId" resultType="java.lang.String">
+        SELECT
+            company_id
+        FROM
+            `qw_external_contact`
+        WHERE
+            fs_user_id = #{userId}
+        ORDER BY
+            create_time ASC LIMIT 1
+    </select>
+
+    <select id="selectAdProfitDetailList" resultType="com.fs.his.dto.AdProfitDetailDto">
+        SELECT
+        a.*,
+        b.company_name,
+        c.nick_name
+        FROM
+        `ad_profit_detail` a
+        LEFT JOIN company b ON a.company_id = b.company_id
+        LEFT JOIN fs_user c ON a.user_id = c.user_id
+        <where>
+            <if test="adTransId != null  and adTransId != ''"> and a.ad_trans_id = #{adTransId}</if>
+            <if test="adTaskId != null  and adTaskId != ''"> and a.ad_task_id = #{adTaskId}</if>
+            <if test="sumMoney != null "> and a.sum_money = #{sumMoney}</if>
+            <if test="userId != null "> and a.user_id = #{userId}</if>
+            <if test="userMoney != null "> and a.user_money = #{userMoney}</if>
+            <if test="companyId != null "> and a.company_id = #{companyId}</if>
+            <if test="companyMoney != null "> and a.company_money = #{companyMoney}</if>
+            <if test="huyiMoney != null "> and a.huyi_money = #{huyiMoney}</if>
+            <if test="yunlianMoney != null "> and a.yunlian_money = #{yunlianMoney}</if>
+            <if test="companyName != null"> and b.company_name = #{companyName}</if>
+            <if test="sTime != null"> and Date(a.create_time) &gt;= Date(#{sTime})</if>
+            <if test="eTime != null"> and Date(a.create_time) &lt;= Date(#{eTime})</if>
+            <if test="nickName != null"> and c.nick_name like concat('%',#{nickName},'%')</if>
+        </where>
+        order by a.create_time desc
+    </select>
+
+    <select id="getWithFinishAndMayWithdraw" resultType="java.util.Map">
+        SELECT
+            SUM(may_withdraw) AS totalMayWithdraw,
+            SUM(withdraw_finish) AS totalWithdrawFinish
+        FROM
+            fs_user
+        WHERE
+            may_withdraw > 0.00
+           OR withdraw_finish > 0.00
+    </select>
+    <select id="statisticsList" resultType="com.fs.his.vo.AdProfitDetailStatisticsVo">
+        <!-- 1. 用户分润 -->
+        SELECT
+        '用户分润' AS `name`,
+        IFNULL(ROUND(SUM(a.user_money) / 100, 2), 0) AS amountYuan,
+        'summary' AS type,
+        1 AS sortOrder
+        FROM ad_profit_detail a
+        WHERE <include refid="timeCondition"/>
+
+        UNION ALL
+
+        <!-- 2. 互医分润 -->
+        SELECT
+        '互医分润' AS `name`,
+        IFNULL(ROUND(SUM(a.huyi_money) / 100, 2), 0) AS amountYuan,
+        'summary' AS type,
+        2 AS sortOrder
+        FROM ad_profit_detail a
+        WHERE <include refid="timeCondition"/>
+
+        UNION ALL
+
+        <!-- 3. 云联分润 -->
+        SELECT
+        '云联分润' AS `name`,
+        IFNULL(ROUND(SUM(a.yunlian_money) / 100, 2), 0) AS amountYuan,
+        'summary' AS type,
+        3 AS sortOrder
+        FROM ad_profit_detail a
+        WHERE <include refid="timeCondition"/>
+
+        UNION ALL
+
+        <!-- 4. 各公司分润 -->
+        SELECT
+        c.company_name AS `name`,
+        IFNULL(ROUND(SUM(a.company_money) / 100, 2), 0) AS amountYuan,
+        'company' AS type,
+        a.company_id + 100 AS sortOrder
+        FROM ad_profit_detail a
+        LEFT JOIN company c ON a.company_id = c.company_id
+        WHERE a.company_id IS NOT NULL
+        AND <include refid="timeCondition"/>
+        GROUP BY a.company_id, c.company_name
+
+        ORDER BY sortOrder, `name`
+    </select>
+
+    <sql id="timeCondition">
+        <choose>
+            <!-- type为null时,使用startTime和endTime -->
+            <when test="param.type == null">
+                a.create_time >= #{param.startTime}
+                AND a.create_time &lt; DATE_ADD(#{param.endTime}, INTERVAL 1 DAY)
+            </when>
+
+            <!-- 1. 今天 -->
+            <when test="param.type == 1">
+                a.create_time >= CURDATE()
+                AND a.create_time &lt; DATE_ADD(CURDATE(), INTERVAL 1 DAY)
+            </when>
+
+            <!-- 2. 昨天 -->
+            <when test="param.type == 2">
+                a.create_time >= DATE_SUB(CURDATE(), INTERVAL 1 DAY)
+                AND a.create_time &lt; CURDATE()
+            </when>
+
+            <!-- 3. 本周 -->
+            <when test="param.type == 3">
+                a.create_time >= DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)
+                AND a.create_time &lt; DATE_ADD(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY)
+            </when>
+
+            <!-- 4. 上周 -->
+            <when test="param.type == 4">
+                a.create_time >= DATE_SUB(DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY), INTERVAL 7 DAY)
+                AND a.create_time &lt; DATE_SUB(CURDATE(), INTERVAL WEEKDAY(CURDATE()) DAY)
+            </when>
+
+            <!-- 5. 本月 -->
+            <when test="param.type == 5">
+                a.create_time >= DATE_FORMAT(CURDATE(), '%Y-%m-01')
+                AND a.create_time &lt; DATE_ADD(DATE_FORMAT(CURDATE(), '%Y-%m-01'), INTERVAL 1 MONTH)
+            </when>
+
+            <!-- 6. 上月 -->
+            <when test="param.type == 6">
+                a.create_time >= DATE_SUB(DATE_FORMAT(CURDATE(), '%Y-%m-01'), INTERVAL 1 MONTH)
+                AND a.create_time &lt; DATE_FORMAT(CURDATE(), '%Y-%m-01')
+            </when>
+
+            <!-- 7. 本季度 -->
+            <when test="param.type == 7">
+                a.create_time >= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', QUARTER(CURDATE())*3-2, '-01'), '%Y-%m-%d')
+                AND a.create_time &lt; DATE_ADD(STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', QUARTER(CURDATE())*3-2, '-01'), '%Y-%m-%d'), INTERVAL 3 MONTH)
+            </when>
+
+            <!-- 8. 上季度 -->
+            <when test="param.type == 8">
+                a.create_time >= DATE_SUB(STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', QUARTER(CURDATE())*3-2, '-01'), '%Y-%m-%d'), INTERVAL 3 MONTH)
+                AND a.create_time &lt; STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-', QUARTER(CURDATE())*3-2, '-01'), '%Y-%m-%d')
+            </when>
+
+            <!-- 9. 本年 -->
+            <when test="param.type == 9">
+                a.create_time >= STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d')
+                AND a.create_time &lt; DATE_ADD(STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d'), INTERVAL 1 YEAR)
+            </when>
+
+            <!-- 10. 去年 -->
+            <when test="param.type == 10">
+                a.create_time >= DATE_SUB(STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d'), INTERVAL 1 YEAR)
+                AND a.create_time &lt; STR_TO_DATE(CONCAT(YEAR(CURDATE()), '-01-01'), '%Y-%m-%d')
+            </when>
+        </choose>
+    </sql>
+
+
+</mapper>

+ 137 - 0
fs-service/src/main/resources/mapper/his/FsConsecutiveWithdrawRecordMapper.xml

@@ -0,0 +1,137 @@
+<?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.his.mapper.FsConsecutiveWithdrawRecordMapper">
+    
+    <resultMap type="FsConsecutiveWithdrawRecord" id="FsConsecutiveWithdrawRecordResult">
+        <result property="id"    column="id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="consecutiveDays"    column="consecutive_days"    />
+        <result property="startDate"    column="start_date"    />
+        <result property="endDate"    column="end_date"    />
+        <result property="withdrawCount"    column="withdraw_count"    />
+        <result property="totalAmount"    column="total_amount"    />
+        <result property="status"    column="status"    />
+        <result property="remark"    column="remark"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectFsConsecutiveWithdrawRecordVo">
+        select id, user_id, consecutive_days, start_date, end_date, withdraw_count, total_amount, status, remark, create_time, update_time from fs_consecutive_withdraw_record
+    </sql>
+
+    <select id="selectFsConsecutiveWithdrawRecordList" parameterType="FsConsecutiveWithdrawRecord" resultType="com.fs.his.vo.FsConsecutiveWithdrawRecordVo">
+        select wr.*,fu.nick_name from fs_consecutive_withdraw_record wr
+        left join fs_user fu on fu.user_id = wr.user_id
+        <where>  
+            <if test="userId != null "> and wr.user_id = #{userId}</if>
+            <if test="consecutiveDays != null "> and wr.consecutive_days = #{consecutiveDays}</if>
+            <if test="startDate != null "> and wr.start_date = #{startDate}</if>
+            <if test="endDate != null "> and wr.end_date = #{endDate}</if>
+            <if test="withdrawCount != null "> and wr.withdraw_count = #{withdrawCount}</if>
+            <if test="totalAmount != null "> and wr.total_amount = #{totalAmount}</if>
+            <if test="status != null "> and wr.status = #{status}</if>
+            <if test="nickName != null and nickName != ''"> and fu.nick_name like concat(#{nickName},"%")</if>
+        </where>
+        order by wr.id desc
+    </select>
+
+    <select id="selectFsConsecutiveWithdrawRecordList_COUNT" parameterType="FsConsecutiveWithdrawRecord" resultType="Long">
+        select count(*) from fs_consecutive_withdraw_record wr
+        <if test="nickName != null and nickName != ''">
+            left join fs_user fu on fu.user_id = wr.user_id
+        </if>
+        <where>
+            <if test="userId != null "> and wr.user_id = #{userId}</if>
+            <if test="consecutiveDays != null "> and wr.consecutive_days = #{consecutiveDays}</if>
+            <if test="startDate != null "> and wr.start_date = #{startDate}</if>
+            <if test="endDate != null "> and wr.end_date = #{endDate}</if>
+            <if test="withdrawCount != null "> and wr.withdraw_count = #{withdrawCount}</if>
+            <if test="totalAmount != null "> and wr.total_amount = #{totalAmount}</if>
+            <if test="status != null "> and wr.status = #{status}</if>
+            <if test="nickName != null and nickName != ''"> and fu.nick_name like concat(#{nickName},"%")</if>
+        </where>
+    </select>
+    
+    <select id="selectFsConsecutiveWithdrawRecordById" parameterType="Long" resultMap="FsConsecutiveWithdrawRecordResult">
+        <include refid="selectFsConsecutiveWithdrawRecordVo"/>
+        where id = #{id}
+    </select>
+    <select id="existsRecordInPeriod" resultType="java.lang.Boolean">
+        SELECT COUNT(1) > 0 FROM fs_consecutive_withdraw_record
+        WHERE user_id = #{userId}
+        AND start_date >= #{startDate}
+        AND end_date &lt;= #{endDate}
+        AND consecutive_days >= #{thresholdDays}
+    </select>
+    <select id="countByUserAndPeriod" resultType="java.lang.Integer">
+                SELECT  * FROM fs_consecutive_withdraw_record
+                WHERE user_id = #{userId}
+                AND start_date = #{startDate}
+                AND end_date = #{endDate}
+    </select>
+    <select id="selectCountByUserIdAndStartTime" resultType="com.fs.his.domain.FsConsecutiveWithdrawRecord">
+        SELECT * FROM fs_consecutive_withdraw_record
+        WHERE user_id = #{userId}
+          AND start_date = #{startDate}
+        order by id desc limit 1
+    </select>
+
+    <insert id="insertFsConsecutiveWithdrawRecord" parameterType="FsConsecutiveWithdrawRecord" useGeneratedKeys="true" keyProperty="id">
+        insert into fs_consecutive_withdraw_record
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="consecutiveDays != null">consecutive_days,</if>
+            <if test="startDate != null">start_date,</if>
+            <if test="endDate != null">end_date,</if>
+            <if test="withdrawCount != null">withdraw_count,</if>
+            <if test="totalAmount != null">total_amount,</if>
+            <if test="status != null">status,</if>
+            <if test="remark != null">remark,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="consecutiveDays != null">#{consecutiveDays},</if>
+            <if test="startDate != null">#{startDate},</if>
+            <if test="endDate != null">#{endDate},</if>
+            <if test="withdrawCount != null">#{withdrawCount},</if>
+            <if test="totalAmount != null">#{totalAmount},</if>
+            <if test="status != null">#{status},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsConsecutiveWithdrawRecord" parameterType="FsConsecutiveWithdrawRecord">
+        update fs_consecutive_withdraw_record
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="consecutiveDays != null">consecutive_days = #{consecutiveDays},</if>
+            <if test="startDate != null">start_date = #{startDate},</if>
+            <if test="endDate != null">end_date = #{endDate},</if>
+            <if test="withdrawCount != null">withdraw_count = #{withdrawCount},</if>
+            <if test="totalAmount != null">total_amount = #{totalAmount},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsConsecutiveWithdrawRecordById" parameterType="Long">
+        delete from fs_consecutive_withdraw_record where id = #{id}
+    </delete>
+
+    <delete id="deleteFsConsecutiveWithdrawRecordByIds" parameterType="String">
+        delete from fs_consecutive_withdraw_record where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 70 - 0
fs-service/src/main/resources/mapper/his/FsIntegralExchangeMapper.xml

@@ -0,0 +1,70 @@
+<?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.his.mapper.FsIntegralExchangeMapper">
+
+    <resultMap type="FsIntegralExchange" id="FsIntegralExchangeResult">
+        <result property="id"    column="id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="integral"    column="integral"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="status"    column="status"    />
+        <result property="phone"    column="phone"    />
+        <result property="nickName"    column="nick_name"    />
+    </resultMap>
+
+    <sql id="selectFsIntegralExchangeVo">
+        select id, user_id, integral, create_time, status, phone, nick_name from fs_integral_exchange
+    </sql>
+
+    <select id="selectFsIntegralExchangeList" parameterType="FsIntegralExchange" resultMap="FsIntegralExchangeResult">
+        <include refid="selectFsIntegralExchangeVo"/>
+        <where>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="integral != null "> and integral = #{integral}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="phone != null  and phone != ''"> and phone = #{phone}</if>
+            <if test="nickName != null  and nickName != ''"> and nick_name like concat('%', #{nickName}, '%')</if>
+        </where>
+    </select>
+
+    <select id="selectFsIntegralExchangeById" parameterType="Long" resultMap="FsIntegralExchangeResult">
+        <include refid="selectFsIntegralExchangeVo"/>
+        where id = #{id}
+    </select>
+
+    <insert id="insertFsIntegralExchange"
+            parameterType="com.fs.his.domain.FsIntegralExchange"
+            useGeneratedKeys="true"
+            keyProperty="id">
+        insert into fs_integral_exchange
+            (user_id, integral, create_time, status, phone, nick_name)
+        values
+            (#{userId}, #{integral}, #{createTime}, #{status}, #{phone}, #{nickName})
+    </insert>
+
+    <update id="updateFsIntegralExchange" parameterType="FsIntegralExchange">
+        update fs_integral_exchange
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="integral != null">integral = #{integral},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="phone != null">phone = #{phone},</if>
+            <if test="nickName != null">nick_name = #{nickName},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFsIntegralExchangeById" parameterType="Long">
+        delete from fs_integral_exchange where id = #{id}
+    </delete>
+
+    <delete id="deleteFsIntegralExchangeByIds" parameterType="String">
+        delete from fs_integral_exchange where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 266 - 0
fs-service/src/main/resources/mapper/his/FsIntegralRedPacketLogMapper.xml

@@ -0,0 +1,266 @@
+<?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.his.mapper.FsIntegralRedPacketLogMapper">
+    
+    <resultMap type="FsIntegralRedPacketLog" id="FsIntegralRedPacketLogResult">
+        <result property="logId"    column="log_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="amount"    column="amount"    />
+        <result property="outBatchNo"    column="out_batch_no"    />
+        <result property="batchId"    column="batch_id"    />
+        <result property="status"    column="status"    />
+        <result property="appId"    column="app_id"    />
+        <result property="remark"    column="remark"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="packageInfo"    column="package_info"    />
+        <result property="mchId"    column="mch_id"    />
+        <result property="returnedStatus"    column="returned_status"    />
+    </resultMap>
+
+    <sql id="selectFsIntegralRedPacketLogVo">
+        select log_id, user_id, amount, out_batch_no, batch_id, status,app_id, remark, create_time, update_time,
+               package_info,mch_id,returned_status from fs_integral_red_packet_log
+    </sql>
+
+    <select id="selectFsIntegralRedPacketLogList" parameterType="FsIntegralRedPacketLog" resultMap="FsIntegralRedPacketLogResult">
+        <include refid="selectFsIntegralRedPacketLogVo"/>
+        <where>  
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="amount != null "> and amount = #{amount}</if>
+            <if test="outBatchNo != null  and outBatchNo != ''"> and out_batch_no = #{outBatchNo}</if>
+            <if test="batchId != null  and batchId != ''"> and batch_id = #{batchId}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="appId != null "> and app_id = #{appId}</if>
+            <if test="mchId != null "> and mch_id = #{mchId}</if>
+            <if test="returnedStatus != null"> and returned_status = #{returnedStatus}</if>
+            <if test="beginTime != null and beginTime != ''">
+                and create_time >= #{beginTime}
+            </if>
+            <if test="endTime != null and endTime != ''">
+                and create_time &lt;= #{endTime}
+            </if>
+        </where>
+        order by log_id desc
+    </select>
+    
+    <select id="selectFsIntegralRedPacketLogByLogId" parameterType="Long" resultMap="FsIntegralRedPacketLogResult">
+        <include refid="selectFsIntegralRedPacketLogVo"/>
+        where log_id = #{logId}
+    </select>
+    <select id="sumMoneyByUserId" resultType="java.math.BigDecimal">
+        SELECT SUM(amount) FROM fs_integral_red_packet_log WHERE user_id = #{userId} AND `status` = 1
+    </select>
+    <select id="selectFsIntegralRedPacketLogByBatchNo" resultType="com.fs.his.domain.FsIntegralRedPacketLog">
+        select * from fs_integral_red_packet_log where out_batch_no = #{outBatchNo}
+    </select>
+    <select id="getList" resultType="com.fs.his.vo.FsIntegralRedPacketLogVo">
+        select pl.log_id, pl.user_id, pl.amount, pl.out_batch_no, pl.batch_id, pl.status,pl.app_id, pl.remark, pl.create_time, pl.update_time,
+        pl.package_info,pl.mch_id,fu.nick_name,pl.returned_status
+        from fs_integral_red_packet_log pl left join fs_user fu on fu.user_id = pl.user_id
+        <where>
+            <if test="userId != null "> and pl.user_id = #{userId}</if>
+            <if test="amount != null "> and pl.amount = #{amount}</if>
+            <if test="outBatchNo != null  and outBatchNo != ''"> and pl.out_batch_no = #{outBatchNo}</if>
+            <if test="batchId != null  and batchId != ''"> and pl.batch_id = #{batchId}</if>
+            <if test="status != null "> and pl.status = #{status}</if>
+            <if test="appId != null "> and pl.app_id = #{appId}</if>
+            <if test="mchId != null "> and pl.mch_id = #{mchId}</if>
+            <if test="returnedStatus != null"> and returned_status = #{returnedStatus}</if>
+            <if test="nickName != null and nickName != ''">
+                and fu.nick_name like concat(#{nickName},"%")
+            </if>
+            <if test="maxAmount != null ">
+                and pl.amount &lt;= #{maxAmount}
+            </if>
+            <if test="minAmount != null ">
+                and pl.amount >= #{minAmount}
+            </if>
+            <if test="beginTime != null and beginTime != ''">
+                and pl.create_time >= #{beginTime}
+            </if>
+            <if test="endTime != null and endTime != ''">
+                and pl.create_time &lt;= #{endTime}
+            </if>
+        </where>
+        order by pl.log_id desc
+    </select>
+
+    <select id="getList_COUNT" resultType="long">
+        select count(*) from fs_integral_red_packet_log pl
+        <if test="nickName != null and nickName != ''">
+            left join fs_user fu on fu.user_id = pl.user_id
+        </if>
+        <where>
+            <if test="userId != null "> and pl.user_id = #{userId}</if>
+            <if test="amount != null "> and pl.amount = #{amount}</if>
+            <if test="outBatchNo != null  and outBatchNo != ''"> and pl.out_batch_no = #{outBatchNo}</if>
+            <if test="batchId != null  and batchId != ''"> and pl.batch_id = #{batchId}</if>
+            <if test="status != null "> and pl.status = #{status}</if>
+            <if test="appId != null "> and pl.app_id = #{appId}</if>
+            <if test="mchId != null "> and pl.mch_id = #{mchId}</if>
+            <if test="returnedStatus != null"> and returned_status = #{returnedStatus}</if>
+            <if test="nickName != null and nickName != ''">
+                and fu.nick_name like concat(#{nickName},"%")
+            </if>
+            <if test="maxAmount != null ">
+                and pl.amount &lt;= #{maxAmount}
+            </if>
+            <if test="minAmount != null ">
+                and pl.amount >= #{minAmount}
+            </if>
+            <if test="beginTime != null and beginTime != ''">
+                and pl.create_time >= #{beginTime}
+            </if>
+            <if test="endTime != null and endTime != ''">
+                and pl.create_time &lt;= #{endTime}
+            </if>
+        </where>
+    </select>
+    <select id="countLogsByToday" resultType="java.lang.Long">
+        SELECT count(*)
+        FROM fs_integral_red_packet_log
+        WHERE user_id = #{userId}
+        AND status IN (0, 1)
+        AND create_time >= CURDATE()
+        AND create_time &lt; DATE_ADD(CURDATE(), INTERVAL 1 DAY);
+    </select>
+    <select id="selectUsersWithWithdrawInPeriod" resultType="java.lang.Long">
+        SELECT DISTINCT user_id
+        FROM fs_integral_red_packet_log
+        WHERE `status` IN (0, 1)
+        AND create_time >= #{startDate}
+        AND create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+        GROUP BY user_id
+        HAVING sum(amount) >= #{limitAmount}
+    </select>
+    <select id="selectWithdrawDatesByUser" resultType="java.time.LocalDate">
+        SELECT DISTINCT DATE(create_time) as withdraw_date
+        FROM fs_integral_red_packet_log
+        WHERE user_id = #{userId}
+        AND status IN (0, 1)
+        AND create_time >= #{startDate}
+        AND create_time &lt; DATE_ADD(#{endDate}, INTERVAL 1 DAY)
+        ORDER BY withdraw_date
+    </select>
+    <select id="selectWithdrawStatsByUserAndPeriod" resultType="java.util.Map">
+            SELECT
+              COUNT(*) as count,
+              SUM(amount) as total_amount
+            FROM fs_integral_red_packet_log
+            WHERE user_id = #{userId}
+            AND `status` IN (0, 1)
+            AND DATE(create_time) BETWEEN #{startDate} AND #{endDate}
+    </select>
+    <select id="sumMoneyByStatus" resultType="java.math.BigDecimal">
+        SELECT
+              SUM(amount)
+        FROM fs_integral_red_packet_log
+        WHERE
+          status = #{status}
+          AND returned_status = 0
+    </select>
+
+    <insert id="insertFsIntegralRedPacketLog" parameterType="FsIntegralRedPacketLog" useGeneratedKeys="true" keyProperty="logId">
+        insert into fs_integral_red_packet_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="amount != null">amount,</if>
+            <if test="outBatchNo != null">out_batch_no,</if>
+            <if test="batchId != null">batch_id,</if>
+            <if test="status != null">status,</if>
+            <if test="appId != null">app_id,</if>
+            <if test="remark != null">remark,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="packageInfo != null">package_info,</if>
+            <if test="mchId != null">mch_id,</if>
+            <if test="returnedStatus != null">returned_status,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="amount != null">#{amount},</if>
+            <if test="outBatchNo != null">#{outBatchNo},</if>
+            <if test="batchId != null">#{batchId},</if>
+            <if test="status != null">#{status},</if>
+            <if test="appId != null">#{appId},</if>
+            <if test="remark != null">#{remark},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="packageInfo != null">#{packageInfo},</if>
+            <if test="mchId != null">#{mchId},</if>
+            <if test="returnedStatus != null">#{returnedStatus},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsIntegralRedPacketLog" parameterType="FsIntegralRedPacketLog">
+        update fs_integral_red_packet_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="amount != null">amount = #{amount},</if>
+            <if test="outBatchNo != null">out_batch_no = #{outBatchNo},</if>
+            <if test="batchId != null">batch_id = #{batchId},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="appId != null">app_id = #{appId},</if>
+            <if test="remark != null">remark = #{remark},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="packageInfo != null and packageInfo !=''">package_info = #{packageInfo},</if>
+            <if test="mchId != null and mchId != ''">mch_id = #{mchId},</if>
+            <if test="returnedStatus != null">returned_status = #{returnedStatus},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+    <update id="batchUpdate">
+        UPDATE fs_integral_red_packet_log
+        SET
+        status = CASE log_id
+        <foreach collection="list" item="item">
+            WHEN #{item.logId} THEN #{item.status}
+        </foreach>
+        ELSE status
+        END,
+        update_time = CASE log_id
+        <foreach collection="list" item="item">
+            WHEN #{item.logId} THEN #{item.updateTime}
+        </foreach>
+        ELSE update_time
+        END,
+        batch_id = CASE log_id
+        <foreach collection="list" item="item">
+            WHEN #{item.logId} THEN #{item.batchId}
+        </foreach>
+        ELSE batch_id
+        END,
+        remark = CASE log_id
+        <foreach collection="list" item="item">
+            WHEN #{item.logId} THEN #{item.remark}
+        </foreach>
+        ELSE remark
+        END,
+        returned_status = CASE log_id
+        <foreach collection="list" item="item">
+            WHEN #{item.logId} THEN #{item.returnedStatus}
+        </foreach>
+        ELSE returned_status
+        END
+        WHERE log_id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.logId}
+        </foreach>
+    </update>
+
+
+    <delete id="deleteFsIntegralRedPacketLogByLogId" parameterType="Long">
+        delete from fs_integral_red_packet_log where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteFsIntegralRedPacketLogByLogIds" parameterType="String">
+        delete from fs_integral_red_packet_log where log_id in 
+        <foreach item="logId" collection="array" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+</mapper>

+ 24 - 1
fs-service/src/main/resources/mapper/his/FsUserIntegralLogsMapper.xml

@@ -14,10 +14,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="createTime"    column="create_time"    />
         <result property="businessType"    column="business_type"    />
         <result property="status"    column="status"    />
+        <result property="nickName"    column="nick_name"    />
+        <result property="phone"    column="phone"    />
     </resultMap>
 
     <sql id="selectFsUserIntegralLogsVo">
-        select id, user_id,status, log_type,business_type, integral, balance, business_id, create_time from fs_user_integral_logs
+        select * from fs_user_integral_logs
     </sql>
 
     <select id="selectFsUserIntegralLogsList" parameterType="FsUserIntegralLogs" resultMap="FsUserIntegralLogsResult">
@@ -47,6 +49,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
     </select>
 
+    <select id="getExchangDetailList" resultType="com.fs.his.vo.ExchangeDetailVo">
+        SELECT
+            id,
+            user_id,
+            log_type,
+            integral,
+            balance,
+            create_time,
+            ROUND(ABS(integral / 1000), 3) AS commission
+        FROM
+            fs_user_integral_logs
+        WHERE
+            user_id = #{userId}
+          AND log_type = #{logType}
+        order by create_time desc
+    </select>
+
     <insert id="insertFsUserIntegralLogs" parameterType="FsUserIntegralLogs" useGeneratedKeys="true" keyProperty="id">
         insert into fs_user_integral_logs
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -58,6 +77,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">create_time,</if>
             <if test="businessType != null">business_type,</if>
             <if test="status != null">status,</if>
+            <if test="nickName != null">nick_name,</if>
+            <if test="phone != null">phone,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="userId != null">#{userId},</if>
@@ -68,6 +89,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="createTime != null">#{createTime},</if>
             <if test="businessType != null">#{businessType},</if>
             <if test="status != null">#{status},</if>
+            <if test="nickName != null">#{nickName},</if>
+            <if test="phone != null">#{phone},</if>
          </trim>
     </insert>
 

+ 38 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -50,10 +50,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="qwUserId"    column="qw_user_id"    />
         <result property="appId"    column="app_id"    />
         <result property="level" column="level"/>
+        <result property="withdrawIntegral"    column="withdraw_integral"    />
+        <result property="withdrawFinish"    column="withdraw_finish"    />
+        <result property="totalCommission"    column="total_commission"    />
+        <result property="mayWithdraw"    column="may_withdraw"    />
     </resultMap>
 
     <sql id="selectFsUserVo">
-        select user_id,qw_ext_id,sex,is_buy,`level`,course_ma_open_id,is_push,is_add_qw,source,login_device,is_individuation_push,store_open_id,password,jpush_id, is_vip,vip_start_date,vip_end_date,vip_level,vip_status,nick_name,integral_status, avatar, phone, integral,sign_num, status, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id, union_id, is_del, user_code, remark, create_time, update_time, last_ip, balance,is_weixin_auth,parent_id,qw_user_id,app_id,company_id,company_user_id,is_promoter,now_money,brokerage_price,spread_user_id, spread_time,pay_count, spread_count,user_type,invited_by_sales_id from fs_user
+        select * from fs_user
     </sql>
 
     <select id="selectFsUserList" parameterType="FsUser" resultMap="FsUserResult">
@@ -674,6 +678,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
          </trim>
     </insert>
 
+    <update id="addIntegral">
+        UPDATE fs_user
+        SET integral = integral + #{integral},
+            withdraw_integral = withdraw_integral + #{integral},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+    </update>
+
     <update id="updateFsUser" parameterType="FsUser">
         update fs_user
         <trim prefix="SET" suffixOverrides=",">
@@ -724,6 +736,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
             <if test="appId != null">app_id = #{appId},</if>
             <if test="invitedBySalesId != null">invited_by_sales_id = #{invitedBySalesId},</if>
+            <if test="withdrawIntegral != null">withdraw_integral = #{withdrawIntegral},</if>
+            <if test="withdrawFinish != null">withdraw_finish = #{withdrawFinish},</if>
+            <if test="totalCommission != null">total_commission = #{totalCommission},</if>
+            <if test="mayWithdraw != null">may_withdraw = #{mayWithdraw},</if>
         </trim>
         where user_id = #{userId}
     </update>
@@ -762,6 +778,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         update fs_user set password = #{password} where phone = #{encryptPhone}
     </update>
 
+    <update id="disabledUsers">
+        UPDATE fs_user
+        SET status = 0
+        <choose>
+            <when test="param.remark != null and param.remark != ''">
+                ,remark = #{param.remark}
+            </when>
+            <otherwise>
+                ,remark = '风控用户'
+            </otherwise>
+        </choose>
+        WHERE user_id IN
+        <foreach collection="param.userIds" item="userId" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+    </update>
+
     <select id="selectUserListByMap" resultType="com.fs.his.vo.OptionsVO">
         select
         u.user_id dictValue,
@@ -2548,4 +2581,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
     </select>
 
+    <select id="getWallet" resultType="com.fs.his.vo.IntegralExchangeVo">
+        SELECT user_id,integral, withdraw_integral, total_commission, withdraw_finish,may_withdraw FROM fs_user WHERE user_id = #{userId}
+    </select>
+
 </mapper>

+ 40 - 0
fs-user-app/src/main/java/com/fs/app/controller/CommonController.java

@@ -641,4 +641,44 @@ public class CommonController {
         return  R.ok().put("isSmsVerification",0);
 
     }
+
+	/**
+	 * 接收 UniApp 激励广告回调
+	 */
+	@GetMapping("/uniCallBack")
+	public R uniCallBack(@RequestParam Map<String,Object> params){
+		return userService.uniCallBack(params);
+	}
+
+	/**
+	 * uniapp广告 返回一个广告id
+	 */
+	@PostMapping("/createLogs")
+	public R createLogs(@RequestBody Map<String,Object> params){
+		return userService.createLogs(Long.valueOf(params.get("userId").toString()));
+	}
+
+	/**
+	 * 获取用户钱包明细
+	 */
+	@GetMapping("/getWallet/{userId}")
+	public R getWallet(@PathVariable("userId") Long userId){
+		return userService.getWallet(userId);
+	}
+
+	/**
+	 * 用户钱包 积分兑换佣金
+	 */
+	@GetMapping("/integralExchange/{userId}")
+	public R integralExchange(@PathVariable("userId") Long userId){
+		return userService.integralExchange(userId);
+	}
+
+	/**
+	 * 用户钱包 获取兑换明细
+	 */
+	@PostMapping("/exchangDetail")
+	public R exchangDetail(@RequestBody Map<String,Object> params){
+		return userService.exchangDetail(params);
+	}
 }

+ 77 - 0
fs-user-app/src/main/java/com/fs/app/controller/IntegralController.java

@@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSON;
 import com.fs.app.annotation.Login;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.his.domain.*;
@@ -27,6 +28,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.servlet.http.HttpServletRequest;
+import java.math.BigDecimal;
 import java.util.*;
 
 
@@ -50,6 +52,9 @@ public class IntegralController extends  AppBaseController {
     private IFsIntegralCartService cartService;
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private IFsIntegralRedPacketLogService fsIntegralRedPacketLogService;
+
     @ApiOperation("获取积分商品列表")
     @GetMapping("/getIntegralGoodsList")
     @Cacheable(value = "getIntegralGoodsList", key = "#param")
@@ -284,4 +289,76 @@ public class IntegralController extends  AppBaseController {
         return integralOrderService.createCartOrder(param);
     }
 
+    @Login
+    @ApiOperation("积分提现")
+    @PostMapping("/withdrawal")
+    @RepeatSubmit
+    public R withdrawal(@RequestBody FsIntegralWithdrawalParam param){
+        String userId = getUserId();
+        if(userId == null){
+            return R.error("请先登录!");
+        }
+        BigDecimal applicationAmount = param.getApplicationAmount();
+        if(applicationAmount == null){
+            return R.error("提现金额不正确");
+        }
+        // 校验1:金额必须大于0
+        if (applicationAmount.compareTo(BigDecimal.ZERO) <= 0) {
+            return R.error("提现金额必须大于0");
+        }
+
+        // 校验2:最小单位为分,即最多保留两位小数
+        if (applicationAmount.scale() > 2) {
+            return R.error("提现金额最小单位为分,请输入正确保留两位小数的金额");
+        }
+        param.setUserId(Long.parseLong(userId));
+        R res = userService.withdrawal(param);
+        if ("200".equals(res.get("code").toString())){
+            return R.ok(res);
+        } else {
+            return res;
+        }
+
+    }
+
+    /**
+     * 查询积分佣金红包记录列表
+     */
+    @Login
+    @GetMapping("/getRedPacketLogList")
+    public TableDataInfo list(FsIntegralRedPacketLog param)
+    {
+        String userId = getUserId();
+        if(userId == null){
+            return new TableDataInfo();
+        }
+        param.setUserId(Long.parseLong(userId));
+        startPage();
+        List<FsIntegralRedPacketLog> list = fsIntegralRedPacketLogService.selectFsIntegralRedPacketLogList(param);
+        return getDataTable(list);
+    }
+
+    /**
+     * 查询积分佣金红包记录列表
+     */
+    @Login
+    @GetMapping("/getRedPacketLogByCode")
+    public R getRedPacketLogByCode(@RequestParam("orderCode") String orderCode)
+    {
+        String userId = getUserId();
+        if(userId == null){
+            return R.error("请先登录");
+        }
+        if(StringUtils.isBlank(orderCode)){
+            return R.error("缺少参数");
+        }
+        FsIntegralRedPacketLog redPacketLog = fsIntegralRedPacketLogService.getRedPacketLogByCode(orderCode);
+        if (redPacketLog != null) {
+            if (!Objects.equals(redPacketLog.getUserId(), Long.valueOf(userId))) {
+                return R.ok();
+            }
+        }
+        return R.ok().put("data", redPacketLog);
+    }
+
 }

+ 7 - 0
fs-user-app/src/main/java/com/fs/app/controller/UserController.java

@@ -1,6 +1,7 @@
 package com.fs.app.controller;
 
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.lang.Validator;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.fs.app.annotation.Login;
@@ -52,6 +53,7 @@ import java.awt.*;
 import java.awt.image.BufferedImage;
 import java.io.*;
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -127,6 +129,11 @@ public class UserController extends  AppBaseController {
             if (user.getPhone()!=null&&user.getPhone().length()>11&&!user.getPhone().matches("\\d+")){
                 user.setPhone(decryptPhoneMk(user.getPhone()));
             }
+
+            if (BeanUtil.isNotEmpty(user.getMayWithdraw()) && user.getMayWithdraw().compareTo(BigDecimal.ZERO) > 0) {
+                user.setMayWithdraw(user.getMayWithdraw().divide(BigDecimal.valueOf(100),2, RoundingMode.DOWN));
+            }
+
             CompanyUser companyUser =new CompanyUser();
             if(user.getInvitedBySalesId()!=null){
                companyUser = companyUserService.getInviteCodeByCompanyUserIdAndUserId(user.getInvitedBySalesId());

+ 4 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxPayController.java

@@ -138,5 +138,9 @@ public class WxPayController {
         }
     }
 
+    @PostMapping( "/integralV3TransferNotify")
+    public String integralV3TransferNotify(@RequestBody String notifyData,HttpServletRequest request, HttpServletResponse response) throws Exception {
+        return paymentService.integralV3TransferNotify(notifyData,request);
+    }
 
 }