优化结构
This commit is contained in:
@@ -1,219 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"kra/internal/conf"
|
||||
"kra/internal/server/handler"
|
||||
"kra/internal/server/httpx"
|
||||
servermiddleware "kra/internal/server/middleware"
|
||||
serverrouter "kra/internal/server/router"
|
||||
"kra/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
kratoshttp "github.com/go-kratos/kratos/v3/transport/http"
|
||||
)
|
||||
|
||||
func NewGinEngine(runtime *conf.Runtime, access *service.AccessControlService, handlers *handler.Set, auth *service.AuthService, security *service.SecurityService, audit *service.AuditRecorder, logger *slog.Logger, version string) *gin.Engine {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
engine := gin.New()
|
||||
if err := engine.SetTrustedProxies(nil); err != nil && logger != nil {
|
||||
logger.Error("disable trusted proxies failed", "error", err)
|
||||
}
|
||||
engine.Use(servermiddleware.RequestMeta(), servermiddleware.Recovery(logger), servermiddleware.AccessLog(runtime, logger, version), servermiddleware.CORS(runtime), servermiddleware.ErrorAudit(logger), servermiddleware.SecurityRateLimit(security))
|
||||
|
||||
prefix := ""
|
||||
config := runtime.Admin()
|
||||
if config != nil {
|
||||
prefix = strings.TrimSuffix(config.RouterPrefix, "/")
|
||||
}
|
||||
public := engine.Group(prefix)
|
||||
public.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, "ok") })
|
||||
serverrouter.RegisterPublic(public, engine, handlers.Public)
|
||||
|
||||
private := engine.Group(prefix)
|
||||
// The reference administration behavior installs operation recording after JWT,
|
||||
// password and Casbin/data-scope middleware. Keeping the recorder after the
|
||||
// equivalent Kra guards avoids persisting rejected unauthenticated or
|
||||
// unauthorized requests as successful business operations.
|
||||
private.Use(servermiddleware.Auth(auth), servermiddleware.MustChangePassword(), servermiddleware.AccessControl(runtime, access), servermiddleware.OperationAudit(runtime, audit))
|
||||
serverrouter.RegisterUser(private, handlers.User)
|
||||
serverrouter.RegisterNavigation(private, handlers.Navigation)
|
||||
serverrouter.RegisterSession(private, handlers.Session)
|
||||
serverrouter.RegisterAuthority(private, handlers.Authority)
|
||||
serverrouter.RegisterMenu(private, handlers.Menu)
|
||||
serverrouter.RegisterAPI(private, public, engine, handlers.API)
|
||||
serverrouter.RegisterPermission(private, handlers.Permission)
|
||||
serverrouter.RegisterOrganization(private, handlers.Organization)
|
||||
serverrouter.RegisterDictionary(private, handlers.Dictionary)
|
||||
serverrouter.RegisterParameter(private, handlers.Parameter)
|
||||
serverrouter.RegisterAPIToken(private, handlers.APIToken)
|
||||
serverrouter.RegisterSystemConfig(private, handlers.SystemConfig)
|
||||
serverrouter.RegisterVersion(private, handlers.Version)
|
||||
serverrouter.RegisterExport(private, public, handlers.Export)
|
||||
serverrouter.RegisterAudit(private, public, handlers.Audit)
|
||||
serverrouter.RegisterTask(private, handlers.Task)
|
||||
serverrouter.RegisterMedia(private, handlers.Media)
|
||||
serverrouter.RegisterAnnouncement(private, public, handlers.Announcement)
|
||||
serverrouter.RegisterEmail(private, handlers.Email)
|
||||
serverrouter.RegisterPayment(private, public, handlers.Payment)
|
||||
registerSwagger(engine, prefix, version, logger)
|
||||
registerLocalStorage(engine, runtime)
|
||||
|
||||
engine.NoRoute(func(c *gin.Context) {
|
||||
if serveLocalStorage(c, runtime) {
|
||||
return
|
||||
}
|
||||
httpx.Fail(c, "请求的接口不存在")
|
||||
})
|
||||
logRegisteredRoutes(engine, logger)
|
||||
return engine
|
||||
}
|
||||
|
||||
func logRegisteredRoutes(engine *gin.Engine, logger *slog.Logger) {
|
||||
if logger == nil {
|
||||
return
|
||||
}
|
||||
routes := append([]gin.RouteInfo(nil), engine.Routes()...)
|
||||
sort.Slice(routes, func(i, j int) bool {
|
||||
if routes[i].Path == routes[j].Path {
|
||||
return routes[i].Method < routes[j].Method
|
||||
}
|
||||
return routes[i].Path < routes[j].Path
|
||||
})
|
||||
systemLogger := logger.With("mod", "system")
|
||||
for _, route := range routes {
|
||||
systemLogger.Info("router registered", "method", route.Method, "path", route.Path)
|
||||
}
|
||||
systemLogger.Info("router register success", "route_count", len(routes))
|
||||
}
|
||||
|
||||
func NewGinServer(c *conf.Server, engine *gin.Engine) *kratoshttp.Server {
|
||||
network, address := "tcp", ":8000"
|
||||
if c != nil && c.Http != nil {
|
||||
if c.Http.Network != "" {
|
||||
network = c.Http.Network
|
||||
}
|
||||
if c.Http.Addr != "" {
|
||||
address = c.Http.Addr
|
||||
}
|
||||
}
|
||||
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, runtime *conf.Runtime) bool {
|
||||
config := runtime.Admin()
|
||||
if config == nil || config.Local == nil || config.Local.StorePath == "" {
|
||||
return false
|
||||
}
|
||||
prefix := "/" + strings.Trim(config.Local.PathPrefix, "/")
|
||||
return serveLocalStorageAt(c, runtime, prefix)
|
||||
}
|
||||
|
||||
func registerLocalStorage(engine *gin.Engine, runtime *conf.Runtime) {
|
||||
config := runtime.Admin()
|
||||
if config == nil || config.Local == nil || config.Local.StorePath == "" || strings.Trim(config.Local.PathPrefix, "/") == "" {
|
||||
return
|
||||
}
|
||||
if config.Storage != nil && config.Storage.Type != "" && config.Storage.Type != "local" {
|
||||
return
|
||||
}
|
||||
prefix := "/" + strings.Trim(config.Local.PathPrefix, "/")
|
||||
if localStorageRouteConflicts(engine.Routes(), prefix) {
|
||||
return
|
||||
}
|
||||
handler := func(c *gin.Context) {
|
||||
if !serveLocalStorageAt(c, runtime, prefix) {
|
||||
c.Status(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
engine.GET(prefix+"/*filepath", handler)
|
||||
engine.HEAD(prefix+"/*filepath", handler)
|
||||
}
|
||||
|
||||
func localStorageRouteConflicts(routes []gin.RouteInfo, prefix string) bool {
|
||||
staticRoot := strings.Split(strings.TrimPrefix(prefix, "/"), "/")[0]
|
||||
for _, route := range routes {
|
||||
if route.Method != http.MethodGet && route.Method != http.MethodHead {
|
||||
continue
|
||||
}
|
||||
routeRoot := strings.Split(strings.TrimPrefix(route.Path, "/"), "/")[0]
|
||||
if routeRoot == staticRoot {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func serveLocalStorageAt(c *gin.Context, runtime *conf.Runtime, prefix string) bool {
|
||||
config := runtime.Admin()
|
||||
if config == nil || config.Local == nil || config.Local.StorePath == "" {
|
||||
return false
|
||||
}
|
||||
if config.Storage != nil && config.Storage.Type != "" && config.Storage.Type != "local" {
|
||||
return false
|
||||
}
|
||||
if currentPrefix := "/" + strings.Trim(config.Local.PathPrefix, "/"); currentPrefix != prefix {
|
||||
return false
|
||||
}
|
||||
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) {
|
||||
file, err := f.FileSystem.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, statErr := file.Stat()
|
||||
if statErr != nil {
|
||||
_ = file.Close()
|
||||
return nil, statErr
|
||||
}
|
||||
if info.IsDir() {
|
||||
_ = file.Close()
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
Reference in New Issue
Block a user