diff --git a/go/controllers/api_getcard.go b/go/controllers/api_getcard.go index f0a87a7..2abb825 100644 --- a/go/controllers/api_getcard.go +++ b/go/controllers/api_getcard.go @@ -66,6 +66,15 @@ func (c *ApiGetCardController) GetCard() { return } + // 读取机器码/MAC + machineCode := strings.TrimSpace(c.GetString("machine_code")) + if machineCode == "" { + machineCode = strings.TrimSpace(c.GetString("machineCode")) + } + if machineCode == "" { + machineCode = strings.TrimSpace(c.GetString("mac")) + } + // 参数校验 if platform == "" { c.cardErr(400, 400, "缺少参数 type(来源平台)") @@ -92,7 +101,7 @@ func (c *ApiGetCardController) GetCard() { switch module { case "cursor": - c.extractCursor(platform, dataType, startID, now) + c.extractCursor(platform, dataType, startID, now, machineCode) case "windsurf": c.extractWindsurf(platform, dataType, startID, now) case "krio": @@ -121,8 +130,25 @@ func (c *ApiGetCardController) readOptionalStartID() (uint64, error) { return id, nil } -func (c *ApiGetCardController) extractCursor(platform, dataType string, startID uint64, now time.Time) { - c.extractWithProbe("cursor", platform, dataType, now, func() (uint64, *string, *string, string, string, *int8, error) { +func (c *ApiGetCardController) extractCursor(platform, dataType string, startID uint64, now time.Time, machineCode string) { + // 优先查询该机器码是否已经绑定过未删除的卡密 + if machineCode != "" { + var existing models.PlatformAccountPoolCursor + err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). + Filter("machine_code", machineCode). + Filter("delete_time__isnull", true). + Exclude("is_used", 0). + OrderBy("-id"). + Limit(1). + One(&existing) + if err == nil { + // 直接返回已绑定的卡密信息 + c.cardOK(buildCardResult(&existing.Account, &existing.Password, existing.Token, existing.DataType)) + return + } + } + + c.extractWithProbe("cursor", platform, dataType, now, machineCode, func() (uint64, *string, *string, string, string, *int8, error) { var row models.PlatformAccountPoolCursor qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). Filter("is_extracted", 0). @@ -141,7 +167,7 @@ func (c *ApiGetCardController) extractCursor(platform, dataType string, startID } func (c *ApiGetCardController) extractWindsurf(platform, dataType string, startID uint64, now time.Time) { - c.extractWithProbe("windsurf", platform, dataType, now, func() (uint64, *string, *string, string, string, *int8, error) { + c.extractWithProbe("windsurf", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { var row models.PlatformAccountPoolWindsurf qs := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)). Filter("is_extracted", 0). @@ -160,7 +186,7 @@ func (c *ApiGetCardController) extractWindsurf(platform, dataType string, startI } func (c *ApiGetCardController) extractKrio(platform, dataType string, startID uint64, now time.Time) { - c.extractWithProbe("krio", platform, dataType, now, func() (uint64, *string, *string, string, string, *int8, error) { + c.extractWithProbe("krio", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { var row models.PlatformAccountPoolKiro qs := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)). Filter("is_extracted", 0). @@ -179,7 +205,7 @@ func (c *ApiGetCardController) extractKrio(platform, dataType string, startID ui } func (c *ApiGetCardController) extractCodex(platform, dataType string, startID uint64, now time.Time) { - c.extractWithProbe("codex", platform, dataType, now, func() (uint64, *string, *string, string, string, *int8, error) { + c.extractWithProbe("codex", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) { var row models.PlatformAccountPoolCodex qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)). Filter("is_extracted", 0). @@ -203,6 +229,7 @@ type poolRowFetcher func() (id uint64, account, password *string, token, rowData func (c *ApiGetCardController) extractWithProbe( module, platform, dataType string, now time.Time, + machineCode string, fetch poolRowFetcher, ) { for { @@ -221,14 +248,20 @@ func (c *ApiGetCardController) extractWithProbe( c.cardErr(500, 500, "无效模块") return } + + params := map[string]interface{}{ + "is_extracted": 1, + "extracted_time": now, + "extracted_platform": platform, + "update_time": now, + } + if module == "cursor" && machineCode != "" { + params["machine_code"] = machineCode + } + _, err = models.Orm.QueryTable(tableName). Filter("id", id). - Update(map[string]interface{}{ - "is_extracted": 1, - "extracted_time": now, - "extracted_platform": platform, - "update_time": now, - }) + Update(params) if err != nil { c.cardErr(500, 500, "提取失败") return @@ -240,10 +273,16 @@ func (c *ApiGetCardController) extractWithProbe( c.cardOK(buildCardResult(account, password, token, rowDataType)) return } + if module == "cursor" && machineCode != "" { + _, _ = models.Orm.QueryTable(tableName).Filter("id", id).Update(map[string]interface{}{"machine_code": "", "update_time": time.Now()}) + } continue } if !poolProbeToken(module, rowDataType, token, id) { + if module == "cursor" && machineCode != "" { + _, _ = models.Orm.QueryTable(tableName).Filter("id", id).Update(map[string]interface{}{"machine_code": "", "update_time": time.Now()}) + } continue } diff --git a/go/controllers/backend_login_verify.go b/go/controllers/backend_login_verify.go index 9a8e0a9..ed9684c 100644 --- a/go/controllers/backend_login_verify.go +++ b/go/controllers/backend_login_verify.go @@ -4,7 +4,6 @@ import ( "encoding/json" "io" "strings" - "time" "server/models" "server/pkg/jwtutil" @@ -209,39 +208,17 @@ func (c *BackendLoginVerifyController) SaveLoginVerifyInfos() { } } - now := time.Now() - var existed models.PlatformLoginVerify - err = models.Orm.QueryTable(new(models.PlatformLoginVerify)).OrderBy("-id").One(&existed) - if err == nil { - _, err = models.Orm.QueryTable(new(models.PlatformLoginVerify)). - Filter("id", existed.ID). - Update(map[string]interface{}{ - "open_verify_enabled": openVerifyEnabled, - "verify_type": verifyType, - "geetest3_id": geetest3ID, - "geetest3_key": geetest3Key, - "geetest4_id": geetest4ID, - "geetest4_key": geetest4Key, - "update_time": now, - }) - if err != nil { - c.jsonErr(500, 500, "保存失败") - return - } - } else { - row := &models.PlatformLoginVerify{ - OpenVerifyEnabled: openVerifyEnabled, - VerifyType: verifyType, - Geetest3ID: geetest3ID, - Geetest3Key: geetest3Key, - Geetest4ID: geetest4ID, - Geetest4Key: geetest4Key, - UpdateTime: &now, - } - if _, err := models.Orm.Insert(row); err != nil { - c.jsonErr(500, 500, "保存失败") - return - } + err = models.SavePlatformLoginVerify(&models.PlatformLoginVerify{ + OpenVerifyEnabled: openVerifyEnabled, + VerifyType: verifyType, + Geetest3ID: geetest3ID, + Geetest3Key: geetest3Key, + Geetest4ID: geetest4ID, + Geetest4Key: geetest4Key, + }) + if err != nil { + c.jsonErr(500, 500, "保存失败") + return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} diff --git a/go/controllers/platform_bark.go b/go/controllers/platform_bark.go new file mode 100644 index 0000000..bd3231b --- /dev/null +++ b/go/controllers/platform_bark.go @@ -0,0 +1,215 @@ +package controllers + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "server/models" + "server/pkg/jwtutil" + + beego "github.com/beego/beego/v2/server/web" +) + +type PlatformBarkController struct { + beego.Controller +} + +func (c *PlatformBarkController) platformClaims() (*jwtutil.Claims, error) { + auth := c.Ctx.Request.Header.Get("Authorization") + if auth == "" { + return nil, fmt.Errorf("未登录") + } + parts := strings.SplitN(auth, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + return nil, fmt.Errorf("认证信息格式错误") + } + claims, err := jwtutil.ParseToken(parts[1]) + if err != nil { + return nil, fmt.Errorf("无效的token") + } + if claims.UserType != "platform" { + return nil, fmt.Errorf("无权访问") + } + return claims, nil +} + +func (c *PlatformBarkController) jsonErr(httpStatus, bizCode int, msg string) { + c.Ctx.Output.SetStatus(httpStatus) + c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg} + _ = c.ServeJSON() +} + +// GetBarkInfo GET /platform/bark/info +func (c *PlatformBarkController) GetBarkInfo() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + enabledStr := models.GetPlatformSettingValue("bark_enabled", "0") + serverURL := models.GetPlatformSettingValue("bark_server_url", "https://api.day.app") + deviceKey := models.GetPlatformSettingValue("bark_device_key", "") + + enabled := false + if enabledStr == "1" { + enabled = true + } + + c.Data["json"] = map[string]interface{}{ + "code": 200, + "msg": "success", + "data": map[string]interface{}{ + "enabled": enabled, + "server_url": serverURL, + "device_key": deviceKey, + }, + } + _ = c.ServeJSON() +} + +type barkEditPayload struct { + Enabled bool `json:"enabled"` + ServerUrl string `json:"server_url"` + DeviceKey string `json:"device_key"` +} + +// EditBarkInfo POST /platform/bark/editinfo +func (c *PlatformBarkController) EditBarkInfo() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p barkEditPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + enabledStr := "0" + if p.Enabled { + enabledStr = "1" + } + + serverURL := strings.TrimSpace(p.ServerUrl) + if serverURL == "" { + serverURL = "https://api.day.app" + } + deviceKey := strings.TrimSpace(p.DeviceKey) + + settings := []struct { + code string + name string + value string + remark string + }{ + {"bark_enabled", "Bark推送启用状态", enabledStr, "0为关闭,1为开启"}, + {"bark_server_url", "Bark推送服务器地址", serverURL, ""}, + {"bark_device_key", "Bark设备Key", deviceKey, ""}, + } + + for _, item := range settings { + var setting models.PlatformNormalSetting + err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } else { + newSetting := models.PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = models.Orm.Insert(&newSetting) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} + _ = c.ServeJSON() +} + +type barkTestPayload struct { + ServerUrl string `json:"server_url"` + DeviceKey string `json:"device_key"` +} + +// SendTestBark POST /platform/bark/sendtest +func (c *PlatformBarkController) SendTestBark() { + if _, err := c.platformClaims(); err != nil { + c.jsonErr(401, 401, err.Error()) + return + } + + raw, err := io.ReadAll(c.Ctx.Request.Body) + if err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + var p barkTestPayload + if err := json.Unmarshal(raw, &p); err != nil { + c.jsonErr(400, 400, "参数错误") + return + } + + serverURL := strings.TrimSpace(p.ServerUrl) + if serverURL == "" { + serverURL = models.GetPlatformSettingValue("bark_server_url", "https://api.day.app") + } + deviceKey := strings.TrimSpace(p.DeviceKey) + if deviceKey == "" { + deviceKey = models.GetPlatformSettingValue("bark_device_key", "") + } + + if deviceKey == "" { + c.jsonErr(400, 400, "设备 Key 不能为空") + return + } + + // 拼接发送 URL,注意去除多余斜杠 + baseURL := strings.TrimRight(serverURL, "/") + // Bark 的格式是: base_url/device_key/title/body + testURL := fmt.Sprintf("%s/%s/测试通知/您配置的 Bark 推送服务已连接成功!", baseURL, deviceKey) + + client := &http.Client{Timeout: 10 * time.Second} + resp, err := client.Get(testURL) + if err != nil { + c.jsonErr(500, 500, "发送失败: "+err.Error()) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + c.jsonErr(500, 500, fmt.Sprintf("发送失败,HTTP 状态码: %d, 返回内容: %s", resp.StatusCode, string(bodyBytes))) + return + } + + c.Data["json"] = map[string]interface{}{"code": 200, "msg": "测试推送已发出,请注意查收"} + _ = c.ServeJSON() +} diff --git a/go/controllers/platform_cursor_equipment.go b/go/controllers/platform_cursor_equipment.go index dfdc68c..a3591a8 100644 --- a/go/controllers/platform_cursor_equipment.go +++ b/go/controllers/platform_cursor_equipment.go @@ -105,10 +105,11 @@ func (c *PlatformCursorEquipmentController) cursorActivationSummary(row *models. return count, &latest } -func (c *PlatformCursorEquipmentController) cursorExtractSummary() (int64, *models.PlatformAccountPoolCursor) { +func (c *PlatformCursorEquipmentController) cursorExtractSummary(machineCode string) (int64, *models.PlatformAccountPoolCursor) { qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). Filter("delete_time__isnull", true). - Filter("is_extracted__gt", 0) + Filter("is_extracted__gt", 0). + Filter("machine_code", machineCode) count, _ := qs.Count() @@ -122,7 +123,7 @@ func (c *PlatformCursorEquipmentController) cursorExtractSummary() (int64, *mode func (c *PlatformCursorEquipmentController) rowToMap(row *models.PlatformCursorEquipment) map[string]interface{} { activationCount, latestActivation := c.cursorActivationSummary(row) - extractCount, latestExtract := c.cursorExtractSummary() + extractCount, latestExtract := c.cursorExtractSummary(row.MachineCode) var bindActivationCode interface{} var activationCodeId interface{} @@ -640,7 +641,8 @@ func (c *PlatformCursorEquipmentController) ExtractRecords() { qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)). Filter("delete_time__isnull", true). - Filter("is_extracted__gt", 0) + Filter("is_extracted__gt", 0). + Filter("machine_code", equipment.MachineCode) total, _ := qs.Count() diff --git a/go/controllers/platform_login_verify.go b/go/controllers/platform_login_verify.go index 79ad429..f016080 100644 --- a/go/controllers/platform_login_verify.go +++ b/go/controllers/platform_login_verify.go @@ -85,37 +85,18 @@ func (c *PlatformLoginVerifyController) SaveLoginVerifyInfos() { } } - var existed models.PlatformLoginVerify - err := models.Orm.QueryTable(new(models.PlatformLoginVerify)).OrderBy("-id").One(&existed) - if err == nil { - update := map[string]interface{}{ - "open_verify_enabled": openVerifyEnabled, - "verify_type": verifyType, - "geetest3_id": p.Geetest3ID, - "geetest3_key": p.Geetest3Key, - "geetest4_id": p.Geetest4ID, - "geetest4_key": p.Geetest4Key, - } - _, err = models.Orm.QueryTable(new(models.PlatformLoginVerify)).Filter("id", existed.ID).Update(update) - if err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} - _ = c.ServeJSON() - return - } - } else { - row := &models.PlatformLoginVerify{ - OpenVerifyEnabled: openVerifyEnabled, - VerifyType: verifyType, - Geetest3ID: p.Geetest3ID, - Geetest3Key: p.Geetest3Key, - Geetest4ID: p.Geetest4ID, - Geetest4Key: p.Geetest4Key, - } - if _, err := models.Orm.Insert(row); err != nil { - c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} - _ = c.ServeJSON() - return - } + err := models.SavePlatformLoginVerify(&models.PlatformLoginVerify{ + OpenVerifyEnabled: openVerifyEnabled, + VerifyType: verifyType, + Geetest3ID: p.Geetest3ID, + Geetest3Key: p.Geetest3Key, + Geetest4ID: p.Geetest4ID, + Geetest4Key: p.Geetest4Key, + }) + if err != nil { + c.Data["json"] = map[string]interface{}{"code": 500, "msg": "保存失败"} + _ = c.ServeJSON() + return } c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"} diff --git a/go/controllers/platform_sms.go b/go/controllers/platform_sms.go index b667403..3c4c5d1 100644 --- a/go/controllers/platform_sms.go +++ b/go/controllers/platform_sms.go @@ -55,24 +55,8 @@ func (c *PlatformSMSController) GetSmsInfo() { return } - var row models.SystemSMS - // 优先默认通道,其次 custom - err := models.Orm.QueryTable(new(models.SystemSMS)). - Filter("is_default", 1). - Filter("status", 1). - OrderBy("-weight", "-id"). - Limit(1). - One(&row) - if err != nil { - _ = models.Orm.QueryTable(new(models.SystemSMS)). - Filter("config_code", "custom"). - OrderBy("-id"). - Limit(1). - One(&row) - } - - backendURL := strings.TrimSpace(row.ApiURL) - apiKey := strings.TrimSpace(row.ApiKey) + backendURL := models.GetPlatformSettingValue("sms_custom_url", "") + apiKey := models.GetPlatformSettingValue("sms_custom_key", "") data := []map[string]interface{}{{ "backend_url": backendURL, @@ -93,7 +77,7 @@ type smsEditPayload struct { } // EditSmsInfo POST /platform/sms/editinfo -// 将旧前端的 backendUrl/apiKey 落到 yz_system_sms 的 api_url/api_key(写入 config_code=custom) +// 将旧前端的 backendUrl/apiKey 落到 yz_platform_normal_setting 表中 func (c *PlatformSMSController) EditSmsInfo() { if _, err := c.platformClaims(); err != nil { c.jsonErr(401, 401, err.Error()) @@ -128,44 +112,46 @@ func (c *PlatformSMSController) EditSmsInfo() { return } - // 确保只有一个默认:先清空默认,再 upsert custom 为默认 - _, _ = models.Orm.QueryTable(new(models.SystemSMS)).Update(map[string]interface{}{"is_default": 0}) + settings := []struct { + code string + name string + value string + remark string + }{ + {"sms_custom_url", "自定义短信网关地址", backendURL, ""}, + {"sms_custom_key", "自定义短信API KEY", apiKey, ""}, + } - var existed models.SystemSMS - e := models.Orm.QueryTable(new(models.SystemSMS)).Filter("config_code", "custom").Limit(1).One(&existed) - if e == nil && existed.ID > 0 { - _, err = models.Orm.QueryTable(new(models.SystemSMS)).Filter("id", existed.ID).Update(map[string]interface{}{ - "config_name": "自定义网关", - "channel_type": 2, - "api_url": backendURL, - "api_key": apiKey, - "weight": 10, - "is_default": 1, - "status": 1, - }) - if err != nil { - c.jsonErr(500, 500, "更新失败: "+err.Error()) - return - } - } else { - row := &models.SystemSMS{ - ConfigCode: "custom", - ConfigName: "自定义网关", - ChannelType: 2, - ApiURL: backendURL, - ApiKey: apiKey, - ApiSecret: "", - SignName: "", - TemplateID: "", - TestPhone: "", - Weight: 10, - IsDefault: 1, - Status: 1, - Remark: "", - } - if _, err := models.Orm.Insert(row); err != nil { - c.jsonErr(500, 500, "更新失败: "+err.Error()) - return + for _, item := range settings { + var setting models.PlatformNormalSetting + err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } + } else { + newSetting := models.PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = models.Orm.Insert(&newSetting) + if err != nil { + c.jsonErr(500, 500, "保存失败: "+err.Error()) + return + } } } @@ -232,18 +218,11 @@ func (c *PlatformSMSController) SendTestSms() { // 兜底:body 未带时从默认配置取 if backendURL == "" || apiKey == "" { - var row models.SystemSMS - _ = models.Orm.QueryTable(new(models.SystemSMS)). - Filter("is_default", 1). - Filter("status", 1). - OrderBy("-weight", "-id"). - Limit(1). - One(&row) if backendURL == "" { - backendURL = strings.TrimSpace(row.ApiURL) + backendURL = models.GetPlatformSettingValue("sms_custom_url", "") } if apiKey == "" { - apiKey = strings.TrimSpace(row.ApiKey) + apiKey = models.GetPlatformSettingValue("sms_custom_key", "") } } if backendURL == "" { diff --git a/go/models/init.go b/go/models/init.go index b218b12..fa05bbb 100644 --- a/go/models/init.go +++ b/go/models/init.go @@ -43,14 +43,11 @@ func Init(_ string) { new(AdminRole), new(SystemFile), new(SystemFilesCategory), - new(SystemEmail), - new(SystemSMS), new(SystemSMSTask), new(SystemOperationLog), new(SystemDomainPool), new(SystemTenantDomain), new(SystemModules), - new(PlatformLoginVerify), new(StorageConfig), new(TenantSiteSetting), new(ComplaintCategory), @@ -67,8 +64,11 @@ func Init(_ string) { new(CmsArticleCategory), new(CmsArticle), - new(SystemSiteReminder), new(SystemReminderList), + + new(SystemNormalSetting), + new(PlatformNormalSetting), + new(BackendNormalSetting), ) // 创建全局 Ormer diff --git a/go/models/platform_account_pool.go b/go/models/platform_account_pool.go index 3dc8b62..7c6b759 100644 --- a/go/models/platform_account_pool.go +++ b/go/models/platform_account_pool.go @@ -75,6 +75,7 @@ type PlatformAccountPoolCursor struct { IsUsed *int8 `orm:"column(is_used);null" json:"is_used"` // 0=用完/不可用 1=可用 NULL=未探测 ExtractedTime *time.Time `orm:"column(extracted_time);type(datetime);null" json:"extracted_time"` ExtractedPlatform *string `orm:"column(extracted_platform);size(32);null" json:"extracted_platform"` + MachineCode string `orm:"column(machine_code);size(128);default('')" json:"machine_code"` CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` diff --git a/go/models/platform_login_verify.go b/go/models/platform_login_verify.go index 69ec5d2..2ed40cb 100644 --- a/go/models/platform_login_verify.go +++ b/go/models/platform_login_verify.go @@ -15,20 +15,118 @@ type PlatformLoginVerify struct { UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` } -func (m *PlatformLoginVerify) TableName() string { - return "yz_system_login_verify" -} - func GetPlatformLoginVerify() (*PlatformLoginVerify, error) { - var cfg PlatformLoginVerify - err := Orm.QueryTable(new(PlatformLoginVerify)).OrderBy("-id").One(&cfg) - if err != nil { - // 默认配置:验证码 - return &PlatformLoginVerify{OpenVerifyEnabled: 1, VerifyType: "captcha"}, nil + // 从 yz_platform_normal_setting 表中按 code 获取各个配置 + enabledStr := GetPlatformSettingValue("login_verify_enabled", "1") + verifyType := GetPlatformSettingValue("login_verify_type", "captcha") + geetest3ID := GetPlatformSettingValue("login_verify_geetest3_id", "") + geetest3Key := GetPlatformSettingValue("login_verify_geetest3_key", "") + geetest4ID := GetPlatformSettingValue("login_verify_geetest4_id", "") + geetest4Key := GetPlatformSettingValue("login_verify_geetest4_key", "") + + openVerifyEnabled := int8(1) + if enabledStr == "0" { + openVerifyEnabled = 0 } - if cfg.VerifyType == "" { - cfg.VerifyType = "captcha" + + cfg := &PlatformLoginVerify{ + OpenVerifyEnabled: openVerifyEnabled, + VerifyType: verifyType, } - return &cfg, nil + if geetest3ID != "" { + cfg.Geetest3ID = &geetest3ID + } + if geetest3Key != "" { + cfg.Geetest3Key = &geetest3Key + } + if geetest4ID != "" { + cfg.Geetest4ID = &geetest4ID + } + if geetest4Key != "" { + cfg.Geetest4Key = &geetest4Key + } + + return cfg, nil +} + +func GetPlatformSettingValue(code string, defaultVal string) string { + var setting PlatformNormalSetting + err := Orm.QueryTable(new(PlatformNormalSetting)). + Filter("code", code). + Filter("delete_time__isnull", true). + One(&setting) + if err != nil { + return defaultVal + } + return setting.Value +} + +func SavePlatformLoginVerify(cfg *PlatformLoginVerify) error { + openVerifyEnabledStr := "1" + if cfg.OpenVerifyEnabled == 0 { + openVerifyEnabledStr = "0" + } + geetest3ID := "" + if cfg.Geetest3ID != nil { + geetest3ID = *cfg.Geetest3ID + } + geetest3Key := "" + if cfg.Geetest3Key != nil { + geetest3Key = *cfg.Geetest3Key + } + geetest4ID := "" + if cfg.Geetest4ID != nil { + geetest4ID = *cfg.Geetest4ID + } + geetest4Key := "" + if cfg.Geetest4Key != nil { + geetest4Key = *cfg.Geetest4Key + } + + settings := []struct { + code string + name string + value string + remark string + }{ + {"login_verify_enabled", "登录验证开启状态", openVerifyEnabledStr, "0为关闭,1为开启"}, + {"login_verify_type", "登录验证类型", cfg.VerifyType, "支持 captcha/sms/geetest/email"}, + {"login_verify_geetest3_id", "极验3 ID", geetest3ID, ""}, + {"login_verify_geetest3_key", "极验3 Key", geetest3Key, ""}, + {"login_verify_geetest4_id", "极验4 ID", geetest4ID, ""}, + {"login_verify_geetest4_key", "极验4 Key", geetest4Key, ""}, + } + + for _, item := range settings { + var setting PlatformNormalSetting + err := Orm.QueryTable(new(PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + return err + } + } else { + newSetting := PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = Orm.Insert(&newSetting) + if err != nil { + return err + } + } + } + return nil } diff --git a/go/models/system_email.go b/go/models/system_email.go index 289dbfa..397a15c 100644 --- a/go/models/system_email.go +++ b/go/models/system_email.go @@ -18,6 +18,4 @@ type SystemEmail struct { UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"` } -func (m *SystemEmail) TableName() string { - return "yz_system_email" -} + diff --git a/go/models/system_normal_setting.go b/go/models/system_normal_setting.go new file mode 100644 index 0000000..61037cc --- /dev/null +++ b/go/models/system_normal_setting.go @@ -0,0 +1,51 @@ +package models + +import "time" + +// SystemNormalSetting 系统通用配置表: yz_system_normal_setting +type SystemNormalSetting struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Name string `orm:"column(name);size(128);default('')" json:"name"` + Value string `orm:"column(value);type(text);null" json:"value"` + Code string `orm:"column(code);size(64);default('')" json:"code"` + Remark string `orm:"column(remark);size(255);default('')" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *SystemNormalSetting) TableName() string { + return "yz_system_normal_setting" +} + +// PlatformNormalSetting 平台通用配置表: yz_platform_normal_setting +type PlatformNormalSetting struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Name string `orm:"column(name);size(128);default('')" json:"name"` + Value string `orm:"column(value);type(text);null" json:"value"` + Code string `orm:"column(code);size(64);default('')" json:"code"` + Remark string `orm:"column(remark);size(255);default('')" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *PlatformNormalSetting) TableName() string { + return "yz_platform_normal_setting" +} + +// BackendNormalSetting 管理端通用配置表: yz_backend_normal_setting +type BackendNormalSetting struct { + ID uint64 `orm:"column(id);pk;auto" json:"id"` + Name string `orm:"column(name);size(128);default('')" json:"name"` + Value string `orm:"column(value);type(text);null" json:"value"` + Code string `orm:"column(code);size(64);default('')" json:"code"` + Remark string `orm:"column(remark);size(255);default('')" json:"remark"` + CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"` + UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` + DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"` +} + +func (m *BackendNormalSetting) TableName() string { + return "yz_backend_normal_setting" +} diff --git a/go/models/system_sitereminder.go b/go/models/system_sitereminder.go index c08af03..ba45555 100644 --- a/go/models/system_sitereminder.go +++ b/go/models/system_sitereminder.go @@ -11,6 +11,4 @@ type SystemSiteReminder struct { UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"` } -func (m *SystemSiteReminder) TableName() string { - return "yz_system_sitereminder" -} + diff --git a/go/models/system_sms.go b/go/models/system_sms.go index 93d7418..094dcb1 100644 --- a/go/models/system_sms.go +++ b/go/models/system_sms.go @@ -27,6 +27,4 @@ type SystemSMS struct { UpdateTime time.Time `orm:"column(update_time);type(datetime);auto_now" json:"update_time"` } -func (m *SystemSMS) TableName() string { - return "yz_system_sms" -} + diff --git a/go/routers/platform/platform.go b/go/routers/platform/platform.go index a754fb1..61842bc 100644 --- a/go/routers/platform/platform.go +++ b/go/routers/platform/platform.go @@ -144,6 +144,11 @@ func Register() { beego.Router("/platform/sms/taskList", &controllers.PlatformSMSController{}, "get:GetSmsTaskList") beego.Router("/platform/sms/taskEdit/:id", &controllers.PlatformSMSController{}, "post:EditSmsTask") + // Bark 推送配置 + beego.Router("/platform/bark/info", &controllers.PlatformBarkController{}, "get:GetBarkInfo") + beego.Router("/platform/bark/editinfo", &controllers.PlatformBarkController{}, "post:EditBarkInfo") + beego.Router("/platform/bark/sendtest", &controllers.PlatformBarkController{}, "post:SendTestBark") + // 文件管理(yz_system_files / yz_system_files_category) beego.Router("/platform/usercate", &controllers.PlatformFileController{}, "get:GetUserCate") beego.Router("/platform/allfiles", &controllers.PlatformFileController{}, "get:GetAllFiles") diff --git a/go/services/login_verify_code.go b/go/services/login_verify_code.go index bef2db4..963027f 100644 --- a/go/services/login_verify_code.go +++ b/go/services/login_verify_code.go @@ -157,25 +157,11 @@ func VerifyBackendLoginCode(tenantName, account, channel, code string) error { } func getDefaultSystemSMSConfig() (backendURL string, apiKey string, err error) { - var row models.SystemSMS - err = models.Orm.QueryTable(new(models.SystemSMS)). - Filter("is_default", 1). - Filter("status", 1). - OrderBy("-weight", "-id"). - Limit(1). - One(&row) - if err != nil { - err2 := models.Orm.QueryTable(new(models.SystemSMS)). - Filter("config_code", "custom"). - OrderBy("-id"). - Limit(1). - One(&row) - if err2 != nil { - return "", "", err2 - } + backendURL = models.GetPlatformSettingValue("sms_custom_url", "") + apiKey = models.GetPlatformSettingValue("sms_custom_key", "") + if backendURL == "" || apiKey == "" { + return "", "", fmt.Errorf("短信网关未配置") } - backendURL = strings.TrimSpace(row.ApiURL) - apiKey = strings.TrimSpace(row.ApiKey) return backendURL, apiKey, nil } diff --git a/go/services/system_email_store.go b/go/services/system_email_store.go index d2a5ea9..24f80f4 100644 --- a/go/services/system_email_store.go +++ b/go/services/system_email_store.go @@ -2,19 +2,51 @@ package services import ( "fmt" + "strconv" "strings" + "time" "server/models" ) -// ListSystemEmails 返回全部邮箱配置(按 id 升序,通常仅一条) +// ListSystemEmails 返回从 yz_platform_normal_setting 组装的邮箱配置(切片,通常仅一条) func ListSystemEmails() ([]models.SystemEmail, error) { - var rows []models.SystemEmail - _, err := models.Orm.QueryTable(new(models.SystemEmail)).OrderBy("id").All(&rows) - return rows, err + enabledStr := models.GetPlatformSettingValue("email_enabled", "0") + fromAddress := models.GetPlatformSettingValue("email_from_address", "") + fromName := models.GetPlatformSettingValue("email_from_name", "") + host := models.GetPlatformSettingValue("email_host", "") + portStr := models.GetPlatformSettingValue("email_port", "465") + password := models.GetPlatformSettingValue("email_password", "") + encryption := models.GetPlatformSettingValue("email_encryption", "ssl") + timeoutStr := models.GetPlatformSettingValue("email_timeout", "30") + + status := int8(0) + if enabledStr == "1" { + status = 1 + } + portVal, _ := strconv.ParseUint(portStr, 10, 32) + timeoutVal, _ := strconv.ParseUint(timeoutStr, 10, 32) + + row := models.SystemEmail{ + ID: 1, + FromAddress: fromAddress, + Host: host, + Port: uint(portVal), + Password: password, + Encryption: encryption, + Timeout: uint(timeoutVal), + Status: status, + CreateTime: time.Now(), + UpdateTime: time.Now(), + } + if fromName != "" { + row.FromName = &fromName + } + + return []models.SystemEmail{row}, nil } -// UpsertFirstSystemEmail 若已有记录则更新第一条,否则插入 +// UpsertFirstSystemEmail 将邮箱配置保存到 yz_platform_normal_setting 表中 func UpsertFirstSystemEmail(fromAddress string, fromName *string, host string, port uint, password string, encryption string, timeout uint, status int8, remark *string) error { if encryption == "" { encryption = "ssl" @@ -28,47 +60,76 @@ func UpsertFirstSystemEmail(fromAddress string, fromName *string, host string, p fromAddress = strings.TrimSpace(fromAddress) host = strings.TrimSpace(host) - cnt, err := models.Orm.QueryTable(new(models.SystemEmail)).Count() - if err != nil { - return err + fn := "" + if fromName != nil { + fn = *fromName } - if cnt == 0 { - if strings.TrimSpace(password) == "" { + + statusStr := "0" + if status == 1 { + statusStr = "1" + } + + settings := []struct { + code string + name string + value string + remark string + }{ + {"email_enabled", "邮件服务启用状态", statusStr, "0为关闭,1为开启"}, + {"email_from_address", "发件人邮箱", fromAddress, ""}, + {"email_from_name", "发件人名称", fn, ""}, + {"email_host", "SMTP 服务器地址", host, ""}, + {"email_port", "SMTP 端口", strconv.FormatUint(uint64(port), 10), ""}, + {"email_encryption", "邮件加密方式", encryption, "支持 ssl/tls/none"}, + {"email_timeout", "邮件发送超时时间", strconv.FormatUint(uint64(timeout), 10), ""}, + } + + // 如果传入了新密码,或者目前还没有保存过密码,才更新密码 + if strings.TrimSpace(password) != "" { + settings = append(settings, struct { + code string + name string + value string + remark string + }{"email_password", "邮件授权码/密码", strings.TrimSpace(password), ""}) + } else { + // 校验:如果完全没有配置过密码,必须填写密码 + existingPass := models.GetPlatformSettingValue("email_password", "") + if existingPass == "" { return fmt.Errorf("首次保存必须填写授权码/密码") } - row := &models.SystemEmail{ - FromAddress: fromAddress, - FromName: fromName, - Host: host, - Port: port, - Password: strings.TrimSpace(password), - Encryption: encryption, - Timeout: timeout, - Status: status, - Remark: remark, + } + + for _, item := range settings { + var setting models.PlatformNormalSetting + err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + return err + } + } else { + newSetting := models.PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = models.Orm.Insert(&newSetting) + if err != nil { + return err + } } - _, err = models.Orm.Insert(row) - return err } - - var first models.SystemEmail - if err := models.Orm.QueryTable(new(models.SystemEmail)).OrderBy("id").Limit(1).One(&first); err != nil { - return err - } - - up := map[string]interface{}{ - "from_address": fromAddress, - "from_name": fromName, - "host": host, - "port": port, - "encryption": encryption, - "timeout": timeout, - "status": status, - "remark": remark, - } - if strings.TrimSpace(password) != "" { - up["password"] = strings.TrimSpace(password) - } - _, err = models.Orm.QueryTable(new(models.SystemEmail)).Filter("id", first.ID).Update(up) - return err + return nil } diff --git a/go/services/system_sitereminder.go b/go/services/system_sitereminder.go index d0eccc0..4705f6f 100644 --- a/go/services/system_sitereminder.go +++ b/go/services/system_sitereminder.go @@ -3,32 +3,36 @@ package services import ( "context" "fmt" + "strconv" "time" "github.com/beego/beego/v2/client/orm" "server/models" ) -// GetSiteReminderConfig 获取站内信配置(只读首条记录,不存在则初始化默认值) +// GetSiteReminderConfig 获取站内信配置(从 yz_platform_normal_setting 读取) func GetSiteReminderConfig() (models.SystemSiteReminder, error) { - var row models.SystemSiteReminder - err := models.Orm.QueryTable(new(models.SystemSiteReminder)).OrderBy("id").Limit(1).One(&row) - if err == orm.ErrNoRows { - // 默认配置 - now := time.Now() - row = models.SystemSiteReminder{ - RetentionDays: 30, - AutoRead: 0, - CreateTime: &now, - UpdateTime: &now, - } - _, err = models.Orm.Insert(&row) - if err != nil { - return row, err - } - return row, nil + retentionDaysStr := models.GetPlatformSettingValue("sitemsg_retention_days", "30") + autoReadStr := models.GetPlatformSettingValue("sitemsg_auto_read", "0") + + retentionDays, _ := strconv.Atoi(retentionDaysStr) + if retentionDays <= 0 { + retentionDays = 30 } - return row, err + autoRead := int8(0) + if autoReadStr == "1" { + autoRead = 1 + } + + now := time.Now() + row := models.SystemSiteReminder{ + ID: 1, + RetentionDays: retentionDays, + AutoRead: autoRead, + CreateTime: &now, + UpdateTime: &now, + } + return row, nil } // SaveSiteReminderConfig 保存/更新配置 @@ -36,17 +40,53 @@ func SaveSiteReminderConfig(retentionDays int, autoRead int8) error { if retentionDays <= 0 { retentionDays = 30 } - cfg, err := GetSiteReminderConfig() - if err != nil { - return err - } - now := time.Now() - cfg.RetentionDays = retentionDays - cfg.AutoRead = autoRead - cfg.UpdateTime = &now - _, err = models.Orm.Update(&cfg, "RetentionDays", "AutoRead", "UpdateTime") - return err + autoReadStr := "0" + if autoRead == 1 { + autoReadStr = "1" + } + + settings := []struct { + code string + name string + value string + remark string + }{ + {"sitemsg_retention_days", "站内信消息保留天数", strconv.Itoa(retentionDays), ""}, + {"sitemsg_auto_read", "自动标记已读状态", autoReadStr, "0为关闭,1为开启"}, + } + + for _, item := range settings { + var setting models.PlatformNormalSetting + err := models.Orm.QueryTable(new(models.PlatformNormalSetting)). + Filter("code", item.code). + Filter("delete_time__isnull", true). + One(&setting) + if err == nil { + setting.Value = item.value + setting.Name = item.name + setting.Remark = item.remark + now := time.Now() + setting.UpdateTime = &now + _, err = models.Orm.Update(&setting, "Value", "Name", "Remark", "UpdateTime") + if err != nil { + return err + } + } else { + newSetting := models.PlatformNormalSetting{ + Name: item.name, + Code: item.code, + Value: item.value, + Remark: item.remark, + CreateTime: time.Now(), + } + _, err = models.Orm.Insert(&newSetting) + if err != nil { + return err + } + } + } + return nil } // SendSiteReminder 发送站内信 diff --git a/platform/src/api/sitesettings.js b/platform/src/api/sitesettings.js index 1ecea97..2d0bf45 100644 --- a/platform/src/api/sitesettings.js +++ b/platform/src/api/sitesettings.js @@ -150,4 +150,41 @@ export function saveStorageConfig(data) { method: "post", data: data, }); +} + +/** + * 获取 Bark 配置 + * @returns {Promise} + */ +export function getBarkConfig() { + return request({ + url: "/platform/bark/info", + method: "get", + }); +} + +/** + * 保存 Bark 配置 + * @param {Object} data 要保存的数据 + * @returns {Promise} + */ +export function saveBarkConfig(data) { + return request({ + url: "/platform/bark/editinfo", + method: "post", + data: data, + }); +} + +/** + * 发送测试 Bark 推送 + * @param {Object} data 测试数据 + * @returns {Promise} + */ +export function sendTestBark(data) { + return request({ + url: "/platform/bark/sendtest", + method: "post", + data: data, + }); } \ No newline at end of file diff --git a/platform/src/views/system/platformsettings/components/notificationSettings.vue b/platform/src/views/system/platformsettings/components/notificationSettings.vue index 9889001..e27d387 100644 --- a/platform/src/views/system/platformsettings/components/notificationSettings.vue +++ b/platform/src/views/system/platformsettings/components/notificationSettings.vue @@ -155,10 +155,12 @@ + 天(过期消息将自动清理) @@ -296,6 +298,11 @@ + + + 发送测试推送 + + @@ -395,10 +402,12 @@ import { import { getSmsInfo, editSmsInfo, sendTestSms } from "@/api/sms"; import { getEmailInfo, editEmailInfo, sendTestEmail } from "@/api/email"; import { getSiteReminderConfig, saveSiteReminderConfig } from "@/api/sitereminder"; +import { getBarkConfig, saveBarkConfig, sendTestBark } from "@/api/sitesettings"; const STORAGE_KEY = "notification_settings_draft"; const activeSubTab = ref("email"); const submitting = ref(false); +const barkTestLoading = ref(false); const formData = reactive({ email: { @@ -659,6 +668,17 @@ const handleSubmit = async () => { } else { throw new Error(res.msg || "保存站内信配置失败"); } + } else if (activeSubTab.value === 'bark') { + const res = await saveBarkConfig({ + enabled: formData.bark.enabled, + server_url: formData.bark.server_url, + device_key: formData.bark.device_key + }); + if (res.code === 200) { + ElMessage.success("Bark 配置保存成功"); + } else { + throw new Error(res.msg || "保存 Bark 配置失败"); + } } saveDraft(); @@ -670,11 +690,49 @@ const handleSubmit = async () => { } }; +const loadBarkConfig = async () => { + try { + const res = await getBarkConfig(); + if (res.code === 200 && res.data) { + formData.bark.enabled = res.data.enabled; + formData.bark.server_url = res.data.server_url || "https://api.day.app"; + formData.bark.device_key = res.data.device_key || ""; + } + } catch (error) { + console.error("加载 Bark 配置失败:", error); + } +}; + +const handleSendBarkTest = async () => { + const key = (formData.bark.device_key || "").trim(); + if (!key) { + ElMessage.warning("请先输入 Bark 设备 Key"); + return; + } + try { + barkTestLoading.value = true; + const res = await sendTestBark({ + server_url: formData.bark.server_url, + device_key: key + }); + if (res.code === 200) { + ElMessage.success(res.msg || "测试推送发送成功"); + } else { + ElMessage.error(res.msg || "测试推送失败,请稍后重试"); + } + } catch (error) { + ElMessage.error(error?.message || "测试推送失败,请稍后重试"); + } finally { + barkTestLoading.value = false; + } +}; + onMounted(() => { loadDraft(); loadSmsConfig(); loadEmailConfig(); loadSiteReminderConfig(); + loadBarkConfig(); });