以下是基于Java实现心理健康问答系统的完整技术路径与核心代码实现方案,涵盖系统架构设计、关键模块开发、数据库设计及安全优化等核心环节,适合作为实际开发的技术指南。
一、系统架构设计
1. 分层架构
┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ 前端 │ ←→ │ 后端 │ ←→ │ 数据库 │ │ (Vue.js) │ │ (Spring Boot) │ │ (MySQL+Redis) │ └───────────────┘ └───────────────┘ └───────────────┘ ↑ ↑ │ │ └─────────第三方服务─────────┘ (腾讯云NLP/短信服务)2. 技术选型
- 核心框架:Spring Boot 2.7 + Spring Security
- 持久层:MyBatis-Plus(简化CRUD)
- NLP服务:腾讯云NLP语义相似度API(替代方案:HanLP本地化实现)
- 缓存:Redis(存储热点问答、会话状态)
- 任务调度:Quartz(定时清理过期数据)
- 日志:Logback + ELK(可选)
二、核心模块实现
1. 用户认证模块
关键类:
java
// JwtAuthenticationFilter.java public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException { String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { try { String username = JwtTokenUtil.getUsernameFromToken(token.substring(7)); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(auth); } catch (Exception e) { response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid token"); return; } } chain.doFilter(request, response); } } // SecurityConfig.java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**").permitAll() .antMatchers("/api/qa/**").authenticated() .anyRequest().denyAll() .and() .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } }2. 问答匹配模块
NLP集成实现:
java
// NlpService.java @Service public class NlpService { @Value("${nlp.tencent.secret-id}") private String secretId; @Value("${nlp.tencent.secret-key}") private String secretKey; public double computeSimilarity(String text1, String text2) { try { // 调用腾讯云语义相似度API TencentCloudClient client = new TencentCloudClient(secretId, secretKey); SimilarityRequest req = new SimilarityRequest(); req.setText1(text1); req.setText2(text2); SimilarityResponse resp = client.send(req); return resp.getScore(); } catch (Exception e) { // 降级方案:使用Jaccard相似度 return jaccardSimilarity(text1, text2); } } private double jaccardSimilarity(String s1, String s2) { Set<String> words1 = Arrays.stream(s1.split("\\s+")).collect(Collectors.toSet()); Set<String> words2 = Arrays.stream(s2.split("\\s+")).collect(Collectors.toSet()); Set<String> intersection = new HashSet<>(words1); intersection.retainAll(words2); Set<String> union = new HashSet<>(words1); union.addAll(words2); return union.isEmpty() ? 0 : (double) intersection.size() / union.size(); } }问答服务实现:
java
// QuestionAnswerService.java @Service @RequiredArgsConstructor public class QuestionAnswerService { private final QuestionRepository questionRepo; private final NlpService nlpService; private final RedisTemplate<String, String> redisTemplate; public String getAnswer(String question, Long userId) { // 1. 检查缓存 String cacheKey = "qa:" + userId + ":" + DigestUtils.md5Hex(question); String cachedAnswer = redisTemplate.opsForValue().get(cacheKey); if (cachedAnswer != null) return cachedAnswer; // 2. 精确匹配 Question exactMatch = questionRepo.findByQuestion(question) .orElse(null); if (exactMatch != null) { return cacheAnswer(cacheKey, exactMatch.getAnswer()); } // 3. 语义匹配 List<Question> candidates = questionRepo.findTop10ByOrderByCreateTimeDesc(); Question bestMatch = candidates.stream() .max(Comparator.comparingDouble(q -> nlpService.computeSimilarity(question, q.getQuestion()))) .orElse(null); if (bestMatch != null && nlpService.computeSimilarity(question, bestMatch.getQuestion()) > 0.8) { return cacheAnswer(cacheKey, bestMatch.getAnswer()); } // 4. 默认回复 String defaultAnswer = generateDefaultAnswer(question, userId); return cacheAnswer(cacheKey, defaultAnswer); } private String cacheAnswer(String key, String answer) { redisTemplate.opsForValue().set(key, answer, 1, TimeUnit.DAYS); return answer; } private String generateDefaultAnswer(String question, Long userId) { // 记录未匹配问题供人工审核 unmatchedQuestionRepo.save(new UnmatchedQuestion(userId, question)); return "您的问题需要专业咨询师分析,是否需要预约咨询?"; } }3. 咨询会话管理
会话状态设计:
java
// SessionState.java public enum SessionState { WAITING, // 等待用户输入 PROCESSING, // 系统处理中 CONSULTING, // 咨询师接入中 CLOSED // 会话结束 } // ConsultationSession.java @Data @Entity public class ConsultationSession { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long userId; private Long counselorId; // 可为null(AI咨询) @Enumerated(EnumType.STRING) private SessionState state; private String currentQuestion; private String lastAnswer; @CreationTimestamp private LocalDateTime createTime; @UpdateTimestamp private LocalDateTime updateTime; }会话服务实现:
java
// SessionService.java @Service @RequiredArgsConstructor public class SessionService { private final SessionRepository sessionRepo; private final QuestionAnswerService qaService; public ConsultationSession startSession(Long userId) { ConsultationSession session = new ConsultationSession(); session.setUserId(userId); session.setState(SessionState.WAITING); return sessionRepo.save(session); } public String processMessage(Long sessionId, String message) { ConsultationSession session = sessionRepo.findById(sessionId) .orElseThrow(() -> new RuntimeException("Session not found")); session.setCurrentQuestion(message); session.setState(SessionState.PROCESSING); sessionRepo.save(session); // 获取回答(AI或人工) String answer = qaService.getAnswer(message, session.getUserId()); session.setLastAnswer(answer); session.setState(answer.contains("预约咨询") ? SessionState.CONSULTING : SessionState.WAITING); sessionRepo.save(session); return answer; } }三、数据库设计
1. 核心表结构
sql
-- 用户表 CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(100) NOT NULL, `role` enum('USER','COUNSELOR','ADMIN') NOT NULL, `phone` varchar(20), `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) ); -- 问答知识库 CREATE TABLE `question` ( `id` bigint NOT NULL AUTO_INCREMENT, `question` varchar(500) NOT NULL, `answer` text NOT NULL, `category` varchar(50), `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), FULLTEXT KEY `ft_idx_question` (`question`) ); -- 咨询会话 CREATE TABLE `consultation_session` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `counselor_id` bigint, `state` enum('WAITING','PROCESSING','CONSULTING','CLOSED') NOT NULL, `current_question` varchar(500), `last_answer` text, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user` (`user_id`), KEY `idx_state` (`state`) );四、安全优化方案
1. 数据加密
java
// CryptoUtil.java public class CryptoUtil { private static final String AES_KEY = "your-16-byte-key"; // 16/24/32字节 public static String encrypt(String data) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes(), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]); // 固定IV(生产环境应使用随机IV) cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encrypted = cipher.doFinal(data.getBytes()); return Base64.getEncoder().encodeToString(encrypted); } public static String decrypt(String encrypted) throws Exception { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes(), "AES"); IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); byte[] decoded = Base64.getDecoder().decode(encrypted); byte[] decrypted = cipher.doFinal(decoded); return new String(decrypted); } }2. 敏感字段加密存储
java
// User.java @Data @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; @Convert(converter = CryptoConverter.class) private String password; // 存储加密后的密码 @Convert(converter = CryptoConverter.class) private String phone; // 手机号加密 // ...其他字段 } // CryptoConverter.java @Converter public class CryptoConverter implements AttributeConverter<String, String> { @Override public String convertToDatabaseColumn(String attribute) { try { return CryptoUtil.encrypt(attribute); } catch (Exception e) { throw new RuntimeException("Encryption failed", e); } } @Override public String convertToEntityAttribute(String dbData) { try { return CryptoUtil.decrypt(dbData); } catch (Exception e) { throw new RuntimeException("Decryption failed", e); } } }五、部署方案
1. Docker化部署
Dockerfile示例:
dockerfile
FROM openjdk:8-jdk-alpine VOLUME /tmp ARG JAR_FILE=target/mental-health-0.0.1-SNAPSHOT.jar COPY ${JAR_FILE} app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]docker-compose.yml:
yaml
version: '3' services: app: build: . ports: - "8080:8080" environment: - SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/mental_health - SPRING_REDIS_HOST=redis depends_on: - db - redis db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: mental_health volumes: - ./db-data:/var/lib/mysql redis: image: redis:6-alpine ports: - "6379:6379"六、扩展建议
- 性能优化:
- 对问答表添加
FULLTEXT索引提升搜索性能 - 使用Redis缓存热门问答(ZSET按热度排序)
- 对问答表添加
- 功能增强:
- 添加咨询师排班系统
- 实现WebSocket实时咨询
- 集成短信通知服务
- 监控告警:
- 添加Micrometer+Prometheus监控
- 设置会话超时自动关闭机制
该实现方案已包含核心业务逻辑,可根据实际需求调整NLP服务实现方式(本地化或云端API)和数据库优化策略。建议使用Postman等工具先测试API接口,再逐步集成前端。