Panasonic 2026: AdonisJS, Lucid và kiến trúc job nền

Phân tích Panasonic 2026: AdonisJS và Lucid, snapshot bài thi, webhook tiến độ, certificate revision, lease worker, PDF và hàng đợi email riêng.
Panasonic 2026 kết hợp nền tảng đào tạo, đánh giá và phát hành chứng nhận. Backend dùng AdonisJS với Lucid, trong khi Next.js cung cấp giao diện và các công việc dài được đưa sang worker. Điểm kỹ thuật nổi bật nằm ở snapshot dữ liệu, cách giành quyền xử lý job bằng lease, sự tách biệt giữa tạo PDF và gửi email, cùng luồng tích hợp tiến độ khóa học. Bài này mô tả source và ranh giới kiến trúc, không công bố dữ liệu người học hoặc thông tin triển khai nội bộ.
Các ứng dụng và các nhóm nghiệp vụ
| Lớp | Công nghệ | Trách nhiệm |
|---|---|---|
| FE / CMS | Next.js, React, TypeScript | Cổng đào tạo, đánh giá và quản trị |
| HTTP API | AdonisJS, VineJS | Route, validation, xác thực và nghiệp vụ |
| Data layer | Lucid, mysql2 / MySQL-compatible SQL | Model quan hệ, query builder, migration và transaction |
| Background worker | Node.js, service functions | Import tài khoản, PDF và các hàng đợi email |
| PDF renderer | pdf-lib, fontkit | Dựng trang PDF và xử lý font từ snapshot |
| Media | Go service và storage nghiệp vụ | Media công khai tách khỏi chứng nhận cần quyền đọc |
| Tích hợp | Hoola client, webhook services | Nhận/đối chiếu tiến độ và tác vụ tài khoản |
FE / CMS → AdonisJS API → Lucid → SQL database
Hoola → webhook intake → event record → processor → tiến độ
Cấp chứng nhận → certificate revision queued
→ PDF worker → private storage
→ email task → mail serviceThư mục services có các nhóm độc lập như exam_generator, exam_scoring, certificate_service, certificate_worker, hoola_webhook_processor và group_import_worker. Tên service phản ánh trách nhiệm cụ thể. Việc tách file worker khỏi controller giúp tránh buộc HTTP request chờ kết nối ngoài hoặc render tài liệu, nhưng worker vẫn phải giải quyết cạnh tranh và trạng thái dở dang.
Model đào tạo và snapshot bài thi
Các đối tượng chính gồm người học, trường, nhóm, câu hỏi, bộ đề, bài thi và tiến độ đào tạo. Quan hệ người học với trường/nhóm hỗ trợ xác định phạm vi dữ liệu; bài thi cần lưu ngữ cảnh câu hỏi đã dùng, vì ngân hàng câu hỏi có thể tiếp tục được chỉnh sửa. Nếu chấm một bài cũ bằng đáp án hiện tại của ngân hàng, kết quả có thể thay đổi ngoài ý muốn.
ExamScoringService nhận questionsData dạng snapshot và userAnswers, làm phẳng các phần câu hỏi rồi xác định đáp án theo vị trí hoặc ID. Với câu hỏi không phải multiple choice, source có nhánh dùng điểm được cung cấp theo quy trình chấm. Cơ chế tính điểm này phải được xem cùng authorization ở controller: không được suy rằng mọi giá trị score gửi từ client đều đáng tin chỉ vì service có trường tương ứng.
Thời điểm bắt đầu và thời lượng cũng là dữ liệu nghiệp vụ, không chỉ đồng hồ đếm ngược ở FE. Service nhận createdAt để tính duration; UI cần phản ánh dữ liệu máy chủ thay vì trở thành nguồn duy nhất của thời gian thi. Đây là một ranh giới cần kiểm tra khi có mất kết nối, gửi lại bài hoặc thay đổi cấu hình câu hỏi.
Webhook tiến độ: event record, sanitization và xử lý
Hoola webhook được tổ chức thành các service tiếp nhận event và processor. Trước khi giữ payload phục vụ điều tra, code duyệt các trường nhạy cảm như authorization, cookie, password, secret, token và API key để redact. Payload đã sanitize được hash và giới hạn kích thước; dữ liệu vượt ngưỡng được ghi theo dạng rút gọn. Đây là quyết định kiến trúc về observability: lưu đủ bằng chứng xử lý nhưng không biến log thành nơi tập trung secrets.
Event record có các trạng thái và kết quả xử lý như HTTP status, result code, authValid, payloadValid, course/member context. Các thông tin này hỗ trợ phân biệt request sai authentication, payload không hợp lệ và lỗi cập nhật nghiệp vụ. Cùng một HTTP 200 ở gateway không nên được dùng thay cho kết luận toàn bộ tiến độ đã đồng bộ đúng.
Hash payload hỗ trợ đối chiếu nội dung, nhưng không tự tạo idempotency nghiệp vụ cho mọi trường hợp. Muốn đánh giá gửi lặp cần xét khóa event và cách processor cập nhật tiến độ. Tương tự, retry khi gọi hệ thống ngoài phải phân biệt lỗi tạm thời với lỗi dữ liệu hoặc quyền; retry vô hạn không sửa được một định danh người học không khớp.
Chứng nhận: snapshot, revision và transaction
CertificateSnapshot chứa fullName, schoolId, schoolName, logoKey, title, typeName và templateVersion. Tại thời điểm cấp, hệ thống lưu thông tin đủ để render theo phiên bản đã chọn. Khi tên người học hoặc logo trường đổi sau đó, chứng nhận cũ không nên bị dựng lại bằng dữ liệu mới một cách âm thầm. Snapshot là lớp dữ liệu lịch sử; revision là lớp theo dõi các lần phát hành hoặc thay đổi tiếp theo.
CertificateService dùng Lucid transaction cho các thao tác liên quan; các luồng sửa/thu hồi có truy vấn forUpdate để khóa bản ghi chứng nhận. Việc tăng version và tham chiếu revision giúp tách danh tính chứng nhận khỏi từng bản PDF cụ thể. Thay vì cập nhật một URL file duy nhất rồi mất dấu bản trước, hệ thống giữ trạng thái tài liệu có cấu trúc.
// Mã giả mô tả ranh giới nghiệp vụ, không phải đoạn chạy độc lập
await db.transaction(async (trx) => {
const certificate = await lockCertificate(trx, id)
const revision = await createRevision(trx, certificate, snapshot)
await enqueuePdfState(trx, revision.id)
return revision
})Transaction ở đây chỉ bao dữ liệu SQL. Tạo file, gửi email và gọi API ngoài không tự rollback cùng nó. Do đó ghi một trạng thái queued trong database rồi để worker thực hiện là cách tránh giữ transaction mở trong suốt thao tác chậm. Cơ chế transaction chung có thể đối chiếu trong tài liệu Lucid; thiết kế snapshot/revision là phần riêng của ứng dụng.
PDF worker: lease token và conditional update
Worker tìm revision có pdf_status queued, tạo token mới rồi update có điều kiện theo id và trạng thái queued để giành quyền xử lý. Chỉ worker update được bản ghi mới tiếp tục render. Lease có thời hạn; trạng thái processing quá hạn được đưa lại về queued. Cách claim này giải quyết cạnh tranh bằng dữ liệu trong database, thay vì một biến in-memory mà tiến trình khác không nhìn thấy.
Khi render xong, worker ghi pdf_path và status ready với điều kiện lease_token vẫn khớp. Nếu lease đã được thay bởi một lượt xử lý khác, worker cũ không nên ghi đè kết quả mới. Khi render lỗi, trạng thái failed giữ error bị giới hạn độ dài để người vận hành xác định bước cần sửa. Lease không mặc nhiên bảo đảm side effect bên ngoài xảy ra đúng một lần, nhất là khi thao tác kéo dài vượt thời hạn lease.
queued
→ claim(id, status=queued, lease_token)
→ processing → render PDF → store file
→ ready nếu lease_token vẫn thuộc worker
processing hết lease → queued lại
render lỗi → failed + thông tin lỗiRenderer dùng pdf-lib và fontkit, có hàm đo độ rộng text, giảm kích thước font trong khoảng cho phép và từ chối nếu nội dung vẫn vượt vùng in. Điều này khác với chụp HTML thành ảnh rồi nhúng vào PDF. Font hỗ trợ tiếng Việt, dòng tiêu đề dài và bố cục in là những điều kiện kiểm chứng cần thiết trước khi xem tài liệu là sẵn sàng.
Email queue, storage riêng và cách chạy background cycle
Email chứng nhận dùng bảng task riêng với vòng đời gửi, lease và trạng thái. Một PDF ready không đồng nghĩa email đã được hệ thống mail nhận; SMTP chấp nhận cũng chưa phải xác nhận thư đã vào inbox người nhận. Tách hai job cho phép gửi lại email khi lỗi mạng mà không buộc tạo lại tài liệu, đồng thời hỗ trợ theo dõi lỗi ở đúng tầng.
Background cycle tập hợp các nhóm job như tài khoản Hoola, PDF, email chứng nhận, email tài khoản và dọn import hết hạn. Hàm điều phối dùng Promise.allSettled để kết quả lỗi của một nhóm không tự làm mất kết quả đã hoàn tất của nhóm khác. Đây là worker dựa trên service và trạng thái database, không có cơ sở để gọi nó là RabbitMQ hay BullMQ khi source không dùng những thành phần đó cho luồng này.
Tệp chứng nhận được đọc qua storage service và luồng quyền tương ứng. Media công khai của landing page có thể phục vụ theo CDN, còn PDF chứa thông tin người học phải được xem trong context ownership và authorization. Cấp quyền ở trang quản trị không thay thế kiểm tra khi tải file. Cũng cần backup cả snapshot/revision lẫn storage để phục hồi được tài liệu.
Những invariant cần bảo vệ khi mở rộng
- Đáp án hoặc hồ sơ đổi sau khi đã phát sinh bài thi/chứng nhận không âm thầm sửa lịch sử snapshot.
- Hai worker cùng claim một revision chỉ có một owner hợp lệ tại thời điểm update.
- Worker cũ quay lại sau hết lease không ghi đè trạng thái của owner mới.
- Lỗi email không làm mất PDF đã ready; lỗi PDF không bị báo như đã gửi tài liệu thành công.
- Webhook lưu bằng chứng xử lý đã redact và giới hạn kích thước, không ghi secrets vào log.
Đây là các tiêu chí kiểm thử được rút ra từ thiết kế; ảnh giao diện không chứng minh các invariant đã qua mọi tình huống tải và sự cố. Phần cần đo thêm là tuổi task, thời gian render, số lượt claim lại và tỷ lệ lỗi theo từng loại job, thay vì gom tất cả thành một nhãn thành công chung.
Giao diện ứng dụng và dữ liệu minh họa
Các ảnh dưới đây được chụp trực tiếp từ FE hoặc trang quản trị của ứng dụng. Với các màn hình chạy trong phiên preview, dữ liệu mẫu được tách khỏi hệ thống vận hành; bố cục và thành phần giao diện do chính source của dự án render.
Landing page chương trình

Cổng đào tạo trực tuyến

Tài liệu kỹ thuật đối chiếu
Xem thêm các dự án khác hoặc trao đổi về kiến trúc và phạm vi triển khai.