Users API
管理当前用户资料与密码,以及管理员的用户 CRUD、邮件与密码生命周期
Users API 分成两组:当前用户自助(/api/v1/users/me/*,任何已认证用户)和管理员用户管理(/api/v1/admin/users/*,需要 admin:user:* 权限码)。
概述
- 当前用户:
GET|PUT /api/v1/users/me、POST /api/v1/users/me/change-password、GET /api/v1/users/me/password-status、DELETE /api/v1/users/me - 管理员:
/api/v1/admin/users下的列表、统计、详情、创建、更新、删除、激活/停用、批量发信,以及密码生命周期端点
认证
所有端点使用 Authorization: Bearer <JWT>。API Key 不能用于用户管理端点。
管理员端点额外需要以下权限码:
| 权限码 | 用途 |
|---|---|
admin:user:read | 列表、详情、用户统计、密码过期统计与过期用户列表 |
admin:user:create | 创建用户 |
admin:user:update | 更新用户、激活/停用、批量发信、全部密码生命周期操作 |
admin:user:delete | 删除用户 |
超级管理员绕过权限码检查。权限不足返回 HTTP 403 / 3000。
当前用户端点
获取当前用户
GET /api/v1/users/mecurl -X GET "https://your-domain.com/api/v1/users/me" \
-H "Authorization: Bearer YOUR_TOKEN"成功响应(200 OK):
{
"code": 0,
"data": {
"id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
"username": "johndoe",
"email": "john.doe@example.com",
"is_active": true,
"approval_status": "approved",
"is_superuser": false,
"email_verified": true,
"avatar_url": "https://example.com/avatars/johndoe.png",
"locale": "en",
"created_at": "2026-01-15T10:00:00Z",
"last_login": "2026-09-24T14:30:00Z",
"auth_source": "local",
"external_id": null,
"force_password_change": false,
"password_expiration_exempt": false,
"status": "active",
"roles": [
{
"id": "9d8e4f7a-8b6c-4d5e-a1f2-3b4c5d6e7f80",
"name": "user",
"description": null,
"is_system_role": true,
"permissions": []
}
],
"sso_connections": [
{
"id": "4d5e6f70-1a2b-4c3d-9e8f-7a6b5c4d3e2f",
"provider_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"provider_name": "github",
"provider_display_name": "GitHub",
"provider_icon_url": null,
"provider_user_id": "12345678",
"provider_username": "johndoe",
"provider_email": "john.doe@example.com",
"first_login": "2026-02-01T08:00:00Z",
"last_login": "2026-09-24T14:30:00Z"
}
]
},
"msg": "success"
}响应中的 status 是响应模型的默认值,这些端点始终返回 "active",不反映真实账号状态。判断账号是否可用请用 is_active(是否启用)与 approval_status(approved / pending)的组合;前端列表也是在这两个字段上派生 active/inactive/pending 三类。
更新当前用户
PUT /api/v1/users/me| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
username | string | 否 | 新用户名,必须全局唯一 |
email | string | 否 | 新邮箱,必须全局唯一;站点开启邮箱验证时需要同时提交验证码 |
email_verification_code | string | 否 | 修改邮箱时的验证码;仅用于校验,不会写入用户资料 |
avatar_url | string | 否 | 头像 URL |
locale | string | 否 | 界面语言,如 en、zh |
curl -X PUT "https://your-domain.com/api/v1/users/me" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username": "johnsmith", "locale": "zh"}'失败路径:
| HTTP | code | 场景 |
|---|---|---|
400 | 5002 | 用户名已被占用 |
400 | 5003 | 邮箱已被占用 |
400 | 5005 | 修改邮箱的验证码不正确(开启邮箱验证时) |
成功响应返回更新后的完整用户对象(结构同「获取当前用户」),msg 为 Profile updated successfully。
修改邮箱不会自动刷新 email_verified:如果站点启用了邮箱验证,必须先完成验证码校验再提交新邮箱,否则下次登录可能被 5004(EMAIL_NOT_VERIFIED)拦下。
修改密码
POST /api/v1/users/me/change-password| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
current_password | string | 是 | 当前密码 |
new_password | string | 是 | 新密码 |
curl -X POST "https://your-domain.com/api/v1/users/me/change-password" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"current_password": "OldPassword123!", "new_password": "NewPassword456!"}'修改成功会同时清零 force_password_change 标记、写入新的密码修改时间并发送安全通知。
失败路径:
| HTTP | code | 场景 |
|---|---|---|
400 | 2003 | 当前密码不正确 |
400 | 5306 | 未达到最小密码年龄(password_min_age_days),暂不能修改 |
400 | 1001 | 新密码不符合密码策略,data.errors.new_password 列出具体原因 |
密码策略由站点设置决定(默认值):长度至少 min_password_length = 8,必须含大写字母(require_uppercase = true)、必须含数字(require_number = true),特殊字符默认不要求(require_special_char = false)。修改密码时还会比对最近 password_history_count = 5 个历史密码,命中则报错 password_recently_used。
成功响应(200 OK):
{ "code": 0, "data": null, "msg": "Password changed successfully" }查询密码状态
GET /api/v1/users/me/password-status用于在「账号安全」页展示密码是否即将过期,避免登录时才被强制跳转。
{
"code": 0,
"data": {
"is_exempt": false,
"password_changed_at": "2026-06-01T08:00:00+00:00",
"password_expires_at": "2026-08-30T08:00:00+00:00",
"is_expired": false,
"days_until_expiration": 12,
"force_change_required": false
},
"msg": "success"
}| 字段 | 类型 | 说明 |
|---|---|---|
is_exempt | boolean | 是否豁免密码过期策略 |
password_changed_at | string | null | 上次修改时间(ISO 8601) |
password_expires_at | string | null | 计算出的过期时间;未启用密码过期策略时为 null |
is_expired | boolean | 是否已过期 |
days_until_expiration | integer | null | 距离过期天数;无过期时间时为 null |
force_change_required | boolean | 是否被管理员标记为必须修改密码 |
注销(删除)本人账号
DELETE /api/v1/users/me请求体需要密码二次确认:
{ "password": "CurrentPassword123!" }curl -X DELETE "https://your-domain.com/api/v1/users/me" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"password": "CurrentPassword123!"}'| HTTP | code | 场景 |
|---|---|---|
400 | 2003 | 密码不正确 |
400 | 3000 | 站点关闭了自助注销(allow_account_deletion = false),或账号是超级管理员 |
注销不可逆。成功响应为 {"code": 0, "data": null, "msg": "Account deleted successfully"},随后该账号的令牌与 API Key 都会失效。管理员如需保留账号,请改用「停用」而不是删除。
管理员端点
列出用户
GET /api/v1/admin/users| 查询参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
page | integer | 1 | 页码 |
page_size | integer | 20 | 每页条数 |
status | string[] | — | active(is_active = true)、inactive(停用且已批准)、pending(停用且待审批)(可重复) |
search | string | — | 用户名或邮箱模糊匹配 |
role | string[] | — | 按角色名过滤(可重复,自动去重) |
exclude_user_id | UUID[] | — | 排除指定用户(可重复) |
curl -X GET "https://your-domain.com/api/v1/admin/users?page=1&page_size=20&status=pending" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"分页响应结构为 { items, total, page, page_size },items 中每项与「获取当前用户」一致。
用户统计
GET /api/v1/admin/users/stats需要 admin:user:read:
{
"code": 0,
"data": { "total": 156, "active": 150, "inactive": 2, "pending": 4 },
"msg": "success"
}total 是全部用户数,active 只统计 is_active = true;inactive 与 pending 都要求 is_active = false,按 approval_status 区分。
获取用户详情
GET /api/v1/admin/users/{user_id}返回单个用户对象;不存在返回 HTTP 404 / 4001。
创建用户
POST /api/v1/admin/users| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
username | string | 是 | — | 用户名,必须唯一 |
email | string | 是 | — | 邮箱,必须唯一 |
password | string | 是 | — | 初始密码,按密码策略校验 |
is_active | boolean | 否 | true | 是否直接启用 |
is_superuser | boolean | 否 | false | 是否超级管理员 |
avatar_url | string | 否 | null | 头像 URL |
locale | string | 否 | 站点默认语言 | 界面语言 |
curl -X POST "https://your-domain.com/api/v1/admin/users" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"username": "alice", "email": "alice@example.com", "password": "SecurePassword123"}'管理员创建的用户总是 approval_status = "approved":请求里携带的 approval_status 会被忽略。若希望新账号先待审批,请改用开放注册流程。terms_accepted、captcha_id、captcha_token 属于注册接口字段,不要在本接口提交。
失败路径:用户名重复 HTTP 400 / 5002;邮箱重复 HTTP 400 / 5003;密码不满足策略 HTTP 400 / 1001。
创建成功会返回完整用户对象(roles 为空数组),并记录审计日志。
更新用户
PUT /api/v1/admin/users/{user_id}所有字段可选:
| 字段 | 类型 | 说明 |
|---|---|---|
email | string | 新邮箱 |
password | string | 重置密码(按密码策略校验,但不检查历史密码) |
is_active | boolean | 启用或停用 |
avatar_url | string | 头像 URL |
locale | string | 界面语言 |
roles | string[] | 角色名列表,整体替换现有角色 |
管理员不能通过本接口修改 username、is_superuser 或 approval_status——这些字段不在请求模型中,传了会被忽略。用户名只能在用户自助的 PUT /users/me 中修改。
角色替换有一条安全约束:如果被修改的用户当前是某个团队的 owner 或 admin,而新角色集合中没有任何角色带 team:manage 权限,请求会被拒绝,返回 HTTP 403 / 3000(permission 字段为 team:manage)。这是为了避免团队管理者失去管理权限后团队无人可管。
删除用户
DELETE /api/v1/admin/users/{user_id}- 用户不存在:HTTP
404/4001 - 目标是超级管理员:HTTP
400/5201(CANNOT_DELETE_SUPERUSER)
响应中 data 是被删除的用户对象(用于审计回显),不是 null。
激活 / 停用用户
POST /api/v1/admin/users/{user_id}/activate
POST /api/v1/admin/users/{user_id}/deactivate两者都返回更新后的用户对象。状态不变时报错:
| HTTP | code | 场景 |
|---|---|---|
400 | 5212 | 对已激活用户调用 activate |
400 | 5213 | 对已停用用户调用 deactivate |
400 | 5214 | 试图停用超级管理员 |
404 | 4001 | 用户不存在 |
批量发送邮件
POST /api/v1/admin/users/send-email需要 admin:user:update 与已启用的 SMTP。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
subject | string | 是 | 邮件主题 |
content | string | 是 | 邮件正文(纯文本,发送时会按换行渲染进 HTML 模板) |
user_ids | UUID[] | 是 | 收件用户列表 |
curl -X POST "https://your-domain.com/api/v1/admin/users/send-email" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"subject": "维护通知", "content": "系统将于周六维护。", "user_ids": ["3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c"]}'额度与限流:每个管理员每小时最多 100 封;同一收件邮箱每天最多 5 封,超额收件人被跳过而不是整批失败。
{
"code": 0,
"data": { "sent_count": 1, "skipped_count": 0, "total": 1 },
"msg": "Emails have been queued for sending"
}| HTTP | code | 场景 |
|---|---|---|
400 | 5007 | SMTP 未配置 |
400 | 5400 | 发信额度不足;data 为 {"limit": 100, "period": "hour"} 或 {"requested": 3, "remaining": 1} |
400 | 4000 | user_ids 中没有任何有效用户 |
发送在后台任务中执行,响应只表示已入队,不代表已投递成功。
密码生命周期管理(管理员)
没有「管理员直接重置密码」的独立端点。重置密码请用 PUT /api/v1/admin/users/{user_id} 的 password 字段;以下端点用于管理密码过期策略,除标注外均需 admin:user:update。
强制修改密码
POST /api/v1/admin/users/{user_id}/force-password-change置位 force_password_change,用户下次登录时会被要求修改密码(登录仍返回 code: 0,但带 force_password_change: true 与 reason)。响应 200 OK,data: null。
重置密码过期时间
POST /api/v1/admin/users/{user_id}/reset-password-expiration以当前时间重新计算过期时间,相当于「确认用户刚改过密码」。响应 200 OK,data: null。
设置过期豁免
POST /api/v1/admin/users/{user_id}/exempt-password-expiration{ "exempt": true }用于服务账号或不适合强制轮换的账号。响应 200 OK,data: null。
批量强制修改密码
POST /api/v1/admin/users/bulk-force-password-change{ "user_ids": ["3f2a1b0c-...", "9d8e4f7a-..."] }响应 data.count 为实际更新的用户数。user_ids 为空返回 400 / 1001;全部 ID 都无效返回 400 / 4001。
密码过期统计
GET /api/v1/admin/users/password-expiration-stats需要 admin:user:read。响应包含 total_users、expired_count、expiring_soon_count、force_change_count、exempt_count。
当站点未启用密码过期策略(password_expiration_enabled = false)时,该接口直接返回全 0,不会计算真实数字。前端应同时判断策略开关,避免把 0 解读成「无人即将过期」。统计只覆盖 auth_source = "local" 的账号(SSO 账号不参与过期策略)。
列出密码过期用户
GET /api/v1/admin/users/expiring-passwords?page=1&page_size=20&filter=expiring需要 admin:user:read。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
filter | string | all | all、expired、expiring、force_change 之一 |
page | integer | 1 | 页码,≥ 1 |
page_size | integer | 20 | 每页条数,范围 1–100 |
路由顺序注意:/api/v1/admin/users/stats、/send-email、/bulk-force-password-change、/password-expiration-stats、/expiring-passwords 都是固定路径,必须写在 /{user_id} 之前,否则会被当成用户 ID。使用客户端 SDK 时无需关心,手写路径时不要漏。
错误码
| HTTP | code | 场景 |
|---|---|---|
400 | 1001 | 请求校验失败(密码策略、空 user_ids 等) |
400 | 2003 | 当前密码错误(修改密码、自助注销) |
403 | 2003 | JWT 无法解析或已过期 |
400 | 3000 | 自助注销被禁用,或试图删除自己的超级管理员账号 |
403 | 3000 | 缺少 admin:user:* 权限,或角色替换会使团队管理员失去 team:manage |
404 | 4001 | 用户不存在 |
400 | 5002 | 用户名已被占用 |
400 | 5003 | 邮箱已被占用 |
400 | 5005 | 修改邮箱的验证码无效 |
400 | 5007 | 批量发信时 SMTP 未配置 |
400 | 5201 | 不能删除超级管理员 |
400 | 5212 | 用户已处于激活状态 |
400 | 5213 | 用户已处于停用状态 |
400 | 5214 | 不能停用超级管理员 |
400 | 5306 | 未达到最小密码年龄 |
400 | 5400 | 发信额度不足 |
最佳实践
资料与安全
- 修改邮箱前先完成验证码校验,避免登录被
5004拦截。 - 用
GET /me/password-status提前提示密码即将过期,而不是等登录时被迫跳转。 - 自助注销前确认站点的
allow_account_deletion设置;企业环境通常应关闭并用「停用」替代。
不要共享账号、不要用弱密码、不要跳过邮箱验证,也不要用超级管理员账号做日常操作。
管理员操作
- 创建用户时使用强初始密码,并按需开启「首次登录强制修改密码」。
- 停用优先于删除:停用可逆,删除会同时失去审计关联。
- 调整角色时先确认目标用户是否管理着团队,避免触发
team:manage约束。 - 批量发信前核对
user_ids,并注意每收件人每天 5 封的上限。
代码示例
Python
import os
import requests
BASE = "https://your-domain.com/api/v1"
def get_password_status(token: str) -> dict:
"""查询当前用户的密码状态,用于提前提醒过期。"""
response = requests.get(
f"{BASE}/users/me/password-status",
headers={"Authorization": f"Bearer {token}"},
)
result = response.json()
if result["code"] != 0:
raise RuntimeError(f"{result['code']} {result['msg']}")
return result["data"]
def change_password(token: str, current_password: str, new_password: str) -> None:
"""修改当前用户密码。"""
response = requests.post(
f"{BASE}/users/me/change-password",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
json={"current_password": current_password, "new_password": new_password},
)
result = response.json()
if result["code"] != 0:
# 1001 时 data.errors.new_password 会列出具体策略原因
raise RuntimeError(f"{result['code']} {result['msg']} {result.get('data')}")
def bulk_send_email(admin_token: str, subject: str, content: str, user_ids: list[str]) -> dict:
"""管理员批量发信,返回入队统计。"""
response = requests.post(
f"{BASE}/admin/users/send-email",
headers={"Authorization": f"Bearer {admin_token}", "Content-Type": "application/json"},
json={"subject": subject, "content": content, "user_ids": user_ids},
)
result = response.json()
if result["code"] != 0:
raise RuntimeError(f"{result['code']} {result['msg']}")
return result["data"]
# 使用
token = os.environ["USER_TOKEN"]
status = get_password_status(token)
print(f"密码 {status['days_until_expiration']} 天后过期")JavaScript
const BASE = 'https://your-domain.com/api/v1';
async function getCurrentUser(token) {
const response = await fetch(`${BASE}/users/me`, {
headers: { Authorization: `Bearer ${token}` },
});
const result = await response.json();
if (result.code !== 0) throw new Error(`${result.code} ${result.msg}`);
return result.data;
}
async function updateProfile(token, { username, locale, avatarUrl } = {}) {
const response = await fetch(`${BASE}/users/me`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ username, locale, avatar_url: avatarUrl }),
});
const result = await response.json();
if (result.code !== 0) throw new Error(`${result.code} ${result.msg}`);
return result.data;
}
async function setUserActive(adminToken, userId, active) {
const action = active ? 'activate' : 'deactivate';
const response = await fetch(`${BASE}/admin/users/${userId}/${action}`, {
method: 'POST',
headers: { Authorization: `Bearer ${adminToken}` },
});
const result = await response.json();
if (result.code !== 0) throw new Error(`${result.code} ${result.msg}`);
return result.data;
}
// 使用
const token = process.env.USER_TOKEN;
const user = await getCurrentUser(token);
console.log('user:', user.username);相关文档
- 认证与用户登录 API:登录、注册、邮箱验证与密码找回
- Teams API:团队成员角色与管理
- API 错误与重试:
4001、5002、5003、5212-5214的统一处理
这篇文章对你有帮助吗?