feat: 实现 RBAC 权限系统和数据权限控制 (004-rbac-data-permission)
主要功能: - 实现完整的 RBAC 权限系统(账号、角色、权限的多对多关联) - 基于 owner_id + shop_id 的自动数据权限过滤 - 使用 PostgreSQL WITH RECURSIVE 查询下级账号 - Redis 缓存优化下级账号查询性能(30分钟过期) - 支持多租户数据隔离和层级权限管理 技术实现: - 新增 Account、Role、Permission 模型及关联关系表 - 实现 GORM Scopes 自动应用数据权限过滤 - 添加数据库迁移脚本(000002_rbac_data_permission、000003_add_owner_id_shop_id) - 完善错误码定义(1010-1027 为 RBAC 相关错误) - 重构 main.go 采用函数拆分提高可读性 测试覆盖: - 添加 Account、Role、Permission 的集成测试 - 添加数据权限过滤的单元测试和集成测试 - 添加下级账号查询和缓存的单元测试 - 添加 API 回归测试确保向后兼容 文档更新: - 更新 README.md 添加 RBAC 功能说明 - 更新 CLAUDE.md 添加技术栈和开发原则 - 添加 docs/004-rbac-data-permission/ 功能总结和使用指南 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
163
internal/handler/account.go
Normal file
163
internal/handler/account.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
accountService "github.com/break/junhong_cmp_fiber/internal/service/account"
|
||||
)
|
||||
|
||||
// AccountHandler 账号 Handler
|
||||
type AccountHandler struct {
|
||||
service *accountService.Service
|
||||
}
|
||||
|
||||
// NewAccountHandler 创建账号 Handler
|
||||
func NewAccountHandler(service *accountService.Service) *AccountHandler {
|
||||
return &AccountHandler{service: service}
|
||||
}
|
||||
|
||||
// Create 创建账号
|
||||
// POST /api/v1/accounts
|
||||
func (h *AccountHandler) Create(c *fiber.Ctx) error {
|
||||
var req model.CreateAccountRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
account, err := h.service.Create(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, account)
|
||||
}
|
||||
|
||||
// Get 获取账号详情
|
||||
// GET /api/v1/accounts/:id
|
||||
func (h *AccountHandler) Get(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的账号 ID")
|
||||
}
|
||||
|
||||
account, err := h.service.Get(c.UserContext(), uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, account)
|
||||
}
|
||||
|
||||
// Update 更新账号
|
||||
// PUT /api/v1/accounts/:id
|
||||
func (h *AccountHandler) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的账号 ID")
|
||||
}
|
||||
|
||||
var req model.UpdateAccountRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
account, err := h.service.Update(c.UserContext(), uint(id), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, account)
|
||||
}
|
||||
|
||||
// Delete 删除账号
|
||||
// DELETE /api/v1/accounts/:id
|
||||
func (h *AccountHandler) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的账号 ID")
|
||||
}
|
||||
|
||||
if err := h.service.Delete(c.UserContext(), uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// List 查询账号列表
|
||||
// GET /api/v1/accounts
|
||||
func (h *AccountHandler) List(c *fiber.Ctx) error {
|
||||
var req model.AccountListRequest
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
accounts, total, err := h.service.List(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.SuccessWithPagination(c, accounts, total, req.Page, req.PageSize)
|
||||
}
|
||||
|
||||
// AssignRoles 为账号分配角色
|
||||
// POST /api/v1/accounts/:id/roles
|
||||
func (h *AccountHandler) AssignRoles(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的账号 ID")
|
||||
}
|
||||
|
||||
var req model.AssignRolesRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
ars, err := h.service.AssignRoles(c.UserContext(), uint(id), req.RoleIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, ars)
|
||||
}
|
||||
|
||||
// GetRoles 获取账号的所有角色
|
||||
// GET /api/v1/accounts/:id/roles
|
||||
func (h *AccountHandler) GetRoles(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的账号 ID")
|
||||
}
|
||||
|
||||
roles, err := h.service.GetRoles(c.UserContext(), uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, roles)
|
||||
}
|
||||
|
||||
// RemoveRole 移除账号的角色
|
||||
// DELETE /api/v1/accounts/:account_id/roles/:role_id
|
||||
func (h *AccountHandler) RemoveRole(c *fiber.Ctx) error {
|
||||
accountID, err := strconv.ParseUint(c.Params("account_id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的账号 ID")
|
||||
}
|
||||
|
||||
roleID, err := strconv.ParseUint(c.Params("role_id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的角色 ID")
|
||||
}
|
||||
|
||||
if err := h.service.RemoveRole(c.UserContext(), uint(accountID), uint(roleID)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
118
internal/handler/permission.go
Normal file
118
internal/handler/permission.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
|
||||
permissionService "github.com/break/junhong_cmp_fiber/internal/service/permission"
|
||||
)
|
||||
|
||||
// PermissionHandler 权限 Handler
|
||||
type PermissionHandler struct {
|
||||
service *permissionService.Service
|
||||
}
|
||||
|
||||
// NewPermissionHandler 创建权限 Handler
|
||||
func NewPermissionHandler(service *permissionService.Service) *PermissionHandler {
|
||||
return &PermissionHandler{service: service}
|
||||
}
|
||||
|
||||
// Create 创建权限
|
||||
// POST /api/v1/permissions
|
||||
func (h *PermissionHandler) Create(c *fiber.Ctx) error {
|
||||
var req model.CreatePermissionRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
permission, err := h.service.Create(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, permission)
|
||||
}
|
||||
|
||||
// Get 获取权限详情
|
||||
// GET /api/v1/permissions/:id
|
||||
func (h *PermissionHandler) Get(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的权限 ID")
|
||||
}
|
||||
|
||||
permission, err := h.service.Get(c.UserContext(), uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, permission)
|
||||
}
|
||||
|
||||
// Update 更新权限
|
||||
// PUT /api/v1/permissions/:id
|
||||
func (h *PermissionHandler) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的权限 ID")
|
||||
}
|
||||
|
||||
var req model.UpdatePermissionRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
permission, err := h.service.Update(c.UserContext(), uint(id), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, permission)
|
||||
}
|
||||
|
||||
// Delete 删除权限
|
||||
// DELETE /api/v1/permissions/:id
|
||||
func (h *PermissionHandler) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的权限 ID")
|
||||
}
|
||||
|
||||
if err := h.service.Delete(c.UserContext(), uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// List 查询权限列表
|
||||
// GET /api/v1/permissions
|
||||
func (h *PermissionHandler) List(c *fiber.Ctx) error {
|
||||
var req model.PermissionListRequest
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
permissions, total, err := h.service.List(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.SuccessWithPagination(c, permissions, total, req.Page, req.PageSize)
|
||||
}
|
||||
|
||||
// GetTree 获取权限树
|
||||
// GET /api/v1/permissions/tree
|
||||
func (h *PermissionHandler) GetTree(c *fiber.Ctx) error {
|
||||
tree, err := h.service.GetTree(c.UserContext())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, tree)
|
||||
}
|
||||
164
internal/handler/role.go
Normal file
164
internal/handler/role.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
roleService "github.com/break/junhong_cmp_fiber/internal/service/role"
|
||||
)
|
||||
|
||||
// RoleHandler 角色 Handler
|
||||
type RoleHandler struct {
|
||||
service *roleService.Service
|
||||
}
|
||||
|
||||
// NewRoleHandler 创建角色 Handler
|
||||
func NewRoleHandler(service *roleService.Service) *RoleHandler {
|
||||
return &RoleHandler{service: service}
|
||||
}
|
||||
|
||||
// Create 创建角色
|
||||
// POST /api/v1/roles
|
||||
func (h *RoleHandler) Create(c *fiber.Ctx) error {
|
||||
var req model.CreateRoleRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
role, err := h.service.Create(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, role)
|
||||
}
|
||||
|
||||
// Get 获取角色详情
|
||||
// GET /api/v1/roles/:id
|
||||
func (h *RoleHandler) Get(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的角色 ID")
|
||||
}
|
||||
|
||||
role, err := h.service.Get(c.UserContext(), uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, role)
|
||||
}
|
||||
|
||||
// Update 更新角色
|
||||
// PUT /api/v1/roles/:id
|
||||
func (h *RoleHandler) Update(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的角色 ID")
|
||||
}
|
||||
|
||||
var req model.UpdateRoleRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
role, err := h.service.Update(c.UserContext(), uint(id), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, role)
|
||||
}
|
||||
|
||||
// Delete 删除角色
|
||||
// DELETE /api/v1/roles/:id
|
||||
func (h *RoleHandler) Delete(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的角色 ID")
|
||||
}
|
||||
|
||||
if err := h.service.Delete(c.UserContext(), uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
|
||||
// List 查询角色列表
|
||||
// GET /api/v1/roles
|
||||
func (h *RoleHandler) List(c *fiber.Ctx) error {
|
||||
var req model.RoleListRequest
|
||||
if err := c.QueryParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
roles, total, err := h.service.List(c.UserContext(), &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.SuccessWithPagination(c, roles, total, req.Page, req.PageSize)
|
||||
}
|
||||
|
||||
// AssignPermissions 为角色分配权限
|
||||
// POST /api/v1/roles/:id/permissions
|
||||
func (h *RoleHandler) AssignPermissions(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的角色 ID")
|
||||
}
|
||||
|
||||
var req model.AssignPermissionsRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "请求参数解析失败")
|
||||
}
|
||||
|
||||
rps, err := h.service.AssignPermissions(c.UserContext(), uint(id), req.PermIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, rps)
|
||||
}
|
||||
|
||||
// GetPermissions 获取角色的所有权限
|
||||
// GET /api/v1/roles/:id/permissions
|
||||
func (h *RoleHandler) GetPermissions(c *fiber.Ctx) error {
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的角色 ID")
|
||||
}
|
||||
|
||||
permissions, err := h.service.GetPermissions(c.UserContext(), uint(id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, permissions)
|
||||
}
|
||||
|
||||
// RemovePermission 移除角色的权限
|
||||
// DELETE /api/v1/roles/:role_id/permissions/:perm_id
|
||||
func (h *RoleHandler) RemovePermission(c *fiber.Ctx) error {
|
||||
roleID, err := strconv.ParseUint(c.Params("role_id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的角色 ID")
|
||||
}
|
||||
|
||||
permID, err := strconv.ParseUint(c.Params("perm_id"), 10, 64)
|
||||
if err != nil {
|
||||
return errors.New(errors.CodeInvalidParam, "无效的权限 ID")
|
||||
}
|
||||
|
||||
if err := h.service.RemovePermission(c.UserContext(), uint(roleID), uint(permID)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Success(c, nil)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func TestRecover_PanicCapture(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/panic", nil)
|
||||
resp, err := app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
// 验证响应状态码为 500 (内部错误)
|
||||
assert.Equal(t, 500, resp.StatusCode, "panic 应转换为 500 错误")
|
||||
@@ -98,7 +98,7 @@ func TestRecover_NilPointerPanic(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/nil-panic", nil)
|
||||
resp, err := app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
assert.Equal(t, 500, resp.StatusCode, "空指针 panic 应转换为 500 错误")
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestRecover_NormalRequest(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "/normal", nil)
|
||||
resp, err := app.Test(req, -1)
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
assert.Equal(t, 200, resp.StatusCode, "正常请求应返回 200")
|
||||
|
||||
|
||||
29
internal/model/account.go
Normal file
29
internal/model/account.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Account 账号模型
|
||||
type Account struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
Username string `gorm:"uniqueIndex:idx_account_username,where:deleted_at IS NULL;not null;size:50" json:"username"`
|
||||
Phone string `gorm:"uniqueIndex:idx_account_phone,where:deleted_at IS NULL;not null;size:20" json:"phone"`
|
||||
Password string `gorm:"not null;size:255" json:"-"` // 不返回给客户端
|
||||
UserType int `gorm:"not null;index" json:"user_type"` // 1=root, 2=平台, 3=代理, 4=企业
|
||||
ShopID *uint `gorm:"index" json:"shop_id,omitempty"`
|
||||
ParentID *uint `gorm:"index" json:"parent_id,omitempty"`
|
||||
Status int `gorm:"not null;default:1" json:"status"` // 0=禁用, 1=启用
|
||||
Creator uint `gorm:"not null" json:"creator"`
|
||||
Updater uint `gorm:"not null" json:"updater"`
|
||||
CreatedAt time.Time `gorm:"not null" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"not null" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Account) TableName() string {
|
||||
return "tb_account"
|
||||
}
|
||||
49
internal/model/account_dto.go
Normal file
49
internal/model/account_dto.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package model
|
||||
|
||||
// CreateAccountRequest 创建账号请求
|
||||
type CreateAccountRequest struct {
|
||||
Username string `json:"username" validate:"required,min=3,max=50"`
|
||||
Phone string `json:"phone" validate:"required,len=11"`
|
||||
Password string `json:"password" validate:"required,min=8,max=32"`
|
||||
UserType int `json:"user_type" validate:"required,min=1,max=4"`
|
||||
ShopID *uint `json:"shop_id"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
}
|
||||
|
||||
// UpdateAccountRequest 更新账号请求
|
||||
type UpdateAccountRequest struct {
|
||||
Username *string `json:"username" validate:"omitempty,min=3,max=50"`
|
||||
Phone *string `json:"phone" validate:"omitempty,len=11"`
|
||||
Password *string `json:"password" validate:"omitempty,min=8,max=32"`
|
||||
Status *int `json:"status" validate:"omitempty,min=0,max=1"`
|
||||
}
|
||||
|
||||
// AccountListRequest 账号列表查询请求
|
||||
type AccountListRequest struct {
|
||||
Page int `json:"page" query:"page" validate:"omitempty,min=1"`
|
||||
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
Username string `json:"username" query:"username" validate:"omitempty,max=50"`
|
||||
Phone string `json:"phone" query:"phone" validate:"omitempty,max=20"`
|
||||
UserType *int `json:"user_type" query:"user_type" validate:"omitempty,min=1,max=4"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1"`
|
||||
}
|
||||
|
||||
// AccountResponse 账号响应
|
||||
type AccountResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Phone string `json:"phone"`
|
||||
UserType int `json:"user_type"`
|
||||
ShopID *uint `json:"shop_id,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Status int `json:"status"`
|
||||
Creator uint `json:"creator"`
|
||||
Updater uint `json:"updater"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AssignRolesRequest 分配角色请求
|
||||
type AssignRolesRequest struct {
|
||||
RoleIDs []uint `json:"role_ids" validate:"required,min=1"`
|
||||
}
|
||||
25
internal/model/account_role.go
Normal file
25
internal/model/account_role.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AccountRole 账号-角色关联模型
|
||||
type AccountRole struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
AccountID uint `gorm:"not null;index;uniqueIndex:idx_account_role_unique,where:deleted_at IS NULL" json:"account_id"`
|
||||
RoleID uint `gorm:"not null;index;uniqueIndex:idx_account_role_unique,where:deleted_at IS NULL" json:"role_id"`
|
||||
Status int `gorm:"not null;default:1" json:"status"`
|
||||
Creator uint `gorm:"not null" json:"creator"`
|
||||
Updater uint `gorm:"not null" json:"updater"`
|
||||
CreatedAt time.Time `gorm:"not null" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"not null" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (AccountRole) TableName() string {
|
||||
return "tb_account_role"
|
||||
}
|
||||
16
internal/model/account_role_dto.go
Normal file
16
internal/model/account_role_dto.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
// AccountRoleResponse 账号-角色关联响应
|
||||
type AccountRoleResponse struct {
|
||||
ID uint `json:"id"`
|
||||
AccountID uint `json:"account_id"`
|
||||
RoleID uint `json:"role_id"`
|
||||
Status int `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// AccountRolesResponse 账号的角色列表响应
|
||||
type AccountRolesResponse struct {
|
||||
AccountID uint `json:"account_id"`
|
||||
Roles []*RoleResponse `json:"roles"`
|
||||
}
|
||||
29
internal/model/permission.go
Normal file
29
internal/model/permission.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Permission 权限模型
|
||||
type Permission struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
PermName string `gorm:"not null;size:50" json:"perm_name"`
|
||||
PermCode string `gorm:"uniqueIndex:idx_permission_code,where:deleted_at IS NULL;not null;size:100" json:"perm_code"`
|
||||
PermType int `gorm:"not null;index" json:"perm_type"` // 1=菜单, 2=按钮
|
||||
URL string `gorm:"size:255" json:"url,omitempty"`
|
||||
ParentID *uint `gorm:"index" json:"parent_id,omitempty"`
|
||||
Sort int `gorm:"not null;default:0" json:"sort"`
|
||||
Status int `gorm:"not null;default:1" json:"status"`
|
||||
Creator uint `gorm:"not null" json:"creator"`
|
||||
Updater uint `gorm:"not null" json:"updater"`
|
||||
CreatedAt time.Time `gorm:"not null" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"not null" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Permission) TableName() string {
|
||||
return "tb_permission"
|
||||
}
|
||||
59
internal/model/permission_dto.go
Normal file
59
internal/model/permission_dto.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package model
|
||||
|
||||
// CreatePermissionRequest 创建权限请求
|
||||
type CreatePermissionRequest struct {
|
||||
PermName string `json:"perm_name" validate:"required,min=1,max=50"`
|
||||
PermCode string `json:"perm_code" validate:"required,min=1,max=100"`
|
||||
PermType int `json:"perm_type" validate:"required,min=1,max=2"`
|
||||
URL string `json:"url" validate:"omitempty,max=255"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
Sort int `json:"sort" validate:"omitempty,min=0"`
|
||||
}
|
||||
|
||||
// UpdatePermissionRequest 更新权限请求
|
||||
type UpdatePermissionRequest struct {
|
||||
PermName *string `json:"perm_name" validate:"omitempty,min=1,max=50"`
|
||||
PermCode *string `json:"perm_code" validate:"omitempty,min=1,max=100"`
|
||||
URL *string `json:"url" validate:"omitempty,max=255"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
Sort *int `json:"sort" validate:"omitempty,min=0"`
|
||||
Status *int `json:"status" validate:"omitempty,min=0,max=1"`
|
||||
}
|
||||
|
||||
// PermissionListRequest 权限列表查询请求
|
||||
type PermissionListRequest struct {
|
||||
Page int `json:"page" query:"page" validate:"omitempty,min=1"`
|
||||
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
PermName string `json:"perm_name" query:"perm_name" validate:"omitempty,max=50"`
|
||||
PermCode string `json:"perm_code" query:"perm_code" validate:"omitempty,max=100"`
|
||||
PermType *int `json:"perm_type" query:"perm_type" validate:"omitempty,min=1,max=2"`
|
||||
ParentID *uint `json:"parent_id" query:"parent_id"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1"`
|
||||
}
|
||||
|
||||
// PermissionResponse 权限响应
|
||||
type PermissionResponse struct {
|
||||
ID uint `json:"id"`
|
||||
PermName string `json:"perm_name"`
|
||||
PermCode string `json:"perm_code"`
|
||||
PermType int `json:"perm_type"`
|
||||
URL string `json:"url,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Sort int `json:"sort"`
|
||||
Status int `json:"status"`
|
||||
Creator uint `json:"creator"`
|
||||
Updater uint `json:"updater"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// PermissionTreeNode 权限树节点(用于层级展示)
|
||||
type PermissionTreeNode struct {
|
||||
ID uint `json:"id"`
|
||||
PermName string `json:"perm_name"`
|
||||
PermCode string `json:"perm_code"`
|
||||
PermType int `json:"perm_type"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Sort int `json:"sort"`
|
||||
Children []*PermissionTreeNode `json:"children,omitempty"`
|
||||
}
|
||||
26
internal/model/role.go
Normal file
26
internal/model/role.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Role 角色模型
|
||||
type Role struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
RoleName string `gorm:"not null;size:50" json:"role_name"`
|
||||
RoleDesc string `gorm:"size:255" json:"role_desc"`
|
||||
RoleType int `gorm:"not null;index" json:"role_type"` // 1=超级, 2=代理, 3=企业
|
||||
Status int `gorm:"not null;default:1" json:"status"`
|
||||
Creator uint `gorm:"not null" json:"creator"`
|
||||
Updater uint `gorm:"not null" json:"updater"`
|
||||
CreatedAt time.Time `gorm:"not null" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"not null" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (Role) TableName() string {
|
||||
return "tb_role"
|
||||
}
|
||||
42
internal/model/role_dto.go
Normal file
42
internal/model/role_dto.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package model
|
||||
|
||||
// CreateRoleRequest 创建角色请求
|
||||
type CreateRoleRequest struct {
|
||||
RoleName string `json:"role_name" validate:"required,min=1,max=50"`
|
||||
RoleDesc string `json:"role_desc" validate:"omitempty,max=255"`
|
||||
RoleType int `json:"role_type" validate:"required,min=1,max=3"`
|
||||
}
|
||||
|
||||
// UpdateRoleRequest 更新角色请求
|
||||
type UpdateRoleRequest struct {
|
||||
RoleName *string `json:"role_name" validate:"omitempty,min=1,max=50"`
|
||||
RoleDesc *string `json:"role_desc" validate:"omitempty,max=255"`
|
||||
Status *int `json:"status" validate:"omitempty,min=0,max=1"`
|
||||
}
|
||||
|
||||
// RoleListRequest 角色列表查询请求
|
||||
type RoleListRequest struct {
|
||||
Page int `json:"page" query:"page" validate:"omitempty,min=1"`
|
||||
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100"`
|
||||
RoleName string `json:"role_name" query:"role_name" validate:"omitempty,max=50"`
|
||||
RoleType *int `json:"role_type" query:"role_type" validate:"omitempty,min=1,max=3"`
|
||||
Status *int `json:"status" query:"status" validate:"omitempty,min=0,max=1"`
|
||||
}
|
||||
|
||||
// RoleResponse 角色响应
|
||||
type RoleResponse struct {
|
||||
ID uint `json:"id"`
|
||||
RoleName string `json:"role_name"`
|
||||
RoleDesc string `json:"role_desc"`
|
||||
RoleType int `json:"role_type"`
|
||||
Status int `json:"status"`
|
||||
Creator uint `json:"creator"`
|
||||
Updater uint `json:"updater"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// AssignPermissionsRequest 分配权限请求
|
||||
type AssignPermissionsRequest struct {
|
||||
PermIDs []uint `json:"perm_ids" validate:"required,min=1"`
|
||||
}
|
||||
25
internal/model/role_permission.go
Normal file
25
internal/model/role_permission.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RolePermission 角色-权限关联模型
|
||||
type RolePermission struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
RoleID uint `gorm:"not null;index;uniqueIndex:idx_role_permission_unique,where:deleted_at IS NULL" json:"role_id"`
|
||||
PermID uint `gorm:"not null;index;uniqueIndex:idx_role_permission_unique,where:deleted_at IS NULL" json:"perm_id"`
|
||||
Status int `gorm:"not null;default:1" json:"status"`
|
||||
Creator uint `gorm:"not null" json:"creator"`
|
||||
Updater uint `gorm:"not null" json:"updater"`
|
||||
CreatedAt time.Time `gorm:"not null" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"not null" json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (RolePermission) TableName() string {
|
||||
return "tb_role_permission"
|
||||
}
|
||||
16
internal/model/role_permission_dto.go
Normal file
16
internal/model/role_permission_dto.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package model
|
||||
|
||||
// RolePermissionResponse 角色-权限关联响应
|
||||
type RolePermissionResponse struct {
|
||||
ID uint `json:"id"`
|
||||
RoleID uint `json:"role_id"`
|
||||
PermID uint `json:"perm_id"`
|
||||
Status int `json:"status"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// RolePermissionsResponse 角色的权限列表响应
|
||||
type RolePermissionsResponse struct {
|
||||
RoleID uint `json:"role_id"`
|
||||
Permissions []*PermissionResponse `json:"permissions"`
|
||||
}
|
||||
24
internal/routes/account.go
Normal file
24
internal/routes/account.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/handler"
|
||||
)
|
||||
|
||||
// registerAccountRoutes 注册账号相关路由
|
||||
func registerAccountRoutes(api fiber.Router, h *handler.AccountHandler) {
|
||||
accounts := api.Group("/accounts")
|
||||
|
||||
// 账号 CRUD
|
||||
accounts.Post("", h.Create) // POST /api/v1/accounts
|
||||
accounts.Get("", h.List) // GET /api/v1/accounts
|
||||
accounts.Get("/:id", h.Get) // GET /api/v1/accounts/:id
|
||||
accounts.Put("/:id", h.Update) // PUT /api/v1/accounts/:id
|
||||
accounts.Delete("/:id", h.Delete) // DELETE /api/v1/accounts/:id
|
||||
|
||||
// 账号-角色关联
|
||||
accounts.Post("/:id/roles", h.AssignRoles) // POST /api/v1/accounts/:id/roles
|
||||
accounts.Get("/:id/roles", h.GetRoles) // GET /api/v1/accounts/:id/roles
|
||||
accounts.Delete("/:account_id/roles/:role_id", h.RemoveRole) // DELETE /api/v1/accounts/:account_id/roles/:role_id
|
||||
}
|
||||
25
internal/routes/health.go
Normal file
25
internal/routes/health.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// registerHealthRoutes 注册健康检查路由
|
||||
// 不需要认证,用于负载均衡器和监控系统
|
||||
func registerHealthRoutes(app *fiber.App) {
|
||||
// 健康检查
|
||||
app.Get("/health", func(c *fiber.Ctx) error {
|
||||
return response.Success(c, fiber.Map{
|
||||
"status": "healthy",
|
||||
"service": "junhong_cmp_fiber",
|
||||
})
|
||||
})
|
||||
|
||||
// 就绪检查
|
||||
app.Get("/ready", func(c *fiber.Ctx) error {
|
||||
return response.Success(c, fiber.Map{
|
||||
"status": "ready",
|
||||
})
|
||||
})
|
||||
}
|
||||
20
internal/routes/permission.go
Normal file
20
internal/routes/permission.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/handler"
|
||||
)
|
||||
|
||||
// registerPermissionRoutes 注册权限相关路由
|
||||
func registerPermissionRoutes(api fiber.Router, h *handler.PermissionHandler) {
|
||||
permissions := api.Group("/permissions")
|
||||
|
||||
// 权限 CRUD
|
||||
permissions.Post("", h.Create) // POST /api/v1/permissions
|
||||
permissions.Get("", h.List) // GET /api/v1/permissions
|
||||
permissions.Get("/tree", h.GetTree) // GET /api/v1/permissions/tree (注意:放在 :id 之前避免路由冲突)
|
||||
permissions.Get("/:id", h.Get) // GET /api/v1/permissions/:id
|
||||
permissions.Put("/:id", h.Update) // PUT /api/v1/permissions/:id
|
||||
permissions.Delete("/:id", h.Delete) // DELETE /api/v1/permissions/:id
|
||||
}
|
||||
24
internal/routes/role.go
Normal file
24
internal/routes/role.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/handler"
|
||||
)
|
||||
|
||||
// registerRoleRoutes 注册角色相关路由
|
||||
func registerRoleRoutes(api fiber.Router, h *handler.RoleHandler) {
|
||||
roles := api.Group("/roles")
|
||||
|
||||
// 角色 CRUD
|
||||
roles.Post("", h.Create) // POST /api/v1/roles
|
||||
roles.Get("", h.List) // GET /api/v1/roles
|
||||
roles.Get("/:id", h.Get) // GET /api/v1/roles/:id
|
||||
roles.Put("/:id", h.Update) // PUT /api/v1/roles/:id
|
||||
roles.Delete("/:id", h.Delete) // DELETE /api/v1/roles/:id
|
||||
|
||||
// 角色-权限关联
|
||||
roles.Post("/:id/permissions", h.AssignPermissions) // POST /api/v1/roles/:id/permissions
|
||||
roles.Get("/:id/permissions", h.GetPermissions) // GET /api/v1/roles/:id/permissions
|
||||
roles.Delete("/:role_id/permissions/:perm_id", h.RemovePermission) // DELETE /api/v1/roles/:role_id/permissions/:perm_id
|
||||
}
|
||||
38
internal/routes/routes.go
Normal file
38
internal/routes/routes.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/handler"
|
||||
)
|
||||
|
||||
// Services 容器,包含所有业务 Handler
|
||||
// 由 main 函数初始化并传递给路由注册函数
|
||||
type Services struct {
|
||||
// RBAC 相关 Handler
|
||||
AccountHandler *handler.AccountHandler
|
||||
RoleHandler *handler.RoleHandler
|
||||
PermissionHandler *handler.PermissionHandler
|
||||
}
|
||||
|
||||
// RegisterRoutes 路由注册总入口
|
||||
// 按业务模块调用各自的路由注册函数
|
||||
func RegisterRoutes(app *fiber.App, services *Services) {
|
||||
// API 路由组
|
||||
api := app.Group("/api/v1")
|
||||
|
||||
// 注册各模块路由
|
||||
registerHealthRoutes(app)
|
||||
registerTaskRoutes(api)
|
||||
|
||||
// RBAC 路由
|
||||
if services.AccountHandler != nil {
|
||||
registerAccountRoutes(api, services.AccountHandler)
|
||||
}
|
||||
if services.RoleHandler != nil {
|
||||
registerRoleRoutes(api, services.RoleHandler)
|
||||
}
|
||||
if services.PermissionHandler != nil {
|
||||
registerPermissionRoutes(api, services.PermissionHandler)
|
||||
}
|
||||
}
|
||||
21
internal/routes/task.go
Normal file
21
internal/routes/task.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/break/junhong_cmp_fiber/pkg/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// registerTaskRoutes 注册任务相关路由
|
||||
// 用于异步任务状态查询等
|
||||
func registerTaskRoutes(api fiber.Router) {
|
||||
tasks := api.Group("/tasks")
|
||||
|
||||
// 获取任务状态(占位实现)
|
||||
tasks.Get("/:id", func(c *fiber.Ctx) error {
|
||||
taskID := c.Params("id")
|
||||
return response.Success(c, fiber.Map{
|
||||
"id": taskID,
|
||||
"status": "pending",
|
||||
})
|
||||
})
|
||||
}
|
||||
324
internal/service/account/service.go
Normal file
324
internal/service/account/service.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Service 账号业务服务
|
||||
type Service struct {
|
||||
accountStore *postgres.AccountStore
|
||||
roleStore *postgres.RoleStore
|
||||
accountRoleStore *postgres.AccountRoleStore
|
||||
}
|
||||
|
||||
// New 创建账号服务
|
||||
func New(accountStore *postgres.AccountStore, roleStore *postgres.RoleStore, accountRoleStore *postgres.AccountRoleStore) *Service {
|
||||
return &Service{
|
||||
accountStore: accountStore,
|
||||
roleStore: roleStore,
|
||||
accountRoleStore: accountRoleStore,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建账号
|
||||
func (s *Service) Create(ctx context.Context, req *model.CreateAccountRequest) (*model.Account, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 验证非 root 用户必须提供 parent_id
|
||||
if req.UserType != constants.UserTypeRoot && req.ParentID == nil {
|
||||
return nil, errors.New(errors.CodeParentIDRequired, "非 root 用户必须提供上级账号")
|
||||
}
|
||||
|
||||
// 检查用户名唯一性
|
||||
existing, err := s.accountStore.GetByUsername(ctx, req.Username)
|
||||
if err == nil && existing != nil {
|
||||
return nil, errors.New(errors.CodeUsernameExists, "用户名已存在")
|
||||
}
|
||||
|
||||
// 检查手机号唯一性
|
||||
existing, err = s.accountStore.GetByPhone(ctx, req.Phone)
|
||||
if err == nil && existing != nil {
|
||||
return nil, errors.New(errors.CodePhoneExists, "手机号已存在")
|
||||
}
|
||||
|
||||
// 验证 parent_id 存在(如果提供)
|
||||
if req.ParentID != nil {
|
||||
parent, err := s.accountStore.GetByID(ctx, *req.ParentID)
|
||||
if err != nil || parent == nil {
|
||||
return nil, errors.New(errors.CodeInvalidParentID, "上级账号不存在或无效")
|
||||
}
|
||||
}
|
||||
|
||||
// bcrypt 哈希密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("密码哈希失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建账号
|
||||
account := &model.Account{
|
||||
Username: req.Username,
|
||||
Phone: req.Phone,
|
||||
Password: string(hashedPassword),
|
||||
UserType: req.UserType,
|
||||
ShopID: req.ShopID,
|
||||
ParentID: req.ParentID,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: currentUserID,
|
||||
Updater: currentUserID,
|
||||
}
|
||||
|
||||
if err := s.accountStore.Create(ctx, account); err != nil {
|
||||
return nil, fmt.Errorf("创建账号失败: %w", err)
|
||||
}
|
||||
|
||||
// 清除父账号的下级 ID 缓存
|
||||
if account.ParentID != nil {
|
||||
_ = s.accountStore.ClearSubordinatesCacheForParents(ctx, *account.ParentID)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Get 获取账号
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*model.Account, error) {
|
||||
account, err := s.accountStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeAccountNotFound, "账号不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Update 更新账号
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *model.UpdateAccountRequest) (*model.Account, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 获取现有账号
|
||||
account, err := s.accountStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeAccountNotFound, "账号不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.Username != nil {
|
||||
// 检查新用户名唯一性
|
||||
existing, err := s.accountStore.GetByUsername(ctx, *req.Username)
|
||||
if err == nil && existing != nil && existing.ID != id {
|
||||
return nil, errors.New(errors.CodeUsernameExists, "用户名已存在")
|
||||
}
|
||||
account.Username = *req.Username
|
||||
}
|
||||
|
||||
if req.Phone != nil {
|
||||
// 检查新手机号唯一性
|
||||
existing, err := s.accountStore.GetByPhone(ctx, *req.Phone)
|
||||
if err == nil && existing != nil && existing.ID != id {
|
||||
return nil, errors.New(errors.CodePhoneExists, "手机号已存在")
|
||||
}
|
||||
account.Phone = *req.Phone
|
||||
}
|
||||
|
||||
if req.Password != nil {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("密码哈希失败: %w", err)
|
||||
}
|
||||
account.Password = string(hashedPassword)
|
||||
}
|
||||
|
||||
if req.Status != nil {
|
||||
account.Status = *req.Status
|
||||
}
|
||||
|
||||
account.Updater = currentUserID
|
||||
|
||||
if err := s.accountStore.Update(ctx, account); err != nil {
|
||||
return nil, fmt.Errorf("更新账号失败: %w", err)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// Delete 软删除账号
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
// 检查账号存在
|
||||
account, err := s.accountStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeAccountNotFound, "账号不存在")
|
||||
}
|
||||
return fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.accountStore.Delete(ctx, id); err != nil {
|
||||
return fmt.Errorf("删除账号失败: %w", err)
|
||||
}
|
||||
|
||||
// 清除该账号和所有上级的下级 ID 缓存
|
||||
_ = s.accountStore.ClearSubordinatesCacheForParents(ctx, id)
|
||||
|
||||
// 如果有上级,也需要清除上级的缓存
|
||||
if account.ParentID != nil {
|
||||
_ = s.accountStore.ClearSubordinatesCacheForParents(ctx, *account.ParentID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 查询账号列表
|
||||
func (s *Service) List(ctx context.Context, req *model.AccountListRequest) ([]*model.Account, int64, error) {
|
||||
opts := &store.QueryOptions{
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
OrderBy: "id DESC",
|
||||
}
|
||||
if opts.Page == 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
opts.PageSize = constants.DefaultPageSize
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.Username != "" {
|
||||
filters["username"] = req.Username
|
||||
}
|
||||
if req.Phone != "" {
|
||||
filters["phone"] = req.Phone
|
||||
}
|
||||
if req.UserType != nil {
|
||||
filters["user_type"] = *req.UserType
|
||||
}
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
|
||||
return s.accountStore.List(ctx, opts, filters)
|
||||
}
|
||||
|
||||
// AssignRoles 为账号分配角色
|
||||
func (s *Service) AssignRoles(ctx context.Context, accountID uint, roleIDs []uint) ([]*model.AccountRole, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 检查账号存在
|
||||
_, err := s.accountStore.GetByID(ctx, accountID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeAccountNotFound, "账号不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证所有角色存在
|
||||
for _, roleID := range roleIDs {
|
||||
_, err := s.roleStore.GetByID(ctx, roleID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeRoleNotFound, fmt.Sprintf("角色 %d 不存在", roleID))
|
||||
}
|
||||
return nil, fmt.Errorf("获取角色失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建关联
|
||||
var ars []*model.AccountRole
|
||||
for _, roleID := range roleIDs {
|
||||
// 检查是否已分配
|
||||
exists, _ := s.accountRoleStore.Exists(ctx, accountID, roleID)
|
||||
if exists {
|
||||
continue // 跳过已存在的关联
|
||||
}
|
||||
|
||||
ar := &model.AccountRole{
|
||||
AccountID: accountID,
|
||||
RoleID: roleID,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: currentUserID,
|
||||
Updater: currentUserID,
|
||||
}
|
||||
if err := s.accountRoleStore.Create(ctx, ar); err != nil {
|
||||
return nil, fmt.Errorf("创建账号-角色关联失败: %w", err)
|
||||
}
|
||||
ars = append(ars, ar)
|
||||
}
|
||||
|
||||
return ars, nil
|
||||
}
|
||||
|
||||
// GetRoles 获取账号的所有角色
|
||||
func (s *Service) GetRoles(ctx context.Context, accountID uint) ([]*model.Role, error) {
|
||||
// 检查账号存在
|
||||
_, err := s.accountStore.GetByID(ctx, accountID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeAccountNotFound, "账号不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取角色 ID 列表
|
||||
roleIDs, err := s.accountRoleStore.GetRoleIDsByAccountID(ctx, accountID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取账号角色 ID 失败: %w", err)
|
||||
}
|
||||
|
||||
if len(roleIDs) == 0 {
|
||||
return []*model.Role{}, nil
|
||||
}
|
||||
|
||||
// 获取角色详情
|
||||
return s.roleStore.GetByIDs(ctx, roleIDs)
|
||||
}
|
||||
|
||||
// RemoveRole 移除账号的角色
|
||||
func (s *Service) RemoveRole(ctx context.Context, accountID, roleID uint) error {
|
||||
// 检查账号存在
|
||||
_, err := s.accountStore.GetByID(ctx, accountID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeAccountNotFound, "账号不存在")
|
||||
}
|
||||
return fmt.Errorf("获取账号失败: %w", err)
|
||||
}
|
||||
|
||||
// 删除关联
|
||||
if err := s.accountRoleStore.Delete(ctx, accountID, roleID); err != nil {
|
||||
return fmt.Errorf("删除账号-角色关联失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePassword 验证密码
|
||||
func (s *Service) ValidatePassword(plainPassword, hashedPassword string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(plainPassword))
|
||||
return err == nil
|
||||
}
|
||||
246
internal/service/permission/service.go
Normal file
246
internal/service/permission/service.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package permission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// permCodeRegex 权限编码格式验证正则(module:action)
|
||||
var permCodeRegex = regexp.MustCompile(`^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*$`)
|
||||
|
||||
// Service 权限业务服务
|
||||
type Service struct {
|
||||
permissionStore *postgres.PermissionStore
|
||||
}
|
||||
|
||||
// New 创建权限服务
|
||||
func New(permissionStore *postgres.PermissionStore) *Service {
|
||||
return &Service{
|
||||
permissionStore: permissionStore,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建权限
|
||||
func (s *Service) Create(ctx context.Context, req *model.CreatePermissionRequest) (*model.Permission, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 验证权限编码格式
|
||||
if !permCodeRegex.MatchString(req.PermCode) {
|
||||
return nil, errors.New(errors.CodeInvalidPermCode, "权限编码格式不正确(应为 module:action 格式)")
|
||||
}
|
||||
|
||||
// 检查权限编码唯一性
|
||||
existing, err := s.permissionStore.GetByCode(ctx, req.PermCode)
|
||||
if err == nil && existing != nil {
|
||||
return nil, errors.New(errors.CodePermCodeExists, "权限编码已存在")
|
||||
}
|
||||
|
||||
// 验证 parent_id 存在(如果提供)
|
||||
if req.ParentID != nil {
|
||||
parent, err := s.permissionStore.GetByID(ctx, *req.ParentID)
|
||||
if err != nil || parent == nil {
|
||||
return nil, errors.New(errors.CodeNotFound, "上级权限不存在")
|
||||
}
|
||||
}
|
||||
|
||||
// 创建权限
|
||||
permission := &model.Permission{
|
||||
PermName: req.PermName,
|
||||
PermCode: req.PermCode,
|
||||
PermType: req.PermType,
|
||||
URL: req.URL,
|
||||
ParentID: req.ParentID,
|
||||
Sort: req.Sort,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: currentUserID,
|
||||
Updater: currentUserID,
|
||||
}
|
||||
|
||||
if err := s.permissionStore.Create(ctx, permission); err != nil {
|
||||
return nil, fmt.Errorf("创建权限失败: %w", err)
|
||||
}
|
||||
|
||||
return permission, nil
|
||||
}
|
||||
|
||||
// Get 获取权限
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*model.Permission, error) {
|
||||
permission, err := s.permissionStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodePermissionNotFound, "权限不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取权限失败: %w", err)
|
||||
}
|
||||
return permission, nil
|
||||
}
|
||||
|
||||
// Update 更新权限
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *model.UpdatePermissionRequest) (*model.Permission, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 获取现有权限
|
||||
permission, err := s.permissionStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodePermissionNotFound, "权限不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取权限失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.PermName != nil {
|
||||
permission.PermName = *req.PermName
|
||||
}
|
||||
if req.PermCode != nil {
|
||||
// 验证权限编码格式
|
||||
if !permCodeRegex.MatchString(*req.PermCode) {
|
||||
return nil, errors.New(errors.CodeInvalidPermCode, "权限编码格式不正确(应为 module:action 格式)")
|
||||
}
|
||||
// 检查新权限编码唯一性
|
||||
existing, err := s.permissionStore.GetByCode(ctx, *req.PermCode)
|
||||
if err == nil && existing != nil && existing.ID != id {
|
||||
return nil, errors.New(errors.CodePermCodeExists, "权限编码已存在")
|
||||
}
|
||||
permission.PermCode = *req.PermCode
|
||||
}
|
||||
if req.URL != nil {
|
||||
permission.URL = *req.URL
|
||||
}
|
||||
if req.ParentID != nil {
|
||||
// 验证 parent_id 存在
|
||||
parent, err := s.permissionStore.GetByID(ctx, *req.ParentID)
|
||||
if err != nil || parent == nil {
|
||||
return nil, errors.New(errors.CodeNotFound, "上级权限不存在")
|
||||
}
|
||||
permission.ParentID = req.ParentID
|
||||
}
|
||||
if req.Sort != nil {
|
||||
permission.Sort = *req.Sort
|
||||
}
|
||||
if req.Status != nil {
|
||||
permission.Status = *req.Status
|
||||
}
|
||||
|
||||
permission.Updater = currentUserID
|
||||
|
||||
if err := s.permissionStore.Update(ctx, permission); err != nil {
|
||||
return nil, fmt.Errorf("更新权限失败: %w", err)
|
||||
}
|
||||
|
||||
return permission, nil
|
||||
}
|
||||
|
||||
// Delete 软删除权限
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
// 检查权限存在
|
||||
_, err := s.permissionStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodePermissionNotFound, "权限不存在")
|
||||
}
|
||||
return fmt.Errorf("获取权限失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.permissionStore.Delete(ctx, id); err != nil {
|
||||
return fmt.Errorf("删除权限失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 查询权限列表
|
||||
func (s *Service) List(ctx context.Context, req *model.PermissionListRequest) ([]*model.Permission, int64, error) {
|
||||
opts := &store.QueryOptions{
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
OrderBy: "sort ASC, id ASC",
|
||||
}
|
||||
if opts.Page == 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
opts.PageSize = constants.DefaultPageSize
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.PermName != "" {
|
||||
filters["perm_name"] = req.PermName
|
||||
}
|
||||
if req.PermCode != "" {
|
||||
filters["perm_code"] = req.PermCode
|
||||
}
|
||||
if req.PermType != nil {
|
||||
filters["perm_type"] = *req.PermType
|
||||
}
|
||||
if req.ParentID != nil {
|
||||
filters["parent_id"] = *req.ParentID
|
||||
}
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
|
||||
return s.permissionStore.List(ctx, opts, filters)
|
||||
}
|
||||
|
||||
// GetTree 获取权限树
|
||||
func (s *Service) GetTree(ctx context.Context) ([]*model.PermissionTreeNode, error) {
|
||||
// 获取所有权限
|
||||
permissions, err := s.permissionStore.GetAll(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取权限列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建树结构
|
||||
return buildPermissionTree(permissions), nil
|
||||
}
|
||||
|
||||
// buildPermissionTree 构建权限树
|
||||
func buildPermissionTree(permissions []*model.Permission) []*model.PermissionTreeNode {
|
||||
// 转换为节点映射
|
||||
nodeMap := make(map[uint]*model.PermissionTreeNode)
|
||||
for _, p := range permissions {
|
||||
nodeMap[p.ID] = &model.PermissionTreeNode{
|
||||
ID: p.ID,
|
||||
PermName: p.PermName,
|
||||
PermCode: p.PermCode,
|
||||
PermType: p.PermType,
|
||||
URL: p.URL,
|
||||
Sort: p.Sort,
|
||||
Children: make([]*model.PermissionTreeNode, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// 构建树
|
||||
var roots []*model.PermissionTreeNode
|
||||
for _, p := range permissions {
|
||||
node := nodeMap[p.ID]
|
||||
if p.ParentID == nil || *p.ParentID == 0 {
|
||||
roots = append(roots, node)
|
||||
} else if parent, ok := nodeMap[*p.ParentID]; ok {
|
||||
parent.Children = append(parent.Children, node)
|
||||
} else {
|
||||
// 如果找不到父节点,作为根节点处理
|
||||
roots = append(roots, node)
|
||||
}
|
||||
}
|
||||
|
||||
return roots
|
||||
}
|
||||
247
internal/service/role/service.go
Normal file
247
internal/service/role/service.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package role
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Service 角色业务服务
|
||||
type Service struct {
|
||||
roleStore *postgres.RoleStore
|
||||
permissionStore *postgres.PermissionStore
|
||||
rolePermissionStore *postgres.RolePermissionStore
|
||||
}
|
||||
|
||||
// New 创建角色服务
|
||||
func New(roleStore *postgres.RoleStore, permissionStore *postgres.PermissionStore, rolePermissionStore *postgres.RolePermissionStore) *Service {
|
||||
return &Service{
|
||||
roleStore: roleStore,
|
||||
permissionStore: permissionStore,
|
||||
rolePermissionStore: rolePermissionStore,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建角色
|
||||
func (s *Service) Create(ctx context.Context, req *model.CreateRoleRequest) (*model.Role, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 创建角色
|
||||
role := &model.Role{
|
||||
RoleName: req.RoleName,
|
||||
RoleDesc: req.RoleDesc,
|
||||
RoleType: req.RoleType,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: currentUserID,
|
||||
Updater: currentUserID,
|
||||
}
|
||||
|
||||
if err := s.roleStore.Create(ctx, role); err != nil {
|
||||
return nil, fmt.Errorf("创建角色失败: %w", err)
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
// Get 获取角色
|
||||
func (s *Service) Get(ctx context.Context, id uint) (*model.Role, error) {
|
||||
role, err := s.roleStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeRoleNotFound, "角色不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取角色失败: %w", err)
|
||||
}
|
||||
return role, nil
|
||||
}
|
||||
|
||||
// Update 更新角色
|
||||
func (s *Service) Update(ctx context.Context, id uint, req *model.UpdateRoleRequest) (*model.Role, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 获取现有角色
|
||||
role, err := s.roleStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeRoleNotFound, "角色不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取角色失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.RoleName != nil {
|
||||
role.RoleName = *req.RoleName
|
||||
}
|
||||
if req.RoleDesc != nil {
|
||||
role.RoleDesc = *req.RoleDesc
|
||||
}
|
||||
if req.Status != nil {
|
||||
role.Status = *req.Status
|
||||
}
|
||||
|
||||
role.Updater = currentUserID
|
||||
|
||||
if err := s.roleStore.Update(ctx, role); err != nil {
|
||||
return nil, fmt.Errorf("更新角色失败: %w", err)
|
||||
}
|
||||
|
||||
return role, nil
|
||||
}
|
||||
|
||||
// Delete 软删除角色
|
||||
func (s *Service) Delete(ctx context.Context, id uint) error {
|
||||
// 检查角色存在
|
||||
_, err := s.roleStore.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeRoleNotFound, "角色不存在")
|
||||
}
|
||||
return fmt.Errorf("获取角色失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.roleStore.Delete(ctx, id); err != nil {
|
||||
return fmt.Errorf("删除角色失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 查询角色列表
|
||||
func (s *Service) List(ctx context.Context, req *model.RoleListRequest) ([]*model.Role, int64, error) {
|
||||
opts := &store.QueryOptions{
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
OrderBy: "id DESC",
|
||||
}
|
||||
if opts.Page == 0 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.PageSize == 0 {
|
||||
opts.PageSize = constants.DefaultPageSize
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.RoleName != "" {
|
||||
filters["role_name"] = req.RoleName
|
||||
}
|
||||
if req.RoleType != nil {
|
||||
filters["role_type"] = *req.RoleType
|
||||
}
|
||||
if req.Status != nil {
|
||||
filters["status"] = *req.Status
|
||||
}
|
||||
|
||||
return s.roleStore.List(ctx, opts, filters)
|
||||
}
|
||||
|
||||
// AssignPermissions 为角色分配权限
|
||||
func (s *Service) AssignPermissions(ctx context.Context, roleID uint, permIDs []uint) ([]*model.RolePermission, error) {
|
||||
// 获取当前用户 ID
|
||||
currentUserID := middleware.GetUserIDFromContext(ctx)
|
||||
if currentUserID == 0 {
|
||||
return nil, errors.New(errors.CodeUnauthorized, "未授权访问")
|
||||
}
|
||||
|
||||
// 检查角色存在
|
||||
_, err := s.roleStore.GetByID(ctx, roleID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeRoleNotFound, "角色不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取角色失败: %w", err)
|
||||
}
|
||||
|
||||
// 验证所有权限存在
|
||||
for _, permID := range permIDs {
|
||||
_, err := s.permissionStore.GetByID(ctx, permID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodePermissionNotFound, fmt.Sprintf("权限 %d 不存在", permID))
|
||||
}
|
||||
return nil, fmt.Errorf("获取权限失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建关联
|
||||
var rps []*model.RolePermission
|
||||
for _, permID := range permIDs {
|
||||
// 检查是否已分配
|
||||
exists, _ := s.rolePermissionStore.Exists(ctx, roleID, permID)
|
||||
if exists {
|
||||
continue // 跳过已存在的关联
|
||||
}
|
||||
|
||||
rp := &model.RolePermission{
|
||||
RoleID: roleID,
|
||||
PermID: permID,
|
||||
Status: constants.StatusEnabled,
|
||||
Creator: currentUserID,
|
||||
Updater: currentUserID,
|
||||
}
|
||||
if err := s.rolePermissionStore.Create(ctx, rp); err != nil {
|
||||
return nil, fmt.Errorf("创建角色-权限关联失败: %w", err)
|
||||
}
|
||||
rps = append(rps, rp)
|
||||
}
|
||||
|
||||
return rps, nil
|
||||
}
|
||||
|
||||
// GetPermissions 获取角色的所有权限
|
||||
func (s *Service) GetPermissions(ctx context.Context, roleID uint) ([]*model.Permission, error) {
|
||||
// 检查角色存在
|
||||
_, err := s.roleStore.GetByID(ctx, roleID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, errors.New(errors.CodeRoleNotFound, "角色不存在")
|
||||
}
|
||||
return nil, fmt.Errorf("获取角色失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取权限 ID 列表
|
||||
permIDs, err := s.rolePermissionStore.GetPermIDsByRoleID(ctx, roleID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取角色权限 ID 失败: %w", err)
|
||||
}
|
||||
|
||||
if len(permIDs) == 0 {
|
||||
return []*model.Permission{}, nil
|
||||
}
|
||||
|
||||
// 获取权限详情
|
||||
return s.permissionStore.GetByIDs(ctx, permIDs)
|
||||
}
|
||||
|
||||
// RemovePermission 移除角色的权限
|
||||
func (s *Service) RemovePermission(ctx context.Context, roleID, permID uint) error {
|
||||
// 检查角色存在
|
||||
_, err := s.roleStore.GetByID(ctx, roleID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return errors.New(errors.CodeRoleNotFound, "角色不存在")
|
||||
}
|
||||
return fmt.Errorf("获取角色失败: %w", err)
|
||||
}
|
||||
|
||||
// 删除关联
|
||||
if err := s.rolePermissionStore.Delete(ctx, roleID, permID); err != nil {
|
||||
return fmt.Errorf("删除角色-权限关联失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
25
internal/store/options.go
Normal file
25
internal/store/options.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package store
|
||||
|
||||
// QueryOptions Store 查询选项
|
||||
// 用于控制 Store 层查询行为,支持分页、排序、数据权限过滤控制等
|
||||
type QueryOptions struct {
|
||||
// 分页选项
|
||||
Page int `json:"page"` // 页码(从 1 开始)
|
||||
PageSize int `json:"page_size"` // 每页记录数
|
||||
|
||||
// 排序选项
|
||||
OrderBy string `json:"order_by"` // 排序字段(例如 "created_at DESC")
|
||||
|
||||
// 数据权限过滤控制
|
||||
WithoutDataFilter bool `json:"without_data_filter"` // 跳过数据权限过滤(谨慎使用)
|
||||
}
|
||||
|
||||
// DefaultQueryOptions 返回默认查询选项
|
||||
func DefaultQueryOptions() *QueryOptions {
|
||||
return &QueryOptions{
|
||||
Page: 1,
|
||||
PageSize: 20,
|
||||
OrderBy: "id DESC",
|
||||
WithoutDataFilter: false,
|
||||
}
|
||||
}
|
||||
78
internal/store/postgres/account_role_store.go
Normal file
78
internal/store/postgres/account_role_store.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// AccountRoleStore 账号-角色关联数据访问层
|
||||
type AccountRoleStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAccountRoleStore 创建账号-角色关联 Store
|
||||
func NewAccountRoleStore(db *gorm.DB) *AccountRoleStore {
|
||||
return &AccountRoleStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建账号-角色关联
|
||||
func (s *AccountRoleStore) Create(ctx context.Context, ar *model.AccountRole) error {
|
||||
return s.db.WithContext(ctx).Create(ar).Error
|
||||
}
|
||||
|
||||
// BatchCreate 批量创建账号-角色关联
|
||||
func (s *AccountRoleStore) BatchCreate(ctx context.Context, ars []*model.AccountRole) error {
|
||||
return s.db.WithContext(ctx).Create(&ars).Error
|
||||
}
|
||||
|
||||
// Delete 软删除账号-角色关联
|
||||
func (s *AccountRoleStore) Delete(ctx context.Context, accountID, roleID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("account_id = ? AND role_id = ?", accountID, roleID).
|
||||
Delete(&model.AccountRole{}).Error
|
||||
}
|
||||
|
||||
// DeleteByAccountID 删除账号的所有角色关联
|
||||
func (s *AccountRoleStore) DeleteByAccountID(ctx context.Context, accountID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("account_id = ?", accountID).
|
||||
Delete(&model.AccountRole{}).Error
|
||||
}
|
||||
|
||||
// GetByAccountID 获取账号的所有角色关联
|
||||
func (s *AccountRoleStore) GetByAccountID(ctx context.Context, accountID uint) ([]*model.AccountRole, error) {
|
||||
var ars []*model.AccountRole
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("account_id = ?", accountID).
|
||||
Find(&ars).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ars, nil
|
||||
}
|
||||
|
||||
// GetRoleIDsByAccountID 获取账号的所有角色 ID
|
||||
func (s *AccountRoleStore) GetRoleIDsByAccountID(ctx context.Context, accountID uint) ([]uint, error) {
|
||||
var roleIDs []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.AccountRole{}).
|
||||
Where("account_id = ?", accountID).
|
||||
Pluck("role_id", &roleIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return roleIDs, nil
|
||||
}
|
||||
|
||||
// Exists 检查账号-角色关联是否存在
|
||||
func (s *AccountRoleStore) Exists(ctx context.Context, accountID, roleID uint) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.AccountRole{}).
|
||||
Where("account_id = ? AND role_id = ?", accountID, roleID).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
183
internal/store/postgres/account_store.go
Normal file
183
internal/store/postgres/account_store.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AccountStore 账号数据访问层
|
||||
type AccountStore struct {
|
||||
db *gorm.DB
|
||||
redis *redis.Client
|
||||
}
|
||||
|
||||
// NewAccountStore 创建账号 Store
|
||||
func NewAccountStore(db *gorm.DB, redis *redis.Client) *AccountStore {
|
||||
return &AccountStore{
|
||||
db: db,
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建账号
|
||||
func (s *AccountStore) Create(ctx context.Context, account *model.Account) error {
|
||||
return s.db.WithContext(ctx).Create(account).Error
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 获取账号
|
||||
func (s *AccountStore) GetByID(ctx context.Context, id uint) (*model.Account, error) {
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).First(&account, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// GetByUsername 根据用户名获取账号
|
||||
func (s *AccountStore) GetByUsername(ctx context.Context, username string) (*model.Account, error) {
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).Where("username = ?", username).First(&account).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// GetByPhone 根据手机号获取账号
|
||||
func (s *AccountStore) GetByPhone(ctx context.Context, phone string) (*model.Account, error) {
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).Where("phone = ?", phone).First(&account).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &account, nil
|
||||
}
|
||||
|
||||
// Update 更新账号
|
||||
func (s *AccountStore) Update(ctx context.Context, account *model.Account) error {
|
||||
return s.db.WithContext(ctx).Save(account).Error
|
||||
}
|
||||
|
||||
// Delete 软删除账号
|
||||
func (s *AccountStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Account{}, id).Error
|
||||
}
|
||||
|
||||
// List 查询账号列表
|
||||
func (s *AccountStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Account, int64, error) {
|
||||
var accounts []*model.Account
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Account{})
|
||||
|
||||
// 应用过滤条件
|
||||
if username, ok := filters["username"].(string); ok && username != "" {
|
||||
query = query.Where("username LIKE ?", "%"+username+"%")
|
||||
}
|
||||
if phone, ok := filters["phone"].(string); ok && phone != "" {
|
||||
query = query.Where("phone LIKE ?", "%"+phone+"%")
|
||||
}
|
||||
if userType, ok := filters["user_type"].(int); ok {
|
||||
query = query.Where("user_type = ?", userType)
|
||||
}
|
||||
if status, ok := filters["status"].(int); ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
// 排序
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
if err := query.Find(&accounts).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return accounts, total, nil
|
||||
}
|
||||
|
||||
// GetSubordinateIDs 获取用户的所有下级 ID(包含自己)
|
||||
// 使用 Redis 缓存优化性能,缓存 30 分钟
|
||||
func (s *AccountStore) GetSubordinateIDs(ctx context.Context, accountID uint) ([]uint, error) {
|
||||
// 1. 尝试从 Redis 缓存读取
|
||||
cacheKey := constants.RedisAccountSubordinatesKey(accountID)
|
||||
cached, err := s.redis.Get(ctx, cacheKey).Result()
|
||||
if err == nil {
|
||||
var ids []uint
|
||||
if err := sonic.Unmarshal([]byte(cached), &ids); err == nil {
|
||||
return ids, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 缓存未命中,执行递归查询
|
||||
query := `
|
||||
WITH RECURSIVE subordinates AS (
|
||||
-- 基础查询:选择当前账号
|
||||
SELECT id FROM tb_account WHERE id = ? AND deleted_at IS NULL
|
||||
UNION ALL
|
||||
-- 递归查询:选择所有下级(包括软删除的账号,因为它们的数据仍需对上级可见)
|
||||
SELECT a.id
|
||||
FROM tb_account a
|
||||
INNER JOIN subordinates s ON a.parent_id = s.id
|
||||
)
|
||||
SELECT id FROM subordinates
|
||||
`
|
||||
|
||||
var ids []uint
|
||||
if err := s.db.WithContext(ctx).Raw(query, accountID).Scan(&ids).Error; err != nil {
|
||||
return nil, fmt.Errorf("递归查询下级 ID 失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 写入 Redis 缓存(30 分钟过期)
|
||||
data, _ := sonic.Marshal(ids)
|
||||
s.redis.Set(ctx, cacheKey, data, 30*time.Minute)
|
||||
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// ClearSubordinatesCache 清除指定账号的下级 ID 缓存
|
||||
func (s *AccountStore) ClearSubordinatesCache(ctx context.Context, accountID uint) error {
|
||||
cacheKey := constants.RedisAccountSubordinatesKey(accountID)
|
||||
return s.redis.Del(ctx, cacheKey).Err()
|
||||
}
|
||||
|
||||
// ClearSubordinatesCacheForParents 递归清除所有上级账号的缓存
|
||||
func (s *AccountStore) ClearSubordinatesCacheForParents(ctx context.Context, accountID uint) error {
|
||||
// 查询当前账号
|
||||
var account model.Account
|
||||
if err := s.db.WithContext(ctx).First(&account, accountID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除当前账号的缓存
|
||||
if err := s.ClearSubordinatesCache(ctx, accountID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果有上级,递归清除上级的缓存
|
||||
if account.ParentID != nil && *account.ParentID != 0 {
|
||||
return s.ClearSubordinatesCacheForParents(ctx, *account.ParentID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
122
internal/store/postgres/permission_store.go
Normal file
122
internal/store/postgres/permission_store.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
)
|
||||
|
||||
// PermissionStore 权限数据访问层
|
||||
type PermissionStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewPermissionStore 创建权限 Store
|
||||
func NewPermissionStore(db *gorm.DB) *PermissionStore {
|
||||
return &PermissionStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建权限
|
||||
func (s *PermissionStore) Create(ctx context.Context, permission *model.Permission) error {
|
||||
return s.db.WithContext(ctx).Create(permission).Error
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 获取权限
|
||||
func (s *PermissionStore) GetByID(ctx context.Context, id uint) (*model.Permission, error) {
|
||||
var permission model.Permission
|
||||
if err := s.db.WithContext(ctx).First(&permission, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &permission, nil
|
||||
}
|
||||
|
||||
// GetByCode 根据权限编码获取权限
|
||||
func (s *PermissionStore) GetByCode(ctx context.Context, code string) (*model.Permission, error) {
|
||||
var permission model.Permission
|
||||
if err := s.db.WithContext(ctx).Where("perm_code = ?", code).First(&permission).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &permission, nil
|
||||
}
|
||||
|
||||
// Update 更新权限
|
||||
func (s *PermissionStore) Update(ctx context.Context, permission *model.Permission) error {
|
||||
return s.db.WithContext(ctx).Save(permission).Error
|
||||
}
|
||||
|
||||
// Delete 软删除权限
|
||||
func (s *PermissionStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Permission{}, id).Error
|
||||
}
|
||||
|
||||
// List 查询权限列表
|
||||
func (s *PermissionStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Permission, int64, error) {
|
||||
var permissions []*model.Permission
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Permission{})
|
||||
|
||||
// 应用过滤条件
|
||||
if name, ok := filters["perm_name"].(string); ok && name != "" {
|
||||
query = query.Where("perm_name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
if code, ok := filters["perm_code"].(string); ok && code != "" {
|
||||
query = query.Where("perm_code LIKE ?", "%"+code+"%")
|
||||
}
|
||||
if permType, ok := filters["perm_type"].(int); ok {
|
||||
query = query.Where("perm_type = ?", permType)
|
||||
}
|
||||
if parentID, ok := filters["parent_id"].(uint); ok {
|
||||
query = query.Where("parent_id = ?", parentID)
|
||||
}
|
||||
if status, ok := filters["status"].(int); ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
// 排序
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
} else {
|
||||
query = query.Order("sort ASC, id ASC")
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
if err := query.Find(&permissions).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return permissions, total, nil
|
||||
}
|
||||
|
||||
// GetByIDs 根据 ID 列表获取权限
|
||||
func (s *PermissionStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Permission, error) {
|
||||
var permissions []*model.Permission
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", ids).Find(&permissions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
// GetAll 获取所有权限(用于构建权限树)
|
||||
func (s *PermissionStore) GetAll(ctx context.Context) ([]*model.Permission, error) {
|
||||
var permissions []*model.Permission
|
||||
if err := s.db.WithContext(ctx).Order("sort ASC, id ASC").Find(&permissions).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
91
internal/store/postgres/role_permission_store.go
Normal file
91
internal/store/postgres/role_permission_store.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
)
|
||||
|
||||
// RolePermissionStore 角色-权限关联数据访问层
|
||||
type RolePermissionStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewRolePermissionStore 创建角色-权限关联 Store
|
||||
func NewRolePermissionStore(db *gorm.DB) *RolePermissionStore {
|
||||
return &RolePermissionStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建角色-权限关联
|
||||
func (s *RolePermissionStore) Create(ctx context.Context, rp *model.RolePermission) error {
|
||||
return s.db.WithContext(ctx).Create(rp).Error
|
||||
}
|
||||
|
||||
// BatchCreate 批量创建角色-权限关联
|
||||
func (s *RolePermissionStore) BatchCreate(ctx context.Context, rps []*model.RolePermission) error {
|
||||
return s.db.WithContext(ctx).Create(&rps).Error
|
||||
}
|
||||
|
||||
// Delete 软删除角色-权限关联
|
||||
func (s *RolePermissionStore) Delete(ctx context.Context, roleID, permID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("role_id = ? AND perm_id = ?", roleID, permID).
|
||||
Delete(&model.RolePermission{}).Error
|
||||
}
|
||||
|
||||
// DeleteByRoleID 删除角色的所有权限关联
|
||||
func (s *RolePermissionStore) DeleteByRoleID(ctx context.Context, roleID uint) error {
|
||||
return s.db.WithContext(ctx).
|
||||
Where("role_id = ?", roleID).
|
||||
Delete(&model.RolePermission{}).Error
|
||||
}
|
||||
|
||||
// GetByRoleID 获取角色的所有权限关联
|
||||
func (s *RolePermissionStore) GetByRoleID(ctx context.Context, roleID uint) ([]*model.RolePermission, error) {
|
||||
var rps []*model.RolePermission
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("role_id = ?", roleID).
|
||||
Find(&rps).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rps, nil
|
||||
}
|
||||
|
||||
// GetPermIDsByRoleID 获取角色的所有权限 ID
|
||||
func (s *RolePermissionStore) GetPermIDsByRoleID(ctx context.Context, roleID uint) ([]uint, error) {
|
||||
var permIDs []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.RolePermission{}).
|
||||
Where("role_id = ?", roleID).
|
||||
Pluck("perm_id", &permIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permIDs, nil
|
||||
}
|
||||
|
||||
// GetPermIDsByRoleIDs 获取多个角色的所有权限 ID
|
||||
func (s *RolePermissionStore) GetPermIDsByRoleIDs(ctx context.Context, roleIDs []uint) ([]uint, error) {
|
||||
var permIDs []uint
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.RolePermission{}).
|
||||
Where("role_id IN ?", roleIDs).
|
||||
Distinct().
|
||||
Pluck("perm_id", &permIDs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permIDs, nil
|
||||
}
|
||||
|
||||
// Exists 检查角色-权限关联是否存在
|
||||
func (s *RolePermissionStore) Exists(ctx context.Context, roleID, permID uint) (bool, error) {
|
||||
var count int64
|
||||
if err := s.db.WithContext(ctx).
|
||||
Model(&model.RolePermission{}).
|
||||
Where("role_id = ? AND perm_id = ?", roleID, permID).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
105
internal/store/postgres/role_store.go
Normal file
105
internal/store/postgres/role_store.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/internal/model"
|
||||
"github.com/break/junhong_cmp_fiber/internal/store"
|
||||
)
|
||||
|
||||
// RoleStore 角色数据访问层
|
||||
type RoleStore struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewRoleStore 创建角色 Store
|
||||
func NewRoleStore(db *gorm.DB) *RoleStore {
|
||||
return &RoleStore{db: db}
|
||||
}
|
||||
|
||||
// Create 创建角色
|
||||
func (s *RoleStore) Create(ctx context.Context, role *model.Role) error {
|
||||
return s.db.WithContext(ctx).Create(role).Error
|
||||
}
|
||||
|
||||
// GetByID 根据 ID 获取角色
|
||||
func (s *RoleStore) GetByID(ctx context.Context, id uint) (*model.Role, error) {
|
||||
var role model.Role
|
||||
if err := s.db.WithContext(ctx).First(&role, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// GetByName 根据名称获取角色
|
||||
func (s *RoleStore) GetByName(ctx context.Context, name string) (*model.Role, error) {
|
||||
var role model.Role
|
||||
if err := s.db.WithContext(ctx).Where("role_name = ?", name).First(&role).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
|
||||
// Update 更新角色
|
||||
func (s *RoleStore) Update(ctx context.Context, role *model.Role) error {
|
||||
return s.db.WithContext(ctx).Save(role).Error
|
||||
}
|
||||
|
||||
// Delete 软删除角色
|
||||
func (s *RoleStore) Delete(ctx context.Context, id uint) error {
|
||||
return s.db.WithContext(ctx).Delete(&model.Role{}, id).Error
|
||||
}
|
||||
|
||||
// List 查询角色列表
|
||||
func (s *RoleStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Role, int64, error) {
|
||||
var roles []*model.Role
|
||||
var total int64
|
||||
|
||||
query := s.db.WithContext(ctx).Model(&model.Role{})
|
||||
|
||||
// 应用过滤条件
|
||||
if name, ok := filters["role_name"].(string); ok && name != "" {
|
||||
query = query.Where("role_name LIKE ?", "%"+name+"%")
|
||||
}
|
||||
if roleType, ok := filters["role_type"].(int); ok {
|
||||
query = query.Where("role_type = ?", roleType)
|
||||
}
|
||||
if status, ok := filters["status"].(int); ok {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
// 计算总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页
|
||||
if opts == nil {
|
||||
opts = store.DefaultQueryOptions()
|
||||
}
|
||||
offset := (opts.Page - 1) * opts.PageSize
|
||||
query = query.Offset(offset).Limit(opts.PageSize)
|
||||
|
||||
// 排序
|
||||
if opts.OrderBy != "" {
|
||||
query = query.Order(opts.OrderBy)
|
||||
}
|
||||
|
||||
// 执行查询
|
||||
if err := query.Find(&roles).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return roles, total, nil
|
||||
}
|
||||
|
||||
// GetByIDs 根据 ID 列表获取角色
|
||||
func (s *RoleStore) GetByIDs(ctx context.Context, ids []uint) ([]*model.Role, error) {
|
||||
var roles []*model.Role
|
||||
if err := s.db.WithContext(ctx).Where("id IN ?", ids).Find(&roles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
86
internal/store/postgres/scopes.go
Normal file
86
internal/store/postgres/scopes.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/break/junhong_cmp_fiber/pkg/constants"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/logger"
|
||||
"github.com/break/junhong_cmp_fiber/pkg/middleware"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// DataPermissionScope 数据权限过滤 Scope
|
||||
// 根据 context 中的用户信息自动过滤数据
|
||||
// - root 用户跳过过滤
|
||||
// - 普通用户只能查看自己和下级的数据
|
||||
// - 同时限制 shop_id 相同
|
||||
func DataPermissionScope(ctx context.Context, accountStore *AccountStore) func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
// 1. 检查是否为 root 用户,root 用户跳过数据权限过滤
|
||||
if middleware.IsRootUser(ctx) {
|
||||
return db
|
||||
}
|
||||
|
||||
// 2. 获取当前用户 ID
|
||||
userID := middleware.GetUserIDFromContext(ctx)
|
||||
if userID == 0 {
|
||||
// 未登录用户返回空结果
|
||||
logger.GetAppLogger().Warn("数据权限过滤:未获取到用户 ID")
|
||||
return db.Where("1 = 0")
|
||||
}
|
||||
|
||||
// 3. 获取当前用户的 shop_id
|
||||
shopID := middleware.GetShopIDFromContext(ctx)
|
||||
|
||||
// 4. 获取当前用户及所有下级的 ID
|
||||
subordinateIDs, err := accountStore.GetSubordinateIDs(ctx, userID)
|
||||
if err != nil {
|
||||
// 查询失败时,降级为只能看自己的数据
|
||||
|
||||
logger.GetAppLogger().Error("数据权限过滤:获取下级 ID 失败",
|
||||
zap.Uint("user_id", userID),
|
||||
zap.Error(err))
|
||||
subordinateIDs = []uint{userID}
|
||||
}
|
||||
|
||||
// 5. 应用数据权限过滤条件
|
||||
// owner_id IN (用户自己及所有下级) AND shop_id = 当前用户 shop_id
|
||||
if len(subordinateIDs) == 0 {
|
||||
subordinateIDs = []uint{userID}
|
||||
}
|
||||
|
||||
// 根据是否有 shop_id 过滤条件决定 SQL
|
||||
if shopID != 0 {
|
||||
return db.Where("owner_id IN ? AND shop_id = ?", subordinateIDs, shopID)
|
||||
}
|
||||
|
||||
// 如果 shop_id 为 0,只根据 owner_id 过滤
|
||||
return db.Where("owner_id IN ?", subordinateIDs)
|
||||
}
|
||||
}
|
||||
|
||||
// WithoutDataPermission 跳过数据权限过滤的 Scope
|
||||
// 用于需要查询所有数据的场景(如管理后台统计、系统任务等)
|
||||
func WithoutDataPermission() func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
// 什么都不做,直接返回原 db
|
||||
return db
|
||||
}
|
||||
}
|
||||
|
||||
// SoftDeleteScope 软删除过滤 Scope(GORM 默认已支持,此处作为示例)
|
||||
// 只查询未软删除的记录
|
||||
func SoftDeleteScope() func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("deleted_at IS NULL")
|
||||
}
|
||||
}
|
||||
|
||||
// StatusEnabledScope 状态启用过滤 Scope
|
||||
// 只查询状态为启用的记录
|
||||
func StatusEnabledScope() func(db *gorm.DB) *gorm.DB {
|
||||
return func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("status = ?", constants.StatusEnabled)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user