refactor(payment): 重构支付功能并优化token消费接口

- 修改reduceBalanceWithToken方法使用TokenConsumptionDto参数对象
- 添加通过会话ID查询用户的功能并验证用户会话有效性
- 在支付控制器中更新订单信息时保存二维码内容
- 为PaymentOrder实体添加codeUrl和qrCode字段支持
- 更新SysUserMapper添加getBySessionId查询方法
- 优化微信和支付宝支付回调日志记录
- 改进token消费参数验证逻辑
- 调整数据库映射文件以支持新增字段
This commit is contained in:
wangzhiwei 2026-04-01 15:50:58 +08:00
parent 770f50302e
commit f2b8a735f2
11 changed files with 97 additions and 62 deletions

View File

@ -116,20 +116,8 @@ public class AccountController {
@Operation(summary = "减少账户余额token消费转换", description = "减少账户余额token消费转换")
@PostMapping("/reduceBalanceWithToken")
@RequireAuth
public CommonResult<Integer> reduceBalanceWithToken(
@RequestBody TokenConsumptionDto tokenConsumptionDto) {
return CommonResult.success(this.accountService.reduceBalanceWithToken(
tokenConsumptionDto.getUserId(),
tokenConsumptionDto.getInputToken(),
tokenConsumptionDto.getOutputToken(),
tokenConsumptionDto.getTotalTokens(),
tokenConsumptionDto.getModelName(),
tokenConsumptionDto.getQuestion(),
tokenConsumptionDto.getTransactionNo(),
tokenConsumptionDto.getBusinessId(),
tokenConsumptionDto.getBusinessType(),
tokenConsumptionDto.getRemark()
));
public CommonResult<Integer> reduceBalanceWithToken( @RequestBody TokenConsumptionDto tokenConsumptionDto) {
return CommonResult.success(this.accountService.reduceBalanceWithToken(tokenConsumptionDto));
}
/**

View File

@ -108,6 +108,11 @@ public class PayController {
PaymentOrder createdOrder = paymentOrderService.createPaymentOrder(order);
// 生成支付宝支付表单
String form = payService.createAlipay(createdOrder);
// 更新订单信息
createdOrder.setQrCode( form);
paymentOrderService.update(createdOrder);
return CommonResult.success(form);
} catch (Exception e) {
logger.error("创建支付宝支付订单失败", e);

View File

@ -44,6 +44,12 @@ public class PaymentOrder extends BaseEntity implements Serializable {
@Schema(description ="支付渠道订单号")
private String channelOrderNo;
@Schema(description ="微信二维码URL")
private String codeUrl;
@Schema(description ="支付宝二维码HTML内容")
private String qrCode;
@Schema(description ="商品名称")
private String productName;

View File

@ -13,7 +13,10 @@ import lombok.Data;
@Data
public class TokenConsumptionDto {
@Schema(description ="用户ID")
@Schema(description ="用户的会话ID")
private String sessionId;
@Schema(description ="用户ID" ,hidden = true)
private Long userId;
@Schema(description ="输入token")
@ -34,10 +37,10 @@ public class TokenConsumptionDto {
@Schema(description ="交易单号")
private String transactionNo;
@Schema(description ="业务ID")
@Schema(description ="业务ID" ,hidden = true)
private Long businessId;
@Schema(description ="业务类型")
@Schema(description ="业务类型",hidden = true)
private String businessType;
@Schema(description ="备注")

View File

@ -99,4 +99,5 @@ public interface AccountMapper {
* @return 影响行数
*/
int deleteById(Long accountId);
}

View File

@ -86,4 +86,6 @@ public interface SysUserMapper {
SysUser getByTel(String tel);
SysUser getBySessionId(String sessionId);
}

View File

@ -3,6 +3,7 @@ package com.kexue.skills.service;
import com.github.pagehelper.PageInfo;
import com.kexue.skills.entity.Account;
import com.kexue.skills.entity.dto.AccountDto;
import com.kexue.skills.entity.dto.TokenConsumptionDto;
import java.math.BigDecimal;
import java.util.List;
@ -104,20 +105,10 @@ public interface AccountService extends BaseService {
/**
* 减少账户余额token消费转换
* @param tokenConsumptionDto token消费
*
* @param userId 用户ID
* @param inputToken 输入token
* @param outputToken 输出token
* @param totalTokens 合计tokens
* @param modelName 处理的模型名称
* @param question 对应回答的问题或需求
* @param transactionNo 交易单号
* @param businessId 业务ID
* @param businessType 业务类型
* @param remark 备注
* @return 影响行数
*/
int reduceBalanceWithToken(Long userId, Integer inputToken, Integer outputToken, Integer totalTokens, String modelName, String question, String transactionNo, Long businessId, String businessType, String remark);
int reduceBalanceWithToken(TokenConsumptionDto tokenConsumptionDto);
/**
* 增加账户余额签到奖励token转换

View File

@ -4,11 +4,14 @@ import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.kexue.skills.entity.Account;
import com.kexue.skills.entity.AccountTransaction;
import com.kexue.skills.entity.SysUser;
import com.kexue.skills.entity.dto.AccountDto;
import com.kexue.skills.common.Assert;
import com.kexue.skills.entity.dto.TokenConsumptionDto;
import com.kexue.skills.exception.BizException;
import com.kexue.skills.mapper.AccountMapper;
import com.kexue.skills.mapper.AccountTransactionMapper;
import com.kexue.skills.mapper.SysUserMapper;
import com.kexue.skills.service.AccountService;
import com.kexue.skills.service.ModelPriceService;
import com.kexue.skills.entity.ModelPrice;
@ -31,6 +34,8 @@ import java.util.List;
public class AccountServiceImpl implements AccountService {
@Resource
private AccountMapper accountMapper;
@Resource
private SysUserMapper sysUserMapper;
@Resource
private AccountTransactionMapper accountTransactionMapper;
@ -246,40 +251,35 @@ public class AccountServiceImpl implements AccountService {
/**
* 减少账户余额token消费转换
*
* @param userId 用户ID
* @param amount 减少金额
* @param inputToken 输入token
* @param outputToken 输出token
* @param totalTokens 合计tokens
* @param modelName 处理的模型名称
* @param question 对应回答的问题或需求
* @param transactionNo 交易单号
* @param businessId 业务ID
* @param businessType 业务类型
* @param remark 备注
* @return 影响行数
*/
@Override
public int reduceBalanceWithToken(Long userId, Integer inputToken, Integer outputToken, Integer totalTokens, String modelName, String question, String transactionNo, Long businessId, String businessType, String remark) {
public int reduceBalanceWithToken(TokenConsumptionDto dto) {
validate(dto);
// 1. 查询账户信息
Long userId = null ;//根据会话ID查询用户ID
SysUser bySessionId = sysUserMapper.getBySessionId(dto.getSessionId());
if(bySessionId == null){
throw new BizException("会话ID不存在");
}
userId = bySessionId.getUserId();
Account account = this.queryByUserId(userId);
Assert.notNull(account, "账户不存在");
// 2. 查询模型价格信息
ModelPrice modelPrice = modelPriceService.queryByModelName(modelName);
ModelPrice modelPrice = modelPriceService.queryByModelName(dto.getModelName());
Assert.notNull(modelPrice, "模型价格信息不存在");
// 3. 计算金额
// 输入token费用输入token数量 / inputPerCent不足1分按1分计算
long inputFee = inputToken / modelPrice.getInputPerCent();
if (inputToken % modelPrice.getInputPerCent() > 0) {
long inputFee = dto.getInputToken() / modelPrice.getInputPerCent();
if (dto.getInputToken() % modelPrice.getInputPerCent() > 0) {
inputFee += 1;
}
// 输出token费用输出token数量 / outputPerCent不足1分按1分计算
long outputFee = outputToken / modelPrice.getOutputPerCent();
if (outputToken % modelPrice.getOutputPerCent() > 0) {
long outputFee = dto.getOutputToken() / modelPrice.getOutputPerCent();
if (dto.getOutputToken() % modelPrice.getOutputPerCent() > 0) {
outputFee += 1;
}
@ -300,23 +300,32 @@ public class AccountServiceImpl implements AccountService {
transaction.setBeforeBalance(account.getBalance());
transaction.setAfterBalance(account.getBalance().subtract(amount));
transaction.setStatus(1); // 成功
transaction.setTransactionNo(transactionNo);
transaction.setTransactionNo(dto.getTransactionNo());
transaction.setPayType(3); // 余额支付
transaction.setBusinessId(businessId);
transaction.setBusinessType(businessType);
transaction.setRemark(remark);
transaction.setRemark(dto.getRemark());
transaction.setIsExpense(1); // 支出
transaction.setInputToken(inputToken);
transaction.setOutputToken(outputToken);
transaction.setTotalTokens(totalTokens);
transaction.setModelName(modelName);
transaction.setQuestion(question);
transaction.setInputToken(dto.getInputToken());
transaction.setOutputToken(dto.getOutputToken());
transaction.setTotalTokens(dto.getTotalTokens());
transaction.setModelName(dto.getModelName());
transaction.setQuestion(dto.getQuestion());
this.accountTransactionMapper.insert(transaction);
// 6. 更新账户余额
return this.accountMapper.updateBalance(userId, amount, 2);
}
void validate(TokenConsumptionDto dto) {
Assert.notNull(dto, "参数不能为空");
Assert.notNull(dto.getSessionId(), "会话ID不能为空");
Assert.notNull(dto.getUserId(), "用户ID不能为空");
Assert.notNull(dto.getQuestion(), "问题不能为空");
Assert.notNull(dto.getModelName(), "模型名称不能为空");
Assert.notNull(dto.getQuestion(), "问题不能为空");
Assert.notNull(dto.getModelName(), "模型名称不能为空");
Assert.notNull(dto.getQuestion(), "问题不能为空");
}
/**
* 增加账户余额签到奖励token转换
*

View File

@ -424,9 +424,15 @@ public class PayServiceImpl implements PayService {
throw new RuntimeException("支付下单失败:" + errorMsg);
}
// 保存微信支付二维码URL到订单
String codeUrl = result.get("code_url");
order.setCodeUrl(codeUrl);
order.setChannelOrderNo(result.get("order_no"));
paymentOrderService.update(order);
// 返回成功结果
Map<String, String> payParams = new HashMap<>();
payParams.put("code_url", result.get("code_url"));
payParams.put("code_url", codeUrl);
payParams.put("order_no", order.getOrderNo());
return payParams;
@ -460,6 +466,8 @@ public class PayServiceImpl implements PayService {
// 解析XML
Map<String, String> params = xmlToMap(xmlData);
logger.info("微信支付回调数据:{}", params);
// 验证签名
String sign = params.get("sign");
if (sign == null) {
@ -543,7 +551,11 @@ public class PayServiceImpl implements PayService {
// 生成支付表单
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
if (response.isSuccess()) {
return response.getBody();
String htmlForm = response.getBody();
// 保存支付宝支付HTML内容到订单
order.setQrCode(htmlForm);
paymentOrderService.update(order);
return htmlForm;
} else {
logger.error("支付宝支付下单失败: {}", response.getMsg());
throw new RuntimeException("支付宝支付下单失败: " + response.getMsg());
@ -570,6 +582,8 @@ public class PayServiceImpl implements PayService {
}
});
logger.info("支付宝支付回调参数: {}", params);
// 初始化支付宝客户端
AlipayClient alipayClient = new DefaultAlipayClient(
paymentConfig.getAlipay().getGatewayUrl(),

View File

@ -11,6 +11,8 @@
<result property="payType" column="pay_type" jdbcType="INTEGER"/>
<result property="status" column="status" jdbcType="INTEGER"/>
<result property="channelOrderNo" column="channel_order_no" jdbcType="VARCHAR"/>
<result property="codeUrl" column="code_url" jdbcType="VARCHAR"/>
<result property="qrCode" column="qr_code" jdbcType="LONGNVARCHAR"/>
<result property="businessId" column="business_id" jdbcType="BIGINT"/>
<result property="businessType" column="business_type" jdbcType="VARCHAR"/>
<result property="remark" column="remark" jdbcType="VARCHAR"/>
@ -25,7 +27,7 @@
<select id="queryById" resultMap="PaymentOrderMap">
select
order_id, order_no, user_id, user_name, amount, pay_type, status, channel_order_no,
business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
code_url, qr_code, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
from payment_order
where order_id = #{orderId}
</select>
@ -34,7 +36,7 @@
<select id="queryByOrderNo" resultMap="PaymentOrderMap">
select
order_id, order_no, user_id, user_name, amount, pay_type, status, channel_order_no,
business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
code_url, qr_code, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
from payment_order
where order_no = #{orderNo}
</select>
@ -43,7 +45,7 @@
<select id="getPageList" resultMap="PaymentOrderMap">
select
order_id, order_no, user_id, user_name, amount, pay_type, status, channel_order_no,
business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
code_url, qr_code, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
from payment_order
<where>
<if test="userId != null">
@ -80,7 +82,7 @@
<select id="getList" resultMap="PaymentOrderMap">
select
order_id, order_no, user_id, user_name, amount, pay_type, status, channel_order_no,
business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
code_url, qr_code, business_id, business_type, remark, create_time, update_time, create_by, update_by, delete_flag
from payment_order
<where>
<if test="userId != null">
@ -121,6 +123,8 @@
pay_type,
<if test="status != null">status,</if>
<if test="channelOrderNo != null">channel_order_no,</if>
<if test="codeUrl != null">code_url,</if>
<if test="qrCode != null">qr_code,</if>
<if test="businessId != null">business_id,</if>
<if test="businessType != null">business_type,</if>
<if test="remark != null">remark,</if>
@ -138,6 +142,8 @@
#{payType},
<if test="status != null">#{status},</if>
<if test="channelOrderNo != null">#{channelOrderNo},</if>
<if test="codeUrl != null">#{codeUrl},</if>
<if test="qrCode != null">#{qrCode},</if>
<if test="businessId != null">#{businessId},</if>
<if test="businessType != null">#{businessType},</if>
<if test="remark != null">#{remark},</if>
@ -160,6 +166,8 @@
<if test="payType != null">pay_type = #{payType},</if>
<if test="status != null">status = #{status},</if>
<if test="channelOrderNo != null">channel_order_no = #{channelOrderNo},</if>
<if test="codeUrl != null">code_url = #{codeUrl},</if>
<if test="qrCode != null">qr_code = #{qrCode},</if>
<if test="businessId != null">business_id = #{businessId},</if>
<if test="businessType != null">business_type = #{businessType},</if>
<if test="remark != null">remark = #{remark},</if>

View File

@ -208,5 +208,13 @@
and delete_flag = 0
limit 1
</select>
<select id="getBySessionId" resultMap="SysUserMap">
select
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
from sys_user
where session_id = #{sessionId}
and delete_flag = 0
limit 1
</select>
</mapper>