开放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 签名算法
- 将请求参数(Query 与 Body,仅一层 key-value)按参数名 ASCII 升序排序。
- 拼接为:
key1=value1&key2=value2&...&appSecret=应用密钥(appSecret 为服务端存储的密钥)。
- 对上述字符串做 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。
七、评审要点
- 开放路径仅限
/open/api/**,与现有 /business、/h5 隔离,且不校验用户登录。
- 签名算法与请求头约定是否满足安全与对接方实现成本平衡。
- 学生列表参数与响应与现有一致,避免业务逻辑重复。
- 应用管理侧保存/回显接口授权与 pg_app_api、pg_api_dict 数据一致性。
- 后续扩展其他开放接口时,仅需在 pg_api_dict 加记录、新增对应 OpenApiXxxController 即可,鉴权与拦截器复用。