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 }