From 36d1569f8388a3086e888139eedb038a9bee23f9 Mon Sep 17 00:00:00 2001 From: engigu Date: Fri, 6 Mar 2026 08:53:22 +0800 Subject: [PATCH] feat: adjust openapi page laod --- internal/controllers/env_controller.go | 2 +- internal/controllers/log_controller.go | 2 +- internal/controllers/settings_controller.go | 10 +- internal/controllers/task_controller.go | 2 +- internal/router/router.go | 511 +++++++++++--------- internal/utils/pagination.go | 4 +- web/src/api/index.ts | 8 +- web/src/views/environments/Environments.vue | 4 +- web/src/views/history/History.vue | 2 +- web/src/views/loginlogs/LoginLogs.vue | 2 +- web/src/views/tasks/Tasks.vue | 2 +- 11 files changed, 293 insertions(+), 256 deletions(-) diff --git a/internal/controllers/env_controller.go b/internal/controllers/env_controller.go index 73b2291..9751fe1 100644 --- a/internal/controllers/env_controller.go +++ b/internal/controllers/env_controller.go @@ -62,7 +62,7 @@ func (ec *EnvController) CreateEnvVar(c *gin.Context) { // @Param name query string false "按名称模糊查询" // @Param page query int false "页码" // @Param page_size query int false "每页数量" -// @Success 200 {object} utils.Response{data=utils.PaginationData{list=[]vo.EnvVO}} +// @Success 200 {object} utils.Response{data=utils.PaginationData{data=[]vo.EnvVO}} // @Router /env [get] func (ec *EnvController) GetEnvVars(c *gin.Context) { userID := c.GetString("userID") diff --git a/internal/controllers/log_controller.go b/internal/controllers/log_controller.go index a7c88bb..a52c05e 100644 --- a/internal/controllers/log_controller.go +++ b/internal/controllers/log_controller.go @@ -28,7 +28,7 @@ func NewLogController() *LogController { // @Param status query string false "状态" // @Param page query int false "页码" // @Param page_size query int false "每页数量" -// @Success 200 {object} utils.Response{data=utils.PaginationData{list=[]vo.TaskLogVO}} +// @Success 200 {object} utils.Response{data=utils.PaginationData{data=[]vo.TaskLogVO}} // @Router /logs [get] func (lc *LogController) GetLogs(c *gin.Context) { p := utils.ParsePagination(c) diff --git a/internal/controllers/settings_controller.go b/internal/controllers/settings_controller.go index 4688191..0d5d93c 100644 --- a/internal/controllers/settings_controller.go +++ b/internal/controllers/settings_controller.go @@ -330,11 +330,11 @@ func (sc *SettingsController) GetLoginLogs(c *gin.Context) { return } - utils.Success(c, gin.H{ - "data": vo.ToLoginLogVOListFromModels(logs), - "total": total, - "page": page, - "page_size": pageSize, + utils.Success(c, utils.PaginationData{ + Data: vo.ToLoginLogVOListFromModels(logs), + Total: total, + Page: page, + PageSize: pageSize, }) } diff --git a/internal/controllers/task_controller.go b/internal/controllers/task_controller.go index f9b2c58..da93629 100644 --- a/internal/controllers/task_controller.go +++ b/internal/controllers/task_controller.go @@ -118,7 +118,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) { // @Param type query string false "任务类型" // @Param page query int false "页码" // @Param page_size query int false "每页数量" -// @Success 200 {object} utils.Response{data=utils.PaginationData{list=[]vo.TaskVO}} +// @Success 200 {object} utils.Response{data=utils.PaginationData{data=[]vo.TaskVO}} // @Router /tasks [get] func (tc *TaskController) GetTasks(c *gin.Context) { p := utils.ParsePagination(c) diff --git a/internal/router/router.go b/internal/router/router.go index 7543bbe..c1f00d3 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -69,25 +69,68 @@ func Setup(c *Controllers) *gin.Engine { root = router.Group("") } - staticFS := static.GetFS() - if staticFS != nil { - // 静态资源服务(Vue SPA),带缓存头部 - assetsGroup := root.Group("/assets") - assetsGroup.Use(cacheControl("public, max-age=31536000, immutable")) // 带哈希的资源缓存 - assetsGroup.StaticFS("/", http.FS(mustSubFS(staticFS, "assets"))) + initStaticRoutes(root) + initOpenAPIRoutes(root, urlPrefix) - // logo.svg 短缓存实现 - root.GET("/logo.svg", func(ctx *gin.Context) { - data, err := static.ReadFile("logo.svg") - if err != nil { - ctx.Status(404) - return - } - ctx.Header("Cache-Control", "public, max-age=86400") // 缓存1天 - ctx.Data(200, "image/svg+xml", data) - }) + // API 路由组 + apiV1 := root.Group("/api/v1") + initPublicAPIRoutes(apiV1, c) + initAuthorizedAPIRoutes(apiV1, c) + initAgentAPIRoutes(root, c) + + // SPA 兜底路由 - 返回 index.html(HTML禁用缓存以保证实时同步) + // 必须在最后注册,作为兜底路由 + router.NoRoute(func(ctx *gin.Context) { + path := ctx.Request.URL.Path + + // 如果配置了前缀,只处理带前缀的路径 + if urlPrefix != "" && !strings.HasPrefix(path, urlPrefix) { + ctx.Status(404) + return + } + + // 解析实际的相对路径 + relPath := strings.TrimPrefix(path, urlPrefix) + if !strings.HasPrefix(relPath, "/") { + relPath = "/" + relPath + } + + // 拦截属于 API 或静态资源目录下不存在的请求,绝不能返回 index.html 造成前端 MIME 错误 + if strings.HasPrefix(relPath, "/api/") || strings.HasPrefix(relPath, "/assets/") { + ctx.String(404, "Not Found") + return + } + + serveSPA(ctx, urlPrefix, 200) + }) + + return router +} + +func initStaticRoutes(root *gin.RouterGroup) { + staticFS := static.GetFS() + if staticFS == nil { + return } + // 静态资源服务(Vue SPA),带缓存头部 + assetsGroup := root.Group("/assets") + assetsGroup.Use(cacheControl("public, max-age=31536000, immutable")) // 带哈希的资源缓存 + assetsGroup.StaticFS("/", http.FS(mustSubFS(staticFS, "assets"))) + + // logo.svg 短缓存实现 + root.GET("/logo.svg", func(ctx *gin.Context) { + data, err := static.ReadFile("logo.svg") + if err != nil { + ctx.Status(404) + return + } + ctx.Header("Cache-Control", "public, max-age=86400") // 缓存1天 + ctx.Data(200, "image/svg+xml", data) + }) +} + +func initOpenAPIRoutes(root *gin.RouterGroup, urlPrefix string) { // OpenAPI documentation using Scalar UI (带 Basic Auth 认证) root.GET("/openapi/*any", func(c *gin.Context) { settingsSvc := services.NewSettingsService() @@ -112,14 +155,13 @@ func Setup(c *Controllers) *gin.Engine { middleware.SwaggerAuth()(c) if c.IsAborted() { // 如果认证失败(且被中间件置为 404,如密码错误且我们想要隐藏它) - if c.Writer.Status() == 404 { + if c.Writer.Status() == http.StatusNotFound { serveSPA(c, urlPrefix, 404) } return } // 获取内部路径并标准化(移除前后的所有斜杠) - // c.Param("any") 对于 *any 匹配通常包含领先斜杠,如 "/index.html" path := strings.Trim(c.Param("any"), "/") // 1. 根路径或空路径 -> 重定向到 index.html @@ -163,201 +205,224 @@ func Setup(c *Controllers) *gin.Engine { // 其他未匹配路径 -> 返回 404 SPA 页面 serveSPA(c, urlPrefix, 404) }) +} - // API 路由组 - api := root.Group("/api/v1") +func initPublicAPIRoutes(api *gin.RouterGroup, c *Controllers) { + // Health check (无需认证) + api.GET("/ping", func(ctx *gin.Context) { + ctx.JSON(200, gin.H{"message": "pong"}) + }) + + // Authentication routes (无需认证) + auth := api.Group("/auth") { - // Health check (无需认证) - api.GET("/ping", func(ctx *gin.Context) { - ctx.JSON(200, gin.H{"message": "pong"}) - }) - - // Authentication routes (无需认证) - auth := api.Group("/auth") - { - auth.POST("/login", c.Auth.Login) - auth.POST("/logout", c.Auth.Logout) - auth.POST("/register", c.Auth.Register) - } - - // 公开的站点设置(无需认证) - api.GET("/settings/public", c.Settings.GetPublicSiteSettings) - - // 需要认证的路由 - authorized := api.Group("") - authorized.Use(middleware.AuthRequired()) - { - // 获取当前用户 - authorized.GET("/auth/me", c.Auth.GetCurrentUser) - - // 仪表盘统计 - authorized.GET("/stats", c.Dashboard.GetStats) - authorized.GET("/sentence", c.Dashboard.GetSentence) - authorized.GET("/sendstats", c.Dashboard.GetSendStats) - authorized.GET("/taskstats", c.Dashboard.GetTaskStats) - - // 任务模块 - tasks := authorized.Group("/tasks") - { - tasks.POST("", c.Task.CreateTask) - tasks.GET("", c.Task.GetTasks) - tasks.GET("/:id", c.Task.GetTask) - tasks.PUT("/:id", c.Task.UpdateTask) - tasks.DELETE("/:id", c.Task.DeleteTask) - tasks.POST("/stop/:logID", c.Task.StopTask) - } - - // 任务执行模块 - execution := authorized.Group("/execute") - { - execution.POST("/task/:id", c.Executor.ExecuteTask) - execution.POST("/command", c.Executor.ExecuteCommand) - execution.GET("/results", c.Executor.GetLastResults) - } - - // 环境变量模块 - env := authorized.Group("/env") - { - env.POST("", c.Env.CreateEnvVar) - env.GET("", c.Env.GetEnvVars) - env.GET("/all", c.Env.GetAllEnvVars) - env.GET("/:id", c.Env.GetEnvVar) - env.GET("/:id/tasks", c.Env.GetAssociatedTasks) - env.PUT("/:id", c.Env.UpdateEnvVar) - env.DELETE("/:id", c.Env.DeleteEnvVar) - } - - // 脚本模块 - scripts := authorized.Group("/scripts") - { - scripts.POST("", c.Script.CreateScript) - scripts.GET("", c.Script.GetScripts) - scripts.GET("/:id", c.Script.GetScript) - scripts.PUT("/:id", c.Script.UpdateScript) - scripts.DELETE("/:id", c.Script.DeleteScript) - } - - // 文件管理模块 - files := authorized.Group("/files") - { - files.GET("/tree", c.File.GetFileTree) - files.GET("/content", c.File.GetFileContent) - files.GET("/download", c.File.DownloadFile) - files.POST("/content", c.File.SaveFileContent) - files.POST("/create", c.File.CreateFile) - files.POST("/delete", c.File.DeleteFile) - files.POST("/rename", c.File.RenameFile) - files.POST("/move", c.File.MoveFile) - files.POST("/copy", c.File.CopyFile) - files.POST("/upload", c.File.UploadArchive) - files.POST("/uploadfiles", c.File.UploadFiles) - } - - // 日志查看模块 - logs := authorized.Group("/logs") - { - logs.GET("", c.Log.GetLogs) - logs.POST("/clear", c.Log.ClearLogs) - logs.GET("/ws", c.LogWS.StreamLog) - logs.GET("/:id", c.Log.GetLogDetail) - logs.DELETE("/:id", c.Log.DeleteLog) - } - - // 终端模块 - authorized.GET("/terminal/ws", c.Terminal.HandleWebSocket) - authorized.POST("/terminal/exec", c.Terminal.ExecuteShellCommand) - authorized.GET("/terminal/cmds", c.Terminal.GetCommands) - - // 设置中心模块 - settings := authorized.Group("/settings") - { - settings.POST("/password", c.Settings.ChangePassword) - settings.GET("/site", c.Settings.GetSiteSettings) - settings.PUT("/site", c.Settings.UpdateSiteSettings) - settings.POST("/site/api-token/generate", c.Settings.GenerateApiToken) - settings.POST("/site/openapi-token/generate", c.Settings.GenerateOpenapiToken) - settings.GET("/paths", c.Settings.GetPaths) - settings.GET("/scheduler", c.Settings.GetSchedulerSettings) - settings.PUT("/scheduler", c.Settings.UpdateSchedulerSettings) - settings.GET("/about", c.Settings.GetAbout) - settings.GET("/loginlogs", c.Settings.GetLoginLogs) - settings.POST("/backup", c.Settings.CreateBackup) - settings.GET("/backup/status", c.Settings.GetBackupStatus) - settings.GET("/backup/download", c.Settings.DownloadBackup) - settings.POST("/restore", c.Settings.RestoreBackup) - // 通用设置接口 - settings.GET("/:section/:key", c.Settings.GetSetting) - settings.POST("/:section/:key/generate", c.Settings.GenerateSettingToken) - } - - // Dependency routes (依赖管理) - deps := authorized.Group("/deps") - { - deps.GET("", c.Dependency.List) - deps.POST("", c.Dependency.Create) - deps.DELETE("/:id", c.Dependency.Delete) - deps.POST("/install", c.Dependency.Install) - deps.POST("/install-cmd", c.Dependency.GetInstallCommand) - deps.POST("/uninstall/:id", c.Dependency.Uninstall) - deps.POST("/reinstall/:id", c.Dependency.Reinstall) - deps.POST("/reinstall-all", c.Dependency.ReinstallAll) - deps.POST("/reinstall-all-cmd", c.Dependency.GetReinstallAllCommand) - deps.GET("/installed", c.Dependency.GetInstalled) - } - - // Agent routes (Agent 管理) - agents := authorized.Group("/agents") - { - agents.GET("", c.Agent.List) - agents.GET("/version", c.Agent.GetVersion) - agents.PUT("/:id", c.Agent.Update) - agents.DELETE("/:id", c.Agent.Delete) - agents.POST("/:id/token", c.Agent.RegenerateToken) - agents.POST("/:id/update", c.Agent.ForceUpdate) - // 令牌管理 - agents.GET("/tokens", c.Agent.ListTokens) - agents.POST("/tokens", c.Agent.CreateToken) - agents.DELETE("/tokens/:id", c.Agent.DeleteToken) - } - - // Mise routes (Mise 管理) - mise := authorized.Group("/mise") - { - mise.GET("/ls", c.Mise.List) - mise.POST("/sync", c.Mise.Sync) - mise.GET("/plugins", c.Mise.Plugins) - mise.GET("/versions", c.Mise.Versions) - mise.GET("/verify-cmd", c.Mise.VerifyCommand) - } - - // Agent API(供前端调用,保持在 v1 下) - agentAPIv1 := authorized.Group("/agent") - { - agentAPIv1.GET("/download", c.Agent.Download) - } - - // 通知推送模块 - notify := authorized.Group("/notify") - { - notify.GET("/types", c.Notification.GetChannelTypes) - notify.GET("/channels", c.Notification.GetChannels) - notify.POST("/channels", c.Notification.SaveChannel) - notify.DELETE("/channels/:id", c.Notification.DeleteChannel) - notify.POST("/channels/test", c.Notification.TestChannel) - notify.GET("/bindings", c.Notification.GetBindings) - notify.POST("/bindings", c.Notification.SaveBinding) - notify.DELETE("/bindings/:id", c.Notification.DeleteBinding) - } - } - - // 通知发送 API(使用通知 Token 认证,供脚本调用) - notifyAPI := api.Group("/notify") - notifyAPI.Use(middleware.NotifyTokenAuth()) - { - notifyAPI.POST("/send", c.Notification.SendNotification) - } + auth.POST("/login", c.Auth.Login) + auth.POST("/logout", c.Auth.Logout) + auth.POST("/register", c.Auth.Register) } + // 公开的站点设置(无需认证) + api.GET("/settings/public", c.Settings.GetPublicSiteSettings) +} + +func initAuthorizedAPIRoutes(api *gin.RouterGroup, c *Controllers) { + authorized := api.Group("") + authorized.Use(middleware.AuthRequired()) + { + // 获取当前用户 + authorized.GET("/auth/me", c.Auth.GetCurrentUser) + + // 仪表盘统计 + authorized.GET("/stats", c.Dashboard.GetStats) + authorized.GET("/sentence", c.Dashboard.GetSentence) + authorized.GET("/sendstats", c.Dashboard.GetSendStats) + authorized.GET("/taskstats", c.Dashboard.GetTaskStats) + + registerTaskRoutes(authorized, c) + registerEnvRoutes(authorized, c) + registerScriptRoutes(authorized, c) + registerFileRoutes(authorized, c) + registerLogRoutes(authorized, c) + registerTerminalRoutes(authorized, c) + registerSettingsRoutes(authorized, c) + registerDependencyRoutes(authorized, c) + registerAgentRoutes(authorized, c) + registerMiseRoutes(authorized, c) + registerNotificationRoutes(authorized, c) + } + + // 通知发送 API(使用通知 Token 认证,供脚本调用) + notifyAPI := api.Group("/notify") + notifyAPI.Use(middleware.NotifyTokenAuth()) + { + notifyAPI.POST("/send", c.Notification.SendNotification) + } +} + +func registerTaskRoutes(g *gin.RouterGroup, c *Controllers) { + tasks := g.Group("/tasks") + { + tasks.POST("", c.Task.CreateTask) + tasks.GET("", c.Task.GetTasks) + tasks.GET("/:id", c.Task.GetTask) + tasks.PUT("/:id", c.Task.UpdateTask) + tasks.DELETE("/:id", c.Task.DeleteTask) + tasks.POST("/stop/:logID", c.Task.StopTask) + } + + execution := g.Group("/execute") + { + execution.POST("/task/:id", c.Executor.ExecuteTask) + execution.POST("/command", c.Executor.ExecuteCommand) + execution.GET("/results", c.Executor.GetLastResults) + } +} + +func registerEnvRoutes(g *gin.RouterGroup, c *Controllers) { + env := g.Group("/env") + { + env.POST("", c.Env.CreateEnvVar) + env.GET("", c.Env.GetEnvVars) + env.GET("/all", c.Env.GetAllEnvVars) + env.GET("/:id", c.Env.GetEnvVar) + env.GET("/:id/tasks", c.Env.GetAssociatedTasks) + env.PUT("/:id", c.Env.UpdateEnvVar) + env.DELETE("/:id", c.Env.DeleteEnvVar) + } +} + +func registerScriptRoutes(g *gin.RouterGroup, c *Controllers) { + scripts := g.Group("/scripts") + { + scripts.POST("", c.Script.CreateScript) + scripts.GET("", c.Script.GetScripts) + scripts.GET("/:id", c.Script.GetScript) + scripts.PUT("/:id", c.Script.UpdateScript) + scripts.DELETE("/:id", c.Script.DeleteScript) + } +} + +func registerFileRoutes(g *gin.RouterGroup, c *Controllers) { + files := g.Group("/files") + { + files.GET("/tree", c.File.GetFileTree) + files.GET("/content", c.File.GetFileContent) + files.GET("/download", c.File.DownloadFile) + files.POST("/content", c.File.SaveFileContent) + files.POST("/create", c.File.CreateFile) + files.POST("/delete", c.File.DeleteFile) + files.POST("/rename", c.File.RenameFile) + files.POST("/move", c.File.MoveFile) + files.POST("/copy", c.File.CopyFile) + files.POST("/upload", c.File.UploadArchive) + files.POST("/uploadfiles", c.File.UploadFiles) + } +} + +func registerLogRoutes(g *gin.RouterGroup, c *Controllers) { + logs := g.Group("/logs") + { + logs.GET("", c.Log.GetLogs) + logs.POST("/clear", c.Log.ClearLogs) + logs.GET("/ws", c.LogWS.StreamLog) + logs.GET("/:id", c.Log.GetLogDetail) + logs.DELETE("/:id", c.Log.DeleteLog) + } +} + +func registerTerminalRoutes(g *gin.RouterGroup, c *Controllers) { + g.GET("/terminal/ws", c.Terminal.HandleWebSocket) + g.POST("/terminal/exec", c.Terminal.ExecuteShellCommand) + g.GET("/terminal/cmds", c.Terminal.GetCommands) +} + +func registerSettingsRoutes(g *gin.RouterGroup, c *Controllers) { + settings := g.Group("/settings") + { + settings.POST("/password", c.Settings.ChangePassword) + settings.GET("/site", c.Settings.GetSiteSettings) + settings.PUT("/site", c.Settings.UpdateSiteSettings) + settings.POST("/site/api-token/generate", c.Settings.GenerateApiToken) + settings.POST("/site/openapi-token/generate", c.Settings.GenerateOpenapiToken) + settings.GET("/paths", c.Settings.GetPaths) + settings.GET("/scheduler", c.Settings.GetSchedulerSettings) + settings.PUT("/scheduler", c.Settings.UpdateSchedulerSettings) + settings.GET("/about", c.Settings.GetAbout) + settings.GET("/loginlogs", c.Settings.GetLoginLogs) + settings.POST("/backup", c.Settings.CreateBackup) + settings.GET("/backup/status", c.Settings.GetBackupStatus) + settings.GET("/backup/download", c.Settings.DownloadBackup) + settings.POST("/restore", c.Settings.RestoreBackup) + // 通用设置接口 + settings.GET("/:section/:key", c.Settings.GetSetting) + settings.POST("/:section/:key/generate", c.Settings.GenerateSettingToken) + } +} + +func registerDependencyRoutes(g *gin.RouterGroup, c *Controllers) { + deps := g.Group("/deps") + { + deps.GET("", c.Dependency.List) + deps.POST("", c.Dependency.Create) + deps.DELETE("/:id", c.Dependency.Delete) + deps.POST("/install", c.Dependency.Install) + deps.POST("/install-cmd", c.Dependency.GetInstallCommand) + deps.POST("/uninstall/:id", c.Dependency.Uninstall) + deps.POST("/reinstall/:id", c.Dependency.Reinstall) + deps.POST("/reinstall-all", c.Dependency.ReinstallAll) + deps.POST("/reinstall-all-cmd", c.Dependency.GetReinstallAllCommand) + deps.GET("/installed", c.Dependency.GetInstalled) + } +} + +func registerAgentRoutes(g *gin.RouterGroup, c *Controllers) { + agents := g.Group("/agents") + { + agents.GET("", c.Agent.List) + agents.GET("/version", c.Agent.GetVersion) + agents.PUT("/:id", c.Agent.Update) + agents.DELETE("/:id", c.Agent.Delete) + agents.POST("/:id/token", c.Agent.RegenerateToken) + agents.POST("/:id/update", c.Agent.ForceUpdate) + // 令牌管理 + agents.GET("/tokens", c.Agent.ListTokens) + agents.POST("/tokens", c.Agent.CreateToken) + agents.DELETE("/tokens/:id", c.Agent.DeleteToken) + } + + // Agent API(供前端调用,保持在 v1 下) + agentAPIv1 := g.Group("/agent") + { + agentAPIv1.GET("/download", c.Agent.Download) + } +} + +func registerMiseRoutes(g *gin.RouterGroup, c *Controllers) { + mise := g.Group("/mise") + { + mise.GET("/ls", c.Mise.List) + mise.POST("/sync", c.Mise.Sync) + mise.GET("/plugins", c.Mise.Plugins) + mise.GET("/versions", c.Mise.Versions) + mise.GET("/verify-cmd", c.Mise.VerifyCommand) + } +} + +func registerNotificationRoutes(g *gin.RouterGroup, c *Controllers) { + notify := g.Group("/notify") + { + notify.GET("/types", c.Notification.GetChannelTypes) + notify.GET("/channels", c.Notification.GetChannels) + notify.POST("/channels", c.Notification.SaveChannel) + notify.DELETE("/channels/:id", c.Notification.DeleteChannel) + notify.POST("/channels/test", c.Notification.TestChannel) + notify.GET("/bindings", c.Notification.GetBindings) + notify.POST("/bindings", c.Notification.SaveBinding) + notify.DELETE("/bindings/:id", c.Notification.DeleteBinding) + } +} + +func initAgentAPIRoutes(root *gin.RouterGroup, c *Controllers) { // Agent API(供远程 Agent 调用,不使用 /v1 版本号) agentAPI := root.Group("/api/agent") { @@ -367,34 +432,6 @@ func Setup(c *Controllers) *gin.Engine { agentAPI.GET("/download", c.Agent.Download) // 也在这里注册,兼容 Agent 调用 agentAPI.GET("/ws", c.Agent.WSConnect) // WebSocket 连接 } - - // SPA 兜底路由 - 返回 index.html(HTML禁用缓存以保证实时同步) - // 必须在最后注册,作为兜底路由 - router.NoRoute(func(ctx *gin.Context) { - path := ctx.Request.URL.Path - - // 如果配置了前缀,只处理带前缀的路径 - if urlPrefix != "" && !strings.HasPrefix(path, urlPrefix) { - ctx.Status(404) - return - } - - // 解析实际的相对路径 - relPath := strings.TrimPrefix(path, urlPrefix) - if !strings.HasPrefix(relPath, "/") { - relPath = "/" + relPath - } - - // 拦截属于 API 或静态资源目录下不存在的请求,绝不能返回 index.html 造成前端 MIME 错误 - if strings.HasPrefix(relPath, "/api/") || strings.HasPrefix(relPath, "/assets/") { - ctx.String(404, "Not Found") - return - } - - serveSPA(ctx, urlPrefix, 200) - }) - - return router } // serveSPA 注入配置并返回 index.html 给前端渲染 diff --git a/internal/utils/pagination.go b/internal/utils/pagination.go index ce24e84..8a96fd0 100644 --- a/internal/utils/pagination.go +++ b/internal/utils/pagination.go @@ -53,7 +53,7 @@ func (p Pagination) Offset() int { // PaginationData 分页数据 type PaginationData struct { - List interface{} `json:"list"` + Data interface{} `json:"data"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` @@ -62,7 +62,7 @@ type PaginationData struct { // PaginatedResponse 分页响应 func PaginatedResponse(c *gin.Context, data interface{}, total int64, p Pagination) { Success(c, PaginationData{ - List: data, + Data: data, Total: total, Page: p.Page, PageSize: p.PageSize, diff --git a/web/src/api/index.ts b/web/src/api/index.ts index b370e72..5b1fa50 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -344,7 +344,7 @@ export interface ExecutionResult { } export interface TaskListResponse { - list: Task[] + data: Task[] total: number page: number page_size: number @@ -365,7 +365,7 @@ export interface EnvVar { } export interface EnvListResponse { - list: EnvVar[] + data: EnvVar[] total: number page: number page_size: number @@ -396,7 +396,7 @@ export interface TaskLog { } export interface LogListResponse { - list: TaskLog[] + data: TaskLog[] total: number page: number page_size: number @@ -457,7 +457,7 @@ export interface LoginLog { } export interface LoginLogListResponse { - list: LoginLog[] + data: LoginLog[] total: number page: number page_size: number diff --git a/web/src/views/environments/Environments.vue b/web/src/views/environments/Environments.vue index 9d6d16a..ef349d2 100644 --- a/web/src/views/environments/Environments.vue +++ b/web/src/views/environments/Environments.vue @@ -34,10 +34,10 @@ let searchTimer: ReturnType | null = null async function loadEnvVars() { try { const res = await api.env.list({ page: currentPage.value, page_size: pageSize.value, name: filterName.value || undefined }) - envVars.value = res.list + envVars.value = res.data total.value = res.total // 初始化显示状态,根据数据库的 hidden 状态同步显示 - res.list.forEach(env => { + res.data.forEach(env => { showValues.value[env.id] = !env.hidden }) } catch { toast.error('加载环境变量失败') } diff --git a/web/src/views/history/History.vue b/web/src/views/history/History.vue index d7b1ebe..c162462 100644 --- a/web/src/views/history/History.vue +++ b/web/src/views/history/History.vue @@ -76,7 +76,7 @@ async function loadLogs() { params.status = filterStatus.value } const response = await api.logs.list(params) - logs.value = response.list + logs.value = response.data total.value = response.total } catch { toast.error('加载日志失败') diff --git a/web/src/views/loginlogs/LoginLogs.vue b/web/src/views/loginlogs/LoginLogs.vue index cdfe4b3..e3da2a2 100644 --- a/web/src/views/loginlogs/LoginLogs.vue +++ b/web/src/views/loginlogs/LoginLogs.vue @@ -81,7 +81,7 @@ async function loadLogs() { page_size: pageSize.value, username: filterUsername.value || undefined }) - logs.value = res.list + logs.value = res.data total.value = res.total } catch { toast.error('加载登录日志失败') diff --git a/web/src/views/tasks/Tasks.vue b/web/src/views/tasks/Tasks.vue index f7f4b5f..7336db3 100644 --- a/web/src/views/tasks/Tasks.vue +++ b/web/src/views/tasks/Tasks.vue @@ -74,7 +74,7 @@ async function loadTasks() { type: filterType.value === 'all' ? undefined : filterType.value, agent_id: filterAgentId.value || undefined }) - tasks.value = res.list + tasks.value = res.data total.value = res.total } catch { toast.error('加载任务失败') } }