# 知岁OS 院校系统 API 对接文档

> 版本：1.0 | 更新日期：2026-05-25

---

## 目录

1. [概述](#1-概述)
2. [统一规范](#2-统一规范)
3. [认证模块 — Auth](#3-认证模块--auth)
4. [校区模块 — School](#4-校区模块--school)
5. [开放接口模块 — Open](#5-开放接口模块--open)
6. [数据加密详解](#6-数据加密详解)
7. [标准数据格式](#7-标准数据格式)
8. [错误码参考](#8-错误码参考)
9. [频率限制](#9-频率限制)
10. [完整对接流程](#10-完整对接流程)

---

## 1. 概述

知岁OS 是一套院校系统数据聚合平台，提供统一的 API 接口，使第三方应用能够便捷地获取多所高校的院校应用数据（课表、成绩、用户信息等）。

**基础信息：**

| 项目 | 说明 |
|------|------|
| 基础 URL | `https://your-domain.com`（部署后替换为实际域名） |
| 协议 | HTTPS |
| 数据格式 | JSON |
| 字符编码 | UTF-8 |
| URL 规则 | `/api/{控制器}/{方法}` （自动转小写） |

---

## 2. 统一规范

### 2.1 请求规范

- 所有 POST 请求使用 `application/x-www-form-urlencoded` 或 `application/json`
- 所有请求需携带 Token（登录后获取），通过以下方式之一传递：
  - **HTTP Header**（推荐）：`Token: your-token-here`
  - **Query 参数**：`?token=your-token-here`
  - **Cookie**：`token=your-token-here`

### 2.2 统一响应格式

**成功响应：**

```json
{
    "code": 1,
    "msg": "ok",
    "time": 1700000000,
    "data": { }
}
```

**失败响应：**

```json
{
    "code": 0,
    "msg": "错误信息",
    "time": 1700000000,
    "data": null
}
```

**特殊响应（弹窗提示）：**

```json
{
    "code": 10,
    "msg": "弹窗消息内容",
    "time": 1700000000,
    "data": { "title": null }
}
```

### 2.3 校区标识

每个高校有一个唯一标识符（如 `zf_fjjxxy`），用于所有校区相关接口的 `school` 参数。

---

## 3. 认证模块 — Auth

### 3.1 微信小程序登录

> 本接口为微信小程序专用，第三方服务商请使用 [Open/useToken](#53-消费数据-token) 接口。

**请求：**

```
POST /api/auth/login
```

**参数：**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| authCode | string | 是 | 微信 `wx.login()` 获取的 code |
| phoneCode | string | 是 | 微信获取手机号得到的 code |

**响应：**

```json
{
    "code": 1,
    "msg": "登录成功",
    "data": {
        "id": 1,
        "username": "S2026011801234",
        "nickname": "微信用户12345",
        "avatar": "/static/user/avatar.png",
        "viptime": 1735689600,
        "authtime": 0,
        "vipexpires": "2025-12-31",
        "authexpires": null,
        "realname": false,
        "token": "uuid-token-string",
        "expires_in": 2592000
    }
}
```

> Token 有效期 30 天（2592000 秒）

### 3.2 检测 Token

**请求：**

```
POST /api/auth/check
```

**需登录：** 是

**响应：**

```json
{
    "code": 1,
    "data": {
        "token": "uuid-token-string",
        "expires_in": 2591999
    }
}
```

### 3.3 刷新 Token

**请求：**

```
POST /api/auth/refresh
```

**需登录：** 是

**说明：** 删除旧 Token，生成新 Token（有效期重新计算 30 天）

**响应：**

```json
{
    "code": 1,
    "data": {
        "token": "new-uuid-token-string",
        "expires_in": 2592000
    }
}
```

---

## 4. 校区模块 — School

> 除 `list` 外，所有 School 接口都需要传递 `school` 和 `decrypt` 参数。

### 4.1 公共参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| school | string | 是 | 校区标识，如 `zf_fjjxxy` |
| decrypt | string | 否 | 加密会话数据（接口返回的 `decrypt` 字段，下次请求时回传） |

### 4.2 获取校区列表

**请求：**

```
GET /api/school/list
```

**需登录：** 是（无需传 school/decrypt）

**响应：**

```json
{
    "code": 1,
    "data": [
        {
            "id": "zf_fjjxxy",
            "name": "福建江夏学院",
            "status": 1,
            "authors": [
                { "name": "渲染", "email": "pay@ktwap.net" }
            ],
            "login": {
                "mode": 1,
                "imageVerify": true,
                "passVerify": true,
                "codeVerify": 0
            },
            "vpnLogin": null,
            "interface": {
                "userInfo": {
                    "action": "userInfo",
                    "title": "校园身份",
                    "icon": "/home/Frame@2x.png",
                    "subtitle": "信息范围学号、姓名、性别、专业、学院、入学年份",
                    "form": false
                },
                "course": {
                    "action": "course",
                    "title": "课表信息",
                    "icon": "/home/Frame@2x(8).png",
                    "subtitle": "信息范围当前学期课程名称、上课节次、老师、地点",
                    "form": false
                },
                "score": { "..." : "..." },
                "progress": { "..." : "..." },
                "classroom": {
                    "action": "classroom",
                    "title": "空教室",
                    "icon": "/home/Frame@2x(11).png",
                    "subtitle": "需先完善条件后使用获取指定条件的空教室信息",
                    "form": true
                }
            },
            "config": [
                {
                    "name": "status",
                    "title": "状态",
                    "type": "select",
                    "value": "1"
                }
            ]
        }
    ]
}
```

**校区状态码：**

| 值 | 含义 |
|----|------|
| 0 | 禁用 |
| 1 | 启用 |
| 2 | 维护 |
| 3 | 下线 |

**登录模式（login.mode）：**

| 值 | 含义 |
|----|------|
| 1 | 账号密码登录 |
| 2 | 扫码登录 |

**登录配置字段：**

| 字段 | 类型 | 说明 |
|------|------|------|
| login.imageVerify | bool | 是否需要图形验证码 |
| login.passVerify | bool | 是否需要密码 |
| login.codeVerify | int | 是否需要短信/邮件验证码（0=不需要, >0=验证码长度） |
| vpnLogin | object/null | VPN 登录配置（与 login 结构相同），null 表示无 VPN |

### 4.3 获取校区详情

**请求：**

```
POST /api/school/info
```

**参数：** 公共参数（school）

**响应：** 与 list 中单个校区结构相同

### 4.4 获取验证码

**请求：**

```
POST /api/school/verify
```

**参数：**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| school | string | 是 | 校区标识 |
| decrypt | string | 否 | 加密会话数据 |
| type | int | 否 | 验证码类型：1=图形（默认），2=短信/邮件 |

**响应（图形验证码）：**

```json
{
    "code": 1,
    "data": {
        "body": "data:image/png;base64,iVBORw0KGgo..."
    }
}
```

**VPN 验证码：** 使用 `/api/school/vpnVerify`，参数同上。

### 4.5 登录院校系统

**请求：**

```
POST /api/school/login
```

**参数：**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| school | string | 是 | 校区标识 |
| decrypt | string | 否 | 加密会话数据 |
| username | string | 是 | 院校系统账号 |
| password | string | 条件必填 | 院校系统密码（当 login.passVerify=true 时必填） |
| imageVerify | string | 条件必填 | 图形验证码（当 login.imageVerify=true 时必填） |
| codeVerify | string | 条件必填 | 短信/邮件验证码（当 login.codeVerify>0 时必填） |

**成功响应：**

```json
{
    "code": 1,
    "data": {
        "body": true,
        "decrypt": "Base64编码的加密会话数据..."
    }
}
```

> **重要**：返回的 `decrypt` 字段需要在后续所有请求中回传，用于保持登录状态。

**VPN 登录：** 使用 `/api/school/vpnLogin`，参数结构同上。

**扫码登录：** 当 `login.mode=2` 时，先调用 `/api/school/qrcode` 获取二维码，用户扫码后调用 `/api/school/login`。

### 4.6 调用数据接口

**请求：**

```
POST /api/school/interface
```

**参数：**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| school | string | 是 | 校区标识 |
| decrypt | string | 是 | 加密会话数据（登录后获取） |
| action | string | 是 | 接口名称，如 `userInfo`、`course`、`score` 等 |
| form | object | 否 | 接口表单参数（如 classroom 需要的查询条件） |

**可用 action 值：**

| action | 说明 | 是否需要表单参数 |
|--------|------|-----------------|
| userInfo | 校园身份 | 否 |
| course | 课表信息 | 否 |
| score | 成绩信息 | 否 |
| progress | 学业信息 | 否 |
| classroom | 空教室 | 是 |
| exam | 考试安排 | 视校区而定 |

**成功响应：**

```json
{
    "code": 1,
    "data": {
        "token": "interface-data-token",
        "decrypt": "更新后的加密会话数据..."
    }
}
```

> 返回的 `token` 用于通过开放接口获取解密后的数据（详见第 5 节）。
> 返回的 `decrypt` 需要在下一次请求时回传。

### 4.7 获取接口配置表单

**请求：**

```
POST /api/school/form
```

**参数：**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| school | string | 是 | 校区标识 |
| action | string | 是 | 接口名称，如 `classroom` |

**响应：**

```json
{
    "code": 1,
    "data": {
        "form": [
            {
                "name": "building",
                "title": "教学楼",
                "type": "select",
                "content": { "1": "教学楼A", "2": "教学楼B" }
            }
        ],
        "rules": {
            "building": "require"
        }
    }
}
```

### 4.8 注销院校登录

**请求：**

```
POST /api/school/logout
```

**参数：** 公共参数（school、decrypt）

**响应：**

```json
{
    "code": 1,
    "msg": "ok"
}
```

---

## 5. 开放接口模块 — Open

> 供第三方服务商消费用户授权数据的核心接口。

### 5.1 清理过期数据

**请求：**

```
GET /api/open/checkToken
```

**需登录：** 否

**说明：** 用于定时任务，清理超过 15 分钟的过期接口数据。

**响应：** 返回清理的记录数。

### 5.2 消费数据 (useToken)

> **这是第三方对接的核心接口。**

**请求：**

```
POST /api/open/useToken
```

**需登录：** 否

**参数：**

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| uuid | string | 是 | 服务商标识（在后台注册服务时获取） |
| data | string | 是 | AES-256-GCM 加密数据（Base64 编码） |

**加密数据格式：**

```
data = Base64( IV + TAG[16字节] + AES-256-GCM加密( UUID + token ) )
```

- `UUID`：服务商的 uuid，与参数中的 uuid 一致
- `token`：用户通过 `/api/school/interface` 获取的数据 token

**响应：**

```json
{
    "code": 1,
    "msg": "ok",
    "data": "Base64( IV + TAG[16字节] + AES-256-GCM加密( 原始JSON数据 ) )"
}
```

> **注意**：数据 token 有效期为 **15 分钟**，过期自动删除。每个 token 只能消费一次。

---

## 6. 数据加密详解

### 6.1 客户端会话加密

用于客户端与服务端之间保持院校系统的登录状态，加密数据通过 `decrypt` 字段在请求间传递。

> **注意**：`decrypt` 字段的加密方案为平台内部实现，第三方无需也**不应**尝试解密或伪造该数据。只需在每次请求中将上一次返回的 `decrypt` 原样回传即可。

### 6.2 开放接口加密（AES-256-GCM）

用于第三方服务商与平台之间的数据传输。

**算法：** AES-256-GCM

**密钥：** 服务商注册时生成的 `secretkey`（hex 编码），使用时需 `hex2bin` 转为二进制。

**请求加密（客户端 → 服务端）：**

```
1. 明文 = UUID + token
2. 生成随机 IV（12字节）
3. 加密：openssl_encrypt(明文, 'aes-256-gcm', secretKey, OPENSSL_RAW_DATA, IV, TAG)
4. 拼接：IV + TAG[16字节] + CipherText
5. 编码：Base64(拼接结果)
```

**响应解密（服务端 → 客户端）：**

```
1. Base64 解码
2. 拆分：IV[前12字节] + TAG[接下来16字节] + CipherText[剩余]
3. 解密：openssl_decrypt(CipherText, 'aes-256-gcm', secretKey, OPENSSL_RAW_DATA, IV, TAG)
4. 得到原始 JSON 数据
```

---

## 7. 标准数据格式

所有校区接口返回的数据严格遵循以下格式：

### 7.1 userInfo — 校园身份

```json
{
    "username": "张三",
    "gender": "男",
    "department": "计算机学院",
    "major": "软件工程",
    "enrollment_year": "2023",
    "student_id": "2023010101"
}
```

### 7.2 course — 课表信息

```json
[
    {
        "name": "高等数学",
        "start": 1,
        "end": 2,
        "floor": "教学楼A-101",
        "weeks": [1, 2, 3, 4, 5, 6, 7, 8],
        "week": 1,
        "teacher": "李教授"
    }
]
```

| 字段 | 类型 | 说明 |
|------|------|------|
| name | string | 课程名称 |
| start | int | 开始节次 |
| end | int | 结束节次 |
| floor | string | 教室位置 |
| weeks | int[] | 上课周次 |
| week | int | 星期几（1=周一, 7=周日） |
| teacher | string | 教师姓名 |

### 7.3 score — 成绩信息

```json
{
    "2023": {
        "1": [
            {
                "course": "高等数学",
                "credit": "4.0",
                "gpa": "3.5",
                "achievement": "85"
            }
        ],
        "2": []
    }
}
```

> 按学年嵌套学期：`{ "学年": { "学期号": [成绩数组] } }`

### 7.4 progress — 学业信息

```json
{
    "gpa": "3.52",
    "all": 50,
    "pass": 45,
    "fail": 2,
    "unfinished": 3,
    "studying": 5,
    "time": "2026-05-20 10:30:00",
    "courses": {
        "compulsory": [
            {
                "academicYear": "2023-2024",
                "semester": "1",
                "name": "高等数学",
                "status": "已通过"
            }
        ],
        "elective": [],
        "restricted": [],
        "other": []
    }
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| gpa | string | 总绩点 |
| all | int | 总课程数 |
| pass | int | 已通过 |
| fail | int | 未通过 |
| unfinished | int | 未修 |
| studying | int | 在修 |
| time | string | 更新时间 |
| courses.compulsory | array | 必修课程列表 |
| courses.elective | array | 选修课程列表 |
| courses.restricted | array | 限选课程列表 |
| courses.other | array | 其他课程列表 |

### 7.5 classroom — 空教室

```json
{
    "教学楼A": [
        {
            "name": "A-101",
            "status": [0, 0, 1, 1, 0, 0, 0, 0]
        },
        {
            "name": "A-102",
            "status": [0, 1, 1, 0, 0, 0, 1, 0]
        }
    ],
    "教学楼B": [
        {
            "name": "B-201",
            "status": [0, 0, 0, 0, 1, 1, 0, 0]
        }
    ]
}
```

> 按教学楼名称分组，每栋楼下的教室列表。`status` 数组中每个元素代表一节课的占用状态（0=空闲, 1=占用），数组长度对应当天总节数。

| 字段 | 类型 | 说明 |
|------|------|------|
| name | string | 教室名称 |
| status | int[] | 各节课占用状态（0=空闲, 1=占用） |

### 7.6 exam — 考试安排

```json
[
    {
        "name": "高等数学",
        "date": "2026-06-20",
        "time": "09:00-11:00",
        "location": "教学楼A-301",
        "seat": "15",
        "type": "闭卷"
    },
    {
        "name": "大学英语",
        "date": "2026-06-22",
        "time": "14:00-16:00",
        "location": "外语楼-105",
        "seat": "22",
        "type": "闭卷"
    }
]
```

| 字段 | 类型 | 说明 |
|------|------|------|
| name | string | 考试科目 |
| date | string | 考试日期 |
| time | string | 考试时间 |
| location | string | 考试地点 |
| seat | string | 座位号 |
| type | string | 考试类型（如闭卷/开卷） |

---

## 8. 错误码参考

| code | 含义 |
|------|------|
| 0 | 通用错误（具体原因见 msg） |
| 1 | 成功 |
| 10 | 弹窗提示（需前端弹窗展示 msg） |

> 内部业务错误码（如院校登录过期、频率限制等）统一以 code=0 返回，具体原因见 `msg` 字段。

---

## 9. 频率限制

系统对数据接口设有频率限制，超出限制将临时封禁使用。请在对接时做好请求节流，避免频繁调用。

> 具体限制阈值以实际返回的错误提示为准。

---

## 10. 完整对接流程

### 10.1 微信小程序对接流程

```
┌─────────────┐     ┌─────────────┐     ┌──────────────┐
│ 1. 微信登录   │────▶│ 2. 获取校区列表 │────▶│ 3. 用户选择校区│
│ /api/auth/   │     │ /api/school/ │     │              │
│ login        │     │ list         │     │              │
└─────────────┘     └─────────────┘     └──────┬───────┘
                                                │
                    ┌───────────────────────────┘
                    ▼
┌─────────────┐     ┌─────────────┐     ┌──────────────┐
│ 4a.获取验证码 │────▶│ 4b. 登录院校  │────▶│ 5. 调用数据接口│
│ /api/school/ │     │ /api/school/ │     │ /api/school/  │
│ verify       │     │ login        │     │ interface     │
└─────────────┘     └─────────────┘     └──────────────┘
                                                │
                    ┌───────────────────────────┘
                    ▼
         ┌──────────────────────┐
         │ 6. 回传 decrypt 保持  │
         │    登录状态            │
         └──────────────────────┘
```

### 10.2 第三方服务商对接流程

```
┌────────────────┐         ┌────────────────┐
│ 1. 注册服务商    │────────▶│ 获取 uuid 和    │
│ （后台管理）     │         │ secretkey      │
└────────────────┘         └───────┬────────┘
                                   │
┌──────────────────────────────────┘
▼
┌────────────────┐         ┌────────────────┐
│ 2. 用户在你的平台│────────▶│ 3. 用户通过知岁OS│
│ 发起数据请求     │         │ 小程序获取token │
└────────────────┘         └───────┬────────┘
                                   │ 用户提供 token
                                   ▼
                         ┌────────────────────┐
                         │ 4. 加密请求          │
                         │ AES-256-GCM加密     │
                         │ (UUID + token)      │
                         └─────────┬──────────┘
                                   │
                                   ▼
                         ┌────────────────────┐
                         │ 5. 调用 useToken    │
                         │ POST /api/open/     │
                         │ useToken            │
                         └─────────┬──────────┘
                                   │
                                   ▼
                         ┌────────────────────┐
                         │ 6. 解密响应数据      │
                         │ AES-256-GCM解密     │
                         │ 得到原始 JSON 数据   │
                         └────────────────────┘
```

---


## 附录 B：技术支持

如有对接问题，请联系平台技术支持获取帮助。
