Selaa lähdekoodia

feat(income): 联调修改

wxd 2 viikkoa sitten
vanhempi
commit
ee34b48194

+ 14 - 1
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreIncomeDetailsRecordVo.java

@@ -31,7 +31,7 @@ public class StoreIncomeDetailsRecordVo extends StoreIncomeDetailsRecord {
     String incomeMoney;
 
     @ApiModelProperty(value = "订单价格(原价)")
-    String price;
+    String orderPrice;
 
 
     @ApiModelProperty(value = "手续费Str")
@@ -79,5 +79,18 @@ public class StoreIncomeDetailsRecordVo extends StoreIncomeDetailsRecord {
     @ApiModelProperty(value = "抽成比例")
     private String commissionRate;
 
+    @ApiModelProperty(value = "收入明细列表")
     List<StoreIncomeDetailsRecordVo> incomeDetailsRecordVoList;
+
+    @ApiModelProperty(value = "总记录数")
+    private Long total;
+
+    @ApiModelProperty(value = "总页数")
+    private Long totalPages;
+
+    @ApiModelProperty(value = "当前页")
+    private Long currentPage;
+
+    @ApiModelProperty(value = "每页大小")
+    private Long pageSize;
 }

+ 2 - 1
alien-entity/src/main/java/shop/alien/mapper/StoreIncomeDetailsRecordMapper.java

@@ -20,7 +20,7 @@ import java.util.List;
 public interface StoreIncomeDetailsRecordMapper extends BaseMapper<StoreIncomeDetailsRecord> {
 
     @Select("select income.id, income.money, income.commission, income.created_time checkTime, uorder.created_time orderTime, date_add(income.created_time, interval 3 day) incomeTime, " +
-            "       income_type, uorder.quan_code,  uorder.price, coupon.name couponName, coupon.coupon_code, store.store_name, if(lfo.store_id is not null ,'true','false') refundType " +
+            "       income_type, uorder.quan_code,  uorder.price as orderPrice, coupon.name couponName, coupon.coupon_code, store.store_name, if(lfo.store_id is not null ,'true','false') refundType " +
             "from store_income_details_record income " +
             "join store_info store on store.id = income.store_id " +
             "left join life_user_order uorder on uorder.id = income.user_order_id " +
@@ -43,6 +43,7 @@ public interface StoreIncomeDetailsRecordMapper extends BaseMapper<StoreIncomeDe
             "SELECT\n" +
             "\tsidr.id,sidr.updated_time,sidr.created_user_id,sidr.created_time,sidr.income_type,sidr.business_id,sidr.cash_out_id,sidr.store_id,sidr.user_order_id,sidr.delete_flag,sidr.money,sidr.commission,sidr.updated_user_id,\n" +
             "\tluo.buy_time orderTime,\n" +
+            "\tluo.price orderPrice,\n" +
             "\tsidr.created_time checkTime,\n" +
             "\tADDDATE(sidr.created_time, 4) incomeTime,\n" +
             "\ttc.name couponName\n" +

+ 32 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/annotation/PreventDuplicateSubmit.java

@@ -0,0 +1,32 @@
+package shop.alien.storeplatform.annotation;
+
+import java.lang.annotation.*;
+
+/**
+ * 防重复提交注解
+ * 
+ * @author ssk
+ * @since 2025-11-25
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface PreventDuplicateSubmit {
+    
+    /**
+     * 锁的过期时间(秒)
+     * 默认5秒内不能重复提交
+     */
+    int expireSeconds() default 5;
+    
+    /**
+     * 锁的key前缀
+     */
+    String keyPrefix() default "prevent_duplicate";
+    
+    /**
+     * 错误提示信息
+     */
+    String message() default "操作过于频繁,请稍后再试";
+}
+

+ 169 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/aspect/PreventDuplicateSubmitAspect.java

@@ -0,0 +1,169 @@
+package shop.alien.storeplatform.aspect;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+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.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import shop.alien.entity.result.R;
+import shop.alien.storeplatform.annotation.PreventDuplicateSubmit;
+import shop.alien.storeplatform.util.LoginUserUtil;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 防重复提交切面
+ * 
+ * @author ssk
+ * @since 2025-11-25
+ */
+@Aspect
+@Component
+@Slf4j
+@RequiredArgsConstructor
+public class PreventDuplicateSubmitAspect {
+
+    private final StringRedisTemplate stringRedisTemplate;
+
+    @Around("@annotation(shop.alien.storeplatform.annotation.PreventDuplicateSubmit)")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        // 1. 获取注解信息
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        PreventDuplicateSubmit annotation = method.getAnnotation(PreventDuplicateSubmit.class);
+
+        // 2. 构建Redis锁的key
+        String lockKey = buildLockKey(joinPoint, annotation);
+        
+        log.info("PreventDuplicateSubmitAspect - 尝试获取锁: key={}", lockKey);
+
+        // 3. 尝试获取锁
+        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(
+                lockKey,
+                String.valueOf(System.currentTimeMillis()),
+                annotation.expireSeconds(),
+                TimeUnit.SECONDS
+        );
+
+        // 4. 获取锁失败,说明重复提交
+        if (success == null || !success) {
+            log.warn("PreventDuplicateSubmitAspect - 重复提交被拦截: key={}", lockKey);
+            return R.fail(annotation.message());
+        }
+
+        try {
+            // 5. 执行业务方法
+            log.info("PreventDuplicateSubmitAspect - 获取锁成功,执行业务: key={}", lockKey);
+            Object result = joinPoint.proceed();
+            
+            // 6. 如果业务执行失败,立即释放锁,允许重试
+            if (result instanceof R) {
+                R<?> r = (R<?>) result;
+                if (!r.isSuccess()) {
+                    stringRedisTemplate.delete(lockKey);
+                    log.info("PreventDuplicateSubmitAspect - 业务执行失败,释放锁: key={}", lockKey);
+                }
+            }
+            
+            return result;
+        } catch (Exception e) {
+            // 7. 发生异常,释放锁
+            stringRedisTemplate.delete(lockKey);
+            log.error("PreventDuplicateSubmitAspect - 执行异常,释放锁: key={}", lockKey, e);
+            throw e;
+        }
+    }
+
+    /**
+     * 构建锁的key
+     * 格式: prefix:userId:methodName:参数摘要
+     */
+    private String buildLockKey(ProceedingJoinPoint joinPoint, PreventDuplicateSubmit annotation) {
+        // 获取当前用户ID
+        Integer userId = null;
+        try {
+            userId = LoginUserUtil.getCurrentUserId();
+        } catch (Exception e) {
+            log.warn("PreventDuplicateSubmitAspect - 无法获取用户ID,使用IP作为标识");
+        }
+
+        // 获取方法名
+        String methodName = joinPoint.getSignature().getName();
+
+        // 获取请求IP(作为备用标识)
+        String ip = getRequestIP();
+
+        // 获取参数摘要(只取金额等关键参数)
+        String paramKey = buildParamKey(joinPoint.getArgs());
+
+        // 构建key: prevent_duplicate:cashOut:userId_123:10000
+        return String.format("%s:%s:%s:%s",
+                annotation.keyPrefix(),
+                methodName,
+                userId != null ? "user_" + userId : "ip_" + ip,
+                paramKey
+        );
+    }
+
+    /**
+     * 构建参数key(针对提现金额等关键参数)
+     */
+    private String buildParamKey(Object[] args) {
+        if (args == null || args.length == 0) {
+            return "noargs";
+        }
+
+        // 针对提现接口,提取金额参数
+        StringBuilder sb = new StringBuilder();
+        for (Object arg : args) {
+            if (arg == null) {
+                continue;
+            }
+            
+            // 如果是CashOutDTO,提取金额
+            if (arg.getClass().getSimpleName().contains("CashOutDTO")) {
+                try {
+                    Method getWithdrawalMoney = arg.getClass().getMethod("getWithdrawalMoney");
+                    Object money = getWithdrawalMoney.invoke(arg);
+                    sb.append(money);
+                } catch (Exception e) {
+                    log.debug("PreventDuplicateSubmitAspect - 无法提取提现金额");
+                }
+            }
+        }
+
+        return sb.length() > 0 ? sb.toString() : "default";
+    }
+
+    /**
+     * 获取请求IP
+     */
+    private String getRequestIP() {
+        try {
+            ServletRequestAttributes attributes = 
+                (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+            if (attributes != null) {
+                HttpServletRequest request = attributes.getRequest();
+                String ip = request.getHeader("X-Forwarded-For");
+                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+                    ip = request.getHeader("X-Real-IP");
+                }
+                if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
+                    ip = request.getRemoteAddr();
+                }
+                return ip != null ? ip.replaceAll("[^0-9.]", "") : "unknown";
+            }
+        } catch (Exception e) {
+            log.warn("PreventDuplicateSubmitAspect - 获取IP失败", e);
+        }
+        return "unknown";
+    }
+}
+

+ 3 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/IncomeManageController.java

@@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreCashOutRecord;
+import shop.alien.storeplatform.annotation.PreventDuplicateSubmit;
 import shop.alien.storeplatform.dto.CashOutDTO;
 import shop.alien.storeplatform.service.IncomeManageService;
 import shop.alien.storeplatform.util.LoginUserUtil;
@@ -79,6 +80,7 @@ public class IncomeManageController {
 
     @ApiOperation("提现申请")
     @ApiOperationSupport(order = 3)
+    @PreventDuplicateSubmit(expireSeconds = 5, message = "提现操作过于频繁,请稍后再试")
     @PostMapping("/cashOut")
     public R<?> cashOut(@RequestBody CashOutDTO cashOutDTO) {
         log.info("IncomeManageController.cashOut? payPassword=***, withdrawalMoney={}",
@@ -180,6 +182,7 @@ public class IncomeManageController {
 
     @ApiOperation("快速提现申请")
     @ApiOperationSupport(order = 7)
+    @PreventDuplicateSubmit(expireSeconds = 5, message = "快速提现操作过于频繁,请稍后再试")
     @PostMapping("/applyFastCashOut")
     public R<?> applyFastCashOut(@RequestBody CashOutDTO cashOutDTO) {
         log.info("IncomeManageController.applyFastCashOut? payPassword=***, withdrawalMoney={}",

+ 34 - 16
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/IncomeManageServiceImpl.java

@@ -298,8 +298,7 @@ public class IncomeManageServiceImpl extends ServiceImpl<StoreIncomeDetailsRecor
             storeCashOutRecord.setIncomeStartTime(new Date());
             storeCashOutRecord.setIncomeEndTime(new Date());
             storeCashOutRecord.setStoreUserId(storeUser.getId());
-            storeCashOutRecordMapper.insert(storeCashOutRecord);
-            
+
             // 插入提现记录
             int insertResult = storeCashOutRecordMapper.insert(storeCashOutRecord);
             if (insertResult <= 0) {
@@ -308,14 +307,14 @@ public class IncomeManageServiceImpl extends ServiceImpl<StoreIncomeDetailsRecor
             }
 
             // 6. 扣减账户余额
-            StoreUser updateUser = new StoreUser();
-            updateUser.setId(storeUser.getId());
-            updateUser.setMoney(storeUser.getMoney() - withdrawalMoney);
-            int updateResult = storeUserMapper.updateById(updateUser);
-            if (updateResult <= 0) {
-                log.error("IncomeManageServiceImpl.cashOut - 更新账户余额失败");
-                throw new RuntimeException("更新账户余额失败");
-            }
+//            StoreUser updateUser = new StoreUser();
+//            updateUser.setId(storeUser.getId());
+//            updateUser.setMoney(storeUser.getMoney() - withdrawalMoney);
+//            int updateResult = storeUserMapper.updateById(updateUser);
+//            if (updateResult <= 0) {
+//                log.error("IncomeManageServiceImpl.cashOut - 更新账户余额失败");
+//                throw new RuntimeException("更新账户余额失败");
+//            }
 
             log.info("IncomeManageServiceImpl.cashOut - 提现申请成功: 提现记录ID={}, 提现金额={}元",
                     storeCashOutRecord.getId(), amountInYuan);
@@ -570,16 +569,34 @@ public class IncomeManageServiceImpl extends ServiceImpl<StoreIncomeDetailsRecor
         vo.setRefundMoney(new BigDecimal(0));
         vo.setCouponCount(0);
 
-        // 正常收入记录(未退款)
+        // 过滤出正常收入记录(不含退款的记录用于分页)
+        List<StoreIncomeDetailsRecordVo> normalRecords = incomeDetailsRecordVoList.stream()
+                .filter(x -> !"true".equals(x.getRefundType()))
+                .collect(Collectors.toList());
+
+        // 正常收入记录(未退款)- 进行分页
         if (collect.containsKey("false")) {
-            vo.setIncomeDetailsRecordVoList(
-                    ListToPage.setPage(incomeDetailsRecordVoList, page, size).getRecords()
-            );
+            // 使用过滤后的正常记录进行分页
+            IPage<StoreIncomeDetailsRecordVo> pageResult = ListToPage.setPage(normalRecords, page, size);
+            vo.setIncomeDetailsRecordVoList(pageResult.getRecords());
+            
+            // 设置分页信息
+            vo.setTotal(pageResult.getTotal());
+            vo.setTotalPages(pageResult.getPages());
+            vo.setCurrentPage(pageResult.getCurrent());
+            vo.setPageSize(pageResult.getSize());
+            
             // 计算券数量(正常订单 - 退款订单)
             vo.setCouponCount(
                     collect.get("false").size() -
                             (collect.containsKey("true") ? collect.get("true").size() : 0)
             );
+        } else {
+            // 没有正常记录时,也设置分页信息
+            vo.setTotal(0L);
+            vo.setTotalPages(0L);
+            vo.setCurrentPage((long) page);
+            vo.setPageSize((long) size);
         }
 
         // 退款金额统计
@@ -593,8 +610,9 @@ public class IncomeManageServiceImpl extends ServiceImpl<StoreIncomeDetailsRecor
             );
         }
 
-        log.info("IncomeManageServiceImpl.getGroupIncome - 查询完成: 总收入={}元, 手续费={}元, 券数量={}",
-                vo.getNoYetPaymentMoney(), vo.getCommissionStr(), vo.getCouponCount());
+        log.info("IncomeManageServiceImpl.getGroupIncome - 查询完成: 总收入={}元, 手续费={}元, 券数量={}, 记录总数={}, 当前页={}/{}, 总页数={}",
+                vo.getNoYetPaymentMoney(), vo.getCommissionStr(), vo.getCouponCount(), 
+                vo.getTotal(), vo.getCurrentPage(), vo.getPageSize(), vo.getTotalPages());
 
         return vo;
     }