689ef6bc3d
主要修复: 1. 修复所有RBAC API函数使用axios-client(自动添加JWT token) - getRoles, createRole, updateRole, deleteRole 从rbacFetch切换到axios-client - 解决401未授权导致的数据加载失败问题 2. 修复用户ID字段不匹配问题 - getAllUsers函数使用user_id字段(兼容user.user_id || user.id) - 确保角色分配时使用正确的用户ID 3. 修复路由ID不匹配问题 - getRoutes函数改用真实后端API(GET /rbac/user/routes) - 解决前端Mock路由ID与数据库不一致导致的400错误 4. 增强axios-client成功响应识别 - 支持code=200作为成功状态(原本只支持code=0) - 兼容不同后端API的响应格式 5. 实现用户单角色限制功能 - 添加getUserRoles API函数 - 分配角色前检查用户现有角色 - 在用户列表中显示当前角色标签 6. 改进创建角色的表单验证 - role_key必须以字母开头(正则:^[a-z][a-z0-9_]*$) - 添加实时验证提示 - 更新提示文案说明规则 7. 添加删除操作的安全确认机制 - 删除角色/移除用户角色前显示确认模态框 - 3秒倒计时后才能确认删除 - 成功删除后自动刷新数据 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
710 lines
16 KiB
Markdown
710 lines
16 KiB
Markdown
# 用户权限管理接口 - 前端对接文档
|
||
|
||
## 📋 文档信息
|
||
|
||
- **版本**: v2.0
|
||
- **更新日期**: 2025-01-24
|
||
- **API基础URL**: `http://YOUR_HOST:8000`
|
||
- **认证方式**: JWT Bearer Token
|
||
|
||
---
|
||
|
||
## ⚠️ 重要提示 - 前端对接必读
|
||
|
||
### 1. Token过期处理
|
||
|
||
**问题现象**: API返回 `code: 4001, msg: "token参数无效"`
|
||
|
||
**原因**: JWT Token已过期(默认有效期1小时)
|
||
|
||
**解决方案**:
|
||
```javascript
|
||
// 检查Token是否过期
|
||
const isTokenExpired = (token) => {
|
||
try {
|
||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||
return Date.now() >= payload.exp * 1000;
|
||
} catch {
|
||
return true;
|
||
}
|
||
};
|
||
|
||
// 在发送请求前检查
|
||
if (isTokenExpired(token)) {
|
||
await login(); // 重新登录获取新Token
|
||
}
|
||
```
|
||
|
||
### 2. 实际角色ID对应关系
|
||
|
||
⚠️ **数据库中的实际角色ID** (请以实际数据为准):
|
||
|
||
| 角色名称 | role_key | 实际role_id | 说明 |
|
||
|---------|----------|------------|------|
|
||
| 省级管理员 | provincial_admin | **52** | 拥有完整RBAC管理权限 |
|
||
| 市级管理员 | admin | **1** | 负责本地区业务管理 |
|
||
| 普通员工 | common | **2** | 只能查看和管理自己的数据 |
|
||
|
||
**调用示例**:
|
||
```javascript
|
||
// ✅ 正确:使用实际的role_id
|
||
const provincialAdminUsers = await getRoleUsers(52);
|
||
const adminUsers = await getRoleUsers(1);
|
||
const commonUsers = await getRoleUsers(2);
|
||
|
||
// ❌ 错误:使用Mock数据的ID
|
||
const wrongUsers = await getRoleUsers(3); // 数据库中不存在
|
||
```
|
||
|
||
### 3. 响应格式说明
|
||
|
||
所有接口统一返回格式:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": { ... }
|
||
}
|
||
```
|
||
|
||
部分接口(如用户列表)直接返回数据对象:
|
||
```json
|
||
{
|
||
"users": [...],
|
||
"total": 10
|
||
}
|
||
```
|
||
|
||
### 4. 权限要求
|
||
|
||
| 接口模块 | 权限要求 | 说明 |
|
||
|---------|----------|------|
|
||
| RBAC管理接口 | `system:rbac:manage` | 仅provincial_admin角色 |
|
||
| 用户管理接口 | JWT认证即可 | 所有登录用户 |
|
||
| 路由权限接口 | JWT认证即可 | 所有登录用户 |
|
||
|
||
---
|
||
|
||
## 📚 接口总览
|
||
|
||
### 一、RBAC管理接口(18个)
|
||
|
||
**基础路径**: `/api/v3/rbac`
|
||
|
||
#### 1.1 角色管理(5个接口)
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/roles` | 获取角色列表(支持分页、搜索) |
|
||
| GET | `/roles/{role_id}` | 获取角色详情 |
|
||
| POST | `/roles` | 创建角色 |
|
||
| PUT | `/roles/{role_id}` | 更新角色 |
|
||
| DELETE | `/roles/{role_id}` | 删除角色(支持force参数) |
|
||
|
||
#### 1.2 权限管理(5个接口)
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/permissions` | 获取权限列表(支持树形/平铺) |
|
||
| GET | `/permissions/{permission_id}` | 获取权限详情 |
|
||
| POST | `/permissions` | 创建权限 |
|
||
| PUT | `/permissions/{permission_id}` | 更新权限 |
|
||
| DELETE | `/permissions/{permission_id}` | 删除权限 |
|
||
|
||
#### 1.3 角色权限关联(4个接口)
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/roles/{role_id}/permissions` | 获取角色的所有权限 |
|
||
| POST | `/roles/{role_id}/permissions` | 批量分配权限给角色(支持替换/追加) |
|
||
| PUT | `/roles/{role_id}/permissions/{permission_id}` | 更新单个权限配置 |
|
||
| DELETE | `/roles/{role_id}/permissions/{permission_id}` | 移除角色权限 |
|
||
|
||
#### 1.4 用户角色管理(4个接口)
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/roles/{role_id}/users` | 获取拥有某角色的用户列表 |
|
||
| GET | `/users/{user_id}/roles` | 获取用户的所有角色 |
|
||
| POST | `/users/{user_id}/roles` | 为用户分配角色 |
|
||
| DELETE | `/users/{user_id}/roles/{role_id}` | 移除用户角色 |
|
||
|
||
### 二、用户管理接口(3个)
|
||
|
||
**基础路径**: `/admin/users`
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/users` | 获取用户列表(支持分页、搜索) |
|
||
| GET | `/organizations` | 获取组织架构(树形结构) |
|
||
| GET | `/organizations/flat` | 获取组织列表(扁平结构) |
|
||
|
||
### 三、路由权限接口(5个)
|
||
|
||
**基础路径**: `/rbac`
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/user/routes` | 获取当前用户可访问路由(树形) |
|
||
| GET | `/user/routes/flat` | 获取当前用户可访问路由(扁平) |
|
||
| GET | `/roles/{role_id}/routes` | 获取角色可访问路由 |
|
||
| PUT | `/roles/{role_id}/routes` | **批量更新角色路由权限** ⭐新增 |
|
||
| GET | `/check-route` | 检查路由访问权限 |
|
||
|
||
---
|
||
|
||
## 📖 详细接口说明
|
||
|
||
## 一、RBAC管理接口
|
||
|
||
### 1.1 获取角色列表
|
||
|
||
**接口**: `GET /api/v3/rbac/roles`
|
||
|
||
**Query参数**:
|
||
```javascript
|
||
{
|
||
page: 1, // 页码
|
||
page_size: 20, // 每页数量
|
||
role_key: "", // 角色标识过滤(可选)
|
||
role_name: "", // 角色名称模糊搜索(可选)
|
||
include_system: true // 是否包含系统角色(可选)
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"total": 3,
|
||
"page": 1,
|
||
"page_size": 20,
|
||
"items": [
|
||
{
|
||
"id": 52,
|
||
"role_key": "provincial_admin",
|
||
"role_name": "省级管理员",
|
||
"description": "省级权限,可管理所有地区",
|
||
"data_scope": "ALL",
|
||
"is_system": true,
|
||
"user_count": 1,
|
||
"permission_count": 15
|
||
},
|
||
{
|
||
"id": 1,
|
||
"role_key": "admin",
|
||
"role_name": "市级管理员",
|
||
"data_scope": "DEPT",
|
||
"is_system": true,
|
||
"user_count": 6
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 1.2 创建角色
|
||
|
||
**接口**: `POST /api/v3/rbac/roles`
|
||
|
||
**请求体**:
|
||
```json
|
||
{
|
||
"role_key": "department_leader",
|
||
"role_name": "部门负责人",
|
||
"description": "负责部门日常管理",
|
||
"data_scope": "DEPT",
|
||
"metadata": {}
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "角色创建成功",
|
||
"data": {
|
||
"id": 6,
|
||
"role_key": "department_leader",
|
||
"role_name": "部门负责人",
|
||
"data_scope": "DEPT",
|
||
"is_system": false
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 1.3 获取角色的所有用户
|
||
|
||
**接口**: `GET /api/v3/rbac/roles/{role_id}/users`
|
||
|
||
**Query参数**:
|
||
```javascript
|
||
{
|
||
page: 1,
|
||
page_size: 20,
|
||
area: "梅州", // 按地区过滤(可选)
|
||
username: "admin" // 用户名模糊搜索(可选)
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"total": 6,
|
||
"page": 1,
|
||
"page_size": 20,
|
||
"items": [
|
||
{
|
||
"user_id": 8,
|
||
"username": "梅州烟草",
|
||
"nick_name": "梅州烟草",
|
||
"area": "梅州管理员账号",
|
||
"ou_name": "梅州管理员账号",
|
||
"phone_number": null,
|
||
"email": null,
|
||
"assigned_at": "2025-11-18T01:40:25.030949+00:00"
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 1.4 批量分配权限给角色
|
||
|
||
**接口**: `POST /api/v3/rbac/roles/{role_id}/permissions`
|
||
|
||
**请求体**:
|
||
```json
|
||
{
|
||
"permissions": [
|
||
{
|
||
"permission_id": 10,
|
||
"grant_type": "GRANT",
|
||
"data_scope": "ALL"
|
||
},
|
||
{
|
||
"permission_id": 11,
|
||
"grant_type": "GRANT",
|
||
"data_scope": "DEPT"
|
||
}
|
||
],
|
||
"replace": true
|
||
}
|
||
```
|
||
|
||
**参数说明**:
|
||
- `replace`: true=替换全部权限,false=追加权限
|
||
- `grant_type`: GRANT(授予)或 DENY(拒绝)
|
||
- `data_scope`: ALL(全部数据)/ DEPT(本部门)/ SELF(仅自己)
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "权限分配成功",
|
||
"data": {
|
||
"role_id": 2,
|
||
"assigned_count": 2
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 1.5 为用户分配角色
|
||
|
||
**接口**: `POST /api/v3/rbac/users/{user_id}/roles`
|
||
|
||
**请求体**:
|
||
```json
|
||
{
|
||
"role_ids": [1, 2]
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "角色分配成功",
|
||
"data": {
|
||
"user_id": 20,
|
||
"username": "test_user",
|
||
"assigned_roles": [
|
||
{"role_id": 1, "role_name": "市级管理员"},
|
||
{"role_id": 2, "role_name": "普通员工"}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**注意事项**:
|
||
- ⚠️ 不能给自己分配角色(防止提权)
|
||
- ⚠️ provincial_admin角色只能由省级管理员分配
|
||
- ⚠️ admin角色只能给本地区用户分配角色
|
||
|
||
---
|
||
|
||
## 二、用户管理接口
|
||
|
||
### 2.1 获取用户列表
|
||
|
||
**接口**: `GET /admin/users/users`
|
||
|
||
**Query参数**:
|
||
```javascript
|
||
{
|
||
page: 1,
|
||
page_size: 20,
|
||
ou_id: "000", // 组织ID过滤(可选)
|
||
is_leader: true, // 是否领导过滤(可选)
|
||
search: "admin" // 搜索关键词(可选)
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"users": [
|
||
{
|
||
"id": 5,
|
||
"username": "admin",
|
||
"nick_name": "admin",
|
||
"ou_id": "000",
|
||
"ou_name": "test",
|
||
"is_leader": true,
|
||
"status": 0
|
||
}
|
||
],
|
||
"total": 10
|
||
}
|
||
```
|
||
|
||
⚠️ **已知问题**: 当前版本 `total` 字段返回0,请以 `users` 数组长度为准。
|
||
|
||
---
|
||
|
||
### 2.2 获取组织架构(树形)
|
||
|
||
**接口**: `GET /admin/users/organizations`
|
||
|
||
**Query参数**:
|
||
```javascript
|
||
{
|
||
include_users: true // 是否包含用户信息
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"organizations": [
|
||
{
|
||
"ou_id": "000",
|
||
"ou_name": "总部",
|
||
"parent_ou_id": null,
|
||
"level": 0,
|
||
"children": [
|
||
{
|
||
"ou_id": "0000000A1ML",
|
||
"ou_name": "梅州市局",
|
||
"parent_ou_id": "000",
|
||
"level": 1,
|
||
"users": [
|
||
{
|
||
"id": 8,
|
||
"username": "梅州烟草",
|
||
"nick_name": "梅州烟草",
|
||
"ou_id": "0000000A1ML",
|
||
"ou_name": "梅州市局"
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"users": []
|
||
}
|
||
],
|
||
"total_organizations": 5,
|
||
"total_users": 10
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 三、路由权限接口
|
||
|
||
### 3.1 获取当前用户可访问路由
|
||
|
||
**接口**: `GET /rbac/user/routes`
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"user_id": 5,
|
||
"username": "admin",
|
||
"routes": [
|
||
{
|
||
"id": 1,
|
||
"route_path": "/",
|
||
"route_name": "Home",
|
||
"route_title": "首页",
|
||
"component": "Layout",
|
||
"parent_id": null,
|
||
"icon": "home",
|
||
"sort_order": 0,
|
||
"is_hidden": false,
|
||
"is_cache": true,
|
||
"meta": {},
|
||
"children": [
|
||
{
|
||
"id": 11,
|
||
"route_path": "/dashboard",
|
||
"route_name": "Dashboard",
|
||
"route_title": "工作台",
|
||
"parent_id": 1
|
||
}
|
||
]
|
||
}
|
||
],
|
||
"routes_flat": [
|
||
{"id": 1, "route_path": "/", "route_name": "Home"},
|
||
{"id": 11, "route_path": "/dashboard", "route_name": "Dashboard"}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
**说明**:
|
||
- `routes`: 树形结构(用于构建菜单)
|
||
- `routes_flat`: 扁平结构(用于路由守卫权限检查)
|
||
|
||
---
|
||
|
||
### 3.2 获取角色可访问路由
|
||
|
||
**接口**: `GET /rbac/roles/{role_id}/routes`
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"role_id": 2,
|
||
"routes": [
|
||
{"id": 1, "route_path": "/", "route_name": "Home", "route_title": "首页"},
|
||
{"id": 11, "route_path": "/dashboard", "route_name": "Dashboard"}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3.3 批量更新角色路由权限 ⭐新增
|
||
|
||
**接口**: `PUT /rbac/roles/{role_id}/routes`
|
||
|
||
**功能说明**:
|
||
- 批量更新指定角色的路由权限
|
||
- 采用**替换模式**:先删除现有所有关联,再插入新关联
|
||
- 自动清除相关缓存(角色缓存 + 用户缓存)
|
||
|
||
**请求体**:
|
||
```json
|
||
{
|
||
"route_ids": [1, 11, 12, 3, 31, 32, 2, 21],
|
||
"permission": "RW"
|
||
}
|
||
```
|
||
|
||
**参数说明**:
|
||
- `route_ids`: 路由ID列表(必填)
|
||
- `permission`: 权限类型,可选值:
|
||
- `R`: 只读权限
|
||
- `W`: 只写权限
|
||
- `RW`: 读写权限(默认)
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"role_id": 2,
|
||
"assigned_count": 8,
|
||
"removed_count": 5,
|
||
"route_ids": [1, 11, 12, 3, 31, 32, 2, 21]
|
||
}
|
||
}
|
||
```
|
||
|
||
**前端调用示例**:
|
||
```javascript
|
||
const updateRoleRoutes = async (roleId, routeIds) => {
|
||
const response = await fetch(`/rbac/roles/${roleId}/routes`, {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`,
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({
|
||
route_ids: routeIds,
|
||
permission: 'RW'
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (result.code === 200) {
|
||
console.log(`成功分配 ${result.data.assigned_count} 个路由`);
|
||
console.log(`移除了 ${result.data.removed_count} 个旧路由`);
|
||
}
|
||
|
||
return result;
|
||
};
|
||
|
||
// 使用示例
|
||
await updateRoleRoutes(2, [1, 11, 12, 3, 31, 32, 2, 21]);
|
||
```
|
||
|
||
**注意事项**:
|
||
- ⚠️ 所有 `route_ids` 必须存在且未删除
|
||
- ⚠️ 使用事务确保数据一致性
|
||
- ⚠️ 会自动清除所有受影响用户的路由缓存
|
||
|
||
---
|
||
|
||
### 3.4 检查路由访问权限
|
||
|
||
**接口**: `GET /rbac/check-route`
|
||
|
||
**Query参数**:
|
||
```javascript
|
||
{
|
||
route_path: "/system/users"
|
||
}
|
||
```
|
||
|
||
**响应示例**:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": {
|
||
"route_path": "/system/users",
|
||
"has_access": true
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🚨 错误码说明
|
||
|
||
### HTTP状态码
|
||
|
||
| 状态码 | 说明 | 处理建议 |
|
||
|--------|------|---------|
|
||
| 200 | 成功 | - |
|
||
| 400 | 请求参数错误 | 检查请求体格式和必填字段 |
|
||
| 401 | 未认证 | Token过期或无效,需要重新登录 |
|
||
| 403 | 无权限 | 显示权限不足提示 |
|
||
| 404 | 资源不存在 | 检查ID是否正确 |
|
||
| 409 | 冲突 | 如role_key重复 |
|
||
| 500 | 服务器错误 | 联系后端排查 |
|
||
|
||
### 常见错误处理
|
||
|
||
```javascript
|
||
// 统一错误处理
|
||
const handleError = (error) => {
|
||
if (error.response?.status === 401) {
|
||
// Token过期,跳转登录
|
||
localStorage.removeItem('token');
|
||
window.location.href = '/login';
|
||
} else if (error.response?.status === 403) {
|
||
alert('权限不足');
|
||
} else if (error.response?.data?.detail) {
|
||
alert(error.response.data.detail);
|
||
} else {
|
||
alert('操作失败,请稍后重试');
|
||
}
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 💡 常见问题FAQ
|
||
|
||
### Q1: 为什么所有接口返回 code: 4001?
|
||
**A**: Token已过期,请重新登录获取新Token
|
||
|
||
### Q2: role_id应该用哪个?文档里写的1,2,3还是52,1,2?
|
||
**A**: 使用实际数据库中的ID:
|
||
- provincial_admin = **52**
|
||
- admin = **1**
|
||
- common = **2**
|
||
|
||
### Q3: 需要用Mock数据吗?
|
||
**A**: **不需要**,所有接口已完整实现,直接调用真实API
|
||
|
||
### Q4: 如何判断当前用户是否有RBAC管理权限?
|
||
```javascript
|
||
// 方式1:检查JWT中的user_role
|
||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||
const isProvincialAdmin = payload.user_role === 'provincial_admin';
|
||
|
||
// 方式2:调用权限检查接口(推荐)
|
||
const routes = await fetch('/rbac/user/routes');
|
||
const hasRBACAccess = routes.data.routes.some(r => r.route_path === '/rbac');
|
||
```
|
||
|
||
### Q5: 如何实现角色权限页面的路由分配功能?
|
||
**A**: 使用新的批量更新接口 `PUT /rbac/roles/{role_id}/routes`:
|
||
1. 通过 `GET /rbac/roles/{role_id}/routes` 获取当前角色已有路由
|
||
2. 用户选择要分配的路由ID列表
|
||
3. 调用 `PUT /rbac/roles/{role_id}/routes` 提交更新
|
||
4. 接口会自动替换所有路由权限并清除缓存
|
||
|
||
### Q6: total字段为什么返回0?
|
||
**A**: `/admin/users/users` 接口的 `total` 字段当前版本存在bug,请以 `users` 数组长度为准。该问题已记录,将在后续版本修复。
|
||
|
||
### Q7: 批量更新路由权限后,用户需要重新登录吗?
|
||
**A**: 不需要。接口会自动清除相关用户的路由缓存,用户刷新页面即可看到新权限。
|
||
|
||
---
|
||
|
||
## 📦 前端开发清单
|
||
|
||
- [ ] 移除所有RBAC相关的Mock数据
|
||
- [ ] 使用实际的role_id(52, 1, 2)
|
||
- [ ] 实现Token过期自动刷新机制
|
||
- [ ] 处理401/403错误(跳转登录/权限提示)
|
||
- [ ] 使用 `PUT /rbac/roles/{role_id}/routes` 实现路由权限分配
|
||
- [ ] 处理 `total` 字段为0的情况(用数组长度代替)
|
||
|
||
---
|
||
|
||
## 📞 技术支持
|
||
|
||
如有问题,请联系后端开发团队。
|
||
|
||
**文档版本**: v2.0
|
||
**最后更新**: 2025-01-24
|
||
**维护者**: Backend Team
|