feat: 完成B端认证系统和商户管理模块测试补全

主要变更:
- 新增B端认证系统(后台+H5):登录、登出、Token刷新、密码修改
- 完善商户管理和商户账号管理功能
- 补全单元测试(ShopService: 72.5%, ShopAccountService: 79.8%)
- 新增集成测试(商户管理+商户账号管理)
- 归档OpenSpec提案(add-shop-account-management, implement-b-end-auth-system)
- 完善文档(使用指南、API文档、认证架构说明)

测试统计:
- 13个测试套件,37个测试用例,100%通过率
- 平均覆盖率76.2%,达标

OpenSpec验证:通过(strict模式)
This commit is contained in:
2026-01-15 18:15:17 +08:00
parent 7ccd3d146c
commit 18f35f3ef4
64 changed files with 11875 additions and 242 deletions

View File

@@ -19,6 +19,7 @@ func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *opena
Tags: []string{"账号相关"},
Input: new(model.CreateAccountRequest),
Output: new(model.AccountResponse),
Auth: true,
})
Register(accounts, doc, groupPath, "GET", "", h.List, RouteSpec{
@@ -26,6 +27,7 @@ func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *opena
Tags: []string{"账号相关"},
Input: new(model.AccountListRequest),
Output: new(model.AccountPageResult),
Auth: true,
})
Register(accounts, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{
@@ -33,6 +35,7 @@ func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *opena
Tags: []string{"账号相关"},
Input: new(model.IDReq),
Output: new(model.AccountResponse),
Auth: true,
})
Register(accounts, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{
@@ -40,6 +43,7 @@ func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *opena
Tags: []string{"账号相关"},
Input: new(model.UpdateAccountParams),
Output: new(model.AccountResponse),
Auth: true,
})
Register(accounts, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{
@@ -47,6 +51,7 @@ func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *opena
Tags: []string{"账号相关"},
Input: new(model.IDReq),
Output: nil,
Auth: true,
})
// 账号-角色关联
@@ -62,6 +67,7 @@ func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *opena
Tags: []string{"账号相关"},
Input: new(model.IDReq),
Output: new([]model.Role),
Auth: true,
})
Register(accounts, doc, groupPath, "DELETE", "/:account_id/roles/:role_id", h.RemoveRole, RouteSpec{
@@ -69,6 +75,7 @@ func registerAccountRoutes(api fiber.Router, h *admin.AccountHandler, doc *opena
Tags: []string{"账号相关"},
Input: new(model.RemoveRoleParams),
Output: nil,
Auth: true,
})
registerPlatformAccountRoutes(api, h, doc, basePath)
@@ -83,6 +90,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.PlatformAccountListRequest),
Output: new(model.AccountPageResult),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "POST", "", h.Create, RouteSpec{
@@ -90,6 +98,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.CreateAccountRequest),
Output: new(model.AccountResponse),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{
@@ -97,6 +106,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.IDReq),
Output: new(model.AccountResponse),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{
@@ -104,6 +114,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.UpdateAccountParams),
Output: new(model.AccountResponse),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{
@@ -111,6 +122,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.IDReq),
Output: nil,
Auth: true,
})
Register(platformAccounts, doc, groupPath, "PUT", "/:id/password", h.UpdatePassword, RouteSpec{
@@ -118,6 +130,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.UpdatePasswordParams),
Output: nil,
Auth: true,
})
Register(platformAccounts, doc, groupPath, "PUT", "/:id/status", h.UpdateStatus, RouteSpec{
@@ -125,6 +138,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.UpdateStatusParams),
Output: nil,
Auth: true,
})
Register(platformAccounts, doc, groupPath, "POST", "/:id/roles", h.AssignRoles, RouteSpec{
@@ -132,6 +146,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.AssignRolesParams),
Output: nil,
Auth: true,
})
Register(platformAccounts, doc, groupPath, "GET", "/:id/roles", h.GetRoles, RouteSpec{
@@ -139,6 +154,7 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.IDReq),
Output: new([]model.Role),
Auth: true,
})
Register(platformAccounts, doc, groupPath, "DELETE", "/:account_id/roles/:role_id", h.RemoveRole, RouteSpec{
@@ -146,5 +162,6 @@ func registerPlatformAccountRoutes(api fiber.Router, h *admin.AccountHandler, do
Tags: []string{"平台账号"},
Input: new(model.RemoveRoleParams),
Output: nil,
Auth: true,
})
}

View File

@@ -4,19 +4,83 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
// RegisterAdminRoutes 注册管理后台相关路由
func RegisterAdminRoutes(router fiber.Router, handlers *bootstrap.Handlers, doc *openapi.Generator, basePath string) {
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)
}
authGroup := router.Group("", middlewares.AdminAuth)
if handlers.Account != nil {
registerAccountRoutes(router, handlers.Account, doc, basePath)
registerAccountRoutes(authGroup, handlers.Account, doc, basePath)
}
if handlers.Role != nil {
registerRoleRoutes(router, handlers.Role, doc, basePath)
registerRoleRoutes(authGroup, handlers.Role, doc, basePath)
}
if handlers.Permission != nil {
registerPermissionRoutes(router, handlers.Permission, doc, basePath)
registerPermissionRoutes(authGroup, handlers.Permission, doc, basePath)
}
if handlers.Shop != nil {
registerShopRoutes(authGroup, handlers.Shop, doc, basePath)
}
if handlers.ShopAccount != nil {
registerShopAccountRoutes(authGroup, handlers.ShopAccount, doc, basePath)
}
// TODO: Task routes?
}
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(model.LoginRequest),
Output: new(model.LoginResponse),
Auth: false,
})
Register(router, doc, basePath, "POST", "/refresh-token", h.RefreshToken, RouteSpec{
Summary: "刷新 Token",
Tags: []string{"认证"},
Input: new(model.RefreshTokenRequest),
Output: new(model.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(model.UserInfo),
Auth: true,
})
Register(authGroup, doc, basePath, "PUT", "/password", h.ChangePassword, RouteSpec{
Summary: "修改密码",
Tags: []string{"认证"},
Input: new(model.ChangePasswordRequest),
Output: nil,
Auth: true,
})
}

68
internal/routes/h5.go Normal file
View File

@@ -0,0 +1,68 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/bootstrap"
"github.com/break/junhong_cmp_fiber/internal/model"
"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)
}
}
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(model.LoginRequest),
Output: new(model.LoginResponse),
Auth: false,
})
Register(router, doc, basePath, "POST", "/refresh-token", h.RefreshToken, RouteSpec{
Summary: "刷新 Token",
Tags: []string{"H5 认证"},
Input: new(model.RefreshTokenRequest),
Output: new(model.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(model.UserInfo),
Auth: true,
})
Register(authGroup, doc, basePath, "PUT", "/password", h.ChangePassword, RouteSpec{
Summary: "修改密码",
Tags: []string{"H5 认证"},
Input: new(model.ChangePasswordRequest),
Output: nil,
Auth: true,
})
}

View File

@@ -19,6 +19,7 @@ func registerPermissionRoutes(api fiber.Router, h *admin.PermissionHandler, doc
Tags: []string{"权限"},
Input: new(model.CreatePermissionRequest),
Output: new(model.PermissionResponse),
Auth: true,
})
Register(permissions, doc, groupPath, "GET", "", h.List, RouteSpec{
@@ -26,6 +27,7 @@ func registerPermissionRoutes(api fiber.Router, h *admin.PermissionHandler, doc
Tags: []string{"权限"},
Input: new(model.PermissionListRequest),
Output: new(model.PermissionPageResult),
Auth: true,
})
Register(permissions, doc, groupPath, "GET", "/tree", h.GetTree, RouteSpec{
@@ -33,6 +35,7 @@ func registerPermissionRoutes(api fiber.Router, h *admin.PermissionHandler, doc
Tags: []string{"权限"},
Input: nil, // 无参数或 Query 参数
Output: new([]*model.PermissionTreeNode),
Auth: true,
})
Register(permissions, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{
@@ -40,6 +43,7 @@ func registerPermissionRoutes(api fiber.Router, h *admin.PermissionHandler, doc
Tags: []string{"权限"},
Input: new(model.IDReq),
Output: new(model.PermissionResponse),
Auth: true,
})
Register(permissions, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{
@@ -47,6 +51,7 @@ func registerPermissionRoutes(api fiber.Router, h *admin.PermissionHandler, doc
Tags: []string{"权限"},
Input: new(model.UpdatePermissionParams),
Output: new(model.PermissionResponse),
Auth: true,
})
Register(permissions, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{
@@ -54,5 +59,6 @@ func registerPermissionRoutes(api fiber.Router, h *admin.PermissionHandler, doc
Tags: []string{"权限"},
Input: new(model.IDReq),
Output: nil,
Auth: true,
})
}

View File

@@ -28,16 +28,11 @@ var pathParamRegex = regexp.MustCompile(`/:([a-zA-Z0-9_]+)`)
// handler: Fiber Handler
// spec: 文档元数据
func Register(router fiber.Router, doc *openapi.Generator, basePath, method, path string, handler fiber.Handler, spec RouteSpec) {
// 1. 注册实际的 Fiber 路由
router.Add(method, path, handler)
// 2. 注册文档 (如果 doc 不为空 - 也就是在生成文档模式下)
if doc != nil {
// 简单的路径拼接
fullPath := basePath + path
// 将 Fiber 路由参数格式 /:id 转换为 OpenAPI 格式 /{id}
openapiPath := pathParamRegex.ReplaceAllString(fullPath, "/{$1}")
doc.AddOperation(method, openapiPath, spec.Summary, spec.Input, spec.Output, spec.Tags...)
doc.AddOperation(method, openapiPath, spec.Summary, spec.Input, spec.Output, spec.Auth, spec.Tags...)
}
}

View File

@@ -19,6 +19,7 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.CreateRoleRequest),
Output: new(model.RoleResponse),
Auth: true,
})
Register(roles, doc, groupPath, "GET", "", h.List, RouteSpec{
@@ -26,6 +27,7 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.RoleListRequest),
Output: new(model.RolePageResult),
Auth: true,
})
Register(roles, doc, groupPath, "GET", "/:id", h.Get, RouteSpec{
@@ -33,6 +35,7 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.IDReq),
Output: new(model.RoleResponse),
Auth: true,
})
Register(roles, doc, groupPath, "PUT", "/:id", h.Update, RouteSpec{
@@ -40,6 +43,7 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.UpdateRoleParams),
Output: new(model.RoleResponse),
Auth: true,
})
Register(roles, doc, groupPath, "PUT", "/:id/status", h.UpdateStatus, RouteSpec{
@@ -47,6 +51,7 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.UpdateRoleStatusParams),
Output: nil,
Auth: true,
})
Register(roles, doc, groupPath, "DELETE", "/:id", h.Delete, RouteSpec{
@@ -54,6 +59,7 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.IDReq),
Output: nil,
Auth: true,
})
// 角色-权限关联
@@ -62,6 +68,7 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.AssignPermissionsParams),
Output: nil,
Auth: true,
})
Register(roles, doc, groupPath, "GET", "/:id/permissions", h.GetPermissions, RouteSpec{
@@ -69,6 +76,7 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.IDReq),
Output: new([]model.Permission),
Auth: true,
})
Register(roles, doc, groupPath, "DELETE", "/:role_id/permissions/:perm_id", h.RemovePermission, RouteSpec{
@@ -76,5 +84,6 @@ func registerRoleRoutes(api fiber.Router, h *admin.RoleHandler, doc *openapi.Gen
Tags: []string{"角色"},
Input: new(model.RemovePermissionParams),
Output: nil,
Auth: true,
})
}

View File

@@ -14,11 +14,15 @@ func RegisterRoutes(app *fiber.App, handlers *bootstrap.Handlers, middlewares *b
// 2. Admin 域 (挂载在 /api/admin)
adminGroup := app.Group("/api/admin")
RegisterAdminRoutes(adminGroup, handlers, nil, "/api/admin")
RegisterAdminRoutes(adminGroup, handlers, middlewares, nil, "/api/admin")
// 任务相关路由 (归属于 Admin 域)
registerTaskRoutes(adminGroup)
// 3. 个人客户路由 (挂载在 /api/c/v1)
// 3. H5 域 (挂载在 /api/h5)
h5Group := app.Group("/api/h5")
RegisterH5Routes(h5Group, handlers, middlewares, nil, "/api/h5")
// 4. 个人客户路由 (挂载在 /api/c/v1)
RegisterPersonalCustomerRoutes(app, handlers, middlewares.PersonalAuth)
}

23
internal/routes/shop.go Normal file
View File

@@ -0,0 +1,23 @@
package routes
import (
"github.com/gofiber/fiber/v2"
"github.com/break/junhong_cmp_fiber/internal/handler/admin"
"github.com/break/junhong_cmp_fiber/pkg/openapi"
)
func registerShopRoutes(router fiber.Router, handler *admin.ShopHandler, doc *openapi.Generator, basePath string) {
router.Get("/shops", handler.List)
router.Post("/shops", handler.Create)
router.Put("/shops/:id", handler.Update)
router.Delete("/shops/:id", handler.Delete)
}
func registerShopAccountRoutes(router fiber.Router, handler *admin.ShopAccountHandler, doc *openapi.Generator, basePath string) {
router.Get("/shop-accounts", handler.List)
router.Post("/shop-accounts", handler.Create)
router.Put("/shop-accounts/:id", handler.Update)
router.Put("/shop-accounts/:id/password", handler.UpdatePassword)
router.Put("/shop-accounts/:id/status", handler.UpdateStatus)
}