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
- Module nghiệp vụ publish
NotificationEvent trong transaction nghiệp vụ.
NotificationEventListener nhận bằng @TransactionalEventListener(AFTER_COMMIT) + @Async.
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
| Type | Prio | Deep-link | Trạng thái phát |
|---|
WALLET_TOPUP_SUCCESS | NORMAL | /wallet | ✅ event |
WALLET_TOPUP_FAILED | NORMAL | /wallet/orders/{id} | ✅ event |
PAYMENT_SUCCESS | NORMAL | /wallet/orders/{id} | ✅ event |
PACKAGE_PURCHASED | NORMAL | /account/packages/{id} | ◐ service trực tiếp |
PACKAGE_RENEWED | NORMAL | /account/packages/{id} | ◐ |
CREDIT_LOW | HIGH | /wallet | ◐ scheduled job |
Package / Subscription
| Type | Prio | Deep-link | Trạng thái phát |
|---|
PACKAGE_EXPIRING | HIGH | /account/packages/{id} | ◐ scheduled job |
PACKAGE_EXPIRED | HIGH | /account/packages/{id} | ✅ event |
PACKAGE_RENEW_FAILED | CRITICAL | /wallet | ✅ event |
UPGRADE_PROMPT | LOW | /pricing | ◐ scheduled job |
Listing
| Type | Prio | Deep-link | Trạng thái phát |
|---|
LISTING_APPROVED | NORMAL | /listings/{id} | ✗ chờ feature moderation |
LISTING_REJECTED | HIGH | /account/listings/{id} | ✗ |
LISTING_REVOKED | HIGH | /account/listings/{id} | ✗ |
LISTING_BANNED | CRITICAL | /account/listings/{id} | ✗ |
LISTING_EXPIRING | NORMAL | /account/listings/{id} | ✗ |
LISTING_EXPIRED | NORMAL | /account/listings/{id} | ✗ |
Interaction (gộp)
| Type | Prio | Deep-link | Trạng thái phát |
|---|
LISTING_FAVORITED | NORMAL | /account/listings/{id} | ✅ event (gộp) |
UNLOCK_PHONE | NORMAL | /account/listings/{id} | ✅ event |
INQUIRY_RECEIVED | NORMAL | /account/inquiries/{id} | ✗ chờ feature inquiry |
Affiliate
| Type | Prio | Deep-link | Trạng thái phát |
|---|
AFFILIATE_COMMISSION_PAID | NORMAL | /affiliate/commissions | ✅ event |
AFFILIATE_REFERRAL_SIGNUP | LOW | /affiliate/referrals | ✅ event |
Account / Security
| Type | Prio | Deep-link | Trạng thái phát |
|---|
ACCOUNT_WELCOME | LOW | / | ✅ event |
PASSWORD_CHANGED | HIGH | /account/security | ✅ event |
NEW_DEVICE_LOGIN | HIGH | /account/security | ✗ chờ device fingerprint |
System / Marketing
| Type | Prio | Deep-link | Trạng thái phát |
|---|
DAILY_DIGEST | LOW | / | ✗ chờ job |
SYSTEM_ANNOUNCEMENT | NORMAL | / | ✗ |
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_count và last_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