package service import ( "encoding/json" "sort" "strings" "github.com/freefire/jiang13-bbs/model" ) // 权限码:后台功能点。路由通过 RequirePerm 声明, // 板块管理员的板块范围由 Actor.BoardIDs 在业务层二次校验。 const ( PermUsers = "users" // 用户与权限管理(超管/站长) PermAnnouncements = "announcements" // 公告管理(管理员及以上;可账号级授予) PermPages = "pages" // 单页管理(管理员及以上;可账号级授予) PermLibrary = "library" // 书库管理(管理员及以上;可账号级授予) PermAds = "ads" // 广告与赞助管理(管理员及以上;可账号级授予) PermSettings = "settings" // 站点外观设置(超管/站长) PermModeration = "moderation" // 内容审核(任意管理角色,板块范围受限) PermMessages = "messages" // 后台消息管理(站长/超管/站点消息 flag;群管另有业务层放行) PermBoards = "boards" // 板块管理(仅站长) ) // GrantablePerms 可账号级授予的渠道权限码(授予者需持有 PermUsers)。 // 管理员及以上角色对这些渠道为角色自带,账号级授予只对板块管理员/普通用户生效。 var GrantablePerms = []string{PermAnnouncements, PermPages, PermLibrary, PermAds} // ValidPermOverride 权限码是否可被账号级授予 func ValidPermOverride(p string) bool { for _, g := range GrantablePerms { if p == g { return true } } return false } // NormalizePermOverrides 白名单过滤 + 去重 + 排序,得到可落库的权限码集 func NormalizePermOverrides(perms []string) []string { seen := map[string]bool{} out := make([]string, 0, len(perms)) for _, p := range perms { p = strings.TrimSpace(p) if p == "" || seen[p] || !ValidPermOverride(p) { continue } seen[p] = true out = append(out, p) } sort.Strings(out) return out } // ParsePermOverrides 解析 users.perm_overrides JSON 列(容错:非法内容视为空) func ParsePermOverrides(raw string) []string { if raw == "" { return []string{} } var arr []string if err := json.Unmarshal([]byte(raw), &arr); err != nil { return []string{} } return NormalizePermOverrides(arr) } // SerializePermOverrides 序列化为落库 JSON(空集统一 "[]",避免 NULL/空串分歧) func SerializePermOverrides(perms []string) string { norm := NormalizePermOverrides(perms) b, err := json.Marshal(norm) if err != nil { return "[]" } return string(b) } // roleDefaultPerm 角色对功能点的固有权限(不含账号级授予) func roleDefaultPerm(role model.Role, p string) bool { switch p { case PermBoards: return role == model.RoleOwner case PermUsers, PermSettings: return role == model.RoleSuperAdmin || role == model.RoleOwner case PermAnnouncements, PermPages, PermLibrary, PermAds: return model.RoleLevel(role) >= model.RoleLevel(model.RoleAdmin) case PermModeration: return model.IsStaff(role) } return false } // EffectivePerms 生效权限码列表(角色默认 ∪ 账号授予 + 消息管理), // 供 /me 下发,前端据此渲染后台入口/按钮;最终权限仍以 HasPerm 校验为准。 func EffectivePerms(role model.Role, permOverridesRaw string, canManageMessages bool) []string { set := map[string]bool{} all := append([]string{ PermUsers, PermAnnouncements, PermPages, PermLibrary, PermAds, PermSettings, PermModeration, PermBoards, }, GrantablePerms...) for _, p := range all { if roleDefaultPerm(role, p) { set[p] = true } } for _, p := range ParsePermOverrides(permOverridesRaw) { set[p] = true } if role == model.RoleOwner || role == model.RoleSuperAdmin || canManageMessages { set[PermMessages] = true } out := make([]string, 0, len(set)) for p := range set { out = append(out, p) } sort.Strings(out) return out } // Actor 当前请求操作者的实时权限快照(每次后台请求从 DB 现取, // 不依赖 JWT 内的 role claim,角色/授权变更立即生效) type Actor struct { ID uint Username string Role model.Role BoardIDs []uint // 板块管理员被授权的板块;其他角色为空 CanManageMessages bool // 站点级消息管理 flag(站长授予);站长/超管不必依赖此字段 ExtraPerms []string // 账号级授予的渠道权限(角色固有之外的增量) } // IsStaff 是否管理团队成员 func (a *Actor) IsStaff() bool { return a != nil && model.IsStaff(a.Role) } // HasPerm 是否拥有某后台功能点:角色固有权限 ∪ 账号级授予 func (a *Actor) HasPerm(p string) bool { if a == nil { return false } if roleDefaultPerm(a.Role, p) { return true } if p == PermMessages { return a.HasSiteMessagePerm() } for _, e := range a.ExtraPerms { if e == p { return true } } return false } // HasSiteMessagePerm 站点级消息管理:站长/超管固有,或被授予 CanManageMessages func (a *Actor) HasSiteMessagePerm() bool { if a == nil { return false } if a.Role == model.RoleOwner || a.Role == model.RoleSuperAdmin { return true } return a.CanManageMessages } // CanPurgeContent 彻底删除帖/评:仅站长 func (a *Actor) CanPurgeContent() bool { return a != nil && a.Role == model.RoleOwner } // CanModerateBoard 是否可管理指定板块的内容: // 管理员及以上不限板块;板块管理员仅限被授权板块 func (a *Actor) CanModerateBoard(boardID uint) bool { if a == nil { return false } if model.RoleLevel(a.Role) >= model.RoleLevel(model.RoleAdmin) { return true } if a.Role != model.RoleBoardAdmin { return false } for _, id := range a.BoardIDs { if id == boardID { return true } } return false } // CanModerateAuthor 板管不可软删/恢复管理员及以上作者的内容;其他角色不受此限 func (a *Actor) CanModerateAuthor(authorRole model.Role) bool { if a == nil { return false } if a.Role == model.RoleBoardAdmin { return model.RoleLevel(authorRole) < model.RoleLevel(model.RoleAdmin) } return true } // CanAssignRole 操作者能否把他人设置为目标角色(站长不可被授予,仅站长可授予超管) func (a *Actor) CanAssignRole(target model.Role) bool { if a == nil { return false } if target == model.RoleOwner { return false // 站长身份不通过授权产生 } if target == model.RoleSuperAdmin { return a.Role == model.RoleOwner } return a.HasPerm(PermUsers) } // LoadActor 读取用户实时角色、板块授权与账号级渠道权限 func (s *AuthService) LoadActor(id uint) (*Actor, error) { var u model.User if err := s.db.Select("id", "username", "role", "can_manage_messages", "perm_overrides").First(&u, id).Error; err != nil { return nil, err } actor := &Actor{ ID: u.ID, Username: u.Username, Role: u.Role, BoardIDs: []uint{}, CanManageMessages: u.CanManageMessages, ExtraPerms: ParsePermOverrides(u.PermOverrides), } if u.Role == model.RoleBoardAdmin { var ids []uint if err := s.db.Model(&model.UserBoard{}). Where("user_id = ?", id). Order("board_id ASC"). Pluck("board_id", &ids).Error; err != nil { return nil, err } actor.BoardIDs = ids } return actor, nil } // GetUserRole 读取用户角色(内容作者保护用);用户不存在返回 user func (s *AuthService) GetUserRole(id uint) (model.Role, error) { var u model.User if err := s.db.Select("role").First(&u, id).Error; err != nil { return model.RoleUser, err } return u.Role, nil } // GetUserBoardIDs 读取用户被授权的板块 ID(供 /me 等场景) func (s *AuthService) GetUserBoardIDs(id uint) ([]uint, error) { var ids []uint err := s.db.Model(&model.UserBoard{}). Where("user_id = ?", id).Order("board_id ASC").Pluck("board_id", &ids).Error return ids, err } // GetUserIDByUsername 按用户名查 ID(登录失败审计用),用户不存在返回 0 func (s *AuthService) GetUserIDByUsername(username string) uint { username = strings.TrimSpace(username) if username == "" { return 0 } var u model.User if err := s.db.Select("id").Where("username = ?", username).First(&u).Error; err != nil { return 0 } return u.ID } // ListBoardModeratorIDs 可审核指定板块的用户 ID: // 站长/超管/全站管理员 + 被授权该板的板块管理员(未封禁、未删除) func (s *AuthService) ListBoardModeratorIDs(boardID uint) ([]uint, error) { var ids []uint // 全站可审:admin / super_admin / owner if err := s.db.Model(&model.User{}). Where("deleted_at IS NULL AND banned = ? AND role IN ?", false, []string{string(model.RoleAdmin), string(model.RoleSuperAdmin), string(model.RoleOwner)}). Pluck("id", &ids).Error; err != nil { return nil, err } // 板块管理员(授权该板) var boardAdminIDs []uint if err := s.db.Model(&model.UserBoard{}). Select("user_boards.user_id"). Joins("JOIN users ON users.id = user_boards.user_id"). Where("user_boards.board_id = ? AND users.deleted_at IS NULL AND users.banned = ? AND users.role = ?", boardID, false, string(model.RoleBoardAdmin)). Pluck("user_boards.user_id", &boardAdminIDs).Error; err != nil { return nil, err } seen := make(map[uint]struct{}, len(ids)+len(boardAdminIDs)) out := make([]uint, 0, len(ids)+len(boardAdminIDs)) for _, id := range ids { if _, ok := seen[id]; ok { continue } seen[id] = struct{}{} out = append(out, id) } for _, id := range boardAdminIDs { if _, ok := seen[id]; ok { continue } seen[id] = struct{}{} out = append(out, id) } return out, nil }