Files

167 lines
6.6 KiB
Go
Raw Permalink Normal View History

// 功能:从本人业务事实生成消息中心列表,并持久化已读回执;版本:1.0.0。
package user
import (
"fmt"
"sort"
"strings"
"time"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm/clause"
)
type userMessage struct {
Key, Category, Title, Summary, StatusText, Target, TargetIdentity string
OccurredAt time.Time
Read bool
}
// ListMessages 返回真实订单、工单和公告形成的消息,不生成不存在的安全告警。
func ListMessages(ctx *gin.Context) {
account, ok := common.UserAccount(ctx)
if !ok {
return
}
messages, err := loadUserMessages(account.ID)
if err != nil {
infra.Response.Error(ctx, err)
return
}
var receipts []models.UserMessageRead
if err = impl.DBService.Where("user_account_id = ?", account.ID).Find(&receipts).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
read := make(map[string]bool, len(receipts))
for _, receipt := range receipts {
read[receipt.MessageKey] = true
}
counts := map[string]int{"all": len(messages), "unread": 0, "safety": 0, "order": 0, "service": 0, "notice": 0}
rows := make([]gin.H, 0, len(messages))
for _, item := range messages {
item.Read = read[item.Key]
counts[item.Category]++
if !item.Read {
counts["unread"]++
}
rows = append(rows, gin.H{"key": item.Key, "category": item.Category, "title": item.Title, "summary": item.Summary,
"status_text": item.StatusText, "target": item.Target, "target_identity": item.TargetIdentity,
"occurred_at": item.OccurredAt, "read": item.Read})
}
infra.Response.Success(ctx, gin.H{"items": rows, "counts": counts})
}
// MarkMessagesRead 仅接受当前列表中存在的消息键,防止写入任意或他人对象标识。
func MarkMessagesRead(ctx *gin.Context) {
account, ok := common.UserAccount(ctx)
if !ok {
return
}
var request struct {
Keys []string `json:"keys" binding:"required,min=1,max=200,dive,required,max=96"`
}
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
messages, err := loadUserMessages(account.ID)
if err != nil {
infra.Response.Error(ctx, err)
return
}
allowed := make(map[string]bool, len(messages))
for _, item := range messages {
allowed[item.Key] = true
}
rows := make([]models.UserMessageRead, 0, len(request.Keys))
seen := map[string]bool{}
for _, key := range request.Keys {
key = strings.TrimSpace(key)
if !allowed[key] || seen[key] {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
seen[key] = true
rows = append(rows, models.UserMessageRead{Entity: models.Entity{Identity: models.NewIdentity(), Status: common.StatusEnable}, UserAccountID: account.ID, MessageKey: key, ReadAt: time.Now()})
}
if err = impl.DBService.Clauses(clause.OnConflict{DoNothing: true}).Create(&rows).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"read_count": len(rows)})
}
func loadUserMessages(userID uint64) ([]userMessage, error) {
messages := make([]userMessage, 0)
var gasOrders []models.GasorderBasic
if err := impl.DBService.Where("user_account_id = ? AND status <> ?", userID, common.StatusArchived).Order("updated_at desc").Limit(50).Find(&gasOrders).Error; err != nil {
return nil, err
}
for _, row := range gasOrders {
messages = append(messages, orderMessage("gas", row.Identity, row.OrderNo, row.OrderStatus, row.UpdatedAt))
}
var shopOrders []models.EcOrder
if err := impl.DBService.Where("user_account_id = ? AND status <> ?", userID, common.StatusArchived).Order("updated_at desc").Limit(50).Find(&shopOrders).Error; err != nil {
return nil, err
}
for _, row := range shopOrders {
messages = append(messages, orderMessage("shop", row.Identity, row.OrderNo, row.OrderStatus, row.UpdatedAt))
}
var tickets []models.CsTicket
if err := impl.DBService.Where("user_account_id = ? AND status <> ? AND category <> ?", userID, common.StatusArchived, "contract_change").Order("updated_at desc").Limit(50).Find(&tickets).Error; err != nil {
return nil, err
}
for _, row := range tickets {
messages = append(messages, userMessage{Key: "ticket:" + row.Identity, Category: "service", Title: ticketTitle(row.TicketStatus), Summary: "报修工单 " + row.TicketNo, StatusText: statusText(row.TicketStatus), Target: "ticket", TargetIdentity: row.Identity, OccurredAt: row.UpdatedAt})
}
var notices []models.CmsContent
if err := impl.DBService.Where("content_type = ? AND publish_status = ? AND status = ?", "notice", "published", common.StatusEnable).Order("updated_at desc").Limit(50).Find(&notices).Error; err != nil {
return nil, err
}
for _, row := range notices {
messages = append(messages, userMessage{Key: "notice:" + row.Identity, Category: "notice", Title: row.Title, Summary: compactMessage(row.Body), StatusText: "平台公告", Target: "content", TargetIdentity: row.Identity, OccurredAt: row.UpdatedAt})
}
sort.SliceStable(messages, func(i, j int) bool { return messages[i].OccurredAt.After(messages[j].OccurredAt) })
return messages, nil
}
func orderMessage(kind, identity, number string, status int, at time.Time) userMessage {
name, target := "燃气订单", "gas_order"
if kind == "shop" {
name, target = "商城订单", "shop_order"
}
return userMessage{Key: kind + ":" + identity, Category: "order", Title: orderTitle(name, status), Summary: fmt.Sprintf("您的%s(订单号:%s)状态已更新", name, number), StatusText: statusText(status), Target: target, TargetIdentity: identity, OccurredAt: at}
}
func statusText(status int) string {
return map[int]string{16: "待支付", 18: "待配送", 21: "异常", 22: "已取消", 23: "已完成", 29: "运输中", 32: "待受理", 31: "处理中", 33: "配送中", 34: "待确认", 35: "已支付"}[status]
}
func orderTitle(name string, status int) string {
if value := statusText(status); value != "" {
return name + value
}
return name + "状态更新"
}
func ticketTitle(status int) string {
if status == common.StatusCompleted {
return "报修工单已完成"
}
if status == common.StatusRepairing {
return "报修工单处理中"
}
return "报修工单状态更新"
}
func compactMessage(value string) string {
value = strings.Join(strings.Fields(value), " ")
if len([]rune(value)) > 70 {
return string([]rune(value)[:70]) + "…"
}
return value
}