[Auto CQUPT Plan] 校园网相关接口(补档)
基于 CQUPT-Net-SDK 项目,按其源码整理。本 SDK 通过 Go 语言封装了重邮校园网的登录、登出、状态检测、上网信息查询、上线记录查询、在线设备查询等能力。
此接口仅供学习使用,请勿用于非法用途,否则造成的危害后果自负。
1. 通用说明
1.1 两类接口
本 SDK 涉及两类输入参数不同、请求地址不同的接口:
| 类型 | 说明 | 参数 |
|---|---|---|
Portal 认证接口(192.168.200.2:801) | 校园网接入网关,负责设备的认证/登出/状态检测,返回 JSONP 格式数据 | LoginData |
自服务接口(202.202.32.120:8443) | 校园网自服务门户(/Self/*),需要先通过网页表单登录获取 Cookie,负责账号信息/记录查询 | LoginData |
1.2 公共参数(LoginData)
| 字段 | 说明 | 取值 |
|---|---|---|
StuID | 统一认证码(学号) | 字符串 |
Password | 密码 | 字符串,明文 |
UA | 设备类型,决定 User-Agent 与认证设备号 | desktop,phone,pad |
Isp | 运营商 | telecom(电信),cmcc(移动),unicom(联通),xyw(教师) |
IPAddr | 设备 IP | 需填局域网内真实 IP |
MACAddr | 路由器 MAC,冒号会被自动去除 | 全部置 "000000000000" 时由认证服务器自动识别 |
登录时不一定要在路由器上执行本 SDK,只需在路由器局域网内任意一台设备上执行即可。
2. 接口列表
| # | 接口(SDK 方法) | 功能 | 请求地址 |
|---|---|---|---|
| 1 | NetLogin | 校园网登录认证 | http://192.168.200.2:801/eportal/ (GET, JSONP) |
| 2 | NetLogout | 校园网注销 | http://192.168.200.2:801/eportal/ (GET, JSONP) |
| 3 | NetChecker | 认证状态检测 | http://192.168.200.2:801/eportal/ (GET, JSONP) |
| 4 | LoginCquptService | 自服务门户登录(内部) | https://202.202.32.120:8443/Self/login/verify (POST, 表单) |
| 5 | GetNetServiceInfo | 获取上网服务信息 | https://202.202.32.120:8443/Self/dashboard (GET, 域名解析) |
| 6 | GetLoginHistory | 获取登录记录 | https://202.202.32.120:8443/Self/dashboard/getLoginHistory (GET, JSON) |
| 7 | GetOnlineList | 获取当前在线设备列表 | https://202.202.32.120:8443/Self/dashboard/getOnlineList (GET, JSON) |
4~7 为自服务接口,内部会先通过
LoginCquptService获取并缓存(15 秒)一个有效 Cookie,再用该 Cookie 请求。
3. Portal 认证接口(网关,192.168.200.2:801)
公共请求头:
Host: 192.168.200.2:801Referer: http://192.168.200.2/User-Agent: <按 UA 类型映射>3.1 校园网登录 — NetLogin
- 请求方式:
GET - JSONP 方法名:
dr1003
Query 参数:
| 参数 | 说明 |
|---|---|
c | 固定 Portal |
a | 固定 login |
callback | 固定 dr1003 |
login_method | 固定 1 |
user_account | ,{device},{StuID}@{Isp};device:desktop=0,phone/pad=1 |
user_password | 明文密码 |
wlan_user_ip | 设备 IP |
wlan_user_mac | MAC 去除冒号后大写 |
jsVersion | 固定 3.3.3 |
响应(JSONP 回调包裹的 JSON):
| 字段 | 类型 | 说明 |
|---|---|---|
result | string | 0 表示成功 |
msg | string | 提示信息 |
ret_code | int | 返回码,2 表示当前设备已认证 |
解析时会对原始响应做
body[7:len-1]截断以去掉 JSONP 外层,再反序列化为 JSON。当result==0 且 ret_code==2时,SDK 会返回当前设备已认证。
net/http 直接调用示例:
func netLogin(stuID, password, isp, ip, mac, ua string) { device := "0" // desktop 为 0,phone/pad 为 1 if ua != "desktop" { device = "1" }
// 构造 query 参数 u, _ := url.Parse("http://192.168.200.2:801/eportal/") q := u.Query() q.Set("c", "Portal") q.Set("a", "login") q.Set("callback", "dr1003") q.Set("login_method", "1") q.Set("user_account", ","+device+","+stuID+"@"+isp) q.Set("user_password", password) q.Set("wlan_user_ip", ip) q.Set("wlan_user_mac", strings.ReplaceAll(mac, ":", "")) q.Set("jsVersion", "3.3.3") u.RawQuery = q.Encode()
req, _ := http.NewRequest(http.MethodGet, u.String(), nil) req.Header.Set("Host", "192.168.200.2:801") req.Header.Set("Referer", "http://192.168.200.2/") req.Header.Set("User-Agent", ua)
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) s := string(body) // dr1003({"result":"0","msg":"...","ret_code":0}) s = s[7 : len(s)-1] // 去掉 JSONP 外壳
var r struct { Result string `json:"result"` Msg string `json:"msg"` RetCode int `json:"ret_code"` } json.Unmarshal([]byte(s), &r) fmt.Printf("result=%s msg=%s ret_code=%d\n", r.Result, r.Msg, r.RetCode)}3.2 校园网注销 — NetLogout
- 请求方式:
GET - JSONP 方法名:
dr1002 - 动作标识:
a=unbind_mac
Query 参数:
| 参数 | 说明 |
|---|---|
c | 固定 Portal |
a | 固定 unbind_mac |
callback | 固定 dr1002 |
user_account | {StuID}@{Isp} |
wlan_user_mac | MAC(去冒号) |
wlan_user_ip | 设备 IP |
jsVersion | 固定 3.3.3 |
v | 随机数(1000~9999) |
响应(JSONP):
| 字段 | 类型 | 说明 |
|---|---|---|
result | string | 结果 |
msg | string | 提示信息 |
net/http 直接调用示例:
func netLogout(stuID, isp, ip, mac, ua string) { u, _ := url.Parse("http://192.168.200.2:801/eportal/") q := u.Query() q.Set("c", "Portal") q.Set("a", "unbind_mac") q.Set("callback", "dr1002") q.Set("user_account", stuID+"@"+isp) q.Set("wlan_user_mac", strings.ReplaceAll(mac, ":", "")) q.Set("wlan_user_ip", ip) q.Set("jsVersion", "3.3.3") q.Set("v", fmt.Sprintf("%d", rand.Intn(9000)+1000)) // 随机数 u.RawQuery = q.Encode()
req, _ := http.NewRequest(http.MethodGet, u.String(), nil) req.Header.Set("Host", "192.168.200.2:801") req.Header.Set("Referer", "http://192.168.200.2/") req.Header.Set("User-Agent", ua)
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) s := string(body)[7 : len(body)-1] // 去 JSONP 外壳
var r struct { Result string `json:"result"` Msg string `json:"msg"` } json.Unmarshal([]byte(s), &r) fmt.Printf("result=%s msg=%s\n", r.Result, r.Msg)}3.3 认证状态检测 — NetChecker
- 请求方式:
GET - JSONP 方法名:
dr1001 - 动作标识:
a=page_type_data
Query 参数:
| 参数 | 说明 |
|---|---|
c | 固定 Portal |
a | 固定 page_type_data |
callback | 固定 dr1001 |
v | 随机数(1000~9999) |
响应(JSONP):
| 字段 | 类型 | 说明 |
|---|---|---|
result | string | 认证状态 |
time | string | 时间 |
msg | string | 提示信息 |
net/http 直接调用示例:
func netChecker(ua string) { u, _ := url.Parse("http://192.168.200.2:801/eportal/") q := u.Query() q.Set("c", "Portal") q.Set("a", "page_type_data") q.Set("callback", "dr1001") q.Set("v", fmt.Sprintf("%d", rand.Intn(9000)+1000)) // 随机数 u.RawQuery = q.Encode()
req, _ := http.NewRequest(http.MethodGet, u.String(), nil) req.Header.Set("Host", "192.168.200.2:801") req.Header.Set("Referer", "http://192.168.200.2/") req.Header.Set("User-Agent", ua)
resp, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body) s := string(body)[7 : len(body)-1] // 去 JSONP 外壳
var r struct { Result string `json:"result"` Time string `json:"time"` Msg string `json:"msg"` } json.Unmarshal([]byte(s), &r) fmt.Printf("result=%s time=%s msg=%s\n", r.Result, r.Time, r.Msg)}4. 自服务门户接口(202.202.32.120:8443)
自服务接口分为两段流程:
- 登录获取 Cookie:
LoginCquptService触发的三步流程(见 §4.1); - 携带 Cookie 查询:用上一步的 Cookie 请求各类数据接口(见 §4.2 / §4.3 / §4.4)。
Cookie 会被缓存 15 秒,过期后自动重新登录。
公共请求头(自服务接口):
Cookie: <登录后获得的会话Cookie>User-Agent: <按 UA 类型映射>X-Requested-With: XMLHttpRequest4.1 自服务登录(内部流程)
仅用于获取会话 Cookie,供后续查询使用。完整流程为:
| 步骤 | 请求 | 说明 |
|---|---|---|
| ① 打开登录页 | GET https://202.202.32.120:8443/Self/login/?302=LI | 抓取 Set-Cookie 与隐藏表单字段 checkcode |
| ② 激活 Cookie | POST https://202.202.32.120:8443/Self/login/randomCode?t={rand} | Content-Type: application/x-www-form-urlencoded,刷新超时 |
| ③ 提交登录 | POST https://202.202.32.120:8443/Self/login/verify | 携带 ② 的 Cookie 提交账号密码,成功后重定向到 /Self/dashboard |
第 ③ 步 表单参数:
| 参数 | 说明 |
|---|---|
foo | 空 |
bar | 空 |
checkcode | 页面隐藏值 |
account | 学号(StuID) |
password | 密码 |
code | 空 |
成功判定: 响应头 Location == "/Self/dashboard"。其他值视为登录失败。
net/http 直接调用示例(三步流程):
// ① 打开登录页,抓取 Cookie 与隐藏字段 checkcodeloginReq, _ := http.NewRequest("GET", "https://202.202.32.120:8443/Self/login/?302=LI", nil)loginReq.Header.Set("User-Agent", ua)loginResp, err := client.Do(loginReq) // client 见 §5,已关闭自签名证书校验并禁用自动重定向if err != nil { log.Fatal(err)}cookie := loginResp.Header.Get("Set-Cookie") // 取第一段,如 "JSESSIONID=..."s, _ := io.ReadAll(loginResp.Body) // 从 HTML 的 <input type="hidden" name="checkcode" value="..."> 里正则提取 checkcodeloginResp.Body.Close()
// ② 激活 Cookie(刷新超时)actReq, _ := http.NewRequest("POST", "https://202.202.32.120:8443/Self/login/randomCode?t=0.5562441101050084", nil)actReq.Header.Set("Cookie", cookie)client.Do(actReq)
// ③ 提交登录,表单校验成功会返回 Location: /Self/dashboardvals := url.Values{}vals.Set("foo", "")vals.Set("bar", "")vals.Set("checkcode", checkcode) // 第 ① 步抓取的值vals.Set("account", stuID)vals.Set("password", password)vals.Set("code", "")verifyReq, _ := http.NewRequest("POST", "https://202.202.32.120:8443/Self/login/verify", strings.NewReader(vals.Encode()))verifyReq.Header.Set("Cookie", cookie)verifyReq.Header.Set("User-Agent", ua)verifyReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")verifyReq.Header.Set("X-Requested-With", "XMLHttpRequest")verifyResp, err := client.Do(verifyReq)if err != nil { log.Fatal(err)}// verifyResp.Header.Get("Location") 若非 "/Self/dashboard" 则登录失败fmt.Printf("会话 Cookie: %s\n", cookie)4.2 获取上网服务信息 — GetNetServiceInfo
- 请求:
GET https://202.202.32.120:8443/Self/dashboard - 使用 HTML 解析器(colly)抓取页面 DOM,非 JSON 返回。
返回字段(NetInfoS):
| 字段 | JSON key | 说明 |
|---|---|---|
UserName | user_name | 姓名 |
UsedTime | used_time | 已用时长 |
UsedFlow | used_flow | 已用流量 |
Balance | balance | 账户余额 |
Account | account | 学号 |
Status | status | 账号状态 |
Package | package | 用户类型/套餐 |
PackageDetail | packageDetail | 套餐详情 |
BillingType | billingType | 计费方式 |
BillingCycle | billingCycle | 计费周期 |
net/http 直接调用示例(需先自服务登录拿到 Cookie):
req, _ := http.NewRequest("GET", "https://202.202.32.120:8443/Self/dashboard", nil)req.Header.Set("Cookie", cookie) // 自服务登录获取的会话 Cookiereq.Header.Set("User-Agent", ua)req.Header.Set("X-Requested-With", "XMLHttpRequest")
resp, err := client.Do(req) // client 见 §5if err != nil { log.Fatal(err)}defer resp.Body.Close()
// 返回的是 HTML 页面,需解析 DOM(如 colly / goquery):// div.thumbnail div.caption h4 → 姓名// div.user-info1 dl 中 dd=已用时长/已用流量/账户余额 对应对应的 dt// div.panel-body div.row 中 label=账号/状态/套餐/计费方式/计费周期 对应 spanbody, _ := io.ReadAll(resp.Body)fmt.Println(string(body))4.3 获取登录记录 — GetLoginHistory
- 请求:
GET https://202.202.32.120:8443/Self/dashboard/getLoginHistory Accept: application/json, text/javascript, */*; q=0.01- 返回为二维数组 JSON,每行 ≥12 个元素,按列映射为一条登录记录。
返回字段(LoginHistoryRecordS):
| 字段 | JSON key | 说明 | 数组下标 |
|---|---|---|---|
LoginTime | loginTime | 上线时间(毫秒时间戳) | 0 |
LogoutTime | logoutTime | 注销时间(毫秒时间戳) | 1 |
IP | ip | IP 地址 | 2 |
MAC | mac | MAC(大写无缝) | 3 |
UseTime | useTime | 使用时长(分钟) | 4 |
UseFlow | useFlow | 使用流量(M) | 5 |
BillingType | billingType | 计费方式(1时长/2流量/3包月) | 6 |
ChargeAmount | chargeAmount | 计费金额 | 7 |
HostName | hostName | 主机名 | 8 |
TerminalType | terminalType | 终端类型(PC/移动终端) | 10 |
net/http 直接调用示例(需先自服务登录拿到 Cookie):
req, _ := http.NewRequest("GET", "https://202.202.32.120:8443/Self/dashboard/getLoginHistory", nil)req.Header.Set("Cookie", cookie)req.Header.Set("User-Agent", ua)req.Header.Set("X-Requested-With", "XMLHttpRequest")req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
resp, err := client.Do(req) // client 见 §5if err != nil { log.Fatal(err)}defer resp.Body.Close()
// 返回为二维数组 JSON,每行 ≥12 个元素,按下标映射(0 上线时间,1 注销时间,...)var rows [][]interface{}json.NewDecoder(resp.Body).Decode(&rows)for _, r := range rows { if len(r) < 12 { continue } // r[0]/r[1] 为毫秒时间戳,r[2]=IP, r[3]=MAC, r[4]=时长(分), r[5]=流量(M) log.Printf("loginTime=%v ip=%v useTime=%v\n", r[0], r[2], r[4])}4.4 获取当前在线列表 — GetOnlineList
- 请求:
GET https://202.202.32.120:8443/Self/dashboard/getOnlineList?t={rand}&order=asc&_={ms} Accept: application/json, text/javascript, */*; q=0.01Content-Type: application/json- 返回为 JSON 对象数组。
返回字段(OnlineRecord):
| 字段 | JSON key | 说明 |
|---|---|---|
Brasid | brasid | BRAS ID |
DownFlow | downFlow | 下行流量(KB,推测) |
HostName | hostName | 主机名 |
IP | ip | IP 地址 |
LoginTime | loginTime | 上线时间(YYYY-MM-DD HH:MM:SS) |
MAC | mac | MAC(无分隔符) |
SessionId | sessionId | 会话 ID |
TerminalType | terminalType | 终端类型(可能带 # 前缀) |
UpFlow | upFlow | 上行流量 |
UseTime | useTime | 使用时长(秒) |
UserId | userId | 用户 ID |
注:在线列表返回值中的流量/时长为字符串,SDK 会再做
ParseInt/Atoi转成数字。
net/http 直接调用示例(需先自服务登录拿到 Cookie):
// 带随机参数与时间戳(用于规避缓存)u, _ := url.Parse("https://202.202.32.120:8443/Self/dashboard/getOnlineList")q := u.Query()q.Set("t", strconv.FormatFloat(rand.Float64(), 'f', -1, 64))q.Set("order", "asc")q.Set("_", fmt.Sprintf("%d", time.Now().UnixMilli()))u.RawQuery = q.Encode()
req, _ := http.NewRequest("GET", u.String(), nil)req.Header.Set("Cookie", cookie) // 自服务登录获取的会话 Cookiereq.Header.Set("User-Agent", ua)req.Header.Set("X-Requested-With", "XMLHttpRequest")req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req) // client 见 §5if err != nil { log.Fatal(err)}defer resp.Body.Close()
// 返回为 JSON 对象数组;流量/时长为字符串,需 ParseInt/Atoi 转数字var list []struct { IP string `json:"ip"` MAC string `json:"mac"` UpFlow string `json:"upFlow"` DownFlow string `json:"downFlow"` UseTime string `json:"useTime"` LoginTime string `json:"loginTime"` // "YYYY-MM-DD HH:MM:SS"}json.NewDecoder(resp.Body).Decode(&list)for _, item := range list { fmt.Printf("%s | %s | up=%s down=%s\n", item.IP, item.MAC, item.UpFlow, item.DownFlow)}5. 公共 HTTP 客户端配置
自服务接口使用自签名的 HTTPS 证书,且登录流程依赖读取 Location 重定向头(不允许自动跟随)。因此用 net/http 直接调用前,需要先配置一个关闭 TLS 证书校验、禁用自动重定向的 http.Client:
client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // 关闭证书校验(自签名 HTTPS) }, // 关闭自动重定向,以便读取登录后响应头里的 Location 判断成败 CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse },}所有示例中统一使用该 client 而非 http.DefaultClient。除特殊说明外,请求都需要设置请求头 User-Agent(按 §1.2 的 UA 类型取值),门户接口额外带上 X-Requested-With: XMLHttpRequest。
各接口的完整请求构造见前文各处示例:
- Portal 认证:见 §3.1 / §3.2 / §3.3 —— 直接
GET,解析 JSONP 响应。 - 自服务登录:见 §4.1 —— 先
GET登录页拿 Cookie,再POST提交。 - 自服务查询:见 §4.2 / §4.3 / §4.4 —— 携带上一步的 Cookie 请求。
由于关闭了自动重定向,自服务登录成功后,需通过响应头
Location手动判断是否跳转到/Self/dashboard。
6. 附注:请求地址常量
见项目
core/spyder/consts.go。
192.168.200.2:801 # 网关 Portal 主机202.202.32.120:8443 # 自服务门户主机http://192.168.200.2:801/eportal/ # Portal 认证接口(登录/登出/检测)https://202.202.32.120:8443/Self/login/?302=LI # 自服务登录页https://202.202.32.120:8443/Self/login/verify # 登录提交https://202.202.32.120:8443/Self/login/randomCode?t=... # Cookie 激活https://202.202.32.120:8443/Self/dashboard # 上网信息页https://202.202.32.120:8443/Self/dashboard/getLoginHistory # 登录记录https://202.202.32.120:8443/Self/dashboard/getOnlineList # 在线列表注:SDK 中 TLS 证书校验被关闭(
InsecureSkipVerify),并关闭了 HTTP 自动重定向,以便解析登录跳转。
7. 相关项目
8. 结尾
没有了喵,谢谢阅读

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