From d844403505318880bf93f091eafcde8227aab6b4 Mon Sep 17 00:00:00 2001 From: skyle1995 Date: Sun, 26 Oct 2025 14:48:02 +0800 Subject: [PATCH] Use the gin framework --- cmd/server.go | 58 +- constants/status.go | 9 +- controllers/admin/api.go | 155 +--- controllers/admin/app.go | 1426 ++++++++++++----------------- controllers/admin/auth.go | 173 ++-- controllers/admin/captcha.go | 65 +- controllers/admin/handlers.go | 78 +- controllers/admin/settings.go | 65 +- controllers/admin/user.go | 127 ++- controllers/admin/variable.go | 222 ++--- controllers/base.go | 159 ++++ controllers/home/home.go | 82 +- database/database.go | 3 +- go.mod | 31 +- go.sum | 79 +- middleware/logging.go | 97 ++ middleware/middleware.go | 133 --- server/admin.go | 130 +-- server/home.go | 7 +- server/routes.go | 24 +- services/query.go | 6 - utils/common.go | 97 -- utils/crypto.go | 13 +- utils/csrf.go | 109 ++- utils/errors.go | 54 +- web/template/admin/apis.html | 20 +- web/template/admin/apps.html | 45 +- web/template/admin/variables.html | 2 +- web/template/layui-theme-dark | 1 - 29 files changed, 1612 insertions(+), 1858 deletions(-) create mode 100644 controllers/base.go create mode 100644 middleware/logging.go delete mode 100644 middleware/middleware.go delete mode 100644 utils/common.go delete mode 160000 web/template/layui-theme-dark diff --git a/cmd/server.go b/cmd/server.go index b52d106..7793474 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -3,6 +3,7 @@ package cmd import ( "context" "fmt" + "io" "net/http" "os" "os/signal" @@ -14,7 +15,9 @@ import ( "networkDev/server" "networkDev/utils" "networkDev/utils/logger" + "networkDev/web" + "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -90,24 +93,46 @@ func getServerPort(cmd *cobra.Command) int { // createHTTPServer 创建HTTP服务器 func createHTTPServer(addr string) *http.Server { - mux := http.NewServeMux() + // 配置Gin模式和日志 + configureGin() + + // 创建Gin引擎 + router := gin.New() + + // 添加恢复中间件 + router.Use(gin.Recovery()) + + // 添加日志中间件 + router.Use(middleware.WrapHandler()) + + // 加载模板 + if err := loadTemplates(router); err != nil { + logrus.WithError(err).Fatal("模板加载失败") + } // 注册路由 - registerRoutes(mux) - - // 使用日志中间件包装处理器 - handler := middleware.WrapHandler(mux) + registerRoutes(router) return &http.Server{ Addr: addr, - Handler: handler, + Handler: router, } } +// loadTemplates 加载模板到Gin引擎 +func loadTemplates(router *gin.Engine) error { + tmpl, err := web.ParseTemplates() + if err != nil { + return err + } + router.SetHTMLTemplate(tmpl) + return nil +} + // registerRoutes 注册HTTP路由 -func registerRoutes(mux *http.ServeMux) { +func registerRoutes(router *gin.Engine) { // 使用server包中的路由注册函数 - server.RegisterRoutes(mux) + server.RegisterRoutes(router) } // startServer 启动服务器并处理优雅关闭 @@ -143,3 +168,20 @@ func startServer(server *http.Server) { logger.LogServerStop() } } + +// configureGin 配置Gin的全局设置 +func configureGin() { + // 禁用Gin的颜色输出,提高控制台兼容性 + gin.DisableConsoleColor() + + // 设置Gin的输出为丢弃,因为我们使用自定义日志中间件 + gin.DefaultWriter = io.Discard + gin.DefaultErrorWriter = io.Discard + + // 根据配置设置Gin模式 + if viper.GetString("app.mode") == "production" { + gin.SetMode(gin.ReleaseMode) + } else { + gin.SetMode(gin.DebugMode) + } +} diff --git a/constants/status.go b/constants/status.go index 0615256..53fd103 100644 --- a/constants/status.go +++ b/constants/status.go @@ -1,10 +1,7 @@ package constants -// 验证码类型常量 -// VerificationCodeType 定义验证码的类型 +// 应用程序版本信息 const ( - // VerificationCodeTypeText 文本验证码 - VerificationCodeTypeText = 1 - // VerificationCodeTypeImage 图片验证码 - VerificationCodeTypeImage = 2 + // AppVersion 应用程序版本号 + AppVersion = "1.2.0" ) diff --git a/controllers/admin/api.go b/controllers/admin/api.go index 2b1415f..2276d86 100644 --- a/controllers/admin/api.go +++ b/controllers/admin/api.go @@ -2,57 +2,52 @@ package admin import ( "encoding/hex" - "encoding/json" "net/http" - "networkDev/database" + "networkDev/controllers" "networkDev/models" - "networkDev/utils" "networkDev/utils/encrypt" "strconv" "strings" + "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) +// 创建基础控制器实例 +var apiBaseController = controllers.NewBaseController() + // APIFragmentHandler 接口列表页面片段处理器 -func APIFragmentHandler(w http.ResponseWriter, r *http.Request) { - utils.RenderTemplate(w, "apis.html", map[string]interface{}{ +func APIFragmentHandler(c *gin.Context) { + c.HTML(http.StatusOK, "apis.html", gin.H{ "Title": "接口管理", }) } // APIListHandler 接口列表API处理器 -func APIListHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func APIListHandler(c *gin.Context) { // 获取分页参数 - page, _ := strconv.Atoi(r.URL.Query().Get("page")) + page, _ := strconv.Atoi(c.Query("page")) if page <= 0 { page = 1 } - limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) + limit, _ := strconv.Atoi(c.Query("limit")) if limit <= 0 { limit = 10 } // 获取应用UUID参数(用于按应用筛选接口) - appUUID := strings.TrimSpace(r.URL.Query().Get("app_uuid")) + appUUID := strings.TrimSpace(c.Query("app_uuid")) // 获取接口类型参数(用于按接口类型筛选) - apiTypeStr := strings.TrimSpace(r.URL.Query().Get("api_type")) + apiTypeStr := strings.TrimSpace(c.Query("api_type")) var apiType int if apiTypeStr != "" { apiType, _ = strconv.Atoi(apiTypeStr) } // 构建查询 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "Internal server error", http.StatusInternalServerError) + db, ok := apiBaseController.GetDB(c) + if !ok { return } @@ -73,7 +68,7 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) { var total int64 if err := query.Count(&total).Error; err != nil { logrus.WithError(err).Error("Failed to count APIs") - http.Error(w, "Internal server error", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "获取接口总数失败", err) return } @@ -82,7 +77,7 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) { offset := (page - 1) * limit if err := query.Offset(offset).Limit(limit).Order("created_at DESC").Find(&apis).Error; err != nil { logrus.WithError(err).Error("Failed to fetch APIs") - http.Error(w, "Internal server error", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "获取接口列表失败", err) return } @@ -133,9 +128,9 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) { // 计算分页信息 totalPages := (total + int64(limit) - 1) / int64(limit) - response := map[string]interface{}{ + response := gin.H{ "success": true, - "data": map[string]interface{}{ + "data": gin.H{ "apis": responseAPIs, "total": total, "page": page, @@ -144,8 +139,7 @@ func APIListHandler(w http.ResponseWriter, r *http.Request) { }, } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + c.JSON(http.StatusOK, response) } // getAPIStatusName 获取API状态名称 @@ -161,12 +155,7 @@ func getAPIStatusName(status int) string { } // APIUpdateHandler 更新接口处理器 -func APIUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func APIUpdateHandler(c *gin.Context) { var req struct { UUID string `json:"uuid"` Status int `json:"status"` @@ -178,39 +167,36 @@ func APIUpdateHandler(w http.ResponseWriter, r *http.Request) { ReturnPrivateKey string `json:"return_private_key"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !apiBaseController.BindJSON(c, &req) { return } // 验证必填字段 if strings.TrimSpace(req.UUID) == "" { - http.Error(w, "接口UUID不能为空", http.StatusBadRequest) + apiBaseController.HandleValidationError(c, "接口UUID不能为空") return } if req.Status != 0 && req.Status != 1 { - http.Error(w, "无效的状态值", http.StatusBadRequest) + apiBaseController.HandleValidationError(c, "无效的状态值") return } if !models.IsValidAlgorithm(req.SubmitAlgorithm) || !models.IsValidAlgorithm(req.ReturnAlgorithm) { - http.Error(w, "无效的算法类型", http.StatusBadRequest) + apiBaseController.HandleValidationError(c, "无效的算法类型") return } // 获取数据库连接 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "Internal server error", http.StatusInternalServerError) + db, ok := apiBaseController.GetDB(c) + if !ok { return } // 查找并更新API记录 var api models.API if err := db.Where("uuid = ?", strings.TrimSpace(req.UUID)).First(&api).Error; err != nil { - http.Error(w, "接口不存在", http.StatusNotFound) + apiBaseController.HandleValidationError(c, "接口不存在") return } @@ -231,32 +217,18 @@ func APIUpdateHandler(w http.ResponseWriter, r *http.Request) { if err := db.Save(&api).Error; err != nil { logrus.WithError(err).Error("Failed to update API") - http.Error(w, "更新接口失败", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "更新接口失败", err) return } - response := map[string]interface{}{ - "success": true, - "message": "接口更新成功", - "data": api, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + apiBaseController.HandleSuccess(c, "接口更新成功", api) } // APIGetAppsHandler 获取应用列表(用于接口页面的应用选择器) -func APIGetAppsHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func APIGetAppsHandler(c *gin.Context) { // 获取数据库连接 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "Internal server error", http.StatusInternalServerError) + db, ok := apiBaseController.GetDB(c) + if !ok { return } @@ -264,26 +236,15 @@ func APIGetAppsHandler(w http.ResponseWriter, r *http.Request) { var apps []models.App if err := db.Select("uuid, name").Order("created_at ASC").Find(&apps).Error; err != nil { logrus.WithError(err).Error("Failed to fetch apps") - http.Error(w, "Internal server error", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "获取应用列表失败", err) return } - response := map[string]interface{}{ - "success": true, - "data": apps, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + apiBaseController.HandleSuccess(c, "获取应用列表成功", apps) } // APIGetTypesHandler 获取接口类型列表API处理器 -func APIGetTypesHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func APIGetTypesHandler(c *gin.Context) { // 构建接口类型列表 type APITypeItem struct { Value int `json:"value"` @@ -310,35 +271,25 @@ func APIGetTypesHandler(w http.ResponseWriter, r *http.Request) { }) } - response := map[string]interface{}{ - "success": true, - "data": apiTypes, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + apiBaseController.HandleSuccess(c, "获取接口类型列表成功", apiTypes) } -func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func APIGenerateKeysHandler(c *gin.Context) { var req struct { Side string `json:"side"` // submit | return Algorithm int `json:"algorithm"` // 与 models.Algorithm* 对应 } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + + if !apiBaseController.BindJSON(c, &req) { return } + if req.Side != "submit" && req.Side != "return" { - http.Error(w, "side参数必须为submit或return", http.StatusBadRequest) + apiBaseController.HandleValidationError(c, "side参数必须为submit或return") return } if !models.IsValidAlgorithm(req.Algorithm) { - http.Error(w, "无效的算法类型", http.StatusBadRequest) + apiBaseController.HandleValidationError(c, "无效的算法类型") return } @@ -355,7 +306,7 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) { key, err := encrypt.GenerateRC4Key(8) // 生成8字节密钥 if err != nil { logrus.WithError(err).Error("Failed to generate RC4 key") - http.Error(w, "生成RC4密钥失败", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "生成RC4密钥失败", err) return } result["public_key"] = "" @@ -365,7 +316,7 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) { publicKey, privateKey, err := encrypt.GenerateRSAKeyPair(2048) if err != nil { logrus.WithError(err).Error("Failed to generate RSA key pair") - http.Error(w, "生成RSA密钥失败", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "生成RSA密钥失败", err) return } @@ -373,14 +324,14 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) { publicKeyPEM, err := encrypt.PublicKeyToPEM(publicKey) if err != nil { logrus.WithError(err).Error("Failed to convert public key to PEM") - http.Error(w, "转换公钥格式失败", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "转换公钥格式失败", err) return } privateKeyPEM, err := encrypt.PrivateKeyToPEM(privateKey) if err != nil { logrus.WithError(err).Error("Failed to convert private key to PEM") - http.Error(w, "转换私钥格式失败", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "转换私钥格式失败", err) return } @@ -391,7 +342,7 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) { publicKeyPEM, privateKeyPEM, err := encrypt.GenerateRSADynamicKeyPair(2048) if err != nil { logrus.WithError(err).Error("Failed to generate RSA dynamic key pair") - http.Error(w, "生成RSA动态密钥失败", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "生成RSA动态密钥失败", err) return } @@ -402,21 +353,15 @@ func APIGenerateKeysHandler(w http.ResponseWriter, r *http.Request) { encryptKey, _, err := encrypt.GenerateEasyKey() if err != nil { logrus.WithError(err).Error("Failed to generate Easy encryption key") - http.Error(w, "生成易加密密钥失败", http.StatusInternalServerError) + apiBaseController.HandleInternalError(c, "生成易加密密钥失败", err) return } result["public_key"] = "" result["private_key"] = encrypt.FormatKeyAsString(encryptKey) default: - http.Error(w, "不支持的算法类型", http.StatusBadRequest) + apiBaseController.HandleValidationError(c, "不支持的算法类型") return } - response := map[string]interface{}{ - "success": true, - "message": "生成成功", - "data": result, - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + apiBaseController.HandleSuccess(c, "生成成功", result) } diff --git a/controllers/admin/app.go b/controllers/admin/app.go index a8546f8..5ef042c 100644 --- a/controllers/admin/app.go +++ b/controllers/admin/app.go @@ -4,50 +4,44 @@ import ( "crypto/rand" "encoding/base64" "encoding/hex" - "encoding/json" "net/http" - "networkDev/database" + "networkDev/controllers" "networkDev/models" - "networkDev/utils" "strconv" "strings" + "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/sirupsen/logrus" ) +var appBaseController = controllers.NewBaseController() + // AppsFragmentHandler 应用列表页面片段处理器 -func AppsFragmentHandler(w http.ResponseWriter, r *http.Request) { - utils.RenderTemplate(w, "apps.html", map[string]interface{}{ +func AppsFragmentHandler(c *gin.Context) { + c.HTML(http.StatusOK, "apps.html", gin.H{ "Title": "应用管理", }) } // AppsListHandler 应用列表API处理器 -func AppsListHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func AppsListHandler(c *gin.Context) { // 获取分页参数 - page, _ := strconv.Atoi(r.URL.Query().Get("page")) + page, _ := strconv.Atoi(c.Query("page")) if page <= 0 { page = 1 } - limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) + limit, _ := strconv.Atoi(c.Query("limit")) if limit <= 0 { limit = 10 } // 获取搜索参数 - search := strings.TrimSpace(r.URL.Query().Get("search")) + search := strings.TrimSpace(c.Query("search")) // 构建查询 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "Internal server error", http.StatusInternalServerError) + db, ok := appBaseController.GetDB(c) + if !ok { return } @@ -64,7 +58,7 @@ func AppsListHandler(w http.ResponseWriter, r *http.Request) { // 获取总数 if err := query.Count(&total).Error; err != nil { logrus.WithError(err).Error("Failed to count apps") - http.Error(w, "Internal server error", http.StatusInternalServerError) + appBaseController.HandleInternalError(c, "获取应用总数失败", err) return } @@ -72,51 +66,36 @@ func AppsListHandler(w http.ResponseWriter, r *http.Request) { offset := (page - 1) * limit if err := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&apps).Error; err != nil { logrus.WithError(err).Error("Failed to query apps") - http.Error(w, "Internal server error", http.StatusInternalServerError) + appBaseController.HandleInternalError(c, "查询应用列表失败", err) return } // 返回结果 - response := map[string]interface{}{ + response := gin.H{ "code": 0, "msg": "success", "count": total, "data": apps, } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + c.JSON(http.StatusOK, response) } // AppGetAppDataHandler 获取应用数据处理器 -func AppGetAppDataHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func AppGetAppDataHandler(c *gin.Context) { // 获取UUID参数 - uuid := r.URL.Query().Get("uuid") + uuid := c.Query("uuid") if uuid == "" { - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "应用UUID不能为空", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } // 获取数据库连接 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - response := map[string]interface{}{ - "code": 1, - "msg": "数据库连接失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + db, ok := appBaseController.GetDB(c) + if !ok { return } @@ -124,12 +103,10 @@ func AppGetAppDataHandler(w http.ResponseWriter, r *http.Request) { var app models.App if err := db.Where("uuid = ?", uuid).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - response := map[string]interface{}{ + c.JSON(http.StatusNotFound, gin.H{ "code": 1, "msg": "应用不存在", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } @@ -146,47 +123,30 @@ func AppGetAppDataHandler(w http.ResponseWriter, r *http.Request) { } } - response := map[string]interface{}{ + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "获取成功", - "data": map[string]interface{}{ + "data": gin.H{ "app_data": appData, }, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } -// AppGetAnnouncementHandler 获取应用程序公告处理器 -func AppGetAnnouncementHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +// AppGetAnnouncementHandler 获取公告处理器 +func AppGetAnnouncementHandler(c *gin.Context) { // 获取UUID参数 - uuid := r.URL.Query().Get("uuid") + uuid := c.Query("uuid") if uuid == "" { - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "应用UUID不能为空", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } // 获取数据库连接 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - response := map[string]interface{}{ - "code": 1, - "msg": "数据库连接失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + db, ok := appBaseController.GetDB(c) + if !ok { return } @@ -194,12 +154,10 @@ func AppGetAnnouncementHandler(w http.ResponseWriter, r *http.Request) { var app models.App if err := db.Where("uuid = ?", uuid).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - response := map[string]interface{}{ + c.JSON(http.StatusNotFound, gin.H{ "code": 1, "msg": "应用不存在", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } @@ -216,90 +174,79 @@ func AppGetAnnouncementHandler(w http.ResponseWriter, r *http.Request) { } } - response := map[string]interface{}{ + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "获取成功", - "data": map[string]interface{}{ + "data": gin.H{ "announcement": announcement, }, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } -// AppResetSecretHandler 重置应用密钥API处理器 -func AppResetSecretHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +// AppResetSecretHandler 重置应用密钥处理器 +func AppResetSecretHandler(c *gin.Context) { var req struct { UUID string `json:"uuid"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !appBaseController.BindJSON(c, &req) { return } - if req.UUID == "" { - http.Error(w, "应用UUID不能为空", http.StatusBadRequest) + // 验证必填字段 + if strings.TrimSpace(req.UUID) == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用UUID不能为空", + }) return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { return } // 查找应用 var app models.App if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { - logrus.WithError(err).Error("Failed to find app by UUID") - http.Error(w, "应用不存在", http.StatusNotFound) + logrus.WithError(err).Error("Failed to find app") + c.JSON(http.StatusNotFound, gin.H{ + "code": 1, + "msg": "应用不存在", + }) return } // 生成新的密钥 - bytes := make([]byte, 16) - if _, err := rand.Read(bytes); err != nil { - logrus.WithError(err).Error("Failed to generate random secret") - http.Error(w, "生成密钥失败", http.StatusInternalServerError) - return - } + bytes := make([]byte, 16) // 16字节 = 32位16进制字符 + rand.Read(bytes) newSecret := strings.ToUpper(hex.EncodeToString(bytes)) // 更新密钥 if err := db.Model(&app).Update("secret", newSecret).Error; err != nil { logrus.WithError(err).Error("Failed to update app secret") - http.Error(w, "更新密钥失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "重置密钥失败", + }) return } - response := map[string]interface{}{ + logrus.WithField("app_uuid", app.UUID).Info("Successfully reset app secret") + + c.JSON(http.StatusOK, gin.H{ "code": 0, - "msg": "密钥重置成功", - "data": map[string]interface{}{ - "uuid": app.UUID, + "msg": "重置成功", + "data": gin.H{ "secret": newSecret, }, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } // AppCreateHandler 创建应用API处理器 -func AppCreateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func AppCreateHandler(c *gin.Context) { var req struct { Name string `json:"name"` Version string `json:"version"` @@ -309,16 +256,17 @@ func AppCreateHandler(w http.ResponseWriter, r *http.Request) { DownloadURL string `json:"download_url"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - logrus.WithError(err).Error("Failed to decode JSON request") - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !appBaseController.BindJSON(c, &req) { return } // 验证必填字段 if strings.TrimSpace(req.Name) == "" { logrus.Error("App name is empty") - http.Error(w, "应用名称不能为空", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用名称不能为空", + }) return } @@ -357,15 +305,21 @@ func AppCreateHandler(w http.ResponseWriter, r *http.Request) { app.Secret = strings.ToUpper(hex.EncodeToString(bytes)) } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + db, ok := appBaseController.GetDB(c) + if !ok { return } // 开始事务 tx := db.Begin() + if tx.Error != nil { + logrus.WithError(tx.Error).Error("Failed to begin transaction") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "开始事务失败", + }) + return + } defer func() { if r := recover(); r != nil { tx.Rollback() @@ -376,7 +330,10 @@ func AppCreateHandler(w http.ResponseWriter, r *http.Request) { if err := tx.Create(&app).Error; err != nil { tx.Rollback() logrus.WithError(err).Error("Failed to create app") - http.Error(w, "创建应用失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "创建应用失败", + }) return } @@ -417,7 +374,10 @@ func AppCreateHandler(w http.ResponseWriter, r *http.Request) { if err := tx.Create(&api).Error; err != nil { tx.Rollback() logrus.WithError(err).WithField("api_type", apiType).Error("Failed to create default API") - http.Error(w, "创建默认接口失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "创建默认接口失败", + }) return } } @@ -425,29 +385,24 @@ func AppCreateHandler(w http.ResponseWriter, r *http.Request) { // 提交事务 if err := tx.Commit().Error; err != nil { logrus.WithError(err).Error("Failed to commit transaction") - http.Error(w, "提交事务失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "提交事务失败", + }) return } logrus.WithField("app_uuid", app.UUID).Info("Successfully created app with default APIs") - response := map[string]interface{}{ + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "创建成功", "data": app, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } // AppUpdateHandler 更新应用API处理器 -func AppUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func AppUpdateHandler(c *gin.Context) { var req struct { ID uint `json:"id"` Name string `json:"name"` @@ -458,36 +413,45 @@ func AppUpdateHandler(w http.ResponseWriter, r *http.Request) { ForceUpdate int `json:"force_update"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !appBaseController.BindJSON(c, &req) { return } // 验证必填字段 if req.ID == 0 { - http.Error(w, "应用ID不能为空", http.StatusBadRequest) - return - } - if strings.TrimSpace(req.Name) == "" { - http.Error(w, "应用名称不能为空", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用ID不能为空", + }) return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + if strings.TrimSpace(req.Name) == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用名称不能为空", + }) + return + } + + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { return } // 查找应用 var app models.App if err := db.First(&app, req.ID).Error; err != nil { - http.Error(w, "应用不存在", http.StatusNotFound) + logrus.WithError(err).Error("Failed to find app") + c.JSON(http.StatusNotFound, gin.H{ + "code": 1, + "msg": "应用不存在", + }) return } - // 更新字段 + // 更新应用信息 app.Name = strings.TrimSpace(req.Name) app.Version = req.Version app.Status = req.Status @@ -497,304 +461,163 @@ func AppUpdateHandler(w http.ResponseWriter, r *http.Request) { if err := db.Save(&app).Error; err != nil { logrus.WithError(err).Error("Failed to update app") - http.Error(w, "更新应用失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "更新应用失败", + }) return } - response := map[string]interface{}{ + logrus.WithField("app_id", app.ID).Info("Successfully updated app") + + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "更新成功", "data": app, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } -// AppDeleteHandler 删除应用API处理器 -func AppDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +// AppDeleteHandler 删除应用处理器 +func AppDeleteHandler(c *gin.Context) { var req struct { ID uint `json:"id"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !appBaseController.BindJSON(c, &req) { return } + // 验证必填字段 if req.ID == 0 { - http.Error(w, "应用ID不能为空", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用ID不能为空", + }) return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { + return + } + + // 查找应用 + var app models.App + if err := db.First(&app, req.ID).Error; err != nil { + logrus.WithError(err).Error("Failed to find app") + c.JSON(http.StatusNotFound, gin.H{ + "code": 1, + "msg": "应用不存在", + }) return } // 开始事务 tx := db.Begin() + if tx.Error != nil { + logrus.WithError(tx.Error).Error("Failed to begin transaction") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "开始事务失败", + }) + return + } + defer func() { if r := recover(); r != nil { tx.Rollback() } }() - // 首先获取应用信息以获取UUID - var app models.App - if err := tx.First(&app, req.ID).Error; err != nil { - tx.Rollback() - logrus.WithError(err).Error("Failed to find app") - http.Error(w, "应用不存在", http.StatusNotFound) - return - } - - // 删除该应用的所有相关接口 + // 删除相关的API记录 if err := tx.Where("app_uuid = ?", app.UUID).Delete(&models.API{}).Error; err != nil { tx.Rollback() logrus.WithError(err).Error("Failed to delete related APIs") - http.Error(w, "删除相关接口失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "删除相关接口失败", + }) + return + } + + // 删除相关的变量记录 + if err := tx.Where("app_uuid = ?", app.UUID).Delete(&models.Variable{}).Error; err != nil { + tx.Rollback() + logrus.WithError(err).Error("Failed to delete related variables") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "删除相关变量失败", + }) return } // 删除应用 - if err := tx.Delete(&models.App{}, req.ID).Error; err != nil { + if err := tx.Delete(&app).Error; err != nil { tx.Rollback() logrus.WithError(err).Error("Failed to delete app") - http.Error(w, "删除应用失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "删除应用失败", + }) return } // 提交事务 if err := tx.Commit().Error; err != nil { logrus.WithError(err).Error("Failed to commit transaction") - http.Error(w, "提交事务失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "提交事务失败", + }) return } logrus.WithFields(logrus.Fields{ - "app_id": req.ID, + "app_id": app.ID, "app_uuid": app.UUID, - }).Info("Successfully deleted app and related APIs") + }).Info("Successfully deleted app and related data") - response := map[string]interface{}{ + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "删除成功", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -// AppsBatchDeleteHandler 批量删除应用API处理器 -func AppsBatchDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - var req struct { - IDs []uint `json:"ids"` - } - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) - return - } - - if len(req.IDs) == 0 { - http.Error(w, "请选择要删除的应用", http.StatusBadRequest) - return - } - - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) - return - } - - // 开始事务 - tx := db.Begin() - defer func() { - if r := recover(); r != nil { - tx.Rollback() - } - }() - - // 首先获取要删除的应用的UUID列表 - var apps []models.App - if err := tx.Where("id IN ?", req.IDs).Find(&apps).Error; err != nil { - tx.Rollback() - logrus.WithError(err).Error("Failed to find apps") - http.Error(w, "查找应用失败", http.StatusInternalServerError) - return - } - - // 提取UUID列表 - var appUUIDs []string - for _, app := range apps { - appUUIDs = append(appUUIDs, app.UUID) - } - - // 删除这些应用的所有相关接口 - if len(appUUIDs) > 0 { - if err := tx.Where("app_uuid IN ?", appUUIDs).Delete(&models.API{}).Error; err != nil { - tx.Rollback() - logrus.WithError(err).Error("Failed to delete related APIs") - http.Error(w, "删除相关接口失败", http.StatusInternalServerError) - return - } - } - - // 批量删除应用 - if err := tx.Delete(&models.App{}, req.IDs).Error; err != nil { - tx.Rollback() - logrus.WithError(err).Error("Failed to batch delete apps") - http.Error(w, "批量删除失败", http.StatusInternalServerError) - return - } - - // 提交事务 - if err := tx.Commit().Error; err != nil { - logrus.WithError(err).Error("Failed to commit transaction") - http.Error(w, "提交事务失败", http.StatusInternalServerError) - return - } - - logrus.WithFields(logrus.Fields{ - "app_ids": req.IDs, - "app_uuids": appUUIDs, - }).Info("Successfully batch deleted apps and related APIs") - - response := map[string]interface{}{ - "code": 0, - "msg": "批量删除成功", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) -} - -// AppsBatchUpdateStatusHandler 批量更新应用状态API处理器 -func AppsBatchUpdateStatusHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - var req struct { - IDs []uint `json:"ids"` - Status int `json:"status"` - } - - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) - return - } - - if len(req.IDs) == 0 { - http.Error(w, "请选择要更新的应用", http.StatusBadRequest) - return - } - - if req.Status != 0 && req.Status != 1 { - http.Error(w, "状态值无效", http.StatusBadRequest) - return - } - - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) - return - } - - // 批量更新状态 - if err := db.Model(&models.App{}).Where("id IN ?", req.IDs).Update("status", req.Status).Error; err != nil { - logrus.WithError(err).Error("Failed to batch update app status") - http.Error(w, "批量更新状态失败", http.StatusInternalServerError) - return - } - - statusText := "禁用" - if req.Status == 1 { - statusText = "启用" - } - - response := map[string]interface{}{ - "code": 0, - "msg": "批量" + statusText + "成功", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } // AppUpdateAppDataHandler 更新应用数据处理器 -func AppUpdateAppDataHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func AppUpdateAppDataHandler(c *gin.Context) { // 解析请求体 var req struct { UUID string `json:"uuid"` AppData string `json:"app_data"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - logrus.WithError(err).Error("Failed to decode request body") - response := map[string]interface{}{ - "code": 1, - "msg": "请求参数格式错误", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + if !appBaseController.BindJSON(c, &req) { return } // 验证UUID if req.UUID == "" { - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "应用UUID不能为空", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } // 验证UUID格式 if _, err := uuid.Parse(req.UUID); err != nil { logrus.WithError(err).Error("Invalid UUID format") - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "无效的UUID格式", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } // 获取数据库连接 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - response := map[string]interface{}{ - "code": 1, - "msg": "数据库连接失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + db, ok := appBaseController.GetDB(c) + if !ok { return } @@ -802,12 +625,10 @@ func AppUpdateAppDataHandler(w http.ResponseWriter, r *http.Request) { var app models.App if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - response := map[string]interface{}{ + c.JSON(http.StatusNotFound, gin.H{ "code": 1, "msg": "应用不存在", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } @@ -817,12 +638,10 @@ func AppUpdateAppDataHandler(w http.ResponseWriter, r *http.Request) { // 更新应用的数据内容 if err := db.Model(&app).Update("app_data", encodedAppData).Error; err != nil { logrus.WithError(err).Error("Failed to update app data") - response := map[string]interface{}{ + c.JSON(http.StatusInternalServerError, gin.H{ "code": 1, "msg": "更新应用数据失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } @@ -831,72 +650,46 @@ func AppUpdateAppDataHandler(w http.ResponseWriter, r *http.Request) { "app_name": app.Name, }).Info("App data updated successfully") - response := map[string]interface{}{ + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "应用数据更新成功", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } // AppUpdateAnnouncementHandler 更新应用程序公告处理器 -func AppUpdateAnnouncementHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func AppUpdateAnnouncementHandler(c *gin.Context) { // 解析请求体 var req struct { UUID string `json:"uuid"` Announcement string `json:"announcement"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - logrus.WithError(err).Error("Failed to decode request body") - response := map[string]interface{}{ - "code": 1, - "msg": "请求参数格式错误", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + if !appBaseController.BindJSON(c, &req) { return } // 验证UUID if req.UUID == "" { - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "应用UUID不能为空", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } // 验证UUID格式 if _, err := uuid.Parse(req.UUID); err != nil { logrus.WithError(err).Error("Invalid UUID format") - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "无效的UUID格式", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } // 获取数据库连接 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - response := map[string]interface{}{ - "code": 1, - "msg": "数据库连接失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + db, ok := appBaseController.GetDB(c) + if !ok { return } @@ -904,12 +697,10 @@ func AppUpdateAnnouncementHandler(w http.ResponseWriter, r *http.Request) { var app models.App if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - response := map[string]interface{}{ + c.JSON(http.StatusNotFound, gin.H{ "code": 1, "msg": "应用不存在", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } @@ -919,12 +710,10 @@ func AppUpdateAnnouncementHandler(w http.ResponseWriter, r *http.Request) { // 更新应用的公告内容 if err := db.Model(&app).Update("announcement", encodedAnnouncement).Error; err != nil { logrus.WithError(err).Error("Failed to update app announcement") - response := map[string]interface{}{ + c.JSON(http.StatusInternalServerError, gin.H{ "code": 1, "msg": "更新程序公告失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } @@ -933,60 +722,66 @@ func AppUpdateAnnouncementHandler(w http.ResponseWriter, r *http.Request) { "app_name": app.Name, }).Info("App announcement updated successfully") - response := map[string]interface{}{ + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "程序公告更新成功", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } -// ... existing code ... - -// AppGetMultiConfigHandler 获取应用多开配置 -func AppGetMultiConfigHandler(w http.ResponseWriter, r *http.Request) { - appUUID := r.URL.Query().Get("uuid") +// AppGetMultiConfigHandler 获取应用多开配置处理器 +func AppGetMultiConfigHandler(c *gin.Context) { + appUUID := c.Query("uuid") if appUUID == "" { - http.Error(w, "缺少应用UUID", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用UUID不能为空", + }) return } // 验证UUID格式 if _, err := uuid.Parse(appUUID); err != nil { - http.Error(w, "无效的UUID格式", http.StatusBadRequest) + logrus.WithError(err).Error("Invalid UUID format") + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "无效的UUID格式", + }) return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { return } + // 查找应用 var app models.App if err := db.Where("uuid = ?", appUUID).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - http.Error(w, "应用不存在", http.StatusNotFound) + c.JSON(http.StatusNotFound, gin.H{ + "code": 1, + "msg": "应用不存在", + }) return } - // 返回多开配置信息 - response := map[string]interface{}{ - "login_type": app.LoginType, - "multi_open_scope": app.MultiOpenScope, - "clean_interval": app.CleanInterval, - "check_interval": app.CheckInterval, - "multi_open_count": app.MultiOpenCount, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": "获取多开配置成功", + "data": gin.H{ + "login_type": app.LoginType, + "multi_open_scope": app.MultiOpenScope, + "clean_interval": app.CleanInterval, + "check_interval": app.CheckInterval, + "multi_open_count": app.MultiOpenCount, + }, + }) } -// AppUpdateMultiConfigHandler 更新应用多开配置 -func AppUpdateMultiConfigHandler(w http.ResponseWriter, r *http.Request) { +// AppUpdateMultiConfigHandler 更新应用多开配置处理器 +func AppUpdateMultiConfigHandler(c *gin.Context) { + // 解析请求体 var req struct { UUID string `json:"uuid"` LoginType int `json:"login_type"` @@ -996,43 +791,69 @@ func AppUpdateMultiConfigHandler(w http.ResponseWriter, r *http.Request) { MultiOpenCount int `json:"multi_open_count"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !appBaseController.BindJSON(c, &req) { + return + } + + // 验证UUID + if req.UUID == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用UUID不能为空", + }) return } // 验证UUID格式 if _, err := uuid.Parse(req.UUID); err != nil { - http.Error(w, "无效的UUID格式", http.StatusBadRequest) + logrus.WithError(err).Error("Invalid UUID format") + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "无效的UUID格式", + }) return } // 验证参数范围 if req.LoginType < 0 || req.LoginType > 1 { - http.Error(w, "登录方式参数无效", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "登录方式参数无效", + }) return } if req.MultiOpenScope < 0 || req.MultiOpenScope > 2 { - http.Error(w, "多开范围参数无效", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "多开范围参数无效", + }) return } if req.CleanInterval < 1 { - http.Error(w, "清理间隔必须大于0", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "清理间隔必须大于0", + }) return } if req.CheckInterval < 1 { - http.Error(w, "校验间隔必须大于0", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "校验间隔必须大于0", + }) return } if req.MultiOpenCount < 1 { - http.Error(w, "多开数量必须大于0", http.StatusBadRequest) + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "多开数量必须大于0", + }) return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { return } @@ -1040,7 +861,10 @@ func AppUpdateMultiConfigHandler(w http.ResponseWriter, r *http.Request) { var app models.App if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - http.Error(w, "应用不存在", http.StatusNotFound) + c.JSON(http.StatusNotFound, gin.H{ + "code": 1, + "msg": "应用不存在", + }) return } @@ -1055,223 +879,127 @@ func AppUpdateMultiConfigHandler(w http.ResponseWriter, r *http.Request) { if err := db.Model(&app).Updates(updates).Error; err != nil { logrus.WithError(err).Error("Failed to update app multi config") - http.Error(w, "更新多开配置失败", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "更新多开配置失败", + }) return } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{"message": "多开配置更新成功"}) + logrus.WithFields(logrus.Fields{ + "app_uuid": req.UUID, + "app_name": app.Name, + }).Info("App multi config updated successfully") + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": "多开配置更新成功", + }) } -// AppGetBindConfigHandler 获取应用绑定配置 -func AppGetBindConfigHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - appUUID := r.URL.Query().Get("uuid") +// AppGetBindConfigHandler 获取应用绑定配置处理器 +func AppGetBindConfigHandler(c *gin.Context) { + appUUID := c.Query("uuid") if appUUID == "" { - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, - "msg": "缺少应用UUID", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + "msg": "应用UUID不能为空", + }) return } // 验证UUID格式 if _, err := uuid.Parse(appUUID); err != nil { - response := map[string]interface{}{ + logrus.WithError(err).Error("Invalid UUID format") + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "无效的UUID格式", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - response := map[string]interface{}{ - "code": 1, - "msg": "数据库连接失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { return } + // 查找应用 var app models.App if err := db.Where("uuid = ?", appUUID).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - response := map[string]interface{}{ + c.JSON(http.StatusNotFound, gin.H{ "code": 1, "msg": "应用不存在", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } - // 返回绑定配置信息 - response := map[string]interface{}{ - "machine_verify": app.MachineVerify, - "machine_rebind_enabled": app.MachineRebindEnabled, - "machine_rebind_limit": app.MachineRebindLimit, - "machine_free_count": app.MachineFreeCount, - "machine_rebind_count": app.MachineRebindCount, - "machine_rebind_deduct": app.MachineRebindDeduct, - "ip_verify": app.IPVerify, - "ip_rebind_enabled": app.IPRebindEnabled, - "ip_rebind_limit": app.IPRebindLimit, - "ip_free_count": app.IPFreeCount, - "ip_rebind_count": app.IPRebindCount, - "ip_rebind_deduct": app.IPRebindDeduct, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": "获取绑定配置成功", + "data": gin.H{ + "machine_verify": app.MachineVerify, + "machine_rebind_enabled": app.MachineRebindEnabled, + "machine_rebind_limit": app.MachineRebindLimit, + "machine_free_count": app.MachineFreeCount, + "machine_rebind_count": app.MachineRebindCount, + "machine_rebind_deduct": app.MachineRebindDeduct, + "ip_verify": app.IPVerify, + "ip_rebind_enabled": app.IPRebindEnabled, + "ip_rebind_limit": app.IPRebindLimit, + "ip_free_count": app.IPFreeCount, + "ip_rebind_count": app.IPRebindCount, + "ip_rebind_deduct": app.IPRebindDeduct, + }, + }) } -// AppUpdateBindConfigHandler 更新应用绑定配置 -func AppUpdateBindConfigHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +// AppUpdateBindConfigHandler 更新应用绑定配置处理器 +func AppUpdateBindConfigHandler(c *gin.Context) { + // 解析请求体 var req struct { - UUID string `json:"uuid"` - MachineVerify int `json:"machine_verify"` - MachineRebindEnabled int `json:"machine_rebind_enabled"` - MachineRebindLimit int `json:"machine_rebind_limit"` - MachineFreeCount int `json:"machine_free_count"` - MachineRebindCount int `json:"machine_rebind_count"` - MachineRebindDeduct int `json:"machine_rebind_deduct"` - IPVerify int `json:"ip_verify"` - IPRebindEnabled int `json:"ip_rebind_enabled"` - IPRebindLimit int `json:"ip_rebind_limit"` - IPFreeCount int `json:"ip_free_count"` - IPRebindCount int `json:"ip_rebind_count"` - IPRebindDeduct int `json:"ip_rebind_deduct"` + UUID string `json:"uuid"` + MachineVerify int `json:"machine_verify"` + MachineRebindEnabled int `json:"machine_rebind_enabled"` + MachineRebindLimit int `json:"machine_rebind_limit"` + MachineFreeCount int `json:"machine_free_count"` + MachineRebindCount int `json:"machine_rebind_count"` + MachineRebindDeduct int `json:"machine_rebind_deduct"` + IPVerify int `json:"ip_verify"` + IPRebindEnabled int `json:"ip_rebind_enabled"` + IPRebindLimit int `json:"ip_rebind_limit"` + IPFreeCount int `json:"ip_free_count"` + IPRebindCount int `json:"ip_rebind_count"` + IPRebindDeduct int `json:"ip_rebind_deduct"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - logrus.WithError(err).Error("Failed to decode JSON request") - response := map[string]interface{}{ - "code": 1, - "msg": "无效的JSON格式", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + if !appBaseController.BindJSON(c, &req) { return } // 验证UUID if req.UUID == "" { - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "应用UUID不能为空", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } + // 验证UUID格式 if _, err := uuid.Parse(req.UUID); err != nil { - response := map[string]interface{}{ + logrus.WithError(err).Error("Invalid UUID format") + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "无效的UUID格式", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } - // 验证参数范围 - if req.MachineVerify < 0 || req.MachineVerify > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "机器验证参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.MachineRebindLimit < 0 || req.MachineRebindLimit > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "机器重绑限制参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.IPVerify < 0 || req.IPVerify > 3 { - response := map[string]interface{}{ - "code": 1, - "msg": "IP地址验证参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.IPRebindLimit < 0 || req.IPRebindLimit > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "IP地址重绑限制参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.MachineRebindEnabled < 0 || req.MachineRebindEnabled > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "机器重绑开关参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.IPRebindEnabled < 0 || req.IPRebindEnabled > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "IP地址重绑开关参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.MachineFreeCount < 0 || req.MachineRebindCount < 0 || req.MachineRebindDeduct < 0 || - req.IPFreeCount < 0 || req.IPRebindCount < 0 || req.IPRebindDeduct < 0 { - response := map[string]interface{}{ - "code": 1, - "msg": "数量参数不能为负数", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - response := map[string]interface{}{ - "code": 1, - "msg": "数据库连接失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { return } @@ -1279,126 +1007,105 @@ func AppUpdateBindConfigHandler(w http.ResponseWriter, r *http.Request) { var app models.App if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - response := map[string]interface{}{ + c.JSON(http.StatusNotFound, gin.H{ "code": 1, "msg": "应用不存在", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } // 更新绑定配置 updates := map[string]interface{}{ - "machine_verify": req.MachineVerify, - "machine_rebind_enabled": req.MachineRebindEnabled, - "machine_rebind_limit": req.MachineRebindLimit, - "machine_free_count": req.MachineFreeCount, - "machine_rebind_count": req.MachineRebindCount, - "machine_rebind_deduct": req.MachineRebindDeduct, - "ip_verify": req.IPVerify, - "ip_rebind_enabled": req.IPRebindEnabled, - "ip_rebind_limit": req.IPRebindLimit, - "ip_free_count": req.IPFreeCount, - "ip_rebind_count": req.IPRebindCount, - "ip_rebind_deduct": req.IPRebindDeduct, + "machine_verify": req.MachineVerify, + "machine_rebind_enabled": req.MachineRebindEnabled, + "machine_rebind_limit": req.MachineRebindLimit, + "machine_free_count": req.MachineFreeCount, + "machine_rebind_count": req.MachineRebindCount, + "machine_rebind_deduct": req.MachineRebindDeduct, + "ip_verify": req.IPVerify, + "ip_rebind_enabled": req.IPRebindEnabled, + "ip_rebind_limit": req.IPRebindLimit, + "ip_free_count": req.IPFreeCount, + "ip_rebind_count": req.IPRebindCount, + "ip_rebind_deduct": req.IPRebindDeduct, } if err := db.Model(&app).Updates(updates).Error; err != nil { logrus.WithError(err).Error("Failed to update app bind config") - response := map[string]interface{}{ + c.JSON(http.StatusInternalServerError, gin.H{ "code": 1, "msg": "更新绑定配置失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } - response := map[string]interface{}{ + logrus.WithFields(logrus.Fields{ + "app_uuid": req.UUID, + "app_name": app.Name, + }).Info("App bind config updated successfully") + + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "绑定配置更新成功", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) } -// AppGetRegisterConfigHandler 获取应用注册配置 -func AppGetRegisterConfigHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - appUUID := r.URL.Query().Get("uuid") +// AppGetRegisterConfigHandler 获取应用注册配置处理器 +func AppGetRegisterConfigHandler(c *gin.Context) { + appUUID := c.Query("uuid") if appUUID == "" { - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, - "msg": "缺少应用UUID", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + "msg": "应用UUID不能为空", + }) return } // 验证UUID格式 if _, err := uuid.Parse(appUUID); err != nil { - response := map[string]interface{}{ + logrus.WithError(err).Error("Invalid UUID format") + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "无效的UUID格式", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - response := map[string]interface{}{ - "code": 1, - "msg": "数据库连接失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { return } + // 查找应用 var app models.App if err := db.Where("uuid = ?", appUUID).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - response := map[string]interface{}{ + c.JSON(http.StatusNotFound, gin.H{ "code": 1, "msg": "应用不存在", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } - // 返回注册配置信息 - response := map[string]interface{}{ - "register_enabled": app.RegisterEnabled, - "register_limit_enabled": app.RegisterLimitEnabled, - "register_limit_time": app.RegisterLimitTime, - "register_count": app.RegisterCount, - "trial_enabled": app.TrialEnabled, - "trial_limit_time": app.TrialLimitTime, - "trial_duration": app.TrialDuration, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": "获取注册配置成功", + "data": gin.H{ + "register_enabled": app.RegisterEnabled, + "register_limit_enabled": app.RegisterLimitEnabled, + "register_limit_time": app.RegisterLimitTime, + "register_count": app.RegisterCount, + "trial_enabled": app.TrialEnabled, + "trial_limit_time": app.TrialLimitTime, + "trial_duration": app.TrialDuration, + }, + }) } -// AppUpdateRegisterConfigHandler 更新应用注册配置 -func AppUpdateRegisterConfigHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +// AppUpdateRegisterConfigHandler 更新应用注册配置处理器 +func AppUpdateRegisterConfigHandler(c *gin.Context) { + // 解析请求体 var req struct { UUID string `json:"uuid"` RegisterEnabled int `json:"register_enabled"` @@ -1410,118 +1117,32 @@ func AppUpdateRegisterConfigHandler(w http.ResponseWriter, r *http.Request) { TrialDuration int `json:"trial_duration"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - logrus.WithError(err).Error("Failed to decode JSON request") - response := map[string]interface{}{ - "code": 1, - "msg": "无效的JSON格式", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + if !appBaseController.BindJSON(c, &req) { return } // 验证UUID if req.UUID == "" { - response := map[string]interface{}{ + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "应用UUID不能为空", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } + // 验证UUID格式 if _, err := uuid.Parse(req.UUID); err != nil { - response := map[string]interface{}{ + logrus.WithError(err).Error("Invalid UUID format") + c.JSON(http.StatusBadRequest, gin.H{ "code": 1, "msg": "无效的UUID格式", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } - // 验证参数范围 - if req.RegisterEnabled < 0 || req.RegisterEnabled > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "账号注册开关参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.RegisterLimitEnabled < 0 || req.RegisterLimitEnabled > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "注册限制开关参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.RegisterLimitTime < 0 || req.RegisterLimitTime > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "注册限制时间参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.RegisterCount < 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "注册次数必须大于0", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.TrialEnabled < 0 || req.TrialEnabled > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "领取试用开关参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.TrialLimitTime < 0 || req.TrialLimitTime > 1 { - response := map[string]interface{}{ - "code": 1, - "msg": "试用限制时间参数无效", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - if req.TrialDuration < 0 { - response := map[string]interface{}{ - "code": 1, - "msg": "试用时间不能为负数", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) - return - } - - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - response := map[string]interface{}{ - "code": 1, - "msg": "数据库连接失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { return } @@ -1529,12 +1150,10 @@ func AppUpdateRegisterConfigHandler(w http.ResponseWriter, r *http.Request) { var app models.App if err := db.Where("uuid = ?", req.UUID).First(&app).Error; err != nil { logrus.WithError(err).Error("Failed to find app") - response := map[string]interface{}{ + c.JSON(http.StatusNotFound, gin.H{ "code": 1, "msg": "应用不存在", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } @@ -1551,20 +1170,189 @@ func AppUpdateRegisterConfigHandler(w http.ResponseWriter, r *http.Request) { if err := db.Model(&app).Updates(updates).Error; err != nil { logrus.WithError(err).Error("Failed to update app register config") - response := map[string]interface{}{ + c.JSON(http.StatusInternalServerError, gin.H{ "code": 1, "msg": "更新注册配置失败", - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + }) return } - response := map[string]interface{}{ + logrus.WithFields(logrus.Fields{ + "app_uuid": req.UUID, + "app_name": app.Name, + }).Info("App register config updated successfully") + + c.JSON(http.StatusOK, gin.H{ "code": 0, "msg": "注册配置更新成功", + }) +} + +// AppsBatchDeleteHandler 批量删除应用处理器 +func AppsBatchDeleteHandler(c *gin.Context) { + var req struct { + IDs []uint `json:"ids"` } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + if !appBaseController.BindJSON(c, &req) { + return + } + + if len(req.IDs) == 0 { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "请选择要删除的应用", + }) + return + } + + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { + return + } + + // 开始事务 + tx := db.Begin() + if tx.Error != nil { + logrus.WithError(tx.Error).Error("Failed to begin transaction") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "开始事务失败", + }) + return + } + + defer func() { + if r := recover(); r != nil { + tx.Rollback() + } + }() + + // 首先获取要删除的应用的UUID列表 + var apps []models.App + if err := tx.Where("id IN ?", req.IDs).Find(&apps).Error; err != nil { + tx.Rollback() + logrus.WithError(err).Error("Failed to find apps") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "查找应用失败", + }) + return + } + + // 提取UUID列表 + var appUUIDs []string + for _, app := range apps { + appUUIDs = append(appUUIDs, app.UUID) + } + + // 删除这些应用的所有相关接口 + if len(appUUIDs) > 0 { + if err := tx.Where("app_uuid IN ?", appUUIDs).Delete(&models.API{}).Error; err != nil { + tx.Rollback() + logrus.WithError(err).Error("Failed to delete related APIs") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "删除相关接口失败", + }) + return + } + + // 删除这些应用的所有相关变量 + if err := tx.Where("app_uuid IN ?", appUUIDs).Delete(&models.Variable{}).Error; err != nil { + tx.Rollback() + logrus.WithError(err).Error("Failed to delete related variables") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "删除相关变量失败", + }) + return + } + } + + // 批量删除应用 + if err := tx.Delete(&models.App{}, req.IDs).Error; err != nil { + tx.Rollback() + logrus.WithError(err).Error("Failed to batch delete apps") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "批量删除失败", + }) + return + } + + // 提交事务 + if err := tx.Commit().Error; err != nil { + logrus.WithError(err).Error("Failed to commit transaction") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "提交事务失败", + }) + return + } + + logrus.WithFields(logrus.Fields{ + "app_ids": req.IDs, + "app_uuids": appUUIDs, + }).Info("Successfully batch deleted apps and related data (APIs and variables)") + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": "批量删除成功", + }) +} + +// AppsBatchUpdateStatusHandler 批量更新应用状态处理器 +func AppsBatchUpdateStatusHandler(c *gin.Context) { + var req struct { + IDs []uint `json:"ids"` + Status int `json:"status"` + } + + if !appBaseController.BindJSON(c, &req) { + return + } + + if len(req.IDs) == 0 { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "请选择要更新的应用", + }) + return + } + + if req.Status != 0 && req.Status != 1 { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "状态值无效", + }) + return + } + + // 获取数据库连接 + db, ok := appBaseController.GetDB(c) + if !ok { + return + } + + // 批量更新状态 + if err := db.Model(&models.App{}).Where("id IN ?", req.IDs).Update("status", req.Status).Error; err != nil { + logrus.WithError(err).Error("Failed to batch update app status") + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "批量更新状态失败", + }) + return + } + + statusText := "禁用" + if req.Status == 1 { + statusText = "启用" + } + + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": "批量" + statusText + "成功", + }) } diff --git a/controllers/admin/auth.go b/controllers/admin/auth.go index 4444712..fd8225c 100644 --- a/controllers/admin/auth.go +++ b/controllers/admin/auth.go @@ -1,52 +1,58 @@ package admin import ( - "encoding/json" "fmt" "net/http" "strings" "time" + "networkDev/controllers" "networkDev/database" "networkDev/models" "networkDev/utils" + "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/spf13/viper" ) +// 创建BaseController实例 +var authBaseController = controllers.NewBaseController() + // LoginPageHandler 管理员登录页渲染处理器 // - 如果已登录则重定向到 /admin // - 否则渲染 web/template/admin/login.html 模板 // - 自动清理失效的JWT Cookie,避免刷新时的问题 -func LoginPageHandler(w http.ResponseWriter, r *http.Request) { +func LoginPageHandler(c *gin.Context) { // 使用带清理功能的JWT校验,避免失效Cookie在登录页面造成问题 - if IsAdminAuthenticatedWithCleanup(w, r) { - http.Redirect(w, r, "/admin", http.StatusFound) + if IsAdminAuthenticatedWithCleanup(c) { + c.Redirect(http.StatusFound, "/admin") return } // 获取或生成CSRF令牌 var token string - if existingToken := utils.GetCSRFTokenFromCookie(r); existingToken != "" { + if existingToken := utils.GetCSRFTokenFromCookie(c); existingToken != "" { // 重用现有的Cookie令牌 token = existingToken } else { // 生成新的CSRF令牌并设置到Cookie newToken, err := utils.GenerateCSRFToken() if err != nil { - http.Error(w, "生成CSRF令牌失败", http.StatusInternalServerError) + c.HTML(http.StatusInternalServerError, "error.html", gin.H{ + "Error": "生成CSRF令牌失败", + }) return } token = newToken - utils.SetCSRFToken(w, token) + utils.SetCSRFToken(c, token) } // 准备模板数据 - extraData := map[string]interface{}{ + extraData := gin.H{ "Title": "管理员登录", } - data := utils.GetDefaultTemplateData() + data := authBaseController.GetDefaultTemplateData() data["CSRFToken"] = token // 合并额外数据 @@ -54,7 +60,7 @@ func LoginPageHandler(w http.ResponseWriter, r *http.Request) { data[key] = value } - utils.RenderTemplate(w, "login.html", data) + c.HTML(http.StatusOK, "login.html", data) } // LoginHandler 管理员登录接口 @@ -62,46 +68,41 @@ func LoginPageHandler(w http.ResponseWriter, r *http.Request) { // - 验证用户存在与密码正确性 // - 仅允许 Role=0 的管理员登录 // - 成功后设置简单的会话Cookie(后续可切换为JWT或更完善的Session) -func LoginHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - +func LoginHandler(c *gin.Context) { var body struct { Username string `json:"username"` Password string `json:"password"` Captcha string `json:"captcha"` } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求参数错误", nil) + + if !authBaseController.BindJSON(c, &body) { return } - if body.Username == "" || body.Password == "" { - utils.JsonResponse(w, http.StatusBadRequest, false, "用户名和密码不能为空", nil) - return - } - if body.Captcha == "" { - utils.JsonResponse(w, http.StatusBadRequest, false, "验证码不能为空", nil) + + if !authBaseController.ValidateRequired(c, map[string]interface{}{ + "用户名": body.Username, + "密码": body.Password, + "验证码": body.Captcha, + }) { return } // 验证验证码 - if !VerifyCaptcha(r, body.Captcha) { - utils.JsonResponse(w, http.StatusBadRequest, false, "验证码错误", nil) + if !VerifyCaptcha(c, body.Captcha) { + authBaseController.HandleValidationError(c, "验证码错误") return } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) + // 获取数据库连接 + db, ok := authBaseController.GetDB(c) + if !ok { return } // 通过前缀匹配一次性获取所有管理员相关设置 var adminSettings []models.Settings - if err = db.Where("name LIKE ?", "admin_%").Find(&adminSettings).Error; err != nil { - utils.JsonResponse(w, http.StatusUnauthorized, false, "用户不存在或密码错误", nil) + if err := db.Where("name LIKE ?", "admin_%").Find(&adminSettings).Error; err != nil { + authBaseController.HandleValidationError(c, "用户不存在或密码错误") return } @@ -117,25 +118,25 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { adminPasswordSalt, hasSalt := settingsMap["admin_password_salt"] if !hasUsername || !hasPassword || !hasSalt { - utils.JsonResponse(w, http.StatusUnauthorized, false, "用户不存在或密码错误", nil) + authBaseController.HandleValidationError(c, "用户不存在或密码错误") return } // 验证用户名 if body.Username != adminUsername { - utils.JsonResponse(w, http.StatusUnauthorized, false, "用户不存在或密码错误", nil) + authBaseController.HandleValidationError(c, "用户不存在或密码错误") return } // 验证密码为空的情况(首次登录需要初始化) if adminPassword == "" || adminPasswordSalt == "" { - utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员账号未初始化,请联系系统管理员", nil) + authBaseController.HandleInternalError(c, "管理员账号未初始化,请联系系统管理员", nil) return } // 使用盐值验证密码 if !utils.VerifyPasswordWithSalt(body.Password, adminPasswordSalt, adminPassword) { - utils.JsonResponse(w, http.StatusUnauthorized, false, "用户不存在或密码错误", nil) + authBaseController.HandleValidationError(c, "用户不存在或密码错误") return } @@ -149,30 +150,30 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) { // 生成JWT令牌 token, err := generateJWTTokenForAdmin(adminUser) if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "生成令牌失败", nil) + authBaseController.HandleInternalError(c, "生成令牌失败", err) return } // 设置JWT Cookie(使用安全配置) cookie := utils.CreateSecureCookie("admin_session", token, utils.GetDefaultCookieMaxAge()) - http.SetCookie(w, cookie) + c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly) - utils.JsonResponse(w, http.StatusOK, true, "登录成功", map[string]interface{}{ + authBaseController.HandleSuccess(c, "登录成功", gin.H{ "redirect": "/admin", }) } // LogoutHandler 管理员登出 -// - 清理JWT Cookie会话 +// - 清理JWT Cookie // - 确保令牌完全失效 -func LogoutHandler(w http.ResponseWriter, r *http.Request) { +func LogoutHandler(c *gin.Context) { // 清理JWT Cookie - clearInvalidJWTCookie(w) + clearInvalidJWTCookie(c) // 可选:将JWT令牌加入黑名单(需要Redis或数据库支持) // 这里可以实现JWT黑名单机制 - utils.JsonResponse(w, http.StatusOK, true, "已退出登录", map[string]interface{}{ + authBaseController.HandleSuccess(c, "已退出登录", gin.H{ "redirect": "/admin/login", }) } @@ -180,9 +181,9 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) { // clearInvalidJWTCookie 清理失效的JWT Cookie // - 统一的Cookie清理函数,确保一致性 // - 在JWT校验失败时自动调用,提升安全性和用户体验 -func clearInvalidJWTCookie(w http.ResponseWriter) { +func clearInvalidJWTCookie(c *gin.Context) { cookie := utils.CreateExpiredCookie("admin_session") - http.SetCookie(w, cookie) + c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly) } // getJWTSecret 动态获取当前的JWT密钥 @@ -246,18 +247,18 @@ func parseJWTToken(tokenString string) (*JWTClaims, error) { } // getJWTCookie 获取JWT cookie的通用函数 -func getJWTCookie(r *http.Request) (*http.Cookie, error) { - return r.Cookie("admin_session") +func getJWTCookie(c *gin.Context) (string, error) { + return c.Cookie("admin_session") } // validateAdminPasswordHash 验证管理员密码哈希的通用函数 -func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool { +func validateAdminPasswordHash(claims *JWTClaims, c *gin.Context) bool { // 【安全修复】验证数据库中的当前密码哈希 // 这确保了密码修改后,旧的JWT令牌会失效 db, err := database.GetDB() if err != nil { fmt.Printf("[SECURITY WARNING] Database connection failed during auth - Username=%s, IP=%s\n", - claims.Username, r.RemoteAddr) + claims.Username, c.ClientIP()) return false } @@ -265,7 +266,7 @@ func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool { var adminPassword models.Settings if err := db.Where("name = ?", "admin_password").First(&adminPassword).Error; err != nil { fmt.Printf("[SECURITY WARNING] Admin password not found in database - Username=%s, IP=%s\n", - claims.Username, r.RemoteAddr) + claims.Username, c.ClientIP()) return false } @@ -275,7 +276,7 @@ func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool { // 验证JWT中的密码哈希是否与当前数据库中的密码哈希一致 if claims.PasswordHash != currentPasswordHash { fmt.Printf("[SECURITY WARNING] Password hash mismatch - JWT token invalidated - Username=%s, IP=%s\n", - claims.Username, r.RemoteAddr) + claims.Username, c.ClientIP()) return false } @@ -285,14 +286,14 @@ func validateAdminPasswordHash(claims *JWTClaims, r *http.Request) bool { // IsAdminAuthenticated 判断管理员是否已认证(导出) // - 检查admin_session Cookie中的JWT令牌 // - 验证令牌签名、过期时间和用户角色 -func IsAdminAuthenticated(r *http.Request) bool { - cookie, err := getJWTCookie(r) - if err != nil || cookie.Value == "" { +func IsAdminAuthenticated(c *gin.Context) bool { + cookie, err := getJWTCookie(c) + if err != nil || cookie == "" { return false } // 解析并验证JWT令牌 - claims, err := parseJWTToken(cookie.Value) + claims, err := parseJWTToken(cookie) if err != nil { return false } @@ -300,31 +301,31 @@ func IsAdminAuthenticated(r *http.Request) bool { // 注释:由于这是管理员专用认证函数,不需要额外的角色验证 // 验证密码哈希 - return validateAdminPasswordHash(claims, r) + return validateAdminPasswordHash(claims, c) } // IsAdminAuthenticatedWithCleanup 带自动清理功能的JWT校验函数 // - 当JWT校验失败时,自动清理失效的Cookie // - 适用于API接口等需要清理失效令牌的场景 -func IsAdminAuthenticatedWithCleanup(w http.ResponseWriter, r *http.Request) bool { - cookie, err := getJWTCookie(r) - if err != nil || cookie.Value == "" { +func IsAdminAuthenticatedWithCleanup(c *gin.Context) bool { + cookie, err := getJWTCookie(c) + if err != nil || cookie == "" { return false } // 解析并验证JWT令牌 - claims, err := parseJWTToken(cookie.Value) + claims, err := parseJWTToken(cookie) if err != nil { // JWT解析失败,清理失效Cookie - clearInvalidJWTCookie(w) + clearInvalidJWTCookie(c) return false } // 注释:由于这是管理员专用认证函数,不需要额外的角色验证 // 验证密码哈希 - if !validateAdminPasswordHash(claims, r) { - clearInvalidJWTCookie(w) + if !validateAdminPasswordHash(claims, c) { + clearInvalidJWTCookie(c) return false } @@ -335,13 +336,13 @@ func IsAdminAuthenticatedWithCleanup(w http.ResponseWriter, r *http.Request) boo // - 从JWT令牌中提取用户信息 // - 自动刷新接近过期的令牌(剩余时间少于6小时时刷新) // - 返回用户ID、用户名和角色 -func GetCurrentAdminUser(r *http.Request) (*JWTClaims, error) { - cookie, err := getJWTCookie(r) +func GetCurrentAdminUser(c *gin.Context) (*JWTClaims, error) { + cookie, err := getJWTCookie(c) if err != nil { return nil, fmt.Errorf("未找到会话信息") } - claims, err := parseJWTToken(cookie.Value) + claims, err := parseJWTToken(cookie) if err != nil { return nil, fmt.Errorf("无效的会话信息") } @@ -355,40 +356,34 @@ func GetCurrentAdminUser(r *http.Request) (*JWTClaims, error) { // - 从JWT令牌中提取用户信息 // - 自动刷新接近过期的令牌(剩余时间少于6小时时刷新) // - 返回用户ID、用户名、角色和是否刷新了令牌 -func GetCurrentAdminUserWithRefresh(w http.ResponseWriter, r *http.Request) (*JWTClaims, bool, error) { - cookie, err := getJWTCookie(r) +func GetCurrentAdminUserWithRefresh(c *gin.Context) (*JWTClaims, bool, error) { + cookie, err := getJWTCookie(c) if err != nil { return nil, false, fmt.Errorf("未找到会话信息") } - claims, err := parseJWTToken(cookie.Value) + claims, err := parseJWTToken(cookie) if err != nil { return nil, false, fmt.Errorf("无效的会话信息") } - // 注释:由于这是管理员专用函数,不需要额外的角色验证 - // 验证密码哈希 - if !validateAdminPasswordHash(claims, r) { + if !validateAdminPasswordHash(claims, c) { return nil, false, fmt.Errorf("会话已失效,请重新登录") } - // 检查是否需要刷新令牌(根据配置的阈值) + // 检查是否需要刷新令牌 refreshed := false refreshThreshold := time.Duration(viper.GetInt("security.jwt_refresh")) * time.Hour if time.Until(claims.ExpiresAt.Time) < refreshThreshold { - // 为管理员生成新的JWT令牌 adminUser := models.User{ Username: claims.Username, } newToken, err := generateJWTTokenForAdmin(adminUser) if err == nil { - // 更新Cookie(使用安全配置) - newCookie := utils.CreateSecureCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge()) - http.SetCookie(w, newCookie) + c.SetCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge(), "/", "", false, true) refreshed = true - // 更新claims的过期时间 claims.ExpiresAt = jwt.NewNumericDate(time.Now().Add(24 * time.Hour)) claims.IssuedAt = jwt.NewNumericDate(time.Now()) } @@ -400,24 +395,30 @@ func GetCurrentAdminUserWithRefresh(w http.ResponseWriter, r *http.Request) (*JW // AdminAuthRequired 管理员认证拦截中间件 // - 未登录:重定向到 /admin/login // - 已登录:自动刷新接近过期的令牌,然后放行到后续处理器 -func AdminAuthRequired(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func AdminAuthRequired() gin.HandlerFunc { + return func(c *gin.Context) { // 尝试获取用户信息并自动刷新令牌 - claims, refreshed, err := GetCurrentAdminUserWithRefresh(w, r) + claims, refreshed, err := GetCurrentAdminUserWithRefresh(c) if err != nil { // 自动清理失效的JWT Cookie,提升安全性和用户体验 - clearInvalidJWTCookie(w) + clearInvalidJWTCookie(c) // 中文注释:区分普通页面请求与AJAX/JSON请求 // - 对 AJAX/JSON:直接返回 401 JSON,便于前端处理(如提示重新登录) // - 对普通页面:保持原有重定向到登录页 - accept := r.Header.Get("Accept") - xrw := strings.ToLower(strings.TrimSpace(r.Header.Get("X-Requested-With"))) + accept := c.GetHeader("Accept") + xrw := strings.ToLower(strings.TrimSpace(c.GetHeader("X-Requested-With"))) if strings.Contains(accept, "application/json") || xrw == "xmlhttprequest" { - utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil) + c.JSON(http.StatusUnauthorized, gin.H{ + "success": false, + "message": "未登录或会话已过期", + "data": nil, + }) + c.Abort() return } - http.Redirect(w, r, "/admin/login", http.StatusFound) + c.Redirect(http.StatusFound, "/admin/login") + c.Abort() return } @@ -427,6 +428,6 @@ func AdminAuthRequired(next http.HandlerFunc) http.HandlerFunc { _ = claims // 避免未使用变量警告 } - next(w, r) + c.Next() } } diff --git a/controllers/admin/captcha.go b/controllers/admin/captcha.go index 56d65c1..1ef69cd 100644 --- a/controllers/admin/captcha.go +++ b/controllers/admin/captcha.go @@ -3,17 +3,21 @@ package admin import ( "crypto/rand" "encoding/base64" - "encoding/json" "math/big" "net/http" "strings" + "github.com/gin-gonic/gin" + "networkDev/controllers" "networkDev/utils" "github.com/mojocn/base64Captcha" "github.com/spf13/viper" ) +// 创建基础控制器实例 +var captchaBaseController = controllers.NewBaseController() + // 全局验证码存储器 var store = base64Captcha.DefaultMemStore @@ -28,17 +32,12 @@ func secureRandomInt(max int) (int, error) { // CaptchaHandler 生成验证码图片 // GET /admin/captcha - 返回验证码图片 -func CaptchaHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - +func CaptchaHandler(c *gin.Context) { // 随机生成4-6位长度 // 使用crypto/rand生成安全的随机数 randomNum, err := secureRandomInt(3) if err != nil { - http.Error(w, "生成随机数失败", http.StatusInternalServerError) + captchaBaseController.HandleInternalError(c, "生成随机数失败", err) return } captchaLength := 4 + randomNum // 4-6位随机长度 @@ -58,20 +57,20 @@ func CaptchaHandler(w http.ResponseWriter, r *http.Request) { captcha := base64Captcha.NewCaptcha(&driver, store) id, b64s, _, err := captcha.Generate() if err != nil { - http.Error(w, "生成验证码失败", http.StatusInternalServerError) + captchaBaseController.HandleInternalError(c, "生成验证码失败", err) return } // 将验证码ID存储到session中(这里简化处理,实际项目中应该使用更安全的方式) // 设置cookie来存储验证码ID cookie := utils.CreateSecureCookie("captcha_id", id, 300) // 5分钟过期 - http.SetCookie(w, cookie) + c.SetCookie(cookie.Name, cookie.Value, cookie.MaxAge, cookie.Path, cookie.Domain, cookie.Secure, cookie.HttpOnly) // 解码base64图片数据并返回 - w.Header().Set("Content-Type", "image/png") - w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") - w.Header().Set("Pragma", "no-cache") - w.Header().Set("Expires", "0") + c.Header("Content-Type", "image/png") + c.Header("Cache-Control", "no-cache, no-store, must-revalidate") + c.Header("Pragma", "no-cache") + c.Header("Expires", "0") // 直接返回base64编码的图片数据,让浏览器解析 // 但是我们需要返回实际的图片数据,所以需要解码base64 @@ -81,29 +80,30 @@ func CaptchaHandler(w http.ResponseWriter, r *http.Request) { imgData, err := base64.StdEncoding.DecodeString(b64s) if err != nil { - http.Error(w, "解码验证码图片失败", http.StatusInternalServerError) + captchaBaseController.HandleInternalError(c, "解码验证码图片失败", err) return } - w.Write(imgData) + c.Data(http.StatusOK, "image/png", imgData) } + + // VerifyCaptcha 验证验证码 // 这个函数将在登录处理中被调用 // 支持大小写不敏感匹配 -func VerifyCaptcha(r *http.Request, captchaValue string) bool { +func VerifyCaptcha(c *gin.Context, captchaValue string) bool { // 检查是否为开发模式,如果是则跳过验证码验证 if viper.GetBool("server.dev_mode") { return true } // 从cookie中获取验证码ID - cookie, err := r.Cookie("captcha_id") + captchaId, err := c.Cookie("captcha_id") if err != nil { return false } - captchaId := cookie.Value if captchaId == "" { return false } @@ -132,36 +132,19 @@ func VerifyCaptcha(r *http.Request, captchaValue string) bool { // CaptchaAPIHandler 验证码API接口(可选,用于AJAX验证) // POST /admin/api/captcha/verify - 验证验证码 -func CaptchaAPIHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - +func CaptchaAPIHandler(c *gin.Context) { var body struct { Captcha string `json:"captcha"` } - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]interface{}{ - "code": 1, - "msg": "请求参数错误", - }) + if !captchaBaseController.BindJSON(c, &body) { return } - isValid := VerifyCaptcha(r, body.Captcha) + isValid := VerifyCaptcha(c, body.Captcha) - w.Header().Set("Content-Type", "application/json") if isValid { - json.NewEncoder(w).Encode(map[string]interface{}{ - "code": 0, - "msg": "验证码正确", - }) + captchaBaseController.HandleSuccess(c, "验证码正确", nil) } else { - json.NewEncoder(w).Encode(map[string]interface{}{ - "code": 1, - "msg": "验证码错误", - }) + captchaBaseController.HandleValidationError(c, "验证码错误") } } diff --git a/controllers/admin/handlers.go b/controllers/admin/handlers.go index 163c7a0..e7c2475 100644 --- a/controllers/admin/handlers.go +++ b/controllers/admin/handlers.go @@ -2,15 +2,20 @@ package admin import ( "net/http" - "networkDev/database" + "networkDev/constants" + "networkDev/controllers" "networkDev/models" "networkDev/services" "networkDev/utils" "networkDev/utils/timeutil" + "github.com/gin-gonic/gin" "github.com/spf13/viper" ) +// 创建基础控制器实例 +var handlersBaseController = controllers.NewBaseController() + // formatDBType 格式化数据库类型显示 // 将配置文件中的小写类型转换为友好的显示格式 func formatDBType(dbType string) string { @@ -32,40 +37,40 @@ func formatDBType(dbType string) string { // - 未登录:重定向到 /admin/login // - 已登录:渲染后台布局页(或重定向到 /admin/layout) // - 自动清理失效的JWT Cookie -func AdminIndexHandler(w http.ResponseWriter, r *http.Request) { - if IsAdminAuthenticatedWithCleanup(w, r) { +func AdminIndexHandler(c *gin.Context) { + if IsAdminAuthenticatedWithCleanup(c) { // 直接渲染布局页,保持URL为 /admin - AdminLayoutHandler(w, r) + AdminLayoutHandler(c) return } - http.Redirect(w, r, "/admin/login", http.StatusFound) + c.Redirect(http.StatusFound, "/admin/login") } // AdminLayoutHandler 后台布局页渲染 // - 渲染 layout.html,包含顶部导航、侧边栏与动态内容容器 -func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) { +func AdminLayoutHandler(c *gin.Context) { // 获取或生成CSRF令牌 var token string - if existingToken := utils.GetCSRFTokenFromCookie(r); existingToken != "" { + if existingToken := utils.GetCSRFTokenFromCookie(c); existingToken != "" { // 重用现有的Cookie令牌 token = existingToken } else { // 生成新的CSRF令牌并设置到Cookie newToken, err := utils.GenerateCSRFToken() if err != nil { - http.Error(w, "生成CSRF令牌失败", http.StatusInternalServerError) + handlersBaseController.HandleInternalError(c, "生成CSRF令牌失败", err) return } token = newToken - utils.SetCSRFToken(w, token) + utils.SetCSRFToken(c, token) } // 准备额外的模板数据 - extraData := make(map[string]interface{}) + extraData := gin.H{} // 从数据库读取站点标题 - db, dbErr := database.GetDB() - if dbErr != nil { + db, ok := handlersBaseController.GetDB(c) + if !ok { extraData["Title"] = "凌动技术" } else { siteTitle, settingErr := services.FindSettingByName("site_title", db) @@ -77,7 +82,7 @@ func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) { } // 准备模板数据 - data := utils.GetDefaultTemplateData() + data := handlersBaseController.GetDefaultTemplateData() data["CSRFToken"] = token // 合并额外数据 @@ -85,13 +90,13 @@ func AdminLayoutHandler(w http.ResponseWriter, r *http.Request) { data[key] = value } - utils.RenderTemplate(w, "layout.html", data) + c.HTML(http.StatusOK, "layout.html", data) } // DashboardFragmentHandler 仪表盘片段渲染 // - 展示系统信息:版本、开发模式、数据库类型、启动时长 -func DashboardFragmentHandler(w http.ResponseWriter, r *http.Request) { - version := "1.0.0" +func DashboardFragmentHandler(c *gin.Context) { + version := constants.AppVersion mode := viper.GetBool("server.dev_mode") dbType := viper.GetString("database.type") if dbType == "" { @@ -99,25 +104,20 @@ func DashboardFragmentHandler(w http.ResponseWriter, r *http.Request) { } uptime := timeutil.GetServerUptimeString() - data := map[string]interface{}{ + data := gin.H{ "Version": version, "Mode": mode, "DBType": formatDBType(dbType), "Uptime": uptime, } - utils.RenderTemplate(w, "dashboard.html", data) + c.HTML(http.StatusOK, "dashboard.html", data) } // SystemInfoHandler 系统信息API接口 // - 返回系统运行状态的JSON数据,用于前端定时刷新 -func SystemInfoHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - version := "1.0.0" +func SystemInfoHandler(c *gin.Context) { + version := constants.AppVersion mode := viper.GetBool("server.dev_mode") dbType := viper.GetString("database.type") if dbType == "" { @@ -125,28 +125,22 @@ func SystemInfoHandler(w http.ResponseWriter, r *http.Request) { } uptime := timeutil.GetServerUptimeString() - data := map[string]interface{}{ + data := gin.H{ "version": version, "mode": mode, "db_type": formatDBType(dbType), "uptime": uptime, } - utils.JsonResponse(w, http.StatusOK, true, "ok", data) + handlersBaseController.HandleSuccess(c, "ok", data) } // DashboardStatsHandler 仪表盘统计数据API接口 // - 返回应用统计数据的JSON数据,包括全部/启用/禁用/变量数量 -func DashboardStatsHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - +func DashboardStatsHandler(c *gin.Context) { // 获取数据库连接 - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) + db, ok := handlersBaseController.GetDB(c) + if !ok { return } @@ -158,34 +152,34 @@ func DashboardStatsHandler(w http.ResponseWriter, r *http.Request) { // 统计全部应用数量 if err := db.Model(&models.App{}).Count(&totalApps).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "统计应用数量失败", nil) + handlersBaseController.HandleInternalError(c, "统计应用数量失败", err) return } // 统计启用应用数量 if err := db.Model(&models.App{}).Where("status = ?", 1).Count(&enabledApps).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "统计启用应用数量失败", nil) + handlersBaseController.HandleInternalError(c, "统计启用应用数量失败", err) return } // 统计禁用应用数量 if err := db.Model(&models.App{}).Where("status = ?", 0).Count(&disabledApps).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "统计禁用应用数量失败", nil) + handlersBaseController.HandleInternalError(c, "统计禁用应用数量失败", err) return } // 统计变量数量 if err := db.Model(&models.Variable{}).Count(&totalVariables).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "统计变量数量失败", nil) + handlersBaseController.HandleInternalError(c, "统计变量数量失败", err) return } - data := map[string]interface{}{ + data := gin.H{ "total_apps": totalApps, "enabled_apps": enabledApps, "disabled_apps": disabledApps, "total_variables": totalVariables, } - utils.JsonResponse(w, http.StatusOK, true, "ok", data) + handlersBaseController.HandleSuccess(c, "ok", data) } diff --git a/controllers/admin/settings.go b/controllers/admin/settings.go index 8282b59..0f5feb0 100644 --- a/controllers/admin/settings.go +++ b/controllers/admin/settings.go @@ -1,49 +1,43 @@ package admin import ( - "encoding/json" - "fmt" - "net/http" - "networkDev/database" - "networkDev/models" - "networkDev/utils" - - // 新增:用于刷新内存缓存 - "networkDev/services" - // 新增:用于RedisDel上下文 "context" - + "fmt" + "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" + "net/http" + "networkDev/controllers" + "networkDev/models" + "networkDev/services" + "networkDev/utils" ) +// 创建基础控制器实例 +var settingsBaseController = controllers.NewBaseController() + // SettingsFragmentHandler 设置片段渲染 // - 渲染设置表单(通过前端JS调用API加载/保存) -func SettingsFragmentHandler(w http.ResponseWriter, r *http.Request) { - utils.RenderTemplate(w, "settings.html", map[string]interface{}{}) +func SettingsFragmentHandler(c *gin.Context) { + c.HTML(http.StatusOK, "settings.html", gin.H{}) } // SettingsQueryHandler 设置查询API // - 返回所有设置项的 name:value 映射 -func SettingsQueryHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) +func SettingsQueryHandler(c *gin.Context) { + db, ok := settingsBaseController.GetDB(c) + if !ok { return } var list []models.Settings if err := db.Find(&list).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "查询失败", nil) + settingsBaseController.HandleInternalError(c, "查询失败", err) return } res := map[string]string{} for _, s := range list { res[s.Name] = s.Value } - utils.JsonResponse(w, http.StatusOK, true, "ok", res) + settingsBaseController.HandleSuccess(c, "ok", res) } // SettingsUpdateHandler 更新系统设置处理器 @@ -56,16 +50,10 @@ func SettingsQueryHandler(w http.ResponseWriter, r *http.Request) { // - 更新完成后: // 1. 删除对应的Redis缓存键,确保后续读取走数据库并重建缓存 // 2. 刷新SettingsService内存缓存 -func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - +func SettingsUpdateHandler(c *gin.Context) { // 先尝试解析为直接字段格式 var directBody map[string]interface{} - if err := json.NewDecoder(r.Body).Decode(&directBody); err != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求体错误", nil) + if !settingsBaseController.BindJSON(c, &directBody) { return } @@ -82,7 +70,7 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) { } } } else { - utils.JsonResponse(w, http.StatusBadRequest, false, "settings字段格式错误", nil) + settingsBaseController.HandleValidationError(c, "settings字段格式错误") return } } else { @@ -99,13 +87,12 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) { } if len(settingsData) == 0 { - utils.JsonResponse(w, http.StatusBadRequest, false, "无设置项", nil) + settingsBaseController.HandleValidationError(c, "无设置项") return } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) + db, ok := settingsBaseController.GetDB(c) + if !ok { return } @@ -120,7 +107,7 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) { s = models.Settings{Name: k, Value: v} if err := db.Create(&s).Error; err != nil { logrus.WithError(err).WithField("setting_name", k).Error("创建设置失败") - utils.JsonResponse(w, http.StatusInternalServerError, false, fmt.Sprintf("保存设置 %s 失败", k), nil) + settingsBaseController.HandleInternalError(c, fmt.Sprintf("保存设置 %s 失败", k), err) return } @@ -128,7 +115,7 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) { // 存在则更新 if err := db.Model(&models.Settings{}).Where("id = ?", s.ID).Update("value", v).Error; err != nil { logrus.WithError(err).WithField("setting_name", k).Error("更新设置失败") - utils.JsonResponse(w, http.StatusInternalServerError, false, fmt.Sprintf("更新设置 %s 失败", k), nil) + settingsBaseController.HandleInternalError(c, fmt.Sprintf("更新设置 %s 失败", k), err) return } @@ -143,5 +130,5 @@ func SettingsUpdateHandler(w http.ResponseWriter, r *http.Request) { // 刷新内存中的设置缓存,保证后续读取一致 services.GetSettingsService().RefreshCache() - utils.JsonResponse(w, http.StatusOK, true, "保存成功", nil) + settingsBaseController.HandleSuccess(c, "保存成功", nil) } diff --git a/controllers/admin/user.go b/controllers/admin/user.go index d6387a9..16cfb37 100644 --- a/controllers/admin/user.go +++ b/controllers/admin/user.go @@ -1,36 +1,35 @@ package admin import ( - "encoding/json" "net/http" - "networkDev/database" + "networkDev/controllers" "networkDev/models" "networkDev/utils" "strings" + + "github.com/gin-gonic/gin" ) +// 创建基础控制器实例 +var baseController = controllers.NewBaseController() + // UserFragmentHandler 个人资料片段渲染 // - 渲染个人资料与修改密码表单 -func UserFragmentHandler(w http.ResponseWriter, r *http.Request) { - utils.RenderTemplate(w, "user.html", map[string]interface{}{}) +func UserFragmentHandler(c *gin.Context) { + c.HTML(http.StatusOK, "user.html", gin.H{}) } // UserProfileQueryHandler 获取当前登录管理员的用户名 // - 返回 JSON: {username} // - 直接从JWT获取用户名信息 -func UserProfileQueryHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - claims, _, err := GetCurrentAdminUserWithRefresh(w, r) +func UserProfileQueryHandler(c *gin.Context) { + claims, _, err := GetCurrentAdminUserWithRefresh(c) if err != nil { - utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil) + baseController.HandleValidationError(c, "未登录或会话已过期") return } - utils.JsonResponse(w, http.StatusOK, true, "ok", map[string]interface{}{ + baseController.HandleSuccess(c, "ok", gin.H{ "username": claims.Username, }) } @@ -40,15 +39,10 @@ func UserProfileQueryHandler(w http.ResponseWriter, r *http.Request) { // - 校验旧密码正确性、新密码与确认一致性 // - 成功后更新密码哈希 // - 自动刷新接近过期的JWT令牌 -func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - claims, _, err := GetCurrentAdminUserWithRefresh(w, r) +func UserPasswordUpdateHandler(c *gin.Context) { + claims, _, err := GetCurrentAdminUserWithRefresh(c) if err != nil { - utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil) + baseController.HandleValidationError(c, "未登录或会话已过期") return } @@ -57,43 +51,45 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) { NewPassword string `json:"new_password"` ConfirmPassword string `json:"confirm_password"` } - var decodeErr error - if decodeErr = json.NewDecoder(r.Body).Decode(&body); decodeErr != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求参数错误", nil) + + if !baseController.BindJSON(c, &body) { return } // 基础校验 - if body.OldPassword == "" || body.NewPassword == "" || body.ConfirmPassword == "" { - utils.JsonResponse(w, http.StatusBadRequest, false, "旧密码/新密码/确认密码均不能为空", nil) + if !baseController.ValidateRequired(c, map[string]interface{}{ + "旧密码": body.OldPassword, + "新密码": body.NewPassword, + "确认密码": body.ConfirmPassword, + }) { return } + if len(body.NewPassword) < 6 { - utils.JsonResponse(w, http.StatusBadRequest, false, "新密码长度不能少于6位", nil) + baseController.HandleValidationError(c, "新密码长度不能少于6位") return } if body.NewPassword != body.ConfirmPassword { - utils.JsonResponse(w, http.StatusBadRequest, false, "两次输入的新密码不一致", nil) + baseController.HandleValidationError(c, "两次输入的新密码不一致") return } if body.NewPassword == body.OldPassword { - utils.JsonResponse(w, http.StatusBadRequest, false, "新密码不能与旧密码相同", nil) + baseController.HandleValidationError(c, "新密码不能与旧密码相同") return } // 注释:由于使用了AdminAuthRequired中间件,已确保是管理员用户 // 获取数据库连接 - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) + db, ok := baseController.GetDB(c) + if !ok { return } // 通过前缀匹配一次性获取所有管理员相关设置 var adminSettings []models.Settings if err = db.Where("name LIKE ?", "admin_%").Find(&adminSettings).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "获取管理员设置失败", nil) + baseController.HandleInternalError(c, "获取管理员设置失败", err) return } @@ -107,37 +103,37 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) { adminPassword, hasPassword := settingsMap["admin_password"] adminPasswordSalt, hasSalt := settingsMap["admin_password_salt"] if !hasPassword || !hasSalt { - utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员密码设置不完整", nil) + baseController.HandleInternalError(c, "管理员密码设置不完整", nil) return } // 校验旧密码 if !utils.VerifyPasswordWithSalt(body.OldPassword, adminPasswordSalt, adminPassword) { - utils.JsonResponse(w, http.StatusUnauthorized, false, "旧密码不正确", nil) + baseController.HandleValidationError(c, "旧密码不正确") return } // 生成新的密码盐值 newSalt, err := utils.GenerateRandomSalt() if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "生成密码盐失败", nil) + baseController.HandleInternalError(c, "生成密码盐失败", err) return } // 生成新密码哈希 newPasswordHash, err := utils.HashPasswordWithSalt(body.NewPassword, newSalt) if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "生成密码哈希失败", nil) + baseController.HandleInternalError(c, "生成密码哈希失败", err) return } // 更新settings中的管理员密码和盐值 if err = db.Model(&models.Settings{}).Where("name = ?", "admin_password").Update("value", newPasswordHash).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "更新密码失败", nil) + baseController.HandleInternalError(c, "更新密码失败", err) return } if err = db.Model(&models.Settings{}).Where("name = ?", "admin_password_salt").Update("value", newSalt).Error; err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "更新密码盐值失败", nil) + baseController.HandleInternalError(c, "更新密码盐值失败", err) return } @@ -149,16 +145,15 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) { } newToken, err := generateJWTTokenForAdmin(adminUser) if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "生成新令牌失败", nil) + baseController.HandleInternalError(c, "生成新令牌失败", err) return } // 更新Cookie(使用安全配置) - cookie := utils.CreateSecureCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge()) - http.SetCookie(w, cookie) + c.SetCookie("admin_session", newToken, utils.GetDefaultCookieMaxAge(), "/", "", false, true) // 密码修改成功,已重新生成JWT令牌 - utils.JsonResponse(w, http.StatusOK, true, "密码修改成功", nil) + baseController.HandleSuccess(c, "密码修改成功", nil) } // UserProfileUpdateHandler 修改当前登录管理员的用户名 @@ -166,15 +161,10 @@ func UserPasswordUpdateHandler(w http.ResponseWriter, r *http.Request) { // - 校验用户名非空、长度与唯一性 // - 更新数据库后重新签发JWT并写入 Cookie,保持前端展示的一致性 // - 自动刷新接近过期的JWT令牌 -func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - - _, _, err := GetCurrentAdminUserWithRefresh(w, r) +func UserProfileUpdateHandler(c *gin.Context) { + _, _, err := GetCurrentAdminUserWithRefresh(c) if err != nil { - utils.JsonResponse(w, http.StatusUnauthorized, false, "未登录或会话已过期", nil) + baseController.HandleValidationError(c, "未登录或会话已过期") return } @@ -182,24 +172,22 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) { Username string `json:"username"` OldPassword string `json:"old_password"` } - if decodeErr := json.NewDecoder(r.Body).Decode(&body); decodeErr != nil { - utils.JsonResponse(w, http.StatusBadRequest, false, "请求参数错误", nil) + if !baseController.BindJSON(c, &body) { return } username := strings.TrimSpace(body.Username) if username == "" { - utils.JsonResponse(w, http.StatusBadRequest, false, "用户名不能为空", nil) + baseController.HandleValidationError(c, "用户名不能为空") return } if len(username) > 64 { - utils.JsonResponse(w, http.StatusBadRequest, false, "用户名长度不能超过64字符", nil) + baseController.HandleValidationError(c, "用户名长度不能超过64字符") return } - db, err := database.GetDB() - if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "数据库连接失败", nil) + db, ok := baseController.GetDB(c) + if !ok { return } @@ -208,7 +196,7 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) { // 获取所有管理员相关设置 var adminSettings []models.Settings if dbErr := db.Where("name LIKE ?", "admin_%").Find(&adminSettings).Error; dbErr != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "获取管理员设置失败", nil) + baseController.HandleInternalError(c, "获取管理员设置失败", dbErr) return } @@ -220,25 +208,25 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) { adminUsername, exists := settingsMap["admin_username"] if !exists { - utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员用户名设置不存在", nil) + baseController.HandleInternalError(c, "管理员用户名设置不存在", nil) return } adminPassword, exists := settingsMap["admin_password"] if !exists { - utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员密码设置不存在", nil) + baseController.HandleInternalError(c, "管理员密码设置不存在", nil) return } adminPasswordSalt, exists := settingsMap["admin_password_salt"] if !exists { - utils.JsonResponse(w, http.StatusInternalServerError, false, "管理员密码盐值设置不存在", nil) + baseController.HandleInternalError(c, "管理员密码盐值设置不存在", nil) return } // 如果用户名未变化则直接返回成功(无需校验旧密码) if strings.EqualFold(username, adminUsername) { - utils.JsonResponse(w, http.StatusOK, true, "保存成功", map[string]interface{}{ + baseController.HandleSuccess(c, "保存成功", gin.H{ "username": username, }) return @@ -246,19 +234,19 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) { // 修改用户名需要进行当前密码校验 if strings.TrimSpace(body.OldPassword) == "" { - utils.JsonResponse(w, http.StatusBadRequest, false, "修改用户名需要提供当前密码", nil) + baseController.HandleValidationError(c, "修改用户名需要提供当前密码") return } // 使用盐值验证当前密码 if !utils.VerifyPasswordWithSalt(body.OldPassword, adminPasswordSalt, adminPassword) { - utils.JsonResponse(w, http.StatusUnauthorized, false, "当前密码不正确", nil) + baseController.HandleValidationError(c, "当前密码不正确") return } // 更新管理员用户名设置 if dbErr := db.Model(&models.Settings{}).Where("name = ?", "admin_username").Update("value", username).Error; dbErr != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "更新管理员用户名失败", nil) + baseController.HandleInternalError(c, "更新管理员用户名失败", dbErr) return } @@ -271,13 +259,12 @@ func UserProfileUpdateHandler(w http.ResponseWriter, r *http.Request) { } token, err := generateJWTTokenForAdmin(adminUser) if err != nil { - utils.JsonResponse(w, http.StatusInternalServerError, false, "生成新令牌失败", nil) + baseController.HandleInternalError(c, "生成新令牌失败", err) return } - cookie := utils.CreateSecureCookie("admin_session", token, utils.GetDefaultCookieMaxAge()) - http.SetCookie(w, cookie) + c.SetCookie("admin_session", token, utils.GetDefaultCookieMaxAge(), "/", "", false, true) - utils.JsonResponse(w, http.StatusOK, true, "保存成功", map[string]interface{}{ + baseController.HandleSuccess(c, "保存成功", gin.H{ "username": username, }) } diff --git a/controllers/admin/variable.go b/controllers/admin/variable.go index 77c5dc1..9f16eb1 100644 --- a/controllers/admin/variable.go +++ b/controllers/admin/variable.go @@ -1,58 +1,57 @@ package admin import ( - "encoding/json" "net/http" - "networkDev/database" + "networkDev/controllers" "networkDev/models" - "networkDev/utils" "regexp" "strconv" "strings" + "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" ) +// 创建基础控制器实例 +var variableBaseController = controllers.NewBaseController() + // VariableFragmentHandler 变量列表页面片段处理器 -func VariableFragmentHandler(w http.ResponseWriter, r *http.Request) { - utils.RenderTemplate(w, "variables", map[string]interface{}{ +func VariableFragmentHandler(c *gin.Context) { + c.HTML(http.StatusOK, "variables.html", gin.H{ "Title": "变量管理", }) } // VariableListHandler 变量列表API处理器 -func VariableListHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func VariableListHandler(c *gin.Context) { // 获取分页参数 - page, _ := strconv.Atoi(r.URL.Query().Get("page")) + page, _ := strconv.Atoi(c.Query("page")) if page <= 0 { page = 1 } - limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) + limit, _ := strconv.Atoi(c.Query("limit")) + // 兼容前端使用的page_size参数 + if limit <= 0 { + limit, _ = strconv.Atoi(c.Query("page_size")) + } if limit <= 0 { limit = 10 } // 获取应用UUID参数(用于按应用筛选变量) - appUUID := strings.TrimSpace(r.URL.Query().Get("app_uuid")) + appUUID := strings.TrimSpace(c.Query("app_uuid")) // 获取搜索关键词参数(支持编号、别名、数据的综合搜索) - search := strings.TrimSpace(r.URL.Query().Get("search")) - + search := strings.TrimSpace(c.Query("search")) + // 兼容旧的别名搜索参数 if search == "" { - search = strings.TrimSpace(r.URL.Query().Get("alias")) + search = strings.TrimSpace(c.Query("alias")) } // 构建查询 - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "Internal server error", http.StatusInternalServerError) + db, ok := variableBaseController.GetDB(c) + if !ok { return } @@ -66,7 +65,7 @@ func VariableListHandler(w http.ResponseWriter, r *http.Request) { // 如果指定了搜索关键词,则在编号、别名、数据、备注中进行模糊搜索 if search != "" { - query = query.Where("number LIKE ? OR alias LIKE ? OR data LIKE ? OR remark LIKE ?", + query = query.Where("number LIKE ? OR alias LIKE ? OR data LIKE ? OR remark LIKE ?", "%"+search+"%", "%"+search+"%", "%"+search+"%", "%"+search+"%") } @@ -74,7 +73,7 @@ func VariableListHandler(w http.ResponseWriter, r *http.Request) { var total int64 if err := query.Count(&total).Error; err != nil { logrus.WithError(err).Error("Failed to count variables") - http.Error(w, "Internal server error", http.StatusInternalServerError) + variableBaseController.HandleInternalError(c, "查询变量总数失败", err) return } @@ -83,7 +82,7 @@ func VariableListHandler(w http.ResponseWriter, r *http.Request) { offset := (page - 1) * limit if err := query.Offset(offset).Limit(limit).Order("created_at DESC").Find(&variables).Error; err != nil { logrus.WithError(err).Error("Failed to fetch variables") - http.Error(w, "Internal server error", http.StatusInternalServerError) + variableBaseController.HandleInternalError(c, "查询变量列表失败", err) return } @@ -141,24 +140,18 @@ func VariableListHandler(w http.ResponseWriter, r *http.Request) { }) } - response := map[string]interface{}{ + response := gin.H{ "code": 0, "msg": "success", "count": total, "data": responseData, } - w.Header().Set("Content-Type", "application/json") - utils.WriteJSONResponse(w, http.StatusOK, response) + c.JSON(http.StatusOK, response) } // VariableCreateHandler 新增变量API处理器 -func VariableCreateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func VariableCreateHandler(c *gin.Context) { var req struct { AppUUID string `json:"app_uuid"` Alias string `json:"alias"` @@ -166,40 +159,34 @@ func VariableCreateHandler(w http.ResponseWriter, r *http.Request) { Remark string `json:"remark"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - logrus.WithError(err).Error("Failed to decode JSON request") - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !variableBaseController.BindJSON(c, &req) { return } // 验证必填字段 - if strings.TrimSpace(req.AppUUID) == "" { - http.Error(w, "应用UUID不能为空", http.StatusBadRequest) - return - } - if strings.TrimSpace(req.Alias) == "" { - http.Error(w, "变量别名不能为空", http.StatusBadRequest) + if !variableBaseController.ValidateRequired(c, map[string]interface{}{ + "应用UUID": req.AppUUID, + "变量别名": req.Alias, + }) { return } // 验证别名格式:必须以英文字母开头,只能包含数字和英文字母 aliasPattern := regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9]*$`) if !aliasPattern.MatchString(req.Alias) { - http.Error(w, "别名必须以英文字母开头,只能包含数字和英文字母", http.StatusBadRequest) + variableBaseController.HandleValidationError(c, "别名必须以英文字母开头,只能包含数字和英文字母") return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + db, ok := variableBaseController.GetDB(c) + if !ok { return } // 验证应用是否存在 var app models.App if err := db.Where("uuid = ?", req.AppUUID).First(&app).Error; err != nil { - http.Error(w, "应用不存在", http.StatusBadRequest) + variableBaseController.HandleValidationError(c, "应用不存在") return } @@ -213,27 +200,15 @@ func VariableCreateHandler(w http.ResponseWriter, r *http.Request) { if err := db.Create(&variable).Error; err != nil { logrus.WithError(err).Error("Failed to create variable") - http.Error(w, "创建变量失败", http.StatusInternalServerError) + variableBaseController.HandleInternalError(c, "创建变量失败", err) return } - response := map[string]interface{}{ - "code": 0, - "msg": "创建成功", - "data": variable, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + variableBaseController.HandleSuccess(c, "创建成功", variable) } // VariableUpdateHandler 更新变量API处理器 -func VariableUpdateHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func VariableUpdateHandler(c *gin.Context) { var req struct { UUID string `json:"uuid"` AppUUID string `json:"app_uuid"` @@ -242,50 +217,42 @@ func VariableUpdateHandler(w http.ResponseWriter, r *http.Request) { Remark string `json:"remark"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !variableBaseController.BindJSON(c, &req) { return } // 验证必填字段 - if strings.TrimSpace(req.UUID) == "" { - http.Error(w, "变量UUID不能为空", http.StatusBadRequest) - return - } - if strings.TrimSpace(req.AppUUID) == "" { - http.Error(w, "应用UUID不能为空", http.StatusBadRequest) - return - } - if strings.TrimSpace(req.Alias) == "" { - http.Error(w, "变量别名不能为空", http.StatusBadRequest) + if !variableBaseController.ValidateRequired(c, map[string]interface{}{ + "变量UUID": req.UUID, + "应用UUID": req.AppUUID, + "变量别名": req.Alias, + }) { return } // 验证别名格式:必须以英文字母开头,只能包含数字和英文字母 aliasPattern := regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9]*$`) if !aliasPattern.MatchString(req.Alias) { - http.Error(w, "别名必须以英文字母开头,只能包含数字和英文字母", http.StatusBadRequest) + variableBaseController.HandleValidationError(c, "别名必须以英文字母开头,只能包含数字和英文字母") return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + db, ok := variableBaseController.GetDB(c) + if !ok { return } // 验证应用是否存在 var app models.App if err := db.Where("uuid = ?", req.AppUUID).First(&app).Error; err != nil { - http.Error(w, "应用不存在", http.StatusBadRequest) + variableBaseController.HandleValidationError(c, "应用不存在") return } // 通过uuid字段查找变量 var variable models.Variable if err := db.Where("uuid = ?", strings.TrimSpace(req.UUID)).First(&variable).Error; err != nil { - http.Error(w, "变量不存在", http.StatusNotFound) + variableBaseController.HandleValidationError(c, "变量不存在") return } @@ -297,139 +264,90 @@ func VariableUpdateHandler(w http.ResponseWriter, r *http.Request) { if err := db.Save(&variable).Error; err != nil { logrus.WithError(err).Error("Failed to update variable") - http.Error(w, "更新变量失败", http.StatusInternalServerError) + variableBaseController.HandleInternalError(c, "更新变量失败", err) return } - response := map[string]interface{}{ - "code": 0, - "msg": "更新成功", - "data": variable, - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + variableBaseController.HandleSuccess(c, "更新成功", variable) } // VariableDeleteHandler 删除变量API处理器 -func VariableDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func VariableDeleteHandler(c *gin.Context) { var req struct { ID uint `json:"id"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !variableBaseController.BindJSON(c, &req) { return } if req.ID == 0 { - http.Error(w, "变量ID不能为空", http.StatusBadRequest) + variableBaseController.HandleValidationError(c, "变量ID不能为空") return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + db, ok := variableBaseController.GetDB(c) + if !ok { return } // 删除变量 if err := db.Delete(&models.Variable{}, req.ID).Error; err != nil { logrus.WithError(err).Error("Failed to delete variable") - http.Error(w, "删除变量失败", http.StatusInternalServerError) + variableBaseController.HandleInternalError(c, "删除变量失败", err) return } logrus.WithField("variable_id", req.ID).Info("Successfully deleted variable") - response := map[string]interface{}{ - "code": 0, - "msg": "删除成功", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + variableBaseController.HandleSuccess(c, "删除成功", nil) } // VariablesBatchDeleteHandler 批量删除变量API处理器 -func VariablesBatchDeleteHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - +func VariablesBatchDeleteHandler(c *gin.Context) { var req struct { IDs []uint `json:"ids"` } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) + if !variableBaseController.BindJSON(c, &req) { return } if len(req.IDs) == 0 { - http.Error(w, "请选择要删除的变量", http.StatusBadRequest) + variableBaseController.HandleValidationError(c, "请选择要删除的变量") return } - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + db, ok := variableBaseController.GetDB(c) + if !ok { return } // 批量删除变量 if err := db.Delete(&models.Variable{}, req.IDs).Error; err != nil { logrus.WithError(err).Error("Failed to batch delete variables") - http.Error(w, "批量删除失败", http.StatusInternalServerError) + variableBaseController.HandleInternalError(c, "批量删除失败", err) return } logrus.WithField("variable_ids", req.IDs).Info("Successfully batch deleted variables") - response := map[string]interface{}{ - "code": 0, - "msg": "批量删除成功", - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + variableBaseController.HandleSuccess(c, "批量删除成功", nil) } // VariableGetAppsHandler 获取应用列表(用于筛选下拉框) -func VariableGetAppsHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - return - } - - db, err := database.GetDB() - if err != nil { - logrus.WithError(err).Error("Failed to get database connection") - http.Error(w, "Internal server error", http.StatusInternalServerError) +func VariableGetAppsHandler(c *gin.Context) { + db, ok := variableBaseController.GetDB(c) + if !ok { return } var apps []models.App if err := db.Select("uuid, name").Find(&apps).Error; err != nil { logrus.WithError(err).Error("Failed to fetch apps") - http.Error(w, "Internal server error", http.StatusInternalServerError) + variableBaseController.HandleInternalError(c, "获取应用列表失败", err) return } - response := map[string]interface{}{ - "code": 0, - "msg": "success", - "data": apps, - } - - w.Header().Set("Content-Type", "application/json") - utils.WriteJSONResponse(w, http.StatusOK, response) + variableBaseController.HandleSuccess(c, "success", apps) } \ No newline at end of file diff --git a/controllers/base.go b/controllers/base.go new file mode 100644 index 0000000..3b1216f --- /dev/null +++ b/controllers/base.go @@ -0,0 +1,159 @@ +package controllers + +import ( + "net/http" + "strconv" + + "networkDev/database" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" +) + +// BaseController 基础控制器结构体 +type BaseController struct{} + +// NewBaseController 创建基础控制器实例 +func NewBaseController() *BaseController { + return &BaseController{} +} + +// GetDB 获取数据库连接,统一错误处理 +func (bc *BaseController) GetDB(c *gin.Context) (*gorm.DB, bool) { + db, err := database.GetDB() + if err != nil { + bc.HandleDatabaseError(c, err) + return nil, false + } + return db, true +} + +// HandleDatabaseError 统一处理数据库连接错误 +func (bc *BaseController) HandleDatabaseError(c *gin.Context, err error) { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "数据库连接失败", + "data": nil, + }) +} + +// HandleValidationError 统一处理验证错误 +func (bc *BaseController) HandleValidationError(c *gin.Context, message string) { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": message, + "data": nil, + }) +} + +// HandleNotFoundError 统一处理资源未找到错误 +func (bc *BaseController) HandleNotFoundError(c *gin.Context, resource string) { + c.JSON(http.StatusNotFound, gin.H{ + "code": 1, + "msg": resource + "不存在", + "data": nil, + }) +} + +// HandleInternalError 统一处理内部服务器错误 +func (bc *BaseController) HandleInternalError(c *gin.Context, message string, err error) { + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": message, + "data": nil, + }) +} + +// HandleSuccess 统一处理成功响应 +func (bc *BaseController) HandleSuccess(c *gin.Context, message string, data interface{}) { + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": message, + "data": data, + }) +} + +// HandleCreated 统一处理创建成功响应 +func (bc *BaseController) HandleCreated(c *gin.Context, message string, data interface{}) { + c.JSON(http.StatusCreated, gin.H{ + "code": 0, + "msg": message, + "data": data, + }) +} + +// ValidateRequired 验证必填字段 +func (bc *BaseController) ValidateRequired(c *gin.Context, fields map[string]interface{}) bool { + for fieldName, fieldValue := range fields { + if fieldValue == nil || fieldValue == "" { + bc.HandleValidationError(c, fieldName+"不能为空") + return false + } + } + return true +} + +// GetPaginationParams 获取分页参数 +func (bc *BaseController) GetPaginationParams(c *gin.Context) (int, int) { + page := 1 + pageSize := 10 + + if p := c.Query("page"); p != "" { + if pageInt, err := strconv.Atoi(p); err == nil && pageInt > 0 { + page = pageInt + } + } + + if ps := c.Query("page_size"); ps != "" { + if pageSizeInt, err := strconv.Atoi(ps); err == nil && pageSizeInt > 0 && pageSizeInt <= 100 { + pageSize = pageSizeInt + } + } + + return page, pageSize +} + +// CalculateOffset 计算数据库查询偏移量 +func (bc *BaseController) CalculateOffset(page, pageSize int) int { + return (page - 1) * pageSize +} + +// BindJSON 绑定JSON数据并处理错误 +func (bc *BaseController) BindJSON(c *gin.Context, obj interface{}) bool { + if err := c.ShouldBindJSON(obj); err != nil { + bc.HandleValidationError(c, "请求参数错误: "+err.Error()) + return false + } + return true +} + +// BindQuery 绑定查询参数并处理错误 +func (bc *BaseController) BindQuery(c *gin.Context, obj interface{}) bool { + if err := c.ShouldBindQuery(obj); err != nil { + bc.HandleValidationError(c, "查询参数错误: "+err.Error()) + return false + } + return true +} + +// BindURI 绑定URI参数并处理错误 +func (bc *BaseController) BindURI(c *gin.Context, obj interface{}) bool { + if err := c.ShouldBindUri(obj); err != nil { + bc.HandleValidationError(c, "URI参数绑定失败: "+err.Error()) + return false + } + return true +} + +// GetDefaultTemplateData 获取默认模板数据 +// 返回包含系统基础信息的数据映射,包括站点标题、页脚文本、备案信息等 +func (bc *BaseController) GetDefaultTemplateData() gin.H { + return gin.H{ + "SystemName": "凌动技术", + "FooterText": "© 2025 凌动技术 保留所有权利", + "ICPRecord": "", + "ICPRecordLink": "https://beian.miit.gov.cn", + "PSBRecord": "", + "PSBRecordLink": "https://www.beian.gov.cn", + } +} diff --git a/controllers/home/home.go b/controllers/home/home.go index dfbec1c..3c386a5 100644 --- a/controllers/home/home.go +++ b/controllers/home/home.go @@ -2,72 +2,48 @@ package home import ( "net/http" + "networkDev/controllers" "networkDev/database" - "networkDev/models" "networkDev/services" - "networkDev/utils" + + "github.com/gin-gonic/gin" + "gorm.io/gorm" ) -// RootHandler 主页处理器 -func RootHandler(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.NotFound(w, r) - return - } +var homeBaseController = controllers.NewBaseController() +// getSettingValue 获取配置值,优先从数据库获取,不存在时使用默认值 +func getSettingValue(settingName string, defaultValue string, db *gorm.DB) string { + if setting, err := services.FindSettingByName(settingName, db); err == nil { + return setting.Value + } + return defaultValue +} + +// RootHandler 主页处理器 +func RootHandler(c *gin.Context) { // 获取数据库连接 db, err := database.GetDB() if err != nil { - http.Error(w, "数据库连接失败", http.StatusInternalServerError) + c.HTML(http.StatusInternalServerError, "error.html", gin.H{ + "error": "数据库连接失败", + }) return } - // 从数据库获取站点标题和页脚文本 - siteTitle, err := services.FindSettingByName("site_title", db) - if err != nil { - siteTitle = &models.Settings{Value: "凌动技术"} - } + // 获取默认模板数据 + defaultData := homeBaseController.GetDefaultTemplateData() - footerText, err := services.FindSettingByName("footer_text", db) - if err != nil { - footerText = &models.Settings{Value: "© 2025 凌动技术 保留所有权利"} - } - - // 从数据库获取备案信息 - icpRecord, err := services.FindSettingByName("icp_record", db) - if err != nil { - icpRecord = &models.Settings{Value: ""} - } - - icpRecordLink, err := services.FindSettingByName("icp_record_link", db) - if err != nil { - icpRecordLink = &models.Settings{Value: "https://beian.miit.gov.cn"} - } - - // 从数据库获取公安备案信息 - psbRecord, err := services.FindSettingByName("psb_record", db) - if err != nil { - psbRecord = &models.Settings{Value: ""} - } - - psbRecordLink, err := services.FindSettingByName("psb_record_link", db) - if err != nil { - psbRecordLink = &models.Settings{Value: "https://www.beian.gov.cn"} - } - - // 准备模板数据 - data := map[string]interface{}{ - "SystemName": siteTitle.Value, - "FooterText": footerText.Value, - "ICPRecord": icpRecord.Value, - "ICPRecordLink": icpRecordLink.Value, - "PSBRecord": psbRecord.Value, - "PSBRecordLink": psbRecordLink.Value, + // 准备模板数据,优先使用数据库配置,不存在时使用默认值 + data := gin.H{ + "SystemName": getSettingValue("site_title", defaultData["SystemName"].(string), db), + "FooterText": getSettingValue("footer_text", defaultData["FooterText"].(string), db), + "ICPRecord": getSettingValue("icp_record", defaultData["ICPRecord"].(string), db), + "ICPRecordLink": getSettingValue("icp_record_link", defaultData["ICPRecordLink"].(string), db), + "PSBRecord": getSettingValue("psb_record", defaultData["PSBRecord"].(string), db), + "PSBRecordLink": getSettingValue("psb_record_link", defaultData["PSBRecordLink"].(string), db), "title": "主页", } - if err := utils.RenderTemplate(w, "index.html", data); err != nil { - http.Error(w, "页面加载失败", http.StatusInternalServerError) - return - } + c.HTML(http.StatusOK, "index.html", data) } diff --git a/database/database.go b/database/database.go index abc00a7..58210b8 100644 --- a/database/database.go +++ b/database/database.go @@ -2,9 +2,8 @@ package database import ( "fmt" - "sync" - "networkDev/utils" + "sync" "github.com/glebarez/sqlite" "github.com/sirupsen/logrus" diff --git a/go.mod b/go.mod index 66b87fe..b6761b8 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module networkDev go 1.24.1 require ( + github.com/gin-gonic/gin v1.11.0 github.com/glebarez/sqlite v1.11.0 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 @@ -19,19 +20,36 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -39,11 +57,20 @@ require ( github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect go.uber.org/atomic v1.9.0 // indirect + go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.20.0 // indirect golang.org/x/image v0.23.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.35.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect diff --git a/go.sum b/go.sum index 410649b..9801ba1 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,14 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -18,20 +24,40 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -42,18 +68,32 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mojocn/base64Captcha v1.3.8 h1:rrN9BhCwXKS8ht1e21kvR3iTaMgf4qPC9sRoV52bqEg= github.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJKZbAucVgLUy4= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/redis/go-redis/v9 v9.13.0 h1:PpmlVykE0ODh8P43U0HqC+2NXHXwG+GUtQyz+MPKGRg= github.com/redis/go-redis/v9 v9.13.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= @@ -79,17 +119,30 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= @@ -104,6 +157,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -112,6 +167,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -119,14 +176,16 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -158,7 +217,11 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/middleware/logging.go b/middleware/logging.go new file mode 100644 index 0000000..af88e30 --- /dev/null +++ b/middleware/logging.go @@ -0,0 +1,97 @@ +package middleware + +import ( + "strings" + "time" + + "github.com/gin-gonic/gin" + "networkDev/utils/logger" +) + +// LoggingMiddleware 日志记录中间件结构体 +// 用于记录HTTP请求的详细信息,包括方法、路径、状态码和响应时间 +type LoggingMiddleware struct { + logger *logger.Logger +} + +// NewLoggingMiddleware 创建新的日志记录中间件实例 +func NewLoggingMiddleware(logger *logger.Logger) *LoggingMiddleware { + return &LoggingMiddleware{ + logger: logger, + } +} + +// Handler 返回Gin中间件函数,用于记录HTTP请求日志 +// 记录格式遵循Apache Common Log Format +func (lm *LoggingMiddleware) Handler() gin.HandlerFunc { + return func(c *gin.Context) { + // 记录开始时间 + start := time.Now() + + // 处理请求 + c.Next() + + // 计算处理时间 + duration := time.Since(start) + + // 获取客户端IP + clientIP := getClientIP(c) + + // 记录日志 - Apache Common Log Format + // 使用专门的HTTP日志方法避免User-Agent中的反斜杠被转义 + lm.logger.LogRequestWithHeaders( + c.Request.Method, + c.Request.RequestURI, + clientIP, + c.Writer.Status(), + duration, + "-", // referer (已废弃) + c.Request.UserAgent(), + ) + } +} + +// getClientIP 获取客户端真实IP地址 +// 优先从X-Forwarded-For、X-Real-IP等头部获取,最后使用RemoteAddr +func getClientIP(c *gin.Context) string { + // 检查X-Forwarded-For头部 + xForwardedFor := c.GetHeader("X-Forwarded-For") + if xForwardedFor != "" { + // X-Forwarded-For可能包含多个IP,取第一个 + ips := strings.Split(xForwardedFor, ",") + if len(ips) > 0 { + return strings.TrimSpace(ips[0]) + } + } + + // 检查X-Real-IP头部 + xRealIP := c.GetHeader("X-Real-IP") + if xRealIP != "" { + return xRealIP + } + + // 检查X-Forwarded头部 + xForwarded := c.GetHeader("X-Forwarded") + if xForwarded != "" { + return xForwarded + } + + // 使用RemoteAddr + remoteAddr := c.Request.RemoteAddr + if strings.Contains(remoteAddr, ":") { + // 移除端口号 + if idx := strings.LastIndex(remoteAddr, ":"); idx != -1 { + return remoteAddr[:idx] + } + } + + return remoteAddr +} + +// WrapHandler 创建Gin日志中间件 +// 使用全局日志记录器创建日志中间件 +func WrapHandler() gin.HandlerFunc { + logger := logger.GetLogger() + middleware := NewLoggingMiddleware(logger) + return middleware.Handler() +} diff --git a/middleware/middleware.go b/middleware/middleware.go deleted file mode 100644 index e6db2af..0000000 --- a/middleware/middleware.go +++ /dev/null @@ -1,133 +0,0 @@ -package middleware - -import ( - "net/http" - "strings" - "time" - - "networkDev/utils/logger" -) - -// LoggingMiddleware HTTP请求日志中间件 -// 记录每个HTTP请求的详细信息,包括方法、路径、状态码和响应时间 -type LoggingMiddleware struct { - logger *logger.Logger -} - -// NewLoggingMiddleware 创建新的日志中间件实例 -func NewLoggingMiddleware(logger *logger.Logger) *LoggingMiddleware { - return &LoggingMiddleware{ - logger: logger, - } -} - -// responseWriter 包装http.ResponseWriter以捕获状态码 -type responseWriter struct { - http.ResponseWriter - statusCode int - written bool -} - -// newResponseWriter 创建新的响应写入器包装器 -func newResponseWriter(w http.ResponseWriter) *responseWriter { - return &responseWriter{ - ResponseWriter: w, - statusCode: http.StatusOK, // 默认状态码 - } -} - -// WriteHeader 重写WriteHeader方法以捕获状态码 -func (rw *responseWriter) WriteHeader(code int) { - if !rw.written { - rw.statusCode = code - rw.written = true - rw.ResponseWriter.WriteHeader(code) - } -} - -// Write 重写Write方法以确保状态码被设置 -func (rw *responseWriter) Write(b []byte) (int, error) { - if !rw.written { - rw.WriteHeader(http.StatusOK) - } - return rw.ResponseWriter.Write(b) -} - -// Handler 中间件处理器函数 -// 包装HTTP处理器以添加请求日志记录功能 -func (lm *LoggingMiddleware) Handler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - - // 包装响应写入器以捕获状态码 - wrapped := newResponseWriter(w) - - // 调用下一个处理器 - next.ServeHTTP(wrapped, r) - - // 计算响应时间 - duration := time.Since(start) - - // 记录请求日志 - lm.logger.LogRequestWithHeaders( - r.Method, - r.URL.Path, - getClientIP(r), - wrapped.statusCode, - duration, - "-", - r.Header.Get("User-Agent"), - ) - }) -} - -// getClientIP 获取客户端真实IP地址 -// 优先从X-Forwarded-For、X-Real-IP等头部获取,最后使用RemoteAddr -func getClientIP(r *http.Request) string { - // 检查X-Forwarded-For头部 - xForwardedFor := r.Header.Get("X-Forwarded-For") - if xForwardedFor != "" { - // X-Forwarded-For可能包含多个IP,取第一个 - ips := strings.Split(xForwardedFor, ",") - if len(ips) > 0 { - return strings.TrimSpace(ips[0]) - } - } - - // 检查X-Real-IP头部 - xRealIP := r.Header.Get("X-Real-IP") - if xRealIP != "" { - return xRealIP - } - - // 检查X-Forwarded头部 - xForwarded := r.Header.Get("X-Forwarded") - if xForwarded != "" { - return xForwarded - } - - // 使用RemoteAddr - remoteAddr := r.RemoteAddr - if strings.Contains(remoteAddr, ":") { - // 移除端口号 - if idx := strings.LastIndex(remoteAddr, ":"); idx != -1 { - return remoteAddr[:idx] - } - } - - return remoteAddr -} - -// WrapHandler 包装HTTP处理器以添加日志记录功能 -// 使用全局日志记录器创建日志中间件 -func WrapHandler(handler http.Handler) http.Handler { - logger := logger.GetLogger() - middleware := NewLoggingMiddleware(logger) - return middleware.Handler(handler) -} - -// WrapHandlerFunc 包装HTTP处理器函数以添加日志记录功能 -// 将HandlerFunc转换为Handler并添加日志中间件 -func WrapHandlerFunc(handlerFunc http.HandlerFunc) http.Handler { - return WrapHandler(handlerFunc) -} diff --git a/server/admin.go b/server/admin.go index 752bafd..f957bc2 100644 --- a/server/admin.go +++ b/server/admin.go @@ -1,9 +1,10 @@ package server import ( - "net/http" adminctl "networkDev/controllers/admin" "networkDev/utils" + + "github.com/gin-gonic/gin" ) // RegisterAdminRoutes 注册管理员后台相关路由 @@ -12,91 +13,98 @@ import ( // - /admin/dashboard: 管理员仪表盘(示例) // - /admin/fragment/*: 布局内动态片段加载 // - /admin/api/settings*: 设置接口(查询/更新) -func RegisterAdminRoutes(mux *http.ServeMux) { +func RegisterAdminRoutes(router *gin.Engine) { // /admin 根与前缀统一入口:根据是否登录跳转 - mux.HandleFunc("/admin", adminctl.AdminIndexHandler) - mux.HandleFunc("/admin/", adminctl.AdminIndexHandler) + router.GET("/admin", adminctl.AdminIndexHandler) + router.GET("/admin/", adminctl.AdminIndexHandler) // Admin 认证相关路由 - mux.HandleFunc("/admin/login", func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - adminctl.LoginPageHandler(w, r) - return - } - if r.Method == http.MethodPost { - // 应用CSRF保护 - utils.RequireCSRFToken(adminctl.LoginHandler)(w, r) - return - } - w.WriteHeader(http.StatusMethodNotAllowed) - }) + router.GET("/admin/login", adminctl.LoginPageHandler) + router.POST("/admin/login", adminctl.LoginHandler) // CSRF验证在控制器内部处理 // 退出登录(无需拦截,幂等清理) - mux.HandleFunc("/admin/logout", adminctl.LogoutHandler) + router.POST("/admin/logout", adminctl.LogoutHandler) // 验证码生成路由(无需认证) - mux.HandleFunc("/admin/captcha", adminctl.CaptchaHandler) + router.GET("/admin/captcha", adminctl.CaptchaHandler) // CSRF令牌获取API(无需认证,但需要在登录页面等地方获取) - mux.HandleFunc("/admin/api/csrf-token", utils.CSRFTokenHandler) + router.GET("/admin/api/csrf-token", func(c *gin.Context) { + // 生成新的CSRF令牌 + token, err := utils.GenerateCSRFToken() + if err != nil { + c.JSON(500, gin.H{"success": false, "message": "生成CSRF令牌失败"}) + return + } + + // 设置令牌到Cookie和响应头 + utils.SetCSRFToken(c, token) + + // 返回令牌给前端 + c.JSON(200, gin.H{ + "success": true, + "message": "CSRF令牌生成成功", + "csrf_token": token, + }) + }) // 后台布局页(需要管理员认证) - mux.HandleFunc("/admin/layout", adminctl.AdminAuthRequired(adminctl.AdminLayoutHandler)) + router.GET("/admin/layout", adminctl.AdminAuthRequired(), adminctl.AdminLayoutHandler) // 片段路由(需要管理员认证) - mux.HandleFunc("/admin/dashboard", adminctl.AdminAuthRequired(adminctl.DashboardFragmentHandler)) - mux.HandleFunc("/admin/user", adminctl.AdminAuthRequired(adminctl.UserFragmentHandler)) - mux.HandleFunc("/admin/settings", adminctl.AdminAuthRequired(adminctl.SettingsFragmentHandler)) - mux.HandleFunc("/admin/apps", adminctl.AdminAuthRequired(adminctl.AppsFragmentHandler)) - mux.HandleFunc("/admin/apis", adminctl.AdminAuthRequired(adminctl.APIFragmentHandler)) - mux.HandleFunc("/admin/variables", adminctl.AdminAuthRequired(adminctl.VariableFragmentHandler)) + router.GET("/admin/dashboard", adminctl.AdminAuthRequired(), adminctl.DashboardFragmentHandler) + router.GET("/admin/user", adminctl.AdminAuthRequired(), adminctl.UserFragmentHandler) + router.GET("/admin/settings", adminctl.AdminAuthRequired(), adminctl.SettingsFragmentHandler) + router.GET("/admin/apps", adminctl.AdminAuthRequired(), adminctl.AppsFragmentHandler) + router.GET("/admin/apis", adminctl.AdminAuthRequired(), adminctl.APIFragmentHandler) + router.GET("/admin/variables", adminctl.AdminAuthRequired(), adminctl.VariableFragmentHandler) // 系统信息API(用于仪表盘定时刷新) - mux.HandleFunc("/admin/api/system/info", adminctl.AdminAuthRequired(adminctl.SystemInfoHandler)) + router.GET("/admin/api/system/info", adminctl.AdminAuthRequired(), adminctl.SystemInfoHandler) // 仪表盘统计数据API - mux.HandleFunc("/admin/api/dashboard/stats", adminctl.AdminAuthRequired(adminctl.DashboardStatsHandler)) + router.GET("/admin/api/dashboard/stats", adminctl.AdminAuthRequired(), adminctl.DashboardStatsHandler) // 个人资料API - mux.HandleFunc("/admin/api/user/profile", adminctl.AdminAuthRequired(adminctl.UserProfileQueryHandler)) - mux.HandleFunc("/admin/api/user/profile/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.UserProfileUpdateHandler))) - mux.HandleFunc("/admin/api/user/password", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.UserPasswordUpdateHandler))) + router.GET("/admin/api/user/profile", adminctl.AdminAuthRequired(), adminctl.UserProfileQueryHandler) + router.POST("/admin/api/user/profile/update", adminctl.AdminAuthRequired(), adminctl.UserProfileUpdateHandler) + router.POST("/admin/api/user/password", adminctl.AdminAuthRequired(), adminctl.UserPasswordUpdateHandler) // 系统设置API - mux.HandleFunc("/admin/api/settings", adminctl.AdminAuthRequired(adminctl.SettingsQueryHandler)) - mux.HandleFunc("/admin/api/settings/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.SettingsUpdateHandler))) + router.GET("/admin/api/settings", adminctl.AdminAuthRequired(), adminctl.SettingsQueryHandler) + router.POST("/admin/api/settings/update", adminctl.AdminAuthRequired(), adminctl.SettingsUpdateHandler) // 应用管理API - mux.HandleFunc("/admin/api/apps/list", adminctl.AdminAuthRequired(adminctl.AppsListHandler)) - mux.HandleFunc("/admin/api/apps/create", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppCreateHandler))) - mux.HandleFunc("/admin/api/apps/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateHandler))) - mux.HandleFunc("/admin/api/apps/delete", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppDeleteHandler))) - mux.HandleFunc("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppsBatchDeleteHandler))) - mux.HandleFunc("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppsBatchUpdateStatusHandler))) - mux.HandleFunc("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppResetSecretHandler))) - mux.HandleFunc("/admin/api/apps/get_app_data", adminctl.AdminAuthRequired(adminctl.AppGetAppDataHandler)) - mux.HandleFunc("/admin/api/apps/update_app_data", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateAppDataHandler))) - mux.HandleFunc("/admin/api/apps/get_announcement", adminctl.AdminAuthRequired(adminctl.AppGetAnnouncementHandler)) - mux.HandleFunc("/admin/api/apps/update_announcement", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateAnnouncementHandler))) - mux.HandleFunc("/admin/api/apps/get_multi_config", adminctl.AdminAuthRequired(adminctl.AppGetMultiConfigHandler)) - mux.HandleFunc("/admin/api/apps/update_multi_config", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateMultiConfigHandler))) - mux.HandleFunc("/admin/api/apps/get_bind_config", adminctl.AdminAuthRequired(adminctl.AppGetBindConfigHandler)) - mux.HandleFunc("/admin/api/apps/update_bind_config", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateBindConfigHandler))) - mux.HandleFunc("/admin/api/apps/get_register_config", adminctl.AdminAuthRequired(adminctl.AppGetRegisterConfigHandler)) - mux.HandleFunc("/admin/api/apps/update_register_config", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.AppUpdateRegisterConfigHandler))) + router.GET("/admin/api/apps/list", adminctl.AdminAuthRequired(), adminctl.AppsListHandler) + router.POST("/admin/api/apps/create", adminctl.AdminAuthRequired(), adminctl.AppCreateHandler) + router.POST("/admin/api/apps/update", adminctl.AdminAuthRequired(), adminctl.AppUpdateHandler) + router.POST("/admin/api/apps/delete", adminctl.AdminAuthRequired(), adminctl.AppDeleteHandler) + router.POST("/admin/api/apps/batch_delete", adminctl.AdminAuthRequired(), adminctl.AppsBatchDeleteHandler) + router.POST("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(), adminctl.AppsBatchUpdateStatusHandler) + router.POST("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(), adminctl.AppResetSecretHandler) + router.GET("/admin/api/apps/get_app_data", adminctl.AdminAuthRequired(), adminctl.AppGetAppDataHandler) + router.POST("/admin/api/apps/update_app_data", adminctl.AdminAuthRequired(), adminctl.AppUpdateAppDataHandler) + router.GET("/admin/api/apps/get_announcement", adminctl.AdminAuthRequired(), adminctl.AppGetAnnouncementHandler) + router.POST("/admin/api/apps/update_announcement", adminctl.AdminAuthRequired(), adminctl.AppUpdateAnnouncementHandler) + router.GET("/admin/api/apps/get_multi_config", adminctl.AdminAuthRequired(), adminctl.AppGetMultiConfigHandler) + router.POST("/admin/api/apps/update_multi_config", adminctl.AdminAuthRequired(), adminctl.AppUpdateMultiConfigHandler) + router.GET("/admin/api/apps/get_bind_config", adminctl.AdminAuthRequired(), adminctl.AppGetBindConfigHandler) + router.POST("/admin/api/apps/update_bind_config", adminctl.AdminAuthRequired(), adminctl.AppUpdateBindConfigHandler) + router.GET("/admin/api/apps/get_register_config", adminctl.AdminAuthRequired(), adminctl.AppGetRegisterConfigHandler) + router.POST("/admin/api/apps/update_register_config", adminctl.AdminAuthRequired(), adminctl.AppUpdateRegisterConfigHandler) // API接口管理API - mux.HandleFunc("/admin/api/apis/list", adminctl.AdminAuthRequired(adminctl.APIListHandler)) - mux.HandleFunc("/admin/api/apis/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.APIUpdateHandler))) - mux.HandleFunc("/admin/api/apis/apps", adminctl.AdminAuthRequired(adminctl.APIGetAppsHandler)) - mux.HandleFunc("/admin/api/apis/types", adminctl.AdminAuthRequired(adminctl.APIGetTypesHandler)) - mux.HandleFunc("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.APIGenerateKeysHandler))) + router.GET("/admin/api/apis/list", adminctl.AdminAuthRequired(), adminctl.APIListHandler) + router.POST("/admin/api/apis/update", adminctl.AdminAuthRequired(), adminctl.APIUpdateHandler) + router.GET("/admin/api/apis/apps", adminctl.AdminAuthRequired(), adminctl.APIGetAppsHandler) + router.GET("/admin/api/apis/types", adminctl.AdminAuthRequired(), adminctl.APIGetTypesHandler) + router.POST("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(), adminctl.APIGenerateKeysHandler) // 变量管理API - mux.HandleFunc("/admin/variable/list", adminctl.AdminAuthRequired(adminctl.VariableListHandler)) - mux.HandleFunc("/admin/variable/apps", adminctl.AdminAuthRequired(adminctl.VariableGetAppsHandler)) - mux.HandleFunc("/admin/variable/create", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.VariableCreateHandler))) - mux.HandleFunc("/admin/variable/update", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.VariableUpdateHandler))) - mux.HandleFunc("/admin/variable/delete", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.VariableDeleteHandler))) - mux.HandleFunc("/admin/variable/batch_delete", adminctl.AdminAuthRequired(utils.RequireCSRFToken(adminctl.VariablesBatchDeleteHandler))) + router.GET("/admin/variable/list", adminctl.AdminAuthRequired(), adminctl.VariableListHandler) + router.GET("/admin/variable/apps", adminctl.AdminAuthRequired(), adminctl.VariableGetAppsHandler) + router.POST("/admin/variable/create", adminctl.AdminAuthRequired(), adminctl.VariableCreateHandler) + router.POST("/admin/variable/update", adminctl.AdminAuthRequired(), adminctl.VariableUpdateHandler) + router.POST("/admin/variable/delete", adminctl.AdminAuthRequired(), adminctl.VariableDeleteHandler) + router.POST("/admin/variable/batch_delete", adminctl.AdminAuthRequired(), adminctl.VariablesBatchDeleteHandler) } diff --git a/server/home.go b/server/home.go index 858c7f6..2578898 100644 --- a/server/home.go +++ b/server/home.go @@ -1,13 +1,14 @@ package server import ( - "net/http" "networkDev/controllers/home" + + "github.com/gin-gonic/gin" ) // RegisterHomeRoutes 注册主页路由 // 只包含根路径,用于主页功能 -func RegisterHomeRoutes(mux *http.ServeMux) { +func RegisterHomeRoutes(router *gin.Engine) { // 根路径 - 主页 - mux.HandleFunc("/", home.RootHandler) + router.GET("/", home.RootHandler) } diff --git a/server/routes.go b/server/routes.go index ac74901..52cc118 100644 --- a/server/routes.go +++ b/server/routes.go @@ -5,30 +5,32 @@ import ( "log" "net/http" "networkDev/web" + + "github.com/gin-gonic/gin" ) // RegisterRoutes 聚合注册所有路由 -func RegisterRoutes(mux *http.ServeMux) { - registerStaticRoutes(mux) - registerFaviconRoute(mux) - RegisterHomeRoutes(mux) - RegisterAdminRoutes(mux) +func RegisterRoutes(router *gin.Engine) { + registerStaticRoutes(router) + registerFaviconRoute(router) + RegisterHomeRoutes(router) + RegisterAdminRoutes(router) } // registerStaticRoutes 注册静态资源路由 // 静态资源服务,将 /static/ 和 /assets/ 映射到嵌入的文件系统 -func registerStaticRoutes(mux *http.ServeMux) { +func registerStaticRoutes(router *gin.Engine) { if fsys, err := web.GetStaticFS(); err == nil { // 为 /static/ 路径创建子文件系统 if staticSubFS, staticErr := fs.Sub(fsys, "static"); staticErr == nil { - mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticSubFS)))) + router.StaticFS("/static", http.FS(staticSubFS)) } else { log.Printf("创建静态资源子文件系统失败: %v", staticErr) } // 为 /assets/ 路径创建子文件系统 if assetsSubFS, assetsErr := fs.Sub(fsys, "assets"); assetsErr == nil { - mux.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.FS(assetsSubFS)))) + router.StaticFS("/assets", http.FS(assetsSubFS)) } else { log.Printf("创建资产资源子文件系统失败: %v", assetsErr) } @@ -38,9 +40,9 @@ func registerStaticRoutes(mux *http.ServeMux) { } // registerFaviconRoute 注册favicon路由 -func registerFaviconRoute(mux *http.ServeMux) { +func registerFaviconRoute(router *gin.Engine) { // 将 /favicon.ico 重定向到 /assets/favicon.svg - mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, "/assets/favicon.svg", http.StatusMovedPermanently) + router.GET("/favicon.ico", func(c *gin.Context) { + c.Redirect(http.StatusMovedPermanently, "/assets/favicon.svg") }) } diff --git a/services/query.go b/services/query.go index a42fe05..0f7047c 100644 --- a/services/query.go +++ b/services/query.go @@ -10,10 +10,6 @@ import ( "gorm.io/gorm" ) - - - - // FindSettingByName 根据名称查找设置 // name: 设置名称 // db: 数据库连接 @@ -30,8 +26,6 @@ func FindSettingByName(name string, db *gorm.DB) (*models.Settings, error) { }) } - - // UpdateEntityByID 根据ID更新实体 // model: 模型类型 // id: 实体ID diff --git a/utils/common.go b/utils/common.go deleted file mode 100644 index 22b50b0..0000000 --- a/utils/common.go +++ /dev/null @@ -1,97 +0,0 @@ -package utils - -import ( - "encoding/json" - "net/http" - "networkDev/web" - "strings" -) - -// JsonResponse 通用JSON响应函数 -// 将 success 转换为 code:true -> 0, false -> 1,并输出 data -func JsonResponse(w http.ResponseWriter, status int, success bool, message string, data interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - - // 将success转换为code格式:true -> 0, false -> 1 - code := 1 - if success { - code = 0 - } - - json.NewEncoder(w).Encode(map[string]interface{}{ - "code": code, - "msg": message, - "data": data, - }) -} - -// RenderTemplate 通用模板渲染函数 -// templateName: 模板文件名 -// data: 模板数据 -// w: HTTP响应写入器 -func RenderTemplate(w http.ResponseWriter, templateName string, data map[string]interface{}) error { - w.Header().Set("Content-Type", "text/html; charset=utf-8") - - tmpl, err := web.ParseTemplates() - if err != nil { - http.Error(w, "模板解析失败", http.StatusInternalServerError) - return err - } - - if err := tmpl.ExecuteTemplate(w, templateName, data); err != nil { - http.Error(w, "模板渲染失败", http.StatusInternalServerError) - return err - } - return nil -} - -// GetDefaultTemplateData 获取默认模板数据 -// 返回包含系统基础信息的数据映射 -func GetDefaultTemplateData() map[string]interface{} { - return map[string]interface{}{ - "SystemName": "网络验证系统", - "FooterText": "© 2025 凌动技术 保留所有权利", - } -} - -// GetTemplateDataWithCSRF 获取包含CSRF令牌的模板数据 -// 合并默认数据和CSRF令牌,用于需要CSRF保护的页面 -func GetTemplateDataWithCSRF(r *http.Request, additionalData map[string]interface{}) map[string]interface{} { - // 获取默认模板数据 - data := GetDefaultTemplateData() - - // 添加CSRF令牌 - data["CSRFToken"] = GetCSRFTokenForTemplate(r) - - // 合并额外数据 - for key, value := range additionalData { - data[key] = value - } - - return data -} - -// GetClientIP 获取客户端IP地址 -// 优先从 X-Forwarded-For 和 X-Real-IP 头部获取,否则使用 RemoteAddr -func GetClientIP(r *http.Request) string { - // 检查 X-Forwarded-For 头部 - if xff := r.Header.Get("X-Forwarded-For"); xff != "" { - // X-Forwarded-For 可能包含多个IP,取第一个 - if idx := strings.Index(xff, ","); idx != -1 { - return strings.TrimSpace(xff[:idx]) - } - return strings.TrimSpace(xff) - } - - // 检查 X-Real-IP 头部 - if xri := r.Header.Get("X-Real-IP"); xri != "" { - return strings.TrimSpace(xri) - } - - // 使用 RemoteAddr - if idx := strings.LastIndex(r.RemoteAddr, ":"); idx != -1 { - return r.RemoteAddr[:idx] - } - return r.RemoteAddr -} diff --git a/utils/crypto.go b/utils/crypto.go index 01ba823..aa44831 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -121,15 +121,6 @@ func DecryptString(enc string) (string, error) { return string(plain), nil } -// ResetCrypto 重置加密管理器(用于配置更新后重新初始化) -func ResetCrypto() { - cryptoManager.mutex.Lock() - defer cryptoManager.mutex.Unlock() - cryptoManager.inited = false - cryptoManager.key = nil - cryptoManager.gcm = nil -} - // EncryptStringBatch 批量加密字符串 // 减少锁竞争,提高批量处理性能 func EncryptStringBatch(plains []string) ([]string, error) { @@ -281,12 +272,12 @@ func DecryptStringWithSalt(enc, salt string) (string, error) { if len(combined) < len(salt) { return "", errors.New("decrypted data too short") } - + // 验证盐值是否匹配 if combined[len(combined)-len(salt):] != salt { return "", errors.New("salt mismatch") } - + return combined[:len(combined)-len(salt)], nil } diff --git a/utils/csrf.go b/utils/csrf.go index 55c7aba..e8ff026 100644 --- a/utils/csrf.go +++ b/utils/csrf.go @@ -5,6 +5,8 @@ import ( "crypto/subtle" "encoding/base64" "net/http" + + "github.com/gin-gonic/gin" ) const ( @@ -34,55 +36,51 @@ func GenerateCSRFToken() (string, error) { } // SetCSRFToken 设置CSRF令牌到Cookie和响应头 -func SetCSRFToken(w http.ResponseWriter, token string) { - // 设置CSRF令牌到Cookie - cookie := CreateSecureCookie(CSRFCookieName, token, 3600) // 1小时过期 - http.SetCookie(w, cookie) - - // 设置CSRF令牌到响应头,方便JavaScript获取 - w.Header().Set("X-CSRF-Token", token) +func SetCSRFToken(c *gin.Context, token string) { + c.SetCookie(CSRFCookieName, token, 3600*24, "/", "", false, true) + c.Header(CSRFHeaderName, token) } -// GetCSRFTokenFromRequest 从请求中获取CSRF令牌 +// GetCSRFTokenFromRequest 从Gin请求中获取CSRF令牌 // 优先级:Header > Form > Cookie -func GetCSRFTokenFromRequest(r *http.Request) string { +func GetCSRFTokenFromRequest(c *gin.Context) string { // 1. 从Header获取 - if token := r.Header.Get(CSRFHeaderName); token != "" { + if token := c.GetHeader(CSRFHeaderName); token != "" { return token } // 2. 从Form获取 - if token := r.FormValue(CSRFFormField); token != "" { + if token := c.PostForm(CSRFFormField); token != "" { return token } // 3. 从Cookie获取(作为备选) - if cookie, err := r.Cookie(CSRFCookieName); err == nil { - return cookie.Value + if cookie, err := c.Cookie(CSRFCookieName); err == nil { + return cookie } return "" } // GetCSRFTokenFromCookie 从Cookie中获取CSRF令牌 -func GetCSRFTokenFromCookie(r *http.Request) string { - cookie, err := r.Cookie(CSRFCookieName) +func GetCSRFTokenFromCookie(c *gin.Context) string { + cookie, err := c.Cookie(CSRFCookieName) if err != nil { return "" } - return cookie.Value + return cookie } // ValidateCSRFToken 验证CSRF令牌 -func ValidateCSRFToken(r *http.Request) bool { +func ValidateCSRFToken(c *gin.Context) bool { // 获取Cookie中的令牌(服务器端存储的) - cookieToken := GetCSRFTokenFromCookie(r) + cookieToken := GetCSRFTokenFromCookie(c) if cookieToken == "" { return false } // 获取请求中的令牌(客户端提交的) - requestToken := GetCSRFTokenFromRequest(r) + requestToken := GetCSRFTokenFromRequest(c) if requestToken == "" { return false } @@ -92,47 +90,62 @@ func ValidateCSRFToken(r *http.Request) bool { } // CSRFProtection CSRF保护中间件 -func CSRFProtection(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { +func CSRFProtection() gin.HandlerFunc { + return func(c *gin.Context) { // 对于GET、HEAD、OPTIONS请求,只生成令牌,不验证 - if r.Method == http.MethodGet || r.Method == http.MethodHead || r.Method == http.MethodOptions { + if c.Request.Method == http.MethodGet || c.Request.Method == http.MethodHead || c.Request.Method == http.MethodOptions { // 生成新的CSRF令牌 token, err := GenerateCSRFToken() if err != nil { - http.Error(w, "Internal Server Error", http.StatusInternalServerError) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "Internal Server Error", + "data": nil, + }) + c.Abort() return } - SetCSRFToken(w, token) - next(w, r) + SetCSRFToken(c, token) + c.Next() return } // 对于POST、PUT、DELETE等修改性请求,验证CSRF令牌 - if !ValidateCSRFToken(r) { - JsonResponse(w, http.StatusForbidden, false, "CSRF令牌验证失败", nil) + if !ValidateCSRFToken(c) { + c.JSON(http.StatusForbidden, gin.H{ + "code": 1, + "msg": "CSRF令牌验证失败", + "data": nil, + }) + c.Abort() return } // 验证通过,继续处理请求 - next(w, r) + c.Next() } } // RequireCSRFToken 要求CSRF令牌的中间件(用于特定路由) -func RequireCSRFToken(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if !ValidateCSRFToken(r) { - JsonResponse(w, http.StatusForbidden, false, "CSRF令牌验证失败", nil) +func RequireCSRFToken() gin.HandlerFunc { + return func(c *gin.Context) { + if !ValidateCSRFToken(c) { + c.JSON(http.StatusForbidden, gin.H{ + "code": 1, + "msg": "CSRF令牌验证失败", + "data": nil, + }) + c.Abort() return } - next(w, r) + c.Next() } } // GetCSRFTokenForTemplate 获取用于模板的CSRF令牌 -func GetCSRFTokenForTemplate(r *http.Request) string { +func GetCSRFTokenForTemplate(c *gin.Context) string { // 尝试从Cookie获取现有令牌 - if token := GetCSRFTokenFromCookie(r); token != "" { + if token := GetCSRFTokenFromCookie(c); token != "" { return token } @@ -145,24 +158,36 @@ func GetCSRFTokenForTemplate(r *http.Request) string { } // CSRFTokenHandler 专门用于获取CSRF令牌的API端点 -func CSRFTokenHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - JsonResponse(w, http.StatusMethodNotAllowed, false, "只支持GET请求", nil) +func CSRFTokenHandler(c *gin.Context) { + if c.Request.Method != http.MethodGet { + c.JSON(http.StatusMethodNotAllowed, gin.H{ + "code": 1, + "msg": "只支持GET请求", + "data": nil, + }) return } // 生成新的CSRF令牌 token, err := GenerateCSRFToken() if err != nil { - JsonResponse(w, http.StatusInternalServerError, false, "生成CSRF令牌失败", nil) + c.JSON(http.StatusInternalServerError, gin.H{ + "code": 1, + "msg": "生成CSRF令牌失败", + "data": nil, + }) return } // 设置令牌到Cookie和响应头 - SetCSRFToken(w, token) + SetCSRFToken(c, token) // 返回令牌给前端 - JsonResponse(w, http.StatusOK, true, "CSRF令牌获取成功", map[string]interface{}{ - "csrf_token": token, + c.JSON(http.StatusOK, gin.H{ + "code": 0, + "msg": "CSRF令牌生成成功", + "data": gin.H{ + "csrf_token": token, + }, }) } \ No newline at end of file diff --git a/utils/errors.go b/utils/errors.go index b4faff1..2a0c70c 100644 --- a/utils/errors.go +++ b/utils/errors.go @@ -4,10 +4,10 @@ import ( "encoding/json" "fmt" "log" - "net/http" "runtime" "time" + "github.com/gin-gonic/gin" "gorm.io/gorm" ) @@ -68,27 +68,13 @@ type LogEntry struct { Line int `json:"line"` // 源文件行号 } -// WriteJSONResponse 写入JSON响应 -// w: HTTP响应写入器 -// statusCode: HTTP状态码 -// response: 响应数据 -func WriteJSONResponse(w http.ResponseWriter, statusCode int, response interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCode) - - if err := json.NewEncoder(w).Encode(response); err != nil { - LogError("Failed to encode JSON response", err, nil) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - } -} - // WriteErrorResponse 写入错误响应 -// w: HTTP响应写入器 +// c: Gin上下文 // statusCode: HTTP状态码 // message: 错误消息 // errorCode: 错误代码 // data: 附加数据 -func WriteErrorResponse(w http.ResponseWriter, statusCode int, message, errorCode string, data interface{}) { +func WriteErrorResponse(c *gin.Context, statusCode int, message, errorCode string, data interface{}) { response := ErrorResponse{ Success: false, Message: message, @@ -97,15 +83,15 @@ func WriteErrorResponse(w http.ResponseWriter, statusCode int, message, errorCod Timestamp: time.Now().Unix(), } - WriteJSONResponse(w, statusCode, response) + c.JSON(statusCode, response) } // WriteSuccessResponse 写入成功响应 -// w: HTTP响应写入器 +// c: Gin上下文 // statusCode: HTTP状态码 // message: 成功消息 // data: 响应数据 -func WriteSuccessResponse(w http.ResponseWriter, statusCode int, message string, data interface{}) { +func WriteSuccessResponse(c *gin.Context, statusCode int, message string, data interface{}) { response := SuccessResponse{ Success: true, Message: message, @@ -113,57 +99,57 @@ func WriteSuccessResponse(w http.ResponseWriter, statusCode int, message string, Timestamp: time.Now().Unix(), } - WriteJSONResponse(w, statusCode, response) + c.JSON(statusCode, response) } // HandleDatabaseError 处理数据库错误 -// w: HTTP响应写入器 +// c: Gin上下文 // err: 数据库错误 // operation: 操作描述 -func HandleDatabaseError(w http.ResponseWriter, err error, operation string) { +func HandleDatabaseError(c *gin.Context, err error, operation string) { if err == gorm.ErrRecordNotFound { LogWarn(fmt.Sprintf("Record not found during %s", operation), map[string]interface{}{ "operation": operation, "error": err.Error(), }) - WriteErrorResponse(w, http.StatusNotFound, "记录不存在", ErrCodeNotFound, nil) + WriteErrorResponse(c, 404, "记录不存在", ErrCodeNotFound, nil) return } LogError(fmt.Sprintf("Database error during %s", operation), err, map[string]interface{}{ "operation": operation, }) - WriteErrorResponse(w, http.StatusInternalServerError, "数据库操作失败", ErrCodeDatabaseError, nil) + WriteErrorResponse(c, 500, "数据库操作失败", ErrCodeDatabaseError, nil) } // HandleValidationError 处理验证错误 -// w: HTTP响应写入器 +// c: Gin上下文 // message: 验证错误消息 // details: 验证错误详情 -func HandleValidationError(w http.ResponseWriter, message string, details interface{}) { +func HandleValidationError(c *gin.Context, message string, details interface{}) { LogWarn("Validation error: "+message, map[string]interface{}{ "details": details, }) - WriteErrorResponse(w, http.StatusBadRequest, message, ErrCodeValidationError, details) + WriteErrorResponse(c, 400, message, ErrCodeValidationError, details) } // HandleUnauthorizedError 处理未授权错误 -// w: HTTP响应写入器 +// c: Gin上下文 // message: 错误消息 -func HandleUnauthorizedError(w http.ResponseWriter, message string) { +func HandleUnauthorizedError(c *gin.Context, message string) { LogWarn("Unauthorized access: "+message, nil) - WriteErrorResponse(w, http.StatusUnauthorized, message, ErrCodeUnauthorized, nil) + WriteErrorResponse(c, 401, message, ErrCodeUnauthorized, nil) } // HandleInternalError 处理内部错误 -// w: HTTP响应写入器 +// c: Gin上下文 // err: 错误 // operation: 操作描述 -func HandleInternalError(w http.ResponseWriter, err error, operation string) { +func HandleInternalError(c *gin.Context, err error, operation string) { LogError(fmt.Sprintf("Internal error during %s", operation), err, map[string]interface{}{ "operation": operation, }) - WriteErrorResponse(w, http.StatusInternalServerError, "服务器内部错误", ErrCodeInternalError, nil) + WriteErrorResponse(c, 500, "服务器内部错误", ErrCodeInternalError, nil) } // LogInfo 记录信息日志 diff --git a/web/template/admin/apis.html b/web/template/admin/apis.html index 2f6c540..8315240 100644 --- a/web/template/admin/apis.html +++ b/web/template/admin/apis.html @@ -278,7 +278,7 @@ url: '/admin/api/apis/apps', type: 'GET', success: function (res) { - if (res.success && res.data) { + if (res.code === 0 && res.data) { var filterSelect = $('select[name="app_uuid"]').eq(0); // 清空现有选项(保留默认选项) filterSelect.find('option:not(:first)').remove(); @@ -303,7 +303,7 @@ url: '/admin/api/apis/types', type: 'GET', success: function (res) { - if (res.success && res.data) { + if (res.code === 0 && res.data) { var typeSelect = $('select[name="api_type"]').eq(0); // 清空现有选项(保留默认选项) typeSelect.find('option:not(:first)').remove(); @@ -453,7 +453,7 @@ contentType: 'application/json', data: JSON.stringify({ side: side, algorithm: algorithm }), success: function (res) { - if (res.success && res.data) { + if (res.code === 0 && res.data) { if (algorithm === 1) { // RC4 $('[name="' + prefix + '_private_key"]').val(res.data.private_key || ''); } else if (algorithm === 4) { // 易加密 @@ -464,7 +464,7 @@ } layer.msg('已自动生成' + (side === 'submit' ? '提交' : '返回') + '密钥', { icon: 1, time: 1500 }); } else { - layer.msg(res.message || '自动生成密钥失败', { icon: 2 }); + layer.msg(res.msg || '自动生成密钥失败', { icon: 2 }); } }, error: function () { @@ -498,7 +498,7 @@ contentType: 'application/json', data: JSON.stringify({ side: 'submit', algorithm: algo }), success: function (res) { - if (res.success && res.data) { + if (res.code === 0 && res.data) { if (algo === 1) { // RC4 $('[name="submit_private_key"]').val(res.data.private_key || ''); } else if (algo === 4) { // 易加密 @@ -508,7 +508,7 @@ $('[name="submit_private_key"]').val(res.data.private_key || ''); } } else { - layer.msg(res.message || '生成失败', { icon: 2 }); + layer.msg(res.msg || '生成失败', { icon: 2 }); } }, error: function () { layer.msg('生成失败', { icon: 2 }); } @@ -528,7 +528,7 @@ contentType: 'application/json', data: JSON.stringify({ side: 'return', algorithm: algo }), success: function (res) { - if (res.success && res.data) { + if (res.code === 0 && res.data) { if (algo === 1) { // RC4 $('[name="return_private_key"]').val(res.data.private_key || ''); } else if (algo === 4) { // 易加密 @@ -538,7 +538,7 @@ $('[name="return_private_key"]').val(res.data.private_key || ''); } } else { - layer.msg(res.message || '生成失败', { icon: 2 }); + layer.msg(res.msg || '生成失败', { icon: 2 }); } }, error: function () { layer.msg('生成失败', { icon: 2 }); } @@ -602,12 +602,12 @@ contentType: 'application/json', data: JSON.stringify(formData), success: function (res) { - if (res.success) { + if (res.code === 0) { layer.msg('接口更新成功', { icon: 1 }); layer.close(index); apisTable.reload(); } else { - layer.msg(res.message || '更新失败', { icon: 2 }); + layer.msg(res.msg || '更新失败', { icon: 2 }); } }, error: function () { diff --git a/web/template/admin/apps.html b/web/template/admin/apps.html index 16ffefc..090eaa0 100644 --- a/web/template/admin/apps.html +++ b/web/template/admin/apps.html @@ -761,15 +761,17 @@ $.ajax({ url: '/admin/api/apps/get_multi_config?uuid=' + obj.data.uuid, type: 'GET', - success: function (config) { - // 填充表单数据 - $('input[name="login_type"][value="' + config.login_type + '"]').prop('checked', true); - $('input[name="multi_open_scope"][value="' + config.multi_open_scope + '"]').prop('checked', true); - $('input[name="clean_interval"]').val(config.clean_interval); - $('input[name="check_interval"]').val(config.check_interval); - $('input[name="multi_open_count"]').val(config.multi_open_count); + success: function (res) { + if (res.code === 0 && res.data) { + var config = res.data; + // 填充表单数据 + $('input[name="login_type"][value="' + config.login_type + '"]').prop('checked', true); + $('input[name="multi_open_scope"][value="' + config.multi_open_scope + '"]').prop('checked', true); + $('input[name="clean_interval"]').val(config.clean_interval); + $('input[name="check_interval"]').val(config.check_interval); + $('input[name="multi_open_count"]').val(config.multi_open_count); - // 打开静态弹窗 + // 打开静态弹窗 var multiConfigIndex = layer.open({ type: 1, title: '多开配置 - ' + obj.data.name, @@ -815,7 +817,7 @@ contentType: 'application/json', data: JSON.stringify(formData), success: function (res) { - if (res.message) { + if (res.code === 0) { layer.msg('多开配置更新成功', { icon: 1 }); layer.close(index); table.reload('appsTable'); @@ -836,6 +838,9 @@ form.render(); } }); + } else { + layer.msg(res.msg || '获取多开配置失败', { icon: 2 }); + } }, error: function () { layer.msg('获取多开配置失败,请稍后重试', { icon: 2 }); @@ -884,9 +889,11 @@ $.ajax({ url: '/admin/api/apps/get_bind_config?uuid=' + obj.data.uuid, type: 'GET', - success: function (config) { - // 填充表单数据 - $('#bindConfigModal input[name="machine_verify"][value="' + config.machine_verify + '"]').prop('checked', true); + success: function (res) { + if (res.code === 0 && res.data) { + var config = res.data; + // 填充表单数据 + $('#bindConfigModal input[name="machine_verify"][value="' + config.machine_verify + '"]').prop('checked', true); $('#bindConfigModal input[name="machine_rebind_enabled"][value="' + config.machine_rebind_enabled + '"]').prop('checked', true); $('#bindConfigModal input[name="machine_rebind_limit"][value="' + config.machine_rebind_limit + '"]').prop('checked', true); $('#bindConfigModal input[name="machine_free_count"]').val(config.machine_free_count); @@ -977,6 +984,9 @@ form.render(); } }); + } else { + layer.msg(res.msg || '获取绑定设置失败', { icon: 2 }); + } }, error: function () { layer.msg('获取绑定设置失败,请稍后重试', { icon: 2 }); @@ -987,9 +997,11 @@ $.ajax({ url: '/admin/api/apps/get_register_config?uuid=' + obj.data.uuid, type: 'GET', - success: function (config) { - // 填充表单数据 - $('#registerConfigModal input[name="register_enabled"][value="' + config.register_enabled + '"]').prop('checked', true); + success: function (res) { + if (res.code === 0 && res.data) { + var config = res.data; + // 填充表单数据 + $('#registerConfigModal input[name="register_enabled"][value="' + config.register_enabled + '"]').prop('checked', true); $('#registerConfigModal input[name="register_limit_enabled"][value="' + config.register_limit_enabled + '"]').prop('checked', true); $('#registerConfigModal input[name="register_limit_time"][value="' + config.register_limit_time + '"]').prop('checked', true); $('#registerConfigModal input[name="register_count"]').val(config.register_count); @@ -1074,6 +1086,9 @@ form.render(); } }); + } else { + layer.msg(res.msg || '获取注册设置失败', { icon: 2 }); + } }, error: function () { layer.msg('获取注册设置失败,请稍后重试', { icon: 2 }); diff --git a/web/template/admin/variables.html b/web/template/admin/variables.html index c8a5caf..43c77ae 100644 --- a/web/template/admin/variables.html +++ b/web/template/admin/variables.html @@ -1,4 +1,4 @@ -{{ define "variables" }} +{{ define "variables.html" }}

变量管理

diff --git a/web/template/layui-theme-dark b/web/template/layui-theme-dark deleted file mode 160000 index a89e678..0000000 --- a/web/template/layui-theme-dark +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a89e6787f40f512de9e2ee538bc527a48dfddefa