Chuyển đến nội dung chính (Skip to content)
Tất cả dự án
FeaturedNuxtVuePiniaAdonisJSLucid ORMMySQL-compatible SQLSocket.IOFilesystem storage

Panda Work: Nuxt, AdonisJS, Lucid và quản lý storage

Ảnh dự án Panda Work: Nuxt, AdonisJS, Lucid và quản lý storage

Kiến trúc Panda Work: Nuxt/Vue, Pinia, AdonisJS và Lucid; model dự án, task, quyền API, quota filesystem, row lock và journal phục hồi tệp.

11 phút đọc · Case study dự án

Panda Work là ứng dụng quản lý dự án và công việc, với FE Nuxt/Vue chạy phía client và API AdonisJS/Lucid. Phần kỹ thuật không dừng ở bảng task: nó gồm quan hệ dự án, người giao/nhận việc, trạng thái, tệp đính kèm và cơ chế hạn mức storage. Đối chiếu source hiện tại cho thấy Finder dùng adapter filesystem; không có căn cứ mô tả Appwrite như thành phần storage đang được triển khai trong luồng này.

Nuxt SPA, Pinia và API AdonisJS

LớpCông nghệTrách nhiệm
FENuxt, Vue, ssr:falseRender workspace, biểu mẫu và điều hướng tại trình duyệt
Client statePinia auth storeUser, token và quyền hiển thị theo phiên
HTTP clientAxios wrapperBase URL, header authentication và chuẩn hóa lỗi
APIAdonisJS 6, VineJS, Lucid 21Request, validation, nghiệp vụ và data access
Databasemysql2 / MySQL-compatible SQLQuan hệ dự án, task, role và khóa quota
StorageFilesystem adapters, storage servicesUpload, Finder, quota và phục hồi thao tác tệp
RealtimeSocket.IOCác handler thông báo/sự kiện theo source ứng dụng

Cấu hình Nuxt đặt ssr: false, nên đây là một client-rendered workspace. Pinia giữ context phiên; middleware route dùng trạng thái authentication và permission để điều hướng. Điều này phục vụ trải nghiệm truy cập, nhưng quyền dữ liệu cuối cùng vẫn thuộc API. Không nên diễn giải việc route đã bị ẩn trên FE là một kiểm soát truy cập backend.

Nuxt/Vue page → Pinia context → Axios request
  → AdonisJS route / middleware / controller
  → Lucid model hoặc query builder → SQL database
  → storage service → filesystem
Socket.IO handlers → cập nhật hoặc thông báo theo chức năng

Trong source có các routes mới dưới dashboard cho quản lý dự án; một số đường dẫn projects cũ bị middleware chặn để tránh dùng luồng không còn phù hợp. Đây là ví dụ vì sao mô tả kiến trúc cần đọc cả route đang dùng và code legacy, thay vì thấy một thư mục tên projects rồi kết luận đó là luồng hiện hành.

Cấu trúc source và hợp đồng request/response

panda-web/
  pages/                 # workspace, task, profile, dashboard
  stores/auth.js         # Pinia session state
  middleware/            # route guards
  services/api/          # API wrappers theo tài nguyên
  plugins/config/axios/  # base URL và path mapping
panda-api/
  start/                 # routes, socket setup
  app/controllers/
  app/validators/
  app/models/
  app/services/          # storage, quota và nghiệp vụ
  database/migrations/

Axios wrapper hợp nhất header và lỗi thành APIError để các trang không tự diễn giải từng dạng network error theo một cách khác nhau. Với request có token, Authorization được thêm từ auth store. Các lỗi quota hoặc storage unavailable có mã riêng và thông báo phù hợp, thay vì chỉ một chuỗi lỗi chung khiến người dùng không biết cần giảm dung lượng hay thử lại.

Store auth hiện lưu user/token trong localStorage. Đây là đặc điểm triển khai cần ghi nhận, không phải mặc định an toàn cho mọi threat model. Mã chạy trong cùng origin có thể chịu ảnh hưởng của XSS, vì vậy nội dung HTML, thư viện editor và việc xử lý token phải được đánh giá cùng nhau. Một permission getter trên Pinia chỉ giúp UI đọc được quyền của phiên; backend không được tin permission do client tự gửi.

Lucid model: task gắn với dự án, người giao và người nhận

Task model có projectId, taskTypeMetricId, assignerId, assigneeId, status, deadlineAt, startedAt, finishedAt, taskAttachment và taskResult, cùng các trường phục vụ tính thời gian/điểm. Thiết kế này cho thấy task là một đối tượng nghiệp vụ có nhiều quan hệ, không chỉ title và checkbox completed. Dữ liệu loại nhiệm vụ hoặc metric góp phần định nghĩa cách đánh giá, trong khi project giữ context công trình.

Source còn có project phases, templates, phân công và comments. Phân biệt task với phase giúp mô tả đúng cấp độ: phase tổ chức tiến trình dự án, còn task phản ánh phần việc cụ thể của người nhận. Tình trạng một task hoàn tất không tự kết luận mọi điều kiện của phase đã đạt, đặc biệt nếu còn bước đánh giá hoặc bàn giao.

Dữ liệuCâu hỏi nghiệp vụHệ quả kỹ thuật
projectIdTask thuộc dự án nào?Query và quyền cần giữ context dự án
assignerId / assigneeIdAi giao và ai thực hiện?Không gộp hai vai trò vào một owner
status và thời điểmCông việc đang ở bước nào?Đổi trạng thái phải xét mốc thời gian liên quan
taskTypeMetricIdÁp dụng loại/metric nào?Tránh tính điểm theo quy tắc không phù hợp
attachment / resultTệp nào là đầu vào, tệp nào là kết quả?Quyền và vòng đời tệp phải gắn với nghiệp vụ

Các trường điểm hoặc quy tắc hiệu suất là dữ liệu nghiệp vụ, không nên được dùng để tự suy ra tỷ lệ tăng năng suất. Khi thay thuật toán đánh giá, cần tách dữ liệu đầu vào, phiên bản quy tắc và quyền cập nhật để tránh một thay đổi cấu hình diễn giải lại toàn bộ lịch sử ngoài mong muốn.

Filesystem storage: root, path và adapter

Finder config định nghĩa adapter local và adapter read-only cho cùng khu vực lưu trữ. StorageService giải quyết đường dẫn khi API chạy từ source hay build. Storage quota service lấy tập root được quản lý, resolve real path rồi loại root trùng hoặc root con đã được root cha bao phủ. Nếu không làm bước này, cùng một cây tệp có thể bị đếm nhiều lần dưới các adapter khác nhau.

Khi tính usage, code dùng lstat, bỏ qua symlink lồng và deduplicate tệp theo cặp device/inode. Cách này hạn chế đi theo symlink ra ngoài khu vực quản lý và tránh tính hard link thành nhiều bản dữ liệu độc lập. Tuy vậy, đo usage bằng quét filesystem có chi phí theo số lượng tệp; cần đo thời gian quét khi storage lớn thay vì coi đây là phép tính O(1).

Read-only adapter, quota và quyền nghiệp vụ là ba vấn đề khác nhau. Adapter chặn ghi tại một đường truy cập; quota giới hạn tăng dung lượng; authorization xác định người dùng được thao tác tài nguyên nào. Một tệp nằm dưới root hợp lệ không tự có nghĩa mọi tài khoản đều được đọc hoặc xóa nó.

Row lock, AsyncLocalStorage và journal phục hồi tệp

StorageQuotaService.locked mở Lucid transaction rồi khóa một hàng trong storage_quota_locks bằng forUpdate. Hàng khóa này giúp các API worker cùng chia sẻ filesystem không cùng quyết định “còn đủ chỗ” trên dữ liệu usage đã cũ. AsyncLocalStorage giữ journal cùng thư mục tạm trong phạm vi callback, để thao tác lồng nhau có thể dùng chung context thay vì tạo ranh giới khóa mới.

// Mã giả rút gọn từ storage_quota_service
await db.transaction(async (trx) => {
  await trx.from("storage_quota_locks").where("id", 1).forUpdate().firstOrFail()
  return quotaContext.run({ journal, temporary }, async () => {
    try { return await writeFilesWithinQuota() }
    catch (error) {
      await restoreJournalInReverseOrder(journal)
      throw error
    }
  })
})

Điểm mấu chốt là filesystem không rollback theo SQL. Service tạo journal để nhớ tệp mới hoặc bản backup trước ghi đè; khi callback lỗi, journal được duyệt ngược để phục hồi. Nếu chỉ bao việc ghi tệp bằng db.transaction mà không có xử lý bù, rollback database vẫn có thể để lại tệp thừa hoặc mất bản cũ. Cơ chế này là ví dụ cụ thể về consistency giữa hai loại storage.

Khóa một hàng chung có đánh đổi: thao tác quota được serialize và có thể thành điểm nghẽn khi nhiều upload lớn giữ callback quá lâu. Ngoài ra journal in-memory chủ yếu xử lý lỗi được bắt trong tiến trình; mất điện hoặc kill tiến trình giữa thao tác đòi hỏi cơ chế recovery bền vững khác. Vì vậy không nên mô tả nó như một transaction phân tán bảo đảm mọi tình huống sự cố.

Socket.IO, công việc định kỳ và giới hạn code legacy

Source khởi tạo Socket.IO trên HTTP server riêng và đăng ký các socket handler. Có transport realtime là điều kiện để truyền sự kiện, không phải bằng chứng mọi thay đổi task đều có bảo đảm delivery hoặc mọi subscription đều đã kiểm tra quyền đúng. Các handler cụ thể cần được xét cùng token, scope người nhận và trạng thái khi reconnect.

Repository có model/route cho regular tasks, nhưng file regular_task_scheduler và provider đã đối chiếu có phần triển khai được comment. Vì vậy bài viết không khẳng định scheduler tự chạy theo giờ chỉ từ sự tồn tại của tên file. Đây là khác biệt giữa mô hình dữ liệu cho công việc định kỳ và một background scheduler đang hoạt động. Muốn xác nhận lịch tự động cần kiểm tra provider thực thi, tiến trình và log ở môi trường đích.

Một cấu trúc source lâu năm thường chứa cả các luồng cũ lẫn luồng đang dùng. Mô tả kỹ thuật có ích phải chỉ ra ranh giới đó, thay vì liệt kê dependency như một bảng tính năng đã hoàn tất. Điều tương tự áp dụng với storage: cấu hình filesystem hiện hữu là cơ sở để nói về quota; không thêm tên dịch vụ cloud chỉ vì nó từng xuất hiện trong tài liệu hoặc kế hoạch.

Các phép thử quan trọng cho dữ liệu và storage

  • Hai upload đồng thời gần quota: tổng dung lượng sau thao tác không vượt invariant được định nghĩa.
  • Overwrite rồi phát sinh lỗi: tệp cũ được journal phục hồi và tệp mới được xử lý phù hợp.
  • Symlink/hard link trong storage: phạm vi quét và lượng usage không bị tính sai.
  • Sửa task qua API trực tiếp: quyền được thực thi kể cả khi bỏ qua FE guard.
  • Khởi động lại tiến trình trong thao tác tệp: đánh giá phần nào được journal bảo vệ, phần nào cần recovery riêng.

Các ảnh quản trị bên dưới lấy từ bản build Nuxt thật với API preview riêng. Chúng xác nhận cấu trúc màn hình dự án và biểu mẫu, không dùng để kết luận scheduler, quota concurrency hay toàn bộ phân quyền đã được kiểm thử production.

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.

Không gian quản lý dự án

Không gian quản lý dự án
Ảnh chụp bản build Nuxt thật trong phiên preview tách biệt, dùng tài khoản mẫu và danh sách rỗng.

Biểu mẫu thông tin dự án

Biểu mẫu thông tin dự án
Ảnh chụp form thật với các trường công trình, khách hàng và giai đoạn. Chưa nhập hay lưu dự án nào.

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.

Panda Work: Nuxt, AdonisJS, Lucid và quản lý storage | thanhnh.id.vn