- ASP.NET Core 9 Web API backend with JWT auth, EF Core MySQL - Vue 3 + Vite + Pinia + Ant Design Vue frontend - Multi-platform connection management (UOOC & Zhihuishu) - Video brushing with AES-CBC encryption for Zhihuishu - Multi-task queue with cross-platform parallel execution - Task persistence via MySQL database - Progress tracking with inline catalog enrichment - Mobile-responsive UI Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
8.5 KiB
8.5 KiB
智慧树 (zhihuishu.com) API 逆向分析
日期: 2026-06-23 | 全部接口已验证通过
一、概述
智慧树有两套 API 体系:
| 体系 | 域名 | 加密 | 课程类型 |
|---|---|---|---|
| Zhidao | studyservice-api / onlineservice-api |
AES-CBC (secretStr) | 共享学分课 |
| Hike | hikeservice / studyresources / hike-teaching |
无加密 | 校内学分课 |
二、登录流程
4.1 密码登录(需要滑块验证码)
Step 1: GET passport.zhihuishu.com/login → 获取 Session Cookie (JSESSIONID等)
Step 2: POST passport.zhihuishu.com/user/validateAccountAndPassword
Body: secretStr=Base64(encodeURIComponent(JSON.stringify({
account: "手机号",
password: "密码",
validate: "滑块验证码token"
})))
→ {status: 1, uuid: "...", pwd: "一次性密码"}
Step 3: POST appcomm-user.zhihuishu.com/.../checkNeedAuth
Body: uuid=xxx
→ {rt: {needAuth: 0}}
Step 4: GET passport.zhihuishu.com/login?pwd={pwd}&service={service}
→ 302重定向链 → 设置 CASLOGC Cookie
注意: 必须用 requests 库跟随重定向,urllib 不会自动跨域传 Cookie
加密方式: btoa(encodeURIComponent(JSON)) — 就是 Base64 + URL编码
(不是 AES!这是登录专用加密)
4.2 滑块验证码
网易易盾,captchaId: 75f9f716460a422f89a628f50fd8cc2b
<script src="https://cstaticdun.126.net/load.min.js"></script>
<script>
initNECaptcha({
element: '#captcha',
captchaId: '75f9f716460a422f89a628f50fd8cc2b',
mode: 'popup',
onVerify: function(err, data) {
// data.validate 就是需要的 token
}
}, onload, onerror);
</script>
三、Zhidao API(共享学分课)
5.1 AES 加密
KEY_MAP = {
"home": b"7q9oko0vqb3la20r", # 课程列表
"video": b"azp53h0kft7qi78q", # 视频/学习/进度
"qa": b"kcGOlISPkYKRksSK", # 弹题
}
IV = b"1g3qqdh4jvbskb9x"
def aes_encrypt(data_str, key=KEY_MAP["video"]):
pad_len = 16 - len(data_str) % 16
padded = data_str + chr(pad_len) * pad_len
cipher = AES.new(key, AES.MODE_CBC, IV)
return base64.b64encode(cipher.encrypt(padded.encode())).decode()
请求格式:
POST /api/endpoint
Content-Type: application/x-www-form-urlencoded
Body: secretStr={AES密文}&dateFormate={毫秒时间戳}
⚠️ dateFormate 必须同时在加密 JSON 内和表单字段外,且值相同。
5.2 课程列表
POST onlineservice-api.zhihuishu.com/gateway/t/v1/student/course/share/queryShareCourseInfo
加密密钥: HOME_KEY
加密前: {"status":0,"pageNo":1,"pageSize":10,"dateFormate":时间戳}
返回:
{
"code": 0,
"result": {
"totalCount": 1,
"courseOpenDtos": [{
"secret": "RAC_id", // 后续 API 的课程标识
"courseName": "课程名",
"recruitId": 389213, // 招生ID
"courseId": 1000076607, // 课程ID
"schoolName": "学校",
"teacherName": "教师"
}]
}
}
5.3 章节/视频列表
先调 gologin:
GET studyservice-api.zhihuishu.com/login/gologin?fromurl=...
再调:
POST studyservice-api.zhihuishu.com/gateway/t/v1/learning/videolist
加密密钥: VIDEO_KEY
加密前: {"recruitAndCourseId":"RAC_id","dateFormate":时间戳}
返回:
{
"code": 0,
"data": {
"courseId": 1000076607,
"videoChapterDtos": [{
"id": 1001076678, // chapterId
"name": "章节名",
"videoLessons": [{
"id": 1001297571, // lessonId
"name": "小节名",
"videoSmallLessons": [{
"id": 4001, // smallLessonId (单视频时=0)
"videoId": 63921147,// 视频ID
"videoSec": 600, // 总时长(秒)
"chapterId": 1001076678
}]
}]
}]
}
}
5.4 视频弹题信息
POST studyservice-api.zhihuishu.com/gateway/t/v1/popupAnswer/loadVideoPointerInfo
加密密钥: VIDEO_KEY
加密前: {"lessonId":...,"lessonVideoId":...,"recruitId":...,"courseId":...,"dateFormate":时间戳}
返回: questionPoint 数组 (弹题时间点 + 题目ID)
5.5 视频播放 URL
GET newbase.zhihuishu.com/video/initVideo?jsonpCallBack=result&videoID={videoId}
返回: JSONP,包含 lines[0].lineUrl
5.6 学习状态
POST studyservice-api.zhihuishu.com/gateway/t/v1/learning/queryStuyInfo
加密密钥: VIDEO_KEY
加密前:
{
"lessonIds": [1001297571], // 课时ID列表
"lessonVideoIds": [], // 子视频ID列表,单视频课时(smallLessonId=0)填空数组[]
"recruitId": 389213, // 招生ID
"dateFormate": 1782200000000
}
⚠️ lessonVideoIds 为 0 时不要传 [0],传空数组 []
返回:
{
"code": 0,
"data": {
"lv": {
"4001": { "watchState": 1, "studyTotalTime": 600 }
},
"lesson": {
"1001297571": { "watchState": 1, "studyTotalTime": 600 }
}
}
}
watchState: 0=未看完, 1=已看完
studyTotalTime: 已学习秒数
5.7 提交学习进度
Step 1 (prelearningNote):
POST studyservice-api.zhihuishu.com/gateway/t/v1/learning/prelearningNote
加密密钥: VIDEO_KEY
加密前: {"ccCourseId":...,"chapterId":...,"isApply":1,"lessonId":...,
"lessonVideoId":...,"recruitId":...,"videoId":...,"dateFormate":时间戳}
返回: data.studiedLessonDto.id → Base64编码得到 learningTokenId
Step 2 (saveDatabaseIntervalTimeV2):
POST studyservice-api.zhihuishu.com/gateway/t/v1/learning/saveDatabaseIntervalTimeV2
加密密钥: VIDEO_KEY
加密前: {
"ewssw": "0,1,2", // watchPoint
"sdsew": getEv([...]), // EV混淆的参数
"zwsds": learningTokenId, // Base64编码的token
"courseId": ...,
"dateFormate": 时间戳
}
5.8 EV 混淆算法
def getEv(data_list, key="zzpttjd"):
"""XOR 混淆"""
data = ';'.join(map(str, data_list))
key_cycle = (ord(c) for _ in iter(int,1) for c in key)
ev = ''
for c in data:
tmp = hex(ord(c) ^ next(key_cycle)).replace('0x', '')
if len(tmp) < 2: tmp = '0' + tmp
ev += tmp[-4:]
return ev
# saveDatabaseIntervalTimeV2 的 raw_ev 参数:
raw_ev = [
recruitId, lessonId, smallLessonId, videoId, chapterId,
'0', # studyStatus
played_time - last_submit, # 本次播放时长
played_time, # 累计播放时长
HMS(played_time), # HH:MM:SS 格式
uuid + "zhs" # UUID后缀
]
四、Hike API(校内学分课)
无需 AES 加密,直接 GET 请求,带 Cookie 即可。
6.1 课程列表
GET hikeservice.zhihuishu.com/student/course/aided/getMyCourseList?uuid={uuid}&data={UTC时间}
返回: result.startInngcourseList (注意拼写)
6.2 章节/资源树
GET studyresources.zhihuishu.com/studyResources/stuResouce/queryResourceMenuTree?courseId={id}
返回: rt 数组,childList 非空=目录,childList=null=文件,dataType=3=视频
6.3 视频信息
GET studyresources.zhihuishu.com/studyResources/stuResouce/stuViewFile?courseId={id}&fileId={id}
返回: dataId (视频流ID), totalTime, studyTime
6.4 提交学习记录
GET hike-teaching.zhihuishu.com/stuStudy/saveStuStudyRecord?uuid=...&courseId=...&fileId=...
&studyTotalTime=...&startWatchTime=...&endWatchTime=...&startDate=...&endDate=...
&signature=MD5(SALT + uuid + courseId + fileId + studyTotalTime + startDate + endDate + endWatchTime + startWatchTime + uuid)
SALT = "o6xpt3b#Qy$Z"
五、Cookie 体系
登录后获取的关键 Cookie:
| Cookie | 域名 | 说明 |
|---|---|---|
| CASLOGC | passport.zhihuishu.com | URL编码JSON,含uuid/realName/userId |
| JSESSIONID | passport.zhihuishu.com | Session ID |
| SERVERID | 各子域名 | 服务器路由 |
| SESSION | onlineservice-api / studyservice-api | API会话 (通过CAS重定向获取) |
⚠️ 登录时必须跟随完整 CAS 重定向链,否则缺少 onlineservice-api 的 SESSION Cookie 导致 API 返回 401。
六、关键注意事项
- AES JSON 格式:
json.dumps(data, separators=(',', ':'))— 紧凑格式,无空格 - dateFormate 双写: 加密 JSON 内和表单字段都要有,且值相同
- lessonVideoIds 不能含 0: 单视频课时 smallLessonId=0,此时传空数组
[] - CAS 重定向: 必须用 requests.Session() 自动跟随,urllib 不会跨域传 Cookie
- latin-1 编码: Cookie 值保持 URL 编码状态(纯 ASCII),不要解码成中文
- Encrypt 已变更: 登录接口用
btoa(encodeURIComponent(JSON)),不是 AES