Files
prop-ai-hr/docs/API_INTEGRATION.md
T

174 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端 API 对接指南
前端四条演示流和演示预检已经稳定。后端 API 按页面逐刀接入,避免一次性铺满四个页面。
## 第一阶段范围
| 页面 | 后端接口 | 处理 |
|---|---|---|
| AI面试 `/recruit/interview` | `POST /api/recruit/interview/start`、`/answer`、`/finish` | 已接入真实模型优先链路:配置 chat 模型后 `/start` 动态生成面试题,`/finish` 按前端提交的真实回答做结构化评分;未配置模型时使用本地 Rubric 估分,不返回固定候选人分数 |
| 三角色对练 `/train/practice` | `POST /api/train/practice/start`、`/turn`、`/finish` | 已接入编排 API;数据库启用 chat 模型后,`/turn` 客户回复按人设走真 LLM 生成(seed 剧本作剧情锚点),`/finish` 走单次 temperature=0 结构化评分(4 维分+导师改写+点评);模型未配置或调用失败自动回退 seed,契约不变 |
| 正式试点数据导出 | `GET /api/train/practice/export?startDate=YYYY-MM-DD&endDate=YYYY-MM-DD` | 起止日期必填且包含结束日;只统计窗口内能通过唯一手机号或外部 ID 映射到在职组织快照的正式会话,排除重复手机号和身份碰撞。完训定义为每人至少 10 次,校准必须关联同一窗口内正式会话;CSV 同时给出校准命中数和 SOP 可用数,避免用四舍五入后的比率反推门禁状态;汇总和明细均携带正式人员及项目口径,不混入历史 seed/开发身份 |
| 对练语音 | `POST /api/ai/asr`(multipart 字段 `file`,≤5MB)、`POST /api/ai/tts`(JSON `{text≤300字, voice}`,返回 `{audioUrl}` base64 dataURL) | 走 OpenAI-compatible audio 接口(如硅基流动 SenseVoice/CosyVoice2);模型管理需启用 `category=asr/tts` 配置;移动端优先用浏览器录音,`getUserMedia/MediaRecorder` 不可用或麦克风权限失败时,用 `audio/*` file input 选择/录制音频后继续调同一 ASR 接口;ASR/TTS 未配置或失败返回 fail,前端降级文本 |
| 案例沉淀 `/knowledge/cases` | `POST /api/knowledge/case/upload`、`/organize`、`/curate` | `/upload` 改为 multipart 真实语音上传并走 ASR;`/organize` 用真实转写调 chat 模型整理案例,未配置模型时按真实 transcript 本地结构化;不再用固定样例转写冒充成功 |
| SOP知识库 `/knowledge/sop` | `POST /api/knowledge/search`、`POST /api/knowledge/doc/upload` | 已接入 MySQL Fulltext + Qdrant 混合召回、OSS-first 文档上传、txt/md/PDF/Word/Excel/PPT 解析和 embedding 写入,失败回退 seed |
| 资料处理 `/knowledge/processing` | `GET /api/knowledge/processing/overview`、`POST /api/knowledge/doc/upload-async`、`GET /api/knowledge/doc/upload-items`、`POST /api/knowledge/doc/upload-items/{id}/retry`、`POST /api/knowledge/doc/import-local-task`、`GET /api/knowledge/doc/import-tasks`、`POST /api/knowledge/doc/import-tasks/{id}/cancel` | 已接入解析任务状态聚合;**批量上传走异步队列**:接口只暂存+入队即秒回,后台 worker(并发 2)逐条解析/归类/向量化;ZIP 在 worker 内安全解压后把支持的子文件继续入同一批次队列,页面按批次轮询进度、失败可单文件重试;服务端目录导入、进度轮询和任务取消保留,失败回退 seed |
| 组织人员同步 | `POST /api/aihr/org/sync` | 从开放组织同步系统的 `/api/open/v1/sync/snapshot` 拉取 `company/department/employee` 快照,分页参数使用 `limit`;员工手机号只落 `person_phone` 用于移动端身份映射,不在组织列表响应暴露;岗位识别 `position/job_title/post/job_name/role/title` 等字段。默认 `replaceExisting=true`,写入前必须先用 `{"dryRun":true}`;dry-run 不访问本地快照表、不执行 DDL/写库,返回 `phoneLinked/maskedPhone/suspectText/warnings` 且 `syncedCount=0`。2026-07-10 源接口实测 3474 人仅 1 个可用手机号、3473 个脱敏手机号、8 条疑似乱码,因此未执行覆盖同步,正式试点需上游先开放至少 20 名试点人员手机号 |
| 移动端手机号登录 | `GET /resource/sms/code`、`POST /auth/mobile/sms-login` | 已复用 sms4j 阿里云配置 `config1` 和 RuoYi `sms` 授权策略;短信发送成功后才写 Redis 验证码;手机号不存在时自动注册 `app_user`;`aihr.sms.dev-fixed-code` 非空时不真发短信、验证码固定(dev 默认 `123456`,prod profile 代码级强制失效) |
| 用户侧三端首页 `mobile-uni` hash 路由;旧 `/h5/user`、`/h5/candidate`、`/h5/supervisor` 兼容重定向 | `GET /api/aihr/mobile/home/{role}` | 已接入员工、候选人、主管首页公开只读 API;移动端本地 fallback 保演示 |
| 移动端登录后角色识别 | `GET /api/aihr/mobile/me` | 认证后按手机号匹配组织快照;`position_level` 为“主管/项目经理”时进入主管端,否则进入员工端;接口失败回退员工端 |
| 移动端员工训练与主管复盘闭环 | 员工复用 `POST /api/train/practice/start`、`/turn`、`/finish`,查询 `GET /api/aihr/mobile/practice/history`、`/profile`;主管查询 `GET /api/aihr/mobile/practice/team`、`/practice/alerts`、`/practice/reviews`、`/practice/reviews/{id}`,标记 `POST /api/aihr/mobile/practice/reviews/{id}/reviewed`,指派 `POST /api/aihr/mobile/practice/assignments` | 员工端登录后带 `Authorization` 与 `clientid` 调用;`mode=mobile` 完成后写入 `aihr_practice_session`。主管接口以后端当前登录手机号映射在职组织快照,仅允许岗位为“主管/项目经理”的账号,并按租户和项目范围返回真实成员、全状态训练记录及非 daily 专项;普通员工返回“无主管权限”。复盘标记只允许首次 `待复盘 -> 已复盘` 创建后续专项,并发重复提交幂等;可带 `incentivePoint` 写入贡献度 |
| 移动端候选人闭环 | 页面拆为 `/pages/candidate/index/index`、`/interview/index`、`/materials/index`、`/progress/index`、`/study/index`;面试复用 `POST /api/recruit/interview/start`、`/answer`、`/finish` 和 `GET /records`;资料 `POST/GET /api/aihr/mobile/candidate/materials`;预习 `POST /api/knowledge/search`;HR 审核 `GET /api/aihr/hr/candidate/materials`、`POST /api/aihr/hr/candidate/materials/{id}/review` | 候选人端登录后带 `Authorization` 与 `clientid` 调用;首页只按当前手机号对应的真实面试记录和最新资料状态分流,不读取公开 home seed。APP 候选人身份以后端登录手机号为准,前端 `candidateId/candidateName` 只作非 APP 场景兼容参数;面试拉题/评分走真实模型优先;资料写 `sys_oss`/MinIO 和 `aihr_candidate_material`,HR 审核后进度页同步三态;岗前预习查询正式 SOP,不兜前端示例答案 |
## 后端落点
- 业务模块:`backend/ruoyi-modules/ruoyi-aihr`
- 注册模块:`backend/ruoyi-modules/pom.xml`
- 接入启动包:`backend/ruoyi-admin/pom.xml`
- 包名建议:`org.dromara.aihr`
- Controller 返回统一用 `org.dromara.common.core.domain.R`
当前管理端 AI 面试和案例沉淀已改为真实模型/ASR 优先;移动端首页和部分演示态数据仍保留 fallback。知识库、模型能力、文档解析、RAG、chat 按 [ruoyi-ai 能力分片迁移计划](RUOYI_AI_INCREMENTAL_MIGRATION.md) 逐片引入;知识库 DDL 与住宅类 SOP seed 在 `backend/script/sql/aihr_knowledge_mysql8.sql`,模型 DDL 在 `backend/script/sql/aihr_model_mysql8.sql`,训练记录 DDL 在 `backend/script/sql/aihr_practice_mysql8.sql`,AI 面试结果 DDL 在 `backend/script/sql/aihr_interview_result_mysql8.sql`,候选人资料 DDL 在 `backend/script/sql/aihr_candidate_material_mysql8.sql`,组织人员快照表在 `backend/script/sql/aihr_org_snapshot_mysql8.sql`,本地 reset 带 2 个住宅项目 22 人 seed;配置开放组织同步系统后用 `POST /api/aihr/org/sync` 覆盖为外部快照。
直接打后端 `/api/**` 通常需要登录后的 `Authorization: Bearer <access_token>`;浏览器内通过已登录前端和 `/dev-api` 代理访问。移动端登录接口为 `POST /auth/mobile/sms-login`,请求 `{ phonenumber, smsCode, tenantId }`,内部固定使用 app 客户端 `428a8310cd442757ae699df5d894f051` 和 `sms` grant;验证码通过后若手机号不存在,会创建 `app_user`,用户名为手机号,备注为“移动端短信自动注册”。移动端 MVP 首页接口 `GET /api/aihr/mobile/home/{role}` 目前仍是 `@SaIgnore` 的公开只读 seed 接口,避免 H5 首屏被后台管理登录态阻断;后续接小程序登录后再收紧为移动端 token。
阿里云短信复用 RuoYi 的 `sms.blends.config1`。本地开发把真实短信参数放根目录 `.env.local` 或外部环境变量,`scripts/dev-backend.sh` 会自动加载;`application-dev.yml` / `application-prod.yml` 只保留占位,不写真实密钥。
```bash
AIHR_SMS_LOGIN_TEMPLATE_ID=SMS_xxxxxx
ALIYUN_SMS_ACCESS_KEY_ID=xxx
ALIYUN_SMS_ACCESS_KEY_SECRET=xxx
ALIYUN_SMS_SIGN_NAME=物业AI助手
```
候选人资料上传最小后端边界:
| 能力 | 后端接口 | 处理 |
|---|---|---|
| 候选资料上传 | `POST /api/aihr/mobile/candidate/materials` | `multipart/form-data` 字段 `file`,参数 `candidateId/candidateName/materialType`;需移动端登录返回的 `Authorization` 与 `clientid`;APP 用户场景后端强制使用登录手机号和派生候选人名,不信任前端传入身份;文件用 `ISysOssService` 写 `sys_oss`/MinIO,关系写 `aihr_candidate_material`,状态默认 `待审核`,单文件 20MB |
| 候选资料列表 | `GET /api/aihr/mobile/candidate/materials?candidateId=` | 返回前 20 条 `{id, ossId, fileName, materialType, fileSize, status, time}`,供 `/h5/candidate` 的“已提交资料”展示;APP 用户场景忽略传入 `candidateId`,只返回登录手机号对应资料 |
| HR 资料审核 | `GET /api/aihr/hr/candidate/materials?status=`、`POST /api/aihr/hr/candidate/materials/{id}/review` | 管理端 `/recruit/interview` 展示前 50 条,可按状态筛选;审核请求体 `{status}`,只接受 `待审核/已通过/已驳回`;接口只允许管理端超级管理员或 `hr_operator` 角色访问 |
SOP 文档上传第三片已经落最小后端边界:
| 能力 | 后端接口 | 处理 |
|---|---|---|
| 文档上传解析 | `POST /api/knowledge/doc/upload` | `multipart/form-data`,字段 `file` 和 `category`;支持 `.txt/.md/.markdown/.pdf/.doc/.docx/.xls/.xlsx/.ppt/.pptx` 及图片 `.jpg/.jpeg/.png/.gif/.webp/.bmp`、100MB 内;先写 `sys_oss`,再绑定 `aihr_knowledge_attach.oss_id` 并切分写入 `aihr_knowledge_fragment`;管理端该接口 timeout 为 180s,避免 PDF 同步解析/归类/向量化接近默认 50s 时被客户端断开 |
| 图片视觉 OCR | 同一上传/导入链路 | 上传图片时,若启用了 `aihr_model_config.category='vision'`(无则回退 `category='chat'`)的模型,会把图片编码为 base64 data URL 调用该供应商 OpenAI-compatible `/chat/completions` 提取文字,识别结果按普通正文切分写入 fragment;无可用视觉模型或 OCR 无结果时按「待处理」落库(attach status=0、0 片段),不算失败,启用视觉模型后重新上传同名文件即可解析。不依赖 Tesseract,识别质量由所配置视觉模型决定 |
| 智能归类与标签 | 同一上传/导入链路 | `category=__auto__` 时,解析正文后优先调用已启用的 `aihr_model_config.category='chat'` 模型生成分类、摘要、标签和归类理由,温度固定为 `0`;无模型或调用失败时按文件名/正文关键词兜底。最终分类写入 `aihr_knowledge_info/attach`,摘要和标签写入 `sys_oss.ext1` |
| 重复资料处理 | 同一上传/导入链路 | 上传时计算原始文件 `aihrFileSha256`、解析文本 `aihrTextSha256` 和 `md5` 写入 `sys_oss.ext1`;重复判断优先按文件 SHA-256,其次按文本 SHA-256,最后用同名同大小兼容旧数据。命中重复时复用原附件并迁移到最新分类,删除其他重复附件和旧 fragment |
| 归类稳定性 | 同一上传/导入链路 | 命中重复资料时优先复用已有 `sys_oss.ext1` 里的分类、摘要和标签;只有旧资料没有存过模型结果时才重新分析,避免同文件因重复上传或更换模型导致标签漂移 |
| 文档替换 | 同一 `category + fileName` 再上传 | 复用原 `doc_id`,删除旧 fragment 后重写新 fragment,避免重复文档堆积 |
| 片段向量化 | 同一上传接口内机会性执行 | 若 `aihr_model_config.category='vector'` 且启用,会优先调用配置供应商的 OpenAI-compatible `/embeddings`,把返回向量写入 `aihr_knowledge_fragment.embedding_json`;配置缺失或外部接口失败时使用本地确定性 `local-hash-v1` 兜底,保证 MVP 链路仍有真实向量数据 |
| 缺失向量补跑 | `POST /api/knowledge/doc/vectorize-missing` | 扫描缺少 `embedding_json` 的片段,复用同一向量化与 Qdrant 入库链路,适合模型配置变更后或历史资料补齐向量 |
| 向量库状态与重建 | `GET /api/knowledge/doc/vector-index-status`、`POST /api/knowledge/doc/rebuild-vector-index` | 模型配置页展示当前 vector 模型维度、Qdrant collection 维度、点数和片段向量数;维度不一致时可一键清空旧 embedding、删除 collection 并按当前模型重建 |
| Qdrant 向量索引 | 同一上传接口内机会性执行 | embedding 写入 MySQL 后尽力 upsert 到 Qdrant;同名文档替换会尽力删除旧 points;Qdrant 不可用不影响上传和 MySQL 检索;外部 embedding 成功但 collection 维度不一致时不静默降级为 `local-hash-v1`,通过状态接口和重建入口处理 |
| 混合检索 | `POST /api/knowledge/search` | 先跑中文关键词 `LIKE` 打分和 MySQL Fulltext,再生成 query embedding 走 Qdrant,RRF 融合后交给 `category='rerank'` 模型(如硅基流动 bge-reranker-v2-m3)按语义相关性重排;rerank 未配置或失败保持 RRF 顺序,Qdrant 或外部向量接口不可用时保留关键词/全文检索。命中后若 chat 模型可用,自动生成结构化轻概要写入响应 `answer/keyPoints/cautions`(≤60字直答+3-6要点+0-3注意;temperature=0,15s 超时,硬约束不得编造、答不了明示无依据);概要失败回退片段原文拼接,检索不报错 |
| 检索总结卡 | `POST /api/knowledge/summary-card`(JSON `{queryText, category}`) | 按需生成信息图式总结卡 `{title, steps[], objections[], scripts[], reminders[]}`:复用检索命中片段(最多5条)调 chat 模型,temperature=0、30s 超时;无命中/无 chat 模型/生成失败均抛明确错误供前端重试,不兜假数据。移动端查SOP结果页「生成总结卡」按钮触发,底部弹层展示并可 TTS 朗读 |
| 解析状态聚合 | `GET /api/knowledge/processing/overview` | 聚合 `aihr_knowledge_attach.status`、fragment 数、embedding 数、`sys_oss.ext1.fileSize`,生成资料处理页指标、分类、任务、链路和事件列表 |
| 服务端目录导入 | `POST /api/knowledge/doc/import-local` | JSON `{ directory, category, limit }`;`directory` 只能是 `AIHR_IMPORT_ROOT` / `aihr.import.root` 下的相对目录,默认根目录为 `./.data/import`;逐文件复用上传解析链路,同步执行,保留给小批量/调试 |
| 服务端导入任务 | `POST /api/knowledge/doc/import-local-task`、`GET /api/knowledge/doc/import-tasks`、`POST /api/knowledge/doc/import-tasks/{id}/cancel` | 启动后台目录导入并返回任务;任务写入 `aihr_knowledge_import_task`,页面轮询查看总数、成功数、失败数、当前文件和进度;运行中任务可取消;重试当前按同目录重新启动一轮 |
| 批量异步上传 | `POST /api/knowledge/doc/upload-async`(multipart `file`+`category`+`batchId`)、`GET /api/knowledge/doc/upload-items?batchId=`、`POST /api/knowledge/doc/upload-items/{id}/retry` | 上传只做暂存(`aihr.upload.staging`,默认 `./.data/staging`)+ 写入 `aihr_knowledge_upload_item`(0待处理/1处理中/2完成/3失败)即返回;支持 ZIP ≤500MB,worker 限制最多 1000 个子文件、解压总量 ≤2GB,拒绝嵌套 ZIP/不安全路径并忽略未知格式;支持的子文件写回同一 batch 继续复用解析链路;完成/失败状态 CAS 写入,卡住 30 分钟(视频 120 分钟)由清扫重置,失败暂存文件保留 72h 供重试;同步接口 `POST /api/knowledge/doc/upload` 不支持 ZIP,保留给 SOP 页单文件即时预览 |
| 视频解析 | 走批量异步上传,支持 `.mp4/.mov/.avi/.mkv/.webm/.m4v`,单文件 ≤500MB、时长 ≤60 分钟 | 依赖服务器 ffmpeg/ffprobe;抽音轨按 5 分钟分段调 asr 模型转写(`[mm:ss]` 时间戳),按 30s 间隔(≤40 帧)抽关键帧调 vision 模型提取画面文字(相邻重复画面去重);两类文本合并后走归类/切片/向量化;无 asr 且无 vision 结果时按「待处理」落库不报错;视频同时只加工 1 个 |
模型能力第二片已经落最小后端边界:
| 能力 | 后端接口 | 处理 |
|---|---|---|
| 模型供应商 | `GET /api/aihr/model/providers`、`POST /api/aihr/model/providers`、`PUT /api/aihr/model/providers/{providerCode}`、`PATCH /api/aihr/model/providers/{providerCode}/status` | 优先返回 `aihr_model_provider`,支持新增、编辑、启停;缺表或空表时返回 seed 清单 |
| 模型配置 | `GET /api/aihr/model/configs`、`POST /api/aihr/model/configs`、`PUT /api/aihr/model/configs/{id}`、`PATCH /api/aihr/model/configs/{id}/enabled` | 优先返回 `aihr_model_config`,支持新增、编辑、启停,并计算是否已具备 URL/Key;`category` 支持 `chat/vector/rerank/asr/tts/vision` |
| 模型探针 | `POST /api/aihr/model/chat` | 数据库配置后走 OpenAI-compatible `/chat/completions`,否则 seed fallback;请求携带的模型名在库中不存在时(如前端 seed 占位行 `gpt-4o-mini`),自动回退到默认已启用的 chat 配置 |
本阶段不修改 `.env`,也不自动执行模型 SQL。API Key 通过模型配置页面写入数据库:`api_key` 可放在 `aihr_model_provider` 作为供应商默认值,也可放在 `aihr_model_config` 覆盖单个模型;接口响应只返回 `configured/apiKeyConfigured`,不返回密钥明文。
Qdrant 本地默认值可不配;需要覆盖时用 JVM property 或环境变量:
| 配置 | 默认值 | 用途 |
|---|---|---|
| `AIHR_QDRANT_URL` / `-Daihr.qdrant.url` | `http://127.0.0.1:6333` | Qdrant REST 地址 |
| `AIHR_QDRANT_COLLECTION` / `-Daihr.qdrant.collection` | `aihr_knowledge` | 知识库向量 collection |
| `AIHR_QDRANT_API_KEY` / `-Daihr.qdrant.apiKey` | 空 | 远端 Qdrant API Key,本地不用 |
| `AIHR_IMPORT_ROOT` / `-Daihr.import.root` | `./.data/import` | 服务端资料目录导入根目录 |
组织人员同步配置:
| 配置 | 默认值 | 用途 |
|---|---|---|
| `AIHR_ORG_SYNC_BASE_URL` / `-Daihr.org-sync.base-url` | 空 | 外部开放平台 `/api/open/v1` 前缀,例如 `https://wuye.meihe.cc/api/open/v1` |
| `AIHR_ORG_SYNC_ACCESS_TOKEN` / `-Daihr.org-sync.access-token` | 空 | 已有 Bearer token;为空时尝试用 client credentials 换 token |
| `AIHR_ORG_SYNC_CLIENT_ID` / `-Daihr.org-sync.client-id` | 空 | 开放平台应用 `client_id`,也用于签名头 `X-Client-Id` |
| `AIHR_ORG_SYNC_CLIENT_SECRET` / `-Daihr.org-sync.client-secret` | 空 | `POST /auth/token` 换取访问令牌 |
| `AIHR_ORG_SYNC_SIGNING_SECRET` / `-Daihr.org-sync.signing-secret` | 空 | 可选 HMAC-SHA256 签名密钥;为空时使用 `AIHR_ORG_SYNC_CLIENT_SECRET`,业务请求带 `X-Client-Secret/X-Timestamp/X-Nonce/X-Signature`,签名 path 必须包含 `/api/open/v1` |
本地启动脚本会先加载根目录 `.env.local`,再加载 `backend/.env`。如果 `backend/.env` 沿用开放平台字段名 `client_id` / `client_secret`,脚本会映射为 `AIHR_ORG_SYNC_CLIENT_ID` / `AIHR_ORG_SYNC_CLIENT_SECRET`。
```bash
TOKEN=<登录后 access_token>
API_BASE=${API_BASE:-https://wygj-api.localhost}
curl -fsS "$API_BASE/api/aihr/org/sync" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"dryRun":true}'
curl -fsS "$API_BASE/api/aihr/org/sync" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"replaceExisting":true}'
```
## 前端落点
- 管理端前端:`frontend/`,只承载后台管理、配置、审核、查看结果等页面。
- AI 面试 API 文件:`frontend/src/api/aihr/interview.ts`
- AI 面试页面:`frontend/src/views/recruit/interview.vue`
- 三角色对练 API 文件:`frontend/src/api/aihr/practice.ts`
- 三角色对练页面:`frontend/src/views/train/practice.vue`
- 案例沉淀 API 文件:`frontend/src/api/aihr/case.ts`
- 案例沉淀页面:`frontend/src/views/knowledge/cases.vue`
- SOP 知识库 API 文件:`frontend/src/api/aihr/sop.ts`
- SOP 知识库页面:`frontend/src/views/knowledge/sop.vue`
- 资料处理 API 文件:`frontend/src/api/aihr/processing.ts`
- 资料处理页面:`frontend/src/views/knowledge/processing.vue`
- 模型配置 API 文件:`frontend/src/api/aihr/model.ts`
- 模型配置页面:`frontend/src/views/system/model/index.vue`
- 用户侧工程:`mobile-uni/`,路由为 `/h5/#/pages/user/today/index`、`/h5/#/pages/user/practice/index`、`/h5/#/pages/user/sop/index`、`/h5/#/pages/user/profile/index`、`/h5/#/pages/candidate/index/index`、`/h5/#/pages/supervisor/index/index`。未登录先走手机号短信登录,登录后 API 优先请求真实业务接口,不复用后台页面路由。语音输入不要新增移动端专用接口,浏览器录音和文件选择兜底都复用 `/api/ai/asr`。
- 员工端训练:底部“练”进入 `/h5/#/pages/user/practice/index`,展示今日任务、每日三题、专项训练、训练前预习卡和训练对话;完成后记录写入 `aihr_practice_session`,员工端展示训练历史和能力画像,主管端展示待复盘列表,点进单条可看评分、话术、导师改写,并可标记“已复盘”。
- 候选人端:`/h5/#/pages/candidate/index/index` 的“开始面试/面试练习”复用 AI 面试 API;“补充资料”走候选资料上传接口,成功后展示资料类型和审核状态。管理端 `/recruit/interview` 底部展示“候选资料审核”,支持通过/驳回。
- 演示 fallback 只作为未配置模型或外部接口异常时的降级,不作为真实测试通过证据。
## 验收
```bash
./scripts/demo-check.sh
npm --prefix mobile-uni run build:h5
API_BASE=${API_BASE:-https://wygj-api.localhost}
curl -fsS "$API_BASE/api/aihr/mobile/home/user"
npm --prefix frontend run lint:eslint -- src/api/aihr/interview.ts src/views/recruit/interview.vue src/api/aihr/practice.ts src/views/train/practice.vue src/api/aihr/case.ts src/views/knowledge/cases.vue src/api/aihr/sop.ts src/views/knowledge/sop.vue
mvn -f backend/pom.xml -pl ruoyi-admin -am -DskipTests package
```
模型能力可单独 smoke:
```bash
TOKEN=<登录后 access_token>
API_BASE=${API_BASE:-https://wygj-api.localhost}
curl -fsS "$API_BASE/api/aihr/model/providers" -H "Authorization: Bearer $TOKEN"
curl -fsS "$API_BASE/api/aihr/model/configs" -H "Authorization: Bearer $TOKEN"
curl -fsS -X POST "$API_BASE/api/aihr/model/chat" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"prompt":"物业管家处理漏水投诉第一步是什么?"}'
```
Qdrant 和向量索引可单独 smoke;重建接口会清空旧 embedding 并重建 collection,只用于本地验证或确认维度不一致后的修复:
```bash
TOKEN=<登录后 access_token>
API_BASE=${API_BASE:-https://wygj-api.localhost}
curl -fsS http://127.0.0.1:6333/
curl -fsS "$API_BASE/api/knowledge/doc/vector-index-status" -H "Authorization: Bearer $TOKEN"
curl -fsS -X POST "$API_BASE/api/knowledge/doc/rebuild-vector-index" -H "Authorization: Bearer $TOKEN"
```
浏览器验收仍按 [DEMO_ACCEPTANCE.md](DEMO_ACCEPTANCE.md) 的四条关键路径执行。