From 9f7f00e50cfa4119684643e3e4b3c1661a3d6b68 Mon Sep 17 00:00:00 2001 From: Administrator <1037463791@qq.com> Date: Wed, 27 Aug 2025 15:30:59 +0800 Subject: [PATCH] =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=8F=91=E9=80=81=E6=B6=88?= =?UTF-8?q?=E6=81=AF=20=E2=86=92=20=E9=A2=84=E6=A3=80=E6=9F=A5=E4=BD=99?= =?UTF-8?q?=E9=A2=9D=20=E2=86=92=20=E4=BF=9D=E5=AD=98=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=B6=88=E6=81=AF=20=E2=86=92=20=E5=8F=91=E5=B8=83=E8=AE=A1?= =?UTF-8?q?=E8=B4=B9=E4=BA=8B=E4=BB=B6=20=E2=86=92=20=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E6=89=A3=E8=B4=B9=20=E2=86=92=20=E4=BF=9D=E5=AD=98=E8=B4=A6?= =?UTF-8?q?=E5=8D=95=E8=AE=B0=E5=BD=95=20=E6=B7=BB=E5=8A=A0=E4=BA=86billin?= =?UTF-8?q?gType=E8=AE=A1=E8=B4=B9=E7=B1=BB=E5=9E=8B=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=BF=9D=E5=AD=98=E7=9A=84=E6=97=B6=E5=80=99?= =?UTF-8?q?=E5=86=99=E5=85=A5=E8=BF=9B=E5=8E=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/ruoyi/domain/ChatMessage.java | 5 ++ .../org/ruoyi/domain/bo/ChatMessageBo.java | 5 ++ .../org/ruoyi/domain/vo/ChatMessageVo.java | 10 ++++ .../chat/impl/ChatCostServiceImpl.java | 60 +++++++++++++++---- .../service/chat/impl/SseServiceImpl.java | 24 ++++---- .../sql/update/chat-message-billing-type.sql | 4 ++ 6 files changed, 83 insertions(+), 25 deletions(-) create mode 100644 script/sql/update/chat-message-billing-type.sql diff --git a/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatMessage.java b/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatMessage.java index 4adcded6..6a4fac2c 100644 --- a/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatMessage.java +++ b/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/ChatMessage.java @@ -69,5 +69,10 @@ public class ChatMessage extends BaseEntity { */ private String remark; + /** + * 计费类型(1-token计费,2-次数计费,null-普通消息) + */ + private String billingType; + } diff --git a/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageBo.java b/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageBo.java index fd228f73..5b22b008 100644 --- a/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageBo.java +++ b/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/bo/ChatMessageBo.java @@ -75,5 +75,10 @@ public class ChatMessageBo extends BaseEntity { @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) private String remark; + /** + * 计费类型(1-token计费,2-次数计费,null-普通消息) + */ + private String billingType; + } diff --git a/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/vo/ChatMessageVo.java b/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/vo/ChatMessageVo.java index 00920ef9..140398fc 100644 --- a/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/vo/ChatMessageVo.java +++ b/ruoyi-modules-api/ruoyi-chat-api/src/main/java/org/ruoyi/domain/vo/ChatMessageVo.java @@ -4,6 +4,8 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; +import org.ruoyi.common.excel.annotation.ExcelDictFormat; +import org.ruoyi.common.excel.convert.ExcelDictConvert; import org.ruoyi.domain.ChatMessage; import java.io.Serial; @@ -73,6 +75,13 @@ public class ChatMessageVo implements Serializable { @ExcelProperty(value = "模型名称") private String modelName; + /** + * 计费类型(1-token计费,2-次数计费) + */ + @ExcelProperty(value = "计费类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_model_billing") + private String billingType; + /** * 备注 */ @@ -87,4 +96,5 @@ public class ChatMessageVo implements Serializable { private Date createTime; + } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java index 6947893d..22fffce2 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/ChatCostServiceImpl.java @@ -80,7 +80,7 @@ public class ChatCostServiceImpl implements IChatCostService { } // 记录账单消息 - saveBillingRecord(chatRequest, tokens, numberCost.doubleValue(), "TIMES_BILLING"); + saveBillingRecord(chatRequest, tokens, numberCost.doubleValue(), chatModelVo.getModelType()); return; } @@ -121,7 +121,7 @@ public class ChatCostServiceImpl implements IChatCostService { log.debug("deductToken->扣费成功,更新余数: {}", remainder); // 记录账单消息 - saveBillingRecord(chatRequest, billable, numberCost.doubleValue(), "TOKEN_BILLING"); + saveBillingRecord(chatRequest, billable, numberCost.doubleValue(), chatModelVo.getModelType()); } catch (ServiceException e) { // 余额不足时,不更新token累计,保持原有累计数 log.warn("deductToken->余额不足,本次token累计保持不变: {}", totalTokens); @@ -167,6 +167,19 @@ public class ChatCostServiceImpl implements IChatCostService { // 普通消息不涉及扣费,deductCost保持null chatMessageBo.setDeductCost(null); chatMessageBo.setRemark("用户消息"); + + // 设置计费类型(根据模型配置获取计费类型) + try { + ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); + if (chatModelVo != null) { + chatMessageBo.setBillingType(chatModelVo.getModelType()); + } else { + chatMessageBo.setBillingType(null); // 模型不存在时设为null + } + } catch (Exception e) { + log.warn("saveMessage->获取模型计费类型失败,设为null,模型: {}", chatRequest.getModel()); + chatMessageBo.setBillingType(null); + } try { chatMessageService.insertByBo(chatMessageBo); @@ -261,7 +274,7 @@ public class ChatCostServiceImpl implements IChatCostService { /** * 保存账单记录 */ - private void saveBillingRecord(ChatRequest chatRequest, int billedTokens, double cost, String billingType) { + private void saveBillingRecord(ChatRequest chatRequest, int billedTokens, double cost, String billingTypeCode) { try { ChatMessageBo billingMessage = new ChatMessageBo(); billingMessage.setUserId(chatRequest.getUserId()); @@ -270,26 +283,47 @@ public class ChatCostServiceImpl implements IChatCostService { billingMessage.setModelName(chatRequest.getModel()); billingMessage.setTotalTokens(billedTokens); billingMessage.setDeductCost(cost); - billingMessage.setRemark(billingType); + billingMessage.setRemark(getBillingTypeName(billingTypeCode)); - // 构建账单消息内容 - 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); + // 设置计费类型和构建消息内容 + setBillingTypeAndContent(billingMessage, billingTypeCode, billedTokens, cost); chatMessageService.insertByBo(billingMessage); log.debug("saveBillingRecord->保存账单记录成功,用户ID: {}, 计费类型: {}, 费用: {}", - chatRequest.getUserId(), billingType, cost); + chatRequest.getUserId(), billingTypeCode, cost); } catch (Exception e) { log.error("saveBillingRecord->保存账单记录失败", e); // 账单记录失败不影响主流程,只记录错误日志 } } + /** + * 设置计费类型和构建消息内容 + */ + private void setBillingTypeAndContent(ChatMessageBo billingMessage, String billingTypeCode, int billedTokens, double cost) { + billingMessage.setBillingType(billingTypeCode); + + // 使用枚举获取计费类型并构建消息内容 + BillingType billingType = BillingType.fromCode(billingTypeCode); + if (billingType != null) { + String content = switch (billingType) { + case TIMES -> String.format("%s:消耗 %d tokens,扣费 %.2f 元", billingType.getDescription(), billedTokens, cost); + case TOKEN -> String.format("%s:结算 %d tokens,扣费 %.2f 元", billingType.getDescription(), billedTokens, cost); + }; + billingMessage.setContent(content); + } else { + billingMessage.setContent(String.format("系统计费:处理 %d tokens,扣费 %.2f 元", billedTokens, cost)); + } + } + + /** + * 获取计费类型名称(用于remark字段) + */ + private String getBillingTypeName(String billingTypeCode) { + BillingType billingType = BillingType.fromCode(billingTypeCode); + return billingType != null ? billingType.getDescription() : "系统计费"; + } + /** * 从用户余额中扣除费用 * diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java index e3f7d82b..d07cce19 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java @@ -104,21 +104,21 @@ public class SseServiceImpl implements ISseService { //待优化的地方 (这里请前端提交send的时候传递uuid进来或者sessionId) //待优化的地方 (这里请前端提交send的时候传递uuid进来或者sessionId) //待优化的地方 (这里请前端提交send的时候传递uuid进来或者sessionId) - { - // 设置会话id - if (chatRequest.getUuid() == null) { - //暂时随机生成会话id - chatRequest.setSessionId(System.currentTimeMillis()); - } else { - //这里或许需要修改一下,这里应该用uuid 或者 前端传递 sessionId - chatRequest.setSessionId(chatRequest.getUuid()); - } - } +// { +// // 设置会话id +// if (chatRequest.getUuid() == null) { +// //暂时随机生成会话id +// chatRequest.setSessionId(System.currentTimeMillis()); +// } else { +// //这里或许需要修改一下,这里应该用uuid 或者 前端传递 sessionId +// chatRequest.setSessionId(chatRequest.getUuid()); +// } +// } // 先保存消息,再发布异步计费事件 chatCostService.saveMessage(chatRequest); - chatCostService.publishBillingEvent(chatRequest); + chatRequest.setUserId(chatCostService.getUserId()); if (chatRequest.getSessionId() == null) { ChatSessionBo chatSessionBo = new ChatSessionBo(); @@ -131,7 +131,7 @@ public class SseServiceImpl implements ISseService { } // 自动选择模型并获取对应的聊天服务 IChatService chatService = autoSelectModelAndGetService(chatRequest); - + chatCostService.publishBillingEvent(chatRequest); // 仅当 autoSelectModel = true 时,才启用重试与降级 if (Boolean.TRUE.equals(chatRequest.getAutoSelectModel())) { ChatModelVo currentModel = this.chatModelVo; diff --git a/script/sql/update/chat-message-billing-type.sql b/script/sql/update/chat-message-billing-type.sql new file mode 100644 index 00000000..364645a8 --- /dev/null +++ b/script/sql/update/chat-message-billing-type.sql @@ -0,0 +1,4 @@ +-- 为 chat_message 表添加 billing_type 字段 +ALTER TABLE chat_message + ADD COLUMN billing_type char NULL COMMENT '计费类型(1-token计费,2-次数计费,null-普通消息)'; +