Procházet zdrojové kódy

分销功能代码(开发中)

zyy před 1 měsícem
rodič
revize
e988743668
41 změnil soubory, kde provedl 2281 přidání a 25 odebrání
  1. 136 0
      fs-admin/src/main/java/com/fs/distribution/controller/DistributionController.java
  2. 30 0
      fs-admin/src/main/java/com/fs/distribution/controller/DistributionWithdrawController.java
  3. 49 0
      fs-service/src/main/java/com/fs/distribution/constant/DistributionConstants.java
  4. 51 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionAccount.java
  5. 52 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionCommissionRecord.java
  6. 44 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionConfig.java
  7. 43 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionUserRelation.java
  8. 34 0
      fs-service/src/main/java/com/fs/distribution/domain/DistributionWithdrawRecord.java
  9. 25 0
      fs-service/src/main/java/com/fs/distribution/dto/BindRelationRequest.java
  10. 24 0
      fs-service/src/main/java/com/fs/distribution/dto/CreateCommissionRequest.java
  11. 25 0
      fs-service/src/main/java/com/fs/distribution/dto/QueryDistributionAccountRequest.java
  12. 18 0
      fs-service/src/main/java/com/fs/distribution/dto/QueryDistributionAccountResponse.java
  13. 16 0
      fs-service/src/main/java/com/fs/distribution/dto/WithdrawApplyRequest.java
  14. 19 0
      fs-service/src/main/java/com/fs/distribution/dto/WithdrawAuditRequest.java
  15. 61 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionAccountMapper.java
  16. 23 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionCommissionRecordMapper.java
  17. 10 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionConfigMapper.java
  18. 23 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionUserRelationMapper.java
  19. 24 0
      fs-service/src/main/java/com/fs/distribution/mapper/DistributionWithdrawRecordMapper.java
  20. 53 0
      fs-service/src/main/java/com/fs/distribution/service/DistributionService.java
  21. 12 0
      fs-service/src/main/java/com/fs/distribution/service/IDistributionAccountService.java
  22. 16 0
      fs-service/src/main/java/com/fs/distribution/service/IDistributionCommissionRecordService.java
  23. 4 0
      fs-service/src/main/java/com/fs/distribution/service/IDistributionConfigService.java
  24. 4 0
      fs-service/src/main/java/com/fs/distribution/service/IDistributionUserRelationService.java
  25. 9 0
      fs-service/src/main/java/com/fs/distribution/service/IDistributionWithdrawRecordService.java
  26. 148 0
      fs-service/src/main/java/com/fs/distribution/service/impl/DistributionAccountServiceImpl.java
  27. 231 0
      fs-service/src/main/java/com/fs/distribution/service/impl/DistributionCommissionRecordServiceImpl.java
  28. 11 0
      fs-service/src/main/java/com/fs/distribution/service/impl/DistributionConfigServiceImpl.java
  29. 579 0
      fs-service/src/main/java/com/fs/distribution/service/impl/DistributionServiceImpl.java
  30. 11 0
      fs-service/src/main/java/com/fs/distribution/service/impl/DistributionUserRelationServiceImpl.java
  31. 21 0
      fs-service/src/main/java/com/fs/distribution/service/impl/DistributionWithdrawRecordServiceImpl.java
  32. 6 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsStoreCouponScrm.java
  33. 2 0
      fs-service/src/main/java/com/fs/hisStore/param/LoginMpWxParam.java
  34. 1 1
      fs-service/src/main/java/com/fs/hisStore/service/IFsStoreCouponIssueScrmService.java
  35. 138 0
      fs-service/src/main/resources/mapper/distribution/DistributionAccountMapper.xml
  36. 85 0
      fs-service/src/main/resources/mapper/distribution/DistributionCommissionRecordMapper.xml
  37. 38 0
      fs-service/src/main/resources/mapper/distribution/DistributionConfigMapper.xml
  38. 87 0
      fs-service/src/main/resources/mapper/distribution/DistributionUserRelationMapper.xml
  39. 96 0
      fs-service/src/main/resources/mapper/distribution/DistributionWithdrawRecordMapper.xml
  40. 6 23
      fs-user-app/src/main/java/com/fs/app/controller/store/FsStoreCouponScrmController.java
  41. 16 1
      fs-user-app/src/main/java/com/fs/app/controller/store/WxUserScrmController.java

+ 136 - 0
fs-admin/src/main/java/com/fs/distribution/controller/DistributionController.java

@@ -0,0 +1,136 @@
+package com.fs.distribution.controller;
+
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.distribution.domain.DistributionConfig;
+import com.fs.distribution.dto.*;
+import com.fs.distribution.service.DistributionService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+/**
+ * 分销控制器
+ */
+@RestController
+@RequestMapping("/distribution")
+public class DistributionController {
+
+    @Resource
+    private DistributionService distributionService;
+
+    /**
+     * 绑定分销关系
+     *
+     * 一般不建议前端单独调用这个接口,
+     * 最好是在注册成功后,后端注册逻辑内部调用 distributionService.bindRelation()
+     */
+    @PostMapping("/bind")
+    public AjaxResult bind(@RequestBody BindRelationRequest request) {
+        distributionService.bindRelation(request);
+        return AjaxResult.success();
+    }
+
+
+    /**
+     * 查询分销账户列表
+     */
+    @PostMapping("/account/list")
+    public AjaxResult accountList(@RequestBody QueryDistributionAccountRequest queryDistributionAccountRequest) {
+        return AjaxResult.success(distributionService.getAccountList(queryDistributionAccountRequest));
+
+    }
+
+    /**
+     * 我的分销账户
+     */
+    @GetMapping("/account/{userId}")
+    public AjaxResult account(@PathVariable Long userId) {
+        return AjaxResult.success(distributionService.getAccount(userId));
+    }
+
+    /**
+     * 我的分销关系
+     */
+    @GetMapping("/relation/{userId}")
+    public AjaxResult relation(@PathVariable Long userId) {
+        return AjaxResult.success(distributionService.getRelation(userId));
+    }
+
+    /**
+     * 我的佣金明细
+     */
+    @GetMapping("/commission/list/{userId}")
+    public AjaxResult commissionList(@PathVariable Long userId) {
+        return AjaxResult.success(distributionService.getCommissionList(userId));
+    }
+
+    /**
+     * 申请提现
+     */
+    @PostMapping("/withdraw/apply")
+    public AjaxResult applyWithdraw(@RequestBody WithdrawApplyRequest request) {
+        distributionService.applyWithdraw(request);
+        return AjaxResult.success("提现申请成功");
+    }
+
+    /**
+     * 我的提现记录
+     */
+    @GetMapping("/withdraw/list/{userId}")
+    public AjaxResult withdrawList(@PathVariable Long userId) {
+        return AjaxResult.success(distributionService.getWithdrawList(userId));
+    }
+
+
+
+    /**
+     * 后台审核提现
+     */
+    @PostMapping("/withdraw/audit")
+    public AjaxResult auditWithdraw(@RequestBody WithdrawAuditRequest request) {
+        distributionService.auditWithdraw(request);
+        return AjaxResult.success("审核成功");
+    }
+
+    /**
+     * 订单支付成功后生成佣金
+     *
+     * 这个接口一般不暴露给前端,
+     * 应该在订单支付回调成功后由订单服务内部调用。
+     */
+    @PostMapping("/commission/createAfterPay")
+    public AjaxResult createCommissionAfterPay(@RequestBody CreateCommissionRequest request) {
+        distributionService.createCommissionAfterPay(request);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 退款后佣金失效
+     *
+     * 这个接口一般不暴露给前端,
+     * 应该在退款成功后由订单服务内部调用。
+     */
+    @PostMapping("/commission/invalid/{orderId}")
+    public AjaxResult invalidCommission(@PathVariable Long orderId) {
+        distributionService.invalidCommissionByRefund(orderId);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 查询分销配置
+     */
+    @GetMapping("/config")
+    public AjaxResult getConfig() {
+        return AjaxResult.success(distributionService.getConfig());
+    }
+
+    /**
+     * 保存分销配置
+     */
+    @PostMapping("/config")
+    public AjaxResult saveConfig(@RequestBody DistributionConfig config) {
+        distributionService.saveConfig(config);
+        return AjaxResult.success("保存成功");
+    }
+
+}

+ 30 - 0
fs-admin/src/main/java/com/fs/distribution/controller/DistributionWithdrawController.java

@@ -0,0 +1,30 @@
+package com.fs.distribution.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.distribution.domain.DistributionWithdrawRecord;
+import com.fs.distribution.service.IDistributionWithdrawRecordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/withdraw")
+public class DistributionWithdrawController extends BaseController {
+
+    @Autowired
+    private IDistributionWithdrawRecordService distributionWithdrawService;
+
+    /**
+     * 查询待审核提现记录
+     */
+    @GetMapping("/audit/list")
+    public TableDataInfo auditList(DistributionWithdrawRecord query) {
+        startPage();
+        List<DistributionWithdrawRecord> list = distributionWithdrawService.selectWithdrawList(query);
+        return getDataTable(list);
+    }
+}

+ 49 - 0
fs-service/src/main/java/com/fs/distribution/constant/DistributionConstants.java

@@ -0,0 +1,49 @@
+package com.fs.distribution.constant;
+
+/**
+ * 分销相关常量
+ */
+public class DistributionConstants {
+
+    private DistributionConstants() {}
+
+    /**
+     * 分销关系状态
+     */
+    public static final Integer RELATION_NORMAL = 1;
+    public static final Integer RELATION_DISABLED = 0;
+
+    /**
+     * 分销账户状态
+     */
+    public static final Integer ACCOUNT_NORMAL = 1;
+    public static final Integer ACCOUNT_DISABLED = 0;
+
+    /**
+     * 佣金状态:0冻结中,1可提现,2已提现,3已失效
+     */
+    public static final Integer COMMISSION_FROZEN = 0;
+    public static final Integer COMMISSION_AVAILABLE = 1;
+    public static final Integer COMMISSION_WITHDRAWN = 2;
+    public static final Integer COMMISSION_INVALID = 3;
+
+    /**
+     * 提现状态:0待审核,1已打款,2已拒绝
+     */
+    public static final Integer WITHDRAW_WAIT_AUDIT = 0;
+    public static final Integer WITHDRAW_PAID = 1;
+    public static final Integer WITHDRAW_REJECT = 2;
+
+    /**
+     * 佣金层级
+     */
+    public static final Integer COMMISSION_LEVEL_ONE = 1;
+    public static final Integer COMMISSION_LEVEL_TWO = 2;
+
+    /**
+     * 绑定方式
+     */
+    public static final Integer BIND_TYPE_REGISTER = 1;
+    public static final Integer BIND_TYPE_FIRST_ORDER = 2;
+    public static final Integer BIND_TYPE_ADMIN = 3;
+}

+ 51 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionAccount.java

@@ -0,0 +1,51 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 分销账户
+ */
+@Data
+public class DistributionAccount {
+
+    private Long id;
+
+    private Long userId;
+
+    /**
+     * 累计佣金
+     */
+    private BigDecimal totalCommission;
+
+    /**
+     * 冻结佣金
+     */
+    private BigDecimal frozenCommission;
+
+    /**
+     * 可提现佣金
+     */
+    private BigDecimal availableCommission;
+
+    /**
+     * 提现中佣金
+     */
+    private BigDecimal withdrawingCommission;
+
+    /**
+     * 已提现佣金
+     */
+    private BigDecimal withdrawnCommission;
+
+    /**
+     * 状态:1正常,0禁用
+     */
+    private Integer status;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 52 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionCommissionRecord.java

@@ -0,0 +1,52 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 分销佣金记录
+ */
+@Data
+public class DistributionCommissionRecord {
+
+    private Long id;
+
+    private Long orderId;
+
+    private String orderNo;
+
+    private Long buyerUserId;
+
+    private Long distributorUserId;
+
+    /**
+     * 佣金层级:1一级,2二级
+     */
+    private Integer commissionLevel;
+
+    /**
+     * 订单分佣金额
+     */
+    private BigDecimal orderAmount;
+
+    /**
+     * 佣金比例,例如 10 表示 10%
+     */
+    private BigDecimal commissionRate;
+
+    /**
+     * 佣金金额
+     */
+    private BigDecimal commissionAmount;
+
+    /**
+     * 状态:0冻结中,1可提现,2已提现,3已失效
+     */
+    private Integer status;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 44 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionConfig.java

@@ -0,0 +1,44 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 分销配置
+ */
+@Data
+public class DistributionConfig {
+
+    private Long id;
+
+    /**
+     * 分销开关:1开启,0关闭
+     */
+    private Integer enableStatus;
+
+    /**
+     * 一级佣金比例
+     */
+    private BigDecimal levelOneRate;
+
+    /**
+     * 二级佣金比例
+     */
+    private BigDecimal levelTwoRate;
+
+    /**
+     * 冻结天数
+     */
+    private Integer freezeDays;
+
+    /**
+     * 最低提现金额
+     */
+    private BigDecimal minWithdrawAmount;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 43 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionUserRelation.java

@@ -0,0 +1,43 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 用户分销关系
+ */
+@Data
+public class DistributionUserRelation {
+
+    private Long id;
+
+    /**
+     * 当前用户ID
+     */
+    private Long userId;
+
+    /**
+     * 一级上级用户ID
+     */
+    private Long parentUserId;
+
+    /**
+     * 二级上级用户ID
+     */
+    private Long grandParentUserId;
+
+    /**
+     * 绑定方式:1注册绑定,2首次下单绑定,3后台绑定
+     */
+    private Integer bindType;
+
+    /**
+     * 状态:1正常,0无效
+     */
+    private Integer status;
+
+    private Date createTime;
+
+    private Date updateTime;
+}

+ 34 - 0
fs-service/src/main/java/com/fs/distribution/domain/DistributionWithdrawRecord.java

@@ -0,0 +1,34 @@
+package com.fs.distribution.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 分销提现记录
+ */
+@Data
+public class DistributionWithdrawRecord {
+
+    private Long id;
+
+    private Long userId;
+
+    private String withdrawNo;
+
+    private BigDecimal withdrawAmount;
+
+    /**
+     * 状态:0待审核,1已打款,2已拒绝
+     */
+    private Integer status;
+
+    private String remark;
+
+    private Date createTime;
+
+    private Date auditTime;
+
+    private Date updateTime;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/distribution/dto/BindRelationRequest.java

@@ -0,0 +1,25 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+/**
+ * 绑定分销关系请求
+ */
+@Data
+public class BindRelationRequest {
+
+    /**
+     * 当前注册用户ID
+     */
+    private Long userId;
+
+    /**
+     * 邀请人ID
+     */
+    private Long inviteUserId;
+
+    /**
+     * 绑定方式:1注册绑定,2首次下单绑定,3后台绑定
+     */
+    private Integer bindType;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/distribution/dto/CreateCommissionRequest.java

@@ -0,0 +1,24 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 创建订单佣金请求
+ */
+@Data
+public class CreateCommissionRequest {
+
+    private Long orderId;
+
+    private String orderNo;
+
+    private Long buyerUserId;
+
+    /**
+     * 订单实际参与分佣金额
+     * 商品实付金额,不含运费
+     */
+    private BigDecimal commissionBaseAmount;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/distribution/dto/QueryDistributionAccountRequest.java

@@ -0,0 +1,25 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+/**
+ * 绑定分销关系请求
+ */
+@Data
+public class QueryDistributionAccountRequest {
+
+    /**
+     * 用户ID
+     */
+    private Long userId;
+
+    /**
+     * 邀请人ID
+     */
+    private Long inviteUserId;
+
+    /**
+     * 账户状态
+     */
+    private Integer status;
+}

+ 18 - 0
fs-service/src/main/java/com/fs/distribution/dto/QueryDistributionAccountResponse.java

@@ -0,0 +1,18 @@
+package com.fs.distribution.dto;
+
+import com.fs.distribution.domain.DistributionAccount;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 绑定分销关系请求
+ */
+@Data
+public class QueryDistributionAccountResponse {
+
+    /**
+     * 用户ID
+     */
+    private List<DistributionAccount> list;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/distribution/dto/WithdrawApplyRequest.java

@@ -0,0 +1,16 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 用户申请提现
+ */
+@Data
+public class WithdrawApplyRequest {
+
+    private Long userId;
+
+    private BigDecimal withdrawAmount;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/distribution/dto/WithdrawAuditRequest.java

@@ -0,0 +1,19 @@
+package com.fs.distribution.dto;
+
+import lombok.Data;
+
+/**
+ * 提现审核
+ */
+@Data
+public class WithdrawAuditRequest {
+
+    private Long withdrawId;
+
+    /**
+     * 1通过,2拒绝
+     */
+    private Integer status;
+
+    private String remark;
+}

+ 61 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionAccountMapper.java

@@ -0,0 +1,61 @@
+package com.fs.distribution.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.distribution.domain.DistributionAccount;
+import org.apache.ibatis.annotations.Param;
+
+import java.math.BigDecimal;
+
+public interface DistributionAccountMapper extends BaseMapper<DistributionAccount> {
+
+    DistributionAccount selectByUserId(@Param("userId") Long userId);
+
+    /**
+     * 加锁查询账户,提现、金额变动时使用
+     */
+    DistributionAccount selectByUserIdForUpdate(@Param("userId") Long userId);
+
+    int insertAccount(DistributionAccount account);
+
+    /**
+     * 增加冻结佣金和累计佣金
+     */
+    int addFrozenCommission(@Param("userId") Long userId,
+                            @Param("amount") BigDecimal amount);
+
+    /**
+     * 冻结转可提现
+     */
+    int frozenToAvailable(@Param("userId") Long userId,
+                          @Param("amount") BigDecimal amount);
+
+    /**
+     * 扣减冻结佣金和累计佣金
+     */
+    int deductFrozenAndTotal(@Param("userId") Long userId,
+                             @Param("amount") BigDecimal amount);
+
+    /**
+     * 扣减可提现和累计佣金
+     */
+    int deductAvailableAndTotal(@Param("userId") Long userId,
+                                @Param("amount") BigDecimal amount);
+
+    /**
+     * 申请提现:可提现转提现中
+     */
+    int availableToWithdrawing(@Param("userId") Long userId,
+                               @Param("amount") BigDecimal amount);
+
+    /**
+     * 提现审核通过:提现中转已提现
+     */
+    int withdrawingToWithdrawn(@Param("userId") Long userId,
+                               @Param("amount") BigDecimal amount);
+
+    /**
+     * 提现审核拒绝:提现中退回可提现
+     */
+    int withdrawingBackToAvailable(@Param("userId") Long userId,
+                                   @Param("amount") BigDecimal amount);
+}

+ 23 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionCommissionRecordMapper.java

@@ -0,0 +1,23 @@
+package com.fs.distribution.mapper;
+
+import com.fs.distribution.domain.DistributionCommissionRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+public interface DistributionCommissionRecordMapper {
+
+    int insertRecord(DistributionCommissionRecord record);
+
+    int countByOrderId(@Param("orderId") Long orderId);
+
+    List<DistributionCommissionRecord> selectByOrderId(@Param("orderId") Long orderId);
+
+    List<DistributionCommissionRecord> selectFrozenRecordsBefore(@Param("beforeTime") Date beforeTime);
+
+    int updateStatus(@Param("id") Long id,
+                     @Param("status") Integer status);
+
+    List<DistributionCommissionRecord> selectByUserId(@Param("userId") Long userId);
+}

+ 10 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionConfigMapper.java

@@ -0,0 +1,10 @@
+package com.fs.distribution.mapper;
+
+import com.fs.distribution.domain.DistributionConfig;
+
+public interface DistributionConfigMapper {
+
+    DistributionConfig selectConfig();
+
+    int updateConfig(DistributionConfig config);
+}

+ 23 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionUserRelationMapper.java

@@ -0,0 +1,23 @@
+package com.fs.distribution.mapper;
+
+import com.fs.distribution.domain.DistributionUserRelation;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface DistributionUserRelationMapper {
+
+    DistributionUserRelation selectByUserId(@Param("userId") Long userId);
+
+    int insertRelation(DistributionUserRelation relation);
+
+    int updateRelation(DistributionUserRelation relation);
+
+    int countFirstLevelTeam(@Param("userId") Long userId);
+
+    int countSecondLevelTeam(@Param("userId") Long userId);
+
+    List<DistributionUserRelation> selectFirstLevelTeam(@Param("userId") Long userId);
+
+    List<DistributionUserRelation> selectSecondLevelTeam(@Param("userId") Long userId);
+}

+ 24 - 0
fs-service/src/main/java/com/fs/distribution/mapper/DistributionWithdrawRecordMapper.java

@@ -0,0 +1,24 @@
+package com.fs.distribution.mapper;
+
+import com.fs.distribution.domain.DistributionWithdrawRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface DistributionWithdrawRecordMapper {
+
+    int insertWithdrawRecord(DistributionWithdrawRecord record);
+
+    DistributionWithdrawRecord selectById(@Param("id") Long id);
+
+    DistributionWithdrawRecord selectByIdForUpdate(@Param("id") Long id);
+
+    int updateAuditStatus(DistributionWithdrawRecord record);
+
+    List<DistributionWithdrawRecord> selectByUserId(@Param("userId") Long userId);
+
+    /**
+     * 查询待审核的提现记录
+      */
+    List<DistributionWithdrawRecord> selectWithdrawList(DistributionWithdrawRecord query);
+}

+ 53 - 0
fs-service/src/main/java/com/fs/distribution/service/DistributionService.java

@@ -0,0 +1,53 @@
+package com.fs.distribution.service;
+
+import com.fs.distribution.domain.*;
+import com.fs.distribution.dto.*;
+
+import java.util.List;
+
+public interface DistributionService {
+
+    /**
+     * 注册成功后绑定邀请关系
+     */
+    void bindRelation(BindRelationRequest request);
+
+    /**
+     * 订单支付成功后生成佣金
+     */
+    void createCommissionAfterPay(CreateCommissionRequest request);
+
+    /**
+     * 订单完成并过售后期后,解冻佣金
+     */
+    void unlockFrozenCommission();
+
+    /**
+     * 订单退款时,让佣金失效
+     */
+    void invalidCommissionByRefund(Long orderId);
+
+    /**
+     * 用户申请提现
+     */
+    void applyWithdraw(WithdrawApplyRequest request);
+
+    /**
+     * 后台审核提现
+     */
+    void auditWithdraw(WithdrawAuditRequest request);
+
+    DistributionAccount getAccount(Long userId);
+
+    DistributionUserRelation getRelation(Long userId);
+
+    List<DistributionCommissionRecord> getCommissionList(Long userId);
+
+    List<DistributionWithdrawRecord> getWithdrawList(Long userId);
+
+    DistributionConfig getConfig();
+
+    void saveConfig(DistributionConfig config);
+
+    QueryDistributionAccountResponse getAccountList(QueryDistributionAccountRequest queryDistributionAccountRequest);
+}

+ 12 - 0
fs-service/src/main/java/com/fs/distribution/service/IDistributionAccountService.java

@@ -0,0 +1,12 @@
+package com.fs.distribution.service;
+
+
+import com.fs.distribution.dto.BindRelationRequest;
+
+public interface IDistributionAccountService {
+
+    /**
+     * 注册成功后绑定邀请关系
+     */
+    void bindRelation(BindRelationRequest request);
+}

+ 16 - 0
fs-service/src/main/java/com/fs/distribution/service/IDistributionCommissionRecordService.java

@@ -0,0 +1,16 @@
+package com.fs.distribution.service;
+
+import com.fs.distribution.dto.CreateCommissionRequest;
+
+public interface IDistributionCommissionRecordService {
+
+    /**
+     * 订单支付成功后生成佣金
+     */
+    void createCommissionAfterPay(CreateCommissionRequest request);
+
+    /**
+     * 订单完成并过售后期后,解冻佣金
+     */
+    void unlockFrozenCommission();
+}

+ 4 - 0
fs-service/src/main/java/com/fs/distribution/service/IDistributionConfigService.java

@@ -0,0 +1,4 @@
+package com.fs.distribution.service;
+
+public interface IDistributionConfigService {
+}

+ 4 - 0
fs-service/src/main/java/com/fs/distribution/service/IDistributionUserRelationService.java

@@ -0,0 +1,4 @@
+package com.fs.distribution.service;
+
+public interface IDistributionUserRelationService {
+}

+ 9 - 0
fs-service/src/main/java/com/fs/distribution/service/IDistributionWithdrawRecordService.java

@@ -0,0 +1,9 @@
+package com.fs.distribution.service;
+
+import com.fs.distribution.domain.DistributionWithdrawRecord;
+
+import java.util.List;
+
+public interface IDistributionWithdrawRecordService {
+    List<DistributionWithdrawRecord> selectWithdrawList(DistributionWithdrawRecord query);
+}

+ 148 - 0
fs-service/src/main/java/com/fs/distribution/service/impl/DistributionAccountServiceImpl.java

@@ -0,0 +1,148 @@
+
+package com.fs.distribution.service.impl;
+
+
+import com.fs.distribution.constant.DistributionConstants;
+import com.fs.distribution.domain.DistributionAccount;
+import com.fs.distribution.domain.DistributionUserRelation;
+import com.fs.distribution.dto.BindRelationRequest;
+import com.fs.distribution.mapper.DistributionAccountMapper;
+import com.fs.distribution.mapper.DistributionUserRelationMapper;
+import com.fs.distribution.service.IDistributionAccountService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+
+
+@Service
+public class DistributionAccountServiceImpl implements IDistributionAccountService {
+
+
+    @Resource
+    private DistributionUserRelationMapper relationMapper;
+
+    @Resource
+    private DistributionAccountMapper accountMapper;
+
+    /**
+     * 注册成功后绑定分销关系
+     *
+     * 核心规则:
+     * 1. 只能绑定一次
+     * 2. 不能绑定自己
+     * 3. 邀请人必须存在
+     * 4. 当前用户绑定 parentUserId
+     * 5. 当前用户的 grandParentUserId = 邀请人的 parentUserId
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void bindRelation(BindRelationRequest request) {
+        if (request == null || request.getUserId() == null) {
+            throw new RuntimeException("用户ID不能为空");
+        }
+
+        Long userId = request.getUserId();
+        Long inviteUserId = request.getInviteUserId();
+
+        // 先保证当前用户有分销账户
+        createAccountIfAbsent(userId);
+
+        // 如果没有邀请人,也创建一条空关系,方便后续查询
+        if (inviteUserId == null) {
+//            createEmptyRelationIfAbsent(userId);
+            return;
+        }
+
+        // 不能绑定自己
+        if (userId.equals(inviteUserId)) {
+            throw new RuntimeException("不能绑定自己为邀请人");
+        }
+
+        // 当前用户是否已经有关系
+        DistributionUserRelation currentRelation = relationMapper.selectByUserId(userId);
+        if (currentRelation != null && currentRelation.getParentUserId() != null && currentRelation.getParentUserId() > 0) {
+            // 已绑定过上级,不允许重复绑定
+            return;
+        }
+
+        // 查询邀请人的关系
+        DistributionUserRelation inviterRelation = relationMapper.selectByUserId(inviteUserId);
+
+        // 邀请人必须有账户,没有则创建
+        createAccountIfAbsent(inviteUserId);
+
+        Long grandParentUserId = null;
+
+        if (inviterRelation != null && inviterRelation.getParentUserId() != null) {
+            grandParentUserId = inviterRelation.getParentUserId();
+        }
+
+        DistributionUserRelation relation = new DistributionUserRelation();
+        relation.setUserId(userId);
+        relation.setParentUserId(inviteUserId);
+        relation.setGrandParentUserId(grandParentUserId);
+        relation.setBindType(request.getBindType() == null
+                ? DistributionConstants.BIND_TYPE_REGISTER
+                : request.getBindType());
+        relation.setStatus(DistributionConstants.RELATION_NORMAL);
+
+        if (currentRelation == null) {
+            relationMapper.insertRelation(relation);
+        } else {
+            relationMapper.updateRelation(relation);
+        }
+    }
+
+    /**
+     * 如果账户不存在,则创建账户
+     */
+    private void createAccountIfAbsent(Long userId) {
+        DistributionAccount exist = accountMapper.selectByUserId(userId);
+        if (exist != null) {
+            return;
+        }
+
+        DistributionAccount account = new DistributionAccount();
+        account.setUserId(userId);
+        account.setStatus(DistributionConstants.ACCOUNT_NORMAL);
+
+        try {
+            accountMapper.insertAccount(account);
+        } catch (Exception e) {
+            // 并发场景下,可能两个请求同时创建,唯一索引会挡住
+            // 这里再次查询,存在就忽略,不存在再抛异常
+            DistributionAccount after = accountMapper.selectByUserId(userId);
+            if (after == null) {
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * 没有邀请人的用户,也创建一条空关系
+     */
+    private void createEmptyRelationIfAbsent(Long userId) {
+        DistributionUserRelation exist = relationMapper.selectByUserId(userId);
+        if (exist != null) {
+            return;
+        }
+
+        DistributionUserRelation relation = new DistributionUserRelation();
+        relation.setUserId(userId);
+        relation.setParentUserId(0L);
+        relation.setGrandParentUserId(0L);
+        relation.setBindType(DistributionConstants.BIND_TYPE_REGISTER);
+        relation.setStatus(DistributionConstants.RELATION_NORMAL);
+
+        try {
+            relationMapper.insertRelation(relation);
+        } catch (Exception e) {
+            DistributionUserRelation after = relationMapper.selectByUserId(userId);
+            if (after == null) {
+                throw e;
+            }
+        }
+    }
+
+}

+ 231 - 0
fs-service/src/main/java/com/fs/distribution/service/impl/DistributionCommissionRecordServiceImpl.java

@@ -0,0 +1,231 @@
+package com.fs.distribution.service.impl;
+
+
+import com.fs.distribution.constant.DistributionConstants;
+import com.fs.distribution.domain.DistributionAccount;
+import com.fs.distribution.domain.DistributionCommissionRecord;
+import com.fs.distribution.domain.DistributionConfig;
+import com.fs.distribution.domain.DistributionUserRelation;
+import com.fs.distribution.dto.CreateCommissionRequest;
+import com.fs.distribution.mapper.DistributionAccountMapper;
+import com.fs.distribution.mapper.DistributionCommissionRecordMapper;
+import com.fs.distribution.mapper.DistributionConfigMapper;
+import com.fs.distribution.mapper.DistributionUserRelationMapper;
+import com.fs.distribution.service.IDistributionCommissionRecordService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+
+@Service
+public class DistributionCommissionRecordServiceImpl implements IDistributionCommissionRecordService {
+
+
+    @Resource
+    private DistributionUserRelationMapper relationMapper;
+    @Resource
+    private DistributionCommissionRecordMapper commissionRecordMapper;
+    @Resource
+    private DistributionConfigMapper configMapper;
+    @Resource
+    private DistributionAccountMapper accountMapper;
+
+    /**
+     * 订单支付成功后生成佣金
+     *
+     * 注意:
+     * 1. 必须幂等,同一个订单不能重复生成佣金
+     * 2. 佣金先进入冻结状态
+     * 3. 增加分销员 frozenCommission 和 totalCommission
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void createCommissionAfterPay(CreateCommissionRequest request) {
+        if (request == null) {
+            throw new RuntimeException("分佣参数不能为空");
+        }
+        if (request.getOrderId() == null || request.getBuyerUserId() == null) {
+            throw new RuntimeException("订单ID或购买人ID不能为空");
+        }
+        if (request.getCommissionBaseAmount() == null || request.getCommissionBaseAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+
+        DistributionConfig config = configMapper.selectConfig();
+        if (config == null || config.getEnableStatus() == null || config.getEnableStatus() != 1) {
+            return;
+        }
+
+        // 幂等判断:同一个订单如果已经生成过佣金,则不再重复生成
+        int existCount = commissionRecordMapper.countByOrderId(request.getOrderId());
+        if (existCount > 0) {
+            return;
+        }
+
+        DistributionUserRelation buyerRelation = relationMapper.selectByUserId(request.getBuyerUserId());
+        if (buyerRelation == null || buyerRelation.getStatus() == null || buyerRelation.getStatus() != 1) {
+            return;
+        }
+
+        BigDecimal orderAmount = request.getCommissionBaseAmount();
+
+        // 一级分佣
+        if (buyerRelation.getParentUserId() != null && buyerRelation.getParentUserId() > 0) {
+            createSingleCommission(
+                    request,
+                    buyerRelation.getParentUserId(),
+                    DistributionConstants.COMMISSION_LEVEL_ONE,
+                    config.getLevelOneRate(),
+                    orderAmount
+            );
+        }
+
+        // 二级分佣
+        if (buyerRelation.getGrandParentUserId() != null && buyerRelation.getGrandParentUserId() > 0) {
+            createSingleCommission(
+                    request,
+                    buyerRelation.getGrandParentUserId(),
+                    DistributionConstants.COMMISSION_LEVEL_TWO,
+                    config.getLevelTwoRate(),
+                    orderAmount
+            );
+        }
+    }
+
+
+    /**
+     * 解冻佣金
+     *
+     * 一般用定时任务每天执行:
+     * 订单完成 + 超过售后期后,把冻结佣金转为可提现。
+     *
+     * 这里的简化版是:
+     * 按佣金记录 create_time + freezeDays 判断。
+     *
+     * 更严谨做法:
+     * 结合订单状态,只解冻已完成、未退款的订单佣金。
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void unlockFrozenCommission() {
+        DistributionConfig config = configMapper.selectConfig();
+        if (config == null || config.getEnableStatus() == null || config.getEnableStatus() != 1) {
+            return;
+        }
+
+        int freezeDays = config.getFreezeDays() == null ? 7 : config.getFreezeDays();
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.DAY_OF_MONTH, -freezeDays);
+        Date beforeTime = calendar.getTime();
+
+        List<DistributionCommissionRecord> records = commissionRecordMapper.selectFrozenRecordsBefore(beforeTime);
+        if (records == null || records.isEmpty()) {
+            return;
+        }
+
+        for (DistributionCommissionRecord record : records) {
+            BigDecimal amount = record.getCommissionAmount();
+
+            int rows = accountMapper.frozenToAvailable(record.getDistributorUserId(), amount);
+            if (rows <= 0) {
+                throw new RuntimeException("佣金解冻失败,recordId=" + record.getId());
+            }
+
+            commissionRecordMapper.updateStatus(record.getId(), DistributionConstants.COMMISSION_AVAILABLE);
+        }
+    }
+
+
+
+    /**
+     * 创建单条佣金记录
+     */
+    private void createSingleCommission(CreateCommissionRequest request,
+                                        Long distributorUserId,
+                                        Integer commissionLevel,
+                                        BigDecimal rate,
+                                        BigDecimal orderAmount) {
+        if (distributorUserId == null || distributorUserId <= 0) {
+            return;
+        }
+        if (rate == null || rate.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+
+        // 防止自己给自己分佣
+        if (distributorUserId.equals(request.getBuyerUserId())) {
+            return;
+        }
+
+        // 确保分销账户存在
+        createAccountIfAbsent(distributorUserId);
+
+        DistributionAccount account = accountMapper.selectByUserId(distributorUserId);
+        if (account == null || account.getStatus() == null || account.getStatus() != 1) {
+            return;
+        }
+
+        BigDecimal commissionAmount = orderAmount
+                .multiply(rate)
+                .divide(new BigDecimal("100"), 2, RoundingMode.DOWN);
+
+        if (commissionAmount.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+
+        DistributionCommissionRecord record = new DistributionCommissionRecord();
+        record.setOrderId(request.getOrderId());
+        record.setOrderNo(request.getOrderNo());
+        record.setBuyerUserId(request.getBuyerUserId());
+        record.setDistributorUserId(distributorUserId);
+        record.setCommissionLevel(commissionLevel);
+        record.setOrderAmount(orderAmount);
+        record.setCommissionRate(rate);
+        record.setCommissionAmount(commissionAmount);
+        record.setStatus(DistributionConstants.COMMISSION_FROZEN);
+
+        commissionRecordMapper.insertRecord(record);
+
+        // 账户金额增加:冻结佣金 + 累计佣金
+        int rows = accountMapper.addFrozenCommission(distributorUserId, commissionAmount);
+        if (rows <= 0) {
+            throw new RuntimeException("增加冻结佣金失败");
+        }
+    }
+
+
+    /**
+     * 如果账户不存在,则创建账户
+     */
+    private void createAccountIfAbsent(Long userId) {
+        DistributionAccount exist = accountMapper.selectByUserId(userId);
+        if (exist != null) {
+            return;
+        }
+
+        DistributionAccount account = new DistributionAccount();
+        account.setUserId(userId);
+        account.setStatus(DistributionConstants.ACCOUNT_NORMAL);
+
+        try {
+            accountMapper.insertAccount(account);
+        } catch (Exception e) {
+            // 并发场景下,可能两个请求同时创建,唯一索引会挡住
+            // 这里再次查询,存在就忽略,不存在再抛异常
+            DistributionAccount after = accountMapper.selectByUserId(userId);
+            if (after == null) {
+                throw e;
+            }
+        }
+    }
+
+
+
+}

+ 11 - 0
fs-service/src/main/java/com/fs/distribution/service/impl/DistributionConfigServiceImpl.java

@@ -0,0 +1,11 @@
+package com.fs.distribution.service.impl;
+
+
+import com.fs.distribution.service.IDistributionConfigService;
+import org.springframework.stereotype.Service;
+
+
+@Service
+public class DistributionConfigServiceImpl implements IDistributionConfigService {
+
+}

+ 579 - 0
fs-service/src/main/java/com/fs/distribution/service/impl/DistributionServiceImpl.java

@@ -0,0 +1,579 @@
+package com.fs.distribution.service.impl;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.distribution.constant.DistributionConstants;
+import com.fs.distribution.domain.*;
+import com.fs.distribution.dto.*;
+import com.fs.distribution.mapper.*;
+import com.fs.distribution.service.DistributionService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 分销核心业务实现
+ */
+@Service
+public class DistributionServiceImpl implements DistributionService {
+
+    @Resource
+    private DistributionUserRelationMapper relationMapper;
+
+    @Resource
+    private DistributionAccountMapper accountMapper;
+
+    @Resource
+    private DistributionCommissionRecordMapper commissionRecordMapper;
+
+    @Resource
+    private DistributionWithdrawRecordMapper withdrawRecordMapper;
+
+    @Resource
+    private DistributionConfigMapper configMapper;
+
+    /**
+     * 注册成功后绑定分销关系
+     *
+     * 核心规则:
+     * 1. 只能绑定一次
+     * 2. 不能绑定自己
+     * 3. 邀请人必须存在
+     * 4. 当前用户绑定 parentUserId
+     * 5. 当前用户的 grandParentUserId = 邀请人的 parentUserId
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void bindRelation(BindRelationRequest request) {
+        if (request == null || request.getUserId() == null) {
+            throw new RuntimeException("用户ID不能为空");
+        }
+
+        Long userId = request.getUserId();
+        Long inviteUserId = request.getInviteUserId();
+
+        // 先保证当前用户有分销账户
+        createAccountIfAbsent(userId);
+
+        // 如果没有邀请人,也创建一条空关系,方便后续查询
+        if (inviteUserId == null) {
+//            createEmptyRelationIfAbsent(userId);
+            return;
+        }
+
+        // 不能绑定自己
+        if (userId.equals(inviteUserId)) {
+            throw new RuntimeException("不能绑定自己为邀请人");
+        }
+
+        // 当前用户是否已经有关系
+        DistributionUserRelation currentRelation = relationMapper.selectByUserId(userId);
+        if (currentRelation != null && currentRelation.getParentUserId() != null && currentRelation.getParentUserId() > 0) {
+            // 已绑定过上级,不允许重复绑定
+            return;
+        }
+
+        // 查询邀请人的关系
+        DistributionUserRelation inviterRelation = relationMapper.selectByUserId(inviteUserId);
+
+        // 邀请人必须有账户,没有则创建
+        createAccountIfAbsent(inviteUserId);
+
+        Long grandParentUserId = null;
+
+        if (inviterRelation != null && inviterRelation.getParentUserId() != null) {
+            grandParentUserId = inviterRelation.getParentUserId();
+        }
+
+        DistributionUserRelation relation = new DistributionUserRelation();
+        relation.setUserId(userId);
+        relation.setParentUserId(inviteUserId);
+        relation.setGrandParentUserId(grandParentUserId);
+        relation.setBindType(request.getBindType() == null
+                ? DistributionConstants.BIND_TYPE_REGISTER
+                : request.getBindType());
+        relation.setStatus(DistributionConstants.RELATION_NORMAL);
+
+        if (currentRelation == null) {
+            relationMapper.insertRelation(relation);
+        } else {
+            relationMapper.updateRelation(relation);
+        }
+    }
+
+    /**
+     * 订单支付成功后生成佣金
+     *
+     * 注意:
+     * 1. 必须幂等,同一个订单不能重复生成佣金
+     * 2. 佣金先进入冻结状态
+     * 3. 增加分销员 frozenCommission 和 totalCommission
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void createCommissionAfterPay(CreateCommissionRequest request) {
+        if (request == null) {
+            throw new RuntimeException("分佣参数不能为空");
+        }
+        if (request.getOrderId() == null || request.getBuyerUserId() == null) {
+            throw new RuntimeException("订单ID或购买人ID不能为空");
+        }
+        if (request.getCommissionBaseAmount() == null || request.getCommissionBaseAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+
+        DistributionConfig config = configMapper.selectConfig();
+        if (config == null || config.getEnableStatus() == null || config.getEnableStatus() != 1) {
+            return;
+        }
+
+        // 幂等判断:同一个订单如果已经生成过佣金,则不再重复生成
+        int existCount = commissionRecordMapper.countByOrderId(request.getOrderId());
+        if (existCount > 0) {
+            return;
+        }
+
+        DistributionUserRelation buyerRelation = relationMapper.selectByUserId(request.getBuyerUserId());
+        if (buyerRelation == null || buyerRelation.getStatus() == null || buyerRelation.getStatus() != 1) {
+            return;
+        }
+
+        BigDecimal orderAmount = request.getCommissionBaseAmount();
+
+        // 一级分佣
+        if (buyerRelation.getParentUserId() != null && buyerRelation.getParentUserId() > 0) {
+            createSingleCommission(
+                    request,
+                    buyerRelation.getParentUserId(),
+                    DistributionConstants.COMMISSION_LEVEL_ONE,
+                    config.getLevelOneRate(),
+                    orderAmount
+            );
+        }
+
+        // 二级分佣
+        if (buyerRelation.getGrandParentUserId() != null && buyerRelation.getGrandParentUserId() > 0) {
+            createSingleCommission(
+                    request,
+                    buyerRelation.getGrandParentUserId(),
+                    DistributionConstants.COMMISSION_LEVEL_TWO,
+                    config.getLevelTwoRate(),
+                    orderAmount
+            );
+        }
+    }
+
+    /**
+     * 创建单条佣金记录
+     */
+    private void createSingleCommission(CreateCommissionRequest request,
+                                        Long distributorUserId,
+                                        Integer commissionLevel,
+                                        BigDecimal rate,
+                                        BigDecimal orderAmount) {
+        if (distributorUserId == null || distributorUserId <= 0) {
+            return;
+        }
+        if (rate == null || rate.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+
+        // 防止自己给自己分佣
+        if (distributorUserId.equals(request.getBuyerUserId())) {
+            return;
+        }
+
+        // 确保分销账户存在
+        createAccountIfAbsent(distributorUserId);
+
+        DistributionAccount account = accountMapper.selectByUserId(distributorUserId);
+        if (account == null || account.getStatus() == null || account.getStatus() != 1) {
+            return;
+        }
+
+        BigDecimal commissionAmount = orderAmount
+                .multiply(rate)
+                .divide(new BigDecimal("100"), 2, RoundingMode.DOWN);
+
+        if (commissionAmount.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+
+        DistributionCommissionRecord record = new DistributionCommissionRecord();
+        record.setOrderId(request.getOrderId());
+        record.setOrderNo(request.getOrderNo());
+        record.setBuyerUserId(request.getBuyerUserId());
+        record.setDistributorUserId(distributorUserId);
+        record.setCommissionLevel(commissionLevel);
+        record.setOrderAmount(orderAmount);
+        record.setCommissionRate(rate);
+        record.setCommissionAmount(commissionAmount);
+        record.setStatus(DistributionConstants.COMMISSION_FROZEN);
+
+        commissionRecordMapper.insertRecord(record);
+
+        // 账户金额增加:冻结佣金 + 累计佣金
+        int rows = accountMapper.addFrozenCommission(distributorUserId, commissionAmount);
+        if (rows <= 0) {
+            throw new RuntimeException("增加冻结佣金失败");
+        }
+    }
+
+    /**
+     * 解冻佣金
+     *
+     * 一般用定时任务每天执行:
+     * 订单完成 + 超过售后期后,把冻结佣金转为可提现。
+     *
+     * 这里的简化版是:
+     * 按佣金记录 create_time + freezeDays 判断。
+     *
+     * 更严谨做法:
+     * 结合订单状态,只解冻已完成、未退款的订单佣金。
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void unlockFrozenCommission() {
+        DistributionConfig config = configMapper.selectConfig();
+        if (config == null || config.getEnableStatus() == null || config.getEnableStatus() != 1) {
+            return;
+        }
+
+        int freezeDays = config.getFreezeDays() == null ? 7 : config.getFreezeDays();
+
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.DAY_OF_MONTH, -freezeDays);
+        Date beforeTime = calendar.getTime();
+
+        List<DistributionCommissionRecord> records = commissionRecordMapper.selectFrozenRecordsBefore(beforeTime);
+        if (records == null || records.isEmpty()) {
+            return;
+        }
+
+        for (DistributionCommissionRecord record : records) {
+            BigDecimal amount = record.getCommissionAmount();
+
+            int rows = accountMapper.frozenToAvailable(record.getDistributorUserId(), amount);
+            if (rows <= 0) {
+                throw new RuntimeException("佣金解冻失败,recordId=" + record.getId());
+            }
+
+            commissionRecordMapper.updateStatus(record.getId(), DistributionConstants.COMMISSION_AVAILABLE);
+        }
+    }
+
+    /**
+     * 订单退款时佣金失效
+     *
+     * 规则:
+     * 1. 冻结中:扣冻结和累计
+     * 2. 可提现:扣可提现和累计
+     * 3. 已提现:目前不直接扣
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void invalidCommissionByRefund(Long orderId) {
+        if (orderId == null) {
+            throw new RuntimeException("订单ID不能为空");
+        }
+
+        List<DistributionCommissionRecord> records = commissionRecordMapper.selectByOrderId(orderId);
+        if (records == null || records.isEmpty()) {
+            return;
+        }
+
+        for (DistributionCommissionRecord record : records) {
+            if (DistributionConstants.COMMISSION_INVALID.equals(record.getStatus())) {
+                continue;
+            }
+
+            BigDecimal amount = record.getCommissionAmount();
+
+            if (DistributionConstants.COMMISSION_FROZEN.equals(record.getStatus())) {
+                int rows = accountMapper.deductFrozenAndTotal(record.getDistributorUserId(), amount);
+                if (rows <= 0) {
+                    throw new RuntimeException("扣减冻结佣金失败,recordId=" + record.getId());
+                }
+            } else if (DistributionConstants.COMMISSION_AVAILABLE.equals(record.getStatus())) {
+                int rows = accountMapper.deductAvailableAndTotal(record.getDistributorUserId(), amount);
+                if (rows <= 0) {
+                    throw new RuntimeException("扣减可提现佣金失败,recordId=" + record.getId());
+                }
+            } else if (DistributionConstants.COMMISSION_WITHDRAWN.equals(record.getStatus())) {
+                // 已提现的佣金,后续做一条负佣金记录,从未来佣金里抵扣
+            }
+
+            commissionRecordMapper.updateStatus(record.getId(), DistributionConstants.COMMISSION_INVALID);
+        }
+    }
+
+    /**
+     * 用户申请提现
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void applyWithdraw(WithdrawApplyRequest request) {
+        if (request == null || request.getUserId() == null) {
+            throw new RuntimeException("用户ID不能为空");
+        }
+        if (request.getWithdrawAmount() == null
+                || request.getWithdrawAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new RuntimeException("提现金额必须大于0");
+        }
+
+        DistributionConfig config = configMapper.selectConfig();
+        if (config == null || config.getEnableStatus() == null || config.getEnableStatus() != 1) {
+            throw new RuntimeException("分销功能未开启");
+        }
+
+        BigDecimal minWithdrawAmount = config.getMinWithdrawAmount() == null
+                ? BigDecimal.ZERO
+                : config.getMinWithdrawAmount();
+
+        if (request.getWithdrawAmount().compareTo(minWithdrawAmount) < 0) {
+            throw new RuntimeException("提现金额不能低于最低提现金额:" + minWithdrawAmount);
+        }
+
+        // 加锁查询账户,防止并发提现
+        DistributionAccount account = accountMapper.selectByUserIdForUpdate(request.getUserId());
+        if (account == null) {
+            throw new RuntimeException("分销账户不存在");
+        }
+        if (account.getStatus() == null || account.getStatus() != 1) {
+            throw new RuntimeException("分销账户已禁用");
+        }
+
+        if (account.getAvailableCommission().compareTo(request.getWithdrawAmount()) < 0) {
+            throw new RuntimeException("可提现余额不足");
+        }
+
+        // 可提现转提现中
+        int rows = accountMapper.availableToWithdrawing(request.getUserId(), request.getWithdrawAmount());
+        if (rows <= 0) {
+            throw new RuntimeException("申请提现失败");
+        }
+
+        DistributionWithdrawRecord record = new DistributionWithdrawRecord();
+        record.setUserId(request.getUserId());
+        record.setWithdrawNo(generateWithdrawNo());
+        record.setWithdrawAmount(request.getWithdrawAmount());
+        record.setStatus(DistributionConstants.WITHDRAW_WAIT_AUDIT);
+        record.setRemark("用户申请提现");
+
+        withdrawRecordMapper.insertWithdrawRecord(record);
+    }
+
+    /**
+     * 后台审核提现
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void auditWithdraw(WithdrawAuditRequest request) {
+        if (request == null || request.getWithdrawId() == null) {
+            throw new RuntimeException("提现ID不能为空");
+        }
+
+        if (!DistributionConstants.WITHDRAW_PAID.equals(request.getStatus())
+                && !DistributionConstants.WITHDRAW_REJECT.equals(request.getStatus())) {
+            throw new RuntimeException("审核状态不正确");
+        }
+
+        DistributionWithdrawRecord record = withdrawRecordMapper.selectByIdForUpdate(request.getWithdrawId());
+        if (record == null) {
+            throw new RuntimeException("提现记录不存在");
+        }
+
+        if (!DistributionConstants.WITHDRAW_WAIT_AUDIT.equals(record.getStatus())) {
+            throw new RuntimeException("该提现记录已审核,不能重复处理");
+        }
+
+        if (DistributionConstants.WITHDRAW_PAID.equals(request.getStatus())) {
+            // 审核通过:提现中转已提现
+            int rows = accountMapper.withdrawingToWithdrawn(record.getUserId(), record.getWithdrawAmount());
+            if (rows <= 0) {
+                throw new RuntimeException("提现审核通过处理失败");
+            }
+        } else {
+            // 审核拒绝:提现中退回可提现
+            int rows = accountMapper.withdrawingBackToAvailable(record.getUserId(), record.getWithdrawAmount());
+            if (rows <= 0) {
+                throw new RuntimeException("提现审核拒绝处理失败");
+            }
+        }
+
+        DistributionWithdrawRecord update = new DistributionWithdrawRecord();
+        update.setId(record.getId());
+        update.setStatus(request.getStatus());
+        update.setRemark(request.getRemark());
+
+        withdrawRecordMapper.updateAuditStatus(update);
+    }
+
+    @Override
+    public DistributionAccount getAccount(Long userId) {
+        if (userId == null) {
+            throw new RuntimeException("用户ID不能为空");
+        }
+        createAccountIfAbsent(userId);
+        return accountMapper.selectByUserId(userId);
+    }
+
+    @Override
+    public DistributionUserRelation getRelation(Long userId) {
+        if (userId == null) {
+            throw new RuntimeException("用户ID不能为空");
+        }
+        return relationMapper.selectByUserId(userId);
+    }
+
+    @Override
+    public List<DistributionCommissionRecord> getCommissionList(Long userId) {
+        return commissionRecordMapper.selectByUserId(userId);
+    }
+
+    @Override
+    public List<DistributionWithdrawRecord> getWithdrawList(Long userId) {
+        return withdrawRecordMapper.selectByUserId(userId);
+    }
+
+    /**
+     * 如果账户不存在,则创建账户
+     */
+    private void createAccountIfAbsent(Long userId) {
+        DistributionAccount exist = accountMapper.selectByUserId(userId);
+        if (exist != null) {
+            return;
+        }
+
+        DistributionAccount account = new DistributionAccount();
+        account.setUserId(userId);
+        account.setStatus(DistributionConstants.ACCOUNT_NORMAL);
+
+        try {
+            accountMapper.insertAccount(account);
+        } catch (Exception e) {
+            // 并发场景下,可能两个请求同时创建,唯一索引会挡住
+            // 这里再次查询,存在就忽略,不存在再抛异常
+            DistributionAccount after = accountMapper.selectByUserId(userId);
+            if (after == null) {
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * 没有邀请人的用户,也创建一条空关系
+     */
+    private void createEmptyRelationIfAbsent(Long userId) {
+        DistributionUserRelation exist = relationMapper.selectByUserId(userId);
+        if (exist != null) {
+            return;
+        }
+
+        DistributionUserRelation relation = new DistributionUserRelation();
+        relation.setUserId(userId);
+        relation.setParentUserId(0L);
+        relation.setGrandParentUserId(0L);
+        relation.setBindType(DistributionConstants.BIND_TYPE_REGISTER);
+        relation.setStatus(DistributionConstants.RELATION_NORMAL);
+
+        try {
+            relationMapper.insertRelation(relation);
+        } catch (Exception e) {
+            DistributionUserRelation after = relationMapper.selectByUserId(userId);
+            if (after == null) {
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * 生成提现单号
+     */
+    private String generateWithdrawNo() {
+        return "TX" + System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "").substring(0, 8);
+    }
+
+
+    @Override
+    public DistributionConfig getConfig() {
+        DistributionConfig config = configMapper.selectConfig();
+
+        if (config == null) {
+            // 理论上初始化 SQL 已经插入了配置
+            // 这里兜底返回默认值
+            config = new DistributionConfig();
+            config.setEnableStatus(1);
+            config.setLevelOneRate(new BigDecimal("10.00"));
+            config.setLevelTwoRate(new BigDecimal("5.00"));
+            config.setFreezeDays(7);
+            config.setMinWithdrawAmount(new BigDecimal("10.00"));
+        }
+
+        return config;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void saveConfig(DistributionConfig config) {
+        if (config == null) {
+            throw new RuntimeException("配置不能为空");
+        }
+
+        if (config.getEnableStatus() == null) {
+            throw new RuntimeException("请选择分销开关");
+        }
+
+        if (config.getLevelOneRate() == null || config.getLevelOneRate().compareTo(BigDecimal.ZERO) < 0) {
+            throw new RuntimeException("一级佣金比例不能小于0");
+        }
+
+        if (config.getLevelTwoRate() == null || config.getLevelTwoRate().compareTo(BigDecimal.ZERO) < 0) {
+            throw new RuntimeException("二级佣金比例不能小于0");
+        }
+
+        if (config.getLevelOneRate().compareTo(new BigDecimal("100")) > 0
+                || config.getLevelTwoRate().compareTo(new BigDecimal("100")) > 0) {
+            throw new RuntimeException("佣金比例不能大于100%");
+        }
+
+        if (config.getFreezeDays() == null || config.getFreezeDays() < 0) {
+            throw new RuntimeException("冻结天数不能小于0");
+        }
+
+        if (config.getMinWithdrawAmount() == null || config.getMinWithdrawAmount().compareTo(BigDecimal.ZERO) < 0) {
+            throw new RuntimeException("最低提现金额不能小于0");
+        }
+
+        DistributionConfig exist = configMapper.selectConfig();
+
+        if (exist == null) {
+            // 如果你没有 insertConfig 方法,可以直接先用 SQL 初始化一条配置
+            throw new RuntimeException("分销配置不存在,请先初始化 distribution_config 表");
+        }
+
+        config.setId(exist.getId());
+
+        int rows = configMapper.updateConfig(config);
+        if (rows <= 0) {
+            throw new RuntimeException("保存分销配置失败");
+        }
+    }
+
+    @Override
+    public QueryDistributionAccountResponse getAccountList(QueryDistributionAccountRequest queryDistributionAccountRequest) {
+        QueryDistributionAccountResponse queryDistributionAccountResponse = new QueryDistributionAccountResponse();
+        List<DistributionAccount> distributionAccounts = accountMapper.selectList(new LambdaQueryWrapper<DistributionAccount>());
+        if (ObjectUtil.isNotEmpty(distributionAccounts)){
+            queryDistributionAccountResponse.setList(distributionAccounts);
+        }
+        return queryDistributionAccountResponse;
+    }
+}

+ 11 - 0
fs-service/src/main/java/com/fs/distribution/service/impl/DistributionUserRelationServiceImpl.java

@@ -0,0 +1,11 @@
+package com.fs.distribution.service.impl;
+
+
+import com.fs.distribution.service.IDistributionUserRelationService;
+import org.springframework.stereotype.Service;
+
+
+@Service
+public class DistributionUserRelationServiceImpl implements IDistributionUserRelationService {
+
+}

+ 21 - 0
fs-service/src/main/java/com/fs/distribution/service/impl/DistributionWithdrawRecordServiceImpl.java

@@ -0,0 +1,21 @@
+package com.fs.distribution.service.impl;
+
+import com.fs.distribution.domain.DistributionWithdrawRecord;
+import com.fs.distribution.mapper.DistributionWithdrawRecordMapper;
+import com.fs.distribution.service.IDistributionWithdrawRecordService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class DistributionWithdrawRecordServiceImpl implements IDistributionWithdrawRecordService {
+
+    @Autowired
+    private DistributionWithdrawRecordMapper distributionWithdrawMapper;
+
+    @Override
+    public List<DistributionWithdrawRecord> selectWithdrawList(DistributionWithdrawRecord query) {
+        return distributionWithdrawMapper.selectWithdrawList(query);
+    }
+}

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsStoreCouponScrm.java

@@ -3,6 +3,7 @@ package com.fs.hisStore.domain;
 import java.math.BigDecimal;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
@@ -12,6 +13,7 @@ import org.apache.commons.lang3.builder.ToStringStyle;
  * @author fs
  * @date 2022-03-15
  */
+@Data
 public class FsStoreCouponScrm extends BaseEntity
 {
     private static final long serialVersionUID = 1L;
@@ -66,6 +68,9 @@ public class FsStoreCouponScrm extends BaseEntity
     @Excel(name = "普通券种类", readConverterExp = "0=无门槛,1=指定商品,2=满减")
     private Integer couponSubType;
 
+    /** 优惠券使用说明 */
+    private String remark;
+
     public String getPackageCateIds() {
         return packageCateIds;
     }
@@ -199,6 +204,7 @@ public class FsStoreCouponScrm extends BaseEntity
             .append("updateTime", getUpdateTime())
             .append("isDel", getIsDel())
             .append("couponSubType", getCouponSubType())
+            .append("remark", getRemark())
             .toString();
     }
 }

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/param/LoginMpWxParam.java

@@ -27,4 +27,6 @@ public class LoginMpWxParam implements Serializable {
     private String appId;
     // 广告链路id
     private String traceId;
+    //邀请人id
+    private Long inviteUserId;
 }

+ 1 - 1
fs-service/src/main/java/com/fs/hisStore/service/IFsStoreCouponIssueScrmService.java

@@ -18,7 +18,7 @@ import com.fs.hisStore.vo.FsStoreCouponIssueVO;
 public interface IFsStoreCouponIssueScrmService
 {
     /**
-     * 查询优惠券领取
+     * 查询优惠券详情
      *
      * @param id 优惠券领取ID
      * @return 优惠券领取

+ 138 - 0
fs-service/src/main/resources/mapper/distribution/DistributionAccountMapper.xml

@@ -0,0 +1,138 @@
+<?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.distribution.mapper.DistributionAccountMapper">
+
+    <resultMap id="AccountMap" type="com.fs.distribution.domain.DistributionAccount">
+        <id property="id" column="id"/>
+        <result property="userId" column="user_id"/>
+        <result property="totalCommission" column="total_commission"/>
+        <result property="frozenCommission" column="frozen_commission"/>
+        <result property="availableCommission" column="available_commission"/>
+        <result property="withdrawingCommission" column="withdrawing_commission"/>
+        <result property="withdrawnCommission" column="withdrawn_commission"/>
+        <result property="status" column="status"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <select id="selectByUserId" resultMap="AccountMap">
+        SELECT *
+        FROM distribution_account
+        WHERE user_id = #{userId}
+            LIMIT 1
+    </select>
+
+    <select id="selectByUserIdForUpdate" resultMap="AccountMap">
+        SELECT *
+        FROM distribution_account
+        WHERE user_id = #{userId}
+            LIMIT 1
+        FOR UPDATE
+    </select>
+
+    <insert id="insertAccount" parameterType="com.fs.distribution.domain.DistributionAccount">
+        INSERT INTO distribution_account (
+            user_id,
+            total_commission,
+            frozen_commission,
+            available_commission,
+            withdrawing_commission,
+            withdrawn_commission,
+            status,
+            create_time,
+            update_time
+        ) VALUES (
+                     #{userId},
+                     0.00,
+                     0.00,
+                     0.00,
+                     0.00,
+                     0.00,
+                     #{status},
+                     NOW(),
+                     NOW()
+                 )
+    </insert>
+
+    <update id="addFrozenCommission">
+        UPDATE distribution_account
+        SET
+            frozen_commission = frozen_commission + #{amount},
+            total_commission = total_commission + #{amount},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+          AND status = 1
+    </update>
+
+    <update id="frozenToAvailable">
+        UPDATE distribution_account
+        SET
+            frozen_commission = frozen_commission - #{amount},
+            available_commission = available_commission + #{amount},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+          AND frozen_commission >= #{amount}
+          AND status = 1
+    </update>
+
+    <update id="deductFrozenAndTotal">
+        UPDATE distribution_account
+        SET
+            frozen_commission = frozen_commission - #{amount},
+            total_commission = total_commission - #{amount},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+          AND frozen_commission >= #{amount}
+          AND total_commission >= #{amount}
+          AND status = 1
+    </update>
+
+    <update id="deductAvailableAndTotal">
+        UPDATE distribution_account
+        SET
+            available_commission = available_commission - #{amount},
+            total_commission = total_commission - #{amount},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+          AND available_commission >= #{amount}
+          AND total_commission >= #{amount}
+          AND status = 1
+    </update>
+
+    <update id="availableToWithdrawing">
+        UPDATE distribution_account
+        SET
+            available_commission = available_commission - #{amount},
+            withdrawing_commission = withdrawing_commission + #{amount},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+          AND available_commission >= #{amount}
+          AND status = 1
+    </update>
+
+    <update id="withdrawingToWithdrawn">
+        UPDATE distribution_account
+        SET
+            withdrawing_commission = withdrawing_commission - #{amount},
+            withdrawn_commission = withdrawn_commission + #{amount},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+          AND withdrawing_commission >= #{amount}
+          AND status = 1
+    </update>
+
+    <update id="withdrawingBackToAvailable">
+        UPDATE distribution_account
+        SET
+            withdrawing_commission = withdrawing_commission - #{amount},
+            available_commission = available_commission + #{amount},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+          AND withdrawing_commission >= #{amount}
+          AND status = 1
+    </update>
+
+</mapper>

+ 85 - 0
fs-service/src/main/resources/mapper/distribution/DistributionCommissionRecordMapper.xml

@@ -0,0 +1,85 @@
+<?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.distribution.mapper.DistributionCommissionRecordMapper">
+
+    <resultMap id="RecordMap" type="com.fs.distribution.domain.DistributionCommissionRecord">
+        <id property="id" column="id"/>
+        <result property="orderId" column="order_id"/>
+        <result property="orderNo" column="order_no"/>
+        <result property="buyerUserId" column="buyer_user_id"/>
+        <result property="distributorUserId" column="distributor_user_id"/>
+        <result property="commissionLevel" column="commission_level"/>
+        <result property="orderAmount" column="order_amount"/>
+        <result property="commissionRate" column="commission_rate"/>
+        <result property="commissionAmount" column="commission_amount"/>
+        <result property="status" column="status"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <insert id="insertRecord" parameterType="com.fs.distribution.domain.DistributionCommissionRecord">
+        INSERT INTO distribution_commission_record (
+            order_id,
+            order_no,
+            buyer_user_id,
+            distributor_user_id,
+            commission_level,
+            order_amount,
+            commission_rate,
+            commission_amount,
+            status,
+            create_time,
+            update_time
+        ) VALUES (
+                     #{orderId},
+                     #{orderNo},
+                     #{buyerUserId},
+                     #{distributorUserId},
+                     #{commissionLevel},
+                     #{orderAmount},
+                     #{commissionRate},
+                     #{commissionAmount},
+                     #{status},
+                     NOW(),
+                     NOW()
+                 )
+    </insert>
+
+    <select id="countByOrderId" resultType="int">
+        SELECT COUNT(*)
+        FROM distribution_commission_record
+        WHERE order_id = #{orderId}
+    </select>
+
+    <select id="selectByOrderId" resultMap="RecordMap">
+        SELECT *
+        FROM distribution_commission_record
+        WHERE order_id = #{orderId}
+    </select>
+
+    <select id="selectFrozenRecordsBefore" resultMap="RecordMap">
+        SELECT *
+        FROM distribution_commission_record
+        WHERE status = 0
+          and create_time &lt;= #{endTime}
+        ORDER BY id ASC
+    </select>
+
+    <update id="updateStatus">
+        UPDATE distribution_commission_record
+        SET status = #{status},
+            update_time = NOW()
+        WHERE id = #{id}
+    </update>
+
+    <select id="selectByUserId" resultMap="RecordMap">
+        SELECT *
+        FROM distribution_commission_record
+        WHERE distributor_user_id = #{userId}
+        ORDER BY id DESC
+    </select>
+
+</mapper>

+ 38 - 0
fs-service/src/main/resources/mapper/distribution/DistributionConfigMapper.xml

@@ -0,0 +1,38 @@
+<?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.distribution.mapper.DistributionConfigMapper">
+
+    <resultMap id="ConfigMap" type="com.fs.distribution.domain.DistributionConfig">
+        <id property="id" column="id"/>
+        <result property="enableStatus" column="enable_status"/>
+        <result property="levelOneRate" column="level_one_rate"/>
+        <result property="levelTwoRate" column="level_two_rate"/>
+        <result property="freezeDays" column="freeze_days"/>
+        <result property="minWithdrawAmount" column="min_withdraw_amount"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <select id="selectConfig" resultMap="ConfigMap">
+        SELECT *
+        FROM distribution_config
+        ORDER BY id ASC
+            LIMIT 1
+    </select>
+
+    <update id="updateConfig" parameterType="com.fs.distribution.domain.DistributionConfig">
+        UPDATE distribution_config
+        SET
+            enable_status = #{enableStatus},
+            level_one_rate = #{levelOneRate},
+            level_two_rate = #{levelTwoRate},
+            freeze_days = #{freezeDays},
+            min_withdraw_amount = #{minWithdrawAmount},
+            update_time = NOW()
+        WHERE id = #{id}
+    </update>
+
+</mapper>

+ 87 - 0
fs-service/src/main/resources/mapper/distribution/DistributionUserRelationMapper.xml

@@ -0,0 +1,87 @@
+<?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.distribution.mapper.DistributionUserRelationMapper">
+
+    <resultMap id="RelationMap" type="com.fs.distribution.domain.DistributionUserRelation">
+        <id property="id" column="id"/>
+        <result property="userId" column="user_id"/>
+        <result property="parentUserId" column="parent_user_id"/>
+        <result property="grandParentUserId" column="grand_parent_user_id"/>
+        <result property="bindType" column="bind_type"/>
+        <result property="status" column="status"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <select id="selectByUserId" resultMap="RelationMap">
+        SELECT *
+        FROM distribution_user_relation
+        WHERE user_id = #{userId}
+            LIMIT 1
+    </select>
+
+    <insert id="insertRelation" parameterType="com.fs.distribution.domain.DistributionUserRelation">
+        INSERT INTO distribution_user_relation (
+            user_id,
+            parent_user_id,
+            grand_parent_user_id,
+            bind_type,
+            status,
+            create_time,
+            update_time
+        ) VALUES (
+                     #{userId},
+                     #{parentUserId},
+                     #{grandParentUserId},
+                     #{bindType},
+                     #{status},
+                     NOW(),
+                     NOW()
+                 )
+    </insert>
+
+    <update id="updateRelation" parameterType="com.fs.distribution.domain.DistributionUserRelation">
+        UPDATE distribution_user_relation
+        SET
+            parent_user_id = #{parentUserId},
+            grand_parent_user_id = #{grandParentUserId},
+            bind_type = #{bindType},
+            status = #{status},
+            update_time = NOW()
+        WHERE user_id = #{userId}
+    </update>
+
+    <select id="countFirstLevelTeam" resultType="int">
+        SELECT COUNT(*)
+        FROM distribution_user_relation
+        WHERE parent_user_id = #{userId}
+          AND status = 1
+    </select>
+
+    <select id="countSecondLevelTeam" resultType="int">
+        SELECT COUNT(*)
+        FROM distribution_user_relation
+        WHERE grand_parent_user_id = #{userId}
+          AND status = 1
+    </select>
+
+    <select id="selectFirstLevelTeam" resultMap="RelationMap">
+        SELECT *
+        FROM distribution_user_relation
+        WHERE parent_user_id = #{userId}
+          AND status = 1
+        ORDER BY id DESC
+    </select>
+
+    <select id="selectSecondLevelTeam" resultMap="RelationMap">
+        SELECT *
+        FROM distribution_user_relation
+        WHERE grand_parent_user_id = #{userId}
+          AND status = 1
+        ORDER BY id DESC
+    </select>
+
+</mapper>

+ 96 - 0
fs-service/src/main/resources/mapper/distribution/DistributionWithdrawRecordMapper.xml

@@ -0,0 +1,96 @@
+<?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.distribution.mapper.DistributionWithdrawRecordMapper">
+
+    <resultMap id="WithdrawMap" type="com.fs.distribution.domain.DistributionWithdrawRecord">
+        <id property="id" column="id"/>
+        <result property="userId" column="user_id"/>
+        <result property="withdrawNo" column="withdraw_no"/>
+        <result property="withdrawAmount" column="withdraw_amount"/>
+        <result property="status" column="status"/>
+        <result property="remark" column="remark"/>
+        <result property="createTime" column="create_time"/>
+        <result property="auditTime" column="audit_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <insert id="insertWithdrawRecord" parameterType="com.fs.distribution.domain.DistributionWithdrawRecord">
+        INSERT INTO distribution_withdraw_record (
+            user_id,
+            withdraw_no,
+            withdraw_amount,
+            status,
+            remark,
+            create_time,
+            update_time
+        ) VALUES (
+                     #{userId},
+                     #{withdrawNo},
+                     #{withdrawAmount},
+                     #{status},
+                     #{remark},
+                     NOW(),
+                     NOW()
+                 )
+    </insert>
+
+    <select id="selectById" resultMap="WithdrawMap">
+        SELECT *
+        FROM distribution_withdraw_record
+        WHERE id = #{id}
+            LIMIT 1
+    </select>
+
+    <select id="selectByIdForUpdate" resultMap="WithdrawMap">
+        SELECT *
+        FROM distribution_withdraw_record
+        WHERE id = #{id}
+            LIMIT 1
+        FOR UPDATE
+    </select>
+
+    <update id="updateAuditStatus" parameterType="com.fs.distribution.domain.DistributionWithdrawRecord">
+        UPDATE distribution_withdraw_record
+        SET status = #{status},
+            remark = #{remark},
+            audit_time = NOW(),
+            update_time = NOW()
+        WHERE id = #{id}
+    </update>
+
+    <select id="selectByUserId" resultMap="WithdrawMap">
+        SELECT *
+        FROM distribution_withdraw_record
+        WHERE user_id = #{userId}
+        ORDER BY id DESC
+    </select>
+
+
+    <select id="selectWithdrawList" resultType="com.fs.distribution.domain.DistributionWithdrawRecord">
+        select
+        id,
+        withdraw_no as withdrawNo,
+        user_id as userId,
+        withdraw_amount as withdrawAmount,
+        status,
+        remark,
+        create_time as createTime
+        from distribution_withdraw_record
+        <where>
+            <if test="status != null">
+                and status = #{status}
+            </if>
+            <if test="userId != null">
+                and user_id = #{userId}
+            </if>
+            <if test="withdrawNo != null and withdrawNo != ''">
+                and withdraw_no like concat('%', #{withdrawNo}, '%')
+            </if>
+        </where>
+        order by create_time desc
+    </select>
+
+</mapper>

+ 6 - 23
fs-user-app/src/main/java/com/fs/app/controller/store/FsStoreCouponScrmController.java

@@ -37,21 +37,9 @@ public class FsStoreCouponScrmController extends BaseController
     @Autowired
     private IFsStoreCouponIssueScrmService fsStoreCouponIssueService;
 
-//    /**
-//     * 查询优惠券列表
-//     */
-//    @PreAuthorize("@ss.hasPermi('store:storeCoupon:list')")
-//    @GetMapping("/list")
-//    public TableDataInfo list(FsStoreCouponScrm fsStoreCoupon)
-//    {
-//        startPage();
-//        fsStoreCoupon.setIsDel(0);
-//        List<FsStoreCouponScrm> list = fsStoreCouponService.selectFsStoreCouponList(fsStoreCoupon);
-//        return getDataTable(list);
-//    }
 
     /**
-     * 查询优惠券领取列表
+     * 查询可领取优惠券列表
      */
 //    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:list')")
     @PostMapping("/list")
@@ -64,7 +52,7 @@ public class FsStoreCouponScrmController extends BaseController
 
 
     /**
-     * 获取优惠券领取详细信息
+     * 获取优惠券详细信息
      */
 //    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:query')")
     @GetMapping(value = "/{id}")
@@ -74,17 +62,12 @@ public class FsStoreCouponScrmController extends BaseController
     }
 
 
+
     /**
-     * 新增优惠券领取
+     * 领取优惠券(领取时插入领取记录和发放记录)
+     * @param receiveCouponDto
+     * @return
      */
-//    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:add')")
-//    @Log(title = "优惠券领取", businessType = BusinessType.INSERT)
-//    @PostMapping
-//    public AjaxResult add(@RequestBody FsStoreCouponIssueScrm fsStoreCouponIssue)
-//    {
-//        return toAjax(fsStoreCouponIssueService.insertFsStoreCouponIssue(fsStoreCouponIssue));
-//    }
-
 //    @PreAuthorize("@ss.hasPermi('store:storeCouponIssue:add')")
     @Log(title = "优惠券领取", businessType = BusinessType.INSERT)
     @PostMapping

+ 16 - 1
fs-user-app/src/main/java/com/fs/app/controller/store/WxUserScrmController.java

@@ -18,6 +18,9 @@ import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.ip.IpUtils;
 import com.fs.core.config.WxMaConfiguration;
 import com.fs.core.config.WxMpProperties;
+import com.fs.distribution.constant.DistributionConstants;
+import com.fs.distribution.dto.BindRelationRequest;
+import com.fs.distribution.service.DistributionService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.domain.FsUserScrm;
@@ -73,6 +76,8 @@ public class WxUserScrmController extends AppBaseController {
     private ConfigUtil configUtil;
     @Autowired
     private ILeadService leadService;
+    @Autowired
+    private DistributionService distributionService;
 
     /**
      * 登陆接口
@@ -356,7 +361,17 @@ public class WxUserScrmController extends AppBaseController {
                     }
                     String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
                     user.setLastIp(ipAddr);
-                    userService.insertFsUser(user);
+                    if (userService.insertFsUser(user) > 0) {
+                        if (ObjectUtil.isNotEmpty(param.getInviteUserId()) && param.getInviteUserId() > 0) {
+                            //绑定分销关系
+                            BindRelationRequest bindRequest = new BindRelationRequest();
+                            bindRequest.setUserId(user.getUserId());
+                            bindRequest.setInviteUserId(param.getInviteUserId());
+                            bindRequest.setBindType(DistributionConstants.BIND_TYPE_REGISTER);
+
+                            distributionService.bindRelation(bindRequest);
+                        }
+                    }
                 }
             }
             else{