This commit is contained in:
2026-06-22 11:26:35 +08:00
52 changed files with 6176 additions and 411 deletions
+51 -12
View File
@@ -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
}
+109
View File
@@ -0,0 +1,109 @@
package controllers
import (
"time"
"server/models"
beego "github.com/beego/beego/v2/server/web"
)
type ApiReminderController struct {
beego.Controller
}
// AckReminder GET /api/schedule/reminder/ack
// 邮件/Bark 客户端访问此接口进行提醒确认
func (c *ApiReminderController) AckReminder() {
token := c.GetString("token")
if token == "" {
c.Ctx.Output.SetStatus(400)
_ = c.Ctx.Output.Body([]byte("Invalid request: missing token"))
return
}
var reminder models.PlatformScheduleReminder
err := models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("ack_token", token).
Filter("is_deleted", 0).
One(&reminder)
if err != nil {
c.Ctx.Output.SetStatus(404)
_ = c.Ctx.Output.Body([]byte("Error: reminder task not found or token has expired"))
return
}
if reminder.AckStatus == 1 {
// 已经确认过了,直接显示已确认成功的 HTML
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
_ = c.Ctx.Output.Body([]byte(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>确认收到提醒</title>
<style>
body { font-family: sans-serif; text-align: center; padding: 50px; background: #f5f7fa; color: #303133; }
.card { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); display: inline-block; max-width: 400px; }
h2 { color: #67C23A; }
</style>
</head>
<body>
<div class="card">
<h2>提示</h2>
<p>该日程提醒在此之前已确认过了。</p>
<p style="color: #909399; font-size: 14px;">无需重复点击,感谢您的使用!</p>
</div>
</body>
</html>
`))
return
}
// 更新确认状态为已确认,置 remind_status 为已结束(2)
now := time.Now()
reminder.AckStatus = 1
reminder.AckTime = &now
reminder.RemindStatus = 2
reminder.UpdateTime = now
_, err = models.Orm.Update(&reminder, "AckStatus", "AckTime", "RemindStatus", "UpdateTime")
if err != nil {
c.Ctx.Output.SetStatus(500)
_ = c.Ctx.Output.Body([]byte("Database error, please try again later"))
return
}
// 统一关闭该日程下的所有其他待提醒/提醒中渠道,防止重复打扰
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("ScheduleID", reminder.ScheduleID).
Filter("RemindStatus__in", 0, 1).
Update(map[string]interface{}{
"RemindStatus": int8(2),
"UpdateTime": now,
})
// 成功确认
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
_ = c.Ctx.Output.Body([]byte(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>确认成功</title>
<style>
body { font-family: sans-serif; text-align: center; padding: 50px; background: #f5f7fa; color: #303133; }
.card { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1); display: inline-block; max-width: 400px; }
h2 { color: #67C23A; }
</style>
</head>
<body>
<div class="card">
<h2>确认成功</h2>
<p>您已成功确认收到该日程提醒!</p>
<p style="color: #909399; font-size: 14px;">系统已停止向您重复推送,感谢您的配合。</p>
</div>
</body>
</html>
`))
}
+11 -34
View File
@@ -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": "保存成功"}
+151
View File
@@ -0,0 +1,151 @@
package controllers
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"server/pkg/jwtutil"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
type BackendSiteReminderController struct {
beego.Controller
}
func (c *BackendSiteReminderController) backendClaims() (*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 != "backend" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
func (c *BackendSiteReminderController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
// GetMyList GET /backend/sitereminder/myList
func (c *BackendSiteReminderController) GetMyList() {
claims, err := c.backendClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 10)
var isRead *int8
if isReadStr := c.GetString("isRead"); isReadStr != "" {
if val, err := strconv.Atoi(isReadStr); err == nil {
v := int8(val)
isRead = &v
}
}
list, total, err := services.ListReminders(uint64(claims.UserID), "tenant", page, pageSize, isRead)
if err != nil {
c.jsonErr(500, 500, "获取消息列表失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"list": list,
"total": total,
},
}
_ = c.ServeJSON()
}
// MarkRead POST /backend/sitereminder/read
func (c *BackendSiteReminderController) MarkRead() {
claims, err := c.backendClaims()
if 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 struct {
ID uint64 `json:"id"`
}
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
err = services.MarkReminderRead(p.ID, uint64(claims.UserID), "tenant")
if err != nil {
c.jsonErr(500, 500, "操作失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
}
// MarkAllRead POST /backend/sitereminder/readall
func (c *BackendSiteReminderController) MarkAllRead() {
claims, err := c.backendClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
err = services.MarkAllRemindersRead(uint64(claims.UserID), "tenant")
if err != nil {
c.jsonErr(500, 500, "操作失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
}
// Delete POST /backend/sitereminder/delete
func (c *BackendSiteReminderController) Delete() {
claims, err := c.backendClaims()
if 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 struct {
ID uint64 `json:"id"`
}
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
err = services.DeleteReminder(p.ID, uint64(claims.UserID), "tenant")
if err != nil {
c.jsonErr(500, 500, "删除失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
}
+215
View File
@@ -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()
}
+6 -4
View File
@@ -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{}
@@ -676,7 +677,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()
+34 -1
View File
@@ -97,12 +97,44 @@ type emailFormPayload struct {
Password string `json:"password"`
Encryption string `json:"encryption"`
Timeout interface{} `json:"timeout"`
Status interface{} `json:"status"`
}
type testEmailPayload struct {
emailFormPayload
TestEmail string `json:"testEmail"`
}
func parseInt8Flexible(v interface{}) int8 {
if v == nil {
return 1
}
switch x := v.(type) {
case bool:
if x {
return 1
}
return 0
case float64:
return int8(x)
case int:
return int8(x)
case int8:
return x
case string:
s := strings.TrimSpace(x)
if s == "" {
return 1
}
n, err := strconv.ParseInt(s, 10, 8)
if err != nil {
return 1
}
return int8(n)
default:
return 1
}
}
func parseUintFlexible(v interface{}) uint {
if v == nil {
return 0
@@ -191,7 +223,8 @@ func (c *PlatformEmailController) EditInfo() {
s := strings.TrimSpace(p.FromName)
fn = &s
}
err = services.UpsertFirstSystemEmail(from, fn, host, port, strings.TrimSpace(p.Password), enc, timeout, 1, nil)
status := parseInt8Flexible(p.Status)
err = services.UpsertFirstSystemEmail(from, fn, host, port, strings.TrimSpace(p.Password), enc, timeout, status, nil)
if err != nil {
c.jsonErr(500, 500, "保存邮箱配置失败: "+err.Error())
return
+12 -31
View File
@@ -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": "保存成功"}
+631
View File
@@ -0,0 +1,631 @@
package controllers
import (
"context"
"crypto/rand"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
type PlatformReminderController struct {
beego.Controller
}
func (c *PlatformReminderController) 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 *PlatformReminderController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func (c *PlatformReminderController) ok(data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
// generateToken 生成一个随机的 ack_token
func generateToken() string {
b := make([]byte, 16)
_, _ = rand.Read(b)
b[6] = (b[6] & 0x0f) | 0x40
b[8] = (b[8] & 0x3f) | 0x80
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}
type reminderFormPayload struct {
Title string `json:"title"`
Content string `json:"content"`
ScheduleTime string `json:"schedule_time"`
RemindChannels []string `json:"remind_channels"` // EMAIL, BARK, SMS, SITE_MSG
AdvanceMinutes int `json:"advance_minutes"`
RepeatIntervalMinutes int `json:"repeat_interval_minutes"`
MaxSendCount int `json:"max_send_count"`
ReceiverUserID uint64 `json:"receiver_user_id"`
ReceiverTargets map[string]string `json:"receiver_targets"` // "SMS": "1380...", "EMAIL": "...", "BARK": "..."
}
// GetReminderList GET /platform/reminder/list
func (c *PlatformReminderController) GetReminderList() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
// 联表获取日程及提醒信息
var schedules []models.PlatformSchedule
qs := models.Orm.QueryTable(new(models.PlatformSchedule))
total, _ := qs.Count()
_, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&schedules)
if err != nil {
c.jsonErr(500, 500, "查询失败: "+err.Error())
return
}
list := make([]map[string]interface{}, 0, len(schedules))
for _, s := range schedules {
// 查询该日程关联的所有提醒记录
var reminders []models.PlatformScheduleReminder
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("schedule_id", s.ID).
Filter("is_deleted", 0).
All(&reminders)
channels := make([]string, 0, len(reminders))
isFinished := true
if len(reminders) == 0 {
isFinished = false
} else {
for _, r := range reminders {
channels = append(channels, r.RemindChannel)
if r.RemindStatus != 2 {
isFinished = false
}
}
}
item := map[string]interface{}{
"id": s.ID,
"title": s.Title,
"content": s.Content,
"schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"),
"remind_channels": channels,
"user_id": s.UserID,
"is_finished": isFinished,
}
if len(reminders) > 0 {
first := reminders[0]
item["advance_minutes"] = first.AdvanceMinutes
item["repeat_interval_minutes"] = first.RepeatIntervalMinutes
item["max_send_count"] = first.MaxSendCount
item["receiver_user_id"] = first.ReceiverUserID
}
list = append(list, item)
}
c.ok(map[string]interface{}{
"list": list,
"total": total,
"page": page,
"pageSize": pageSize,
})
}
// GetReminderDetail GET /platform/reminder/:id
func (c *PlatformReminderController) GetReminderDetail() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
c.jsonErr(400, 400, "无效的ID")
return
}
var schedule models.PlatformSchedule
err := models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).One(&schedule)
if err != nil {
c.jsonErr(404, 404, "日程未找到")
return
}
var reminders []models.PlatformScheduleReminder
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("schedule_id", schedule.ID).
Filter("is_deleted", 0).
All(&reminders)
channels := make([]string, 0, len(reminders))
targets := make(map[string]string)
var first models.PlatformScheduleReminder
for _, r := range reminders {
channels = append(channels, r.RemindChannel)
if r.ReceiverTarget != nil {
targets[r.RemindChannel] = *r.ReceiverTarget
}
first = r
}
isFinished := true
if len(reminders) == 0 {
isFinished = false
} else {
for _, r := range reminders {
if r.RemindStatus != 2 {
isFinished = false
break
}
}
}
data := map[string]interface{}{
"id": schedule.ID,
"title": schedule.Title,
"content": schedule.Content,
"schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"),
"remind_channels": channels,
"receiver_targets": targets,
"is_finished": isFinished,
}
if first.ID > 0 {
data["advance_minutes"] = first.AdvanceMinutes
data["repeat_interval_minutes"] = first.RepeatIntervalMinutes
data["max_send_count"] = first.MaxSendCount
data["receiver_user_id"] = first.ReceiverUserID
}
c.ok(data)
}
// CreateReminder POST /platform/reminder
func (c *PlatformReminderController) CreateReminder() {
claims, err := c.platformClaims()
if 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 reminderFormPayload
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if strings.TrimSpace(p.ScheduleTime) == "" {
c.jsonErr(400, 400, "日程发生时间不能为空")
return
}
schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local)
if err != nil {
c.jsonErr(400, 400, "日程时间格式不合法,支持 YYYY-MM-DD HH:mm:ss")
return
}
// 1. 插入日程主表
schedule := models.PlatformSchedule{
Title: "日程提醒",
Content: p.Content,
ScheduleTime: schedTime,
UserID: uint64(claims.UserID),
}
schedID, err := models.Orm.Insert(&schedule)
if err != nil {
c.jsonErr(500, 500, "保存日程失败: "+err.Error())
return
}
// 2. 根据选中的渠道循环创建提醒
for _, ch := range p.RemindChannels {
ch = strings.ToUpper(strings.TrimSpace(ch))
if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" {
continue
}
targetVal := p.ReceiverTargets[ch]
var target *string
if targetVal != "" {
target = &targetVal
}
// 计算首次发送时间
firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute)
reminder := models.PlatformScheduleReminder{
ScheduleID: uint64(schedID),
RemindChannel: ch,
AdvanceMinutes: p.AdvanceMinutes,
NextRemindTime: firstSendTime,
ReceiverUserID: uint64(claims.UserID), // 谁创建的就发给谁
ReceiverTarget: target,
RemindStatus: 0, // 待提醒
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if ch == "EMAIL" || ch == "BARK" {
token := generateToken()
reminder.AckToken = &token
reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes
reminder.MaxSendCount = p.MaxSendCount
if reminder.MaxSendCount <= 0 {
reminder.MaxSendCount = 1
}
} else {
// SMS 或 SITE_MSG
reminder.RepeatIntervalMinutes = 0
reminder.MaxSendCount = 1
}
_, err = models.Orm.Insert(&reminder)
if err != nil {
c.jsonErr(500, 500, "创建提醒失败: "+err.Error())
return
}
}
c.ok(map[string]interface{}{"schedule_id": schedID})
}
// UpdateReminder PUT /platform/reminder/:id
func (c *PlatformReminderController) UpdateReminder() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
c.jsonErr(400, 400, "无效的ID")
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
var p reminderFormPayload
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local)
if err != nil {
c.jsonErr(400, 400, "日程时间格式不合法")
return
}
// 1. 更新日程详情
var schedule models.PlatformSchedule
err = models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).One(&schedule)
if err != nil {
c.jsonErr(404, 404, "日程未找到")
return
}
// 检查是否所有关联的提醒都已结束
var reminders []models.PlatformScheduleReminder
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("schedule_id", id).
Filter("is_deleted", 0).
All(&reminders)
isFinished := true
if len(reminders) == 0 {
isFinished = false
} else {
for _, r := range reminders {
if r.RemindStatus != 2 {
isFinished = false
break
}
}
}
if isFinished {
c.jsonErr(400, 400, "该日程提醒已全部结束,无法编辑")
return
}
schedule.Title = "日程提醒"
schedule.Content = p.Content
schedule.ScheduleTime = schedTime
_, err = models.Orm.Update(&schedule, "Title", "Content", "ScheduleTime")
if err != nil {
c.jsonErr(500, 500, "更新失败")
return
}
// 2. 软删除原本的所有提醒
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("schedule_id", id).
Update(map[string]interface{}{
"IsDeleted": 1,
"UpdateTime": time.Now(),
})
// 3. 重新建立提醒
for _, ch := range p.RemindChannels {
ch = strings.ToUpper(strings.TrimSpace(ch))
if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" {
continue
}
targetVal := p.ReceiverTargets[ch]
var target *string
if targetVal != "" {
target = &targetVal
}
firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute)
reminder := models.PlatformScheduleReminder{
ScheduleID: id,
RemindChannel: ch,
AdvanceMinutes: p.AdvanceMinutes,
NextRemindTime: firstSendTime,
ReceiverUserID: schedule.UserID, // 谁创建的就发给谁
ReceiverTarget: target,
RemindStatus: 0,
CreateTime: time.Now(),
UpdateTime: time.Now(),
}
if ch == "EMAIL" || ch == "BARK" {
token := generateToken()
reminder.AckToken = &token
reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes
reminder.MaxSendCount = p.MaxSendCount
if reminder.MaxSendCount <= 0 {
reminder.MaxSendCount = 1
}
} else {
reminder.RepeatIntervalMinutes = 0
reminder.MaxSendCount = 1
}
_, err = models.Orm.Insert(&reminder)
if err != nil {
c.jsonErr(500, 500, "重新创建提醒失败")
return
}
}
c.ok(nil)
}
// DeleteReminder DELETE /platform/reminder/:id
func (c *PlatformReminderController) DeleteReminder() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
c.jsonErr(400, 400, "无效的ID")
return
}
// 检查是否所有关联的提醒都已结束
var reminders []models.PlatformScheduleReminder
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("schedule_id", id).
Filter("is_deleted", 0).
All(&reminders)
isFinished := true
if len(reminders) == 0 {
isFinished = false
} else {
for _, r := range reminders {
if r.RemindStatus != 2 {
isFinished = false
break
}
}
}
if isFinished {
c.jsonErr(400, 400, "该日程提醒已全部结束,无法删除")
return
}
// 软删除日程
// 这里的 id 既可以是主表的 id,也可以是日程的 id
// 我们如果是管理页面,都是基于日程维度的,所以这里 id 指代 schedule_id
_, err := models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id", id).Delete()
if err == nil {
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("schedule_id", id).
Update(map[string]interface{}{
"IsDeleted": 1,
"UpdateTime": time.Now(),
})
}
c.ok(nil)
}
type reminderBatchDeletePayload struct {
Ids []uint64 `json:"ids"`
}
// BatchDeleteReminder POST /platform/reminder/batchDelete
func (c *PlatformReminderController) BatchDeleteReminder() {
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 reminderBatchDeletePayload
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if len(p.Ids) == 0 {
c.ok(nil)
return
}
// 检查选中的日程是否有任何一个是全部结束的,防误操作
for _, scheduleID := range p.Ids {
var reminders []models.PlatformScheduleReminder
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("schedule_id", scheduleID).
Filter("is_deleted", 0).
All(&reminders)
isFinished := true
if len(reminders) == 0 {
isFinished = false
} else {
for _, r := range reminders {
if r.RemindStatus != 2 {
isFinished = false
break
}
}
}
if isFinished {
c.jsonErr(400, 400, fmt.Sprintf("选中的日程ID %d 的提醒已全部结束,无法删除", scheduleID))
return
}
}
// 批量软删除
_, _ = models.Orm.QueryTable(new(models.PlatformSchedule)).Filter("id__in", p.Ids).Delete()
_, _ = models.Orm.QueryTable(new(models.PlatformScheduleReminder)).
Filter("schedule_id__in", p.Ids).
Update(map[string]interface{}{
"IsDeleted": 1,
"UpdateTime": time.Now(),
})
c.ok(nil)
}
type reminderTestPayload struct {
Title string `json:"title"`
Content string `json:"content"`
RemindChannels []string `json:"remind_channels"`
}
// TestReminder POST /platform/reminder/test
func (c *PlatformReminderController) TestReminder() {
claims, err := c.platformClaims()
if 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 reminderTestPayload
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if strings.TrimSpace(p.Title) == "" {
p.Title = "测试提醒"
}
if strings.TrimSpace(p.Content) == "" {
p.Content = "这是一条验证日程提醒配置的测试通知。"
}
senders := map[string]services.ReminderSender{
"SMS": &services.SMSSender{},
"EMAIL": &services.EmailSender{},
"BARK": &services.BarkSender{},
"SITE_MSG": &services.SiteMsgSender{},
}
type TestResult struct {
Channel string `json:"channel"`
Success bool `json:"success"`
Msg string `json:"msg"`
}
results := make([]TestResult, 0)
for _, ch := range p.RemindChannels {
ch = strings.ToUpper(strings.TrimSpace(ch))
sender, ok := senders[ch]
if !ok {
results = append(results, TestResult{Channel: ch, Success: false, Msg: "不支持的提醒渠道"})
continue
}
dummyToken := "test-token-for-verification"
reminder := &models.PlatformScheduleReminder{
RemindChannel: ch,
ReceiverUserID: uint64(claims.UserID),
AckToken: &dummyToken,
}
success, sendErr := sender.Send(context.Background(), reminder, "[测试]"+p.Title, p.Content)
msg := "发送成功"
if !success {
msg = "发送失败"
if sendErr != nil {
msg = sendErr.Error()
}
}
results = append(results, TestResult{Channel: ch, Success: success, Msg: msg})
}
c.ok(results)
}
+339
View File
@@ -0,0 +1,339 @@
package controllers
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"server/pkg/jwtutil"
"server/services"
beego "github.com/beego/beego/v2/server/web"
)
type PlatformSiteReminderController struct {
beego.Controller
}
func (c *PlatformSiteReminderController) 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 *PlatformSiteReminderController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
// GetConfig GET /platform/sitereminder/config
func (c *PlatformSiteReminderController) GetConfig() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
cfg, err := services.GetSiteReminderConfig()
if err != nil {
c.jsonErr(500, 500, "获取配置失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": cfg}
_ = c.ServeJSON()
}
// SaveConfig POST /platform/sitereminder/config
func (c *PlatformSiteReminderController) SaveConfig() {
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 struct {
RetentionDays int `json:"retention_days"`
AutoRead int8 `json:"auto_read"`
}
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if err := services.SaveSiteReminderConfig(p.RetentionDays, p.AutoRead); err != nil {
c.jsonErr(500, 500, "保存配置失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "保存成功"}
_ = c.ServeJSON()
}
// Send POST /platform/sitereminder/send
func (c *PlatformSiteReminderController) Send() {
claims, err := c.platformClaims()
if 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 struct {
Title string `json:"title"`
Content string `json:"content"`
TargetType string `json:"target_type"` // platform, tenant_all, role, tenant
TargetRoleID uint64 `json:"target_role_id"`
TargetTenantID uint64 `json:"target_tenant_id"`
}
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
p.Title = strings.TrimSpace(p.Title)
p.Content = strings.TrimSpace(p.Content)
if p.Title == "" || p.Content == "" {
c.jsonErr(400, 400, "标题与内容不能为空")
return
}
if p.TargetType == "" {
c.jsonErr(400, 400, "发送目标类型不能为空")
return
}
err = services.SendSiteReminder(p.Title, p.Content, uint64(claims.UserID), "platform", p.TargetType, p.TargetRoleID, p.TargetTenantID)
if err != nil {
c.jsonErr(500, 500, "发送失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "发送成功"}
_ = c.ServeJSON()
}
// GetMyList GET /platform/sitereminder/myList
func (c *PlatformSiteReminderController) GetMyList() {
claims, err := c.platformClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 10)
var isRead *int8
if isReadStr := c.GetString("isRead"); isReadStr != "" {
if val, err := strconv.Atoi(isReadStr); err == nil {
v := int8(val)
isRead = &v
}
}
list, total, err := services.ListReminders(uint64(claims.UserID), "platform", page, pageSize, isRead)
if err != nil {
c.jsonErr(500, 500, "获取消息列表失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"list": list,
"total": total,
},
}
_ = c.ServeJSON()
}
// MarkRead POST /platform/sitereminder/read
func (c *PlatformSiteReminderController) MarkRead() {
claims, err := c.platformClaims()
if 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 struct {
ID uint64 `json:"id"`
}
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
err = services.MarkReminderRead(p.ID, uint64(claims.UserID), "platform")
if err != nil {
c.jsonErr(500, 500, "操作失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
}
// MarkAllRead POST /platform/sitereminder/readall
func (c *PlatformSiteReminderController) MarkAllRead() {
claims, err := c.platformClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
err = services.MarkAllRemindersRead(uint64(claims.UserID), "platform")
if err != nil {
c.jsonErr(500, 500, "操作失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
}
// Delete POST /platform/sitereminder/delete
func (c *PlatformSiteReminderController) Delete() {
claims, err := c.platformClaims()
if 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 struct {
ID uint64 `json:"id"`
}
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
err = services.DeleteReminder(p.ID, uint64(claims.UserID), "platform")
if err != nil {
c.jsonErr(500, 500, "删除失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
}
// GetSentList GET /platform/sitereminder/sentList
func (c *PlatformSiteReminderController) GetSentList() {
claims, err := c.platformClaims()
if err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 10)
list, total, err := services.ListSentReminders(uint64(claims.UserID), page, pageSize)
if err != nil {
c.jsonErr(500, 500, "获取发送列表失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"list": list,
"total": total,
},
}
_ = c.ServeJSON()
}
// UpdateSent POST /platform/sitereminder/updateSent
func (c *PlatformSiteReminderController) UpdateSent() {
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 struct {
BatchID string `json:"batch_id"`
Title string `json:"title"`
Content string `json:"content"`
TargetType string `json:"target_type"`
TargetRoleID uint64 `json:"target_role_id"`
TargetTenantID uint64 `json:"target_tenant_id"`
}
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
p.Title = strings.TrimSpace(p.Title)
p.Content = strings.TrimSpace(p.Content)
if p.BatchID == "" || p.Title == "" || p.Content == "" {
c.jsonErr(400, 400, "批次号、标题与内容不能为空")
return
}
if p.TargetType == "" {
c.jsonErr(400, 400, "发送目标类型不能为空")
return
}
err = services.UpdateSentReminder(p.BatchID, p.Title, p.Content, p.TargetType, p.TargetRoleID, p.TargetTenantID)
if err != nil {
c.jsonErr(500, 500, "修改失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"}
_ = c.ServeJSON()
}
// DeleteSentBatch POST /platform/sitereminder/deleteSent
func (c *PlatformSiteReminderController) DeleteSentBatch() {
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 struct {
BatchID string `json:"batch_id"`
}
if err := json.Unmarshal(raw, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if p.BatchID == "" {
c.jsonErr(400, 400, "批次号不能为空")
return
}
err = services.DeleteSentReminderBatch(p.BatchID)
if err != nil {
c.jsonErr(500, 500, "删除失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "删除成功"}
_ = c.ServeJSON()
}
+44 -65
View File
@@ -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 == "" {