补充通知信息
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m11s

This commit is contained in:
2026-07-28 16:14:20 +08:00
parent 4b98612816
commit 1e4f998fe7
8 changed files with 87 additions and 21 deletions

View File

@@ -61,9 +61,9 @@ func NewRegistry() *Registry {
constants.NotificationTypePackageExpiring: {
Type: constants.NotificationTypePackageExpiring, Category: constants.NotificationCategoryExpiry,
Severity: constants.NotificationSeverityWarning,
TitleTemplate: "套餐即将到期",
BodyTemplate: "您的套餐即将到期,请及时查看并处理。",
TemplateFields: map[string]struct{}{},
TitleTemplate: "套餐{{.expiry_status}}",
BodyTemplate: "资产 {{.asset_identifier}} 的套餐{{.expiry_status}},到期日期:{{.expiry_date}}。",
TemplateFields: map[string]struct{}{"asset_identifier": {}, "expiry_status": {}, "expiry_date": {}},
RecipientKinds: map[string]struct{}{
constants.NotificationRecipientKindAccount: {},
constants.NotificationRecipientKindPersonalCustomer: {},
@@ -78,8 +78,8 @@ func NewRegistry() *Registry {
Type: constants.NotificationTypeAgentRechargeCompleted, Category: constants.NotificationCategorySystem,
Severity: constants.NotificationSeverityInfo,
TitleTemplate: "店铺充值已入账",
BodyTemplate: "店铺充值已成功入账,请进入充值详情查看。",
TemplateFields: map[string]struct{}{},
BodyTemplate: "店铺「{{.shop_name}}」充值 {{.amount}} 已成功入账。",
TemplateFields: map[string]struct{}{"shop_name": {}, "amount": {}},
RecipientKinds: map[string]struct{}{constants.NotificationRecipientKindAccount: {}},
AllowedRefTypes: map[string]struct{}{
constants.NotificationRefTypeAgentRecharge: {},

View File

@@ -82,7 +82,7 @@ func (p *ReminderPublisher) appendShopNotification(ctx context.Context, tx *gorm
ResourceType: candidate.AssetType, ResourceID: assetID, BusinessKey: eventID,
Payload: notificationapp.AdminDynamicPayload{
TargetKind: constants.NotificationTargetKindShop, TargetID: shopID,
NotificationType: constants.NotificationTypePackageExpiring, TemplateData: map[string]string{},
NotificationType: constants.NotificationTypePackageExpiring, TemplateData: expiryTemplateData(candidate),
RefType: constants.NotificationRefTypeExpiringAsset, RefID: shopIDText,
ExpiresAt: candidate.EstimatedFinalExpiresAt,
},
@@ -160,7 +160,7 @@ func (p *ReminderPublisher) appendNotification(ctx context.Context, tx *gorm.DB,
ResourceType: candidate.AssetType, ResourceID: assetID, BusinessKey: eventID,
Payload: notificationapp.PersonalCustomerDirectPayload{
RecipientID: customerID, NotificationType: constants.NotificationTypePackageExpiring,
TemplateData: map[string]string{}, RefType: constants.NotificationRefTypeAsset,
TemplateData: expiryTemplateData(candidate), RefType: constants.NotificationRefTypeAsset,
RefID: assetID, RefKey: candidate.Identifier, ExpiresAt: candidate.EstimatedFinalExpiresAt,
},
})
@@ -170,6 +170,28 @@ func (p *ReminderPublisher) appendNotification(ctx context.Context, tx *gorm.DB,
return nil
}
// expiryTemplateData 为后台和个人客户生成同口径的资产到期文案数据。
func expiryTemplateData(candidate dto.ExpiringAssetItem) map[string]string {
status := "即将到期"
if candidate.DaysUntilFinalExpiry != nil {
switch {
case *candidate.DaysUntilFinalExpiry < 0:
status = "已到期"
case *candidate.DaysUntilFinalExpiry == 0:
status = "今天到期"
}
}
expiryDate := ""
if candidate.EstimatedFinalExpiresAt != nil {
expiryDate = candidate.EstimatedFinalExpiresAt.In(shanghaiLocation).Format("2006-01-02")
}
return map[string]string{
"asset_identifier": candidate.Identifier,
"expiry_status": status,
"expiry_date": expiryDate,
}
}
func recipientKey(assetType string, assetID uint) string {
return fmt.Sprintf("%s:%d", assetType, assetID)
}

View File

@@ -2,11 +2,13 @@ package wallet
import (
"context"
"fmt"
"strconv"
notificationapp "github.com/break/junhong_cmp_fiber/internal/application/notification"
walletapp "github.com/break/junhong_cmp_fiber/internal/application/wallet"
"github.com/break/junhong_cmp_fiber/internal/infrastructure/messaging/outbox"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"gorm.io/gorm"
@@ -38,6 +40,10 @@ func (w *CreditEventWriter) Append(ctx context.Context, tx *gorm.DB, event walle
return err
}
rechargeID := strconv.FormatUint(uint64(event.ReferenceID), 10)
var shop model.Shop
if err := tx.WithContext(ctx).Select("shop_name").Where("id = ?", event.ShopID).Take(&shop).Error; err != nil {
return errors.Wrap(errors.CodeDatabaseError, err, "查询充值通知店铺失败")
}
notificationEventID := "agent-recharge:" + rechargeID + ":completed"
_, err = w.outbox.AppendIdempotent(ctx, tx, outbox.Envelope{
EventID: notificationEventID, EventType: constants.OutboxEventTypeAdminDynamicNotification,
@@ -47,7 +53,11 @@ func (w *CreditEventWriter) Append(ctx context.Context, tx *gorm.DB, event walle
BusinessKey: notificationEventID, RequestID: event.RequestID, CorrelationID: event.CorrelationID,
Payload: notificationapp.AdminDynamicPayload{
TargetKind: constants.NotificationTargetKindShop, TargetID: event.ShopID,
NotificationType: constants.NotificationTypeAgentRechargeCompleted, TemplateData: map[string]string{},
NotificationType: constants.NotificationTypeAgentRechargeCompleted,
TemplateData: map[string]string{
"shop_name": shop.ShopName,
"amount": fmt.Sprintf("%d.%02d 元", event.Amount/100, event.Amount%100),
},
RefType: constants.NotificationRefTypeAgentRecharge, RefID: rechargeID,
},
})

View File

@@ -11,7 +11,7 @@ type NotificationUnreadCountResponse struct {
// NotificationListRequest 是后台通知基础分页参数。
type NotificationListRequest struct {
Category string `json:"category" query:"category" validate:"omitempty,oneof=approval expiry sync system" enums:"approval,expiry,sync,system" description:"通知类别 (approval:审批, expiry:临期, sync:同步, system:系统)"`
Type string `json:"type" query:"type" validate:"omitempty,max=100" maxlength:"100" description:"稳定通知类型"`
Type string `json:"type" query:"type" validate:"omitempty,oneof=system.notice package.expiring agent.recharge.completed refund.completed exchange.shipping.created agent.main_wallet.low_balance" enums:"system.notice,package.expiring,agent.recharge.completed,refund.completed,exchange.shipping.created,agent.main_wallet.low_balance" description:"稳定通知类型 (system.notice:系统通知, package.expiring:套餐临期, agent.recharge.completed:店铺充值入账, refund.completed:店铺退款完成, exchange.shipping.created:换货申请待处理, agent.main_wallet.low_balance:主钱包低余额)"`
Severity string `json:"severity" query:"severity" validate:"omitempty,oneof=info warning error critical" enums:"info,warning,error,critical" description:"通知级别 (info:提示, warning:警告, error:错误, critical:严重)"`
IsRead *bool `json:"is_read" query:"is_read" description:"已读状态;不传时查询全部"`
Page int `json:"page" query:"page" validate:"omitempty,min=1,max=10000" minimum:"1" maximum:"10000" description:"页码,默认 1最大 10000"`
@@ -21,9 +21,9 @@ type NotificationListRequest struct {
// NotificationItem 是后台账号可见的站内通知投影。
type NotificationItem struct {
ID uint `json:"id" description:"通知ID"`
Category string `json:"category" description:"通知类别 (approval:审批, expiry:临期, sync:同步, system:系统)"`
Type string `json:"type" description:"稳定通知类型"`
Severity string `json:"severity" description:"通知级别 (info:提示, warning:警告, error:错误, critical:严重)"`
Category string `json:"category" enums:"approval,expiry,sync,system" description:"通知类别 (approval:审批, expiry:临期, sync:同步, system:系统)"`
Type string `json:"type" enums:"system.notice,package.expiring,agent.recharge.completed,refund.completed,exchange.shipping.created,agent.main_wallet.low_balance" description:"稳定通知类型 (system.notice:系统通知, package.expiring:套餐临期, agent.recharge.completed:店铺充值入账, refund.completed:店铺退款完成, exchange.shipping.created:换货申请待处理, agent.main_wallet.low_balance:主钱包低余额)"`
Severity string `json:"severity" enums:"info,warning,error,critical" description:"通知级别 (info:提示, warning:警告, error:错误, critical:严重)"`
Title string `json:"title" description:"纯文本标题"`
Body string `json:"body" description:"纯文本正文"`
RefType string `json:"ref_type" description:"受控资源类型可能为空。可选值及含义system_config:系统配置, integration_log:外部集成日志, package:套餐, asset:C端资产, refund:退款, agent_recharge:代理充值, wecom_approval:企微审批, iot_card:物联网卡, device:设备, expiring_asset:临期资产列表, shop_fund:店铺资金概况, card_sync:卡同步记录。后台点击通知应调用目标解析接口,不得直接拼接路由"`