国内高校广泛使用的新版正方教务管理系统(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 临时重定向,Header Location 附带 ticket=ST-xxxx-cas。
    • 跟随该重定向请求 https://jw.zwu.edu.cn/sso/jziotlogin?ticket=ST-xxxx,正方教务服务端完成票据校验并下发正式鉴权 Cookie(JSESSIONID 与 route)。

若采用手动复制 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 网格渲染可视化课表。

四、工程实践与踩坑总结

  1. 体育俱乐部选课定位(板块课):

    • 体育课在正方底层归类为“板块课”,kklxdm 常为 05,如果只在主修课程 01 中检索可能查不到。
  2. 校区过滤陷阱:

    • 体育场馆(操场、网球场、体育馆)在后台录入时部分教务员可能未绑定校区标签,若前端过滤参数带了 校区:钱湖校区,会被正方 SQL 的 WHERE campus_id = ? 直接滤除。
  3. 高频轮询限流:

    • 在写自动抢课(--rush)脚本时,请求间隔建议不要低于 0.3s,避免被教务网关 WAF 或 Session 并发锁拦截引发 502 Bad Gateway。
  4. layout=default 导致参数污染与服务端报错:

    • 请求 zzxkyzb_cxZzxkYzbIndex.html 时切勿带 layout=default。带该参数会下发顶层门户外壳的 20+ 个非表单字段(如 csrftoken, cdTsxx, bbxtlx),当这些参数混合在后续 zzxkyzb_cxZzxkYzbPartDisplay.html 请求体中时,会直接引发服务端 系统运行异常,请稍后再试。必须使用纯净无外壳的入口页面提取基础表单。
  5. bklx_id 触发“非法访问”拦截:

    • 查询教学班明细 zzxkyzbjk_cxJxbWithKchZzxkYzb.html 时,请求体中绝对不能包含 bklx_id,否则正方服务端安全拦截器会直接返回 "0"(前端对应 (JS-1) 警告:你正在非法访问!)。必须在组装参数后移除 bklx_id。
  6. 当前开选轮次自动识别:

    • 页面表单隐藏域会动态注入 firstKklxdm(如当前开放的 05 体育分项)、firstKklxmc 和 firstXkkzId。客户端必须优先读取该动态值并作为默认查询板块,不可盲目硬编码主修课 01,因为不同阶段开放的选课板块截然不同。
  7. 已选课程查询与退选 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,周五):

  1. 计算开学日所在周的周一(2026-09-07,第一周周一 00:00:00);
  2. 计算当前真实日期的周一;
  3. 相差天数整除 7 加 1 即可得出当前的教学周次(Math.floor(diffDays / 7) + 1);
  4. 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)

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