refactor: admin 模块按 handler/model/service 分层

This commit is contained in:
root
2026-03-09 21:13:49 +08:00
parent 7591a443d9
commit f45940cbc5
11 changed files with 101 additions and 38 deletions
+56
View File
@@ -0,0 +1,56 @@
package admin
import (
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
adminhandler "wx_service/internal/admin/handler"
adminmodel "wx_service/internal/admin/model"
adminservice "wx_service/internal/admin/service"
)
type Admin = adminmodel.Admin
type Handler = adminhandler.Handler
type Service = adminservice.Service
type Claims = adminservice.Claims
type LoginResult = adminservice.LoginResult
type ListMiniProgramsQuery = adminservice.ListMiniProgramsQuery
type MiniProgramItem = adminservice.MiniProgramItem
type ListMiniProgramsResult = adminservice.ListMiniProgramsResult
type CreateMiniProgramInput = adminservice.CreateMiniProgramInput
type UpdateMiniProgramInput = adminservice.UpdateMiniProgramInput
type MiniProgramDetailStats = adminservice.MiniProgramDetailStats
type OverviewStats = adminservice.OverviewStats
type MiniProgramStatsItem = adminservice.MiniProgramStatsItem
type UserGrowthPoint = adminservice.UserGrowthPoint
var (
ErrInvalidCredentials = adminservice.ErrInvalidCredentials
ErrInvalidToken = adminservice.ErrInvalidToken
ErrMissingJWTSecret = adminservice.ErrMissingJWTSecret
ErrMiniProgramNotFound = adminservice.ErrMiniProgramNotFound
ErrMiniProgramHasUsers = adminservice.ErrMiniProgramHasUsers
ErrMiniProgramAppIDUsed = adminservice.ErrMiniProgramAppIDUsed
ErrInvalidInput = adminservice.ErrInvalidInput
)
const ContextAdminClaimsKey = adminhandler.ContextAdminClaimsKey
func NewService(db *gorm.DB, jwtSecret string, tokenTTL time.Duration, defaultUsername, defaultPassword string) *Service {
return adminservice.NewService(db, jwtSecret, tokenTTL, defaultUsername, defaultPassword)
}
func NewHandler(svc *Service) *Handler {
return adminhandler.NewHandler(svc)
}
func AuthMiddleware(svc *Service) gin.HandlerFunc {
return adminhandler.AuthMiddleware(svc)
}
func CurrentAdminClaims(c *gin.Context) (*Claims, bool) {
return adminhandler.CurrentAdminClaims(c)
}
@@ -1,4 +1,4 @@
package admin package handler
import ( import (
"errors" "errors"
@@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
adminservice "wx_service/internal/admin/service"
"wx_service/internal/model" "wx_service/internal/model"
) )
@@ -24,11 +25,11 @@ func (h *Handler) Login(c *gin.Context) {
result, err := h.svc.Login(c.Request.Context(), req.Username, req.Password) result, err := h.svc.Login(c.Request.Context(), req.Username, req.Password)
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, ErrInvalidCredentials): case errors.Is(err, adminservice.ErrInvalidCredentials):
c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "用户名或密码错误")) c.JSON(http.StatusUnauthorized, model.Error(http.StatusUnauthorized, "用户名或密码错误"))
case errors.Is(err, ErrMissingJWTSecret): case errors.Is(err, adminservice.ErrMissingJWTSecret):
c.JSON(http.StatusServiceUnavailable, model.Error(http.StatusServiceUnavailable, "管理员认证未配置,请检查 JWT_SECRET")) c.JSON(http.StatusServiceUnavailable, model.Error(http.StatusServiceUnavailable, "管理员认证未配置,请检查 JWT_SECRET"))
case errors.Is(err, ErrInvalidInput): case errors.Is(err, adminservice.ErrInvalidInput):
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "用户名和密码不能为空")) c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "用户名和密码不能为空"))
default: default:
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "admin login failed")) c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "admin login failed"))
@@ -1,4 +1,4 @@
package admin package handler
import ( import (
"errors" "errors"
@@ -6,13 +6,15 @@ import (
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
adminservice "wx_service/internal/admin/service"
) )
type Handler struct { type Handler struct {
svc *Service svc *adminservice.Service
} }
func NewHandler(svc *Service) *Handler { func NewHandler(svc *adminservice.Service) *Handler {
return &Handler{svc: svc} return &Handler{svc: svc}
} }
@@ -1,4 +1,4 @@
package admin package handler
import ( import (
"net/http" "net/http"
@@ -6,12 +6,13 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
adminservice "wx_service/internal/admin/service"
"wx_service/internal/model" "wx_service/internal/model"
) )
const ContextAdminClaimsKey = "adminClaims" const ContextAdminClaimsKey = "adminClaims"
func AuthMiddleware(svc *Service) gin.HandlerFunc { func AuthMiddleware(svc *adminservice.Service) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
token := extractBearerToken(c.GetHeader("Authorization")) token := extractBearerToken(c.GetHeader("Authorization"))
if token == "" { if token == "" {
@@ -30,12 +31,12 @@ func AuthMiddleware(svc *Service) gin.HandlerFunc {
} }
} }
func CurrentAdminClaims(c *gin.Context) (*Claims, bool) { func CurrentAdminClaims(c *gin.Context) (*adminservice.Claims, bool) {
value, ok := c.Get(ContextAdminClaimsKey) value, ok := c.Get(ContextAdminClaimsKey)
if !ok { if !ok {
return nil, false return nil, false
} }
claims, ok := value.(*Claims) claims, ok := value.(*adminservice.Claims)
return claims, ok return claims, ok
} }
@@ -1,4 +1,4 @@
package admin package handler
import ( import (
"errors" "errors"
@@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
adminservice "wx_service/internal/admin/service"
"wx_service/internal/model" "wx_service/internal/model"
) )
@@ -15,7 +16,7 @@ func (h *Handler) ListMiniPrograms(c *gin.Context) {
page, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("page", "1"))) page, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("page", "1")))
pageSize, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("page_size", "20"))) pageSize, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("page_size", "20")))
data, err := h.svc.ListMiniPrograms(c.Request.Context(), ListMiniProgramsQuery{ data, err := h.svc.ListMiniPrograms(c.Request.Context(), adminservice.ListMiniProgramsQuery{
Page: page, Page: page,
PageSize: pageSize, PageSize: pageSize,
Keyword: c.Query("keyword"), Keyword: c.Query("keyword"),
@@ -36,7 +37,7 @@ func (h *Handler) GetMiniProgram(c *gin.Context) {
data, err := h.svc.GetMiniProgram(c.Request.Context(), id) data, err := h.svc.GetMiniProgram(c.Request.Context(), id)
if err != nil { if err != nil {
if errors.Is(err, ErrMiniProgramNotFound) { if errors.Is(err, adminservice.ErrMiniProgramNotFound) {
c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "mini program not found")) c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "mini program not found"))
return return
} }
@@ -55,7 +56,7 @@ func (h *Handler) GetMiniProgramStats(c *gin.Context) {
data, err := h.svc.GetMiniProgramStats(c.Request.Context(), id) data, err := h.svc.GetMiniProgramStats(c.Request.Context(), id)
if err != nil { if err != nil {
if errors.Is(err, ErrMiniProgramNotFound) { if errors.Is(err, adminservice.ErrMiniProgramNotFound) {
c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "mini program not found")) c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "mini program not found"))
return return
} }
@@ -79,7 +80,7 @@ func (h *Handler) CreateMiniProgram(c *gin.Context) {
return return
} }
data, err := h.svc.CreateMiniProgram(c.Request.Context(), CreateMiniProgramInput{ data, err := h.svc.CreateMiniProgram(c.Request.Context(), adminservice.CreateMiniProgramInput{
Name: req.Name, Name: req.Name,
AppID: req.AppID, AppID: req.AppID,
AppSecret: req.AppSecret, AppSecret: req.AppSecret,
@@ -87,9 +88,9 @@ func (h *Handler) CreateMiniProgram(c *gin.Context) {
}) })
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, ErrInvalidInput): case errors.Is(err, adminservice.ErrInvalidInput):
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "name/app_id/app_secret are required")) c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "name/app_id/app_secret are required"))
case errors.Is(err, ErrMiniProgramAppIDUsed): case errors.Is(err, adminservice.ErrMiniProgramAppIDUsed):
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "app_id already exists")) c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "app_id already exists"))
default: default:
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "create mini-program failed")) c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "create mini-program failed"))
@@ -119,7 +120,7 @@ func (h *Handler) UpdateMiniProgram(c *gin.Context) {
return return
} }
data, err := h.svc.UpdateMiniProgram(c.Request.Context(), id, UpdateMiniProgramInput{ data, err := h.svc.UpdateMiniProgram(c.Request.Context(), id, adminservice.UpdateMiniProgramInput{
Name: req.Name, Name: req.Name,
AppID: req.AppID, AppID: req.AppID,
AppSecret: req.AppSecret, AppSecret: req.AppSecret,
@@ -127,11 +128,11 @@ func (h *Handler) UpdateMiniProgram(c *gin.Context) {
}) })
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, ErrMiniProgramNotFound): case errors.Is(err, adminservice.ErrMiniProgramNotFound):
c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "mini program not found")) c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "mini program not found"))
case errors.Is(err, ErrInvalidInput): case errors.Is(err, adminservice.ErrInvalidInput):
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "name/app_id are required")) c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "name/app_id are required"))
case errors.Is(err, ErrMiniProgramAppIDUsed): case errors.Is(err, adminservice.ErrMiniProgramAppIDUsed):
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "app_id already exists")) c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "app_id already exists"))
default: default:
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "update mini-program failed")) c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "update mini-program failed"))
@@ -151,9 +152,9 @@ func (h *Handler) DeleteMiniProgram(c *gin.Context) {
err = h.svc.DeleteMiniProgram(c.Request.Context(), id) err = h.svc.DeleteMiniProgram(c.Request.Context(), id)
if err != nil { if err != nil {
switch { switch {
case errors.Is(err, ErrMiniProgramNotFound): case errors.Is(err, adminservice.ErrMiniProgramNotFound):
c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "mini program not found")) c.JSON(http.StatusNotFound, model.Error(http.StatusNotFound, "mini program not found"))
case errors.Is(err, ErrMiniProgramHasUsers): case errors.Is(err, adminservice.ErrMiniProgramHasUsers):
c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "mini program has related users")) c.JSON(http.StatusBadRequest, model.Error(http.StatusBadRequest, "mini program has related users"))
default: default:
c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "delete mini-program failed")) c.JSON(http.StatusInternalServerError, model.Error(http.StatusInternalServerError, "delete mini-program failed"))
@@ -1,4 +1,4 @@
package admin package handler
import ( import (
"net/http" "net/http"
@@ -1,4 +1,4 @@
package admin package model
import ( import (
"time" "time"
@@ -1,4 +1,4 @@
package admin package service
import ( import (
"context" "context"
@@ -7,6 +7,8 @@ import (
"strings" "strings"
"time" "time"
adminmodel "wx_service/internal/admin/model"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gorm.io/gorm" "gorm.io/gorm"
@@ -14,7 +16,7 @@ import (
func (s *Service) EnsureDefaultAdmin(ctx context.Context) error { func (s *Service) EnsureDefaultAdmin(ctx context.Context) error {
var count int64 var count int64
if err := s.db.WithContext(ctx).Model(&Admin{}).Count(&count).Error; err != nil { if err := s.db.WithContext(ctx).Model(&adminmodel.Admin{}).Count(&count).Error; err != nil {
return err return err
} }
if count > 0 { if count > 0 {
@@ -35,7 +37,7 @@ func (s *Service) EnsureDefaultAdmin(ctx context.Context) error {
return err return err
} }
record := &Admin{ record := &adminmodel.Admin{
Username: username, Username: username,
Password: string(hashedPassword), Password: string(hashedPassword),
Role: "super_admin", Role: "super_admin",
@@ -51,7 +53,7 @@ func (s *Service) EnsureDefaultAdmin(ctx context.Context) error {
type LoginResult struct { type LoginResult struct {
Token string `json:"token"` Token string `json:"token"`
Admin *Admin `json:"admin"` Admin *adminmodel.Admin `json:"admin"`
} }
func (s *Service) Login(ctx context.Context, username, password string) (*LoginResult, error) { func (s *Service) Login(ctx context.Context, username, password string) (*LoginResult, error) {
@@ -60,7 +62,7 @@ func (s *Service) Login(ctx context.Context, username, password string) (*LoginR
return nil, ErrInvalidInput return nil, ErrInvalidInput
} }
var admin Admin var admin adminmodel.Admin
if err := s.db.WithContext(ctx).Where("username = ?", username).First(&admin).Error; err != nil { if err := s.db.WithContext(ctx).Where("username = ?", username).First(&admin).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrInvalidCredentials return nil, ErrInvalidCredentials
@@ -111,7 +113,7 @@ func (s *Service) ParseToken(token string) (*Claims, error) {
return claims, nil return claims, nil
} }
func (s *Service) generateToken(admin *Admin) (string, error) { func (s *Service) generateToken(admin *adminmodel.Admin) (string, error) {
if len(s.jwtSecret) == 0 { if len(s.jwtSecret) == 0 {
return "", ErrMissingJWTSecret return "", ErrMissingJWTSecret
} }
@@ -132,8 +134,8 @@ func (s *Service) generateToken(admin *Admin) (string, error) {
return t.SignedString(s.jwtSecret) return t.SignedString(s.jwtSecret)
} }
func (s *Service) GetProfile(ctx context.Context, adminID uint) (*Admin, error) { func (s *Service) GetProfile(ctx context.Context, adminID uint) (*adminmodel.Admin, error) {
var admin Admin var admin adminmodel.Admin
if err := s.db.WithContext(ctx). if err := s.db.WithContext(ctx).
Select("id", "username", "role", "last_login_at", "created_at", "updated_at"). Select("id", "username", "role", "last_login_at", "created_at", "updated_at").
Where("id = ?", adminID). Where("id = ?", adminID).
@@ -1,4 +1,4 @@
package admin package service
import ( import (
"context" "context"
@@ -1,4 +1,4 @@
package admin package service
import ( import (
"context" "context"
@@ -1,4 +1,4 @@
package admin package service
import ( import (
"errors" "errors"