mirror of
https://github.com/skyle1995/NetworkAuth.git
synced 2026-05-25 02:24:05 +08:00
The development mode supports hot reloading of templates
This commit is contained in:
@@ -105,6 +105,9 @@ func createHTTPServer(addr string) *http.Server {
|
|||||||
// 添加日志中间件
|
// 添加日志中间件
|
||||||
router.Use(middleware.WrapHandler())
|
router.Use(middleware.WrapHandler())
|
||||||
|
|
||||||
|
// 添加开发模式中间件(统一管理开发模式功能)
|
||||||
|
router.Use(middleware.DevModeMiddleware(router))
|
||||||
|
|
||||||
// 加载模板
|
// 加载模板
|
||||||
if err := loadTemplates(router); err != nil {
|
if err := loadTemplates(router); err != nil {
|
||||||
logrus.WithError(err).Fatal("模板加载失败")
|
logrus.WithError(err).Fatal("模板加载失败")
|
||||||
|
|||||||
@@ -274,6 +274,55 @@ func APIGetTypesHandler(c *gin.Context) {
|
|||||||
apiBaseController.HandleSuccess(c, "获取接口类型列表成功", apiTypes)
|
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) {
|
func APIGenerateKeysHandler(c *gin.Context) {
|
||||||
var req struct {
|
var req struct {
|
||||||
Side string `json:"side"` // submit | return
|
Side string `json:"side"` // submit | return
|
||||||
|
|||||||
@@ -339,25 +339,25 @@ func AppCreateHandler(c *gin.Context) {
|
|||||||
|
|
||||||
// 为应用创建所有默认接口
|
// 为应用创建所有默认接口
|
||||||
defaultAPITypes := []int{
|
defaultAPITypes := []int{
|
||||||
models.APITypeGetBulletin, // 获取程序公告
|
models.APITypeGetBulletin, // 获取程序公告
|
||||||
models.APITypeGetUpdateUrl, // 获取更新地址
|
models.APITypeGetUpdateUrl, // 获取更新地址
|
||||||
models.APITypeCheckAppVersion, // 检测最新版本
|
models.APITypeCheckAppVersion, // 检测最新版本
|
||||||
models.APITypeGetCardInfo, // 获取卡密信息
|
models.APITypeGetCardInfo, // 获取卡密信息
|
||||||
models.APITypeSingleLogin, // 卡密登录
|
models.APITypeSingleLogin, // 卡密登录
|
||||||
models.APITypeUserLogin, // 用户登录
|
models.APITypeUserLogin, // 用户登录
|
||||||
models.APITypeUserRegin, // 用户注册
|
models.APITypeUserRegin, // 用户注册
|
||||||
models.APITypeUserRecharge, // 用户充值
|
models.APITypeUserRecharge, // 用户充值
|
||||||
models.APITypeCardRegin, // 卡密注册
|
models.APITypeCardRegin, // 卡密注册
|
||||||
models.APITypeLogOut, // 退出登录
|
models.APITypeLogOut, // 退出登录
|
||||||
models.APITypeGetExpired, // 获取到期时间
|
models.APITypeGetExpired, // 获取到期时间
|
||||||
models.APITypeCheckUserStatus, // 检测账号状态
|
models.APITypeCheckUserStatus, // 检测账号状态
|
||||||
models.APITypeGetAppData, // 获取程序数据
|
models.APITypeGetAppData, // 获取程序数据
|
||||||
models.APITypeGetVariable, // 获取变量数据
|
models.APITypeGetVariable, // 获取变量数据
|
||||||
models.APITypeUpdatePwd, // 修改账号密码
|
models.APITypeUpdatePwd, // 修改账号密码
|
||||||
models.APITypeMacChangeBind, // 机器码转绑
|
models.APITypeMacChangeBind, // 机器码转绑
|
||||||
models.APITypeIPChangeBind, // IP转绑
|
models.APITypeIPChangeBind, // IP转绑
|
||||||
models.APITypeDisableUser, // 封停用户
|
models.APITypeDisableUser, // 封停用户
|
||||||
models.APITypeBlackUser, // 添加黑名单
|
models.APITypeBlackUser, // 添加黑名单
|
||||||
models.APITypeUserDeductedTime, // 扣除时间
|
models.APITypeUserDeductedTime, // 扣除时间
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +366,7 @@ func AppCreateHandler(c *gin.Context) {
|
|||||||
api := models.API{
|
api := models.API{
|
||||||
APIType: apiType,
|
APIType: apiType,
|
||||||
AppUUID: app.UUID,
|
AppUUID: app.UUID,
|
||||||
Status: 1, // 默认启用
|
Status: 0, // 默认禁用
|
||||||
SubmitAlgorithm: models.AlgorithmNone, // 默认不加密
|
SubmitAlgorithm: models.AlgorithmNone, // 默认不加密
|
||||||
ReturnAlgorithm: models.AlgorithmNone, // 默认不加密
|
ReturnAlgorithm: models.AlgorithmNone, // 默认不加密
|
||||||
}
|
}
|
||||||
@@ -928,18 +928,18 @@ func AppGetBindConfigHandler(c *gin.Context) {
|
|||||||
"code": 0,
|
"code": 0,
|
||||||
"msg": "获取绑定配置成功",
|
"msg": "获取绑定配置成功",
|
||||||
"data": gin.H{
|
"data": gin.H{
|
||||||
"machine_verify": app.MachineVerify,
|
"machine_verify": app.MachineVerify,
|
||||||
"machine_rebind_enabled": app.MachineRebindEnabled,
|
"machine_rebind_enabled": app.MachineRebindEnabled,
|
||||||
"machine_rebind_limit": app.MachineRebindLimit,
|
"machine_rebind_limit": app.MachineRebindLimit,
|
||||||
"machine_free_count": app.MachineFreeCount,
|
"machine_free_count": app.MachineFreeCount,
|
||||||
"machine_rebind_count": app.MachineRebindCount,
|
"machine_rebind_count": app.MachineRebindCount,
|
||||||
"machine_rebind_deduct": app.MachineRebindDeduct,
|
"machine_rebind_deduct": app.MachineRebindDeduct,
|
||||||
"ip_verify": app.IPVerify,
|
"ip_verify": app.IPVerify,
|
||||||
"ip_rebind_enabled": app.IPRebindEnabled,
|
"ip_rebind_enabled": app.IPRebindEnabled,
|
||||||
"ip_rebind_limit": app.IPRebindLimit,
|
"ip_rebind_limit": app.IPRebindLimit,
|
||||||
"ip_free_count": app.IPFreeCount,
|
"ip_free_count": app.IPFreeCount,
|
||||||
"ip_rebind_count": app.IPRebindCount,
|
"ip_rebind_count": app.IPRebindCount,
|
||||||
"ip_rebind_deduct": app.IPRebindDeduct,
|
"ip_rebind_deduct": app.IPRebindDeduct,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -948,19 +948,19 @@ func AppGetBindConfigHandler(c *gin.Context) {
|
|||||||
func AppUpdateBindConfigHandler(c *gin.Context) {
|
func AppUpdateBindConfigHandler(c *gin.Context) {
|
||||||
// 解析请求体
|
// 解析请求体
|
||||||
var req struct {
|
var req struct {
|
||||||
UUID string `json:"uuid"`
|
UUID string `json:"uuid"`
|
||||||
MachineVerify int `json:"machine_verify"`
|
MachineVerify int `json:"machine_verify"`
|
||||||
MachineRebindEnabled int `json:"machine_rebind_enabled"`
|
MachineRebindEnabled int `json:"machine_rebind_enabled"`
|
||||||
MachineRebindLimit int `json:"machine_rebind_limit"`
|
MachineRebindLimit int `json:"machine_rebind_limit"`
|
||||||
MachineFreeCount int `json:"machine_free_count"`
|
MachineFreeCount int `json:"machine_free_count"`
|
||||||
MachineRebindCount int `json:"machine_rebind_count"`
|
MachineRebindCount int `json:"machine_rebind_count"`
|
||||||
MachineRebindDeduct int `json:"machine_rebind_deduct"`
|
MachineRebindDeduct int `json:"machine_rebind_deduct"`
|
||||||
IPVerify int `json:"ip_verify"`
|
IPVerify int `json:"ip_verify"`
|
||||||
IPRebindEnabled int `json:"ip_rebind_enabled"`
|
IPRebindEnabled int `json:"ip_rebind_enabled"`
|
||||||
IPRebindLimit int `json:"ip_rebind_limit"`
|
IPRebindLimit int `json:"ip_rebind_limit"`
|
||||||
IPFreeCount int `json:"ip_free_count"`
|
IPFreeCount int `json:"ip_free_count"`
|
||||||
IPRebindCount int `json:"ip_rebind_count"`
|
IPRebindCount int `json:"ip_rebind_count"`
|
||||||
IPRebindDeduct int `json:"ip_rebind_deduct"`
|
IPRebindDeduct int `json:"ip_rebind_deduct"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if !appBaseController.BindJSON(c, &req) {
|
if !appBaseController.BindJSON(c, &req) {
|
||||||
@@ -1005,18 +1005,18 @@ func AppUpdateBindConfigHandler(c *gin.Context) {
|
|||||||
|
|
||||||
// 更新绑定配置
|
// 更新绑定配置
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
"machine_verify": req.MachineVerify,
|
"machine_verify": req.MachineVerify,
|
||||||
"machine_rebind_enabled": req.MachineRebindEnabled,
|
"machine_rebind_enabled": req.MachineRebindEnabled,
|
||||||
"machine_rebind_limit": req.MachineRebindLimit,
|
"machine_rebind_limit": req.MachineRebindLimit,
|
||||||
"machine_free_count": req.MachineFreeCount,
|
"machine_free_count": req.MachineFreeCount,
|
||||||
"machine_rebind_count": req.MachineRebindCount,
|
"machine_rebind_count": req.MachineRebindCount,
|
||||||
"machine_rebind_deduct": req.MachineRebindDeduct,
|
"machine_rebind_deduct": req.MachineRebindDeduct,
|
||||||
"ip_verify": req.IPVerify,
|
"ip_verify": req.IPVerify,
|
||||||
"ip_rebind_enabled": req.IPRebindEnabled,
|
"ip_rebind_enabled": req.IPRebindEnabled,
|
||||||
"ip_rebind_limit": req.IPRebindLimit,
|
"ip_rebind_limit": req.IPRebindLimit,
|
||||||
"ip_free_count": req.IPFreeCount,
|
"ip_free_count": req.IPFreeCount,
|
||||||
"ip_rebind_count": req.IPRebindCount,
|
"ip_rebind_count": req.IPRebindCount,
|
||||||
"ip_rebind_deduct": req.IPRebindDeduct,
|
"ip_rebind_deduct": req.IPRebindDeduct,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Model(&app).Updates(updates).Error; err != nil {
|
if err := db.Model(&app).Updates(updates).Error; err != nil {
|
||||||
@@ -1334,3 +1334,67 @@ func AppsBatchUpdateStatusHandler(c *gin.Context) {
|
|||||||
"msg": "批量" + statusText + "成功",
|
"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 + "成功",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"networkDev/controllers"
|
"networkDev/controllers"
|
||||||
|
"networkDev/middleware"
|
||||||
"networkDev/utils"
|
"networkDev/utils"
|
||||||
|
|
||||||
"github.com/mojocn/base64Captcha"
|
"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 {
|
func VerifyCaptcha(c *gin.Context, captchaValue string) bool {
|
||||||
// 检查是否为开发模式,如果是则跳过验证码验证
|
// 检查是否为开发模式,如果是则跳过验证码验证
|
||||||
if viper.GetBool("server.dev_mode") {
|
if middleware.ShouldSkipCaptcha(c) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"networkDev/constants"
|
"networkDev/constants"
|
||||||
"networkDev/controllers"
|
"networkDev/controllers"
|
||||||
|
"networkDev/middleware"
|
||||||
"networkDev/models"
|
"networkDev/models"
|
||||||
"networkDev/services"
|
"networkDev/services"
|
||||||
"networkDev/utils"
|
"networkDev/utils"
|
||||||
@@ -97,7 +98,7 @@ func AdminLayoutHandler(c *gin.Context) {
|
|||||||
// - 展示系统信息:版本、开发模式、数据库类型、启动时长
|
// - 展示系统信息:版本、开发模式、数据库类型、启动时长
|
||||||
func DashboardFragmentHandler(c *gin.Context) {
|
func DashboardFragmentHandler(c *gin.Context) {
|
||||||
version := constants.AppVersion
|
version := constants.AppVersion
|
||||||
mode := viper.GetBool("server.dev_mode")
|
mode := middleware.IsDevModeFromContext(c)
|
||||||
dbType := viper.GetString("database.type")
|
dbType := viper.GetString("database.type")
|
||||||
if dbType == "" {
|
if dbType == "" {
|
||||||
dbType = "sqlite"
|
dbType = "sqlite"
|
||||||
@@ -118,7 +119,7 @@ func DashboardFragmentHandler(c *gin.Context) {
|
|||||||
// - 返回系统运行状态的JSON数据,用于前端定时刷新
|
// - 返回系统运行状态的JSON数据,用于前端定时刷新
|
||||||
func SystemInfoHandler(c *gin.Context) {
|
func SystemInfoHandler(c *gin.Context) {
|
||||||
version := constants.AppVersion
|
version := constants.AppVersion
|
||||||
mode := viper.GetBool("server.dev_mode")
|
mode := middleware.IsDevModeFromContext(c)
|
||||||
dbType := viper.GetString("database.type")
|
dbType := viper.GetString("database.type")
|
||||||
if dbType == "" {
|
if dbType == "" {
|
||||||
dbType = "sqlite"
|
dbType = "sqlite"
|
||||||
|
|||||||
100
middleware/devmode.go
Normal file
100
middleware/devmode.go
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ type API struct {
|
|||||||
AppUUID string `gorm:"size:36;not null;index;comment:关联的应用UUID" json:"app_uuid"`
|
AppUUID string `gorm:"size:36;not null;index;comment:关联的应用UUID" json:"app_uuid"`
|
||||||
|
|
||||||
// 接口状态(1=启用,0=禁用)
|
// 接口状态(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=易加密
|
// 支持的算法:0=不加密,1=RC4,2=RSA,3=RSA(动态),4=易加密
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ type App struct {
|
|||||||
// UUID:应用唯一标识符,自动生成
|
// UUID:应用唯一标识符,自动生成
|
||||||
UUID string `gorm:"uniqueIndex;size:36;not null;comment:应用UUID,唯一标识符" json:"uuid"`
|
UUID string `gorm:"uniqueIndex;size:36;not null;comment:应用UUID,唯一标识符" json:"uuid"`
|
||||||
// Status:状态(1=启用,0=禁用);json 名称与前端一致
|
// 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:应用名称;json 名称与前端一致
|
||||||
Name string `gorm:"size:100;not null;comment:应用名称" json:"name"`
|
Name string `gorm:"size:100;not null;comment:应用名称" json:"name"`
|
||||||
// Secret:应用密钥,用于API认证
|
// Secret:应用密钥,用于API认证
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ func RegisterAdminRoutes(router *gin.Engine) {
|
|||||||
router.POST("/admin/api/apps/delete", adminctl.AdminAuthRequired(), adminctl.AppDeleteHandler)
|
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_delete", adminctl.AdminAuthRequired(), adminctl.AppsBatchDeleteHandler)
|
||||||
router.POST("/admin/api/apps/batch_update_status", adminctl.AdminAuthRequired(), adminctl.AppsBatchUpdateStatusHandler)
|
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.POST("/admin/api/apps/reset_secret", adminctl.AdminAuthRequired(), adminctl.AppResetSecretHandler)
|
||||||
router.GET("/admin/api/apps/get_app_data", adminctl.AdminAuthRequired(), adminctl.AppGetAppDataHandler)
|
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.POST("/admin/api/apps/update_app_data", adminctl.AdminAuthRequired(), adminctl.AppUpdateAppDataHandler)
|
||||||
@@ -96,6 +97,7 @@ func RegisterAdminRoutes(router *gin.Engine) {
|
|||||||
// API接口管理API
|
// API接口管理API
|
||||||
router.GET("/admin/api/apis/list", adminctl.AdminAuthRequired(), adminctl.APIListHandler)
|
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", 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/apps", adminctl.AdminAuthRequired(), adminctl.APIGetAppsHandler)
|
||||||
router.GET("/admin/api/apis/types", adminctl.AdminAuthRequired(), adminctl.APIGetTypesHandler)
|
router.GET("/admin/api/apis/types", adminctl.AdminAuthRequired(), adminctl.APIGetTypesHandler)
|
||||||
router.POST("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(), adminctl.APIGenerateKeysHandler)
|
router.POST("/admin/api/apis/generate_keys", adminctl.AdminAuthRequired(), adminctl.APIGenerateKeysHandler)
|
||||||
|
|||||||
@@ -65,3 +65,9 @@ func GetStaticFS() (fs.FS, error) { // Go 顶级函数不支持箭头写法
|
|||||||
}
|
}
|
||||||
return staticFS, nil
|
return staticFS, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDevMode 检查是否为开发模式
|
||||||
|
// 注意:这个函数保留用于向后兼容,建议使用 middleware.IsDevMode()
|
||||||
|
func IsDevMode() bool {
|
||||||
|
return viper.GetBool("server.dev_mode")
|
||||||
|
}
|
||||||
|
|||||||
@@ -226,10 +226,10 @@
|
|||||||
{
|
{
|
||||||
field: 'status_name',
|
field: 'status_name',
|
||||||
title: '状态',
|
title: '状态',
|
||||||
width: 80,
|
width: 100,
|
||||||
templet: (d) => {
|
templet: (d) => {
|
||||||
if (d.status === 1) return '<span style="color: #5FB878;">启用</span>';
|
const checked = d.status === 1 ? 'checked' : '';
|
||||||
return '<span style="color: #FF5722;">禁用</span>';
|
return `<input type="checkbox" ${checked} lay-skin="switch" lay-text="启用|禁用" lay-filter="api-status-switch" data-id="${d.id}">`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -374,8 +374,8 @@
|
|||||||
title: '应用状态',
|
title: '应用状态',
|
||||||
width: 100,
|
width: 100,
|
||||||
templet: (d) => {
|
templet: (d) => {
|
||||||
if (d.status === 1) return '<span style="color: #5FB878;">启用</span>';
|
const checked = d.status === 1 ? 'checked' : '';
|
||||||
return '<span style="color: #FF5722;">禁用</span>';
|
return `<input type="checkbox" name="app-status-${d.id}" lay-skin="switch" lay-text="启用|禁用" ${checked} lay-filter="app-status-switch" data-id="${d.id}">`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -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统一管理
|
// Tips提示功能已移至admin.js统一管理
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,25 +11,31 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 120px; font-weight: bold;">程序版本</td>
|
<td style="width: 120px; font-weight: bold;">程序版本</td>
|
||||||
<td><span style="font-size: 18px; font-weight: bold; color: var(--lay-color-normal);">{{ .Version }}</span></td>
|
<td style="height: 20px; vertical-align: middle;">
|
||||||
|
<span class="layui-badge layui-bg-blue" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">v{{ .Version }}</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="font-weight: bold;">存储方案</td>
|
<td style="font-weight: bold;">存储方案</td>
|
||||||
<td><span style="font-size: 18px; font-weight: bold; color: var(--lay-color-info);">{{ .DBType }}</span></td>
|
<td style="height: 20px; vertical-align: middle;">
|
||||||
|
<span class="layui-badge layui-bg-cyan" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">{{ .DBType }}</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="font-weight: bold;">开发模式</td>
|
<td style="font-weight: bold;">开发模式</td>
|
||||||
<td>
|
<td style="height: 20px; vertical-align: middle;">
|
||||||
{{ if .Mode }}
|
{{ if .Mode }}
|
||||||
<span style="font-size: 18px; font-weight: bold; color: var(--lay-color-danger);">开启</span>
|
<span class="layui-badge layui-bg-orange" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">开启</span>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<span style="font-size: 18px; font-weight: bold; color: var(--lay-color-success);">关闭</span>
|
<span class="layui-badge layui-bg-green" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">关闭</span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="font-weight: bold;">运行时长</td>
|
<td style="font-weight: bold;">运行时长</td>
|
||||||
<td><span id="uptime-display" style="font-size: 18px; font-weight: bold; color: var(--lay-color-normal);">{{ .Uptime }}</span></td>
|
<td style="height: 20px; vertical-align: middle;">
|
||||||
|
<span id="uptime-display" class="layui-badge layui-bg-gray" style="font-size: 14px; padding: 2px 8px; line-height: 1.2;">{{ .Uptime }}</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -46,19 +52,27 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 120px; font-weight: bold;">全部应用</td>
|
<td style="width: 120px; font-weight: bold;">全部应用</td>
|
||||||
<td><span id="total-apps" style="font-size: 18px; font-weight: bold;">0</span></td>
|
<td style="height: 20px; vertical-align: middle;">
|
||||||
|
<span id="total-apps" class="layui-badge" style="font-size: 14px; padding: 2px 8px; min-width: 30px; text-align: center; line-height: 1.2;">0</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="font-weight: bold;">启用应用</td>
|
<td style="font-weight: bold;">启用应用</td>
|
||||||
<td><span id="enabled-apps" style="font-size: 18px; font-weight: bold; color: var(--lay-color-success);">0</span></td>
|
<td style="height: 20px; vertical-align: middle;">
|
||||||
|
<span id="enabled-apps" class="layui-badge layui-bg-green" style="font-size: 14px; padding: 2px 8px; min-width: 30px; text-align: center; line-height: 1.2;">0</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="font-weight: bold;">禁用应用</td>
|
<td style="font-weight: bold;">禁用应用</td>
|
||||||
<td><span id="disabled-apps" style="font-size: 18px; font-weight: bold; color: var(--lay-color-danger);">0</span></td>
|
<td style="height: 20px; vertical-align: middle;">
|
||||||
|
<span id="disabled-apps" class="layui-badge layui-bg-orange" style="font-size: 14px; padding: 2px 8px; min-width: 30px; text-align: center; line-height: 1.2;">0</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="font-weight: bold;">变量数量</td>
|
<td style="font-weight: bold;">变量数量</td>
|
||||||
<td><span id="total-variables" style="font-size: 18px; font-weight: bold; color: var(--lay-color-info);">0</span></td>
|
<td style="height: 20px; vertical-align: middle;">
|
||||||
|
<span id="total-variables" class="layui-badge layui-bg-blue" style="font-size: 14px; padding: 2px 8px; min-width: 30px; text-align: center; line-height: 1.2;">0</span>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -98,9 +112,19 @@
|
|||||||
$.get('/admin/api/system/info', (res) => {
|
$.get('/admin/api/system/info', (res) => {
|
||||||
if (res && res.code === 0 && res.data) {
|
if (res && res.code === 0 && res.data) {
|
||||||
const data = res.data;
|
const data = res.data;
|
||||||
// 更新运行时长
|
// 更新运行时长,保持徽章样式
|
||||||
if (data.uptime) {
|
if (data.uptime) {
|
||||||
$('#uptime-display').text(data.uptime);
|
const uptimeElement = $('#uptime-display');
|
||||||
|
uptimeElement.text(data.uptime);
|
||||||
|
// 确保徽章样式保持一致
|
||||||
|
if (!uptimeElement.hasClass('layui-badge')) {
|
||||||
|
uptimeElement.addClass('layui-badge layui-bg-gray');
|
||||||
|
uptimeElement.css({
|
||||||
|
'font-size': '14px',
|
||||||
|
'padding': '2px 8px',
|
||||||
|
'line-height': '1.2'
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).fail(() => {
|
}).fail(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user