# i-view **Repository Path**: sunrise-jay/i-view ## Basic Information - **Project Name**: i-view - **Description**: No description available - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-23 - **Last Updated**: 2026-07-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # i-view —— AI Interview Platform 一个面向求职者的 AI 面试训练平台。项目将简历分析、文字模拟面试、实时语音面试、知识库 RAG 问答、面试日程管理和报告导出整合到同一套前后端系统中。 > 当前项目是一个可本地开发、也可通过 Docker Compose 部署的全栈应用。后端默认端口为 **4000**,前端开发服务器默认端口为 **5173**。 ## 目录 - [功能概览](#功能概览) - [技术栈](#技术栈) - [系统架构](#系统架构) - [项目结构](#项目结构) - [运行环境](#运行环境) - [配置环境变量](#配置环境变量) - [本地开发](#本地开发) - [Docker 部署](#docker-部署) - [页面与路由](#页面与路由) - [API 概览](#api-概览) - [实时与异步机制](#实时与异步机制) - [测试与构建](#测试与构建) - [常见问题](#常见问题) - [安全注意事项](#安全注意事项) - [许可证](#许可证) ## 功能概览 ### 简历管理 - 上传 PDF、DOC、DOCX、TXT 简历; - 使用 Apache Tika 提取简历文本; - 对简历进行异步 AI 分析和评分; - 查看简历历史、详情、分析状态和分析报告; - 支持重新分析、删除和 PDF 报告导出; - 支持文件哈希去重和对象存储。 ### 文字模拟面试 - 基于简历、技能方向、难度和题目数量创建面试会话; - 支持岗位 JD 解析和自定义面试分类; - 支持获取当前问题、保存草稿、提交回答和继续未完成面试; - 支持 AI 追问、提前结束、异步生成评价报告; - 支持查看面试历史、详情、报告、删除和 PDF 导出。 ### 实时语音面试 - 浏览器麦克风录音和实时音频上传; - WebSocket 双向通信; - Qwen Realtime ASR 语音识别; - LLM 实时生成面试官问题和追问; - Qwen Realtime TTS 合成面试官语音; - 实时字幕、文本、分段音频和控制消息下发; - 支持开始、暂停、恢复、结束、历史记录和异步评估报告; - 支持启动时缓存预置开场问题的 TTS 音频。 ### 知识库与 RAG 问答 - 上传和管理知识库文档; - 支持分类、搜索、统计、下载、删除和重新向量化; - 使用 PostgreSQL + pgvector 保存文档向量; - 支持普通问答和 SSE 流式问答; - 支持创建、编辑标题、置顶、关联知识库和删除 RAG 聊天会话。 ### 面试日程 - 从面试邀约文本中解析时间、地点、公司和职位信息; - 创建、编辑、删除和查询面试日程; - 更新日程状态并在日历中查看。 ### 模型与系统设置 - 管理多个 OpenAI 兼容的 LLM Provider; - 测试 Provider 连通性; - 设置默认聊天模型和默认 Embedding 模型; - 配置和测试语音 ASR/TTS; - 提供 SpringDoc OpenAPI、Actuator、Metrics 和 Prometheus 端点。 ## 技术栈 ### 后端 - Java 21; - Spring Boot 4.1.0; - Gradle Wrapper 8.14; - Spring AI 2.0.0; - Spring MVC、Spring Data JPA、Spring WebSocket、Spring Validation; - PostgreSQL 16 + pgvector; - Redis 7 + Redisson + Redis Streams; - RustFS 或其他 S3 兼容对象存储; - Apache Tika 文档解析; - MapStruct 对象映射; - iText 8 PDF 导出; - SpringDoc OpenAPI; - Micrometer Prometheus。 ### 前端 - React 18; - TypeScript; - Vite 5; - Tailwind CSS 4; - React Router; - Axios; - WebSocket、SSE; - Lucide React、Framer Motion、Recharts、React Markdown。 ### AI 服务 默认 Provider 为阿里云百炼 DashScope,承担默认聊天、Embedding,以及 Qwen 实时 ASR/TTS 能力。项目同时提供 OpenAI 兼容 Provider 配置入口,可配置 LM Studio、Kimi、DeepSeek、GLM 或其他兼容服务。 ## 系统架构 ```text ┌──────────────────────┐ │ React + Vite 前端 │ │ 页面 / API / SSE / WS│ └──────────┬───────────┘ │ HTTP / SSE / WebSocket ┌──────────▼───────────┐ │ Spring Boot 后端 │ │ Controller │ │ ↓ │ │ Service │ │ ↓ │ │ Repository / Adapter │ └─────┬──────┬─────┬───┘ │ │ │ │ │ └── S3 / RustFS:原始文件和导出相关资源 │ └──────── Redis:缓存、限流、Streams 异步任务 └─────────────── PostgreSQL + pgvector:业务数据和向量 外部 AI:DashScope / Qwen Realtime / OpenAI 兼容 Provider ``` 后端主要遵循 `Controller -> Service -> Repository` 分层。业务模块位于 `app/src/main/java/interview/iview/modules/`,公共 AI、异步、异常、限流和统一响应能力位于 `common/`,文件、对象存储、Redis 和映射能力位于 `infrastructure/`。 简历分析、知识库向量化、文字面试评估和语音面试评估通过 Redis Streams 异步执行,避免将耗时的 AI 调用放在数据库事务中。RAG 问答使用 SSE 流式返回,语音面试使用 WebSocket 实时传输音频、字幕和面试官回复。 ## 项目结构 ```text . ├── app/ │ ├── build.gradle │ └── src/ │ ├── main/java/interview/iview/ │ │ ├── common/ # AI、异步、限流、异常、配置和统一响应 │ │ ├── infrastructure/ # 文件、S3、Redis、Mapper、导出 │ │ └── modules/ # 业务模块 │ │ ├── interview/ # 文字面试和技能 │ │ ├── interviewschedule/ # 面试日程 │ │ ├── knowledgebase/ # 知识库和 RAG 聊天 │ │ ├── llmprovider/ # Provider 和语音配置 │ │ ├── resume/ # 简历解析、分析和导出 │ │ └── voiceinterview/ # 实时语音面试 │ ├── main/resources/ │ │ ├── application.yml # 后端主配置 │ │ ├── prompts/ # StringTemplate Prompt │ │ ├── skills/ # 内置技能资料 │ │ └── voice-interview-opening.yml │ └── test/ # JUnit 5 测试 ├── frontend/ │ ├── src/ │ │ ├── api/ # Axios、SSE、WebSocket API 客户端 │ │ ├── components/ # 可复用组件 │ │ ├── constants/ # 路由常量 │ │ ├── hooks/ # React Hooks │ │ ├── pages/ # 页面 │ │ ├── types/ # TypeScript 类型 │ │ └── utils/ # 前端工具 │ ├── nginx.conf # 容器部署反向代理 │ └── package.json ├── docker/ │ └── postgres/init.sql # 启用 pgvector 扩展 ├── docker-compose.dev.yml # PostgreSQL、Redis、RustFS ├── docker-compose.yml # app + frontend ├── .env.example # 环境变量模板 ├── gradlew / gradlew.bat # Gradle Wrapper └── README.md ``` ## 运行环境 本地开发建议准备: - JDK 21; - Node.js 22 或更高版本; - pnpm 10.26.2; - Docker Desktop 与 Docker Compose; - PostgreSQL 16 + pgvector; - Redis 7; - S3 兼容对象存储。使用本地开发 Compose 时为 RustFS; - 可用的 DashScope API Key。 ## 配置环境变量 ### 1. 创建 `.env` Linux/macOS: ```bash cp .env.example .env ``` Windows Git Bash: ```bash cp .env.example .env ``` 也可以直接复制文件并命名为 `.env`。`.env` 已加入 `.gitignore`,不要将真实密钥提交到仓库。 ### 2. 核心变量 | 变量 | 用途 | 默认值 | |---|---|---| | `AI_BAILIAN_API_KEY` | DashScope 聊天、Embedding、ASR、TTS | 必填 | | `APP_AI_CONFIG_ENCRYPTION_KEY` | 加密运行时 Provider API Key | 必填,生产环境使用稳定随机长字符串 | | `POSTGRES_HOST` | PostgreSQL 主机 | `localhost` | | `POSTGRES_PORT` | PostgreSQL 端口 | `5432` | | `POSTGRES_DB` | 数据库名 | `interview_guide` | | `POSTGRES_USER` | 数据库用户 | `postgres` | | `POSTGRES_PASSWORD` | 数据库密码 | `password`(后端回退值) | | `REDIS_HOST` | Redis 主机 | `localhost` | | `REDIS_PORT` | Redis 端口 | `6379` | | `REDIS_PASSWORD` | Redis 密码 | 空 | | `APP_STORAGE_ENDPOINT` | S3/RustFS 地址 | `http://localhost:9000` | | `APP_STORAGE_ACCESS_KEY` | 对象存储 Access Key | 无 | | `APP_STORAGE_SECRET_KEY` | 对象存储 Secret Key | 无 | | `APP_STORAGE_BUCKET` | Bucket 名称 | `interview-guide` | | `APP_STORAGE_REGION` | 对象存储 Region | `us-east-1` | | `APP_STORAGE_FORCE_PATH_STYLE` | 是否使用 path-style | `false` | | `AI_MODEL` | 默认聊天模型 | `qwen3.5-flash` | | `SERVER_PORT` | 后端监听端口 | `4000` | | `VITE_API_PROXY_TARGET` | Vite 代理目标 | `http://localhost:4000` | | `BACKEND_PORT` | Docker 后端宿主机端口 | `4000` | | `FRONTEND_PORT` | Docker 前端宿主机端口 | `5173` | | `APP_VOICE_INTERVIEW_WEBSOCKET_BASE_URL` | 浏览器访问的语音 WebSocket 基址 | `ws://localhost:4000` | ### 3. 可选 Provider ```dotenv # LM Studio PROVIDER_LMSTUDIO_API_KEY=lm-studio # Kimi PROVIDER_KIMI_API_KEY= PROVIDER_KIMI_MODEL=kimi-latest # DeepSeek PROVIDER_DEEPSEEK_API_KEY= PROVIDER_DEEPSEEK_MODEL=deepseek-v4-flash # GLM PROVIDER_GLM_API_KEY= PROVIDER_GLM_MODEL=glm-5 ``` Provider 的运行时可写配置默认保存到: ```text ${user.home}/.interview-guide/llm-providers.yml ${user.home}/.interview-guide/llm-providers.env ``` `APP_AI_CONFIG_ENCRYPTION_KEY` 部署后不要随意更换,否则已保存的 Provider 密钥可能无法解密。 ## 本地开发 ### 1. 启动基础设施 ```bash docker compose -f docker-compose.dev.yml up -d ``` 该命令启动: | 服务 | 地址 | 说明 | |---|---|---| | PostgreSQL | `localhost:5432` | 使用 `pgvector/pgvector:pg16` | | Redis | `localhost:6379` | 缓存、限流和异步 Streams | | RustFS S3 API | `localhost:9000` | 文件对象存储 | | RustFS Console | `http://localhost:9001` | 对象存储管理控制台 | 首次使用 RustFS 时访问 `http://localhost:9001`,使用 Compose 中的 `RUSTFS_ACCESS_KEY` 和 `RUSTFS_SECRET_KEY` 登录,并创建名为 `interview-guide` 的 Bucket。 > 建议在 `.env` 中显式设置 PostgreSQL 用户、密码和数据库名,保证 `docker-compose.dev.yml` 与后端连接配置一致。 ### 2. 启动后端 在项目根目录执行: ```bash ./gradlew :app:bootRun ``` Windows 也可以执行: ```bash ./gradlew.bat :app:bootRun ``` `app/build.gradle` 会在 `bootRun` 时读取根目录 `.env`,并将其中的环境变量注入后端进程。 后端启动后访问: ```text http://localhost:4000 ``` `bootRun` 是常驻 Web 服务,启动成功后 Gradle 终端会持续显示 `:app:bootRun`,这是正常现象;停止服务请按 `Ctrl + C`。 ### 3. 启动前端 另开一个终端: ```bash cd frontend pnpm install pnpm run dev ``` 访问: ```text http://localhost:5173 ``` Vite 默认将 `/api` 和 `/ws` 代理到 `http://localhost:4000`。后端端口或地址不同时,设置: ```dotenv VITE_API_PROXY_TARGET=http://localhost:4000 ``` ### 4. 常用地址 | 地址 | 用途 | |---|---| | `http://localhost:5173` | 前端开发页面 | | `http://localhost:4000/swagger-ui.html` | Swagger UI | | `http://localhost:4000/v3/api-docs` | OpenAPI JSON | | `http://localhost:4000/actuator/health` | 健康检查 | | `http://localhost:4000/actuator/metrics` | 指标 | | `http://localhost:4000/actuator/prometheus` | Prometheus 指标 | | `http://localhost:9001` | RustFS Console | ## Docker 部署 ### 开发依赖容器 ```bash docker compose -f docker-compose.dev.yml up -d docker compose -f docker-compose.dev.yml ps docker compose -f docker-compose.dev.yml logs -f docker compose -f docker-compose.dev.yml down ``` 如需同时删除 PostgreSQL、Redis 和 RustFS 持久化卷: ```bash docker compose -f docker-compose.dev.yml down -v ``` ### 应用容器 `docker-compose.yml` 只启动 `app` 和 `frontend`,不会创建 PostgreSQL、Redis、RustFS 或 AI 服务。使用前请先准备可被后端容器访问的远程基础设施,并配置根目录 `.env`。 ```bash docker compose up -d --build docker compose ps docker compose logs -f app frontend docker compose down ``` 容器部署结构: - `app`:构建 Spring Boot 后端,容器内端口 `4000`; - `frontend`:构建 React 静态资源并由 Nginx 提供服务,容器内端口 `80`; - Nginx 将 `/api/` 反向代理到 `app:4000`; - Nginx 将 `/ws/` 升级并代理到 `app:4000`; - Nginx 关闭 API 代理缓冲,以支持 SSE 流式响应; - 默认映射到宿主机 `127.0.0.1:4000` 和 `127.0.0.1:5173`; - 容器启用 `no-new-privileges` 并删除不必要的 Linux capabilities。 部署到域名或 HTTPS 环境时,应将 WebSocket 基址设置为浏览器可访问的地址,例如: ```dotenv APP_VOICE_INTERVIEW_WEBSOCKET_BASE_URL=wss://example.com/ws ``` 具体地址需要根据实际反向代理、域名和 TLS 配置调整。 ## 页面与路由 | 路由 | 页面 | |---|---| | `/` | 重定向到简历库 | | `/upload` | 上传简历 | | `/history` | 简历历史和简历库 | | `/history/:resumeId` | 简历详情和分析报告 | | `/interview-hub` | 面试中心 | | `/interview` | 通用文字模拟面试 | | `/interview/:resumeId` | 基于指定简历的文字模拟面试 | | `/interviews` | 面试记录 | | `/interviews/:sessionId` | 文字面试详情报告 | | `/voice-interview` | 实时语音面试 | | `/voice-interview/:sessionId/evaluation` | 语音面试评估报告 | | `/knowledgebase` | 知识库管理 | | `/knowledgebase/upload` | 上传知识库文档 | | `/knowledgebase/chat` | RAG 问答助手 | | `/interview-schedule` | 面试日程 | | `/settings` | LLM Provider、ASR/TTS 设置 | ## API 概览 完整接口参数和响应 Schema 请在后端启动后查看 Swagger UI: ```text http://localhost:4000/swagger-ui.html ``` 主要 API 分组如下: | 模块 | 前缀或代表接口 | |---|---| | 简历 | `/api/resumes` | | 文字面试 | `/api/interview/sessions` | | 面试技能 | `/api/interview/skills` | | 语音面试 | `/api/voice-interview/sessions` | | 知识库 | `/api/knowledgebase` | | RAG 聊天 | `/api/rag-chat/sessions` | | 面试日程 | `/api/interview-schedule` | | LLM Provider | `/api/llm-provider` | | 语音 WebSocket | `/ws/voice-interview/{sessionId}` | 常用接口示例: ```text GET /api/resumes GET /api/resumes/{id}/detail POST /api/resumes/{id}/reanalyze GET /api/resumes/{id}/export GET /api/interview/sessions POST /api/interview/sessions GET /api/interview/sessions/{sessionId}/question POST /api/interview/sessions/{sessionId}/answers POST /api/interview/sessions/{sessionId}/complete GET /api/interview/sessions/{sessionId}/export GET /api/knowledgebase/list POST /api/knowledgebase/query GET /api/knowledgebase/search POST /api/knowledgebase/{id}/revectorize POST /api/voice-interview/sessions POST /api/voice-interview/sessions/{sessionId}/end GET /api/voice-interview/sessions/{sessionId}/evaluation POST /api/interview-schedule/parse GET /api/interview-schedule/{id} GET /api/llm-provider/list POST /api/llm-provider/{id}/test PUT /api/llm-provider/default-provider ``` 普通业务接口使用统一响应结构: ```json { "code": 200, "message": "success", "data": {} } ``` 文件下载接口直接返回二进制内容;SSE 和 WebSocket 接口不使用普通 JSON 响应封装。 ## 实时与异步机制 ### Redis Streams 以下任务采用异步处理: - 简历 AI 分析; - 知识库文档向量化; - 文字面试评估; - 语音面试评估。 消费者使用 Redis Stream 消费组处理任务,并根据任务状态和重试策略更新业务记录。实体不存在或已删除时,异步消费者会丢弃对应任务,避免重新写入无效数据。 ### SSE 知识库问答和 RAG 聊天支持 SSE 流式响应。生产环境 Nginx 已关闭 `/api/` 的代理缓冲,并配置了长连接超时。 ### WebSocket 语音面试 WebSocket 地址为: ```text /ws/voice-interview/{sessionId} ``` 典型流程为: ```text 浏览器录音 → WebSocket 上传音频 → Qwen Realtime ASR → 用户语句合并和提交 → LLM 生成面试官回复 → Qwen Realtime TTS → WebSocket 返回字幕、文本和音频 ``` 语音面试要求浏览器允许麦克风访问,并确保浏览器能够访问配置的 WebSocket 地址。 ## 测试与构建 ### 后端 ```bash # 编译 ./gradlew :app:compileJava # 运行全部测试 ./gradlew :app:test --no-daemon # 启动后端 ./gradlew :app:bootRun ``` Windows: ```bash ./gradlew.bat :app:compileJava ./gradlew.bat :app:test --no-daemon ./gradlew.bat :app:bootRun ``` 后端测试使用 JUnit 5、Spring Boot Test、Mockito 和 AssertJ。测试环境使用 H2;涉及限流、Redis Streams 或实时 AI 服务的测试,需要额外准备对应依赖或有效密钥。 ### 前端 ```bash cd frontend pnpm install pnpm run build pnpm run dev pnpm run preview ``` 当前前端 `package.json` 提供 `dev`、`build` 和 `preview` 脚本,没有单独的 `test` 或 `lint` 脚本。 ## 常见问题 ### 1. `bootRun` 一直显示 `EXECUTING` 是不是卡住了? 不是。Spring Boot 是常驻 Web 服务,`./gradlew :app:bootRun` 会持续运行,因此 Gradle 不会立即显示 `BUILD SUCCESSFUL`。看到应用启动日志后即可访问前端;停止时按 `Ctrl + C`。 ### 2. 启动时为什么会连续出现 TTS 日志? 语音面试处理器会在启动后异步预热预置开场问题的音频缓存。每个开场模板都会调用一次 TTS,完成后会出现类似日志: ```text Opening audio cache warmed: N entries ``` 这是启动预热,不是请求无限循环。如果该日志在应用重启后重复出现,请检查是否启用了自动重启、是否同时运行了多个后端进程,或是否出现了 `APPLICATION FAILED TO START`。 ### 3. 后端端口是 8080 还是 4000? 当前 `application.yml` 的实际默认配置是 `4000`: ```yaml server: port: ${SERVER_PORT:4000} ``` 如果通过环境变量设置了 `SERVER_PORT`,以环境变量为准。 ### 4. 前端访问不到后端怎么办? 确认后端运行在 `4000`,并检查 Vite 代理配置: ```dotenv VITE_API_PROXY_TARGET=http://localhost:4000 ``` 如果使用 Docker,确认 `app` 健康检查通过,再查看: ```bash docker compose ps docker compose logs -f app frontend ``` ### 5. 简历或知识库文件上传失败怎么办? 当前 Spring Multipart 请求大小上限为 50 MB,Nginx 也设置了 `client_max_body_size 50M`。同时检查文件类型、对象存储连接、Bucket 权限和 API Key 配置。 ### 6. pgvector 初始化失败怎么办? 确认 PostgreSQL 使用支持 pgvector 的镜像或已经安装 `vector` 扩展。开发 Compose 会执行 `docker/postgres/init.sql`,其中包含: ```sql CREATE EXTENSION IF NOT EXISTS vector; ``` 如果数据库卷已经存在而初始化脚本没有重新执行,请手动在目标数据库执行该 SQL。 ### 7. 语音面试没有声音或字幕怎么办? 检查以下项目: - 浏览器是否授予麦克风权限; - `AI_BAILIAN_API_KEY` 是否有效; - ASR/TTS 模型是否可用; - `APP_VOICE_INTERVIEW_WEBSOCKET_BASE_URL` 是否为浏览器可访问的地址; - HTTPS 页面是否使用 `wss://`; - Nginx 或其他反向代理是否转发了 WebSocket Upgrade 请求头。 ## 安全注意事项 - 不要提交 `.env`、API Key、数据库密码、Redis 密码或对象存储密钥; - 生产环境必须设置随机且稳定的 `APP_AI_CONFIG_ENCRYPTION_KEY`; - 不要在生产环境依赖 `spring.jpa.hibernate.ddl-auto=update` 自动变更数据库结构,应使用受控的数据库迁移方案; - 生产环境建议关闭 pgvector 的自动 schema 初始化: ```yaml spring: ai: vectorstore: pgvector: initialize-schema: false ``` - Actuator、Swagger 和 Prometheus 端点应根据部署环境设置访问控制; - 生产环境应限制 `CORS_ALLOWED_ORIGINS`,不要直接使用过宽的跨域配置; - 对象存储 Bucket 和数据库应使用最小权限账号; - 部署在公网时,应使用 HTTPS/WSS,并通过反向代理或网关保护管理端点。 ## 许可证 本项目许可证见根目录 [`LICENSE`](./LICENSE) 文件。