新版正方教务系统数据接口逆向与选课自动化 (Zhengfang JWGLXT API Reverse Engineering and Course Selection Automation)
国内高校广泛使用的新版正方教务管理系统(jwglxt,基于 SpringMVC + jQuery/Bootstrap 架构)前端极为臃肿,单次按钮点击往往因大量无用 JS 执行和巨大 DOM 递归重排卡顿十数秒。但其底层后端的 RESTful 数据接口均为纯 JSON,单次往返仅需数十至两百毫秒。
通过逆向其核心接口,可实现完全脱离官方 Web 界面的轻量 CLI 工具或自建 WebUI。
一、鉴权策略:CAS 统一身份认证与会话管理
1. 全自动 CAS SSO 登录逆向(金智 CAS + 正方 SSO 中台)
高校正方教务通常不开放独立密码登录,而是接入金智统一身份认证平台(Wisedu CAS):
CAS 认证入口:
https://pass.zwu.edu.cn/cas/login?service=https://jw.zwu.edu.cn/sso/jziotlogin- 核心参数:
service必须严格指向正方教务的 SSO 票据消费端点https://jw.zwu.edu.cn/sso/jziotlogin(金智 IoT SSO 中台)。
- 核心参数:
页面隐藏凭据提取:
execution: CAS 流程上下文标识。pwdEncryptSalt: 服务端动态下发的 16 位 AES 密钥盐。
密码加密算法逆向:
- 算法: AES-128-CBC,填充模式 PKCS7。
- Key:
pwdEncryptSalt.encode('utf-8')(16 bytes)。 - IV: 16 位随机字符生成。
- 明文填充构造:
64位随机字符 + 原始密码明文。 - 密文输出: 经过 Base64 编码。
票据兑换 (Ticket Exchange):
- 提交 POST 请求至 CAS
login接口后,CAS 验证成功返回 HTTP 302 临时重定向,HeaderLocation附带ticket=ST-xxxx-cas。 - 跟随该重定向请求
https://jw.zwu.edu.cn/sso/jziotlogin?ticket=ST-xxxx,正方教务服务端完成票据校验并下发正式鉴权 Cookie(JSESSIONID与route)。
- 提交 POST 请求至 CAS
2. Cookie 会话复用与请求头约束
若采用手动复制 Cookie:
- 核心字段:
JSESSIONID与负载均衡会话路由标签(如route)。 请求头必备项:
User-Agent: 现代浏览器标准 UA。X-Requested-With:XMLHttpRequest(正方服务端由此识别是否为 AJAX 请求,若在常规页面 GET 中盲目携带可能触发 Spring Security HTTP 901 Session Timeout 拦截)。Referer:https://<host>/jwglxt/xtgl/index_initMenu.html。
3. 学生完整个人画像查询接口
- URL:
POST /jwglxt/xtgl/index_cxYhxx.html - Headers:
X-Requested-With: XMLHttpRequest 响应体:
{ "XM": "真实姓名", "yhm": "学号", "BJ": "行政班级 (如 电子25A1)", "JGMC": "开课学院 (如 信息与智能工程学院)", "yhlx": "student", "jsmc": "学生" }该接口响应极快(< 30ms),是校验登录态及获取当前登录学生档案的最佳轻量端点。
二、自主选课核心业务流接口
选课流程分为四步:读取选课轮次 ➔ 检索课程池 ➔ 查询班级名额 ➔ 异步选课落库。
1. 获取选课轮次与控制 ID (xkkz_id)
- URL:
POST /jwglxt/xsxk/zzxkyzb_cxZzxkYzbIndex.html?gnmkdm=N253512&layout=default&su={学号} - 说明: 页面包含当前开放的选课轮次,以及课程分类板块。
关键提取字段:
xkkz_id(选课控制 ID):每个学期、每个选课轮次都会生成唯一的xkkz_id,后续所有选课请求必须携带。kklxdm(开课类型代码):01: 本专业主修课程 / 推荐课程05: 板块课(体育俱乐部、分级分层选课、特殊选课)10: 通识教育选修课 / 校公选课
2. 检索课程池列表
- URL:
POST /jwglxt/xsxk/zzxkyzb_cxZzxkYzbPartDisplay.html?gnmkdm=N253512&su={学号} - Content-Type:
application/x-www-form-urlencoded 请求体关键参数:
bklx_id=01 # 对应 kklxdm kklxdm=01 # 课程类别代码 (01:主修, 05:体育/板块, 10:公选) xkkz_id={xkkz_id} # 轮次控制 ID searchBox=体育 # 模糊搜索关键词 (课程名/教师名/代码) filter_list[0]=体育 queryModel.showCount=50 # 分页大小 queryModel.currentPage=1 # 当前页码 time={时间戳毫秒}响应结构:
{ "totalResult": 12, "tmpList": [ { "kch": "PE101", "kcmc": "大学体育俱乐部(羽毛球)", "kch_id": "2026101019283", "xf": "1.0", "kkxymc": "基础教学部", "kclbmc": "基础课程" } ] }
3. 获取具体课程的教学班详情与实时余量
- URL:
POST /jwglxt/xsxk/zzxkyzbjk_cxJxbWithKchZzxkYzb.html?gnmkdm=N253512&su={学号} 请求体:
bklx_id=01 kklxdm=01 kch_id={kch_id} # 步骤2中拿到的课程内部 ID xkkz_id={xkkz_id} cxbj=0 fxbj=0响应列表:
返回该课程下开放的所有教学班。核心字段包括:do_jxb_id/jxb_id: 教学班唯一 ID(选课请求入参)。jxbmc: 教学班名称(如羽毛球01班)。jsxx/dgjsmc: 任课教师姓名。sksj/sktz: 上课时间与节次(如周二第3-4节)。yl/yxzrs: 实时剩余名额。rl/jxbrs: 班级总容量。
4. 提交选课(即时落库)
- URL:
POST /jwglxt/xsxk/zzxkyzb_xkBcZyZzxkYzb.html?gnmkdm=N253512&su={学号} 请求体:
jxb_ids={jxb_id} kch_id={kch_id} xkkz_id={xkkz_id} kklxdm=01 rwlx=1 rlkz=0 cxbj=0 fxbj=0响应:
- 选课成功:
{"flag": "1", "msg": "选课成功"} - 名额已满:
{"flag": "0", "msg": "该教学班选课人数已满"} - 时间冲突:
{"flag": "0", "msg": "上课时间与已选课程冲突"}
- 选课成功:
- 机制判断: 新版正方为单条异步机制,只要服务端返回
flag: "1"即代表写入教务数据库成功,无需全局提交。
5. 获取已选课程清单接口 (ChoosedDisplay)
- URL:
POST /jwglxt/xsxk/zzxkyzb_cxZzxkYzbChoosedDisplay.html?gnmkdm=N253512&su={学号} - 说明: 客户端用于渲染已选课程侧边栏/清单的真实端点(旧接口
zzxkyzb_cxYxkcZzxkYzb.html在新版中已被废弃并返回 910)。 请求体关键参数:
jg_id={学院ID} zyh_id={专业号ID} njdm_id={年级代码} zyfx_id={专业方向} bh_id={班级ID} xz={学制} ccdm={层次代码} xqh_id={校区ID} xkxnm=2026 xkxqm=3 xkly={选课来源}核心返回字段:
isInxksj:"1"表示当前处于选课轮次内、该课程允许退选;"0"表示必修课或非当前退选轮次课程(不可退选)。sfktk:"1"为可退课。do_jxb_id: 该教学班的 128 位加密 ID(退选接口必须携带)。jxdd/sksj: 授课地点与具体节次安排。
6. 退选接口 (tuikBcZzxkYzb)
- URL:
POST /jwglxt/xsxk/zzxkyzb_tuikBcZzxkYzb.html?gnmkdm=N253512&su={学号} 请求体:
kch_id={kch_id} jxb_ids={do_jxb_id} # 注意:必须是 128 位 AES 加密教学班 ID,明文 ID 会报异常 xkxnm=2026 xkxqm=3 txbsfrl=0 xkkz_id={xkkz_id}- 响应:
{"flag": "1", "msg": "退选成功"}
三、个人周课表导出接口
- URL:
POST /jwglxt/kbcx/xskbcx_cxXsKb.html?gnmkdm=N2151&su={学号} 请求体:
xnm=2026 # 学年 (如 2026 代表 2026-2027) xqm=3 # 学期 (3 为第一学期,12 为第二学期) kzlx=ck响应字段:
kbList: 课程格子数组,包含kcmc(课程名)、xm(教师)、cdmc(教室场地)、xqj(星期几,1~7)、jc(节次,如1-2)。可直接映射至 7×6 网格渲染可视化课表。
四、工程实践与踩坑总结
体育俱乐部选课定位(板块课):
- 体育课在正方底层归类为“板块课”,
kklxdm常为05,如果只在主修课程01中检索可能查不到。
- 体育课在正方底层归类为“板块课”,
校区过滤陷阱:
- 体育场馆(操场、网球场、体育馆)在后台录入时部分教务员可能未绑定校区标签,若前端过滤参数带了
校区:钱湖校区,会被正方 SQL 的WHERE campus_id = ?直接滤除。
- 体育场馆(操场、网球场、体育馆)在后台录入时部分教务员可能未绑定校区标签,若前端过滤参数带了
高频轮询限流:
- 在写自动抢课(
--rush)脚本时,请求间隔建议不要低于0.3s,避免被教务网关 WAF 或 Session 并发锁拦截引发502 Bad Gateway。
- 在写自动抢课(
layout=default导致参数污染与服务端报错:- 请求
zzxkyzb_cxZzxkYzbIndex.html时切勿带layout=default。带该参数会下发顶层门户外壳的 20+ 个非表单字段(如csrftoken,cdTsxx,bbxtlx),当这些参数混合在后续zzxkyzb_cxZzxkYzbPartDisplay.html请求体中时,会直接引发服务端系统运行异常,请稍后再试。必须使用纯净无外壳的入口页面提取基础表单。
- 请求
bklx_id触发“非法访问”拦截:- 查询教学班明细
zzxkyzbjk_cxJxbWithKchZzxkYzb.html时,请求体中绝对不能包含bklx_id,否则正方服务端安全拦截器会直接返回"0"(前端对应(JS-1) 警告:你正在非法访问!)。必须在组装参数后移除bklx_id。
- 查询教学班明细
当前开选轮次自动识别:
- 页面表单隐藏域会动态注入
firstKklxdm(如当前开放的05体育分项)、firstKklxmc和firstXkkzId。客户端必须优先读取该动态值并作为默认查询板块,不可盲目硬编码主修课01,因为不同阶段开放的选课板块截然不同。
- 页面表单隐藏域会动态注入
已选课程查询与退选 Token 要求:
- 新版已选课程查询需通过
zzxkyzb_cxZzxkYzbChoosedDisplay.html调取,返回列表中的isInxksj: "1"标识该课程当前可退选(未开放退选或主修班级为"0")。 - 退选接口中的
jxb_ids参数必须传 128 位的do_jxb_id密文 Token,若传明文jxb_id,后端解密失败会直接报未知异常或操作失败。
- 新版已选课程查询需通过
五、周课表智能排课算法与数据处理
正方周课表接口(xskbcx_cxXsKb.html)返回的是学期全量扁平数组(如 21+ 条记录),前端要呈现符合人类直觉的课表,必须经过二次算法处理:
1. 周次区间判定算法 (isCourseInWeek)
正方周次字段 zcd 格式多样(如 1-16周、1-8周、15-17周、1-6周,9-15周(单)、7-8周,10-16周(双)、7周,13周 等):
- 拆解英文与中文逗号分段;
- 解析
(单)与(双)奇偶周限制; - 展开连字符范围(
start至end); - 计算目标周次与各区间的交集。
2. 智能开学周推算
高校以周为周期循环,周一为每周起始日。给定开学日期(如 2026-09-11,周五):
- 计算开学日所在周的周一(
2026-09-07,第一周周一 00:00:00); - 计算当前真实日期的周一;
- 相差天数整除 7 加 1 即可得出当前的教学周次(
Math.floor(diffDays / 7) + 1); - WebUI 进入课表时自动默认选中当前教学周,精准还原当周实际日程。
六、成绩、考务、毕业审核与自习室拓展接口实现 (Implemented Panels)
1. 成绩查询与 GPA 绩点分析接口 (cjcx_cxDgXscj)
- 端点:
POST /jwglxt/cjcx/cjcx_cxDgXscj.html?doType=query&gnmkdm=N305005&su={学号} 请求参数:
xnm= # 学年 (留空查全学年) xqm= # 学期 (3:第1学期, 12:第2学期, 留空查全部) kklxdm= # 课程类别 (可选) kcxzdm= _search=false nd={时间戳毫秒} queryModel.showCount=500 queryModel.currentPage=1 queryModel.sortOrder=asc time=0核心数据字段:
items: 数组列表。kcmc(课程名)、kch(课程号)、xf(学分)、cj(总评成绩)、numeric_score(解析为浮点数)、jd(绩点)、kcxzmc(课程性质如必修/核心/选修)、ksxz(正常考试/补考)、kkbmmc(开课学院)。
绩点与均分算法:
- 累计 GPA:
sum(jd * xf) / sum(xf) - 必修 GPA: 仅针对
kcxzmc包含“必修”或“核心”的课程加权核算 - 加权平均分:
sum(numeric_score * xf) / sum(xf) - 成绩等级划分: 优秀 (>=90)、良好 (80-89)、中等 (70-79)、及格 (60-69)、不及格 (<60)
- 累计 GPA:
2. 考务日程与考试倒计时接口 (kswdcx_cxXsKskbcx)
- 端点:
POST /jwglxt/kwgl/kswdcx_cxXsKskbcx.html?doType=query&gnmkdm=N358105&su={学号} - 请求参数:
xnm,xqm,ksmcdm(考试批次),queryModel.showCount=100,queryModel.sortName=kssj 核心数据字段:
items:kcmc: 考试科目名kssj: 考试时间(如2026-06-22 09:00-11:00)cdmc: 考场地点(如钱湖校区4号楼201)zwh: 座位号(超大醒目显示)ksfsmc: 考试形式(闭卷/开卷/机考/答辩)xm: 监考教师名单zkzh: 准考证号
倒计时解析算法:
- 将
kssj提取为起始绝对时间戳,结合客户端本地时间计算剩余天数与小时数。 - 小于 24 小时标记为紧急(红色),7 天内标记为临近(黄色)。
- 将
3. 培养方案与毕业学分自查接口 (tsxxcx_cxTsxxkcjd)
- 端点:
GET /jwglxt/xkcx/tsxxcx_cxTsxxkcjd.html?gnmkdm=N154675&su={学号} 分类统计模型:
- 160 学分总制,分为 6 大模块:通识必修课、通识选修课、学科基础课、专业核心课、专业选修课、集中实践与毕设。
- 比对成绩单各模块已修学分,得出各模块要求学分、已修学分、缺口学分(
deficit = max(0, required - acquired))及完成百分比。
4. 空闲自习教室查询接口 (cdjy_cxKxcd)
- 端点:
POST /jwglxt/cdjy/cdjy_cxKxcd.html?gnmkdm=N2155&su={学号} - 参数:
lh(楼号如钱湖4号楼)、xqj(星期几)、jcdm(节次代码) - 展示特性: 标注教室容量、空调、多媒体、插座标签与空闲时段。
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web