fix(doc): fix role/permission docs

This commit is contained in:
2026-01-31 17:31:25 +08:00
parent 41cdbb5b29
commit e49b33a464
2 changed files with 234 additions and 26 deletions

View File

@@ -26,6 +26,41 @@ use tracing::instrument;
) )
)] )]
#[instrument(skip(state))] #[instrument(skip(state))]
/// 查询权限列表Permission Catalog
///
/// 返回全局权限目录(`permissions` 表)的分页结果,用于:
/// - 后台展示可分配权限;
/// - 角色绑定权限前的检索与校验;
/// - 按应用(`app_code`/资源(`resource`/动作(`action`)筛选。
///
/// 权限编码规范为 `${app_code}:${resource}:${action}`,例如 `cms:article:publish`。
///
/// ## Parameters
/// - `TenantId(tenant_id): TenantId``Uuid`):当前租户 ID用于鉴权与跨租户隔离
/// - `State(state): State<AppState>`:应用状态,包含 `PermissionService`/`AuthorizationService`。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。
/// - `Query(query): Query<ListPermissionsQuery>`:查询参数(分页/搜索/筛选/排序)。
/// - `page: Option<u32>`:页码(默认 1
/// - `page_size: Option<u32>`:每页条数(默认 20范围 1..=200
/// - `search: Option<String>`:按 `code` 或 `description` 模糊搜索。
/// - `app_code/resource/action`:精确筛选(用于权限目录分组)。
///
/// ## Returns
/// - `Ok(AppResponse<Vec<Permission>>)`:成功返回 `200``data` 为权限列表。
///
/// ## Errors
/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `Err(AppError::PermissionDenied("role:read"))`:调用方缺少 `role:read` 权限(用于控制“查看权限目录”的能力)。
/// - `Err(AppError::BadRequest(_))`:分页参数非法(如 `page_size>200` 或为 0
/// - `Err(AppError::DbError(_))`:数据库查询失败。
///
/// ## Example
/// ```rust,ignore
/// // GET /permissions?page=1&page_size=20&app_code=cms&search=article
/// // Headers:
/// // Authorization: Bearer <access_token>
/// // X-Tenant-ID: <tenant_uuid> // 可选,但若提供必须与 token 中 tenant_id 一致
/// ```
pub async fn list_permissions_handler( pub async fn list_permissions_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,

View File

@@ -32,24 +32,36 @@ use uuid::Uuid;
) )
)] )]
#[instrument(skip(state, payload))] #[instrument(skip(state, payload))]
/// Create role in current tenant. /// 在当前租户下创建自定义角色Role
/// 在当前租户下创建角色。
/// ///
/// 业务规则: /// 本接口用于创建 **租户级自定义角色**`roles.tenant_id = 当前租户`)。创建后可继续通过
/// - 角色归属到当前租户(由 `TenantId` 决定),禁止跨租户写入 /// “角色-权限绑定”接口为角色授予权限,再通过“用户-角色绑定”接口把角色授予用户
/// - 需要具备 `role:write` 权限,否则返回 403。
/// ///
/// 输入: /// ## Parameters
/// - Header `Authorization: Bearer <access_token>`(必填) /// - `TenantId(tenant_id): TenantId`(内部包含 `Uuid`):当前请求的租户 ID由中间件解析并注入
/// - Body `CreateRoleRequest`(必填) /// - `State(state): State<AppState>`:应用状态,包含 `RoleService`/`AuthorizationService` 等依赖。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`
/// 从 `Authorization: Bearer <access_token>` 解析得到的认证上下文,包含调用方用户 ID 与租户 ID。
/// - `Json(payload): Json<CreateRoleRequest>`:创建角色的请求体(角色名与描述)。
/// ///
/// 输出: /// ## Returns
/// - `201`:返回新建角色信息(含 `id` /// - `Ok(AppResponse<RoleResponse>)`:创建成功返回 `201``data` 为新建角色(含 `id/name/description`
/// ///
/// 异常: /// ## Errors
/// - `401`:未认证 /// - `Err(AppError::PermissionDenied("tenant:mismatch"))`Token 中租户与当前租户不一致(跨租户访问被拒绝)。
/// - `403`:租户不匹配或无权限 /// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限
/// - `400`:请求参数错误 /// - `Err(AppError::AlreadyExists(_))`:角色名冲突(同租户下同名角色已存在)。
/// - `Err(AppError::DbError(_))`:数据库写入失败(连接异常、约束失败等)。
///
/// ## Example
/// ```rust,ignore
/// // POST /roles
/// // Headers:
/// // Authorization: Bearer <access_token>
/// // X-Tenant-ID: <tenant_uuid> // 可选,但若提供必须与 token 中 tenant_id 一致
/// // Body:
/// // { "name": "ContentAdmin", "description": "CMS content admins" }
/// ```
pub async fn create_role_handler( pub async fn create_role_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,
@@ -97,22 +109,23 @@ pub async fn create_role_handler(
) )
)] )]
#[instrument(skip(state))] #[instrument(skip(state))]
/// List roles in current tenant. /// 查询当前租户下的角色列表Role
/// 查询当前租户下的角色列表。
/// ///
/// 业务规则: /// 返回当前租户的所有角色(包含系统角色与自定义角色)。系统角色通常 `is_system=true`
/// - 仅返回当前租户角色;若 `X-Tenant-ID` 与 Token 不一致则返回 403 /// 用于内置管理员/平台管理员等能力;自定义角色用于业务场景的权限组合
/// - 需要具备 `role:read` 权限。
/// ///
/// 输入: /// ## Parameters
/// - Header `Authorization: Bearer <access_token>`(必填) /// - `TenantId(tenant_id): TenantId``Uuid`):当前请求的租户 ID。
/// - `State(state): State<AppState)`:应用状态。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。
/// ///
/// 输出: /// ## Returns
/// - `200`:角色列表 /// - `Ok(AppResponse<Vec<RoleResponse>>)`:成功返回 `200``data` 为角色数组。
/// ///
/// 异常: /// ## Errors
/// - `401`:未认证 /// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `403`:租户不匹配或无权限 /// - `Err(AppError::PermissionDenied("role:read"))`:调用方缺少 `role:read` 权限
/// - `Err(AppError::DbError(_))`:数据库查询失败。
pub async fn list_roles_handler( pub async fn list_roles_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,
@@ -162,6 +175,24 @@ pub async fn list_roles_handler(
) )
)] )]
#[instrument(skip(state))] #[instrument(skip(state))]
/// 查询角色详情Role
///
/// 仅允许读取当前租户内的角色,避免跨租户信息泄露。
///
/// ## Parameters
/// - `TenantId(tenant_id): TenantId``Uuid`):当前租户 ID。
/// - `State(state): State<AppState>`:应用状态。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。
/// - `Path(role_id): Path<Uuid>`:目标角色 ID。
///
/// ## Returns
/// - `Ok(AppResponse<RoleResponse>)`:成功返回 `200``data` 为角色信息。
///
/// ## Errors
/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `Err(AppError::PermissionDenied("role:read"))`:调用方缺少 `role:read` 权限。
/// - `Err(AppError::NotFound(_))`:角色不存在或不属于当前租户。
/// - `Err(AppError::DbError(_))`:数据库查询失败。
pub async fn get_role_handler( pub async fn get_role_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,
@@ -209,6 +240,28 @@ pub async fn get_role_handler(
) )
)] )]
#[instrument(skip(state, payload))] #[instrument(skip(state, payload))]
/// 更新角色基础信息Role
///
/// 仅允许修改 **自定义角色**。当目标角色为系统角色(`is_system=true`)时返回 403
/// 以防止线上误操作导致全局权限体系漂移。
///
/// ## Parameters
/// - `TenantId(tenant_id): TenantId``Uuid`):当前租户 ID。
/// - `State(state): State<AppState>`:应用状态。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。
/// - `Path(role_id): Path<Uuid>`:目标角色 ID。
/// - `Json(payload): Json<UpdateRoleRequest>`:可选更新字段(`name`/`description`)。
///
/// ## Returns
/// - `Ok(AppResponse<RoleResponse>)`:成功返回 `200``data` 为更新后的角色信息。
///
/// ## Errors
/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。
/// - `Err(AppError::PermissionDenied("role:system_immutable"))`:系统角色不可修改。
/// - `Err(AppError::AlreadyExists(_))`:角色名冲突。
/// - `Err(AppError::NotFound(_))`:角色不存在。
/// - `Err(AppError::DbError(_))`:数据库写入失败。
pub async fn update_role_handler( pub async fn update_role_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,
@@ -258,6 +311,26 @@ pub async fn update_role_handler(
) )
)] )]
#[instrument(skip(state))] #[instrument(skip(state))]
/// 删除角色Role
///
/// 仅允许删除 **自定义角色**;系统角色(`is_system=true`)不可删除。
/// 删除角色会级联删除 `user_roles` 与 `role_permissions` 关联(由外键约束实现)。
///
/// ## Parameters
/// - `TenantId(tenant_id): TenantId``Uuid`):当前租户 ID。
/// - `State(state): State<AppState>`:应用状态。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。
/// - `Path(role_id): Path<Uuid>`:目标角色 ID。
///
/// ## Returns
/// - `Ok(AppResponse<serde_json::Value>)`:成功返回 `200``data` 为 `{}`。
///
/// ## Errors
/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。
/// - `Err(AppError::PermissionDenied("role:system_immutable"))`:系统角色不可删除。
/// - `Err(AppError::NotFound(_))`:角色不存在。
/// - `Err(AppError::DbError(_))`:数据库删除失败。
pub async fn delete_role_handler( pub async fn delete_role_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,
@@ -304,6 +377,38 @@ pub async fn delete_role_handler(
) )
)] )]
#[instrument(skip(state, payload))] #[instrument(skip(state, payload))]
/// 为角色批量授予权限Role → Permission 绑定)。
///
/// 将 `permission_codes` 批量写入 `role_permissions`(幂等:重复绑定不会报错)。
/// 仅允许对 **自定义角色** 执行此操作;系统角色不可通过 API 修改权限集。
///
/// ## Parameters
/// - `TenantId(tenant_id): TenantId``Uuid`):当前租户 ID。
/// - `State(state): State<AppState>`:应用状态。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。
/// - `Path(role_id): Path<Uuid>`:目标角色 ID。
/// - `Json(payload): Json<RolePermissionsRequest>`:待授予的权限码数组(`Vec<String>`)。
///
/// ## Returns
/// - `Ok(AppResponse<serde_json::Value>)`:成功返回 `200``data` 为 `{}`。
///
/// ## Errors
/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。
/// - `Err(AppError::PermissionDenied("role:system_immutable"))`:系统角色不可变更权限集。
/// - `Err(AppError::BadRequest(_))`:存在非法的 `permission_codes`(权限码未在 `permissions` 表中定义)。
/// - `Err(AppError::NotFound(_))`:角色不存在。
/// - `Err(AppError::DbError(_))`:数据库写入失败。
///
/// ## Example
/// ```rust,ignore
/// // POST /roles/{role_id}/permissions/grant
/// // Body:
/// // { "permission_codes": ["cms:article:create", "cms:article:publish", "cms:*:*"] }
/// //
/// // 说明:
/// // - 通配符权限(如 cms:*:*)只有在 permissions 表中存在并被绑定到角色时才会生效。
/// ```
pub async fn grant_role_permissions_handler( pub async fn grant_role_permissions_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,
@@ -351,6 +456,27 @@ pub async fn grant_role_permissions_handler(
) )
)] )]
#[instrument(skip(state, payload))] #[instrument(skip(state, payload))]
/// 从角色批量回收权限Role → Permission 解绑)。
///
/// 从 `role_permissions` 删除指定 `permission_codes` 的关联(幂等:不存在的关联不会报错)。
/// 仅允许对 **自定义角色** 执行此操作;系统角色不可通过 API 修改权限集。
///
/// ## Parameters
/// - `TenantId(tenant_id): TenantId``Uuid`):当前租户 ID。
/// - `State(state): State<AppState>`:应用状态。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文。
/// - `Path(role_id): Path<Uuid>`:目标角色 ID。
/// - `Json(payload): Json<RolePermissionsRequest>`:待回收的权限码数组(`Vec<String>`)。
///
/// ## Returns
/// - `Ok(AppResponse<serde_json::Value>)`:成功返回 `200``data` 为 `{}`。
///
/// ## Errors
/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `Err(AppError::PermissionDenied("role:write"))`:调用方缺少 `role:write` 权限。
/// - `Err(AppError::PermissionDenied("role:system_immutable"))`:系统角色不可变更权限集。
/// - `Err(AppError::NotFound(_))`:角色不存在。
/// - `Err(AppError::DbError(_))`:数据库写入失败。
pub async fn revoke_role_permissions_handler( pub async fn revoke_role_permissions_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,
@@ -398,6 +524,34 @@ pub async fn revoke_role_permissions_handler(
) )
)] )]
#[instrument(skip(state, payload))] #[instrument(skip(state, payload))]
/// 批量把角色授予用户User ← Role 绑定)。
///
/// 将 `user_ids` 批量写入 `user_roles`(幂等:重复授予不会报错)。
/// 该接口用于“按角色”为多个用户授权,适用于组织/部门批量开通权限的场景。
///
/// ## Parameters
/// - `TenantId(tenant_id): TenantId``Uuid`):当前租户 ID。
/// - `State(state): State<AppState>`:应用状态。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文(`user_id` 为操作者)。
/// - `Path(role_id): Path<Uuid>`:目标角色 ID。
/// - `Json(payload): Json<RoleUsersRequest>`:待授予用户 ID 列表(`Vec<Uuid>`)。
///
/// ## Returns
/// - `Ok(AppResponse<serde_json::Value>)`:成功返回 `200``data` 为 `{}`。
///
/// ## Errors
/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `Err(AppError::PermissionDenied("user:write"))`:调用方缺少 `user:write` 权限。
/// - `Err(AppError::NotFound(_))`:角色不存在。
/// - `Err(AppError::BadRequest(_))`:存在非法 `user_ids`(用户不在当前租户内)。
/// - `Err(AppError::DbError(_))`:数据库写入失败。
///
/// ## Example
/// ```rust,ignore
/// // POST /roles/{role_id}/users/grant
/// // Body:
/// // { "user_ids": ["<user_uuid_1>", "<user_uuid_2>"] }
/// ```
pub async fn grant_role_users_handler( pub async fn grant_role_users_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,
@@ -445,6 +599,25 @@ pub async fn grant_role_users_handler(
) )
)] )]
#[instrument(skip(state, payload))] #[instrument(skip(state, payload))]
/// 批量从用户回收角色User ← Role 解绑)。
///
/// 从 `user_roles` 删除指定 `user_ids` 的角色关联(幂等:不存在的关联不会报错)。
///
/// ## Parameters
/// - `TenantId(tenant_id): TenantId``Uuid`):当前租户 ID。
/// - `State(state): State<AppState>`:应用状态。
/// - `AuthContext { tenant_id: auth_tenant_id, user_id, .. }`:认证上下文(`user_id` 为操作者)。
/// - `Path(role_id): Path<Uuid>`:目标角色 ID。
/// - `Json(payload): Json<RoleUsersRequest>`:待回收用户 ID 列表(`Vec<Uuid>`)。
///
/// ## Returns
/// - `Ok(AppResponse<serde_json::Value>)`:成功返回 `200``data` 为 `{}`。
///
/// ## Errors
/// - `Err(AppError::PermissionDenied("tenant:mismatch"))`:跨租户访问被拒绝。
/// - `Err(AppError::PermissionDenied("user:write"))`:调用方缺少 `user:write` 权限。
/// - `Err(AppError::NotFound(_))`:角色不存在。
/// - `Err(AppError::DbError(_))`:数据库写入失败。
pub async fn revoke_role_users_handler( pub async fn revoke_role_users_handler(
TenantId(tenant_id): TenantId, TenantId(tenant_id): TenantId,
State(state): State<AppState>, State(state): State<AppState>,