签入版本
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# 8.5 字典数据接口
|
||||
|
||||
:::tip 概述
|
||||
字典接口分三层:管理接口(dict/dict_item 七件套,带权限)、前端消费接口(dictionary 别名 + getDictItemList,登录即可)、缓存刷新接口。机制详见 [5.10 字典模块](../core/dict)。
|
||||
:::
|
||||
|
||||
## 管理接口(带权限)
|
||||
|
||||
```text
|
||||
dict(/api/dict)七件套:sys:dict:{page|detail|add|update|delete|batchDelete}
|
||||
(注意:无 /dict/list——列表经 page 或 /api/dictionary/list 别名)
|
||||
GET /api/dict/refreshCache sys:dict:cache 刷新字典缓存
|
||||
|
||||
dict_item(/api/dict/item)七件套:sys:dictitem:{page|detail|add|update|delete|batchDelete}
|
||||
(页面用 page;无标准 list)
|
||||
```
|
||||
|
||||
## 前端消费接口(登录即可,无 permission)
|
||||
|
||||
```text
|
||||
GET /api/dictionary/list 字典列表别名(前端 dictionary.ts 调用)
|
||||
GET /api/dictionary/info 字典详情别名
|
||||
GET /api/dict/item/getDictItemList/:code 按编码查字典项(走 Redis 缓存)
|
||||
```
|
||||
|
||||
## 典型消费流
|
||||
|
||||
```text
|
||||
前端下拉/单选组件初始化:
|
||||
GET /api/dict/item/getDictItemList/{code}
|
||||
→ 缓存命中直接返回(easygoadmin:dict:item:{code},TTL 3600s)
|
||||
→ 返回 [{name:"正常", value:1}, {name:"禁用", value:2}, ...]
|
||||
→ 组件 options 渲染
|
||||
|
||||
行内显示名({field}Text):
|
||||
后端基座已填充(DictGetText),前端直接展示 statusText——
|
||||
不需要再调字典接口
|
||||
```
|
||||
|
||||
## 缓存与刷新
|
||||
|
||||
| 层 | TTL | 刷新方式 |
|
||||
|----|-----|---------|
|
||||
| Redis `dict:item:{code}` | 3600s | `GET /api/dict/refreshCache`(可全量或按 code 单刷) |
|
||||
| 进程内 dictTextCache | 300s | 只能等自然过期(各实例独立) |
|
||||
|
||||
::: warning 刷新语义
|
||||
RefreshCache **只刷 Redis 层**——{field}Text 的进程内层仍按 300s 自然过期。改字典文案"接口立即变、{field}Text 最迟 5 分钟变"是预期行为。
|
||||
:::
|
||||
|
||||
## 缓存失效注意
|
||||
|
||||
```text
|
||||
dict_item 增删改不主动清 Redis 缓存(最长 1 小时延迟)——
|
||||
关键场景(如刚改完下拉选项要立即生效)调 GET /api/dict/refreshCache。
|
||||
```
|
||||
|
||||
## 接口使用速查
|
||||
|
||||
| 需求 | 接口 |
|
||||
|------|------|
|
||||
| 页面展示字典项下拉 | GET /api/dict/item/getDictItemList/{code} |
|
||||
| 字典管理页列表 | GET /api/dict/page |
|
||||
| 字典项管理页列表 | GET /api/dict/item/page?dictId=x |
|
||||
| 新建字典 | POST /api/dict/add(code 全局唯一) |
|
||||
| 新建字典项 | POST /api/dict/item/add(name/value/sort) |
|
||||
| 改文案后立即生效 | GET /api/dict/refreshCache(Redis 层);{field}Text 等 300s |
|
||||
|
||||
## 开发规约
|
||||
|
||||
+ 字典编码规范 `{模块snake}_{字段snake}`(user_status / job_execute_policy)
|
||||
+ 列注释枚举建表 → 生成器自动建字典(幂等)
|
||||
+ 前端下拉选项优先字典驱动(勿硬编码 options——example 的静态 options 是教学示例)
|
||||
+ 行内显示名走后端 {field}Text(唯一真相在字典管理页)
|
||||
@@ -0,0 +1,64 @@
|
||||
# 8.2 错误码对照表
|
||||
|
||||
:::tip 概述
|
||||
业务错误码定义在 internal/consts/biz_err.go(gcode.Register 语义值)。注意 Fail 映射规则:除 401 外全部降级为 code=1 返回前端——细分码主要用于日志与语义区分。
|
||||
:::
|
||||
|
||||
## 基础错误码
|
||||
|
||||
| 码 | 常量 | 含义 | 触发场景 |
|
||||
|----|------|------|---------|
|
||||
| 1 | CodeFailed | 通用操作失败 | 未归类错误的默认出口 |
|
||||
| 400 | CodeParamError | 参数校验失败 | gvalid 标签校验不过(文案取 # 后缀) |
|
||||
| 401 | CodeUnauthorized | 登录已过期,请重新登录 | 令牌缺失/过期/黑名单(**唯一保留原码返回**) |
|
||||
| 403 | CodeForbidden | 没有该操作权限 | HasPerm 权限判定不过 |
|
||||
| 429 | CodeTooManyRequests | 请求过于频繁 | RateLimit 超限 |
|
||||
| 500 | CodeServerError | 服务器内部错误 | panic 兜底 / 未预期错误 |
|
||||
|
||||
## 业务错误码(1000+)
|
||||
|
||||
| 码 | 含义 | 触发模块/场景 |
|
||||
|----|------|-------------|
|
||||
| 1001 | 用户不存在 | auth.Login findUser 未命中 |
|
||||
| 1002 | 账号禁用 | auth.Login status!=1 |
|
||||
| 1003 | 密码错误 | auth.Login bcrypt 比对失败 |
|
||||
| 1004 | 验证码错误 | captcha.Verify 失败/过期 |
|
||||
| 1005 | 租户已过期 | Tenant 中间件 / mustLoadTenant expire_time 已过 |
|
||||
| 1006 | 租户已禁用 | Tenant 中间件 / mustLoadTenant status!=1 |
|
||||
| 1007 | 租户用户数上限 | user.Add checkTenantUserMax 超 max_user_num |
|
||||
| 1008 | Token 无效 | oauth2 Refresh:typ 非 refresh / 已在黑名单(防重放) |
|
||||
| 1009 | 记录已存在 | 唯一预检(UniqueFields/CheckUnique)命中 |
|
||||
| 1010 | 记录不存在 | 详情/更新/删除目标缺失 |
|
||||
| 1011 | 存在下级或关联 | 删除守卫(dept 子级 / menu 子菜单等) |
|
||||
| 1012 | 原密码错误 | index.UpdatePassword 旧密码校验失败 |
|
||||
| 1013 | 租户不存在 | 租户定位失败 |
|
||||
| 1014 | 记录已被引用 | 删除守卫(role 被用户引用等) |
|
||||
| 1015 | 演示环境禁止操作 | Demo 中间件拦截写操作(app.demo=true) |
|
||||
|
||||
## 使用规约
|
||||
|
||||
```text
|
||||
后端上抛:gerror.NewCodef(consts.CodeUserNotExist, "用户不存在")
|
||||
→ Bind 包装层 response.Fail → 除 401 外降级 code=1(msg 保留)
|
||||
|
||||
前端识别:
|
||||
code=0 成功
|
||||
code=1 失败(弹 msg——"用户不存在"等细分语义在 msg 里)
|
||||
code=401 未认证(跳登录)
|
||||
code=403 无权限(可定制提示)
|
||||
code=429 限流(可定制"请稍后再试")
|
||||
```
|
||||
|
||||
::: tip 为什么细分码降级为 1
|
||||
前端只需三条分流规则(0/1/401)即可覆盖全部场景;细分码留在服务端日志与 msg 中,避免前端维护 20+ 分支。需要按细分码做 UI 差异化时,读 msg 或扩展信封(不建议破坏现有约定)。
|
||||
:::
|
||||
|
||||
## 排障对照
|
||||
|
||||
| 用户看到 | 码 | 深层原因排查 |
|
||||
|---------|-----|------------|
|
||||
| "登录已过期" | 401 | 令牌过期/被拉黑(登出/轮换/强退) |
|
||||
| "没有该操作权限" | 403 | gmeta permission ↔ 菜单节点 ↔ 角色勾选 ↔ 身份缓存 |
|
||||
| "请求过于频繁" | 429 | 限流阈值(认证入口 10/分钟更严) |
|
||||
| "演示环境,禁止操作!" | 1→1015 msg | app.demo=true |
|
||||
| 其他业务提示 | 1 | msg 即细分语义;服务端日志有细分码 |
|
||||
@@ -0,0 +1,101 @@
|
||||
# 8.7 API 接口总览
|
||||
|
||||
:::tip 概述
|
||||
全部接口经 gmeta 注解自动生成 OpenAPI:GET /api.json(原生 JSON)+ GET /swagger(本地 Redoc UI)。本节给出接口域总览与权限码总规律,完整清单以 /api.json 为准。
|
||||
:::
|
||||
|
||||
## 在线文档
|
||||
|
||||
```text
|
||||
GET /swagger 本地托管 Redoc 页面(免登录;内置页依赖境外 CDN,已改本地托管)
|
||||
GET /api.json 原生 OpenAPI JSON(gmeta 补注册闭包路由后全量可用)
|
||||
```
|
||||
|
||||
gmeta 的 method/path/summary/permission 会完整体现在文档中——接口与文档天然同步。
|
||||
|
||||
## 接口域总览(27 模块 170+ 接口)
|
||||
|
||||
| 域 | 模块 | 路由前缀 | 说明 |
|
||||
|----|------|---------|------|
|
||||
| auth | (无表) | /api/login 等顶层 | 登录/登出/验证码/令牌(公开+顶层) |
|
||||
| index | (无表) | /api/index/* | 个人中心(登录即可) |
|
||||
| system | user | /api/user | 用户(含导入导出/重置密码) |
|
||||
| | role | /api/role | 角色(权限分配/data_scope) |
|
||||
| | menu | /api/menu | 菜单(树形) |
|
||||
| | dept | /api/dept | 部门(树形) |
|
||||
| | level | /api/level | 职级(导入导出) |
|
||||
| | position | /api/position | 岗位 |
|
||||
| | tenant | /api/tenant | 租户(开户/用户列表) |
|
||||
| data | config | /api/config | 配置(+web/index、web/save) |
|
||||
| | config_item | /api/config/item | 配置项 |
|
||||
| | param | /api/param | 参数 |
|
||||
| | dict | /api/dict | 字典(+refreshCache、dictionary 别名) |
|
||||
| | dict_item | /api/dict/item | 字典项(+getDictItemList) |
|
||||
| | notice | /api/notice | 公告 |
|
||||
| | city | /api/city(别名 /api/area) | 行政区划(级联懒加载) |
|
||||
| content | article | /api/article | 文章(富文本/多图) |
|
||||
| | category | /api/category | 分类(树形) |
|
||||
| | link | /api/link | 友链 |
|
||||
| monitor | operation_log | /api/oper/log | 操作日志(仅查询删除) |
|
||||
| | login_log | /api/login/log | 登录日志(仅查询删除) |
|
||||
| | job | /api/job | 定时任务(+runOnce/pause/resume/daemonStatus) |
|
||||
| | job_log | /api/job/log | 任务日志(仅查询删除) |
|
||||
| | online_user | /api/onlineUser | 在线用户(无表,Redis) |
|
||||
| | server_monitor | /api/server | 服务器监控(无表) |
|
||||
| | cache_monitor | /api/cache | 缓存监控(无表) |
|
||||
| tool | example | /api/example | 案例演示(导入导出) |
|
||||
| | file_template | /api/file/template | 文件模板(+getTemplateByCode) |
|
||||
| | generator | /api/generator | 代码生成(page/preview/generate/batchGenerate) |
|
||||
| | upload | /api/upload | 上传(uploadFile/uploadFiles) |
|
||||
|
||||
基础设施(router.go 手工注册):`GET /api/health`(公开)、`GET /swagger`、`GET /api.json`、`GET /upload/**`(静态)。
|
||||
|
||||
## 标准接口形态(七件套 + 扩展)
|
||||
|
||||
```text
|
||||
GET /api/{prefix}/page 分页
|
||||
GET /api/{prefix}/list 列表(下拉/树形)
|
||||
GET /api/{prefix}/detail/:id 详情
|
||||
POST /api/{prefix}/add 新增
|
||||
PUT /api/{prefix}/update 修改
|
||||
DELETE /api/{prefix}/delete/:id 删除(软删)
|
||||
DELETE /api/{prefix}/batchDelete 批量删除(body {ids:[]})
|
||||
扩展:PUT /api/{prefix}/status(状态)
|
||||
POST /api/{prefix}/import · GET /api/{prefix}/export(导入导出)
|
||||
```
|
||||
|
||||
## 权限码总规律
|
||||
|
||||
```text
|
||||
sys:{模块小驼峰}:{动作}
|
||||
动作与方法名一致:page/list/detail/add/update/delete/batchDelete/
|
||||
status/import/export/resetPwd/execute/pause/resume/
|
||||
savePermission/getPermission/cache/users/save 等
|
||||
|
||||
特例:配置网站 sys:configWeb:data|save
|
||||
生成器 sys:generator:generator|batchGenerator
|
||||
上传 sys:upload:file|files
|
||||
|
||||
超管(ROLE_SUPER_ADMIN)绕过全部权限校验
|
||||
```
|
||||
|
||||
## 公开路由(免登录白名单)
|
||||
|
||||
```text
|
||||
/api/login · /api/register(白名单放行,路由未注册)· /api/captcha
|
||||
/api/oauth2/token · /api/health
|
||||
另放行 /api.json 与 /swagger*(文档)
|
||||
|
||||
三处一致:middleware/public.go 白名单 / gmeta public:"true" / router.go 手工注册
|
||||
全部路由不带 /v1 版本段
|
||||
```
|
||||
|
||||
## 通用约定速记
|
||||
|
||||
```text
|
||||
认证:Authorization: Bearer <access_token>(登录接口返回 access_token 下划线)
|
||||
响应:{code, ok, msg, data};0 成功 / 1 失败 / 401 未认证
|
||||
分页:PageData[T](records/total/size/current/pages)
|
||||
命名:出入参驼峰(例外仅登录/令牌接口四字段)
|
||||
限流:普通 120/分钟/IP/路径;认证入口 10/分钟/IP
|
||||
```
|
||||
@@ -0,0 +1,90 @@
|
||||
# 8.3 分页查询规范
|
||||
|
||||
:::tip 概述
|
||||
分页契约三件套:PageReq 入参(pageNo/pageSize)+ PageData 出参(records/total/size/current/pages)+ 筛选对齐三件套(querySchemas ↔ Req 字段 ↔ spec 声明)。导出与列表同套筛选。
|
||||
:::
|
||||
|
||||
## 分页入参(common.PageReq)
|
||||
|
||||
```text
|
||||
pageNo 页码,默认 1
|
||||
pageSize 每页条数,默认 10,上限 500(GetPageSize 截断——防恶意大 pageSize)
|
||||
|
||||
请求示例:GET /api/user/page?pageNo=1&pageSize=10&keyword=张
|
||||
```
|
||||
|
||||
## 分页出参(PageData[T])
|
||||
|
||||
```json
|
||||
{
|
||||
"records": [], "total": 0, "size": 10, "current": 1, "pages": 0
|
||||
}
|
||||
```
|
||||
|
||||
records 为空输出 `[]` 而非 null(前端无需判空兼容)。
|
||||
|
||||
## 筛选对齐三件套(2026-10-14 定稿)
|
||||
|
||||
```text
|
||||
前端 querySchemas.ts 每个筛选输入
|
||||
↔ XxxPageReq 同名驼峰字段
|
||||
↔ logic spec 声明
|
||||
|
||||
字符串 Input → LikeFields(逐列模糊,值取 req 同名字段,空跳过)
|
||||
Select/InputNumber → EqFields(等值,零值跳过)
|
||||
关键词框(可选) → KeywordFields(req.keyword 多列 OR)
|
||||
——能力保留在基座,模块默认不声明,需要时再用
|
||||
```
|
||||
|
||||
### 对齐示例(example 模块)
|
||||
|
||||
| 前端 querySchemas | 后端 PageReq | spec 声明 |
|
||||
|-------------------|-------------|-----------|
|
||||
| `field: 'name'`(Input) | `Name string \`json:"name"\`` | LikeFields: []string{"name"} |
|
||||
| `field: 'type'`(Select) | `Type int \`json:"type"\`` | EqFields: []string{"type", "status"} |
|
||||
| `field: 'status'`(Select) | `Status int \`json:"status"\`` | 同上 |
|
||||
|
||||
### 筛选链执行顺序(固定)
|
||||
|
||||
```text
|
||||
applyFilters:LikeFields → EqFields → KeywordFields → Filter → DataScope
|
||||
前置:dao.Ctx 已注入 del_flag=0 + 租户 IN(0,自身)
|
||||
```
|
||||
|
||||
复杂条件写 Filter 钩子,**不要重复 Like/Eq 已覆盖的条件**。
|
||||
|
||||
## 导出=所见
|
||||
|
||||
```text
|
||||
导出接口统一走 xxxSpec.applyFilters 与列表同套筛选:
|
||||
Export 请求结构含与 PageReq 相同的筛选字段(不带 pageNo/pageSize)
|
||||
→ applyFilters 同链过滤 → 全量(ChunkQuery 分批)→ Excel
|
||||
保证:列表看到什么,导出就是什么(含数据权限过滤)
|
||||
```
|
||||
|
||||
## 排序规范
|
||||
|
||||
```text
|
||||
spec.OrderBy 默认排序(如 "id DESC"、"sort, id")
|
||||
spec.ListOrderBy 列表专用排序
|
||||
spec.OrderableFields 前端排序白名单(orderField/orderType 仅白名单内生效)
|
||||
```
|
||||
|
||||
前端排序参数不可直通 SQL——白名单外的排序字段忽略(防注入)。
|
||||
|
||||
## 前端分页组件约定
|
||||
|
||||
```text
|
||||
分页组件(components/pagination):
|
||||
change 事件携带 pageNo/pageSize → 重新请求 /page
|
||||
pageSize 切换回到第 1 页
|
||||
上限保护:≤500(与后端 GetPageSize 截断一致)
|
||||
```
|
||||
|
||||
## 开发规约
|
||||
|
||||
+ 分页接口 PageReq 内嵌 common.PageReq(复用默认值与截断逻辑)
|
||||
+ 筛选字段必须三件套对齐——querySchemas 加了输入,Req 与 spec 都要跟上
|
||||
+ 字符串筛选走 LikeFields 声明(列名显式声明,值取 req 同名驼峰字段)
|
||||
+ 零值跳过是 EqFields 语义(status=0 是有效筛选值时需在 Filter 处理或调整字段设计)
|
||||
+ 大表分页深翻页注意:pageNo×pageSize 过大时的 offset 成本(管理后台低频场景可接受)
|
||||
@@ -0,0 +1,109 @@
|
||||
# 8.1 统一响应格式
|
||||
|
||||
:::tip 概述
|
||||
全部接口返回统一 JSON 信封:code/ok/msg/data 四字段。code=0 成功、1 通用失败、401 未认证(配合 HTTP 401);前端按 code 分流。分页用 PageData 泛型壳。
|
||||
:::
|
||||
|
||||
## 信封结构
|
||||
|
||||
```json
|
||||
// 成功
|
||||
{
|
||||
"code": 0,
|
||||
"ok": true,
|
||||
"msg": "操作成功",
|
||||
"data": { "id": 1 }
|
||||
}
|
||||
|
||||
// 失败
|
||||
{
|
||||
"code": 1,
|
||||
"ok": false,
|
||||
"msg": "用户不存在",
|
||||
"data": {}
|
||||
}
|
||||
|
||||
// 未认证(HTTP 401)
|
||||
{
|
||||
"code": 401,
|
||||
"ok": false,
|
||||
"msg": "登录已过期,请重新登录",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
## 字段约定
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | int | 0=成功;1=通用失败;401=未认证;细分业务码见 8.2(前端主要看 0/1/401) |
|
||||
| ok | bool | 与 code==0 等价的布尔便捷字段 |
|
||||
| msg | string | 提示信息;失败时前端弹出 |
|
||||
| data | object | 业务数据;失败时为空对象 `{}`(非 null) |
|
||||
|
||||
## Fail 映射规则
|
||||
|
||||
```text
|
||||
response.Fail(err):
|
||||
gerror 码 == 401 → 原样保留(配合 HTTP 401,前端跳登录)
|
||||
其余 → 一律降级 code=1
|
||||
|
||||
设计含义:consts 中的细分错误码(1001 用户不存在……)主要用于
|
||||
日志与服务端语义区分;前端统一按 1 弹 msg 即可。
|
||||
```
|
||||
|
||||
## 分页壳(PageData[T])
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0, "ok": true, "msg": "操作成功",
|
||||
"data": {
|
||||
"records": [ ... ],
|
||||
"total": 100,
|
||||
"size": 10,
|
||||
"current": 1,
|
||||
"pages": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| records | 行数组;**为空输出 `[]` 而非 null** |
|
||||
| total | 总条数 |
|
||||
| size | 每页条数(pageSize,默认 10 上限 500) |
|
||||
| current | 当前页码(pageNo,默认 1) |
|
||||
| pages | 总页数 |
|
||||
|
||||
## 其他通用出参形态
|
||||
|
||||
```text
|
||||
新增: {"id": 1} (IdRes)
|
||||
修改/删除:{} (空结构体)
|
||||
列表(非分页):[ ... ] (数组直接作 data)
|
||||
详情: { ...单对象... }
|
||||
```
|
||||
|
||||
## 状态页
|
||||
|
||||
```text
|
||||
404 → {"code":1,"msg":"接口不存在: GET /api/xxx"}
|
||||
500 → {"code":1,"msg":"服务器内部错误"}
|
||||
非 200 一律输出统一 JSON 信封,不返回 HTML
|
||||
```
|
||||
|
||||
## 前端处理模板
|
||||
|
||||
```ts
|
||||
// 响应拦截器分流(语义示意)
|
||||
if (code === 0) return data; // 成功解包
|
||||
if (code === 401) { 跳登录/尝试refresh; }
|
||||
if (code === 1) ElMessage.error(msg); // 统一弹窗
|
||||
```
|
||||
|
||||
## 开发规约
|
||||
|
||||
+ 出参 json 一律驼峰;唯一例外是登录/令牌接口的下划线字段(见 8.6)
|
||||
+ 业务错误用 gerror.NewCode(consts.Xxx, "消息") 上抛,由 Bind 包装层统一 Fail
|
||||
+ 不要在业务代码手写 JSON 响应(统一信封在包装层完成)
|
||||
+ data 为 nil 时框架输出 `{}`/`[]`,业务无需特殊处理
|
||||
@@ -0,0 +1,98 @@
|
||||
# 8.6 Token 使用说明
|
||||
|
||||
:::tip 概述
|
||||
本文档面向 API 对接方:如何获取令牌、如何携带、如何刷新、如何撤销。令牌为 JWT HS256 双令牌(access 2h + refresh 7d),唯一使用下划线命名的响应。
|
||||
:::
|
||||
|
||||
## 获取令牌(POST /api/login,公开)
|
||||
|
||||
```sh
|
||||
curl -X POST http://127.0.0.1:8000/api/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"123456","code":"618618","key":"<captcha-key>"}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0, "ok": true, "msg": "操作成功",
|
||||
"data": {
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…",
|
||||
"expires_in": 7200,
|
||||
"token_type": "Bearer"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: warning 命名例外
|
||||
登录/令牌接口返回 `access_token/refresh_token/expires_in/token_type`(OAuth2 惯例下划线命名)——这是全系统唯一例外,其余接口出入参一律驼峰。
|
||||
:::
|
||||
|
||||
## 携带令牌调用业务接口
|
||||
|
||||
```sh
|
||||
curl http://127.0.0.1:8000/api/user/page?pageNo=1&pageSize=10 \
|
||||
-H "Authorization: Bearer <access_token>"
|
||||
```
|
||||
|
||||
兼容细节:Bearer 前缀大小写不敏感、可无前缀直接传 token;个别场景支持查询参数 `?token=`(URL 直传)。
|
||||
|
||||
## 刷新令牌(POST /api/oauth2/token,公开)
|
||||
|
||||
access 过期(收到 code=401)时用 refresh_token 轮换新令牌对:
|
||||
|
||||
```sh
|
||||
curl -X POST http://127.0.0.1:8000/api/oauth2/token \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"grant_type":"refresh_token","refresh_token":"<refresh_token>"}'
|
||||
```
|
||||
|
||||
```json
|
||||
{ "data": { "access_token": "…", "refresh_token": "…", "expires_in": 7200, "token_type": "Bearer" } }
|
||||
```
|
||||
|
||||
刷新语义:
|
||||
|
||||
```text
|
||||
* 旧 refresh 入黑名单(轮换)——旧 refresh_token 立即作废,必须存新值
|
||||
* 旧 access 自然过期(不主动拉黑)
|
||||
* 返回同登录格式四字段
|
||||
* 兼容密码模式:grant_type=password 或无 refresh_token → 复用登录逻辑
|
||||
```
|
||||
|
||||
## 撤销与校验
|
||||
|
||||
```text
|
||||
GET /api/logout 登出(当前 access jti 拉黑)
|
||||
DELETE /api/oauth2/remove/:token 撤销指定令牌(登录可用;解析 jti 入黑名单)
|
||||
GET /api/oauth2/check/token 校验令牌有效性(JWT 中间件通过即有效)
|
||||
```
|
||||
|
||||
## 对接方实现要点
|
||||
|
||||
```text
|
||||
1. 存储:access 与 refresh 分开存(内存/localStorage);refresh 泄露=账号风险
|
||||
2. 请求:全部业务请求带 Authorization: Bearer <access>
|
||||
3. 401 处理:单飞刷新(并发 401 只触发一次 refresh,其余等结果);
|
||||
refresh 失败(1008/401)→ 清令牌 → 跳登录
|
||||
4. 刷新成功:立即用新令牌对覆盖旧值(旧 refresh 已作废)
|
||||
5. 时钟偏移:以 expires_in 倒计时提前刷新(如过期前 60s),减少 401
|
||||
6. 登出:调 /api/logout 后清本地——不调的话令牌有效期内仍可用
|
||||
```
|
||||
|
||||
## 令牌状态速查
|
||||
|
||||
| 状态 | 表现 | 处理 |
|
||||
|------|------|------|
|
||||
| 有效 | 业务正常 | — |
|
||||
| access 过期 | code=401 | refresh 轮换 |
|
||||
| refresh 过期/已轮换 | code=1 或 1008 | 重新登录 |
|
||||
| 已拉黑(登出/强退) | code=401 | 重新登录(强退的 refresh 也拉黑,无法复活) |
|
||||
| 算法/签名无效 | code=401 | 检查是否跨环境混用 jwt.secret |
|
||||
|
||||
## 安全建议
|
||||
|
||||
+ HTTPS 传输(令牌明文等价于会话)
|
||||
+ refresh_token 泄露面最小化(不入 URL、不入日志——后端已脱敏)
|
||||
+ 客户端不持久化 access(内存优先);persist refresh 的场景加密存储
|
||||
+ 多端登录场景用在线用户模块管理(forceLogout 双令牌拉黑)
|
||||
@@ -0,0 +1,95 @@
|
||||
# 8.4 文件上传接口
|
||||
|
||||
:::tip 概述
|
||||
上传接口统一 multipart:POST /api/upload/uploadFile(单文件)与 uploadFiles(多文件),字段名 file。返回可预览 URL 与入库形态 Path。两段式协议详见 [7.1 文件上传全流程](../tools/upload)。
|
||||
:::
|
||||
|
||||
## 接口定义
|
||||
|
||||
```text
|
||||
POST /api/upload/uploadFile 权限 sys:upload:file multipart 字段名 file
|
||||
POST /api/upload/uploadFiles 权限 sys:upload:files 多文件
|
||||
|
||||
入参 type=image 时按图片规则校验(扩展名 + 魔数嗅探),否则按普通文件
|
||||
```
|
||||
|
||||
multipart 标签约定:`*ghttp.UploadFile` + `p:"file"`。
|
||||
|
||||
## 请求示例
|
||||
|
||||
```sh
|
||||
curl -X POST http://127.0.0.1:8000/api/upload/uploadFile \
|
||||
-H "Authorization: Bearer <access_token>" \
|
||||
-F "file=@avatar.png" \
|
||||
-F "type=image"
|
||||
```
|
||||
|
||||
## 响应结构
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0, "ok": true, "msg": "操作成功",
|
||||
"data": {
|
||||
"originalName": "avatar.png",
|
||||
"fileExtension": "png",
|
||||
"fileType": "image/png",
|
||||
"fileSize": 102400,
|
||||
"fileName": "a1b2c3....png",
|
||||
"filePath": "temp/20261018/a1b2c3....png",
|
||||
"fileUrl": "http://file.example.com/temp/20261018/a1b2c3....png"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 用途 |
|
||||
|------|------|
|
||||
| originalName | 原始文件名(前端展示/回显用) |
|
||||
| fileExtension | 扩展名(小写,无点) |
|
||||
| fileType | MIME 类型(按文件头内容嗅探,识别不出为 application/octet-stream) |
|
||||
| fileSize | 文件大小(字节) |
|
||||
| fileName | 存储文件名(= filePath 的 basename,32位随机.ext) |
|
||||
| filePath | 相对 uploadDir 路径(**入库形态**;表单保存时转正) |
|
||||
| fileUrl | 可直接预览的完整地址(domainUrl 拼接) |
|
||||
|
||||
## 校验失败场景
|
||||
|
||||
| 场景 | 响应 |
|
||||
|------|------|
|
||||
| 超过 maxSize(默认 10MB) | code=1,msg 提示大小超限 |
|
||||
| 扩展名不在白名单 | code=1,msg 提示类型不允许 |
|
||||
| type=image 但魔数非 image/* | code=1,拒绝(防改后缀伪造) |
|
||||
| 未登录/令牌无效 | code=401 |
|
||||
| 无权限 | code=403 |
|
||||
| 触发限流 | code=429 |
|
||||
|
||||
## 两段式协议(对接要点)
|
||||
|
||||
```text
|
||||
上传阶段:只落 temp 目录(upload/temp/日期/随机名)
|
||||
前端持有 url;表单字段存 url 或 path
|
||||
|
||||
保存阶段:业务接口(userAdd/articleAdd/...)的 spec.saveTransform
|
||||
自动 SaveFromTemp 转正到业务目录(user/日期/时间戳_随机.ext)
|
||||
库存相对路径
|
||||
|
||||
读取阶段:Detail/List 的 readTransform 经 GetFileUrl 补域名
|
||||
```
|
||||
|
||||
前端不要在上传后直接把 url 当最终地址持久化到业务表——转正由业务保存触发。
|
||||
|
||||
## 批量上传(uploadFiles)
|
||||
|
||||
```text
|
||||
POST /api/upload/uploadFiles
|
||||
multipart 多个 file 字段 → 逐个 SaveTemp → 返回结果数组
|
||||
(每项同单文件结构;单项校验失败不影响其他项——对照导入的逐行语义)
|
||||
```
|
||||
|
||||
## 安全链速查
|
||||
|
||||
```text
|
||||
大小限制 → 扩展名白名单 → 图片魔数嗅探 → 随机文件名 → 两段式
|
||||
→ svg 附件化+nosniff → temp 定期清理(tempExpireDays + 03:30 gcron)
|
||||
```
|
||||
|
||||
公网开放上传时收紧 fileExt(当前含 js/css/sql)——配置见 [7.2 上传配置详解](../tools/upload-config)。
|
||||
Reference in New Issue
Block a user