150 lines
13 KiB
Markdown
150 lines
13 KiB
Markdown
# AIHR BRD 生产迁移 Runbook
|
||
|
||
本文件只覆盖 AIHR 阶段一 BRD 已落地代码所需的 MySQL 8 结构和内置内容迁移。它不执行正式组织同步、试点训练、人工校准、SOP 评审、案例导入或满意度补录;这些属于业务数据和正式验收,必须单独审批。
|
||
|
||
## 当前生产基线
|
||
|
||
2026-07-18 已在生产发布功能包提交 `064816a7`:
|
||
|
||
- 22 个版本化迁移按本文件顺序执行完成,目标结构核验为 `35/35`;
|
||
- 最后一条排序规则迁移把 19 张发布表对齐到 `aihr_knowledge_info.tenant_id` 的实际排序规则,核验为 `19/19`;
|
||
- 管理端、H5、后端产物与本地发布产物哈希一致,`wygj-aihr.service` 为 `active`;
|
||
- 根站、H5、租户接口、员工首页 API 和关键管理页通过生产回归;
|
||
- 当前发布备份位于 `/opt/wygj/backups/ask-memory-20260718-2130`,内含完整数据库、JAR、H5 和 SHA-256 清单。
|
||
|
||
上述事实只证明本批版本和 schema 已发布。正式 OTP、真机能力、角色权限正反例和严格试点窗口仍需按发布后回归及 `DEMO_ACCEPTANCE.md` 验收。
|
||
|
||
2026-07-19 增量在上述基线上新增“项目服务记忆最小切片”:候选信息只有在当前员工明确确认后,才会写入其已授权项目的服务记录;本批不包含个人想法/经验的长期知识库、跨项目共享、自动提醒或向量化个人资料。同日的“工作上报语音会话化”把既有 `aihr_work_report` 纳入结构门禁,并补充 `request_key/request_hash` 和租户内提交人唯一索引,用于确认上报的幂等重试,不新建草稿或会话表。全网 AI 提供方加密主密钥改为数据库内部配置后,发布门禁统一为 `40/40`。
|
||
|
||
## 发布前置条件
|
||
|
||
1. 确认发布窗口、DB 备份和回滚负责人;迁移脚本包含 `ALTER TABLE`,不能在无备份状态执行。
|
||
2. 先执行 `scripts/release-preflight.sh` 的只读检查;生产静态资源、后端包和当前提交必须一致。若要同时核对线上结构,追加 `RELEASE_VERIFY_REMOTE_SCHEMA=true`,该开关只读检查必需表,不执行迁移。
|
||
3. 确认迁移目标库、租户和字符集为 MySQL 8 / `utf8mb4`;脚本只允许在目标业务库执行。
|
||
4. 生产组织同步凭据、AI 供应商留存/费用口径和正式试点窗口仍未闭合时,不要把迁移完成宣称为 BRD 验收完成。
|
||
|
||
## 执行顺序
|
||
|
||
在生产数据库连接上按以下顺序执行,每个脚本执行后记录退出码和关键表结构。所有脚本均设计为可重复执行,但仍应按顺序执行。
|
||
|
||
```bash
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_catalog_schema_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_brd_scenarios_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_brd_sop_seed_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_prompt_template_seed_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_candidate_interview_review_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_evidence_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_case_media_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_case_learning_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_practice_five_dimensions_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_practice_session_evidence_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_daily_drill_score_mode_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_sop_requester_identity_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_knowledge_feedback_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260716_knowledge_space_platform_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260714_org_hire_date_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260715_position_sop_qualification_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_practice_assignment_batch_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_learning_closure_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_practice_five_position_catalog_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260717_web_ai_question_reward_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_knowledge_conversation_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_release_collation_compat_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260718_unify_all_collations_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_service_memory_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_work_report_idempotency_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_web_ai_database_secret_mysql8.sql
|
||
mysql --default-character-set=utf8mb4 "$DB_NAME" < backend/script/sql/update/aihr_20260719_web_search_qwen_mysql8.sql
|
||
```
|
||
|
||
顺序原因:场景补充依赖三张场景/Rubric 基础表;五维迁移依赖场景和 Rubric;岗位/SOP/任务/资格迁移只补正式数据契约,不写业务行,也不代表岗位适用范围、任务规则或资格标准已经获得 HR 确认。住宅 SOP 和 Prompt 迁移只补内置内容,不代表内容已完成业务复核。知识会话迁移只新增短期上下文表,不生成业务对话。排序规则兼容迁移先对齐本批发布表,全量排序规则迁移再统一历史 `aihr_*` 表;两者都读取目标库 `aihr_knowledge_info.tenant_id` 的实际排序规则。服务记忆迁移创建候选、正式记录和版本留痕三张表;工作上报幂等迁移随后只补原表列与唯一索引。两者同样对齐目标排序规则,兼容历史生产库与全新 MySQL 8 数据库。
|
||
|
||
## 迁移后只读核验
|
||
|
||
执行后只读检查结构和内置内容,不修改业务数据:
|
||
|
||
```sql
|
||
SELECT table_name
|
||
FROM information_schema.tables
|
||
WHERE table_schema = DATABASE()
|
||
AND table_name IN (
|
||
'aihr_practice_scenario', 'aihr_practice_rubric', 'aihr_practice_rubric_dimension', 'aihr_learning_question',
|
||
'aihr_practice_audio', 'aihr_practice_audio_upload', 'aihr_practice_calibration',
|
||
'aihr_knowledge_gap', 'aihr_knowledge_answer_feedback',
|
||
'aihr_knowledge_space_grant', 'aihr_knowledge_app', 'aihr_knowledge_app_space',
|
||
'aihr_knowledge_query_log', 'aihr_knowledge_admin_audit', 'aihr_knowledge_conversation',
|
||
'aihr_practice_question_feedback',
|
||
'aihr_onboard_exam', 'aihr_onboard_exam_target', 'aihr_onboard_exam_question',
|
||
'aihr_onboard_exam_attempt', 'aihr_onboard_exam_answer',
|
||
'aihr_web_search_provider', 'aihr_web_ai_secret', 'aihr_web_ai_query_audit',
|
||
'aihr_community_question', 'aihr_community_answer',
|
||
'aihr_incentive_rule', 'aihr_points_ledger',
|
||
'aihr_candidate_material', 'aihr_interview_result', 'aihr_candidate_employee_link',
|
||
'aihr_position_responsibility', 'aihr_sop_applicability',
|
||
'aihr_onboard_task', 'aihr_qualification_gate',
|
||
'aihr_memory_candidate', 'aihr_service_memory', 'aihr_service_memory_version',
|
||
'aihr_work_report'
|
||
)
|
||
ORDER BY table_name;
|
||
|
||
SELECT table_name, column_name
|
||
FROM information_schema.columns
|
||
WHERE table_schema = DATABASE()
|
||
AND (
|
||
(table_name = 'aihr_case_record' AND column_name IN ('media_oss_id', 'media_url', 'learning_points', 'supervisor_comment'))
|
||
OR (table_name = 'aihr_practice_session' AND column_name IN ('dim_task_completion', 'dim_response_timeliness', 'response_latency_ms', 'annotations_json', 'review_advice', 'incentive_point', 'satisfaction_score', 'satisfaction_comment'))
|
||
OR (table_name = 'aihr_practice_scenario' AND column_name IN ('content_version', 'content_hash'))
|
||
OR (table_name = 'aihr_practice_assignment' AND column_name IN ('score', 'score_mode', 'due_date', 'request_key', 'content_version', 'question_snapshot', 'reference_answer_snapshot', 'content_hash'))
|
||
OR (table_name = 'aihr_sop_answer_review' AND column_name IN ('prompt_version', 'requester_ext_party_id'))
|
||
OR (table_name = 'aihr_knowledge_info' AND column_name IN ('code', 'space_type', 'sensitivity_level', 'status'))
|
||
OR (table_name = 'aihr_knowledge_upload_item' AND column_name = 'space_codes_json')
|
||
OR (table_name = 'aihr_org_snapshot' AND column_name = 'hire_date')
|
||
OR (table_name = 'aihr_candidate_material' AND column_name IN ('reviewer', 'reviewed_time'))
|
||
OR (table_name = 'aihr_interview_result' AND column_name IN ('questions_json', 'reviewed_score', 'review_status', 'reviewer', 'review_note', 'reviewed_time'))
|
||
OR (table_name = 'aihr_position_responsibility' AND column_name IN ('position_code', 'responsibility_text', 'sop_id', 'sop_code'))
|
||
OR (table_name = 'aihr_sop_applicability' AND column_name IN ('sop_id', 'sop_code', 'project_type', 'industry', 'org_scope'))
|
||
OR (table_name = 'aihr_onboard_task' AND column_name IN ('ext_party_id', 'position_code', 'task_type', 'status', 'assign_by'))
|
||
OR (table_name = 'aihr_qualification_gate' AND column_name IN ('ext_party_id', 'position_code', 'cert_id', 'passed', 'valid_thru'))
|
||
OR (table_name = 'aihr_work_report' AND column_name IN ('request_key', 'request_hash'))
|
||
OR (table_name = 'aihr_web_search_provider' AND column_name IN ('agent_id', 'agent_version'))
|
||
)
|
||
ORDER BY table_name, column_name;
|
||
|
||
SELECT COUNT(*) AS built_in_prompt_templates
|
||
FROM aihr_prompt_template
|
||
WHERE tenant_id = '000000'
|
||
AND code IN ('summary_card', 'sop_answer', 'mentor_rewrite', 'review_annotation', 'prep_card', 'supervision_image_analysis_maintenance');
|
||
|
||
SELECT position, COUNT(*) AS scenario_count, SUM(enabled = 0) AS disabled_pending_review
|
||
FROM aihr_practice_scenario
|
||
WHERE tenant_id = '000000'
|
||
GROUP BY position
|
||
ORDER BY position;
|
||
|
||
SELECT question_type, COUNT(*) AS question_count, SUM(enabled = 0) AS disabled_pending_review
|
||
FROM aihr_learning_question
|
||
WHERE tenant_id = '000000'
|
||
GROUP BY question_type
|
||
ORDER BY question_type;
|
||
```
|
||
|
||
预期是三张场景/Rubric 表、岗位题库、训练反馈表、五张考试表、三张全网检索表、问题榜/激励六张表、三张证据表、知识平台治理表、三张候选人/面试表、四张岗位/SOP/任务/资格契约表,以及三张项目服务记忆表和原工作上报表均存在;工作上报幂等字段为 `2/2`,会话证据字段为 `8/8`,专项批量/内容快照字段为 `8/8`,候选资料审核字段为 `2/2`,面试复核字段为 `6/6`,字段查询覆盖所有列,内置 Prompt 数量为 `6`。五个岗位各有 `20` 个场景,`daily/special` 各有 `100` 道题;其中 `14` 个未完成正式审核的高风险场景及其 `28` 道题保持禁用。岗位/SOP/任务/资格表及新服务记忆表为空是允许的;这只证明 schema/seed 迁移完成,不证明已有员工确认过记忆、完整个人知识库或严格试点完成。
|
||
|
||
## 迁移后应用回归
|
||
|
||
1. 重启 `wygj-aihr.service`,确认状态为 `active`。
|
||
2. 检查根站、`/h5/`、租户接口和员工首页 API 的 HTTP 与业务码均为 `200`。
|
||
3. 用正式测试账号完成员工端训练、主管复盘和管理端场景/Prompt 只读检查;不要使用开发固定验证码验证生产安全策略。
|
||
4. 用具备单一项目授权的正式测试账号,在“工作助手”说出一条包含房号、住户需求和跟进时间的测试信息;确认候选卡前数据库不得出现正式服务记录,确认后只能写入该账号已授权的 `project_code`。随后新开对话查询该事项,必须能召回;双击确认不得重复写入。测试结束保留审计证据,不直接删除生产业务记录。
|
||
5. 用员工测试账号进入“工作上报”,确认首屏为语音会话而非传统表单;完成一次整理、建议类型修改和显式确认。确认前 `aihr_work_report` 行数不变,重复提交同一 `request_key` 只有一条 `PENDING` 记录,“我的上报”返回同一 ID;普通员工审核仍为 `403`,审核通过不自动写入案例、知识或积分表。
|
||
6. 迁移完成后再按同一正式时间窗运行:
|
||
|
||
```bash
|
||
AIHR_PILOT_TENANT_ID=000000 \
|
||
AIHR_PILOT_START_DATE=YYYY-MM-DD \
|
||
AIHR_PILOT_END_DATE=YYYY-MM-DD \
|
||
AIHR_PILOT_STRICT=true ./scripts/demo-check.sh
|
||
```
|
||
|
||
严格门禁中的校准、SOP 可用率、完训率、满意度、正式组织身份和案例数量仍必须来自真实业务数据;不允许用本 runbook 的内置 seed 或开发库样本替代。
|