Browse Source

排行榜以及团队接口

xudm 2 months ago
parent
commit
506365ebed
55 changed files with 1676 additions and 93 deletions
  1. 5 1
      src/main/java/com/xs/core/common/constant/GoldCoinConstant.java
  2. 0 1
      src/main/java/com/xs/core/config/RedisConfig.java
  3. 23 0
      src/main/java/com/xs/core/config/mq/RabbitMQConfig.java
  4. 30 0
      src/main/java/com/xs/core/controller/competition/CompetitionController.java
  5. 30 0
      src/main/java/com/xs/core/controller/team/TeamShareController.java
  6. 16 0
      src/main/java/com/xs/core/mapper/coin/BoostTemporaryRateRecordMapper.java
  7. 16 0
      src/main/java/com/xs/core/mapper/team/TeamInviteConfigMapper.java
  8. 16 0
      src/main/java/com/xs/core/mapper/team/TeamInviteRewardRecordMapper.java
  9. 16 0
      src/main/java/com/xs/core/mapper/team/TeamShareRewardRecordMapper.java
  10. 37 0
      src/main/java/com/xs/core/mapper/user/TgUserMapper.java
  11. 60 0
      src/main/java/com/xs/core/model/coin/entity/BoostTemporaryRateRecord.java
  12. 8 0
      src/main/java/com/xs/core/model/coin/entity/GoldCoinProdRecord.java
  13. 6 0
      src/main/java/com/xs/core/model/coin/entity/GoldCoinProdState.java
  14. 11 1
      src/main/java/com/xs/core/model/coin/msg/CoinProducerMessage.java
  15. 7 0
      src/main/java/com/xs/core/model/coin/msg/RetryableMessage.java
  16. 63 0
      src/main/java/com/xs/core/model/coin/msg/TeamShareMessage.java
  17. 66 0
      src/main/java/com/xs/core/model/team/entity/TeamInviteConfig.java
  18. 75 0
      src/main/java/com/xs/core/model/team/entity/TeamInviteRewardRecord.java
  19. 99 0
      src/main/java/com/xs/core/model/team/entity/TeamShareRewardRecord.java
  20. 72 0
      src/main/java/com/xs/core/model/team/resp/TeamUserResp.java
  21. 21 0
      src/main/java/com/xs/core/model/team/resp/TeamsInfoResp.java
  22. 15 0
      src/main/java/com/xs/core/model/user/req/GoldCoinCompetitionRankingReq.java
  23. 86 0
      src/main/java/com/xs/core/model/user/resp/UserCompetitionResp.java
  24. 56 25
      src/main/java/com/xs/core/mq/consumer/GoldCoinProductMessageConsumer.java
  25. 43 0
      src/main/java/com/xs/core/mq/consumer/TeamShareMessageConsumer.java
  26. 61 29
      src/main/java/com/xs/core/mq/producer/GoldCoinMessageProducer.java
  27. 20 0
      src/main/java/com/xs/core/service/Impl/coin/BoostTemporaryRateRecordServiceImpl.java
  28. 8 1
      src/main/java/com/xs/core/service/Impl/coin/GoldCoinProdRecordServiceImpl.java
  29. 1 3
      src/main/java/com/xs/core/service/Impl/coin/GoldCoinProdStateServiceImpl.java
  30. 44 7
      src/main/java/com/xs/core/service/Impl/coin/GoldCoinServiceImpl.java
  31. 21 0
      src/main/java/com/xs/core/service/Impl/competition/CompetitionServiceImpl.java
  32. 28 0
      src/main/java/com/xs/core/service/Impl/team/TeamInviteConfigServiceImpl.java
  33. 20 0
      src/main/java/com/xs/core/service/Impl/team/TeamInviteRewardRecordServiceImpl.java
  34. 132 0
      src/main/java/com/xs/core/service/Impl/team/TeamShareGoldCoinSettlementServiceImpl.java
  35. 20 0
      src/main/java/com/xs/core/service/Impl/team/TeamShareRewardRecordServiceImpl.java
  36. 22 5
      src/main/java/com/xs/core/service/Impl/user/AppLoginServiceImpl.java
  37. 12 0
      src/main/java/com/xs/core/service/Impl/user/TgUserServiceImpl.java
  38. 1 1
      src/main/java/com/xs/core/service/coin/GoldCoinService.java
  39. 16 0
      src/main/java/com/xs/core/service/coin/IBoostTemporaryRateRecordService.java
  40. 8 1
      src/main/java/com/xs/core/service/coin/IGoldCoinProdRecordService.java
  41. 19 0
      src/main/java/com/xs/core/service/competition/CompetitionService.java
  42. 21 0
      src/main/java/com/xs/core/service/team/ITeamInviteConfigService.java
  43. 16 0
      src/main/java/com/xs/core/service/team/ITeamInviteRewardRecordService.java
  44. 16 0
      src/main/java/com/xs/core/service/team/ITeamShareRewardRecordService.java
  45. 46 0
      src/main/java/com/xs/core/service/team/TeamShareService.java
  46. 12 0
      src/main/java/com/xs/core/service/user/ITgUserService.java
  47. 1 1
      src/main/java/com/xs/core/utils/MyGenerator.java
  48. 1 1
      src/main/resources/application-dev.yml
  49. 19 0
      src/main/resources/mapper/BoostTemporaryRateRecordMapper.xml
  50. 10 16
      src/main/resources/mapper/GoldCoinProdRecordMapper.xml
  51. 1 0
      src/main/resources/mapper/GoldCoinProdStateMapper.xml
  52. 20 0
      src/main/resources/mapper/TeamInviteConfigMapper.xml
  53. 14 0
      src/main/resources/mapper/TeamInviteRewardRecordMapper.xml
  54. 18 0
      src/main/resources/mapper/TeamShareRewardRecordMapper.xml
  55. 171 0
      src/main/resources/mapper/TgUserMapper.xml

+ 5 - 1
src/main/java/com/xs/core/common/constant/GoldCoinConstant.java

@@ -13,12 +13,16 @@ public interface GoldCoinConstant {
      */
     String GOLD_COIN_RATE_UPGRADES_KEY = "gold:coin:product:rate:upgrades:record:";
 
+    /**
+     * 金币产出计算已处理的消息key
+     */
+    String GOLD_COIN_CALCULATE_PROCESSED_MESSAGE = "gold:coin:calculate:processed:message:";
     /**
      * 金币产出时间,单位秒
      */
     long GOLD_COIN_PRODUCT_TIME = 60 * 60 * 12L;
 
-    long BOOST_RATE_RESIDUE_TIMESTAMP = 60 * 60 *12L;
+    long BOOST_RATE_RESIDUE_TIMESTAMP = 60 * 60 * 12L;
 
 
     /**

+ 0 - 1
src/main/java/com/xs/core/config/RedisConfig.java

@@ -16,7 +16,6 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
 public class RedisConfig implements CachingConfigurer {
     @Bean
     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
-
         FastJson2JsonRedisSerializer<String> serializer = new FastJson2JsonRedisSerializer<>(String.class);
         RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
         redisTemplate.setConnectionFactory(redisConnectionFactory);

+ 23 - 0
src/main/java/com/xs/core/config/mq/RabbitMQConfig.java

@@ -14,11 +14,26 @@ public class RabbitMQConfig {
     public static final String GOLD_COIN_COMPLETE_QUEUE = "gold.coin.complete.queue";
     public static final String GOLD_COIN_COMPLETE_EXCHANGE = "gold.coin.complete.exchange";
 
+    public static final String GOLD_COIN_TEAM_SHARE_QUEUE = "gold.coin.team.share.queue";
+    public static final String GOLD_COIN_TEAM_SHARE_EXCHANGE = "gold.coin.team.share.exchange";
+
     @Bean
     public Queue goldCoinCompleteQueue() {
         return new Queue(GOLD_COIN_COMPLETE_QUEUE, true, false, false);
     }
 
+    @Bean
+    public Queue goldCoinTeamShareQueue() {
+        return new Queue(GOLD_COIN_TEAM_SHARE_QUEUE, true, false, false);
+    }
+
+    @Bean
+    public CustomExchange goldCoinTeamShareExchange() {
+        Map<String, Object> args = new HashMap<>();
+        args.put("x-delayed-type", "direct");
+        return new CustomExchange(GOLD_COIN_TEAM_SHARE_EXCHANGE, "x-delayed-message", true, false, args);
+    }
+
     @Bean
     public DirectExchange goldCoinCompleteExchange() {
         return new DirectExchange(GOLD_COIN_COMPLETE_EXCHANGE, true, false);
@@ -51,4 +66,12 @@ public class RabbitMQConfig {
                 .noargs();
     }
 
+    @Bean
+    public Binding bindingTeamShare() {
+        return BindingBuilder.bind(goldCoinTeamShareQueue())
+                .to(goldCoinTeamShareExchange())
+                .with(GOLD_COIN_TEAM_SHARE_QUEUE)
+                .noargs();
+    }
+
 }

+ 30 - 0
src/main/java/com/xs/core/controller/competition/CompetitionController.java

@@ -0,0 +1,30 @@
+package com.xs.core.controller.competition;
+
+import com.xs.core.model.ResponseResult;
+import com.xs.core.model.user.req.GoldCoinCompetitionRankingReq;
+import com.xs.core.model.user.resp.UserCompetitionResp;
+import com.xs.core.service.competition.CompetitionService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/competition")
+@Tag(name = "竞赛排行榜")
+public class CompetitionController {
+    @Autowired
+    private CompetitionService competitionService;
+
+    @Operation(summary = "金币排行榜", description = "金币排行榜")
+    @PostMapping("/goldCoinCompetitionRanking")
+    public ResponseResult<List<UserCompetitionResp>> goldCoinCompetitionRanking(@RequestBody GoldCoinCompetitionRankingReq query) {
+        List<UserCompetitionResp> userCompetitionRespList = competitionService.goldCoinCompetitionRanking(query.getLimit(), query.getInvitees());
+        return ResponseResult.success(userCompetitionRespList);
+    }
+}

+ 30 - 0
src/main/java/com/xs/core/controller/team/TeamShareController.java

@@ -0,0 +1,30 @@
+package com.xs.core.controller.team;
+
+import com.xs.core.common.content.UserContext;
+import com.xs.core.common.content.UserContextHolder;
+import com.xs.core.model.ResponseResult;
+import com.xs.core.model.team.resp.TeamsInfoResp;
+import com.xs.core.service.team.TeamShareService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/team/share")
+@Tag(name = "团队邀请", description = "团队邀请")
+public class TeamShareController {
+    @Autowired
+    private TeamShareService teamShareService;
+
+    @Operation(summary = "获取我的团队信息", description = "获取我的团队信息")
+    @PostMapping("/getMyTeamsInfo")
+    public ResponseResult<TeamsInfoResp> getMyTeamsInfo() {
+        UserContext userContext = UserContextHolder.getContext();
+        TeamsInfoResp teamsInfoResp = teamShareService.getMyTeamsInfo(userContext.getId());
+        return ResponseResult.success(teamsInfoResp);
+    }
+}

+ 16 - 0
src/main/java/com/xs/core/mapper/coin/BoostTemporaryRateRecordMapper.java

@@ -0,0 +1,16 @@
+package com.xs.core.mapper.coin;
+
+import com.xs.core.model.coin.entity.BoostTemporaryRateRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 用户临时速率提升记录 Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+public interface BoostTemporaryRateRecordMapper extends BaseMapper<BoostTemporaryRateRecord> {
+
+}

+ 16 - 0
src/main/java/com/xs/core/mapper/team/TeamInviteConfigMapper.java

@@ -0,0 +1,16 @@
+package com.xs.core.mapper.team;
+
+import com.xs.core.model.team.entity.TeamInviteConfig;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 团队邀请配置 Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+public interface TeamInviteConfigMapper extends BaseMapper<TeamInviteConfig> {
+
+}

+ 16 - 0
src/main/java/com/xs/core/mapper/team/TeamInviteRewardRecordMapper.java

@@ -0,0 +1,16 @@
+package com.xs.core.mapper.team;
+
+import com.xs.core.model.team.entity.TeamInviteRewardRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 团队邀请奖励领取列表(每邀请一人奖励1000) Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+public interface TeamInviteRewardRecordMapper extends BaseMapper<TeamInviteRewardRecord> {
+
+}

+ 16 - 0
src/main/java/com/xs/core/mapper/team/TeamShareRewardRecordMapper.java

@@ -0,0 +1,16 @@
+package com.xs.core.mapper.team;
+
+import com.xs.core.model.team.entity.TeamShareRewardRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 团队产出奖励记录 A B队抽成 Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+public interface TeamShareRewardRecordMapper extends BaseMapper<TeamShareRewardRecord> {
+
+}

+ 37 - 0
src/main/java/com/xs/core/mapper/user/TgUserMapper.java

@@ -1,7 +1,13 @@
 package com.xs.core.mapper.user;
 
+import com.xs.core.model.team.resp.TeamUserResp;
 import com.xs.core.model.user.entity.TgUser;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.xs.core.model.user.resp.UserCompetitionResp;
+import org.apache.ibatis.annotations.Param;
+
+import java.io.Serial;
+import java.util.List;
 
 /**
  * <p>
@@ -12,5 +18,36 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  * @since 2024-12-18
  */
 public interface TgUserMapper extends BaseMapper<TgUser> {
+    /**
+     * 查询我的团队列表
+     *
+     * @param userId   用户id
+     * @param teamType 团队类型
+     * @return
+     */
+    List<TeamUserResp> getMyTeamUserList(@Param("userId") Long userId, @Param("teamType") String teamType);
+
+    /**
+     * 统计我的团队人数
+     *
+     * @param userId   用户id
+     * @param teamType 团队类型
+     * @return
+     */
+    int countMyTeamUserList(@Param("userId") Long userId, @Param("teamType") String teamType);
+
+    /**
+     * 金币排行榜
+     *
+     * @return 用户金币排行信息
+     */
+    List<UserCompetitionResp> goldCoinCompetitionRanking(@Param("limit") int limit, @Param("invitees") int invitees);
 
+    /**
+     * 查询我的邀请人
+     *
+     * @param userId
+     * @return
+     */
+    TgUser getInviterByUserId(Long userId);
 }

+ 60 - 0
src/main/java/com/xs/core/model/coin/entity/BoostTemporaryRateRecord.java

@@ -0,0 +1,60 @@
+package com.xs.core.model.coin.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 用户临时速率提升记录
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+@Getter
+@Setter
+@TableName("b_boost_temporary_rate_record")
+public class BoostTemporaryRateRecord extends Model<BoostTemporaryRateRecord> {
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 用户id
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 升级时间戳
+     */
+    @TableField("boost_timestamp")
+    private Long boostTimestamp;
+
+    /**
+     * 升级时间
+     */
+    @TableField("boost_time")
+    private LocalDateTime boostTime;
+
+    /**
+     * 临时速率 0.01
+     */
+    @TableField("temporary_rate")
+    private String temporaryRate;
+
+    @Override
+    public Serializable pkVal() {
+        return this.id;
+    }
+}

+ 8 - 0
src/main/java/com/xs/core/model/coin/entity/GoldCoinProdRecord.java

@@ -5,9 +5,11 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.extension.activerecord.Model;
+
 import java.io.Serializable;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+
 import lombok.Getter;
 import lombok.Setter;
 
@@ -30,6 +32,12 @@ public class GoldCoinProdRecord extends Model<GoldCoinProdRecord> {
     @TableId(value = "id", type = IdType.ASSIGN_ID)
     private Long id;
 
+    /**
+     * 批次id
+     */
+    @TableField("batch_id")
+    private String batchId;
+
     /**
      * 用户
      */

+ 6 - 0
src/main/java/com/xs/core/model/coin/entity/GoldCoinProdState.java

@@ -31,6 +31,12 @@ public class GoldCoinProdState extends Model<GoldCoinProdState> {
     @TableId(value = "id", type = IdType.ASSIGN_ID)
     private Long id;
 
+    /**
+     * 批次id
+     */
+    @TableField("batch_id")
+    private String batchId;
+
     /**
      * 用户id
      */

+ 11 - 1
src/main/java/com/xs/core/model/coin/msg/CoinProducerMessage.java

@@ -13,12 +13,22 @@ import java.math.BigDecimal;
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
-public class CoinProducerMessage implements Serializable {
+public class CoinProducerMessage implements Serializable, RetryableMessage {
     /**
      * 用户id
      */
     private Long userId;
 
+    /**
+     * 批次id
+     */
+    private String batchId;
+
+    /**
+     * 消息id 用于幂等性校验
+     */
+    private String msgId;
+
     /**
      * 当前生产的金币数
      */

+ 7 - 0
src/main/java/com/xs/core/model/coin/msg/RetryableMessage.java

@@ -0,0 +1,7 @@
+package com.xs.core.model.coin.msg;
+
+public interface RetryableMessage {
+    int getRetryCount();
+
+    void setRetryCount(int count);
+}

+ 63 - 0
src/main/java/com/xs/core/model/coin/msg/TeamShareMessage.java

@@ -0,0 +1,63 @@
+package com.xs.core.model.coin.msg;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 团队分享产出表
+ * </p>
+ */
+@Data
+@Schema(name = "TeamShareMessage", description = "团队分享产出表")
+public class TeamShareMessage implements Serializable, RetryableMessage {
+
+    /**
+     * 用户
+     */
+    @Schema(description = "用户")
+    private Long userId;
+
+    /**
+     * 消息id 用于幂等性校验
+     */
+    @Schema(description = "消息id 用于幂等性校验")
+    private String msgId;
+
+    /**
+     * 批次id
+     */
+    @Schema(description = "批次id")
+    private String batchId;
+
+    /**
+     * 金币产出
+     */
+    @Schema(description = "金币产出")
+    private BigDecimal goldCoinYieldTotal;
+
+    /**
+     * 开始秒级时间戳
+     */
+    @Schema(description = "开始秒级时间戳")
+    private Long startTimestamp;
+
+    /**
+     * 结束秒级时间戳
+     */
+    @Schema(description = "结束秒级时间戳")
+    private Long endTimestamp;
+
+    /**
+     * 产出时间
+     */
+    @Schema(description = "产出时间")
+    private LocalDateTime yieldTime;
+
+    private int retryCount;
+
+}

+ 66 - 0
src/main/java/com/xs/core/model/team/entity/TeamInviteConfig.java

@@ -0,0 +1,66 @@
+package com.xs.core.model.team.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 团队邀请配置
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+@Getter
+@Setter
+@TableName("b_team_invite_config")
+public class TeamInviteConfig extends Model<TeamInviteConfig> {
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * A队抽成比例 百分比 0.01
+     */
+    @TableField("team_a_scale")
+    private String teamAScale;
+
+    /**
+     * B队抽成比例 百分比 0.01
+     */
+    @TableField("team_b_scale")
+    private String teamBScale;
+
+    /**
+     * 邀请一人金币奖励
+     */
+    @TableField("Invite_one_reward")
+    private Integer inviteOneReward;
+
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private LocalDateTime createTime;
+
+    /**
+     * 修改时间
+     */
+    @TableField("update_time")
+    private LocalDateTime updateTime;
+
+    @Override
+    public Serializable pkVal() {
+        return this.id;
+    }
+}

+ 75 - 0
src/main/java/com/xs/core/model/team/entity/TeamInviteRewardRecord.java

@@ -0,0 +1,75 @@
+package com.xs.core.model.team.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 团队邀请奖励领取列表(每邀请一人奖励1000)
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+@Getter
+@Setter
+@TableName("b_team_invite_reward_record")
+public class TeamInviteRewardRecord extends Model<TeamInviteRewardRecord> {
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 奖励领取人id
+     */
+    @TableField("receiver_id")
+    private Long receiverId;
+
+    /**
+     * 被邀请人id
+     */
+    @TableField("invitee_id")
+    private Long inviteeId;
+
+    /**
+     * 奖励金额
+     */
+    @TableField("reward_amount")
+    private BigDecimal rewardAmount;
+
+    /**
+     * 领取状态 0 待领取 1 已领取
+     */
+    @TableField("claim_status")
+    private Integer claimStatus;
+
+    /**
+     * 领取时间
+     */
+    @TableField("claim_time")
+    private LocalDateTime claimTime;
+
+    /**
+     * 创建时间
+     */
+    @TableField("create_time")
+    private LocalDateTime createTime;
+
+    @Override
+    public Serializable pkVal() {
+        return this.id;
+    }
+}

+ 99 - 0
src/main/java/com/xs/core/model/team/entity/TeamShareRewardRecord.java

@@ -0,0 +1,99 @@
+package com.xs.core.model.team.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 团队产出奖励记录 A B队抽成
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+@Getter
+@Setter
+@TableName("b_team_share_reward_record")
+public class TeamShareRewardRecord extends Model<TeamShareRewardRecord> {
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 奖励领取人id
+     */
+    @TableField("target_id")
+    private Long targetId;
+
+    /**
+     * 金币产出人(被邀请人)
+     */
+    @TableField("invitee_id")
+    private Long inviteeId;
+
+    /**
+     * 团队类型 【A ,B】
+     */
+    @TableField("team_type")
+    private String teamType;
+
+    /**
+     * 奖励金额
+     */
+    @TableField("reward_amount")
+    private BigDecimal rewardAmount;
+
+    /**
+     * 产出总额
+     */
+    @TableField("gold_coin_yield_total")
+    private BigDecimal goldCoinYieldTotal;
+
+    /**
+     * 产出批次号
+     */
+    @TableField("batch_id")
+    private String batchId;
+
+    /**
+     * 抽成比例
+     */
+    @TableField("scale")
+    private String scale;
+
+    /**
+     * 领取状态 0 待领取 1 已领取
+     */
+    @TableField("claim_status")
+    private Integer claimStatus;
+
+    /**
+     * 领取时间
+     */
+    @TableField("claim_time")
+    private LocalDateTime claimTime;
+
+    /**
+     * 生成时间
+     */
+    @TableField("create_time")
+    private LocalDateTime createTime;
+
+    @Override
+    public Serializable pkVal() {
+        return this.id;
+    }
+}

+ 72 - 0
src/main/java/com/xs/core/model/team/resp/TeamUserResp.java

@@ -0,0 +1,72 @@
+package com.xs.core.model.team.resp;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Data
+@Schema(description = "团队用户信息")
+public class TeamUserResp implements Serializable {
+
+    /**
+     * tg小程序id
+     */
+    @Schema(description = "tg小程序id")
+    private String tgId;
+
+    /**
+     * tg账号
+     */
+    @Schema(description = "tg账号")
+    private String tgAccount;
+
+    /**
+     * 名
+     */
+    @Schema(description = "名")
+    private String firstName;
+
+    /**
+     * 姓
+     */
+    @Schema(description = "姓")
+    private String lastName;
+
+    /**
+     * 昵称
+     */
+    @Schema(description = "昵称")
+    private String nickname;
+
+    /**
+     * 真实姓名
+     */
+    @Schema(description = "真实姓名")
+    private String realName;
+
+    /**
+     * 头像
+     */
+    @Schema(description = "头像")
+    private String avatar;
+
+
+    /**
+     * 客流途径 1 新增 2 邀请
+     */
+    @Schema(description = "流途径 1 新增 2 邀请")
+    private Integer passengerFlowWay;
+
+    /**
+     * 团队类型
+     */
+    @Schema(description = "团队类型")
+    private String teamType;
+
+}

+ 21 - 0
src/main/java/com/xs/core/model/team/resp/TeamsInfoResp.java

@@ -0,0 +1,21 @@
+package com.xs.core.model.team.resp;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@Schema(name = "TeamsInfoResp", description = "团队信息")
+public class TeamsInfoResp implements Serializable {
+    @Schema(description = "总人数")
+    private int total;
+    @Schema(description = "团队A人数")
+    private Long teamACount;
+    @Schema(description = "团队B人数")
+    private Long teamBCount;
+    @Schema(description = "团队A金币抽成占比")
+    private String proportionA;
+    @Schema(description = "团队B金币抽成占比")
+    private String proportionB;
+}

+ 15 - 0
src/main/java/com/xs/core/model/user/req/GoldCoinCompetitionRankingReq.java

@@ -0,0 +1,15 @@
+package com.xs.core.model.user.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@Schema(description = "金币排行榜请求")
+public class GoldCoinCompetitionRankingReq implements Serializable {
+    @Schema(name = "limit", description = "排行榜人数", defaultValue = "10", example = "10")
+    private Integer limit = 10;
+    @Schema(name = "invitees", description = "最小邀请人数", defaultValue = "5000", example = "500")
+    private Integer invitees = 5000;
+}

+ 86 - 0
src/main/java/com/xs/core/model/user/resp/UserCompetitionResp.java

@@ -0,0 +1,86 @@
+package com.xs.core.model.user.resp;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 竞赛排行榜
+ */
+@Data
+@Schema(name = "UserCompetitionResp", description = "竞赛排行榜")
+public class UserCompetitionResp {
+    /**
+     * 排名
+     */
+    @Schema(description = "排名")
+    private int ranking;
+
+    /**
+     * tg小程序id
+     */
+    @Schema(description = "tg小程序id")
+    private String tgId;
+
+    /**
+     * tg账号
+     */
+    @Schema(description = "tg账号")
+    private String tgAccount;
+
+    /**
+     * 名
+     */
+    @Schema(description = "名")
+    private String firstName;
+
+    /**
+     * 姓
+     */
+    @Schema(description = "姓")
+    private String lastName;
+
+    /**
+     * 昵称
+     */
+    @Schema(description = "昵称")
+    private String nickname;
+
+    /**
+     * 真实姓名
+     */
+    @Schema(description = "真实姓名")
+    private String realName;
+
+    /**
+     * 头像
+     */
+    @Schema(description = "头像")
+    private String avatar;
+
+    /**
+     * 介绍人_id
+     */
+    @Schema(description = "介绍人_id")
+    private String referrerId;
+
+    /**
+     * 空投数量
+     */
+    @Schema(description = "空投数量")
+    private Integer airdropCoin;
+
+    /**
+     * 金币余额
+     */
+    @Schema(description = "金币余额")
+    private BigDecimal goldCoinAmount;
+
+    /**
+     * 金币总数量
+     */
+    @Schema(description = "金币总数量")
+    private BigDecimal goldCoinTotalHis;
+
+}

+ 56 - 25
src/main/java/com/xs/core/mq/consumer/GoldCoinProductMessageConsumer.java

@@ -2,28 +2,27 @@ package com.xs.core.mq.consumer;
 
 import cn.hutool.core.util.ObjUtil;
 import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.rabbitmq.client.Channel;
 import com.xs.core.common.constant.GoldCoinConstant;
 import com.xs.core.config.mq.RabbitMQConfig;
 import com.xs.core.model.coin.dto.GoldRateUpgradesState;
 import com.xs.core.model.coin.msg.CoinProducerMessage;
-import com.xs.core.model.coin.entity.GoldCoinProdRecord;
 import com.xs.core.model.coin.entity.GoldCoinProdState;
 import com.xs.core.mq.producer.GoldCoinMessageProducer;
-import com.xs.core.service.coin.IGoldCoinProdRecordService;
-import com.xs.core.service.coin.IGoldCoinProdStateService;
 import com.xs.core.service.redis.RedisService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.amqp.core.Message;
 import org.springframework.amqp.rabbit.annotation.RabbitListener;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Component;
 
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.time.Instant;
-import java.time.LocalDateTime;
+import java.util.concurrent.TimeUnit;
 
 @Component
 @Slf4j
@@ -31,15 +30,26 @@ public class GoldCoinProductMessageConsumer {
     @Autowired
     private RedisService redisService;
 
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
     @Autowired
     private GoldCoinMessageProducer goldCoinProducer;
 
     @RabbitListener(queues = RabbitMQConfig.GOLD_COIN_CALCULATION_QUEUE, concurrency = "10")
     public void handleCalculation(Message m, Channel channel) throws IOException {
         CoinProducerMessage message = null;
+        boolean completed = false;
+        CoinProducerMessage newMessage = null;
         try {
             String msg = new String(m.getBody());
             message = JSON.parseObject(msg, CoinProducerMessage.class);
+            boolean processed = isMessageProcessed(message.getMsgId());
+            if (processed) {
+                // 消息已经处理过,直接确认
+                channel.basicAck(m.getMessageProperties().getDeliveryTag(), false);
+                return;
+            }
             // 处理计算逻辑
             if (shouldContinueCalculation(message)) {
                 GoldCoinProdState coinProdState = calculateAndUpdateGame(message);
@@ -47,27 +57,48 @@ public class GoldCoinProductMessageConsumer {
                     // 更新Redis中的金币产出状态
                     redisService.set(GoldCoinConstant.GOLD_COIN_STATE_KEY + message.getUserId(), coinProdState);
                     // 发送下一次延迟计算消息
-                    if (!coinProdState.isCompleted()) {
-                        CoinProducerMessage newMessage = new CoinProducerMessage();
+                    if (!coinProdState.isCompleted() || coinProdState.getResidueTimestamp() > 0) {
+                        newMessage = new CoinProducerMessage();
                         BeanUtils.copyProperties(coinProdState, newMessage);
-                        goldCoinProducer.sendDelayCalculationMessage(newMessage);
+                        //重新设置消息id
+                        newMessage.setMsgId(IdWorker.getIdStr());
+
                     } else {
                         //发送金币结算消息
-                        log.info("Send gold coin complete message for user: {}", message.getUserId());
-                        CoinProducerMessage newMessage = new CoinProducerMessage();
+                        newMessage = new CoinProducerMessage();
                         BeanUtils.copyProperties(coinProdState, newMessage);
-                        goldCoinProducer.sendCalculationCompleteMessage(newMessage);
+                        //重新设置消息id
+                        newMessage.setMsgId(IdWorker.getIdStr());
+                        completed = true;
                     }
-                } else {
-                    // 记录错误日志,可以考虑报警
-                    log.error("Failed to calculate for user: {}", message.getUserId());
                 }
             }
             channel.basicAck(m.getMessageProperties().getDeliveryTag(), false);
         } catch (Exception e) {
-            log.error("Failed to handle calculation for user: " + message.getUserId(), e);
-            handleCalculationError(message);
+            log.error("Failed to handle calculation for user: {}", message.getUserId(), e);
+            //删除消息的处理状态
+            deleteMessageProcessed(message.getMsgId());
+            // 拒绝消息 并重新入队
+            channel.basicNack(m.getMessageProperties().getDeliveryTag(), false, true);
         }
+        //如过没有发生异常才发送下一次延迟计算消息
+        if (completed) {
+            goldCoinProducer.sendCalculationCompleteMessage(newMessage);
+        } else {
+            goldCoinProducer.sendDelayCalculationMessage(newMessage);
+        }
+    }
+
+    // 使用Redis做幂等性检查
+    private boolean isMessageProcessed(String messageId) {
+        String key = GoldCoinConstant.GOLD_COIN_CALCULATE_PROCESSED_MESSAGE + messageId;
+        return Boolean.FALSE.equals(redisTemplate.opsForValue()
+                .setIfAbsent(key, "1", 20, TimeUnit.MINUTES));
+    }
+
+    private void deleteMessageProcessed(String messageId) {
+        String key = GoldCoinConstant.GOLD_COIN_CALCULATE_PROCESSED_MESSAGE + messageId;
+        redisTemplate.delete(key);
     }
 
     private GoldCoinProdState calculateAndUpdateGame(CoinProducerMessage message) {
@@ -90,7 +121,7 @@ public class GoldCoinProductMessageConsumer {
      * @param goldCoinProdState
      */
     private GoldCoinProdState calculateGoldCoin(GoldCoinProdState goldCoinProdState) {
-        log.info("计算金币产出======>{}", goldCoinProdState.getUserId());
+        log.info("计算金币产出======>用户Id:{},批次Id:{}", goldCoinProdState.getUserId(), goldCoinProdState.getBatchId());
         String rateUpgradesKey = GoldCoinConstant.GOLD_COIN_RATE_UPGRADES_KEY + goldCoinProdState.getUserId();
         //拿到速率升级状态
         GoldRateUpgradesState upgradesState = redisService.get(rateUpgradesKey, GoldRateUpgradesState.class);
@@ -185,14 +216,14 @@ public class GoldCoinProductMessageConsumer {
         return message.getRunning() && message.getResidueTimestamp() > 0;
     }
 
-    private void handleCalculationError(CoinProducerMessage message) {
-        if (message.getRetryCount() < 3) {
-            message.setRetryCount(message.getRetryCount() + 1);
-            goldCoinProducer.sendDelayCalculationMessage(message);
-        } else {
-            // 记录错误日志,可以考虑报警
-            log.error("Failed to calculate after 3 retries for user: {}", message.getUserId());
-        }
-    }
+//    private void handleCalculationError(CoinProducerMessage message) {
+//        if (message.getRetryCount() < 3) {
+//            message.setRetryCount(message.getRetryCount() + 1);
+//            goldCoinProducer.sendDelayCalculationMessage(message);
+//        } else {
+//            // 记录错误日志,可以考虑报警
+//            log.error("Failed to calculate after 3 retries for user: {}", message.getUserId());
+//        }
+//    }
 
 }

+ 43 - 0
src/main/java/com/xs/core/mq/consumer/TeamShareMessageConsumer.java

@@ -0,0 +1,43 @@
+package com.xs.core.mq.consumer;
+
+import com.alibaba.fastjson2.JSON;
+import com.rabbitmq.client.Channel;
+import com.xs.core.config.mq.RabbitMQConfig;
+import com.xs.core.model.coin.msg.TeamShareMessage;
+import com.xs.core.service.team.TeamShareService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+/**
+ * 团队分享产出消息消费者
+ */
+@Component
+@Slf4j
+public class TeamShareMessageConsumer {
+
+    @Autowired
+    private TeamShareService teamShareService;
+
+    @RabbitListener(queues = RabbitMQConfig.GOLD_COIN_TEAM_SHARE_QUEUE, concurrency = "5")
+    public void handleTeamShare(Message m, Channel channel) throws IOException {
+        try {
+            String s = new String(m.getBody());
+            log.info("Received TeamShare message: {}", s);
+            TeamShareMessage teamShareMessage = JSON.parseObject(s, TeamShareMessage.class);
+            if (teamShareMessage != null) {
+                teamShareService.teamShareGoldCoinSettlement(teamShareMessage);
+            }
+            // 处理完成逻辑
+            channel.basicAck(m.getMessageProperties().getDeliveryTag(), false);
+        } catch (Exception e) {
+            // 处理失败逻辑 (可以选择重新入队或丢弃消息)
+            log.error("Error handleTeamShare message: {}", e.getMessage());
+            channel.basicNack(m.getMessageProperties().getDeliveryTag(), false, true);
+        }
+    }
+}

+ 61 - 29
src/main/java/com/xs/core/mq/producer/GoldCoinMessageProducer.java

@@ -1,15 +1,20 @@
 package com.xs.core.mq.producer;
 
+import cn.hutool.core.util.RandomUtil;
 import com.alibaba.fastjson2.JSON;
 import com.xs.core.config.mq.RabbitMQConfig;
 import com.xs.core.model.coin.msg.CoinProducerMessage;
+import com.xs.core.model.coin.msg.RetryableMessage;
+import com.xs.core.model.coin.msg.TeamShareMessage;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.core.MessageDeliveryMode;
 import org.springframework.amqp.rabbit.core.RabbitTemplate;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.function.*;
 
 @Component
 @Slf4j
@@ -17,52 +22,79 @@ public class GoldCoinMessageProducer {
     @Autowired
     private RabbitTemplate rabbitTemplate;
 
+    private static final int MAX_RETRY_COUNT = 3;
+    private static final int BASE_DELAY = 1000; // 1 second
+
     /**
      * 发送开始游戏的消息
      */
     public void sendDelayCalculationMessage(CoinProducerMessage coinProducerMessage) {
-        try {
-            String messageStr = JSON.toJSONString(coinProducerMessage);
-            rabbitTemplate.convertAndSend(RabbitMQConfig.GOLD_COIN_CALCULATION_EXCHANGE, RabbitMQConfig.GOLD_COIN_CALCULATION_QUEUE, messageStr, msg -> {
+        sendMessageWithRetry(coinProducerMessage, MAX_RETRY_COUNT, m -> {
+            String jsonString = JSON.toJSONString(m);
+            rabbitTemplate.convertAndSend(RabbitMQConfig.GOLD_COIN_CALCULATION_EXCHANGE, RabbitMQConfig.GOLD_COIN_CALCULATION_QUEUE, jsonString, msg -> {
                 //延时10秒
                 msg.getMessageProperties().setHeader("x-delay", 10 * 1000L);
                 return msg;
             });
-        } catch (Exception e) {
-            log.error(e.getMessage(), e);
-            // 重试机制
-            handleSendFailure(coinProducerMessage);
-        }
-    }
-
-    private void handleSendFailure(CoinProducerMessage message) {
-        if (message.getRetryCount() < 3) {
-            message.setRetryCount(message.getRetryCount() + 1);
-            // 使用指数退避策略
-            int delay = (int) Math.pow(2, message.getRetryCount()) * 1000;
-            // 可以使用线程池来处理重试
-            CompletableFuture.delayedExecutor(delay, TimeUnit.MILLISECONDS)
-                    .execute(() -> sendDelayCalculationMessage(message));
-        } else {
-            // 记录失败日志,可以考虑报警
-            log.error("Failed to send message after 3 retries for user: {}", message.getUserId());
-        }
+        });
     }
 
     /**
      * 发送计算完成的消息
      *
-     * @param coinProducerMessage
+     * @param coinProducerMessage 计算完成的消息
      */
     public void sendCalculationCompleteMessage(CoinProducerMessage coinProducerMessage) {
+        sendMessageWithRetry(coinProducerMessage, MAX_RETRY_COUNT, m -> {
+            rabbitTemplate.convertAndSend(RabbitMQConfig.GOLD_COIN_COMPLETE_EXCHANGE, RabbitMQConfig.GOLD_COIN_COMPLETE_QUEUE, JSON.toJSONString(m));
+            log.info("发送计算结束的消息: {}", JSON.toJSONString(m));
+        });
+    }
+
+    /**
+     * 发送团队奖励的消息
+     *
+     * @param teamShareMessage
+     */
+    public void sendTeamShareMessage(TeamShareMessage teamShareMessage) {
+        sendMessageWithRetry(teamShareMessage, MAX_RETRY_COUNT, m -> rabbitTemplate.convertAndSend(RabbitMQConfig.GOLD_COIN_TEAM_SHARE_EXCHANGE, RabbitMQConfig.GOLD_COIN_TEAM_SHARE_QUEUE, JSON.toJSONString(m), msg -> {
+            //延时10秒
+            msg.getMessageProperties().setHeader("x-delay", 10 * 1000L);
+            return msg;
+        }));
+    }
+
+    /**
+     * 通用的消息发送方法
+     *
+     * @param message    要发送的消息
+     * @param maxRetries 最大重试次数
+     * @param sendAction 发送消息的动作
+     * @param <T>        消息类型
+     */
+    public <T> void sendMessageWithRetry(T message, int maxRetries, Consumer<T> sendAction) {
         try {
-            String messageStr = JSON.toJSONString(coinProducerMessage);
-            rabbitTemplate.convertAndSend(RabbitMQConfig.GOLD_COIN_COMPLETE_EXCHANGE, RabbitMQConfig.GOLD_COIN_COMPLETE_QUEUE, messageStr);
-            log.info("发送计算结束的消息: {}", messageStr);
+            sendAction.accept(message);
         } catch (Exception e) {
-            e.printStackTrace();
-            // 重试机制
-            handleSendFailure(coinProducerMessage);
+            // 处理发送失败的逻辑
+//            log.error("Failed to send message: {}", message, e);
+            //handleSendFailure(message, maxRetries, sendAction);
+        }
+    }
+
+    /**
+     * 处理发送失败的通用方法
+     */
+    private <T> void handleSendFailure(T message, int maxRetries, Consumer<T> sendAction) {
+        if (message instanceof RetryableMessage retryableMessage && ((RetryableMessage) message).getRetryCount() < maxRetries) {
+            retryableMessage.setRetryCount(retryableMessage.getRetryCount() + 1);
+
+            // 使用指数退避策略
+            int delay = (int) Math.pow(2, retryableMessage.getRetryCount()) * BASE_DELAY;
+            CompletableFuture.delayedExecutor(delay, TimeUnit.MILLISECONDS).execute(() -> sendMessageWithRetry((T) retryableMessage, maxRetries, sendAction));
+        } else {
+            // 记录失败日志,可以考虑报警
+            log.error("Failed to send message after {} retries: {}", maxRetries, message);
         }
     }
 }

+ 20 - 0
src/main/java/com/xs/core/service/Impl/coin/BoostTemporaryRateRecordServiceImpl.java

@@ -0,0 +1,20 @@
+package com.xs.core.service.Impl.coin;
+
+import com.xs.core.model.coin.entity.BoostTemporaryRateRecord;
+import com.xs.core.mapper.coin.BoostTemporaryRateRecordMapper;
+import com.xs.core.service.coin.IBoostTemporaryRateRecordService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 用户临时速率提升记录 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+@Service
+public class BoostTemporaryRateRecordServiceImpl extends ServiceImpl<BoostTemporaryRateRecordMapper, BoostTemporaryRateRecord> implements IBoostTemporaryRateRecordService {
+
+}

+ 8 - 1
src/main/java/com/xs/core/service/Impl/coin/GoldCoinProdRecordServiceImpl.java

@@ -19,11 +19,18 @@ import org.springframework.stereotype.Service;
 public class GoldCoinProdRecordServiceImpl extends ServiceImpl<GoldCoinProdRecordMapper, GoldCoinProdRecord> implements IGoldCoinProdRecordService {
 
     @Override
-    public GoldCoinProdRecord getNoReciviedGoldCoinProdRecordByUserId(Long id) {
+    public GoldCoinProdRecord getNoReceivedGoldCoinProdRecordByUserId(Long id) {
         LambdaQueryWrapper<GoldCoinProdRecord> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(GoldCoinProdRecord::getUserId, id);
         queryWrapper.eq(GoldCoinProdRecord::getReceiving, Boolean.FALSE);
         queryWrapper.orderByDesc(GoldCoinProdRecord::getYieldTime).last("limit 1");
         return getOne(queryWrapper);
     }
+
+    @Override
+    public GoldCoinProdRecord getGoldCoinProdRecordByBatchId(String batchId) {
+        LambdaQueryWrapper<GoldCoinProdRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(GoldCoinProdRecord::getBatchId, batchId);
+        return getOne(queryWrapper);
+    }
 }

+ 1 - 3
src/main/java/com/xs/core/service/Impl/coin/GoldCoinProdStateServiceImpl.java

@@ -1,8 +1,6 @@
 package com.xs.core.service.Impl.coin;
 
 import cn.hutool.core.util.ObjUtil;
-import cn.hutool.core.util.StrUtil;
-import com.alibaba.fastjson2.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.xs.core.common.constant.GoldCoinConstant;
 import com.xs.core.model.coin.entity.GoldCoinProdRecord;
@@ -58,7 +56,7 @@ public class GoldCoinProdStateServiceImpl extends ServiceImpl<GoldCoinProdStateM
                 resp.setStatus(1);
             } else {
                 //判断是否存在待领取的金币
-                GoldCoinProdRecord record = recordService.getNoReciviedGoldCoinProdRecordByUserId(userId);
+                GoldCoinProdRecord record = recordService.getNoReceivedGoldCoinProdRecordByUserId(userId);
                 if (ObjUtil.isNotNull(record)) {
                     resp.setStatus(2);
                 } else {

+ 44 - 7
src/main/java/com/xs/core/service/Impl/coin/GoldCoinServiceImpl.java

@@ -3,6 +3,7 @@ package com.xs.core.service.Impl.coin;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.xs.core.common.constant.GoldCoinConstant;
 import com.xs.core.common.content.UserContext;
 import com.xs.core.common.content.UserContextHolder;
@@ -10,11 +11,9 @@ import com.xs.core.common.enums.CoinTransactionCategoryEnum;
 import com.xs.core.common.enums.CoinTransactionTypeEnum;
 import com.xs.core.common.validation.CheckUtils;
 import com.xs.core.model.coin.dto.GoldRateUpgradesState;
-import com.xs.core.model.coin.entity.CoinSpeedUpgradesRules;
-import com.xs.core.model.coin.entity.GoldCoinProdRecord;
+import com.xs.core.model.coin.entity.*;
 import com.xs.core.model.coin.msg.CoinProducerMessage;
-import com.xs.core.model.coin.entity.GoldCoinProdState;
-import com.xs.core.model.coin.entity.UserCoinSpeedUpgradesRecord;
+import com.xs.core.model.coin.msg.TeamShareMessage;
 import com.xs.core.model.coin.req.CoinSpeedUpgradesRulesReq;
 import com.xs.core.model.coin.req.TemporaryRateReq;
 import com.xs.core.model.coin.resp.BoostResidueTimesResp;
@@ -36,6 +35,7 @@ import java.math.RoundingMode;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
 @Service
 @Slf4j
@@ -61,6 +61,9 @@ public class GoldCoinServiceImpl implements GoldCoinService {
     @Autowired
     private ICoinSpeedUpgradesRulesService speedRulesService;
 
+    @Autowired
+    private IBoostTemporaryRateRecordService boostTemporaryRateRecordService;
+
     @Override
     @Transactional
     public void startProductGoldCoin() {
@@ -83,13 +86,15 @@ public class GoldCoinServiceImpl implements GoldCoinService {
         //4、发送延时循环计算的消息
         CoinProducerMessage message = new CoinProducerMessage();
         BeanUtils.copyProperties(goldCoinProdState, message);
+        //生成唯一的消息id
+        message.setMsgId(IdWorker.getIdStr());
         messageProducer.sendDelayCalculationMessage(message);
     }
 
     private void checkProductState(GoldCoinProdState goldCoinProdState, UserContext userContext) {
         CheckUtils.throwIf(Boolean.TRUE.equals(goldCoinProdState.getRunning()), "金币产出中,不能重复开启");
         //若是非运行状态 则检查待领取的金币
-        GoldCoinProdRecord record = recordService.getNoReciviedGoldCoinProdRecordByUserId(userContext.getId());
+        GoldCoinProdRecord record = recordService.getNoReceivedGoldCoinProdRecordByUserId(userContext.getId());
         CheckUtils.throwIf(ObjUtil.isNotNull(record) && !record.getReceiving(), "待领取的金币未领取,不能重复开启");
     }
 
@@ -108,10 +113,13 @@ public class GoldCoinServiceImpl implements GoldCoinService {
         goldCoinProdState.setLastTimestamp(secondsTimestamp);
         goldCoinProdState.setResidueTimestamp(GoldCoinConstant.GOLD_COIN_PRODUCT_TIME);
         goldCoinProdState.setCurrentValue(BigDecimal.ZERO);
+        goldCoinProdState.setTemporaryRate("0");
         //2、获取永久速率
         UserCoinSpeedUpgradesRecord speedUpgradesRecord = speedRecordService.getSpeedUpgradesRecordByUserId(userContext.getId());
         goldCoinProdState.setCurrentRate(speedUpgradesRecord.getProductRate());
         goldCoinProdState.setDurableRate(speedUpgradesRecord.getProductRate());
+        //设置批次id
+        goldCoinProdState.setBatchId(IdWorker.getIdStr());
     }
 
     /**
@@ -145,6 +153,8 @@ public class GoldCoinServiceImpl implements GoldCoinService {
         goldCoinProdState.setLastTimestamp(secondsTimestamp);
         goldCoinProdState.setDurableRate(speedUpgradesRecord.getProductRate());
         goldCoinProdState.setTemporaryRate("0");
+        //设置批次id
+        goldCoinProdState.setBatchId(IdWorker.getIdStr());
         return goldCoinProdState;
     }
 
@@ -158,7 +168,7 @@ public class GoldCoinServiceImpl implements GoldCoinService {
     public void collectGoldCoin() {
         UserContext userContext = UserContextHolder.getContext();
         //先查询是否存在待领取的金币
-        GoldCoinProdRecord record = recordService.getNoReciviedGoldCoinProdRecordByUserId(userContext.getId());
+        GoldCoinProdRecord record = recordService.getNoReceivedGoldCoinProdRecordByUserId(userContext.getId());
         GoldCoinProdState state = stateService.getGoldCoinProdStateByUser(userContext.getId());
         CheckUtils.throwIfNull(record, "没有待领取的金币");
         //把金币结算到用户的账户中
@@ -169,10 +179,19 @@ public class GoldCoinServiceImpl implements GoldCoinService {
         record.setReceivedTime(LocalDateTime.now());
         stateService.updateById(state);
         recordService.updateById(record);
+        //推送一条团队奖励消息
+        CompletableFuture.runAsync(() -> {
+            //推送一条团队奖励消息
+            TeamShareMessage teamShareMessage = new TeamShareMessage();
+            BeanUtils.copyProperties(record, teamShareMessage, "msgId");
+            teamShareMessage.setMsgId(IdWorker.getIdStr());
+            messageProducer.sendTeamShareMessage(teamShareMessage);
+        });
     }
 
     private void resetGoldCoinProdState(GoldCoinProdState state) {
         state.setCurrentValue(BigDecimal.ZERO);
+        state.setStartTimestamp(null);
         state.setLastTimestamp(null);
         state.setEndTimestamp(null);
         state.setResidueTimestamp(null);
@@ -282,6 +301,14 @@ public class GoldCoinServiceImpl implements GoldCoinService {
             upgradesState.setRateUpdateFlag(1);
             redisService.set(rateTemporarilyKey, upgradesState);
         }
+        //异步记录临时速率升级记录
+        CompletableFuture.runAsync(() -> {
+            BoostTemporaryRateRecord record = new BoostTemporaryRateRecord();
+            record.setUserId(context.getId());
+            record.setTemporaryRate(rateNum.toPlainString());
+            record.setBoostTime(LocalDateTime.now());
+            boostTemporaryRateRecordService.save(record);
+        });
     }
 
     @Override
@@ -308,6 +335,8 @@ public class GoldCoinServiceImpl implements GoldCoinService {
 
         //生成金币生产记录
         GoldCoinProdRecord goldCoinProdRecord = new GoldCoinProdRecord();
+        // 生成批次id
+        goldCoinProdRecord.setBatchId(coinProdState.getBatchId());
         goldCoinProdRecord.setUserId(coinProdState.getUserId());
         goldCoinProdRecord.setGoldCoinYieldTotal(coinProdState.getCurrentValue());
         goldCoinProdRecord.setReceiving(false);
@@ -315,10 +344,18 @@ public class GoldCoinServiceImpl implements GoldCoinService {
         goldCoinProdRecord.setStartTimestamp(coinProdState.getStartTimestamp());
         goldCoinProdRecord.setEndTimestamp(coinProdState.getEndTimestamp());
 
+        log.info("生成金币生产记录");
         // 重置金币产出状态
         coinProdState.setCompleted(Boolean.TRUE);
         coinProdState.setRunning(Boolean.FALSE);
-        recordService.save(goldCoinProdRecord);
+        //保存之前先检查是否存在一个批次的记录
+        GoldCoinProdRecord record = recordService.getGoldCoinProdRecordByBatchId(coinProdState.getBatchId());
+        if (ObjUtil.isNotNull(record)) {
+            //更新金币产出记录
+            record.setGoldCoinYieldTotal(coinProdState.getCurrentValue());
+        } else {
+            recordService.save(goldCoinProdRecord);
+        }
         stateService.updateById(coinProdState);
         //清除redis中的金币产出状态
         redisService.del(productKey);

+ 21 - 0
src/main/java/com/xs/core/service/Impl/competition/CompetitionServiceImpl.java

@@ -0,0 +1,21 @@
+package com.xs.core.service.Impl.competition;
+
+import com.xs.core.mapper.user.TgUserMapper;
+import com.xs.core.model.user.resp.UserCompetitionResp;
+import com.xs.core.service.competition.CompetitionService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class CompetitionServiceImpl implements CompetitionService {
+
+    @Autowired
+    private TgUserMapper userMapper;
+
+    @Override
+    public List<UserCompetitionResp> goldCoinCompetitionRanking(int limit, int invitees) {
+        return userMapper.goldCoinCompetitionRanking(limit, invitees);
+    }
+}

+ 28 - 0
src/main/java/com/xs/core/service/Impl/team/TeamInviteConfigServiceImpl.java

@@ -0,0 +1,28 @@
+package com.xs.core.service.Impl.team;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.xs.core.model.team.entity.TeamInviteConfig;
+import com.xs.core.mapper.team.TeamInviteConfigMapper;
+import com.xs.core.service.team.ITeamInviteConfigService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 团队邀请配置 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+@Service
+public class TeamInviteConfigServiceImpl extends ServiceImpl<TeamInviteConfigMapper, TeamInviteConfig> implements ITeamInviteConfigService {
+    @Override
+    public TeamInviteConfig getTeamInviteConfig() {
+        LambdaQueryWrapper<TeamInviteConfig> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.orderByDesc(TeamInviteConfig::getCreateTime);
+        queryWrapper.last("limit 1");
+        return getOne(queryWrapper);
+    }
+}
+

+ 20 - 0
src/main/java/com/xs/core/service/Impl/team/TeamInviteRewardRecordServiceImpl.java

@@ -0,0 +1,20 @@
+package com.xs.core.service.Impl.team;
+
+import com.xs.core.model.team.entity.TeamInviteRewardRecord;
+import com.xs.core.mapper.team.TeamInviteRewardRecordMapper;
+import com.xs.core.service.team.ITeamInviteRewardRecordService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 团队邀请奖励领取列表(每邀请一人奖励1000) 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+@Service
+public class TeamInviteRewardRecordServiceImpl extends ServiceImpl<TeamInviteRewardRecordMapper, TeamInviteRewardRecord> implements ITeamInviteRewardRecordService {
+
+}

+ 132 - 0
src/main/java/com/xs/core/service/Impl/team/TeamShareGoldCoinSettlementServiceImpl.java

@@ -0,0 +1,132 @@
+package com.xs.core.service.Impl.team;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import com.xs.core.model.coin.msg.TeamShareMessage;
+import com.xs.core.model.team.entity.TeamInviteConfig;
+import com.xs.core.model.team.entity.TeamInviteRewardRecord;
+import com.xs.core.model.team.entity.TeamShareRewardRecord;
+import com.xs.core.model.team.resp.TeamUserResp;
+import com.xs.core.model.team.resp.TeamsInfoResp;
+import com.xs.core.model.user.entity.TgUser;
+import com.xs.core.service.team.ITeamInviteConfigService;
+import com.xs.core.service.team.ITeamInviteRewardRecordService;
+import com.xs.core.service.team.ITeamShareRewardRecordService;
+import com.xs.core.service.team.TeamShareService;
+import com.xs.core.service.user.ITgUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+
+
+@Service
+@Slf4j
+public class TeamShareGoldCoinSettlementServiceImpl implements TeamShareService {
+    @Autowired
+    private ITgUserService userService;
+
+    @Autowired
+    private ITeamInviteConfigService teamInviteConfigService;
+
+    @Autowired
+    private ITeamInviteRewardRecordService teamInviteRewardRecordService;
+
+    @Autowired
+    private ITeamShareRewardRecordService teamShareRewardRecordService;
+
+    @Override
+    @Transactional
+    public void teamShareGoldCoinSettlement(TeamShareMessage teamShareMessage) {
+        Long userId = teamShareMessage.getUserId();
+        //获取用户的上一级邀请人 即a队
+        TgUser inviteA = userService.getInviterByUserId(userId);
+        TgUser inviteB = null;
+        if (inviteA != null && StrUtil.isNotBlank(inviteA.getReferrerId())) {
+            //获取a队的上一级邀请人 即b队
+            inviteB = userService.getById(inviteA.getReferrerId());
+        }
+        TeamInviteConfig config = teamInviteConfigService.getTeamInviteConfig();
+        List<TeamShareRewardRecord> saveList = new ArrayList<>();
+        if (ObjUtil.isNotNull(inviteA)) {
+            //发放a队金币
+            TeamShareRewardRecord teamA = new TeamShareRewardRecord();
+            teamA.setTargetId(inviteA.getId());
+            BigDecimal yieldTotal = teamShareMessage.getGoldCoinYieldTotal();
+            //计算a队的奖励
+            BigDecimal multiply = yieldTotal.multiply(new BigDecimal(config.getTeamAScale()));
+            teamA.setRewardAmount(multiply.setScale(2, RoundingMode.HALF_UP));
+            teamA.setTeamType("A");
+            teamA.setGoldCoinYieldTotal(yieldTotal);
+            teamA.setCreateTime(LocalDateTime.now());
+            teamA.setScale(config.getTeamAScale());
+            teamA.setInviteeId(userId);
+            teamA.setBatchId(teamShareMessage.getBatchId());
+            teamA.setClaimStatus(0);
+            saveList.add(teamA);
+        }
+        if (ObjUtil.isNotNull(inviteB)) {
+            //发放b队金币
+            TeamShareRewardRecord teamB = new TeamShareRewardRecord();
+            teamB.setTargetId(inviteB.getId());
+            BigDecimal yieldTotal = teamShareMessage.getGoldCoinYieldTotal();
+            BigDecimal multiply = yieldTotal.multiply(new BigDecimal(config.getTeamBScale()));
+            teamB.setRewardAmount(multiply.setScale(2, RoundingMode.HALF_UP));
+            teamB.setTeamType("B");
+            teamB.setGoldCoinYieldTotal(yieldTotal);
+            teamB.setCreateTime(LocalDateTime.now());
+            teamB.setScale(config.getTeamBScale());
+            teamB.setInviteeId(userId);
+            teamB.setBatchId(teamShareMessage.getBatchId());
+            teamB.setClaimStatus(0);
+            saveList.add(teamB);
+        }
+        teamShareRewardRecordService.saveBatch(saveList);
+    }
+
+    @Override
+    public void teamInviteGoldCoinRewardGeneral(Long inviterId, Long inviteeId) {
+        TeamInviteRewardRecord record = new TeamInviteRewardRecord();
+        record.setReceiverId(inviterId);
+        record.setInviteeId(inviteeId);
+        record.setRewardAmount(new BigDecimal(1000));
+        record.setCreateTime(LocalDateTime.now());
+        record.setClaimStatus(0);
+        teamInviteRewardRecordService.save(record);
+    }
+
+    @Override
+    public TeamsInfoResp getMyTeamsInfo(Long id) {
+        List<TeamUserResp> teamUserList = getMyTeamUserList(id, null);
+        TeamsInfoResp teamsInfoResp = new TeamsInfoResp();
+        TeamInviteConfig config = teamInviteConfigService.getTeamInviteConfig();
+        if (CollUtil.isNotEmpty(teamUserList)) {
+            teamsInfoResp.setTotal(teamUserList.size());
+            long countA = teamUserList.stream().filter(t -> "A".equals(t.getTeamType())).count();
+            long countB = teamUserList.stream().filter(t -> "B".equals(t.getTeamType())).count();
+            teamsInfoResp.setTeamACount(countA);
+            teamsInfoResp.setTeamBCount(countB);
+        } else {
+            teamsInfoResp.setTotal(0);
+            teamsInfoResp.setTeamACount(0L);
+            teamsInfoResp.setTeamBCount(0L);
+        }
+        if (ObjUtil.isNotNull(config)) {
+            teamsInfoResp.setProportionA(config.getTeamAScale());
+            teamsInfoResp.setProportionB(config.getTeamBScale());
+        }
+        return teamsInfoResp;
+    }
+
+    @Override
+    public List<TeamUserResp> getMyTeamUserList(Long userId, String teamType) {
+        return userService.getMyTeamUserList(userId, teamType);
+    }
+}

+ 20 - 0
src/main/java/com/xs/core/service/Impl/team/TeamShareRewardRecordServiceImpl.java

@@ -0,0 +1,20 @@
+package com.xs.core.service.Impl.team;
+
+import com.xs.core.model.team.entity.TeamShareRewardRecord;
+import com.xs.core.mapper.team.TeamShareRewardRecordMapper;
+import com.xs.core.service.team.ITeamShareRewardRecordService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 团队产出奖励记录 A B队抽成 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+@Service
+public class TeamShareRewardRecordServiceImpl extends ServiceImpl<TeamShareRewardRecordMapper, TeamShareRewardRecord> implements ITeamShareRewardRecordService {
+
+}

+ 22 - 5
src/main/java/com/xs/core/service/Impl/user/AppLoginServiceImpl.java

@@ -7,12 +7,15 @@ import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson2.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.xs.core.common.constant.ConstantConfig;
 import com.xs.core.common.content.UserContext;
 import com.xs.core.common.enums.DisEnableStatusEnum;
 import com.xs.core.common.validation.CheckUtils;
 import com.xs.core.model.user.entity.TgUser;
 import com.xs.core.model.user.req.AppLoginReq;
+import com.xs.core.service.team.ITeamInviteRewardRecordService;
+import com.xs.core.service.team.TeamShareService;
 import com.xs.core.service.user.AppLoginService;
 import com.xs.core.service.user.ITgUserService;
 import com.xs.core.utils.InviteCodeGenerator;
@@ -33,6 +36,8 @@ import java.util.concurrent.CompletableFuture;
 public class AppLoginServiceImpl implements AppLoginService {
     private final ITgUserService userService;
 
+    private final TeamShareService teamShareService;
+
     @Override
     public SaTokenInfo tgLogin(AppLoginReq loginReq) {
         LambdaQueryWrapper<TgUser> queryWrapper = new LambdaQueryWrapper<>();
@@ -63,17 +68,27 @@ public class AppLoginServiceImpl implements AppLoginService {
         if (StrUtil.isNotBlank(loginReq.getAge_limit())) {
             user.setAgeLimit(Integer.parseInt(loginReq.getAge_limit()));
         }
+        TgUser inviteUser;
         if (StrUtil.isNotBlank(loginReq.getShare_code())) {
             //如邀请码不为空 则写入邀请人信息
             LambdaQueryWrapper<TgUser> queryWrapper = new LambdaQueryWrapper<>();
             queryWrapper.eq(TgUser::getInviteCode, loginReq.getShare_code());
-            TgUser inviteUser = userService.getOne(queryWrapper);
+            inviteUser = userService.getOne(queryWrapper);
             if (ObjUtil.isNotNull(inviteUser)) {
                 user.setReferrerId(String.valueOf(inviteUser.getId()));
+                user.setPassengerFlowWay(2);
+                user.setId(IdWorker.getId());
             }
-            user.setPassengerFlowWay(2);
+        } else {
+            inviteUser = null;
         }
         userService.save(user);
+        CompletableFuture.runAsync(() -> {
+            //注册成功后发放邀请奖励
+            if (ObjUtil.isNotNull(inviteUser)) {
+                teamShareService.teamInviteGoldCoinRewardGeneral(inviteUser.getId(), user.getId());
+            }
+        });
         return user;
     }
 
@@ -94,11 +109,13 @@ public class AppLoginServiceImpl implements AppLoginService {
         userContext.setId(userId);
         // 记录登录时间以及ip等信息
         userContext.setExtraInfo(SpringWebUtils.getRequest());
+        user.setLoginIp(userContext.getIp());
+        user.setIpAddressConvert(userContext.getAddress());
+        //先登出 防止同一个账号重复登录生成多个token
+        StpUtil.logout(userId);
         // 登录并缓存用户信息
         StpUtil.login(userId, SaLoginConfig.setExtra(ConstantConfig.USER_CONTENT_EXTRA_KEY, JSON.toJSONString(userContext)));
-        CompletableFuture.runAsync(() -> {
-            userService.updateById(user);
-        });
+        CompletableFuture.runAsync(() -> userService.updateById(user));
         return StpUtil.getTokenInfo();
     }
 }

+ 12 - 0
src/main/java/com/xs/core/service/Impl/user/TgUserServiceImpl.java

@@ -3,6 +3,7 @@ package com.xs.core.service.Impl.user;
 import com.xs.core.common.enums.CoinTransactionCategoryEnum;
 import com.xs.core.common.enums.CoinTransactionTypeEnum;
 import com.xs.core.model.coin.entity.UserCoinTransaction;
+import com.xs.core.model.team.resp.TeamUserResp;
 import com.xs.core.model.user.entity.TgUser;
 import com.xs.core.mapper.user.TgUserMapper;
 import com.xs.core.service.coin.IUserCoinTransactionService;
@@ -13,6 +14,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.util.List;
 
 /**
  * <p>
@@ -54,4 +56,14 @@ public class TgUserServiceImpl extends ServiceImpl<TgUserMapper, TgUser> impleme
         updateById(user);
         userCoinTransactionService.save(record);
     }
+
+    @Override
+    public TgUser getInviterByUserId(Long userId) {
+        return getBaseMapper().getInviterByUserId(userId);
+    }
+
+    @Override
+    public List<TeamUserResp> getMyTeamUserList(Long userId, String teamType) {
+        return getBaseMapper().getMyTeamUserList(userId, teamType);
+    }
 }

+ 1 - 1
src/main/java/com/xs/core/service/coin/GoldCoinService.java

@@ -39,7 +39,7 @@ public interface GoldCoinService {
     GoldCoinProdStateResp getGoldCoinProdState();
 
     /**
-     * 金币产出结算
+     * 金币产出完成
      *
      * @param coinProdState
      */

+ 16 - 0
src/main/java/com/xs/core/service/coin/IBoostTemporaryRateRecordService.java

@@ -0,0 +1,16 @@
+package com.xs.core.service.coin;
+
+import com.xs.core.model.coin.entity.BoostTemporaryRateRecord;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 用户临时速率提升记录 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+public interface IBoostTemporaryRateRecordService extends IService<BoostTemporaryRateRecord> {
+
+}

+ 8 - 1
src/main/java/com/xs/core/service/coin/IGoldCoinProdRecordService.java

@@ -19,7 +19,14 @@ public interface IGoldCoinProdRecordService extends IService<GoldCoinProdRecord>
      * @param id
      * @return
      */
-    GoldCoinProdRecord getNoReciviedGoldCoinProdRecordByUserId(Long id);
+    GoldCoinProdRecord getNoReceivedGoldCoinProdRecordByUserId(Long id);
 
+    /**
+     * 金币产出记录 根据批次id查询
+     *
+     * @param batchId 批次id
+     * @return
+     */
+    GoldCoinProdRecord getGoldCoinProdRecordByBatchId(String batchId);
 
 }

+ 19 - 0
src/main/java/com/xs/core/service/competition/CompetitionService.java

@@ -0,0 +1,19 @@
+package com.xs.core.service.competition;
+
+import com.xs.core.model.user.resp.UserCompetitionResp;
+
+import java.util.List;
+
+/**
+ * 竞赛服务接口
+ */
+public interface CompetitionService {
+    /**
+     * 金币排行榜
+     *
+     * @param limit    限制
+     * @param invitees 最小邀请人数
+     * @return 用户金币排行信息
+     */
+    List<UserCompetitionResp> goldCoinCompetitionRanking(int limit, int invitees);
+}

+ 21 - 0
src/main/java/com/xs/core/service/team/ITeamInviteConfigService.java

@@ -0,0 +1,21 @@
+package com.xs.core.service.team;
+
+import com.xs.core.model.team.entity.TeamInviteConfig;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 团队邀请配置 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+public interface ITeamInviteConfigService extends IService<TeamInviteConfig> {
+    /**
+     * 获取团队邀请配置
+     *
+     * @return 团队邀请配置
+     */
+    TeamInviteConfig getTeamInviteConfig();
+}

+ 16 - 0
src/main/java/com/xs/core/service/team/ITeamInviteRewardRecordService.java

@@ -0,0 +1,16 @@
+package com.xs.core.service.team;
+
+import com.xs.core.model.team.entity.TeamInviteRewardRecord;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 团队邀请奖励领取列表(每邀请一人奖励1000) 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+public interface ITeamInviteRewardRecordService extends IService<TeamInviteRewardRecord> {
+
+}

+ 16 - 0
src/main/java/com/xs/core/service/team/ITeamShareRewardRecordService.java

@@ -0,0 +1,16 @@
+package com.xs.core.service.team;
+
+import com.xs.core.model.team.entity.TeamShareRewardRecord;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 团队产出奖励记录 A B队抽成 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-21
+ */
+public interface ITeamShareRewardRecordService extends IService<TeamShareRewardRecord> {
+
+}

+ 46 - 0
src/main/java/com/xs/core/service/team/TeamShareService.java

@@ -0,0 +1,46 @@
+package com.xs.core.service.team;
+
+import com.xs.core.model.coin.msg.TeamShareMessage;
+import com.xs.core.model.team.resp.TeamUserResp;
+import com.xs.core.model.team.resp.TeamsInfoResp;
+import com.xs.core.model.user.entity.TgUser;
+
+import java.util.List;
+
+/**
+ * 团队相关接口
+ */
+public interface TeamShareService {
+
+    /**
+     * 团队产出金币结算
+     *
+     * @param teamShareMessage 团队产出消息体
+     */
+    void teamShareGoldCoinSettlement(TeamShareMessage teamShareMessage);
+
+    /**
+     * 团队邀请奖励发放
+     *
+     * @param inviterId 邀请人id
+     * @param inviteeId 被邀请人id
+     */
+    void teamInviteGoldCoinRewardGeneral(Long inviterId, Long inviteeId);
+
+    /**
+     * 获取我的团队用户列表
+     *
+     * @param userId   用户id
+     * @param teamType 团队类型
+     * @return 我的团队用户列表
+     */
+    List<TeamUserResp> getMyTeamUserList(Long userId, String teamType);
+
+    /**
+     * 获取我的团队信息
+     *
+     * @param id
+     * @return
+     */
+    TeamsInfoResp getMyTeamsInfo(Long id);
+}

+ 12 - 0
src/main/java/com/xs/core/service/user/ITgUserService.java

@@ -2,10 +2,12 @@ package com.xs.core.service.user;
 
 import com.xs.core.common.enums.CoinTransactionCategoryEnum;
 import com.xs.core.common.enums.CoinTransactionTypeEnum;
+import com.xs.core.model.team.resp.TeamUserResp;
 import com.xs.core.model.user.entity.TgUser;
 import com.baomidou.mybatisplus.extension.service.IService;
 
 import java.math.BigDecimal;
+import java.util.List;
 
 /**
  * <p>
@@ -27,4 +29,14 @@ public interface ITgUserService extends IService<TgUser> {
      * @param userId
      */
     void coinTransaction(CoinTransactionTypeEnum type, CoinTransactionCategoryEnum category, String reason, String detail, BigDecimal amount, Long userId);
+
+    /**
+     * 获取用户的上一级邀请人
+     *
+     * @param userId
+     * @return
+     */
+    TgUser getInviterByUserId(Long userId);
+
+    List<TeamUserResp> getMyTeamUserList(Long userId, String teamType);
 }

+ 1 - 1
src/main/java/com/xs/core/utils/MyGenerator.java

@@ -57,7 +57,7 @@ public class MyGenerator {
 
     public static void main(String[] args) {
         // 项目名 例如app  web
-        generatorByBusinessModule("coin", new String[]{"b_user_coin_transaction"});
+        generatorByBusinessModule("team", new String[]{"b_team_invite_reward_record"});
     }
 
     /**

+ 1 - 1
src/main/resources/application-dev.yml

@@ -71,7 +71,7 @@ mybatis-plus:
   configuration:
     # MyBatis 自动映射策略
     # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
-    auto-mapping-behavior: PARTIAL
+    auto-mapping-behavior: FULL
     log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
     # 开启驼峰命名转换
     map-underscore-to-camel-case: true

+ 19 - 0
src/main/resources/mapper/BoostTemporaryRateRecordMapper.xml

@@ -0,0 +1,19 @@
+<?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.xs.core.mapper.coin.BoostTemporaryRateRecordMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.xs.core.model.coin.entity.BoostTemporaryRateRecord">
+        <id column="id" property="id" />
+        <result column="user_id" property="userId" />
+        <result column="boost_timestamp" property="boostTimestamp" />
+        <result column="boost_time" property="boostTime" />
+        <result column="temporary_rate" property="temporaryRate" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, user_id, boost_timestamp, boost_time, temporary_rate
+    </sql>
+
+</mapper>

+ 10 - 16
src/main/resources/mapper/GoldCoinProdRecordMapper.xml

@@ -1,23 +1,17 @@
 <?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.xs.core.mapper.coin.GoldCoinProdRecordMapper">
-
     <!-- 通用查询映射结果 -->
     <resultMap id="BaseResultMap" type="com.xs.core.model.coin.entity.GoldCoinProdRecord">
-        <result column="id" property="id" />
-        <result column="user_id" property="userId" />
-        <result column="gold_coin_yield_total" property="goldCoinYieldTotal" />
-        <result column="start_timestamp" property="startTimestamp" />
-        <result column="end_timestamp" property="endTimestamp" />
-        <result column="yield_time" property="yieldTime" />
-        <result column="receiving" property="receiving" />
-        <result column="received_time" property="receivedTime" />
-        <result column="remark" property="remark" />
+        <result column="id" property="id"/>
+        <result column="batch_id" property="batchId"/>
+        <result column="user_id" property="userId"/>
+        <result column="gold_coin_yield_total" property="goldCoinYieldTotal"/>
+        <result column="start_timestamp" property="startTimestamp"/>
+        <result column="end_timestamp" property="endTimestamp"/>
+        <result column="yield_time" property="yieldTime"/>
+        <result column="receiving" property="receiving"/>
+        <result column="received_time" property="receivedTime"/>
+        <result column="remark" property="remark"/>
     </resultMap>
-
-    <!-- 通用查询结果列 -->
-    <sql id="Base_Column_List">
-        id, user_id, gold_coin_yield_total, start_timestamp, end_timestamp, yield_time, receiving, received_time, remark
-    </sql>
-
 </mapper>

+ 1 - 0
src/main/resources/mapper/GoldCoinProdStateMapper.xml

@@ -4,6 +4,7 @@
     <!-- 通用查询映射结果 -->
     <resultMap id="BaseResultMap" type="com.xs.core.model.coin.entity.GoldCoinProdState">
         <result column="id" property="id"/>
+        <result column="batch_id" property="batchId"/>
         <result column="user_id" property="userId"/>
         <result column="current_value" property="currentValue"/>
         <result column="start_timestamp" property="startTimestamp"/>

+ 20 - 0
src/main/resources/mapper/TeamInviteConfigMapper.xml

@@ -0,0 +1,20 @@
+<?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.xs.core.mapper.team.TeamInviteConfigMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.xs.core.model.team.entity.TeamInviteConfig">
+        <id column="id" property="id" />
+        <result column="team_a_scale" property="teamAScale" />
+        <result column="team_b_scale" property="teamBScale" />
+        <result column="Invite_one_reward" property="inviteOneReward" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, team_a_scale, team_b_scale, Invite_one_reward, create_time, update_time
+    </sql>
+
+</mapper>

+ 14 - 0
src/main/resources/mapper/TeamInviteRewardRecordMapper.xml

@@ -0,0 +1,14 @@
+<?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.xs.core.mapper.team.TeamInviteRewardRecordMapper">
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.xs.core.model.team.entity.TeamInviteRewardRecord">
+        <id column="id" property="id"/>
+        <result column="receiver_id" property="receiverId"/>
+        <result column="invitee_id" property="inviteeId"/>
+        <result column="reward_amount" property="rewardAmount"/>
+        <result column="claim_status" property="claimStatus"/>
+        <result column="claim_time" property="claimTime"/>
+        <result column="create_time" property="createTime"/>
+    </resultMap>
+</mapper>

+ 18 - 0
src/main/resources/mapper/TeamShareRewardRecordMapper.xml

@@ -0,0 +1,18 @@
+<?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.xs.core.mapper.team.TeamShareRewardRecordMapper">
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.xs.core.model.team.entity.TeamShareRewardRecord">
+        <id column="id" property="id"/>
+        <result column="target_id" property="targetId"/>
+        <result column="invitee_id" property="inviteeId"/>
+        <result column="team_type" property="teamType"/>
+        <result column="reward_amount" property="rewardAmount"/>
+        <result column="gold_coin_yield_total" property="goldCoinYieldTotal"/>
+        <result column="batch_id" property="batchId"/>
+        <result column="scale" property="scale"/>
+        <result column="claim_status" property="claimStatus"/>
+        <result column="claim_time" property="claimTime"/>
+        <result column="create_time" property="createTime"/>
+    </resultMap>
+</mapper>

+ 171 - 0
src/main/resources/mapper/TgUserMapper.xml

@@ -37,4 +37,175 @@
         <result column="created_time" property="createdTime"/>
         <result column="updated_time" property="updatedTime"/>
     </resultMap>
+
+    <resultMap id="TeamUserRespResultMap" type="com.xs.core.model.team.resp.TeamUserResp">
+        <result column="tg_id" property="tgId"/>
+        <result column="tg_account" property="tgAccount"/>
+        <result column="first_name" property="firstName"/>
+        <result column="last_name" property="lastName"/>
+        <result column="nickname" property="nickname"/>
+        <result column="real_name" property="realName"/>
+        <result column="avatar" property="avatar"/>
+        <result column="passenger_flow_way" property="passengerFlowWay"/>
+        <result column="team_type" property="teamType"/>
+    </resultMap>
+
+
+    <resultMap id="UserCompetitionResultMap" type="com.xs.core.model.user.resp.UserCompetitionResp">
+        <result column="ranking" property="ranking"/>
+        <result column="tg_id" property="tgId"/>
+        <result column="tg_account" property="tgAccount"/>
+        <result column="first_name" property="firstName"/>
+        <result column="last_name" property="lastName"/>
+        <result column="nickname" property="nickname"/>
+        <result column="real_name" property="realName"/>
+        <result column="avatar" property="avatar"/>
+        <result column="referrer_id" property="referrerId"/>
+        <result column="airdrop_coin" property="airdropCoin"/>
+        <result column="gold_coin_amount" property="goldCoinAmount"/>
+        <result column="gold_coin_total_his" property="goldCoinTotalHis"/>
+    </resultMap>
+
+    <select id="getMyTeamUserList" resultMap="TeamUserRespResultMap">
+        WITH RECURSIVE team_hierarchy AS (
+        -- A队:直接被你邀请的用户
+        SELECT id,
+        tg_id,
+        tg_account,
+        first_name,
+        last_name,
+        nickname,
+        real_name,
+        avatar,
+        passenger_flow_way,
+        referrer_id,
+        'A' as team_type,
+        1 as level
+        FROM b_tg_user
+        WHERE referrer_id = #{userId}
+
+        UNION ALL
+
+        -- B队:被A队邀请的用户
+        SELECT u.id,
+        u.tg_id,
+        u.tg_account,
+        u.first_name,
+        u.last_name,
+        u.nickname,
+        u.real_name,
+        u.avatar,
+        u.passenger_flow_way,
+        u.referrer_id,
+        'B' as team_type,
+        h.level + 1
+        FROM b_tg_user u
+        JOIN team_hierarchy h ON u.referrer_id = h.id
+        -- 只获取A队成员邀请的用户 即b队的用户
+        WHERE h.level = 1 )
+
+        SELECT id,
+        tg_id,
+        tg_account,
+        first_name,
+        last_name,
+        nickname,
+        real_name,
+        avatar,
+        passenger_flow_way,
+        referrer_id,
+        team_type
+        FROM team_hierarchy h
+        <where>
+            <if test="teamType!=null">
+                and h.team_type=#{teamType}
+            </if>
+        </where>
+    </select>
+
+    <select id="countMyTeamUserList" resultType="int">
+        WITH RECURSIVE team_hierarchy AS (
+        -- A队:直接被你邀请的用户
+        SELECT id,
+        tg_id,
+        tg_account,
+        first_name,
+        last_name,
+        nickname,
+        real_name,
+        avatar,
+        passenger_flow_way,
+        referrer_id,
+        created_time,
+        'A' as team_type,
+        1 as level
+        FROM b_tg_user
+        WHERE referrer_id = #{userId}
+
+        UNION ALL
+
+        -- B队:被A队邀请的用户
+        SELECT u.id,
+        u.tg_id,
+        u.tg_account,
+        u.first_name,
+        u.last_name,
+        u.nickname,
+        u.real_name,
+        u.avatar,
+        u.passenger_flow_way,
+        u.referrer_id,
+        u.created_time,
+        'B' as team_type,
+        h.level + 1
+        FROM b_tg_user u
+        JOIN team_hierarchy h ON u.referrer_id = h.id
+        WHERE h.level = 1 -- 只获取A队成员邀请的用户 即b队的用户
+        )
+
+        SELECT count(id) as num
+        FROM team_hierarchy h
+        <where>
+            <if test="teamType!=null">
+                and h.team_type=#{teamType}
+            </if>
+        </where>
+    </select>
+
+    <select id="goldCoinCompetitionRanking" resultMap="UserCompetitionResultMap">
+        WITH invite_count AS (
+            -- 统计每个用户的邀请人数
+            SELECT referrer_id,
+                   COUNT(*) as invited_users_count
+            FROM b_tg_user
+            WHERE referrer_id IS NOT NULL
+            GROUP BY referrer_id
+            HAVING COUNT(*) >= #{invitees})
+
+        SELECT u.tg_id,
+               u.tg_account,
+               u.first_name,
+               u.last_name,
+               u.airdrop_coin,
+               u.nickname,
+               u.real_name,
+               u.gold_coin_amount,
+               u.referrer_id,
+               u.gold_coin_total_his,
+               u.avatar,
+               -- 添加排名
+               ROW_NUMBER() OVER (ORDER BY u.gold_coin_total_his DESC) as ranking
+        FROM b_tg_user u
+                 INNER JOIN invite_count ic ON u.id = ic.referrer_id
+        WHERE u.disable_flag = 0 -- 排除被禁用的用户
+        ORDER BY u.gold_coin_total_his desc
+        limit #{limit};
+    </select>
+
+    <select id="getInviterByUserId" resultMap="BaseResultMap">
+        select iu.*
+        from b_tg_user u
+                 inner join b_tg_user iu on u.referrer_id = iu.id
+        where u.id = #{userId};
+    </select>
 </mapper>