649 lines
17 KiB
Go
649 lines
17 KiB
Go
package service
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"regexp"
|
||
"strings"
|
||
"time"
|
||
"unicode/utf8"
|
||
|
||
"git.iioio.com/freefire/jiang13-forum/model"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
var (
|
||
ErrCannotMessageSelf = errors.New("不能给自己发私信")
|
||
)
|
||
|
||
type MessageService struct {
|
||
filter *SensitiveFilter
|
||
settings *ForumSettingsService
|
||
}
|
||
|
||
func NewMessageService(filter *SensitiveFilter, settings *ForumSettingsService) *MessageService {
|
||
return &MessageService{filter: filter, settings: settings}
|
||
}
|
||
|
||
type MessageSendInput struct {
|
||
FromUserID uint
|
||
ToUserID uint
|
||
Subject string
|
||
Content string
|
||
Kind string
|
||
RelatedPostID *uint
|
||
RelatedReportID *uint
|
||
RelatedCommentID *uint
|
||
RelatedFloor *int
|
||
}
|
||
|
||
// Send 发送私信(用户互发或系统通知)
|
||
func (s *MessageService) Send(in MessageSendInput) (*model.PrivateMessage, error) {
|
||
if in.ToUserID == 0 {
|
||
return nil, errors.New("收件人不存在")
|
||
}
|
||
if in.FromUserID > 0 && in.FromUserID == in.ToUserID {
|
||
return nil, ErrCannotMessageSelf
|
||
}
|
||
if in.FromUserID > 0 {
|
||
var to model.User
|
||
if err := model.DB.Select("id", "banned").First(&to, in.ToUserID).Error; err != nil {
|
||
return nil, errors.New("收件人不存在")
|
||
}
|
||
if to.Banned {
|
||
return nil, errors.New("对方账号已被禁言,暂时无法私信")
|
||
}
|
||
}
|
||
|
||
subject := strings.TrimSpace(in.Subject)
|
||
content := strings.TrimSpace(in.Content)
|
||
if content == "" {
|
||
return nil, errors.New("请填写内容")
|
||
}
|
||
// 会话式私信可不填标题,用正文摘要兜底
|
||
if subject == "" {
|
||
subject = truncateRunes(content, 40)
|
||
}
|
||
if utf8.RuneCountInString(subject) > 80 {
|
||
return nil, errors.New("标题过长")
|
||
}
|
||
if utf8.RuneCountInString(content) > 4000 {
|
||
return nil, errors.New("内容过长")
|
||
}
|
||
|
||
if s.filter != nil {
|
||
subject = s.filter.Filter(subject)
|
||
content = s.filter.Filter(content)
|
||
}
|
||
|
||
kind := in.Kind
|
||
if kind == "" {
|
||
if in.FromUserID == 0 {
|
||
kind = model.MessageKindSystem
|
||
} else {
|
||
kind = model.MessageKindUser
|
||
}
|
||
}
|
||
|
||
msg := &model.PrivateMessage{
|
||
FromUserID: in.FromUserID,
|
||
ToUserID: in.ToUserID,
|
||
Subject: subject,
|
||
Content: content,
|
||
Kind: kind,
|
||
RelatedPostID: in.RelatedPostID,
|
||
RelatedReportID: in.RelatedReportID,
|
||
RelatedCommentID: in.RelatedCommentID,
|
||
RelatedFloor: in.RelatedFloor,
|
||
IsRead: false,
|
||
}
|
||
if err := model.DB.Create(msg).Error; err != nil {
|
||
return nil, err
|
||
}
|
||
_ = model.DB.Preload("FromUser").Preload("ToUser").First(msg, msg.ID).Error
|
||
return msg, nil
|
||
}
|
||
|
||
// SystemNotifyRefs 系统通知关联目标(帖子 / 评论 / 举报)
|
||
type SystemNotifyRefs struct {
|
||
PostID *uint
|
||
ReportID *uint
|
||
CommentID *uint
|
||
Floor *int
|
||
}
|
||
|
||
// SendSystem 系统私信(管理员/系统 → 用户)
|
||
func (s *MessageService) SendSystem(toUserID uint, subject, content, kind string, relatedPostID, relatedReportID *uint) (*model.PrivateMessage, error) {
|
||
return s.SendSystemWithRefs(toUserID, subject, content, kind, SystemNotifyRefs{
|
||
PostID: relatedPostID,
|
||
ReportID: relatedReportID,
|
||
})
|
||
}
|
||
|
||
// SendSystemWithRefs 系统私信(可附带评论楼层深链)
|
||
func (s *MessageService) SendSystemWithRefs(toUserID uint, subject, content, kind string, refs SystemNotifyRefs) (*model.PrivateMessage, error) {
|
||
if kind == "" {
|
||
kind = model.MessageKindSystem
|
||
}
|
||
return s.Send(MessageSendInput{
|
||
FromUserID: 0,
|
||
ToUserID: toUserID,
|
||
Subject: subject,
|
||
Content: content,
|
||
Kind: kind,
|
||
RelatedPostID: refs.PostID,
|
||
RelatedReportID: refs.ReportID,
|
||
RelatedCommentID: refs.CommentID,
|
||
RelatedFloor: refs.Floor,
|
||
})
|
||
}
|
||
|
||
// MarkMessageRead 将单条消息标为已读(仅收件人本人)
|
||
func (s *MessageService) MarkMessageRead(userID, messageID uint) error {
|
||
if messageID == 0 {
|
||
return errors.New("无效的消息")
|
||
}
|
||
res := model.DB.Model(&model.PrivateMessage{}).
|
||
Where("id = ? AND to_user_id = ? AND is_read = ?", messageID, userID, false).
|
||
Update("is_read", true)
|
||
if res.Error != nil {
|
||
return res.Error
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// MarkAllRead 全部标为已读
|
||
func (s *MessageService) MarkAllRead(userID uint) error {
|
||
return model.DB.Model(&model.PrivateMessage{}).
|
||
Where("to_user_id = ? AND is_read = ?", userID, false).
|
||
Update("is_read", true).Error
|
||
}
|
||
|
||
// UnreadCount 未读数
|
||
func (s *MessageService) UnreadCount(userID uint) (int64, error) {
|
||
var n int64
|
||
err := model.DB.Model(&model.PrivateMessage{}).
|
||
Where("to_user_id = ? AND is_read = ?", userID, false).
|
||
Count(&n).Error
|
||
return n, err
|
||
}
|
||
|
||
// UnreadCounts 未读总数,以及私信 / 系统通知分项
|
||
func (s *MessageService) UnreadCounts(userID uint) (total, dm, notify int64, err error) {
|
||
err = model.DB.Model(&model.PrivateMessage{}).
|
||
Where("to_user_id = ? AND is_read = ?", userID, false).
|
||
Count(&total).Error
|
||
if err != nil {
|
||
return 0, 0, 0, err
|
||
}
|
||
err = model.DB.Model(&model.PrivateMessage{}).
|
||
Where("to_user_id = ? AND is_read = ? AND from_user_id = 0", userID, false).
|
||
Count(¬ify).Error
|
||
if err != nil {
|
||
return 0, 0, 0, err
|
||
}
|
||
dm = total - notify
|
||
if dm < 0 {
|
||
dm = 0
|
||
}
|
||
return total, dm, notify, nil
|
||
}
|
||
|
||
// ListNotifications 系统通知列表(按时间倒序,非聊天气泡)
|
||
func (s *MessageService) ListNotifications(userID uint, page, size int, kind string) ([]model.PrivateMessage, int64, error) {
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
size = s.settings.NormalizePageSize(size)
|
||
db := model.DB.Model(&model.PrivateMessage{}).
|
||
Where("from_user_id = 0 AND to_user_id = ?", userID)
|
||
kind = strings.TrimSpace(kind)
|
||
if kind != "" && kind != "all" {
|
||
db = db.Where("kind = ?", kind)
|
||
}
|
||
var total int64
|
||
if err := db.Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
var list []model.PrivateMessage
|
||
err := db.Order("id desc").Offset((page - 1) * size).Limit(size).Find(&list).Error
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
if list == nil {
|
||
list = []model.PrivateMessage{}
|
||
}
|
||
s.enrichModerationStatus(list)
|
||
return list, total, nil
|
||
}
|
||
|
||
// enrichModerationStatus 为待审通知回填目标当前审核状态
|
||
func (s *MessageService) enrichModerationStatus(list []model.PrivateMessage) {
|
||
if len(list) == 0 {
|
||
return
|
||
}
|
||
|
||
resolvedByIndex := enrichModerationCommentIDs(list)
|
||
|
||
commentIDs := make([]uint, 0, len(list))
|
||
postIDs := make([]uint, 0, len(list))
|
||
// 历史评论通知:按帖+楼层回查(兜底)
|
||
type pfKey struct {
|
||
PostID uint
|
||
Floor int
|
||
}
|
||
pfNeeded := make([]pfKey, 0, len(list))
|
||
seenC := map[uint]struct{}{}
|
||
seenP := map[uint]struct{}{}
|
||
seenPF := map[pfKey]struct{}{}
|
||
|
||
for i := range list {
|
||
m := &list[i]
|
||
if m.Kind != model.MessageKindModeration {
|
||
continue
|
||
}
|
||
if m.RelatedCommentID != nil && *m.RelatedCommentID > 0 {
|
||
id := *m.RelatedCommentID
|
||
if _, ok := seenC[id]; !ok {
|
||
seenC[id] = struct{}{}
|
||
commentIDs = append(commentIDs, id)
|
||
}
|
||
continue
|
||
}
|
||
if cid, ok := resolvedByIndex[i]; ok && cid > 0 {
|
||
if _, ok := seenC[cid]; !ok {
|
||
seenC[cid] = struct{}{}
|
||
commentIDs = append(commentIDs, cid)
|
||
}
|
||
continue
|
||
}
|
||
if m.RelatedPostID == nil || *m.RelatedPostID == 0 {
|
||
continue
|
||
}
|
||
pid := *m.RelatedPostID
|
||
if looksLikeModerationComment(m.Subject, m.Content) {
|
||
floor := 0
|
||
if m.RelatedFloor != nil && *m.RelatedFloor > 0 {
|
||
floor = *m.RelatedFloor
|
||
} else {
|
||
floor = parseNotifyFloor(m.Content)
|
||
}
|
||
if floor > 0 {
|
||
k := pfKey{PostID: pid, Floor: floor}
|
||
if _, ok := seenPF[k]; !ok {
|
||
seenPF[k] = struct{}{}
|
||
pfNeeded = append(pfNeeded, k)
|
||
}
|
||
}
|
||
continue
|
||
}
|
||
if _, ok := seenP[pid]; !ok {
|
||
seenP[pid] = struct{}{}
|
||
postIDs = append(postIDs, pid)
|
||
}
|
||
}
|
||
|
||
commentStatus := map[uint]string{}
|
||
if len(commentIDs) > 0 {
|
||
type row struct {
|
||
ID uint
|
||
Status string
|
||
DeletedAt gorm.DeletedAt
|
||
}
|
||
var rows []row
|
||
_ = model.DB.Unscoped().Model(&model.Comment{}).
|
||
Select("id", "status", "deleted_at").
|
||
Where("id IN ?", commentIDs).
|
||
Find(&rows)
|
||
for _, r := range rows {
|
||
commentStatus[r.ID] = contentStatusOrDeleted(r.Status, r.DeletedAt)
|
||
}
|
||
for _, id := range commentIDs {
|
||
if _, ok := commentStatus[id]; !ok {
|
||
commentStatus[id] = "deleted"
|
||
}
|
||
}
|
||
}
|
||
|
||
statusByPF := map[pfKey]string{}
|
||
if len(pfNeeded) > 0 {
|
||
postSet := map[uint]struct{}{}
|
||
for _, k := range pfNeeded {
|
||
postSet[k.PostID] = struct{}{}
|
||
}
|
||
pids := make([]uint, 0, len(postSet))
|
||
for id := range postSet {
|
||
pids = append(pids, id)
|
||
}
|
||
type row struct {
|
||
PostID uint
|
||
Floor int
|
||
Status string
|
||
DeletedAt gorm.DeletedAt
|
||
}
|
||
var rows []row
|
||
_ = model.DB.Unscoped().Model(&model.Comment{}).
|
||
Select("post_id", "floor", "status", "deleted_at").
|
||
Where("post_id IN ?", pids).
|
||
Find(&rows)
|
||
for _, r := range rows {
|
||
k := pfKey{PostID: r.PostID, Floor: r.Floor}
|
||
// 同楼多条时后者覆盖;正常业务一帖一楼唯一
|
||
statusByPF[k] = contentStatusOrDeleted(r.Status, r.DeletedAt)
|
||
}
|
||
for _, k := range pfNeeded {
|
||
if _, ok := statusByPF[k]; !ok {
|
||
statusByPF[k] = "deleted"
|
||
}
|
||
}
|
||
}
|
||
|
||
postStatus := map[uint]string{}
|
||
if len(postIDs) > 0 {
|
||
type row struct {
|
||
ID uint
|
||
Status string
|
||
DeletedAt gorm.DeletedAt
|
||
}
|
||
var rows []row
|
||
_ = model.DB.Unscoped().Model(&model.Post{}).
|
||
Select("id", "status", "deleted_at").
|
||
Where("id IN ?", postIDs).
|
||
Find(&rows)
|
||
for _, r := range rows {
|
||
postStatus[r.ID] = contentStatusOrDeleted(r.Status, r.DeletedAt)
|
||
}
|
||
for _, id := range postIDs {
|
||
if _, ok := postStatus[id]; !ok {
|
||
postStatus[id] = "deleted"
|
||
}
|
||
}
|
||
}
|
||
|
||
for i := range list {
|
||
m := &list[i]
|
||
if m.Kind != model.MessageKindModeration {
|
||
continue
|
||
}
|
||
if m.RelatedCommentID != nil && *m.RelatedCommentID > 0 {
|
||
m.RelatedStatus = commentStatus[*m.RelatedCommentID]
|
||
continue
|
||
}
|
||
if cid, ok := resolvedByIndex[i]; ok && cid > 0 {
|
||
m.RelatedStatus = commentStatus[cid]
|
||
continue
|
||
}
|
||
if m.RelatedPostID == nil || *m.RelatedPostID == 0 {
|
||
continue
|
||
}
|
||
pid := *m.RelatedPostID
|
||
if looksLikeModerationComment(m.Subject, m.Content) {
|
||
floor := 0
|
||
if m.RelatedFloor != nil && *m.RelatedFloor > 0 {
|
||
floor = *m.RelatedFloor
|
||
} else {
|
||
floor = parseNotifyFloor(m.Content)
|
||
}
|
||
if floor > 0 {
|
||
m.RelatedStatus = statusByPF[pfKey{PostID: pid, Floor: floor}]
|
||
}
|
||
continue
|
||
}
|
||
m.RelatedStatus = postStatus[pid]
|
||
}
|
||
}
|
||
|
||
var notifyFloorRe = regexp.MustCompile(`#(\d+)\s*楼`)
|
||
|
||
// parseNotifyFloor 从待审评论文案解析楼号(如「#2 楼评论」「#1 楼下」)
|
||
func parseNotifyFloor(content string) int {
|
||
m := notifyFloorRe.FindStringSubmatch(content)
|
||
if len(m) < 2 {
|
||
return 0
|
||
}
|
||
var n int
|
||
_, _ = fmt.Sscanf(m[1], "%d", &n)
|
||
if n < 0 {
|
||
return 0
|
||
}
|
||
return n
|
||
}
|
||
|
||
func contentStatusOrDeleted(status string, deletedAt gorm.DeletedAt) string {
|
||
if deletedAt.Valid {
|
||
return "deleted"
|
||
}
|
||
if status != "" {
|
||
return status
|
||
}
|
||
return model.ContentStatusPublished
|
||
}
|
||
|
||
// MarkNotificationsRead 将系统通知全部标为已读
|
||
func (s *MessageService) MarkNotificationsRead(userID uint) error {
|
||
return s.MarkConversationRead(userID, 0)
|
||
}
|
||
|
||
// MessageConversation 按对方聚合的会话摘要
|
||
type MessageConversation struct {
|
||
PeerUserID uint `json:"peer_user_id"` // 0 = 系统通知
|
||
PeerUser *model.User `json:"peer_user,omitempty"`
|
||
IsSystem bool `json:"is_system"`
|
||
LastMessage *model.PrivateMessage `json:"last_message,omitempty"`
|
||
UnreadCount int64 `json:"unread_count"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
type ConversationListQuery struct {
|
||
UserID uint
|
||
Page int
|
||
Size int
|
||
}
|
||
|
||
type ConversationMessagesQuery struct {
|
||
UserID uint
|
||
PeerID uint // 0 = 系统通知
|
||
Page int
|
||
Size int
|
||
Before uint // 可选:加载更早消息(id < Before)
|
||
}
|
||
|
||
// ListConversations 会话列表(按对方聚合,最近消息优先)
|
||
func (s *MessageService) ListConversations(q ConversationListQuery) ([]MessageConversation, int64, error) {
|
||
if q.Page < 1 {
|
||
q.Page = 1
|
||
}
|
||
q.Size = s.settings.NormalizePageSize(q.Size)
|
||
|
||
type peerRow struct {
|
||
PeerID uint
|
||
LastID uint
|
||
}
|
||
var rows []peerRow
|
||
// peer_id:系统通知为 0;否则为对话另一方
|
||
err := model.DB.Raw(`
|
||
SELECT
|
||
CASE
|
||
WHEN from_user_id = 0 THEN 0
|
||
WHEN from_user_id = ? THEN to_user_id
|
||
ELSE from_user_id
|
||
END AS peer_id,
|
||
MAX(id) AS last_id
|
||
FROM private_messages
|
||
WHERE to_user_id = ? OR from_user_id = ?
|
||
GROUP BY peer_id
|
||
ORDER BY last_id DESC
|
||
LIMIT ? OFFSET ?
|
||
`, q.UserID, q.UserID, q.UserID, q.Size, (q.Page-1)*q.Size).Scan(&rows).Error
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
var total int64
|
||
err = model.DB.Raw(`
|
||
SELECT COUNT(*) FROM (
|
||
SELECT
|
||
CASE
|
||
WHEN from_user_id = 0 THEN 0
|
||
WHEN from_user_id = ? THEN to_user_id
|
||
ELSE from_user_id
|
||
END AS peer_id
|
||
FROM private_messages
|
||
WHERE to_user_id = ? OR from_user_id = ?
|
||
GROUP BY peer_id
|
||
)
|
||
`, q.UserID, q.UserID, q.UserID).Scan(&total).Error
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
if len(rows) == 0 {
|
||
return []MessageConversation{}, total, nil
|
||
}
|
||
|
||
lastIDs := make([]uint, len(rows))
|
||
peerIDs := make([]uint, 0, len(rows))
|
||
for i, r := range rows {
|
||
lastIDs[i] = r.LastID
|
||
if r.PeerID > 0 {
|
||
peerIDs = append(peerIDs, r.PeerID)
|
||
}
|
||
}
|
||
|
||
var lastMsgs []model.PrivateMessage
|
||
if err := model.DB.Preload("FromUser").Preload("ToUser").
|
||
Where("id IN ?", lastIDs).Find(&lastMsgs).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
msgByID := make(map[uint]model.PrivateMessage, len(lastMsgs))
|
||
for i := range lastMsgs {
|
||
msgByID[lastMsgs[i].ID] = lastMsgs[i]
|
||
}
|
||
|
||
usersByID := make(map[uint]model.User)
|
||
if len(peerIDs) > 0 {
|
||
var users []model.User
|
||
if err := model.DB.Where("id IN ?", peerIDs).Find(&users).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
for i := range users {
|
||
usersByID[users[i].ID] = users[i]
|
||
}
|
||
}
|
||
|
||
type unreadRow struct {
|
||
PeerID uint
|
||
Cnt int64
|
||
}
|
||
var unreadRows []unreadRow
|
||
_ = model.DB.Raw(`
|
||
SELECT
|
||
CASE WHEN from_user_id = 0 THEN 0 ELSE from_user_id END AS peer_id,
|
||
COUNT(*) AS cnt
|
||
FROM private_messages
|
||
WHERE to_user_id = ? AND is_read = 0
|
||
GROUP BY peer_id
|
||
`, q.UserID).Scan(&unreadRows)
|
||
unreadByPeer := make(map[uint]int64, len(unreadRows))
|
||
for _, u := range unreadRows {
|
||
unreadByPeer[u.PeerID] = u.Cnt
|
||
}
|
||
|
||
out := make([]MessageConversation, 0, len(rows))
|
||
for _, r := range rows {
|
||
msg, ok := msgByID[r.LastID]
|
||
if !ok {
|
||
continue
|
||
}
|
||
conv := MessageConversation{
|
||
PeerUserID: r.PeerID,
|
||
IsSystem: r.PeerID == 0,
|
||
LastMessage: &msg,
|
||
UnreadCount: unreadByPeer[r.PeerID],
|
||
UpdatedAt: msg.CreatedAt,
|
||
}
|
||
if r.PeerID > 0 {
|
||
if u, ok := usersByID[r.PeerID]; ok {
|
||
uu := u
|
||
conv.PeerUser = &uu
|
||
}
|
||
}
|
||
out = append(out, conv)
|
||
}
|
||
return out, total, nil
|
||
}
|
||
|
||
// ListConversationMessages 某会话内消息(时间正序,支持 Before 向上翻页)
|
||
func (s *MessageService) ListConversationMessages(q ConversationMessagesQuery) ([]model.PrivateMessage, int64, error) {
|
||
if q.Page < 1 {
|
||
q.Page = 1
|
||
}
|
||
q.Size = s.settings.NormalizePageSize(q.Size)
|
||
|
||
countDB := model.DB.Model(&model.PrivateMessage{})
|
||
if q.PeerID == 0 {
|
||
countDB = countDB.Where("from_user_id = 0 AND to_user_id = ?", q.UserID)
|
||
} else {
|
||
countDB = countDB.Where(
|
||
"(from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?)",
|
||
q.UserID, q.PeerID, q.PeerID, q.UserID,
|
||
)
|
||
}
|
||
|
||
var total int64
|
||
if err := countDB.Count(&total).Error; err != nil {
|
||
return nil, 0, err
|
||
}
|
||
|
||
qdb := model.DB.Preload("FromUser").Preload("ToUser")
|
||
if q.PeerID == 0 {
|
||
qdb = qdb.Where("from_user_id = 0 AND to_user_id = ?", q.UserID)
|
||
} else {
|
||
qdb = qdb.Where(
|
||
"(from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?)",
|
||
q.UserID, q.PeerID, q.PeerID, q.UserID,
|
||
)
|
||
}
|
||
if q.Before > 0 {
|
||
qdb = qdb.Where("id < ?", q.Before)
|
||
}
|
||
|
||
var list []model.PrivateMessage
|
||
// 先按 id desc 取一页,再反转为正序(聊天从旧到新)
|
||
err := qdb.Order("id desc").Limit(q.Size).Find(&list).Error
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
|
||
list[i], list[j] = list[j], list[i]
|
||
}
|
||
return list, total, nil
|
||
}
|
||
|
||
// MarkConversationRead 将会话内未读标为已读
|
||
func (s *MessageService) MarkConversationRead(userID, peerID uint) error {
|
||
db := model.DB.Model(&model.PrivateMessage{}).
|
||
Where("to_user_id = ? AND is_read = ?", userID, false)
|
||
if peerID == 0 {
|
||
db = db.Where("from_user_id = 0")
|
||
} else {
|
||
db = db.Where("from_user_id = ?", peerID)
|
||
}
|
||
return db.Update("is_read", true).Error
|
||
}
|
||
|
||
// FormatRejectContent 拒帖私信正文
|
||
func FormatRejectContent(postTitle string, postID uint, reason string) string {
|
||
return fmt.Sprintf(
|
||
"你的帖子《%s》(#%d)未通过审核。\n\n原因:\n%s\n\n如有疑问,可回复本私信联系管理员。",
|
||
postTitle, postID, strings.TrimSpace(reason),
|
||
)
|
||
}
|
||
|
||
// FormatCommentRejectContent 拒评论私信正文
|
||
func FormatCommentRejectContent(postTitle string, postID uint, floor int, reason string) string {
|
||
return fmt.Sprintf(
|
||
"你在帖子《%s》(#%d)中的评论(#%d 楼)未通过审核。\n\n原因:\n%s\n\n如有疑问,可回复本私信联系管理员。",
|
||
postTitle, postID, floor, strings.TrimSpace(reason),
|
||
)
|
||
}
|