fix: 第1批 M9 套餐临期提醒按资产、扫码进入时触发
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m17s

- 每日扫描不再给个人客户写临期通知,店铺通知不变
- H5 资产信息在当前资产剩余 0~15 天且当天首次进入时返回 expiry_reminder,并为该客户、该资产写一条临期通知(Redis 去重,失败不占键)
- 个人通知列表、未读数、全部已读中临期提醒只处理当前资产(按 ref_key)
- OpenSpec Change scope-expiry-reminder-to-current-asset:6/9,临期 fixture 端到端验证与每日扫描验证待测试部署后完成
This commit is contained in:
邹进
2026-10-12 00:15:27 +09:00
parent 9ced14fa1b
commit 0f1ea5bc3e
18 changed files with 418 additions and 100 deletions

View File

@@ -46,6 +46,17 @@ type ClientAssetHandler struct {
observationSeries cardObservationApp.BestEffortSeriesDispatcher
paymentMethodPolicy ClientPaymentMethodPolicy
forceRechargeChecker ClientForceRechargeChecker
expiryReminder ClientExpiryReminder
}
// ClientExpiryReminder 在 H5 进入资产时判定并发出套餐临期提醒。
type ClientExpiryReminder interface {
Remind(ctx context.Context, customerID uint, candidate dto.ExpiringAssetItem) *dto.AssetExpiryReminder
}
// SetExpiryReminder 注入进入资产时的套餐临期提醒用例。
func (h *ClientAssetHandler) SetExpiryReminder(reminder ClientExpiryReminder) {
h.expiryReminder = reminder
}
// ClientPaymentMethodPolicy 提供 C 端支付方式读取和校验能力。
@@ -279,6 +290,10 @@ func (h *ClientAssetHandler) GetAssetInfo(c *fiber.Ctx) error {
BoundDeviceName: resolved.Asset.BoundDeviceName,
}
if h.expiryReminder != nil {
resp.ExpiryReminder = h.expiryReminder.Remind(c.UserContext(), resolved.CustomerID, expiringCandidateOf(resolved.Asset))
}
// 调用服务层获取设备实时 Gateway 数据(per D-11)
// Gateway 失败时 DeviceRealtime 仅含 gateway_msg,不阻断主流程(per D-06)
if resp.AssetType == "device" {
@@ -851,3 +866,24 @@ func mapDeviceGatewayInfoToClientInfo(g *dto.DeviceGatewayInfo) *dto.DeviceRealt
info.GatewayMsg = g.GatewayMsg
return info
}
// expiringCandidateOf 把资产解析结果转换为临期提醒候选;资产标识与通知中心过滤口径一致(卡 ICCID;设备虚拟号,空则 IMEI)。
func expiringCandidateOf(asset *dto.AssetResolveResponse) dto.ExpiringAssetItem {
candidate := dto.ExpiringAssetItem{
AssetID: asset.AssetID, ShopID: asset.ShopID, PackageExpiryEstimate: asset.PackageExpiryEstimate,
}
if asset.CurrentPackageUsageID != nil {
candidate.PackageUsageID = *asset.CurrentPackageUsageID
}
if asset.AssetType == "device" {
candidate.AssetType = constants.AssetTypeDevice
candidate.Identifier = asset.VirtualNo
if candidate.Identifier == "" {
candidate.Identifier = asset.IMEI
}
return candidate
}
candidate.AssetType = constants.AssetTypeIotCard
candidate.Identifier = asset.ICCID
return candidate
}

View File

@@ -36,7 +36,11 @@ func (h *ClientNotificationHandler) UnreadCount(c *fiber.Ctx) error {
if !ok || customerID == 0 {
return errors.New(errors.CodeUnauthorized)
}
result, err := h.query.PersonalUnreadCount(c.UserContext(), customerID)
assetKey, err := h.currentAssetKey(c)
if err != nil {
return err
}
result, err := h.query.PersonalUnreadCount(c.UserContext(), customerID, assetKey)
if err != nil {
return err
}
@@ -61,7 +65,11 @@ func (h *ClientNotificationHandler) List(c *fiber.Ctx) error {
if !ok || customerID == 0 {
return errors.New(errors.CodeUnauthorized)
}
result, err := h.query.PersonalList(c.UserContext(), customerID, request)
assetKey, err := h.currentAssetKey(c)
if err != nil {
return err
}
result, err := h.query.PersonalList(c.UserContext(), customerID, assetKey, request)
if err != nil {
return err
}
@@ -75,7 +83,11 @@ func (h *ClientNotificationHandler) MarkAllRead(c *fiber.Ctx) error {
if !ok || customerID == 0 {
return errors.New(errors.CodeUnauthorized)
}
result, err := h.readService.MarkAllPersonalRead(c.UserContext(), customerID)
assetKey, err := h.currentAssetKey(c)
if err != nil {
return err
}
result, err := h.readService.MarkAllPersonalRead(c.UserContext(), customerID, assetKey)
if err != nil {
return err
}
@@ -99,6 +111,15 @@ func (h *ClientNotificationHandler) MarkRead(c *fiber.Ctx) error {
return response.Success(c, dto.NotificationReadResponse{Success: true})
}
// currentAssetKey 读取令牌当前资产的通知引用标识,用于只展示当前资产的套餐临期提醒。
func (h *ClientNotificationHandler) currentAssetKey(c *fiber.Ctx) (string, error) {
assetType, assetID, ok := middleware.GetCurrentAsset(c)
if !ok {
return "", nil
}
return h.query.CurrentAssetRefKey(c.UserContext(), assetType, assetID)
}
func logPersonalNotificationListValidationFailure(c *fiber.Ctx, request dto.PersonalNotificationListRequest, err error) {
logger.GetAppLogger().Warn("个人客户站内通知列表参数验证失败",
zap.String("method", c.Method()),