diff --git a/controllers/admin/app.go b/controllers/admin/app.go index c4da5ab..6541493 100644 --- a/controllers/admin/app.go +++ b/controllers/admin/app.go @@ -745,3 +745,238 @@ func AppUpdateMultiConfigHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"message": "多开配置更新成功"}) } + +// 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") + if appUUID == "" { + response := map[string]interface{}{ + "code": 1, + "msg": "缺少应用UUID", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 验证UUID格式 + if _, err := uuid.Parse(appUUID); err != nil { + response := map[string]interface{}{ + "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) + 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{}{ + "code": 1, + "msg": "应用不存在", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 返回绑定配置信息 + response := map[string]interface{}{ + "machine_code_verify": app.MachineCodeVerify, + "machine_code_option": app.MachineCodeOption, + "machine_code_free_count": app.MachineCodeFreeCount, + "machine_code_rebind_count": app.MachineCodeRebindCount, + "machine_code_rebind_deduct": app.MachineCodeRebindDeduct, + "ip_verify": app.IPVerify, + "ip_option": app.IPOption, + "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) +} + +// AppUpdateBindConfigHandler 更新应用绑定配置 +func AppUpdateBindConfigHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var req struct { + UUID string `json:"uuid"` + MachineCodeVerify int `json:"machine_code_verify"` + MachineCodeOption int `json:"machine_code_option"` + MachineCodeFreeCount int `json:"machine_code_free_count"` + MachineCodeRebindCount int `json:"machine_code_rebind_count"` + MachineCodeRebindDeduct int `json:"machine_code_rebind_deduct"` + IPVerify int `json:"ip_verify"` + IPOption int `json:"ip_option"` + 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) + return + } + + // 验证UUID + if req.UUID == "" { + response := map[string]interface{}{ + "code": 1, + "msg": "应用UUID不能为空", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + if _, err := uuid.Parse(req.UUID); err != nil { + response := map[string]interface{}{ + "code": 1, + "msg": "无效的UUID格式", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 验证参数范围 + if req.MachineCodeVerify < 0 || req.MachineCodeVerify > 1 { + response := map[string]interface{}{ + "code": 1, + "msg": "机器码验证参数无效", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + if req.MachineCodeOption < 0 || req.MachineCodeOption > 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.IPOption < 0 || req.IPOption > 1 { + response := map[string]interface{}{ + "code": 1, + "msg": "IP地址选项参数无效", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + if req.MachineCodeFreeCount < 0 || req.MachineCodeRebindCount < 0 || req.MachineCodeRebindDeduct < 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) + 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") + response := map[string]interface{}{ + "code": 1, + "msg": "应用不存在", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + // 更新绑定配置 + updates := map[string]interface{}{ + "machine_code_verify": req.MachineCodeVerify, + "machine_code_option": req.MachineCodeOption, + "machine_code_free_count": req.MachineCodeFreeCount, + "machine_code_rebind_count": req.MachineCodeRebindCount, + "machine_code_rebind_deduct": req.MachineCodeRebindDeduct, + "ip_verify": req.IPVerify, + "ip_option": req.IPOption, + "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{}{ + "code": 1, + "msg": "更新绑定配置失败", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + return + } + + response := map[string]interface{}{ + "code": 0, + "msg": "绑定配置更新成功", + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} diff --git a/models/app.go b/models/app.go index 594a2b3..263dbf5 100644 --- a/models/app.go +++ b/models/app.go @@ -38,8 +38,10 @@ type App struct { DownloadType int `gorm:"default:0;not null;comment:更新方式,0=不启用更新,1=自动更新,2=手动下载" json:"download_type"` // DownloadURL:下载地址 DownloadURL string `gorm:"size:500;comment:下载地址" json:"download_url"` + // Announcement:程序公告内容(base64编码存储) Announcement string `gorm:"type:text;comment:程序公告内容,base64编码存储" json:"announcement"` + // LoginType:登陆方式(0=顶号登录(默认),1=非顶号登录) LoginType int `gorm:"default:0;not null;comment:登陆方式,0=顶号登录,1=非顶号登录" json:"login_type"` // MultiOpenScope:多开范围(0=单电脑,1=单IP,2=全部电脑(默认)) @@ -50,6 +52,31 @@ type App struct { CheckInterval int `gorm:"default:10;not null;comment:校验间隔,单位分钟" json:"check_interval"` // MultiOpenCount:多开数量(默认1) MultiOpenCount int `gorm:"default:1;not null;comment:多开数量" json:"multi_open_count"` + + // 机器码验证相关字段 + // MachineCodeVerify:机器码验证(0=关闭,1=开启) + MachineCodeVerify int `gorm:"default:0;not null;comment:机器码验证,0=关闭,1=开启" json:"machine_code_verify"` + // MachineCodeOption:机器码选项(0=每天,1=永久) + MachineCodeOption int `gorm:"default:0;not null;comment:机器码选项,0=每天,1=永久" json:"machine_code_option"` + // MachineCodeFreeCount:机器码免费次数(默认0) + MachineCodeFreeCount int `gorm:"default:0;not null;comment:机器码免费次数" json:"machine_code_free_count"` + // MachineCodeRebindCount:机器码重绑次数(默认0) + MachineCodeRebindCount int `gorm:"default:0;not null;comment:机器码重绑次数" json:"machine_code_rebind_count"` + // MachineCodeRebindDeduct:机器码重绑扣除(默认0,单位:分钟) + MachineCodeRebindDeduct int `gorm:"default:0;not null;comment:机器码重绑扣除,单位分钟" json:"machine_code_rebind_deduct"` + + // IP地址验证相关字段 + // IPVerify:IP地址验证(0=关闭,1=开启,2=开启(市),3=开启(省)) + IPVerify int `gorm:"default:0;not null;comment:IP地址验证,0=关闭,1=开启,2=开启(市),3=开启(省)" json:"ip_verify"` + // IPOption:IP地址选项(0=每天,1=永久) + IPOption int `gorm:"default:0;not null;comment:IP地址选项,0=每天,1=永久" json:"ip_option"` + // IPFreeCount:IP地址免费次数(默认0) + IPFreeCount int `gorm:"default:0;not null;comment:IP地址免费次数" json:"ip_free_count"` + // IPRebindCount:IP地址重绑次数(默认0) + IPRebindCount int `gorm:"default:0;not null;comment:IP地址重绑次数" json:"ip_rebind_count"` + // IPRebindDeduct:IP地址重绑扣除(默认0,单位:分钟) + IPRebindDeduct int `gorm:"default:0;not null;comment:IP地址重绑扣除,单位分钟" json:"ip_rebind_deduct"` + // CreatedAt/UpdatedAt:时间字段,返回为 created_at/updated_at,便于前端展示 CreatedAt time.Time `gorm:"comment:创建时间" json:"created_at"` UpdatedAt time.Time `gorm:"comment:更新时间" json:"updated_at"` diff --git a/server/admin.go b/server/admin.go index a681d0c..818c12c 100644 --- a/server/admin.go +++ b/server/admin.go @@ -65,6 +65,8 @@ func RegisterAdminRoutes(mux *http.ServeMux) { mux.HandleFunc("/admin/api/apps/update_announcement", adminctl.AdminAuthRequired(adminctl.AppUpdateAnnouncementHandler)) mux.HandleFunc("/admin/api/apps/get_multi_config", adminctl.AdminAuthRequired(adminctl.AppGetMultiConfigHandler)) mux.HandleFunc("/admin/api/apps/update_multi_config", adminctl.AdminAuthRequired(adminctl.AppUpdateMultiConfigHandler)) + mux.HandleFunc("/admin/api/apps/get_bind_config", adminctl.AdminAuthRequired(adminctl.AppGetBindConfigHandler)) + mux.HandleFunc("/admin/api/apps/update_bind_config", adminctl.AdminAuthRequired(adminctl.AppUpdateBindConfigHandler)) // 系统信息API(用于仪表盘定时刷新) diff --git a/web/template/admin/apps.html b/web/template/admin/apps.html index 5157f4e..f19ba91 100644 --- a/web/template/admin/apps.html +++ b/web/template/admin/apps.html @@ -326,6 +326,10 @@ title: '多开配置', id: 'multi_instance' }, + { + title: '绑定设置', + id: 'bind_settings' + }, { title: '重置密钥', id: 'reset_secret' @@ -549,6 +553,164 @@ }); layer.close(index); }); + } else if (menudata.id === 'bind_settings') { + // 绑定设置 + $.ajax({ + url: '/admin/api/apps/get_bind_config?uuid=' + obj.data.uuid, + type: 'GET', + success: function(config) { + layer.open({ + type: 1, + title: '绑定设置 - ' + obj.data.name, + area: ['650px', '600px'], + content: '