优化站内信模块

This commit is contained in:
2026-06-17 23:07:39 +08:00
parent d3886e2475
commit 22e0e75c35
25 changed files with 2538 additions and 39 deletions
+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()
}
+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
+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()
}
+3
View File
@@ -66,6 +66,9 @@ func Init(_ string) {
new(CmsArticleCategory),
new(CmsArticle),
new(SystemSiteReminder),
new(SystemReminderList),
)
// 创建全局 Ormer
+26
View File
@@ -0,0 +1,26 @@
package models
import "time"
// SystemReminderList 站内信消息列表表 yz_system_reminderlist
type SystemReminderList struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Title string `orm:"column(title);size(255)" json:"title"`
Content string `orm:"column(content);type(text)" json:"content"`
SenderID uint64 `orm:"column(sender_id);default(0)" json:"sender_id"`
SenderType string `orm:"column(sender_type);size(32);default('system')" json:"sender_type"` // system, platform, tenant
ReceiverID uint64 `orm:"column(receiver_id)" json:"receiver_id"`
ReceiverType string `orm:"column(receiver_type);size(32)" json:"receiver_type"` // platform, tenant
IsRead int8 `orm:"column(is_read);default(0)" json:"is_read"` // 0-未读, 1-已读
ReadTime *time.Time `orm:"column(read_time);type(datetime);null" json:"read_time"`
CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
BatchID string `orm:"column(batch_id);size(64);default('')" json:"batch_id"`
TargetType string `orm:"column(target_type);size(32);default('')" json:"target_type"`
TargetRoleID uint64 `orm:"column(target_role_id);default(0)" json:"target_role_id"`
TargetTenantID uint64 `orm:"column(target_tenant_id);default(0)" json:"target_tenant_id"`
}
func (m *SystemReminderList) TableName() string {
return "yz_system_reminderlist"
}
+16
View File
@@ -0,0 +1,16 @@
package models
import "time"
// SystemSiteReminder 站内信配置表 yz_system_sitereminder
type SystemSiteReminder struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
RetentionDays int `orm:"column(retention_days);default(30)" json:"retention_days"`
AutoRead int8 `orm:"column(auto_read);default(0)" json:"auto_read"`
CreateTime *time.Time `orm:"column(create_time);type(datetime);null" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
}
func (m *SystemSiteReminder) TableName() string {
return "yz_system_sitereminder"
}
+6
View File
@@ -51,6 +51,12 @@ func RegisterAuthRoutes() {
beego.Router("/backend/loginVerifyInfos", &controllers.BackendLoginVerifyController{}, "get:GetLoginVerifyInfos")
beego.Router("/backend/saveloginVerifyInfos", &controllers.BackendLoginVerifyController{}, "post:SaveLoginVerifyInfos")
// 站内信(yz_system_reminderlist
beego.Router("/backend/sitereminder/myList", &controllers.BackendSiteReminderController{}, "get:GetMyList")
beego.Router("/backend/sitereminder/read", &controllers.BackendSiteReminderController{}, "post:MarkRead")
beego.Router("/backend/sitereminder/readall", &controllers.BackendSiteReminderController{}, "post:MarkAllRead")
beego.Router("/backend/sitereminder/delete", &controllers.BackendSiteReminderController{}, "post:Delete")
// 文件管理(yz_system_files / yz_system_files_category
beego.Router("/backend/usercate", &controllers.BackendFileController{}, "get:GetUserCate")
beego.Router("/backend/allfiles", &controllers.BackendFileController{}, "get:GetAllFiles")
+11
View File
@@ -126,6 +126,17 @@ func Register() {
beego.Router("/platform/email/editinfo", &controllers.PlatformEmailController{}, "post:EditInfo")
beego.Router("/platform/email/sendtestemail", &controllers.PlatformEmailController{}, "post:SendTestEmail")
// 站内信配置与发送(yz_system_sitereminder / yz_system_reminderlist
beego.Router("/platform/sitereminder/config", &controllers.PlatformSiteReminderController{}, "get:GetConfig;post:SaveConfig")
beego.Router("/platform/sitereminder/send", &controllers.PlatformSiteReminderController{}, "post:Send")
beego.Router("/platform/sitereminder/myList", &controllers.PlatformSiteReminderController{}, "get:GetMyList")
beego.Router("/platform/sitereminder/read", &controllers.PlatformSiteReminderController{}, "post:MarkRead")
beego.Router("/platform/sitereminder/readall", &controllers.PlatformSiteReminderController{}, "post:MarkAllRead")
beego.Router("/platform/sitereminder/delete", &controllers.PlatformSiteReminderController{}, "post:Delete")
beego.Router("/platform/sitereminder/sentList", &controllers.PlatformSiteReminderController{}, "get:GetSentList")
beego.Router("/platform/sitereminder/updateSent", &controllers.PlatformSiteReminderController{}, "post:UpdateSent")
beego.Router("/platform/sitereminder/deleteSent", &controllers.PlatformSiteReminderController{}, "post:DeleteSentBatch")
// 短信配置(yz_system_sms
beego.Router("/platform/sms/info", &controllers.PlatformSMSController{}, "get:GetSmsInfo")
beego.Router("/platform/sms/editinfo", &controllers.PlatformSMSController{}, "post:EditSmsInfo")
-3
View File
@@ -25,9 +25,6 @@ func UpsertFirstSystemEmail(fromAddress string, fromName *string, host string, p
if timeout == 0 {
timeout = 30
}
if status == 0 {
status = 1
}
fromAddress = strings.TrimSpace(fromAddress)
host = strings.TrimSpace(host)
+399
View File
@@ -0,0 +1,399 @@
package services
import (
"context"
"fmt"
"time"
"github.com/beego/beego/v2/client/orm"
"server/models"
)
// GetSiteReminderConfig 获取站内信配置(只读首条记录,不存在则初始化默认值)
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
}
return row, err
}
// SaveSiteReminderConfig 保存/更新配置
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
}
// SendSiteReminder 发送站内信
// targetType: platform (平台端), tenant_all (管理端所有用户), role (平台角色), tenant (特定租户)
func SendSiteReminder(title, content string, senderID uint64, senderType string, targetType string, targetRoleID uint64, targetTenantID uint64) error {
var receiverIDs []uint64
var receiverType string
switch targetType {
case "platform":
receiverType = "platform"
var list []models.AdminUser
_, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id")
if err != nil {
return fmt.Errorf("查询平台用户失败: %w", err)
}
for _, u := range list {
receiverIDs = append(receiverIDs, u.ID)
}
case "tenant_all":
receiverType = "tenant"
var list []models.SystemTenantUser
_, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id")
if err != nil {
return fmt.Errorf("查询租户用户失败: %w", err)
}
for _, u := range list {
receiverIDs = append(receiverIDs, u.ID)
}
case "role":
receiverType = "platform"
var list []models.AdminUser
_, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("role_id", targetRoleID).Filter("delete_time__isnull", true).All(&list, "id")
if err != nil {
return fmt.Errorf("根据角色查询用户失败: %w", err)
}
for _, u := range list {
receiverIDs = append(receiverIDs, u.ID)
}
case "tenant":
receiverType = "tenant"
var list []models.SystemTenantUser
_, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("tid", targetTenantID).Filter("delete_time__isnull", true).All(&list, "id")
if err != nil {
return fmt.Errorf("根据租户查询用户失败: %w", err)
}
for _, u := range list {
receiverIDs = append(receiverIDs, u.ID)
}
default:
return fmt.Errorf("未知的发送目标类型: %s", targetType)
}
if len(receiverIDs) == 0 {
return nil
}
now := time.Now()
batchID := fmt.Sprintf("%d_%d", now.UnixNano(), senderID)
var reminders []models.SystemReminderList
for _, rid := range receiverIDs {
reminders = append(reminders, models.SystemReminderList{
Title: title,
Content: content,
SenderID: senderID,
SenderType: senderType,
ReceiverID: rid,
ReceiverType: receiverType,
IsRead: 0,
CreateTime: &now,
BatchID: batchID,
TargetType: targetType,
TargetRoleID: targetRoleID,
TargetTenantID: targetTenantID,
})
}
// 批量插入
_, err := models.Orm.InsertMulti(100, reminders)
return err
}
// ListReminders 列表查询
func ListReminders(receiverID uint64, receiverType string, page, pageSize int, isRead *int8) ([]models.SystemReminderList, int64, error) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
var list []models.SystemReminderList
qs := models.Orm.QueryTable(new(models.SystemReminderList)).
Filter("receiver_id", receiverID).
Filter("receiver_type", receiverType).
Filter("delete_time__isnull", true)
if isRead != nil {
qs = qs.Filter("is_read", *isRead)
}
total, err := qs.Count()
if err != nil {
return nil, 0, err
}
offset := (page - 1) * pageSize
_, err = qs.OrderBy("-create_time", "-id").Limit(pageSize, offset).All(&list)
return list, total, err
}
// MarkReminderRead 标记单条已读
func MarkReminderRead(id uint64, receiverID uint64, receiverType string) error {
now := time.Now()
_, err := models.Orm.QueryTable(new(models.SystemReminderList)).
Filter("id", id).
Filter("receiver_id", receiverID).
Filter("receiver_type", receiverType).
Update(map[string]interface{}{
"is_read": 1,
"read_time": &now,
})
return err
}
// MarkAllRemindersRead 一键全部已读
func MarkAllRemindersRead(receiverID uint64, receiverType string) error {
now := time.Now()
_, err := models.Orm.QueryTable(new(models.SystemReminderList)).
Filter("receiver_id", receiverID).
Filter("receiver_type", receiverType).
Filter("is_read", 0).
Update(map[string]interface{}{
"is_read": 1,
"read_time": &now,
})
return err
}
// DeleteReminder 删除消息
func DeleteReminder(id uint64, receiverID uint64, receiverType string) error {
now := time.Now()
_, err := models.Orm.QueryTable(new(models.SystemReminderList)).
Filter("id", id).
Filter("receiver_id", receiverID).
Filter("receiver_type", receiverType).
Update(map[string]interface{}{
"delete_time": &now,
})
return err
}
// AutoCleanExpiredReminders 自动清理过期站内信
func AutoCleanExpiredReminders() error {
cfg, err := GetSiteReminderConfig()
if err != nil {
return err
}
if cfg.RetentionDays <= 0 {
return nil
}
expireTime := time.Now().AddDate(0, 0, -cfg.RetentionDays)
_, err = models.Orm.QueryTable(new(models.SystemReminderList)).
Filter("create_time__lt", expireTime).
Delete()
return err
}
// ListSentReminders 获取已发送的消息列表(按 batch_id 分组)
func ListSentReminders(senderID uint64, page, pageSize int) ([]models.SystemReminderList, int64, error) {
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
offset := (page - 1) * pageSize
var total int64
err := models.Orm.Raw("SELECT COUNT(DISTINCT batch_id) FROM yz_system_reminderlist WHERE sender_id = ? AND delete_time IS NULL", senderID).QueryRow(&total)
if err != nil {
return nil, 0, err
}
var list []models.SystemReminderList
_, err = models.Orm.Raw("SELECT * FROM yz_system_reminderlist WHERE id IN (SELECT MIN(id) FROM yz_system_reminderlist WHERE sender_id = ? AND delete_time IS NULL GROUP BY batch_id) ORDER BY id DESC LIMIT ? OFFSET ?", senderID, pageSize, offset).QueryRows(&list)
if err != nil {
return nil, 0, err
}
return list, total, nil
}
// UpdateSentReminder 更新已发出的消息(更新该批次下所有接收者的消息,支持修改目标接收群体)
func UpdateSentReminder(batchID string, title, content, targetType string, targetRoleID, targetTenantID uint64) error {
// 1. 获取当前发送者ID (从该批次中任意一条记录中获取)
var firstRecord models.SystemReminderList
err := models.Orm.QueryTable(new(models.SystemReminderList)).Filter("batch_id", batchID).Limit(1).One(&firstRecord)
if err != nil {
return fmt.Errorf("找不到该批次的站内信记录: %w", err)
}
senderID := firstRecord.SenderID
senderType := firstRecord.SenderType
// 2. 根据新的目标接收群体获取接收人列表
var receiverIDs []uint64
var receiverType string
switch targetType {
case "platform":
receiverType = "platform"
var list []models.AdminUser
_, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id")
if err != nil {
return fmt.Errorf("查询平台用户失败: %w", err)
}
for _, u := range list {
receiverIDs = append(receiverIDs, u.ID)
}
case "tenant_all":
receiverType = "tenant"
var list []models.SystemTenantUser
_, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("delete_time__isnull", true).All(&list, "id")
if err != nil {
return fmt.Errorf("查询租户用户失败: %w", err)
}
for _, u := range list {
receiverIDs = append(receiverIDs, u.ID)
}
case "role":
receiverType = "platform"
var list []models.AdminUser
_, err := models.Orm.QueryTable(new(models.AdminUser)).Filter("status", 1).Filter("role_id", targetRoleID).Filter("delete_time__isnull", true).All(&list, "id")
if err != nil {
return fmt.Errorf("根据角色查询用户失败: %w", err)
}
for _, u := range list {
receiverIDs = append(receiverIDs, u.ID)
}
case "tenant":
receiverType = "tenant"
var list []models.SystemTenantUser
_, err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("status", 1).Filter("tid", targetTenantID).Filter("delete_time__isnull", true).All(&list, "id")
if err != nil {
return fmt.Errorf("根据租户查询用户失败: %w", err)
}
for _, u := range list {
receiverIDs = append(receiverIDs, u.ID)
}
default:
return fmt.Errorf("未知的发送目标类型: %s", targetType)
}
// 3. 获取该批次中现有的所有记录 (包括已删除的)
var existingRecords []models.SystemReminderList
_, err = models.Orm.QueryTable(new(models.SystemReminderList)).Filter("batch_id", batchID).All(&existingRecords)
if err != nil {
return fmt.Errorf("获取现有记录失败: %w", err)
}
// 建立 map 快速查找,Key 为 "receiverType_receiverID"
existingMap := make(map[string]*models.SystemReminderList)
for i := range existingRecords {
key := fmt.Sprintf("%s_%d", existingRecords[i].ReceiverType, existingRecords[i].ReceiverID)
existingMap[key] = &existingRecords[i]
}
newReceiverMap := make(map[string]bool)
for _, rid := range receiverIDs {
key := fmt.Sprintf("%s_%d", receiverType, rid)
newReceiverMap[key] = true
}
now := time.Now()
// 事务处理
err = models.Orm.DoTx(func(c context.Context, txOrm orm.TxOrmer) error {
// A. 对于已经不在新接收者列表中的用户,软删除
for key, rec := range existingMap {
if !newReceiverMap[key] {
if rec.DeleteTime == nil {
rec.DeleteTime = &now
if _, e := txOrm.Update(rec, "DeleteTime"); e != nil {
return e
}
}
}
}
// B. 对于仍然在新接收者列表中的用户,更新标题、内容、以及 target 信息;如果原来被删除了,清除 delete_time
var newInserts []models.SystemReminderList
for _, rid := range receiverIDs {
key := fmt.Sprintf("%s_%d", receiverType, rid)
if rec, exists := existingMap[key]; exists {
rec.Title = title
rec.Content = content
rec.TargetType = targetType
rec.TargetRoleID = targetRoleID
rec.TargetTenantID = targetTenantID
cols := []string{"Title", "Content", "TargetType", "TargetRoleID", "TargetTenantID"}
if rec.DeleteTime != nil {
rec.DeleteTime = nil
rec.IsRead = 0
rec.ReadTime = nil
cols = append(cols, "DeleteTime", "IsRead", "ReadTime")
}
if _, e := txOrm.Update(rec, cols...); e != nil {
return e
}
} else {
// C. 对于新增加的接收者,插入新记录
newInserts = append(newInserts, models.SystemReminderList{
Title: title,
Content: content,
SenderID: senderID,
SenderType: senderType,
ReceiverID: rid,
ReceiverType: receiverType,
IsRead: 0,
CreateTime: &now,
BatchID: batchID,
TargetType: targetType,
TargetRoleID: targetRoleID,
TargetTenantID: targetTenantID,
})
}
}
if len(newInserts) > 0 {
if _, e := txOrm.InsertMulti(100, newInserts); e != nil {
return e
}
}
return nil
})
return err
}
// DeleteSentReminderBatch 删除已发送消息(删除该批次下所有记录)
func DeleteSentReminderBatch(batchID string) error {
now := time.Now()
_, err := models.Orm.QueryTable(new(models.SystemReminderList)).
Filter("batch_id", batchID).
Update(map[string]interface{}{
"delete_time": &now,
})
return err
}