feat: 新增批量移除角色权限接口
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled

- 新增 DELETE /api/admin/roles/:role_id/permissions 接口
- Store 层新增 BatchDelete 方法,单次 SQL 批量软删除
- Service 层新增 BatchRemovePermissions 方法
- Handler 层新增 BatchRemovePermissions 处理函数
- DTO 新增 BatchRemovePermissionsRequest/Params
- 修复 openapi generator 对 DELETE 方法 requestBody 不生成的问题
- RouteSpec 新增 Body 字段,支持显式指定 JSON 请求体

💘 Generated with Crush

Assisted-by: Claude Sonnet 4.6 via Crush <crush@charm.land>
This commit is contained in:
2026-04-09 11:41:58 +08:00
parent a7dfe858c7
commit 384a54164b
8 changed files with 159 additions and 7 deletions

View File

@@ -1356,6 +1356,19 @@ components:
description: 跳过原因
type: string
type: object
DtoBatchRemovePermissionsRequest:
properties:
perm_ids:
description: 权限ID列表
items:
minimum: 0
type: integer
minItems: 1
nullable: true
type: array
required:
- perm_ids
type: object
DtoBatchSetCardSeriesBindngRequest:
properties:
iccids:
@@ -6641,6 +6654,9 @@ components:
data_usage_mb:
description: 累计流量使用(MB)
type: integer
device_virtual_no:
description: 绑定设备的虚拟号(未绑定时为空字符串)
type: string
enable_polling:
description: 是否参与轮询
type: boolean
@@ -14541,13 +14557,13 @@ paths:
description: 卡接入号(模糊查询)
maxLength: 20
type: string
- description: 虚拟号(模糊查询)
- description: 是否为独立卡(true:仅返回未绑定设备的卡, false:仅返回已绑定设备的卡, 不传:返回全部)
in: query
name: virtual_no
name: is_standalone
schema:
description: 虚拟号(模糊查询)
maxLength: 50
type: string
description: 是否为独立卡(true:仅返回未绑定设备的卡, false:仅返回已绑定设备的卡, 不传:返回全部)
nullable: true
type: boolean
- description: 批次号
in: query
name: batch_no
@@ -19500,6 +19516,52 @@ paths:
summary: 更新角色状态
tags:
- 角色
/api/admin/roles/{role_id}/permissions:
delete:
parameters:
- description: 角色ID
in: path
name: role_id
required: true
schema:
description: 角色ID
minimum: 0
type: integer
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DtoBatchRemovePermissionsRequest'
responses:
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 批量移除权限
tags:
- 角色
/api/admin/roles/{role_id}/permissions/{perm_id}:
delete:
parameters:

View File

@@ -197,6 +197,35 @@ func (h *RoleHandler) RemovePermission(c *fiber.Ctx) error {
return response.Success(c, nil)
}
// BatchRemovePermissions 批量移除角色的权限
// DELETE /api/admin/roles/:role_id/permissions
func (h *RoleHandler) BatchRemovePermissions(c *fiber.Ctx) error {
roleID, err := strconv.ParseUint(c.Params("role_id"), 10, 64)
if err != nil {
return errors.New(errors.CodeInvalidParam, "无效的角色 ID")
}
var req dto.BatchRemovePermissionsRequest
if err := c.BodyParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
}
if err := h.validator.Struct(&req); err != nil {
logger.GetAppLogger().Warn("参数验证失败",
zap.String("path", c.Path()),
zap.String("method", c.Method()),
zap.Error(err),
)
return errors.New(errors.CodeInvalidParam)
}
if err := h.service.BatchRemovePermissions(c.UserContext(), uint(roleID), req.PermIDs); err != nil {
return err
}
return response.Success(c, nil)
}
// UpdateStatus 更新角色状态
// PUT /api/admin/roles/:id/status
func (h *RoleHandler) UpdateStatus(c *fiber.Ctx) error {

View File

@@ -67,6 +67,17 @@ type RemovePermissionParams struct {
PermID uint `path:"perm_id" required:"true" description:"权限ID"`
}
// BatchRemovePermissionsRequest 批量移除权限请求
type BatchRemovePermissionsRequest struct {
PermIDs []uint `json:"perm_ids" validate:"required,min=1" required:"true" minItems:"1" description:"权限ID列表"`
}
// BatchRemovePermissionsParams 批量移除权限参数聚合
type BatchRemovePermissionsParams struct {
RoleID uint `path:"role_id" required:"true" description:"角色ID"`
BatchRemovePermissionsRequest
}
// UpdateRoleStatusRequest 更新角色状态请求
type UpdateRoleStatusRequest struct {
Status *int `json:"status" validate:"required,min=0,max=1" description:"状态 (0:禁用, 1:启用)"`

View File

@@ -20,6 +20,7 @@ type RouteSpec struct {
Summary string // 简短摘要(中文,一行)
Description string // 详细说明,支持 Markdown 语法(可选)
Input interface{} // 请求参数结构体 (Query/Path/Body)
Body interface{} // 显式 JSON 请求体(用于 DELETE 等方法,库不自动生成 requestBody 的场景)
Output interface{} // 响应参数结构体
Tags []string // 分类标签
Auth bool // 是否需要认证图标 (预留)
@@ -54,7 +55,7 @@ func Register(router fiber.Router, doc *openapi.Generator, basePath, method, pat
}
doc.AddMultipartOperation(method, openapiPath, spec.Summary, spec.Description, spec.Input, spec.Output, spec.Auth, fileFields, spec.Tags...)
} else {
doc.AddOperation(method, openapiPath, spec.Summary, spec.Description, spec.Input, spec.Output, spec.Auth, spec.Tags...)
doc.AddOperation(method, openapiPath, spec.Summary, spec.Description, spec.Input, spec.Body, spec.Output, spec.Auth, spec.Tags...)
}
}
}

View File

@@ -87,4 +87,13 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Output: nil,
Auth: true,
})
Register(roles, doc, groupPath, "DELETE", "/:role_id/permissions", h.BatchRemovePermissions, RouteSpec{
Summary: "批量移除权限",
Tags: []string{"角色"},
Input: new(dto.BatchRemovePermissionsParams),
Body: new(dto.BatchRemovePermissionsRequest),
Output: nil,
Auth: true,
})
}

View File

@@ -286,6 +286,23 @@ func (s *Service) RemovePermission(ctx context.Context, roleID, permID uint) err
return nil
}
// BatchRemovePermissions 批量移除角色的权限
func (s *Service) BatchRemovePermissions(ctx context.Context, roleID uint, permIDs []uint) error {
_, err := s.roleStore.GetByID(ctx, roleID)
if err != nil {
if err == gorm.ErrRecordNotFound {
return errors.New(errors.CodeRoleNotFound, "角色不存在")
}
return errors.Wrap(errors.CodeInternalError, err, "获取角色失败")
}
if err := s.rolePermissionStore.BatchDelete(ctx, roleID, permIDs); err != nil {
return errors.Wrap(errors.CodeInternalError, err, "批量删除角色-权限关联失败")
}
return nil
}
// UpdateStatus 更新角色状态
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
currentUserID := middleware.GetUserIDFromContext(ctx)

View File

@@ -58,6 +58,17 @@ func (s *RolePermissionStore) Delete(ctx context.Context, roleID, permID uint) e
return nil
}
// BatchDelete 批量软删除角色-权限关联
func (s *RolePermissionStore) BatchDelete(ctx context.Context, roleID uint, permIDs []uint) error {
if err := s.db.WithContext(ctx).
Where("role_id = ? AND perm_id IN ?", roleID, permIDs).
Delete(&model.RolePermission{}).Error; err != nil {
return err
}
s.clearRoleUsersCaches(ctx, roleID)
return nil
}
// DeleteByRoleID 删除角色的所有权限关联
func (s *RolePermissionStore) DeleteByRoleID(ctx context.Context, roleID uint) error {
if err := s.db.WithContext(ctx).

View File

@@ -120,7 +120,7 @@ type FileUploadField struct {
// - output: 响应结构体(可为 nil
// - tags: 标签列表
// - requiresAuth: 是否需要认证
func (g *Generator) AddOperation(method, path, summary, description string, input interface{}, output interface{}, requiresAuth bool, tags ...string) {
func (g *Generator) AddOperation(method, path, summary, description string, input interface{}, body interface{}, output interface{}, requiresAuth bool, tags ...string) {
op := openapi3.Operation{
Summary: &summary,
Tags: tags,
@@ -138,6 +138,18 @@ func (g *Generator) AddOperation(method, path, summary, description string, inpu
}
}
// u663eu5f0fu4f20u5165u7684 JSON bodyuff0cu7528u4e8e DELETE u7b49u65b9u6cd5u4e0du81eau52a8u751fu6210 requestBody u7684u573au666f
if body != nil {
tempOp := openapi3.Operation{}
if err := g.Reflector.SetRequest(&tempOp, body, "POST"); err != nil {
panic(err)
}
if tempOp.RequestBody != nil {
op.RequestBody = tempOp.RequestBody
}
}
// 反射输出 (响应 Body)
if output != nil {
// 将输出包裹在 envelope 中