feat: 新增批量移除角色权限接口
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
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:
@@ -1356,6 +1356,19 @@ components:
|
|||||||
description: 跳过原因
|
description: 跳过原因
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
DtoBatchSetCardSeriesBindngRequest:
|
||||||
properties:
|
properties:
|
||||||
iccids:
|
iccids:
|
||||||
@@ -6641,6 +6654,9 @@ components:
|
|||||||
data_usage_mb:
|
data_usage_mb:
|
||||||
description: 累计流量使用(MB)
|
description: 累计流量使用(MB)
|
||||||
type: integer
|
type: integer
|
||||||
|
device_virtual_no:
|
||||||
|
description: 绑定设备的虚拟号(未绑定时为空字符串)
|
||||||
|
type: string
|
||||||
enable_polling:
|
enable_polling:
|
||||||
description: 是否参与轮询
|
description: 是否参与轮询
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -14541,13 +14557,13 @@ paths:
|
|||||||
description: 卡接入号(模糊查询)
|
description: 卡接入号(模糊查询)
|
||||||
maxLength: 20
|
maxLength: 20
|
||||||
type: string
|
type: string
|
||||||
- description: 虚拟号(模糊查询)
|
- description: 是否为独立卡(true:仅返回未绑定设备的卡, false:仅返回已绑定设备的卡, 不传:返回全部)
|
||||||
in: query
|
in: query
|
||||||
name: virtual_no
|
name: is_standalone
|
||||||
schema:
|
schema:
|
||||||
description: 虚拟号(模糊查询)
|
description: 是否为独立卡(true:仅返回未绑定设备的卡, false:仅返回已绑定设备的卡, 不传:返回全部)
|
||||||
maxLength: 50
|
nullable: true
|
||||||
type: string
|
type: boolean
|
||||||
- description: 批次号
|
- description: 批次号
|
||||||
in: query
|
in: query
|
||||||
name: batch_no
|
name: batch_no
|
||||||
@@ -19500,6 +19516,52 @@ paths:
|
|||||||
summary: 更新角色状态
|
summary: 更新角色状态
|
||||||
tags:
|
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}:
|
/api/admin/roles/{role_id}/permissions/{perm_id}:
|
||||||
delete:
|
delete:
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
@@ -197,6 +197,35 @@ func (h *RoleHandler) RemovePermission(c *fiber.Ctx) error {
|
|||||||
return response.Success(c, nil)
|
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 更新角色状态
|
// UpdateStatus 更新角色状态
|
||||||
// PUT /api/admin/roles/:id/status
|
// PUT /api/admin/roles/:id/status
|
||||||
func (h *RoleHandler) UpdateStatus(c *fiber.Ctx) error {
|
func (h *RoleHandler) UpdateStatus(c *fiber.Ctx) error {
|
||||||
|
|||||||
@@ -67,6 +67,17 @@ type RemovePermissionParams struct {
|
|||||||
PermID uint `path:"perm_id" required:"true" description:"权限ID"`
|
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 更新角色状态请求
|
// UpdateRoleStatusRequest 更新角色状态请求
|
||||||
type UpdateRoleStatusRequest struct {
|
type UpdateRoleStatusRequest struct {
|
||||||
Status *int `json:"status" validate:"required,min=0,max=1" description:"状态 (0:禁用, 1:启用)"`
|
Status *int `json:"status" validate:"required,min=0,max=1" description:"状态 (0:禁用, 1:启用)"`
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type RouteSpec struct {
|
|||||||
Summary string // 简短摘要(中文,一行)
|
Summary string // 简短摘要(中文,一行)
|
||||||
Description string // 详细说明,支持 Markdown 语法(可选)
|
Description string // 详细说明,支持 Markdown 语法(可选)
|
||||||
Input interface{} // 请求参数结构体 (Query/Path/Body)
|
Input interface{} // 请求参数结构体 (Query/Path/Body)
|
||||||
|
Body interface{} // 显式 JSON 请求体(用于 DELETE 等方法,库不自动生成 requestBody 的场景)
|
||||||
Output interface{} // 响应参数结构体
|
Output interface{} // 响应参数结构体
|
||||||
Tags []string // 分类标签
|
Tags []string // 分类标签
|
||||||
Auth bool // 是否需要认证图标 (预留)
|
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...)
|
doc.AddMultipartOperation(method, openapiPath, spec.Summary, spec.Description, spec.Input, spec.Output, spec.Auth, fileFields, spec.Tags...)
|
||||||
} else {
|
} 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...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,4 +87,13 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
|
|||||||
Output: nil,
|
Output: nil,
|
||||||
Auth: true,
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -286,6 +286,23 @@ func (s *Service) RemovePermission(ctx context.Context, roleID, permID uint) err
|
|||||||
return nil
|
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 更新角色状态
|
// UpdateStatus 更新角色状态
|
||||||
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
|
func (s *Service) UpdateStatus(ctx context.Context, id uint, status int) error {
|
||||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||||
|
|||||||
@@ -58,6 +58,17 @@ func (s *RolePermissionStore) Delete(ctx context.Context, roleID, permID uint) e
|
|||||||
return nil
|
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 删除角色的所有权限关联
|
// DeleteByRoleID 删除角色的所有权限关联
|
||||||
func (s *RolePermissionStore) DeleteByRoleID(ctx context.Context, roleID uint) error {
|
func (s *RolePermissionStore) DeleteByRoleID(ctx context.Context, roleID uint) error {
|
||||||
if err := s.db.WithContext(ctx).
|
if err := s.db.WithContext(ctx).
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ type FileUploadField struct {
|
|||||||
// - output: 响应结构体(可为 nil)
|
// - output: 响应结构体(可为 nil)
|
||||||
// - tags: 标签列表
|
// - tags: 标签列表
|
||||||
// - requiresAuth: 是否需要认证
|
// - 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{
|
op := openapi3.Operation{
|
||||||
Summary: &summary,
|
Summary: &summary,
|
||||||
Tags: tags,
|
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)
|
// 反射输出 (响应 Body)
|
||||||
if output != nil {
|
if output != nil {
|
||||||
// 将输出包裹在 envelope 中
|
// 将输出包裹在 envelope 中
|
||||||
|
|||||||
Reference in New Issue
Block a user