Browse Source

Merge branch 'master' into cpq-dev

chenlei1231 2 months ago
parent
commit
813b14038a
12 changed files with 258 additions and 213 deletions
  1. 1 1
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/dmp/service/RealtimeReportService.java
  2. 1 1
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/dmp/service/google/GAReportService.java
  3. 10 14
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/cache/LocalCache.java
  4. 10 20
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/controller/ChatController.java
  5. 32 18
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/entity/ChatHistory.java
  6. 34 39
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/listeners/OpenAISSEEventSourceListener.java
  7. 9 7
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/service/ChatService.java
  8. 13 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/service/IChatHistoryService.java
  9. 21 0
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/service/impl/ChatHistoryServiceImpl.java
  10. 112 109
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/service/impl/ChatServiceImpl.java
  11. 4 1
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/quota/service/IResourceQuotaService.java
  12. 11 3
      jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/quota/service/impl/ResourceQuotaServiceImpl.java

+ 1 - 1
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/dmp/service/RealtimeReportService.java

@@ -4,13 +4,13 @@ import static org.jeecg.modules.adweb.dmp.vo.report.SiteOverviewStatsVO.DailySta
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
 import lombok.extern.slf4j.Slf4j;
 
 import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.compress.utils.Lists;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.ImmutableTriple;

+ 1 - 1
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/dmp/service/google/GAReportService.java

@@ -3,13 +3,13 @@ package org.jeecg.modules.adweb.dmp.service.google;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.google.common.collect.Lists;
 
 import jakarta.annotation.PostConstruct;
 
 import lombok.extern.slf4j.Slf4j;
 
 import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.compress.utils.Lists;
 import org.apache.commons.lang3.reflect.TypeUtils;
 import org.jeecg.common.util.DateUtils;
 import org.jeecg.common.util.FastJsonUtil;

+ 10 - 14
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/cache/LocalCache.java

@@ -4,31 +4,27 @@ import cn.hutool.cache.CacheUtil;
 import cn.hutool.cache.impl.TimedCache;
 import cn.hutool.core.date.DateUnit;
 
-//update-begin---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
 /**
- * 聊天记录本地缓存
+ * 本地缓存 - {@link SseEmitter}
+ *
  * @author chenrui
  * @date 2024/1/26 20:06
  */
 public class LocalCache {
-    /**
-     * 缓存时长
-     */
+
+    /** 缓存时长 */
     public static final long TIMEOUT = 5 * DateUnit.MINUTE.getMillis();
-    /**
-     * 清理间隔
-     */
+
+    /** 清理间隔 */
     private static final long CLEAN_TIMEOUT = 5 * DateUnit.MINUTE.getMillis();
-    /**
-     * 缓存对象
-     */
+
+    /** 缓存对象 */
     public static final TimedCache<String, Object> CACHE = CacheUtil.newTimedCache(TIMEOUT);
 
     static {
-        //启动定时任务
+        // 启动定时任务
         CACHE.schedulePrune(CLEAN_TIMEOUT);
     }
 }
-
-//update-end---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------

+ 10 - 20
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/controller/ChatController.java

@@ -8,35 +8,30 @@ import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
-//update-begin---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------
-
 /**
- * @Description: chatGpt-聊天接口
+ * @Description: ChatGPT-聊天接口
  * @Author: chenrui
  * @Date: 2024/1/9 16:30
  */
 @Controller
-@RequestMapping("/test/ai/chat")
+@RequestMapping("/ai/chat")
 public class ChatController {
 
-    @Autowired
-    ChatService chatService;
+    @Autowired private ChatService chatService;
 
-    /**
-     * 创建sse连接
-     *
-     * @return
-     */
+    /** 创建sse连接 */
     @GetMapping(value = "/send")
-    public SseEmitter createConnect(@RequestParam(name = "topicId", required = false) String topicId, @RequestParam(name = "message", required = true) String message) {
+    public SseEmitter createConnect(
+            @RequestParam(name = "topicId", required = false) String topicId,
+            @RequestParam(name = "message", required = true) String message) {
         SseEmitter sse = chatService.createChat();
         chatService.sendMessage(topicId, message);
         return sse;
     }
 
-    //update-begin---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
     /**
      * 保存聊天记录
+     *
      * @param chatHistoryVO
      * @return
      * @author chenrui
@@ -50,6 +45,7 @@ public class ChatController {
 
     /**
      * 查询聊天记录
+     *
      * @return
      * @author chenrui
      * @date 2024/2/22 14:03
@@ -59,16 +55,10 @@ public class ChatController {
     public Result<ChatHistoryVO> getHistoryByTopic() {
         return chatService.getHistoryByTopic();
     }
-    //update-end---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
 
-    /**
-     * 关闭连接
-     */
+    /** 关闭连接 */
     @GetMapping(value = "/close")
     public void closeConnect() {
         chatService.closeChat();
     }
-
-
 }
-//update-end---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------

+ 32 - 18
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/entity/ChatHistory.java

@@ -4,49 +4,63 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
+
 import io.swagger.v3.oas.annotations.media.Schema;
+
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
+
 import org.jeecgframework.poi.excel.annotation.Excel;
 import org.springframework.format.annotation.DateTimeFormat;
 
 import java.io.Serializable;
-import java.util.Date;
 
 /**
  * @Description: chatGPT聊天历史记录表
  * @Author: jeecg-boot
- * @Date:   2025-01-07
+ * @Date: 2025-01-07
  * @Version: V1.0
  */
 @Data
-@TableName("sys_chat_history")
+@TableName("adweb_ai_chat_history")
 @Accessors(chain = true)
 @EqualsAndHashCode(callSuper = false)
-@Schema(description="chatGPT聊天历史记录表")
+@Schema(description = "chatGPT聊天历史记录表")
 public class ChatHistory implements Serializable {
     private static final long serialVersionUID = 1L;
 
-	/**主键*/
-	@TableId(type = IdType.ASSIGN_ID)
+    /** 主键 */
+    @TableId(type = IdType.ASSIGN_ID)
     @Schema(description = "主键")
     private String id;
-	/**用户ID*/
-	@Excel(name = "用户ID", width = 15)
-    @Schema(description = "用户ID")
-    private String userId;
-	/**角色*/
-	@Excel(name = "角色", width = 15)
+
+    /** 用户ID,与用户表关联 */
+    @Excel(name = "用户ID,与用户表关联", width = 15)
+    @Schema(description = "用户ID,与用户表关联")
+    private String uid;
+
+    /** 角色 */
+    @Excel(name = "角色", width = 15)
     @Schema(description = "角色")
     private String role;
-	/**内容*/
-	@Excel(name = "内容", width = 15)
+
+    /** 内容 */
+    @Excel(name = "内容", width = 15)
     @Schema(description = "内容")
     private String content;
-	/**创建时间*/
-	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
-    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+
+    /** 创建时间 */
+    @Excel(name = "创建时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @Schema(description = "创建时间")
-    private Date createTime;
+    private java.util.Date ctime;
+
+    /** 修改时间 */
+    @Excel(name = "修改时间", width = 20, format = "yyyy-MM-dd HH:mm:ss")
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Schema(description = "修改时间")
+    private java.util.Date utime;
 }

+ 34 - 39
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/listeners/OpenAISSEEventSourceListener.java

@@ -3,21 +3,24 @@ package org.jeecg.modules.adweb.gpt.listeners;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse;
 import com.unfbx.chatgpt.entity.chat.Message;
+
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
+
 import okhttp3.Response;
 import okhttp3.ResponseBody;
 import okhttp3.sse.EventSource;
 import okhttp3.sse.EventSourceListener;
+
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
 import java.util.Objects;
 
-//update-begin---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------
 /**
  * OpenAI的SSE监听
+ *
  * @author chenrui
  * @date 2024/1/26 20:06
  */
@@ -34,67 +37,62 @@ public class OpenAISSEEventSourceListener extends EventSourceListener {
         this.sseEmitter = sseEmitter;
     }
 
-    public OpenAISSEEventSourceListener(String topicId,SseEmitter sseEmitter){
+    public OpenAISSEEventSourceListener(String topicId, SseEmitter sseEmitter) {
         this.topicId = topicId;
         this.sseEmitter = sseEmitter;
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @Override
     public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
         log.info("OpenAI建立sse连接...");
     }
 
-    /**
-     * {@inheritDoc}
-     */
+    /** {@inheritDoc} */
     @SneakyThrows
     @Override
-    public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) {
+    public void onEvent(
+            @NotNull EventSource eventSource, String id, String type, @NotNull String data) {
         log.debug("OpenAI返回数据:{}", data);
         tokens += 1;
         if (data.equals("[DONE]")) {
             log.info("OpenAI返回数据结束了");
-            sseEmitter.send(SseEmitter.event()
-                    .id("[TOKENS]")
-                    .data("<br/><br/>tokens:" + tokens())
-                    .reconnectTime(3000));
-            sseEmitter.send(SseEmitter.event()
-                    .id("[DONE]")
-                    .data("[DONE]")
-                    .reconnectTime(3000));
+            sseEmitter.send(
+                    SseEmitter.event()
+                            .id("[TOKENS]")
+                            .data("<br/><br/>tokens:" + tokens())
+                            .reconnectTime(3000));
+            sseEmitter.send(SseEmitter.event().id("[DONE]").data("[DONE]").reconnectTime(3000));
             // 传输完成后自动关闭sse
             sseEmitter.complete();
             return;
         }
         ObjectMapper mapper = new ObjectMapper();
-        ChatCompletionResponse completionResponse = mapper.readValue(data, ChatCompletionResponse.class); // 读取Json
+        ChatCompletionResponse completionResponse =
+                mapper.readValue(data, ChatCompletionResponse.class); // 读取Json
         try {
-            sseEmitter.send(SseEmitter.event()
-                    .id(this.topicId)
-                    .data(completionResponse.getChoices().get(0).getDelta())
-                    .reconnectTime(3000));
+            sseEmitter.send(
+                    SseEmitter.event()
+                            .id(this.topicId)
+                            .data(completionResponse.getChoices().get(0).getDelta())
+                            .reconnectTime(3000));
         } catch (Exception e) {
-            log.error(e.getMessage(),e);
+            log.error(e.getMessage(), e);
             eventSource.cancel();
         }
     }
 
-
     @Override
     public void onClosed(@NotNull EventSource eventSource) {
         log.info("流式输出返回值总共{}tokens", tokens() - 2);
         log.info("OpenAI关闭sse连接...");
     }
 
-
     @SneakyThrows
     @Override
     public void onFailure(@NotNull EventSource eventSource, Throwable t, Response response) {
         String errMsg = "";
-        ResponseBody body = null == response ? null:response.body();
+        ResponseBody body = null == response ? null : response.body();
         if (Objects.nonNull(body)) {
             log.error("OpenAI  sse连接异常data:{},异常:{}", body.string(), t.getMessage());
             errMsg = body.string();
@@ -103,22 +101,20 @@ public class OpenAISSEEventSourceListener extends EventSourceListener {
             errMsg = t.getMessage();
         }
         eventSource.cancel();
-        sseEmitter.send(SseEmitter.event()
-                .id("[ERR]")
-                .data(Message.builder().content(explainErr(errMsg)).build())
-                .reconnectTime(3000));
-        sseEmitter.send(SseEmitter.event()
-                .id("[DONE]")
-                .data("[DONE]")
-                .reconnectTime(3000));
+        sseEmitter.send(
+                SseEmitter.event()
+                        .id("[ERR]")
+                        .data(Message.builder().content(explainErr(errMsg)).build())
+                        .reconnectTime(3000));
+        sseEmitter.send(SseEmitter.event().id("[DONE]").data("[DONE]").reconnectTime(3000));
         sseEmitter.complete();
     }
 
-    private String explainErr(String errMsg){
-        if(StringUtils.isEmpty(errMsg)){
+    private String explainErr(String errMsg) {
+        if (StringUtils.isEmpty(errMsg)) {
             return "";
         }
-        if(errMsg.contains("Rate limit")){
+        if (errMsg.contains("Rate limit")) {
             return "请求频率太快了,请等待20秒再试.";
         }
         return errMsg;
@@ -126,11 +122,10 @@ public class OpenAISSEEventSourceListener extends EventSourceListener {
 
     /**
      * tokens
+     *
      * @return
      */
     public long tokens() {
         return tokens;
     }
 }
-
-//update-end---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------

+ 9 - 7
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/service/ChatService.java

@@ -4,23 +4,23 @@ import org.jeecg.common.api.vo.Result;
 import org.jeecg.modules.adweb.gpt.vo.ChatHistoryVO;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
-//update-begin---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------
+// update-begin---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------
 
 /**
  * AI助手聊天Service
+ *
  * @author chenrui
  * @date 2024/1/26 20:08
  */
 public interface ChatService {
     /**
      * 创建SSE
+     *
      * @return
      */
     SseEmitter createChat();
 
-    /**
-     * 关闭SSE
-     */
+    /** 关闭SSE */
     void closeChat();
 
     /**
@@ -33,9 +33,10 @@ public interface ChatService {
      */
     void sendMessage(String topicId, String message);
 
-    //update-begin---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
+    // update-begin---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
     /**
      * 保存聊天记录
+     *
      * @param chatHistoryVO
      * @return
      * @author chenrui
@@ -45,12 +46,13 @@ public interface ChatService {
 
     /**
      * 查询聊天记录
+     *
      * @return
      * @author chenrui
      * @date 2024/2/22 13:59
      */
     Result<ChatHistoryVO> getHistoryByTopic();
-    //update-end---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
+    // update-end---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
 }
 
-//update-end---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------
+// update-end---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------

+ 13 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/service/IChatHistoryService.java

@@ -0,0 +1,13 @@
+package org.jeecg.modules.adweb.gpt.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import org.jeecg.modules.adweb.gpt.entity.ChatHistory;
+
+/**
+ * @Description: adweb_ai_chat_history
+ * @Author: jeecg-boot
+ * @Date: 2025-12-09
+ * @Version: V1.0
+ */
+public interface IChatHistoryService extends IService<ChatHistory> {}

+ 21 - 0
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/service/impl/ChatHistoryServiceImpl.java

@@ -0,0 +1,21 @@
+package org.jeecg.modules.adweb.gpt.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+
+import lombok.extern.slf4j.Slf4j;
+
+import org.jeecg.modules.adweb.gpt.entity.ChatHistory;
+import org.jeecg.modules.adweb.gpt.mapper.ChatHistoryMapper;
+import org.jeecg.modules.adweb.gpt.service.IChatHistoryService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Description: adweb_ai_chat_history
+ * @Author: jeecg-boot
+ * @Date: 2025-12-09
+ * @Version: V1.0
+ */
+@Slf4j
+@Service
+public class ChatHistoryServiceImpl extends ServiceImpl<ChatHistoryMapper, ChatHistory>
+        implements IChatHistoryService {}

+ 112 - 109
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/gpt/service/impl/ChatServiceImpl.java

@@ -1,39 +1,40 @@
 package org.jeecg.modules.adweb.gpt.service.impl;
 
-import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONUtil;
-import com.alibaba.fastjson.JSONArray;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.google.common.collect.Lists;
 import com.unfbx.chatgpt.OpenAiStreamClient;
 import com.unfbx.chatgpt.entity.chat.ChatCompletion;
 import com.unfbx.chatgpt.entity.chat.Message;
 import com.unfbx.chatgpt.exception.BaseException;
+
 import lombok.extern.slf4j.Slf4j;
+
+import org.apache.commons.lang3.StringUtils;
 import org.apache.shiro.SecurityUtils;
 import org.jeecg.common.api.vo.Result;
 import org.jeecg.common.exception.JeecgBootException;
 import org.jeecg.common.system.vo.LoginUser;
+import org.jeecg.common.util.FastJsonUtil;
 import org.jeecg.common.util.SpringContextUtils;
 import org.jeecg.common.util.UUIDGenerator;
+import org.jeecg.modules.adweb.common.util.AdwebRedisUtil;
 import org.jeecg.modules.adweb.gpt.cache.LocalCache;
 import org.jeecg.modules.adweb.gpt.entity.ChatHistory;
 import org.jeecg.modules.adweb.gpt.listeners.OpenAISSEEventSourceListener;
-import org.jeecg.modules.adweb.gpt.mapper.ChatHistoryMapper;
 import org.jeecg.modules.adweb.gpt.service.ChatService;
+import org.jeecg.modules.adweb.gpt.service.IChatHistoryService;
 import org.jeecg.modules.adweb.gpt.vo.ChatHistoryVO;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-//update-begin---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * AI助手聊天Service
+ *
  * @author chenrui
  * @date 2024/1/26 20:07
  */
@@ -41,90 +42,62 @@ import java.util.List;
 @Slf4j
 public class ChatServiceImpl implements ChatService {
 
-    @Autowired
-    private ChatHistoryMapper chatHistoryMapper;
-
-    //update-begin---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
-    private static final String CACHE_KEY_PREFIX = "ai:chart:";
+    @Autowired private IChatHistoryService chatHistoryService;
 
-    /**
-     *
-     */
-    private static final String CACHE_KEY_MSG_CONTEXT = "msg_content";
+    // private static final String CACHE_KEY_PREFIX = "ai:chat:";
 
+    // private static final String CACHE_KEY_MSG_HISTORY = "msg_history";
 
-    /**
-     *
-     */
-    private static final String CACHE_KEY_MSG_HISTORY = "msg_history";
+    private static final String REDIS_KEY_PREFIX_MSG_CONTEXT = "ai:chat:msg_content";
 
-    @Autowired
-    RedisTemplate redisTemplate;
-    //update-end---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
+    @Autowired AdwebRedisUtil adwebRedisUtil;
 
-    private OpenAiStreamClient openAiStreamClient = null;
-
-    //update-begin---author:chenrui ---date:20240131  for:[QQYUN-8212]fix 没有配置启动报错------------
-    public ChatServiceImpl() {
-        try {
-            this.openAiStreamClient = SpringContextUtils.getBean(OpenAiStreamClient.class);
-        } catch (Exception ignored) {
-        }
-    }
+    @Autowired private OpenAiStreamClient openAiStreamClient;
 
-    /**
-     * 防止client不能成功注入
-     * @return
-     * @author chenrui
-     * @date 2024/2/3 23:08
-     */
-    private OpenAiStreamClient ensureClient(){
-        if(null == this.openAiStreamClient){
+    /** 防止client不能成功注入 */
+    private OpenAiStreamClient ensureClient() {
+        if (Objects.isNull(this.openAiStreamClient)) {
             this.openAiStreamClient = SpringContextUtils.getBean(OpenAiStreamClient.class);
         }
         return this.openAiStreamClient;
     }
-    //update-end---author:chenrui ---date:20240131  for:[QQYUN-8212]fix 没有配置启动报错------------
-
-    private String getUserId() {
-        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
-        return sysUser.getId();
-    }
 
     @Override
     public SseEmitter createChat() {
         String uid = getUserId();
-        //默认30秒超时,设置为0L则永不超时
+        // 默认30秒超时,设置为0L则永不超时
         SseEmitter sseEmitter = new SseEmitter(-0L);
-        //完成后回调
-        sseEmitter.onCompletion(() -> {
-            log.info("[{}]结束连接...................",uid);
-            LocalCache.CACHE.remove(uid);
-        });
-        //超时回调
-        sseEmitter.onTimeout(() -> {
-            log.info("[{}]连接超时...................", uid);
-        });
-        //异常回调
+        // 完成后回调
+        sseEmitter.onCompletion(
+                () -> {
+                    log.info("[{}]结束连接...................", uid);
+                    LocalCache.CACHE.remove(uid);
+                });
+        // 超时回调
+        sseEmitter.onTimeout(
+                () -> {
+                    log.info("[{}]连接超时...................", uid);
+                });
+        // 异常回调
         sseEmitter.onError(
                 throwable -> {
                     try {
                         log.info("[{}]连接异常,{}", uid, throwable.toString());
-                        sseEmitter.send(SseEmitter.event()
-                                .id(uid)
-                                .name("发生异常!")
-                                .data(Message.builder().content("发生异常请重试!").build())
-                                .reconnectTime(3000));
+                        sseEmitter.send(
+                                SseEmitter.event()
+                                        .id(uid)
+                                        .name("发生异常!")
+                                        .data(Message.builder().content("发生异常请重试!").build())
+                                        .reconnectTime(3000));
                         LocalCache.CACHE.put(uid, sseEmitter);
                     } catch (IOException e) {
-                        log.error(e.getMessage(),e);
+                        log.error(e.getMessage(), e);
                     }
-                }
-        );
+                });
         try {
             sseEmitter.send(SseEmitter.event().reconnectTime(5000));
         } catch (IOException e) {
-            log.error(e.getMessage(),e);
+            log.error(e.getMessage(), e);
         }
         LocalCache.CACHE.put(uid, sseEmitter);
         log.info("[{}]创建sse连接成功!", uid);
@@ -137,7 +110,7 @@ public class ChatServiceImpl implements ChatService {
         SseEmitter sse = (SseEmitter) LocalCache.CACHE.get(uid);
         if (sse != null) {
             sse.complete();
-            //移除
+            // 移除
             LocalCache.CACHE.remove(uid);
         }
     }
@@ -145,71 +118,101 @@ public class ChatServiceImpl implements ChatService {
     @Override
     public void sendMessage(String topicId, String message) {
         String uid = getUserId();
-        if (StrUtil.isBlank(message)) {
+        if (StringUtils.isBlank(message)) {
             log.info("参数异常,message为null");
             throw new BaseException("参数异常,message不能为空~");
         }
-        if (StrUtil.isBlank(topicId)) {
+        if (StringUtils.isBlank(topicId)) {
             topicId = UUIDGenerator.generate();
         }
-        //update-begin---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
-        log.info("话题id:{}", topicId);
-        String cacheKey = CACHE_KEY_PREFIX + uid + "_" + topicId;
-        String messageContext = (String) redisTemplate.opsForHash().get(cacheKey, CACHE_KEY_MSG_CONTEXT);
-        List<Message> msgHistory = new ArrayList<>();
-        if (StrUtil.isNotBlank(messageContext)) {
-            List<Message> messages = JSONArray.parseArray(messageContext, Message.class);
-            msgHistory = messages == null ? new ArrayList<>() : messages;
-        }
-        Message currentMessage = Message.builder().content(message).role(Message.Role.USER).build();
-        msgHistory.add(currentMessage);
 
+        log.info("话题id:{}", topicId);
+        // 1. 获取当前话题下的message context
+        String contextKey = String.format("%s:%s:%s", REDIS_KEY_PREFIX_MSG_CONTEXT, uid, topicId);
+        String contextValue = adwebRedisUtil.getString(contextKey);
+        List<Message> messageContext =
+                StringUtils.isNotEmpty(contextValue)
+                        ? FastJsonUtil.parseList(contextValue, Message.class)
+                        : Lists.newArrayList();
+        // 1.1 添加当前message到context
+        messageContext.add(Message.builder().content(message).role(Message.Role.USER).build());
+
+        // 2. 发送GPT请求
         SseEmitter sseEmitter = (SseEmitter) LocalCache.CACHE.get(uid);
         if (sseEmitter == null) {
             log.info("聊天消息推送失败uid:[{}],没有创建连接,请重试。", uid);
             throw new JeecgBootException("聊天消息推送失败uid:[{}],没有创建连接,请重试。~");
         }
-        OpenAISSEEventSourceListener openAIEventSourceListener = new OpenAISSEEventSourceListener(topicId, sseEmitter);
-        ChatCompletion completion = ChatCompletion
-                .builder()
-                .messages(msgHistory)
-                .model(ChatCompletion.Model.GPT_3_5_TURBO.getName())
-                .build();
+        OpenAISSEEventSourceListener openAIEventSourceListener =
+                new OpenAISSEEventSourceListener(topicId, sseEmitter);
+        ChatCompletion completion =
+                ChatCompletion.builder()
+                        .messages(messageContext)
+                        .model(ChatCompletion.Model.GPT_3_5_TURBO.getName())
+                        .build();
         ensureClient().streamChatCompletion(completion, openAIEventSourceListener);
-        redisTemplate.opsForHash().put(cacheKey, CACHE_KEY_MSG_CONTEXT, JSONUtil.toJsonStr(msgHistory));
-        //update-end---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
+
+        // 3. 将message context保存到Redis
+        adwebRedisUtil.set(
+                contextKey,
+                FastJsonUtil.toJSONString(
+                        messageContext.stream()
+                                // 每个话题仅保存最后100条message
+                                .skip(Math.max(0, messageContext.size() - 100))
+                                .collect(Collectors.toList())),
+                // 话题 TTL设置为3小时
+                3 * 60 * 60);
+
         Result.ok(completion.tokens());
     }
 
-    //update-begin---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
     @Override
-//    public Result<?> saveHistory(ChatHistoryVO chatHistoryVO) {
-//        String uid = getUserId();
-//        String cacheKey = CACHE_KEY_PREFIX + CACHE_KEY_MSG_HISTORY + ":" + uid;
-//        redisTemplate.opsForValue().set(cacheKey, chatHistoryVO.getContent());
-//        return Result.OK("保存成功");
-//    }
     public Result<?> saveHistory(ChatHistoryVO chatHistoryVO) {
+        // String uid = getUserId();
+        // String cacheKey = CACHE_KEY_PREFIX + CACHE_KEY_MSG_HISTORY + ":" + uid;
+        // redisTemplate.opsForValue().set(cacheKey, chatHistoryVO.getContent());
+        // return Result.OK("保存成功");
+
+        // AdWeb重构 - 聊天记录保存到DB,不存Redis
         String uid = getUserId();
-        ChatHistory chatHistory = new ChatHistory();
-        chatHistory.setUserId(uid);
-        chatHistory.setRole("USER");
+        ChatHistory chatHistory =
+                chatHistoryService.getOne(
+                        new LambdaQueryWrapper<ChatHistory>().eq(ChatHistory::getUid, uid));
+
+        if (Objects.isNull(chatHistory)) {
+            chatHistory = new ChatHistory();
+            chatHistory.setUid(uid);
+            chatHistory.setRole(Message.Role.USER.getName());
+        }
+
         chatHistory.setContent(chatHistoryVO.getContent());
-        chatHistory.setCreateTime(new Date());
-        chatHistoryMapper.insert(chatHistory);
+        chatHistoryService.saveOrUpdate(chatHistory);
+
         return Result.OK("保存成功");
     }
 
     @Override
     public Result<ChatHistoryVO> getHistoryByTopic() {
+        // String uid = getUserId();
+        // String cacheKey = CACHE_KEY_PREFIX + CACHE_KEY_MSG_HISTORY + ":" + uid;
+        // String historyContent = (String) redisTemplate.opsForValue().get(cacheKey);
+        // ChatHistoryVO chatHistoryVO = new ChatHistoryVO();
+        // chatHistoryVO.setContent(historyContent);
+        // return Result.OK(chatHistoryVO);
+
+        // AdWeb重构 - 聊天记录保存到DB,不存Redis
         String uid = getUserId();
-        String cacheKey = CACHE_KEY_PREFIX + CACHE_KEY_MSG_HISTORY + ":" + uid;
-        String historyContent = (String) redisTemplate.opsForValue().get(cacheKey);
+        ChatHistory chatHistory =
+                chatHistoryService.getOne(
+                        new LambdaQueryWrapper<ChatHistory>().eq(ChatHistory::getUid, uid));
+
         ChatHistoryVO chatHistoryVO = new ChatHistoryVO();
-        chatHistoryVO.setContent(historyContent);
+        chatHistoryVO.setContent(Objects.nonNull(chatHistory) ? chatHistory.getContent() : null);
         return Result.OK(chatHistoryVO);
     }
-    //update-end---author:chenrui ---date:20240223  for:[QQYUN-8225]聊天记录保存------------
-}
 
-//update-end---author:chenrui ---date:20240126  for:【QQYUN-7932】AI助手------------
+    private String getUserId() {
+        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
+        return sysUser.getId();
+    }
+}

+ 4 - 1
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/quota/service/IResourceQuotaService.java

@@ -7,7 +7,10 @@ import org.jeecg.modules.adweb.quota.entity.ResourceQuota;
 import org.jeecg.modules.adweb.quota.vo.ResourceQuotaVO;
 
 /**
- * @Description: adweb_resource_quota @Author: jeecg-boot @Date: 2025-12-07 @Version: V1.0
+ * @Description: adweb_resource_quota
+ * @Author: jeecg-boot
+ * @Date: 2025-12-07
+ * @Version: V1.0
  */
 public interface IResourceQuotaService extends IService<ResourceQuota> {
 

+ 11 - 3
jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/adweb/quota/service/impl/ResourceQuotaServiceImpl.java

@@ -26,7 +26,10 @@ import org.springframework.stereotype.Service;
 import java.util.Objects;
 
 /**
- * @Description: adweb_resource_quota @Author: jeecg-boot @Date: 2025-12-07 @Version: V1.0
+ * @Description: adweb_resource_quota
+ * @Author: jeecg-boot
+ * @Date: 2025-12-07
+ * @Version: V1.0
  */
 @Slf4j
 @Service
@@ -82,7 +85,7 @@ public class ResourceQuotaServiceImpl extends ServiceImpl<ResourceQuotaMapper, R
             this.saveOrUpdate(resourceQuota);
             log.info(
                     "管理员 {} 为用户 {} 更新资源额度 {}",
-                    ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername(),
+                    this.getLoginUser().getUsername(),
                     resourceQuotaVO.getUid(),
                     FastJsonUtil.toJSONString(resourceQuota));
             return true;
@@ -150,10 +153,15 @@ public class ResourceQuotaServiceImpl extends ServiceImpl<ResourceQuotaMapper, R
         this.save(resourceQuota);
         log.info(
                 "管理员 {} 为用户 {} 配置资源额度 {}",
-                ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername(),
+                this.getLoginUser().getUsername(),
                 uid,
                 FastJsonUtil.toJSONString(resourceQuota));
 
         return resourceQuota;
     }
+
+    /** 获取当前登陆用户 */
+    private LoginUser getLoginUser() {
+        return (LoginUser) SecurityUtils.getSubject().getPrincipal();
+    }
 }