Files
jiang13-bbs/backend/service/actor.go
freefire 3e55b5d230 feat: 管理端板块管理与旧版数据导入,补充部署运营文档
- 新增管理端板块管理页面与后端接口(admin_board)
- 新增旧版数据导入:legacyimport 服务、导入面板、importusers 命令行工具
- 聊天用户卡片、板块图标等 UI 组件与界面优化
- 补充 about/公告/1Panel 部署等文档
- gitignore 排除 dist/ 构建产物与 .agents/ 本地工具目录
2026-09-24 03:37:44 +08:00

208 lines
6.3 KiB
Go
Raw Permalink 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" // 内容审核(任意管理角色,板块范围受限)
PermMessages = "messages" // 后台消息管理(站长/超管/站点消息 flag;群管另有业务层放行)
PermBoards = "boards" // 板块管理(仅站长)
)
// Actor 当前请求操作者的实时权限快照(每次后台请求从 DB 现取,
// 不依赖 JWT 内的 role claim,角色/授权变更立即生效)
type Actor struct {
ID uint
Username string
Role model.Role
BoardIDs []uint // 板块管理员被授权的板块;其他角色为空
CanManageMessages bool // 站点级消息管理 flag(站长授予);站长/超管不必依赖此字段
}
// 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 PermBoards:
return a.Role == model.RoleOwner
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()
case PermMessages:
return a.HasSiteMessagePerm()
}
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").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,
}
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
}