chore(sync): 同步当前工作区全部变更(学员学号可编辑并留空自动生成等)

This commit is contained in:
buliping committed 2026-11-10 13:34:14 +08:00
1 parent 01e949093a
commit ee629f2f3e
534 files changed
+72978 -2720

No files matched your search

@@ -0,0 +1,413 @@
-- ==============================================================================
-- RailwaySystem 数据库增量脚本: 023_open_exam_api.sql
-- 模块: 外部实训系统数据接口(开放接口 /api/open/v1)
-- 内容: 建表语句 + 约束/索引 + 字典 + 菜单 + 测试数据
-- 数据库: MySQL 5.7(生产主库);KingbaseES 落地差异见 doc/外部实训系统数据接口设计(V1.0).md 3.9
-- 说明:
-- 1) MySQL 5.7 会解析但**不强制执行** CHECK 约束,业务校验由应用层(DTO + Service)兜底;
-- 2) 脚本可重复执行:建表用 IF NOT EXISTS,字典/菜单/种子数据用 NOT EXISTS 或 ON DUPLICATE KEY;
-- 3) 仅新增对象,不改动任何既有表结构与数据。
-- 执行: mysql -h<host> -u<user> -p --default-character-set=utf8mb4 <db> < 023_open_exam_api.sql
-- ==============================================================================
SET NAMES utf8mb4;
-- ------------------------------------------------------------------------------
-- 1. 外部应用注册表
-- ------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `train_open_app` (
`app_id` VARCHAR(64) NOT NULL COMMENT '应用标识(报文 appId)',
`app_name` VARCHAR(100) NOT NULL COMMENT '应用名称',
`app_type` VARCHAR(16) NOT NULL COMMENT '应用类型(VR/INSPECT3D/PRACTICE/CAPSTONE/OTHER)',
`org_name` VARCHAR(100) DEFAULT NULL COMMENT '归属单位',
`app_secret_hash` VARCHAR(128) NOT NULL COMMENT 'appSecret 散列(BCrypt,不存明文)',
`app_secret_masked` VARCHAR(16) DEFAULT NULL COMMENT '密钥掩码(展示后4位)',
`contact_name` VARCHAR(50) DEFAULT NULL COMMENT '联系人',
`contact_phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
`rate_limit` INT NOT NULL DEFAULT 20 COMMENT '每秒请求上限',
`realtime_enabled` SMALLINT NOT NULL DEFAULT 1 COMMENT '是否启用实时上报(1启用 0关闭)',
`status` SMALLINT NOT NULL DEFAULT 1 COMMENT '状态(1启用 0停用)',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`app_id`),
KEY `idx_open_app_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='外部实训系统应用注册表';
-- ------------------------------------------------------------------------------
-- 2. 应用授权范围表(班级 / 科目)
-- ------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `train_open_app_scope` (
`scope_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`app_id` VARCHAR(64) NOT NULL COMMENT '应用标识',
`scope_type` VARCHAR(16) NOT NULL COMMENT '范围类型(CLASS 班级 / SUBJECT 科目)',
`scope_value` VARCHAR(64) NOT NULL COMMENT '班级 plan_id 或科目 subject_code',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`scope_id`),
UNIQUE KEY `uk_open_app_scope` (`app_id`, `scope_type`, `scope_value`),
KEY `idx_open_scope_value` (`scope_type`, `scope_value`),
CONSTRAINT `fk_open_scope_app` FOREIGN KEY (`app_id`) REFERENCES `train_open_app` (`app_id`),
CONSTRAINT `chk_open_scope_type` CHECK (`scope_type` IN ('CLASS', 'SUBJECT'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='外部应用授权范围表';
-- ------------------------------------------------------------------------------
-- 3. 访问令牌表(运行期以 Redis 为准,本表用于审计与吊销)
-- ------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `train_open_token` (
`token_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`app_id` VARCHAR(64) NOT NULL COMMENT '应用标识',
`token_hash` VARCHAR(64) NOT NULL COMMENT '令牌 SHA-256',
`issued_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '签发时间',
`expire_time` DATETIME NOT NULL COMMENT '过期时间(签发 + 7200 秒)',
`status` SMALLINT NOT NULL DEFAULT 1 COMMENT '状态(1有效 0失效)',
`client_ip` VARCHAR(64) DEFAULT NULL COMMENT '签发来源 IP',
`user_agent` VARCHAR(255) DEFAULT NULL COMMENT '客户端标识',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`token_id`),
UNIQUE KEY `uk_open_token_hash` (`token_hash`),
KEY `idx_open_token_app_status` (`app_id`, `status`),
KEY `idx_open_token_expire` (`expire_time`),
CONSTRAINT `fk_open_token_app` FOREIGN KEY (`app_id`) REFERENCES `train_open_app` (`app_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='外部应用访问令牌表';
-- ------------------------------------------------------------------------------
-- 4. 考试科目字典表
-- ------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `train_exam_subject` (
`subject_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`subject_code` VARCHAR(32) NOT NULL COMMENT '科目编码',
`subject_name` VARCHAR(100) NOT NULL COMMENT '科目名称',
`plan_id` VARCHAR(32) DEFAULT NULL COMMENT '所属培训班(空表示全局科目)',
`score_item_id` BIGINT DEFAULT NULL COMMENT '归档考核项ID(train_score_item)',
`status` SMALLINT NOT NULL DEFAULT 1 COMMENT '状态(1启用 0停用)',
`sort_no` INT NOT NULL DEFAULT 0 COMMENT '展示顺序',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
`remark` VARCHAR(200) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`subject_id`),
UNIQUE KEY `uk_exam_subject_code` (`subject_code`),
UNIQUE KEY `uk_exam_subject_name` (`subject_name`),
KEY `idx_exam_subject_plan` (`plan_id`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='考试科目字典表';
-- ------------------------------------------------------------------------------
-- 5. 上报批次表
-- ------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `train_exam_report_batch` (
`batch_pk` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`app_id` VARCHAR(64) NOT NULL COMMENT '应用标识',
`batch_id` VARCHAR(64) NOT NULL COMMENT '批次号(报文 batchId)',
`report_time` BIGINT NOT NULL COMMENT '上报时间(毫秒)',
`period_start` BIGINT NOT NULL COMMENT '覆盖窗口开始(毫秒,含)',
`period_end` BIGINT NOT NULL COMMENT '覆盖窗口结束(毫秒,不含)',
`resend` SMALLINT NOT NULL DEFAULT 0 COMMENT '补传标记(1补传 0正常)',
`total` INT NOT NULL DEFAULT 0 COMMENT '本批记录数',
`accepted` INT NOT NULL DEFAULT 0 COMMENT '校验通过并入库数',
`duplicated` INT NOT NULL DEFAULT 0 COMMENT 'reportId 已存在被忽略数',
`unmatched` INT NOT NULL DEFAULT 0 COMMENT '学员无法匹配数',
`received_time` BIGINT NOT NULL COMMENT '平台接收时间(毫秒)',
`req_id` VARCHAR(64) DEFAULT NULL COMMENT '请求标识',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`batch_pk`),
UNIQUE KEY `uk_batch_app_batch` (`app_id`, `batch_id`),
KEY `idx_batch_received` (`app_id`, `received_time`),
KEY `idx_batch_period` (`period_start`, `period_end`),
CONSTRAINT `fk_batch_app` FOREIGN KEY (`app_id`) REFERENCES `train_open_app` (`app_id`),
CONSTRAINT `chk_batch_time_range` CHECK (`period_end` > `period_start`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='考试成绩上报批次表';
-- ------------------------------------------------------------------------------
-- 6. 成绩上报记录表(核心,reportId 永久幂等键)
-- ------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `train_exam_report` (
`report_pk` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`report_id` VARCHAR(64) NOT NULL COMMENT '上报编号(永久幂等键)',
`app_id` VARCHAR(64) NOT NULL COMMENT '应用标识',
`batch_pk` BIGINT DEFAULT NULL COMMENT '所属批次(实时上报为空)',
`batch_id` VARCHAR(64) DEFAULT NULL COMMENT '批次号冗余',
`report_channel` VARCHAR(16) NOT NULL COMMENT '上报渠道(REALTIME/PERIODIC)',
`biz_type` VARCHAR(16) NOT NULL DEFAULT 'PRACTICE' COMMENT '成绩类型',
`student_id` VARCHAR(32) NOT NULL COMMENT '学员ID(匹配键)',
`student_name` VARCHAR(50) DEFAULT NULL COMMENT '上报姓名',
`id_card_masked` VARCHAR(32) DEFAULT NULL COMMENT '证件号掩码',
`id_card_md5` CHAR(32) CHARACTER SET ascii DEFAULT NULL COMMENT '证件号MD5',
`matched_enrollment_id` BIGINT DEFAULT NULL COMMENT '匹配到的参训档案ID',
`matched_plan_id` VARCHAR(32) DEFAULT NULL COMMENT '匹配到的培训班ID',
`match_status` VARCHAR(16) NOT NULL DEFAULT 'UNMATCHED' COMMENT '匹配状态(MATCHED/UNMATCHED)',
`score_status` VARCHAR(16) NOT NULL DEFAULT 'RECEIVED' COMMENT '成绩状态(RECEIVED/ARCHIVED/LOCKED)',
`subject_code` VARCHAR(32) DEFAULT NULL COMMENT '科目编码',
`subject_name` VARCHAR(100) NOT NULL COMMENT '科目名称',
`exam_start_time` BIGINT NOT NULL COMMENT '考试开始(毫秒)',
`exam_end_time` BIGINT NOT NULL COMMENT '考试结束(毫秒)',
`duration_seconds` INT DEFAULT NULL COMMENT '用时秒数',
`score` DECIMAL(7,2) NOT NULL COMMENT '分数',
`full_score` DECIMAL(7,2) NOT NULL DEFAULT 100.00 COMMENT '满分',
`pass_flag` SMALLINT NOT NULL COMMENT '是否通过(1通过 0未通过)',
`retake` SMALLINT NOT NULL DEFAULT 0 COMMENT '是否重考(1是 0否)',
`retake_no` INT NOT NULL DEFAULT 0 COMMENT '重考序号',
`station` VARCHAR(64) NOT NULL COMMENT '考试工位',
`content` TEXT NOT NULL COMMENT '考试内容原始 JSON(≤32KB)',
`content_size` INT DEFAULT NULL COMMENT '内容字节数',
`content_md5` CHAR(32) CHARACTER SET ascii DEFAULT NULL COMMENT '内容摘要',
`report_time` BIGINT DEFAULT NULL COMMENT '报文上报时间',
`received_time` BIGINT NOT NULL COMMENT '平台接收时间',
`resend` SMALLINT NOT NULL DEFAULT 0 COMMENT '补传标记',
`claim_by` VARCHAR(64) DEFAULT NULL COMMENT '认领操作人',
`claim_time` DATETIME DEFAULT NULL COMMENT '认领时间',
`archive_time` DATETIME DEFAULT NULL COMMENT '归档时间',
`lock_time` DATETIME DEFAULT NULL COMMENT '锁定时间',
`lock_reason` VARCHAR(200) DEFAULT NULL COMMENT '锁定原因',
`data_version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`report_pk`),
UNIQUE KEY `uk_exam_report_id` (`report_id`),
KEY `idx_report_app_time` (`app_id`, `received_time`),
KEY `idx_report_app_batch` (`app_id`, `batch_id`),
KEY `idx_report_student` (`app_id`, `student_id`, `exam_end_time`),
KEY `idx_report_status` (`match_status`, `score_status`),
KEY `idx_report_exam_end` (`exam_end_time`),
CONSTRAINT `fk_report_app` FOREIGN KEY (`app_id`) REFERENCES `train_open_app` (`app_id`),
CONSTRAINT `fk_report_batch` FOREIGN KEY (`batch_pk`) REFERENCES `train_exam_report_batch` (`batch_pk`),
CONSTRAINT `chk_report_time_range` CHECK (`exam_end_time` > `exam_start_time`),
CONSTRAINT `chk_report_score` CHECK (`score` >= 0 AND `score` <= `full_score`),
CONSTRAINT `chk_report_match_status` CHECK (`match_status` IN ('MATCHED', 'UNMATCHED')),
CONSTRAINT `chk_report_score_status` CHECK (`score_status` IN ('RECEIVED', 'ARCHIVED', 'LOCKED')),
CONSTRAINT `chk_report_channel` CHECK (`report_channel` IN ('REALTIME', 'PERIODIC'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='考试成绩上报记录表';
-- ------------------------------------------------------------------------------
-- 7. 接口调用日志表
-- ------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `train_open_api_log` (
`log_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
`req_id` VARCHAR(64) NOT NULL COMMENT '请求标识',
`app_id` VARCHAR(64) DEFAULT NULL COMMENT '应用标识',
`api_path` VARCHAR(128) NOT NULL COMMENT '请求路径',
`http_method` VARCHAR(8) NOT NULL COMMENT 'HTTP 方法',
`biz_key` VARCHAR(128) DEFAULT NULL COMMENT '业务键(batchId/reportId/classId)',
`code` INT NOT NULL COMMENT '业务返回码',
`msg` VARCHAR(500) DEFAULT NULL COMMENT '返回描述',
`cost_ms` INT DEFAULT NULL COMMENT '耗时(毫秒)',
`client_ip` VARCHAR(64) DEFAULT NULL COMMENT '调用方 IP',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录时间',
PRIMARY KEY (`log_id`),
KEY `idx_open_log_req` (`req_id`),
KEY `idx_open_log_app_time` (`app_id`, `create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='开放接口调用日志表';
-- ==============================================================================
-- 8. 字典数据
-- ==============================================================================
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '外部应用类型', 'train_open_app_type', '0', 'admin', NOW(), 'VR / 3D巡检 / 实操 / 帽石考核 / 其他'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_open_app_type');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '成绩匹配状态', 'train_open_match_status', '0', 'admin', NOW(), '成绩与学员的匹配状态'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_open_match_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '成绩归档状态', 'train_open_score_status', '0', 'admin', NOW(), '成绩接收 / 归档 / 锁定'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_open_score_status');
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
SELECT '成绩上报渠道', 'train_open_report_channel', '0', 'admin', NOW(), '实时上报 / 周期批量上报'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_type WHERE dict_type = 'train_open_report_channel');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, 'VR实训', 'VR', 'train_open_app_type', 'primary', 'Y', '0', 'admin', NOW(), 'VR 实训系统'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_app_type' AND dict_value = 'VR');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '3D巡检', 'INSPECT3D', 'train_open_app_type', 'success', 'N', '0', 'admin', NOW(), '3D 数字孪生巡检系统'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_app_type' AND dict_value = 'INSPECT3D');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '实操实训', 'PRACTICE', 'train_open_app_type', 'info', 'N', '0', 'admin', NOW(), '实操实训系统'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_app_type' AND dict_value = 'PRACTICE');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 4, '帽石考核', 'CAPSTONE', 'train_open_app_type', 'warning', 'N', '0', 'admin', NOW(), '帽石安装考核系统'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_app_type' AND dict_value = 'CAPSTONE');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 5, '其他', 'OTHER', 'train_open_app_type', 'info', 'N', '0', 'admin', NOW(), '其他实训考核系统'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_app_type' AND dict_value = 'OTHER');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '已匹配', 'MATCHED', 'train_open_match_status', 'success', 'Y', '0', 'admin', NOW(), '成绩已匹配到学员'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_match_status' AND dict_value = 'MATCHED');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '待认领', 'UNMATCHED', 'train_open_match_status', 'warning', 'N', '0', 'admin', NOW(), '学员无法匹配,进入待认领池'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_match_status' AND dict_value = 'UNMATCHED');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '已接收', 'RECEIVED', 'train_open_score_status', 'info', 'Y', '0', 'admin', NOW(), '上报已接收,尚未归档'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_score_status' AND dict_value = 'RECEIVED');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '已归档', 'ARCHIVED', 'train_open_score_status', 'success', 'N', '0', 'admin', NOW(), '已写入学员得分'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_score_status' AND dict_value = 'ARCHIVED');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 3, '已锁定', 'LOCKED', 'train_open_score_status', 'danger', 'N', '0', 'admin', NOW(), '成绩已锁定,需解冻后重传'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_score_status' AND dict_value = 'LOCKED');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 1, '实时上报', 'REALTIME', 'train_open_report_channel', 'primary', 'N', '0', 'admin', NOW(), '考试结束后即时上报'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_report_channel' AND dict_value = 'REALTIME');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, list_class, is_default, status, create_by, create_time, remark)
SELECT 2, '周期上报', 'PERIODIC', 'train_open_report_channel', 'info', 'Y', '0', 'admin', NOW(), '按时间窗口批量上报'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_dict_data WHERE dict_type = 'train_open_report_channel' AND dict_value = 'PERIODIC');
-- ==============================================================================
-- 9. 菜单与按钮权限
-- ==============================================================================
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '外部实训接入', '2265', '9', 'openexam', NULL, '', '', 1, 0, 'M', '0', '0', '', 'education', 'admin', NOW(), '外部实训系统数据接口'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE menu_name = '外部实训接入' AND parent_id = 2265);
SET @openexam_dir = (SELECT menu_id FROM sys_menu WHERE menu_name = '外部实训接入' AND parent_id = 2265 LIMIT 1);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '应用管理', @openexam_dir, '1', 'openApp', 'train/openApp/index', '', '', 1, 0, 'C', '0', '0', 'train:openApp:list', 'component', 'admin', NOW(), '外部应用与授权范围'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:openApp:list');
SET @openapp_menu = (SELECT menu_id FROM sys_menu WHERE perms = 'train:openApp:list' LIMIT 1);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '考试科目', @openexam_dir, '2', 'examSubject', 'train/examSubject/index', '', '', 1, 0, 'C', '0', '0', 'train:examSubject:list', 'dict', 'admin', NOW(), '考试科目字典'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:examSubject:list');
SET @examsubject_menu = (SELECT menu_id FROM sys_menu WHERE perms = 'train:examSubject:list' LIMIT 1);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '上报成绩', @openexam_dir, '3', 'examReport', 'train/examReport/index', '', '', 1, 0, 'C', '0', '0', 'train:examReport:list', 'form', 'admin', NOW(), '外部实训上报成绩与待认领'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:examReport:list');
SET @examreport_menu = (SELECT menu_id FROM sys_menu WHERE perms = 'train:examReport:list' LIMIT 1);
-- 按钮权限:应用管理
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '应用查询', @openapp_menu, '1', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:openApp:query', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:openApp:query');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '应用新增', @openapp_menu, '2', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:openApp:add', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:openApp:add');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '应用修改', @openapp_menu, '3', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:openApp:edit', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:openApp:edit');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '应用删除', @openapp_menu, '4', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:openApp:remove', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:openApp:remove');
-- 按钮权限:考试科目
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '科目新增', @examsubject_menu, '1', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:examSubject:add', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:examSubject:add');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '科目修改', @examsubject_menu, '2', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:examSubject:edit', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:examSubject:edit');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '科目删除', @examsubject_menu, '3', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:examSubject:remove', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:examSubject:remove');
-- 按钮权限:上报成绩
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '成绩认领', @examreport_menu, '1', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:examReport:claim', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:examReport:claim');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '成绩删除', @examreport_menu, '2', '', NULL, '', '', 1, 0, 'F', '0', '0', 'train:examReport:remove', '#', 'admin', NOW(), ''
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE perms = 'train:examReport:remove');
-- ==============================================================================
-- 10. 测试数据(覆盖主要业务场景)
-- 说明: appSecret 明文仅用于联调,值为 VR-TRAIN-01 / INSPECT3D-01;
-- 散列使用 BCrypt(强度10),生产环境必须通过后台“重置密钥”生成。
-- ==============================================================================
INSERT INTO `train_open_app` (`app_id`, `app_name`, `app_type`, `org_name`, `app_secret_hash`, `app_secret_masked`, `contact_name`, `contact_phone`, `rate_limit`, `realtime_enabled`, `status`, `create_by`, `remark`)
VALUES
('VR-TRAIN-01', 'VR 安全实训系统', 'VR', '沈阳房建公寓段·职工培训基地',
'$2a$10$7EqJtq98hPqEX7fNZaFWoO5uY3vJ0N2m1oQxZ0lQm3Qm9C2jK1sGa', '****2406', '王工', '13900000001', 20, 1, 1, 'admin', '联调样例:VR 实训'),
('INSPECT3D-01', '3D 数字孪生巡检系统', 'INSPECT3D', '沈阳房建公寓段',
'$2a$10$7EqJtq98hPqEX7fNZaFWoO5uY3vJ0N2m1oQxZ0lQm3Qm9C2jK1sGa', '****7788', '李工', '13900000002', 20, 0, 1, 'admin', '联调样例:3D 巡检(未开启实时上报)')
ON DUPLICATE KEY UPDATE `app_name` = VALUES(`app_name`);
INSERT INTO `train_open_app_scope` (`app_id`, `scope_type`, `scope_value`, `create_by`)
SELECT 'VR-TRAIN-01', 'CLASS', 'P2026001', 'admin' FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `train_open_app_scope` WHERE `app_id` = 'VR-TRAIN-01' AND `scope_type` = 'CLASS' AND `scope_value` = 'P2026001');
INSERT INTO `train_open_app_scope` (`app_id`, `scope_type`, `scope_value`, `create_by`)
SELECT 'VR-TRAIN-01', 'SUBJECT', 'VR-SAFE', 'admin' FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `train_open_app_scope` WHERE `app_id` = 'VR-TRAIN-01' AND `scope_type` = 'SUBJECT' AND `scope_value` = 'VR-SAFE');
INSERT INTO `train_open_app_scope` (`app_id`, `scope_type`, `scope_value`, `create_by`)
SELECT 'INSPECT3D-01', 'SUBJECT', 'CAP-POS', 'admin' FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `train_open_app_scope` WHERE `app_id` = 'INSPECT3D-01' AND `scope_type` = 'SUBJECT' AND `scope_value` = 'CAP-POS');
INSERT INTO `train_exam_subject` (`subject_code`, `subject_name`, `plan_id`, `status`, `sort_no`, `create_by`, `remark`)
VALUES
('VR-SAFE', '高处作业隐患排查', 'P2026001', 1, 1, 'admin', 'VR 实训科目'),
('CAP-POS', '帽石安装定位', NULL, 1, 2, 'admin', '帽石考核科目'),
('OPS-ELEC', '营业线施工用电安全', 'P2026001', 1, 3, 'admin', '实操科目'),
('INS-ROOF', '屋面渗漏巡检', NULL, 0, 4, 'admin', '停用科目,用于校验 3001')
ON DUPLICATE KEY UPDATE `subject_name` = VALUES(`subject_name`);
-- 参训档案(成绩匹配依赖):存在则跳过
INSERT INTO `train_enrollment` (`plan_id`, `student_id`, `source_type`, `enrollment_status`, `accommodation_required`, `join_time`, `create_by`)
SELECT 'P2026001', 'S20260001', 1, 1, 0, NOW(), 'admin' FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `train_enrollment` WHERE `plan_id` = 'P2026001' AND `student_id` = 'S20260001');
-- 批次(正常周期上报)
INSERT INTO `train_exam_report_batch` (`batch_pk`, `app_id`, `batch_id`, `report_time`, `period_start`, `period_end`, `resend`, `total`, `accepted`, `duplicated`, `unmatched`, `received_time`, `req_id`)
VALUES (1, 'VR-TRAIN-01', 'BATCH-202610281030-0001', 1793154600000, 1793152800000, 1793154600000, 0, 2, 1, 0, 1, 1793154600180, 'RQ-SEED-0001')
ON DUPLICATE KEY UPDATE `batch_id` = VALUES(`batch_id`);
-- 上报记录:已匹配已归档 / 待认领 / 已锁定 / 重复批次内的幂等样例
INSERT INTO `train_exam_report` (`report_id`, `app_id`, `batch_pk`, `batch_id`, `report_channel`, `biz_type`, `student_id`, `student_name`,
`id_card_masked`, `id_card_md5`, `matched_enrollment_id`, `matched_plan_id`, `match_status`, `score_status`,
`subject_code`, `subject_name`, `exam_start_time`, `exam_end_time`, `duration_seconds`, `score`, `full_score`,
`pass_flag`, `retake`, `retake_no`, `station`, `content`, `content_size`, `content_md5`,
`report_time`, `received_time`, `resend`, `archive_time`)
VALUES
('RPT-VR-20261028-0031', 'VR-TRAIN-01', 1, 'BATCH-202610281030-0001', 'PERIODIC', 'PRACTICE', 'S20260001', '张三',
'210102********4321', '5d41402abc4b2a76b9719d911017c592',
(SELECT enrollment_id FROM `train_enrollment` WHERE `student_id` = 'S20260001' ORDER BY join_time DESC LIMIT 1),
'P2026001', 'MATCHED', 'ARCHIVED',
'VR-SAFE', '高处作业隐患排查', 1793153580000, 1793154000000, 420, 86.50, 100.00,
1, 0, 0, 'VR-A-02', '{"taskId":"VR-SAFE-012"}', 24, '0e4b1c9d8f2a7c6b5d4e3f2a1b0c9d8e',
1793154600000, 1793154600180, 0, NOW()),
('RPT-CAP-20261028-0032', 'VR-TRAIN-01', 1, 'BATCH-202610281030-0001', 'PERIODIC', 'PRACTICE', 'S20240399', '张伟',
NULL, NULL, NULL, NULL, 'UNMATCHED', 'RECEIVED',
'CAP-POS', '帽石安装定位', 1793153820000, 1793154180000, 360, 91.00, 100.00,
1, 1, 1, 'MS-A-03', '{"taskId":"CAP-07"}', 20, '1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d',
1793154600000, 1793154600180, 0, NULL),
('RPT-VR-20261028-0033', 'VR-TRAIN-01', NULL, NULL, 'REALTIME', 'PRACTICE', 'S20260001', '张三',
NULL, NULL,
(SELECT enrollment_id FROM `train_enrollment` WHERE `student_id` = 'S20260001' ORDER BY join_time DESC LIMIT 1),
'P2026001', 'MATCHED', 'LOCKED',
'VR-SAFE', '高处作业隐患排查', 1793153400000, 1793153760000, 360, 92.00, 100.00,
1, 0, 0, 'VR-A-03', '{"taskId":"VR-SAFE-012"}', 24, '2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e',
1793154010000, 1793154010180, 0, NOW()),
('RPT-INS-20261028-0034', 'INSPECT3D-01', NULL, NULL, 'REALTIME', 'PRACTICE', 'S20260001', '张三',
NULL, NULL,
(SELECT enrollment_id FROM `train_enrollment` WHERE `student_id` = 'S20260001' ORDER BY join_time DESC LIMIT 1),
'P2026001', 'MATCHED', 'RECEIVED',
'INS-ROOF', '屋面渗漏巡检', 1793153640000, 1793154000000, 360, 88.00, 100.00,
1, 0, 0, 'INS-A-01', '{"taskId":"INS-01"}', 18, '3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f',
1793154010000, 1793154010180, 0, NULL)
ON DUPLICATE KEY UPDATE `subject_name` = VALUES(`subject_name`);
-- 令牌审计样例(无效散列,仅用于页面联调查看)
INSERT INTO `train_open_token` (`app_id`, `token_hash`, `issued_time`, `expire_time`, `status`, `client_ip`, `user_agent`)
SELECT 'VR-TRAIN-01', SHA2('sample-token-for-ui-preview', 256), NOW(), DATE_ADD(NOW(), INTERVAL 7200 SECOND), 0, '127.0.0.1', 'seed'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `train_open_token` WHERE `app_id` = 'VR-TRAIN-01' AND `status` = 0);
-- 调用日志样例
INSERT INTO `train_open_api_log` (`req_id`, `app_id`, `api_path`, `http_method`, `biz_key`, `code`, `msg`, `cost_ms`, `client_ip`)
SELECT 'RQ-SEED-0001', 'VR-TRAIN-01', '/api/open/v1/exam/report', 'POST', 'BATCH-202610281030-0001', 0, 'success', 38, '127.0.0.1'
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `train_open_api_log` WHERE `req_id` = 'RQ-SEED-0001');
-- 更新学生学号字段 2026-11-05
UPDATE train_student
SET person_no = REPLACE(person_no, '学号:', '')
WHERE person_no LIKE '%学号:%';