增加权限系统
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"hfb_sys/backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// RequirePermission 检查当前管理员是否拥有指定权限。
|
||||
// 超级管理员(拥有 super_admin 角色的管理员)自动放行。
|
||||
func RequirePermission(permCode string, rdb *redis.Client) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
value, ok := c.Get(ContextAdminID)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少管理员上下文")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
adminID, ok := value.(uint64)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "管理员上下文无效")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
codes, err := getPermCodes(c, rdb, adminID)
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "perm_check_failed", "权限校验服务暂时不可用")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
for _, code := range codes {
|
||||
if code == permCode || code == "*" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
response.Error(c, http.StatusForbidden, "forbidden", "没有操作权限")
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
func getPermCodes(c *gin.Context, rdb *redis.Client, adminID uint64) ([]string, error) {
|
||||
if rdb == nil {
|
||||
return nil, errors.New("redis unavailable")
|
||||
}
|
||||
ctx := context.Background()
|
||||
key := fmt.Sprintf("admin:perms:%d", adminID)
|
||||
raw, err := rdb.Get(ctx, key).Result()
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return nil, nil // 缓存未命中,视为无权限
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var codes []string
|
||||
if err := json.Unmarshal([]byte(raw), &codes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return codes, nil
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Role struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
Code string `gorm:"size:64;not null;uniqueIndex" json:"code"`
|
||||
Name string `gorm:"size:64;not null" json:"name"`
|
||||
Description string `gorm:"size:255;not null;default:''" json:"description"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (Role) TableName() string { return "roles" }
|
||||
|
||||
type Permission struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
Code string `gorm:"size:128;not null;uniqueIndex" json:"code"`
|
||||
Name string `gorm:"size:64;not null" json:"name"`
|
||||
Resource string `gorm:"size:128;not null" json:"resource"`
|
||||
Action string `gorm:"size:64;not null" json:"action"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (Permission) TableName() string { return "permissions" }
|
||||
|
||||
type AdminUserRole struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
AdminID uint64 `gorm:"column:admin_user_id;not null;uniqueIndex:uk_admin_user_roles_user_role" json:"admin_id"`
|
||||
RoleID uint64 `gorm:"column:role_id;not null;uniqueIndex:uk_admin_user_roles_user_role" json:"role_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (AdminUserRole) TableName() string { return "admin_user_roles" }
|
||||
|
||||
type RolePermission struct {
|
||||
ID uint64 `gorm:"primaryKey" json:"id"`
|
||||
RoleID uint64 `gorm:"column:role_id;not null;uniqueIndex:uk_role_permissions_role_permission" json:"role_id"`
|
||||
PermissionID uint64 `gorm:"column:permission_id;not null;uniqueIndex:uk_role_permissions_role_permission" json:"permission_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func (RolePermission) TableName() string { return "role_permissions" }
|
||||
@@ -11,9 +11,17 @@ type AdminDTO struct {
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Status string `json:"status"`
|
||||
Roles []RoleDTO `json:"roles"`
|
||||
Permissions []string `json:"permissions"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
}
|
||||
|
||||
type RoleDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
@@ -87,7 +88,9 @@ func (r *Repository) Login(username string, password string, captchaID string, c
|
||||
if err != nil {
|
||||
return LoginResult{}, err
|
||||
}
|
||||
return LoginResult{Admin: toDTO(admin), Tokens: tokens}, nil
|
||||
dto := toDTO(admin)
|
||||
r.loadRolesAndPerms(&dto)
|
||||
return LoginResult{Admin: dto, Tokens: tokens}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) verifyCaptcha(captchaID string, captchaCode string) error {
|
||||
@@ -119,6 +122,7 @@ func (r *Repository) FindByID(id uint64) (*AdminDTO, error) {
|
||||
return nil, ErrAdminDisabled
|
||||
}
|
||||
dto := toDTO(admin)
|
||||
r.loadRolesAndPerms(&dto)
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
@@ -153,6 +157,37 @@ func toDTO(admin model.AdminUser) AdminDTO {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Repository) loadRolesAndPerms(dto *AdminDTO) {
|
||||
if r.db == nil {
|
||||
return
|
||||
}
|
||||
// 加载角色
|
||||
var roles []RoleDTO
|
||||
r.db.Table("roles").
|
||||
Joins("JOIN admin_user_roles aur ON aur.role_id = roles.id").
|
||||
Where("aur.admin_user_id = ?", dto.ID).
|
||||
Find(&roles)
|
||||
dto.Roles = roles
|
||||
|
||||
// 加载权限
|
||||
var permCodes []string
|
||||
r.db.Table("permissions").
|
||||
Select("DISTINCT permissions.code").
|
||||
Joins("JOIN role_permissions rp ON rp.permission_id = permissions.id").
|
||||
Joins("JOIN admin_user_roles aur ON aur.role_id = rp.role_id").
|
||||
Where("aur.admin_user_id = ?", dto.ID).
|
||||
Pluck("code", &permCodes)
|
||||
dto.Permissions = permCodes
|
||||
|
||||
// 缓存权限到 Redis
|
||||
if r.redis != nil && len(permCodes) > 0 {
|
||||
ctx := context.Background()
|
||||
key := fmt.Sprintf("admin:perms:%d", dto.ID)
|
||||
raw, _ := json.Marshal(permCodes)
|
||||
r.redis.Set(ctx, key, string(raw), 2*time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func captchaKey(id string) string {
|
||||
return "admin:captcha:" + id
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package adminmgr
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"hfb_sys/backend/internal/modules/adminrole"
|
||||
)
|
||||
|
||||
type AdminUserDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Status string `json:"status"`
|
||||
Roles []adminrole.RoleDTO `json:"roles"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PaginatedResult struct {
|
||||
Items []AdminUserDTO `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type CreateAdminRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
type UpdateAdminRequest struct {
|
||||
Nickname string `json:"nickname"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type AssignRolesRequest struct {
|
||||
RoleIDs []uint64 `json:"role_ids"`
|
||||
}
|
||||
|
||||
type ChangePasswordRequest struct {
|
||||
OldPassword string `json:"old_password" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package adminmgr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"hfb_sys/backend/internal/middleware"
|
||||
"hfb_sys/backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func NewHandler(service *Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
func (h *Handler) List(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
result, err := h.service.List(page, pageSize)
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) FindByID(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
item, err := h.service.FindByID(id)
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) Create(c *gin.Context) {
|
||||
var req CreateAdminRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "用户名和密码不能为空")
|
||||
return
|
||||
}
|
||||
item, err := h.service.Create(req)
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.Created(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) Update(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req UpdateAdminRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "请求格式不正确")
|
||||
return
|
||||
}
|
||||
item, err := h.service.Update(id, req)
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) Delete(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
currentAdminID, _ := c.Get(middleware.ContextAdminID)
|
||||
if err := h.service.Delete(id, currentAdminID.(uint64)); err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
func (h *Handler) AssignRoles(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req AssignRolesRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "请求格式不正确")
|
||||
return
|
||||
}
|
||||
if err := h.service.AssignRoles(id, req); err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
func (h *Handler) ChangePassword(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req ChangePasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "密码不能为空")
|
||||
return
|
||||
}
|
||||
if err := h.service.ChangePassword(id, req); err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
func parseID(c *gin.Context) (uint64, bool) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "ID 不正确")
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
}
|
||||
|
||||
func writeError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "数据库未连接")
|
||||
case errors.Is(err, ErrCannotDeleteSelf):
|
||||
response.BadRequest(c, "不能删除自己的账号")
|
||||
case errors.Is(err, ErrLastSuperAdmin):
|
||||
response.BadRequest(c, "不能删除最后一个超级管理员")
|
||||
case errors.Is(err, ErrWrongPassword):
|
||||
response.BadRequest(c, "原密码错误")
|
||||
case IsNotFound(err):
|
||||
response.Error(c, http.StatusNotFound, "not_found", "管理员不存在")
|
||||
default:
|
||||
response.Error(c, http.StatusInternalServerError, "internal_error", "管理员管理服务暂时不可用")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package adminmgr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
"hfb_sys/backend/internal/modules/adminrole"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB, redis *redis.Client) *Repository {
|
||||
return &Repository{db: db, redis: redis}
|
||||
}
|
||||
|
||||
func (r *Repository) List(page, pageSize int) (*PaginatedResult, error) {
|
||||
var total int64
|
||||
if err := r.db.Model(&model.AdminUser{}).Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
var admins []model.AdminUser
|
||||
if err := r.db.Order("id DESC").Offset(offset).Limit(pageSize).Find(&admins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]AdminUserDTO, 0, len(admins))
|
||||
for _, admin := range admins {
|
||||
roles, _ := r.getAdminRoles(admin.ID)
|
||||
items = append(items, AdminUserDTO{
|
||||
ID: admin.ID,
|
||||
Username: admin.Username,
|
||||
Nickname: admin.Nickname,
|
||||
Status: admin.Status,
|
||||
Roles: roles,
|
||||
LastLoginAt: admin.LastLoginAt,
|
||||
CreatedAt: admin.CreatedAt,
|
||||
UpdatedAt: admin.UpdatedAt,
|
||||
})
|
||||
}
|
||||
return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
|
||||
}
|
||||
|
||||
func (r *Repository) FindByID(id uint64) (*AdminUserDTO, error) {
|
||||
var admin model.AdminUser
|
||||
if err := r.db.First(&admin, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roles, _ := r.getAdminRoles(admin.ID)
|
||||
dto := AdminUserDTO{
|
||||
ID: admin.ID,
|
||||
Username: admin.Username,
|
||||
Nickname: admin.Nickname,
|
||||
Status: admin.Status,
|
||||
Roles: roles,
|
||||
LastLoginAt: admin.LastLoginAt,
|
||||
CreatedAt: admin.CreatedAt,
|
||||
UpdatedAt: admin.UpdatedAt,
|
||||
}
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func (r *Repository) Create(req CreateAdminRequest) (*AdminUserDTO, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
admin := model.AdminUser{
|
||||
Username: req.Username,
|
||||
PasswordHash: string(hash),
|
||||
Nickname: req.Nickname,
|
||||
Status: "active",
|
||||
}
|
||||
if err := r.db.Create(&admin).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindByID(admin.ID)
|
||||
}
|
||||
|
||||
func (r *Repository) Update(id uint64, req UpdateAdminRequest) (*AdminUserDTO, error) {
|
||||
var admin model.AdminUser
|
||||
if err := r.db.First(&admin, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Nickname != "" {
|
||||
admin.Nickname = req.Nickname
|
||||
}
|
||||
if req.Status != "" {
|
||||
admin.Status = req.Status
|
||||
}
|
||||
if err := r.db.Save(&admin).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindByID(id)
|
||||
}
|
||||
|
||||
func (r *Repository) Delete(id uint64, currentAdminID uint64) error {
|
||||
if id == currentAdminID {
|
||||
return ErrCannotDeleteSelf
|
||||
}
|
||||
var admin model.AdminUser
|
||||
if err := r.db.First(&admin, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 检查是否是最后一个 super_admin
|
||||
isLastSuper, err := r.isLastSuperAdmin(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isLastSuper {
|
||||
return ErrLastSuperAdmin
|
||||
}
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("admin_user_id = ?", id).Delete(&model.AdminUserRole{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&model.AdminUser{}, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
r.invalidatePermCache(id)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) AssignRoles(adminID uint64, roleIDs []uint64) error {
|
||||
var admin model.AdminUser
|
||||
if err := r.db.First(&admin, adminID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("admin_user_id = ?", adminID).Delete(&model.AdminUserRole{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rid := range roleIDs {
|
||||
aur := model.AdminUserRole{AdminID: adminID, RoleID: rid}
|
||||
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&aur).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.invalidatePermCache(adminID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) ChangePassword(id uint64, oldPwd, newPwd string) error {
|
||||
var admin model.AdminUser
|
||||
if err := r.db.First(&admin, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(admin.PasswordHash), []byte(oldPwd)); err != nil {
|
||||
return ErrWrongPassword
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(newPwd), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.db.Model(&admin).Update("password_hash", string(hash)).Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetPermissionCodes(adminID uint64) ([]string, error) {
|
||||
var codes []string
|
||||
err := r.db.Table("admin_user_roles aur").
|
||||
Select("DISTINCT p.code").
|
||||
Joins("JOIN role_permissions rp ON rp.role_id = aur.role_id").
|
||||
Joins("JOIN permissions p ON p.id = rp.permission_id").
|
||||
Where("aur.admin_user_id = ?", adminID).
|
||||
Pluck("code", &codes).Error
|
||||
return codes, err
|
||||
}
|
||||
|
||||
func (r *Repository) getAdminRoles(adminID uint64) ([]adminrole.RoleDTO, error) {
|
||||
var roles []model.Role
|
||||
err := r.db.Table("roles").
|
||||
Joins("JOIN admin_user_roles aur ON aur.role_id = roles.id").
|
||||
Where("aur.admin_user_id = ?", adminID).
|
||||
Find(&roles).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]adminrole.RoleDTO, 0, len(roles))
|
||||
for _, role := range roles {
|
||||
result = append(result, adminrole.RoleDTO{
|
||||
ID: role.ID, Code: role.Code, Name: role.Name, Description: role.Description,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) isLastSuperAdmin(adminID uint64) (bool, error) {
|
||||
var superRole model.Role
|
||||
if err := r.db.Where("code = ?", "super_admin").First(&superRole).Error; err != nil {
|
||||
return false, nil // 没有 super_admin 角色,不限制
|
||||
}
|
||||
var count int64
|
||||
err := r.db.Model(&model.AdminUserRole{}).
|
||||
Where("role_id = ? AND admin_user_id != ?", superRole.ID, adminID).
|
||||
Count(&count).Error
|
||||
return count == 0, err
|
||||
}
|
||||
|
||||
func (r *Repository) invalidatePermCache(adminID uint64) {
|
||||
if r.redis == nil {
|
||||
return
|
||||
}
|
||||
ctx := context.Background()
|
||||
r.redis.Del(ctx, permCacheKey(adminID))
|
||||
}
|
||||
|
||||
func permCacheKey(adminID uint64) string {
|
||||
return fmt.Sprintf("admin:perms:%d", adminID)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrCannotDeleteSelf = errors.New("cannot delete self")
|
||||
ErrLastSuperAdmin = errors.New("cannot delete the last super admin")
|
||||
ErrWrongPassword = errors.New("wrong password")
|
||||
)
|
||||
|
||||
func IsNotFound(err error) bool {
|
||||
return errors.Is(err, gorm.ErrRecordNotFound)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package adminmgr
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrDependencyUnavailable = errors.New("dependency unavailable")
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
}
|
||||
|
||||
func NewService(repo *Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) List(page, pageSize int) (*PaginatedResult, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
return s.repo.List(page, pageSize)
|
||||
}
|
||||
|
||||
func (s *Service) FindByID(id uint64) (*AdminUserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.FindByID(id)
|
||||
}
|
||||
|
||||
func (s *Service) Create(req CreateAdminRequest) (*AdminUserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if len(req.Password) < 6 {
|
||||
return nil, errors.New("password too short")
|
||||
}
|
||||
return s.repo.Create(req)
|
||||
}
|
||||
|
||||
func (s *Service) Update(id uint64, req UpdateAdminRequest) (*AdminUserDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.Update(id, req)
|
||||
}
|
||||
|
||||
func (s *Service) Delete(id uint64, currentAdminID uint64) error {
|
||||
if s.repo == nil {
|
||||
return ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.Delete(id, currentAdminID)
|
||||
}
|
||||
|
||||
func (s *Service) AssignRoles(adminID uint64, req AssignRolesRequest) error {
|
||||
if s.repo == nil {
|
||||
return ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.AssignRoles(adminID, req.RoleIDs)
|
||||
}
|
||||
|
||||
func (s *Service) ChangePassword(id uint64, req ChangePasswordRequest) error {
|
||||
if s.repo == nil {
|
||||
return ErrDependencyUnavailable
|
||||
}
|
||||
if len(req.NewPassword) < 6 {
|
||||
return errors.New("new password too short")
|
||||
}
|
||||
return s.repo.ChangePassword(id, req.OldPassword, req.NewPassword)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package adminrole
|
||||
|
||||
import "time"
|
||||
|
||||
type RoleDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
PermCount int `json:"perm_count"`
|
||||
Permissions []PermissionDTO `json:"permissions,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PermissionDTO struct {
|
||||
ID uint64 `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
Resource string `json:"resource"`
|
||||
Action string `json:"action"`
|
||||
}
|
||||
|
||||
type CreateRoleRequest struct {
|
||||
Code string `json:"code" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type UpdateRoleRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type AssignPermissionsRequest struct {
|
||||
PermissionIDs []uint64 `json:"permission_ids"`
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package adminrole
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"hfb_sys/backend/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
service *Service
|
||||
}
|
||||
|
||||
func NewHandler(service *Service) *Handler {
|
||||
return &Handler{service: service}
|
||||
}
|
||||
|
||||
func (h *Handler) List(c *gin.Context) {
|
||||
items, err := h.service.List()
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, items)
|
||||
}
|
||||
|
||||
func (h *Handler) FindByID(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
item, err := h.service.FindByID(id)
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) Create(c *gin.Context) {
|
||||
var req CreateRoleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "角色编码和名称不能为空")
|
||||
return
|
||||
}
|
||||
item, err := h.service.Create(req)
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.Created(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) Update(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req UpdateRoleRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "角色名称不能为空")
|
||||
return
|
||||
}
|
||||
item, err := h.service.Update(id, req)
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) Delete(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := h.service.Delete(id); err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"deleted": true})
|
||||
}
|
||||
|
||||
func (h *Handler) AssignPermissions(c *gin.Context) {
|
||||
id, ok := parseID(c)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var req AssignPermissionsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "请求格式不正确")
|
||||
return
|
||||
}
|
||||
if err := h.service.AssignPermissions(id, req); err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"updated": true})
|
||||
}
|
||||
|
||||
func (h *Handler) ListPermissions(c *gin.Context) {
|
||||
items, err := h.service.ListPermissions()
|
||||
if err != nil {
|
||||
writeError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, items)
|
||||
}
|
||||
|
||||
func parseID(c *gin.Context) (uint64, bool) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.BadRequest(c, "ID 不正确")
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
}
|
||||
|
||||
func writeError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "数据库未连接")
|
||||
case errors.Is(err, ErrProtectedRole):
|
||||
response.BadRequest(c, "该角色为系统内置角色,不可删除")
|
||||
case IsNotFound(err):
|
||||
response.Error(c, http.StatusNotFound, "not_found", "角色不存在")
|
||||
default:
|
||||
response.Error(c, http.StatusInternalServerError, "internal_error", "角色管理服务暂时不可用")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package adminrole
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"hfb_sys/backend/internal/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
}
|
||||
|
||||
func (r *Repository) List() ([]RoleDTO, error) {
|
||||
var roles []model.Role
|
||||
if err := r.db.Order("id ASC").Find(&roles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]RoleDTO, 0, len(roles))
|
||||
for _, role := range roles {
|
||||
var count int64
|
||||
r.db.Model(&model.RolePermission{}).Where("role_id = ?", role.ID).Count(&count)
|
||||
result = append(result, RoleDTO{
|
||||
ID: role.ID,
|
||||
Code: role.Code,
|
||||
Name: role.Name,
|
||||
Description: role.Description,
|
||||
PermCount: int(count),
|
||||
CreatedAt: role.CreatedAt,
|
||||
UpdatedAt: role.UpdatedAt,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) FindByID(id uint64) (*RoleDTO, error) {
|
||||
var role model.Role
|
||||
if err := r.db.First(&role, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
perms, err := r.getRolePermissions(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dto := RoleDTO{
|
||||
ID: role.ID,
|
||||
Code: role.Code,
|
||||
Name: role.Name,
|
||||
Description: role.Description,
|
||||
PermCount: len(perms),
|
||||
Permissions: perms,
|
||||
CreatedAt: role.CreatedAt,
|
||||
UpdatedAt: role.UpdatedAt,
|
||||
}
|
||||
return &dto, nil
|
||||
}
|
||||
|
||||
func (r *Repository) Create(req CreateRoleRequest) (*RoleDTO, error) {
|
||||
role := model.Role{
|
||||
Code: req.Code,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
}
|
||||
if err := r.db.Create(&role).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindByID(role.ID)
|
||||
}
|
||||
|
||||
func (r *Repository) Update(id uint64, req UpdateRoleRequest) (*RoleDTO, error) {
|
||||
var role model.Role
|
||||
if err := r.db.First(&role, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
role.Name = req.Name
|
||||
role.Description = req.Description
|
||||
if err := r.db.Save(&role).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.FindByID(id)
|
||||
}
|
||||
|
||||
func (r *Repository) Delete(id uint64) error {
|
||||
var role model.Role
|
||||
if err := r.db.First(&role, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if role.Code == "super_admin" {
|
||||
return ErrProtectedRole
|
||||
}
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("role_id = ?", id).Delete(&model.RolePermission{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("role_id = ?", id).Delete(&model.AdminUserRole{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Delete(&model.Role{}, id).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) AssignPermissions(roleID uint64, permIDs []uint64) error {
|
||||
var role model.Role
|
||||
if err := r.db.First(&role, roleID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("role_id = ?", roleID).Delete(&model.RolePermission{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pid := range permIDs {
|
||||
rp := model.RolePermission{RoleID: roleID, PermissionID: pid}
|
||||
if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&rp).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Repository) ListPermissions() ([]PermissionDTO, error) {
|
||||
var perms []model.Permission
|
||||
if err := r.db.Order("resource ASC, action ASC").Find(&perms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]PermissionDTO, 0, len(perms))
|
||||
for _, p := range perms {
|
||||
result = append(result, PermissionDTO{
|
||||
ID: p.ID, Code: p.Code, Name: p.Name, Resource: p.Resource, Action: p.Action,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) getRolePermissions(roleID uint64) ([]PermissionDTO, error) {
|
||||
var rps []model.RolePermission
|
||||
if err := r.db.Where("role_id = ?", roleID).Find(&rps).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rps) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
ids := make([]uint64, 0, len(rps))
|
||||
for _, rp := range rps {
|
||||
ids = append(ids, rp.PermissionID)
|
||||
}
|
||||
var perms []model.Permission
|
||||
if err := r.db.Where("id IN ?", ids).Find(&perms).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]PermissionDTO, 0, len(perms))
|
||||
for _, p := range perms {
|
||||
result = append(result, PermissionDTO{
|
||||
ID: p.ID, Code: p.Code, Name: p.Name, Resource: p.Resource, Action: p.Action,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var ErrProtectedRole = errors.New("protected role")
|
||||
|
||||
func IsNotFound(err error) bool {
|
||||
return errors.Is(err, gorm.ErrRecordNotFound)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package adminrole
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrDependencyUnavailable = errors.New("dependency unavailable")
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
}
|
||||
|
||||
func NewService(repo *Repository) *Service {
|
||||
return &Service{repo: repo}
|
||||
}
|
||||
|
||||
func (s *Service) List() ([]RoleDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.List()
|
||||
}
|
||||
|
||||
func (s *Service) FindByID(id uint64) (*RoleDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.FindByID(id)
|
||||
}
|
||||
|
||||
func (s *Service) Create(req CreateRoleRequest) (*RoleDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
if req.Code == "" || req.Name == "" {
|
||||
return nil, errors.New("code and name are required")
|
||||
}
|
||||
return s.repo.Create(req)
|
||||
}
|
||||
|
||||
func (s *Service) Update(id uint64, req UpdateRoleRequest) (*RoleDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.Update(id, req)
|
||||
}
|
||||
|
||||
func (s *Service) Delete(id uint64) error {
|
||||
if s.repo == nil {
|
||||
return ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.Delete(id)
|
||||
}
|
||||
|
||||
func (s *Service) AssignPermissions(roleID uint64, req AssignPermissionsRequest) error {
|
||||
if s.repo == nil {
|
||||
return ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.AssignPermissions(roleID, req.PermissionIDs)
|
||||
}
|
||||
|
||||
func (s *Service) ListPermissions() ([]PermissionDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.ListPermissions()
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"hfb_sys/backend/internal/modules/adminaudit"
|
||||
"hfb_sys/backend/internal/modules/adminauth"
|
||||
"hfb_sys/backend/internal/modules/admindashboard"
|
||||
"hfb_sys/backend/internal/modules/adminmgr"
|
||||
"hfb_sys/backend/internal/modules/adminrole"
|
||||
"hfb_sys/backend/internal/modules/adminuser"
|
||||
"hfb_sys/backend/internal/modules/auth"
|
||||
"hfb_sys/backend/internal/modules/chat"
|
||||
@@ -114,6 +116,18 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
}
|
||||
systemConfigService := systemconfig.NewService(systemConfigRepo)
|
||||
systemConfigHandler := systemconfig.NewHandler(systemConfigService)
|
||||
var adminRoleRepo *adminrole.Repository
|
||||
if deps.DB != nil {
|
||||
adminRoleRepo = adminrole.NewRepository(deps.DB)
|
||||
}
|
||||
adminRoleService := adminrole.NewService(adminRoleRepo)
|
||||
adminRoleHandler := adminrole.NewHandler(adminRoleService)
|
||||
var adminMgrRepo *adminmgr.Repository
|
||||
if deps.DB != nil {
|
||||
adminMgrRepo = adminmgr.NewRepository(deps.DB, deps.Redis)
|
||||
}
|
||||
adminMgrService := adminmgr.NewService(adminMgrRepo)
|
||||
adminMgrHandler := adminmgr.NewHandler(adminMgrService)
|
||||
var fileStorage *filemodule.Storage
|
||||
if cfg.Storage.Endpoint != "" && cfg.Storage.Bucket != "" {
|
||||
var err error
|
||||
@@ -129,6 +143,9 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
requireAuth := middleware.Auth(jwtManager)
|
||||
requireAdmin := middleware.AdminAuth(jwtManager)
|
||||
requireRealname := middleware.RequireRealname(userRepo)
|
||||
requirePerm := func(code string) gin.HandlerFunc {
|
||||
return middleware.RequirePermission(code, deps.Redis)
|
||||
}
|
||||
|
||||
api := engine.Group("/api")
|
||||
{
|
||||
@@ -240,34 +257,52 @@ func New(cfg config.Config, deps Dependencies, logger *zap.Logger) *gin.Engine {
|
||||
adminRoutes.GET("/me", adminAuthHandler.Me)
|
||||
adminRoutes.POST("/auth/logout", adminAuthHandler.Logout)
|
||||
adminRoutes.POST("/files/upload", fileHandler.Upload)
|
||||
adminRoutes.GET("/dashboard", adminDashboardHandler.Summary)
|
||||
adminRoutes.GET("/dashboard", requirePerm("dashboard:view"), adminDashboardHandler.Summary)
|
||||
adminRoutes.GET("/files/object", fileHandler.Object)
|
||||
adminRoutes.GET("/users", adminUserHandler.List)
|
||||
adminRoutes.POST("/users/:id/freeze", adminUserHandler.Freeze)
|
||||
adminRoutes.POST("/users/:id/unfreeze", adminUserHandler.Unfreeze)
|
||||
adminRoutes.GET("/orders", orderHandler.AdminList)
|
||||
adminRoutes.GET("/orders/:id", orderHandler.AdminDetail)
|
||||
adminRoutes.GET("/orders/:id/handoff-records", orderHandler.AdminHandoffRecords)
|
||||
adminRoutes.POST("/orders/:id/close", orderHandler.AdminClose)
|
||||
adminRoutes.POST("/orders/:id/mark-abnormal", orderHandler.AdminMarkAbnormal)
|
||||
adminRoutes.GET("/listings", listingHandler.ListAdmin)
|
||||
adminRoutes.GET("/listings/pending", listingHandler.ListPendingReview)
|
||||
adminRoutes.GET("/listings/:id", listingHandler.FindAdmin)
|
||||
adminRoutes.POST("/listings/:id/approve", listingHandler.Approve)
|
||||
adminRoutes.POST("/listings/:id/reject", listingHandler.Reject)
|
||||
adminRoutes.POST("/listings/:id/offline", listingHandler.AdminOffline)
|
||||
adminRoutes.POST("/listings/:id/mark-abnormal", listingHandler.AdminMarkAbnormal)
|
||||
adminRoutes.GET("/disputes", disputeHandler.AdminList)
|
||||
adminRoutes.POST("/disputes/:id/arbitrate", disputeHandler.AdminArbitrate)
|
||||
adminRoutes.GET("/wallet/ledger", walletHandler.AdminLedger)
|
||||
adminRoutes.GET("/system-configs", systemConfigHandler.List)
|
||||
adminRoutes.PUT("/system-configs/:key", systemConfigHandler.Update)
|
||||
adminRoutes.GET("/audit-logs", adminAuditHandler.List)
|
||||
adminRoutes.GET("/chats", chatHandler.AdminList)
|
||||
adminRoutes.GET("/chats/:id", chatHandler.AdminDetail)
|
||||
adminRoutes.GET("/chats/:id/messages", chatHandler.AdminMessages)
|
||||
adminRoutes.POST("/chats/:id/messages", chatHandler.AdminSend)
|
||||
adminRoutes.POST("/chats/:id/read", chatHandler.AdminMarkRead)
|
||||
adminRoutes.GET("/users", requirePerm("user:view"), adminUserHandler.List)
|
||||
adminRoutes.POST("/users/:id/freeze", requirePerm("user:freeze"), adminUserHandler.Freeze)
|
||||
adminRoutes.POST("/users/:id/unfreeze", requirePerm("user:unfreeze"), adminUserHandler.Unfreeze)
|
||||
adminRoutes.GET("/orders", requirePerm("order:view"), orderHandler.AdminList)
|
||||
adminRoutes.GET("/orders/:id", requirePerm("order:view"), orderHandler.AdminDetail)
|
||||
adminRoutes.GET("/orders/:id/handoff-records", requirePerm("order:view"), orderHandler.AdminHandoffRecords)
|
||||
adminRoutes.POST("/orders/:id/close", requirePerm("order:close"), orderHandler.AdminClose)
|
||||
adminRoutes.POST("/orders/:id/mark-abnormal", requirePerm("order:mark_abnormal"), orderHandler.AdminMarkAbnormal)
|
||||
adminRoutes.GET("/listings", requirePerm("listing:view"), listingHandler.ListAdmin)
|
||||
adminRoutes.GET("/listings/pending", requirePerm("listing:approve"), listingHandler.ListPendingReview)
|
||||
adminRoutes.GET("/listings/:id", requirePerm("listing:view"), listingHandler.FindAdmin)
|
||||
adminRoutes.POST("/listings/:id/approve", requirePerm("listing:approve"), listingHandler.Approve)
|
||||
adminRoutes.POST("/listings/:id/reject", requirePerm("listing:reject"), listingHandler.Reject)
|
||||
adminRoutes.POST("/listings/:id/offline", requirePerm("listing:offline"), listingHandler.AdminOffline)
|
||||
adminRoutes.POST("/listings/:id/mark-abnormal", requirePerm("listing:offline"), listingHandler.AdminMarkAbnormal)
|
||||
adminRoutes.GET("/disputes", requirePerm("dispute:view"), disputeHandler.AdminList)
|
||||
adminRoutes.POST("/disputes/:id/arbitrate", requirePerm("dispute:arbitrate"), disputeHandler.AdminArbitrate)
|
||||
adminRoutes.GET("/wallet/ledger", requirePerm("wallet:view"), walletHandler.AdminLedger)
|
||||
adminRoutes.GET("/system-configs", requirePerm("system_config:view"), systemConfigHandler.List)
|
||||
adminRoutes.PUT("/system-configs/:key", requirePerm("system_config:update"), systemConfigHandler.Update)
|
||||
adminRoutes.GET("/audit-logs", requirePerm("audit_log:view"), adminAuditHandler.List)
|
||||
adminRoutes.GET("/chats", requirePerm("chat:view"), chatHandler.AdminList)
|
||||
adminRoutes.GET("/chats/:id", requirePerm("chat:view"), chatHandler.AdminDetail)
|
||||
adminRoutes.GET("/chats/:id/messages", requirePerm("chat:view"), chatHandler.AdminMessages)
|
||||
adminRoutes.POST("/chats/:id/messages", requirePerm("chat:send"), chatHandler.AdminSend)
|
||||
adminRoutes.POST("/chats/:id/read", requirePerm("chat:view"), chatHandler.AdminMarkRead)
|
||||
|
||||
// 角色管理
|
||||
adminRoutes.GET("/roles", requirePerm("role:manage"), adminRoleHandler.List)
|
||||
adminRoutes.GET("/roles/:id", requirePerm("role:manage"), adminRoleHandler.FindByID)
|
||||
adminRoutes.POST("/roles", requirePerm("role:manage"), adminRoleHandler.Create)
|
||||
adminRoutes.PUT("/roles/:id", requirePerm("role:manage"), adminRoleHandler.Update)
|
||||
adminRoutes.DELETE("/roles/:id", requirePerm("role:manage"), adminRoleHandler.Delete)
|
||||
adminRoutes.PUT("/roles/:id/permissions", requirePerm("role:manage"), adminRoleHandler.AssignPermissions)
|
||||
adminRoutes.GET("/permissions", requirePerm("role:manage"), adminRoleHandler.ListPermissions)
|
||||
|
||||
// 管理员管理
|
||||
adminRoutes.GET("/admin-users", requirePerm("admin_user:manage"), adminMgrHandler.List)
|
||||
adminRoutes.GET("/admin-users/:id", requirePerm("admin_user:manage"), adminMgrHandler.FindByID)
|
||||
adminRoutes.POST("/admin-users", requirePerm("admin_user:manage"), adminMgrHandler.Create)
|
||||
adminRoutes.PUT("/admin-users/:id", requirePerm("admin_user:manage"), adminMgrHandler.Update)
|
||||
adminRoutes.DELETE("/admin-users/:id", requirePerm("admin_user:manage"), adminMgrHandler.Delete)
|
||||
adminRoutes.PUT("/admin-users/:id/roles", requirePerm("admin_user:manage"), adminMgrHandler.AssignRoles)
|
||||
adminRoutes.PUT("/admin-users/:id/password", adminMgrHandler.ChangePassword)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
-- RBAC seed: 默认角色和权限
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- 插入权限
|
||||
INSERT INTO permissions (code, name, resource, action) VALUES
|
||||
('dashboard:view', '查看仪表盘', 'dashboard', 'view'),
|
||||
('user:view', '查看用户', 'user', 'view'),
|
||||
('user:freeze', '冻结用户', 'user', 'freeze'),
|
||||
('user:unfreeze', '解冻用户', 'user', 'unfreeze'),
|
||||
('order:view', '查看订单', 'order', 'view'),
|
||||
('order:close', '关闭订单', 'order', 'close'),
|
||||
('order:mark_abnormal', '标记异常订单', 'order', 'mark_abnormal'),
|
||||
('listing:view', '查看商品', 'listing', 'view'),
|
||||
('listing:approve', '审核通过商品', 'listing', 'approve'),
|
||||
('listing:reject', '审核拒绝商品', 'listing', 'reject'),
|
||||
('listing:offline', '下架商品', 'listing', 'offline'),
|
||||
('dispute:view', '查看纠纷', 'dispute', 'view'),
|
||||
('dispute:arbitrate', '仲裁纠纷', 'dispute', 'arbitrate'),
|
||||
('chat:view', '查看客服群聊', 'chat', 'view'),
|
||||
('chat:send', '发送客服消息', 'chat', 'send'),
|
||||
('wallet:view', '查看资金流水', 'wallet', 'view'),
|
||||
('admin_user:manage', '管理员管理', 'admin_user', 'manage'),
|
||||
('role:manage', '角色管理', 'role', 'manage'),
|
||||
('system_config:view', '查看系统配置', 'system_config', 'view'),
|
||||
('system_config:update', '修改系统配置', 'system_config', 'update'),
|
||||
('audit_log:view', '查看审计日志', 'audit_log', 'view')
|
||||
ON DUPLICATE KEY UPDATE name = VALUES(name);
|
||||
|
||||
-- 插入角色
|
||||
INSERT INTO roles (code, name, description) VALUES
|
||||
('super_admin', '超级管理员', '拥有所有权限,不可被删除'),
|
||||
('cs', '客服', '处理用户咨询、群聊、纠纷仲裁'),
|
||||
('ops', '运营', '管理商品审核、订单管理、用户管理'),
|
||||
('finance', '财务', '查看资金流水、订单信息')
|
||||
ON DUPLICATE KEY UPDATE name = VALUES(name), description = VALUES(description);
|
||||
|
||||
-- super_admin 拥有所有权限
|
||||
INSERT IGNORE INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id FROM roles r CROSS JOIN permissions p WHERE r.code = 'super_admin';
|
||||
|
||||
-- cs 角色权限
|
||||
INSERT IGNORE INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id FROM roles r, permissions p
|
||||
WHERE r.code = 'cs' AND p.code IN ('dashboard:view', 'user:view', 'dispute:view', 'dispute:arbitrate', 'chat:view', 'chat:send', 'order:view');
|
||||
|
||||
-- ops 角色权限
|
||||
INSERT IGNORE INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id FROM roles r, permissions p
|
||||
WHERE r.code = 'ops' AND p.code IN ('dashboard:view', 'user:view', 'user:freeze', 'user:unfreeze', 'order:view', 'order:close', 'order:mark_abnormal', 'listing:view', 'listing:approve', 'listing:reject', 'listing:offline', 'dispute:view', 'audit_log:view');
|
||||
|
||||
-- finance 角色权限
|
||||
INSERT IGNORE INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id FROM roles r, permissions p
|
||||
WHERE r.code = 'finance' AND p.code IN ('dashboard:view', 'wallet:view', 'order:view');
|
||||
|
||||
-- 默认 admin 用户自动绑定 super_admin 角色
|
||||
INSERT IGNORE INTO admin_user_roles (admin_user_id, role_id)
|
||||
SELECT au.id, r.id FROM admin_users au, roles r WHERE au.username = 'admin' AND r.code = 'super_admin';
|
||||
Reference in New Issue
Block a user