状态:P0 已落地(待联调验收)
更新时间:2026-08-18
入口页面:/student/classmsg/majormsg(专业信息)
背景:生产环境存在重复专业定义(如「中药」与「中药(技师)」),需将源专业下班级/学生及业务当前归属合并到目标专业。
| 项 | 路径 |
|---|---|
| 预览/合并 API | POST /xg/major/merge/preview、POST /xg/major/merge |
| Controller | ruoyi-xg/.../XgMajorMergeController.java |
| Service | ruoyi-xg/.../XgMajorMergeServiceImpl.java |
| 前端 | web/src/views/xg/xsjcxx/classmsg/majormsg/index.vue |
| DDL | docs/设计文档/database/专业信息管理/专业合并快照表结构.sql |
| 权限 | docs/设计文档/database/专业信息管理/专业合并菜单权限.sql |
在专业信息界面多选专业 → 点击【专业合并】→ 弹窗从已选中指定一个目标专业 → 其余源专业的当前归属数据全部改挂到目标专业 → 源专业停用。
| 规则 | 结论 |
|---|---|
| 班级处理 | 班级整体挂到目标专业;班级 ID 不变;同名班不合并 |
| 学生处理 | 只改专业归属,不改班级 |
| 源专业处理后 | 本版仅 停用(status=1),是否逻辑删除后续再定 |
| 学院约束 | 仅允许同一学院下的专业互相合并 |
| 历史快照 | 转班等「原专业/新专业」历史记录 不回写 |
凡表示「当前专业归属」的字段,major_id 与 major_name(以及存在的 major_code)必须成对同步更新。
禁止只改 ID、不改名称:列表/导出/筛选会出现「ID 已是中药、名称仍显示中药(技师)」的异常。
更新条件统一为:
WHERE major_id IN (源专业ID列表)
SET major_id = 目标专业ID,
major_name = 目标专业名称
-- 若表有 major_code / major_dept_id,一并改为目标专业对应值
| 序号 | 表 | 动作 | ID / 名称字段 |
|---|---|---|---|
| 1 | sys_dept(班级节点 bj-*) |
parent_id 改为目标专业;重算 ancestors |
无冗余名称 |
| 2 | sys_dept(源专业 zy-*) |
status = '1'(停用) |
dept_name 保留作追溯 |
| 3 | xg_class_msg |
当前归属改挂 | major_id + major_name 同改 |
| 4 | xg_student_msg |
当前归属改挂 | major_id + major_name 同改 |
| 5 | xg_student_manager_source |
data_id 若等于源专业 ID,改为目标专业 ID |
无名称字段 |
说明:sys_user.dept_id 本系统学生通常挂班级;合并时班级 ID 不变则用户表一般无需改。合并前应用 SQL 探测是否存在 dept_id IN (源专业ID),有则改为目标专业 ID。
原则:有 ID 改 ID,有名称改名称,有编码改编码;能按 ID 定位的优先按 ID。
| 模块 | 表 | 同改字段 | 备注 |
|---|---|---|---|
| 请假 | xg_student_leave |
major_id + major_name |
|
| 销假 | xg_student_cancel_leave |
major_id + major_name |
|
| 宿舍申请 | xg_dorm_application |
major_id + major_name |
|
| 床位分配 | xg_dorm_bed_student |
major_id |
无名称字段 |
| 卫生评分 | xg_dorm_hygiene_student_score |
major_id + major_name |
|
| 查寝明细 | xg_dorm_roll_call_detail |
major_id + major_name |
|
| 违纪 | xg_dorm_violation |
major_id + major_name |
|
| 假期留宿 | xg_holiday_stay |
major_id + major_name |
|
| 信息收集批次班级 | xg_stu_col_batch_class |
major_id + major_name |
|
| 学生信息批次 | xg_student_msg_batch |
major_id + major_name |
|
| 心理测评 | xljk_evaluation |
major_id + major_name |
|
| 考核申请 | xskh_assessment_application |
major_id + major_name |
|
| 考核审批 | xskh_assessment_application_approval |
major_id + major_name |
|
| 处分 | xskh_discipline_record |
major_id + major_name |
|
| 奖励 | xskh_reward_record |
major_id + major_name |
|
| 贫困认定 | jcbt_poverty_application |
major_id + major_name(+major_code 若有) |
|
| 奖学金申请 | jcbt_scholarship_application |
major_id |
无名称字段 |
| 勤工助学 | jcbt_work_study_application |
仅有 major_name |
见 §2.4 特殊处理 |
| 毕业生 | zsjy_graduate_info |
major_id + major_name |
|
| 实习 | zsjy_internship_info |
major_id |
无名称字段 |
| 招生专业配置 | zsjy_enrollment_major |
major_dept_id + major_name + major_code |
特殊:存在 uk_major_dept 唯一键;目标已有配置则逻辑删除源行,否则改挂 |
| 表 | 动作 |
|---|---|
jcbt_scholarship_quota |
target_dept_id 若等于源专业 ID → 改为目标专业 ID(注意唯一约束冲突时需合并名额行) |
sys_role_dept |
dept_id 若等于源专业 ID → 改为目标专业 ID(注意主键冲突去重) |
| 表 | 策略 |
|---|---|
jcbt_work_study_application |
仅有 major_name:按源专业名称精确匹配更新为目标专业名称;合并前日志记录影响行数 |
xg_student_class_change |
不更新(original_* / new_* 为历史真相) |
*_his、备份表、sys_oper_log |
不更新 |
cx_zysj / cx_bjsj / cx_xssj |
本版不更新;文档标注超星同步回写风险,P2 做映射 |
zsjy_major_info / his_* |
非学工主链路组织树;本版不改,避免误伤招生目录 |
以下按 class_id 关联,班级 ID 不变则无需因合并改行(除非另有 major_id):
xg_conduct_scorexg_class_pos_msgxg_class_teacher_counselor_rel合并后统计「按专业」时走班级 → xg_class_msg.major_id,因此班级主数据改对即可。
不要散落多处手写 UPDATE。在服务内维护白名单清单:
MajorMergeTableSpec {
tableName
idColumn // major_id / major_dept_id / data_id / target_dept_id / dept_id
nameColumn? // major_name,可空
codeColumn? // major_code,可空
extraWhere? // 如 deleted=0
strategy // BY_ID | BY_NAME_ONLY | DEPT_TREE | SKIP_HISTORY
}
合并服务循环执行:
COUNT(源专业ID) == 0,否则整单回滚并报错对每个候选源专业 ID,输出各表命中行数;发现「新表有 major_id 但不在白名单」时,禁止上线,先补清单。
可维护仓库脚本:test/专业合并影响行数预检.sql(开发阶段补充)。
IF 表同时存在 major_id 与 major_name:
禁止只更新其中一个
IF 仅存在 major_name:
使用源专业名称精确匹配更新(并记录日志)
IF 仅存在 major_id:
只更新 ID
目标专业名称取自合并时刻 sys_dept.dept_name(目标节点),保证与组织树一致。
| 方式 | 结论 |
|---|---|
整表 CREATE TABLE AS SELECT |
不采用:学生/业务表体量大,每次合并磁盘与耗时不可控 |
| 仅事务回滚 | 不够:合并成功提交后若发现选错目标专业,事务已结束无法回退 |
| 只备份将被修改的行 | 采用:体积小、可按 merge_batch_id 追溯与回滚 |
1)合并批次主表 xg_major_merge_batch
| 字段 | 说明 |
|---|---|
id |
雪花 ID,即 merge_batch_id |
target_major_id / target_major_name |
目标专业 |
source_major_ids / source_major_names |
JSON,源专业列表 |
college_id |
所属学院 |
status |
SUCCESS / FAILED / ROLLED_BACK |
summary_json |
各表更新行数摘要 |
operator_id / create_time 等 |
标准审计字段 |
2)行快照表 xg_major_merge_row_snapshot
| 字段 | 说明 |
|---|---|
id |
雪花 ID |
merge_batch_id |
关联批次 |
table_name |
原表名,如 xg_student_msg |
row_pk |
原表主键值(字符串,兼容联合主键时用约定拼接) |
row_json |
更新前整行 JSON(或至少还原所需列:专业相关字段 + 主键 + 组织树字段) |
create_time |
快照时间 |
说明:sys_dept 班级改挂、源专业停用前,同样写入快照(记录改前的 parent_id/ancestors/status)。
1. 校验参数与同学院
2. 生成 merge_batch_id,写入 batch 主表(status 可先 SUCCESS 前用 RUNNING)
3. 按白名单:把「即将被 UPDATE 的行」INSERT 进 row_snapshot
4. 再执行各表 UPDATE / 组织树改挂 / 停用源专业
5. 复查无源专业 ID 残留
6. 更新 batch.summary_json、status=SUCCESS
失败则事务回滚(含已写入的本批快照);若需「成功后回滚」见下
事务策略建议:
merge_batch_id 读取 row_json 回写原表(P0 至少落库快照并文档化手工回滚步骤;P1 再做一键 rollback API)。sys_role_dept / 名额表若发生「删源行 + 合并到目标行」,快照需记录删除前整行,回滚逻辑单独处理。POST /system/dept/major/merge/preview
权限: majormsg:major:merge
Body:
{
"targetMajorId": 20251223133304109,
"sourceMajorIds": [20251223133304116]
}
Response data:
{
"targetMajorId": ...,
"targetMajorName": "中药",
"sources": [
{
"majorId": ...,
"majorName": "中药(技师)",
"classCount": 1,
"studentCount": 50,
"tableHits": [
{ "table": "xg_student_leave", "rows": 0 },
...
]
}
],
"warnings": ["同学院校验通过", "..."]
}
POST /system/dept/major/merge
权限: majormsg:major:merge
Body: 同 preview
Response data:
{
"success": true,
"mergeBatchId": 2089...,
"movedClassCount": 1,
"updatedStudentCount": 50,
"disabledMajorCount": 1,
"snapshotRowCount": 120,
"tableUpdateSummary": [ { "table": "xg_class_msg", "updated": 1 }, ... ]
}
校验失败示例:
majormsg:major:mergemajormsg:major:* 一并配置 SQLmajormsg/index.vue)v-hasPermi="['majormsg:major:merge']")selection.length < 2 时禁用| 项 | 说明 |
|---|---|
| 模块位置 | 建议放在系统部门能力旁:SysDept 相关 Service,或学工模块包装后调部门服务;事务边界在同一服务方法 |
| 事务 | @Transactional(rollbackFor = Exception.class) |
| 祖先路径 | 班级 ancestors = 目标专业.ancestors + ',' + 目标专业.deptId |
| 操作日志 | @Log 记录目标/源/摘要 |
| 幂等 | 源专业已停用且无子班级时可提示「无需合并」 |
伪代码顺序:
1. 校验参数与同学院
2. 创建 merge_batch,按白名单把「将改行」写入 xg_major_merge_row_snapshot
3. 更新各业务表白名单(ID+名称同改)
4. 更新 xg_student_msg / xg_class_msg
5. 更新 sys_dept 班级 parent/ancestors
6. 处理 manager_source / role_dept / scholarship_quota
7. 停用源专业
8. 复查 COUNT(源ID)==0,否则抛错回滚
9. 回写 batch 摘要与 SUCCESS;接口返回 mergeBatchId
| 阶段 | 内容 | 验收 |
|---|---|---|
| P0 | 前端按钮+弹窗;preview+merge API;行级快照表+批次表;sys_dept+xg_class_msg+xg_student_msg+manager_source;白名单业务表 ID+名称同改;源专业停用;权限+日志;文档化按 batch 手工回滚步骤 |
用「中药(技师)→中药」演练:班级挂对、学生名称变「中药」、源专业停用、复查无残留;快照行数与更新行数一致 |
| P1 | 预检脚本入库;名额/角色冲突处理增强;一键按 mergeBatchId 回滚 API;合并结果导出 | 成功合并后可一键恢复到合并前 |
| P2 | 超星同步映射;可选逻辑删除源专业;快照归档策略 | 同步不回写重复专业 |
xg_student_msg、xg_student_leave 等 major_id 与 major_name 一致xg_student_class_change 历史行不变docs/开发文档/API/ 下增补「专业合并」章节(或写入专业信息管理 API)docs/设计文档/database/| 问题 | 默认建议 |
|---|---|
| 勤工助学仅名称字段误伤同名? | 精确匹配源 dept_name;合并前 preview 展示行数人工确认 |
| 奖学金名额行冲突 | 目标已有名额则累加 total_quota 后删源行,或报错人工处理(P0 可报错) |
| 超星 | P0 合并后口头告知运营:勿从超星再灌该源专业码 |
| 快照保留多久 | 默认 180 天,可配置 |
请确认后进入开发:
majormsg:major:merge 同意确认后按本计划实现前后端与预检 SQL。