656 lines
23 KiB
Markdown
656 lines
23 KiB
Markdown
# 萌芽账户认证中心 API 文档
|
||
|
||
访问 **`GET /`** 或 **`GET /api`**(无鉴权)可得到 JSON 格式的简要说明(服务名、版本、`links.health`、`routePrefixes` 等)。**运行时未挂载**返回 Markdown 的 **`GET /api/docs`**;完整 HTTP 清单以 **`sproutgate-backend/main.go`** 与本文件、**`sproutgate-backend/后端文档.md`** 为准。
|
||
|
||
接入地址:
|
||
- 统一登录前端:`https://auth.shumengya.top`
|
||
- 后端 API:`https://auth.api.shumengya.top`
|
||
- 本地开发 API:`http://<host>:8080`
|
||
|
||
对外接入建议:
|
||
1. 第三方「登录」按钮跳转到统一登录前端的 **授权入口**(推荐 `.../authorize?...`,见下节)。
|
||
2. 用户在统一登录站点完成登录与授权确认后,回跳到业务站点。
|
||
3. 业务站点使用回跳 URL **`#`** 哈希中的 `token`,由**自有后端**调用 `POST /api/auth/verify` 或 `GET /api/auth/me` 校验并建立会话。
|
||
|
||
**推荐入口 URL(生产):** 将下列主机换为你的统一登录部署域名;子路径部署时在前端 `base` 后追加 `authorize`。
|
||
|
||
```html
|
||
<a href="https://auth.shumengya.top/authorize?redirect_uri=https%3A%2F%2Fapp.example.com%2Fauth%2Fcallback&state=abc123&client_id=my-app&client_name=%E6%88%91%E7%9A%84%E5%BA%94%E7%94%A8">
|
||
使用萌芽统一账户认证登录
|
||
</a>
|
||
```
|
||
|
||
说明:
|
||
- 若用户打开的是站点根路径且仅携带 `redirect_uri` / `return_url` 等查询参数,前端会自动 **重定向** 到 **`/authorize`**(保留同一套 query),以使用独立授权确认页。
|
||
- `client_id` / `client_name` 会由浏览器写入 session,并在用户登录时提交给后端,用于累计 **应用接入记录**(详见下文「应用接入记录」)。
|
||
|
||
回跳说明:
|
||
- 在 **`/authorize`** 流程下,已登录用户会看到明确的授权页(应用信息、权限说明、允许/拒绝);拒绝时回跳地址的 hash 中会携带 `error=access_denied` 等。
|
||
- 登录并「允许」后,浏览器回跳到 `redirect_uri`(或 `return_url`),并在 URL **`#fragment`**(哈希)中带上令牌与用户信息(见下表)。
|
||
- 第三方拿到 `token` 后,建议服务端调用 **`POST /api/auth/verify`**(不更新「最后访问」等侧写,适合网关鉴权)或 **`GET /api/auth/me`**(会更新访问/签到相关侧写,适合拉取个人中心级资料)校验并解析用户身份。
|
||
|
||
### 统一登录前端:查询参数
|
||
|
||
| 参数 | 必填 | 说明 |
|
||
|------|------|------|
|
||
| `redirect_uri` | 与 `return_url` 至少其一 | 登录成功后的回跳地址,须进行 URL 编码;可为绝对 URL 或相对路径(相对路径相对统一登录站点解析)。 |
|
||
| `return_url` | 同上 | 与 `redirect_uri` 同义,二者都传时优先 `redirect_uri`。 |
|
||
| `state` | 否 | OAuth 风格透传字符串;回跳时原样写入哈希参数,供业务防 CSRF 或关联会话。 |
|
||
| `prompt` | 否 | 预留;前端可读,当前可用于将来扩展交互策略。 |
|
||
| `client_id` | 否 | 第三方应用稳定标识;格式须符合后端校验(见下文「应用接入记录」)。写入 session 后随登录请求提交,用于 **应用接入记录**。 |
|
||
| `client_name` | 否 | 展示用名称(最长 128),与 `client_id` 配对;可选。 |
|
||
|
||
### 回跳 URL:`#` 哈希参数
|
||
|
||
成功授权后,前端将使用 [`URLSearchParams`](https://developer.mozilla.org/zh-CN/docs/Web/API/URLSearchParams) 写入哈希,例如:`https://app.example.com/auth/callback#token=...&expiresAt=...&account=...&username=...&state=...`。
|
||
|
||
| 参数 | 说明 |
|
||
|------|------|
|
||
| `token` | JWT,调用受保护接口时放在请求头 `Authorization: Bearer <token>`。 |
|
||
| `expiresAt` | 过期时间,RFC3339(与签发侧一致,当前默认为登录时起算 **7 天**)。 |
|
||
| `account` | 账户名(与 JWT `sub` 一致)。 |
|
||
| `username` | 展示用昵称,可能为空。 |
|
||
| `state` | 若登录请求携带了 `state`,则原样返回。 |
|
||
|
||
业务站点回调页应用脚本读取 `location.hash`,解析后**仅在 HTTPS 环境**将 `token` 存于内存或安全存储,并尽快用后端 **`POST /api/auth/verify`** 校验(勿仅信任哈希中的明文字段)。
|
||
|
||
### 第三方后端接入建议
|
||
|
||
1. **仅信服务端**:回调页将 `token` 交给自有后端,由后端请求 `POST https://<api-host>/api/auth/verify`(JSON body:`{"token":"..."}`),根据 `valid` 与 `user.account` 建立会话。
|
||
2. **CORS**:浏览器直连 API 时须后端已配置 CORS(本服务默认允许任意 `Origin`);若从服务端发起请求则不受 CORS 限制。
|
||
3. **令牌无效或封禁**:`verify` / `me` 返回 **401**、`verify` 因封禁返回 **403**(`valid: false`)时,应作废本地会话并引导用户重新走统一登录。
|
||
|
||
## 应用接入记录(authClients)
|
||
|
||
用户在统一登录前端的 **个人中心 → 应用接入** 中可看到已与账号发生过认证关联的第三方应用列表。每条记录对应一个稳定的 **`client_id`**,并展示展示名(若有)、**首次接入时间**、**最近接入时间**(均为 RFC3339)。持久化在 **MySQL `users` 表**中对应用户的 **`auth_clients` JSON 列**;对外 API 字段名仍为 **`authClients`**(与历史 JSON 文件存储时的形状一致)。
|
||
|
||
示例展示含义(与界面文案一致):**infogenie**(万象口袋)首次 `2026-03-22T16:33:24+08:00` · 最近 `2026-03-30T19:22:45+08:00` —— 其中括号内为 `client_name` / `X-Auth-Client-Name` / 登录体 `clientName` 带来的展示名,`infogenie` 为 `client_id`。
|
||
|
||
### `client_id` 与名称规则
|
||
|
||
- **`client_id`(必填才能记一条)**:长度 1–64;必须以 **英文字母或数字** 开头;其余字符仅可为 `[A-Za-z0-9_.:-]`(与后端正则一致)。
|
||
- **`client_name` / 展示名(可选)**:任意去首尾空白后的字符串,最长 **128** 字符,超出由服务端截断;对应存储字段为 `displayName`。
|
||
|
||
### 在哪些接口上累计记录
|
||
|
||
记录仅在 **令牌有效、用户未封禁、且 `client_id` 通过校验** 时写入或刷新 **`lastSeenAt`**;已存在同一 `client_id` 时更新 **`lastSeenAt`**,必要时更新 **`displayName`**,**不改动** **`firstSeenAt`**。
|
||
|
||
| 方式 | 接口 / 场景 | 如何传递 `client_id` 与展示名 |
|
||
|------|----------------|--------------------------------|
|
||
| **A. 统一登录 URL** | 用户经前端完成登录 | 授权页 URL 查询参数 **`client_id`**、**`client_name`**(见上表)。前端写入 session,登录请求 `POST /api/auth/login` 的 JSON 中会带 **`clientId`** / **`clientName`**。 |
|
||
| **B. 资源拥有者密码登录** | `POST /api/auth/login` | JSON **`clientId`**、**`clientName`**(可选,规则同上)。 |
|
||
| **C. 服务端持 JWT 调用** | `POST /api/auth/verify` | 请求头 **`X-Auth-Client`**(必须为合法 id)、**`X-Auth-Client-Name`**(可选)。**Body 仅含 `token`,不能替代请求头。** |
|
||
| **D. 服务端持 JWT 调用** | `GET /api/auth/me` | 同上:**`X-Auth-Client`**、**`X-Auth-Client-Name`**,与 **`Authorization: Bearer`** 同时使用。 |
|
||
|
||
说明:
|
||
|
||
- **A + B**:在登录成功、签发 JWT **之后**写库;`POST /api/auth/login` 的响应 `user` 为 `OwnerPublic()`,**含** `authClients`(若本次写入了新记录)。
|
||
- **C**:`POST /api/auth/verify` 在校验通过且未封禁后写库;响应 `user` 为 **`Public()`,不含 `authClients`**,避免向第三方泄露用户在其他应用上的关联列表。
|
||
- **D**:`GET /api/auth/me` 在校验通过且未封禁后写库;响应 `user` 为 **`OwnerPublic()`,含 `authClients`**,与前端个人中心一致。
|
||
|
||
**封禁用户**:`POST /api/auth/verify` 与 `GET /api/auth/me` 在命中封禁或令牌无效时 **不会** 更新应用接入记录(在写库逻辑之前即返回错误)。
|
||
|
||
**浏览器直连 API**:若前端或网关需带上 `X-Auth-Client`,请确认 CORS 允许该自定义头(本服务 CORS 已包含 `X-Auth-Client`、`X-Auth-Client-Name`)。
|
||
|
||
### 存储结构(`authClients` 数组元素)
|
||
|
||
```json
|
||
{
|
||
"clientId": "infogenie",
|
||
"displayName": "万象口袋",
|
||
"firstSeenAt": "2026-03-22T16:33:24+08:00",
|
||
"lastSeenAt": "2026-03-30T19:22:45+08:00"
|
||
}
|
||
```
|
||
|
||
## 认证与统一登录
|
||
|
||
### 登录获取统一令牌
|
||
`POST /api/auth/login`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"account": "demo",
|
||
"password": "demo123",
|
||
"clientId": "my-app",
|
||
"clientName": "我的应用"
|
||
}
|
||
```
|
||
|
||
`clientId` / `clientName` 可选;规则与应用接入请求头一致。传入且格式合法时,会在登录成功后写入 **应用接入记录**(见上文 **「应用接入记录(authClients)」**)。
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"token": "jwt-token",
|
||
"expiresAt": "2026-03-14T12:00:00Z",
|
||
"user": {
|
||
"account": "demo",
|
||
"username": "示例用户",
|
||
"email": "demo@example.com",
|
||
"level": 0,
|
||
"sproutCoins": 10,
|
||
"secondaryEmails": ["demo2@example.com"],
|
||
"phone": "13800000000",
|
||
"avatarUrl": "https://example.com/avatar.png",
|
||
"websiteUrl": "https://example.com",
|
||
"bio": "### 简介",
|
||
"createdAt": "2026-03-14T12:00:00Z",
|
||
"updatedAt": "2026-03-14T12:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
若账户已被管理员封禁,返回 **403**,且**不会签发 JWT**,响应示例:
|
||
|
||
```json
|
||
{
|
||
"error": "account is banned",
|
||
"banReason": "违规内容"
|
||
}
|
||
```
|
||
|
||
`banReason` 可能为空字符串或省略。
|
||
|
||
**常见 HTTP 状态码(登录)**
|
||
|
||
| 状态码 | 含义 |
|
||
|--------|------|
|
||
| 200 | 成功,返回 `token`、`expiresAt`、`user`。 |
|
||
| 400 | 请求体非法或缺少 `account` / `password`。 |
|
||
| 401 | 账户不存在或密码错误(统一文案 `invalid credentials`)。 |
|
||
| 403 | 账户已封禁(见上文 JSON)。 |
|
||
| 500 | 服务器内部错误(读库、签发 JWT 失败等)。 |
|
||
|
||
**JWT 概要**:算法 **HS256**;载荷含 `account`(与 `sub` 一致)、`iss`(见 `data/config/auth.json`)、`iat` / `exp`。客户端只需透传字符串,**勿在前端解析密钥**。
|
||
|
||
### 校验令牌
|
||
`POST /api/auth/verify`
|
||
|
||
请求头:**`Content-Type: application/json`**。可选 **`X-Auth-Client`**、**`X-Auth-Client-Name`**(累计应用接入记录,见专章)。
|
||
|
||
请求体:
|
||
```json
|
||
{
|
||
"token": "jwt-token"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"valid": true,
|
||
"user": { "account": "demo", "...": "..." }
|
||
}
|
||
```
|
||
|
||
若账户已封禁,返回 **403**,正文示例(不返回 `user`):
|
||
|
||
```json
|
||
{
|
||
"valid": false,
|
||
"error": "account is banned",
|
||
"banReason": "违规内容"
|
||
}
|
||
```
|
||
|
||
令牌过期、签名错误、issuer 不匹配、`token` 被撤销(`token_epoch`)等失败时返回 **401**,示例:`{"valid": false, "error": "invalid token"}` 或 `{"valid": false, "error": "token revoked"}`(以服务端实际字段为准)。
|
||
|
||
`verify` 与 `me` 的取舍:**仅校验身份、默认不更新「最后访问」等侧写**时用 `verify`;需要最新资料、签到状态或写入「最后访问」时用 `GET /api/auth/me`(需 Bearer)。
|
||
|
||
**应用接入记录**:在 **`verify` / `me`** 上使用 **`X-Auth-Client`**、**`X-Auth-Client-Name`** 的约定,以及响应 `user` 是否包含 `authClients`,见上文 **「应用接入记录(authClients)」**。
|
||
|
||
### 获取当前用户信息
|
||
`GET /api/auth/me`
|
||
|
||
请求头:
|
||
- **`Authorization: Bearer <jwt-token>`**(必填)
|
||
|
||
可选 — **应用接入记录**(与 `verify` 相同语义,见 **「应用接入记录(authClients)」**):
|
||
- **`X-Auth-Client`**、**`X-Auth-Client-Name`**
|
||
|
||
可选(由前端调用 `https://cf-ip-geo.smyhub.com/api` 等接口解析后传入,用于记录「最后访问 IP」与「最后显示位置」):
|
||
- `X-Visit-Ip`:客户端公网 IP(与地理接口返回的 `ip` 一致即可)
|
||
- `X-Visit-Location`:展示用位置文案(例如将 `geo.countryName`、`regionName`、`cityName` 拼接为 `中国 四川 成都`)
|
||
|
||
**服务端回退(避免浏览器跨域导致头缺失)**:若未传 `X-Visit-Location`,后端会用 `X-Visit-Ip`;若也未传 `X-Visit-Ip`,则用连接的 `ClientIP()`(请在前置反向代理上正确传递 `X-Forwarded-For` 等,并在生产环境为 Gin 配置可信代理)。随后服务端请求环境变量 **`GEO_LOOKUP_URL`** 指定的基址(默认与 `internal/clientgeo.DefaultLookupURL` 一致:`https://cf-ip-geo.smyhub.com/api`,实际请求会附加 **`?ip=`**)解析展示位置并写入用户记录。
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"user": { "account": "demo", "...": "..." },
|
||
"checkIn": {
|
||
"rewardCoins": 1,
|
||
"checkedInToday": false,
|
||
"lastCheckInDate": "",
|
||
"lastCheckInAt": "",
|
||
"today": "2026-03-14"
|
||
}
|
||
}
|
||
```
|
||
|
||
> `user` 还会包含 `lastVisitAt`、`lastVisitDate`、`checkInDays`、`checkInStreak`、`visitDays`、`visitStreak` 等统计字段。
|
||
|
||
> 在登录用户本人、管理员列表等场景下,`user` 还可包含 `lastVisitIp`、`lastVisitDisplayLocation`(最近一次通过 `/api/auth/me` 上报的访问 IP 与位置文案)。**`POST /api/auth/verify` 返回的 `user`(`Public()`)中不包含这两项**(避免第三方校验响应携带访问隐私)。**`GET /api/public/users/:account` 的 `user`(`PublicProfile()`)会包含这两项**,与公开主页展示策略一致。
|
||
|
||
> 说明:密码不会返回。
|
||
|
||
若账户在登录后被封禁,持旧 JWT 调用 `GET /api/auth/me`、`PUT /api/auth/profile`、`POST /api/auth/check-in`、辅助邮箱等需登录接口时,返回 **403**,正文同登录封禁响应(`error` + 可选 `banReason`)。客户端应作废本地令牌。
|
||
|
||
### 每日签到
|
||
`POST /api/auth/check-in`
|
||
|
||
请求头:
|
||
`Authorization: Bearer <jwt-token>`
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"checkedIn": true,
|
||
"alreadyCheckedIn": false,
|
||
"rewardCoins": 1,
|
||
"awardedCoins": 1,
|
||
"message": "签到成功",
|
||
"user": { "account": "demo", "...": "..." }
|
||
}
|
||
```
|
||
|
||
### 更新当前用户资料
|
||
`PUT /api/auth/profile`
|
||
|
||
请求头:
|
||
`Authorization: Bearer <jwt-token>`
|
||
|
||
请求(字段可选):
|
||
```json
|
||
{
|
||
"password": "newpass",
|
||
"username": "新昵称",
|
||
"phone": "13800000000",
|
||
"avatarUrl": "https://example.com/avatar.png",
|
||
"websiteUrl": "https://example.com",
|
||
"bio": "### 新简介"
|
||
}
|
||
```
|
||
|
||
说明:`websiteUrl` 须为 `http`/`https` 地址;可传空字符串清除;未写协议时服务端会补全为 `https://`。
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"user": { "account": "demo", "...": "..." }
|
||
}
|
||
```
|
||
|
||
## 用户广场
|
||
|
||
### 获取公开用户目录
|
||
`GET /api/public/users`
|
||
|
||
无需鉴权。返回未封禁用户的简要列表,默认按 **`createdAt`**(注册时间)升序。
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"total": 2,
|
||
"users": [
|
||
{
|
||
"account": "demo",
|
||
"username": "示例用户",
|
||
"level": 0,
|
||
"sproutCoins": 10,
|
||
"avatarUrl": "https://example.com/avatar.png",
|
||
"websiteUrl": "https://example.com",
|
||
"bio": "### 简介",
|
||
"createdAt": "2026-03-14T12:00:00+08:00"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### 获取用户公开主页
|
||
`GET /api/public/users/{account}`
|
||
|
||
说明:
|
||
- 仅支持路径参数 **账户名** `account`(可与存储大小写不同,服务端按不区分大小写匹配),不支持昵称查询。
|
||
- 适合第三方应用展示用户公开资料。
|
||
- 若该账户已被封禁,返回 **404** `{"error":"user not found"}`(与不存在账户相同,避免公开资料泄露)。
|
||
- 响应体在 `user` 之外包含累计赞数 **`profileLikeCount`**。
|
||
- **`Authorization: Bearer`** 可选:若传入**有效未封禁**用户令牌且非主页主人,响应可额外包含 `viewerHasLikedToday`、`viewerLikesRemainingToday`、`profileLikeDailyMax`(当日是否已赞该主页、当日还可给多少人点赞、每自然日上限当前为 **5**,以后端常量为准)。若浏览者即主页主人,可返回 `viewerIsOwner: true`。
|
||
- `user` 中含该用户**最近一次被服务端记录的**访问 IP(`lastVisitIp`)与展示地理位置(`lastVisitDisplayLocation`);`POST /api/auth/verify` 返回的用户 JSON **不含**上述两项。
|
||
|
||
响应示例(未带 Bearer):
|
||
```json
|
||
{
|
||
"user": {
|
||
"account": "demo",
|
||
"username": "示例用户",
|
||
"level": 3,
|
||
"sproutCoins": 10,
|
||
"avatarUrl": "https://example.com/avatar.png",
|
||
"websiteUrl": "https://example.com",
|
||
"lastVisitIp": "203.0.113.1",
|
||
"lastVisitDisplayLocation": "中国 广东省 深圳市",
|
||
"bio": "### 简介"
|
||
},
|
||
"profileLikeCount": 12
|
||
}
|
||
```
|
||
|
||
### 公开主页点赞
|
||
`POST /api/public/users/{account}/like`
|
||
|
||
请求头:**`Authorization: Bearer <jwt-token>`**(必填)。
|
||
|
||
为路径中 **`account`** 对应用户的公开主页点赞。规则摘要(与 `internal/storage` 一致):
|
||
|
||
- 不能给自己点赞;每个点赞者对同一主页 **每个自然日最多一次**。
|
||
- 每个点赞者每个自然日最多给 **5** 位**不同**用户点赞(超限返回 **400**,正文中可含 `viewerLikesRemainingToday`、`profileLikeDailyMax`)。
|
||
- 目标用户不存在或已封禁:**404**;点赞者封禁或令牌无效:**401** / **403** 等,以服务端 `error` 为准。
|
||
|
||
成功响应示例:
|
||
```json
|
||
{
|
||
"profileLikeCount": 13,
|
||
"viewerHasLikedToday": true,
|
||
"viewerLikesRemainingToday": 4,
|
||
"profileLikeDailyMax": 5
|
||
}
|
||
```
|
||
|
||
### 公开注册策略
|
||
`GET /api/public/registration-policy`
|
||
|
||
无需鉴权。用于前端判断是否展示「邀请码」输入框。
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"requireInviteCode": false
|
||
}
|
||
```
|
||
|
||
当 `requireInviteCode` 为 **true** 时,`POST /api/auth/register` 必须携带有效 `inviteCode`(见下节)。
|
||
|
||
### 注册账号(发送邮箱验证码)
|
||
`POST /api/auth/register`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"account": "demo",
|
||
"password": "demo123",
|
||
"username": "示例用户",
|
||
"email": "demo@example.com",
|
||
"inviteCode": "ABCD1234"
|
||
}
|
||
```
|
||
|
||
- `inviteCode`:可选。若服务端开启「强制邀请码」,则必填且须为管理员发放的未过期、未用尽邀请码。邀请码**不区分大小写**;成功完成 `verify-email` 创建用户后才会扣减使用次数。
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"sent": true,
|
||
"expiresAt": "2026-03-14T12:10:00Z"
|
||
}
|
||
```
|
||
|
||
### 验证邮箱并完成注册
|
||
`POST /api/auth/verify-email`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"account": "demo",
|
||
"code": "123456"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"created": true,
|
||
"user": { "account": "demo", "...": "..." }
|
||
}
|
||
```
|
||
|
||
### 忘记密码(发送重置验证码)
|
||
`POST /api/auth/forgot-password`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"account": "demo",
|
||
"email": "demo@example.com"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"sent": true,
|
||
"expiresAt": "2026-03-14T12:10:00Z"
|
||
}
|
||
```
|
||
|
||
### 重置密码
|
||
`POST /api/auth/reset-password`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"account": "demo",
|
||
"code": "123456",
|
||
"newPassword": "newpass"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{ "reset": true }
|
||
```
|
||
|
||
### 申请添加辅助邮箱(发送验证码)
|
||
`POST /api/auth/secondary-email/request`
|
||
|
||
请求头:
|
||
`Authorization: Bearer <jwt-token>`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"email": "demo2@example.com"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"sent": true,
|
||
"expiresAt": "2026-03-14T12:10:00Z"
|
||
}
|
||
```
|
||
|
||
### 验证辅助邮箱
|
||
`POST /api/auth/secondary-email/verify`
|
||
|
||
请求头:
|
||
`Authorization: Bearer <jwt-token>`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"email": "demo2@example.com",
|
||
"code": "123456"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"verified": true,
|
||
"user": { "account": "demo", "...": "..." }
|
||
}
|
||
```
|
||
|
||
## 管理端接口(需要管理员 Token)
|
||
|
||
管理员 Token 存放在 `data/config/admin.json` 中;如果文件不存在,后端启动时会自动生成并写入该文件。
|
||
请求时可使用以下任一方式携带:
|
||
- Query:`?token=<admin-token>`
|
||
- Header:`X-Admin-Token: <admin-token>`
|
||
|
||
### 签到奖励设置
|
||
`GET /api/admin/check-in/config`
|
||
|
||
`PUT /api/admin/check-in/config`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"rewardCoins": 1
|
||
}
|
||
```
|
||
- Header:`Authorization: Bearer <admin-token>`
|
||
|
||
### 注册策略与邀请码
|
||
|
||
`GET /api/admin/registration`
|
||
|
||
响应含 `requireInviteCode` 与 `invites` 数组(每项含 `code`、`note`、`maxUses`、`uses`、`expiresAt`、`createdAt`)。`maxUses` 为 0 表示不限次数。
|
||
|
||
`PUT /api/admin/registration`
|
||
|
||
请求:
|
||
```json
|
||
{ "requireInviteCode": true }
|
||
```
|
||
|
||
`POST /api/admin/registration/invites`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"note": "内测批次",
|
||
"maxUses": 10,
|
||
"expiresAt": "2026-12-31T15:59:59Z"
|
||
}
|
||
```
|
||
|
||
`expiresAt` 可省略;须为 RFC3339。响应 `201`,`invite` 内含服务端生成的 8 位邀请码。
|
||
|
||
`DELETE /api/admin/registration/invites/{code}`
|
||
|
||
删除指定邀请码(`code` 与存储大小写可能不同,按不区分大小写匹配)。
|
||
|
||
### 获取用户列表
|
||
`GET /api/admin/users`
|
||
|
||
响应:
|
||
```json
|
||
{
|
||
"total": 1,
|
||
"users": [{ "account": "demo", "...": "..." }]
|
||
}
|
||
```
|
||
|
||
### 新建用户
|
||
`POST /api/admin/users`
|
||
|
||
请求:
|
||
```json
|
||
{
|
||
"account": "demo",
|
||
"password": "demo123",
|
||
"username": "示例用户",
|
||
"email": "demo@example.com",
|
||
"level": 0,
|
||
"sproutCoins": 10,
|
||
"secondaryEmails": ["demo2@example.com"],
|
||
"phone": "13800000000",
|
||
"avatarUrl": "https://example.com/avatar.png",
|
||
"websiteUrl": "https://example.com",
|
||
"bio": "### 简介"
|
||
}
|
||
```
|
||
|
||
### 更新用户
|
||
`PUT /api/admin/users/{account}`
|
||
|
||
请求(字段可选):
|
||
```json
|
||
{
|
||
"password": "newpass",
|
||
"username": "新昵称",
|
||
"level": 1,
|
||
"secondaryEmails": ["demo2@example.com"],
|
||
"sproutCoins": 99,
|
||
"websiteUrl": "https://example.com",
|
||
"banned": true,
|
||
"banReason": "违规说明(最多 500 字)"
|
||
}
|
||
```
|
||
|
||
- `banned`:是否封禁;解封时请传 `false`,并可将 `banReason` 置为空字符串。
|
||
- `banReason`:仅当用户处于封禁状态时允许设为非空;封禁时若首次写入会记录 `bannedAt`(RFC3339,存于数据库用户记录)。
|
||
|
||
管理员列表 `GET /api/admin/users` 中每条 `user` 为 `OwnerPublic()`,可含 `banned`、`banReason`、`bannedAt`(及 `authClients` 等)——以后端实际 JSON 为准。
|
||
|
||
### 删除用户
|
||
`DELETE /api/admin/users/{account}`
|
||
|
||
响应:
|
||
```json
|
||
{ "deleted": true }
|
||
```
|
||
|
||
## 数据存储说明
|
||
|
||
当前发行版**以 MySQL 为唯一运行时数据源**(GORM `AutoMigrate`),不再使用 `data/users/*.json` 等文件作为线上读写路径。表与用途概要:
|
||
|
||
| 表名 | 用途 |
|
||
|------|------|
|
||
| `users` | 用户主数据;`auth_clients` 等为 JSON 列 |
|
||
| `pending_users` | 注册邮箱待验证 |
|
||
| `password_resets` | 找回密码验证码 |
|
||
| `secondary_email_verifications` | 辅助邮箱验证 |
|
||
| `app_configs` | `admin` / `auth` / `email` / `checkin` / `registration` 等 JSON 配置 |
|
||
| `invite_codes` | 邀请码 |
|
||
| `profile_likes` | 公开主页点赞记录 |
|
||
| `profile_like_daily_quota` | 点赞每日额度辅助 |
|
||
|
||
从旧版 **`sproutgate-backend/data/`** 目录迁移时,使用 **`go run ./cmd/migrate --data-dir ./data`**(详见 **`sproutgate-backend/后端文档.md`**)。
|
||
|
||
## 快速联调用示例
|
||
|
||
```bash
|
||
# 服务根路径 JSON 说明
|
||
curl -s http://localhost:8080/ | jq .
|
||
|
||
# 登录
|
||
curl -X POST http://localhost:8080/api/auth/login \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"account":"demo","password":"demo123"}'
|
||
|
||
# 校验令牌(推荐第三方网关先调此接口)
|
||
curl -X POST http://localhost:8080/api/auth/verify \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'X-Auth-Client: my-app' \
|
||
-d '{"token":"<jwt-token>"}'
|
||
|
||
# 同上,并携带应用展示名(累计应用接入记录)
|
||
curl -X POST http://localhost:8080/api/auth/verify \
|
||
-H 'Content-Type: application/json' \
|
||
-H 'X-Auth-Client: infogenie' \
|
||
-H 'X-Auth-Client-Name: 万象口袋' \
|
||
-d '{"token":"<jwt-token>"}'
|
||
|
||
# 使用令牌获取用户信息(会更新访问记录);可一并携带 X-Auth-Client 刷新「应用接入」最近时间
|
||
curl http://localhost:8080/api/auth/me \
|
||
-H 'Authorization: Bearer <jwt-token>' \
|
||
-H 'X-Auth-Client: infogenie' \
|
||
-H 'X-Auth-Client-Name: 万象口袋'
|
||
```
|