From ca62ab0ae9abc25b48dadd8f9f0946e0d48a82ae Mon Sep 17 00:00:00 2001 From: dzq Date: Tue, 25 Nov 2025 11:36:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=AE=A2=E5=8D=95):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B1=87=E9=82=A6=E4=BA=91=E7=94=A8=E6=88=B7ID=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B9=B6=E9=87=8D=E6=9E=84=E4=BD=99=E9=A2=9D=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在订单相关表中添加ab98_user_id字段 - 新增MoneyUtil工具类处理金额转换 - 重构订单和退款逻辑使用user_balance表存储余额 - 添加根据汇邦云用户ID查询余额的接口 --- .../api/controller/CaffeineController.java | 16 +++-- .../api/controller/PaymentController.java | 60 ++++++++++++++-- .../com/agileboot/common/utils/MoneyUtil.java | 70 +++++++++++++++++++ .../ReturnApprovalApplicationService.java | 33 +++++++-- .../shop/order/OrderApplicationService.java | 21 +++++- .../order/command/SubmitOrderCommand.java | 3 + .../domain/shop/order/db/ShopOrderEntity.java | 4 ++ sql/20251124_user_balance.sql | 2 + 8 files changed, 188 insertions(+), 21 deletions(-) create mode 100644 agileboot-common/src/main/java/com/agileboot/common/utils/MoneyUtil.java diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/CaffeineController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/CaffeineController.java index 83d0a53..e027150 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/CaffeineController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/CaffeineController.java @@ -167,19 +167,23 @@ public class CaffeineController { result.put("data", caffeineCacheService.dynamicCodeCache.getAll()); break; default: + Map map = new HashMap<>(); + map.put("error", "未知的缓存类型: " + cacheName); + map.put("supportedTypes", new String[]{ + "captchaCache", "loginUserCache", "userCache", + "roleCache", "postCache", "qyUseridCache", "dynamicCodeCache" + }); return ResponseEntity.badRequest() - .body(Map.of("error", "未知的缓存类型: " + cacheName, - "supportedTypes", new String[]{ - "captchaCache", "loginUserCache", "userCache", - "roleCache", "postCache", "qyUseridCache", "dynamicCodeCache" - })); + .body(map); } return ResponseEntity.ok(result); } catch (Exception e) { log.error("获取缓存数据失败: {}", cacheName, e); + Map map = new HashMap<>(); + map.put("error", "获取缓存数据失败: " + e.getMessage()); return ResponseEntity.internalServerError() - .body(Map.of("error", "获取缓存数据失败: " + e.getMessage())); + .body(map); } } } diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java index 6722ee1..e72b46f 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/PaymentController.java @@ -10,9 +10,12 @@ import com.agileboot.common.core.dto.ResponseDTO; import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode.Client; +import com.agileboot.common.utils.MoneyUtil; import com.agileboot.common.utils.OpenSignUtil; import com.agileboot.domain.ab98.user.Ab98UserApplicationService; import com.agileboot.domain.ab98.user.db.Ab98UserEntity; +import com.agileboot.domain.ab98.user_balance.UserBalanceApplicationService; +import com.agileboot.domain.ab98.user_balance.dto.UserBalanceDTO; import com.agileboot.domain.common.dto.QyLoginDTO; import com.agileboot.domain.qywx.accessToken.AccessTokenApplicationService; import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity; @@ -72,6 +75,7 @@ public class PaymentController { private final PaymentApplicationService paymentApplicationService; private final PaymentOperationLogApplicationService paymentOperationLogApplicationService; private final Ab98UserApplicationService ab98UserApplicationService; + private final UserBalanceApplicationService userBalanceApplicationService; private final WxshopConfig wxshopConfig; // 新增回调接口 @@ -308,7 +312,7 @@ public class PaymentController { Ab98UserEntity ab98User = ab98UserApplicationService.getByAb98UserId(qyUser.getAb98UserId()); try { - if (qyUser.getAb98UserId()!= null) { + if (qyUser.getAb98UserId() != null) { ab98User.setOpenid(openid); ab98User.updateById(); } @@ -316,13 +320,15 @@ public class PaymentController { log.error("更新汇邦云绑定的微信openid失败", e); } + UserBalanceDTO userBalance = userBalanceApplicationService.getByCorpidAndAb98UserId(corpid, ab98User.getAb98UserId()); + // 创建响应对象(假设GetBalanceResponse包含balance字段) GetBalanceResponse response = new GetBalanceResponse( qyUser.getUserid(), qyUser.getCorpid(), - qyUser.getBalance(), - qyUser.getUseBalance(), - qyUser.getBalanceLimit(), + userBalance != null ? MoneyUtil.fenToYuan(userBalance.getBalance()) : BigDecimal.ZERO, + userBalance != null ? MoneyUtil.fenToYuan(userBalance.getUseBalance()) : BigDecimal.ZERO, + userBalance != null ? MoneyUtil.fenToYuan(userBalance.getBalanceLimit()) : BigDecimal.ZERO, ab98User); return ResponseDTO.ok(response); } @@ -342,13 +348,53 @@ public class PaymentController { ab98User = ab98UserApplicationService.getByAb98UserId(qyUser.getAb98UserId()); } + if (ab98User == null) { + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "未找到对应的汇邦云用户记录")); + } + + UserBalanceDTO userBalance = userBalanceApplicationService.getByCorpidAndAb98UserId(corpid, ab98User.getAb98UserId()); + // 创建响应对象(假设GetBalanceResponse包含balance字段) GetBalanceResponse response = new GetBalanceResponse( qyUser.getUserid(), qyUser.getCorpid(), - qyUser.getBalance(), - qyUser.getUseBalance(), - qyUser.getBalanceLimit(), + userBalance != null ? MoneyUtil.fenToYuan(userBalance.getBalance()) : BigDecimal.ZERO, + userBalance != null ? MoneyUtil.fenToYuan(userBalance.getUseBalance()) : BigDecimal.ZERO, + userBalance != null ? MoneyUtil.fenToYuan(userBalance.getBalanceLimit()) : BigDecimal.ZERO, + ab98User); + return ResponseDTO.ok(response); + } + + /** + * 根据企业微信ID和汇邦云用户ID查询用户余额 + * @param corpid 企业微信ID + * @param ab98UserId 汇邦云用户ID + * @return 包含用户余额信息的响应结果 + * @apiNote 该接口直接查询user_balance表,返回原始余额数据(单位:分) + */ + @GetMapping("/getUserBalance") + public ResponseDTO getUserBalance(@RequestParam String corpid, @RequestParam Long ab98UserId) { + if (corpid == null || ab98UserId == null) { + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "corpid和ab98UserId不能为空")); + } + + Ab98UserEntity ab98User = ab98UserApplicationService.getByAb98UserId(ab98UserId); + if (ab98User == null) { + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "未找到对应的汇邦云用户记录")); + } + + UserBalanceDTO userBalance = userBalanceApplicationService.getByCorpidAndAb98UserId(corpid, ab98UserId); + + if (userBalance == null) { + return ResponseDTO.fail(new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "未找到对应的用户余额记录")); + } + + GetBalanceResponse response = new GetBalanceResponse( + null, + corpid, + MoneyUtil.fenToYuan(userBalance.getBalance()), + MoneyUtil.fenToYuan(userBalance.getUseBalance()), + MoneyUtil.fenToYuan(userBalance.getBalanceLimit()), ab98User); return ResponseDTO.ok(response); } diff --git a/agileboot-common/src/main/java/com/agileboot/common/utils/MoneyUtil.java b/agileboot-common/src/main/java/com/agileboot/common/utils/MoneyUtil.java new file mode 100644 index 0000000..68031d6 --- /dev/null +++ b/agileboot-common/src/main/java/com/agileboot/common/utils/MoneyUtil.java @@ -0,0 +1,70 @@ +package com.agileboot.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 金额工具类 + * 提供分(Long)和元(BigDecimal)之间的转换 + * + * @author agileboot + */ +public class MoneyUtil { + + /** + * 分转换为元(BigDecimal,保留两位小数) + * + * @param fen 金额(单位:分) + * @return 金额(单位:元),保留两位小数 + */ + public static BigDecimal fenToYuan(Long fen) { + if (fen == null) { + return BigDecimal.ZERO; + } + return new BigDecimal(fen).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP); + } + + /** + * 元转换为分(Long) + * + * @param yuan 金额(单位:元) + * @return 金额(单位:分) + */ + public static Long yuanToFen(BigDecimal yuan) { + if (yuan == null) { + return 0L; + } + return yuan.multiply(new BigDecimal(100)).setScale(0, RoundingMode.HALF_UP).longValue(); + } + + /** + * 分转换为元格式的字符串(保留两位小数) + * + * @param fen 金额(单位:分) + * @return 金额字符串(单位:元),格式如:"100.00" + */ + public static String fenToYuanString(Long fen) { + if (fen == null) { + return "0.00"; + } + return fenToYuan(fen).toString(); + } + + /** + * 元格式的字符串转换为分 + * + * @param yuanStr 金额字符串(单位:元),格式如:"100.00" + * @return 金额(单位:分) + */ + public static Long yuanStringToFen(String yuanStr) { + if (yuanStr == null || yuanStr.trim().isEmpty()) { + return 0L; + } + try { + BigDecimal yuan = new BigDecimal(yuanStr.trim()); + return yuanToFen(yuan); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("无效的金额格式: " + yuanStr, e); + } + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java index 5f5a7a8..3d52737 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/approval/ReturnApprovalApplicationService.java @@ -3,6 +3,10 @@ package com.agileboot.domain.shop.approval; import cn.hutool.core.date.DateUtil; import cn.hutool.json.JSONUtil; import com.agileboot.common.config.WxshopConfig; +import com.agileboot.common.utils.MoneyUtil; +import com.agileboot.domain.ab98.user_balance.db.UserBalanceEntity; +import com.agileboot.domain.ab98.user_balance.db.UserBalanceService; +import com.agileboot.domain.ab98.user_balance.dto.UserBalanceDTO; import org.springframework.beans.factory.annotation.Autowired; import com.agileboot.common.constant.UrlConstants; import com.agileboot.common.constant.WeixinConstants; @@ -112,6 +116,7 @@ public class ReturnApprovalApplicationService { private final CabinetMainboardService cabinetMainboardService; private final CabinetCellOperationModelFactory cabinetCellOperationModelFactory; private final MqttService mqttService; + private final UserBalanceService userBalanceService; @Autowired private WxshopConfig wxshopConfig; @@ -259,15 +264,31 @@ public class ReturnApprovalApplicationService { } else if (Objects.equals(orderModel.getPaymentMethod(), "balance")) { // 余额退款 try { - QyAuthCorpInfoEntity authCorpInfo = authCorpInfoApplicationService.selectByCorpid(model.getCorpid()); - QyAccessTokenEntity accessToken = accessTokenApplicationService.getByAppid(authCorpInfo.getAppid(), model.getCorpid()); - String userid = QywxApiUtil.convertToUserid(accessToken.getAccessToken(), orderModel.getOpenid()).getUserid(); - QyUserEntity qyUser = userService.getUserByUserIdAndCorpid(userid, model.getCorpid()); - if (null != qyUser) { + Long ab98UserId = orderModel.getAb98UserId(); + if (null == ab98UserId) { + QyAuthCorpInfoEntity authCorpInfo = authCorpInfoApplicationService.selectByCorpid(model.getCorpid()); + QyAccessTokenEntity accessToken = accessTokenApplicationService.getByAppid(authCorpInfo.getAppid(), model.getCorpid()); + String userid = QywxApiUtil.convertToUserid(accessToken.getAccessToken(), orderModel.getOpenid()).getUserid(); + QyUserEntity qyUser = userService.getUserByUserIdAndCorpid(userid, model.getCorpid()); + ab98UserId = qyUser.getAb98UserId(); + } + + if (null == ab98UserId) { + throw new IllegalArgumentException("ab98用户不存在"); + } + + UserBalanceEntity userBalance = userBalanceService.getByCorpidAndAb98UserId(model.getCorpid(), ab98UserId); + if (null == userBalance) { + throw new IllegalArgumentException("用户余额不存在"); + } + userBalance.setBalance(userBalance.getBalance() + MoneyUtil.yuanToFen(command.getReturnAmount())); + userBalance.setUseBalance(userBalance.getUseBalance() - MoneyUtil.yuanToFen(command.getReturnAmount())); + userBalance.updateById(); + /*if (null != qyUser) { qyUser.setBalance(qyUser.getBalance().add(command.getReturnAmount())); qyUser.setUseBalance(qyUser.getUseBalance().subtract(command.getReturnAmount())); } - userService.updateById(qyUser); + userService.updateById(qyUser);*/ } catch (Exception e) { log.error("余额退款失败", e); } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java index d269196..688420e 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/OrderApplicationService.java @@ -3,6 +3,10 @@ package com.agileboot.domain.shop.order; import cn.hutool.core.date.DateUtil; import cn.hutool.json.JSONUtil; import com.agileboot.common.config.WxshopConfig; +import com.agileboot.common.utils.MoneyUtil; +import com.agileboot.domain.ab98.user_balance.db.UserBalanceEntity; +import com.agileboot.domain.ab98.user_balance.db.UserBalanceService; +import com.agileboot.domain.ab98.user_balance.dto.UserBalanceDTO; import org.springframework.beans.factory.annotation.Autowired; import com.agileboot.common.core.page.PageDTO; import com.agileboot.common.exception.ApiException; @@ -96,6 +100,7 @@ public class OrderApplicationService { private final QyUserService qyUserService; private final Ab98UserService ab98UserService; private final WxUserService wxUserService; + private final UserBalanceService userBalanceService; @Autowired private WxshopConfig wxshopConfig; @@ -187,6 +192,7 @@ public class OrderApplicationService { orderModel.setIsInternal(command.getIsInternal()); orderModel.setUserid(command.getQyUserid()); orderModel.setName(command.getName()); + orderModel.setAb98UserId(command.getAb98UserId()); orderModel.setIsDeductStock(0); orderModel.setMode(command.getMode()); orderModel.insert(); @@ -217,7 +223,7 @@ public class OrderApplicationService { return new CreateOrderResult(orderModel.getOrderId(), orderModel.getTotalAmount(), paymentResponse, BigDecimal.valueOf(0)); } else if (Objects.equals(command.getPaymentType(), "balance")) { - QyUserEntity qyUser = userService.getUserByUserIdAndCorpid(command.getUserid(), command.getCorpid()); + /*QyUserEntity qyUser = userService.getUserByUserIdAndCorpid(command.getUserid(), command.getCorpid()); // 余额不足 if (qyUser.getBalance().compareTo(orderModel.getTotalAmount()) < 0) { throw new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "余额不足"); @@ -225,14 +231,25 @@ public class OrderApplicationService { qyUser.setBalance(qyUser.getBalance().subtract(orderModel.getTotalAmount())); qyUser.setUseBalance(qyUser.getUseBalance().add(orderModel.getTotalAmount())); userService.updateById(qyUser); + }*/ + + UserBalanceEntity userBalance = userBalanceService.getByCorpidAndAb98UserId(command.getCorpid(), command.getAb98UserId()); + // 余额不足 + if (userBalance.getBalance().compareTo(MoneyUtil.yuanToFen(orderModel.getTotalAmount())) < 0) { + throw new ApiException(ErrorCode.Client.COMMON_REQUEST_PARAMETERS_INVALID, "余额不足"); + } else { + userBalance.setBalance(userBalance.getBalance() - MoneyUtil.yuanToFen(orderModel.getTotalAmount())); + userBalance.setUseBalance(userBalance.getUseBalance() + MoneyUtil.yuanToFen(orderModel.getTotalAmount())); + userBalanceService.updateById(userBalance); } + // 金额转换(元转分)并四舍五入 BigDecimal amountInFen = orderModel.getTotalAmount() .multiply(new BigDecimal("100")) .setScale(0, RoundingMode.HALF_UP); handlePaymentSuccess(orderModel.getOrderId(), Integer.valueOf(amountInFen.toPlainString()), "balance-" + orderModel.getOrderId(), DateUtil.formatDateTime(new Date())); - return new CreateOrderResult(orderModel.getOrderId(), orderModel.getTotalAmount(), null, qyUser.getBalance()); + return new CreateOrderResult(orderModel.getOrderId(), orderModel.getTotalAmount(), null, MoneyUtil.fenToYuan(userBalance.getBalance())); } else if (Objects.equals(command.getPaymentType(), "approval")) { submitAssetApproval(orderModel, goodsList, command); return new CreateOrderResult(orderModel.getOrderId(), orderModel.getTotalAmount(), null, BigDecimal.valueOf(0)); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/command/SubmitOrderCommand.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/command/SubmitOrderCommand.java index b8c4fad..736a9c4 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/command/SubmitOrderCommand.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/command/SubmitOrderCommand.java @@ -45,4 +45,7 @@ public class SubmitOrderCommand { @ApiModelProperty("是否为微信小程序支付,0否 1是") private Integer isWxMp; + + @ApiModelProperty("汇邦云用户ID") + private Long ab98UserId; } \ No newline at end of file diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderEntity.java index eec94e1..a850483 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderEntity.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/order/db/ShopOrderEntity.java @@ -57,6 +57,10 @@ public class ShopOrderEntity extends BaseEntity { @TableField("name") private String name; + @ApiModelProperty("汇邦云用户ID") + @TableField(value = "ab98_user_id") + private Long ab98UserId; + @ApiModelProperty("是否内部用户(0否 1汇邦云用户 2企业微信用户)") @TableField("is_internal") private Integer isInternal; diff --git a/sql/20251124_user_balance.sql b/sql/20251124_user_balance.sql index 0b22a61..30de305 100644 --- a/sql/20251124_user_balance.sql +++ b/sql/20251124_user_balance.sql @@ -21,3 +21,5 @@ CREATE TABLE `user_balance` ( -- 添加 deleted 字段的 ALTER 语句 ALTER TABLE `user_balance` ADD COLUMN `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)'; + +ALTER TABLE `shop_order` ADD COLUMN `ab98_user_id` bigint DEFAULT NULL COMMENT '汇邦云用户ID' AFTER `name`;