优化结构
This commit is contained in:
@@ -2,11 +2,11 @@ package handler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
"kra/internal/service/dto"
|
||||
|
||||
@@ -215,17 +215,17 @@ func failLogViewer(c *gin.Context, err error, logger *slog.Logger) {
|
||||
}
|
||||
message := "读取日志失败"
|
||||
switch {
|
||||
case errors.Is(err, biz.ErrInvalidLogMonth):
|
||||
case errors.Is(err, system.ErrInvalidLogMonth):
|
||||
message = "日志月份格式不正确"
|
||||
case errors.Is(err, biz.ErrInvalidLogDate):
|
||||
case errors.Is(err, system.ErrInvalidLogDate):
|
||||
message = "日志日期格式不正确"
|
||||
case errors.Is(err, biz.ErrInvalidLogPath):
|
||||
case errors.Is(err, system.ErrInvalidLogPath):
|
||||
message = "日志文件路径不合法"
|
||||
case errors.Is(err, biz.ErrLogFileNotFound):
|
||||
case errors.Is(err, system.ErrLogFileNotFound):
|
||||
message = "日志文件不存在"
|
||||
case errors.Is(err, biz.ErrLogFileUnreadable):
|
||||
case errors.Is(err, system.ErrLogFileUnreadable):
|
||||
message = "日志文件不可读取"
|
||||
case errors.Is(err, biz.ErrLogRootUnavailable):
|
||||
case errors.Is(err, system.ErrLogRootUnavailable):
|
||||
message = "日志目录不可读取"
|
||||
}
|
||||
Fail(c, message)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"kra/internal/biz"
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/server/httpx"
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -22,4 +23,4 @@ func OKWithData(c *gin.Context, data any) { httpx.OKWithDat
|
||||
func Fail(c *gin.Context, message string) { httpx.Fail(c, message) }
|
||||
func NoAuth(c *gin.Context, message string) { httpx.NoAuth(c, message) }
|
||||
func SetTokenCookie(c *gin.Context, value string, maxAge int) { httpx.SetTokenCookie(c, value, maxAge) }
|
||||
func Claims(c *gin.Context) *biz.AuthClaims { return middleware.Claims(c) }
|
||||
func Claims(c *gin.Context) *system.AuthClaims { return middleware.Claims(c) }
|
||||
|
||||
@@ -3,11 +3,11 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
"kra/internal/service/dto"
|
||||
|
||||
@@ -75,23 +75,23 @@ func (h *Public) Login(c *gin.Context) {
|
||||
result, err := h.auth.Login(c.Request.Context(), &req, c.ClientIP(), c.Request.UserAgent())
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, biz.ErrAccountLocked):
|
||||
var locked *biz.AccountLockedError
|
||||
case errors.Is(err, system.ErrAccountLocked):
|
||||
var locked *system.AccountLockedError
|
||||
errors.As(err, &locked)
|
||||
minutes := 0
|
||||
if locked != nil {
|
||||
minutes = locked.Minutes
|
||||
}
|
||||
Fail(c, "账号已锁定,请 "+strconv.Itoa(minutes)+" 分钟后再试")
|
||||
case errors.Is(err, biz.ErrCaptchaInvalid):
|
||||
case errors.Is(err, system.ErrCaptchaInvalid):
|
||||
Fail(c, "验证码错误")
|
||||
case errors.Is(err, biz.ErrUserDisabled):
|
||||
case errors.Is(err, system.ErrUserDisabled):
|
||||
Fail(c, "用户被禁止登录")
|
||||
case errors.Is(err, biz.ErrTokenIssue):
|
||||
case errors.Is(err, system.ErrTokenIssue):
|
||||
Fail(c, "获取token失败")
|
||||
case errors.Is(err, biz.ErrLoginState):
|
||||
case errors.Is(err, system.ErrLoginState):
|
||||
Fail(c, "设置登录状态失败")
|
||||
case errors.Is(err, biz.ErrTokenRevoke):
|
||||
case errors.Is(err, system.ErrTokenRevoke):
|
||||
Fail(c, "jwt作废失败")
|
||||
default:
|
||||
Fail(c, "用户名不存在或者密码错误")
|
||||
|
||||
@@ -3,11 +3,11 @@ package handler
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"kra/internal/biz/system"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
"kra/internal/service/dto"
|
||||
|
||||
@@ -18,8 +18,8 @@ type Version struct{ service *service.VersionService }
|
||||
|
||||
func NewVersion(service *service.VersionService) *Version { return &Version{service: service} }
|
||||
|
||||
func versionStage(err error) (biz.VersionStage, bool) {
|
||||
var stageErr *biz.VersionStageError
|
||||
func versionStage(err error) (system.VersionStage, bool) {
|
||||
var stageErr *system.VersionStageError
|
||||
if !errors.As(err, &stageErr) {
|
||||
return "", false
|
||||
}
|
||||
@@ -89,15 +89,15 @@ func (h *Version) Export(c *gin.Context) {
|
||||
}
|
||||
if err := h.service.Export(c.Request.Context(), req.VersionName, req.VersionCode, req.Description, req.MenuIDs, req.APIIDs, req.DictIDs); err != nil {
|
||||
switch stage, _ := versionStage(err); stage {
|
||||
case biz.VersionStageMenus:
|
||||
case system.VersionStageMenus:
|
||||
Fail(c, "获取菜单数据失败:"+err.Error())
|
||||
case biz.VersionStageAPIs:
|
||||
case system.VersionStageAPIs:
|
||||
Fail(c, "获取API数据失败:"+err.Error())
|
||||
case biz.VersionStageDictionaries:
|
||||
case system.VersionStageDictionaries:
|
||||
Fail(c, "获取字典数据失败:"+err.Error())
|
||||
case biz.VersionStageJSON:
|
||||
case system.VersionStageJSON:
|
||||
Fail(c, "JSON序列化失败:"+err.Error())
|
||||
case biz.VersionStageSave:
|
||||
case system.VersionStageSave:
|
||||
Fail(c, "保存版本记录失败:"+err.Error())
|
||||
default:
|
||||
Fail(c, "创建发版失败:"+err.Error())
|
||||
@@ -131,16 +131,16 @@ func (h *Version) Import(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if err := h.service.ImportRequest(c.Request.Context(), &req); err != nil {
|
||||
if errors.Is(err, biz.ErrInvalidVersion) {
|
||||
if errors.Is(err, system.ErrInvalidVersion) {
|
||||
Fail(c, "版本信息格式错误")
|
||||
return
|
||||
}
|
||||
switch stage, _ := versionStage(err); stage {
|
||||
case biz.VersionStageMenus:
|
||||
case system.VersionStageMenus:
|
||||
Fail(c, "导入菜单失败: "+err.Error())
|
||||
case biz.VersionStageAPIs:
|
||||
case system.VersionStageAPIs:
|
||||
Fail(c, "导入API失败: "+err.Error())
|
||||
case biz.VersionStageDictionaries:
|
||||
case system.VersionStageDictionaries:
|
||||
Fail(c, "导入字典失败: "+err.Error())
|
||||
default:
|
||||
Fail(c, "导入失败:"+err.Error())
|
||||
|
||||
@@ -2,8 +2,8 @@ package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"kra/internal/biz/system"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/service"
|
||||
|
||||
@@ -39,13 +39,13 @@ func AccessControl(runtime *conf.Runtime, access accessController) gin.HandlerFu
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if scope, ok := biz.DataScopeFromContext(requestContext); ok {
|
||||
if scope, ok := system.DataScopeFromContext(requestContext); ok {
|
||||
requestID, _ := c.Get("request_id")
|
||||
scope.RequestID, _ = requestID.(string)
|
||||
scope.Method, scope.Path = c.Request.Method, path
|
||||
requestContext = biz.NewDataScopeContext(requestContext, scope)
|
||||
requestContext = system.NewDataScopeContext(requestContext, scope)
|
||||
}
|
||||
requestContext = biz.NewActorContext(requestContext, biz.Actor{UserID: claims.ID, AuthorityID: claims.AuthorityID})
|
||||
requestContext = system.NewActorContext(requestContext, system.Actor{UserID: claims.ID, AuthorityID: claims.AuthorityID})
|
||||
c.Request = c.Request.WithContext(requestContext)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"kra/internal/biz/system"
|
||||
"log/slog"
|
||||
"mime"
|
||||
"net/http"
|
||||
@@ -14,7 +15,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -33,7 +33,7 @@ func AccessLog(runtime *conf.Runtime, logger *slog.Logger, version string) gin.H
|
||||
config := runtime.Admin()
|
||||
bodyLimit := defaultRequestBodyLimit
|
||||
if mediaUpload {
|
||||
bodyLimit = biz.DefaultMaxMediaFileSize + (1 << 20)
|
||||
bodyLimit = system.DefaultMaxMediaFileSize + (1 << 20)
|
||||
if config != nil && config.Media != nil && config.Media.MaxFileSize > 0 {
|
||||
bodyLimit = config.Media.MaxFileSize + (1 << 20)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -26,14 +26,14 @@ func (s *accessControllerStub) ContextWithDataScope(ctx context.Context, authori
|
||||
if s.scopeErr != nil {
|
||||
return ctx, s.scopeErr
|
||||
}
|
||||
return biz.NewDataScopeContext(ctx, biz.DataScope{UserID: userID, AuthorityID: authorityID, Scope: 1, All: true}), nil
|
||||
return system.NewDataScopeContext(ctx, system.DataScope{UserID: userID, AuthorityID: authorityID, Scope: 1, All: true}), nil
|
||||
}
|
||||
|
||||
func TestAccessControlFailsClosedWhenDataScopeResolutionFails(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
engine := gin.New()
|
||||
engine.Use(func(c *gin.Context) {
|
||||
c.Set(claimsKey, &biz.AuthClaims{ID: 7, AuthorityID: 888})
|
||||
c.Set(claimsKey, &system.AuthClaims{ID: 7, AuthorityID: 888})
|
||||
c.Next()
|
||||
})
|
||||
engine.Use(AccessControl(conf.NewRuntime(nil, &conf.AdminBackend{}), &accessControllerStub{scopeErr: errors.New("database unavailable")}))
|
||||
|
||||
@@ -3,12 +3,11 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/sync/singleflight"
|
||||
)
|
||||
@@ -18,7 +17,7 @@ const claimsKey = "admin_claims"
|
||||
var refreshTokens singleflight.Group
|
||||
|
||||
type TokenAuthenticator interface {
|
||||
AuthenticateToken(context.Context, string) (*biz.TokenAuthentication, error)
|
||||
AuthenticateToken(context.Context, string) (*system.TokenAuthentication, error)
|
||||
}
|
||||
|
||||
func Auth(auth TokenAuthenticator) gin.HandlerFunc {
|
||||
@@ -54,7 +53,7 @@ func authenticate(c *gin.Context, auth TokenAuthenticator, allowQueryToken bool)
|
||||
NoAuth(c, tokenErrorMessage(err))
|
||||
return false
|
||||
}
|
||||
authentication, ok := value.(*biz.TokenAuthentication)
|
||||
authentication, ok := value.(*system.TokenAuthentication)
|
||||
if !ok || authentication == nil || authentication.Claims == nil {
|
||||
SetTokenCookie(c, "", -1)
|
||||
NoAuth(c, "无法处理此token")
|
||||
@@ -95,23 +94,23 @@ func requestToken(c *gin.Context, allowQueryToken bool) string {
|
||||
func tokenErrorMessage(err error) string {
|
||||
message := "无法处理此token"
|
||||
switch {
|
||||
case errors.Is(err, biz.ErrTokenExpired):
|
||||
case errors.Is(err, system.ErrTokenExpired):
|
||||
message = "登录已过期,请重新登录"
|
||||
case errors.Is(err, biz.ErrTokenMalformed):
|
||||
case errors.Is(err, system.ErrTokenMalformed):
|
||||
message = "这不是一个token"
|
||||
case errors.Is(err, biz.ErrTokenSignatureInvalid):
|
||||
case errors.Is(err, system.ErrTokenSignatureInvalid):
|
||||
message = "无效签名"
|
||||
case errors.Is(err, biz.ErrTokenNotValidYet):
|
||||
case errors.Is(err, system.ErrTokenNotValidYet):
|
||||
message = "token尚未激活"
|
||||
case errors.Is(err, biz.ErrTokenDisabled):
|
||||
case errors.Is(err, system.ErrTokenDisabled):
|
||||
message = "您的帐户异地登陆或令牌失效"
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func Claims(c *gin.Context) *biz.AuthClaims {
|
||||
func Claims(c *gin.Context) *system.AuthClaims {
|
||||
value, _ := c.Get(claimsKey)
|
||||
claims, _ := value.(*biz.AuthClaims)
|
||||
claims, _ := value.(*system.AuthClaims)
|
||||
return claims
|
||||
}
|
||||
|
||||
|
||||
@@ -3,32 +3,31 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"kra/internal/biz"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type tokenAuthenticatorStub struct {
|
||||
claims *biz.AuthClaims
|
||||
claims *system.AuthClaims
|
||||
err error
|
||||
got string
|
||||
}
|
||||
|
||||
func (s *tokenAuthenticatorStub) AuthenticateToken(_ context.Context, token string) (*biz.TokenAuthentication, error) {
|
||||
func (s *tokenAuthenticatorStub) AuthenticateToken(_ context.Context, token string) (*system.TokenAuthentication, error) {
|
||||
s.got = token
|
||||
if s.err != nil {
|
||||
return nil, s.err
|
||||
}
|
||||
return &biz.TokenAuthentication{Claims: s.claims}, nil
|
||||
return &system.TokenAuthentication{Claims: s.claims}, nil
|
||||
}
|
||||
|
||||
func TestAuthenticateWebSocketAcceptsQueryToken(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
stub := &tokenAuthenticatorStub{claims: &biz.AuthClaims{ID: 7}}
|
||||
stub := &tokenAuthenticatorStub{claims: &system.AuthClaims{ID: 7}}
|
||||
recorder := httptest.NewRecorder()
|
||||
context, _ := gin.CreateTestContext(recorder)
|
||||
context.Request = httptest.NewRequest(http.MethodGet, "/ws?token=query-token", nil)
|
||||
@@ -46,7 +45,7 @@ func TestAuthenticateWebSocketAcceptsQueryToken(t *testing.T) {
|
||||
|
||||
func TestHTTPAuthDoesNotAcceptQueryToken(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
stub := &tokenAuthenticatorStub{claims: &biz.AuthClaims{ID: 7}}
|
||||
stub := &tokenAuthenticatorStub{claims: &system.AuthClaims{ID: 7}}
|
||||
engine := gin.New()
|
||||
called := false
|
||||
engine.GET("/protected", Auth(stub), func(c *gin.Context) { called = true })
|
||||
@@ -60,7 +59,7 @@ func TestHTTPAuthDoesNotAcceptQueryToken(t *testing.T) {
|
||||
|
||||
func TestAuthenticateWebSocketSupportsBearerHeaderAndReportsInvalidToken(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
stub := &tokenAuthenticatorStub{claims: &biz.AuthClaims{ID: 7}}
|
||||
stub := &tokenAuthenticatorStub{claims: &system.AuthClaims{ID: 7}}
|
||||
context, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
context.Request = httptest.NewRequest(http.MethodGet, "/ws", nil)
|
||||
context.Request.Header.Set("Authorization", "Bearer header-token")
|
||||
|
||||
@@ -3,13 +3,13 @@ package middleware
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"kra/internal/biz/system"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -17,10 +17,10 @@ import (
|
||||
|
||||
type rateLimitSecurityRepo struct{}
|
||||
|
||||
func (rateLimitSecurityRepo) SecurityConfig(context.Context) (*biz.SecurityConfig, error) {
|
||||
return &biz.SecurityConfig{LimitEnable: true, LimitWindow: 60, LimitCount: 1}, nil
|
||||
func (rateLimitSecurityRepo) SecurityConfig(context.Context) (*system.SecurityConfig, error) {
|
||||
return &system.SecurityConfig{LimitEnable: true, LimitWindow: 60, LimitCount: 1}, nil
|
||||
}
|
||||
func (rateLimitSecurityRepo) SaveSecurityConfig(context.Context, *biz.SecurityConfig) error {
|
||||
func (rateLimitSecurityRepo) SaveSecurityConfig(context.Context, *system.SecurityConfig) error {
|
||||
return nil
|
||||
}
|
||||
func (rateLimitSecurityRepo) BackfillPasswordUpdatedAt(context.Context, time.Time) error {
|
||||
@@ -38,7 +38,7 @@ func (c rateLimitCache) Increment(context.Context, string, time.Duration) (int64
|
||||
|
||||
func TestSecurityRateLimitMatchesResponseContract(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
settings := service.NewSecurityService(biz.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{}, nil, nil))
|
||||
settings := service.NewSecurityService(system.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{}, nil, nil))
|
||||
engine := gin.New()
|
||||
engine.Use(SecurityRateLimit(settings))
|
||||
engine.POST("/base/login", func(c *gin.Context) { c.Status(http.StatusNoContent) })
|
||||
@@ -56,7 +56,7 @@ func TestSecurityRateLimitMatchesResponseContract(t *testing.T) {
|
||||
|
||||
func TestSecurityRateLimitFailsClosedWhenCacheIsUnavailable(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
settings := service.NewSecurityService(biz.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{err: errors.New("cache unavailable")}, nil, nil))
|
||||
settings := service.NewSecurityService(system.NewSecurityUsecase(rateLimitSecurityRepo{}, rateLimitCache{err: errors.New("cache unavailable")}, nil, nil))
|
||||
engine := gin.New()
|
||||
called := false
|
||||
engine.Use(SecurityRateLimit(settings))
|
||||
|
||||
@@ -3,12 +3,12 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"kra/internal/biz/system"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"kra/internal/biz"
|
||||
"kra/internal/conf"
|
||||
"kra/internal/integration/runtimeconfig"
|
||||
websocketintegration "kra/internal/integration/websocket"
|
||||
@@ -19,13 +19,13 @@ import (
|
||||
)
|
||||
|
||||
type websocketAuthStub struct {
|
||||
claims *biz.AuthClaims
|
||||
claims *system.AuthClaims
|
||||
got string
|
||||
}
|
||||
|
||||
func (s *websocketAuthStub) AuthenticateToken(_ context.Context, token string) (*biz.TokenAuthentication, error) {
|
||||
func (s *websocketAuthStub) AuthenticateToken(_ context.Context, token string) (*system.TokenAuthentication, error) {
|
||||
s.got = token
|
||||
return &biz.TokenAuthentication{Claims: s.claims}, nil
|
||||
return &system.TokenAuthentication{Claims: s.claims}, nil
|
||||
}
|
||||
|
||||
var _ middleware.TokenAuthenticator = (*websocketAuthStub)(nil)
|
||||
@@ -43,7 +43,7 @@ func TestWebSocketRouteRequiresAndAcceptsLoginToken(t *testing.T) {
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
auth := &websocketAuthStub{claims: &biz.AuthClaims{ID: 7}}
|
||||
auth := &websocketAuthStub{claims: &system.AuthClaims{ID: 7}}
|
||||
engine := NewGinEngineWithRuntime(conf.NewRuntime(nil, &conf.AdminBackend{}), nil, auth, nil, nil, nil, "test", nil, ws)
|
||||
|
||||
unauthorized := httptest.NewRecorder()
|
||||
|
||||
Reference in New Issue
Block a user