202 lines
7.7 KiB
Markdown
202 lines
7.7 KiB
Markdown
# oapi 课表查询接口 · 调用手册
|
||
|
||
> 服务:AcaSyllabu(课程表) 接口:`GET /oapi/timetable/Get` 更新:2026-10-13
|
||
|
||
按「用户 + 角色 + 学校」查询其可见范围内所有班级的当前学年学期课程表。身份由调用方显式传入(不依赖浏览器登录态),适用于平台内其他应用(首页卡片、移动端、大屏等)服务端直调。
|
||
|
||
| 调用方身份 | 可见班级 | 可见科目 |
|
||
|---|---|---|
|
||
| 管理员(roleId=7)/ 校领导(roleId=2) | 全部班级 | 全部科目 |
|
||
| 班主任 | 所带班级(正/副班主任) | 全部科目 |
|
||
| 任教老师(roleId=1) | 任教班级 | 仅任教科目(其余科目格清空返回) |
|
||
| 学生(roleId=3) | 本人所在班级 | 全部科目 |
|
||
|
||
- `roleId` 不传时**默认按普通教师(1)**处理。
|
||
- 一个用户兼任多重身份(如既是班主任又任教其他班)时,取并集:班主任班给全部科目,任教班只给任教科目。
|
||
- 无任何可见范围(如普通教师没有任教记录)时返回空数组 `data: []`,不报错。
|
||
|
||
---
|
||
|
||
## 1. 请求
|
||
|
||
```
|
||
GET {base}/oapi/timetable/Get?userid={userId}&roleId={roleId}&schId={schId}
|
||
```
|
||
|
||
- 本地联调:`http://localhost:8109`(无 context-path)
|
||
- 生产:`http://{host}/acasyllabu`(context-path `/acasyllabu`,以网关实际地址为准)
|
||
|
||
| 参数 | 必填 | 说明 |
|
||
|---|---|---|
|
||
| `userid` | 是 | 平台用户ID。教师=教师ID;学生=学生树里的学生节点ID |
|
||
| `roleId` | 否 | 角色:1 教师(默认)、2 校领导、3 学生、7 管理员 |
|
||
| `schId` | 是 | 学校ID(数据严格限定在该校范围内) |
|
||
|
||
## 2. 签名鉴权(必须)
|
||
|
||
接口在 `/oapi/*` 强制验签下:浏览器/匿名直访一律 **401**,只有注册应用带上签名三件套才能通过。
|
||
|
||
### 2.1 请求头
|
||
|
||
| Header | 说明 |
|
||
|---|---|
|
||
| `X-App-Key` | 调用方应用ID(在服务方 `trusted-apps` 白名单登记的 appKey) |
|
||
| `X-Timestamp` | 当前毫秒时间戳。**有效期默认 5 分钟**,过期拒绝,请用当前时间 |
|
||
| `X-Signature` | 签名,算法见下 |
|
||
|
||
### 2.2 签名算法
|
||
|
||
```
|
||
待签串 = 请求方法 + "\n" + target + "\n" + X-Timestamp
|
||
target = 请求路径 + "?" + 查询参数(按参数名字典序排列后的 k=v&k=v)
|
||
签名 = Base64( HmacSHA256( 待签串, appSecret ) )
|
||
```
|
||
|
||
要点:
|
||
- 方法用大写 `GET`;分隔符是换行 `\n`,共两处;
|
||
- query 参数**按参数名排序**后再拼(如 `roleId` 在 `schId` 在 `userid` 前),排序后**原样拼接、不做 URL 编码**;
|
||
- 密钥即服务方 `trusted-apps` 里登记的该 appKey 对应 secret(找 AcaSyllabu 管理员索取)。
|
||
|
||
### 2.3 接入新调用方
|
||
|
||
服务方在 `application.yml`(生产为 Nacos `acasyllabu.yaml`)的 `yh.app-auth.trusted-apps` 下追加一行:
|
||
|
||
```yaml
|
||
yh:
|
||
app-auth:
|
||
trusted-apps:
|
||
"100021": "d3ee380e..." # 既有
|
||
"100xxx": "新调用方密钥" # 新增
|
||
```
|
||
|
||
> 生产环境接了中央密钥链(Redis 注册表)后可改走 `trust-registry`,届时以平台注册为准,无需手工维护白名单。
|
||
|
||
## 3. 响应
|
||
|
||
外层为平台统一包装:
|
||
|
||
```json
|
||
{ "code": 0, "msg": "", "data": [ …班级数组… ] }
|
||
```
|
||
|
||
`data` 数组每项:
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `classId` / `className` / `gradeName` | 班级ID / 班级名 / 年级名 |
|
||
| `headTeacher` | 该用户在本班是否班主任 |
|
||
| `viewAllSubjects` | 是否全部科目可见(班主任/管理员/校领导/学生=true,仅任教=false) |
|
||
| `subjects` | 任教科目列表(仅 viewAllSubjects=false 时非空) |
|
||
| `timetable` | 该班课表,结构同 `TimetableVo`(见下) |
|
||
|
||
`timetable`:
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `year` / `term` | 课表所属学年学期(term:1 上学期 2 下学期) |
|
||
| `photoFileId` / `photoName` / `photoSize` | 照片课表附件(未上传为 null) |
|
||
| `slots[]` | 节次数组;`slotNo` 从 1 起,`start`/`end` 为 HH:mm |
|
||
| `slots[].subjects` | 固定 7 位,下标 0~6 = 周一~周日,空串=未排课 |
|
||
|
||
示例(节选):
|
||
|
||
```json
|
||
{
|
||
"code": 0, "msg": "",
|
||
"data": [
|
||
{
|
||
"classId": 371, "className": "2班", "gradeName": "高一",
|
||
"headTeacher": false, "viewAllSubjects": false,
|
||
"subjects": ["语文"],
|
||
"timetable": {
|
||
"classId": 371, "year": 2026, "term": 1,
|
||
"className": "2班", "gradeName": "高一",
|
||
"photoFileId": null, "photoName": null, "photoSize": null,
|
||
"slots": [
|
||
{ "slotNo": 1, "start": "08:20", "end": "09:00",
|
||
"subjects": ["语文", "", "", "", "", "", ""] }
|
||
],
|
||
"viewAllSubjects": false, "mySubjects": ["语文"]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## 4. 错误情况
|
||
|
||
| 现象 | 原因 |
|
||
|---|---|
|
||
| HTTP 401 `missing app signature` | 未带签名三件套 |
|
||
| HTTP 401 `invalid signature or expired` | 密钥错误、待签串拼错或时间戳超过 5 分钟 |
|
||
| HTTP 401 `unknown appKey` | appKey 未在服务方 `trusted-apps` 登记 |
|
||
| HTTP 200 `{"code":6,"msg":"内部服务错误"}` | 缺 `userid`/`schId` 等必填参数 |
|
||
| HTTP 200 `code=0, data=[]` | 该身份在该校无可见班级(如无任教记录的教师) |
|
||
|
||
## 5. 调用示例
|
||
|
||
### 5.1 Node.js(可直接运行)
|
||
|
||
```js
|
||
const crypto = require('crypto');
|
||
|
||
const BASE = 'http://localhost:8109';
|
||
const APP_KEY = '100xxx';
|
||
const APP_SECRET = '你的密钥';
|
||
|
||
function get(pathAndQuery) {
|
||
const q = pathAndQuery.indexOf('?');
|
||
const path = q < 0 ? pathAndQuery : pathAndQuery.substring(0, q);
|
||
const pairs = q < 0 ? [] : pathAndQuery.substring(q + 1).split('&').sort();
|
||
const target = path + (pairs.length ? '?' + pairs.join('&') : '');
|
||
const ts = String(Date.now());
|
||
const sign = crypto.createHmac('sha256', APP_SECRET)
|
||
.update(`GET\n${target}\n${ts}`)
|
||
.digest('base64');
|
||
return fetch(BASE + pathAndQuery, {
|
||
headers: { 'X-App-Key': APP_KEY, 'X-Timestamp': ts, 'X-Signature': sign },
|
||
}).then(r => r.json());
|
||
}
|
||
|
||
// 管理员查全校
|
||
get('/oapi/timetable/Get?userid=1&roleId=7&schId=3').then(console.log);
|
||
```
|
||
|
||
### 5.2 Java(平台内服务,有 yh-utils-core 依赖)
|
||
|
||
```java
|
||
String pathAndQuery = "/oapi/timetable/Get?userid=1&roleId=7&schId=3";
|
||
// query 按参数名排序后拼回
|
||
String target = "/oapi/timetable/Get?roleId=7&schId=3&userid=1";
|
||
String ts = String.valueOf(System.currentTimeMillis());
|
||
String sign = SignUtil.signRequest("GET", target, ts, appSecret); // 平台工具,同款算法
|
||
|
||
HttpHeaders headers = new HttpHeaders();
|
||
headers.set("X-App-Key", appKey);
|
||
headers.set("X-Timestamp", ts);
|
||
headers.set("X-Signature", sign);
|
||
restTemplate.exchange(baseUrl + pathAndQuery, HttpMethod.GET, new HttpEntity<>(headers), String.class);
|
||
```
|
||
|
||
> 平台 Feign 客户端加 `SignFeignInterceptor` 可自动签名,无需手工拼。
|
||
|
||
### 5.3 curl(Git Bash,签名用 openssl 生成)
|
||
|
||
```bash
|
||
SECRET="你的密钥"
|
||
TARGET="/oapi/timetable/Get?roleId=7&schId=3&userid=1" # 参数已按名排序
|
||
TS=$(date +%s000)
|
||
SIGN=$(printf "GET\n%b\n%b" "$TARGET" "$TS" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)
|
||
|
||
curl -s "http://localhost:8109$TARGET" \
|
||
-H "X-App-Key: 100xxx" -H "X-Timestamp: $TS" -H "X-Signature: $SIGN"
|
||
```
|
||
|
||
> 注意 curl 的 URL 必须与参与签名的 target 完全一致(含参数顺序)。
|
||
|
||
## 6. 本地联调
|
||
|
||
- 服务方后端:8109 端口(devtools 热重启);本地验签白名单只收了本应用 `100021`(application.yml `trusted-apps`)。
|
||
- 现成冒烟脚本:`E:\YHXT\java\gui-test-screenshots\oapi-sign-smoke.js`(用法 `node oapi-sign-smoke.js "/oapi/timetable/Get?userid=1&roleId=7&schId=3"`,密钥内置 100021)。
|
||
- 真实数据参考(schId=3):管理员 userid=1 返回全部班级;教师 10008 = 班主任(1班 全部科目) + 任教(2班 仅语文);学生 10066 = 仅 1 班。
|