Browse Source

完善空投功能 app功能完成

xudm 2 months ago
parent
commit
88a1f73762

+ 0 - 110
src/main/java/com/xs/core/common/aspect/ApiLogAdvice.java

@@ -1,110 +0,0 @@
-//package com.xs.core.common.aspect;
-//
-//import com.alibaba.fastjson2.JSON;
-//import com.alibaba.fastjson2.JSONWriter;
-//import com.xs.core.model.log.entity.ApiRequestLog;
-//import com.xs.core.service.log.IApiRequestLogService;
-//import jakarta.servlet.http.HttpServletRequest;
-//import jakarta.servlet.http.HttpServletResponse;
-//import lombok.extern.slf4j.Slf4j;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.core.MethodParameter;
-//import org.springframework.core.annotation.Order;
-//import org.springframework.http.HttpInputMessage;
-//import org.springframework.http.MediaType;
-//import org.springframework.http.converter.HttpMessageConverter;
-//import org.springframework.http.server.ServerHttpRequest;
-//import org.springframework.http.server.ServerHttpResponse;
-//import org.springframework.http.server.ServletServerHttpRequest;
-//import org.springframework.http.server.ServletServerHttpResponse;
-//import org.springframework.web.bind.annotation.ControllerAdvice;
-//import org.springframework.web.context.request.RequestContextHolder;
-//import org.springframework.web.context.request.ServletRequestAttributes;
-//import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
-//import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
-//
-//import java.io.IOException;
-//import java.lang.reflect.Type;
-//import java.time.LocalDateTime;
-//import java.time.ZoneOffset;
-//import java.util.concurrent.CompletableFuture;
-//
-//@ControllerAdvice
-//@Slf4j
-//@Order(10)
-//public class ApiLogAdvice extends RequestBodyAdviceAdapter implements ResponseBodyAdvice<Object> {
-//
-//    @Autowired
-//    private IApiRequestLogService logService;
-//
-//    private ThreadLocal<ApiRequestLog> apiLogThreadLocal = new ThreadLocal<>();
-//
-//    @Override
-//    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
-//        return true;
-//    }
-//
-//    @Override
-//    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
-//        ApiRequestLog log = new ApiRequestLog();
-//        log.setRequestTime(LocalDateTime.now());
-//        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
-//        if (attributes != null) {
-//            HttpServletRequest request = attributes.getRequest();
-//            log.setRequestMethod(request.getMethod());
-//            log.setRequestUrl(request.getRequestURI());
-//            log.setIpAddress(request.getRemoteAddr());
-//            log.setUserAgent(request.getHeader("User-Agent"));
-//        }
-//        apiLogThreadLocal.set(log);
-//        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
-//    }
-//
-//    @Override
-//    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
-//        return true;
-//    }
-//
-//    @Override
-//    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
-//        ApiRequestLog log = apiLogThreadLocal.get();
-//        log.setRequestParams(objectToJson(body));
-//        return body;
-//    }
-//
-//    @Override
-//    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest servletRequest, ServerHttpResponse servletResponse) {
-//        ApiRequestLog log1 = apiLogThreadLocal.get();
-//        ServletServerHttpRequest request = (ServletServerHttpRequest) servletRequest;
-//        ServletServerHttpResponse response = (ServletServerHttpResponse) servletResponse;
-//        HttpServletRequest httpServletRequest = request.getServletRequest();
-//        HttpServletResponse httpServletResponse = response.getServletResponse();
-//        if (log1 != null) {
-//            log1.setResponseBody(objectToJson(body));
-//            log1.setResponseStatus(httpServletResponse.getStatus());
-//            log1.setExecutionTime(System.currentTimeMillis() - log1.getRequestTime().toInstant(ZoneOffset.UTC).toEpochMilli());
-//            saveLog(log1);
-//            apiLogThreadLocal.remove();
-//        }
-//        return body;
-//    }
-//
-//    private void saveLog(ApiRequestLog log1) {
-//        try {
-//            CompletableFuture.runAsync(() -> {
-//                logService.save(log1);
-//            });
-//        } catch (Exception e) {
-//            log.error("Failed to save API log", e);
-//        }
-//    }
-//
-//    private String objectToJson(Object obj) {
-//        try {
-//            return JSON.toJSONString(obj, JSONWriter.Feature.WriteMapNullValue);
-//        } catch (Exception e) {
-//            log.error("Error converting object to JSON", e);
-//            return null;
-//        }
-//    }
-//}

+ 0 - 94
src/main/java/com/xs/core/common/aspect/ApiLogAspect.java

@@ -1,94 +0,0 @@
-//package com.xs.core.common.aspect;
-//
-//import com.alibaba.fastjson2.JSON;
-//import com.alibaba.fastjson2.JSONWriter;
-//import com.xs.core.common.content.UserContext;
-//import com.xs.core.common.content.UserContextHolder;
-//import com.xs.core.model.log.entity.ApiRequestLog;
-//import com.xs.core.service.log.IApiRequestLogService;
-//import jakarta.servlet.http.HttpServletRequest;
-//import lombok.extern.slf4j.Slf4j;
-//import org.apache.commons.lang3.exception.ExceptionUtils;
-//import org.aspectj.lang.ProceedingJoinPoint;
-//import org.aspectj.lang.annotation.Around;
-//import org.aspectj.lang.annotation.Aspect;
-//import org.aspectj.lang.reflect.MethodSignature;
-//import org.springframework.beans.factory.annotation.Autowired;
-//import org.springframework.http.HttpStatus;
-//import org.springframework.stereotype.Component;
-//
-//import java.time.LocalDateTime;
-//import java.util.HashMap;
-//import java.util.Map;
-//import java.util.concurrent.CompletableFuture;
-//
-////@Aspect
-////@Component
-//@Slf4j
-//public class ApiLogAspect {
-////    @Autowired
-////    private IApiRequestLogService logService;
-////
-////    @Autowired
-////    private HttpServletRequest request;
-//
-////    @Around("execution(* com.xs.core.controller..*.*(..))")
-//    public Object logApiRequest(ProceedingJoinPoint joinPoint) throws Throwable {
-//        long startTime = System.currentTimeMillis();
-//
-//
-//        ApiRequestLog log1 = new ApiRequestLog();
-//        log1.setRequestTime(LocalDateTime.now());
-//        log1.setRequestMethod(request.getMethod());
-//        log1.setRequestUrl(request.getRequestURI());
-//        log1.setIpAddress(request.getRemoteAddr());
-//        log1.setUserAgent(request.getHeader("User-Agent"));
-//        UserContext context = UserContextHolder.getContext();
-//        if (context != null) {
-//            log1.setUserId(context.getId());
-//        }
-//        // 获取方法参数
-//        Object[] args = joinPoint.getArgs();
-//        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
-//        String[] parameterNames = signature.getParameterNames();
-//
-//        // 记录请求参数
-//        Map<String, Object> requestParams = new HashMap<>();
-//        for (int i = 0; i < args.length; i++) {
-//            requestParams.put(parameterNames[i], args[i]);
-//        }
-//        log1.setRequestParams(objectToJson(requestParams));
-//
-//        Object result = null;
-//        try {
-//            result = joinPoint.proceed();
-//            log1.setResponseStatus(HttpStatus.OK.value());
-//            log1.setResponseBody(objectToJson(result));
-//        } catch (Exception e) {
-//            log1.setResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
-//            log1.setErrorMessage(ExceptionUtils.getStackTrace(e));
-//            log1.setResponseBody(objectToJson(e));
-//            result = e;  // 将异常赋值给 result,以便后续可能的处理
-//        } finally {
-//            log1.setExecutionTime(System.currentTimeMillis() - startTime);
-//            //异步保存日志
-//            CompletableFuture.runAsync(() -> {
-//                logService.save(log1);
-//            });
-//        }
-//        // 如果发生了异常,在这里重新抛出
-//        if (result instanceof Throwable) {
-//            throw (Throwable) result;
-//        }
-//        return result;
-//    }
-//
-//    private String objectToJson(Object obj) {
-//        try {
-//            return JSON.toJSONString(obj, JSONWriter.Feature.WriteMapNullValue);
-//        } catch (Exception e) {
-//            log.error("Error converting object to JSON", e);
-//            return null;
-//        }
-//    }
-//}

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

@@ -17,6 +17,9 @@ public class RabbitMQConfig {
     public static final String GOLD_COIN_TEAM_SHARE_QUEUE = "gold.coin.team.share.queue";
     public static final String GOLD_COIN_TEAM_SHARE_EXCHANGE = "gold.coin.team.share.exchange";
 
+    public static final String AIRDROP_QUEUE = "gold.coin.airdrop.queue";
+    public static final String AIRDROP_EXCHANGE = "gold.coin.airdrop.exchange";
+
     @Bean
     public Queue goldCoinCompleteQueue() {
         return new Queue(GOLD_COIN_COMPLETE_QUEUE, true, false, false);

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

@@ -10,7 +10,6 @@ import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.ConstraintViolationException;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.context.NoSuchMessageException;
 import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.util.CollectionUtils;
 import org.springframework.validation.BindException;
@@ -25,9 +24,7 @@ import org.springframework.web.multipart.MaxUploadSizeExceededException;
 import org.springframework.web.multipart.support.MissingServletRequestPartException;
 import org.springframework.web.servlet.resource.NoResourceFoundException;
 
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 @RestControllerAdvice
 @Slf4j
@@ -56,7 +53,7 @@ public class GlobalExceptionHandler {
                         String field = fieldError.getField();
                         String defaultMessage = fieldError.getDefaultMessage();
                         // 尝试从消息源获取国际化消息,如果没有找到,则使用默认消息
-                        return ResponseResult.failed(defaultMessage);
+                        return ResponseResult.failed(defaultMessage,null);
                     }
                 }
             }

+ 31 - 0
src/main/java/com/xs/core/model/coin/msg/AirdropManagerMessage.java

@@ -0,0 +1,31 @@
+package com.xs.core.model.coin.msg;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class AirdropManagerMessage implements Serializable {
+
+    /**
+     * 空投目标人
+     */
+    private Long targetUser;
+
+    /**
+     * 空投金币数量
+     */
+    private Integer goldCoinNum;
+
+
+    /**
+     * 操作类型【0 新增空投 1 减去空投】
+     */
+    private Integer operationType;
+
+    /**
+     * 操作人
+     */
+    private String operationUser;
+}

+ 1 - 0
src/main/java/com/xs/core/model/user/req/AppLoginReq.java

@@ -13,6 +13,7 @@ public class AppLoginReq {
     @Schema(description = "姓", example = "prpr")
     private String lastName;
 
+    @NotBlank(message = "validation.login.tg.id.not.null")
     @Schema(description = "tg小程序用户id", example = "7678353292")
     private String id;
 

+ 3 - 1
src/main/java/com/xs/core/model/wallet/resp/WalletInfoResp.java

@@ -1,5 +1,7 @@
 package com.xs.core.model.wallet.resp;
 
+import cn.hutool.core.util.ObjUtil;
+import com.xs.core.common.constant.GoldCoinConstant;
 import com.xs.core.model.coin.entity.UserCoinSpeedUpgradesRecord;
 import com.xs.core.model.wallet.entity.UserWallet;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -45,7 +47,7 @@ public class WalletInfoResp implements Serializable {
         this.taskRewards = taskRewards;
         this.teamSharing = teamSharing;
         this.resourcePlacement = airdrops;
-        BigDecimal decimal = new BigDecimal(speedUpgradesRecord.getProductRate());
+        BigDecimal decimal = new BigDecimal(ObjUtil.isNotNull(speedUpgradesRecord) ? speedUpgradesRecord.getProductRate() : GoldCoinConstant.GOLD_INIT_RATE);
         this.profitPerHour = decimal.multiply(new BigDecimal(60 * 60L));
         this.totalBalance = wallet.getTotalBalance();
         this.availableBalance = wallet.getAvailableBalance();

+ 81 - 0
src/main/java/com/xs/core/mq/consumer/AirdropMessageConsumer.java

@@ -0,0 +1,81 @@
+package com.xs.core.mq.consumer;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import com.alibaba.fastjson2.JSON;
+import com.rabbitmq.client.Channel;
+import com.xs.core.common.enums.CoinTransactionCategoryEnum;
+import com.xs.core.common.enums.CoinTransactionTypeEnum;
+import com.xs.core.config.mq.RabbitMQConfig;
+import com.xs.core.model.coin.entity.UserCoinTransaction;
+import com.xs.core.model.coin.msg.AirdropManagerMessage;
+import com.xs.core.service.wallet.IUserWalletService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.core.Message;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 空投消息消费者
+ */
+@Component
+@Slf4j
+public class AirdropMessageConsumer {
+    @Autowired
+    private IUserWalletService walletService;
+
+    @RabbitListener(queues = RabbitMQConfig.AIRDROP_QUEUE, concurrency = "10")
+    public void handleComplete(Message m, Channel channel) throws IOException {
+        String msg = new String(m.getBody());
+        AirdropManagerMessage airdropManagerMessage = JSON.parseObject(msg, AirdropManagerMessage.class);
+        if (ObjUtil.isNull(airdropManagerMessage)) {
+            // 消息为空,直接确认消息
+            channel.basicAck(m.getMessageProperties().getDeliveryTag(), false);
+            return;
+        }
+        try {
+            if (airdropManagerMessage.getOperationType() == 0) {
+                walletService.coinTransaction(CoinTransactionTypeEnum.ADD, CoinTransactionCategoryEnum.RESOURCE_PLACEMENT, "空投增加金币", "空投增加金币,用户id:" + airdropManagerMessage.getTargetUser(), new BigDecimal(airdropManagerMessage.getGoldCoinNum()), airdropManagerMessage.getTargetUser());
+                // 新增
+            } else if (airdropManagerMessage.getOperationType() == 1) {
+                // 减去
+                //先获取用户的空投数量
+                List<UserCoinTransaction> transactionList = walletService.getCoinTransactionByType(airdropManagerMessage.getTargetUser(), CoinTransactionCategoryEnum.RESOURCE_PLACEMENT);
+                BigDecimal reduce = BigDecimal.ZERO;
+                BigDecimal amount = new BigDecimal(airdropManagerMessage.getGoldCoinNum());
+                if (CollUtil.isNotEmpty(transactionList)) {
+                    reduce = transactionList.stream().filter(trans -> ObjUtil.isNotNull(trans.getAmount()))
+                            .map(transaction -> {
+                                if (Objects.equals(transaction.getTransactionType(), CoinTransactionTypeEnum.ADD.getCode())) {
+                                    // 增加
+                                    return transaction.getAmount();
+                                } else if (Objects.equals(transaction.getTransactionType(), CoinTransactionTypeEnum.SUBTRACT.getCode())) {
+                                    // 消耗(负值)
+                                    return transaction.getAmount().negate();
+                                } else {
+                                    // 未知类型,当作0处理
+                                    return BigDecimal.ZERO;
+                                }
+                            })
+                            .reduce(BigDecimal.ZERO, BigDecimal::add);
+                }
+                if (reduce.compareTo(amount) >= 0) {
+                    //减去
+                    walletService.coinTransaction(CoinTransactionTypeEnum.SUBTRACT, CoinTransactionCategoryEnum.RESOURCE_PLACEMENT, "空投减少金币", "空投减少金币,用户id:" + airdropManagerMessage.getTargetUser(), new BigDecimal(airdropManagerMessage.getGoldCoinNum()), airdropManagerMessage.getTargetUser());
+                } else {
+                    walletService.coinTransaction(CoinTransactionTypeEnum.SUBTRACT, CoinTransactionCategoryEnum.RESOURCE_PLACEMENT, "空投减少金币", "空投减少金币,用户id:" + airdropManagerMessage.getTargetUser() + ",用户空投金币不足,实际减去金额为" + reduce.toPlainString(), reduce, airdropManagerMessage.getTargetUser());
+                }
+            }
+        } catch (Exception e) {
+            log.error("空投消息处理失败,消息内容:{}", msg, e);
+            channel.basicNack(m.getMessageProperties().getDeliveryTag(), false, true);
+        }
+        channel.basicAck(m.getMessageProperties().getDeliveryTag(), false);
+    }
+}

+ 1 - 2
src/main/java/com/xs/core/mq/consumer/GoldCoinCompleteMessageConsumer.java

@@ -38,10 +38,9 @@ public class GoldCoinCompleteMessageConsumer {
 
     @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());
+        log.info("Received handleComplete message: {}", msg);
         CoinProducerMessage coinProducerMessage = JSON.parseObject(msg, CoinProducerMessage.class);
         if (coinProducerMessage != null) {
             // 从Redis中获取金币产出状态

+ 1 - 2
src/main/java/com/xs/core/service/Impl/wallet/UserWalletServiceImpl.java

@@ -5,7 +5,6 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 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.User;
 import com.xs.core.model.wallet.entity.UserWallet;
 import com.xs.core.mapper.wallet.UserWalletMapper;
 import com.xs.core.service.coin.IUserCoinTransactionService;
@@ -67,7 +66,7 @@ public class UserWalletServiceImpl extends ServiceImpl<UserWalletMapper, UserWal
             userWallet.setAvailableBalance(userWallet.getAvailableBalance().subtract(amount));
         }
         record.setAmount(amount);
-        record.setBalance(userWallet.getTotalBalance());
+        record.setBalance(userWallet.getAvailableBalance());
         record.setTransactionType(type.getCode());
         record.setTransactionCategory(category.getCode());
         record.setTransactionReason(reason);

+ 36 - 28
src/main/resources/messages/messages_en.properties

@@ -1,28 +1,36 @@
-response.success=操作成功
-response.server.error=服务器错误
-response.not.found=找不到资源
-response.unsupported.media.type=不支持的媒体类型
-response.request.too.large=请求体过大
-response.bad.request=请求参数错误
-response.forbidden=没有权限
-response.unauthorized=用户未登录或用户信息为空
-response.failed=操作失败
-response.expected.file.missing=预期的文件缺失
-request.api.not.exists=请求的接口不存在
-response.error.encrypt=加解密错误
-no.gold.claimed=没有待领取的金币
-response.user.disable=此账号已被禁用,如有疑问,请联系管理员
-error.gold.product.record.not.exists=金币产出速率升级规则不存在
-error.gold.product.not.start=用户未开启金币产出
-error.gold.product.end=金币产出结束或未开启金币产出,加成已失效
-error.gold.product.coin.not.claimed=待领取的金币未领取,不能重复开启
-error.gold.product.exists=金币产出中,不能重复开启
-error.gold.boost.temporary.exists=当前生产批次已存在临时速率,暂不允许升级
-error.gold.insufficient=金币不足
-error.not.exists={0} 为 [{1}] 的 {2} 记录已不存在
-error.exists={0} 为 [{1}] 的 {2} 记录已存在
-response.validation.failed=输入验证失败
-validation.team.type=团队类型不能为空
-error.team.share.reward.empty=没有可领取的团队奖励
-error.team.invite.reward.empty=没有可领取的邀请奖励
-
+response.success=Operation successful
+response.server.error=Server error
+response.not.found=Resource not found
+response.unsupported.media.type=Unsupported media type
+response.request.too.large=Request body too large
+response.bad.request=Invalid request parameters
+response.forbidden=Permission denied
+response.unauthorized=User not logged in or user information is empty
+response.failed=Operation failed
+response.expected.file.missing=Expected file is missing
+request.api.not.exists=Requested API does not exist
+response.error.encrypt=Encryption/Decryption error
+no.gold.claimed=No gold available to claim
+response.user.disable=This account has been disabled. Please contact the administrator if you have any questions
+error.gold.product.record.not.exists=Gold production rate upgrade rule does not exist
+error.gold.product.not.start=User has not started gold production
+error.gold.product.end=Gold production has ended or not started, boost is no longer effective
+error.gold.product.coin.not.claimed=Unclaimed gold exists, cannot restart production
+error.gold.product.exists=Gold production is in progress, cannot start again
+error.gold.boost.temporary.exists=Temporary rate already exists for the current production batch, upgrade not allowed
+error.gold.insufficient=Insufficient gold
+error.not.exists={2} record with {0} [{1}] no longer exists
+error.exists={2} record with {0} [{1}] already exists
+response.validation.failed=Input validation failed
+validation.team.type=Team type cannot be empty
+validation.team.claim.ruleId=Rule ID cannot be empty
+validation.login.tg.id.not.null=tg account ID cannot be null
+error.team.share.reward.empty=No team rewards available to claim
+error.team.invite.reward.empty=No invitation rewards available to claim
+error.team.invite.reward.task.claim.done=Invitation task already completed
+error.team.invite.reward.task.claim.exists=Invitation reward for this level has already been claimed
+error.team.invite.reward.task.claim.previous.not.claim=Previous level invitation reward has not been claimed
+error.team.invite.reward.task.claim.not.enough=Current invitation task not completed, insufficient number of invited friends
+error.team.invite.reward.task.claim.not.found=Invitation reward rule not found
+error.task.handle.id.is.null=Task ID cannot be empty
+error.task.handle.not.exist=Task does not exist

+ 1 - 0
src/main/resources/messages/messages_zh_CN.properties

@@ -24,6 +24,7 @@ error.exists={0} 为 [{1}] 的 {2} 记录已存在
 response.validation.failed=输入验证失败
 validation.team.type=团队类型不能为空
 validation.team.claim.ruleId=规则id不能为空
+validation.login.tg.id.not.null=tg账号id不能为空
 error.team.share.reward.empty=没有可领取的团队奖励
 error.team.invite.reward.empty=没有可领取的邀请奖励
 error.team.invite.reward.task.claim.done=邀请任务已完成