fix: align admin behavior and Kratos integrations

This commit is contained in:
2026-08-14 14:14:51 +08:00
parent 8c4c6ba661
commit e0b0e1d8c8
123 changed files with 4334 additions and 1877 deletions
+39 -37
View File
@@ -1,13 +1,13 @@
package server
import (
"context"
"errors"
"log/slog"
"net"
"mime"
"net/http"
"os"
"path"
"strings"
"time"
"kra/internal/conf"
"kra/internal/server/handler"
@@ -17,23 +17,16 @@ import (
"kra/internal/service"
"github.com/gin-gonic/gin"
kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
)
// GinServer adapts Gin to the Kratos transport.Server lifecycle.
type GinServer struct {
network string
address string
server *http.Server
listener net.Listener
logger *slog.Logger
}
func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.SystemService, access *service.AccessService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, media *handler.Media, auditHandler *handler.Audit, export *handler.Export, version *handler.Version, dictionary *handler.Dictionary, parameter *handler.Parameter, apiToken *handler.APIToken, systemConfig *handler.SystemConfig, publicHandler *handler.Public, user *handler.User, navigation *handler.Navigation, session *handler.Session, settings *service.SettingsService, audit *service.AuditService, emails *service.EmailService, logger *slog.Logger) *GinServer {
func NewGinServer(c *conf.Server, runtime *conf.Runtime, system *service.SystemService, access *service.AccessService, authority *handler.Authority, menu *handler.Menu, api *handler.API, permission *handler.Permission, organization *handler.Organization, announcement *handler.Announcement, email *handler.Email, task *handler.Task, media *handler.Media, auditHandler *handler.Audit, export *handler.Export, version *handler.Version, dictionary *handler.Dictionary, parameter *handler.Parameter, apiToken *handler.APIToken, systemConfig *handler.SystemConfig, publicHandler *handler.Public, user *handler.User, navigation *handler.Navigation, session *handler.Session, settings *service.SettingsService, audit *service.AuditService, emails *service.EmailService, logger *slog.Logger) *kratoshttp.Server {
gin.SetMode(gin.ReleaseMode)
engine := gin.New()
engine.Use(servermiddleware.RequestMeta(), servermiddleware.EmailErrorAlert(emails, logger), gin.Recovery(), servermiddleware.SecurityRateLimit(system, settings), servermiddleware.OperationAudit(audit))
prefix := ""
config := runtime.Admin()
if config != nil {
prefix = strings.TrimSuffix(config.RouterPrefix, "/")
}
@@ -42,13 +35,13 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.Sys
serverrouter.RegisterPublic(public, engine, publicHandler)
private := engine.Group(prefix)
private.Use(servermiddleware.Auth(config, settings), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(access, audit))
private.Use(servermiddleware.Auth(runtime, settings), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(runtime, access, audit))
serverrouter.RegisterUser(private, user)
serverrouter.RegisterNavigation(private, navigation)
serverrouter.RegisterSession(private, session)
serverrouter.RegisterAuthority(private, authority)
serverrouter.RegisterMenu(private, menu)
serverrouter.RegisterAPI(private, engine, api)
serverrouter.RegisterAPI(private, public, engine, api)
serverrouter.RegisterPermission(private, permission)
serverrouter.RegisterOrganization(private, organization)
serverrouter.RegisterDictionary(private, dictionary)
@@ -64,7 +57,7 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.Sys
serverrouter.RegisterEmail(private, email)
engine.NoRoute(func(c *gin.Context) {
if serveLocalStorage(c, config) {
if serveLocalStorage(c, runtime) {
return
}
httpx.Fail(c, "请求的接口不存在")
@@ -79,12 +72,24 @@ func NewGinServer(c *conf.Server, config *conf.AdminBackend, system *service.Sys
address = c.Http.Addr
}
}
return &GinServer{network: network, address: address, server: &http.Server{Addr: address, Handler: engine}, logger: logger}
timeout := 10 * time.Minute
if c != nil && c.Http != nil && c.Http.Timeout != nil && c.Http.Timeout.AsDuration() > 0 {
timeout = c.Http.Timeout.AsDuration()
}
server := kratoshttp.NewServer(kratoshttp.Network(network), kratoshttp.Address(address), kratoshttp.Timeout(timeout))
server.HandlePrefix("/", engine)
server.Server.ReadHeaderTimeout = 10 * time.Second
server.Server.ReadTimeout = timeout
server.Server.WriteTimeout = timeout
server.Server.IdleTimeout = 2 * time.Minute
server.Server.MaxHeaderBytes = 1 << 20
return server
}
// serveLocalStorage resolves the local path for every request so a config
// reload takes effect without rebuilding the Gin engine.
func serveLocalStorage(c *gin.Context, config *conf.AdminBackend) bool {
func serveLocalStorage(c *gin.Context, runtime *conf.Runtime) bool {
config := runtime.Admin()
if config == nil || config.Local == nil || config.Local.StorePath == "" {
return false
}
@@ -95,10 +100,26 @@ func serveLocalStorage(c *gin.Context, config *conf.AdminBackend) bool {
if prefix == "/" || (c.Request.URL.Path != prefix && !strings.HasPrefix(c.Request.URL.Path, prefix+"/")) {
return false
}
c.Header("X-Content-Type-Options", "nosniff")
filename := path.Base(c.Request.URL.Path)
if !canServeUploadInline(filename) {
c.Header("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename}))
}
http.StripPrefix(prefix, http.FileServer(filesOnly{FileSystem: http.Dir(config.Local.StorePath)})).ServeHTTP(c.Writer, c.Request)
return true
}
func canServeUploadInline(filename string) bool {
switch strings.ToLower(path.Ext(filename)) {
case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".ico", ".avif",
".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac",
".mp4", ".webm", ".mov", ".avi", ".mkv":
return true
default:
return false
}
}
type filesOnly struct{ http.FileSystem }
func (f filesOnly) Open(name string) (http.File, error) {
@@ -117,22 +138,3 @@ func (f filesOnly) Open(name string) (http.File, error) {
}
return file, nil
}
func (s *GinServer) Start(ctx context.Context) error {
listener, err := net.Listen(s.network, s.address)
if err != nil {
return err
}
s.listener = listener
s.server.BaseContext = func(net.Listener) context.Context { return ctx }
s.logger.InfoContext(ctx, "Gin HTTP server listening", "addr", listener.Addr().String())
if err := s.server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
}
func (s *GinServer) Stop(ctx context.Context) error {
s.logger.InfoContext(ctx, "Gin HTTP server stopping")
return s.server.Shutdown(ctx)
}