From 7e9e5e164b5ef65ef9094354f0273c72f555e9d8 Mon Sep 17 00:00:00 2001 From: huangqizhen <15552608129@163.com> Date: Sun, 23 Nov 2025 16:59:18 +0800 Subject: [PATCH] =?UTF-8?q?11.23=20=E6=B6=88=E6=81=AF=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 +++ .../com/example/demo/config/RateLimitUtil.java | 39 +++++++++++++++ .../demo/controller/cash/CashRefundController.java | 58 ++++++++++++++++++++-- .../serviceImpl/cash/CashAuditServiceImpl.java | 20 ++++---- .../cash/CashCollectionServiceImpl.java | 2 +- .../serviceImpl/cash/CashRefundServiceImpl.java | 28 +++++++---- 6 files changed, 130 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/example/demo/config/RateLimitUtil.java diff --git a/pom.xml b/pom.xml index a329465..26116b3 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,12 @@ hutool-all 5.8.24 + + + com.google.guava + guava + 32.1.3-jre + org.springframework.boot spring-boot-starter-web diff --git a/src/main/java/com/example/demo/config/RateLimitUtil.java b/src/main/java/com/example/demo/config/RateLimitUtil.java new file mode 100644 index 0000000..12453b5 --- /dev/null +++ b/src/main/java/com/example/demo/config/RateLimitUtil.java @@ -0,0 +1,39 @@ +package com.example.demo.config; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.util.concurrent.TimeUnit; + +/** + * 本地限流工具类(基于Guava Cache) + */ +public class RateLimitUtil { + // 缓存:key=限流标识(用户ID/IP),value=占位符(无实际意义) + private static final Cache RATE_LIMIT_CACHE = CacheBuilder.newBuilder() + .expireAfterWrite(3, TimeUnit.SECONDS) // 3秒后自动过期(时间窗口) + .maximumSize(5000) // 最大缓存容量(防止内存溢出,根据用户量调整) + .build(); + + /** + * 判断是否允许请求 + * @param key 限流唯一标识(如用户ID、IP) + * @return true=允许请求,false=限流中 + */ + public static boolean isAllowed(String key) { + // 1. 尝试从缓存获取key,存在则说明3秒内已请求过(限流) + if (RATE_LIMIT_CACHE.getIfPresent(key) != null) { + return false; + } + // 2. 缓存中不存在,存入缓存(占位符用new Object()即可) + RATE_LIMIT_CACHE.put(key, new Object()); + return true; + } + + /** + * 手动移除限流标识(如接口执行失败时,释放限流) + * @param key 限流唯一标识 + */ + public static void removeKey(String key) { + RATE_LIMIT_CACHE.invalidate(key); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/demo/controller/cash/CashRefundController.java b/src/main/java/com/example/demo/controller/cash/CashRefundController.java index d3d0281..6f0339e 100644 --- a/src/main/java/com/example/demo/controller/cash/CashRefundController.java +++ b/src/main/java/com/example/demo/controller/cash/CashRefundController.java @@ -2,6 +2,7 @@ package com.example.demo.controller.cash; import com.example.demo.Util.JWTUtil; +import com.example.demo.config.RateLimitUtil; import com.example.demo.domain.entity.Admin; import com.example.demo.domain.vo.cash.CashRecordDTO; import com.example.demo.domain.vo.cash.CashRecordDone; @@ -26,6 +27,7 @@ import org.springframework.web.context.request.ServletRequestAttributes; import java.util.Arrays; import java.util.List; +import java.util.Objects; /** * @program: GOLD @@ -196,9 +198,38 @@ public class CashRefundController { } } @PostMapping("/finalReview") - public Result finalReview(@RequestBody CashRecordDone cashRecordDone) { - return Result.success(refundService.finalreview(cashRecordDone)); - } + public Result finalReview(@RequestBody CashRecordDone cashRecordDone,HttpServletRequest request) { + { + // --------------- 限流逻辑开始 --------------- + String limitKey = null; + try { + // 1. 优先用「用户ID」作为限流标识(从token解析,精准限流) + HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); + String token = req.getHeader("token"); + Admin admin = (Admin) JWTUtil.getUserDetailsList(String.valueOf(token), Admin.class); + if (admin != null && admin.getId() != null) { + limitKey = "finalReview_" + admin.getId(); // 格式:接口名_用户ID + } + } catch (Exception e) { + // token解析失败(用户未登录),降级用「IP地址」限流 + limitKey = "finalReview_" + getIpAddress(request); // 格式:接口名_IP + } + + // 2. 校验限流:3秒内同一key不允许重复请求 + if (Objects.isNull(limitKey) || !RateLimitUtil.isAllowed(limitKey)) { + return Result.error("3秒内只能请求一次,请稍后再试"); // 限流提示 + } + // --------------- 限流逻辑结束 --------------- + + try { + // 原有业务逻辑:执行最终审核 + return Result.success(refundService.finalreview(cashRecordDone)); + } catch (Exception e) { + // 接口执行失败时,移除限流标识(允许用户重新尝试) + RateLimitUtil.removeKey(limitKey); + return Result.error("审核失败:" + e.getMessage()); + } + }} @PostMapping("/executor") public Result executor(@RequestBody CashRecordDone cashRecordDone) throws Exception { try { @@ -239,4 +270,25 @@ public class CashRefundController { public Result ceshi() { return Result.success("测试消息"); } + + /** + * 辅助方法:获取用户真实IP(处理反向代理/负载均衡场景) + */ + private String getIpAddress(HttpServletRequest request) { + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + // 多IP场景(如多层代理),取第一个非unknown的IP + if (ip != null && ip.contains(",")) { + ip = ip.split(",")[0].trim(); + } + return ip; + } } diff --git a/src/main/java/com/example/demo/serviceImpl/cash/CashAuditServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/cash/CashAuditServiceImpl.java index 69f2a40..328c554 100644 --- a/src/main/java/com/example/demo/serviceImpl/cash/CashAuditServiceImpl.java +++ b/src/main/java/com/example/demo/serviceImpl/cash/CashAuditServiceImpl.java @@ -6,6 +6,7 @@ import com.example.demo.domain.entity.CashRecord; import com.example.demo.domain.entity.User; import com.example.demo.domain.entity.UserGoldRecord; import com.example.demo.domain.vo.cash.CashCollectionMessage; +import com.example.demo.domain.vo.coin.Messages; import com.example.demo.mapper.cash.CashAuditMapper; import com.example.demo.mapper.cash.CashCollectionMapper; import com.example.demo.mapper.coin.AuditMapper; @@ -130,17 +131,16 @@ public class CashAuditServiceImpl implements CashAuditService { //更新订单 cashAuditMapper.updateOrder(updateOrder); // 创建消息队列,用于发送审核结果通知 - CashCollectionMessage message = new CashCollectionMessage(); - message.setId(order.getId()); - message.setOrderCode(orderCode); + Messages message = new Messages(); + message.setJwcode(order.getJwcode()); + message.setName(order.getName()); message.setStatus(updateOrder.getStatus()); - message.setStatusDescription(action == 1 ? "线下财务审核通过待填手续费" : "线下财务审核驳回"); - message.setMessage(action == 1 ? "收款订单审核通过" : "收款订单审核驳回"); - message.setSubmitterId(order.getSubmitterId()); - message.setAuditId(auditId); - message.setAuditName(auditName); - message.setTimestamp(LocalDateTime.now()); - + message.setDesc(order.getJwcode() + action==1?"收款记录需补充手续费,前往填写":"现金收款申请已被驳回,前往查看驳回理"); + message.setTitle(action==1?"收款订单审核通过":"收款订单审核驳回"); + message.setType(1); + message.setTypeId(order.getId()); + message.setMarket(Integer.valueOf(order.getMarket())); + rabbitTemplate.convertAndSend(RabbitMQConfig.CASH_COLLECTION_EXCHANGE, "cash.collection.save", message); // 根据审核结果发送不同的消息 if (action == 1) { // 发送审核通过消息 diff --git a/src/main/java/com/example/demo/serviceImpl/cash/CashCollectionServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/cash/CashCollectionServiceImpl.java index 891dd33..53628c0 100644 --- a/src/main/java/com/example/demo/serviceImpl/cash/CashCollectionServiceImpl.java +++ b/src/main/java/com/example/demo/serviceImpl/cash/CashCollectionServiceImpl.java @@ -132,7 +132,7 @@ public class CashCollectionServiceImpl implements CashCollectionService { message.setJwcode(cashRecord.getJwcode()); message.setName(cashRecord.getName()); message.setStatus(cashRecord.getStatus()); - message.setDesc(cashRecord.getJwcode()+"用户有条收款订单需审核"); + message.setDesc(cashRecord.getJwcode()+"用户的现金收款申请待审核,请前往审核"); message.setTitle("现金收款--新增收款"); message.setType(1); message.setTypeId(cashRecord.getId()); diff --git a/src/main/java/com/example/demo/serviceImpl/cash/CashRefundServiceImpl.java b/src/main/java/com/example/demo/serviceImpl/cash/CashRefundServiceImpl.java index 1cad9dd..cf01b1b 100644 --- a/src/main/java/com/example/demo/serviceImpl/cash/CashRefundServiceImpl.java +++ b/src/main/java/com/example/demo/serviceImpl/cash/CashRefundServiceImpl.java @@ -204,7 +204,7 @@ public class CashRefundServiceImpl implements RefundService { message.setJwcode(cashRecordRefund.getJwcode()); message.setName(cashRecordRefund.getName()); message.setStatus(cashRecordRefund.getStatus()); - message.setDesc(cashRecordRefund.getJwcode()+"用户有条退款订单需审核"); + message.setDesc(cashRecordRefund.getJwcode()+"用户的客服退款申请待审核,前往处理"); message.setTitle("现金退款--新增退款"); message.setType(0); message.setTypeId(cashRecordRefund.getId()); @@ -239,7 +239,21 @@ public class CashRefundServiceImpl implements RefundService { cashRecordDone.setNewRefundFree(BigDecimal.valueOf(0)); } int result = cashRefundMapper.update(cashRecordDone); - return (result > 0 ? Result.success("提交成功") : Result.error("提交失败")).getCode(); + CashRecordDTO cashRecordDTO = cashRefundMapper.selectById(cashRecordDone.getId()); + if (result > 0) { + // 发送审核消息 + Messages message = new Messages(); + message.setJwcode(cashRecordDTO.getJwcode()); + message.setName(cashRecordDTO.getName()); + message.setStatus(cashRecordDTO.getStatus()); + message.setDesc(cashRecordDTO.getJwcode() + "用户的退款申请待审核,前往处理"); + message.setTitle("现金退款--当地退款审核(编辑后提交)"); + message.setType(1); + message.setTypeId(cashRecordDTO.getId()); + message.setMarket(cashRecordDTO.getMarket()); + rabbitTemplate.convertAndSend(RabbitMQConfig.CASH_REFUND_EXCHANGE, "cash.refund.save", message); + } + return (result > 0 ? Result.success("提交成功") : Result.error("提交失败")).getCode(); } @Override @@ -269,15 +283,14 @@ CashRecordDone cashRecordDone1 = new CashRecordDone(); message.setJwcode(cashRecordDTO.getJwcode()); message.setName(cashRecordDTO.getName()); message.setStatus(cashRecordDTO.getStatus()); - message.setDesc(cashRecordDTO.getJwcode()+"用户有条退款订单需审核"); + message.setDesc(cashRecordDTO.getJwcode()+cashRecordDTO.getStatus()!=12|| cashRecordDTO.getStatus()!=22?"用户的退款申请待审核,前往处理":"用户的现金退款申请已被驳回,前往查看详情"); message.setTitle("现金退款--当地退款审核"); message.setType(1); message.setTypeId(cashRecordDTO.getId()); message.setMarket(cashRecordDTO.getMarket()); - if (cashRecordDTO.getStatus() != 12 || cashRecordDTO.getStatus() != 22) { rabbitTemplate.convertAndSend(RabbitMQConfig.CASH_REFUND_EXCHANGE, "cash.refund.save", message); - } + } return (result > 0 ? Result.success("提交成功") : Result.error("提交失败")).getCode(); @@ -378,15 +391,12 @@ CashRecordDone cashRecordDone1 = new CashRecordDone(); message.setJwcode(cashRecordDTO.getJwcode()); message.setName(cashRecordDTO.getName()); message.setStatus(cashRecordDTO.getStatus()); - message.setDesc(cashRecordDTO.getJwcode()+"用户有条退款订单需审核"); + message.setDesc(cashRecordDTO.getJwcode()+cashRecordDTO.getStatus()!=32?"用户的退款申请待审核,前往处理":"用户的现金退款申请已被驳回,前往查看详情"); message.setTitle("现金退款--执行人退款提交"); message.setType(1); message.setTypeId(cashRecordDTO.getId()); message.setMarket(cashRecordDTO.getMarket()); - if (cashRecordDTO.getStatus() != 32) { - rabbitTemplate.convertAndSend(RabbitMQConfig.CASH_REFUND_EXCHANGE, "cash.refund.save", message); - } } return (result > 0 ? Result.success("提交成功") : Result.error("提交失败")).getCode(); }