# in-ai-interview **Repository Path**: hanser-developer/in-ai-interview ## Basic Information - **Project Name**: in-ai-interview - **Description**: in-ai-interview:专为AI面试准备的开源项目,提供算法题解、技术面试指南及实战模拟,助你轻松应对AI领域面试挑战。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 2 - **Created**: 2025-11-11 - **Last Updated**: 2025-12-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # InAI Interview - AI 全流程模拟面试后端 一个基于 **Spring Boot 3.5** + **Spring AI** + **PostgreSQL/pgvector** + **Redis** 构建的完整 AI 面试系统。支持简历智能解析、动态面试计划生成、实时语音交互、智能报告生成与个性化题目推荐,为求职者提供全链路模拟面试体验。 ## 🎯 功能亮点 ### 身份认证与用户管理 - **多渠道注册/登录**:邮箱验证码登录、邮箱密码登录、密码重置 - **会话管理**:基于 Spring Session + Redis 的分布式会话维持 - **用户信息管理**:基本资料更新、头像上传(COS)、个人简历绑定 ### 简历解析与智能分析 - **多格式支持**:PDF、Word、TXT 格式一键上传解析 - **智能提取**:调用 DashScope 对简历做结构化提取 - **能力分析**:自动识别技能、经验、教育背景等核心信息 - **历史管理**:支持切换/删除历史简历 ### 面试模板与计划定制 - **分类管理**:按行业、岗位、难度分类管理面试模板 - **智能匹配**:根据用户简历自动推荐合适的面试模板 - **动态计划**:结合简历背景生成定制化问题列表与难度梯度 - **灵活配置**:管理员可创建/编辑/删除面试模板 ### 核心面试流程 - **开场白生成**:AI 根据岗位/候选人背景生成个性化面试开场 - **多轮对话**:DashScope Chat 实现智能问答与动态追问 - **智能推进**:AI 根据候选人回答自动判断(追问/换题/结束) - **会话管理**:完整的面试生命周期追踪(开始/进行中/已结束) ### 实时语音交互 - **TTS 语音推流**: - 支持 DashScope `qwen-tts-realtime` 实时语音合成 - 可切换豆包 TTS(支持更多音色和语言) - 通过 STOMP WebSocket `/topic/interview/audio/{sessionId}` 推送 - 音频格式:PCM 24kHz 单声道 16bit(可配置) - **ASR 语音识别**: - 集成火山引擎(Volcengine)ASR 服务 - WebSocket 路由 `/ws/asr` 实时语音转写 - 支持标点符号、数字展开、口语化等功能 - 实时返回字幕与最终识别结果 ### 面试报告生成 - **异步处理**:基于虚拟线程池的高效异步生成 - **实时进度推送**:通过 WebSocket `/topic/interview/report/{sessionId}` 推送状态 - `QUEUED`:等待处理 - `PROCESSING`:处理中 - `SUCCESS`:生成成功 - `FAILED`:生成失败 - **详细报告**:涵盖评分、优缺点分析、知识点覆盖率等 - **数据可视化**:支持图表展示(前端实现) ### 题库与向量检索 - **分类管理**:按行业、岗位、难度等多维度分类 - **题目 CRUD**:创建、编辑、删除、查询题目 - **批量操作**:支持 CSV/Excel 批量导入题目 - **收藏功能**:用户可收藏常用题目 - **向量存储**:基于 pgvector 存储题目向量 - **智能推荐**:根据面试报告薄弱点进行向量相似度检索,推荐练习题 ### 文件与媒体存储 - **腾讯 COS 集成**:用户头像、简历、报告等文件存储 - **邮件通知**:SMTP 邮件发送(报告生成通知等) - **文件转换**:音频格式转换(WAV ↔ PCM 等) ## 🛠️ 技术栈详解 ### 核心框架 - **Java 21**:最新 LTS 版本,支持虚拟线程(Virtual Threads)异步处理 - **Spring Boot 3.5**:轻量级 REST 应用框架,自动配置与快速开发 - **Spring AI 1.1.0**:AI 应用集成框架,统一 AI 模型接口 - **Maven**:项目依赖管理与构建工具 ### 数据持久化 - **PostgreSQL**:主要关系型数据库 - **pgvector**:PostgreSQL 向量存储扩展,用于 Embedding 向量检索 - **MyBatis-Plus 3.5.14**:简化数据库操作的 ORM 框架 - 自动代码生成 - 动态 SQL 支持 - 插件机制 ### 缓存与会话 - **Redis**:分布式缓存与会话存储 - **Spring Session + Redis**:分布式会话管理 - **Spring Data Redis**:Redis 操作模板 ### AI 与大模型 - **DashScope(阿里)**: - **Chat API**:多轮对话(`qwen3-max` 等模型) - **Embedding API**:文本向量化(`text-embedding-v4` 1024 维) - **TTS API**:实时语音合成(`qwen-tts-realtime`) - **Spring AI DashScope Starter**:官方集成库 - **豆包 TTS**(字节):可选 TTS 方案,支持更多音色 - **火山引擎 ASR**:实时语音识别,支持多种识别功能 ### 实时通信 - **Spring WebSocket**:WebSocket 支持 - **STOMP 协议**:文本消息推送协议 - `/ws`:STOMP 端点(支持 SockJS 降级) - `/topic/interview/audio/{sessionId}`:AI 语音推流 - `/topic/interview/report/{sessionId}`:报告进度推送 - **原生 WebSocket**:`/ws/asr` 用于火山 ASR 桥接 ### 文件处理 - **Spring AI Document Readers**: - `spring-ai-pdf-document-reader`:PDF 解析 - `spring-ai-tika-document-reader`:Word/TXT 解析 - `spring-ai-markdown-document-reader`:Markdown 解析 - **Apache Tika**:文档格式转换 - **Flexmark**:Markdown 处理与转换 ### 云存储与通信 - **腾讯 COS**:对象存储服务 - 文件上传(头像、简历、报告) - 文件下载与分享 - **Spring Mail**:邮件发送框架 - **OkHttp 4.12**:HTTP 客户端 - 火山 ASR WebSocket 连接 - 日志拦截 ### 工具库 - **Lombok**:注解处理器,简化 POJO 代码 - **Hutool 5.8.41**:Java 工具库集合 - **Guava 33.5**:Google 核心库 - **Apache Commons Lang3 3.19**:常用字符串、日期处理工具 - **Jackson**:JSON 序列化/反序列化 - 自定义 Jackson 配置(YearMonth 灵活解析) - JSR310 时间类型支持 ### AOP 与拦截 - **Spring AOP**:切面编程(日志、异常处理等) - **自定义 Advisor**:全局异常处理 - **WebSocket 拦截器**:ASR 握手日志 ### 数据验证 - **Spring Validation (JSR-380)**:声明式数据验证 - **自定义验证注解**:特定业务规则验证 ## 📁 项目结构详解 ``` InAI Interview ├── src/main/java/com/hanserdev/interview/ │ ├── common/ # 通用代码 │ │ ├── advisor/ # 全局异常处理 Advisor │ │ ├── aop/ # AOP 切面(日志、性能监控等) │ │ └── response/ # 统一响应格式 │ │ │ ├── config/ # 配置类 │ │ ├── jackson/ # Jackson 自定义配置 │ │ ├── AsrClientConfig.java # 火山 ASR 客户端配置 │ │ ├── ChatClientConfiguration.java # Chat 客户端配置 │ │ ├── CorsConfig.java # CORS 跨域配置 │ │ ├── CosConfig.java # 腾讯 COS 配置 │ │ ├── DashScopeDocumentConfig.java # DashScope 文档配置 │ │ ├── InterviewAsrProperties.java # ASR 配置属性 │ │ ├── InterviewAsrWebSocketConfig.java # ASR WebSocket 配置 │ │ ├── InterviewAudioProperties.java # 音频通用属性 │ │ ├── InterviewAudioPropertiesDouBao.java # 豆包 TTS 属性 │ │ ├── MybatisPlusConfig.java # MyBatis-Plus 配置 │ │ ├── MybatisTypeHandlerRegistrar.java # 类型处理器注册 │ │ ├── PgVectorConfig.java # PgVector 向量库配置 │ │ ├── RedisSessionConfig.java # Redis Session 配置 │ │ ├── RedisTemplateConfig.java # Redis 模板配置 │ │ ├── VirtualThreadPoolConfig.java # 虚拟线程池配置 │ │ └── WebSocketConfig.java # WebSocket/STOMP 配置 │ │ │ ├── constants/ # 常量定义 │ │ ├── AIPromptConstants.java # AI 提示词常量 │ │ ├── DateConstants.java # 日期相关常量 │ │ ├── RedisConstants.java # Redis Key 前缀 │ │ └── SessionConstants.java # Session 相关常量 │ │ │ ├── controller/ # 控制层 │ │ ├── AuthController.java # 认证控制器 │ │ ├── CategoryController.java # 分类管理控制器 │ │ ├── FavoriteController.java # 收藏管理控制器 │ │ ├── InterviewController.java # 面试会话控制器 │ │ ├── InterviewTemplateAdminController.java # 模板管理控制器 │ │ ├── QuestionBankController.java # 题库管理控制器 │ │ ├── QuestionController.java # 题目管理控制器 │ │ ├── QuestionVectorController.java # 向量索引管理控制器 │ │ └── UserController.java # 用户管理控制器 │ │ │ ├── domain/ # 数据层 │ │ ├── dataobject/ # 数据对象(PO) │ │ ├── mapper/ # MyBatis-Plus Mapper │ │ └── typehandler/ # 自定义类型处理器 │ │ │ ├── enums/ # 枚举类型 │ │ ├── ConversationRoleEnum.java # 对话角色(User/AI) │ │ ├── GenderTypeEnum.java # 性别类型 │ │ ├── QuestionDifficultyEnum.java # 题目难度 │ │ ├── QuestionSourceEnum.java # 题目来源 │ │ ├── ReportGenerationStatus.java # 报告生成状态 │ │ ├── ResponseCodeEnum.java # 响应码 │ │ └── UserRoleEnum.java # 用户角色 │ │ │ ├── event/ # 事件类 │ │ └── InterviewReportGeneratedEvent.java # 报告生成事件 │ │ │ ├── exception/ # 异常处理 │ │ ├── ApiException.java # 自定义 API 异常 │ │ ├── BaseExceptionInterface.java # 异常接口 │ │ └── GlobalExceptionHandler.java # 全局异常处理 │ │ │ ├── messaging/ # 消息推送 │ │ ├── InterviewAudioWebSocketPublisher.java # 语音推送 │ │ └── InterviewReportWebSocketNotifier.java # 报告推送 │ │ │ ├── model/ # 数据传输对象 │ │ ├── convert/ # 对象转换器 │ │ ├── dto/ # DTO(数据传输对象) │ │ └── vo/ # VO(视图对象) │ │ │ ├── protocol/ # 自定义协议 │ │ ├── CompressionBits.java # 压缩位标志 │ │ ├── EventType.java # 事件类型 │ │ ├── HeaderSizeBits.java # 头部大小位 │ │ ├── Message.java # 消息类 │ │ ├── MsgType.java # 消息类型 │ │ ├── MsgTypeFlagBits.java # 消息类型标志位 │ │ ├── SerializationBits.java # 序列化标志位 │ │ ├── SpeechWebSocketClient.java # 语音 WebSocket 客户端 │ │ └── VersionBits.java # 版本位 │ │ │ ├── service/ # 业务逻辑层 │ │ ├── impl/ # 接口实现 │ │ ├── support/ # 支持类 │ │ ├── AuthService.java # 认证服务 │ │ ├── CategoryService.java # 分类服务 │ │ ├── FavoriteService.java # 收藏服务 │ │ ├── InterviewAIService.java # AI 对话服务 │ │ ├── InterviewAudioService.java # 音频处理服务 │ │ ├── InterviewReportService.java # 报告服务 │ │ ├── InterviewSessionService.java # 会话管理服务 │ │ ├── InterviewTemplateManageService.java # 模板管理服务 │ │ ├── QuestionBankService.java # 题库服务 │ │ ├── QuestionRecommendationService.java # 推荐服务 │ │ ├── QuestionService.java # 题目服务 │ │ ├── QuestionVectorService.java # 向量索引服务 │ │ ├── ResumeAnalysisService.java # 简历分析服务 │ │ ├── ResumeParseService.java # 简历解析服务 │ │ ├── ResumeUploadService.java # 简历上传服务 │ │ └── UserService.java # 用户服务 │ │ │ ├── utils/ # 工具类 │ │ ├── helper/ # 工具类辅助 │ │ ├── AudioConverter.java # 音频转换工具 │ │ ├── DashScopeDocumentExtractor.java # DashScope 文档提取 │ │ ├── DateUtils.java # 日期工具 │ │ ├── DocumentParserUtils.java # 文档解析工具 │ │ ├── FileUtils.java # 文件工具 │ │ ├── JsonUtils.java # JSON 工具 │ │ ├── MarkdownUtils.java # Markdown 工具 │ │ ├── ParamUtils.java # 参数工具 │ │ ├── PasswordUtils.java # 密码工具 │ │ ├── UserSessionUtils.java # 会话工具 │ │ └── VectorBatchUtils.java # 向量批处理工具 │ │ │ ├── websocket/ # WebSocket 处理 │ │ ├── AsrHandshakeLoggingInterceptor.java # ASR 握手拦截 │ │ └── InterviewAsrWebSocketHandler.java # ASR 处理器 │ │ │ └── InAiInterviewApplication.java # Spring Boot 启动类 │ ├── src/main/resources/ │ ├── config/ │ │ ├── application-prod.yml # 生产配置(环境变量化) │ │ └── banner.txt # 启动横幅 │ └── templates/ # 邮件模板等 │ ├── src/test/ # 测试代码 │ ├── sql/ # 数据库脚本 │ ├── new_table.sql # 主表创建脚本 │ ├── new_ai.sql # AI 相关表脚本 │ └── question_favorites.sql # 收藏表脚本 │ ├── pom.xml # Maven 依赖配置 ├── Dockerfile # Docker 构建文件 ├── docker-compose.yml # Docker Compose 编排 └── README.md # 本文档 ``` ## 🔄 核心业务流程 ### 1️⃣ 用户注册与认证流程 ``` 用户 → 请求邮箱验证码 → 后端发送邮件 → 用户输入验证码 → 后端验证 → 创建用户 → 保存密码(加密)→ 返回成功 ``` **相关接口**: - `POST /auth/email/register/code` - 发送注册验证码 - `POST /auth/email/register` - 邮箱验证码注册 - `POST /auth/email/login/code/send` - 发送登录验证码 - `POST /auth/email/login/code` - 验证码登录 - `POST /auth/email/login/password` - 密码登录 ### 2️⃣ 简历上传与智能分析流程 ``` 用户上传简历 (PDF/Word/TXT) ↓ 后端接收文件 → 使用 Spring AI 解析文档 ↓ 提取文本内容 → 调用 DashScope Chat API ↓ AI 结构化提取信息(技能、经验、教育等) ↓ 保存到数据库 → 返回解析结果 ``` **相关接口**: - `POST /users/{id}/resume/upload` - 上传简历 - `GET /users/{id}/resume` - 获取简历信息 - `PATCH /users/{id}/resume` - 切换简历 ### 3️⃣ 面试计划生成流程 ``` 用户选择面试模板 ↓ 后端获取用户简历 → 分析用户背景 ↓ 根据模板规则生成定制化问题列表 ↓ 设置难度梯度 → 创建面试会话 ↓ 返回会话 ID 与初始问题列表 ``` **相关接口**: - `GET /interview/templates` - 获取面试模板列表 - `POST /interview/sessions` - 创建面试会话 - `GET /interview/sessions/{id}` - 获取会话信息 ### 4️⃣ 多轮面试对话流程(核心) ``` ┌─────────────────────────────────────────────────────────────┐ │ 获取开场白 │ │ GET /interview/sessions/{sessionId}/opening │ │ ↓ │ │ AI 生成开场白 → TTS 推流 → 用户听语音 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 多轮问答循环 │ │ │ │ 1. 用户点击"开始回答" │ │ │ │ 2. 前端通过 WebSocket /ws/asr 发送 PCM 音频 │ │ - 每 ~200ms 发送一帧 16k/16bit/mono PCM 数据 │ │ - 火山 ASR 返回实时字幕 & 最终识别结果 │ │ │ │ 3. 用户点击"提交答案" │ │ POST /interview/sessions/{sessionId}/answer │ │ { "answer": "用户回答文本" } │ │ │ │ 4. 后端处理流程 │ │ a) 保存答案到数据库 │ │ b) 调用 DashScope Chat 评估答案 │ │ c) AI 返回标记: │ │ - FOLLOW_UP: 追问 │ │ - NEXT_QUESTION: 下一题 │ │ - END_INTERVIEW: 结束面试 │ │ d) 生成对应的 AI 回复 │ │ e) TTS 合成语音 → STOMP 推送到 /topic/interview/audio │ │ │ │ 5. 前端接收推流 │ │ - 订阅 /topic/interview/audio/{sessionId} │ │ - 接收事件: START/CHUNK/COMPLETE/ERROR │ │ - 播放 PCM_24000HZ_MONO_16BIT 音频 │ │ │ │ 6. 判断是否继续 │ │ - FOLLOW_UP/NEXT_QUESTION → 显示下一个问题,回到步骤 1 │ │ - END_INTERVIEW → 进入报告生成流程 │ └─────────────────────────────────────────────────────────────┘ ``` **相关接口**: - `GET /interview/sessions/{id}/opening` - 获取开场白 - `POST /interview/sessions/{id}/answer` - 提交答案 - `GET /interview/sessions/{id}/messages` - 获取会话消息历史 - `POST /interview/sessions/{id}/end` - 结束会话 **WebSocket 相关**: - **STOMP 推流**: - 端点:`/ws` (支持 SockJS) - 订阅:`/topic/interview/audio/{sessionId}` - 事件格式:`{ type: 'START'|'CHUNK'|'COMPLETE'|'ERROR', data: '...' }` - **ASR 原生 WS**: - 端点:`/ws/asr` - 发送:`{ type: 'init', sessionId: '...' }` → PCM 数据 → `{ type: 'END' }` - 接收:`{ type: 'transcript', text: '...', isFinal: true|false }` ### 5️⃣ 面试报告生成流程(异步) ``` 用户点击"生成报告" ↓ POST /interview/sessions/{sessionId}/report/generate ↓ 后端入队任务到虚拟线程池(TaskExecutor) ↓ WebSocket 推送 QUEUED 状态 ↓ 异步线程执行: a) WebSocket 推送 PROCESSING 状态 b) 分析所有答案 → 调用 DashScope 评估 c) 生成报告内容(评分、优缺点、建议等) d) 保存报告到数据库 e) WebSocket 推送 SUCCESS 状态 ↓ 用户通过 GET /interview/sessions/{sessionId}/report 拉取报告详情 ``` **相关接口**: - `POST /interview/sessions/{id}/report/generate` - 触发报告生成 - `GET /interview/sessions/{id}/report` - 获取报告详情 **WebSocket 相关**: - **STOMP 推送**: - 端点:`/ws` - 推送主题:`/topic/interview/report/{sessionId}` - 状态消息:`{ status: 'QUEUED'|'PROCESSING'|'SUCCESS'|'FAILED', message: '...' }` ### 6️⃣ 题目推荐流程(向量检索) ``` 报告生成完成 ↓ GET /interview/sessions/{sessionId}/recommendations ↓ 后端获取报告中的薄弱点 ↓ 调用 DashScope Embedding API: - 对薄弱点生成向量(1024 维) ↓ 在 pgvector 中执行相似度检索 ↓ 返回相似度最高的练习题 ↓ 前端展示推荐题目,用户可选择练习 ``` **相关接口**: - `GET /interview/sessions/{id}/recommendations` - 获取推荐题目 - `POST /admin/vector/{questionId}/index` - 手动索引题目 - `POST /admin/vector/banks/{bankId}/index` - 索引整个题库 - `POST /admin/vector/index/rebuild` - 重建所有索引 - `DELETE /admin/vector/{questionId}/index` - 删除题目索引 ## 🌐 REST API 完整列表 ### 认证接口(Authentication) ``` POST /auth/email/register/code # 发送注册邮件验证码 POST /auth/email/register # 邮箱验证码注册 POST /auth/email/login/code/send # 发送登录邮件验证码 POST /auth/email/login/code # 验证码登录 POST /auth/email/login/password # 密码登录 POST /auth/logout # 登出 POST /auth/email/password/reset # 密码重置 ``` ### 用户管理(User Management) ``` GET /users/{id} # 获取用户信息 POST /users # 创建用户 PUT /users/{id} # 更新用户信息 DELETE /users/{id} # 删除用户 GET /users/{id}/resume # 获取用户简历 POST /users/{id}/resume/upload # 上传简历 PATCH /users/{id}/resume # 切换简历 DELETE /users/{id}/resume/{resumeId} # 删除简历 ``` ### 分类管理(Category Management) ``` GET /categories # 获取分类列表 GET /categories/{id} # 获取分类详情 POST /categories # 创建分类 PUT /categories/{id} # 更新分类 DELETE /categories/{id} # 删除分类 ``` ### 题库管理(Question Bank Management) ``` GET /question-banks # 获取题库列表(分页) GET /question-banks/{id} # 获取题库详情 POST /question-banks # 创建题库 PUT /question-banks/{id} # 更新题库 DELETE /question-banks/{id} # 删除题库 ``` ### 题目管理(Question Management) ``` GET /questions # 获取题目列表(分页、筛选) GET /questions/{id} # 获取题目详情 POST /questions # 创建题目 PUT /questions/{id} # 更新题目 DELETE /questions/{id} # 删除题目 POST /questions/batch # 批量导入题目(CSV/Excel) GET /questions/bank/{bankId} # 获取题库下的所有题目 ``` ### 收藏管理(Favorite Management) ``` GET /favorites # 获取收藏列表 GET /favorites/{id} # 获取收藏详情 POST /favorites # 收藏题目 DELETE /favorites/{id} # 取消收藏 DELETE /favorites/question/{questionId} # 取消收藏指定题目 ``` ### 面试模板(Interview Template) ``` GET /interview/templates # 获取面试模板列表 GET /interview/templates/{id} # 获取模板详情 # 管理员接口 POST /admin/interview-templates # 创建模板 PUT /admin/interview-templates/{id} # 更新模板 DELETE /admin/interview-templates/{id} # 删除模板 GET /admin/interview-templates # 分页获取模板列表 ``` ### 面试会话(Interview Session) ``` GET /interview/sessions # 获取用户面试历史 GET /interview/sessions/{id} # 获取会话详情 GET /interview/sessions/{id}/messages # 获取会话消息历史 POST /interview/sessions # 创建新面试会话 GET /interview/sessions/{id}/opening # 获取开场白 POST /interview/sessions/{id}/answer # 提交答案 POST /interview/sessions/{id}/end # 结束会话 ``` ### 面试报告(Interview Report) ``` GET /interview/sessions/{id}/report # 获取报告详情 POST /interview/sessions/{id}/report/generate # 生成报告 DELETE /interview/sessions/{id}/report # 删除报告 ``` ### 题目推荐(Question Recommendation) ``` GET /interview/sessions/{id}/recommendations # 获取推荐题目列表 ``` ### 向量索引管理(Vector Index Administration) ``` POST /admin/vector/{questionId}/index # 为题目创建向量索引 POST /admin/vector/banks/{bankId}/index # 为整个题库创建索引 POST /admin/vector/index/rebuild # 重建所有向量索引 DELETE /admin/vector/{questionId}/index # 删除题目向量索引 ``` ## 📡 WebSocket 与流式通信详解 ### STOMP WebSocket 端点 #### 1. 连接信息 - **URL**: `ws://localhost:8080/ws` 或 `https://your-server/ws` - **备选**: 支持 SockJS (`/ws/sockjs`) - **协议**: STOMP #### 2. 面试官语音推流:`/topic/interview/audio/{sessionId}` **事件类型与格式**: ```javascript // 事件 1: 开始推流 { type: 'START', sessionId: 'xxx', format: 'PCM_24000HZ_MONO_16BIT', timestamp: 1234567890 } // 事件 2: 音频数据块(CHUNK) { type: 'CHUNK', data: 'base64 编码的 PCM 音频数据', sequence: 1, timestamp: 1234567891 } // 事件 3: 推流完成 { type: 'COMPLETE', sessionId: 'xxx', totalChunks: 100, timestamp: 1234567990 } // 事件 4: 错误信息 { type: 'ERROR', error: '错误描述', sessionId: 'xxx', timestamp: 1234567990 } ``` **前端订阅示例**: ```javascript import SockJS from 'sockjs-client'; import Stomp from 'stompjs'; const sock = new SockJS('/ws'); const stompClient = Stomp.over(sock); stompClient.connect({}, (frame) => { stompClient.subscribe(`/topic/interview/audio/${sessionId}`, (message) => { const event = JSON.parse(message.body); if (event.type === 'CHUNK') { // 解码 base64 PCM 数据并播放 const pcmData = atob(event.data); // ... 播放逻辑 } }); }); ``` #### 3. 报告生成进度:`/topic/interview/report/{sessionId}` **状态推送格式**: ```javascript // 入队 { status: 'QUEUED', sessionId: 'xxx', message: '报告生成已入队,排队等待处理', queuePosition: 1, timestamp: 1234567890 } // 处理中 { status: 'PROCESSING', sessionId: 'xxx', message: '正在分析答案内容...', progress: 30, // 进度百分比(可选) timestamp: 1234567891 } // 成功 { status: 'SUCCESS', sessionId: 'xxx', message: '报告生成完成', reportId: 'report-123', timestamp: 1234567990 } // 失败 { status: 'FAILED', sessionId: 'xxx', message: '报告生成失败: 后端服务异常', errorCode: 'INTERNAL_ERROR', timestamp: 1234567990 } ``` **前端订阅示例**: ```javascript stompClient.subscribe(`/topic/interview/report/${sessionId}`, (message) => { const update = JSON.parse(message.body); switch (update.status) { case 'QUEUED': console.log('等待处理中...'); break; case 'PROCESSING': console.log(`进度: ${update.progress}%`); break; case 'SUCCESS': // 报告生成完成,调用 GET /interview/sessions/{id}/report 获取详情 break; case 'FAILED': console.error('生成失败:', update.message); break; } }); ``` ### 原生 WebSocket 端点(ASR) #### 端点信息 - **URL**: `ws://localhost:8080/ws/asr` - **协议**: 自定义二进制协议(火山引擎 ASR) #### 通信流程 ``` 前端 后端 火山 ASR │ │ │ ├─ 发送 init 消息 ─────────────>│ │ │ { type:'init', sessionId } │ │ │ ├─ WebSocket 连接 ───────>│ │ │ 建立 ASR 连接 │ │ │<─ 连接成功 ───────────────│ │ │ │ │<─ 推送 backend_connected ────│ │ │ { type: 'backend_connected' } │ │ │ │ ├─ 发送 PCM 数据 ──────────────>│ │ │ (每 ~200ms 一帧) ├─ 转发到 ASR ─────────────>│ │ 16k/16bit/mono │ │ │ 总共 N 帧 │<─ 返回字幕 ──────────────┤ │ │ │ │<─ 推送 transcript ────────────│ │ │ { text: '...', isFinal: false } │ │ │ │ │ ... (继续接收 ASR 字幕) │ ... (继续处理) │ │ │ │ ├─ 发送 END 标记 ───────────────>│ │ │ { type: 'END' } ├─ 等待最终结果 ────────────>│ │ │<─ 最终识别结果 ───────────│ │ │ │ │<─ 推送最终 transcript ────────│ │ │ { text: '...', isFinal: true } │ │ │ │ │ 连接关闭 ├─ 关闭 ASR 连接 ─────────>│ │ │ │ ``` #### 消息格式 **客户端发送**: ```javascript // 1. 初始化(必须) { type: 'init', sessionId: 'session-123' } // 2. PCM 音频数据(二进制) // 直接发送原始 PCM 字节数据 // 格式:16kHz, 16-bit, mono // 建议每 ~200ms 发送一帧(3200 字节) // 3. 结束标记 { type: 'END' } ``` **服务器推送**: ```javascript // 1. 连接成功 { type: 'backend_connected', message: '后端已连接到 ASR 服务' } // 2. 实时字幕 { type: 'transcript', text: '我想应聘...', // 当前识别文本 isFinal: false, // false: 临时结果, true: 最终结果 confidence: 0.95, // 置信度(可选) startTime: 0, // 音频开始位置(毫秒) endTime: 1500 // 音频结束位置(毫秒) } // 3. 错误信息 { type: 'error', error: '与 ASR 服务连接失败', code: 'ASR_CONNECTION_ERROR' } ``` **前端示例代码**: ```javascript const ws = new WebSocket('ws://localhost:8080/ws/asr'); const audioContext = new AudioContext(); const audioProcessor = audioContext.createScriptProcessor(4096, 1, 1); ws.onopen = () => { ws.send(JSON.stringify({ type: 'init', sessionId: 'xxx' })); // 开始收集音频 navigator.mediaDevices.getUserMedia({ audio: true }) .then(stream => { const source = audioContext.createMediaStreamAudioSource(stream); source.connect(audioProcessor); audioProcessor.connect(audioContext.destination); audioProcessor.onaudioprocess = (e) => { const pcmData = e.inputBuffer.getChannelData(0); // 转换为 16-bit PCM 并发送 ws.send(pcmData.buffer); }; }); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === 'transcript') { console.log(`[${message.isFinal ? '最终' : '临时'}] ${message.text}`); } }; function stopRecording() { ws.send(JSON.stringify({ type: 'END' })); ws.close(); } ``` ## 🚀 快速启动指南 ### 环境需求 - **JDK**: Java 21+ (支持虚拟线程) - **Maven**: 3.6+ - **PostgreSQL**: 12+ (需启用 `uuid-ossp` 与 `pgvector` 扩展) - **Redis**: 6.0+ - **运行内存**: 建议 2GB+ ### 步骤 1: 准备数据库 #### PostgreSQL 初始化 ```sql -- 1. 连接 PostgreSQL psql -U postgres -d postgres -- 2. 创建数据库 CREATE DATABASE interview; -- 3. 连接到新数据库 \c interview -- 4. 启用扩展 CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "vector"; -- 5. 执行建表脚本 \i /path/to/sql/new_table.sql \i /path/to/sql/new_ai.sql \i /path/to/sql/question_favorites.sql -- 如需要 ``` ### 步骤 2: 配置环境变量 创建 `.env` 文件在项目根目录: ```bash # Spring 配置 SPRING_PROFILES_ACTIVE=prod SERVER_PORT=8080 # 数据库配置 SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/interview?serverTimezone=Asia/Shanghai&reWriteBatchedInserts=true SPRING_DATASOURCE_USERNAME=postgres SPRING_DATASOURCE_PASSWORD=your_password SPRING_DATASOURCE_MAX_POOL_SIZE=20 SPRING_DATASOURCE_MIN_IDLE=5 # Redis 配置 REDIS_HOST=localhost REDIS_PORT=6379 REDIS_PASSWORD=your_redis_password REDIS_DATABASE=0 SPRING_SESSION_NAMESPACE=inai:session # 邮件配置 MAIL_HOST=smtp.qq.com MAIL_PORT=465 MAIL_USERNAME=your_email@qq.com MAIL_PASSWORD=your_smtp_token # 注意:QQ 邮箱需要授权码 # DashScope 配置(阿里) DASHSCOPE_API_KEY=sk-your-api-key DASHSCOPE_CHAT_MODEL=qwen3-max DASHSCOPE_EMBEDDING_MODEL=text-embedding-v4 DASHSCOPE_EMBEDDING_DIMENSIONS=1024 # pgvector 配置 PGVECTOR_INIT_SCHEMA=true PGVECTOR_SCHEMA=public PGVECTOR_TABLE=interview_vectorstore PGVECTOR_DIMENSIONS=1024 # TTS 配置(DashScope) TTS_ENABLED=true TTS_VOICE=Chelsie TTS_LANGUAGE=Chinese TTS_MODEL=qwen-tts-realtime TTS_RESPONSE_FORMAT=PCM_24000HZ_MONO_16BIT TTS_WS_URL=wss://dashscope.aliyuncs.com/api-ws/v1/realtime TTS_MODE=server_commit TTS_AWAIT_TIMEOUT_MS=45000 # ASR 配置(火山引擎) ASR_ENABLED=true ASR_APP_ID=your_app_id ASR_TOKEN=your_token ASR_RESOURCE_ID=volc.bigasr.sauc.duration ASR_MODEL_NAME=bigmodel ASR_SAMPLE_RATE=16000 ASR_BITS=16 ASR_CHANNEL=1 ASR_ENABLE_ITN=true ASR_ENABLE_PUNC=true ASR_ENABLE_DDC=true ASR_SHOW_UTTERANCES=true ASR_ENABLE_NONSTREAM=true ASR_SEGMENT_DURATION_MS=200 ASR_REQUEST_TIMEOUT_MS=60000 # 豆包 TTS 配置(字节)- 可选 DOUBAO_ENABLED=false DOUBAO_APP_ID= DOUBAO_ACCESS_TOKEN= DOUBAO_RESOURCE_ID= DOUBAO_VOICE=ICL_zh_female_zhixingwenwan_tob DOUBAO_ENCODING=pcm DOUBAO_WS_ENDPOINT=wss://openspeech.bytedance.com/api/v3/tts/bidirection DOUBAO_AWAIT_TIMEOUT_MS=45000 # 腾讯 COS 配置(文件存储) COS_ENDPOINT=your_endpoint COS_BUCKET=your_bucket COS_APP_ID=your_app_id COS_REGION=ap-shanghai COS_SECRET_ID=your_secret_id COS_SECRET_KEY=your_secret_key # Google API(可选) GOOGLE_API_KEY=your_google_api_key # 阿里云 API(可选) ALIYUN_AK_ID=your_aliyun_ak ALIYUN_AK_SECRET=your_aliyun_secret # 日志配置 LOG_LEVEL_SPRING_AI=INFO LOG_LEVEL_MAPPER=INFO ``` ### 步骤 3: 构建项目 ```bash # 使用 Maven 构建(跳过测试) mvn clean package -DskipTests # 或者使用 Maven Wrapper(如果项目包含) ./mvnw clean package -DskipTests ``` ### 步骤 4: 本地运行 ```bash # 方式 1: 直接运行 JAR java -jar target/in-ai-interview-1.0.0.jar --spring.profiles.active=prod # 方式 2: 使用 Maven 运行 mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=prod" # 方式 3: IDE 运行 # 直接在 IDE 中运行 InAiInterviewApplication.main() # 确保已配置 JVM 参数:-Dspring.profiles.active=prod ``` ### 步骤 5: Docker 部署 ```bash # 1. 构建 Docker 镜像 docker build -t in-ai-interview:latest . # 2. 使用 docker-compose 运行 docker-compose up -d # 3. 查看日志 docker-compose logs -f app # 4. 停止服务 docker-compose down ``` ### 步骤 6: 验证启动 应用启动成功后,您应该看到: ``` ████████████████████████████████████████ █ InAI Interview Backend Service █ █ v1.0.0 █ ████████████████████████████████████████ 启动完成! HTTP 服务地址: http://localhost:8080 WebSocket 端点: ws://localhost:8080/ws ASR WebSocket: ws://localhost:8080/ws/asr ``` ### 步骤 7: 测试 API ```bash # 健康检查 curl http://localhost:8080/actuator/health # 注册用户(示例) curl -X POST http://localhost:8080/auth/email/register \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "123456", "verificationCode": "123456" }' ``` ## 目录速览 - `src/main/java/com/hanserdev/interview/controller`:REST 接口(Auth/用户/简历/题库/面试/报告/向量等) - `src/main/java/com/hanserdev/interview/service`:业务服务与实现(简历解析、AI 对话、报告、音频推流、向量索引等) - `src/main/java/com/hanserdev/interview/config`:数据源、Redis/Session、PgVector、AI/TTS、WebSocket、CORS 等配置 - `src/main/java/com/hanserdev/interview/messaging`:报告/语音的 WebSocket 推送 - `src/main/java/com/hanserdev/interview/websocket`:原生 WS 处理器(火山 ASR 桥接) - `src/main/resources/config`:`application.yml`(本地示例)与 `application-prod.yml`(环境变量化) - `docs/`:流程与前端对接说明(面试流程、语音推流、报告推送、ASR 等) - `sql/`:PostgreSQL 建表与样例数据脚本(包含 pgvector 相关表) ## 核心流程 1. 用户注册/登录 → 上传简历或从历史简历读取。 2. 选择面试模板,后端解析简历并选择合适的计划模板,生成定制化问题列表。 3. 获取开场白 `/interview/sessions/{sessionId}/opening`。 4. 多轮作答 `/interview/sessions/{sessionId}/answer`,AI 根据标记决定追问/换题/结束,并实时 TTS 推送。 5. 结束会话 `/interview/sessions/{sessionId}/end`。 6. 触发报告生成 `/interview/sessions/{sessionId}/report/generate` → WebSocket 推送进度 → GET 报告。 7. 基于报告薄弱点生成题目推荐 `/interview/sessions/{sessionId}/recommendations`,依赖向量检索。 ## 主要 REST 接口(节选) - Auth:`POST /auth/email/register/code`、`/auth/email/register`、`/auth/email/login/code/send`、`/auth/email/login/code`、`/auth/email/login/password` - 用户/简历:`POST /users`、`PUT /users/{id}`、`DELETE /users/{id}`、`GET /users/{id}`;`POST /users/{id}/resume/upload`、`PATCH /users/{id}/resume` - 分类/题库/题目:`/categories` CRUD;`/question-banks` CRUD;`/questions` CRUD + `POST /questions/batch` 批量导入;`/favorites/*` 收藏 - 面试:`GET /interview/templates`、`POST /interview/sessions`、`GET /interview/sessions/{id}`、`GET /interview/sessions/{id}/messages`、`GET /interview/sessions/{id}/opening`、`POST /interview/sessions/{id}/answer`、`POST /interview/sessions/{id}/end` - 报告 & 推荐:`POST /interview/sessions/{id}/report/generate`、`GET /interview/sessions/{id}/report`、`GET /interview/sessions/{id}/recommendations` - 管理:`/admin/interview-templates` CRUD+分页;向量索引 `/admin/vector/{questionId}/index`、`/admin/vector/banks/{bankId}/index`、`/admin/vector/index/rebuild`、`DELETE /admin/vector/{questionId}/index` ## WebSocket 与流式能力 - STOMP 端点:`/ws`(SockJS 可用) - 面试官语音:`/topic/interview/audio/{sessionId}`,事件 `START` / `CHUNK` / `COMPLETE` / `ERROR`,音频默认为 `PCM_24000HZ_MONO_16BIT` - 报告生成:`/topic/interview/report/{sessionId}`,状态 `QUEUED` / `PROCESSING` / `SUCCESS` / `FAILED` - 原生 WS(火山 ASR 桥接):`/ws/asr` - 前端先发 `{"type":"init","sessionId":"<业务ID>"}`,随后每 ~200ms 发送 16k/16bit/mono PCM;发送 `END` 结束 - 返回事件:`backend_connected`、`transcript`(含 `text`/`isFinal`)、`error` - 详情参考:`docs/realtime-audio-websocket.md`、`docs/report-generation-websocket.md`、`docs/volcengine-asr-websocket.md`、`docs/AI模拟面试完整流程说明 (Spring AI + 简历版).md`。 ## 配置与环境变量 `src/main/resources/config/application-prod.yml` 通过环境变量注入,常用项: - 端口:`SERVER_PORT`(默认 8080) - 数据源:`SPRING_DATASOURCE_URL`、`SPRING_DATASOURCE_USERNAME`、`SPRING_DATASOURCE_PASSWORD`、`SPRING_DATASOURCE_MAX_POOL_SIZE`... - Redis / Session:`REDIS_HOST`、`REDIS_PORT`、`REDIS_PASSWORD`、`SPRING_SESSION_NAMESPACE` - 邮件:`MAIL_HOST`、`MAIL_USERNAME`、`MAIL_PASSWORD`、`MAIL_PORT` - DashScope:`DASHSCOPE_API_KEY`、`DASHSCOPE_CHAT_MODEL`、`DASHSCOPE_EMBEDDING_MODEL`/`DASHSCOPE_EMBEDDING_DIMENSIONS` - pgvector:`PGVECTOR_INIT_SCHEMA`、`PGVECTOR_SCHEMA`、`PGVECTOR_TABLE`、`PGVECTOR_DIMENSIONS` - TTS:`TTS_ENABLED`、`TTS_VOICE`、`TTS_MODEL`、`TTS_WS_URL`、`TTS_RESPONSE_FORMAT` - ASR(火山):`ASR_APP_ID`、`ASR_TOKEN`、`ASR_RESOURCE_ID` 等 - 豆包 TTS:`DOUBAO_ENABLED`、`DOUBAO_APP_ID`、`DOUBAO_ACCESS_TOKEN`... - 文件存储:`COS_ENDPOINT`、`COS_BUCKET`、`COS_APP_ID`、`COS_REGION`、`COS_SECRET_ID`、`COS_SECRET_KEY` - 其他:`GOOGLE_API_KEY`、`ALIYUN_AK_ID`/`ALIYUN_AK_SECRET` 示例 `.env`(请替换为自己的密钥,勿使用仓库中的示例值): ```env SPRING_PROFILES_ACTIVE=prod SPRING_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/interview SPRING_DATASOURCE_USERNAME=postgres SPRING_DATASOURCE_PASSWORD=postgres REDIS_HOST=host.docker.internal REDIS_PORT=6379 REDIS_PASSWORD=redis MAIL_HOST=smtp.qq.com MAIL_USERNAME=you@example.com MAIL_PASSWORD=your_smtp_token DASHSCOPE_API_KEY=sk-xxx DASHSCOPE_CHAT_MODEL=qwen3-max DASHSCOPE_EMBEDDING_MODEL=text-embedding-v4 COS_ENDPOINT=... COS_BUCKET=... ASR_APP_ID=... ASR_TOKEN=... DOUBAO_ENABLED=false ``` ## 快速启动 1. 准备依赖:JDK 21、Maven、PostgreSQL(启用 `uuid-ossp`、`pgvector` 扩展)、Redis。 2. 初始化数据库:执行 `sql/new_table.sql` 和 `sql/new_ai.sql`(如需收藏表再运行 `sql/question_favorites.sql`),确保表、扩展与示例数据就绪。 3. 配置环境变量或 `.env`,优先使用 `application-prod.yml` 中的变量,不要在代码中硬编码密钥。 4. 构建:`mvn clean package -DskipTests`。 5. 运行: - 本地 Jar:`java -jar target/in-ai-interview-1.0.0.jar --spring.profiles.active=prod` - 或 Docker:`docker-compose up --build -d`(容器通过 `host.docker.internal` 访问宿主机数据库/Redis) ## 开发提示 - WebSocket/STOMP 调试可直接连接 `/ws`,订阅对应 Topic;ASR 需按 16k/16bit PCM 分片发送。 - 虚拟线程池 Bean 名称为 `TaskExecutor`,所有异步方法默认挂在其上。 - 向量索引需在题目创建后手动调用 `/admin/vector/...` 或使用重建接口。 - 默认 `application.yml` 中的密钥仅示例用途,线上务必改为安全的环境变量。 ## 🛠️ 开发与调试提示 ### 本地开发环境配置 **IDE 设置**(IntelliJ IDEA 推荐) ``` 1. 设置 JDK 版本 File > Project Structure > Project > SDK > 选择 JDK 21 2. 启用 Annotation Processing File > Settings > Build, Execution, Deployment > Compiler > Annotation Processors ✓ Enable annotation processing 3. 设置 Active Profile Run > Edit Configurations > Environment variables SPRING_PROFILES_ACTIVE=prod ``` ### WebSocket 调试 #### 使用 WebSocket 客户端工具 **方案 1: 浏览器 WebSocket 客户端(推荐)** ```html
日志输出...