SEC - Education Management System
Hệ thống quản lý giáo dục với học viên, lớp học, giáo viên, thanh toán, điểm danh, ticket, notification, Zoom/Zalo và report Excel.
CodLuck Technology JSC
Mình tách phần này thành 2 mục dự án. Mục 01 là SEC, một backend Spring Boot lớn và nhiều nghiệp vụ. Mục 02 là 3D viewer, một demo Web 3D giúp thay đổi texture/vật liệu của đồ vật trong không gian 3D.
Hệ thống quản lý giáo dục với học viên, lớp học, giáo viên, thanh toán, điểm danh, ticket, notification, Zoom/Zalo và report Excel.
Ứng dụng Web 3D cho phép xem không gian kiến trúc và thay đổi texture/vật liệu của các bề mặt như sàn, tường, tủ hoặc nội thất theo thời gian thực.
Mục 01
SEC làm mình thấy rõ cảm giác đọc một backend doanh nghiệp thật: không phải lúc nào code cũng gọn như tutorial, cũng không phải mọi thứ đều có mapper, clean architecture hay annotation có sẵn. Có những nơi rất thực dụng, có những nơi hơi nặng, nhưng chính vì vậy mình học được nhiều hơn về trade-off trong dự án thật.
Vai trò
Tìm hiểu codebase, phân tích flow nghiệp vụ, đọc service/repository/scheduler/security và ghi lại những điểm đáng học trong backend.
Phạm vi
JWT security, phân quyền custom, scheduled jobs, notification đa kênh, OTP, audit log, JPA/Hibernate, native query/report, Excel import/export và tích hợp Zoom/Zalo.
SEC không thể đọc theo kiểu mở controller rồi lần từng API đơn lẻ. Nhiều flow chạy xuyên qua học viên, lớp học, lịch học, thanh toán, điểm danh, notification và Zoom meeting. Một hành động nhỏ ở UI có thể kéo theo cập nhật DB, tạo task nền, gửi thông báo và đồng bộ sang hệ thống ngoài.
Dự án không tách mapper rõ ràng kiểu MapStruct hay mapper class riêng cho mọi entity. Nhiều chỗ không map entity sang DTO ở service, mà repository query trả thẳng DTO/projection để service lấy dùng luôn. Cách này giảm lớp trung gian và nhanh cho màn hình report/list, nhưng DTO bị gắn khá chặt với query.
Phần report và màn hình quản trị khiến repository có nhiều JPQL/native query khá dài. Có query join nhiều bảng rồi trả thẳng về DTO dùng luôn, thay vì lấy entity ra rồi mapper sang response ở service. Cách này thực dụng cho list/search/export Excel vì lấy đúng field cần hiển thị, nhưng repository dễ thành một lớp query lớn, khó type-safe và dễ vỡ nếu DTO constructor/projection không khớp field.
Phần phân quyền của SEC không dùng kiểu rải @PreAuthorize có sẵn, mà custom annotation @AuthFilter và bắt bằng AuthFilterAspect. Aspect dùng @Around để chặn method có @AuthFilter, lấy current user qua util.getCurrentUser(), gọi permissionService.getListByRoleId(user.getRoleId()), rồi so permission trong DB bằng modulePath + actionPath với danh sách permission khai báo trong annotation. Nếu khớp thì joinPoint.proceed(), nếu không thì throw ResponseStatusException(HttpStatus.FORBIDDEN).
Các scheduled job là phần mình thấy thú vị nhất. Dự án có job tạo/update/xóa Zoom meeting, lấy recording, tạo attendance, cập nhật trạng thái lớp, nhắc thanh toán, nhắc học, xử lý ticket quá hạn và upsale/payment schedule. Pattern mình học được là API lưu trạng thái/task vào DB, job nền quét và xử lý sau.
SEC không để mỗi service tự gửi Zalo/Firebase/Email theo cách riêng. Backend tạo NotificationChannelMessage gồm templateCode, channelType và channelData rồi gọi notification API riêng. Cách này giúp core backend không hard-code nội dung tin nhắn, đồng thời gom tracking/template/channel về một nơi.
Dự án có BaseEntity/JPA auditing để tự động ghi createdAt, updatedAt, createdUser, updatedUser. Ngoài ra còn có custom annotation và aspect cho activity log. Lúc đầu đọc AOP hơi khó theo flow vì logic không nằm trực tiếp trong method, nhưng sau đó mình hiểu đây là cách tách cross-cutting concern như audit/logging ra khỏi business code.
Trước khi đọc SEC, mình hay nghĩ backend chủ yếu là API JSON. Nhưng dự án có nhiều template Excel, export report và import dữ liệu bằng Apache POI. Đây là phần code dễ dài, dễ sai index/style nhưng lại rất quan trọng với hệ thống nội bộ.
Mình cố tình không viết SEC như một danh sách công nghệ đẹp mắt. Phần giá trị nhất với mình là trải nghiệm đọc một codebase có cả điểm hay, điểm nặng, và nhiều quyết định rất thực tế.
Mục 02
3D viewer là một demo Web 3D có mục tiêu giúp người dùng xem không gian kiến trúc trên trình duyệt và thay đổi texture/vật liệu của đồ vật trong không gian 3D. Thay vì nhìn bản vẽ 2D hoặc ảnh render tĩnh rồi tự tưởng tượng, người dùng có thể chọn vật liệu cho sàn, tường, tủ, nội thất và xem kết quả gần như ngay lập tức.
Mục tiêu
Tạo một viewer 3D tương tác để thay đổi texture/vật liệu của các bề mặt trong mô hình, giúp khách hàng hình dung không gian sau khi chọn gạch, gỗ, màu sơn hoặc vật liệu nội thất.
Phạm vi
Load model GLB, xử lý camera, đổi material/texture theo nhóm object, thử nghiệm tương tác chọn mesh, và dùng Supabase để quản lý nhóm vật thể/texture theo hướng động.
Trong nội thất/xây dựng, khách hàng thường phải nhìn bản vẽ 2D, ảnh render hoặc catalogue vật liệu rồi tự tưởng tượng khi ghép vào nhà thật sẽ ra sao. 3D viewer giải quyết đúng khoảng trống đó: đưa không gian lên web, cho người dùng xoay nhìn hoặc đi vào bên trong, rồi đổi texture để thấy cảm giác vật liệu trong bối cảnh thật hơn.
Khi đổi vật liệu trong Three.js, mình không chỉ thay một tấm ảnh lên mesh. Cần hiểu material, map texture, độ lặp texture, ánh sáng, độ bóng/nhám và cảm giác PBR để bề mặt như gạch, vân gỗ hoặc tường nhìn không bị giả. Đây là phần làm mình thấy Web 3D khác hẳn UI web thông thường.
Dự án có 2 kiểu trải nghiệm camera: orbit mode để nhìn tổng thể mô hình và interior/FPS mode để đi vào bên trong không gian. Phần này không chỉ là kỹ thuật điều khiển camera, mà còn là cách giúp người xem cảm nhận tỷ lệ, chiều sâu và bố cục căn phòng.
Ban đầu mình làm tính năng click trực tiếp vào vật thể trong 3D bằng Raycaster: hover vào mesh thì highlight, click vào sofa/tường/sàn thì hiện menu để đổi vật liệu cho đúng vật thể đó. Về mặt kỹ thuật nó rất thú vị, gần giống cơ chế interaction trong game, và mình từng nghĩ đây sẽ là điểm demo ấn tượng nhất.
Sau khi demo nội bộ, mentor góp ý rằng tính năng click chọn mesh tuy kỹ thuật rất hay nhưng có thể quá rối với khách hàng cuối. Người dùng không nhất thiết là người rành công nghệ; bắt họ rê chuột trong không gian 3D để tìm đúng mesh đang highlight có thể tạo friction. Thay vào đó, UI nên có danh sách rõ ràng như Sàn nhà, Tường, Tủ, Bàn ghế; người dùng chỉ cần chọn nhóm rồi chọn texture.
Việc bỏ hoặc vô hiệu hóa phần code phức tạp mình đã mất nhiều thời gian làm không dễ chịu, nhưng sau khi chuyển sang UI menu cố định, ứng dụng trở nên rõ ràng hơn hẳn. Người mới nhìn vào biết ngay phải bấm đâu để đổi màu sàn hoặc đổi vật liệu tường, không cần giải thích thao tác chuột trong không gian 3D.
Một hướng quan trọng của dự án là không fix cứng mọi thứ trong code. Supabase được dùng để admin có thể cấu hình nhóm object, ví dụ nhóm tường, nhóm sàn, nhóm tủ, rồi map từng nhóm với thư viện texture tương ứng. Nhờ vậy viewer có thể mở rộng vật liệu hoặc nhóm đối tượng mà không phải sửa logic render quá nhiều.
Dự án giúp mình học Three.js/WebGL, GLB, camera và material, nhưng bài học lớn nhất lại là tư duy sản phẩm: khách hàng không mua độ phức tạp của code, họ mua một cách dễ hơn để ra quyết định. Một tính năng càng kỹ thuật không có nghĩa là càng tốt nếu nó làm người dùng mất nhiều công để hiểu.
Với 3D viewer, mình học được rằng sản phẩm tốt không phải là sản phẩm có nhiều thứ phức tạp nhất, mà là sản phẩm giúp người dùng đạt mục tiêu nhanh và ít bối rối nhất.