diff --git a/.claude/settings.json b/.claude/settings.json index 7c448d9..ec89e79 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -24,7 +24,10 @@ "Bash(git check-ignore:*)", "Bash(git commit:*)", "Bash(git push:*)", - "Bash(git restore:*)" + "Bash(git restore:*)", + "Bash(ssh root@8.155.172.147 \"tail -100 /www/wwwroot/logs/app.log\")", + "Bash(mvn compile:*)", + "Bash(git checkout:*)" ], "additionalDirectories": [ "/Users/sion/Desktop/projects/monisuo/monisuo-admin/.git" diff --git a/src/main/java/com/it/rattan/monisuo/filter/TokenFilter.java b/src/main/java/com/it/rattan/monisuo/filter/TokenFilter.java index 97f9ee8..048b7c5 100644 --- a/src/main/java/com/it/rattan/monisuo/filter/TokenFilter.java +++ b/src/main/java/com/it/rattan/monisuo/filter/TokenFilter.java @@ -48,42 +48,6 @@ public class TokenFilter implements Filter { } } - // 新增:钱包接口(用户充值前需要看到钱包地址) - if (uri.equals("/api/wallet/default")) { - return true; - } - - // 获取Token - String token = httpRequest.getHeader("Authorization"); - if (token != null && token.startsWith("Bearer ")) { - token = token.substring(7); - } - - if (token == null || token.isEmpty()) { - writeUnauthorized(httpResponse, "请先登录"); - return; - } - - // 验证Token - if (!JwtUtil.isValid(token)) { - writeUnauthorized(httpResponse, "Token已过期,请重新登录"); - return; - } - - // 设置用户上下文 - UserContext context = new UserContext(); - context.setUserId(JwtUtil.getUserId(token)); - context.setUsername(JwtUtil.getUsername(token)); - context.setType(JwtUtil.getType(token)); - UserContext.set(context); - - try { - chain.doFilter(request, response); - } finally { - UserContext.clear(); - } - } - // 获取Token String token = httpRequest.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { diff --git a/src/main/java/com/it/rattan/monisuo/service/FundService.java b/src/main/java/com/it/rattan/monisuo/service/FundService.java index ac72bcc..16b00e4 100644 --- a/src/main/java/com/it/rattan/monisuo/service/FundService.java +++ b/src/main/java/com/it/rattan/monisuo/service/FundService.java @@ -1,3 +1,112 @@ +package com.it.rattan.monisuo.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.it.rattan.monisuo.entity.AccountFund; +import com.it.rattan.monisuo.entity.ColdWallet; +import com.it.rattan.monisuo.entity.OrderFund; +import com.it.rattan.monisuo.entity.User; +import com.it.rattan.monisuo.mapper.AccountFundMapper; +import com.it.rattan.monisuo.mapper.OrderFundMapper; +import com.it.rattan.monisuo.mapper.UserMapper; +import com.it.rattan.monisuo.util.OrderNoUtil; +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.LocalDateTime; +import java.util.*; + +/** + * 充提服务 + * + * 状态定义: + * 充值: 1=待付款, 2=待确认, 3=已完成, 4=已驳回, 5=已取消 + * 提现: 1=待审批, 2=已完成, 3=已驳回, 4=已取消 + */ +@Service +public class FundService { + + @Autowired + private OrderFundMapper orderFundMapper; + + @Autowired + private AccountFundMapper accountFundMapper; + + @Autowired + private AssetService assetService; + + @Autowired + private ColdWalletService coldWalletService; + + @Autowired + private UserMapper userMapper; + + /** + * 申请充值 - 关联默认冷钱包 + */ + @Transactional + public Map deposit(Long userId, BigDecimal amount, String remark) { + if (amount.compareTo(BigDecimal.ZERO) <= 0) { + throw new RuntimeException("充值金额必须大于0"); + } + + User user = userMapper.selectById(userId); + if (user == null) { + throw new RuntimeException("用户不存在"); + } + + // 获取默认冷钱包 + ColdWallet wallet = coldWalletService.getDefaultWallet(); + if (wallet == null) { + throw new RuntimeException("系统暂未配置充值地址"); + } + + OrderFund order = new OrderFund(); + order.setOrderNo(OrderNoUtil.fundOrderNo()); + order.setUserId(userId); + order.setUsername(user.getUsername()); + order.setType(1); // 充值 + order.setAmount(amount); + order.setStatus(1); // 待付款 + order.setWalletId(wallet.getId()); + order.setWalletAddress(wallet.getAddress()); + order.setRemark(remark); + order.setCreateTime(LocalDateTime.now()); + orderFundMapper.insert(order); + + Map result = new HashMap<>(); + result.put("orderNo", order.getOrderNo()); + result.put("amount", amount); + result.put("status", order.getStatus()); + result.put("walletAddress", wallet.getAddress()); + result.put("walletNetwork", wallet.getNetwork()); + return result; + } + + /** + * 用户确认已打款 + */ + @Transactional + public void confirmPay(Long userId, String orderNo) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(OrderFund::getUserId, userId) + .eq(OrderFund::getOrderNo, orderNo) + .eq(OrderFund::getType, 1) // 仅充值订单 + .eq(OrderFund::getStatus, 1); // 仅待付款可确认 + + OrderFund order = orderFundMapper.selectOne(wrapper); + if (order == null) { + throw new RuntimeException("订单不存在或状态不可操作"); + } + + order.setStatus(2); // 待确认 + order.setPayTime(LocalDateTime.now()); + order.setUpdateTime(LocalDateTime.now()); + orderFundMapper.updateById(order); + } + /** * 申请提现 - 冻结余额 */ @@ -17,14 +126,233 @@ throw new RuntimeException("用户不存在"); } - // 新增:检查交易账户余额(提示) - AccountTrade tradeAccount = assetService.getOrCreateTradeAccount(userId, "USDT"); - if (tradeAccount.getQuantity().compareTo(BigDecimal.ZERO) > 0) { - throw new RuntimeException("交易账户有余额,请先划转到资金账户后再提现"); - } - - // 检查并冻结资金账户余额 + // 检查并冻结余额 AccountFund fund = assetService.getOrCreateFundAccount(userId); if (fund.getBalance().compareTo(amount) < 0) { throw new RuntimeException("资金账户余额不足"); } + + // 冻结余额 + fund.setBalance(fund.getBalance().subtract(amount)); + fund.setFrozen(fund.getFrozen() != null ? fund.getFrozen().add(amount) : amount); + fund.setUpdateTime(LocalDateTime.now()); + accountFundMapper.updateById(fund); + + // 创建订单 + OrderFund order = new OrderFund(); + order.setOrderNo(OrderNoUtil.fundOrderNo()); + order.setUserId(userId); + order.setUsername(user.getUsername()); + order.setType(2); // 提现 + order.setAmount(amount); + order.setStatus(1); // 待审批 + order.setWalletAddress(withdrawAddress); + order.setWithdrawContact(withdrawContact); + order.setRemark(remark); + order.setCreateTime(LocalDateTime.now()); + orderFundMapper.insert(order); + + Map result = new HashMap<>(); + result.put("orderNo", order.getOrderNo()); + result.put("amount", amount); + result.put("status", order.getStatus()); + return result; + } + + /** + * 取消订单 - 仅充值待付款状态可取消 + */ + @Transactional + public void cancel(Long userId, String orderNo) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(OrderFund::getUserId, userId) + .eq(OrderFund::getOrderNo, orderNo); + + OrderFund order = orderFundMapper.selectOne(wrapper); + if (order == null) { + throw new RuntimeException("订单不存在"); + } + + // 充值订单仅待付款可取消 + if (order.getType() == 1 && order.getStatus() != 1) { + throw new RuntimeException("当前状态不可取消"); + } + + // 提现订单仅待审批可取消,需要解冻余额 + if (order.getType() == 2) { + if (order.getStatus() != 1) { + throw new RuntimeException("当前状态不可取消"); + } + // 解冻余额 + AccountFund fund = assetService.getOrCreateFundAccount(userId); + fund.setBalance(fund.getBalance().add(order.getAmount())); + fund.setFrozen(fund.getFrozen().subtract(order.getAmount())); + fund.setUpdateTime(LocalDateTime.now()); + accountFundMapper.updateById(fund); + } + + order.setStatus(5); // 已取消 + order.setUpdateTime(LocalDateTime.now()); + orderFundMapper.updateById(order); + } + + /** + * 获取充提记录 + */ + public IPage getOrders(Long userId, Integer type, int pageNum, int pageSize) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(OrderFund::getUserId, userId); + if (type != null && type > 0) { + wrapper.eq(OrderFund::getType, type); + } + wrapper.orderByDesc(OrderFund::getCreateTime); + + Page page = new Page<>(pageNum, pageSize); + return orderFundMapper.selectPage(page, wrapper); + } + + /** + * 获取待审批订单数量 + */ + public int getPendingCount() { + // 充值待确认 + 提现待审批 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.and(w -> w.eq(OrderFund::getType, 1).eq(OrderFund::getStatus, 2)) + .or(w -> w.eq(OrderFund::getType, 2).eq(OrderFund::getStatus, 1)); + return Math.toIntExact(orderFundMapper.selectCount(wrapper)); + } + + /** + * 管理员审批 + * 充值: 仅待确认(status=2)可审批 + * 提现: 仅待审批(status=1)可审批 + */ + @Transactional + public void approve(Long adminId, String adminName, String orderNo, Integer status, + String rejectReason, String adminRemark) { + OrderFund order = orderFundMapper.selectOne( + new LambdaQueryWrapper().eq(OrderFund::getOrderNo, orderNo)); + + if (order == null) { + throw new RuntimeException("订单不存在"); + } + + // 充值审批: 仅待确认可审批 + if (order.getType() == 1 && order.getStatus() != 2) { + throw new RuntimeException("该充值订单不可审批,等待用户确认打款"); + } + + // 提现审批: 仅待审批可审批 + if (order.getType() == 2 && order.getStatus() != 1) { + throw new RuntimeException("该提现订单已处理"); + } + + AccountFund fund = assetService.getOrCreateFundAccount(order.getUserId()); + + if (status == 2) { + // 审批通过 + if (order.getType() == 1) { + // 充值通过:增加余额 + BigDecimal balanceBefore = fund.getBalance(); + fund.setBalance(fund.getBalance().add(order.getAmount())); + fund.setTotalDeposit(fund.getTotalDeposit().add(order.getAmount())); + fund.setUpdateTime(LocalDateTime.now()); + accountFundMapper.updateById(fund); + + // 记录流水 + assetService.createFlow(order.getUserId(), 1, order.getAmount(), + balanceBefore, fund.getBalance(), "USDT", orderNo, "充值"); + } else { + // 提现通过:从冻结转为扣除,更新累计提现 + if (fund.getFrozen().compareTo(order.getAmount()) < 0) { + throw new RuntimeException("冻结金额不足"); + } + BigDecimal balanceBefore = fund.getBalance(); + fund.setFrozen(fund.getFrozen().subtract(order.getAmount())); + fund.setTotalWithdraw(fund.getTotalWithdraw().add(order.getAmount())); + fund.setUpdateTime(LocalDateTime.now()); + accountFundMapper.updateById(fund); + + // 记录流水 (负数表示支出) + assetService.createFlow(order.getUserId(), 2, order.getAmount().negate(), + balanceBefore, fund.getBalance(), "USDT", orderNo, "提现"); + } + + order.setConfirmTime(LocalDateTime.now()); + + } else if (status == 3) { + // 审批驳回 + if (rejectReason == null || rejectReason.isEmpty()) { + throw new RuntimeException("请填写驳回原因"); + } + order.setRejectReason(rejectReason); + + if (order.getType() == 2) { + // 提现驳回:解冻金额退还 + BigDecimal balanceBefore = fund.getBalance(); + fund.setBalance(fund.getBalance().add(order.getAmount())); + fund.setFrozen(fund.getFrozen().subtract(order.getAmount())); + fund.setUpdateTime(LocalDateTime.now()); + accountFundMapper.updateById(fund); + + // 记录流水 + assetService.createFlow(order.getUserId(), 2, order.getAmount(), + balanceBefore, fund.getBalance(), "USDT", orderNo, "提现驳回退还"); + } + } + + order.setStatus(status); + order.setApproveAdminId(adminId); + order.setApproveAdminName(adminName); + order.setApproveTime(LocalDateTime.now()); + order.setAdminRemark(adminRemark); + order.setUpdateTime(LocalDateTime.now()); + orderFundMapper.updateById(order); + } + + /** + * 获取待审批订单列表 + * 充值待确认(status=2) + 提现待审批(status=1) + */ + public IPage getPendingOrders(Integer type, Integer status, int pageNum, int pageSize) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + if (type != null && type > 0) { + // 指定类型 + wrapper.eq(OrderFund::getType, type); + if (type == 1) { + // 充值:待确认 + wrapper.eq(OrderFund::getStatus, status != null ? status : 2); + } else { + // 提现:待审批 + wrapper.eq(OrderFund::getStatus, status != null ? status : 1); + } + } else { + // 不指定类型:充值待确认 + 提现待审批 + wrapper.and(w -> w.eq(OrderFund::getType, 1).eq(OrderFund::getStatus, 2)) + .or(w -> w.eq(OrderFund::getType, 2).eq(OrderFund::getStatus, 1)); + } + + wrapper.orderByAsc(OrderFund::getCreateTime); + + Page page = new Page<>(pageNum, pageSize); + return orderFundMapper.selectPage(page, wrapper); + } + + /** + * 获取所有充提订单(管理员) + */ + public IPage getAllOrders(Integer type, Integer status, int pageNum, int pageSize) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (type != null && type > 0) { + wrapper.eq(OrderFund::getType, type); + } + if (status != null && status > 0) { + wrapper.eq(OrderFund::getStatus, status); + } + wrapper.orderByDesc(OrderFund::getCreateTime); + + Page page = new Page<>(pageNum, pageSize); + return orderFundMapper.selectPage(page, wrapper); + } +}