diff --git a/cmd/server.go b/cmd/server.go index 7793474..3a42c06 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -105,6 +105,9 @@ func createHTTPServer(addr string) *http.Server { // 添加日志中间件 router.Use(middleware.WrapHandler()) + // 添加开发模式中间件(统一管理开发模式功能) + router.Use(middleware.DevModeMiddleware(router)) + // 加载模板 if err := loadTemplates(router); err != nil { logrus.WithError(err).Fatal("模板加载失败") diff --git a/controllers/admin/api.go b/controllers/admin/api.go index 2276d86..a431fdc 100644 --- a/controllers/admin/api.go +++ b/controllers/admin/api.go @@ -274,6 +274,55 @@ func APIGetTypesHandler(c *gin.Context) { apiBaseController.HandleSuccess(c, "获取接口类型列表成功", apiTypes) } +// APIUpdateStatusHandler 更新单个接口状态处理器 +func APIUpdateStatusHandler(c *gin.Context) { + var req struct { + ID uint `json:"id"` + Status int `json:"status"` + } + + if !apiBaseController.BindJSON(c, &req) { + return + } + + if req.ID == 0 { + apiBaseController.HandleValidationError(c, "接口ID不能为空") + return + } + + if req.Status != 0 && req.Status != 1 { + apiBaseController.HandleValidationError(c, "状态值无效") + return + } + + // 获取数据库连接 + db, ok := apiBaseController.GetDB(c) + if !ok { + return + } + + // 检查接口是否存在 + var api models.API + if err := db.Where("id = ?", req.ID).First(&api).Error; err != nil { + apiBaseController.HandleValidationError(c, "接口不存在") + return + } + + // 更新状态 + if err := db.Model(&api).Update("status", req.Status).Error; err != nil { + logrus.WithError(err).Error("Failed to update API status") + apiBaseController.HandleInternalError(c, "更新状态失败", err) + return + } + + statusText := "禁用" + if req.Status == 1 { + statusText = "启用" + } + + apiBaseController.HandleSuccess(c, "接口"+statusText+"成功", nil) +} + func APIGenerateKeysHandler(c *gin.Context) { var req struct { Side string `json:"side"` // submit | return diff --git a/controllers/admin/app.go b/controllers/admin/app.go index 1495e99..9c7cac3 100644 --- a/controllers/admin/app.go +++ b/controllers/admin/app.go @@ -339,25 +339,25 @@ func AppCreateHandler(c *gin.Context) { // 为应用创建所有默认接口 defaultAPITypes := []int{ - models.APITypeGetBulletin, // 获取程序公告 - models.APITypeGetUpdateUrl, // 获取更新地址 - models.APITypeCheckAppVersion, // 检测最新版本 - models.APITypeGetCardInfo, // 获取卡密信息 - models.APITypeSingleLogin, // 卡密登录 - models.APITypeUserLogin, // 用户登录 - models.APITypeUserRegin, // 用户注册 - models.APITypeUserRecharge, // 用户充值 - models.APITypeCardRegin, // 卡密注册 - models.APITypeLogOut, // 退出登录 - models.APITypeGetExpired, // 获取到期时间 - models.APITypeCheckUserStatus, // 检测账号状态 - models.APITypeGetAppData, // 获取程序数据 - models.APITypeGetVariable, // 获取变量数据 - models.APITypeUpdatePwd, // 修改账号密码 - models.APITypeMacChangeBind, // 机器码转绑 - models.APITypeIPChangeBind, // IP转绑 - models.APITypeDisableUser, // 封停用户 - models.APITypeBlackUser, // 添加黑名单 + models.APITypeGetBulletin, // 获取程序公告 + models.APITypeGetUpdateUrl, // 获取更新地址 + models.APITypeCheckAppVersion, // 检测最新版本 + models.APITypeGetCardInfo, // 获取卡密信息 + models.APITypeSingleLogin, // 卡密登录 + models.APITypeUserLogin, // 用户登录 + models.APITypeUserRegin, // 用户注册 + models.APITypeUserRecharge, // 用户充值 + models.APITypeCardRegin, // 卡密注册 + models.APITypeLogOut, // 退出登录 + models.APITypeGetExpired, // 获取到期时间 + models.APITypeCheckUserStatus, // 检测账号状态 + models.APITypeGetAppData, // 获取程序数据 + models.APITypeGetVariable, // 获取变量数据 + models.APITypeUpdatePwd, // 修改账号密码 + models.APITypeMacChangeBind, // 机器码转绑 + models.APITypeIPChangeBind, // IP转绑 + models.APITypeDisableUser, // 封停用户 + models.APITypeBlackUser, // 添加黑名单 models.APITypeUserDeductedTime, // 扣除时间 } @@ -366,7 +366,7 @@ func AppCreateHandler(c *gin.Context) { api := models.API{ APIType: apiType, AppUUID: app.UUID, - Status: 1, // 默认启用 + Status: 0, // 默认禁用 SubmitAlgorithm: models.AlgorithmNone, // 默认不加密 ReturnAlgorithm: models.AlgorithmNone, // 默认不加密 } @@ -928,18 +928,18 @@ func AppGetBindConfigHandler(c *gin.Context) { "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, + "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, }, }) } @@ -948,19 +948,19 @@ func AppGetBindConfigHandler(c *gin.Context) { 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 !appBaseController.BindJSON(c, &req) { @@ -1005,18 +1005,18 @@ func AppUpdateBindConfigHandler(c *gin.Context) { // 更新绑定配置 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 { @@ -1334,3 +1334,67 @@ func AppsBatchUpdateStatusHandler(c *gin.Context) { "msg": "批量" + statusText + "成功", }) } + +// AppUpdateStatusHandler 更新单个应用状态处理器 +func AppUpdateStatusHandler(c *gin.Context) { + var req struct { + ID uint `json:"id"` + Status int `json:"status"` + } + + if !appBaseController.BindJSON(c, &req) { + return + } + + if req.ID == 0 { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用ID不能为空", + }) + 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 + } + + // 检查应用是否存在 + var app models.App + if err := db.Where("id = ?", req.ID).First(&app).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "code": 1, + "msg": "应用不存在", + }) + return + } + + // 更新状态 + if err := db.Model(&app).Update("status", req.Status).Error; err != nil { + logrus.WithError(err).Error("Failed to 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/captcha.go b/controllers/admin/captcha.go index 1ef69cd..c4074b8 100644 --- a/controllers/admin/captcha.go +++ b/controllers/admin/captcha.go @@ -9,10 +9,10 @@ import ( "github.com/gin-gonic/gin" "networkDev/controllers" + "networkDev/middleware" "networkDev/utils" "github.com/mojocn/base64Captcha" - "github.com/spf13/viper" ) // 创建基础控制器实例 @@ -94,7 +94,7 @@ func CaptchaHandler(c *gin.Context) { // 支持大小写不敏感匹配 func VerifyCaptcha(c *gin.Context, captchaValue string) bool { // 检查是否为开发模式,如果是则跳过验证码验证 - if viper.GetBool("server.dev_mode") { + if middleware.ShouldSkipCaptcha(c) { return true } diff --git a/controllers/admin/handlers.go b/controllers/admin/handlers.go index e7c2475..a3ecff4 100644 --- a/controllers/admin/handlers.go +++ b/controllers/admin/handlers.go @@ -4,6 +4,7 @@ import ( "net/http" "networkDev/constants" "networkDev/controllers" + "networkDev/middleware" "networkDev/models" "networkDev/services" "networkDev/utils" @@ -97,7 +98,7 @@ func AdminLayoutHandler(c *gin.Context) { // - 展示系统信息:版本、开发模式、数据库类型、启动时长 func DashboardFragmentHandler(c *gin.Context) { version := constants.AppVersion - mode := viper.GetBool("server.dev_mode") + mode := middleware.IsDevModeFromContext(c) dbType := viper.GetString("database.type") if dbType == "" { dbType = "sqlite" @@ -118,7 +119,7 @@ func DashboardFragmentHandler(c *gin.Context) { // - 返回系统运行状态的JSON数据,用于前端定时刷新 func SystemInfoHandler(c *gin.Context) { version := constants.AppVersion - mode := viper.GetBool("server.dev_mode") + mode := middleware.IsDevModeFromContext(c) dbType := viper.GetString("database.type") if dbType == "" { dbType = "sqlite" diff --git a/middleware/devmode.go b/middleware/devmode.go new file mode 100644 index 0000000..8cfa9ac --- /dev/null +++ b/middleware/devmode.go @@ -0,0 +1,100 @@ +package middleware + +import ( + "networkDev/web" + + "github.com/gin-gonic/gin" + "github.com/spf13/viper" +) + +// DevModeConfig 开发模式配置 +type DevModeConfig struct { + // 是否启用模板热重载 + EnableTemplateReload bool + // 是否跳过验证码验证 + SkipCaptcha bool + // 是否显示详细错误信息 + ShowDetailedErrors bool + // 是否启用调试日志 + EnableDebugLog bool +} + +// DevModeMiddleware 开发模式中间件 +// 统一管理所有开发模式相关的功能 +func DevModeMiddleware(engine *gin.Engine) gin.HandlerFunc { + return func(c *gin.Context) { + // 检查是否为开发模式 + if IsDevMode() { + // 设置开发模式标识到上下文 + c.Set("dev_mode", true) + c.Set("dev_config", GetDevModeConfig()) + + // 如果启用了模板热重载,则重新加载模板 + config := GetDevModeConfig() + if config.EnableTemplateReload { + reloadTemplates(engine) + } + + // 设置开发模式相关的响应头 + c.Header("X-Dev-Mode", "true") + } else { + c.Set("dev_mode", false) + } + + c.Next() + } +} + +// IsDevMode 检查是否为开发模式 +func IsDevMode() bool { + return viper.GetBool("server.dev_mode") +} + +// GetDevModeConfig 获取开发模式配置 +func GetDevModeConfig() DevModeConfig { + if !IsDevMode() { + return DevModeConfig{} + } + + return DevModeConfig{ + EnableTemplateReload: true, // 开发模式下默认启用模板热重载 + SkipCaptcha: true, // 开发模式下默认跳过验证码 + ShowDetailedErrors: true, // 开发模式下显示详细错误 + EnableDebugLog: true, // 开发模式下启用调试日志 + } +} + +// IsDevModeFromContext 从上下文中检查是否为开发模式 +func IsDevModeFromContext(c *gin.Context) bool { + if devMode, exists := c.Get("dev_mode"); exists { + if isDevMode, ok := devMode.(bool); ok { + return isDevMode + } + } + // 回退到配置检查 + return IsDevMode() +} + +// GetDevModeConfigFromContext 从上下文中获取开发模式配置 +func GetDevModeConfigFromContext(c *gin.Context) DevModeConfig { + if config, exists := c.Get("dev_config"); exists { + if devConfig, ok := config.(DevModeConfig); ok { + return devConfig + } + } + // 回退到默认配置 + return GetDevModeConfig() +} + +// ShouldSkipCaptcha 检查是否应该跳过验证码验证 +func ShouldSkipCaptcha(c *gin.Context) bool { + config := GetDevModeConfigFromContext(c) + return config.SkipCaptcha +} + +// reloadTemplates 重新加载模板(内部函数) +func reloadTemplates(engine *gin.Engine) { + if tmpl, err := web.ParseTemplates(); err == nil { + engine.SetHTMLTemplate(tmpl) + } +} \ No newline at end of file diff --git a/models/api.go b/models/api.go index 9cf7698..7da7df5 100644 --- a/models/api.go +++ b/models/api.go @@ -27,7 +27,7 @@ type API struct { AppUUID string `gorm:"size:36;not null;index;comment:关联的应用UUID" json:"app_uuid"` // 接口状态(1=启用,0=禁用) - Status int `gorm:"default:1;not null;comment:接口状态,1=启用,0=禁用" json:"status"` + Status int `gorm:"default:0;not null;comment:接口状态,1=启用,0=禁用" json:"status"` // 接口提交算法 // 支持的算法:0=不加密,1=RC4,2=RSA,3=RSA(动态),4=易加密 diff --git a/models/app.go b/models/app.go index a2537fe..a848a1a 100644 --- a/models/app.go +++ b/models/app.go @@ -25,7 +25,7 @@ type App struct { // UUID:应用唯一标识符,自动生成 UUID string `gorm:"uniqueIndex;size:36;not null;comment:应用UUID,唯一标识符" json:"uuid"` // Status:状态(1=启用,0=禁用);json 名称与前端一致 - Status int `gorm:"default:1;not null;comment:应用状态,1=启用,0=禁用" json:"status"` + Status int `gorm:"default:0;not null;comment:应用状态,1=启用,0=禁用" json:"status"` // Name:应用名称;json 名称与前端一致 Name string `gorm:"size:100;not null;comment:应用名称" json:"name"` // Secret:应用密钥,用于API认证 diff --git a/server/admin.go b/server/admin.go index 955c005..2851f27 100644 --- a/server/admin.go +++ b/server/admin.go @@ -81,6 +81,7 @@ func RegisterAdminRoutes(router *gin.Engine) { 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/update_status", adminctl.AdminAuthRequired(), adminctl.AppUpdateStatusHandler) 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) @@ -96,6 +97,7 @@ func RegisterAdminRoutes(router *gin.Engine) { // API接口管理API router.GET("/admin/api/apis/list", adminctl.AdminAuthRequired(), adminctl.APIListHandler) router.POST("/admin/api/apis/update", adminctl.AdminAuthRequired(), adminctl.APIUpdateHandler) + router.POST("/admin/api/apis/update_status", adminctl.AdminAuthRequired(), adminctl.APIUpdateStatusHandler) 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) diff --git a/web/public.go b/web/public.go index ab454b6..990a8f9 100644 --- a/web/public.go +++ b/web/public.go @@ -65,3 +65,9 @@ func GetStaticFS() (fs.FS, error) { // Go 顶级函数不支持箭头写法 } return staticFS, nil } + +// IsDevMode 检查是否为开发模式 +// 注意:这个函数保留用于向后兼容,建议使用 middleware.IsDevMode() +func IsDevMode() bool { + return viper.GetBool("server.dev_mode") +} diff --git a/web/template/admin/apis.html b/web/template/admin/apis.html index 8315240..5cbe70a 100644 --- a/web/template/admin/apis.html +++ b/web/template/admin/apis.html @@ -226,10 +226,10 @@ { field: 'status_name', title: '状态', - width: 80, + width: 100, templet: (d) => { - if (d.status === 1) return '启用'; - return '禁用'; + const checked = d.status === 1 ? 'checked' : ''; + return ``; } }, { @@ -626,6 +626,35 @@ } }); + // 接口状态switch开关事件监听 + form.on('switch(api-status-switch)', function(data) { + const apiId = data.elem.getAttribute('data-id'); + const status = data.elem.checked ? 1 : 0; + + $.ajax({ + url: '/admin/api/apis/update_status', + type: 'POST', + data: JSON.stringify({ id: parseInt(apiId), status: status }), + contentType: 'application/json', + success: function (res) { + if (res.code === 0) { + layer.msg(res.msg || '状态更新成功', { icon: 1 }); + } else { + layer.msg(res.msg || '状态更新失败', { icon: 2 }); + // 如果更新失败,恢复开关状态 + data.elem.checked = !data.elem.checked; + form.render('checkbox'); + } + }, + error: function (xhr) { + layer.msg(xhr.responseText || '状态更新失败', { icon: 2 }); + // 如果更新失败,恢复开关状态 + data.elem.checked = !data.elem.checked; + form.render('checkbox'); + } + }); + }); + }); diff --git a/web/template/admin/apps.html b/web/template/admin/apps.html index 090eaa0..3c31133 100644 --- a/web/template/admin/apps.html +++ b/web/template/admin/apps.html @@ -374,8 +374,8 @@ title: '应用状态', width: 100, templet: (d) => { - if (d.status === 1) return '启用'; - return '禁用'; + const checked = d.status === 1 ? 'checked' : ''; + return ``; } }, { @@ -1195,6 +1195,35 @@ }); }); + // 应用状态switch开关事件监听 + form.on('switch(app-status-switch)', function(data) { + const appId = data.elem.getAttribute('data-id'); + const status = data.elem.checked ? 1 : 0; + + $.ajax({ + url: '/admin/api/apps/update_status', + type: 'POST', + data: JSON.stringify({ id: parseInt(appId), status: status }), + contentType: 'application/json', + success: function (res) { + if (res.code === 0) { + layer.msg(res.msg || '状态更新成功', { icon: 1 }); + } else { + layer.msg(res.msg || '状态更新失败', { icon: 2 }); + // 如果更新失败,恢复开关状态 + data.elem.checked = !data.elem.checked; + form.render('checkbox'); + } + }, + error: function (xhr) { + layer.msg(xhr.responseText || '状态更新失败', { icon: 2 }); + // 如果更新失败,恢复开关状态 + data.elem.checked = !data.elem.checked; + form.render('checkbox'); + } + }); + }); + // Tips提示功能已移至admin.js统一管理 }); }); diff --git a/web/template/admin/dashboard.html b/web/template/admin/dashboard.html index 18734b6..372498c 100644 --- a/web/template/admin/dashboard.html +++ b/web/template/admin/dashboard.html @@ -11,25 +11,31 @@