Notification Server
Giới thiệu
Notification Server là 1 service module thuộc kiến trúc hệ thống DPS, có khả năng gửi thông báo bất đồng bộ:
- Host socket:
wss://socket.jee.vn/notification - Host API:
https://notification.jee.vn
Có 2 kiểu gửi thông báo. Gửi message qua websocket và gửi email.
Cài đặt chung
Trong file Startup.cs phần ConfigureServices thêm vào service notification. Lưu ý: Notification cần kafka để chạy, phải thêm vào sau khi đã thêm service kafka.
services.addNotificationService();
Cũng trong file Startup.cs phần ConfigureServices thêm vào đoạn code này để lấy internal_secret từ vault.
Secret<SecretData> jwtSecret = vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(path: "jwt", mountPoint: "kv").Result;
IDictionary<string, object> jwtData = jwtSecret.Data.Data;
Configuration["Jwt:internal_secret"] = jwtData["internal_secret"].ToString();
Ở controller cần sử dụng, chỉ cần inject service vào rồi gọi method gửi thông báo tương ứng.
Websocket
Kết nối đầu nhận
Đầu nhận message từ server này cần sử dụng thư viện từ socket.io. ***LƯU Ý: Chỉ sử dụng phiên bản client có khả năng connect vào socket.io server phiên bản 2.x
Các bước kết nối (Thực hiện sau khi đăng nhập và có được JWT token):
- Thực hiện kết nối từ Socket IO Client đến server qua host phía trên, trong bước kết nối đầu tiên quá trình handshake cần được gửi kèm 1
extraHeaderscó key làx-auth-token, với value là JWT token sau khi hoàn tất các bước đăng nhập nhận được. - Dùng instance đã connect đến server listen 1 event tên là
notification. Các data được emit đến event này sẽ là data notification được server gửi đến.
Để có thể nhận thêm thông báo push từ OneSignal cần thực hiện thêm kiểm tra đăng ký. Sau khi thực hiện xác thực người dùng và đã lấy được username, thực hiện thêm đoạn script sau vào trang layout của ứng dụng:
const username = // sau khi xác thực có username
const host = {
portal: 'https://portal.jee.vn',
}
// Thiết lập iframe đến trang đăng ký
const iframeSource = `${host.portal}/?getstatus=true`
const iframe = document.createElement('iframe')
iframe.setAttribute('src', iframeSource)
iframe.style.display = 'none'
document.body.appendChild(iframe)
// Thiết lập Event Listener để xác nhận người dùng đăng ký chưa
window.addEventListener(
'message',
(event) => {
if (event.origin !== host.portal) return // Quan trọng, bảo mật, nếu không phải message từ portal thì ko làm gì cả, tránh XSS attack
// event.data = false là user chưa đăng ký nhận thông báo, nếu đăng ký rồi thì là true
if (event.data === false) {
// Đoạn setTimeout này chỉ là 1 ví dụ -> Nếu người dùng vào trang mà chưa đăng ký thì 2s sau sẽ hiện popup cho người dùng đăng ký
// Có thể tùy chỉnh đoạn này, thêm vào cookie, popup, button,... để tự chủ động trong việc đăng ký
setTimeout(() => {
// Lệnh window.open này chính là lệnh gọi mở popup đến trang đăng ký
// Trang này vừa có thể đăng ký, vừa có thể hủy đăng ký
// Có thể sử dụng lệnh này gán vào 1 nút nào đó trên trang cho người dùng chủ động trong việc đăng ký hoặc hủy đăng ký
window.open(
`${host.portal}/notificationsubscribe?username=${username}`, // username điền vào đây
'childWin',
'width=400,height=400'
)
}, 2000)
}
},
false
)
Lưu ý: đọc kỹ các comment trong đoạn code bên trên và sử dụng phù hợp với mục đích của trang
Kết nối đầu gửi
Tại controller muốn gửi thông báo, inject service notifier với interface là INotifier lấy được từ DPSinfra. Trong ví dụ dưới, service đã được inject vào biến _notifier. Gọi method sendSocket với data thuộc class socketMessage để gửi thông báo qua websocket
public void testnotification()
{
var json = new
{
abc = "123"
};
socketMessage asyncnotice = new socketMessage()
{
sender = "Username người gửi",
receivers = new string[] { "superuser" },
message_text = "test abcde",
message_html = "<h1>abc</h1>",
message_json = JsonConvert.SerializeObject(json),
// Các field dưới đây là của OneSignal
osTitle = "Test title",
osMessage = "Test message",
osWebURL = "https://google.vn",
osAppURL = "https://google.vn",
osIcon = "https://api.jeehr.com/images/logokhachhang/25.jpg",
disabled = false // (mặc định false) trường dữ liệu này nếu bằng true sẽ không gửi notify và không hiện thông báo trên danh sách, nhưng vẫn sẽ đếm số lượng của thông báo new ở menu (không hiện new ở chuông thông báo)
};
_notifier.sendSocket(asyncnotice);
}
Lưu ý: Push notify socket sẽ hoạt động nếu 1 trong 3 field message_text, message_html, message_json có dữ liệu. Push notify của OneSignal sẽ hoạt động nếu 1 trong 2 field osTitle hoặc osMessage có dữ liệu. User nhận notify sẽ dùng chung field receivers
Tại controller muốn gửi thông báo, inject service notifier với interface là INotifier lấy được từ DPSinfra. Trong ví dụ dưới, service đã được inject vào biến _notifier. Gọi method sendEmail với data thuộc class emailMessage để gửi thông báo qua websocket Gửi email sẽ được chia làm 2 trường hợp dưới đây
- Trường hợp dùng access_token
public async void testnotificationemail()
{
emailMessage asyncnotice = new emailMessage()
{
access_token = "abc...",
from = "ABCDE",
to = "hoaauquoc@gmail.com",
subject = "subject",
html = "<h1>Hello World</h1>",
disabled = false // mặc định false, field này dùng để không gửi email
};
await _notifier.sendEmail(asyncnotice);
}
- Trường hợp dùng CustomerID, nếu vừa bỏ CustomerID và access_token vào model thì lib sẽ sử dụng CustomerID
public async void testnotificationemail()
{
emailMessage asyncnotice = new emailMessage()
{
CustomerID = 1,
from = "ABCDE",
to = "hoaauquoc@gmail.com",
subject = "subject",
html = "<h1>Hello World</h1>",
disabled = false // mặc định false, field này dùng để không gửi email
};
await _notifier.sendEmail(asyncnotice);
}
Method sendEmail là async, nếu dùng await có thể dùng thêm try-catch để bắt lỗi, nếu có.
- Update Lib v1.8.1: Lưu ý khi sửa dụng method
sendEmailTrường hợp sửa dụng lib 1.8.0 cần truyền thêm 4 fields dưới đây ở cả 2 trường hợp trên (Có thể bỏ trống 4 fieldsfrom,to,ccvàbcc)
public async void testnotificationemail()
{
emailMessage asyncnotice = new emailMessage()
{
//access_token = "abc...", chọn 1 trong 2 như 2 trường hợp trên
//CustomerID = 1, chọn 1 trong 2 như 2 trường hợp trên
sender = "Username người gửi", // trường thêm mới (có thể bỏ trống, nhưng có thể truyền vào thì nên truyền)
receivers = new string[] { "superuser" }, // trường thêm mới
cc_receivers = new string[] { "superuser" }, // trường thêm mới
bcc_receivers = new string[] { "superuser" }, // trường thêm mới
from = "ABCDE",
to = "hoaauquoc@gmail.com",
cc = "hoaauquoc@gmail.com",
bcc = "hoaauquoc@gmail.com",
subject = "subject",
html = "<h1>Hello World</h1>",
disabled = false // mặc định false, field này dùng để không gửi email
};
await _notifier.sendEmail(asyncnotice);
}
Thông tin setting mặc định của email là không nhận thông báo (muốn nhận thông báo từ email vui lòng cập nhật lại setting notify của user đó).
Desktop
Tại controller muốn gửi thông báo đến desktop, inject service notifier với interface là INotifier lấy được từ DPSinfra. Trong ví dụ dưới, service đã được inject vào biến _notifier. Gọi method sendDesktop với data thuộc class desktopMessage để gửi thông báo qua Desktop
public async void testnotificationdesktop()
{
desktopMessage asyncnotice = new desktopMessage()
{
sender = "Username người gửi",
receivers = new string[] { "superuser" },
message_text = "test abcde",
message_html = "<h1>abc</h1>",
message_json = JsonConvert.SerializeObject(json),
// Các field dưới đây là của Desktop
dtTitle = "Test title",
dtMessage = "Test message",
dtWebURL = "https://google.vn",
dtAppURL = "https://google.vn",
dtIcon = "https://api.jeehr.com/images/logokhachhang/25.jpg",
disabled = false // mặc định false, field này dùng để không gửi notify
};
await _notifier.sendDesktop(asyncnotice);
}
Method sendDesktop là async, nếu dùng await có thể dùng thêm try-catch để bắt lỗi, nếu có.
API Truy xuất dữ liệu
Lấy thông báo của người dùng hiện tại
Path: /notification/pull
Method: GET
Header:
- Authorization:
access-token
Query param:
- status: enum["read", "unread"]
Query param status là optional, truyền vào read sẽ lấy các thông báo đã đọc, unread là các thông báo chưa đọc. Nếu không truyền vào sẽ lấy tất cả.
Đọc thông báo
Path: /notification/read
Method: POST
Header:
- Authorization:
access-token
Body: application/json
{
"id": "string"
}
Query param id là bắt buộc, khi người dùng có thao tác liên quan đến thông báo thì gọi API này để ghi lại thông tin, thực hiện với cả thông báo chưa được đọc và đã từng đọc.
Đọc tất cả thông báo
Path: /notification/readall
Method: POST
Header:
- Authorization:
access-token
Lấy thông báo mới của người dùng hiện tại (Bao gồm thông báo chuông, mainMenu, subMenu, Item)
Path: /notification/pull
Method: GET
Header:
- Authorization:
access-token
Lấy thông danh sách tất cả các thông báo mới: Query param
statuslà optional, truyền vàonewsẽ lấy các thông báo mới,unnewlà các thông báo không còn mới. Nếu không truyền vào sẽ lấy tất cả (Count hoặc Length để đếm số lượng thông báo mới).Lấy thông danh sách các thông báo mới ở MainMenu: Query param
statusvàidlà optional, với paramstatustruyền vàomainmenuvà với paramidtruyền vàoIDcủa mainMenu đó (Count hoặc Length để đếm số lượng thông báo mới).Lấy thông danh sách các thông báo mới ở SubMenu: Query param
statusvàidlà optional, với paramstatustruyền vàosubmenuvà với paramidtruyền vàoIDcủa subMenu đó (Count hoặc Length để đếm số lượng thông báo mới).Lấy thông danh sách các thông báo mới ở Item: Query param
statusvàidlà optional, với paramstatustruyền vàoitemvà với paramidtruyền vàoIDcủa item đó (Count hoặc Length để đếm số lượng thông báo mới).các id của menu vào item và cũng như là Appcode sẽ được truyền bằng thư viện DPSinfra Version 1.7.3 trở lên, sẽ bao gồm các thông tin cần truyền như sau: socketMessage asyncnotice = new socketMessage()
public void testnotification()
{
var json = new
{
abc = "123"
};
socketMessage asyncnotice = new socketMessage()
{
...
idObject = 1, // tương ứng với itemid
AppCode = "CODE", // BẮT BUỘC
menuID = 1,
subMenuID = 1,
};
_notifier.sendSocket(asyncnotice);
}
Đánh dấu đã xem tất cả thông báo mới
Path: /notification/readallnew
Method: POST
Header:
- Authorization:
access-token
Đánh dấu đã xem thông báo ở từng mục (mainmenu, submenu, item)
Path: /notification/readnew
Method: POST
Header:
- Authorization:
access-token
Body: application/json
{
"appCode": "string", //bắt buộc
"mainMenuID": "number", //bắt buộc
"subMenuID": "number", // có thể để trống hoặc bằng 0
"itemID": "number" // (idObject gửi từ thư viện) có thể để trống hoặc bằng 0
}
Query param AppCode, mainMenuID là bắt buộc các thông tin khác có thể để trống
Tùy vào trường hợp muốn cập nhật thông báo từ đâu thì sẽ truyền các thông tin cần thiết như ví dụ sau:
- Muốn cập nhật lại thông báo new ở mainMenu: fields cần truyền là
appCodevàmainMenuID - Muốn cập nhật lại thông báo new ở subMenu: fields cần truyền là
appCode,mainMenuIDvàsubMenuID - Muốn cập nhật lại thông báo new ở item : fields cần truyền là
appCode,mainMenuID,subMenuIDvàitemID
Đánh dấu đã xem và đã đọc thông báo từ trang chi tiết
Thay vì click vào cái chuông xong click vào thông báo thì mới tính là đã đọc đó,thay vào đó người dùng vào chi tiết (gõ tay hoặc click onesignal để vào chi tiết) sẽ gọi api này để đánh dấu đã đọc cho thông báo đó Path:
/notification/readDetail
Method:POST
Header:Authorization:
access-token
Body: application/json
{
"appCode": "string",
"itemID": "number" (idObject gửi từ thư viện)
}