新增: - 书库导入导出(library_import/library_export)及测试 - 图片变体生成(image_variants)与响应式图片(responsiveImage) - 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件 - 小组件运行时(widgetRuntime)与静态检查(widgetLint) - 上传缓存中间件(upload_cache)与字体 CSS 提取脚本 其它: - 后端 handlers/services 全量调整 - 前端页面、组件、库函数与配置更新
308 lines
9.5 KiB
Go
308 lines
9.5 KiB
Go
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
|
||
}
|