Add binding Settings

This commit is contained in:
2025-10-24 05:09:22 +08:00
parent 7f09502d92
commit 1aff8ff459
4 changed files with 426 additions and 0 deletions

View File

@@ -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)
}

View File

@@ -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=单IP2=全部电脑(默认))
@@ -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地址验证相关字段
// IPVerifyIP地址验证0=关闭1=开启2=开启(市)3=开启(省)
IPVerify int `gorm:"default:0;not null;comment:IP地址验证0=关闭1=开启2=开启(市)3=开启(省)" json:"ip_verify"`
// IPOptionIP地址选项0=每天1=永久)
IPOption int `gorm:"default:0;not null;comment:IP地址选项0=每天1=永久" json:"ip_option"`
// IPFreeCountIP地址免费次数默认0
IPFreeCount int `gorm:"default:0;not null;comment:IP地址免费次数" json:"ip_free_count"`
// IPRebindCountIP地址重绑次数默认0
IPRebindCount int `gorm:"default:0;not null;comment:IP地址重绑次数" json:"ip_rebind_count"`
// IPRebindDeductIP地址重绑扣除默认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"`

View File

@@ -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用于仪表盘定时刷新

View File

@@ -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: '<div style="padding: 20px;">' +
'<form class="layui-form layui-form-pane" lay-filter="bindConfigForm">' +
// 机器码验证设置
'<fieldset class="layui-elem-field layui-field-title">' +
'<legend>机器码验证设置</legend>' +
'</fieldset>' +
'<div class="layui-form-item" pane>' +
'<label class="layui-form-label">机器码验证</label>' +
'<div class="layui-input-block">' +
'<input type="radio" name="machine_code_verify" value="0" title="关闭" ' + (config.machine_code_verify === 0 ? 'checked' : '') + '>' +
'<input type="radio" name="machine_code_verify" value="1" title="开启" ' + (config.machine_code_verify === 1 ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item" pane>' +
'<label class="layui-form-label">机器码选项</label>' +
'<div class="layui-input-block">' +
'<input type="radio" name="machine_code_option" value="0" title="每天" ' + (config.machine_code_option === 0 ? 'checked' : '') + '>' +
'<input type="radio" name="machine_code_option" value="1" title="永久" ' + (config.machine_code_option === 1 ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">免费次数</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="machine_code_free_count" class="layui-input" value="' + config.machine_code_free_count + '" placeholder="请输入" lay-verify="number" min="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">重绑次数</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="machine_code_rebind_count" class="layui-input" value="' + config.machine_code_rebind_count + '" placeholder="请输入" lay-verify="number" min="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">重绑扣除</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="machine_code_rebind_deduct" class="layui-input" value="' + config.machine_code_rebind_deduct + '" placeholder="请输入重绑扣除时间(分钟)" lay-verify="number" min="0">' +
'</div>' +
'</div>' +
// IP地址验证设置
'<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">' +
'<legend>IP地址验证设置</legend>' +
'</fieldset>' +
'<div class="layui-form-item" pane>' +
'<label class="layui-form-label">IP地址验证</label>' +
'<div class="layui-input-block">' +
'<input type="radio" name="ip_verify" value="0" title="关闭" ' + (config.ip_verify === 0 ? 'checked' : '') + '>' +
'<input type="radio" name="ip_verify" value="1" title="开启" ' + (config.ip_verify === 1 ? 'checked' : '') + '>' +
'<input type="radio" name="ip_verify" value="2" title="开启(市)" ' + (config.ip_verify === 2 ? 'checked' : '') + '>' +
'<input type="radio" name="ip_verify" value="3" title="开启(省)" ' + (config.ip_verify === 3 ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item" pane>' +
'<label class="layui-form-label">IP地址选项</label>' +
'<div class="layui-input-block">' +
'<input type="radio" name="ip_option" value="0" title="每天" ' + (config.ip_option === 0 ? 'checked' : '') + '>' +
'<input type="radio" name="ip_option" value="1" title="永久" ' + (config.ip_option === 1 ? 'checked' : '') + '>' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">免费次数</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="ip_free_count" class="layui-input" value="' + config.ip_free_count + '" placeholder="请输入" lay-verify="number" min="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">重绑次数</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="ip_rebind_count" class="layui-input" value="' + config.ip_rebind_count + '" placeholder="请输入" lay-verify="number" min="0">' +
'</div>' +
'</div>' +
'<div class="layui-form-item">' +
'<label class="layui-form-label">重绑扣除</label>' +
'<div class="layui-input-block">' +
'<input type="number" name="ip_rebind_deduct" class="layui-input" value="' + config.ip_rebind_deduct + '" placeholder="请输入重绑扣除时间(分钟)" lay-verify="number" min="0">' +
'</div>' +
'</div>' +
'</form>' +
'</div>',
btn: ['保存', '取消'],
yes: function(index, layero) {
var formData = {
uuid: obj.data.uuid,
machine_code_verify: parseInt($('input[name="machine_code_verify"]:checked').val()),
machine_code_option: parseInt($('input[name="machine_code_option"]:checked').val()),
machine_code_free_count: parseInt($('input[name="machine_code_free_count"]').val()) || 0,
machine_code_rebind_count: parseInt($('input[name="machine_code_rebind_count"]').val()) || 0,
machine_code_rebind_deduct: parseInt($('input[name="machine_code_rebind_deduct"]').val()) || 0,
ip_verify: parseInt($('input[name="ip_verify"]:checked').val()),
ip_option: parseInt($('input[name="ip_option"]:checked').val()),
ip_free_count: parseInt($('input[name="ip_free_count"]').val()) || 0,
ip_rebind_count: parseInt($('input[name="ip_rebind_count"]').val()) || 0,
ip_rebind_deduct: parseInt($('input[name="ip_rebind_deduct"]').val()) || 0
};
// 验证数据
if (isNaN(formData.machine_code_verify) || formData.machine_code_verify < 0 || formData.machine_code_verify > 1) {
layer.msg('请选择机器码验证选项', {icon: 2});
return;
}
if (isNaN(formData.machine_code_option) || formData.machine_code_option < 0 || formData.machine_code_option > 1) {
layer.msg('请选择机器码选项', {icon: 2});
return;
}
if (isNaN(formData.ip_verify) || formData.ip_verify < 0 || formData.ip_verify > 3) {
layer.msg('请选择IP地址验证选项', {icon: 2});
return;
}
if (isNaN(formData.ip_option) || formData.ip_option < 0 || formData.ip_option > 1) {
layer.msg('请选择IP地址选项', {icon: 2});
return;
}
// 发送更新请求
$.ajax({
url: '/admin/api/apps/update_bind_config',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function(res) {
if (res.code === 0) {
layer.msg('绑定设置更新成功', {icon: 1});
layer.close(index);
table.reload('appsTable');
} else {
layer.msg(res.msg || '更新绑定设置失败', {icon: 2});
}
},
error: function() {
layer.msg('网络错误,请稍后重试', {icon: 2});
}
});
},
btn2: function(index) {
layer.close(index);
},
success: function() {
// 重新渲染表单
form.render();
}
});
},
error: function() {
layer.msg('获取绑定设置失败,请稍后重试', {icon: 2});
}
});
}
},
align: 'right', // 右对齐弹出