Files
yunzerwebsiteallinone/go/services/system_sitereminder.go
T
2026-09-20 00:19:08 +08:00

533 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package services
import (
"context"
"encoding/json"
"fmt"
"strconv"
"time"
"github.com/beego/beego/v2/client/orm"
"server/models"
)
// GetSiteReminderConfig 获取站内信配置(从 yz_platform_normal_setting 读取)
func GetSiteReminderConfig() (models.SystemSiteReminder, error) {
retentionDaysStr := models.GetPlatformSettingValue("sitemsg_retention_days", "30")
autoReadStr := models.GetPlatformSettingValue("sitemsg_auto_read", "0")
retentionDays, _ := strconv.Atoi(retentionDaysStr)
if retentionDays <= 0 {
retentionDays = 30
}
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 保存/更新配置
func SaveSiteReminderConfig(retentionDays int, autoRead int8) error {
if retentionDays <= 0 {
retentionDays = 30
}
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
}
// reminderReceiver 站内信接收人(用户ID + 所属端类型)
type reminderReceiver struct {
ID uint64
Type string // platform-平台端 tenant-租户端
}
// queryEnabledAdminUserIDs 查询启用的平台用户ID;cond 为附加过滤条件(可为 nil)。
func queryEnabledAdminUserIDs(cond *orm.Condition) ([]uint64, error) {
qs := models.Orm.QueryTable(new(models.AdminUser)).
Filter("status", 1).
Filter("delete_time__isnull", true)
if cond != nil {
qs = qs.SetCond(orm.NewCondition().
And("status", 1).
And("delete_time__isnull", true).
AndCond(cond))
}
var list []models.AdminUser
if _, err := qs.All(&list, "id"); err != nil {
return nil, err
}
ids := make([]uint64, 0, len(list))
for _, u := range list {
ids = append(ids, u.ID)
}
return ids, nil
}
// queryEnabledTenantUserIDs 查询启用的租户用户ID;cond 为附加过滤条件(可为 nil)。
func queryEnabledTenantUserIDs(cond *orm.Condition) ([]uint64, error) {
qs := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("status", 1).
Filter("delete_time__isnull", true)
if cond != nil {
qs = qs.SetCond(orm.NewCondition().
And("status", 1).
And("delete_time__isnull", true).
AndCond(cond))
}
var list []models.SystemTenantUser
if _, err := qs.All(&list, "id"); err != nil {
return nil, err
}
ids := make([]uint64, 0, len(list))
for _, u := range list {
ids = append(ids, u.ID)
}
return ids, nil
}
// marshalTargetIDs 将多选目标ID序列化为 JSON 字符串(空列表返回 nil)。
func marshalTargetIDs(targetIDs []uint64) *string {
if len(targetIDs) == 0 {
return nil
}
b, err := json.Marshal(targetIDs)
if err != nil {
return nil
}
s := string(b)
return &s
}
// legacyTargetColumns 旧字段兼容写入:role → target_role_id,tenant → target_tenant_id(取首个)
func legacyTargetColumns(targetType string, targetIDs []uint64) (roleID, tenantID uint64) {
if len(targetIDs) == 0 {
return 0, 0
}
switch targetType {
case "role":
return targetIDs[0], 0
case "tenant":
return 0, targetIDs[0]
}
return 0, 0
}
// resolveReminderReceivers 解析接收目标 → 接收人集合(按端区分类型,已去重)。
// targetType:
// all 全平台(平台端 + 租户端所有启用用户)
// platform 平台端所有启用用户
// tenant_all 租户端所有租户的启用用户
// tenant_role 指定租户角色(可多选)下的启用用户
// tenant 指定租户(可多选)下的启用用户
// tenant_user 指定租户用户(可多选)
// role 兼容旧数据:指定平台角色(可多选)下的启用用户
func resolveReminderReceivers(targetType string, targetIDs []uint64) ([]reminderReceiver, error) {
receivers := make([]reminderReceiver, 0)
seen := map[string]bool{}
appendIDs := func(ids []uint64, rtype string) {
for _, id := range ids {
if id == 0 {
continue
}
key := fmt.Sprintf("%s_%d", rtype, id)
if seen[key] {
continue
}
seen[key] = true
receivers = append(receivers, reminderReceiver{ID: id, Type: rtype})
}
}
switch targetType {
case "all": // 全平台:平台端 + 租户端所有启用用户
adminIDs, err := queryEnabledAdminUserIDs(nil)
if err != nil {
return nil, fmt.Errorf("查询平台用户失败: %w", err)
}
tenantIDs, err := queryEnabledTenantUserIDs(nil)
if err != nil {
return nil, fmt.Errorf("查询租户用户失败: %w", err)
}
appendIDs(adminIDs, "platform")
appendIDs(tenantIDs, "tenant")
case "platform": // 平台端所有启用用户
adminIDs, err := queryEnabledAdminUserIDs(nil)
if err != nil {
return nil, fmt.Errorf("查询平台用户失败: %w", err)
}
appendIDs(adminIDs, "platform")
case "tenant_all": // 租户端所有租户的启用用户
tenantIDs, err := queryEnabledTenantUserIDs(nil)
if err != nil {
return nil, fmt.Errorf("查询租户用户失败: %w", err)
}
appendIDs(tenantIDs, "tenant")
case "tenant_role": // 指定租户角色(可多选)下的启用用户
if len(targetIDs) == 0 {
return nil, fmt.Errorf("请选择接收的租户角色")
}
tenantIDs, err := queryEnabledTenantUserIDs(orm.NewCondition().And("group_id__in", targetIDs))
if err != nil {
return nil, fmt.Errorf("根据租户角色查询用户失败: %w", err)
}
appendIDs(tenantIDs, "tenant")
case "tenant": // 指定租户(可多选)下的启用用户
if len(targetIDs) == 0 {
return nil, fmt.Errorf("请选择接收的租户")
}
tenantIDs, err := queryEnabledTenantUserIDs(orm.NewCondition().And("tid__in", targetIDs))
if err != nil {
return nil, fmt.Errorf("根据租户查询用户失败: %w", err)
}
appendIDs(tenantIDs, "tenant")
case "tenant_user": // 指定租户用户(可多选)
if len(targetIDs) == 0 {
return nil, fmt.Errorf("请选择接收的租户用户")
}
tenantIDs, err := queryEnabledTenantUserIDs(orm.NewCondition().And("id__in", targetIDs))
if err != nil {
return nil, fmt.Errorf("查询租户用户失败: %w", err)
}
appendIDs(tenantIDs, "tenant")
case "role": // 兼容旧数据:平台角色(可多选)下的启用用户
if len(targetIDs) == 0 {
return nil, fmt.Errorf("请选择接收的角色")
}
adminIDs, err := queryEnabledAdminUserIDs(orm.NewCondition().And("role_id__in", targetIDs))
if err != nil {
return nil, fmt.Errorf("根据角色查询用户失败: %w", err)
}
appendIDs(adminIDs, "platform")
default:
return nil, fmt.Errorf("未知的发送目标类型: %s", targetType)
}
return receivers, nil
}
// SendSiteReminder 发送站内信
// targetType: all (全平台), platform (平台端), tenant_all (租户端所有租户),
// tenant_role (租户角色), tenant (指定租户), tenant_user (指定租户用户), role (平台角色-旧)
func SendSiteReminder(title, content string, senderID uint64, senderType string, targetType string, targetIDs []uint64) error {
receivers, err := resolveReminderReceivers(targetType, targetIDs)
if err != nil {
return err
}
if len(receivers) == 0 {
return nil
}
targetIDsJSON := marshalTargetIDs(targetIDs)
legacyRoleID, legacyTenantID := legacyTargetColumns(targetType, targetIDs)
now := time.Now()
batchID := fmt.Sprintf("%d_%d", now.UnixNano(), senderID)
reminders := make([]models.SystemReminderList, 0, len(receivers))
for _, r := range receivers {
reminders = append(reminders, models.SystemReminderList{
Title: title,
Content: content,
SenderID: senderID,
SenderType: senderType,
ReceiverID: r.ID,
ReceiverType: r.Type,
IsRead: 0,
CreateTime: &now,
BatchID: batchID,
TargetType: targetType,
TargetIDs: targetIDsJSON,
TargetRoleID: legacyRoleID,
TargetTenantID: legacyTenantID,
})
}
// 批量插入
_, 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, targetIDs []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. 根据新的目标接收群体获取接收人列表
receivers, err := resolveReminderReceivers(targetType, targetIDs)
if err != nil {
return err
}
targetIDsJSON := marshalTargetIDs(targetIDs)
legacyRoleID, legacyTenantID := legacyTargetColumns(targetType, targetIDs)
// 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 _, r := range receivers {
key := fmt.Sprintf("%s_%d", r.Type, r.ID)
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 _, r := range receivers {
key := fmt.Sprintf("%s_%d", r.Type, r.ID)
if rec, exists := existingMap[key]; exists {
rec.Title = title
rec.Content = content
rec.TargetType = targetType
rec.TargetIDs = targetIDsJSON
rec.TargetRoleID = legacyRoleID
rec.TargetTenantID = legacyTenantID
cols := []string{"Title", "Content", "TargetType", "TargetIDs", "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: r.ID,
ReceiverType: r.Type,
IsRead: 0,
CreateTime: &now,
BatchID: batchID,
TargetType: targetType,
TargetIDs: targetIDsJSON,
TargetRoleID: legacyRoleID,
TargetTenantID: legacyTenantID,
})
}
}
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
}