package admin import ( "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/facades" "github.com/goravel/framework/support/str" apperrors "goravel/app/errors" "goravel/app/http/helpers" adminrequests "goravel/app/http/requests/admin" "goravel/app/http/response" "goravel/app/models" "goravel/app/services" ) type RoleController struct { roleService services.RoleService } func NewRoleController() *RoleController { return &RoleController{ roleService: services.NewRoleServiceImpl(), } } // findRoleByID 根据ID查找角色,如果不存在则返回错误响应 // withRelations 为 true 时会预加载 Permissions 和 Menus 关联 func (r *RoleController) findRoleByID(ctx http.Context, id uint, withRelations bool) (*models.Role, http.Response) { role, err := r.roleService.GetByID(id, withRelations) if err != nil { return nil, response.Error(ctx, http.StatusNotFound, apperrors.ErrRoleNotFound.Code) } return role, nil } // buildFilters 构建查询过滤器 func (r *RoleController) buildFilters(ctx http.Context) services.RoleFilters { name := ctx.Request().Query("name", "") status := ctx.Request().Query("status", "") // 使用辅助函数自动转换时区 startTime := helpers.GetTimeQueryParam(ctx, "start_time") endTime := helpers.GetTimeQueryParam(ctx, "end_time") orderBy := ctx.Request().Query("order_by", "") return services.RoleFilters{ Name: name, Status: status, StartTime: startTime, EndTime: endTime, OrderBy: orderBy, } } // Index 角色列表 func (r *RoleController) Index(ctx http.Context) http.Response { page := helpers.GetIntQuery(ctx, "page", 1) pageSize := helpers.GetIntQuery(ctx, "page_size", 10) filters := r.buildFilters(ctx) roles, total, err := r.roleService.GetList(filters, page, pageSize) if err != nil { return response.Error(ctx, http.StatusInternalServerError, err.Error()) } return response.Success(ctx, http.Json{ "list": roles, "total": total, "page": page, "page_size": pageSize, }) } // Show 角色详情 func (r *RoleController) Show(ctx http.Context) http.Response { id := helpers.GetUintRoute(ctx, "id") role, resp := r.findRoleByID(ctx, id, true) // 预加载关联 if resp != nil { return resp } return response.Success(ctx, http.Json{ "role": *role, }) } // Store 创建角色 func (r *RoleController) Store(ctx http.Context) http.Response { // 使用请求验证 var roleCreate adminrequests.RoleCreate errors, err := ctx.Request().ValidateRequest(&roleCreate) if err != nil { return response.Error(ctx, http.StatusBadRequest, err.Error()) } if errors != nil { return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All()) } // 检查名称是否已存在 exists, err := facades.Orm().Query().Model(&models.Role{}).Where("name", roleCreate.Name).Exists() if err != nil { return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrCreateFailed.Code) } if exists { return response.Error(ctx, http.StatusBadRequest, apperrors.ErrRoleNameExists.Code) } // 检查标识是否已存在 exists, err = facades.Orm().Query().Model(&models.Role{}).Where("slug", roleCreate.Slug).Exists() if err != nil { return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrCreateFailed.Code) } if exists { return response.Error(ctx, http.StatusBadRequest, apperrors.ErrRoleSlugExists.Code) } role, err := r.roleService.Create( roleCreate.Name, roleCreate.Slug, roleCreate.Description, roleCreate.Status, roleCreate.Sort, ) if err != nil { return response.ErrorWithLog(ctx, "role", err, map[string]any{ "name": roleCreate.Name, "slug": roleCreate.Slug, }) } // 处理权限关联 permissionIDs := r.roleService.ParseIDsFromRequest(ctx, "permission_ids") if len(permissionIDs) > 0 { if err := r.roleService.SyncPermissions(role, permissionIDs); err != nil { return response.ErrorWithLog(ctx, "role", err, map[string]any{ "role_id": role.ID, "permission_ids": permissionIDs, }) } } // 处理菜单关联 menuIDs := r.roleService.ParseIDsFromRequest(ctx, "menu_ids") if len(menuIDs) > 0 { if err := r.roleService.SyncMenus(role, menuIDs); err != nil { return response.ErrorWithLog(ctx, "role", err, map[string]any{ "role_id": role.ID, "menu_ids": menuIDs, }) } } return response.Success(ctx, http.Json{ "role": *role, }) } // parseProtectedRoleSlugs 解析受保护的角色标识字符串(支持逗号分隔) func (r *RoleController) parseProtectedRoleSlugs(slugsStr string) []string { var slugs []string if slugsStr == "" { return slugs } parts := str.Of(slugsStr).Split(",") for _, part := range parts { part = str.Of(part).Trim().String() if !str.Of(part).IsEmpty() { slugs = append(slugs, part) } } return slugs } // isProtectedRole 检查角色是否是受保护的(通过slug判断) func (r *RoleController) isProtectedRole(roleSlug string) bool { protectedSlugsStr := facades.Config().GetString("role.protected_slugs", "super-admin") protectedSlugs := r.parseProtectedRoleSlugs(protectedSlugsStr) for _, protectedSlug := range protectedSlugs { if roleSlug == protectedSlug { return true } } return false } // Update 更新角色 func (r *RoleController) Update(ctx http.Context) http.Response { id := helpers.GetUintRoute(ctx, "id") role, resp := r.findRoleByID(ctx, id, false) if resp != nil { return resp } // 检查是否是受保护的角色(通过slug判断) isProtected := r.isProtectedRole(role.Slug) // 使用请求验证 var roleUpdate adminrequests.RoleUpdate errors, err := ctx.Request().ValidateRequest(&roleUpdate) if err != nil { return response.Error(ctx, http.StatusBadRequest, err.Error()) } if errors != nil { return response.ValidationError(ctx, http.StatusBadRequest, "validation_failed", errors.All()) } // 使用 All() 方法检查字段是否存在 allInputs := ctx.Request().All() if _, exists := allInputs["name"]; exists { // 检查名称是否已被其他角色使用(排除当前角色) exists, err := facades.Orm().Query().Model(&models.Role{}).Where("name", roleUpdate.Name).Where("id != ?", id).Exists() if err != nil { return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrUpdateFailed.Code) } if exists { return response.Error(ctx, http.StatusBadRequest, apperrors.ErrRoleNameExists.Code) } role.Name = roleUpdate.Name } if _, exists := allInputs["slug"]; exists { // 只有当 slug 值真正改变时才检查 if roleUpdate.Slug != role.Slug { // 受保护角色的标识不能修改 if isProtected { return response.Error(ctx, http.StatusForbidden, apperrors.ErrRoleProtectedCannotModifySlug.Code) } // 检查标识是否已被其他角色使用(排除当前角色) exists, err := facades.Orm().Query().Model(&models.Role{}).Where("slug", roleUpdate.Slug).Where("id != ?", id).Exists() if err != nil { return response.Error(ctx, http.StatusInternalServerError, apperrors.ErrUpdateFailed.Code) } if exists { return response.Error(ctx, http.StatusBadRequest, apperrors.ErrRoleSlugExists.Code) } role.Slug = roleUpdate.Slug } // 如果 slug 值未改变,跳过更新(允许其他字段正常更新) } if _, exists := allInputs["description"]; exists { // 描述字段允许修改,包括受保护角色(如 super-admin)也可以修改描述 role.Description = roleUpdate.Description } if _, exists := allInputs["status"]; exists { // 受保护角色不能禁用 if isProtected && roleUpdate.Status == 0 { return response.Error(ctx, http.StatusForbidden, apperrors.ErrRoleProtectedCannotDisable.Code) } role.Status = roleUpdate.Status } if _, exists := allInputs["sort"]; exists { role.Sort = roleUpdate.Sort } if err := r.roleService.Update(role); err != nil { return response.ErrorWithLog(ctx, "role", err, map[string]any{ "role_id": role.ID, }) } // super-admin 角色拥有所有权限,不需要设置菜单和权限 // 处理权限关联 if !isProtected && ctx.Request().Input("permission_ids") != "" { permissionIDs := r.roleService.ParseIDsFromRequest(ctx, "permission_ids") if err := r.roleService.SyncPermissions(role, permissionIDs); err != nil { return response.ErrorWithLog(ctx, "role", err, map[string]any{ "role_id": role.ID, "permission_ids": permissionIDs, }) } } // 处理菜单关联 if !isProtected && ctx.Request().Input("menu_ids") != "" { menuIDs := r.roleService.ParseIDsFromRequest(ctx, "menu_ids") if err := r.roleService.SyncMenus(role, menuIDs); err != nil { return response.ErrorWithLog(ctx, "role", err, map[string]any{ "role_id": role.ID, "menu_ids": menuIDs, }) } } return response.Success(ctx, http.Json{ "role": *role, }) } // Destroy 删除角色 func (r *RoleController) Destroy(ctx http.Context) http.Response { id := helpers.GetUintRoute(ctx, "id") role, resp := r.findRoleByID(ctx, id, false) if resp != nil { return resp } // 检查是否是受保护的角色(通过slug判断) if r.isProtectedRole(role.Slug) { return response.Error(ctx, http.StatusForbidden, apperrors.ErrRoleProtectedCannotDelete.Code) } if _, err := facades.Orm().Query().Delete(role); err != nil { return response.ErrorWithLog(ctx, "role", err, map[string]any{ "role_id": role.ID, }) } return response.Success(ctx) }