视频加载失败

曲阜师范大学图书馆预约系统 API 逆向文档

2989 字
15 分钟
曲阜师范大学图书馆预约系统 API 逆向文档

本文档通过逆向分析曲阜师范大学图书馆预约系统得出,供第三方开发者参考,用于开发基于图书馆预约系统的辅助工具。请注意:所有接口均为 HTTP 协议(非 HTTPS)。


目录#

  1. 域名与基础设施
  2. 认证流程(IDS + CAS)
  3. 通用请求头
  4. AES 加密方案汇总
  5. API 接口清单
  6. 响应码约定
  7. 预约错误消息速查表
  8. 安全与风控注意事项

1. 域名与基础设施#

系统域名
IDS 统一认证http://ids.qfnu.edu.cn
图书馆业务http://libyy.qfnu.edu.cn

所有接口均基于 HTTP(非 HTTPS),Content-Type 以 application/json 和 application/x-www-form-urlencoded 为主。


2. 认证流程(IDS + CAS)#

图书馆业务接口需要携带 Bearer Token。获取 Token 的完整链路为 5 步 IDS 登录 + 1 个可选滑块验证:

Step 1: GET IDS 登录页 → 提取 salt + execution
Step 1.5: GET/POST 滑块验证码 → 获取/提交滑块(IDS 按需开启)
Step 2: POST IDS 登录表单 → 302 跳转至 CAS 回调
Step 3: GET 跟随跳转 → 302 跳转至 /api/cas/cas
Step 4: GET /api/cas/cas → 从 Location 末尾 32 字符提取 CAS Token
Step 5: POST /api/cas/user → 用 CAS Token 换取 Bearer Token

Step 1: 获取 IDS 登录页#

项目内容
方法GET
URLhttp://ids.qfnu.edu.cn/authserver/login?service=http%3A%2F%2Flibyy.qfnu.edu.cn%2Fapi%2Fcas%2Fcas
请求头User-Agent: <浏览器 UA>,Referer: http://libyy.qfnu.edu.cn/
响应HTML 页面

从 HTML 中正则提取两个参数:

  • id="pwdEncryptSalt" value="..." → salt(用于密码加密的 AES 密钥)
  • name="execution" value="..." → execution(登录表单必需参数)

Step 1.5: 滑块验证码(按需触发)#

并非每次登录都要求滑块验证,需探测式处理。

1.5a 获取滑块图片#

项目内容
方法GET
URLhttp://ids.qfnu.edu.cn/authserver/common/openSliderCaptcha.htl
请求头User-Agent: <浏览器 UA>,Referer: http://ids.qfnu.edu.cn/authserver/login?service=...

成功响应:

{
"bigImage": "<base64 编码的缺口背景图>",
"smallImage": "<base64 编码的滑块小块图>"
}
  • 若 bigImage / smallImage 为空,或响应体不是合法 JSON → IDS 未开启滑块验证,跳过此步。
  • smallImage 末 16 字节用作后续滑块加密的 AES 密钥。

1.5b 提交滑块验证#

项目内容
方法POST
URLhttp://ids.qfnu.edu.cn/authserver/common/verifySliderCaptcha.htl
Content-Typeapplication/x-www-form-urlencoded
请求头Referer: http://ids.qfnu.edu.cn/authserver/login?service=...,Origin: http://ids.qfnu.edu.cn

请求体: sign=<AES加密后的轨迹数据>(form-urlencoded)

轨迹数据明文 JSON:

{
"canvasLength": 280,
"moveLength": <移动距离(px)>,
"tracks": [
{"a": <累计x位移>, "b": <累计y偏移>, "c": <时间间隔(ms)>},
...
]
}
  • 加密方案:AES-128-CBC,Key = smallImage 末 16 字节,IV = 随机 16 字符,64 字符随机前缀 + JSON,PKCS7 填充。
  • 缺口识别思路:灰度化 → 5×5 模糊 → Sobel X 边缘检测 → Y 轴 30%-70% 区域列梯度求和 → 最大梯度列为缺口位置。
  • 轨迹生成思路:25-40 步 6 阶段模拟(暂停→低速→中速→高速→低速→暂停),带随机 Y 轴抖动。

成功响应: {"errorCode": 1}

建议每次失败后重新获取验证码图片再试,最多重试 10 次(间隔 3 秒)。

Step 2: 提交 IDS 登录#

项目内容
方法POST
URLhttp://ids.qfnu.edu.cn/authserver/login?service=http%3A%2F%2Flibyy.qfnu.edu.cn%2Fapi%2Fcas%2Fcas
Content-Typeapplication/x-www-form-urlencoded

请求体(form 字段):

字段值说明
username学号明文
passwordAES 密文加密方案见下方
captcha""留空
_eventIdsubmit固定值
clltuserNameLogin固定值
dlltgeneralLogin固定值
lt""留空
executionStep 1 提取的值

密码加密方案(AES-128-CBC):

  • Key = Step 1 提取的 salt
  • IV = 随机 16 字符
  • 明文 = 64 字符随机前缀 + 原始密码
  • PKCS7 填充 → Base64 编码

成功响应: HTTP 302,Location 头指向图书馆 CAS 回调地址。

Step 3-4: CAS 重定向#

Step方法URL说明
3GETStep 2 的 Location跟随 IDS 跳转到图书馆
4GEThttp://libyy.qfnu.edu.cn/api/cas/cas核心 CAS 接口

Step 4 返回 HTTP 302,从响应的 Location 头末尾 32 个字符提取 CAS Token。

Step 5: 换取 Bearer Token#

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/cas/user
Content-Typeapplication/json

请求体:

{"cas": "<32位CAS Token>"}

成功响应:

{
"code": 1,
"member": {
"name": "用户名",
"token": "<Bearer Token>"
},
"msg": "..."
}
  • 返回的 member.token 即为后续所有业务接口使用的 Bearer Token。
  • 实测 Token 有效期约 90-100 分钟。
  • 整个认证到业务请求链路必须共用同一个 HTTP Cookie Jar(重定向不自动跟随,需手动提取 Location)。

3. 通用请求头#

图书馆业务接口(Step 5 之后)统一携带以下请求头:

Content-Type: application/json
Connection: keep-alive
Accept: application/json, text/plain, */*
lang: zh
X-Requested-With: XMLHttpRequest
User-Agent: <浏览器UA>
Origin: http://libyy.qfnu.edu.cn
Referer: http://libyy.qfnu.edu.cn/h5/index.html
Authorization: bearer<Token>

⚠️ 注意: Authorization 头格式为 "bearer" + Token,bearer 与 Token 之间没有空格。这与标准 OAuth 2.0 的 Bearer <token> 格式不同,是此系统特有的格式。


4. AES 加密方案汇总#

系统中存在 3 种 AES-128-CBC 加密场景:

用途KeyIV明文格式填充
IDS 密码加密salt(登录页提取)随机 16 字符64 字符随机前缀 + 密码PKCS7
滑块 sign 加密smallImage 末 16 字节随机 16 字符64 字符随机前缀 + 轨迹 JSONPKCS7
预约/签到数据加密YYYYMMDD + reverse(YYYYMMDD)(共 16 字节,如日期 20260705 时 Key = 2026070550706020)ZZWBKJ_ZHIHUAWEI(固定)纯 JSONPKCS7

5. API 接口清单#

5.1 获取校区/楼宇/区域#

获取三级层级结构(校区 → 楼层 → 区域)及可用日期列表。

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/reserve/index/quickSelect
鉴权不需要(authorization 可传空字符串)

请求体:

{
"id": "1",
"date": "2026-07-05",
"categoryIds": ["1"],
"members": 0,
"authorization": ""
}

成功响应(code 为字符串 "0",注意与其他接口不同):

{
"code": "0",
"data": {
"date": ["2026-07-05", "2026-07-06"],
"premises": [
{
"id": "1",
"name": "曲阜校区",
"nameMerge": "",
"parentId": "0",
"topId": "1",
"total_num": 1200,
"free_num": 586
}
],
"storey": [
{
"id": "10",
"name": "2F",
"nameMerge": "",
"parentId": "1",
"topId": "10",
"total_num": 300,
"free_num": 150
}
],
"area": [
{
"id": "100",
"name": "东区阅览室",
"nameMerge": "",
"parentId": "10",
"topId": "100",
"total_num": 56,
"free_num": 23
}
]
},
"msg": "..."
}

字段说明:

  • premises:校区级
  • storey:楼层级(通过 parentId 关联到 premises[].id)
  • area:区域级(通过 parentId 关联到 storey[].id),预约时使用的 roomID 即 area 的 id
  • total_num / free_num:可能是字符串也可能是数字,需兼容解析

5.2 获取时间段#

根据 area ID 获取对应日期可预约的时间段。

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/Seat/date
鉴权需要 Bearer Token

请求体:

{"build_id": 100}

成功响应:

{
"code": 1,
"data": [
{
"day": "2026-07-05",
"times": [
{"id": 123456},
{"id": 123457}
]
}
],
"msg": "..."
}
  • 匹配 day == 目标日期 的条目,取第一个 times[0].id 作为时间段 ID(segment)。
  • id 字段可能是字符串或数字。

5.3 获取座位列表#

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/Seat/seat
鉴权通常需要 Bearer Token(但部分查询场景可不校验)

请求体:

{
"area": 100,
"segment": 123456,
"day": "2026-07-05",
"startTime": "08:00",
"endTime": "22:00"
}

成功响应:

{
"code": 1,
"data": [
{
"id": "13855",
"no": "071",
"name": "71号",
"status_name": "空闲",
"status": "1"
},
{
"id": "13856",
"no": "072",
"name": "72号",
"status_name": "已预约",
"status": "2"
}
],
"msg": "..."
}

座位状态码对照:

statusstatus_name含义
"1"空闲可预约
"2"已预约已被占用
"3"锁定暂时不可用
"6"使用中正在被使用

5.4 预约座位#

最核心的接口。

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/Seat/confirm
鉴权需要 Bearer Token(注意 Authorization: bearer<Token> 无空格)

请求体:

{"aesjson": "<AES加密后的预约数据>"}

加密前明文 JSON:

{"seat_id": "13855", "segment": "123456"}
  • 加密方案:AES-128-CBC,Key = 当天日期(YYYYMMDD) + 反转后的当天日期,IV = ZZWBKJ_ZHIHUAWEI,PKCS7 填充后 Base64 编码。
  • 将加密结果填入 aesjson 字段。

成功响应:

{
"code": 0,
"msg": "预约成功",
"data": {"id": "2599221"}
}

data 字段兼容性: 可能是对象 {"id":"..."}、字符串 "..." 或数字,需全部兼容。

所有可能的响应情况及处理建议:

响应特征含义处理建议
code:0, msg:"预约成功"预约成功提取 data 中的预约 ID
code:0, msg:"当前用户在该时段已存在座位预约,不可重复预约"已预约视为成功,标记已预约状态
code:0, msg:null开放临界态(放号瞬间过渡状态)短暂重试(秒级),超时后判定失败
code:0, msg:"该空间当前状态不可预约"该座位已被占用/不可用换下一个候选座位
code:0, msg:"申请人已被列入此系统黑名单,不能预约!"黑名单立即终止全部重试,通知用户
code:-7, msg:...限流立即终止,5 分钟后重试
code:10001, msg:"其他设备登录"登录态被踢下线重新登录后重试
msg 含"开放预约时间"还未到预约开放时间等待至开放时间
msg 含"尚未登录"Token 过期重新登录后重试

5.5 取消预约#

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/Space/cancel

请求体:

{
"id": "2599221",
"authorization": "bearer<Token>"
}

成功响应: {"code": 1, "msg": "..."} 失败: code != 1

Token 需同时出现在 Header Authorization 和请求体 authorization 中。


5.6 查询预约列表#

查询当前用户的所有预约记录。

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/index/subscribe

请求体:

{"authorization": "bearer<Token>"}

成功响应:

{
"code": 1,
"data": [
{
"id": "2599221",
"areaName": "东区阅览室",
"spaceName": "71号",
"no": "071",
"statusName": "已预约",
"cancel": 0
}
],
"msg": "..."
}
  • cancel:0 = 可取消,1 = 不可取消。

5.7 签到#

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/Seat/touch_qr_books

请求体:

{
"aesjson": "<AES加密后的签到数据>",
"authorization": "bearer<Token>"
}

加密前明文: {"method":"checkin"}

  • 加密方案与预约一致(日期 Key + 固定 IV)。

成功响应: {"code": 1, "msg": "..."} 已签到(仍视为成功): 响应体含 "不用重复签到" 字样


5.8 查询当前座位#

签退前查询当前用户正在使用或已预约的座位。

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/Member/seat

请求体:

{
"page": 1,
"limit": 3,
"authorization": "bearer<Token>"
}

成功响应:

{
"code": 1,
"data": {
"data": [
{
"id": "13855",
"area": "东区阅览室",
"no": "071",
"statusName": "使用中"
}
]
},
"msg": "..."
}
  • 签退时应优先查找 statusName 含 "使用中" 的座位。
  • 若没有 "使用中" 状态的座位,则无需签退。

5.9 签退#

项目内容
方法POST
URLhttp://libyy.qfnu.edu.cn/api/Space/checkout

请求体:

{
"id": "<座位ID,来自 Member/seat 接口>",
"authorization": "bearer<Token>"
}

成功条件(满足任一即成功):

  • code == 1
  • msg 包含 "完全离开操作成功"
  • msg 包含 "已经签退"

6. 响应码约定#

接口成功码备注
QuickSelect"0"(字符串)与其他接口语义相反
CAS /user1
Seat/date1
Seat/seat1
Seat/confirm0预约成功
Seat/confirm-7访问频繁(限流)
Seat/confirm10001其他设备登录导致被踢下线
Space/cancel1
index/subscribe1
Seat/touch_qr_books1
Member/seat1
Space/checkout1

7. 预约错误消息速查表#

消息关键词类型处理方式
"预约成功"✅ 成功提取预约 ID
"不可重复预约"✅ 已预约视为成功
msg:null(code=0)⚠️ 临界态短暂重试后判定
"该空间当前状态不可预约"❌ 座位级失败换下一候选座位
"黑名单"🚫 永久失败立即终止,通知用户
code:-7 / "访问频繁"🚫 限流立即终止,5 分钟后重试
"开放预约时间"⏳ 未到时间等待至开放
"尚未登录"🔄 需重登录重新登录
code:10001 / "其他设备登录"🔄 被踢下线重新登录

8. 安全与风控注意事项#

  1. User-Agent 轮换: 建议随机选用主流浏览器最新版 UA(Chrome / Edge / Firefox / Safari),同一会话期间保持 UA 不变。
  2. Cookie Jar 保持: 认证→业务请求全链路必须共用同一个 HTTP Cookie Jar,不要自动跟随重定向。
  3. Token 有效期: Bearer Token 约 90-100 分钟,建议缓存不超过 90 分钟。
  4. 限流(code=-7): 一旦触发立即停止所有后续请求。
  5. 预约频率控制: 不要对 /api/Seat/confirm 发起高频请求。
  6. 授权头特殊格式: Authorization: bearer<Token>(无空格),这是该系统特有的格式。
  7. 密码传输: 密码在传输中使用 AES 加密,不要在日志中明文记录。
  8. 滑块验证探测: IDS 按需开启滑块验证——先请求 openSliderCaptcha,若返回空则跳过。
  9. 预约加密 Key 的日期依赖: 预约/签到数据加密使用当天日期生成密钥,跨天亮时需留意密钥变更。

附录:接口地址速查表#

接口方法完整 URL
IDS 登录页GEThttp://ids.qfnu.edu.cn/authserver/login?service=http%3A%2F%2Flibyy.qfnu.edu.cn%2Fapi%2Fcas%2Fcas
IDS 登录提交POSThttp://ids.qfnu.edu.cn/authserver/login?service=http%3A%2F%2Flibyy.qfnu.edu.cn%2Fapi%2Fcas%2Fcas
获取滑块验证码GEThttp://ids.qfnu.edu.cn/authserver/common/openSliderCaptcha.htl
提交滑块验证POSThttp://ids.qfnu.edu.cn/authserver/common/verifySliderCaptcha.htl
CAS 回调GEThttp://libyy.qfnu.edu.cn/api/cas/cas
换取 Bearer TokenPOSThttp://libyy.qfnu.edu.cn/api/cas/user
校区/楼层/区域查询POSThttp://libyy.qfnu.edu.cn/reserve/index/quickSelect
获取时间段POSThttp://libyy.qfnu.edu.cn/api/Seat/date
获取座位列表POSThttp://libyy.qfnu.edu.cn/api/Seat/seat
预约座位POSThttp://libyy.qfnu.edu.cn/api/Seat/confirm
取消预约POSThttp://libyy.qfnu.edu.cn/api/Space/cancel
查询预约列表POSThttp://libyy.qfnu.edu.cn/api/index/subscribe
签到POSThttp://libyy.qfnu.edu.cn/api/Seat/touch_qr_books
查询当前座位POSThttp://libyy.qfnu.edu.cn/api/Member/seat
签退POSThttp://libyy.qfnu.edu.cn/api/Space/checkout

文档版本:v1.0 · 最后更新:2026-07-05

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

曲阜师范大学图书馆预约系统 API 逆向文档
https://blog.w1ndys.top/posts/2026-10-30-qfnu-library-booking-api/
作者
w1ndys
发布于
2026-10-30
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
w1ndys
技术踩坑、服务运维与随手记。
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
站点统计
文章
3
分类
3
标签
10
总字数
5,102
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录