Files
jiang13-bbs/backend/service/actor.go
freefire 42c395a864 feat: 实时消息与管理端运营能力
新增群聊/私信与公共 WebSocket 总线,站长用户内容档案,并将通知与待审角标改为推送驱动;同步精简管理仪表盘并修复主题换肤 DOM 冲突。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 22:58:16 +08:00

162 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"strings"
"github.com/freefire/jiang13-bbs/model"
)
// 权限码:后台功能点。路由通过 RequirePerm 声明,
// 板块管理员的板块范围由 Actor.BoardIDs 在业务层二次校验。
const (
PermUsers = "users" // 用户与权限管理(超管/站长)
PermAnnouncements = "announcements" // 公告管理(管理员及以上)
PermSettings = "settings" // 站点外观设置(超管/站长)
PermModeration = "moderation" // 内容审核(任意管理角色,板块范围受限)
)
// Actor 当前请求操作者的实时权限快照(每次后台请求从 DB 现取,
// 不依赖 JWT 内的 role claim,角色/授权变更立即生效)
type Actor struct {
ID uint
Username string
Role model.Role
BoardIDs []uint // 板块管理员被授权的板块;其他角色为空
}
// 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
}
switch p {
case PermUsers, PermSettings:
return a.Role == model.RoleSuperAdmin || a.Role == model.RoleOwner
case PermAnnouncements:
return model.RoleLevel(a.Role) >= model.RoleLevel(model.RoleAdmin)
case PermModeration:
return a.IsStaff()
}
return false
}
// 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
}
// 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").First(&u, id).Error; err != nil {
return nil, err
}
actor := &Actor{ID: u.ID, Username: u.Username, Role: u.Role, BoardIDs: []uint{}}
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
}
// 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
}