Răsfoiți Sursa

金币产出核心接口相关逻辑

xudm 2 luni în urmă
părinte
comite
dc41ae7c32
60 a modificat fișierele cu 2800 adăugiri și 26 ștergeri
  1. 28 4
      pom.xml
  2. 26 0
      src/main/java/com/xs/core/common/constant/GoldCoinConstant.java
  3. 6 1
      src/main/java/com/xs/core/common/content/UserContext.java
  4. 37 0
      src/main/java/com/xs/core/common/enums/CoinTransactionCategoryEnum.java
  5. 32 0
      src/main/java/com/xs/core/common/enums/CoinTransactionTypeEnum.java
  6. 54 0
      src/main/java/com/xs/core/config/mq/RabbitMQConfig.java
  7. 15 0
      src/main/java/com/xs/core/config/mybatis/MybatisPlusConfig.java
  8. 1 4
      src/main/java/com/xs/core/config/satoken/SaTokenConfigure.java
  9. 69 0
      src/main/java/com/xs/core/controller/coin/GoldCoinProductController.java
  10. 4 2
      src/main/java/com/xs/core/controller/user/LoginController.java
  11. 4 1
      src/main/java/com/xs/core/filter/GlobalExceptionHandler.java
  12. 7 2
      src/main/java/com/xs/core/filter/UserContextInterceptor.java
  13. 4 2
      src/main/java/com/xs/core/init/DatabaseInitializer.java
  14. 20 0
      src/main/java/com/xs/core/mapper/coin/CoinSpeedUpgradesRulesMapper.java
  15. 16 0
      src/main/java/com/xs/core/mapper/coin/GoldCoinProdRecordMapper.java
  16. 16 0
      src/main/java/com/xs/core/mapper/coin/GoldCoinProdStateMapper.java
  17. 16 0
      src/main/java/com/xs/core/mapper/coin/UserCoinSpeedUpgradesRecordMapper.java
  18. 16 0
      src/main/java/com/xs/core/mapper/coin/UserCoinTransactionMapper.java
  19. 14 0
      src/main/java/com/xs/core/model/ResponseResult.java
  20. 1 1
      src/main/java/com/xs/core/model/ResultCode.java
  21. 37 0
      src/main/java/com/xs/core/model/coin/dto/GoldRateUpgradesState.java
  22. 100 0
      src/main/java/com/xs/core/model/coin/entity/CoinSpeedUpgradesRules.java
  23. 85 0
      src/main/java/com/xs/core/model/coin/entity/GoldCoinProdRecord.java
  24. 105 0
      src/main/java/com/xs/core/model/coin/entity/GoldCoinProdState.java
  25. 60 0
      src/main/java/com/xs/core/model/coin/entity/UserCoinSpeedUpgradesRecord.java
  26. 93 0
      src/main/java/com/xs/core/model/coin/entity/UserCoinTransaction.java
  27. 77 0
      src/main/java/com/xs/core/model/coin/msg/CoinProducerMessage.java
  28. 13 0
      src/main/java/com/xs/core/model/coin/req/CoinSpeedUpgradesRulesReq.java
  29. 15 0
      src/main/java/com/xs/core/model/coin/req/TemporaryRateReq.java
  30. 43 0
      src/main/java/com/xs/core/model/coin/resp/GoldCoinProdStateResp.java
  31. 56 0
      src/main/java/com/xs/core/mq/consumer/GoldCoinCompleteMessageConsumer.java
  32. 191 0
      src/main/java/com/xs/core/mq/consumer/GoldCoinProductMessageConsumer.java
  33. 68 0
      src/main/java/com/xs/core/mq/producer/GoldCoinMessageProducer.java
  34. 267 0
      src/main/java/com/xs/core/service/Impl/RedisServiceImpl.java
  35. 26 0
      src/main/java/com/xs/core/service/Impl/coin/CoinSpeedUpgradesRulesServiceImpl.java
  36. 29 0
      src/main/java/com/xs/core/service/Impl/coin/GoldCoinProdRecordServiceImpl.java
  37. 72 0
      src/main/java/com/xs/core/service/Impl/coin/GoldCoinProdStateServiceImpl.java
  38. 325 0
      src/main/java/com/xs/core/service/Impl/coin/GoldCoinServiceImpl.java
  39. 29 0
      src/main/java/com/xs/core/service/Impl/coin/UserCoinSpeedUpgradesRecordServiceImpl.java
  40. 20 0
      src/main/java/com/xs/core/service/Impl/coin/UserCoinTransactionServiceImpl.java
  41. 1 1
      src/main/java/com/xs/core/service/Impl/user/AppLoginServiceImpl.java
  42. 37 0
      src/main/java/com/xs/core/service/Impl/user/TgUserServiceImpl.java
  43. 47 0
      src/main/java/com/xs/core/service/coin/GoldCoinService.java
  44. 26 0
      src/main/java/com/xs/core/service/coin/ICoinSpeedUpgradesRulesService.java
  45. 25 0
      src/main/java/com/xs/core/service/coin/IGoldCoinProdRecordService.java
  46. 21 0
      src/main/java/com/xs/core/service/coin/IGoldCoinProdStateService.java
  47. 18 0
      src/main/java/com/xs/core/service/coin/IUserCoinSpeedUpgradesRecordService.java
  48. 16 0
      src/main/java/com/xs/core/service/coin/IUserCoinTransactionService.java
  49. 201 0
      src/main/java/com/xs/core/service/redis/RedisService.java
  50. 15 1
      src/main/java/com/xs/core/service/user/ITgUserService.java
  51. 1 1
      src/main/java/com/xs/core/utils/MyGenerator.java
  52. 27 1
      src/main/resources/application-dev.yml
  53. 147 0
      src/main/resources/application-prod.yml
  54. 14 0
      src/main/resources/application.yml
  55. 32 0
      src/main/resources/mapper/CoinSpeedUpgradesRulesMapper.xml
  56. 23 0
      src/main/resources/mapper/GoldCoinProdRecordMapper.xml
  57. 18 0
      src/main/resources/mapper/GoldCoinProdStateMapper.xml
  58. 0 5
      src/main/resources/mapper/TgUserMapper.xml
  59. 12 0
      src/main/resources/mapper/UserCoinSpeedUpgradesRecordMapper.xml
  60. 22 0
      src/main/resources/mapper/UserCoinTransactionMapper.xml

+ 28 - 4
pom.xml

@@ -22,7 +22,6 @@
         <fastjson2.version>2.0.53</fastjson2.version>
         <knife4j.version>4.4.0</knife4j.version>
         <redisson.version>3.36.0</redisson.version>
-        <mybatis-plus.version>3.5.9</mybatis-plus.version>
         <velocity-engine.version>2.4.1</velocity-engine.version>
         <mysql-connector.version>8.4.0</mysql-connector.version>
         <esapi.version>2.5.3.1</esapi.version>
@@ -32,14 +31,28 @@
         <cosid.version>2.9.9</cosid.version>
         <mica-ip2region.version>3.2.6</mica-ip2region.version>
     </properties>
+
+    <!-- 使用 maven bom 管理依赖,减少版本号的冲突。-->
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-bom</artifactId>
+                <version>${mybatis-plus.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>
             <exclusions>
+                <!-- Exclude the Tomcat dependency -->
                 <exclusion>
-                    <groupId>org.apache.tomcat.embed</groupId>
-                    <artifactId>tomcat-embed-core</artifactId>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-tomcat</artifactId>
                 </exclusion>
             </exclusions>
         </dependency>
@@ -50,6 +63,7 @@
             <artifactId>spring-boot-starter-data-redis</artifactId>
         </dependency>
 
+        <!-- undertow替换Tomcat    -->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-undertow</artifactId>
@@ -59,7 +73,12 @@
         <dependency>
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
-            <version>${mybatis-plus.version}</version>
+        </dependency>
+
+        <!-- jdk 11+ 分页插件 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-jsqlparser</artifactId>
         </dependency>
 
         <!--  mybatis-plus 代码生成      -->
@@ -169,7 +188,12 @@
             <version>${mica-ip2region.version}</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-amqp</artifactId>
+        </dependency>
 
+        <!-- esapi 防xss攻击 -->
         <dependency>
             <groupId>org.owasp.esapi</groupId>
             <artifactId>esapi</artifactId>

+ 26 - 0
src/main/java/com/xs/core/common/constant/GoldCoinConstant.java

@@ -0,0 +1,26 @@
+package com.xs.core.common.constant;
+
+/**
+ * 金币生成想关的常量
+ */
+public interface GoldCoinConstant {
+    /**
+     * 金币产出状态key
+     */
+    String GOLD_COIN_STATE_KEY = "gold:coin:product:state:";
+    /**
+     * 金币速率升级记录 key
+     */
+    String GOLD_COIN_RATE_UPGRADES_KEY = "gold:coin:product:rate:upgrades:record:";
+
+    /**
+     * 金币产出时间,单位秒
+     */
+    long GOLD_COIN_PRODUCT_TIME = 600L;
+
+
+    /**
+     * 初始金币产出率
+     */
+    String GOLD_INIT_RATE = "0.1";
+}

+ 6 - 1
src/main/java/com/xs/core/common/content/UserContext.java

@@ -20,7 +20,7 @@ public class UserContext implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
     /**
-     * ID
+     * 用户id
      */
     private Long id;
 
@@ -90,6 +90,11 @@ public class UserContext implements Serializable {
      */
     private LocalDateTime loginTime;
 
+    /**
+     * 语言代码
+     */
+    private String languageCode;
+
     public void setExtraInfo(HttpServletRequest request) {
         this.ip = JakartaServletUtil.getClientIP(request);
         this.address = ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip));

+ 37 - 0
src/main/java/com/xs/core/common/enums/CoinTransactionCategoryEnum.java

@@ -0,0 +1,37 @@
+package com.xs.core.common.enums;
+
+/**
+ * 交易类型枚举
+ */
+public enum CoinTransactionCategoryEnum {
+    RESOURCE_PLACEMENT("空投", "RESOURCE_PLACEMENT"),
+    PRODUCT("挂机产出", "PRODUCT"),
+    SPEED_UPGRADES("速率升级", "SPEED_UPGRADES"),
+    BLOCKCHAIN_EXCHANGE("区块链兑换", "BLOCKCHAIN_EXCHANGE"),
+    TASK_REWARDS("任务奖励", "TASK_REWARDS"),
+    TEAM_SHARING("团队抽成", "TEAM_SHARING"),
+    SHARE_REWARDS("邀请奖励", "SHARE_REWARDS");
+    private String name;
+    private String code;
+
+    CoinTransactionCategoryEnum(String name, String code) {
+        this.name = name;
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+}

+ 32 - 0
src/main/java/com/xs/core/common/enums/CoinTransactionTypeEnum.java

@@ -0,0 +1,32 @@
+package com.xs.core.common.enums;
+
+/**
+ * 交易类型枚举
+ */
+public enum CoinTransactionTypeEnum {
+    ADD("增加", 1),
+    SUBTRACT("消耗", 2);
+    private String name;
+    private Integer code;
+
+    CoinTransactionTypeEnum(String name, Integer code) {
+        this.name = name;
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Integer getCode() {
+        return code;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+}

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

@@ -0,0 +1,54 @@
+package com.xs.core.config.mq;
+
+import org.springframework.amqp.core.*;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class RabbitMQConfig {
+    public static final String GOLD_COIN_CALCULATION_QUEUE = "gold.coin.calculation.queue";
+    public static final String GOLD_COIN_CALCULATION_EXCHANGE = "gold.coin.calculation.exchange";
+    public static final String GOLD_COIN_COMPLETE_QUEUE = "gold.coin.complete.queue";
+    public static final String GOLD_COIN_COMPLETE_EXCHANGE = "gold.coin.complete.exchange";
+
+    @Bean
+    public Queue goldCoinCompleteQueue() {
+        return new Queue(GOLD_COIN_COMPLETE_QUEUE, true, false, false);
+    }
+
+    @Bean
+    public DirectExchange goldCoinCompleteExchange() {
+        return new DirectExchange(GOLD_COIN_COMPLETE_EXCHANGE, true, false);
+    }
+
+    @Bean
+    public Binding bindingComplete() {
+        return BindingBuilder.bind(goldCoinCompleteQueue())
+                .to(goldCoinCompleteExchange())
+                .with(GOLD_COIN_COMPLETE_QUEUE);
+    }
+
+    @Bean
+    public CustomExchange delayExchange() {
+        Map<String, Object> args = new HashMap<>();
+        args.put("x-delayed-type", "direct");
+        return new CustomExchange(GOLD_COIN_CALCULATION_EXCHANGE, "x-delayed-message", true, false, args);
+    }
+
+    @Bean
+    public Queue goldCoinCalculationQueue() {
+        return new Queue(GOLD_COIN_CALCULATION_QUEUE, true, false, false);
+    }
+
+    @Bean
+    public Binding bindingCalculation() {
+        return BindingBuilder.bind(goldCoinCalculationQueue())
+                .to(delayExchange())
+                .with(GOLD_COIN_CALCULATION_QUEUE)
+                .noargs();
+    }
+
+}

+ 15 - 0
src/main/java/com/xs/core/config/mybatis/MybatisPlusConfig.java

@@ -1,6 +1,9 @@
 package com.xs.core.config.mybatis;
 
+import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -10,4 +13,16 @@ public class MybatisPlusConfig {
     public IdentifierGenerator idGenerator() {
         return new CosIdKeyGenerator();
     }
+
+    /**
+     * 添加分页插件
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor() {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 如果配置多个插件, 切记分页最后添加
+        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
+        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
+        return interceptor;
+    }
 }

+ 1 - 4
src/main/java/com/xs/core/config/satoken/SaTokenConfigure.java

@@ -29,10 +29,7 @@ public class SaTokenConfigure implements WebMvcConfigurer {
     @Override
     public void addInterceptors(InterceptorRegistry registry) {
         // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
-        registry.addInterceptor(new SaInterceptor(handle -> {
-                    StpUtil.checkLogin();
-                    log.info("进入到Sa-Token 拦截器拦截器");
-                }))
+        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                 .addPathPatterns("/**")
                 .excludePathPatterns("/login/coinLogin").order(-100);
         registry.addInterceptor(new UserContextInterceptor())

+ 69 - 0
src/main/java/com/xs/core/controller/coin/GoldCoinProductController.java

@@ -0,0 +1,69 @@
+package com.xs.core.controller.coin;
+
+import com.xs.core.model.ResponseResult;
+import com.xs.core.model.coin.entity.CoinSpeedUpgradesRules;
+import com.xs.core.model.coin.entity.GoldCoinProdState;
+import com.xs.core.model.coin.req.CoinSpeedUpgradesRulesReq;
+import com.xs.core.model.coin.req.TemporaryRateReq;
+import com.xs.core.model.coin.resp.GoldCoinProdStateResp;
+import com.xs.core.service.coin.GoldCoinService;
+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("/gold/coin/product")
+@Tag(name = "金币产出相关api")
+public class GoldCoinProductController {
+    @Autowired
+    private GoldCoinService goldCoinService;
+
+    @Operation(description = "开始产出金币", summary = "开始产出金币")
+    @PostMapping("/start")
+    public ResponseResult<?> startGoldCoinProduct() {
+        goldCoinService.startProductGoldCoin();
+        return ResponseResult.success();
+    }
+
+    @PostMapping("/getGoldCoinProductState")
+    @Operation(description = "获取金币产出状态", summary = "获取金币产出状态")
+    public ResponseResult<GoldCoinProdStateResp> getGoldCoinProductState() {
+        GoldCoinProdStateResp goldCoinProdState = goldCoinService.getGoldCoinProdState();
+        return ResponseResult.success(goldCoinProdState);
+    }
+
+
+    @PostMapping("/updateGoldCoinProductRate")
+    @Operation(description = "提升金币产出永久速率", summary = "提升金币产出永久速率")
+    public ResponseResult<?> updateGoldCoinProductPermanentRate(@RequestBody CoinSpeedUpgradesRulesReq req) {
+        goldCoinService.permanentBoostProductGoldCoinRate(req);
+        return ResponseResult.success();
+    }
+
+    @PostMapping("/getRateUpgradesRules")
+    @Operation(description = "获取金币产出速率升级规则", summary = "获取金币产出速率升级规则")
+    public ResponseResult<List<CoinSpeedUpgradesRules>> getRateUpgradesRules() {
+        List<CoinSpeedUpgradesRules> rules = goldCoinService.getRateUpgradesRulesByUser();
+        return ResponseResult.success(rules);
+    }
+
+    @Operation(description = "更新临时速率", summary = "更新临时速率")
+    @PostMapping("/updateGoldCoinProductTemporaryRate")
+    public ResponseResult<?> updateGoldCoinProductTemporaryRate(@RequestBody TemporaryRateReq req) {
+        goldCoinService.temporaryBoostProductGoldCoinRate(req);
+        return ResponseResult.success();
+    }
+
+    @Operation(description = "收取产出的币", summary = "收取产出的币")
+    @PostMapping("/collectGoldCoin")
+    public ResponseResult<?> collectGoldCoin() {
+        goldCoinService.collectGoldCoin();
+        return ResponseResult.success();
+    }
+}

+ 4 - 2
src/main/java/com/xs/core/controller/user/LoginController.java

@@ -1,6 +1,7 @@
 package com.xs.core.controller.user;
 
 import cn.dev33.satoken.stp.SaTokenInfo;
+import com.xs.core.model.ResponseResult;
 import com.xs.core.model.user.req.AppLoginReq;
 import com.xs.core.service.user.AppLoginService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -18,7 +19,8 @@ public class LoginController {
 
     @PostMapping("/coinLogin")
     @Operation(summary = "tg账号登录", description = "根据tg用户id进行登录")
-    public SaTokenInfo coinLogin(@Validated @RequestBody AppLoginReq req) {
-        return loginService.tgLogin(req);
+    public ResponseResult<SaTokenInfo> coinLogin(@Validated @RequestBody AppLoginReq req) {
+        SaTokenInfo saTokenInfo = loginService.tgLogin(req);
+        return ResponseResult.success(saTokenInfo);
     }
 }

+ 4 - 1
src/main/java/com/xs/core/filter/GlobalExceptionHandler.java

@@ -22,6 +22,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.multipart.MaxUploadSizeExceededException;
 import org.springframework.web.multipart.support.MissingServletRequestPartException;
+import org.springframework.web.servlet.resource.NoResourceFoundException;
 
 import java.util.List;
 
@@ -31,7 +32,7 @@ public class GlobalExceptionHandler {
     @ExceptionHandler({Exception.class, NotLoginException.class, HttpMessageNotReadableException.class,
             HttpMediaTypeNotSupportedException.class, MaxUploadSizeExceededException.class, NotPermissionException.class,
             HttpRequestMethodNotSupportedException.class, MethodArgumentNotValidException.class, BindException.class,
-            ConstraintViolationException.class, MissingServletRequestPartException.class, SaJwtException.class})
+            ConstraintViolationException.class, MissingServletRequestPartException.class, SaJwtException.class, NoResourceFoundException.class})
     @ResponseBody
     ResponseResult<?> serverExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception e) {
         if (e instanceof NotLoginException || e instanceof SaJwtException) {
@@ -63,6 +64,8 @@ public class GlobalExceptionHandler {
             return ResponseResult.failed(e.getMessage());
         } else if (e instanceof MissingServletRequestPartException) {
             return ResponseResult.failed("预期的文件缺失");
+        } else if (e instanceof NoResourceFoundException) {
+            return ResponseResult.failed("请求的接口不存在");
         } else {
             log.error("请求:{} 异常,异常信息:{}", request.getRequestURI(), ExceptionUtil.stacktraceToString(e));
             return ResponseResult.serverError(null);

+ 7 - 2
src/main/java/com/xs/core/filter/UserContextInterceptor.java

@@ -11,6 +11,8 @@ import jakarta.servlet.http.HttpServletResponse;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.servlet.HandlerInterceptor;
 
+import java.util.Optional;
+
 /**
  * 用户上下文拦截器
  */
@@ -18,10 +20,13 @@ import org.springframework.web.servlet.HandlerInterceptor;
 public class UserContextInterceptor implements HandlerInterceptor {
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-        log.info("UserContextInterceptor preHandle");
         String extra = (String) StpUtil.getExtra(ConstantConfig.USER_CONTENT_EXTRA_KEY);
         if (StrUtil.isNotBlank(extra)) {
-            UserContextHolder.setContext(JSON.parseObject(extra, UserContext.class));
+            UserContext userContext = JSON.parseObject(extra, UserContext.class);
+            //设置语言
+            String languageCode = Optional.ofNullable(request.getHeader("languageCode")).orElse("zh_CN");
+            userContext.setLanguageCode(languageCode);
+            UserContextHolder.setContext(userContext);
         }
         return HandlerInterceptor.super.preHandle(request, response, handler);
     }

+ 4 - 2
src/main/java/com/xs/core/init/DatabaseInitializer.java

@@ -1,6 +1,7 @@
 package com.xs.core.init;
 
 import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.stereotype.Component;
 
@@ -9,6 +10,7 @@ import java.sql.Connection;
 
 @Component
 @AllArgsConstructor
+@Slf4j
 public class DatabaseInitializer implements CommandLineRunner {
     private final DataSource dataSource;
 
@@ -16,8 +18,8 @@ public class DatabaseInitializer implements CommandLineRunner {
     public void run(String... args) throws Exception {
         try (Connection connection = dataSource.getConnection()) {
             // 执行一个简单的查询来初始化连接
-            connection.createStatement().execute("SELECT 1");
-            System.out.println("Database connection initialized");
+            connection.createStatement().execute("SELECT 1 from dual");
+            log.info("Database connection initialized successfully.");
         }
     }
 }

+ 20 - 0
src/main/java/com/xs/core/mapper/coin/CoinSpeedUpgradesRulesMapper.java

@@ -0,0 +1,20 @@
+package com.xs.core.mapper.coin;
+
+import com.xs.core.model.coin.entity.CoinSpeedUpgradesRules;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 金币产出速率升级规则 Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+public interface CoinSpeedUpgradesRulesMapper extends BaseMapper<CoinSpeedUpgradesRules> {
+
+    List<CoinSpeedUpgradesRules> getCoinSpeedUpgradesRulesByUserId(Long userId);
+
+}

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

@@ -0,0 +1,16 @@
+package com.xs.core.mapper.coin;
+
+import com.xs.core.model.coin.entity.GoldCoinProdRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 金币产出记录表 Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+public interface GoldCoinProdRecordMapper extends BaseMapper<GoldCoinProdRecord> {
+
+}

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

@@ -0,0 +1,16 @@
+package com.xs.core.mapper.coin;
+
+import com.xs.core.model.coin.entity.GoldCoinProdState;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 金币生产状态 Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+public interface GoldCoinProdStateMapper extends BaseMapper<GoldCoinProdState> {
+
+}

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

@@ -0,0 +1,16 @@
+package com.xs.core.mapper.coin;
+
+import com.xs.core.model.coin.entity.UserCoinSpeedUpgradesRecord;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 用户金币产出速率升级记录 Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+public interface UserCoinSpeedUpgradesRecordMapper extends BaseMapper<UserCoinSpeedUpgradesRecord> {
+
+}

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

@@ -0,0 +1,16 @@
+package com.xs.core.mapper.coin;
+
+import com.xs.core.model.coin.entity.UserCoinTransaction;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 用户金币交易流水记录表 Mapper 接口
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+public interface UserCoinTransactionMapper extends BaseMapper<UserCoinTransaction> {
+
+}

+ 14 - 0
src/main/java/com/xs/core/model/ResponseResult.java

@@ -33,6 +33,13 @@ public class ResponseResult<T> implements Serializable {
         this.msg = msg;
     }
 
+    /**
+     * 成功返回结果
+     */
+    public static <T> ResponseResult<T> success() {
+        return new ResponseResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg());
+    }
+
     /**
      * 成功返回结果
      *
@@ -151,4 +158,11 @@ public class ResponseResult<T> implements Serializable {
     public static <T> ResponseResult<T> serverError(T data) {
         return new ResponseResult<>(ResultCode.SERVER_ERROR.getCode(), ResultCode.SERVER_ERROR.getMsg(), data);
     }
+
+    /**
+     * 接口未找到
+     */
+    public static <T> ResponseResult<T> notFound() {
+        return new ResponseResult<>(ResultCode.NOT_FOUND.getCode(), ResultCode.NOT_FOUND.getMsg());
+    }
 }

+ 1 - 1
src/main/java/com/xs/core/model/ResultCode.java

@@ -5,7 +5,7 @@ package com.xs.core.model;
  * 枚举了一些常用API操作码
  */
 public enum ResultCode implements IErrorCode {
-    SUCCESS(1000, "操作成功"),
+    SUCCESS(1, "操作成功"),
     FAILED(500, "操作失败"),
 
     NOT_FOUND(404, "接口未找到"),

+ 37 - 0
src/main/java/com/xs/core/model/coin/dto/GoldRateUpgradesState.java

@@ -0,0 +1,37 @@
+package com.xs.core.model.coin.dto;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 速率升级状态 redis中使用 生产完毕会删除
+ */
+@Data
+public class GoldRateUpgradesState implements Serializable {
+    /**
+     * 当前速率
+     */
+    private String currentRate;
+    /**
+     * 永久速率
+     */
+    private String durableRate;
+    /**
+     * 临时速率
+     */
+    private String temporaryRate;
+    /**
+     * 速率升级的时间戳
+     */
+    private Long updateTimestamp;
+    /**
+     * 速率升级标志 0 开始游戏后为未升级  1 升级后待计算 2 升级后已计算过
+     */
+    private int rateUpdateFlag;
+
+    /**
+     * 判断当前生产批次内是否有临时速率 如过进行过临时速率升级 则不升级
+     */
+    private boolean hasTemporaryRate;
+}

+ 100 - 0
src/main/java/com/xs/core/model/coin/entity/CoinSpeedUpgradesRules.java

@@ -0,0 +1,100 @@
+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.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 金币产出速率升级规则
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+@Getter
+@Setter
+@TableName("b_coin_speed_upgrades_rules")
+public class CoinSpeedUpgradesRules extends Model<CoinSpeedUpgradesRules> {
+
+    /**
+     * id
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 消耗金币数量
+     */
+    @TableField("consume_gold_coin")
+    private Integer consumeGoldCoin;
+
+    /**
+     * 消耗真实货币数量
+     */
+    @TableField("real_money")
+    private Integer realMoney;
+
+    /**
+     * 数值 每秒产量
+     */
+    @TableField("numerical_value")
+    private String numericalValue;
+
+    /**
+     * 等级
+     */
+    @TableField("level")
+    private Integer level;
+
+    /**
+     * 创建人
+     */
+    @TableField("created_by")
+    private String createdBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+
+    /**
+     * 更新人
+     */
+    @TableField("updated_by")
+    private String updatedBy;
+
+    /**
+     * 更新时间
+     */
+    @TableField("updated_time")
+    private LocalDateTime updatedTime;
+
+    /**
+     * 数值 每小时产量
+     */
+    @TableField(exist = false)
+    private String numericalValueHour;
+
+
+    public String getNumericalValueHour() {
+        BigDecimal bigDecimal = new BigDecimal(numericalValue);
+        bigDecimal = bigDecimal.multiply(new BigDecimal(3600));
+        return bigDecimal.toPlainString();
+    }
+
+    @Override
+    public Serializable pkVal() {
+        return this.id;
+    }
+}

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

@@ -0,0 +1,85 @@
+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.math.BigDecimal;
+import java.time.LocalDateTime;
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 金币产出记录表
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+@Getter
+@Setter
+@TableName("b_gold_coin_prod_record")
+public class GoldCoinProdRecord extends Model<GoldCoinProdRecord> {
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 用户
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 金币产出
+     */
+    @TableField("gold_coin_yield_total")
+    private BigDecimal goldCoinYieldTotal;
+
+    /**
+     * 开始秒级时间戳
+     */
+    @TableField("start_timestamp")
+    private Long startTimestamp;
+
+    /**
+     * 结束秒级时间戳
+     */
+    @TableField("end_timestamp")
+    private Long endTimestamp;
+
+    /**
+     * 产出时间
+     */
+    @TableField("yield_time")
+    private LocalDateTime yieldTime;
+
+    /**
+     * 收获状态
+     */
+    @TableField("receiving")
+    private Boolean receiving;
+
+    /**
+     * 收获时间
+     */
+    @TableField("received_time")
+    private LocalDateTime receivedTime;
+
+    /**
+     * 备注
+     */
+    @TableField("remark")
+    private String remark;
+
+    @Override
+    public Serializable pkVal() {
+        return null;
+    }
+}

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

@@ -0,0 +1,105 @@
+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.math.BigDecimal;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 金币生产状态
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+@Getter
+@Setter
+@TableName("b_gold_coin_prod_state")
+public class GoldCoinProdState extends Model<GoldCoinProdState> {
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 用户id
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 当前生产的金币数
+     */
+    @TableField("current_value")
+    private BigDecimal currentValue;
+
+    /**
+     * 开始秒级时间戳
+     */
+    @TableField("start_timestamp")
+    private Long startTimestamp;
+
+    /**
+     * 结束秒级时间戳
+     */
+    @TableField("end_timestamp")
+    private Long endTimestamp;
+
+    /**
+     * 剩余的时间(秒)
+     */
+    @TableField("residue_timestamp")
+    private Long residueTimestamp;
+
+    /**
+     * 上次计算的时间戳
+     */
+    @TableField("last_timestamp")
+    private Long lastTimestamp;
+
+    /**
+     * 当前速率
+     */
+    @TableField("current_rate")
+    private String currentRate;
+
+    /**
+     * 永久速率
+     */
+    @TableField("durable_rate")
+    private String durableRate;
+
+    /**
+     * 临时速率
+     */
+    @TableField("temporary_rate")
+    private String temporaryRate;
+
+    /**
+     * 是否运行
+     */
+    @TableField("running")
+    private Boolean running;
+
+
+    /**
+     * 金币产出是否已完成
+     */
+    @TableField(exist = false)
+    private boolean completed = false;
+
+    @Override
+    public Serializable pkVal() {
+        return null;
+    }
+}

+ 60 - 0
src/main/java/com/xs/core/model/coin/entity/UserCoinSpeedUpgradesRecord.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-18
+ */
+@Getter
+@Setter
+@TableName("b_user_coin_speed_upgrades_record")
+public class UserCoinSpeedUpgradesRecord extends Model<UserCoinSpeedUpgradesRecord> {
+
+    /**
+     * 主键
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 用户id
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 产出速率 0.01
+     */
+    @TableField("product_rate")
+    private String productRate;
+
+    /**
+     * 速率层级
+     */
+    @TableField("speed_level")
+    private Integer speedLevel;
+
+    /**
+     * 升级时间
+     */
+    @TableField("upgrades_time")
+    private LocalDateTime upgradesTime;
+
+    @Override
+    public Serializable pkVal() {
+        return this.id;
+    }
+}

+ 93 - 0
src/main/java/com/xs/core/model/coin/entity/UserCoinTransaction.java

@@ -0,0 +1,93 @@
+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.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * <p>
+ * 用户金币交易流水记录表
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+@Getter
+@Setter
+@TableName("b_user_coin_transaction")
+public class UserCoinTransaction extends Model<UserCoinTransaction> {
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_ID)
+    private Long id;
+
+    /**
+     * 用户ID
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 交易类型:1-增加,2-消耗
+     */
+    @TableField("transaction_type")
+    private Integer transactionType;
+
+    /**
+     * 交易金额
+     */
+    @TableField("amount")
+    private BigDecimal amount;
+
+    /**
+     * 交易后余额
+     */
+    @TableField("balance")
+    private BigDecimal balance;
+
+    /**
+     * 交易原因
+     */
+    @TableField("transaction_reason")
+    private String transactionReason;
+
+    /**
+     * 交易类型 [RESOURCE_PLACEMENT 空投,PRODUCT 挂机产出,SPEED_UPGRADES 速率升级,BLOCKCHAIN_EXCHANGE 区块链兑换,TASK_REWARDS 任务奖励,TEAM_SHARING 团队抽成,SHARE_REWARDS 邀请奖励]
+     */
+    @TableField("transaction_category")
+    private String transactionCategory;
+
+    /**
+     * 交易详情
+     */
+    @TableField("transaction_detail")
+    private String transactionDetail;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_time")
+    private LocalDateTime createdTime;
+
+    /**
+     * 创建人
+     */
+    @TableField("created_by")
+    private String createdBy;
+
+    @Override
+    public Serializable pkVal() {
+        return this.id;
+    }
+}

+ 77 - 0
src/main/java/com/xs/core/model/coin/msg/CoinProducerMessage.java

@@ -0,0 +1,77 @@
+package com.xs.core.model.coin.msg;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 金币生产消息体
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class CoinProducerMessage implements Serializable {
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 当前生产的金币数
+     */
+    private BigDecimal currentValue;
+
+    /**
+     * 开始秒级时间戳
+     */
+    private Long startTimestamp;
+
+    /**
+     * 结束秒级时间戳
+     */
+    private Long endTimestamp;
+
+    /**
+     * 剩余的时间(秒)
+     */
+    private Long residueTimestamp;
+
+    /**
+     * 上次计算的时间戳
+     */
+    private Long lastTimestamp;
+
+    /**
+     * 当前速率
+     */
+    private String currentRate;
+
+    /**
+     * 永久速率
+     */
+    private String durableRate;
+
+    /**
+     * 临时速率
+     */
+    private String temporaryRate;
+
+    /**
+     * 是否运行
+     */
+    private Boolean running;
+    /**
+     * 速率升级标志 0 开始游戏后为未升级  1 升级后待计算 2 升级后已计算过
+     */
+    private int rateUpdateFlag;
+
+    /**
+     * 速率升级的时间戳
+     */
+    private Long rateUpdateTimestamp;
+
+    private int retryCount;
+}

+ 13 - 0
src/main/java/com/xs/core/model/coin/req/CoinSpeedUpgradesRulesReq.java

@@ -0,0 +1,13 @@
+package com.xs.core.model.coin.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Schema(name = "金币产出速率升级规则请求参数")
+@Data
+public class CoinSpeedUpgradesRulesReq implements Serializable {
+    @Schema(name = "ruleId", description = "规则id")
+    private Long ruleId;
+}

+ 15 - 0
src/main/java/com/xs/core/model/coin/req/TemporaryRateReq.java

@@ -0,0 +1,15 @@
+package com.xs.core.model.coin.req;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.Serializable;
+
+@Slf4j
+@Data
+@Schema(name = "临时速率请求参数")
+public class TemporaryRateReq implements Serializable {
+    @Schema(name = "rate", description = "临时速率")
+    private String rate;
+}

+ 43 - 0
src/main/java/com/xs/core/model/coin/resp/GoldCoinProdStateResp.java

@@ -0,0 +1,43 @@
+package com.xs.core.model.coin.resp;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+@Data
+@Schema(name = "GoldCoinProdStateResp", description = "金币产出状态")
+public class GoldCoinProdStateResp implements Serializable {
+
+    @Schema(description = "用户id")
+    private Long userId;
+
+    @Schema(description = "当前生产的金币数")
+    private BigDecimal currentValue;
+
+    @Schema(description = "开始秒级时间戳")
+    private Long startTimestamp;
+
+
+    @Schema(description = "结束秒级时间戳")
+    private Long endTimestamp;
+
+    @Schema(description = "剩余的时间(秒)")
+    private Long residueTimestamp;
+
+
+    @Schema(description = "当前速率")
+    private String currentRate;
+
+
+    @Schema(description = "永久速率")
+    private String durableRate;
+
+
+    @Schema(description = "临时速率")
+    private String temporaryRate;
+
+    @Schema(description = "状态 0 未开始 1 进行中 2 已完成待领取")
+    private int status;
+}

+ 56 - 0
src/main/java/com/xs/core/mq/consumer/GoldCoinCompleteMessageConsumer.java

@@ -0,0 +1,56 @@
+package com.xs.core.mq.consumer;
+
+import com.alibaba.fastjson2.JSON;
+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.entity.GoldCoinProdRecord;
+import com.xs.core.model.coin.entity.GoldCoinProdState;
+import com.xs.core.model.coin.msg.CoinProducerMessage;
+import com.xs.core.service.coin.GoldCoinService;
+import com.xs.core.service.coin.IGoldCoinProdRecordService;
+import com.xs.core.service.coin.IGoldCoinProdStateService;
+import com.xs.core.service.redis.RedisService;
+import jdk.jfr.Label;
+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.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+
+/**
+ * 金币产出完成消息消费者
+ */
+@Component
+@Slf4j
+public class GoldCoinCompleteMessageConsumer {
+    @Autowired
+    private RedisService redisService;
+
+    @Autowired
+    @Lazy
+    private GoldCoinService goldCoinService;
+
+    @RabbitListener(queues = RabbitMQConfig.GOLD_COIN_COMPLETE_QUEUE, concurrency = "10")
+    public void handleComplete(Message m, Channel channel) throws IOException {
+        log.info("Received handleComplete message: {}", new String(m.getBody()));
+        // 处理完成逻辑
+        // 可以在这里更新数据库中的金币产出状态为完成
+        String msg = new String(m.getBody());
+        CoinProducerMessage coinProducerMessage = JSON.parseObject(msg, CoinProducerMessage.class);
+        if (coinProducerMessage != null) {
+            // 从Redis中获取金币产出状态
+            GoldCoinProdState coinProdState = redisService.get(GoldCoinConstant.GOLD_COIN_STATE_KEY + coinProducerMessage.getUserId(), GoldCoinProdState.class);
+            if (coinProdState != null) {
+                // 执行金币结算逻辑
+                goldCoinService.completedGoldCoinSettlement(coinProdState);
+            }
+        }
+        channel.basicAck(m.getMessageProperties().getDeliveryTag(), false);
+    }
+}

+ 191 - 0
src/main/java/com/xs/core/mq/consumer/GoldCoinProductMessageConsumer.java

@@ -0,0 +1,191 @@
+package com.xs.core.mq.consumer;
+
+import cn.hutool.core.util.ObjUtil;
+import com.alibaba.fastjson2.JSON;
+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.stereotype.Component;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDateTime;
+
+@Component
+@Slf4j
+public class GoldCoinProductMessageConsumer {
+    @Autowired
+    private RedisService redisService;
+
+    @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;
+        try {
+            String msg = new String(m.getBody());
+            message = JSON.parseObject(msg, CoinProducerMessage.class);
+            // 处理计算逻辑
+            if (shouldContinueCalculation(message)) {
+                GoldCoinProdState coinProdState = calculateAndUpdateGame(message);
+                if (coinProdState != null) {
+                    // 更新Redis中的金币产出状态
+                    redisService.set(GoldCoinConstant.GOLD_COIN_STATE_KEY + message.getUserId(), coinProdState);
+                    // 发送下一次延迟计算消息
+                    if (!coinProdState.isCompleted()) {
+                        CoinProducerMessage newMessage = new CoinProducerMessage();
+                        BeanUtils.copyProperties(coinProdState, newMessage);
+                        goldCoinProducer.sendDelayCalculationMessage(newMessage);
+                    } else {
+                        //发送金币结算消息
+                        log.info("Send gold coin complete message for user: {}", message.getUserId());
+                        CoinProducerMessage newMessage = new CoinProducerMessage();
+                        BeanUtils.copyProperties(coinProdState, newMessage);
+                        goldCoinProducer.sendCalculationCompleteMessage(newMessage);
+                    }
+                } 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);
+        }
+    }
+
+    private GoldCoinProdState calculateAndUpdateGame(CoinProducerMessage message) {
+        String key = GoldCoinConstant.GOLD_COIN_STATE_KEY + message.getUserId();
+        //拿到当前的金币产出状态
+        GoldCoinProdState goldCoinProdState = redisService.get(key, GoldCoinProdState.class);
+        if (ObjUtil.isNotNull(goldCoinProdState)) {
+            // 计算金币产出
+            return calculateGoldCoin(goldCoinProdState);
+        } else {
+            // 记录错误日志,可以考虑报警
+            log.error("Failed to get gold coin prod state for user: {}", message.getUserId());
+            return null;
+        }
+    }
+
+    /**
+     * 计算金币产出
+     *
+     * @param goldCoinProdState
+     */
+    private GoldCoinProdState calculateGoldCoin(GoldCoinProdState goldCoinProdState) {
+        log.info("计算金币产出======>{}", goldCoinProdState.getUserId());
+        String rateUpgradesKey = GoldCoinConstant.GOLD_COIN_RATE_UPGRADES_KEY + goldCoinProdState.getUserId();
+        //拿到速率升级状态
+        GoldRateUpgradesState upgradesState = redisService.get(rateUpgradesKey, GoldRateUpgradesState.class);
+        int rateupdateFLag = 0;
+        if (ObjUtil.isNotNull(upgradesState)) {
+            rateupdateFLag = upgradesState.getRateUpdateFlag();
+        }
+        long currentTimestamp = Instant.now().getEpochSecond();
+        Long lastTimestamp = goldCoinProdState.getLastTimestamp();
+        Long endTimestamp = goldCoinProdState.getEndTimestamp();
+        long timeDifference;
+        boolean isCompleted = false;
+
+        // 判断是否超过结束时间
+        if (endTimestamp != null && currentTimestamp >= endTimestamp) {
+            // 如果超过结束时间,只计算到结束时间为止
+            timeDifference = endTimestamp - lastTimestamp;
+            isCompleted = true;
+        } else {
+            timeDifference = currentTimestamp - lastTimestamp;
+        }
+
+        // 如果时间差为 0 或负数,无需更新
+        if (timeDifference <= 0) {
+            // 如果时间差为 0 或负数,直接返回
+            goldCoinProdState.setCompleted(true);
+            return goldCoinProdState;
+        }
+
+        BigDecimal currentValue = goldCoinProdState.getCurrentValue();
+        BigDecimal newValue;
+
+        //若是速率升级
+        if (rateupdateFLag == 1) {
+            Long rateUpdateTimestamp = upgradesState.getUpdateTimestamp();
+
+            // 安全检查:确保 rateUpdateTimestamp 在有效范围内
+            if (rateUpdateTimestamp == null || rateUpdateTimestamp < lastTimestamp || rateUpdateTimestamp > (isCompleted ? endTimestamp : currentTimestamp)) {
+                rateUpdateTimestamp = isCompleted ? endTimestamp : currentTimestamp;
+            }
+
+            // 计算旧速率的金币产出
+            long oldRateDuration = Math.min(rateUpdateTimestamp - lastTimestamp, timeDifference);
+            BigDecimal oldRate = new BigDecimal(goldCoinProdState.getDurableRate());
+            newValue = currentValue.add(oldRate.multiply(BigDecimal.valueOf(oldRateDuration)));
+
+            // 计算新速率的金币产出
+            long newRateDuration = timeDifference - oldRateDuration;
+            if (newRateDuration > 0) {
+                String newRateStr = upgradesState.getCurrentRate();
+                BigDecimal newRate = new BigDecimal(newRateStr != null ? newRateStr : "0");
+                newValue = newValue.add(newRate.multiply(BigDecimal.valueOf(newRateDuration)));
+                goldCoinProdState.setCurrentRate(newRateStr);
+            }
+            goldCoinProdState.setDurableRate(upgradesState.getDurableRate());
+            goldCoinProdState.setTemporaryRate(upgradesState.getTemporaryRate());
+            upgradesState.setRateUpdateFlag(2);
+            redisService.set(rateUpgradesKey, upgradesState);
+        } else {
+            // 使用当前速率计算
+            BigDecimal currentRate = new BigDecimal(goldCoinProdState.getCurrentRate());
+            newValue = currentValue.add(currentRate.multiply(BigDecimal.valueOf(timeDifference)));
+        }
+        // 更新通用状态
+        goldCoinProdState.setLastTimestamp(isCompleted ? endTimestamp : currentTimestamp);
+        goldCoinProdState.setCurrentValue(newValue);
+        goldCoinProdState.setResidueTimestamp(Math.max(0, goldCoinProdState.getResidueTimestamp() - timeDifference));
+
+        // 如果达到或超过结束时间,设置相应的标志
+        if (isCompleted) {
+            goldCoinProdState.setCompleted(true);
+        }
+        return goldCoinProdState;
+    }
+
+
+    /**
+     * 判断是否需要继续计算
+     *
+     * @param message
+     * @return
+     */
+    private boolean shouldContinueCalculation(CoinProducerMessage message) {
+        // 处理判断是否需要计算的逻辑
+        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());
+        }
+    }
+
+}

+ 68 - 0
src/main/java/com/xs/core/mq/producer/GoldCoinMessageProducer.java

@@ -0,0 +1,68 @@
+package com.xs.core.mq.producer;
+
+import com.alibaba.fastjson2.JSON;
+import com.xs.core.config.mq.RabbitMQConfig;
+import com.xs.core.model.coin.msg.CoinProducerMessage;
+import lombok.extern.slf4j.Slf4j;
+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;
+
+@Component
+@Slf4j
+public class GoldCoinMessageProducer {
+    @Autowired
+    private RabbitTemplate rabbitTemplate;
+
+    /**
+     * 发送开始游戏的消息
+     */
+    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 -> {
+                //延时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
+     */
+    public void sendCalculationCompleteMessage(CoinProducerMessage coinProducerMessage) {
+        try {
+            String messageStr = JSON.toJSONString(coinProducerMessage);
+            rabbitTemplate.convertAndSend(RabbitMQConfig.GOLD_COIN_COMPLETE_EXCHANGE, RabbitMQConfig.GOLD_COIN_COMPLETE_QUEUE, messageStr);
+            log.info("发送计算结束的消息: {}", messageStr);
+        } catch (Exception e) {
+            e.printStackTrace();
+            // 重试机制
+            handleSendFailure(coinProducerMessage);
+        }
+    }
+}

+ 267 - 0
src/main/java/com/xs/core/service/Impl/RedisServiceImpl.java

@@ -0,0 +1,267 @@
+package com.xs.core.service.Impl;
+
+import cn.hutool.json.JSONUtil;
+import com.xs.core.config.FastJson2JsonRedisSerializer;
+import com.xs.core.service.redis.RedisService;
+import jakarta.annotation.Resource;
+import org.springframework.dao.DataAccessException;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.core.Cursor;
+import org.springframework.data.redis.core.RedisCallback;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.ScanOptions;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author zxw
+ * redis操作实现类
+ */
+
+@Component
+public class RedisServiceImpl implements RedisService {
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Override
+    public void set(String key, Object value, long time) {
+        redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void setStr(String key, String value, long time) {
+        redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void set(String key, Object value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    @Override
+    public Object get(String key) {
+        return redisTemplate.opsForValue().get(key);
+    }
+
+    @Override
+    public <T> T get(String key, Class<T> clazz) {
+        return redisTemplate.execute((RedisCallback<T>) connection -> {
+            byte[] value = null;
+            int retryCount = 3; // 设置重试次数
+            while (retryCount > 0) {
+                try {
+                    value = connection.get(Objects.requireNonNull(redisTemplate.getStringSerializer().serialize(key)));
+                    break;
+                } catch (Exception e) {
+                    retryCount--;
+                    // 重试次数用尽,抛出异常
+                    if (retryCount == 0) {
+                        throw e;
+                    }
+                    // 等待一段时间后重试
+                    try {
+                        Thread.sleep(50);
+                    } catch (InterruptedException ie) {
+                        Thread.currentThread().interrupt();
+                    }
+                }
+            }
+            FastJson2JsonRedisSerializer<T> serializer = new FastJson2JsonRedisSerializer<>(clazz);
+            return serializer.deserialize(value);
+        });
+    }
+
+    @Override
+    public Boolean del(String key) {
+        return redisTemplate.delete(key);
+    }
+
+    @Override
+    public Long del(List<String> keys) {
+        return redisTemplate.delete(keys);
+    }
+
+    @Override
+    public Boolean expire(String key, long time) {
+        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public Long getExpire(String key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public Boolean hasKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+    @Override
+    public Long incr(String key, long delta) {
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    @Override
+    public Long decr(String key, long delta) {
+        return redisTemplate.opsForValue().increment(key, -delta);
+    }
+
+    @Override
+    public Object hGet(String key, String hashKey) {
+        return redisTemplate.opsForHash().get(key, hashKey);
+    }
+
+    @Override
+    public Boolean hSet(String key, String hashKey, Object value, long time) {
+        redisTemplate.opsForHash().put(key, hashKey, value);
+        return expire(key, time);
+    }
+
+    @Override
+    public void hSet(String key, String hashKey, Object value) {
+        redisTemplate.opsForHash().put(key, hashKey, value);
+    }
+
+    @Override
+    public Map<Object, Object> hGetAll(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    @Override
+    public Boolean hSetAll(String key, Map<String, Object> map, long time) {
+        redisTemplate.opsForHash().putAll(key, map);
+        return expire(key, time);
+    }
+
+    @Override
+    public void hSetAll(String key, Map<String, ?> map) {
+        redisTemplate.opsForHash().putAll(key, map);
+    }
+
+    @Override
+    public void hDel(String key, Object... hashKey) {
+        redisTemplate.opsForHash().delete(key, hashKey);
+    }
+
+    @Override
+    public Boolean hHasKey(String key, String hashKey) {
+        return redisTemplate.opsForHash().hasKey(key, hashKey);
+    }
+
+    @Override
+    public Long hIncr(String key, String hashKey, Long delta) {
+        return redisTemplate.opsForHash().increment(key, hashKey, delta);
+    }
+
+    @Override
+    public Long hDecr(String key, String hashKey, Long delta) {
+        return redisTemplate.opsForHash().increment(key, hashKey, -delta);
+    }
+
+    @Override
+    public Set<Object> sMembers(String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    @Override
+    public Long sAdd(String key, Object... values) {
+        return redisTemplate.opsForSet().add(key, values);
+    }
+
+    @Override
+    public Long sAdd(String key, long time, Object... values) {
+        Long count = redisTemplate.opsForSet().add(key, values);
+        expire(key, time);
+        return count;
+    }
+
+    @Override
+    public Boolean sIsMember(String key, Object value) {
+        return redisTemplate.opsForSet().isMember(key, value);
+    }
+
+    @Override
+    public Long sSize(String key) {
+        return redisTemplate.opsForSet().size(key);
+    }
+
+    @Override
+    public Long sRemove(String key, Object... values) {
+        return redisTemplate.opsForSet().remove(key, values);
+    }
+
+    @Override
+    public List<Object> lRange(String key, long start, long end) {
+        return redisTemplate.opsForList().range(key, start, end);
+    }
+
+    @Override
+    public Long lSize(String key) {
+        return redisTemplate.opsForList().size(key);
+    }
+
+    @Override
+    public Object lIndex(String key, long index) {
+        return redisTemplate.opsForList().index(key, index);
+    }
+
+    @Override
+    public Long lPush(String key, Object value) {
+        return redisTemplate.opsForList().rightPush(key, value);
+    }
+
+    @Override
+    public Long lPush(String key, Object value, long time) {
+        Long index = redisTemplate.opsForList().rightPush(key, value);
+        expire(key, time);
+        return index;
+    }
+
+    @Override
+    public Long lPushAll(String key, Object... values) {
+        return redisTemplate.opsForList().rightPushAll(key, values);
+    }
+
+    @Override
+    public Long lPushAll(String key, Long time, Object... values) {
+        Long count = redisTemplate.opsForList().rightPushAll(key, values);
+        expire(key, time);
+        return count;
+    }
+
+    @Override
+    public Long lRemove(String key, long count, Object value) {
+        return redisTemplate.opsForList().remove(key, count, value);
+    }
+
+    @Override
+    public boolean setNx(final String key, final Object value, final long expireSeconds) {
+        return (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
+            @Override
+            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
+                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
+                boolean flag = connection.setNX(serializer.serialize(key), serializer.serialize(JSONUtil.toJsonStr(value)));
+                if (flag) {
+                    connection.expire(serializer.serialize(key), expireSeconds);
+                }
+                return flag;
+            }
+        });
+    }
+
+    @Override
+    public List<String> scanKeys(String pattern) {
+        List<String> keys = new ArrayList<>();
+        this.redisTemplate.execute((RedisConnection connection) -> {
+            Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match("*" + pattern + "*").count(1000).build());
+            while (cursor.hasNext()) {
+                keys.add(new String(cursor.next()));
+            }
+            return null;
+        });
+        return keys;
+    }
+}

+ 26 - 0
src/main/java/com/xs/core/service/Impl/coin/CoinSpeedUpgradesRulesServiceImpl.java

@@ -0,0 +1,26 @@
+package com.xs.core.service.Impl.coin;
+
+import com.xs.core.model.coin.entity.CoinSpeedUpgradesRules;
+import com.xs.core.mapper.coin.CoinSpeedUpgradesRulesMapper;
+import com.xs.core.service.coin.ICoinSpeedUpgradesRulesService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 金币产出速率升级规则 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+@Service
+public class CoinSpeedUpgradesRulesServiceImpl extends ServiceImpl<CoinSpeedUpgradesRulesMapper, CoinSpeedUpgradesRules> implements ICoinSpeedUpgradesRulesService {
+
+    @Override
+    public List<CoinSpeedUpgradesRules> getCoinSpeedUpgradesRulesByUserId(Long userId) {
+        return getBaseMapper().getCoinSpeedUpgradesRulesByUserId(userId);
+    }
+}

+ 29 - 0
src/main/java/com/xs/core/service/Impl/coin/GoldCoinProdRecordServiceImpl.java

@@ -0,0 +1,29 @@
+package com.xs.core.service.Impl.coin;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.xs.core.model.coin.entity.GoldCoinProdRecord;
+import com.xs.core.mapper.coin.GoldCoinProdRecordMapper;
+import com.xs.core.service.coin.IGoldCoinProdRecordService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 金币产出记录表 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+@Service
+public class GoldCoinProdRecordServiceImpl extends ServiceImpl<GoldCoinProdRecordMapper, GoldCoinProdRecord> implements IGoldCoinProdRecordService {
+
+    @Override
+    public GoldCoinProdRecord getNoReciviedGoldCoinProdRecordByUserId(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);
+    }
+}

+ 72 - 0
src/main/java/com/xs/core/service/Impl/coin/GoldCoinProdStateServiceImpl.java

@@ -0,0 +1,72 @@
+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;
+import com.xs.core.model.coin.entity.GoldCoinProdState;
+import com.xs.core.mapper.coin.GoldCoinProdStateMapper;
+import com.xs.core.model.coin.resp.GoldCoinProdStateResp;
+import com.xs.core.service.coin.IGoldCoinProdRecordService;
+import com.xs.core.service.coin.IGoldCoinProdStateService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.xs.core.service.redis.RedisService;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 金币生产状态 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+@Service
+public class GoldCoinProdStateServiceImpl extends ServiceImpl<GoldCoinProdStateMapper, GoldCoinProdState> implements IGoldCoinProdStateService {
+
+    @Autowired
+    private RedisService redisService;
+
+    @Autowired
+    private IGoldCoinProdRecordService recordService;
+
+    @Override
+    public GoldCoinProdState getGoldCoinProdStateByUser(Long userId) {
+        //先尝试获取redis中的值
+        GoldCoinProdState coinProdState = redisService.get(GoldCoinConstant.GOLD_COIN_STATE_KEY + userId, GoldCoinProdState.class);
+        if (ObjUtil.isNotNull(coinProdState)) {
+            return coinProdState;
+        } else {
+            //redis中没有值,尝试获取数据库中的值
+            LambdaQueryWrapper<GoldCoinProdState> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(GoldCoinProdState::getUserId, userId);
+            return getOne(queryWrapper);
+        }
+    }
+
+    @Override
+    public GoldCoinProdStateResp getGoldCoinProdRespStateByUser(Long userId) {
+        GoldCoinProdState state = getGoldCoinProdStateByUser(userId);
+        if (ObjUtil.isNotNull(state)) {
+            GoldCoinProdStateResp resp = new GoldCoinProdStateResp();
+            BeanUtils.copyProperties(state, resp);
+            if (Boolean.TRUE.equals(state.getRunning())) {
+                resp.setStatus(1);
+            } else {
+                //判断是否存在待领取的金币
+                GoldCoinProdRecord record = recordService.getNoReciviedGoldCoinProdRecordByUserId(userId);
+                if (ObjUtil.isNotNull(record)) {
+                    resp.setStatus(2);
+                } else {
+                    resp.setStatus(0);
+                }
+            }
+            return resp;
+        }
+        return null;
+    }
+}

+ 325 - 0
src/main/java/com/xs/core/service/Impl/coin/GoldCoinServiceImpl.java

@@ -0,0 +1,325 @@
+package com.xs.core.service.Impl.coin;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import com.xs.core.common.constant.GoldCoinConstant;
+import com.xs.core.common.content.UserContext;
+import com.xs.core.common.content.UserContextHolder;
+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.msg.CoinProducerMessage;
+import com.xs.core.model.coin.entity.GoldCoinProdState;
+import com.xs.core.model.coin.entity.UserCoinSpeedUpgradesRecord;
+import com.xs.core.model.coin.req.CoinSpeedUpgradesRulesReq;
+import com.xs.core.model.coin.req.TemporaryRateReq;
+import com.xs.core.model.coin.resp.GoldCoinProdStateResp;
+import com.xs.core.model.user.entity.TgUser;
+import com.xs.core.mq.producer.GoldCoinMessageProducer;
+import com.xs.core.service.coin.*;
+import com.xs.core.service.redis.RedisService;
+import com.xs.core.service.user.ITgUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+@Slf4j
+public class GoldCoinServiceImpl implements GoldCoinService {
+    @Autowired
+    private IGoldCoinProdRecordService recordService;
+
+    @Autowired
+    private ITgUserService userService;
+
+    @Autowired
+    private RedisService redisService;
+
+    @Autowired
+    private IUserCoinSpeedUpgradesRecordService speedRecordService;
+
+    @Autowired
+    private IGoldCoinProdStateService stateService;
+
+    @Autowired
+    private GoldCoinMessageProducer messageProducer;
+
+    private ICoinSpeedUpgradesRulesService speedRulesService;
+
+    @Override
+    @Transactional
+    public void startProductGoldCoin() {
+        UserContext userContext = UserContextHolder.getContext();
+        //先获取金币产出状态
+        GoldCoinProdState goldCoinProdState = stateService.getGoldCoinProdStateByUser(userContext.getId());
+        if (ObjUtil.isNull(goldCoinProdState)) {
+            //如果为空,就创建一个
+            goldCoinProdState = firstInitGoldCoinProdState(userContext);
+            stateService.save(goldCoinProdState);
+        }
+        //判断是否已经在产出中 或者 待领取的金币未领取
+        checkProductState(goldCoinProdState, userContext);
+        //1、初始化产出信息
+        initializeProductInfo(goldCoinProdState, userContext);
+        //2、保存金币产出状态到redis
+        saveSateToRedis(goldCoinProdState, userContext.getId());
+        //3、保存金币产出状态到数据库
+        stateService.updateById(goldCoinProdState);
+        //4、发送延时循环计算的消息
+        CoinProducerMessage message = new CoinProducerMessage();
+        BeanUtils.copyProperties(goldCoinProdState, message);
+        messageProducer.sendDelayCalculationMessage(message);
+    }
+
+    private void checkProductState(GoldCoinProdState goldCoinProdState, UserContext userContext) {
+        CheckUtils.throwIf(Boolean.TRUE.equals(goldCoinProdState.getRunning()), "金币产出中,不能重复开启");
+        //若是非运行状态 则检查待领取的金币
+        GoldCoinProdRecord record = recordService.getNoReciviedGoldCoinProdRecordByUserId(userContext.getId());
+        CheckUtils.throwIf(ObjUtil.isNotNull(record) && !record.getReceiving(), "待领取的金币未领取,不能重复开启");
+    }
+
+    /**
+     * 初始化产出信息
+     *
+     * @param goldCoinProdState 金币产出状态
+     * @param userContext       用户上下文
+     */
+    private void initializeProductInfo(GoldCoinProdState goldCoinProdState, UserContext userContext) {
+        //1、开启金币产出状态
+        goldCoinProdState.setRunning(Boolean.TRUE);
+        long secondsTimestamp = Instant.now().getEpochSecond();
+        goldCoinProdState.setStartTimestamp(secondsTimestamp);
+        goldCoinProdState.setEndTimestamp(secondsTimestamp + GoldCoinConstant.GOLD_COIN_PRODUCT_TIME);
+        goldCoinProdState.setLastTimestamp(secondsTimestamp);
+        goldCoinProdState.setResidueTimestamp(GoldCoinConstant.GOLD_COIN_PRODUCT_TIME);
+        goldCoinProdState.setCurrentValue(BigDecimal.ZERO);
+        //2、获取永久速率
+        UserCoinSpeedUpgradesRecord speedUpgradesRecord = speedRecordService.getSpeedUpgradesRecordByUserId(userContext.getId());
+        goldCoinProdState.setCurrentRate(speedUpgradesRecord.getProductRate());
+        goldCoinProdState.setDurableRate(speedUpgradesRecord.getProductRate());
+    }
+
+    /**
+     * 首次初始化状态信息
+     *
+     * @param userContext
+     * @return
+     */
+    private GoldCoinProdState firstInitGoldCoinProdState(UserContext userContext) {
+        GoldCoinProdState goldCoinProdState = new GoldCoinProdState();
+        goldCoinProdState.setUserId(userContext.getId());
+        goldCoinProdState.setRunning(Boolean.FALSE);
+        //获取永久速率
+        UserCoinSpeedUpgradesRecord speedUpgradesRecord = speedRecordService.getSpeedUpgradesRecordByUserId(userContext.getId());
+        if (ObjUtil.isNull(speedUpgradesRecord)) {
+            //如果为空,就创建一个
+            speedUpgradesRecord = new UserCoinSpeedUpgradesRecord();
+            speedUpgradesRecord.setUserId(userContext.getId());
+            speedUpgradesRecord.setSpeedLevel(0);
+            speedUpgradesRecord.setProductRate(GoldCoinConstant.GOLD_INIT_RATE);
+            speedUpgradesRecord.setUpgradesTime(LocalDateTime.now());
+            speedRecordService.save(speedUpgradesRecord);
+        }
+        goldCoinProdState.setCurrentRate(speedUpgradesRecord.getProductRate());
+        goldCoinProdState.setCurrentValue(BigDecimal.ZERO);
+        //long secondsTimestamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
+        long secondsTimestamp = Instant.now().getEpochSecond();
+        goldCoinProdState.setStartTimestamp(secondsTimestamp);
+        goldCoinProdState.setEndTimestamp(secondsTimestamp + GoldCoinConstant.GOLD_COIN_PRODUCT_TIME);
+        goldCoinProdState.setResidueTimestamp(GoldCoinConstant.GOLD_COIN_PRODUCT_TIME);
+        goldCoinProdState.setLastTimestamp(secondsTimestamp);
+        goldCoinProdState.setDurableRate(speedUpgradesRecord.getProductRate());
+        goldCoinProdState.setTemporaryRate("0");
+        return goldCoinProdState;
+    }
+
+    private void saveSateToRedis(GoldCoinProdState goldCoinProdState, Long userId) {
+        String key = GoldCoinConstant.GOLD_COIN_STATE_KEY + userId;
+        redisService.set(key, goldCoinProdState);
+    }
+
+    @Override
+    @Transactional
+    public void collectGoldCoin() {
+        UserContext userContext = UserContextHolder.getContext();
+        //先查询是否存在待领取的金币
+        GoldCoinProdRecord record = recordService.getNoReciviedGoldCoinProdRecordByUserId(userContext.getId());
+        GoldCoinProdState state = stateService.getGoldCoinProdStateByUser(userContext.getId());
+        CheckUtils.throwIfNull(record, "没有待领取的金币");
+        //把金币结算到用户的账户中
+        userService.coinTransaction(CoinTransactionTypeEnum.ADD, CoinTransactionCategoryEnum.PRODUCT, "金币产出结算", "挂机产出金币结算", record.getGoldCoinYieldTotal(), userContext.getId());
+        //更新金币产出记录的领取状态
+        resetGoldCoinProdState(state);
+        record.setReceiving(Boolean.TRUE);
+        record.setReceivedTime(LocalDateTime.now());
+        stateService.updateById(state);
+        recordService.updateById(record);
+    }
+
+    private void resetGoldCoinProdState(GoldCoinProdState state) {
+        state.setCurrentValue(BigDecimal.ZERO);
+        state.setLastTimestamp(null);
+        state.setEndTimestamp(null);
+        state.setResidueTimestamp(null);
+        state.setCurrentRate(state.getDurableRate());
+        state.setTemporaryRate("0");
+        state.setRunning(Boolean.FALSE);
+    }
+
+    @Override
+    public void permanentBoostProductGoldCoinRate(CoinSpeedUpgradesRulesReq req) {
+        UserContext context = UserContextHolder.getContext();
+        //获取金币升级速率
+        CoinSpeedUpgradesRules rules = speedRulesService.getById(req.getRuleId());
+        TgUser user = userService.getById(context.getId());
+        BigDecimal goldCoinAmount = user.getGoldCoinAmount();
+        Integer consumeGoldCoin = rules.getConsumeGoldCoin();
+        CheckUtils.throwIf(consumeGoldCoin > goldCoinAmount.intValue(), "金币不足");
+        //扣除金币
+        userService.coinTransaction(CoinTransactionTypeEnum.SUBTRACT, CoinTransactionCategoryEnum.SPEED_UPGRADES, "金币产出速率升级", "金币产出速率升级", new BigDecimal(consumeGoldCoin), context.getId());
+        //增加一条速率升级记录
+        UserCoinSpeedUpgradesRecord speedUpgradesRecord = new UserCoinSpeedUpgradesRecord();
+        speedUpgradesRecord.setSpeedLevel(rules.getLevel());
+        String numericalValue = rules.getNumericalValue();
+        speedUpgradesRecord.setProductRate(numericalValue);
+        speedUpgradesRecord.setUserId(context.getId());
+        speedUpgradesRecord.setUpgradesTime(LocalDateTime.now());
+        speedRecordService.save(speedUpgradesRecord);
+        String rateKey = GoldCoinConstant.GOLD_COIN_RATE_UPGRADES_KEY + context.getId();
+        //判断当前是否在在产出中
+        GoldCoinProdState goldCoinProdState = stateService.getGoldCoinProdStateByUser(context.getId());
+        if (ObjUtil.isNotNull(goldCoinProdState) && Boolean.TRUE.equals(goldCoinProdState.getRunning())) {
+            long epochSecond = Instant.now().getEpochSecond();
+            //把速率写入到redis中
+            //若是存在key 说明之前进行过速率升级 则把当前的速率加上新的速率
+            if (redisService.hasKey(rateKey)) {
+                GoldRateUpgradesState upgradesState = redisService.get(rateKey, GoldRateUpgradesState.class);
+                String temporaryRateStr = goldCoinProdState.getTemporaryRate();
+
+                BigDecimal temporaryRate = new BigDecimal(temporaryRateStr);
+                BigDecimal numericalValueDec = new BigDecimal(numericalValue);
+                BigDecimal currentRate = temporaryRate.add(numericalValueDec);
+
+                upgradesState.setCurrentRate(currentRate.toPlainString());
+                upgradesState.setDurableRate(numericalValue);
+                upgradesState.setRateUpdateFlag(1);
+                upgradesState.setUpdateTimestamp(epochSecond);
+                //设置最新永久速率
+                redisService.set(rateKey, upgradesState);
+            } else {
+                //设置用户新永久速率 不存在key则说明是第一次进行速率升级
+                GoldRateUpgradesState upgradesState = new GoldRateUpgradesState();
+                BigDecimal currentRate = new BigDecimal(numericalValue);
+
+                upgradesState.setHasTemporaryRate(false);
+                upgradesState.setDurableRate(numericalValue);
+                upgradesState.setCurrentRate(currentRate.toPlainString());
+                upgradesState.setUpdateTimestamp(epochSecond);
+                upgradesState.setTemporaryRate("");
+                upgradesState.setRateUpdateFlag(1);
+                redisService.set(rateKey, upgradesState);
+            }
+        }
+    }
+
+    @Override
+    public void temporaryBoostProductGoldCoinRate(TemporaryRateReq temporaryRateReq) {
+        String rate = temporaryRateReq.getRate();
+        UserContext context = UserContextHolder.getContext();
+        CheckUtils.throwIfNull(context, "用户未登录或用户信息为空");
+        //获取用户的金币产出状态
+        GoldCoinProdState goldCoinProdState = stateService.getGoldCoinProdStateByUser(context.getId());
+        CheckUtils.throwIf(Boolean.FALSE.equals(goldCoinProdState.getRunning()), "用户未开启金币产出");
+        //获取用户的临时速率
+        String rateTemporarilyKey = GoldCoinConstant.GOLD_COIN_RATE_UPGRADES_KEY + context.getId();
+        String goldCoinStateKey = GoldCoinConstant.GOLD_COIN_STATE_KEY + context.getId();
+        long epochSecond = Instant.now().getEpochSecond();
+        //判断用户的速率升级记录是否存在  存在则不允许升级
+        if (redisService.hasKey(rateTemporarilyKey)) {
+            GoldRateUpgradesState upgradesState = redisService.get(rateTemporarilyKey, GoldRateUpgradesState.class);
+            CheckUtils.throwIf(upgradesState.isHasTemporaryRate(), "当前生产批次已存在临时速率,暂不允许升级");
+            upgradesState.setHasTemporaryRate(true);
+            String currentRateStr = upgradesState.getCurrentRate();
+            BigDecimal currentRate = new BigDecimal(currentRateStr);
+            BigDecimal added = currentRate.add(new BigDecimal(rate));
+            upgradesState.setCurrentRate(added.toPlainString());
+            upgradesState.setTemporaryRate(rate);
+            upgradesState.setRateUpdateFlag(1);
+            upgradesState.setUpdateTimestamp(epochSecond);
+            //设置用户的临时速率
+            redisService.set(rateTemporarilyKey, upgradesState);
+        } else {
+            //设置用户的临时速率
+            GoldRateUpgradesState upgradesState = new GoldRateUpgradesState();
+            upgradesState.setHasTemporaryRate(true);
+            GoldCoinProdState coinProdState = redisService.get(goldCoinStateKey, GoldCoinProdState.class);
+            CheckUtils.throwIfNull(coinProdState, "用户未开启金币产出");
+            String currentRateStr = coinProdState.getCurrentRate();
+            BigDecimal currentRate = new BigDecimal(currentRateStr);
+            BigDecimal added = currentRate.add(new BigDecimal(rate));
+            upgradesState.setCurrentRate(added.toPlainString());
+            upgradesState.setUpdateTimestamp(epochSecond);
+            upgradesState.setTemporaryRate(rate);
+            upgradesState.setRateUpdateFlag(1);
+            redisService.set(rateTemporarilyKey, upgradesState);
+        }
+    }
+
+    @Override
+    public GoldCoinProdStateResp getGoldCoinProdState() {
+        UserContext context = UserContextHolder.getContext();
+        CheckUtils.throwIfNull(context, "用户未登录或用户信息为空");
+        GoldCoinProdStateResp state = stateService.getGoldCoinProdRespStateByUser(context.getId());
+        if (state == null) {
+            state = new GoldCoinProdStateResp();
+            UserCoinSpeedUpgradesRecord speedUpgradesRecord = speedRecordService.getSpeedUpgradesRecordByUserId(context.getId());
+            state.setCurrentValue(BigDecimal.ZERO);
+            state.setCurrentRate(ObjUtil.isNotNull(speedUpgradesRecord) ? speedUpgradesRecord.getProductRate() : GoldCoinConstant.GOLD_INIT_RATE);
+            state.setDurableRate(ObjUtil.isNotNull(speedUpgradesRecord) ? speedUpgradesRecord.getProductRate() : GoldCoinConstant.GOLD_INIT_RATE);
+            state.setStatus(0);
+        }
+        return state;
+    }
+
+    @Override
+    @Transactional
+    public void completedGoldCoinSettlement(GoldCoinProdState coinProdState) {
+        String productKey = GoldCoinConstant.GOLD_COIN_STATE_KEY + coinProdState.getUserId();
+        String rateTemporarilyKey = GoldCoinConstant.GOLD_COIN_RATE_UPGRADES_KEY + coinProdState.getUserId();
+
+        //生成金币生产记录
+        GoldCoinProdRecord goldCoinProdRecord = new GoldCoinProdRecord();
+        goldCoinProdRecord.setUserId(coinProdState.getUserId());
+        goldCoinProdRecord.setGoldCoinYieldTotal(coinProdState.getCurrentValue());
+        goldCoinProdRecord.setReceiving(false);
+        goldCoinProdRecord.setYieldTime(LocalDateTime.now());
+        goldCoinProdRecord.setStartTimestamp(coinProdState.getStartTimestamp());
+        goldCoinProdRecord.setEndTimestamp(coinProdState.getEndTimestamp());
+
+        // 重置金币产出状态
+        coinProdState.setCompleted(Boolean.TRUE);
+        coinProdState.setRunning(Boolean.FALSE);
+        recordService.save(goldCoinProdRecord);
+        stateService.updateById(coinProdState);
+        //清除redis中的金币产出状态
+        redisService.del(productKey);
+        //清除redis中的速率升级记录
+        redisService.del(rateTemporarilyKey);
+    }
+
+    @Override
+    public List<CoinSpeedUpgradesRules> getRateUpgradesRulesByUser() {
+        UserContext context = UserContextHolder.getContext();
+        return speedRulesService.getCoinSpeedUpgradesRulesByUserId(context.getId());
+    }
+}

+ 29 - 0
src/main/java/com/xs/core/service/Impl/coin/UserCoinSpeedUpgradesRecordServiceImpl.java

@@ -0,0 +1,29 @@
+package com.xs.core.service.Impl.coin;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.xs.core.model.coin.entity.UserCoinSpeedUpgradesRecord;
+import com.xs.core.mapper.coin.UserCoinSpeedUpgradesRecordMapper;
+import com.xs.core.service.coin.IUserCoinSpeedUpgradesRecordService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 用户金币产出速率升级记录 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+@Service
+public class UserCoinSpeedUpgradesRecordServiceImpl extends ServiceImpl<UserCoinSpeedUpgradesRecordMapper, UserCoinSpeedUpgradesRecord> implements IUserCoinSpeedUpgradesRecordService {
+
+    @Override
+    public UserCoinSpeedUpgradesRecord getSpeedUpgradesRecordByUserId(Long id) {
+        LambdaQueryWrapper<UserCoinSpeedUpgradesRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserCoinSpeedUpgradesRecord::getUserId, id);
+        queryWrapper.orderByDesc(UserCoinSpeedUpgradesRecord::getSpeedLevel);
+        queryWrapper.last("limit 1");
+        return getOne(queryWrapper);
+    }
+}

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

@@ -0,0 +1,20 @@
+package com.xs.core.service.Impl.coin;
+
+import com.xs.core.model.coin.entity.UserCoinTransaction;
+import com.xs.core.mapper.coin.UserCoinTransactionMapper;
+import com.xs.core.service.coin.IUserCoinTransactionService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 用户金币交易流水记录表 服务实现类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+@Service
+public class UserCoinTransactionServiceImpl extends ServiceImpl<UserCoinTransactionMapper, UserCoinTransaction> implements IUserCoinTransactionService {
+
+}

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

@@ -51,7 +51,7 @@ public class AppLoginServiceImpl implements AppLoginService {
         TgUser user = TgUser.builder()
                 .tgAccount(loginReq.getUsername())
                 .tgId(loginReq.getId())
-                .avatar(URLDecoder.decode(loginReq.getPhotoUrl(), StandardCharsets.UTF_8))
+                .avatar(StrUtil.isNotBlank(loginReq.getPhotoUrl()) ? URLDecoder.decode(loginReq.getPhotoUrl(), StandardCharsets.UTF_8) : "")
                 .firstName(loginReq.getFirstName())
                 .lastName(loginReq.getLastName())
                 //注册时指定用户的唯一邀请码

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

@@ -1,10 +1,18 @@
 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.user.entity.TgUser;
 import com.xs.core.mapper.user.TgUserMapper;
+import com.xs.core.service.coin.IUserCoinTransactionService;
 import com.xs.core.service.user.ITgUserService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
 
 /**
  * <p>
@@ -16,5 +24,34 @@ import org.springframework.stereotype.Service;
  */
 @Service
 public class TgUserServiceImpl extends ServiceImpl<TgUserMapper, TgUser> implements ITgUserService {
+    @Autowired
+    private IUserCoinTransactionService userCoinTransactionService;
 
+    @Override
+    @Transactional
+    public void coinTransaction(CoinTransactionTypeEnum type, CoinTransactionCategoryEnum category, String reason, String detail, BigDecimal amount, Long userId) {
+        TgUser user = getById(userId);
+        // 记录交易记录
+        UserCoinTransaction record = new UserCoinTransaction();
+        record.setUserId(userId);
+        // 更新用户金币数量
+        if (type.equals(CoinTransactionTypeEnum.ADD)) {
+            // 增加金币
+            BigDecimal added = user.getGoldCoinAmount().add(amount);
+            user.setGoldCoinAmount(added);
+            BigDecimal his = user.getGoldCoinTotalHis().add(amount);
+            user.setGoldCoinTotalHis(his);
+        } else if (type.equals(CoinTransactionTypeEnum.SUBTRACT)) {
+            // 减少金币
+            user.setGoldCoinAmount(user.getGoldCoinAmount().subtract(amount));
+        }
+        record.setAmount(amount);
+        record.setBalance(user.getGoldCoinAmount());
+        record.setTransactionType(type.getCode());
+        record.setTransactionCategory(category.getCode());
+        record.setTransactionReason(reason);
+        record.setTransactionDetail(detail);
+        updateById(user);
+        userCoinTransactionService.save(record);
+    }
 }

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

@@ -0,0 +1,47 @@
+package com.xs.core.service.coin;
+
+import com.xs.core.model.coin.entity.CoinSpeedUpgradesRules;
+import com.xs.core.model.coin.entity.GoldCoinProdState;
+import com.xs.core.model.coin.req.CoinSpeedUpgradesRulesReq;
+import com.xs.core.model.coin.req.TemporaryRateReq;
+import com.xs.core.model.coin.resp.GoldCoinProdStateResp;
+
+import java.util.List;
+
+public interface GoldCoinService {
+    /**
+     * 开始产出
+     */
+    void startProductGoldCoin();
+
+    /**
+     * 收取产出的金币
+     */
+    void collectGoldCoin();
+
+    /**
+     * 永久提升产币速率
+     */
+    void permanentBoostProductGoldCoinRate(CoinSpeedUpgradesRulesReq req);
+
+    /**
+     * 临时提升产币速率
+     */
+    void temporaryBoostProductGoldCoinRate(TemporaryRateReq temporaryRateReq);
+
+    /**
+     * 获取金币产出状态
+     *
+     * @return
+     */
+    GoldCoinProdStateResp getGoldCoinProdState();
+
+    /**
+     * 金币产出结算
+     *
+     * @param coinProdState
+     */
+    void completedGoldCoinSettlement(GoldCoinProdState coinProdState);
+
+    List<CoinSpeedUpgradesRules> getRateUpgradesRulesByUser();
+}

+ 26 - 0
src/main/java/com/xs/core/service/coin/ICoinSpeedUpgradesRulesService.java

@@ -0,0 +1,26 @@
+package com.xs.core.service.coin;
+
+import com.xs.core.model.coin.entity.CoinSpeedUpgradesRules;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 金币产出速率升级规则 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+public interface ICoinSpeedUpgradesRulesService extends IService<CoinSpeedUpgradesRules> {
+
+    /**
+     * 获取用户金币速率升级规则列表
+     *
+     * @param id
+     * @return
+     */
+    List<CoinSpeedUpgradesRules> getCoinSpeedUpgradesRulesByUserId(Long id);
+
+}

+ 25 - 0
src/main/java/com/xs/core/service/coin/IGoldCoinProdRecordService.java

@@ -0,0 +1,25 @@
+package com.xs.core.service.coin;
+
+import com.xs.core.model.coin.entity.GoldCoinProdRecord;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 金币产出记录表 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+public interface IGoldCoinProdRecordService extends IService<GoldCoinProdRecord> {
+
+    /**
+     * 金币产出记录 根据用户id查询
+     *
+     * @param id
+     * @return
+     */
+    GoldCoinProdRecord getNoReciviedGoldCoinProdRecordByUserId(Long id);
+
+
+}

+ 21 - 0
src/main/java/com/xs/core/service/coin/IGoldCoinProdStateService.java

@@ -0,0 +1,21 @@
+package com.xs.core.service.coin;
+
+import com.xs.core.model.coin.entity.GoldCoinProdState;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.xs.core.model.coin.resp.GoldCoinProdStateResp;
+
+/**
+ * <p>
+ * 金币生产状态 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+public interface IGoldCoinProdStateService extends IService<GoldCoinProdState> {
+
+    GoldCoinProdState getGoldCoinProdStateByUser(Long userId);
+
+    GoldCoinProdStateResp getGoldCoinProdRespStateByUser(Long userId);
+
+}

+ 18 - 0
src/main/java/com/xs/core/service/coin/IUserCoinSpeedUpgradesRecordService.java

@@ -0,0 +1,18 @@
+package com.xs.core.service.coin;
+
+import com.xs.core.model.coin.entity.UserCoinSpeedUpgradesRecord;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 用户金币产出速率升级记录 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-18
+ */
+public interface IUserCoinSpeedUpgradesRecordService extends IService<UserCoinSpeedUpgradesRecord> {
+
+    UserCoinSpeedUpgradesRecord getSpeedUpgradesRecordByUserId(Long id);
+
+}

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

@@ -0,0 +1,16 @@
+package com.xs.core.service.coin;
+
+import com.xs.core.model.coin.entity.UserCoinTransaction;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 用户金币交易流水记录表 服务类
+ * </p>
+ *
+ * @author xudm
+ * @since 2024-12-19
+ */
+public interface IUserCoinTransactionService extends IService<UserCoinTransaction> {
+
+}

+ 201 - 0
src/main/java/com/xs/core/service/redis/RedisService.java

@@ -0,0 +1,201 @@
+package com.xs.core.service.redis;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * redis操作Service
+ * Created by macro on 2020/3/3.
+ */
+public interface RedisService {
+
+    /**
+     * 保存key 带时间
+     */
+    void set(String key, Object value, long time);
+
+
+    void setStr(String key, String value, long time);
+
+    /**
+     * 保存key 不带时间
+     */
+    void set(String key, Object value);
+
+    /**
+     * 获取key,对象对OBJECT
+     */
+    Object get(String key);
+
+    /**
+     * 获取key
+     */
+    <T> T get(String key,Class<T> clazz);
+
+    /**
+     * 删除key
+     */
+    Boolean del(String key);
+
+    /**
+     * 批量删除key
+     */
+    Long del(List<String> keys);
+
+    /**
+     * 设置过期时间
+     */
+    Boolean expire(String key, long time);
+
+    /**
+     * 获取过期时间
+     */
+    Long getExpire(String key);
+
+    /**
+     * 判断是否有该key
+     */
+    Boolean hasKey(String key);
+
+    /**
+     * 按delta递增
+     */
+    Long incr(String key, long delta);
+
+    /**
+     * 按delta递减
+     */
+    Long decr(String key, long delta);
+
+    /**
+     * 获取Hash结构中的属性
+     */
+    Object hGet(String key, String hashKey);
+
+    /**
+     * 向Hash结构中放入一个属性
+     */
+    Boolean hSet(String key, String hashKey, Object value, long time);
+
+    /**
+     * 向Hash结构中放入一个属性
+     */
+    void hSet(String key, String hashKey, Object value);
+
+    /**
+     * 直接获取整个Hash结构
+     */
+    Map<Object, Object> hGetAll(String key);
+
+    /**
+     * 直接设置整个Hash结构
+     */
+    Boolean hSetAll(String key, Map<String, Object> map, long time);
+
+    /**
+     * 直接设置整个Hash结构
+     */
+    void hSetAll(String key, Map<String, ?> map);
+
+    /**
+     * 删除Hash结构中的属性
+     */
+    void hDel(String key, Object... hashKey);
+
+    /**
+     * 判断Hash结构中是否有该属性
+     */
+    Boolean hHasKey(String key, String hashKey);
+
+    /**
+     * Hash结构中属性递增
+     */
+    Long hIncr(String key, String hashKey, Long delta);
+
+    /**
+     * Hash结构中属性递减
+     */
+    Long hDecr(String key, String hashKey, Long delta);
+
+    /**
+     * 获取Set结构
+     */
+    Set<Object> sMembers(String key);
+
+    /**
+     * 向Set结构中添加key
+     */
+    Long sAdd(String key, Object... values);
+
+    /**
+     * 向Set结构中添加key
+     */
+    Long sAdd(String key, long time, Object... values);
+
+    /**
+     * 是否为Set中的key
+     */
+    Boolean sIsMember(String key, Object value);
+
+    /**
+     * 获取Set结构的长度
+     */
+    Long sSize(String key);
+
+    /**
+     * 删除Set结构中的key
+     */
+    Long sRemove(String key, Object... values);
+
+    /**
+     * 获取List结构中的key
+     */
+    List<Object> lRange(String key, long start, long end);
+
+    /**
+     * 获取List结构的长度
+     */
+    Long lSize(String key);
+
+    /**
+     * 根据索引获取List中的key
+     */
+    Object lIndex(String key, long index);
+
+    /**
+     * 向List结构中添加key
+     */
+    Long lPush(String key, Object value);
+
+    /**
+     * 向List结构中添加key
+     */
+    Long lPush(String key, Object value, long time);
+
+    /**
+     * 向List结构中批量添加key
+     */
+    Long lPushAll(String key, Object... values);
+
+    /**
+     * 向List结构中批量添加key
+     */
+    Long lPushAll(String key, Long time, Object... values);
+
+    /**
+     * 从List结构中移除key
+     */
+    Long lRemove(String key, long count, Object value);
+
+    /**
+     * 保存key 带时间 带锁
+     */
+    boolean setNx(String key, Object value, long expireSeconds);
+
+    /**
+     * 扫描键 根据传入的参数
+     */
+    List<String> scanKeys(String pattern);
+
+}

+ 15 - 1
src/main/java/com/xs/core/service/user/ITgUserService.java

@@ -1,8 +1,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.user.entity.TgUser;
 import com.baomidou.mybatisplus.extension.service.IService;
 
+import java.math.BigDecimal;
+
 /**
  * <p>
  * tg用户 服务类
@@ -12,5 +16,15 @@ import com.baomidou.mybatisplus.extension.service.IService;
  * @since 2024-12-18
  */
 public interface ITgUserService extends IService<TgUser> {
-
+    /**
+     * 用户金币交易
+     *
+     * @param type
+     * @param category
+     * @param reason
+     * @param detail
+     * @param amount
+     * @param userId
+     */
+    void coinTransaction(CoinTransactionTypeEnum type, CoinTransactionCategoryEnum category, String reason, String detail, BigDecimal amount, Long userId);
 }

+ 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("user", new String[]{"b_tg_user"});
+        generatorByBusinessModule("coin", new String[]{"b_user_coin_transaction"});
     }
 
     /**

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

@@ -36,7 +36,30 @@ spring:
       max-lifetime: 1800000
       # 启动时建立连接
       initialization-fail-timeout: 60000
-
+  rabbitmq:
+    virtual-host: /
+    host: 192.168.241.130
+    port: 5672
+    username: admin
+    password: admin
+    listener:
+      simple:
+        acknowledge-mode: manual
+        concurrency: 5
+        max-concurrency: 10
+        prefetch: 1
+        retry:
+          enabled: true
+          initial-interval: 1000
+          max-attempts: 3
+          multiplier: 2
+    # 重试配置
+    template:
+      retry:
+        enabled: true
+        initial-interval: 1000
+        max-attempts: 3
+        multiplier: 2
 
 #myabtis-plus
 mybatis-plus:
@@ -49,6 +72,9 @@ mybatis-plus:
     # MyBatis 自动映射策略
     # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
     auto-mapping-behavior: PARTIAL
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    # 开启驼峰命名转换
+    map-underscore-to-camel-case: true
   ## 全局配置
   global-config:
     banner: true

+ 147 - 0
src/main/resources/application-prod.yml

@@ -0,0 +1,147 @@
+spring:
+  data:
+    redis:
+      host: 192.168.241.130
+      port: 6379
+      password: xudm5200
+      database: 6
+      lettuce:
+        pool:
+          # 连接池最大连接数
+          max-active: 200
+          # 连接池最大阻塞等待时间(使用负值表示没有限制)
+          max-wait: -1ms
+          # 连接池中的最大空闲连接
+          max-idle: 10
+          # 连接池中的最小空闲连接
+          min-idle: 2
+  datasource:
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    url: jdbc:mysql://192.168.241.130:3306/continew?useUnicode=true&characterEncoding=utf-8&useSSL=false
+    username: continew
+    password: continew123456
+    hikari:
+      # 最大连接数量(默认 10,根据实际环境调整)
+      # 注意:当连接达到上限,并且没有空闲连接可用时,获取连接将在超时前阻塞最多 connectionTimeout 毫秒
+      maximum-pool-size: 20
+      minimum-idle: 5
+      connection-test-query: SELECT 1
+      # 获取连接超时时间(默认 30000 毫秒,30 秒)
+      connection-timeout: 30000
+      # 空闲连接最大存活时间(默认 600000 毫秒,10 分钟)
+      idle-timeout: 600000
+      # 保持连接活动的频率,以防止它被数据库或网络基础设施超时。该值必须小于 maxLifetime(默认 0,禁用)
+      keepaliveTime: 30000
+      # 连接最大生存时间(默认 1800000 毫秒,30 分钟)
+      max-lifetime: 1800000
+      # 启动时建立连接
+      initialization-fail-timeout: 60000
+  rabbitmq:
+    virtual-host: /
+    host: 192.168.241.130
+    port: 5672
+    username: guest
+    password: guest
+    listener:
+      simple:
+        acknowledge-mode: manual
+        concurrency: 5
+        max-concurrency: 10
+        prefetch: 1
+        retry:
+          enabled: true
+          initial-interval: 1000
+          max-attempts: 3
+          multiplier: 2
+    # 重试配置
+    template:
+      retry:
+        enabled: true
+        initial-interval: 1000
+        max-attempts: 3
+        multiplier: 2
+
+#myabtis-plus
+mybatis-plus:
+  # Mapper XML 文件目录配置
+  mapper-locations: classpath*:/mapper/**/*.xml
+  # 类型别名扫描包配置
+  type-aliases-package: com.xs.core.model
+  ## MyBatis 配置
+  configuration:
+    # MyBatis 自动映射策略
+    # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射
+    auto-mapping-behavior: PARTIAL
+  ## 全局配置
+  global-config:
+    banner: true
+    db-config:
+      # 主键类型(默认 assign_id,表示自行赋值)
+      # auto 代表使用数据库自增策略(需要在表中设置好自增约束)
+      id-type: ASSIGN_ID
+      # 逻辑删除字段
+      logic-delete-field: deleteFlag
+      # 逻辑删除全局值(默认 1,表示已删除)
+      logic-delete-value: 1
+      # 逻辑未删除全局值(默认 0,表示未删除)
+      logic-not-delete-value: 0
+
+--- ### CosId 配置
+cosid:
+  namespace: ${spring.application.name}
+  machine:
+    enabled: true
+    # 机器号分配器
+    distributor:
+      type: REDIS
+    guarder:
+      # 开启机器号守护
+      enabled: true
+  snowflake:
+    enabled: true
+    zone-id: Asia/Shanghai
+    epoch: 1577203200000
+    share:
+      # 开启时钟回拨同步
+      clock-sync: true
+      friendly: true
+    provider:
+      safe-js:
+        machine-bit: 7
+        sequence-bit: 9
+
+############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
+sa-token:
+  # token 名称(同时也是 cookie 名称)
+  token-name: Authorization
+  # token前缀
+  token-prefix: Bearer
+  # token 有效期(单位:秒) 默认30天,-1 代表永久有效
+  timeout: 86400
+  # token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
+  active-timeout: -1
+  # 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
+  is-concurrent: false
+  # 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
+  is-share: true
+  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+  token-style: tik
+  # 是否输出操作日志
+  is-log: false
+  jwt-secret-key: asdaefghifhueuiwyurfewbfjsdafjk
+
+# 配置请求是否加密
+encryption:
+  isEncryption: false
+  requestPrivateKey: 14FDF4948E60F856524FBB1175E83716558B8CBF71A68318875F506B9C6D2A4A
+  requestPublicKey: 04F36CD10986CE214D0E5C540C30E0552DC8499B64E5B2709245D03BF2CADAA0CCA3C2BC2C8DB511012A50FAA1E43FCD4B8ABC521418EAB2D96F0075AD940EB25F
+  responsePrivateKey: 00C83135E19EBD958593091F42A3442DE3D03D975A5DBD4CE19F85C9FBF2D364B7
+  responsePublicKey: 040D10F8CA3AAC83345DA54472CCE5AB495BBB15E21E960B2A2EEE1D9EEC2E9EB1BE3902606904BD767FF056F59CC1AD237D3074A3F8D452AE376FFE84113640C0
+
+
+# 防止xss攻击配置
+xssFilter:
+  # 是否开启防止xss攻击
+  isOpenXssFilter: false
+  # 不拦截路径,多个用“,”分隔,例如/userBusiness/*,/sysRole/*
+  xssFilterWhileUrl: /process/*

+ 14 - 0
src/main/resources/application.yml

@@ -2,6 +2,20 @@ server:
   port: 8083
   servlet:
     context-path: /app
+  ## Undertow 服务器配置
+  undertow:
+    # HTTP POST 请求内容的大小上限(默认 -1,不限制)
+    max-http-post-size: -1
+    # 以下的配置会影响 buffer,这些 buffer 会用于服务器连接的 IO 操作,有点类似 Netty 的池化内存管理
+    # 每块 buffer的空间大小(越小的空间被利用越充分,不要设置太大,以免影响其他应用,合适即可)
+    buffer-size: 512
+    # 是否分配的直接内存(NIO 直接分配的堆外内存)
+    direct-buffers: true
+    threads:
+      # 设置 IO 线程数,它主要执行非阻塞的任务,它们会负责多个连接(默认每个 CPU 核心一个线程)
+      io: 8
+      # 阻塞任务线程池,当执行类似 Servlet 请求阻塞操作,Undertow 会从这个线程池中取得线程(它的值设置取决于系统的负载)
+      worker: 256
 
 spring:
   application:

+ 32 - 0
src/main/resources/mapper/CoinSpeedUpgradesRulesMapper.xml

@@ -0,0 +1,32 @@
+<?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.CoinSpeedUpgradesRulesMapper">
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.xs.core.model.coin.entity.CoinSpeedUpgradesRules">
+        <id column="id" property="id"/>
+        <result column="consume_gold_coin" property="consumeGoldCoin"/>
+        <result column="real_money" property="realMoney"/>
+        <result column="numerical_value" property="numericalValue"/>
+        <result column="level" property="level"/>
+        <result column="created_by" property="createdBy"/>
+        <result column="created_time" property="createdTime"/>
+        <result column="updated_by" property="updatedBy"/>
+        <result column="updated_time" property="updatedTime"/>
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, consume_gold_coin, real_money, numerical_value, level, created_by, created_time, updated_by, updated_time
+    </sql>
+
+    <select id="getCoinSpeedUpgradesRulesByUserId" resultMap="BaseResultMap">
+        SELECT bcsur.*
+        FROM b_coin_speed_upgrades_rules bcsur
+                 JOIN (SELECT COALESCE(MAX(speed_level), 0) AS max_level
+                       FROM b_user_coin_speed_upgrades_record
+                       WHERE user_id = #{userId}) AS user_level
+        WHERE bcsur.level >= user_level.max_level
+        order by bcsur.level
+        limit 2;
+    </select>
+</mapper>

+ 23 - 0
src/main/resources/mapper/GoldCoinProdRecordMapper.xml

@@ -0,0 +1,23 @@
+<?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" />
+    </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>

+ 18 - 0
src/main/resources/mapper/GoldCoinProdStateMapper.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.coin.GoldCoinProdStateMapper">
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.xs.core.model.coin.entity.GoldCoinProdState">
+        <result column="id" property="id"/>
+        <result column="user_id" property="userId"/>
+        <result column="current_value" property="currentValue"/>
+        <result column="start_timestamp" property="startTimestamp"/>
+        <result column="end_timestamp" property="endTimestamp"/>
+        <result column="residue_timestamp" property="residueTimestamp"/>
+        <result column="last_timestamp" property="lastTimestamp"/>
+        <result column="current_rate" property="currentRate"/>
+        <result column="durable_rate" property="durableRate"/>
+        <result column="temporary_rate" property="temporaryRate"/>
+        <result column="running" property="running"/>
+    </resultMap>
+</mapper>

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

@@ -37,9 +37,4 @@
         <result column="created_time" property="createdTime"/>
         <result column="updated_time" property="updatedTime"/>
     </resultMap>
-
-    <!-- 通用查询结果列 -->
-    <sql id="Base_Column_List">
-        id, tg_id, tg_account, first_name, last_name, nickname, real_name, avatar, language_code, wallet_address, age_limit, old_user, check_language, login_ip, login_time, ip_address_convert, disable_flag, channel, passenger_flow_way, login_telegram, coin_address, password, mobile, sex, invite_code, referrer_id, airdrop_coin, gold_coin_amount, gold_coin_total_his, online_time, user_amount, created_time, updated_time
-    </sql>
 </mapper>

+ 12 - 0
src/main/resources/mapper/UserCoinSpeedUpgradesRecordMapper.xml

@@ -0,0 +1,12 @@
+<?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.UserCoinSpeedUpgradesRecordMapper">
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.xs.core.model.coin.entity.UserCoinSpeedUpgradesRecord">
+        <id column="id" property="id"/>
+        <result column="user_id" property="userId"/>
+        <result column="product_rate" property="productRate"/>
+        <result column="speed_level" property="speedLevel"/>
+        <result column="upgrades_time" property="upgradesTime"/>
+    </resultMap>
+</mapper>

+ 22 - 0
src/main/resources/mapper/UserCoinTransactionMapper.xml

@@ -0,0 +1,22 @@
+<?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.UserCoinTransactionMapper">
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.xs.core.model.coin.entity.UserCoinTransaction">
+        <id column="id" property="id"/>
+        <result column="user_id" property="userId"/>
+        <result column="transaction_type" property="transactionType"/>
+        <result column="amount" property="amount"/>
+        <result column="balance" property="balance"/>
+        <result column="transaction_reason" property="transactionReason"/>
+        <result column="transaction_category" property="transactionCategory"/>
+        <result column="transaction_detail" property="transactionDetail"/>
+        <result column="created_time" property="createdTime"/>
+        <result column="created_by" property="createdBy"/>
+    </resultMap>
+
+    <!-- 通用查询结果列 -->
+    <sql id="Base_Column_List">
+        id, user_id, transaction_type, amount, balance, transaction_reason, transaction_category, transaction_detail, created_time, created_by
+    </sql>
+</mapper>