Files
platforms/backend/api/internal/logic/client/user/messages.go
czl231 3ef33b531d 已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
2026-09-13 00:57:32 +08:00

167 lines
6.6 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.
// 功能:从本人业务事实生成消息中心列表,并持久化已读回执;版本: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
}