mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-18 06:13:39 +00:00
Compare commits
2 Commits
bd9ffb10a9
...
affdc5e3a6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
affdc5e3a6 | ||
|
|
5a2e08f87d |
@@ -0,0 +1,31 @@
|
|||||||
|
package org.ruoyi.chat.event;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 聊天消息创建事件(用于异步计费/累计等)
|
||||||
|
*/
|
||||||
|
public class ChatMessageCreatedEvent extends ApplicationEvent {
|
||||||
|
|
||||||
|
private final Long userId;
|
||||||
|
private final Long sessionId;
|
||||||
|
private final String modelName;
|
||||||
|
private final String role;
|
||||||
|
private final String content;
|
||||||
|
|
||||||
|
public ChatMessageCreatedEvent(Long userId, Long sessionId, String modelName, String role, String content) {
|
||||||
|
super(userId);
|
||||||
|
this.userId = userId;
|
||||||
|
this.sessionId = sessionId;
|
||||||
|
this.modelName = modelName;
|
||||||
|
this.role = role;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId() { return userId; }
|
||||||
|
public Long getSessionId() { return sessionId; }
|
||||||
|
public String getModelName() { return modelName; }
|
||||||
|
public String getRole() { return role; }
|
||||||
|
public String getContent() { return content; }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package org.ruoyi.chat.listener;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.ruoyi.chat.event.ChatMessageCreatedEvent;
|
||||||
|
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||||
|
import org.ruoyi.common.chat.request.ChatRequest;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.transaction.event.TransactionPhase;
|
||||||
|
import org.springframework.transaction.event.TransactionalEventListener;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class BillingEventListener {
|
||||||
|
|
||||||
|
private final IChatCostService chatCostService;
|
||||||
|
|
||||||
|
@Async
|
||||||
|
@EventListener
|
||||||
|
public void onChatMessageCreated(ChatMessageCreatedEvent event) {
|
||||||
|
log.debug("BillingEventListener->接收到计费事件,用户ID: {},会话ID: {},模型: {}",
|
||||||
|
event.getUserId(), event.getSessionId(), event.getModelName());
|
||||||
|
try {
|
||||||
|
ChatRequest chatRequest = new ChatRequest();
|
||||||
|
chatRequest.setUserId(event.getUserId());
|
||||||
|
chatRequest.setSessionId(event.getSessionId());
|
||||||
|
chatRequest.setModel(event.getModelName());
|
||||||
|
chatRequest.setRole(event.getRole());
|
||||||
|
chatRequest.setPrompt(event.getContent());
|
||||||
|
// 异步执行计费累计与扣费
|
||||||
|
log.debug("BillingEventListener->开始执行计费逻辑");
|
||||||
|
chatCostService.deductToken(chatRequest);
|
||||||
|
log.debug("BillingEventListener->计费逻辑执行完成");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// 由于已有预检查,这里的异常主要是系统异常(数据库连接等)
|
||||||
|
// 记录错误但不中断异步线程
|
||||||
|
log.error("BillingEventListener->异步计费异常,用户ID: {},模型: {},错误: {}",
|
||||||
|
event.getUserId(), event.getModelName(), ex.getMessage(), ex);
|
||||||
|
|
||||||
|
// TODO: 可以考虑加入重试机制或者错误通知机制
|
||||||
|
// 例如:发送到死信队列,或者通知运维人员
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -87,7 +87,9 @@ public class SSEEventSourceListener extends EventSourceListener {
|
|||||||
chatRequest.setPrompt(stringBuffer.toString());
|
chatRequest.setPrompt(stringBuffer.toString());
|
||||||
// 记录会话token
|
// 记录会话token
|
||||||
BaseContext.setCurrentToken(token);
|
BaseContext.setCurrentToken(token);
|
||||||
chatCostService.deductToken(chatRequest);
|
// 先保存助手消息,再发布异步计费事件
|
||||||
|
chatCostService.saveMessage(chatRequest);
|
||||||
|
chatCostService.publishBillingEvent(chatRequest);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,22 @@ public interface IChatCostService {
|
|||||||
|
|
||||||
void deductToken(ChatRequest chatRequest);
|
void deductToken(ChatRequest chatRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存聊天消息记录(不进行计费)
|
||||||
|
*
|
||||||
|
* @param chatRequest 对话信息
|
||||||
|
*/
|
||||||
|
void saveMessage(ChatRequest chatRequest);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 仅发布异步计费事件(不做入库)
|
||||||
|
*
|
||||||
|
* @param chatRequest 对话信息
|
||||||
|
*/
|
||||||
|
void publishBillingEvent(ChatRequest chatRequest);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 直接扣除用户的余额
|
* 直接扣除用户的余额
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package org.ruoyi.chat.service.chat.impl;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import org.ruoyi.chat.enums.BillingType;
|
import org.ruoyi.chat.enums.BillingType;
|
||||||
|
import org.ruoyi.chat.event.ChatMessageCreatedEvent;
|
||||||
import org.ruoyi.chat.enums.UserGradeType;
|
import org.ruoyi.chat.enums.UserGradeType;
|
||||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||||
import org.ruoyi.common.chat.request.ChatRequest;
|
import org.ruoyi.common.chat.request.ChatRequest;
|
||||||
@@ -20,6 +23,7 @@ import org.ruoyi.service.IChatModelService;
|
|||||||
import org.ruoyi.service.IChatTokenService;
|
import org.ruoyi.service.IChatTokenService;
|
||||||
import org.ruoyi.system.domain.SysUser;
|
import org.ruoyi.system.domain.SysUser;
|
||||||
import org.ruoyi.system.mapper.SysUserMapper;
|
import org.ruoyi.system.mapper.SysUserMapper;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
|
||||||
@@ -42,106 +46,248 @@ public class ChatCostServiceImpl implements IChatCostService {
|
|||||||
|
|
||||||
private final IChatModelService chatModelService;
|
private final IChatModelService chatModelService;
|
||||||
|
|
||||||
|
private final ApplicationEventPublisher eventPublisher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扣除用户余额
|
* 扣除用户余额(仅计费与累计,不保存消息)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void deductToken(ChatRequest chatRequest) {
|
public void deductToken(ChatRequest chatRequest) {
|
||||||
|
if (chatRequest.getUserId() == null) {
|
||||||
|
log.warn("deductToken->用户ID为空,跳过计费");
|
||||||
if(chatRequest.getUserId()==null || chatRequest.getSessionId()==null){
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int tokens = TikTokensUtil.tokens(chatRequest.getModel(), chatRequest.getPrompt());
|
int tokens = TikTokensUtil.tokens(chatRequest.getModel(), chatRequest.getPrompt());
|
||||||
|
log.debug("deductToken->本次提交token数: {}", tokens);
|
||||||
System.out.println("deductToken->本次提交token数 : "+tokens);
|
|
||||||
|
|
||||||
String modelName = chatRequest.getModel();
|
String modelName = chatRequest.getModel();
|
||||||
|
ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName);
|
||||||
|
BigDecimal unitPrice = BigDecimal.valueOf(chatModelVo.getModelPrice());
|
||||||
|
|
||||||
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
// 按次计费:每次调用都直接扣费,不累计token
|
||||||
|
if (BillingType.TIMES.getCode().equals(chatModelVo.getModelType())) {
|
||||||
|
BigDecimal numberCost = unitPrice.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
deductUserBalance(chatRequest.getUserId(), numberCost.doubleValue());
|
||||||
|
log.debug("deductToken->按次数扣费,费用: {},模型: {}", numberCost, modelName);
|
||||||
|
|
||||||
|
// 清理可能存在的历史累计token(模型计费方式可能发生过变更)
|
||||||
|
ChatUsageToken existingToken = chatTokenService.queryByUserId(chatRequest.getUserId(), modelName);
|
||||||
|
if (existingToken != null && existingToken.getToken() > 0) {
|
||||||
|
existingToken.setToken(0);
|
||||||
|
chatTokenService.editToken(existingToken);
|
||||||
|
log.debug("deductToken->按次计费,清理历史累计token: {}", existingToken.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录账单消息
|
||||||
|
saveBillingRecord(chatRequest, tokens, numberCost.doubleValue(), "TIMES_BILLING");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 设置用户id
|
// 按token计费:累加并按阈值批量扣费,保留余数
|
||||||
chatMessageBo.setUserId(chatRequest.getUserId());
|
final int threshold = 100;
|
||||||
// 设置会话id
|
|
||||||
chatMessageBo.setSessionId(chatRequest.getSessionId());
|
|
||||||
|
|
||||||
// 设置对话角色
|
|
||||||
chatMessageBo.setRole(chatRequest.getRole());
|
|
||||||
|
|
||||||
// 设置对话内容
|
|
||||||
chatMessageBo.setContent(chatRequest.getPrompt());
|
|
||||||
|
|
||||||
// 设置模型名字
|
|
||||||
chatMessageBo.setModelName(chatRequest.getModel());
|
|
||||||
|
|
||||||
// 获得记录的累计token数
|
// 获得记录的累计token数
|
||||||
ChatUsageToken chatToken = chatTokenService.queryByUserId(chatMessageBo.getUserId(), modelName);
|
// TODO: 这里存在并发竞态条件,需要在chatTokenService层面添加乐观锁或分布式锁
|
||||||
|
ChatUsageToken chatToken = chatTokenService.queryByUserId(chatRequest.getUserId(), modelName);
|
||||||
|
|
||||||
if (chatToken == null) {
|
if (chatToken == null) {
|
||||||
chatToken = new ChatUsageToken();
|
chatToken = new ChatUsageToken();
|
||||||
chatToken.setToken(0);
|
chatToken.setToken(0);
|
||||||
|
chatToken.setModelName(modelName);
|
||||||
|
chatToken.setUserId(chatRequest.getUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算总token数
|
int previousUnpaid = chatToken.getToken();
|
||||||
int totalTokens = chatToken.getToken() + tokens;
|
int totalTokens = previousUnpaid + tokens;
|
||||||
|
log.debug("deductToken->未付费token数: {},本次累计后总数: {}", previousUnpaid, totalTokens);
|
||||||
|
|
||||||
//当前未付费token
|
int billable = (totalTokens / threshold) * threshold; // 可计费整批token数
|
||||||
int token = chatToken.getToken();
|
int remainder = totalTokens - billable; // 结算后保留的余数
|
||||||
|
|
||||||
System.out.println("deductToken->未付费的token数 : "+token);
|
if (billable > 0) {
|
||||||
System.out.println("deductToken->本次提交+未付费token数 : "+totalTokens);
|
BigDecimal numberCost = unitPrice
|
||||||
|
.multiply(BigDecimal.valueOf(billable))
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
//扣费核心逻辑(总token大于100就要对未结清的token进行扣费)
|
log.debug("deductToken->按token扣费,结算token数量: {},单价: {},费用: {}", billable, unitPrice, numberCost);
|
||||||
if (totalTokens >= 100) {// 如果总token数大于等于100,进行费用扣除
|
|
||||||
|
try {
|
||||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName);
|
// 先尝试扣费
|
||||||
double cost = chatModelVo.getModelPrice();
|
deductUserBalance(chatRequest.getUserId(), numberCost.doubleValue());
|
||||||
if (BillingType.TIMES.getCode().equals(chatModelVo.getModelType())) {
|
// 扣费成功后,保存余数
|
||||||
// 按次数扣费
|
|
||||||
deductUserBalance(chatMessageBo.getUserId(), cost);
|
|
||||||
chatMessageBo.setDeductCost(cost);
|
|
||||||
}else {
|
|
||||||
// 按token扣费
|
|
||||||
Double numberCost = totalTokens * cost;
|
|
||||||
System.out.println("deductToken->按token扣费 计算token数量: "+totalTokens);
|
|
||||||
System.out.println("deductToken->按token扣费 每token的价格: "+cost);
|
|
||||||
|
|
||||||
deductUserBalance(chatMessageBo.getUserId(), numberCost);
|
|
||||||
chatMessageBo.setDeductCost(numberCost);
|
|
||||||
|
|
||||||
// 保存剩余tokens
|
|
||||||
chatToken.setModelName(modelName);
|
chatToken.setModelName(modelName);
|
||||||
chatToken.setUserId(chatMessageBo.getUserId());
|
chatToken.setUserId(chatRequest.getUserId());
|
||||||
chatToken.setToken(0);//因为判断大于100token直接全部计算扣除了所以这里直接=0就可以了
|
chatToken.setToken(remainder);
|
||||||
chatTokenService.editToken(chatToken);
|
chatTokenService.editToken(chatToken);
|
||||||
|
log.debug("deductToken->扣费成功,更新余数: {}", remainder);
|
||||||
|
|
||||||
|
// 记录账单消息
|
||||||
|
saveBillingRecord(chatRequest, billable, numberCost.doubleValue(), "TOKEN_BILLING");
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
// 余额不足时,不更新token累计,保持原有累计数
|
||||||
|
log.warn("deductToken->余额不足,本次token累计保持不变: {}", totalTokens);
|
||||||
|
throw e; // 重新抛出异常
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//不满100Token,不需要进行扣费啊啊啊
|
// 未达阈值,累积token
|
||||||
//deductUserBalance(chatMessageBo.getUserId(), 0.0);
|
log.debug("deductToken->未达到计费阈值({}),累积到下次", threshold);
|
||||||
chatMessageBo.setDeductCost(0d);
|
chatToken.setModelName(modelName);
|
||||||
chatMessageBo.setRemark("不满100Token,计入下一次!");
|
chatToken.setUserId(chatRequest.getUserId());
|
||||||
System.out.println("deductToken->不满100Token,计入下一次!");
|
|
||||||
chatToken.setToken(totalTokens);
|
chatToken.setToken(totalTokens);
|
||||||
chatToken.setModelName(chatMessageBo.getModelName());
|
|
||||||
chatToken.setUserId(chatMessageBo.getUserId());
|
|
||||||
chatTokenService.editToken(chatToken);
|
chatTokenService.editToken(chatToken);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存聊天消息记录(不进行计费)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void saveMessage(ChatRequest chatRequest) {
|
||||||
|
if (chatRequest.getUserId() == null || chatRequest.getSessionId() == null) {
|
||||||
|
log.warn("saveMessage->用户ID或会话ID为空,跳过保存消息");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证消息内容
|
||||||
|
if (chatRequest.getPrompt() == null || chatRequest.getPrompt().trim().isEmpty()) {
|
||||||
|
log.warn("saveMessage->消息内容为空,跳过保存");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatMessageBo chatMessageBo = new ChatMessageBo();
|
||||||
|
chatMessageBo.setUserId(chatRequest.getUserId());
|
||||||
|
chatMessageBo.setSessionId(chatRequest.getSessionId());
|
||||||
|
chatMessageBo.setRole(chatRequest.getRole());
|
||||||
|
chatMessageBo.setContent(chatRequest.getPrompt().trim());
|
||||||
|
chatMessageBo.setModelName(chatRequest.getModel());
|
||||||
|
|
||||||
|
// 计算并保存本次消息的token数
|
||||||
|
int tokens = TikTokensUtil.tokens(chatRequest.getModel(), chatRequest.getPrompt());
|
||||||
|
chatMessageBo.setTotalTokens(tokens);
|
||||||
|
|
||||||
|
// 普通消息不涉及扣费,deductCost保持null
|
||||||
|
chatMessageBo.setDeductCost(null);
|
||||||
|
chatMessageBo.setRemark("用户消息");
|
||||||
|
|
||||||
|
try {
|
||||||
|
chatMessageService.insertByBo(chatMessageBo);
|
||||||
|
log.debug("saveMessage->成功保存消息,用户ID: {}, 会话ID: {}, tokens: {}",
|
||||||
|
chatRequest.getUserId(), chatRequest.getSessionId(), tokens);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("saveMessage->保存消息失败", e);
|
||||||
|
throw new ServiceException("保存消息失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
// 保存消息记录
|
public void publishBillingEvent(ChatRequest chatRequest) {
|
||||||
chatMessageService.insertByBo(chatMessageBo);
|
log.debug("publishBillingEvent->发布计费事件,用户ID: {},会话ID: {},模型: {}",
|
||||||
|
chatRequest.getUserId(), chatRequest.getSessionId(), chatRequest.getModel());
|
||||||
System.out.println("deductToken->chatMessageService.insertByBo(: "+chatMessageBo);
|
|
||||||
System.out.println("----------------------------------------");
|
// 预检查:评估可能的扣费金额,如果余额不足则直接抛异常
|
||||||
|
try {
|
||||||
|
preCheckBalance(chatRequest);
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
log.warn("publishBillingEvent->预检查余额不足,用户ID: {},模型: {}",
|
||||||
|
chatRequest.getUserId(), chatRequest.getModel());
|
||||||
|
throw e; // 直接抛出,阻止消息保存和对话继续
|
||||||
|
}
|
||||||
|
|
||||||
|
eventPublisher.publishEvent(new ChatMessageCreatedEvent(
|
||||||
|
chatRequest.getUserId(),
|
||||||
|
chatRequest.getSessionId(),
|
||||||
|
chatRequest.getModel(),
|
||||||
|
chatRequest.getRole(),
|
||||||
|
chatRequest.getPrompt()
|
||||||
|
));
|
||||||
|
log.debug("publishBillingEvent->计费事件发布完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预检查用户余额是否足够支付可能的费用
|
||||||
|
*/
|
||||||
|
private void preCheckBalance(ChatRequest chatRequest) {
|
||||||
|
if (chatRequest.getUserId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int tokens = TikTokensUtil.tokens(chatRequest.getModel(), chatRequest.getPrompt());
|
||||||
|
String modelName = chatRequest.getModel();
|
||||||
|
ChatModelVo chatModelVo = chatModelService.selectModelByName(modelName);
|
||||||
|
BigDecimal unitPrice = BigDecimal.valueOf(chatModelVo.getModelPrice());
|
||||||
|
|
||||||
|
// 按次计费:直接检查单次费用
|
||||||
|
if (BillingType.TIMES.getCode().equals(chatModelVo.getModelType())) {
|
||||||
|
BigDecimal numberCost = unitPrice.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
checkUserBalanceWithoutDeduct(chatRequest.getUserId(), numberCost.doubleValue());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按token计费:检查累计后可能的费用
|
||||||
|
final int threshold = 100;
|
||||||
|
ChatUsageToken chatToken = chatTokenService.queryByUserId(chatRequest.getUserId(), modelName);
|
||||||
|
int previousUnpaid = (chatToken == null) ? 0 : chatToken.getToken();
|
||||||
|
int totalTokens = previousUnpaid + tokens;
|
||||||
|
|
||||||
|
int billable = (totalTokens / threshold) * threshold;
|
||||||
|
if (billable > 0) {
|
||||||
|
BigDecimal numberCost = unitPrice
|
||||||
|
.multiply(BigDecimal.valueOf(billable))
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
checkUserBalanceWithoutDeduct(chatRequest.getUserId(), numberCost.doubleValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查用户余额是否足够,但不扣除
|
||||||
|
*/
|
||||||
|
private void checkUserBalanceWithoutDeduct(Long userId, Double numberCost) {
|
||||||
|
SysUser sysUser = sysUserMapper.selectById(userId);
|
||||||
|
if (sysUser == null) {
|
||||||
|
throw new ServiceException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal userBalance = BigDecimal.valueOf(sysUser.getUserBalance() == null ? 0D : sysUser.getUserBalance())
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
BigDecimal cost = BigDecimal.valueOf(numberCost == null ? 0D : numberCost)
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
|
if (userBalance.compareTo(cost) < 0 || userBalance.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
|
throw new ServiceException("余额不足, 请充值。当前余额: " + userBalance + ",需要: " + cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存账单记录
|
||||||
|
*/
|
||||||
|
private void saveBillingRecord(ChatRequest chatRequest, int billedTokens, double cost, String billingType) {
|
||||||
|
try {
|
||||||
|
ChatMessageBo billingMessage = new ChatMessageBo();
|
||||||
|
billingMessage.setUserId(chatRequest.getUserId());
|
||||||
|
billingMessage.setSessionId(chatRequest.getSessionId());
|
||||||
|
billingMessage.setRole("system"); // 系统账单消息
|
||||||
|
billingMessage.setModelName(chatRequest.getModel());
|
||||||
|
billingMessage.setTotalTokens(billedTokens);
|
||||||
|
billingMessage.setDeductCost(cost);
|
||||||
|
billingMessage.setRemark(billingType);
|
||||||
|
|
||||||
|
// 构建账单消息内容
|
||||||
|
String content;
|
||||||
|
if ("TIMES_BILLING".equals(billingType)) {
|
||||||
|
content = String.format("按次计费:消耗 %d tokens,扣费 %.2f 元", billedTokens, cost);
|
||||||
|
} else {
|
||||||
|
content = String.format("按量计费:结算 %d tokens,扣费 %.2f 元", billedTokens, cost);
|
||||||
|
}
|
||||||
|
billingMessage.setContent(content);
|
||||||
|
|
||||||
|
chatMessageService.insertByBo(billingMessage);
|
||||||
|
log.debug("saveBillingRecord->保存账单记录成功,用户ID: {}, 计费类型: {}, 费用: {}",
|
||||||
|
chatRequest.getUserId(), billingType, cost);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("saveBillingRecord->保存账单记录失败", e);
|
||||||
|
// 账单记录失败不影响主流程,只记录错误日志
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,22 +304,26 @@ public class ChatCostServiceImpl implements IChatCostService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Double userBalance = sysUser.getUserBalance();
|
BigDecimal userBalance = BigDecimal.valueOf(sysUser.getUserBalance() == null ? 0D : sysUser.getUserBalance())
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
BigDecimal cost = BigDecimal.valueOf(numberCost == null ? 0D : numberCost)
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
|
log.debug("deductUserBalance->准备扣除: {},当前余额: {}", cost, userBalance);
|
||||||
|
|
||||||
System.out.println("deductUserBalance->准备扣除:numberCost: "+numberCost);
|
if (userBalance.compareTo(cost) < 0 || userBalance.compareTo(BigDecimal.ZERO) == 0) {
|
||||||
System.out.println("deductUserBalance->剩余金额:userBalance: "+userBalance);
|
|
||||||
|
|
||||||
|
|
||||||
if (userBalance < numberCost || userBalance == 0) {
|
|
||||||
throw new ServiceException("余额不足, 请充值");
|
throw new ServiceException("余额不足, 请充值");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BigDecimal newBalance = userBalance.subtract(cost);
|
||||||
|
if (newBalance.compareTo(BigDecimal.ZERO) < 0) {
|
||||||
|
newBalance = BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
newBalance = newBalance.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
|
||||||
sysUserMapper.update(null,
|
sysUserMapper.update(null,
|
||||||
new LambdaUpdateWrapper<SysUser>()
|
new LambdaUpdateWrapper<SysUser>()
|
||||||
.set(SysUser::getUserBalance, Math.max(userBalance - numberCost, 0))
|
.set(SysUser::getUserBalance, newBalance.doubleValue())
|
||||||
.eq(SysUser::getUserId, userId));
|
.eq(SysUser::getUserId, userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,9 @@ public class DifyServiceImpl implements IChatService {
|
|||||||
chatRequestResponse.setUserId(chatRequest.getUserId());
|
chatRequestResponse.setUserId(chatRequest.getUserId());
|
||||||
chatRequestResponse.setSessionId(chatRequest.getSessionId());
|
chatRequestResponse.setSessionId(chatRequest.getSessionId());
|
||||||
chatRequestResponse.setPrompt(respMessage.toString());
|
chatRequestResponse.setPrompt(respMessage.toString());
|
||||||
chatCostService.deductToken(chatRequestResponse);
|
// 先保存助手消息,再发布异步计费事件
|
||||||
|
chatCostService.saveMessage(chatRequestResponse);
|
||||||
|
chatCostService.publishBillingEvent(chatRequestResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ public class SseServiceImpl implements ISseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 保存消息记录 并扣除费用
|
// 先保存消息,再发布异步计费事件
|
||||||
chatCostService.deductToken(chatRequest);
|
chatCostService.saveMessage(chatRequest);
|
||||||
|
chatCostService.publishBillingEvent(chatRequest);
|
||||||
chatRequest.setUserId(chatCostService.getUserId());
|
chatRequest.setUserId(chatCostService.getUserId());
|
||||||
if(chatRequest.getSessionId()==null){
|
if(chatRequest.getSessionId()==null){
|
||||||
ChatSessionBo chatSessionBo = new ChatSessionBo();
|
ChatSessionBo chatSessionBo = new ChatSessionBo();
|
||||||
|
|||||||
Reference in New Issue
Block a user