近年来,随着大型语言模型的快速发展,智能问答系统已经成为企业提升用户体验、降低客服成本的重要工具。本文将介绍如何利用知乎开源的FUST微服务框架和DeepSeek API,搭建一个高性能、可扩展的智能问答系统。
FUST 谐音 Fast,是知乎开发的一款基于Spring Boot的微服务开发框架,旨在帮助开发者快速构建高质量的微服务应用。它集成了主流组件,提供标准化的开发范式,并在知乎的大规模生产环境经受了考验。
我们将通过实现一个名为”Deep QA”的项目,展示如何将FUST框架与DeepSeek API结合,构建一个功能完善的智能问答系统。
二、技术栈概述 2.1 FUST框架 FUST是基于Spring Boot 3.x开发的微服务框架,具有以下优势:
开箱即用 :集成主流组件,提供标准化的开发范式
高性能 :基于高性能的Spring Boot 3.x和Armeria构建
可观测性 :内置完整的监控和追踪方案
灵活扩展 :提供丰富的扩展点,满足各种定制需求
生产验证 :在知乎大规模生产环境经受考验
2.2 DeepSeek API DeepSeek提供了一系列强大的语言模型API,包括:
deepseek-chat :通用对话模型
deepseek-coder :代码生成和理解模型
三、项目架构 Deep QA采用经典的多模块微服务架构,主要包含以下两个核心模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 deep-qa/ ├── deep-qa-api/ # HTTP API模块 │ └── src/ │ ├── main/java/com/deepqa/api/ │ │ ├── config/ # 配置类 │ │ ├── controller/ # 控制器 │ │ └── dto/ # 数据传输对象 │ └── main/resources/ │ ├── static/ # 静态资源 │ ├── templates/ # Thymeleaf模板 │ └── application.properties # 应用配置 ├── deep-qa-business/ # 业务逻辑模块 │ └── src/ │ └── main/java/com/deepqa/business/ │ ├── client/ # 外部服务客户端 │ ├── dao/ # 数据访问对象 │ ├── model/ # 数据模型 │ └── service/ # 业务服务 │ └── impl/ # 服务实现 └── proto/ # Protocol Buffers定义 ├── buf.yaml # Buf模块配置 └── hello.proto # 示例服务Proto定义
这种模块化设计有以下优势:
关注点分离 :API层负责处理HTTP请求和响应,业务层负责核心业务逻辑
代码复用 :业务逻辑可以被不同的接口层(HTTP、gRPC)复用
便于测试 :各层可以独立测试
维护简单 :模块边界清晰,降低了代码耦合度
四、环境准备 在开始项目开发前,需要准备以下环境:
JDK 17+
Maven 3.8+
MySQL 5.7+
Redis 6.0+
DeepSeek API密钥
由于FUST框架尚未发布到Maven中央仓库,需要先将其发布到本地Maven仓库:
1 2 3 4 5 6 git clone https://github.com/zhihu/fust.git cd fust./gradlew publishToMavenLocal
五、项目实现 5.1 项目配置 首先,我们来看父项目的pom.xml
配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <parent > <groupId > com.zhihu.fust</groupId > <artifactId > fust-boot-bom</artifactId > <version > 0.1.0</version > </parent > <artifactId > deep-qa</artifactId > <groupId > com.deepqa</groupId > <packaging > pom</packaging > <version > 1.0</version > <modules > <module > deep-qa-business</module > <module > deep-qa-api</module > </modules > </project >
注意这里使用了FUST框架的fust-boot-bom
作为父项目,这样就可以统一管理FUST相关依赖的版本。
5.2 数据模型设计 在deep-qa-business
模块中,我们定义了以下主要数据模型:
5.2.1 聊天历史模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @Data @Table(name = TABLE_NAME) public class ChatHistoryModel { public static final String TABLE_NAME = "t_chat_history" ; @Id private Long id; @DbAutoColumn private LocalDateTime createdAt; @DbAutoColumn private LocalDateTime updatedAt; private Long userId; private String sessionId; private String role; private String content; private Boolean hasImage; private String imageUrl; }
这里我们使用了@DbAutoColumn
注解来标记自动填充的字段,FUST框架会自动处理这些字段的创建和更新。
5.3 数据访问层 数据访问层使用MyBatis实现,FUST框架提供了TemplateDao
接口,简化了常见的CRUD操作。以ChatHistoryDao
为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Mapper public interface ChatHistoryDao extends TemplateDao <ChatHistoryModel> { @Select({"SELECT * FROM ", ChatHistoryModel.TABLE_NAME, " WHERE user_id = #{userId} AND session_id = #{sessionId} ORDER BY created_at ASC"}) @ResultMap("ChatHistoryModel") List<ChatHistoryModel> findByUserIdAndSessionId (@Param("userId") Long userId, @Param("sessionId") String sessionId) ; @Delete({"DELETE FROM ", ChatHistoryModel.TABLE_NAME, " WHERE user_id = #{userId} AND session_id = #{sessionId}"}) int deleteByUserIdAndSessionId (@Param("userId") Long userId, @Param("sessionId") String sessionId) ; @Select({"SELECT DISTINCT session_id FROM ", ChatHistoryModel.TABLE_NAME, " WHERE user_id = #{userId} ORDER BY MAX(created_at) DESC LIMIT #{limit}"}) List<String> findRecentSessionsByUserId (@Param("userId") Long userId, @Param("limit") int limit) ; }
通过继承TemplateDao
,我们自动获得了常见的CRUD操作,同时还可以根据业务需求自定义其他查询方法。
5.4 业务服务层 业务服务层是应用的核心,负责实现业务逻辑。以ChatService
接口及其实现类为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public interface ChatService { ChatHistoryModel sendMessage (Long userId, String sessionId, String message) ; List<ChatHistoryModel> getChatHistory (Long userId, String sessionId) ; List<String> getUserSessions (Long userId, int limit) ; String createNewSession (Long userId) ; boolean deleteSession (Long userId, String sessionId) ; }
实现类ChatServiceImpl
负责具体的业务逻辑实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 @Service @Slf4j public class ChatServiceImpl implements ChatService { @Autowired private ChatHistoryDao chatHistoryDao; @Autowired private ConfigService configService; @Autowired private DeepSeekClient deepSeekClient; @Override public ChatHistoryModel sendMessage (Long userId, String sessionId, String message) { ChatHistoryModel userMessage = new ChatHistoryModel (); userMessage.setUserId(userId); userMessage.setSessionId(sessionId); userMessage.setRole("user" ); userMessage.setContent(message); userMessage.setHasImage(false ); chatHistoryDao.create(userMessage); ConfigModel config = configService.getUserConfig(userId); try { List<ChatHistoryModel> historyList = chatHistoryDao.findByUserIdAndSessionId(userId, sessionId); List<Map<String, String>> conversationHistory = new java .util.ArrayList<>(); int startIndex = Math.max(0 , historyList.size() - 10 ); for (int i = startIndex; i < historyList.size(); i++) { ChatHistoryModel historyItem = historyList.get(i); if (!"user" .equals(historyItem.getRole()) && !"assistant" .equals(historyItem.getRole())) { continue ; } Map<String, String> messageMap = new java .util.HashMap<>(); messageMap.put("role" , historyItem.getRole()); messageMap.put("content" , historyItem.getContent()); conversationHistory.add(messageMap); } String aiReply = deepSeekClient.generateResponse(config, message, conversationHistory); ChatHistoryModel aiMessage = new ChatHistoryModel (); aiMessage.setUserId(userId); aiMessage.setSessionId(sessionId); aiMessage.setRole("assistant" ); aiMessage.setContent(aiReply); aiMessage.setHasImage(false ); chatHistoryDao.create(aiMessage); return aiMessage; } catch (Exception e) { log.error("Failed to get response from DeepSeek API" , e); ChatHistoryModel errorMessage = new ChatHistoryModel (); errorMessage.setUserId(userId); errorMessage.setSessionId(sessionId); errorMessage.setRole("assistant" ); errorMessage.setContent("抱歉,AI助手暂时无法回复。错误信息: " + e.getMessage()); errorMessage.setHasImage(false ); chatHistoryDao.create(errorMessage); return errorMessage; } } }
在这个实现中,我们可以看到:
保存用户消息到数据库
获取历史对话作为上下文
调用DeepSeek API获取AI回复
保存AI回复到数据库
异常处理机制
5.5 DeepSeek API集成 我们通过DeepSeekClient
类与DeepSeek API进行交互:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 @Component public class DeepSeekClient { private static final Logger LOGGER = LoggerFactory.getLogger(DeepSeekClient.class); public String generateResponse (ConfigModel config, String prompt, List<Map<String, String>> history) { try { DeepSeekApi deepSeekApi = new DeepSeekApi (config.getServerUrl(), config.getApiKey()); Map<String, Object> options = new HashMap <>(); options.put("temperature" , config.getTemperature()); options.put("top_p" , config.getTopP()); options.put("context_window" , config.getContextSize()); return deepSeekApi.generateCompletion(config.getModelName(), prompt, history, options); } catch (Exception e) { LOGGER.error("Error calling DeepSeek API" , e); return "调用AI服务出错: " + e.getMessage(); } } public List<String> getAvailableModels (String serverUrl, String apiKey) { try { DeepSeekApi deepSeekApi = new DeepSeekApi (serverUrl, apiKey); List<DeepSeekApi.Model> models = deepSeekApi.listModels(); return models.stream() .map(DeepSeekApi.Model::getName) .collect(Collectors.toList()); } catch (Exception e) { LOGGER.error("Error fetching DeepSeek models" , e); return List.of("deepseek-chat" , "deepseek-coder" , "deepseek-lite" ); } } public boolean testConnection (String serverUrl, String apiKey) { try { DeepSeekApi deepSeekApi = new DeepSeekApi (serverUrl, apiKey); return deepSeekApi.testConnection(); } catch (Exception e) { LOGGER.error("Failed to connect to DeepSeek API: {}" , serverUrl, e); return false ; } } }
这个客户端类提供了三个主要功能:生成回复、获取可用模型列表和测试连接。
5.6 Web控制器层 Web控制器层负责处理HTTP请求和响应,项目中主要有三个控制器:ChatController
、ConfigController
和HomeController
。以ChatController
为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 @RestController @RequestMapping("/api/chat") public class ChatController { private static final int MAX_SESSIONS_COUNT = 10 ; private static final int SESSION_TITLE_LENGTH = 8 ; private final ChatService chatService; @Autowired public ChatController (ChatService chatService) { this .chatService = chatService; } @PostMapping("/send") public ResponseDTO<ChatMessageDTO> sendMessage (@RequestBody SendMessageRequestDTO request) { if (request.getMessage() == null || request.getMessage().trim().isEmpty()) { return ResponseDTO.error("消息内容不能为空" ); } Long userId = 1L ; String sessionId = request.getSessionId(); if (sessionId == null || sessionId.trim().isEmpty()) { sessionId = chatService.createNewSession(userId); } ChatHistoryModel response = chatService.sendMessage(userId, sessionId, request.getMessage()); ChatMessageDTO messageDTO = convertToDTO(response); return ResponseDTO.success(messageDTO); } @GetMapping("/history/{sessionId}") public ResponseDTO<List<ChatMessageDTO>> getChatHistory (@PathVariable String sessionId) { Long userId = 1L ; List<ChatHistoryModel> history = chatService.getChatHistory(userId, sessionId); List<ChatMessageDTO> historyDTO = history.stream() .map(this ::convertToDTO) .collect(Collectors.toList()); return ResponseDTO.success(historyDTO); } private ChatMessageDTO convertToDTO (ChatHistoryModel model) { ChatMessageDTO dto = new ChatMessageDTO (); dto.setId(model.getId().toString()); dto.setRole(model.getRole()); dto.setContent(model.getContent()); if (model.getCreatedAt() != null ) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ); dto.setTime(model.getCreatedAt().format(formatter)); } if (model.getHasImage() && model.getImageUrl() != null ) { dto.setHasImage(true ); dto.setImageUrl(model.getImageUrl()); } else { dto.setHasImage(false ); } return dto; } }
FUST框架提供了统一的响应格式ResponseDTO
,让API接口返回的数据更加规范和一致。
5.7 应用启动类 最后,我们看一下应用的入口类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @SpringBootApplication @Import(ServiceConfiguration.class) @ComponentScan(basePackages = {"com.deepqa.api", "com.deepqa.business"}) @EntityScan(basePackages = {"com.deepqa.business.model"}) public class DeepQAMain { public static void main (String[] args) { if (System.getProperty("env.name" ) == null && System.getenv("ENV_NAME" ) == null ) { System.setProperty("env.name" , "dev" ); } TelemetryInitializer.init(); SpringApplication application = new SpringApplication (DeepQAMain.class); application.setAdditionalProfiles("api" ); application.run(args); } }
注意这里调用了TelemetryInitializer.init()
,这是FUST框架提供的遥测初始化方法,用于收集应用指标和监控数据。
六、项目部署与运行 FUST框架提供了一系列脚本来简化项目的构建和运行过程。本文 demo 项目代码地址访问 deep-qa 。
6.1 项目构建 首先,编译Maven项目:
然后,使用构建脚本:
6.2 项目运行 根据不同的环境,使用不同的环境配置脚本:
开发环境 1 2 3 4 5 source ./dev-env.shbash run.sh deep-qa-api/target
生产环境 1 2 3 4 5 6 7 8 9 export DB_USER="production_username" export DB_PASSWORD="production_password" source ./prod-env.shbash run.sh deep-qa-api/target
6.3 服务访问 API服务启动后,可以通过以下URL访问:
七、FUST框架的企业级优势 通过Deep QA项目的实现,我们可以看到FUST框架在企业级开发中的一些显著优势:
7.1 结构化的项目组织 FUST框架提供了清晰的项目结构和模块划分,使得代码组织更加合理,便于团队协作和维护。
7.2 统一的异常处理 FUST提供了全局异常处理机制和统一的响应格式,使得API接口的错误处理更加规范和一致。
7.3 内置的可观测性 通过TelemetryInitializer
,FUST框架提供了内置的监控和跟踪功能,便于问题排查和性能优化。
7.4 灵活的环境配置 FUST框架支持通过环境变量或系统属性来配置不同的运行环境,使得应用可以在不同环境中无缝切换。
7.5 高性能的服务框架 FUST基于高性能的Armeria服务器构建,提供了出色的性能和吞吐能力,特别适合高并发场景。
7.6 多协议支持 FUST同时支持HTTP和gRPC协议,使得服务可以同时为不同类型的客户端提供接口。
7.7 企业级数据库访问 FUST提供了对MyBatis的增强支持,简化了数据库访问层的开发,提高了代码的可维护性。
八、总结与展望 通过本文,我们详细介绍了如何使用FUST框架和DeepSeek API构建一个功能完整的智能问答系统。这个系统具有以下特点:
模块化架构 :清晰的模块划分和责任分配
扩展性强 :可以方便地添加新功能和扩展现有功能
高性能 :基于FUST和Armeria的高性能服务框架
可靠稳定 :完善的异常处理和错误恢复机制
可观测性 :内置的监控和跟踪功能
未来,我们可以考虑在以下方面进行扩展和优化:
多模态支持 :支持图像识别和处理
用户认证与权限管理 :完善用户管理功能
多语言支持 :支持更多语言和地区
对话记忆优化 :改进对话上下文管理
模型微调 :根据特定领域需求对模型进行微调
九、参考资料
FUST框架官方文档
DeepSeek API文档
Spring Boot官方文档
MyBatis官方文档
Armeria服务器文档
通过本文的学习,相信读者已经对如何使用FUST框架构建企业级应用有了深入的理解,并能够将这些知识应用到自己的项目中。