8 Commits

  1. 6
      pom.xml
  2. 39
      src/main/java/com/example/demo/config/RateLimitUtil.java
  3. 58
      src/main/java/com/example/demo/controller/cash/CashRefundController.java
  4. 1
      src/main/java/com/example/demo/domain/entity/User.java
  5. 5
      src/main/java/com/example/demo/domain/vo/cash/CashCollection.java
  6. 3
      src/main/java/com/example/demo/domain/vo/cash/CashRecordDone.java
  7. 2
      src/main/java/com/example/demo/domain/vo/coin/GoldUser.java
  8. 3
      src/main/java/com/example/demo/domain/vo/coin/RechargeActivity.java
  9. 2
      src/main/java/com/example/demo/mapper/cash/CashCollectionMapper.java
  10. 7
      src/main/java/com/example/demo/serviceImpl/bean/BeanConsumeServiceImpl.java
  11. 20
      src/main/java/com/example/demo/serviceImpl/cash/CashAuditServiceImpl.java
  12. 5
      src/main/java/com/example/demo/serviceImpl/cash/CashCollectionServiceImpl.java
  13. 36
      src/main/java/com/example/demo/serviceImpl/cash/CashRefundServiceImpl.java
  14. 3
      src/main/java/com/example/demo/serviceImpl/cash/MessageServiceImpl.java
  15. 2
      src/main/resources/cashMapper/CashCollectionMapper.xml
  16. 21
      src/main/resources/cashMapper/CashRefundMapper.xml

6
pom.xml

@ -70,6 +70,12 @@
<artifactId>hutool-all</artifactId>
<version>5.8.24</version>
</dependency>
<!-- Guava:提供缓存+过期时间功能,适合本地限流 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version> <!-- 推荐稳定版 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

39
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/IPvalue=占位符无实际意义
private static final Cache<String, Object> RATE_LIMIT_CACHE = CacheBuilder.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS) // 3秒后自动过期时间窗口
.maximumSize(5000) // 最大缓存容量防止内存溢出根据用户量调整
.build();
/**
* 判断是否允许请求
* @param key 限流唯一标识如用户IDIP
* @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);
}
}

58
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;
}
}

1
src/main/java/com/example/demo/domain/entity/User.java

@ -28,6 +28,7 @@ public class User implements Serializable {
private String name; // 客户姓名
@ExcelProperty("所属地区")
private String market; // 所属地区
@ExcelIgnore
private String marketName; // 所属地区
@ExcelIgnore
private BigDecimal sumPermanentGold; // 历史永久金币

5
src/main/java/com/example/demo/domain/vo/cash/CashCollection.java

@ -30,6 +30,7 @@ public class CashCollection implements Serializable {
@ExcelProperty("序号")
private Integer id;
//订单信息
@ExcelIgnore
private Integer orderType; // 订单类型1-收款2-退款
@ExcelProperty("精网号")
private Integer jwcode; // 精网号
@ -68,7 +69,7 @@ public class CashCollection implements Serializable {
private BigDecimal receivedAmount; // 到账金额
@ExcelProperty("手续费")
private BigDecimal handlingCharge; // 手续费
@ExcelProperty("到账币种")
@ExcelProperty("到账地区")
private String receivedMarket; //到账地区
// 支付信息
@ExcelProperty("支付方式")
@ -90,7 +91,7 @@ public class CashCollection implements Serializable {
private String submitterName; // 提交人 姓名
@ExcelProperty("转账凭证")
private String voucher; // 转账凭证
@ExcelIgnore
@ExcelProperty("备注")
private String remark; // 备注
@ExcelIgnore
private String receivedRemark; //到账备注

3
src/main/java/com/example/demo/domain/vo/cash/CashRecordDone.java

@ -119,6 +119,9 @@ public class CashRecordDone {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date eTime; // 结束时间
private Integer relatedId;
private BigDecimal NewRefundGold;
private BigDecimal NewRefundFree;
private Integer adminId;
}

2
src/main/java/com/example/demo/domain/vo/coin/GoldUser.java

@ -1,5 +1,6 @@
package com.example.demo.domain.vo.coin;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
@ -56,5 +57,6 @@ public class GoldUser {
@ExcelProperty("首充日期")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date firstRecharge; // 首充日期
@ExcelIgnore
private List<String> markets; // 地区列表
}

3
src/main/java/com/example/demo/domain/vo/coin/RechargeActivity.java

@ -1,5 +1,6 @@
package com.example.demo.domain.vo.coin;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
@ -30,7 +31,7 @@ public class RechargeActivity {
@ExcelProperty("业绩归属地")
private String businessBelong; // 业绩归属地
@ExcelIgnore
private String area; // 地区
@ExcelProperty("地区")

2
src/main/java/com/example/demo/mapper/cash/CashCollectionMapper.java

@ -51,7 +51,7 @@ public interface CashCollectionMapper {
//根据精网号获取市场名
String getMarketNameByJwcode(Integer jwcode);
//获取收款活动列表
List<RechargeActivity> getActivityList();
List<RechargeActivity> getActivityList(@Param("now")LocalDateTime now);
//查找未同步的订单
List<GOrder>getUnSync(@Param("size")int size);
//给同步过去的gOrder设置同步状态

7
src/main/java/com/example/demo/serviceImpl/bean/BeanConsumeServiceImpl.java

@ -158,7 +158,7 @@ public class BeanConsumeServiceImpl implements BeanConsumeService {
//筛选查询直播消费
@Override
public Object selectLiveBy(Integer pageNum, Integer pageSize, BeanConsumeLive beanConsumeLive) {
PageHelper.startPage(pageNum, pageSize);
String channel=roleMapper.getChannel(beanConsumeLive.getRoleId());
if (channel==null){
return "角色频道有误";
@ -166,7 +166,7 @@ public class BeanConsumeServiceImpl implements BeanConsumeService {
if (!channel.equals("全部")){
beanConsumeLive.setLiveChannel(channel);
}
PageHelper.startPage(pageNum, pageSize);
List<BeanConsumeLive> beanConsumeLives = liveMapper.selectLiveBy(beanConsumeLive);
//int total = liveMapper.selectLiveCount(beanConsumeLive);
return new PageInfo<>(beanConsumeLives);
@ -174,7 +174,7 @@ public class BeanConsumeServiceImpl implements BeanConsumeService {
//筛选查询铁粉消费
@Override
public Object selectFanBy(Integer pageNum, Integer pageSize, BeanConsumeFan beanConsumeFan) {
PageHelper.startPage(pageNum, pageSize);
String channel=roleMapper.getChannel(beanConsumeFan.getRoleId());
if (channel==null){
return "角色频道有误";
@ -182,6 +182,7 @@ public class BeanConsumeServiceImpl implements BeanConsumeService {
if (!channel.equals("全部")){
beanConsumeFan.setChannel(channel);
}
PageHelper.startPage(pageNum, pageSize);
List<BeanConsumeFan> beanConsumeFans = beanConsumeMapper.selectFanBy(beanConsumeFan);
return new PageInfo<>(beanConsumeFans);
}

20
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) {
// 发送审核通过消息

5
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());
@ -326,7 +326,8 @@ public User getNameAndMarket(Integer jwcode) {
//获取收款活动列表
@Override
public List<RechargeActivity> getActivityList() {
return cashCollectionMapper.getActivityList();
LocalDateTime now = LocalDateTime.now();
return cashCollectionMapper.getActivityList(now);
}
//同步g_order订单到cash_record表

36
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());
@ -232,8 +232,28 @@ public class CashRefundServiceImpl implements RefundService {
if (cashRecordDone.getRefundReason()== null) {
throw new RuntimeException("请填写退款理由");
}
if(cashRecordDone.getNewRefundGold()== null){
cashRecordDone.setNewRefundGold(BigDecimal.valueOf(0));
}
if(cashRecordDone.getNewRefundFree()== null){
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
@ -263,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();
@ -346,7 +365,7 @@ CashRecordDone cashRecordDone1 = new CashRecordDone();
userGoldRecord.setGoodsName(cashRecordDone.getGoodsName());
userGoldRecord.setPayPlatform("金币系统");
userGoldRecord.setRemark(cashRecordDone.getRemark());
userGoldRecord.setAdminId(cashRecordDone.getAuditId());
userGoldRecord.setAdminId(cashRecordDone.getAdminId());
userGoldRecord.setAuditStatus(1);
userGoldRecord.setTaskGold(0);
userGoldRecord.setCreateTime(new Date());
@ -372,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();
}

3
src/main/java/com/example/demo/serviceImpl/cash/MessageServiceImpl.java

@ -24,6 +24,9 @@ public class MessageServiceImpl implements MessageService {
private MessageMapper messageMapper;
@Override
public List<Messages> getMessage(List<String> markets, List<Integer> status) {
if(status== null|| status.size()==0){
status.add(99);
}
return messageMapper.getMessage(markets, status) ;
}

2
src/main/resources/cashMapper/CashCollectionMapper.xml

@ -222,7 +222,7 @@
select ra.id,ra.activity_name,ra.business_belong,m.name as area,ra.status
from recharge_activity ra
left join market m on m.id=ra.area
where ra.flag=1
where ra.flag=1 and ra.status=1 and #{now} between start_time and end_time
</select>
<!--查找未同步的订单-->
<select id="getUnSync" resultType="com.example.demo.domain.entity.GOrder">

21
src/main/resources/cashMapper/CashRefundMapper.xml

@ -72,13 +72,22 @@
)
</insert>
<!-- ✅ 正确写法:CashRefundMapper.xml -->
<update id="update">
update cash_record_refund
set
status = 10,
refund_model = #{refundModel},
refund_reason = #{refundReason}
where id = #{id}
UPDATE cash_record_refund
<set>
status = 10,
refund_model = #{refundModel},
refund_reason = #{refundReason},
<if test="newRefundGold != null">
permanent_gold = #{newRefundGold},
</if>
<if test="newRefundFree != null">
free_gold = #{newRefundFree}
<!-- 注意:最后一个字段不要加逗号!<set> 会自动处理 -->
</if>
</set>
WHERE id = #{id}
</update>
<update id="withdraw">
update cash_record_refund set status = 11

Loading…
Cancel
Save