refactor(account): 统一账号管理API、完善权限检查和操作审计
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 6m17s

- 合并 customer_account 和 shop_account 路由到统一的 account 接口
- 新增统一认证接口 (auth handler)
- 实现越权防护中间件和权限检查工具函数
- 新增操作审计日志模型和服务
- 更新数据库迁移 (版本 39: account_operation_log 表)
- 补充集成测试覆盖权限检查和审计日志场景
This commit is contained in:
2026-02-02 17:23:20 +08:00
parent 5851cc6403
commit 80f560df33
58 changed files with 10743 additions and 4915 deletions

View File

@@ -4,163 +4,114 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// registerAccountRoutes 注册账号相关路由
// 统一路由结构:/api/admin/accounts/*
// 账号类型通过请求体的 user_type 字段区分2=平台用户3=代理账号4=企业账号)
func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *openapi.Generator, basePath string) {
accounts := api.Group("/accounts")
groupPath := basePath + "/accounts"
accountsPath := basePath + "/accounts"
// 账号 CRUD
Register(accounts, doc, groupPath, "POST", "", h.Create, RouteSpec{
// 企业用户拦截中间件:禁止企业用户访问账号管理接口
accounts.Use(func(c *fiber.Ctx) error {
userType := middleware.GetUserTypeFromContext(c.UserContext())
if userType == constants.UserTypeEnterprise {
return errors.New(errors.CodeForbidden, "无权限访问账号管理功能")
}
return c.Next()
})
// 创建账号user_type: 2=平台, 3=代理, 4=企业)
Register(accounts, doc, accountsPath, "POST", "", h.Create, RouteSpec{
Summary: "创建账号",
Tags: []string{"账号相关"},
Tags: []string{"账号管理"},
Input: new(dto.CreateAccountRequest),
Output: new(dto.AccountResponse),
Auth: true,
})
Register(accounts, doc, groupPath, "GET", "", h.List, RouteSpec{
Summary: "账号列表",
Tags: []string{"账号相关"},
// 查询账号列表(可通过 user_type 参数筛选)
Register(accounts, doc, accountsPath, "GET", "", h.List, RouteSpec{
Summary: "查询账号列表",
Tags: []string{"账号管理"},
Input: new(dto.AccountListRequest),
Output: new(dto.AccountPageResult),
Auth: true,
})
Register(accounts, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{
// 获取账号详情
Register(accounts, doc, accountsPath, "GET", "/:id", h.Get, RouteSpec{
Summary: "获取账号详情",
Tags: []string{"账号相关"},
Tags: []string{"账号管理"},
Input: new(dto.IDReq),
Output: new(dto.AccountResponse),
Auth: true,
})
Register(accounts, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{
// 更新账号
Register(accounts, doc, accountsPath, "PUT", "/:id", h.Update, RouteSpec{
Summary: "更新账号",
Tags: []string{"账号相关"},
Tags: []string{"账号管理"},
Input: new(dto.UpdateAccountParams),
Output: new(dto.AccountResponse),
Auth: true,
})
Register(accounts, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{
// 删除账号
Register(accounts, doc, accountsPath, "DELETE", "/:id", h.Delete, RouteSpec{
Summary: "删除账号",
Tags: []string{"账号相关"},
Tags: []string{"账号管理"},
Input: new(dto.IDReq),
Output: nil,
Auth: true,
})
// 账号-角色关联
Register(accounts, doc, groupPath, "POST", "/:id/roles", h.AssignRoles, RouteSpec{
Summary: "分配角色",
Tags: []string{"账号相关"},
Input: new(dto.AssignRolesParams),
Output: nil, // TODO: Define AccountRole response DTO
})
Register(accounts, doc, groupPath, "GET", "/:id/roles", h.GetRoles, RouteSpec{
Summary: "获取账号角色",
Tags: []string{"账号相关"},
Input: new(dto.IDReq),
Output: new([]model.Role),
Auth: true,
})
Register(accounts, doc, groupPath, "DELETE", "/:account_id/roles/:role_id", h.RemoveRole, RouteSpec{
Summary: "移除角色",
Tags: []string{"账号相关"},
Input: new(dto.RemoveRoleParams),
Output: nil,
Auth: true,
})
registerPlatformAccountRoutes(api, h, doc, basePath)
}
func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *openapi.Generator, basePath string) {
platformAccounts := api.Group("/platform-accounts")
groupPath := basePath + "/platform-accounts"
Register(platformAccounts, doc, groupPath, "GET", "", h.ListPlatformAccounts, RouteSpec{
Summary: "平台账号列表",
Tags: []string{"平台账号"},
Input: new(dto.PlatformAccountListRequest),
Output: new(dto.AccountPageResult),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "POST", "", h.Create, RouteSpec{
Summary: "新增平台账号",
Tags: []string{"平台账号"},
Input: new(dto.CreateAccountRequest),
Output: new(dto.AccountResponse),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{
Summary: "获取平台账号详情",
Tags: []string{"平台账号"},
Input: new(dto.IDReq),
Output: new(dto.AccountResponse),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{
Summary: "编辑平台账号",
Tags: []string{"平台账号"},
Input: new(dto.UpdateAccountParams),
Output: new(dto.AccountResponse),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{
Summary: "删除平台账号",
Tags: []string{"平台账号"},
Input: new(dto.IDReq),
Output: nil,
Auth: true,
})
Register(platformAccounts, doc, groupPath, "PUT", "/:id/password", h.UpdatePassword, RouteSpec{
Summary: "修改密码",
Tags: []string{"平台账号"},
// 修改账号密码
Register(accounts, doc, accountsPath, "PUT", "/:id/password", h.UpdatePassword, RouteSpec{
Summary: "修改账号密码",
Tags: []string{"账号管理"},
Input: new(dto.UpdatePasswordParams),
Output: nil,
Auth: true,
})
Register(platformAccounts, doc, groupPath, "PUT", "/:id/status", h.UpdateStatus, RouteSpec{
Summary: "启用/禁用账号",
Tags: []string{"平台账号"},
// 修改账号状态
Register(accounts, doc, accountsPath, "PUT", "/:id/status", h.UpdateStatus, RouteSpec{
Summary: "修改账号状态",
Tags: []string{"账号管理"},
Input: new(dto.UpdateStatusParams),
Output: nil,
Auth: true,
})
Register(platformAccounts, doc, groupPath, "POST", "/:id/roles", h.AssignRoles, RouteSpec{
Summary: "分配角色",
Tags: []string{"平台账号"},
// 为账号分配角色
Register(accounts, doc, accountsPath, "POST", "/:id/roles", h.AssignRoles, RouteSpec{
Summary: "为账号分配角色",
Tags: []string{"账号管理"},
Input: new(dto.AssignRolesParams),
Output: nil,
Output: new([]dto.AccountRoleResponse),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "GET", "/:id/roles", h.GetRoles, RouteSpec{
// 获取账号角色
Register(accounts, doc, accountsPath, "GET", "/:id/roles", h.GetRoles, RouteSpec{
Summary: "获取账号角色",
Tags: []string{"平台账号"},
Tags: []string{"账号管理"},
Input: new(dto.IDReq),
Output: new([]model.Role),
Output: new(dto.AccountRolesResponse),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "DELETE", "/:account_id/roles/:role_id", h.RemoveRole, RouteSpec{
Summary: "移除角色",
Tags: []string{"平台账号"},
// 移除账号角色
Register(accounts, doc, accountsPath, "DELETE", "/:account_id/roles/:role_id", h.RemoveRole, RouteSpec{
Summary: "移除账号角色",
Tags: []string{"账号管理"},
Input: new(dto.RemoveRoleParams),
Output: nil,
Auth: true,

View File

@@ -4,16 +4,12 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// RegisterAdminRoutes 注册管理后台相关路由
func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, middlewares *bootstrap.Middlewares, doc *openapi.Generator, basePath string) {
if handlers.AdminAuth != nil {
registerAdminAuthRoutes(router, handlers.AdminAuth, middlewares.AdminAuth, doc, basePath)
}
// 认证路由已迁移到 /api/auth参见 RegisterAuthRoutes
authGroup := router.Group("", middlewares.AdminAuth)
if handlers.Account != nil {
@@ -28,9 +24,7 @@ func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, midd
if handlers.Shop != nil {
registerShopRoutes(authGroup, handlers.Shop, doc, basePath)
}
if handlers.ShopAccount != nil {
registerShopAccountRoutes(authGroup, handlers.ShopAccount, doc, basePath)
}
if handlers.ShopCommission != nil {
registerShopCommissionRoutes(authGroup, handlers.ShopCommission, doc, basePath)
}
@@ -52,9 +46,7 @@ func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, midd
if handlers.Authorization != nil {
registerAuthorizationRoutes(authGroup, handlers.Authorization, doc, basePath)
}
if handlers.CustomerAccount != nil {
registerCustomerAccountRoutes(authGroup, handlers.CustomerAccount, doc, basePath)
}
if handlers.MyCommission != nil {
registerMyCommissionRoutes(authGroup, handlers.MyCommission, doc, basePath)
}
@@ -95,55 +87,3 @@ func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, midd
registerAdminOrderRoutes(authGroup, handlers.AdminOrder, doc, basePath)
}
}
func registerAdminAuthRoutes(router fiber.Router, handler interface{}, authMiddleware fiber.Handler, doc *openapi.Generator, basePath string) {
h := handler.(interface {
Login(c *fiber.Ctx) error
Logout(c *fiber.Ctx) error
RefreshToken(c *fiber.Ctx) error
GetMe(c *fiber.Ctx) error
ChangePassword(c *fiber.Ctx) error
})
Register(router, doc, basePath, "POST", "/login", h.Login, RouteSpec{
Summary: "后台登录",
Tags: []string{"认证"},
Input: new(dto.LoginRequest),
Output: new(dto.LoginResponse),
Auth: false,
})
Register(router, doc, basePath, "POST", "/refresh-token", h.RefreshToken, RouteSpec{
Summary: "刷新 Token",
Tags: []string{"认证"},
Input: new(dto.RefreshTokenRequest),
Output: new(dto.RefreshTokenResponse),
Auth: false,
})
authGroup := router.Group("", authMiddleware)
Register(authGroup, doc, basePath, "POST", "/logout", h.Logout, RouteSpec{
Summary: "登出",
Tags: []string{"认证"},
Input: nil,
Output: nil,
Auth: true,
})
Register(authGroup, doc, basePath, "GET", "/me", h.GetMe, RouteSpec{
Summary: "获取当前用户信息",
Tags: []string{"认证"},
Input: nil,
Output: new(dto.UserInfo),
Auth: true,
})
Register(authGroup, doc, basePath, "PUT", "/password", h.ChangePassword, RouteSpec{
Summary: "修改密码",
Tags: []string{"认证"},
Input: new(dto.ChangePasswordRequest),
Output: nil,
Auth: true,
})
}

57
internal/routes/auth.go Normal file
View File

@@ -0,0 +1,57 @@
package routes
import (
"github.com/gofiber/fiber/v2"
authHandler "github.com/break/junhong_cmp_fiber/internal/handler/auth"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// RegisterAuthRoutes 注册统一认证路由
// 路由挂载在 /api/auth 下
func RegisterAuthRoutes(router fiber.Router, handler *authHandler.Handler, authMiddleware fiber.Handler, doc *openapi.Generator, basePath string) {
// 公开路由(不需要认证)
Register(router, doc, basePath, "POST", "/login", handler.Login, RouteSpec{
Summary: "统一登录(后台+H5",
Tags: []string{"统一认证"},
Input: new(dto.LoginRequest),
Output: new(dto.LoginResponse),
Auth: false,
})
Register(router, doc, basePath, "POST", "/refresh-token", handler.RefreshToken, RouteSpec{
Summary: "刷新 Token",
Tags: []string{"统一认证"},
Input: new(dto.RefreshTokenRequest),
Output: new(dto.RefreshTokenResponse),
Auth: false,
})
// 需要认证的路由
authGroup := router.Group("", authMiddleware)
Register(authGroup, doc, basePath, "POST", "/logout", handler.Logout, RouteSpec{
Summary: "统一登出",
Tags: []string{"统一认证"},
Input: nil,
Output: nil,
Auth: true,
})
Register(authGroup, doc, basePath, "GET", "/me", handler.GetMe, RouteSpec{
Summary: "获取用户信息",
Tags: []string{"统一认证"},
Input: nil,
Output: new(dto.UserInfo),
Auth: true,
})
Register(authGroup, doc, basePath, "PUT", "/password", handler.ChangePassword, RouteSpec{
Summary: "修改密码",
Tags: []string{"统一认证"},
Input: new(dto.ChangePasswordRequest),
Output: nil,
Auth: true,
})
}

View File

@@ -1,54 +0,0 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
func registerCustomerAccountRoutes(router fiber.Router, handler *admin.CustomerAccountHandler, doc *openapi.Generator, basePath string) {
accounts := router.Group("/customer-accounts")
groupPath := basePath + "/customer-accounts"
Register(accounts, doc, groupPath, "GET", "", handler.List, RouteSpec{
Summary: "客户账号列表",
Tags: []string{"客户账号管理"},
Input: new(dto.CustomerAccountListReq),
Output: new(dto.CustomerAccountPageResult),
Auth: true,
})
Register(accounts, doc, groupPath, "POST", "", handler.Create, RouteSpec{
Summary: "新增代理商账号",
Tags: []string{"客户账号管理"},
Input: new(dto.CreateCustomerAccountReq),
Output: new(dto.CustomerAccountItem),
Auth: true,
})
Register(accounts, doc, groupPath, "PUT", "/:id", handler.Update, RouteSpec{
Summary: "编辑账号",
Tags: []string{"客户账号管理"},
Input: new(dto.UpdateCustomerAccountReq),
Output: new(dto.CustomerAccountItem),
Auth: true,
})
Register(accounts, doc, groupPath, "PUT", "/:id/password", handler.UpdatePassword, RouteSpec{
Summary: "修改账号密码",
Tags: []string{"客户账号管理"},
Input: new(dto.UpdateCustomerAccountPasswordReq),
Output: nil,
Auth: true,
})
Register(accounts, doc, groupPath, "PUT", "/:id/status", handler.UpdateStatus, RouteSpec{
Summary: "修改账号状态",
Tags: []string{"客户账号管理"},
Input: new(dto.UpdateCustomerAccountStatusReq),
Output: nil,
Auth: true,
})
}

View File

@@ -4,17 +4,12 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// RegisterH5Routes 注册H5相关路由
func RegisterH5Routes(router fiber.Router, handlers *bootstrap.Handlers, middlewares *bootstrap.Middlewares, doc *openapi.Generator, basePath string) {
if handlers.H5Auth != nil {
registerH5AuthRoutes(router, handlers.H5Auth, middlewares.H5Auth, doc, basePath)
}
// 需要认证的路由组
// 认证路由已迁移到 /api/auth参见 RegisterAuthRoutes
authGroup := router.Group("", middlewares.H5Auth)
if handlers.H5Order != nil {
@@ -27,55 +22,3 @@ func RegisterH5Routes(router fiber.Router, handlers *bootstrap.Handlers, middlew
registerH5EnterpriseDeviceRoutes(authGroup, handlers.EnterpriseDeviceH5, doc, basePath)
}
}
func registerH5AuthRoutes(router fiber.Router, handler interface{}, authMiddleware fiber.Handler, doc *openapi.Generator, basePath string) {
h := handler.(interface {
Login(c *fiber.Ctx) error
Logout(c *fiber.Ctx) error
RefreshToken(c *fiber.Ctx) error
GetMe(c *fiber.Ctx) error
ChangePassword(c *fiber.Ctx) error
})
Register(router, doc, basePath, "POST", "/login", h.Login, RouteSpec{
Summary: "H5 登录",
Tags: []string{"H5 认证"},
Input: new(dto.LoginRequest),
Output: new(dto.LoginResponse),
Auth: false,
})
Register(router, doc, basePath, "POST", "/refresh-token", h.RefreshToken, RouteSpec{
Summary: "刷新 Token",
Tags: []string{"H5 认证"},
Input: new(dto.RefreshTokenRequest),
Output: new(dto.RefreshTokenResponse),
Auth: false,
})
authGroup := router.Group("", authMiddleware)
Register(authGroup, doc, basePath, "POST", "/logout", h.Logout, RouteSpec{
Summary: "登出",
Tags: []string{"H5 认证"},
Input: nil,
Output: nil,
Auth: true,
})
Register(authGroup, doc, basePath, "GET", "/me", h.GetMe, RouteSpec{
Summary: "获取当前用户信息",
Tags: []string{"H5 认证"},
Input: nil,
Output: new(dto.UserInfo),
Auth: true,
})
Register(authGroup, doc, basePath, "PUT", "/password", h.ChangePassword, RouteSpec{
Summary: "修改密码",
Tags: []string{"H5 认证"},
Input: new(dto.ChangePasswordRequest),
Output: nil,
Auth: true,
})
}

View File

@@ -18,19 +18,25 @@ func RegisterRoutesWithDoc(app *fiber.App, handlers *bootstrap.Handlers, middlew
// 1. 全局路由
registerHealthRoutes(app, doc)
// 2. Admin 域 (挂载在 /api/admin)
// 2. 统一认证路由 (挂载在 /api/auth)
if handlers.Auth != nil {
authGroup := app.Group("/api/auth")
RegisterAuthRoutes(authGroup, handlers.Auth, middlewares.AdminAuth, doc, "/api/auth")
}
// 3. Admin 域 (挂载在 /api/admin)
adminGroup := app.Group("/api/admin")
RegisterAdminRoutes(adminGroup, handlers, middlewares, doc, "/api/admin")
// 3. H5 域 (挂载在 /api/h5)
// 4. H5 域 (挂载在 /api/h5)
h5Group := app.Group("/api/h5")
RegisterH5Routes(h5Group, handlers, middlewares, doc, "/api/h5")
// 4. 个人客户路由 (挂载在 /api/c/v1)
// 5. 个人客户路由 (挂载在 /api/c/v1)
personalGroup := app.Group("/api/c/v1")
RegisterPersonalCustomerRoutes(personalGroup, doc, "/api/c/v1", handlers, middlewares.PersonalAuth)
// 5. 支付回调路由 (挂载在 /api/callback无需认证)
// 6. 支付回调路由 (挂载在 /api/callback无需认证)
if handlers.PaymentCallback != nil {
callbackGroup := app.Group("/api/callback")
registerPaymentCallbackRoutes(callbackGroup, handlers.PaymentCallback, doc, "/api/callback")

View File

@@ -45,51 +45,6 @@ func registerShopRoutes(router fiber.Router, handler *admin.ShopHandler, doc *op
})
}
func registerShopAccountRoutes(router fiber.Router, handler *admin.ShopAccountHandler, doc *openapi.Generator, basePath string) {
shopAccounts := router.Group("/shop-accounts")
groupPath := basePath + "/shop-accounts"
Register(shopAccounts, doc, groupPath, "GET", "", handler.List, RouteSpec{
Summary: "代理账号列表",
Tags: []string{"代理账号管理"},
Input: new(dto.ShopAccountListRequest),
Output: new(dto.ShopAccountPageResult),
Auth: true,
})
Register(shopAccounts, doc, groupPath, "POST", "", handler.Create, RouteSpec{
Summary: "创建代理账号",
Tags: []string{"代理账号管理"},
Input: new(dto.CreateShopAccountRequest),
Output: new(dto.ShopAccountResponse),
Auth: true,
})
Register(shopAccounts, doc, groupPath, "PUT", "/:id", handler.Update, RouteSpec{
Summary: "更新代理账号",
Tags: []string{"代理账号管理"},
Input: new(dto.UpdateShopAccountParams),
Output: new(dto.ShopAccountResponse),
Auth: true,
})
Register(shopAccounts, doc, groupPath, "PUT", "/:id/password", handler.UpdatePassword, RouteSpec{
Summary: "重置代理账号密码",
Tags: []string{"代理账号管理"},
Input: new(dto.UpdateShopAccountPasswordParams),
Output: nil,
Auth: true,
})
Register(shopAccounts, doc, groupPath, "PUT", "/:id/status", handler.UpdateStatus, RouteSpec{
Summary: "启用/禁用代理账号",
Tags: []string{"代理账号管理"},
Input: new(dto.UpdateShopAccountStatusParams),
Output: nil,
Auth: true,
})
}
func registerShopCommissionRoutes(router fiber.Router, handler *admin.ShopCommissionHandler, doc *openapi.Generator, basePath string) {
shops := router.Group("/shops")
groupPath := basePath + "/shops"