From f3869785ff7e41126ba5e2fae2c4249bd798a38f Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Wed, 22 Jul 2026 09:51:31 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/README.md | 2 +- backend/pom.xml | 4 +- ...tjson1.2.83漏洞整改-升级Fastjson2兼容包.md | 52 +++ .../database/vw_jw_student_export.sql | 203 +++++++++ .../学生基本信息对接说明.md | 78 ++++ .../学生当前状态(附件2).xlsx | Bin 0 -> 3926 bytes .../第一技师学生基本信息.docx | Bin 0 -> 14879 bytes .../api_service/test_jw_cx_api.py | 132 ++++++ .../学生信息接口和学生异动接口调用说明.md | 402 ++++++++++++++++++ 9 files changed, 870 insertions(+), 3 deletions(-) create mode 100644 docs/技术问题解决记录/Fastjson1.2.83漏洞整改-升级Fastjson2兼容包.md create mode 100644 docs/设计文档/database/vw_jw_student_export.sql create mode 100644 跟教务系统同步学生基础信息/学生基本信息对接说明.md create mode 100644 跟教务系统同步学生基础信息/学生当前状态(附件2).xlsx create mode 100644 跟教务系统同步学生基础信息/第一技师学生基本信息.docx create mode 100755 跟教务系统同步学生异动信息/api_service/test_jw_cx_api.py create mode 100644 跟教务系统同步学生异动信息/api_service/学生信息接口和学生异动接口调用说明.md diff --git a/backend/README.md b/backend/README.md index 5d6dc9c0..199ac325 100644 --- a/backend/README.md +++ b/backend/README.md @@ -68,7 +68,7 @@ Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11
| 数据库主键 | 采用 雪花ID 基于时间戳的 有序增长 唯一ID 再也不用为分库分表 数据合并主键冲突重复而发愁 | 采用 数据库自增ID 支持数据量有限 不支持多数据源主键唯一 | | WebSocket协议 | 基于 Spring 封装的 WebSocket 协议 扩展了Token鉴权与分布式会话同步 不再只是基于单机的废物 | 无 | | SSE推送 | 采用 Spring SSE 实现 扩展了Token鉴权与分布式会话同步 | 无 | -| 序列化 | 采用 Jackson Spring官方内置序列化 靠谱!!! | 采用 fastjson bugjson 远近闻名 | +| 序列化 | 采用 Jackson Spring官方内置序列化 靠谱!!!(JustAuth 传递依赖已统一升级为 Fastjson2 1.x 兼容包 2.0.61) | 采用 fastjson bugjson 远近闻名 | | 分布式幂等 | 参考美团GTIS防重系统简化实现(细节可看文档) | 手动编写注解基于aop实现 | | 分布式锁 | 采用 Lock4j 底层基于 Redisson | 无 | | 分布式任务调度 | 采用 SnailJob 天生支持分布式 统一的管理中心 支持多种数据库 支持分片重试DAG任务流等 | 采用 Quartz 基于数据库锁性能差 集群需要做很多配置与改造 | diff --git a/backend/pom.xml b/backend/pom.xml index 261234a4..417a8c33 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -43,8 +43,8 @@ 2.28.22 3.3.4 - - 1.2.83 + + 2.0.61 8.7.2-20250101 diff --git a/docs/技术问题解决记录/Fastjson1.2.83漏洞整改-升级Fastjson2兼容包.md b/docs/技术问题解决记录/Fastjson1.2.83漏洞整改-升级Fastjson2兼容包.md new file mode 100644 index 00000000..5da03810 --- /dev/null +++ b/docs/技术问题解决记录/Fastjson1.2.83漏洞整改-升级Fastjson2兼容包.md @@ -0,0 +1,52 @@ +# Fastjson 1.2.83 漏洞整改(升级 Fastjson2 兼容包) + +## 背景 + +客户通报 Fastjson `<= 1.2.83` 存在远程代码执行风险,要求排查整改。建议升级至 Fastjson2。 + +## 排查结论 + +| 项 | 说明 | +|---|---| +| 业务序列化 | 使用 Jackson(`ruoyi-common-json`),业务代码未直接调用 Fastjson | +| 引入路径 | `JustAuth 1.16.7`(`ruoyi-common-social`)传递依赖 `com.alibaba:fastjson:1.2.83` | +| 影响功能 | 第三方社交登录/绑定、微信小程序登录(JustAuth) | + +## 整改方案 + +采用 **Fastjson2 的 1.x 兼容包**(坐标仍为 `com.alibaba:fastjson`,版本切到 `2.0.x`),无需修改 JustAuth 调用代码。 + +修改位置:`backend/pom.xml` + +```xml + +2.0.61 +``` + +`dependencyManagement` 中已统一锁定该版本,JustAuth 传递依赖会解析为: + +``` +JustAuth +└─ com.alibaba:fastjson:2.0.61 + └─ com.alibaba.fastjson2:fastjson2-extension:2.0.61 + └─ com.alibaba.fastjson2:fastjson2:2.0.61 +``` + +## 验证 + +```bash +cd backend +mvn -pl ruoyi-admin -am dependency:tree -Dincludes=com.alibaba:fastjson,com.alibaba.fastjson2:fastjson2 +``` + +确认结果中为 `com.alibaba:fastjson:jar:2.0.61`,且无 `1.2.83`。 + +## 回归建议 + +1. 账号密码登录(应不受影响) +2. 实际启用的第三方登录/绑定(如有) +3. 微信小程序登录(如有启用) + +## 变更日期 + +2026-07-22 diff --git a/docs/设计文档/database/vw_jw_student_export.sql b/docs/设计文档/database/vw_jw_student_export.sql new file mode 100644 index 00000000..52ecd432 --- /dev/null +++ b/docs/设计文档/database/vw_jw_student_export.sql @@ -0,0 +1,203 @@ +-- ============================================================================= +-- 学生基本信息 → 教务对接宽表视图 +-- 说明见:跟教务系统同步学生基础信息/学生基本信息对接说明.md +-- +-- 口径摘要: +-- * 身份证件类型:有 id_card 则「居民身份证」,否则 NULL +-- * 所在校区:有有效宿舍分配则取 xg_dorm_campus.campus_name,否则 NULL +-- * 班主任工号:class_teacher_id → sys_user.user_no(空则 user_name) +-- * 学生是否在校:默认「在校」;退学(4)、毕业(5) 为「不在校」 +-- * 异动类型:固定 NULL +-- * 毕业时间/毕业学年学期:在籍按 入学年份+学制 推算;已毕业(5)取 BI_YE 异动 effective_date +-- * 学习经历/何年何月/在何地学习/在校照片:固定 NULL +-- * 性别:0→男,1→女 +-- * 成员1/2:父亲/母亲及关系、电话 +-- * 学生当前状态:输出教务中文名称(见对接说明 §状态映射) +-- ============================================================================= + +DROP VIEW IF EXISTS `vw_jw_student_export`; + +CREATE VIEW `vw_jw_student_export` AS +SELECT + (m.`name` COLLATE utf8mb4_unicode_ci) AS `姓名`, + (m.student_no COLLATE utf8mb4_unicode_ci) AS `学号`, + (m.phone COLLATE utf8mb4_unicode_ci) AS `联系电话`, + (CASE + WHEN NULLIF(TRIM(m.id_card), '') IS NOT NULL + THEN ('居民身份证' COLLATE utf8mb4_unicode_ci) + ELSE (CAST(NULL AS CHAR(32) CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci) + END) AS `身份证件类型`, + (m.id_card COLLATE utf8mb4_unicode_ci) AS `身份证件号`, + (COALESCE(m.class_name, cm.class_name) COLLATE utf8mb4_unicode_ci) AS `班级信息`, + (dorm.campus_name COLLATE utf8mb4_unicode_ci) AS `所在校区`, + (COALESCE(m.grade, cm.grade) COLLATE utf8mb4_unicode_ci) AS `所在年级`, + m.enrollment_year AS `入学年份`, + (COALESCE(m.college_name, cm.college_name) COLLATE utf8mb4_unicode_ci) AS `系部信息`, + (COALESCE(m.major_name, cm.major_name) COLLATE utf8mb4_unicode_ci) AS `专业信息`, + (COALESCE(NULLIF(TRIM(su.user_no), ''), su.user_name) COLLATE utf8mb4_unicode_ci) AS `班主任工号`, + (COALESCE(m.class_teacher_name, cm.class_teacher_name) COLLATE utf8mb4_unicode_ci) AS `班主任姓名`, + (CASE m.student_status + WHEN '1' THEN '在读' + WHEN '2' THEN '休学' + WHEN '3' THEN '复学' + WHEN '4' THEN '退学' + WHEN '5' THEN '毕业' + WHEN '6' THEN '其他' + WHEN '7' THEN '结业' + WHEN '8' THEN '停学' + WHEN '9' THEN '其他' + ELSE COALESCE(m.student_status, '') + END COLLATE utf8mb4_unicode_ci) AS `学生当前状态`, + (CASE + WHEN (m.student_status COLLATE utf8mb4_unicode_ci) IN ( + ('4' COLLATE utf8mb4_unicode_ci), + ('5' COLLATE utf8mb4_unicode_ci) + ) THEN ('不在校' COLLATE utf8mb4_unicode_ci) + ELSE ('在校' COLLATE utf8mb4_unicode_ci) + END) AS `学生是否在校`, + (CAST(NULL AS CHAR(32) CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci) AS `异动类型`, + (CASE cm.class_type + WHEN '0' THEN '三年制中级技工' + WHEN '1' THEN '三年制技师' + WHEN '2' THEN '四年制高级工' + WHEN '3' THEN '五年制高级技工' + WHEN '4' THEN '六年制技师' + WHEN '5' THEN '中高职一体' + ELSE cm.class_type + END COLLATE utf8mb4_unicode_ci) AS `培养层次`, + COALESCE(cm.school_system, m.school_system) AS `学制`, + ( + CASE + WHEN (m.student_status COLLATE utf8mb4_unicode_ci) = ('5' COLLATE utf8mb4_unicode_ci) + AND grad_sc.effective_date IS NOT NULL + THEN YEAR(grad_sc.effective_date) + WHEN m.enrollment_year IS NOT NULL + AND COALESCE(cm.school_system, m.school_system) IS NOT NULL + THEN m.enrollment_year + COALESCE(cm.school_system, m.school_system) + WHEN cm.graduation_year IS NOT NULL + THEN cm.graduation_year + ELSE NULL + END + ) AS `毕业时间`, + ( + CASE + WHEN ( + CASE + WHEN (m.student_status COLLATE utf8mb4_unicode_ci) = ('5' COLLATE utf8mb4_unicode_ci) + AND grad_sc.effective_date IS NOT NULL + THEN YEAR(grad_sc.effective_date) + WHEN m.enrollment_year IS NOT NULL + AND COALESCE(cm.school_system, m.school_system) IS NOT NULL + THEN m.enrollment_year + COALESCE(cm.school_system, m.school_system) + WHEN cm.graduation_year IS NOT NULL + THEN cm.graduation_year + ELSE NULL + END + ) IS NOT NULL + THEN CONCAT( + ( + CASE + WHEN (m.student_status COLLATE utf8mb4_unicode_ci) = ('5' COLLATE utf8mb4_unicode_ci) + AND grad_sc.effective_date IS NOT NULL + THEN YEAR(grad_sc.effective_date) + WHEN m.enrollment_year IS NOT NULL + AND COALESCE(cm.school_system, m.school_system) IS NOT NULL + THEN m.enrollment_year + COALESCE(cm.school_system, m.school_system) + WHEN cm.graduation_year IS NOT NULL + THEN cm.graduation_year + ELSE NULL + END + ) - 1, + '-', + ( + CASE + WHEN (m.student_status COLLATE utf8mb4_unicode_ci) = ('5' COLLATE utf8mb4_unicode_ci) + AND grad_sc.effective_date IS NOT NULL + THEN YEAR(grad_sc.effective_date) + WHEN m.enrollment_year IS NOT NULL + AND COALESCE(cm.school_system, m.school_system) IS NOT NULL + THEN m.enrollment_year + COALESCE(cm.school_system, m.school_system) + WHEN cm.graduation_year IS NOT NULL + THEN cm.graduation_year + ELSE NULL + END + ), + '-2' + ) + ELSE NULL + END + ) AS `毕业学年学期`, + (CAST(NULL AS CHAR(500) CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci) AS `学习经历`, + (CAST(NULL AS CHAR(128) CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci) AS `何年何月-何年何月`, + (CAST(NULL AS CHAR(256) CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci) AS `在何地学习`, + (CASE m.gender + WHEN '0' THEN '男' + WHEN '1' THEN '女' + ELSE NULL + END COLLATE utf8mb4_unicode_ci) AS `性别`, + (m.native_place COLLATE utf8mb4_unicode_ci) AS `籍贯地`, + (CAST(NULL AS CHAR(500) CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci) AS `在校照片`, + DATE(m.birth_date) AS `出生日期`, + (COALESCE(NULLIF(TRIM(m.home_address), ''), m.current_address) COLLATE utf8mb4_unicode_ci) AS `家庭现住址`, + (m.father_name COLLATE utf8mb4_unicode_ci) AS `成员1姓名`, + (m.mother_name COLLATE utf8mb4_unicode_ci) AS `成员2姓名`, + (CASE + WHEN NULLIF(TRIM(m.father_name), '') IS NOT NULL + THEN ('父亲' COLLATE utf8mb4_unicode_ci) + ELSE (CAST(NULL AS CHAR(16) CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci) + END) AS `成员1关系`, + (CASE + WHEN NULLIF(TRIM(m.mother_name), '') IS NOT NULL + THEN ('母亲' COLLATE utf8mb4_unicode_ci) + ELSE (CAST(NULL AS CHAR(16) CHARACTER SET utf8mb4) COLLATE utf8mb4_unicode_ci) + END) AS `成员2关系`, + (m.father_phone COLLATE utf8mb4_unicode_ci) AS `成员1联系电话`, + (m.mother_phone COLLATE utf8mb4_unicode_ci) AS `成员2联系电话`, + m.id AS `来源记录id` +FROM xg_student_msg m +LEFT JOIN xg_class_msg cm + ON cm.deleted = 0 + AND (cm.tenant_id COLLATE utf8mb4_unicode_ci) = (m.tenant_id COLLATE utf8mb4_unicode_ci) + AND cm.id = CAST(NULLIF(TRIM(m.class_id), '') AS UNSIGNED) +LEFT JOIN sys_user su + ON su.user_id = COALESCE(m.class_teacher_id, cm.class_teacher_id) + AND (su.del_flag COLLATE utf8mb4_unicode_ci) = ('0' COLLATE utf8mb4_unicode_ci) + AND (su.tenant_id COLLATE utf8mb4_unicode_ci) = (m.tenant_id COLLATE utf8mb4_unicode_ci) +LEFT JOIN ( + SELECT + dbs.student_id, + MAX(dc.campus_name) AS campus_name + FROM xg_dorm_bed_student dbs + INNER JOIN ( + SELECT student_id, MAX(assign_time) AS max_assign + FROM xg_dorm_bed_student + WHERE deleted = '0' + AND (status COLLATE utf8mb4_unicode_ci) = ('0' COLLATE utf8mb4_unicode_ci) + GROUP BY student_id + ) latest + ON latest.student_id = dbs.student_id + AND dbs.assign_time = latest.max_assign + INNER JOIN xg_dorm_campus dc + ON dc.id = dbs.campus_id + AND (dc.deleted COLLATE utf8mb4_unicode_ci) = ('0' COLLATE utf8mb4_unicode_ci) + WHERE dbs.deleted = '0' + AND (dbs.status COLLATE utf8mb4_unicode_ci) = ('0' COLLATE utf8mb4_unicode_ci) + GROUP BY dbs.student_id +) dorm ON dorm.student_id = m.id +LEFT JOIN ( + SELECT + sc.student_id, + MAX(sc.effective_date) AS effective_date + FROM xg_student_status_change sc + WHERE sc.deleted = 0 + AND sc.process_status = 1 + AND (sc.change_type COLLATE utf8mb4_unicode_ci) = ('BI_YE' COLLATE utf8mb4_unicode_ci) + GROUP BY sc.student_id +) grad_sc ON grad_sc.student_id = m.id +WHERE m.deleted = 0; + +-- ----------------------------------------------------------------------------- +-- 授权:用户 jw_cx 仅查询本视图(需使用具备 GRANT 权限的账号执行,如 root) +-- ----------------------------------------------------------------------------- +GRANT SELECT ON `hzdyjs_xg`.`vw_jw_student_export` TO 'jw_cx'@'%'; +FLUSH PRIVILEGES; diff --git a/跟教务系统同步学生基础信息/学生基本信息对接说明.md b/跟教务系统同步学生基础信息/学生基本信息对接说明.md new file mode 100644 index 00000000..2940056f --- /dev/null +++ b/跟教务系统同步学生基础信息/学生基本信息对接说明.md @@ -0,0 +1,78 @@ +# 学生基本信息 — 教务对接说明 + +## 接口 + +| 项目 | 说明 | +|------|------| +| 路径 | `GET /api/v1/students` | +| 鉴权 | Header `X-Jw-Cx-Key` | +| 数据范围 | 全部 `xg_student_msg.deleted = 0` 的学生,**分页返回** | +| 视图 DDL | `docs/设计文档/database/vw_jw_student_export.sql` | +| 服务代码 | `跟教务系统同步学生异动信息/api_service/app.py`(与异动接口同进程) | + +## 字段口径(已确认) + +| 教务字段 | 口径 | +|----------|------| +| 身份证件类型 | 有 `id_card` →「居民身份证」,否则 `null` | +| 所在校区 | 有有效宿舍分配(`xg_dorm_bed_student.status=0`)→ `xg_dorm_campus.campus_name`,否则 `null` | +| 班主任工号 | `class_teacher_id` → `sys_user.user_no`,空则 `user_name` | +| 学生是否在校 | 默认「在校」;学籍状态为 **退学(4)、毕业(5)** →「不在校」 | +| 异动类型 | 固定 `null` | +| 毕业时间 | 年份整数。已毕业(5):取 `BI_YE` 已处理异动的 `effective_date` 年份;否则 `入学年份 + 学制`(或班级 `graduation_year`) | +| 毕业学年学期 | `{毕业年-1}-{毕业年}-2`,例如 `2025-2026-2`;无法推算则为 `null` | +| 学习经历 / 何年何月-何年何月 / 在何地学习 | 固定 `null` | +| 在校照片 | 固定 `null` | +| 性别 | `0`→「男」,`1`→「女」 | +| 成员1 | 父亲姓名/关系「父亲」/ `father_phone` | +| 成员2 | 母亲姓名/关系「母亲」/ `mother_phone` | + +## 学生当前状态映射(本系统 → 教务中文名) + +| 本系统 `student_status` | 本系统标签 | 接口输出(教务中文名) | +|-------------------------|------------|------------------------| +| 1 | 在读 | 在读 | +| 2 | 休学 | 休学 | +| 3 | 复学 | 复学 | +| 4 | 退学 | 退学 | +| 5 | 毕业 | 毕业 | +| 6 | 不在籍 | 其他 | +| 7 | 结业 | 结业 | +| 8 | 停学 | 停学 | +| 9 | 分流 | 其他 | + +教务附件2 字典值(供联调参考,接口当前输出**中文名称**): + +| 教务状态名称 | 字典值 | +|--------------|--------| +| 在读 | XSDQZT016 | +| 休学 | XSDQZT015 | +| 退学 | XSDQZT014 | +| 停学 | XSDQZT013 | +| 复学 | XSDQZT012 | +| 毕业 | XSDQZT010 | +| 结业 | XSDQZT009 | +| 其他 | XSDQZT007 | + +## 部署 + +```bash +# 1. 建视图 +mysql -h -u root -p hzdyjs_xg < docs/设计文档/database/vw_jw_student_export.sql + +# 2. 重启 API(与异动接口相同进程) +export JW_CX_CONFIG=/path/to/config.prod.ini +python app.py + +# 3. 调用示例 +curl -s -H "X-Jw-Cx-Key: JWCX001" \ + "http://127.0.0.1:5050/api/v1/students?page=1&page_size=500" +``` + +响应含 `total`(总条数)、`page`、`page_size`、`total_pages`,教务可按页循环直至 `page >= total_pages`。 + +## 与异动接口关系 + +- 异动接口:`/api/v1/student-changes?year=2026`(按异动生效年份) +- 学生基本信息:`/api/v1/students?page=1&page_size=500`(分页,含 total / total_pages) +- 毕业时间在学生接口中仍可使用;**毕业记录不再通过异动接口同步**(异动接口已排除 `BI_YE`) diff --git a/跟教务系统同步学生基础信息/学生当前状态(附件2).xlsx b/跟教务系统同步学生基础信息/学生当前状态(附件2).xlsx new file mode 100644 index 0000000000000000000000000000000000000000..461d1b23b08cc999b154207d9b52767c0aad5cb8 GIT binary patch literal 3926 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 MDB{vY_%rl>0j3p7uK)l5 literal 0 HcmV?d00001 diff --git a/跟教务系统同步学生基础信息/第一技师学生基本信息.docx b/跟教务系统同步学生基础信息/第一技师学生基本信息.docx new file mode 100644 index 0000000000000000000000000000000000000000..8956958e3acdd03b80a29b2d0fb0c9bd2a472a17 GIT binary patch literal 14879 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 literal 0 HcmV?d00001 diff --git a/跟教务系统同步学生异动信息/api_service/test_jw_cx_api.py b/跟教务系统同步学生异动信息/api_service/test_jw_cx_api.py new file mode 100755 index 00000000..39e7fb74 --- /dev/null +++ b/跟教务系统同步学生异动信息/api_service/test_jw_cx_api.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +教务同步只读 API 联调脚本(Python 3.7+,仅标准库) + +调用两个业务接口并打印成功结果: + - GET /api/v1/students 学生基本信息(第 1 页) + - GET /api/v1/student-changes 学生异动(按年) + +用法: + python test_jw_cx_api.py --prod + python test_jw_cx_api.py --base-url http://web.hzdyjsxy.cn:18081/jw-cx-api --year 2026 + +环境变量:JW_CX_BASE_URL、JW_CX_API_KEY +""" +from __future__ import print_function + +import argparse +import json +import os +import sys +import time +import urllib.error +import urllib.parse +import urllib.request + +HEADER_API_KEY = "X-Jw-Cx-Key" +DEFAULT_BASE_URL = "http://127.0.0.1:5050" +PROD_BASE_URL = "http://web.hzdyjsxy.cn:18081" +DEFAULT_API_KEY = "JWCX001" + + +def _get_json(url, api_key, timeout=120): + req = urllib.request.Request(url, method="GET") + req.add_header(HEADER_API_KEY, api_key) + with urllib.request.urlopen(req, timeout=timeout) as resp: + body = resp.read().decode("utf-8") + return resp.status, json.loads(body) + + +def _join_url(base, path): + return base.rstrip("/") + "/" + path.lstrip("/") + + +def fetch_students(base_url, api_key, page=1, page_size=10): + qs = urllib.parse.urlencode({"page": page, "page_size": page_size}) + url = _join_url(base_url, "/api/v1/students?" + qs) + status, data = _get_json(url, api_key) + if status != 200 or data.get("code") != 200: + raise RuntimeError("学生基本信息接口失败: status=%s body=%s" % (status, data)) + return data + + +def fetch_student_changes(base_url, api_key, year): + qs = urllib.parse.urlencode({"year": year}) + url = _join_url(base_url, "/api/v1/student-changes?" + qs) + status, data = _get_json(url, api_key) + if status != 200 or data.get("code") != 200: + raise RuntimeError("学生异动接口失败: status=%s body=%s" % (status, data)) + return data + + +def main(): + parser = argparse.ArgumentParser(description="教务同步 API:获取学生信息与学生异动") + parser.add_argument("--base-url", default=None, help="API 根地址") + parser.add_argument("--prod", action="store_true", help="使用 %s" % PROD_BASE_URL) + parser.add_argument( + "--api-key", + default=os.environ.get("JW_CX_API_KEY", DEFAULT_API_KEY), + help="请求头 %s" % HEADER_API_KEY, + ) + parser.add_argument( + "--year", + type=int, + default=None, + help="异动筛选年份,默认当前自然年", + ) + parser.add_argument( + "--page-size", + type=int, + default=10, + help="学生基本信息每页条数,默认 10", + ) + args = parser.parse_args() + + if args.base_url: + base_url = args.base_url + elif args.prod: + base_url = PROD_BASE_URL + else: + base_url = os.environ.get("JW_CX_BASE_URL", DEFAULT_BASE_URL) + + year = args.year if args.year is not None else time.localtime().tm_year + + print("BASE_URL: %s" % base_url) + print("") + + try: + students = fetch_students(base_url, args.api_key, page=1, page_size=args.page_size) + print("=== 学生基本信息 GET /api/v1/students ===") + print( + "total=%s page=%s/%s 本页=%s条" + % ( + students.get("total"), + students.get("page"), + students.get("total_pages"), + len(students.get("data") or []), + ) + ) + if students.get("data"): + print("首条:", json.dumps(students["data"][0], ensure_ascii=False, indent=2)) + print("") + + changes = fetch_student_changes(base_url, args.api_key, year) + print("=== 学生异动 GET /api/v1/student-changes?year=%s ===" % year) + print("total=%s filter_year=%s" % (changes.get("total"), changes.get("filter_year"))) + if changes.get("data"): + print("首条:", json.dumps(changes["data"][0], ensure_ascii=False, indent=2)) + elif changes.get("total") == 0: + print("(当年无异动记录)") + + except (urllib.error.URLError, urllib.error.HTTPError, RuntimeError, ValueError) as e: + print("调用失败: %s" % e, file=sys.stderr) + sys.exit(1) + + print("") + print("调用成功") + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/跟教务系统同步学生异动信息/api_service/学生信息接口和学生异动接口调用说明.md b/跟教务系统同步学生异动信息/api_service/学生信息接口和学生异动接口调用说明.md new file mode 100644 index 00000000..15fd3b2f --- /dev/null +++ b/跟教务系统同步学生异动信息/api_service/学生信息接口和学生异动接口调用说明.md @@ -0,0 +1,402 @@ +# 教务系统对接 — 学工数据只读 API 调用说明 + +本文档面向**教务系统**开发/联调人员,说明如何调用学工系统提供的学生基本信息、学生学籍异动两类只读接口。 + +--- + +## 1. 概述 + +| 项目 | 说明 | +|------|------| +| 协议 | HTTP GET | +| 数据格式 | JSON,`Content-Type: application/json` | +| 字符编码 | UTF-8 | +| 鉴权方式 | 请求头 `X-Jw-Cx-Key`(由学工方分配) | +| 接口数量 | 2 个业务接口 + 1 个健康检查 | + +| 接口 | 路径 | 用途 | +|------|------|------| +| 学生基本信息 | `GET /api/v1/students` | 分页拉取全部在库学生快照 | +| 学生学籍异动 | `GET /api/v1/student-changes` | 按自然年拉取已办结异动 | +| 健康检查 | `GET /health` | 探测服务是否存活(无需密钥) | + +--- + +## 2. 访问地址 + +| 项目 | 值 | +|------|-----| +| Base URL | `http://web.hzdyjsxy.cn:18081/jw-cx-api` | + +完整示例: + +``` +http://web.hzdyjsxy.cn:18081/jw-cx-api/api/v1/students?page=1 +http://web.hzdyjsxy.cn:18081/jw-cx-api/api/v1/student-changes?year=2026 +http://web.hzdyjsxy.cn:18081/jw-cx-api/health +``` + +> 若域名、端口或路径前缀有调整,以学工方最新通知为准。 + +--- + +## 3. 鉴权 + +除 `/health` 外,所有业务接口必须在 **HTTP Header** 中携带: + +```http +X-Jw-Cx-Key: <学工方分配的密钥> +``` + +| 情况 | HTTP 状态 | 响应示例 | +|------|-----------|----------| +| 未携带或密钥错误 | 401 | `{"code":401,"msg":"无效的密钥或未提供请求头 X-Jw-Cx-Key"}` | +| 参数错误 | 400 | `{"code":400,"msg":"..."}` | +| 成功 | 200 | `{"code":200,"msg":"success",...}` | +| 服务端异常 | 500 | `{"code":500,"msg":"数据库查询失败",...}` | + +--- + +## 4. 统一响应结构 + +### 4.1 成功 + +```json +{ + "code": 200, + "msg": "success", + "data": [ /* 记录数组 */ ], + "total": 100 +} +``` + +各接口还会在顶层返回分页或筛选元数据(见下文)。 + +### 4.2 字段值约定 + +- 日期:`YYYY-MM-DD`(如 `"2026-04-21"`) +- 日期时间:`YYYY-MM-DD HH:mm:ss` +- 无数据:JSON `null`(不是空字符串 `""`) +- 异动类型、学生当前状态等枚举:**中文名称**(非内部代码) + +--- + +## 5. 接口一:学生基本信息 + +### 5.1 请求 + +``` +GET /api/v1/students +``` + +**Query 参数** + +| 参数 | 必填 | 说明 | 默认值 | +|------|------|------|--------| +| `page` | 否 | 页码,从 1 开始 | `1` | +| `page_size` | 否 | 每页条数,范围 1~5000 | `500` | + +**示例** + +```http +GET /api/v1/students?page=1&page_size=500 +X-Jw-Cx-Key: JWCX001 +``` + +### 5.2 响应元数据 + +| 字段 | 说明 | +|------|------| +| `total` | 学生总条数 | +| `page` | 当前页码 | +| `page_size` | 本页大小 | +| `total_pages` | 总页数 | + +### 5.3 拉取全量建议流程 + +学生数量可能达到数万条,**必须分页循环**: + +``` +1. 请求 page=1,读取 total、total_pages +2. 处理 data 数组 +3. page 递增,直到 page >= total_pages +``` + +伪代码: + +```python +page = 1 +page_size = 500 +while True: + resp = GET(f"/api/v1/students?page={page}&page_size={page_size}") + # 入库或处理 resp["data"] + if page >= resp["total_pages"]: + break + page += 1 +``` + +### 5.4 `data` 每条记录字段 + +| 字段名 | 必填(教务) | 说明 | +|--------|--------------|------| +| 姓名 | 是 | | +| 学号 | 是 | | +| 联系电话 | 否 | | +| 身份证件类型 | 否 | 有身份证号时为「居民身份证」,否则 `null` | +| 身份证件号 | 否 | | +| 班级信息 | 是 | 班级名称 | +| 所在校区 | 是 | 有宿舍分配时取校区名,否则 `null` | +| 所在年级 | 是 | | +| 入学年份 | 是 | 整数,如 `2024` | +| 系部信息 | 是 | 院系名称 | +| 专业信息 | 是 | 专业名称 | +| 班主任工号 | 是 | | +| 班主任姓名 | 是 | | +| 学生当前状态 | 是 | 教务中文名:在读、休学、复学、退学、毕业、结业、停学、其他 | +| 学生是否在校 | 是 | 「在校」或「不在校」;退学、毕业为不在校 | +| 异动类型 | 否 | 固定 `null` | +| 培养层次 | 否 | 如三年制技师、六年制技师等 | +| 学制 | 是 | 年数,整数 | +| 毕业时间 | 是 | **年份**整数;已毕业取异动生效年,否则按入学年+学制推算 | +| 毕业学年学期 | 是 | 如 `2025-2026-2`;无法推算时为 `null` | +| 学习经历 | 否 | 固定 `null` | +| 何年何月-何年何月 | 否 | 固定 `null` | +| 在何地学习 | 否 | 固定 `null` | +| 性别 | 是 | 「男」或「女」 | +| 籍贯地 | 否 | | +| 在校照片 | 否 | 固定 `null` | +| 出生日期 | 否 | `YYYY-MM-DD` | +| 家庭现住址 | 否 | | +| 成员1姓名 | 否 | 父亲姓名 | +| 成员2姓名 | 否 | 母亲姓名 | +| 成员1关系 | 否 | 有父亲姓名时为「父亲」 | +| 成员2关系 | 否 | 有母亲姓名时为「母亲」 | +| 成员1联系电话 | 否 | 父亲电话 | +| 成员2联系电话 | 否 | 母亲电话 | + +### 5.5 响应示例(节选) + +```json +{ + "code": 200, + "msg": "success", + "data": [ + { + "姓名": "张三", + "学号": "2024010101", + "联系电话": "13800138000", + "身份证件类型": "居民身份证", + "身份证件号": "3301************", + "班级信息": "24技师1班", + "所在校区": "西溪校区", + "所在年级": "2024", + "入学年份": 2024, + "系部信息": "电气工程系", + "专业信息": "电气工程", + "班主任工号": "T001", + "班主任姓名": "李老师", + "学生当前状态": "在读", + "学生是否在校": "在校", + "异动类型": null, + "培养层次": "三年制技师", + "学制": 3, + "毕业时间": 2027, + "毕业学年学期": "2026-2027-2", + "性别": "男" + } + ], + "total": 12500, + "page": 1, + "page_size": 500, + "total_pages": 25 +} +``` + +--- + +## 6. 接口二:学生学籍异动 + +### 6.1 请求 + +``` +GET /api/v1/student-changes +``` + +**Query 参数** + +| 参数 | 必填 | 说明 | 默认值 | +|------|------|------|--------| +| `year` | 否 | 按「异动生效时间」所在**自然年**筛选,范围 2000~2100 | 当前自然年 | + +**示例** + +```http +GET /api/v1/student-changes?year=2026 +X-Jw-Cx-Key: JWCX001 +``` + +### 6.2 数据范围说明 + +- 仅返回 **已办结** 记录(学籍异动 `process_status=1`,换班级 `status=1`) +- 按 **`异动生效时间` 的年份** 过滤(非提交时间) +- **不分页**,一次返回该年全部记录(数据量通常远小于学生全量) +- **不包含毕业(BI_YE)** 记录 +- **去重**:同一学号、同一登记日(`提交时间`)、同一异动类型仅保留一条;视图与 API 双层去重 + +### 6.3 响应元数据 + +| 字段 | 说明 | +|------|------| +| `total` | 本次返回条数(等于 `data` 长度) | +| `filter_year` | 实际筛选年份 | +| `filter_by_column` | 固定为 `异动生效时间` | + +### 6.4 `data` 每条记录字段 + +| 字段名 | 说明 | +|--------|------| +| 提交时间 | `YYYY-MM-DD` | +| 异动生效学期 | 固定 `null`(本系统无学年学期) | +| 异动类型 | 教务中文名:**退学、休学、复学、转班级** | +| 申请人学号 | | +| 姓名 | | +| 异动前所在年级 / 院系 / 专业 / 班级 / 学制 | | +| 异动前所在校区 | 固定 `null` | +| 异动后所在年级 / 班级 / 院系 / 专业 / 学制 | | +| 异动后所在校区 | 固定 `null` | +| 异动后班主任工号 / 姓名 | | +| 新名字 | 固定 `null` | +| 审核状态 | 固定「已通过」 | + +### 6.5 异动类型说明 + +| 接口返回值 | 含义 | +|------------|------| +| 退学 | 学籍异动-退学 | +| 休学 | 学籍异动-休学 | +| 复学 | 学籍异动-复学 | +| 转班级 | 换班级记录 | + +> 本系统**不同步**毕业异动;毕业相关信息请从「学生基本信息」接口获取。 + +### 6.6 响应示例 + +```json +{ + "code": 200, + "msg": "success", + "data": [ + { + "提交时间": "2026-03-15", + "异动生效学期": null, + "异动类型": "休学", + "申请人学号": "2024010101", + "姓名": "张三", + "异动前所在年级": "2024", + "异动前所在校区": null, + "异动前所在院系": "电气工程系", + "异动前所在专业": "电气工程", + "异动前所在班级": "24技师1班", + "异动前学制": 3, + "异动后所在校区": null, + "异动后所在年级": "2024", + "异动后所在班级": "24技师1班", + "异动后所在院系": "电气工程系", + "异动后所在专业": "电气工程", + "异动后学制": 3, + "异动后班主任工号": "T001", + "异动后班主任姓名": "李老师", + "新名字": null, + "审核状态": "已通过" + } + ], + "total": 12, + "filter_year": 2026, + "filter_by_column": "异动生效时间" +} +``` + +--- + +## 7. 调用示例 + +### 7.1 cURL + +```bash +# 健康检查 +curl -s "http://web.hzdyjsxy.cn:18081/jw-cx-api/health" + +# 学生基本信息(第 1 页) +curl -s -H "X-Jw-Cx-Key: JWCX001" \ + "http://web.hzdyjsxy.cn:18081/jw-cx-api/api/v1/students?page=1&page_size=500" + +# 学生异动(2026 年) +curl -s -H "X-Jw-Cx-Key: JWCX001" \ + "http://web.hzdyjsxy.cn:18081/jw-cx-api/api/v1/student-changes?year=2026" +``` + +### 7.2 Postman + +1. Method 选 **GET** +2. URL 填入完整地址(含 Query) +3. **Headers** 增加一行: + - Key: `X-Jw-Cx-Key` + - Value: 学工分配的密钥 +4. 点击 Send + +### 7.3 Python(标准库) + +项目内提供联调脚本: + +```bash +cd 跟教务系统同步学生异动信息/api_service +python test_jw_cx_api.py --base-url http://web.hzdyjsxy.cn:18081/jw-cx-api --year 2026 +``` + +--- + +## 8. 联调检查清单 + +| 步骤 | 操作 | +|------|------| +| 1 | 访问 `/health` 返回 `{"status":"ok"}` | +| 2 | 不带密钥访问业务接口,应返回 401 | +| 3 | 带正确密钥访问 `/api/v1/students?page=1`,`code=200` 且含 `total`、`total_pages` | +| 4 | 循环分页直至拉取全部学生 | +| 5 | 带密钥访问 `/api/v1/student-changes?year=2026`,核对异动类型中文名 | +| 6 | 确认 `null` 字段(校区、异动生效学期等)教务侧可接受 | + +--- + +## 9. 常见问题 + +**Q:为什么 80 端口访问 404?** +A:公网 API 走 **18081** 端口及 `/jw-cx-api` 路径,请使用本文 2.1 节地址。 + +**Q:学生接口为什么要分页?** +A:全量学生可能数万条,单次响应过大易超时;请按 `total_pages` 逐页拉取。 + +**Q:异动接口为何没有毕业?** +A:按对接约定,毕业不同步至异动接口;毕业状态与毕业时间见学生基本信息接口。 + +**Q:「复学并换班级」会有几条异动?** +A:可能同时存在「复学」「转班级」两条记录,请按学号+提交时间处理。 + +**Q:密钥如何更换?** +A:由学工方在服务端配置 `[api] api_key` 后通知教务更新 Header。 + +--- + +## 10. 相关文档 + +| 文档 | 说明 | +|------|------| +| [README.md](./README.md) | 服务部署与运维(学工侧) | +| [../异动类型映射说明.md](../异动类型映射说明.md) | 异动类型详细映射 | +| [../../跟教务系统同步学生基础信息/学生基本信息对接说明.md](../../跟教务系统同步学生基础信息/学生基本信息对接说明.md) | 学生字段口径与状态映射 | +| [test_jw_cx_api.py](./test_jw_cx_api.py) | 联调测试脚本 | + +--- + +**文档版本**:2026-07-02 +**维护**:学工系统开发组(接口变更时请同步更新本文档)