已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
166
backend/api/internal/logic/client/user/messages.go
Normal file
166
backend/api/internal/logic/client/user/messages.go
Normal file
@@ -0,0 +1,166 @@
|
||||
// 功能:从本人业务事实生成消息中心列表,并持久化已读回执;版本: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(¬ices).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
|
||||
}
|
||||
Reference in New Issue
Block a user