diff --git a/pom.xml b/pom.xml
index 177ffa0..97bfe89 100644
--- a/pom.xml
+++ b/pom.xml
@@ -150,6 +150,31 @@
org.springframework.ai
spring-ai-tika-document-reader
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.12.6
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.12.6
+ runtime
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.12.6
+ runtime
+
+
org.springframework.security
diff --git a/src/main/java/com/wok/supportbot/SupportBotApplication.java b/src/main/java/com/wok/supportbot/SupportBotApplication.java
index 87771ef..aab707c 100644
--- a/src/main/java/com/wok/supportbot/SupportBotApplication.java
+++ b/src/main/java/com/wok/supportbot/SupportBotApplication.java
@@ -2,6 +2,8 @@ package com.wok.supportbot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 主启动类
@@ -9,6 +11,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* 确保 ToolCallingAutoConfiguration 不会抛出 ClassNotFoundException
*/
@SpringBootApplication
+@EnableScheduling
+@EnableAsync
public class SupportBotApplication {
public static void main(String[] args) {
diff --git a/src/main/java/com/wok/supportbot/app/AssistantApp.java b/src/main/java/com/wok/supportbot/app/AssistantApp.java
index 0b2e505..0e8b11a 100644
--- a/src/main/java/com/wok/supportbot/app/AssistantApp.java
+++ b/src/main/java/com/wok/supportbot/app/AssistantApp.java
@@ -9,6 +9,7 @@ import com.wok.supportbot.rag.preretrieval.CompressionQueryRewriter;
import com.wok.supportbot.rag.preretrieval.MultiQueryExpanderRewriter;
import com.wok.supportbot.rag.preretrieval.RewriteQueryRewriter;
import com.wok.supportbot.rag.preretrieval.TranslationQueryRewriter;
+import com.wok.supportbot.service.FaqMatchEngine;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
@@ -38,6 +39,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -60,6 +62,9 @@ public class AssistantApp {
@Resource
private ContentSafetyAdvisor contentSafetyAdvisor;
+ @Resource
+ private FaqMatchEngine faqMatchEngine;
+
private final ChatModelFactory chatModelFactory;
private final DatabaseChatMemory chatMemory;
@@ -170,6 +175,25 @@ public class AssistantApp {
return spec.stream().content();
}
+ // ==================== FAQ 优先匹配 ====================
+
+ /**
+ * 尝试 FAQ 三级匹配(精确→关键词→语义),命中则返回标准答案。
+ * 在 RAG 对话入口处优先调用,避免不必要的知识库检索开销。
+ *
+ * @param message 用户消息
+ * @return FAQ 标准答案(未命中时返回 empty)
+ */
+ private Optional tryFaqMatch(String message) {
+ try {
+ return faqMatchEngine.match(message)
+ .map(result -> result.getFaq().getAnswer());
+ } catch (Exception e) {
+ log.warn("FAQ 匹配异常,降级到 RAG: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+
// AI 恋爱知识库问答功能
@Resource
RewriteQueryRewriter rewriteQueryRewriter;
@@ -226,6 +250,13 @@ public class AssistantApp {
}
public String doChatWithRagStrategy(String message, String chatId, String strategy, List categoryIds, String systemPrompt) {
+ // FAQ 优先匹配:三级匹配(精确→关键词→语义)命中则直接返回标准答案
+ Optional faqAnswer = tryFaqMatch(message);
+ if (faqAnswer.isPresent()) {
+ log.info("FAQ 命中,直接返回标准答案: chatId={}", chatId);
+ return faqAnswer.get();
+ }
+
// 对于 MULTI_QUERY 策略,需要使用特殊的处理方式
if ("MULTI_QUERY".equalsIgnoreCase(strategy)) {
return doChatWithMultiQueryRag(message, chatId, categoryIds, systemPrompt);
@@ -282,6 +313,13 @@ public class AssistantApp {
*/
public Flux doChatWithRagStrategyByStream(String message, String chatId, String strategy,
List categoryIds, String systemPrompt) {
+ // FAQ 优先匹配:命中则直接以流式形式返回标准答案
+ Optional faqAnswer = tryFaqMatch(message);
+ if (faqAnswer.isPresent()) {
+ log.info("FAQ 命中(流式),直接返回标准答案: chatId={}", chatId);
+ return Flux.just(faqAnswer.get());
+ }
+
// 对于 MULTI_QUERY 策略,需要先手动检索合并再流式生成
if ("MULTI_QUERY".equalsIgnoreCase(strategy)) {
return doChatWithMultiQueryRagByStream(message, chatId, categoryIds, systemPrompt);
diff --git a/src/main/java/com/wok/supportbot/auth/AuthController.java b/src/main/java/com/wok/supportbot/auth/AuthController.java
new file mode 100644
index 0000000..3399d9e
--- /dev/null
+++ b/src/main/java/com/wok/supportbot/auth/AuthController.java
@@ -0,0 +1,162 @@
+package com.wok.supportbot.auth;
+
+import com.wok.supportbot.entity.SysRole;
+import com.wok.supportbot.entity.SysUser;
+import com.wok.supportbot.security.JwtTokenProvider;
+import com.wok.supportbot.service.SysUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 认证控制器
+ * 处理登录、Token 刷新、当前用户信息查询
+ */
+@Slf4j
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+ private final AuthenticationManager authenticationManager;
+ private final JwtTokenProvider jwtTokenProvider;
+ private final SysUserService sysUserService;
+
+ public AuthController(AuthenticationManager authenticationManager,
+ JwtTokenProvider jwtTokenProvider,
+ SysUserService sysUserService) {
+ this.authenticationManager = authenticationManager;
+ this.jwtTokenProvider = jwtTokenProvider;
+ this.sysUserService = sysUserService;
+ }
+
+ /**
+ * 用户登录
+ */
+ @PostMapping("/login")
+ public ResponseEntity