Преглед изворни кода

feat: 新增统一身份认证用户数据定时同步脚本

将校园系统部门、教职工、学生数据同步到认证中间库6张标准表,支持 MySQL 本地测试与 Oracle 生产目标库配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
“wuzhen” пре 2 месеци
родитељ
комит
334bd8742e

+ 313 - 0
用户信息同步统一身份认证/3.在认证中间库建立表.sql

@@ -0,0 +1,313 @@
+-- Create table
+create table GXXX_YXSDWJBSJZL
+(
+  dwh    VARCHAR2(10) not null,
+  dwmc   VARCHAR2(60),
+  dwywmc VARCHAR2(180),
+  dwjc   VARCHAR2(12),
+  dwywjc VARCHAR2(8),
+  dwjp   VARCHAR2(6),
+  dwdz   VARCHAR2(180),
+  lsdwh  VARCHAR2(10),
+  dwlbm  VARCHAR2(1),
+  dwbbm  VARCHAR2(1),
+  dwyxbs VARCHAR2(1),
+  sxrq   VARCHAR2(8),
+  sfst   VARCHAR2(1),
+  jlny   VARCHAR2(6),
+  dwfzrh VARCHAR2(20),
+  zhgxsj VARCHAR2(20) default to_char(sysdate,'yyyymmddhh24miss'),
+  scbz   VARCHAR2(1),
+  dwjb   VARCHAR2(8)
+);
+
+-- Add comments to the table 
+comment on table GXXX_YXSDWJBSJZL  is 'GXXX0301院系所单位基本数据子类';
+-- Add comments to the columns 
+comment on column GXXX_YXSDWJBSJZL.dwh  is '单位号';
+comment on column GXXX_YXSDWJBSJZL.dwmc  is '单位名称';
+comment on column GXXX_YXSDWJBSJZL.dwywmc  is '单位英文名称';
+comment on column GXXX_YXSDWJBSJZL.dwjc  is '单位简称';
+comment on column GXXX_YXSDWJBSJZL.dwywjc  is '单位英文简称';
+comment on column GXXX_YXSDWJBSJZL.dwjp  is '单位简拼';
+comment on column GXXX_YXSDWJBSJZL.dwdz  is '单位地址';
+comment on column GXXX_YXSDWJBSJZL.lsdwh  is '隶属单位号';
+comment on column GXXX_YXSDWJBSJZL.dwlbm  is '单位类别码';
+comment on column GXXX_YXSDWJBSJZL.dwbbm  is '单位办别码';
+comment on column GXXX_YXSDWJBSJZL.dwyxbs  is '单位有效标识';
+comment on column GXXX_YXSDWJBSJZL.sxrq  is '失效日期';
+comment on column GXXX_YXSDWJBSJZL.sfst  is '是否实体';
+comment on column GXXX_YXSDWJBSJZL.jlny  is '建立年月';
+comment on column GXXX_YXSDWJBSJZL.dwfzrh  is '单位负责人号';
+comment on column GXXX_YXSDWJBSJZL.zhgxsj  is '最后更新时间';
+comment on column GXXX_YXSDWJBSJZL.scbz   is '删除标志:y/n';
+comment on column GXXX_YXSDWJBSJZL.dwjb  is '单位级别';
+-- Create/Recreate primary, unique and foreign key constraints 
+alter table GXXX_YXSDWJBSJZL  add primary key (DWH);
+/
+
+-- Create table
+create table GXJG_JZGJCSJZLB
+(
+  gh      VARCHAR2(20) not null,
+  dwh     VARCHAR2(10) not null,
+  xm      VARCHAR2(36),
+  ywxm    VARCHAR2(60),
+  xmpy    VARCHAR2(60),
+  cym     VARCHAR2(36),
+  xbm     VARCHAR2(1),
+  csrq    VARCHAR2(8),
+  csdm    VARCHAR2(6),
+  jg      VARCHAR2(20),
+  mzm     VARCHAR2(2),
+  gjdqm   VARCHAR2(3),
+  sfzjlxm VARCHAR2(1),
+  sfzjh   VARCHAR2(20),
+  hyztm   VARCHAR2(2),
+  gatqwm  VARCHAR2(2),
+  zzmmm   VARCHAR2(2),
+  jkzkm   VARCHAR2(2),
+  xyzjm   VARCHAR2(2),
+  xxm     VARCHAR2(1),
+  zp      BLOB,
+  sfzjyxq VARCHAR2(17),
+  xqh     VARCHAR2(2),
+  zgxlm   VARCHAR2(3),
+  whcdm   VARCHAR2(3),
+  cjgzny  VARCHAR2(6),
+  lxrq    VARCHAR2(8),
+  qxrq    VARCHAR2(8),
+  cjny    VARCHAR2(6),
+  bzlbm   VARCHAR2(2),
+  jzglbm  VARCHAR2(3),
+  rkzkm   VARCHAR2(2),
+  dabh    VARCHAR2(20),
+  dawb    CLOB,
+  dqztm   VARCHAR2(2),
+  tc      VARCHAR2(100),
+  xklbm   VARCHAR2(6),
+  yjxkm   VARCHAR2(4),
+  ejxkm   VARCHAR2(6),
+  yjfx    VARCHAR2(100),
+  zhgxsj  VARCHAR2(20) default to_char(sysdate,'yyyymmddhh24miss'),
+  scbz    VARCHAR2(1),
+  zhxw    VARCHAR2(32),
+  zc      VARCHAR2(32),
+  ygxs    VARCHAR2(2)
+);
+
+-- Add comments to the table 
+comment on table GXJG_JZGJCSJZLB  is 'GXJG0101教职工基础数据子类表';
+-- Add comments to the columns 
+comment on column GXJG_JZGJCSJZLB.gh  is '工号';
+comment on column GXJG_JZGJCSJZLB.dwh  is '单位号';
+comment on column GXJG_JZGJCSJZLB.xm  is '姓名';
+comment on column GXJG_JZGJCSJZLB.ywxm  is '英文姓名';
+comment on column GXJG_JZGJCSJZLB.xmpy  is '姓名拼音';
+comment on column GXJG_JZGJCSJZLB.cym  is '曾用名';
+comment on column GXJG_JZGJCSJZLB.xbm  is '性别码';
+comment on column GXJG_JZGJCSJZLB.csrq  is '出生日期';
+comment on column GXJG_JZGJCSJZLB.csdm  is '出生地码';
+comment on column GXJG_JZGJCSJZLB.jg  is '籍贯';
+comment on column GXJG_JZGJCSJZLB.mzm  is '民族码';
+comment on column GXJG_JZGJCSJZLB.gjdqm  is '国籍/地区码';
+comment on column GXJG_JZGJCSJZLB.sfzjlxm  is '身份证件类型码';
+comment on column GXJG_JZGJCSJZLB.sfzjh  is '身份证件号';
+comment on column GXJG_JZGJCSJZLB.hyztm  is '婚姻状况码';
+comment on column GXJG_JZGJCSJZLB.gatqwm  is '港澳台侨外码';
+comment on column GXJG_JZGJCSJZLB.zzmmm  is '政治面貌码';
+comment on column GXJG_JZGJCSJZLB.jkzkm  is '健康状况码[DM_GB_B_JKZKEWSZDM]';
+comment on column GXJG_JZGJCSJZLB.xyzjm  is '信仰宗教码';
+comment on column GXJG_JZGJCSJZLB.xxm  is '血型码';
+comment on column GXJG_JZGJCSJZLB.zp  is '照片';
+comment on column GXJG_JZGJCSJZLB.sfzjyxq  is '身份证件有效期';
+comment on column GXJG_JZGJCSJZLB.xqh  is '校区号';
+comment on column GXJG_JZGJCSJZLB.zgxlm  is '最高学历码';
+comment on column GXJG_JZGJCSJZLB.whcdm  is '文化程度码';
+comment on column GXJG_JZGJCSJZLB.cjgzny  is '参加工作年月';
+comment on column GXJG_JZGJCSJZLB.lxrq  is '来校日期';
+comment on column GXJG_JZGJCSJZLB.qxrq  is '起薪日期';
+comment on column GXJG_JZGJCSJZLB.cjny  is '从教年月';
+comment on column GXJG_JZGJCSJZLB.bzlbm  is '编制类别码';
+comment on column GXJG_JZGJCSJZLB.jzglbm  is '教职工类别码';
+comment on column GXJG_JZGJCSJZLB.rkzkm  is '任课状况码';
+comment on column GXJG_JZGJCSJZLB.dabh  is '档案编号';
+comment on column GXJG_JZGJCSJZLB.dawb  is '档案文本';
+comment on column GXJG_JZGJCSJZLB.dqztm  is '当前状态码';
+comment on column GXJG_JZGJCSJZLB.tc  is '特长';
+comment on column GXJG_JZGJCSJZLB.xklbm  is '学科类别码';
+comment on column GXJG_JZGJCSJZLB.yjxkm  is '一级学科码';
+comment on column GXJG_JZGJCSJZLB.ejxkm  is '二级学科码';
+comment on column GXJG_JZGJCSJZLB.yjfx  is '研究方向';
+comment on column GXJG_JZGJCSJZLB.zhgxsj  is '最后更新时间';
+comment on column GXJG_JZGJCSJZLB.scbz  is '删除标志:y/n';
+comment on column GXJG_JZGJCSJZLB.zhxw  is '最后学位';
+comment on column GXJG_JZGJCSJZLB.zc  is '职称';
+comment on column GXJG_JZGJCSJZLB.ygxs  is '用工形式';
+-- Create/Recreate primary, unique and foreign key constraints 
+alter table GXJG_JZGJCSJZLB  add primary key (GH);
+/
+
+-- Create table
+create table GXXS_XSJBSJZL
+(
+  xh      VARCHAR2(20) not null,
+  xm      VARCHAR2(60),
+  ywxm    VARCHAR2(60),
+  xmpy    VARCHAR2(60),
+  cym     VARCHAR2(60),
+  xbm     VARCHAR2(1),
+  csrq    VARCHAR2(10),
+  csdm    VARCHAR2(200),
+  jg      VARCHAR2(60),
+  mzm     VARCHAR2(2),
+  gjdqm   VARCHAR2(50),
+  sfzjlxm VARCHAR2(2),
+  sfzjh   VARCHAR2(20),
+  hyzkm   VARCHAR2(10),
+  gatqwm  VARCHAR2(2),
+  zzmmm   VARCHAR2(2),
+  jkzkm   VARCHAR2(20),
+  xyzjm   VARCHAR2(50),
+  xxm     VARCHAR2(30),
+  sfzjyxq VARCHAR2(17),
+  sfdszn  VARCHAR2(1),
+  zp      BLOB,
+  zhgxsj  VARCHAR2(20) default to_char(sysdate,'yyyymmddhh24miss'),
+  scbz    VARCHAR2(1),
+  ksh     VARCHAR2(20),
+  dwh     VARCHAR2(20) not null,
+  szbh    VARCHAR2(20),
+  sfzx    VARCHAR2(10),
+  sfyby   VARCHAR2(6)
+);
+
+-- Add comments to the table 
+comment on table GXXS_XSJBSJZL  is 'GXXS0101学生基本数据子类';
+-- Add comments to the columns 
+comment on column GXXS_XSJBSJZL.xh   is '学号';
+comment on column GXXS_XSJBSJZL.xm   is '姓名';
+comment on column GXXS_XSJBSJZL.ywxm  is '英文姓名';
+comment on column GXXS_XSJBSJZL.xmpy  is '姓名拼音';
+comment on column GXXS_XSJBSJZL.cym    is '曾用名';
+comment on column GXXS_XSJBSJZL.xbm   is '性别码';
+comment on column GXXS_XSJBSJZL.csrq    is '出生日期';
+comment on column GXXS_XSJBSJZL.csdm  is '出生地码';
+comment on column GXXS_XSJBSJZL.jg       is '籍贯';
+comment on column GXXS_XSJBSJZL.mzm     is '民族码';
+comment on column GXXS_XSJBSJZL.gjdqm   is '国籍/地区码';
+comment on column GXXS_XSJBSJZL.sfzjlxm  is '身份证件类型码';
+comment on column GXXS_XSJBSJZL.sfzjh     is '身份证件号';
+comment on column GXXS_XSJBSJZL.hyzkm  is '婚姻状况码';
+comment on column GXXS_XSJBSJZL.gatqwm  is '港澳台侨外码';
+comment on column GXXS_XSJBSJZL.zzmmm  is '政治面貌码';
+comment on column GXXS_XSJBSJZL.jkzkm  is '健康状况码';
+comment on column GXXS_XSJBSJZL.xyzjm  is '信仰宗教码';
+comment on column GXXS_XSJBSJZL.xxm     is '血型码';
+comment on column GXXS_XSJBSJZL.sfzjyxq  is '身份证件有效期';
+comment on column GXXS_XSJBSJZL.sfdszn  is '是否独生子女';
+comment on column GXXS_XSJBSJZL.zp        is '照片';
+comment on column GXXS_XSJBSJZL.zhgxsj  is '最后更新时间';
+comment on column GXXS_XSJBSJZL.scbz   is '删除标志:y/n';
+comment on column GXXS_XSJBSJZL.ksh    is '考生号';
+comment on column GXXS_XSJBSJZL.dwh   is 'null';
+comment on column GXXS_XSJBSJZL.szbh   is '所在班号';
+comment on column GXXS_XSJBSJZL.sfzx    is '是否在校';
+comment on column GXXS_XSJBSJZL.sfyby  is '是否已毕业';
+-- Create/Recreate primary, unique and foreign key constraints 
+alter table GXXS_XSJBSJZL  add primary key (XH);
+/
+
+-- Create table
+create table GXXS_GRTXZL
+(
+  yzbm   VARCHAR2(6),
+  txdz   VARCHAR2(180),
+  dh     VARCHAR2(50),
+  yddh   VARCHAR2(30),
+  czdh   VARCHAR2(30),
+  dzxx   VARCHAR2(40),
+  wldz   VARCHAR2(60),
+  jstxh  VARCHAR2(40),
+  xh     VARCHAR2(20) not null,
+  zhgxsj VARCHAR2(20) default to_char(sysdate,'yyyymmddhh24miss'),
+  scbz   VARCHAR2(1)
+);
+
+-- Add comments to the table 
+comment on table GXXS_GRTXZL  is 'GXXS0109学生个人通讯子类';
+-- Add comments to the columns 
+comment on column GXXS_GRTXZL.yzbm  is '邮政编码';
+comment on column GXXS_GRTXZL.txdz  is '通信地址';
+comment on column GXXS_GRTXZL.dh  is '电话';
+comment on column GXXS_GRTXZL.yddh  is '移动电话';
+comment on column GXXS_GRTXZL.czdh  is '传真电话';
+comment on column GXXS_GRTXZL.dzxx  is '电子信箱';
+comment on column GXXS_GRTXZL.wldz  is '网络地址';
+comment on column GXXS_GRTXZL.jstxh  is '即时通讯号';
+comment on column GXXS_GRTXZL.xh  is '学号';
+comment on column GXXS_GRTXZL.zhgxsj  is '最后更新时间';
+comment on column GXXS_GRTXZL.scbz  is '删除标志:y/n';
+-- Create/Recreate primary, unique and foreign key constraints 
+alter table GXXS_GRTXZL  add primary key (XH);
+/
+
+-- Create table
+create table GXJG_GRTXFSZL
+(
+  yzbm   VARCHAR2(6),
+  txdz   VARCHAR2(180),
+  dh     VARCHAR2(30),
+  yddh   VARCHAR2(30),
+  czdh   VARCHAR2(30),
+  wldz   VARCHAR2(60),
+  jstxh  VARCHAR2(40),
+  gh     VARCHAR2(20) not null,
+  dzxx   VARCHAR2(40),
+  zhgxsj VARCHAR2(20) default to_char(sysdate,'yyyymmddhh24miss'),
+  scbz   VARCHAR2(1),
+  id     VARCHAR2(48) not null
+);
+
+-- Add comments to the table 
+comment on table GXJG_GRTXFSZL  is 'GXJG0102教职工个人通讯方式子类';
+-- Add comments to the columns 
+comment on column GXJG_GRTXFSZL.yzbm  is '邮政编码';
+comment on column GXJG_GRTXFSZL.txdz  is '通信地址';
+comment on column GXJG_GRTXFSZL.dh  is '电话';
+comment on column GXJG_GRTXFSZL.yddh  is '移动电话';
+comment on column GXJG_GRTXFSZL.czdh  is '传真电话';
+comment on column GXJG_GRTXFSZL.wldz  is '网络地址';
+comment on column GXJG_GRTXFSZL.jstxh  is '即时通讯号';
+comment on column GXJG_GRTXFSZL.gh  is '工号';
+comment on column GXJG_GRTXFSZL.dzxx  is '电子信箱';
+comment on column GXJG_GRTXFSZL.zhgxsj  is '最后更新时间';
+comment on column GXJG_GRTXFSZL.scbz  is '删除标志:y/n';
+-- Create/Recreate primary, unique and foreign key constraints 
+alter table GXJG_GRTXFSZL  add primary key (ID);
+/
+
+-- Create table
+create table ZFTAL_XTGL_ERRLOG
+(
+  YWXTMC VARCHAR2(100),
+  YWXTBM VARCHAR2(100),
+  YWXTCZ VARCHAR2(200),
+  SJCFQ VARCHAR2(100),
+  SJCCGC VARCHAR2(100),
+  CWZDXX VARCHAR2(1000),
+  CWSJ   VARCHAR2(20) default to_char(sysdate,'yyyy-mm-dd hh24:mi:ss')
+);
+
+-- Add comments to the table 
+comment on table ZFTAL_XTGL_ERRLOG  is '系统错误日志表';
+-- Add comments to the columns 
+comment on column ZFTAL_XTGL_ERRLOG.YWXTMC  is '业务系统名称(例:认证系统)';
+comment on column ZFTAL_XTGL_ERRLOG.YWXTBM  is '业务系统表名(例:ZFTAL_XTGL_YHB)';
+comment on column ZFTAL_XTGL_ERRLOG.YWXTCZ  is '业务系统操作(例:修改学生用户联系电话,地址邮箱)';
+comment on column ZFTAL_XTGL_ERRLOG.SJCFQ  is '操作涉及触发器(例:pro_gxxs_grtxzl)';
+comment on column ZFTAL_XTGL_ERRLOG.SJCCGC  is '操作涉及存储过程(例:pro_gxxs_grtxzl)';
+comment on column ZFTAL_XTGL_ERRLOG.CWZDXX  is '显示会导致报错的字段(例如: ZGH:200010)';
+comment on column ZFTAL_XTGL_ERRLOG.CWSJ  is '错误时间';
+/
+

+ 68 - 0
用户信息同步统一身份认证/README.md

@@ -0,0 +1,68 @@
+# 统一身份认证用户数据同步
+
+将校园管理系统用户/组织数据定时同步到认证中间库,供第三方统一身份认证平台拉取。
+
+## 目录结构
+
+```
+用户信息同步统一身份认证/
+├── 3.在认证中间库建立表.sql   # 第三方原始 Oracle 建表脚本(勿修改)
+├── 建表脚本_mysql.sql          # MySQL 适配建表脚本(本地测试用)
+├── data.conf                   # 研发环境配置
+├── data.conf.prod              # 生产环境配置(Oracle 目标库)
+├── create_tables.sh            # 创建中间表
+├── sync_user_to_sso.sh         # 主同步脚本
+├── verify_sync.sh              # 同步结果校验
+├── sync_sql/sync_all_mysql.sql # MySQL 同步 SQL
+├── sync_oracle/merge_all.sql   # Oracle MERGE 模板(生产)
+├── 字段映射说明.md
+└── logs/                       # 运行日志(自动创建)
+```
+
+## 快速开始(研发环境)
+
+```bash
+cd 用户信息同步统一身份认证
+
+# 1. 创建中间表(仅新增6张表,不修改已有表)
+chmod +x *.sh
+./create_tables.sh
+
+# 2. 执行一次全量同步
+./sync_user_to_sso.sh --full --once
+
+# 3. 校验同步结果
+./verify_sync.sh
+```
+
+## 定时运行
+
+```bash
+# 守护进程模式,默认每 3600 秒(1小时)同步一次
+./sync_user_to_sso.sh
+
+# 修改同步频率:编辑 data.conf 中 CYCLE_INTERVAL(秒)
+```
+
+## 生产部署
+
+1. 复制 `data.conf.prod` 为 `data.conf` 或指定 `--config=data.conf.prod`
+2. 填写源库(校园系统 MySQL)和目标库(认证中间库 Oracle)连接信息
+3. 目标库使用第三方提供的 `3.在认证中间库建立表.sql` 在 Oracle 中建表
+4. 确保服务器已安装 `sqlplus`(Oracle 客户端)
+5. 使用 crontab 或 systemd 定时执行:
+
+```bash
+# crontab 示例:每小时执行一次
+0 * * * * /path/to/用户信息同步统一身份认证/sync_user_to_sso.sh --once >> /path/to/logs/cron.log 2>&1
+```
+
+## 配置说明
+
+| 配置项 | 说明 |
+|--------|------|
+| SOURCE_DB_* | 源库(校园管理系统,只读) |
+| TARGET_DB_* | 目标库(认证中间库,读写) |
+| TARGET_DB_TYPE | `mysql`(测试)或 `oracle`(生产) |
+| SYNC_MODE | `full` 全量 / `incremental` 增量 |
+| CYCLE_INTERVAL | 循环间隔秒数,默认 3600 |

+ 31 - 0
用户信息同步统一身份认证/create_tables.sh

@@ -0,0 +1,31 @@
+#!/bin/bash
+# ===========================================
+# 创建统一身份认证中间库表(MySQL 测试环境)
+# ===========================================
+
+set -euo pipefail
+export LANG=C
+export LC_ALL=C
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+CONFIG_FILE="${SCRIPT_DIR}/data.conf"
+
+if [[ ! -f "$CONFIG_FILE" ]]; then
+    echo "错误:配置文件 $CONFIG_FILE 未找到"
+    exit 1
+fi
+
+source "$CONFIG_FILE"
+
+SQL_FILE="${SCRIPT_DIR}/建表脚本_mysql.sql"
+if [[ ! -f "$SQL_FILE" ]]; then
+    echo "错误:建表脚本 $SQL_FILE 未找到"
+    exit 1
+fi
+
+echo "正在目标库创建中间表: ${TARGET_DB_USER}@${TARGET_DB_HOST}:${TARGET_DB_PORT}/${TARGET_DB_NAME}"
+python3 "${SCRIPT_DIR}/db_helper.py" \
+    --host "$TARGET_DB_HOST" --port "$TARGET_DB_PORT" \
+    --user "$TARGET_DB_USER" --password "$TARGET_DB_PASS" --database "$TARGET_DB_NAME" \
+    file "$SQL_FILE"
+echo "建表完成"

+ 41 - 0
用户信息同步统一身份认证/data.conf

@@ -0,0 +1,41 @@
+# ===========================================
+# 统一身份认证用户数据同步 - 研发环境配置
+# ===========================================
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# ---------- 源库(校园管理系统,只读) ----------
+SOURCE_DB_TYPE="mysql"
+SOURCE_DB_HOST="47.99.164.66"
+SOURCE_DB_PORT="3306"
+SOURCE_DB_NAME="hzdyjs_xg"
+SOURCE_DB_USER="root"
+SOURCE_DB_PASS="hn02le.34lkdLKD"
+
+# ---------- 目标库(认证中间库,读写) ----------
+# 研发环境:与源库同库,仅新增中间表
+TARGET_DB_TYPE="mysql"
+TARGET_DB_HOST="47.99.164.66"
+TARGET_DB_PORT="3306"
+TARGET_DB_NAME="hzdyjs_xg"
+TARGET_DB_USER="root"
+TARGET_DB_PASS="hn02le.34lkdLKD"
+
+# ---------- 同步策略 ----------
+# 同步模式:full=全量,incremental=增量(按 update_time)
+SYNC_MODE="full"
+# 循环间隔(秒),默认 3600 = 1 小时
+CYCLE_INTERVAL=3600
+# 单次批量大小
+BATCH_SIZE=500
+# 失败重试
+MAX_RETRY=3
+RETRY_DELAY=5
+
+# ---------- 日志与锁 ----------
+LOG_DIR="${SCRIPT_DIR}/logs"
+LOCK_FILE="${SCRIPT_DIR}/sync_user_to_sso.lock"
+STATE_FILE="${SCRIPT_DIR}/.sync_state"
+
+# ---------- 业务系统标识(写入错误日志) ----------
+YWXTMC="校园管理系统"

+ 38 - 0
用户信息同步统一身份认证/data.conf.prod

@@ -0,0 +1,38 @@
+# ===========================================
+# 统一身份认证用户数据同步 - 生产环境配置
+# ===========================================
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# ---------- 源库(校园管理系统,只读) ----------
+SOURCE_DB_TYPE="mysql"
+SOURCE_DB_HOST="127.0.0.1"
+SOURCE_DB_PORT="3306"
+SOURCE_DB_NAME="hzdyjs_xg"
+SOURCE_DB_USER="root"
+SOURCE_DB_PASS="请填写生产源库密码"
+
+# ---------- 目标库(认证中间库 Oracle,读写) ----------
+TARGET_DB_TYPE="oracle"
+TARGET_DB_HOST="请填写Oracle主机"
+TARGET_DB_PORT="1521"
+TARGET_DB_NAME="请填写Oracle服务名或SID"
+TARGET_DB_USER="请填写Oracle用户名"
+TARGET_DB_PASS="请填写Oracle密码"
+# Oracle 连接串示例://host:1521/service_name
+TARGET_ORACLE_CONNECT="${TARGET_DB_HOST}:${TARGET_DB_PORT}/${TARGET_DB_NAME}"
+
+# ---------- 同步策略 ----------
+SYNC_MODE="incremental"
+CYCLE_INTERVAL=3600
+BATCH_SIZE=500
+MAX_RETRY=3
+RETRY_DELAY=5
+
+# ---------- 日志与锁 ----------
+LOG_DIR="${SCRIPT_DIR}/logs"
+LOCK_FILE="${SCRIPT_DIR}/sync_user_to_sso.lock"
+STATE_FILE="${SCRIPT_DIR}/.sync_state"
+
+# ---------- 业务系统标识 ----------
+YWXTMC="校园管理系统"

+ 85 - 0
用户信息同步统一身份认证/db_helper.py

@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""数据库操作辅助脚本,供 shell 同步脚本调用。"""
+
+import argparse
+import sys
+from pathlib import Path
+
+try:
+    import pymysql
+except ImportError:
+    print("ERROR: 需要安装 pymysql: pip install pymysql", file=sys.stderr)
+    sys.exit(1)
+
+
+def connect_mysql(host, port, user, password, database):
+    return pymysql.connect(
+        host=host,
+        port=int(port),
+        user=user,
+        password=password,
+        database=database,
+        charset="utf8mb4",
+        autocommit=True,
+        cursorclass=pymysql.cursors.Cursor,
+    )
+
+
+def exec_sql_file(conn, sql_file):
+    sql = Path(sql_file).read_text(encoding="utf-8")
+    statements = [s.strip() for s in sql.split(";") if s.strip()]
+    with conn.cursor() as cur:
+        for stmt in statements:
+            if stmt.upper().startswith("SET "):
+                cur.execute(stmt)
+                continue
+            cur.execute(stmt)
+
+
+def exec_sql(conn, sql):
+    with conn.cursor() as cur:
+        cur.execute(sql)
+        if cur.description:
+            return cur.fetchall()
+    return None
+
+
+def main():
+    parser = argparse.ArgumentParser(description="MySQL 数据库辅助工具")
+    parser.add_argument("--host", required=True)
+    parser.add_argument("--port", default="3306")
+    parser.add_argument("--user", required=True)
+    parser.add_argument("--password", required=True)
+    parser.add_argument("--database", required=True)
+    sub = parser.add_subparsers(dest="action", required=True)
+
+    p_file = sub.add_parser("file", help="执行 SQL 文件")
+    p_file.add_argument("sql_file")
+
+    p_query = sub.add_parser("query", help="执行单条 SQL 并输出结果")
+    p_query.add_argument("sql")
+
+    p_scalar = sub.add_parser("scalar", help="执行 SQL 并输出单个值")
+    p_scalar.add_argument("sql")
+
+    args = parser.parse_args()
+    conn = connect_mysql(args.host, args.port, args.user, args.password, args.database)
+    try:
+        if args.action == "file":
+            exec_sql_file(conn, args.sql_file)
+            print("OK")
+        elif args.action == "query":
+            rows = exec_sql(conn, args.sql)
+            for row in rows or []:
+                print("\t".join("" if v is None else str(v) for v in row))
+        elif args.action == "scalar":
+            rows = exec_sql(conn, args.sql)
+            if rows:
+                print(rows[0][0])
+    finally:
+        conn.close()
+
+
+if __name__ == "__main__":
+    main()

+ 17 - 0
用户信息同步统一身份认证/sync_oracle/merge_all.sql

@@ -0,0 +1,17 @@
+-- ==========================================
+-- Oracle 目标库 MERGE 同步脚本(生产环境)
+-- 由 sync_user_to_sso.sh 通过 sqlplus 调用
+-- 注意:需根据实际导出文件格式调整
+-- ==========================================
+
+-- 示例:部门 MERGE(生产部署时需配合 sqlldr 或外部表导入)
+-- 当前为占位模板,待 Oracle 环境连通后完善
+
+WHENEVER SQLERROR EXIT SQL.SQLCODE
+SET ECHO OFF
+SET FEEDBACK ON
+SET VERIFY OFF
+
+PROMPT Oracle MERGE sync placeholder - 请根据生产环境完善
+
+EXIT;

+ 146 - 0
用户信息同步统一身份认证/sync_sql/sync_all_mysql.sql

@@ -0,0 +1,146 @@
+-- ==========================================
+-- 统一身份认证中间库 - MySQL 目标库同步 SQL
+-- 适用:SOURCE 与 TARGET 均为 MySQL(研发同库测试)
+-- 使用方式:由 sync_user_to_sso.sh 调用,可传入增量时间条件
+-- ==========================================
+
+SET NAMES utf8mb4;
+
+-- 1. 院系所单位 GXXX_YXSDWJBSJZL <- sys_dept
+INSERT INTO GXXX_YXSDWJBSJZL (
+  dwh, dwmc, lsdwh, dwyxbs, zhgxsj, scbz
+)
+SELECT
+  RIGHT(CAST(d.dept_id AS CHAR), 10) AS dwh,
+  LEFT(IFNULL(d.dept_name, ''), 60) AS dwmc,
+  CASE
+    WHEN d.parent_id IS NULL OR d.parent_id = 0 THEN NULL
+    ELSE RIGHT(CAST(d.parent_id AS CHAR), 10)
+  END AS lsdwh,
+  CASE WHEN d.status = '0' AND d.del_flag = '0' THEN '1' ELSE '0' END AS dwyxbs,
+  DATE_FORMAT(IFNULL(d.update_time, d.create_time), '%Y%m%d%H%i%s') AS zhgxsj,
+  CASE WHEN d.del_flag = '1' THEN 'y' ELSE 'n' END AS scbz
+FROM sys_dept d
+WHERE 1=1
+  /*INCREMENTAL_FILTER_DEPT*/
+ON DUPLICATE KEY UPDATE
+  dwmc = VALUES(dwmc),
+  lsdwh = VALUES(lsdwh),
+  dwyxbs = VALUES(dwyxbs),
+  zhgxsj = VALUES(zhgxsj),
+  scbz = VALUES(scbz);
+
+-- 2. 教职工基础 GXJG_JZGJCSJZLB <- sys_user (account_type=1)
+INSERT INTO GXJG_JZGJCSJZLB (
+  gh, dwh, xm, xbm, dqztm, zhgxsj, scbz
+)
+SELECT
+  LEFT(u.user_name, 20) AS gh,
+  RIGHT(CAST(IFNULL(u.dept_id, 0) AS CHAR), 10) AS dwh,
+  LEFT(IFNULL(u.nick_name, u.user_name), 36) AS xm,
+  CASE u.sex WHEN '0' THEN '1' WHEN '1' THEN '2' ELSE '0' END AS xbm,
+  CASE WHEN u.status = '0' THEN '11' ELSE '99' END AS dqztm,
+  DATE_FORMAT(IFNULL(u.update_time, u.create_time), '%Y%m%d%H%i%s') AS zhgxsj,
+  CASE WHEN u.del_flag = '1' THEN 'y' ELSE 'n' END AS scbz
+FROM sys_user u
+WHERE u.account_type = '1'
+  /*INCREMENTAL_FILTER_TEACHER*/
+ON DUPLICATE KEY UPDATE
+  dwh = VALUES(dwh),
+  xm = VALUES(xm),
+  xbm = VALUES(xbm),
+  dqztm = VALUES(dqztm),
+  zhgxsj = VALUES(zhgxsj),
+  scbz = VALUES(scbz);
+
+-- 3. 教职工通讯 GXJG_GRTXFSZL <- sys_user (account_type=1)
+INSERT INTO GXJG_GRTXFSZL (
+  id, gh, yddh, dzxx, zhgxsj, scbz
+)
+SELECT
+  LEFT(u.user_name, 48) AS id,
+  LEFT(u.user_name, 20) AS gh,
+  LEFT(NULLIF(u.phonenumber, ''), 30) AS yddh,
+  LEFT(NULLIF(u.email, ''), 40) AS dzxx,
+  DATE_FORMAT(IFNULL(u.update_time, u.create_time), '%Y%m%d%H%i%s') AS zhgxsj,
+  CASE WHEN u.del_flag = '1' THEN 'y' ELSE 'n' END AS scbz
+FROM sys_user u
+WHERE u.account_type = '1'
+  /*INCREMENTAL_FILTER_TEACHER*/
+ON DUPLICATE KEY UPDATE
+  gh = VALUES(gh),
+  yddh = VALUES(yddh),
+  dzxx = VALUES(dzxx),
+  zhgxsj = VALUES(zhgxsj),
+  scbz = VALUES(scbz);
+
+-- 4. 学生基础 GXXS_XSJBSJZL <- xg_student_msg
+INSERT INTO GXXS_XSJBSJZL (
+  xh, xm, ywxm, cym, xbm, csrq, jg, sfzjlxm, sfzjh, zzmmm, xxm,
+  sfdszn, zhgxsj, scbz, dwh, szbh, sfzx, sfyby
+)
+SELECT
+  LEFT(s.student_no, 20) AS xh,
+  LEFT(IFNULL(s.name, ''), 60) AS xm,
+  LEFT(IFNULL(s.name_english, ''), 60) AS ywxm,
+  LEFT(IFNULL(s.former_name, ''), 60) AS cym,
+  CASE s.gender WHEN '0' THEN '1' WHEN '1' THEN '2' ELSE '0' END AS xbm,
+  DATE_FORMAT(s.birth_date, '%Y%m%d') AS csrq,
+  LEFT(IFNULL(s.native_place, ''), 60) AS jg,
+  '1' AS sfzjlxm,
+  LEFT(IFNULL(s.id_card, ''), 20) AS sfzjh,
+  LEFT(IFNULL(s.political_status, ''), 2) AS zzmmm,
+  LEFT(IFNULL(s.blood_type, ''), 30) AS xxm,
+  CASE WHEN s.is_single_child = 1 THEN '1' ELSE '0' END AS sfdszn,
+  DATE_FORMAT(IFNULL(s.update_time, s.create_time), '%Y%m%d%H%i%s') AS zhgxsj,
+  CASE WHEN s.deleted = 1 THEN 'y' ELSE 'n' END AS scbz,
+  LEFT(IFNULL(s.college_id, '0'), 20) AS dwh,
+  LEFT(IFNULL(c.class_code, IFNULL(s.class_id, '')), 20) AS szbh,
+  CASE WHEN s.student_status IN ('1', '2', '3') THEN '1' ELSE '0' END AS sfzx,
+  CASE WHEN s.student_status = '5' THEN '1' ELSE '0' END AS sfyby
+FROM xg_student_msg s
+LEFT JOIN xg_class_msg c ON c.id = s.class_id AND c.deleted = 0
+WHERE 1=1
+  /*INCREMENTAL_FILTER_STUDENT*/
+ON DUPLICATE KEY UPDATE
+  xm = VALUES(xm),
+  ywxm = VALUES(ywxm),
+  cym = VALUES(cym),
+  xbm = VALUES(xbm),
+  csrq = VALUES(csrq),
+  jg = VALUES(jg),
+  sfzjh = VALUES(sfzjh),
+  zzmmm = VALUES(zzmmm),
+  xxm = VALUES(xxm),
+  sfdszn = VALUES(sfdszn),
+  zhgxsj = VALUES(zhgxsj),
+  scbz = VALUES(scbz),
+  dwh = VALUES(dwh),
+  szbh = VALUES(szbh),
+  sfzx = VALUES(sfzx),
+  sfyby = VALUES(sfyby);
+
+-- 5. 学生通讯 GXXS_GRTXZL <- xg_student_msg
+INSERT INTO GXXS_GRTXZL (
+  xh, yzbm, txdz, yddh, dzxx, jstxh, zhgxsj, scbz
+)
+SELECT
+  LEFT(s.student_no, 20) AS xh,
+  LEFT(IFNULL(s.postal_code, ''), 6) AS yzbm,
+  LEFT(IFNULL(NULLIF(s.current_address, ''), s.home_address), 180) AS txdz,
+  LEFT(IFNULL(s.phone, ''), 30) AS yddh,
+  LEFT(IFNULL(s.email, ''), 40) AS dzxx,
+  LEFT(IFNULL(NULLIF(s.qq, ''), s.wechat), 40) AS jstxh,
+  DATE_FORMAT(IFNULL(s.update_time, s.create_time), '%Y%m%d%H%i%s') AS zhgxsj,
+  CASE WHEN s.deleted = 1 THEN 'y' ELSE 'n' END AS scbz
+FROM xg_student_msg s
+WHERE 1=1
+  /*INCREMENTAL_FILTER_STUDENT*/
+ON DUPLICATE KEY UPDATE
+  yzbm = VALUES(yzbm),
+  txdz = VALUES(txdz),
+  yddh = VALUES(yddh),
+  dzxx = VALUES(dzxx),
+  jstxh = VALUES(jstxh),
+  zhgxsj = VALUES(zhgxsj),
+  scbz = VALUES(scbz);

+ 283 - 0
用户信息同步统一身份认证/sync_user_to_sso.sh

@@ -0,0 +1,283 @@
+#!/bin/bash
+# ===========================================
+# 统一身份认证用户数据同步脚本
+# 参考:学生换班级 process_student_class_change_fixed.sh
+# 功能:定时将校园系统用户/组织数据同步到认证中间库
+# 用法:
+#   ./sync_user_to_sso.sh          # 守护进程,按 CYCLE_INTERVAL 循环
+#   ./sync_user_to_sso.sh --once   # 仅执行一次同步
+#   ./sync_user_to_sso.sh --full   # 强制全量同步(一次)
+# ===========================================
+
+set -euo pipefail
+export LANG=C
+export LC_ALL=C
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+CONFIG_FILE="${SCRIPT_DIR}/data.conf"
+RUN_ONCE=false
+FORCE_FULL=false
+
+for arg in "$@"; do
+    case "$arg" in
+        --once) RUN_ONCE=true ;;
+        --full) FORCE_FULL=true ;;
+        --config=*) CONFIG_FILE="${arg#*=}" ;;
+    esac
+done
+
+if [[ ! -f "$CONFIG_FILE" ]]; then
+    echo "错误:配置文件 $CONFIG_FILE 未找到"
+    exit 1
+fi
+
+source "$CONFIG_FILE"
+
+mkdir -p "$LOG_DIR"
+LOG_FILE="${LOG_DIR}/sync_user_to_sso_$(date +%Y%m%d_%H%M%S).log"
+
+log() {
+    local level="$1"
+    local message="$2"
+    local timestamp
+    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
+    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
+}
+
+error_exit() {
+    log "ERROR" "$1"
+    [[ -f "$LOCK_FILE" ]] && rm -f "$LOCK_FILE"
+    exit 1
+}
+
+acquire_lock() {
+    if [[ -f "$LOCK_FILE" ]]; then
+        local pid
+        pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
+        if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
+            error_exit "同步进程已在运行 (PID: $pid)"
+        fi
+        rm -f "$LOCK_FILE"
+    fi
+    echo $$ > "$LOCK_FILE"
+}
+
+release_lock() {
+    [[ -f "$LOCK_FILE" ]] && rm -f "$LOCK_FILE"
+}
+
+trap release_lock EXIT
+
+_db_helper() {
+    local host="$1" port="$2" user="$3" pass="$4" db="$5" action="$6"
+    shift 6
+    python3 "${SCRIPT_DIR}/db_helper.py" \
+        --host "$host" --port "$port" \
+        --user "$user" --password "$pass" --database "$db" \
+        "$action" "$@"
+}
+
+source_mysql() {
+    local sql="$1"
+    _db_helper "$SOURCE_DB_HOST" "$SOURCE_DB_PORT" "$SOURCE_DB_USER" "$SOURCE_DB_PASS" "$SOURCE_DB_NAME" scalar "$sql" 2>&1
+}
+
+target_mysql() {
+    local sql="$1"
+    _db_helper "$TARGET_DB_HOST" "$TARGET_DB_PORT" "$TARGET_DB_USER" "$TARGET_DB_PASS" "$TARGET_DB_NAME" scalar "$sql" 2>&1
+}
+
+target_mysql_file() {
+    local file="$1"
+    _db_helper "$TARGET_DB_HOST" "$TARGET_DB_PORT" "$TARGET_DB_USER" "$TARGET_DB_PASS" "$TARGET_DB_NAME" file "$file" 2>&1
+}
+
+write_errlog() {
+    local table_name="$1"
+    local action="$2"
+    local err_msg="$3"
+    local escaped_msg
+    escaped_msg=$(echo "$err_msg" | sed "s/'/''/g" | head -c 900)
+    local sql="INSERT INTO ZFTAL_XTGL_ERRLOG (YWXTMC, YWXTBM, YWXTCZ, SJCCGC, CWZDXX, CWSJ)
+               VALUES ('${YWXTMC}', '${table_name}', '${action}', 'sync_user_to_sso.sh', '${escaped_msg}', DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s'));"
+    if [[ "$TARGET_DB_TYPE" == "mysql" ]]; then
+        target_mysql "$sql" >/dev/null 2>&1 || true
+    fi
+}
+
+get_last_sync_time() {
+    if [[ -f "$STATE_FILE" ]]; then
+        cat "$STATE_FILE"
+    else
+        echo "1970-01-01 00:00:00"
+    fi
+}
+
+save_sync_time() {
+    date '+%Y-%m-%d %H:%M:%S' > "$STATE_FILE"
+}
+
+build_incremental_filter() {
+    local alias="$1"
+    local last_sync="$2"
+    if [[ "$FORCE_FULL" == true ]] || [[ "$SYNC_MODE" == "full" ]]; then
+        echo ""
+    else
+        echo "AND (${alias}.update_time > '${last_sync}' OR ${alias}.create_time > '${last_sync}')"
+    fi
+}
+
+prepare_mysql_sync_sql() {
+    local last_sync="$1"
+    local template="${SCRIPT_DIR}/sync_sql/sync_all_mysql.sql"
+    local output="/tmp/sso_sync_mysql_$$.sql"
+
+    if [[ ! -f "$template" ]]; then
+        error_exit "同步 SQL 模板未找到: $template"
+    fi
+
+    local dept_filter teacher_filter student_filter
+    dept_filter=$(build_incremental_filter "d" "$last_sync")
+    teacher_filter=$(build_incremental_filter "u" "$last_sync")
+    student_filter=$(build_incremental_filter "s" "$last_sync")
+
+    sed \
+        -e "s|/\*INCREMENTAL_FILTER_DEPT\*/|${dept_filter}|g" \
+        -e "s|/\*INCREMENTAL_FILTER_TEACHER\*/|${teacher_filter}|g" \
+        -e "s|/\*INCREMENTAL_FILTER_STUDENT\*/|${student_filter}|g" \
+        "$template" > "$output"
+    echo "$output"
+}
+
+sync_mysql_target() {
+    local last_sync
+    last_sync=$(get_last_sync_time)
+    log "INFO" "开始 MySQL 目标库同步,模式: $([[ "$FORCE_FULL" == true || "$SYNC_MODE" == "full" ]] && echo 全量 || echo 增量),上次同步: $last_sync"
+
+    local sql_file
+    sql_file=$(prepare_mysql_sync_sql "$last_sync")
+    local result
+    if result=$(target_mysql_file "$sql_file" 2>&1); then
+        log "INFO" "MySQL 同步 SQL 执行成功"
+        rm -f "$sql_file"
+        save_sync_time
+        return 0
+    else
+        log "ERROR" "MySQL 同步失败: $result"
+        write_errlog "ALL" "数据同步" "$result"
+        rm -f "$sql_file"
+        return 1
+    fi
+}
+
+sync_oracle_target() {
+    local last_sync
+    last_sync=$(get_last_sync_time)
+    log "INFO" "开始 Oracle 目标库同步,上次同步: $last_sync"
+
+    if ! command -v sqlplus >/dev/null 2>&1; then
+        error_exit "未找到 sqlplus 命令,无法连接 Oracle 目标库"
+    fi
+
+    local export_dir="/tmp/sso_sync_export_$$"
+    mkdir -p "$export_dir"
+
+    # 导出部门
+    source_mysql "
+    SELECT
+      RIGHT(CAST(d.dept_id AS CHAR), 10),
+      REPLACE(IFNULL(d.dept_name,''),'\t',' '),
+      CASE WHEN d.parent_id IS NULL OR d.parent_id=0 THEN '' ELSE RIGHT(CAST(d.parent_id AS CHAR),10) END,
+      CASE WHEN d.status='0' AND d.del_flag='0' THEN '1' ELSE '0' END,
+      DATE_FORMAT(IFNULL(d.update_time,d.create_time),'%Y%m%d%H%i%s'),
+      CASE WHEN d.del_flag='1' THEN 'y' ELSE 'n' END
+    FROM sys_dept d
+    " > "${export_dir}/dept.tsv" || true
+
+    local oracle_script="${SCRIPT_DIR}/sync_oracle/merge_all.sql"
+    if [[ ! -f "$oracle_script" ]]; then
+        error_exit "Oracle MERGE 脚本未找到: $oracle_script"
+    fi
+
+    local conn="${TARGET_DB_USER}/${TARGET_DB_PASS}@${TARGET_ORACLE_CONNECT:-${TARGET_DB_HOST}:${TARGET_DB_PORT}/${TARGET_DB_NAME}}"
+    if sqlplus -S "$conn" @"$oracle_script" "$export_dir" >> "$LOG_FILE" 2>&1; then
+        log "INFO" "Oracle 同步完成"
+        save_sync_time
+        rm -rf "$export_dir"
+        return 0
+    else
+        log "ERROR" "Oracle 同步失败,详见日志"
+        write_errlog "ALL" "Oracle数据同步" "sqlplus执行失败"
+        rm -rf "$export_dir"
+        return 1
+    fi
+}
+
+run_sync_cycle() {
+    log "INFO" "========== 开始同步周期 =========="
+
+    # 测试源库连接
+    if ! source_mysql "SELECT 1;" >/dev/null 2>&1; then
+        log "ERROR" "源库连接失败: ${SOURCE_DB_USER}@${SOURCE_DB_HOST}:${SOURCE_DB_PORT}/${SOURCE_DB_NAME}"
+        return 1
+    fi
+
+    if [[ "$TARGET_DB_TYPE" == "mysql" ]]; then
+        if ! target_mysql "SELECT 1;" >/dev/null 2>&1; then
+            log "ERROR" "目标库连接失败: ${TARGET_DB_USER}@${TARGET_DB_HOST}:${TARGET_DB_PORT}/${TARGET_DB_NAME}"
+            return 1
+        fi
+        sync_mysql_target
+    elif [[ "$TARGET_DB_TYPE" == "oracle" ]]; then
+        sync_oracle_target
+    else
+        log "ERROR" "不支持的目标库类型: $TARGET_DB_TYPE"
+        return 1
+    fi
+
+    log "INFO" "========== 同步周期结束 =========="
+}
+
+verify_counts() {
+    log "INFO" "---------- 数据校验 ----------"
+    if [[ "$TARGET_DB_TYPE" != "mysql" ]]; then
+        log "INFO" "Oracle 目标库校验请使用 verify_sync.sh"
+        return 0
+    fi
+
+    local tables=("GXXX_YXSDWJBSJZL" "GXJG_JZGJCSJZLB" "GXJG_GRTXFSZL" "GXXS_XSJBSJZL" "GXXS_GRTXZL")
+    for t in "${tables[@]}"; do
+        local cnt
+        cnt=$(target_mysql "SELECT COUNT(*) FROM ${t};" 2>/dev/null | tail -1)
+        log "INFO" "目标表 ${t}: ${cnt} 条"
+    done
+
+    local src_dept src_teacher src_student
+    src_dept=$(source_mysql "SELECT COUNT(*) FROM sys_dept;" 2>/dev/null | tail -1)
+    src_teacher=$(source_mysql "SELECT COUNT(*) FROM sys_user WHERE account_type='1';" 2>/dev/null | tail -1)
+    src_student=$(source_mysql "SELECT COUNT(*) FROM xg_student_msg WHERE deleted=0;" 2>/dev/null | tail -1)
+    log "INFO" "源表 sys_dept: ${src_dept} 条"
+    log "INFO" "源表 sys_user(教职工): ${src_teacher} 条"
+    log "INFO" "源表 xg_student_msg(未删除): ${src_student} 条"
+}
+
+main() {
+    acquire_lock
+    log "INFO" "统一身份认证用户数据同步启动"
+    log "INFO" "源库: ${SOURCE_DB_TYPE} ${SOURCE_DB_USER}@${SOURCE_DB_HOST}:${SOURCE_DB_PORT}/${SOURCE_DB_NAME}"
+    log "INFO" "目标库: ${TARGET_DB_TYPE} ${TARGET_DB_USER}@${TARGET_DB_HOST}:${TARGET_DB_PORT}/${TARGET_DB_NAME}"
+    log "INFO" "同步模式: ${SYNC_MODE},循环间隔: ${CYCLE_INTERVAL}秒"
+
+    if [[ "$RUN_ONCE" == true ]]; then
+        run_sync_cycle && verify_counts
+        exit $?
+    fi
+
+    while true; do
+        run_sync_cycle && verify_counts || log "WARN" "本轮同步失败,将在 ${CYCLE_INTERVAL} 秒后重试"
+        log "INFO" "等待 ${CYCLE_INTERVAL} 秒后开始下一轮..."
+        sleep "$CYCLE_INTERVAL"
+    done
+}
+
+main "$@"

+ 81 - 0
用户信息同步统一身份认证/verify_sync.sh

@@ -0,0 +1,81 @@
+#!/bin/bash
+# ===========================================
+# 统一身份认证同步结果校验脚本
+# ===========================================
+
+set -euo pipefail
+export LANG=C
+export LC_ALL=C
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+CONFIG_FILE="${SCRIPT_DIR}/data.conf"
+[[ -f "$CONFIG_FILE" ]] || { echo "配置文件未找到"; exit 1; }
+source "$CONFIG_FILE"
+
+mysql_q() {
+    python3 "${SCRIPT_DIR}/db_helper.py" \
+        --host "$1" --port "$2" --user "$3" --password "$4" --database "$5" \
+        scalar "$6" 2>/dev/null
+}
+
+echo "========== 统一身份认证同步校验 =========="
+echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
+echo ""
+
+# 源库统计
+SRC_DEPT=$(mysql_q "$SOURCE_DB_HOST" "$SOURCE_DB_PORT" "$SOURCE_DB_USER" "$SOURCE_DB_PASS" "$SOURCE_DB_NAME" "SELECT COUNT(*) FROM sys_dept;")
+SRC_TEACHER=$(mysql_q "$SOURCE_DB_HOST" "$SOURCE_DB_PORT" "$SOURCE_DB_USER" "$SOURCE_DB_PASS" "$SOURCE_DB_NAME" "SELECT COUNT(*) FROM sys_user WHERE account_type='1';")
+SRC_STUDENT=$(mysql_q "$SOURCE_DB_HOST" "$SOURCE_DB_PORT" "$SOURCE_DB_USER" "$SOURCE_DB_PASS" "$SOURCE_DB_NAME" "SELECT COUNT(*) FROM xg_student_msg WHERE deleted=0;")
+
+echo "[源库] sys_dept:              ${SRC_DEPT}"
+echo "[源库] sys_user(教职工):      ${SRC_TEACHER}"
+echo "[源库] xg_student_msg(有效): ${SRC_STUDENT}"
+echo ""
+
+if [[ "$TARGET_DB_TYPE" == "mysql" ]]; then
+    TGT_DEPT=$(mysql_q "$TARGET_DB_HOST" "$TARGET_DB_PORT" "$TARGET_DB_USER" "$TARGET_DB_PASS" "$TARGET_DB_NAME" "SELECT COUNT(*) FROM GXXX_YXSDWJBSJZL;")
+    TGT_TEACHER=$(mysql_q "$TARGET_DB_HOST" "$TARGET_DB_PORT" "$TARGET_DB_USER" "$TARGET_DB_PASS" "$TARGET_DB_NAME" "SELECT COUNT(*) FROM GXJG_JZGJCSJZLB;")
+    TGT_TEACHER_TX=$(mysql_q "$TARGET_DB_HOST" "$TARGET_DB_PORT" "$TARGET_DB_USER" "$TARGET_DB_PASS" "$TARGET_DB_NAME" "SELECT COUNT(*) FROM GXJG_GRTXFSZL;")
+    TGT_STUDENT=$(mysql_q "$TARGET_DB_HOST" "$TARGET_DB_PORT" "$TARGET_DB_USER" "$TARGET_DB_PASS" "$TARGET_DB_NAME" "SELECT COUNT(*) FROM GXXS_XSJBSJZL;")
+    TGT_STUDENT_TX=$(mysql_q "$TARGET_DB_HOST" "$TARGET_DB_PORT" "$TARGET_DB_USER" "$TARGET_DB_PASS" "$TARGET_DB_NAME" "SELECT COUNT(*) FROM GXXS_GRTXZL;")
+    TGT_ERR=$(mysql_q "$TARGET_DB_HOST" "$TARGET_DB_PORT" "$TARGET_DB_USER" "$TARGET_DB_PASS" "$TARGET_DB_NAME" "SELECT COUNT(*) FROM ZFTAL_XTGL_ERRLOG;")
+
+    echo "[目标库] GXXX_YXSDWJBSJZL:    ${TGT_DEPT}"
+    echo "[目标库] GXJG_JZGJCSJZLB:     ${TGT_TEACHER}"
+    echo "[目标库] GXJG_GRTXFSZL:       ${TGT_TEACHER_TX}"
+    echo "[目标库] GXXS_XSJBSJZL:       ${TGT_STUDENT}"
+    echo "[目标库] GXXS_GRTXZL:         ${TGT_STUDENT_TX}"
+    echo "[目标库] ZFTAL_XTGL_ERRLOG:   ${TGT_ERR}"
+    echo ""
+
+    # 抽样对比
+    echo "---------- 抽样数据对比 ----------"
+    python3 "${SCRIPT_DIR}/db_helper.py" \
+        --host "$TARGET_DB_HOST" --port "$TARGET_DB_PORT" \
+        --user "$TARGET_DB_USER" --password "$TARGET_DB_PASS" --database "$TARGET_DB_NAME" \
+        query "SELECT gh, xm, dwh, scbz FROM GXJG_JZGJCSJZLB LIMIT 3" 2>/dev/null
+    python3 "${SCRIPT_DIR}/db_helper.py" \
+        --host "$TARGET_DB_HOST" --port "$TARGET_DB_PORT" \
+        --user "$TARGET_DB_USER" --password "$TARGET_DB_PASS" --database "$TARGET_DB_NAME" \
+        query "SELECT xh, xm, dwh, szbh, scbz FROM GXXS_XSJBSJZL LIMIT 3" 2>/dev/null
+
+    echo ""
+    # 简单一致性检查
+    PASS=true
+    [[ "$TGT_DEPT" -ge 1 ]] || { echo "WARN: 部门同步数量为0"; PASS=false; }
+    [[ "$TGT_TEACHER" -ge 1 ]] || { echo "WARN: 教职工同步数量为0"; PASS=false; }
+    [[ "$TGT_STUDENT" -ge 1 ]] || { echo "WARN: 学生同步数量为0"; PASS=false; }
+    [[ "$TGT_TEACHER" == "$TGT_TEACHER_TX" ]] || echo "WARN: 教职工基础表与通讯表数量不一致"
+    [[ "$TGT_STUDENT" == "$TGT_STUDENT_TX" ]] || echo "WARN: 学生基础表与通讯表数量不一致"
+
+    if [[ "$PASS" == true ]]; then
+        echo "校验结果: 通过(基础数据已写入)"
+        exit 0
+    else
+        echo "校验结果: 未通过"
+        exit 1
+    fi
+else
+    echo "目标库为 Oracle,请使用 sqlplus 手动校验目标表记录数"
+    exit 0
+fi

+ 110 - 0
用户信息同步统一身份认证/字段映射说明.md

@@ -0,0 +1,110 @@
+# 统一身份认证用户数据同步 - 字段映射说明
+
+## 1. 概述
+
+将校园管理系统(`hzdyjs_xg`)的组织与用户数据,定时同步到统一身份认证中间库 6 张标准表,供第三方平台拉取。
+
+| 目标表 | 说明 | 数据源 |
+|--------|------|--------|
+| `GXXX_YXSDWJBSJZL` | 院系所单位 | `sys_dept` |
+| `GXJG_JZGJCSJZLB` | 教职工基础 | `sys_user`(`account_type='1'`) |
+| `GXJG_GRTXFSZL` | 教职工通讯 | `sys_user`(`account_type='1'`) |
+| `GXXS_XSJBSJZL` | 学生基础 | `xg_student_msg` |
+| `GXXS_GRTXZL` | 学生通讯 | `xg_student_msg` |
+| `ZFTAL_XTGL_ERRLOG` | 同步错误日志 | 同步脚本写入 |
+
+**不同步字段**:`zp`(照片 BLOB)按约定不处理。
+
+---
+
+## 2. 单位表 GXXX_YXSDWJBSJZL
+
+| 目标字段 | 源字段 | 转换规则 |
+|----------|--------|----------|
+| dwh | sys_dept.dept_id | `RIGHT(CAST(dept_id AS CHAR), 10)`(雪花ID超长,取后10位) |
+| dwmc | sys_dept.dept_name | 截断至60字符 |
+| lsdwh | sys_dept.parent_id | parent_id=0 时 NULL,否则取后10位 |
+| dwyxbs | status + del_flag | 正常且未删除='1',否则='0' |
+| zhgxsj | update_time / create_time | `YYYYMMDDhhmmss` |
+| scbz | del_flag | '0'→'n','1'→'y' |
+
+---
+
+## 3. 教职工表 GXJG_JZGJCSJZLB
+
+| 目标字段 | 源字段 | 转换规则 |
+|----------|--------|----------|
+| gh | sys_user.user_name | 工号,截断至20字符 |
+| dwh | sys_user.dept_id | 取后10位 |
+| xm | sys_user.nick_name | 姓名 |
+| xbm | sys_user.sex | 0男→'1',1女→'2',其他→'0' |
+| dqztm | sys_user.status | 0正常→'11',停用→'99' |
+| zhgxsj | update_time / create_time | `YYYYMMDDhhmmss` |
+| scbz | del_flag | '0'→'n','1'→'y' |
+
+---
+
+## 4. 教职工通讯表 GXJG_GRTXFSZL
+
+| 目标字段 | 源字段 | 转换规则 |
+|----------|--------|----------|
+| id | sys_user.user_name | 主键,用工号 |
+| gh | sys_user.user_name | 工号 |
+| yddh | sys_user.phonenumber | 手机号 |
+| dzxx | sys_user.email | 邮箱 |
+| zhgxsj | update_time / create_time | `YYYYMMDDhhmmss` |
+| scbz | del_flag | '0'→'n','1'→'y' |
+
+---
+
+## 5. 学生表 GXXS_XSJBSJZL
+
+| 目标字段 | 源字段 | 转换规则 |
+|----------|--------|----------|
+| xh | xg_student_msg.student_no | 学号 |
+| xm | name | 姓名 |
+| ywxm | name_english | 英文名 |
+| cym | former_name | 曾用名 |
+| xbm | gender | 0男→'1',1女→'2' |
+| csrq | birth_date | `YYYYMMDD` |
+| jg | native_place | 籍贯 |
+| sfzjlxm | - | 固定 '1'(身份证) |
+| sfzjh | id_card | 身份证号 |
+| zzmmm | political_status | 政治面貌 |
+| xxm | blood_type | 血型 |
+| sfdszn | is_single_child | 1→'1',0→'0' |
+| dwh | college_id | 学院ID |
+| szbh | xg_class_msg.class_code | 关联班级表取班级代码 |
+| sfzx | student_status | 1/2/3→'1'在校,其他→'0' |
+| sfyby | student_status | 5毕业→'1',其他→'0' |
+| zhgxsj | update_time / create_time | `YYYYMMDDhhmmss` |
+| scbz | deleted | 0→'n',1→'y' |
+
+---
+
+## 6. 学生通讯表 GXXS_GRTXZL
+
+| 目标字段 | 源字段 | 转换规则 |
+|----------|--------|----------|
+| xh | student_no | 学号 |
+| yzbm | postal_code | 邮编 |
+| txdz | current_address / home_address | 优先现住址 |
+| yddh | phone | 手机 |
+| dzxx | email | 邮箱 |
+| jstxh | qq / wechat | 优先QQ |
+| zhgxsj | update_time / create_time | `YYYYMMDDhhmmss` |
+| scbz | deleted | 0→'n',1→'y' |
+
+---
+
+## 7. 注意事项
+
+1. **单位号 dwh 长度限制**:第三方表 `dwh` 仅 10 位,而 `sys_dept.dept_id` 为雪花ID(19位),当前取后10位;若对方有编码规范需另行映射。
+2. **增量同步**:基于源表 `update_time` / `create_time`,状态记录在 `.sync_state`。
+3. **全量同步**:设置 `SYNC_MODE=full` 或执行 `./sync_user_to_sso.sh --full --once`。
+4. **生产 Oracle**:修改 `data.conf.prod` 中 `TARGET_DB_TYPE=oracle` 及连接信息,使用 `sqlplus` 执行 MERGE(模板见 `sync_oracle/`)。
+
+---
+
+**版本**: v1.0.0  
+**更新时间**: 2026-08-24

+ 179 - 0
用户信息同步统一身份认证/建表脚本_mysql.sql

@@ -0,0 +1,179 @@
+-- ==========================================
+-- 统一身份认证中间库建表脚本(MySQL 适配版)
+-- 说明:由第三方 Oracle 建表语句转换,仅供本地 MySQL 测试使用
+-- 原始 Oracle 脚本:3.在认证中间库建立表.sql(请勿修改原文件)
+-- ==========================================
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- GXXX_YXSDWJBSJZL 院系所单位基本数据子类
+-- ----------------------------
+DROP TABLE IF EXISTS `GXXX_YXSDWJBSJZL`;
+CREATE TABLE `GXXX_YXSDWJBSJZL` (
+  `dwh`    VARCHAR(10)  NOT NULL COMMENT '单位号',
+  `dwmc`   VARCHAR(60)  DEFAULT NULL COMMENT '单位名称',
+  `dwywmc` VARCHAR(180) DEFAULT NULL COMMENT '单位英文名称',
+  `dwjc`   VARCHAR(12)  DEFAULT NULL COMMENT '单位简称',
+  `dwywjc` VARCHAR(8)   DEFAULT NULL COMMENT '单位英文简称',
+  `dwjp`   VARCHAR(6)   DEFAULT NULL COMMENT '单位简拼',
+  `dwdz`   VARCHAR(180) DEFAULT NULL COMMENT '单位地址',
+  `lsdwh`  VARCHAR(10)  DEFAULT NULL COMMENT '隶属单位号',
+  `dwlbm`  VARCHAR(1)   DEFAULT NULL COMMENT '单位类别码',
+  `dwbbm`  VARCHAR(1)   DEFAULT NULL COMMENT '单位办别码',
+  `dwyxbs` VARCHAR(1)   DEFAULT NULL COMMENT '单位有效标识',
+  `sxrq`   VARCHAR(8)   DEFAULT NULL COMMENT '失效日期',
+  `sfst`   VARCHAR(1)   DEFAULT NULL COMMENT '是否实体',
+  `jlny`   VARCHAR(6)   DEFAULT NULL COMMENT '建立年月',
+  `dwfzrh` VARCHAR(20)  DEFAULT NULL COMMENT '单位负责人号',
+  `zhgxsj` VARCHAR(20)  DEFAULT NULL COMMENT '最后更新时间',
+  `scbz`   VARCHAR(1)   DEFAULT NULL COMMENT '删除标志:y/n',
+  `dwjb`   VARCHAR(8)   DEFAULT NULL COMMENT '单位级别',
+  PRIMARY KEY (`dwh`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GXXX0301院系所单位基本数据子类';
+
+-- ----------------------------
+-- GXJG_JZGJCSJZLB 教职工基础数据子类表
+-- ----------------------------
+DROP TABLE IF EXISTS `GXJG_JZGJCSJZLB`;
+CREATE TABLE `GXJG_JZGJCSJZLB` (
+  `gh`      VARCHAR(20) NOT NULL COMMENT '工号',
+  `dwh`     VARCHAR(10) NOT NULL COMMENT '单位号',
+  `xm`      VARCHAR(36) DEFAULT NULL COMMENT '姓名',
+  `ywxm`    VARCHAR(60) DEFAULT NULL COMMENT '英文姓名',
+  `xmpy`    VARCHAR(60) DEFAULT NULL COMMENT '姓名拼音',
+  `cym`     VARCHAR(36) DEFAULT NULL COMMENT '曾用名',
+  `xbm`     VARCHAR(1)  DEFAULT NULL COMMENT '性别码',
+  `csrq`    VARCHAR(8)  DEFAULT NULL COMMENT '出生日期',
+  `csdm`    VARCHAR(6)  DEFAULT NULL COMMENT '出生地码',
+  `jg`      VARCHAR(20) DEFAULT NULL COMMENT '籍贯',
+  `mzm`     VARCHAR(2)  DEFAULT NULL COMMENT '民族码',
+  `gjdqm`   VARCHAR(3)  DEFAULT NULL COMMENT '国籍/地区码',
+  `sfzjlxm` VARCHAR(1)  DEFAULT NULL COMMENT '身份证件类型码',
+  `sfzjh`   VARCHAR(20) DEFAULT NULL COMMENT '身份证件号',
+  `hyztm`   VARCHAR(2)  DEFAULT NULL COMMENT '婚姻状况码',
+  `gatqwm`  VARCHAR(2)  DEFAULT NULL COMMENT '港澳台侨外码',
+  `zzmmm`   VARCHAR(2)  DEFAULT NULL COMMENT '政治面貌码',
+  `jkzkm`   VARCHAR(2)  DEFAULT NULL COMMENT '健康状况码',
+  `xyzjm`   VARCHAR(2)  DEFAULT NULL COMMENT '信仰宗教码',
+  `xxm`     VARCHAR(1)  DEFAULT NULL COMMENT '血型码',
+  `zp`      LONGBLOB    DEFAULT NULL COMMENT '照片(不同步)',
+  `sfzjyxq` VARCHAR(17) DEFAULT NULL COMMENT '身份证件有效期',
+  `xqh`     VARCHAR(2)  DEFAULT NULL COMMENT '校区号',
+  `zgxlm`   VARCHAR(3)  DEFAULT NULL COMMENT '最高学历码',
+  `whcdm`   VARCHAR(3)  DEFAULT NULL COMMENT '文化程度码',
+  `cjgzny`  VARCHAR(6)  DEFAULT NULL COMMENT '参加工作年月',
+  `lxrq`    VARCHAR(8)  DEFAULT NULL COMMENT '来校日期',
+  `qxrq`    VARCHAR(8)  DEFAULT NULL COMMENT '起薪日期',
+  `cjny`    VARCHAR(6)  DEFAULT NULL COMMENT '从教年月',
+  `bzlbm`   VARCHAR(2)  DEFAULT NULL COMMENT '编制类别码',
+  `jzglbm`  VARCHAR(3)  DEFAULT NULL COMMENT '教职工类别码',
+  `rkzkm`   VARCHAR(2)  DEFAULT NULL COMMENT '任课状况码',
+  `dabh`    VARCHAR(20) DEFAULT NULL COMMENT '档案编号',
+  `dawb`    LONGTEXT    DEFAULT NULL COMMENT '档案文本',
+  `dqztm`   VARCHAR(2)  DEFAULT NULL COMMENT '当前状态码',
+  `tc`      VARCHAR(100) DEFAULT NULL COMMENT '特长',
+  `xklbm`   VARCHAR(6)  DEFAULT NULL COMMENT '学科类别码',
+  `yjxkm`   VARCHAR(4)  DEFAULT NULL COMMENT '一级学科码',
+  `ejxkm`   VARCHAR(6)  DEFAULT NULL COMMENT '二级学科码',
+  `yjfx`    VARCHAR(100) DEFAULT NULL COMMENT '研究方向',
+  `zhgxsj`  VARCHAR(20) DEFAULT NULL COMMENT '最后更新时间',
+  `scbz`    VARCHAR(1)  DEFAULT NULL COMMENT '删除标志:y/n',
+  `zhxw`    VARCHAR(32) DEFAULT NULL COMMENT '最后学位',
+  `zc`      VARCHAR(32) DEFAULT NULL COMMENT '职称',
+  `ygxs`    VARCHAR(2)  DEFAULT NULL COMMENT '用工形式',
+  PRIMARY KEY (`gh`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GXJG0101教职工基础数据子类表';
+
+-- ----------------------------
+-- GXXS_XSJBSJZL 学生基本数据子类
+-- ----------------------------
+DROP TABLE IF EXISTS `GXXS_XSJBSJZL`;
+CREATE TABLE `GXXS_XSJBSJZL` (
+  `xh`      VARCHAR(20) NOT NULL COMMENT '学号',
+  `xm`      VARCHAR(60) DEFAULT NULL COMMENT '姓名',
+  `ywxm`    VARCHAR(60) DEFAULT NULL COMMENT '英文姓名',
+  `xmpy`    VARCHAR(60) DEFAULT NULL COMMENT '姓名拼音',
+  `cym`     VARCHAR(60) DEFAULT NULL COMMENT '曾用名',
+  `xbm`     VARCHAR(1)  DEFAULT NULL COMMENT '性别码',
+  `csrq`    VARCHAR(10) DEFAULT NULL COMMENT '出生日期',
+  `csdm`    VARCHAR(200) DEFAULT NULL COMMENT '出生地码',
+  `jg`      VARCHAR(60) DEFAULT NULL COMMENT '籍贯',
+  `mzm`     VARCHAR(2)  DEFAULT NULL COMMENT '民族码',
+  `gjdqm`   VARCHAR(50) DEFAULT NULL COMMENT '国籍/地区码',
+  `sfzjlxm` VARCHAR(2)  DEFAULT NULL COMMENT '身份证件类型码',
+  `sfzjh`   VARCHAR(20) DEFAULT NULL COMMENT '身份证件号',
+  `hyzkm`   VARCHAR(10) DEFAULT NULL COMMENT '婚姻状况码',
+  `gatqwm`  VARCHAR(2)  DEFAULT NULL COMMENT '港澳台侨外码',
+  `zzmmm`   VARCHAR(2)  DEFAULT NULL COMMENT '政治面貌码',
+  `jkzkm`   VARCHAR(20) DEFAULT NULL COMMENT '健康状况码',
+  `xyzjm`   VARCHAR(50) DEFAULT NULL COMMENT '信仰宗教码',
+  `xxm`     VARCHAR(30) DEFAULT NULL COMMENT '血型码',
+  `sfzjyxq` VARCHAR(17) DEFAULT NULL COMMENT '身份证件有效期',
+  `sfdszn`  VARCHAR(1)  DEFAULT NULL COMMENT '是否独生子女',
+  `zp`      LONGBLOB    DEFAULT NULL COMMENT '照片(不同步)',
+  `zhgxsj`  VARCHAR(20) DEFAULT NULL COMMENT '最后更新时间',
+  `scbz`    VARCHAR(1)  DEFAULT NULL COMMENT '删除标志:y/n',
+  `ksh`     VARCHAR(20) DEFAULT NULL COMMENT '考生号',
+  `dwh`     VARCHAR(20) NOT NULL COMMENT '单位号(学院)',
+  `szbh`    VARCHAR(20) DEFAULT NULL COMMENT '所在班号',
+  `sfzx`    VARCHAR(10) DEFAULT NULL COMMENT '是否在校',
+  `sfyby`   VARCHAR(6)  DEFAULT NULL COMMENT '是否已毕业',
+  PRIMARY KEY (`xh`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GXXS0101学生基本数据子类';
+
+-- ----------------------------
+-- GXXS_GRTXZL 学生个人通讯子类
+-- ----------------------------
+DROP TABLE IF EXISTS `GXXS_GRTXZL`;
+CREATE TABLE `GXXS_GRTXZL` (
+  `yzbm`   VARCHAR(6)   DEFAULT NULL COMMENT '邮政编码',
+  `txdz`   VARCHAR(180) DEFAULT NULL COMMENT '通信地址',
+  `dh`     VARCHAR(50)  DEFAULT NULL COMMENT '电话',
+  `yddh`   VARCHAR(30)  DEFAULT NULL COMMENT '移动电话',
+  `czdh`   VARCHAR(30)  DEFAULT NULL COMMENT '传真电话',
+  `dzxx`   VARCHAR(40)  DEFAULT NULL COMMENT '电子信箱',
+  `wldz`   VARCHAR(60)  DEFAULT NULL COMMENT '网络地址',
+  `jstxh`  VARCHAR(40)  DEFAULT NULL COMMENT '即时通讯号',
+  `xh`     VARCHAR(20)  NOT NULL COMMENT '学号',
+  `zhgxsj` VARCHAR(20)  DEFAULT NULL COMMENT '最后更新时间',
+  `scbz`   VARCHAR(1)   DEFAULT NULL COMMENT '删除标志:y/n',
+  PRIMARY KEY (`xh`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GXXS0109学生个人通讯子类';
+
+-- ----------------------------
+-- GXJG_GRTXFSZL 教职工个人通讯方式子类
+-- ----------------------------
+DROP TABLE IF EXISTS `GXJG_GRTXFSZL`;
+CREATE TABLE `GXJG_GRTXFSZL` (
+  `yzbm`   VARCHAR(6)   DEFAULT NULL COMMENT '邮政编码',
+  `txdz`   VARCHAR(180) DEFAULT NULL COMMENT '通信地址',
+  `dh`     VARCHAR(30)  DEFAULT NULL COMMENT '电话',
+  `yddh`   VARCHAR(30)  DEFAULT NULL COMMENT '移动电话',
+  `czdh`   VARCHAR(30)  DEFAULT NULL COMMENT '传真电话',
+  `wldz`   VARCHAR(60)  DEFAULT NULL COMMENT '网络地址',
+  `jstxh`  VARCHAR(40)  DEFAULT NULL COMMENT '即时通讯号',
+  `gh`     VARCHAR(20)  NOT NULL COMMENT '工号',
+  `dzxx`   VARCHAR(40)  DEFAULT NULL COMMENT '电子信箱',
+  `zhgxsj` VARCHAR(20)  DEFAULT NULL COMMENT '最后更新时间',
+  `scbz`   VARCHAR(1)   DEFAULT NULL COMMENT '删除标志:y/n',
+  `id`     VARCHAR(48)  NOT NULL COMMENT '主键ID',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GXJG0102教职工个人通讯方式子类';
+
+-- ----------------------------
+-- ZFTAL_XTGL_ERRLOG 系统错误日志表
+-- ----------------------------
+DROP TABLE IF EXISTS `ZFTAL_XTGL_ERRLOG`;
+CREATE TABLE `ZFTAL_XTGL_ERRLOG` (
+  `YWXTMC`  VARCHAR(100)  DEFAULT NULL COMMENT '业务系统名称',
+  `YWXTBM`  VARCHAR(100)  DEFAULT NULL COMMENT '业务系统表名',
+  `YWXTCZ`  VARCHAR(200)  DEFAULT NULL COMMENT '业务系统操作',
+  `SJCFQ`   VARCHAR(100)  DEFAULT NULL COMMENT '操作涉及触发器',
+  `SJCCGC`  VARCHAR(100)  DEFAULT NULL COMMENT '操作涉及存储过程',
+  `CWZDXX`  VARCHAR(1000) DEFAULT NULL COMMENT '报错字段信息',
+  `CWSJ`    VARCHAR(20)   DEFAULT NULL COMMENT '错误时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统错误日志表';
+
+SET FOREIGN_KEY_CHECKS = 1;