1. 概述与核心场景 (Overview & Scenario)

正方教务管理系统 (Zhengfang Educational Administration System, JWGLXT) 是国内高校广泛采用的教务系统。在对其选课模块 (zzxkYzb.js / zzxkYzbChoosedZy.js) 进行深度逆向工程时,发现其采用了经典的企业级 jQuery 架构:大量次级情报(如教师履历、教材清单、课程大纲、选课生源结构、等级考成绩等)并不直接混在主列表 JSON 中,而是通过统一的弹窗协议 $.showDialog(url, title, options) 动态分步加载。

本文档系统沉淀正方教务系统次级弹窗端点的数据契约、参数格式、响应解析机制以及现代化 WebUI 的高可用适配方案。


2. 关键弹窗接口逆向清单 (Reverse-Engineered Dialog Endpoints)

正方教务前端使用 _path + '/xkgl/...' 或 _path + '/xsxk/...' 拼接请求路径,请求通常携带 X-Requested-With: XMLHttpRequest 头。

业务场景接口端点 (Endpoint)请求方法核心参数 (Payload)响应类型数据提取字段
教师档案/xkgl/common_cxJsxxModel.htmlPOSTjgh_id, kch_idHTML 片段教师姓名 (xm)、职称 (zc)、所属学院 (xy)、邮箱 (email)、研究方向 (yjfx)、教师简介 (jj)
教材清单/xsxk/tjxkyzb_cxJcxxList.htmlPOSTjxb_idJSON / HTML教材名称 (jcmc)、著者 (zz)、出版社 (cbs)、ISBN、定价 (dj)、版次 (banci)、订购状态 (sfyd)
课程大纲/xkgl/common_cxKcxxModel.htmlPOSTkch_idHTML 片段课程代码 (kch)、学分 (xf)、学时 (xs)、考核方式 (khfs)、教学目标 (jxmb)、平时/期末权重
生源分布/xkgl/common_cxJxbrsmxIndex.htmlPOSTkch_id, jxb_id, xnm, xqmHTML / JSON已选人数 (total_enrolled)、班级容量 (capacity)、年级分布 (by_grade)、学院分布 (by_college)
等级考试/cjcx/cjcx_cxDgXsdjks.htmlPOSTqueryModel.showCount=100, ndJSON 数组考试名称 (ksmc)、考试时间 (kssj)、成绩 (cj)、准考证号 (zwh)、证书状态 (zt)、分项成绩 (fxcj)
通识进度/xkcx/tsxxcx_cxTsxxkcjd.htmlGETgnmkdm=N154675, suJSON 结构模块代码 (code)、模块名称 (name)、已修学分 (acquired)、应修学分 (required)、达标状态 (status)

3. 成绩分项解析与 GPA 计算 (Sub-Score Breakdown & GPA Parsing)

在正方教务成绩查询接口 /cjcx/cjcx_cxDgXscj.html 的响应结果中,每门课程的原始记录对象本身已内嵌以下关键分项成绩字段:

  • pscj: 平时成绩 (Regular / Continuous Evaluation Score)
  • qzcj: 期中成绩 (Midterm Exam Score)
  • sycj: 实验成绩 (Laboratory / Practical Score)
  • qmcj: 期末成绩 / 卷面成绩 (Final Exam Score)
  • cj: 综合总评成绩 (Overall Final Grade)
  • jd: 课程学分绩点 (Grade Point, 4.0/5.0 Scale)
  • xf: 课程学分 (Course Credit)

现代前端解析时,应直接将上述分项映射为标准对象属性,避免二次发起网络请求,并在成绩行内以微型标签 (Chips) 形式提供明细展开。


4. 双模容灾与架构设计 (Dual-Mode Architecture & Resilience)

高校教务系统在实际运行中存在两个典型痛点:

  1. 网络拦截与内网隔离:校外 IP 直连往往被深信服 aTrust 或校园网统一身份认证网关拦截,无法直连;
  2. 会话生命周期 (Session Lifecycle):JSESSIONID 闲置 30 分钟后自动失效,导致接口返回重定向或空白页面。

设计规范:

  • 直连与演示双模热切换 (Live / Demo Toggle):
    前端提供一键状态切换按钮,后台客户端优先直连教务系统;在未配置 Cookie、会话过期或用户显式开启演示模式时,平滑降级至高保真结构化数据层 (demo_data.py)。
  • 无感解析与防御性字段映射:
    对所有来自正方教务的响应做空值校验与多别名字段兼容(例如等级考试中兼容 djksmc 与 ksmc、zwh 与 zkzh),防止因个别学期字段微调引起前端渲染异常。

5. 质量保证与端到端自动化验证 (Quality Assurance via Playwright)

为杜绝功能开发中的“打包票”问题,必须引入无头浏览器自动化测试 (Playwright),构建端到端回归测试套件:

  1. 测试用例覆盖矩阵:

    • 选课检索页通识选修进度看板正常计算与渲染;
    • 课程卡片【大纲考核】弹窗正常展开并读取能力目标;
    • 教学班列表内【教师简介】、【教材信息】、【生源分布】弹窗分别触发独立接口并回显内容;
    • 成绩页面国家等级考试证书展示卡片与平时/期中/实验/期末分项徽章正常呈现;
    • 周课表、考务倒计时、毕业自查、空闲自习室等全部 Tab 切换无控制台错误 (pageerror = 0, console.error = 0)。
  2. 测试脚本模板参考:
    通过 playwright.sync_api 监听 page.on("pageerror") 与 page.on("console"),并在断言失败时中断退出,形成可靠的发布门禁。

6. 功能模块代码 (gnmkdm) 与全局布局穿透机制 (Module Code & Layout Penetration)

在逆向正方教务管理系统架构时,经常遇到门户主页 index_initMenu.html 发生弹窗阻断(例如学生存在挂科未通过课程时强制弹窗 你有课程未通过,请点击查看详情并确认后方可进行后续操作,未点击确认前整个主菜单树被锁死)。

突破与穿透原理:

正方各功能模块均分配有统一的 功能模块代码 (gnmkdm,如 N100801、N253512 等)。只要在 URL 参数中附带目标模块代码与布局参数:

GET /jwglxt/{module_path}.html?gnmkdm={gnmkdm}&layout=default

系统安全网关即可识别为直达特定受权业务,从而彻底绕过门户首页的阻断弹窗与菜单树渲染流程。

核心功能模块代码表 (gnmkdm Matrix):

  • N100801: 查询个人信息 / 学籍全景画像档案 (/xsxxxggl/xsgrxxwh_cxXsgrxx.html)
  • N105505: 学籍预警与学业预警查询 (/xjyj/xjyj_cxXjyjIndex.html)
  • N105515: 学生学业情况综合查询 (/xsxy/xsxyqk_cxXsxyqkIndex.html)
  • N106005: 学生学籍异动 / 补办申请 (/xjyd/xjydsq_cxXjydsqIndex.html)
  • N253512: 自主选课主控台 (/xsxk/zzxkyzb_cxZzxkYzbIndex.html)
  • N305005: 学生学业成绩查询 (/cjcx/cjcx_cxDgXscj.html)
  • N358105: 学生期末考务与考试安排 (/kwgl/kscx_cxXsksxxIndex.html)
  • N2155: 空闲教室与自习室资源 (/cdjy/cdjy_cxKxcd.html)

7. 学籍档案与挂科预警数据协议 (Student Dossier & Unpassed Course Protocols)

7.1 全景学籍画像档案 (Student Academic Dossier)

  • 请求端点:GET /xsxxxggl/xsgrxxwh_cxXsgrxx.html?gnmkdm=N100801&layout=default&ckfa=1
  • 报文格式:服务端渲染 HTML。关键信息集中在表单静态组与隐藏 div(#jtcyHidDiv, #jcxxHidDiv, #cjxxHidDiv, #pyfaHidDiv)。
  • 核心抽取字段 (22 维):

    • 身份与生源:学号 (su)、姓名 (xm)、姓名拼音、性别、证件类型、身份证号码、出生日期、民族、高考考生号 (ksh)、入学报到日期;
    • 院系与学籍:年级、学院名称、专业名称、班级名称、学制、学籍状态 (在读/休学/毕业)、是否在校、培养层次 (本科)、学生类别;
    • 联络与多媒体:电子邮箱、通讯与家庭地址、学生电子证件照编码 URL (/photo/photo_cxEncodedXszp.html?type=2&encodeXhid=...)。

7.2 不及格与待重修课程查询 (Unpassed & Retake Courses)

  • 请求端点:POST /xtgl/index_cxXsNopassKcView.html?doType=query
  • 请求头:X-Requested-With: XMLHttpRequest
  • 响应格式:标准 jqGrid JSON 报文
  • 数据契约结构:

    {
      "currentPage": 1,
      "totalResult": 5,
      "items": [
        {
          "xnmmc": "2025-2026",
          "xqmmc": "2",
          "kch": "N1J00048",
          "kcmc": "高等数学A(下)",
          "xf": "4.0",
          "cj": "21",
          "maxcj": "21",
          "bfzcj": 21,
          "jd": 0,
          "cjbz": "需重修"
        }
      ]
    }
  • 选课应对与业务闭环:
    前端将挂科记录汇总统计未通过门数与累计未修学分;行末提供【去选课重修】快捷动作,点击后自动携带该门课程名称直跳选课搜索栏执行教学班秒查。

8. 反向代理子路径部署与动态 API URL 计算 (Reverse Proxy Subpath URL Resolution Pitfall)

故障现象:

本地开发环境下服务运行于根路径 http://127.0.0.1:8765/,前端直接调用 fetch('/api/student_profile') 正常;但一旦将应用反代挂载到二级路径(如 https://6767.chat/jwgl/),浏览器发起的网络请求仍会指向 https://6767.chat/api/student_profile,脱离 /jwgl/ 代理作用域,导致上游 Nginx 返回 HTTP 410 Gone 或 404 Not Found,并在前端抛出 Unexpected token '<', "<html>..." is not valid JSON。

根治原则:

纯静态前端必须实现动态基路径计算函数,基于当前页面的 window.location.pathname 拼接 API 前缀:

function getApiUrl(path) {
  const cleanPath = path.startsWith('/') ? path.slice(1) : path;
  let pathname = window.location.pathname.replace(/\/index\.html$/, '/');
  const prefix = pathname.endsWith('/') ? pathname : pathname + '/';
  return prefix + cleanPath;
}

应用内部所有 fetch() 调用均须统一通过 fetch(getApiUrl('/api/...')) 发起,确保无论是本地根域名调试还是生产子路径反代部署均能精确命中后端路由。


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web