153 lines
6.6 KiB
Markdown
153 lines
6.6 KiB
Markdown
|
|
# 开放API-学生列表授权 技术设计方案
|
|||
|
|
|
|||
|
|
> 作者:pangu
|
|||
|
|
> 创建时间:2026-02-04
|
|||
|
|
> 评审状态:待评审
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、总体架构
|
|||
|
|
|
|||
|
|
### 1.1 请求链路
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
外部请求 GET /open/api/student/list?pageNum=1&pageSize=10
|
|||
|
|
→ 不经过 Sa-Token 登录校验(security.excludes 排除 /open/api/**)
|
|||
|
|
→ ApiAuthInterceptor 拦截 /open/api/**
|
|||
|
|
→ 校验 X-App-Id、X-Timestamp、X-Sign
|
|||
|
|
→ 查应用信息、校验接口授权
|
|||
|
|
→ OpenApiStudentController.list() → IPgStudentService.selectPageList()
|
|||
|
|
→ 返回 TableDataInfo<StudentVo>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.2 模块与职责
|
|||
|
|
|
|||
|
|
| 模块/组件 | 职责 |
|
|||
|
|
|-----------|------|
|
|||
|
|
| Security 配置 | 将 `/open/api/**` 加入排除路径,不校验登录 |
|
|||
|
|
| ApiAuthInterceptor | 仅对 `/open/api/**` 生效:校验头、签名、接口授权 |
|
|||
|
|
| OpenApiStudentController | 提供 GET `/open/api/student/list`,委托现有 StudentService |
|
|||
|
|
| IPgApplicationService | 新增 selectByAppCode、checkApiPermission;新增/编辑时同步 pg_app_api |
|
|||
|
|
| PgAppApiMapper | 新增按 appCode 查授权 api_path 列表方法(联表 pg_api_dict) |
|
|||
|
|
| pg_api_dict | 增加「学生列表」开放接口记录,api_path=/open/api/student/list |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、安全与鉴权
|
|||
|
|
|
|||
|
|
### 2.1 请求头约定
|
|||
|
|
|
|||
|
|
| 请求头 | 必填 | 说明 |
|
|||
|
|
|--------|------|------|
|
|||
|
|
| X-App-Id | 是 | 应用编码(appCode) |
|
|||
|
|
| X-Timestamp | 是 | 当前时间毫秒时间戳,防重放 |
|
|||
|
|
| X-Sign | 是 | 签名,见 2.2 |
|
|||
|
|
|
|||
|
|
### 2.2 签名算法
|
|||
|
|
|
|||
|
|
1. 将请求参数(Query 与 Body,仅一层 key-value)按参数名 ASCII 升序排序。
|
|||
|
|
2. 拼接为:`key1=value1&key2=value2&...&appSecret=应用密钥`(appSecret 为服务端存储的密钥)。
|
|||
|
|
3. 对上述字符串做 **MD5**,结果转为 **大写**,即 X-Sign。
|
|||
|
|
|
|||
|
|
示例(GET,无 body):
|
|||
|
|
|
|||
|
|
- 请求:GET /open/api/student/list?pageNum=1&pageSize=10
|
|||
|
|
- 假设 appSecret=abc123
|
|||
|
|
- 参数字符串:`pageNum=1&pageSize=10&appSecret=abc123`
|
|||
|
|
- sign = MD5(参数字符串).toUpperCase()
|
|||
|
|
|
|||
|
|
### 2.3 时间戳防重放
|
|||
|
|
|
|||
|
|
- 服务端收到 X-Timestamp 后,与当前服务器时间比较,若 |now - timestamp| > 5 分钟,返回 400「请求已过期」。
|
|||
|
|
|
|||
|
|
### 2.4 接口授权校验
|
|||
|
|
|
|||
|
|
- 请求 URI 取 path(如 `/open/api/student/list`),与当前应用在 pg_app_api + pg_api_dict 中关联的 api_path 集合比对;不在集合内则返回 403「无权访问该接口」。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、接口设计
|
|||
|
|
|
|||
|
|
### 3.1 开放接口:学生列表
|
|||
|
|
|
|||
|
|
| 项目 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 方法 | GET |
|
|||
|
|
| 路径 | /open/api/student/list |
|
|||
|
|
| 鉴权 | 应用签名 + 接口授权 |
|
|||
|
|
| 参数 | 与现有 /business/student/list 一致:studentName、studentNo、schoolId、schoolGradeId、schoolClassId、status 等;分页 pageNum、pageSize |
|
|||
|
|
| 响应 | 与现有一致:`{ code, msg, rows, total }`(TableDataInfo) |
|
|||
|
|
|
|||
|
|
### 3.2 错误响应
|
|||
|
|
|
|||
|
|
| HTTP 状态 | 场景 | 示例 msg |
|
|||
|
|
|-----------|------|----------|
|
|||
|
|
| 400 | 缺少认证参数 / 时间戳格式错误 / 请求已过期 | 缺少认证参数 |
|
|||
|
|
| 401 | 应用不存在 / 应用已停用 / 签名验证失败 | 签名验证失败 |
|
|||
|
|
| 403 | 未授权该接口 | 无权访问该接口 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、数据与缓存
|
|||
|
|
|
|||
|
|
### 4.1 表使用
|
|||
|
|
|
|||
|
|
- **pg_application**:已有;需按 app_code 查询(selectByAppCode)。
|
|||
|
|
- **pg_api_dict**:已有;需新增一条 api_path=`/open/api/student/list` 的记录,供授权勾选与校验。
|
|||
|
|
- **pg_app_api**:已有;新增/编辑应用时按 apiCodes 写入/更新;鉴权时按 app_id 联表 pg_api_dict 得到 api_path 列表。
|
|||
|
|
|
|||
|
|
### 4.2 授权生效
|
|||
|
|
|
|||
|
|
- 方案 A:每次请求查库(或查缓存)。
|
|||
|
|
- 方案 B:应用授权变更时更新缓存,校验时先查缓存。
|
|||
|
|
本期可采用**不缓存**或**短 TTL(如 5 分钟)缓存**「应用授权 path 集合」,技术实现时在 Service 中按 appCode 查授权 path 列表即可;若后续性能有要求再加 Redis。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、实现清单
|
|||
|
|
|
|||
|
|
### 5.1 配置
|
|||
|
|
|
|||
|
|
- **application.yml**:security.excludes 增加 `/open/api/**`。
|
|||
|
|
|
|||
|
|
### 5.2 应用模块(application)
|
|||
|
|
|
|||
|
|
- **IPgApplicationService**:新增 `PgApplication selectByAppCode(String appCode)`、`boolean checkApiPermission(String appCode, String apiPath)`。
|
|||
|
|
- **PgApplicationServiceImpl**:实现上述方法;insert/update 时增加对 apiCodes 的处理,同步 pg_app_api(先删后插)。
|
|||
|
|
- **PgApplication**:增加 `@TableField(exist = false) private List<String> apiCodes;`,用于接收前端与返回详情。
|
|||
|
|
- **PgAppApiMapper**:新增方法,如 `List<String> selectApiPathsByAppCode(String appCode)`(联表 pg_api_dict 取 api_path)。
|
|||
|
|
- **PgAppApiMapper.xml**:对应 SQL(a.app_id = app_id 且 a.api_id = d.api_id,d.api_path)。
|
|||
|
|
|
|||
|
|
### 5.3 开放 API 模块(新建或放在 business 下)
|
|||
|
|
|
|||
|
|
- **OpenApiConfig**:WebMvcConfigurer,注册 ApiAuthInterceptor,仅 addPathPatterns("/open/api/**")。
|
|||
|
|
- **ApiAuthInterceptor**:preHandle 中取 X-App-Id、X-Timestamp、X-Sign;校验时间戳 → selectByAppCode → 校验签名 → checkApiPermission(uri) → 通过则 return true。
|
|||
|
|
- **OpenApiStudentController**:GET `/open/api/student/list`,参数与 PgStudentController.list 一致,委托 IPgStudentService.selectPageList。
|
|||
|
|
|
|||
|
|
### 5.4 接口字典数据
|
|||
|
|
|
|||
|
|
- 在 pg_api_dict 中 INSERT 一条:api_code=OPEN_STUDENT_LIST,api_name=学生列表,api_path=/open/api/student/list,api_method=GET,status=0。可使用 SQL 脚本或启动时初始化。
|
|||
|
|
|
|||
|
|
### 5.5 前端
|
|||
|
|
|
|||
|
|
- 已有接口授权勾选(apiCodes)与 apiList 接口;只需保证后端保存/回显 apiCodes 与 pg_app_api 一致即可,无需改前端逻辑(若当前未回显 apiCodes,需后端在查询应用详情时填充 apiCodes)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、调用测试说明
|
|||
|
|
|
|||
|
|
- 提供「开放API-学生列表授权-调用测试说明.md」或同目录下测试脚本:
|
|||
|
|
- 签名算法说明与示例(含 GET 示例)。
|
|||
|
|
- 示例:在管理端创建应用并勾选「学生列表」,记录 appCode、appSecret,用 curl 或 Postman 调用 GET /open/api/student/list,携带正确 X-App-Id、X-Timestamp、X-Sign。
|
|||
|
|
- 预期:授权应用返回 200 与列表数据;未授权或错误签名返回 401/403。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、评审要点
|
|||
|
|
|
|||
|
|
1. 开放路径仅限 `/open/api/**`,与现有 /business、/h5 隔离,且不校验用户登录。
|
|||
|
|
2. 签名算法与请求头约定是否满足安全与对接方实现成本平衡。
|
|||
|
|
3. 学生列表参数与响应与现有一致,避免业务逻辑重复。
|
|||
|
|
4. 应用管理侧保存/回显接口授权与 pg_app_api、pg_api_dict 数据一致性。
|
|||
|
|
5. 后续扩展其他开放接口时,仅需在 pg_api_dict 加记录、新增对应 OpenApiXxxController 即可,鉴权与拦截器复用。
|