Bỏ qua nội dung

Notification

Tổng quan hệ thống

  • In-app notification: mỗi thông báo gắn 1 user nhận (user_id), lưu Postgres.
  • Event-driven: module nghiệp vụ chỉ publish NotificationEvent, module notifications tự tạo + đẩy.
  • Localize-at-read: lưu template_key + template_args, render theo Accept-Language lúc đọc.
  • Deep-link: BE tính link từ refType + refId lúc trả dữ liệu.
  • Aggregation: gộp thông báo tương tác theo actor (vd thích tin).
  • Realtime: STOMP/WebSocket dùng chung foundation với chat.

Luồng kỹ thuật

  1. Module nghiệp vụ publish NotificationEvent trong transaction nghiệp vụ.
  2. NotificationEventListener nhận bằng @TransactionalEventListener(AFTER_COMMIT) + @Async.
  3. NotificationDispatcher xử lý:
    • resolve metadata theo NotificationTypeEnum,
    • upsert aggregation (nếu loại gộp) hoặc idempotency insert (loại thường),
    • persist DB,
    • push /user/queue/notifications,
    • push unread-count /user/queue/notifications.unread-count.

Cấu trúc dữ liệu

Bảng notifications

  • Định danh: id, user_id.
  • Loại và tham chiếu: type, ref_type, ref_id.
  • Nội dung: title, body, template_key, template_args.
  • Metadata: category, priority, icon.
  • Trạng thái: is_read, read_at, seen_at, expired_at.
  • Aggregation: aggregation_key, actor_count, actors, last_actor_at.
  • Audit: created_at, updated_at.
  • link không lưu DB, tính lúc đọc.

Bảng notification_actors

  • notification_id, actor_id, created_at.
  • PK kép (notification_id, actor_id) để mỗi actor chỉ đếm 1 lần.

Enums chuẩn

  • NotificationCategoryEnum: TRANSACTION, LISTING, ACCOUNT, AFFILIATE, MARKETING, SYSTEM.
  • NotificationPriorityEnum: LOW, NORMAL, HIGH, CRITICAL.
  • NotificationRefTypeEnum: LISTING, USER_PACKAGE, TRANSACTION, PAYMENT_ORDER, WALLET, INQUIRY, UNLOCK, COMMISSION, USER, SYSTEM.

Danh mục thông báo (registry)

Transaction / Wallet / Payment

TypePrioDeep-linkTrạng thái phát
WALLET_TOPUP_SUCCESSNORMAL/wallet✅ event
WALLET_TOPUP_FAILEDNORMAL/wallet/orders/{id}✅ event
PAYMENT_SUCCESSNORMAL/wallet/orders/{id}✅ event
PACKAGE_PURCHASEDNORMAL/account/packages/{id}◐ service trực tiếp
PACKAGE_RENEWEDNORMAL/account/packages/{id}
CREDIT_LOWHIGH/wallet◐ scheduled job

Package / Subscription

TypePrioDeep-linkTrạng thái phát
PACKAGE_EXPIRINGHIGH/account/packages/{id}◐ scheduled job
PACKAGE_EXPIREDHIGH/account/packages/{id}✅ event
PACKAGE_RENEW_FAILEDCRITICAL/wallet✅ event
UPGRADE_PROMPTLOW/pricing◐ scheduled job

Listing

TypePrioDeep-linkTrạng thái phát
LISTING_APPROVEDNORMAL/listings/{id}✗ chờ feature moderation
LISTING_REJECTEDHIGH/account/listings/{id}
LISTING_REVOKEDHIGH/account/listings/{id}
LISTING_BANNEDCRITICAL/account/listings/{id}
LISTING_EXPIRINGNORMAL/account/listings/{id}
LISTING_EXPIREDNORMAL/account/listings/{id}

Interaction (gộp)

TypePrioDeep-linkTrạng thái phát
LISTING_FAVORITEDNORMAL/account/listings/{id}✅ event (gộp)
UNLOCK_PHONENORMAL/account/listings/{id}✅ event
INQUIRY_RECEIVEDNORMAL/account/inquiries/{id}✗ chờ feature inquiry

Affiliate

TypePrioDeep-linkTrạng thái phát
AFFILIATE_COMMISSION_PAIDNORMAL/affiliate/commissions✅ event
AFFILIATE_REFERRAL_SIGNUPLOW/affiliate/referrals✅ event

Account / Security

TypePrioDeep-linkTrạng thái phát
ACCOUNT_WELCOMELOW/✅ event
PASSWORD_CHANGEDHIGH/account/security✅ event
NEW_DEVICE_LOGINHIGH/account/security✗ chờ device fingerprint

System / Marketing

TypePrioDeep-linkTrạng thái phát
DAILY_DIGESTLOW/✗ chờ job
SYSTEM_ANNOUNCEMENTNORMAL/
VIP_FLASH_DEAL (legacy)NORMAL/✗ giữ dữ liệu cũ

Gộp thông báo — favorite listing (LISTING_FAVORITED)

Khi một người dùng khác chủ tin thích (favorite) một listing, hệ thống phát NotificationEvent (type LISTING_FAVORITED, kèm actor) cho chủ tin. Đây là loại thông báo gộp theo actor:

  • Trigger: chỉ phát khi favorite được thêm mới — thao tác idempotent nên thích lại tin đã thích không phát lại. Chạy nền sau khi transaction favorite commit (@TransactionalEventListener(AFTER_COMMIT) + @Async), không chặn request của người thích.
  • Bỏ qua self-favorite: chủ tin tự thích listing của chính mình không tạo thông báo.
  • Khóa gộp: theo (chủ tin nhận, LISTING_FAVORITED, listing) qua aggregation_key với refType=LISTING, refId=listingId — mọi lượt thích cùng một tin gộp vào một thông báo cho chủ tin thay vì tạo nhiều thông báo lẻ.
  • Đếm actor: mỗi người thích ghi một dòng trong notification_actors (PK kép notification_id + actor_id → mỗi người chỉ đếm 1 lần); actor_countlast_actor_at cập nhật theo lượt mới nhất.
  • Hiển thị: dạng “A và N người khác đã thích tin của bạn”, dựng từ latestActor (tên/avatar người thích gần nhất) + actor_count trong NotificationResponse.
  • Metadata: category LISTING, priority NORMAL, deep-link /account/listings/{id}.
  • Unfavorite: không phát thông báo và không thu hồi thông báo đã gộp; chỉ ảnh hưởng số lượt thích (favorite_count) của listing.

Chi tiết feature: listing-favorite.

API và realtime

  • REST:
    • GET /api/notifications
    • GET /api/notifications/unread-count
    • POST /api/notifications/{id}/read
    • POST /api/notifications/read-all
  • WS/STOMP:
    • /user/queue/notifications
    • /user/queue/notifications.unread-count
  • Hợp đồng chi tiết: WebSocket / STOMP.

Payload chuẩn NotificationResponse

  • id, userId, type, category, priority
  • title, body, link, refType, refId, icon
  • actorCount, latestActor{id,name,avatar}
  • read, readAt, expiredAt, createdAt