尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

AI互助平台开发实战:Spring Boot 3 + 大模型接口全流程实现

AI互助平台开发实战:Spring Boot 3 + 大模型接口全流程实现 好helppeer.ai 这个项目名很直白拆开看就是help帮助 peer同伴/同行者 .ai人工智能。如果你在开发一个 AI 技能互助平台、AI 学习社区或者以 AI 为核心的知识问答与同伴互助产品那么这个名字非常贴切。本文会围绕这类 AI 互助平台的典型技术需求从项目定位、技术选型、后端核心模块、AI 会话接入到接口联调与生产部署整理一套可以直接落地的实战方案。无论你是正在规划类似产品还是单纯想练习一个AI 业务系统的综合项目这篇文章都能给你一个完整的参考。代码以 Spring Boot 3 原生 HTTP 调用大模型接口为例重点讲解设计思路、核心表结构、AI 模块接入方式与常见坑点前端部分只给出基础交互逻辑不展开复杂界面。1. helppeer.ai 项目定位与技术架构1.1 这类平台解决什么问题在线学习或者技能成长过程中最容易遇到的瓶颈是遇到问题找不到人问。传统的问答社区存在几个体验问题提问后回答周期长质量参差不齐。新手不知道怎么描述问题更不知道找谁问。没有激励机制高手不愿意持续回答。没有沉淀重复问题反复出现。helppeer.ai 的核心思路是把人和AI组合起来AI 先即时回答解决大部分通用问题如果 AI 解决不了再通过智能匹配把问题转给领域相近的同伴Peer。这种模式既能保证响应速度又能保留人与人之间的互助价值。1.2 核心功能模块一个最小可用版本MVP至少要包含以下模块模块功能说明用户中心注册、登录、个人资料、技能标签维护互助广场发布问题、浏览问题、搜索问题智能问答调用大模型 API 实现 AI 首轮回答同伴匹配根据问题标签匹配擅长该领域的用户解答与评价回答、采纳、点赞、评价消息中心问题被回答、被采纳时发送通知1.3 技术选型建议以 Java 技术栈为例推荐如下组合后端框架Spring Boot 3.x简化配置生态成熟。持久层MyBatis-Plus代码生成效率高适合快速迭代。数据库MySQL 8.x存储业务数据。缓存Redis保存会话上下文、热点数据与在线状态。AI 接入使用 HTTP 接口调用大模型服务不强制绑定某个 SDK。前端Vue 3 Element Plus本文只演示核心调用逻辑。部署Docker Docker Compose。这套选型的特点是技术栈通用性好招聘成本低资料多且 AI 模块可以被替换——以后想换模型、换供应商只需要改一个接口封装层。2. 环境准备与项目初始化2.1 本地开发环境开始编码前先确认以下工具已经安装JDK 17 Maven 3.6 MySQL 8.x Redis 6.x IntelliJ IDEA 或 Eclipse版本不必完全一致但尽量使用较新的稳定版本。JDK 使用 17 是因为 Spring Boot 3.x 要求最低 JDK 17。2.2 Spring Boot 项目创建你可以通过 Spring Initializr 初始化项目也可以直接用 IDEA 创建。需要引入的依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.25/version /dependency /dependencies说明Hutool 是工具类库用来简化 HTTP 请求、日期处理、随机数生成等常见操作属于可选依赖。如果不喜欢引入额外依赖也可以直接使用 Spring 的RestTemplate或 JDK 11 的java.net.http.HttpClient。2.3 项目目录结构helppeer-ai/ ├── src/main/java/com/helppeer/ │ ├── HelppeerApplication.java │ ├── config/ │ │ ├── RedisConfig.java │ │ └── WebConfig.java │ ├── controller/ │ │ ├── UserController.java │ │ ├── QuestionController.java │ │ └── AiChatController.java │ ├── service/ │ │ ├── UserService.java │ │ ├── QuestionService.java │ │ ├── AiChatService.java │ │ └── PeerMatchService.java │ ├── mapper/ │ │ ├── UserMapper.java │ │ ├── QuestionMapper.java │ │ └── AnswerMapper.java │ ├── entity/ │ │ ├── User.java │ │ ├── Question.java │ │ └── Answer.java │ └── dto/ │ ├── QuestionDTO.java │ └── ChatRequestDTO.java └── src/main/resources/ ├── application.yml └── mapper/这个结构保留了经典的三层架构风格职责清晰Controller 只做参数接收与结果返回Service 处理业务逻辑Mapper 进行数据库操作。3. 数据库设计与核心表结构3.1 设计思路互助平台的数据模型相比电商系统要简单一些但有几个点需要提前想清楚用户除了基础信息外需要有技能标签用于同类问题匹配。问题与标签是多对多关系建议单独建关联表方便后续扩展标签体系。AI 回答需要保留记录方便用户查看历史对话也方便统计 token 消耗。解答采纳后要更新问题状态避免用户重复作答。3.2 建表 SQL下面是核心表的 SQL 设计生产环境可以增加更多索引MVP 阶段这些字段足够。-- 用户表 CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 用户名, password VARCHAR(100) NOT NULL COMMENT 加密后的密码, nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像URL, bio VARCHAR(500) DEFAULT NULL COMMENT 个人简介, level INT DEFAULT 1 COMMENT 等级, points INT DEFAULT 0 COMMENT 积分, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 问题表 CREATE TABLE question ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT 提问人ID, title VARCHAR(200) NOT NULL COMMENT 问题标题, content TEXT COMMENT 问题详细描述, status TINYINT DEFAULT 0 COMMENT 状态0-待解答1-AI已回复2-已采纳, ai_answer TEXT COMMENT AI首轮回答内容, view_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT问题表; -- 回答表 CREATE TABLE answer ( id BIGINT NOT NULL AUTO_INCREMENT, question_id BIGINT NOT NULL, user_id BIGINT NOT NULL, content TEXT NOT NULL, is_accepted TINYINT DEFAULT 0 COMMENT 是否被采纳, like_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_question_id (question_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT回答表; -- 用户技能标签表 CREATE TABLE user_skill ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, skill_name VARCHAR(50) NOT NULL, level TINYINT DEFAULT 1 COMMENT 熟练度 1-5, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_skill_name (skill_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户技能表; -- AI对话记录表 CREATE TABLE ai_chat_history ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, question_id BIGINT DEFAULT NULL, role VARCHAR(20) NOT NULL COMMENT user/assistant/system, content TEXT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENTAI对话记录表;需要注意密码字段要和数据库字段保持一致本文示例实体类用 String 类型数据库用 VARCHAR。ai_chat_history保存了完整的对话内容可以支持多轮追问也可以用于后续数据分析比如统计高频问题、优化智能匹配。所有表都使用utf8mb4因为 utf8mb4 完整支持 emoji 和特殊字符AI 返回内容中经常会有这类字符。4. 后端核心模块实现4.1 通用返回结果封装在开发前后端分离项目时最好定义一个统一的返回结果类否则接口格式五花八门前端联调会很痛苦。package com.helppeer.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }4.2 用户注册与密码处理用户注册的密码不能明文存储。BCrypt 是目前最普遍的密码哈希方案Spring Security 的BCryptPasswordEncoder可以直接使用也可以引入spring-security-crypto依赖单独使用。先添加依赖dependency groupIdorg.springframework.security/groupId artifactIdspring-security-crypto/artifactId /dependency注意spring-security-crypto独立使用时不需要额外配置BCryptPasswordEncoder可以直接 new。用户 Service 核心代码package com.helppeer.service.impl; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.helppeer.entity.User; import com.helppeer.mapper.UserMapper; import com.helppeer.service.UserService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import javax.annotation.Resource; Service public class UserServiceImpl implements UserService { Resource private UserMapper userMapper; private final BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); Override public User register(String username, String password) { if (StrUtil.isBlank(username) || StrUtil.isBlank(password)) { throw new RuntimeException(用户名和密码不能为空); } Long count userMapper.selectCount( new LambdaQueryWrapperUser().eq(User::getUsername, username) ); if (count 0) { throw new RuntimeException(用户名已存在); } User user new User(); user.setUsername(username); user.setPassword(encoder.encode(password)); user.setNickname(username); user.setLevel(1); user.setPoints(0); userMapper.insert(user); return user; } Override public User login(String username, String password) { User user userMapper.selectOne( new LambdaQueryWrapperUser().eq(User::getUsername, username) ); if (user null) { throw new RuntimeException(用户不存在); } if (!encoder.matches(password, user.getPassword())) { throw new RuntimeException(密码错误); } return user; } }代码里有两个关键点LambdaQueryWrapper是 MyBatis-Plus 提供的条件构造器可以避免把 SQL 字符串写在代码里减少拼写错误。encoder.matches(password, user.getPassword())用于校验明文密码与数据库中的哈希值是否匹配不要直接调用equals比较。4.3 发布问题并触发 AI 回答当用户发布问题时合理的顺序应该是校验参数。保存问题到数据库。调用 AI 服务生成首轮回答。更新问题的ai_answer字段和状态。异步匹配可能擅长该问题的同伴。这个流程中AI 接口调用耗时可能较长如果同步执行用户会一直等待。MVP 阶段可以先同步调用因为大模型接口通常 3-10 秒内就能返回如果想优化体验可以把 AI 调用和同伴匹配放入消息队列或线程池异步执行。QuestionService 中的发布方法Override public Question publishQuestion(QuestionDTO dto, Long userId) { Question question new Question(); question.setUserId(userId); question.setTitle(dto.getTitle()); question.setContent(dto.getContent()); question.setStatus(0); question.setViewCount(0); questionMapper.insert(question); // 异步调用 AI避免阻塞主流程 aiChatService.asyncGenerateFirstAnswer(question.getId()); return question; }这里使用了asyncGenerateFirstAnswer方法内部的异步实现需要配合 Spring 的Async注解。在启动类或配置类上加上EnableAsync然后在 Service 实现方法上标注AsyncSpring 会在线程池中执行该方法。4.4 AI 智能问答模块AI 模块是 helppeer.ai 的核心也是最容易踩坑的部分。本节给出一个通用的 HTTP 调用示例不绑定具体厂商你可以直接对接 OpenAI 兼容接口、国内大模型平台的 HTTP 接口或者其他兼容接口。前提是对方提供 OpenAI 风格的chat/completions接口这种格式现在已经成为事实标准。4.4.1 配置管理在application.yml中增加 AI 配置ai: api-key: ${AI_API_KEY:sk-xxxxxxxx} base-url: ${AI_BASE_URL:https://api.openai.com/v1} model: ${AI_MODEL:gpt-3.5-turbo} max-tokens: 1000 temperature: 0.7这里使用了环境变量占位符${AI_API_KEY:sk-xxxxxxxx}意思是优先从环境变量AI_API_KEY读取如果不存在则使用默认值。这样做的好处是敏感信息不会被提交到代码仓库。4.4.2 AI 调用服务使用 Spring 的RestTemplate发送 HTTP 请求。先注册 Beanpackage com.helppeer.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; Configuration public class RestTemplateConfig { Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5000); factory.setReadTimeout(30000); return new RestTemplate(factory); } }设置readTimeout30000很重要。大模型接口生成内容需要时间特别是较长的回答可能超过 10 秒。如果使用默认超时通常很短会出现频繁的 SocketTimeoutException。AI 调用代码package com.helppeer.service.impl; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; Slf4j Service public class AiChatServiceImpl implements AiChatService { Resource private RestTemplate restTemplate; Value(${ai.api-key}) private String apiKey; Value(${ai.base-url}) private String baseUrl; Value(${ai.model}) private String model; Value(${ai.max-tokens}) private Integer maxTokens; Value(${ai.temperature}) private Double temperature; private final ObjectMapper objectMapper new ObjectMapper(); Override public String chat(String userMessage, ListMapString, String history) { String url baseUrl /chat/completions; HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(apiKey); // 构建完整的消息序列 ListMapString, String messages new ArrayList(); messages.add(Map.of(role, system, content, 你是一个技术互助平台的AI助手回答要简洁、准确、有条理。)); if (history ! null) { messages.addAll(history); } MapString, String userMsg new HashMap(); userMsg.put(role, user); userMsg.put(content, userMessage); messages.add(userMsg); MapString, Object body new HashMap(); body.put(model, model); body.put(messages, messages); body.put(max_tokens, maxTokens); body.put(temperature, temperature); HttpEntityMapString, Object request new HttpEntity(body, headers); try { ResponseEntityString response restTemplate.exchange( url, HttpMethod.POST, request, String.class); if (response.getStatusCode() ! HttpStatus.OK) { log.error(AI接口返回异常状态码: {}, response.getStatusCode()); return 抱歉AI服务暂时不可用请稍后再试。; } JsonNode root objectMapper.readTree(response.getBody()); JsonNode content root.path(choices).get(0).path(message).path(content); return content.asText(); } catch (Exception e) { log.error(调用AI接口失败, e); return 抱歉AI服务暂时不可用请稍后再试。; } } }代码说明headers.setBearerAuth(apiKey)对应 HTTP 请求头的Authorization: Bearer sk-xxx这是目前 OpenAI 兼容接口的标准认证方式。system 消息用来设定 AI 的人设和行为准则。在 helppeer.ai 的场景里可以告诉 AI 它是互助平台的助手回答要简洁、有条理。history参数用于支持多轮对话把之前几轮的消息按顺序传过去。异常处理非常关键。AI 接口可能因网络、限流、内容审核等原因失败不能因为 AI 异常导致整个业务报错必须降级处理。4.4.3 同伴匹配逻辑简化版同伴匹配的完整实现可以做得很复杂比如基于向量相似度、用户活跃度、历史采纳率等。MVP 阶段先用一个简单规则从问题标题和内容中提取技能标签。根据标签在user_skill表中查找匹配用户。排除提问者自己。按技能熟练度排序取前 N 个。Override public ListUser matchPeers(Long questionId, int limit) { Question question questionMapper.selectById(questionId); ListString keywords extractKeywords(question.getTitle() question.getContent()); SetLong userIds new HashSet(); for (String keyword : keywords) { ListUserSkill skills userSkillMapper.selectList( new LambdaQueryWrapperUserSkill() .eq(UserSkill::getSkillName, keyword) ); for (UserSkill skill : skills) { userIds.add(skill.getUserId()); } } userIds.remove(question.getUserId()); if (userIds.isEmpty()) { return new ArrayList(); } return userMapper.selectList( new LambdaQueryWrapperUser() .in(User::getId, userIds) .last(LIMIT limit) ); }extractKeywords是关键词抽取方法MVP 阶段可以直接用简单的规则去匹配数据库中的技能标签不引入 NLP 库。例如把用户技能表中已有的标签拿出来然后判断问题文本中是否包含该标签。生产环境的升级方向是使用 Embedding 向量表示问题文本检索相似技能标签。结合消息中心把推荐结果转成站内通知。根据用户采纳率动态调整推荐权重。5. 前端调用逻辑与接口联调示例5.1 接口规范设计后端接口建议统一使用/api前缀方便后面配置网关和统一鉴权。方法路径说明POST/api/user/register用户注册POST/api/user/login用户登录POST/api/question/publish发布问题GET/api/question/list问题列表GET/api/question/detail/{id}问题详情POST/api/ai/chatAI 多轮对话GET/api/peer/match/{questionId}匹配同伴5.2 Axios 调用 AI 接口示例前端使用 Vue 3 Axios 时核心请求代码如下import axios from axios const request axios.create({ baseURL: /api, timeout: 60000 }) // 发布问题并获取 AI 回答 export async function publishQuestion(questionData) { const res await request.post(/question/publish, questionData) return res.data } // AI 多轮对话 export async function sendChatMessage(historyList) { const res await request.post(/ai/chat, { questionId: 123, history: historyList, userMessage: 具体的问题内容 }) return res.data }前端联调时需要注意Axios 的timeout需要设置得比后端接口耗时更长比如 60 秒。history列表结构要和后端定义一致避免序列化失败。如果部署时存在跨域问题后端需要配置 CORS 或使用 Nginx 反向代理。5.3 Nginx 反向代理配置示例前后端分离项目部署时通常用 Nginx 代理前端静态资源和后端接口。server { listen 80; server_name helppeer.ai; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 大模型返回较慢需要提高超时时间 proxy_connect_timeout 60s; proxy_read_timeout 120s; } location / { try_files $uri $uri/ /index.html; } }注意proxy_read_timeout这个配置。默认情况下 Nginx 读取后端响应超时是 60 秒如果大模型生成内容耗时较长加上前端轮询或等待逻辑容易在 Nginx 层超时。部署环境如果不是 Nginx也要检查网关层的超时配置。6. 运行与验证流程6.1 启动后端服务确认 MySQL 和 Redis 已启动在application.yml中配置好连接信息后直接启动 Spring Boot 应用。mvn spring-boot:run看到启动日志中有类似以下内容说明启动成功Tomcat started on port 8080 (http) with context path Started HelppeerApplication in 3.21 seconds6.2 用 curl 验证接口注册用户curl -X POST http://localhost:8080/api/user/register \ -H Content-Type: application/json \ -d {username: zhangsan, password: 123456}预期返回{ code: 200, message: success, data: { id: 1, username: zhangsan, nickname: zhangsan } }发布问题curl -X POST http://localhost:8080/api/question/publish \ -H Content-Type: application/json \ -d {title: Spring Boot 3 如何集成 Redis, content: 我按照文档配置了依赖和连接信息但启动时报错 Unable to connect to Redis请问是什么原因}如果 AI 模块配置正确过一段时间后查询问题详情能看到ai_answer字段已经有内容。6.3 AI 接口常见响应结构正常情况下OpenAI 兼容接口的响应是一个 JSON{ id: chatcmpl-xxx, object: chat.completion, choices: [ { index: 0, message: { role: assistant, content: Spring Boot 3 集成 Redis 报错的原因通常有以下几种... }, finish_reason: stop } ], usage: { prompt_tokens: 50, completion_tokens: 120, total_tokens: 170 } }后端代码中解析的是choices[0].message.content。不同的模型供应商可能返回结构略有差异但兼容 OpenAI 格式的接口通常都遵循这个结构。7. 常见问题与排查思路在开发 helppeer.ai 这类 AI 业务系统时遇到频率最高的问题集中在以下几类。下面以表格形式给出排查方向后面再展开说明。问题现象常见原因解决思路AI 接口调用超时RestTemplate 默认超时太短设置 readTimeout 为 30-60 秒AI 返回内容被截断max_tokens 太小调大 max_tokens或开启流式输出API Key 报 401Key 配置不正确检查环境变量配置确认 Key 前后无空格Redis 连接失败密码、端口、IP 配置错误用 redis-cli 验证连接接口返回中文乱码数据库编码不是 utf8mb4建库时指定 CHARACTER SET utf8mb4发布问题后 AI 回答为空AI 调用异步执行失败被吞掉查看日志检查 AI 接口返回与异常堆栈前后端联调跨域后端未配置 CORS添加 CORS 配置或使用 Nginx 代理7.1 AI 接口超时这个问题的根源是 RestTemplate 默认的 readTimeout 太短。Spring 的SimpleClientHttpRequestFactory默认超时通常为 0 或很短但大模型生成回答普遍需要几秒到几十秒。解决方法是像上文一样设置 connectTimeout 和 readTimeout或者改用 WebClient。7.2 AI 返回内容不完整如果 AI 回答在中间被硬生生截断大概率是max_tokens不够。注意max_tokens限制的是生成的最大 token 数不是字符数。对于中文内容1 个 token 大约对应 1-2 个汉字。如果需要生成长文建议把max_tokens设置为 2000 或更高。7.3 异步 AI 调用失败无感知使用Async后子线程中的异常默认不会被主线程捕获如果不处理日志中甚至看不到错误。建议在异步方法内部显式 try-catchAsync Override public void asyncGenerateFirstAnswer(Long questionId) { try { String answer chat(questionTitle, null); // 更新 question 的 ai_answer 字段 } catch (Exception e) { log.error(生成AI回答失败, questionId{}, questionId, e); } }这是很重要的一点。异步任务里的异常一定要自己兜底否则排查问题时非常困难。7.4 跨域问题Spring Boot 后端可以直接配置 CORSpackage com.helppeer.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }生产环境不建议使用allowedOriginPatterns(*)配合allowCredentials(true)应该把允许的域名写死。开发阶段为了方便可以放行所有来源。8. 最佳实践与工程建议8.1 API Key 安全AI 平台的 API Key 是敏感凭证必须遵循以下规范不要硬编码在前端代码中否则会被用户直接抓包获取。不要提交到 Git 仓库使用环境变量或配置中心管理。定期轮换 Key必要时对单个用户做调用频率限制。如果团队规模较大建议通过后端代理 AI 接口而不是让前端直连。8.2 对话上下文管理AI 多轮对话需要保留上下文。MVP 阶段可以把最近 N 轮对话存入 Redis设置过期时间避免用户长时间不活跃导致上下文堆积。示例伪代码public String chatWithContext(Long userId, String userMessage) { String historyKey chat:history: userId; // 从 Redis 读取最近 10 条记录 ListMapString, String history redisTemplate.opsForList().range(historyKey, -10, -1); String reply aiChatService.chat(userMessage, history); // 写入本次问答 redisTemplate.opsForList().rightPush(historyKey, Map.of(role, user, content, userMessage)); redisTemplate.opsForList().rightPush(historyKey, Map.of(role, assistant, content, reply)); // 设置过期时间为 1 小时 redisTemplate.expire(historyKey, Duration.ofHours(1)); return reply; }通过 Redis 保存上下文的前提是问题不敏感。如果涉及用户隐私需要评估数据存储方案与合规要求。8.3 降级与容灾AI 服务再稳定也可能出现不可用因此业务链路要做降级设计AI 调用失败时问题仍然要保存成功只是ai_answer为空。前端展示时AI 不可用区域显示AI 服务繁忙等待人工回答。对 AI 接口增加熔断机制连续失败 N 次后暂停调用一段时间。关键业务接口增加重试但要注意避免重复扣费。8.4 Token 消耗监控大模型 API 是按 token 计费的上线前必须对 token 消耗做监控。建议在 AI 调用完成后把响应中的 usage 字段保存到日志或数据库请求 ID、用户 ID、模型名称、提示词 token、生成 token、总计 token、耗时这样每天可以算出用户维度的成本设置预算上限避免单个用户异常消耗大量 token。8.5 数据库与接口安全所有 SQL 操作都通过 MyBatis-Plus 条件构造器或参数绑定避免字符串拼接导致 SQL 注入。发布问题和回答接口要做内容长度校验防止超长内容打满数据库。敏感操作如删除问题、修改用户信息需要做权限校验不能只看前端隐藏按钮。上线前给数据库加好索引尤其是question.status、question.user_id这些高频查询字段。9. 总结与下一步学习方向helppeer.ai 这个项目虽然看起来是一个垂直的互助社区但把它拆开后涉及的技术点其实覆盖了一条完整的技术链路用户注册登录、业务数据建模、AI 大模型接入、异步任务、Redis 缓存、消息通知、部署上线。这是一道性价比很高的综合实战训练题。本文给出的代码是 MVP 版本的核心骨架你在实际开发中可以沿这个方向继续扩展接入 WebSocket让用户实时收到有人回答了你的通知提升互动率。把 AI 回答改为流式输出SSE逐字展示回答内容体验会好很多。引入消息队列RocketMQ 或 RabbitMQ把 AI 调用、积分赠送、通知发送解耦。用向量数据库 Embedding 做技能标签推荐让同伴匹配更准确。给问题列表增加 Elasticsearch 或 MySQL 全文索引优化搜索体验。总体来看helppeer.ai 这类产品能不能做好除了工程实现外更重要的是产品机制——怎么让提问者快速获得高质量答案怎么让回答者愿意持续贡献。技术上我们要保证的是AI 回答快、同伴匹配准、系统稳定可扩展。如果你正在搭建类似项目可以从本文的代码骨架开始先把提问 → AI 回答 → 匹配同伴这条主链路跑通再逐步叠加消息通知、积分体系、搜索等模块。技术选型不是最难的难的是把每个环节的异常处理和服务降级做扎实。希望这篇文章对你的项目有帮助也欢迎在评论区交流你在开发 AI 互助平台时遇到的问题。
返回列表