接入医科大开放平台数据同步
新增 com.gxwebsoft.gxmu.openplat:开放平台客户端(oauth 鉴权、token 缓存与失效 重取、分页拉取)、部门/教师/班级/学生四段同步、异步编排与手动同步接口、学生认领、 学生名册查询与导出、每天 03:30 的定时任务、同步记录。 关键设计: - 名册与登录账号分离,学生通过「认领」(手机号自动 / 学号姓名自助)关联 sys_user - 组织数据以 sys_organization 为唯一载体,gxmu_college 废弃 - 平台无可用增量字段,只做全量拉取 + 幂等 upsert - 上游消失只停用不删除,且只作用于同步来源的记录(避免误停用人工维护的研究生班) - 平台权威字段按白名单覆盖,人工字段不触碰 - 码表可配置并保留原始码,码值实测存在两套编码混用,不做硬编码 - 手动同步为异步:提交返回批次号,前端轮询 /status 看分段进度 性能: - 班级/部门由逐条 update 改为多行 upsert,班级 1584 条 41s -> 0.19s - 码表解析加进程内缓存,学生同步由 >17 分钟不完成 -> 170 秒完成 98187 行 迁移脚本需手动执行(可重复执行): sql/20260912_create_openplat_sync_tables.sql sql/20260912_add_openplat_menu.sql 离线单测 43 个;另有联网集成测试 OpenPlatformClientIT(需 -Dopenplat.it=true)。
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
-- ============================================================================
|
||||
-- 开放平台数据同步:后台菜单与按钮权限
|
||||
-- 参见 .scratch/openplat-sync/issues/14-menu-permission-sql.md
|
||||
-- 可重复执行
|
||||
--
|
||||
-- 说明:后台菜单是动态从 sys_menu 读取的(GET /auth/user -> formatMenus -> addRoute)。
|
||||
-- menu_type=1 的记录不生成路由,只作按钮权限。
|
||||
-- path 与 component 填相同值,组件按 import.meta.glob('/src/views/**/index.vue') 解析。
|
||||
-- ============================================================================
|
||||
|
||||
-- 菜单:数据同步
|
||||
INSERT INTO `sys_menu`
|
||||
(`menu_id`, `parent_id`, `title`, `path`, `component`, `menu_type`, `sort_number`,
|
||||
`authority`, `hide`, `app_id`, `deleted`, `merchant_code`, `tenant_id`)
|
||||
SELECT 14400, 8574, '数据同步', '/gxmu/dataSync', '/gxmu/dataSync', 0, 100,
|
||||
'', 0, 0, 0, '', 10049
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` m WHERE m.`menu_id` = 14400);
|
||||
|
||||
-- 按钮:手动同步
|
||||
INSERT INTO `sys_menu`
|
||||
(`menu_id`, `parent_id`, `title`, `path`, `component`, `menu_type`, `sort_number`,
|
||||
`authority`, `hide`, `app_id`, `deleted`, `merchant_code`, `tenant_id`)
|
||||
SELECT 14401, 14400, '手动同步', '', '', 1, 1,
|
||||
'gxmu:sync:manual', 0, 0, 0, '', 10049
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` m WHERE m.`menu_id` = 14401);
|
||||
|
||||
-- 菜单:学生名册
|
||||
INSERT INTO `sys_menu`
|
||||
(`menu_id`, `parent_id`, `title`, `path`, `component`, `menu_type`, `sort_number`,
|
||||
`authority`, `hide`, `app_id`, `deleted`, `merchant_code`, `tenant_id`)
|
||||
SELECT 14402, 8574, '学生名册', '/gxmu/studentRoster', '/gxmu/studentRoster', 0, 105,
|
||||
'', 0, 0, 0, '', 10049
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` m WHERE m.`menu_id` = 14402);
|
||||
|
||||
-- 按钮:名册查询
|
||||
INSERT INTO `sys_menu`
|
||||
(`menu_id`, `parent_id`, `title`, `path`, `component`, `menu_type`, `sort_number`,
|
||||
`authority`, `hide`, `app_id`, `deleted`, `merchant_code`, `tenant_id`)
|
||||
SELECT 14403, 14402, '名册查询', '', '', 1, 1,
|
||||
'gxmu:student:list', 0, 0, 0, '', 10049
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` m WHERE m.`menu_id` = 14403);
|
||||
|
||||
-- 按钮:名册导出(泄露风险最高,单独授权)
|
||||
INSERT INTO `sys_menu`
|
||||
(`menu_id`, `parent_id`, `title`, `path`, `component`, `menu_type`, `sort_number`,
|
||||
`authority`, `hide`, `app_id`, `deleted`, `merchant_code`, `tenant_id`)
|
||||
SELECT 14404, 14402, '名册导出', '', '', 1, 2,
|
||||
'gxmu:student:export', 0, 0, 0, '', 10049
|
||||
FROM DUAL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `sys_menu` m WHERE m.`menu_id` = 14404);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 给超级管理员授权(其它角色请按需在后台「角色管理」里勾选)
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`, `tenant_id`)
|
||||
SELECT r.`role_id`, m.`menu_id`, 10049
|
||||
FROM `sys_role` r
|
||||
JOIN `sys_menu` m ON m.`menu_id` IN (14400, 14401, 14402, 14403, 14404)
|
||||
LEFT JOIN `sys_role_menu` rm ON rm.`role_id` = r.`role_id` AND rm.`menu_id` = m.`menu_id`
|
||||
WHERE r.`role_code` = 'superAdmin'
|
||||
AND r.`tenant_id` = 10049
|
||||
AND rm.`id` IS NULL;
|
||||
@@ -0,0 +1,143 @@
|
||||
-- ============================================================================
|
||||
-- 医科大开放平台数据同步:名册表、同步记录表、唯一键、码表字典
|
||||
-- 参见 .scratch/openplat-sync/spec.md 与 docs/adr/0001、0006
|
||||
-- 可重复执行
|
||||
-- ============================================================================
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 1. 学生名册(与 sys_user 账号分离,见 ADR-0001)
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `gxmu_student` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`xh` VARCHAR(32) NOT NULL COMMENT '学号(开放平台外部键)',
|
||||
`xm` VARCHAR(64) DEFAULT NULL COMMENT '姓名',
|
||||
`xbm` VARCHAR(8) DEFAULT NULL COMMENT '性别码(平台原始)',
|
||||
`sex` VARCHAR(16) DEFAULT NULL COMMENT '性别名称',
|
||||
`mzm` VARCHAR(8) DEFAULT NULL COMMENT '民族码(平台原始)',
|
||||
`nation` VARCHAR(32) DEFAULT NULL COMMENT '民族名称',
|
||||
`zzmmm` VARCHAR(8) DEFAULT NULL COMMENT '政治面貌码(平台原始)',
|
||||
`politics` VARCHAR(64) DEFAULT NULL COMMENT '政治面貌名称',
|
||||
`sfzjh` VARCHAR(32) DEFAULT NULL COMMENT '身份证件号',
|
||||
`sjh` VARCHAR(32) DEFAULT NULL COMMENT '手机号(认领键)',
|
||||
`bjm` VARCHAR(64) DEFAULT NULL COMMENT '平台班级码(原始)',
|
||||
`bh` VARCHAR(64) DEFAULT NULL COMMENT '平台班号(学籍库原始)',
|
||||
`class_id` INT DEFAULT NULL COMMENT '解析出的班级ID',
|
||||
`college_id` INT DEFAULT NULL COMMENT '解析出的学院ID',
|
||||
`dwh` VARCHAR(64) DEFAULT NULL COMMENT '单位号(平台原始)',
|
||||
`zyh` VARCHAR(32) DEFAULT NULL COMMENT '专业号',
|
||||
`zymc` VARCHAR(128) DEFAULT NULL COMMENT '专业名称',
|
||||
`pyccmc` VARCHAR(32) DEFAULT NULL COMMENT '培养层次',
|
||||
`sfzx` VARCHAR(8) DEFAULT NULL COMMENT '是否在校',
|
||||
`xjzt` VARCHAR(32) DEFAULT NULL COMMENT '学籍状态',
|
||||
`xsdqzt` VARCHAR(32) DEFAULT NULL COMMENT '学生当前状态',
|
||||
`csrq` VARCHAR(20) DEFAULT NULL COMMENT '出生日期',
|
||||
`user_id` INT DEFAULT NULL COMMENT '认领后的 sys_user.user_id',
|
||||
`claim_time` DATETIME DEFAULT NULL COMMENT '认领时间',
|
||||
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1在校 0毕业/结业/休学',
|
||||
`sync_time` DATETIME DEFAULT NULL COMMENT '最近同步时间',
|
||||
`tenant_id` INT DEFAULT NULL COMMENT '租户ID',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0未删 1已删',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_gxmu_student_xh` (`tenant_id`, `xh`),
|
||||
KEY `idx_gxmu_student_xh` (`xh`),
|
||||
KEY `idx_gxmu_student_sjh` (`sjh`),
|
||||
KEY `idx_gxmu_student_class` (`class_id`),
|
||||
KEY `idx_gxmu_student_college` (`college_id`),
|
||||
KEY `idx_gxmu_student_uid` (`user_id`),
|
||||
KEY `idx_gxmu_student_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生名册(开放平台同步)';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 2. 教师名册
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `gxmu_teacher` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`jgh` VARCHAR(32) NOT NULL COMMENT '教工号(开放平台外部键)',
|
||||
`xm` VARCHAR(64) DEFAULT NULL COMMENT '姓名',
|
||||
`xbm` VARCHAR(8) DEFAULT NULL COMMENT '性别码(平台原始)',
|
||||
`sex` VARCHAR(16) DEFAULT NULL COMMENT '性别名称',
|
||||
`dwh` VARCHAR(64) DEFAULT NULL COMMENT '单位号(平台原始)',
|
||||
`college_id` INT DEFAULT NULL COMMENT '解析出的学院ID',
|
||||
`ksjybh` VARCHAR(64) DEFAULT NULL COMMENT '科室教研室编号',
|
||||
`sjh` VARCHAR(32) DEFAULT NULL COMMENT '手机号(敏感字段, 不对外输出)',
|
||||
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1启用 0停用',
|
||||
`sync_time` DATETIME DEFAULT NULL COMMENT '最近同步时间',
|
||||
`tenant_id` INT DEFAULT NULL COMMENT '租户ID',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
`deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记:0未删 1已删',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_gxmu_teacher_jgh` (`tenant_id`, `jgh`),
|
||||
KEY `idx_gxmu_teacher_jgh` (`jgh`),
|
||||
KEY `idx_gxmu_teacher_college` (`college_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教师名册(开放平台同步)';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 3. 同步记录
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS `gxmu_sync_record` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`batch_id` VARCHAR(64) DEFAULT NULL COMMENT '批次号(同一次触发的各段共享)',
|
||||
`sync_type` VARCHAR(32) NOT NULL COMMENT '同步类型:dept/teacher/class/student',
|
||||
`trigger_type` VARCHAR(16) NOT NULL COMMENT '触发方式:schedule/manual',
|
||||
`trigger_user` VARCHAR(100) DEFAULT NULL COMMENT '触发人(手动触发时)',
|
||||
`start_time` DATETIME DEFAULT NULL COMMENT '开始时间',
|
||||
`end_time` DATETIME DEFAULT NULL COMMENT '结束时间',
|
||||
`success_count` INT NOT NULL DEFAULT 0 COMMENT '成功条数',
|
||||
`fail_count` INT NOT NULL DEFAULT 0 COMMENT '失败条数',
|
||||
`skip_count` INT NOT NULL DEFAULT 0 COMMENT '跳过条数',
|
||||
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0成功 1失败',
|
||||
`error_msg` VARCHAR(1000) DEFAULT NULL COMMENT '错误摘要(不含手机号/身份证)',
|
||||
`tenant_id` INT DEFAULT NULL COMMENT '租户ID',
|
||||
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_gxmu_sync_record_batch` (`batch_id`),
|
||||
KEY `idx_gxmu_sync_record_start` (`start_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='开放平台同步记录';
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 4. 唯一键(幂等 upsert 的前提)
|
||||
-- 先把手写的空串规范成 NULL,否则唯一键会被一堆 '' 互撞。
|
||||
-- ---------------------------------------------------------------------------
|
||||
UPDATE `gxmu_class` SET `class_code` = NULL WHERE `class_code` = '';
|
||||
UPDATE `sys_organization` SET `organization_code` = NULL WHERE `organization_code` = '';
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'gxmu_class'
|
||||
AND INDEX_NAME = 'uk_gxmu_class_code');
|
||||
SET @stmt := IF(@exist = 0,
|
||||
'ALTER TABLE `gxmu_class` ADD UNIQUE KEY `uk_gxmu_class_code` (`tenant_id`, `class_code`)',
|
||||
'SELECT 1');
|
||||
PREPARE s FROM @stmt; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'sys_organization'
|
||||
AND INDEX_NAME = 'uk_sys_organization_code');
|
||||
SET @stmt := IF(@exist = 0,
|
||||
'ALTER TABLE `sys_organization` ADD UNIQUE KEY `uk_sys_organization_code` (`tenant_id`, `organization_code`)',
|
||||
'SELECT 1');
|
||||
PREPARE s FROM @stmt; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 5. 码表字典
|
||||
-- 平台只返回代码不返回名称,且平台本身没有任何码表接口(见 ADR-0006)。
|
||||
-- dict_data_code = 平台原始码,dict_data_name = 解析后的名称。
|
||||
-- ---------------------------------------------------------------------------
|
||||
INSERT INTO `sys_dict` (`dict_id`, `dict_code`, `dict_name`, `sort_number`, `comments`, `tenant_id`)
|
||||
VALUES
|
||||
(9001, 'openplat_sex', '开放平台-性别码', 1, '1=男 2=女(已由实测确认)', 10049),
|
||||
(9002, 'openplat_nation', '开放平台-民族码', 2, '待 RS_JZGXX 授权后用 MZM/MZM1 反推回填', 10049),
|
||||
(9003, 'openplat_politics', '开放平台-政治面貌码', 3, '待 RS_JZGXX 授权后用 ZZZTM/ZZZTM1 反推回填', 10049)
|
||||
ON DUPLICATE KEY UPDATE `dict_code` = VALUES(`dict_code`), `dict_name` = VALUES(`dict_name`);
|
||||
|
||||
INSERT INTO `sys_dict_data` (`dict_data_code`, `dict_data_name`, `dict_id`, `sort_number`, `tenant_id`)
|
||||
SELECT t.c, t.n, t.d, t.s, t.t
|
||||
FROM (
|
||||
SELECT '1' AS c, '男' AS n, 9001 AS d, 1 AS s, 10049 AS t
|
||||
UNION ALL SELECT '2', '女', 9001, 2, 10049
|
||||
) t
|
||||
LEFT JOIN `sys_dict_data` d ON d.`dict_id` = t.d AND d.`dict_data_code` = t.c
|
||||
WHERE d.`dict_data_id` IS NULL;
|
||||
Reference in New Issue
Block a user