Chương 13. Phát triển module bằng AI
Back To BlogsChương 13. Phát triển module bằng AI
📚 Mục lục sáchĐến chương này, bạn đã biết cách tạo trang, bài viết, chức năng và tích hợp dịch vụ bên ngoài. Nhưng nếu nhu cầu của bạn vượt ra ngoài những gì EzyPlatform có sẵn, bạn có thể tự phát triển module (plugin) riêng — và đăng lên Marketplace để chia sẻ hoặc kinh doanh.
Khi nào cần tự phát triển module?
- Bạn cần một tính năng đặc thù mà không plugin có sẵn nào đáp ứng được.
- Bạn muốn đóng gói một bộ tính năng để tái sử dụng cho nhiều website khác nhau.
- Bạn muốn phát triển một sản phẩm để bán trên Marketplace của EzyPlatform.
Plugin: đơn vị mở rộng cơ bản
Một Plugin là một gói mở rộng độc lập, có thể cài vào hoặc gỡ khỏi EzyPlatform mà không ảnh hưởng đến phần còn lại của hệ thống. Khi mô tả yêu cầu phát triển plugin cho AI, hãy làm rõ:
- Plugin giải quyết vấn đề gì, dành cho ai sử dụng.
- Nó cần những màn hình quản trị (Admin) nào để cấu hình.
- Nó cần hiển thị gì ở phía Website.
- Nó có cần lắng nghe Event nào từ hệ thống không.
Marketplace Item: đóng gói để chia sẻ hoặc bán
Sau khi plugin hoàn thiện, bạn có thể đóng gói thành một Marketplace Item — sản phẩm có thể được cài đặt bởi bất kỳ ai dùng EzyPlatform. Một Marketplace Item tốt cần có:
- Mô tả rõ ràng plugin dùng để làm gì, giải quyết vấn đề gì.
- Ảnh chụp minh họa (screenshot) giao diện thực tế.
- Tài liệu hướng dẫn cài đặt và sử dụng cơ bản.
Theme: bộ giao diện có thể thay đổi toàn bộ
Theme (chủ đề giao diện) định hình toàn bộ diện mạo của một website — màu sắc, font chữ, bố cục tổng thể. Việc phát triển theme khác với việc chỉnh sửa một trang đơn lẻ: bạn cần đảm bảo tính nhất quán xuyên suốt tất cả các trang, bài viết, và khu vực khác nhau của website.
Khi phát triển theme, hãy yêu cầu AI thiết lập trước một bộ quy tắc chung: bảng màu, font chữ, khoảng cách, kiểu nút bấm — rồi mới áp dụng cho từng trang cụ thể. Điều này giúp toàn bộ website đồng bộ và chuyên nghiệp hơn.
Cài đặt môi trường cục bộ
Phát triển module khác với việc tạo trang hay bài viết qua giao diện Admin: bạn cần một môi trường lập trình Java chạy ngay trên máy để AI có thể sửa code, build và chạy thử trước khi cài vào hệ thống thật. Gồm 5 bước, mỗi bước bạn chỉ cần đưa prompt tương ứng cho AI Assistant (Claude Code, Codex...) — nó sẽ tự kiểm tra và cài đặt giúp bạn.
Bước 1: Cài đặt JDK 8
EzyPlatform chạy trên nền Java, nên cần đúng JDK 8 (không phải bản mới hơn). Sau khi cài, biến môi trường JAVA_HOME phải trỏ đúng đến thư mục cài đặt.
Máy tôi đang chạy {Windows / macOS / Linux}. Hãy kiểm tra máy tôi đã cài JDK 8 chưa (lệnh java -version).
Nếu chưa có hoặc đang là bản khác, hướng dẫn tôi tải và cài JDK 8 từ Oracle hoặc Eclipse Adoptium, rồi khai báo biến môi trường JAVA_HOME trỏ đến đúng thư mục cài đặt.
Sau khi cài xong, đưa tôi lệnh để xác nhận java -version và JAVA_HOME đã đúng.
Bước 2: Cài đặt MySQL
MySQL là nơi EzyPlatform lưu trữ dữ liệu. Bạn cần cài MySQL, sau đó tạo sẵn một database rỗng dành riêng cho EzyPlatform.
Hãy kiểm tra máy tôi đã cài MySQL chưa. Nếu chưa, hướng dẫn tôi cài MySQL trên {Windows / macOS / Linux} và khởi động dịch vụ.
Sau đó hướng dẫn tôi tạo một database rỗng tên ezyplatform cùng một user có quyền truy cập database đó, và cho tôi biết cần ghi lại những thông tin gì (host, port, username, password) để dùng ở bước cấu hình sau.
Bước 3: Cài đặt Maven
Maven là công cụ build cho các plugin viết bằng Java — dùng để biên dịch và đóng gói module thành file jar trước khi cài vào EzyPlatform.
Hãy kiểm tra máy tôi đã cài Maven chưa (lệnh mvn -version). Nếu chưa có, hướng dẫn tôi cài Maven trên {Windows / macOS / Linux} và đảm bảo nó dùng đúng JDK 8 vừa cài ở Bước 1.
Sau khi cài xong, đưa tôi lệnh để xác nhận mvn -version chạy đúng.
Bước 4: Tải và cấu hình EzyPlatform
Sau khi có đủ JDK, MySQL và Maven, đến lượt tải chính EzyPlatform về, giải nén, rồi khai báo biến môi trường EZYPLATFORM_HOME để chạy lệnh từ bất kỳ đâu.
Hãy giúp tôi tải gói cài đặt EzyPlatform mới nhất, giải nén vào thư mục {đường dẫn bạn muốn}, rồi khai báo biến môi trường EZYPLATFORM_HOME trỏ đến thư mục đó.
Sau đó mở file settings/setup.properties giúp tôi điền thông tin kết nối tới database MySQL đã tạo ở Bước 2 (host, port, tên database, username, password).
Bước 5: Chạy thử
Khởi tạo ở chế độ console trước để kiểm tra cấu hình không lỗi, sau đó tạo tài khoản super admin và chuyển sang chạy nền.
Hãy chạy giúp tôi EzyPlatform ở chế độ console (trên Linux/macOS dùng cli.sh, trên Windows dùng cli.bat) để kiểm tra cấu hình không có lỗi. Nếu chạy thành công, hướng dẫn tôi truy cập http://localhost:9090/setup-admin để tạo tài khoản super admin đầu tiên. Sau khi tạo xong, giúp tôi dừng tiến trình console và khởi động lại ở chế độ nền kèm theo dõi log.
Khi cần dừng toàn bộ hệ thống, chỉ cần nhờ AI chạy lệnh bash cli.sh stop (hoặc cli.bat stop trên Windows).
Muốn thử nhanh EzyPlatform mà chưa cần build plugin, bạn có thể chạy bằng Docker (docker pull youngmonkeys/ezyplatform) — nhưng cách này chỉ phù hợp để trải nghiệm, không thay thế được môi trường JDK, Maven, MySQL cài trực tiếp khi thực sự phát triển module.
Xem hướng dẫn cài đặt chi tiết từng bước tại trang hướng dẫn chính thức của EzyPlatform.
Cài đặt bộ công cụ hỗ trợ phát triển (EzyPlatform SDK)
EzyPlatform cài xong ở phần trên là nơi chạy website/ứng dụng của bạn. Còn EzyPlatform SDK là bộ công cụ dòng lệnh (ezy.sh) dành riêng cho việc phát triển plugin — sinh khung project, build và đóng gói module. Gồm 3 bước:
Bước 1: Tải và giải nén SDK
Tải EzyPlatform SDK về máy, rồi giải nén vào một thư mục cố định, ví dụ app/ezyplatform-sdk.
Hãy giúp tôi tải EzyPlatform SDK từ https://ezyplatform.com/ và giải nén vào thư mục app/ezyplatform-sdk trên máy {Windows / macOS / Linux} của tôi.
Bước 2: Khai báo biến môi trường
Khai báo biến EZYPLATFORM_SDK trỏ đến thư mục vừa giải nén, rồi thêm $EZYPLATFORM_SDK/bin vào PATH để có thể gọi lệnh ezy.sh từ bất kỳ đâu.
Hãy giúp tôi khai báo biến môi trường EZYPLATFORM_SDK trỏ đến thư mục app/ezyplatform-sdk vừa giải nén, và thêm $EZYPLATFORM_SDK/bin vào biến PATH. Sau đó đưa tôi lệnh để xác nhận ezy.sh đã gọi được từ Terminal.
Bước 3: Build các thư viện cần thiết
SDK cần một số thư viện được build sẵn bằng Maven từ kho mã nguồn ezyplatform-development trước khi dùng được. Bước này yêu cầu đã cài Maven ở phần trước.
Hãy giúp tôi clone kho mã nguồn ezyplatform-development (địa chỉ ghi trong hướng dẫn cài đặt EzyPlatform SDK chính thức), vào thư mục dự án của tôi, sau đó chạy bash build.sh để build các thư viện cần thiết bằng Maven. Nếu có lỗi trong quá trình build, hãy đọc log và giải thích cho tôi nguyên nhân.
Sau khi hoàn tất cả 3 bước, bạn đã có đủ công cụ để bắt đầu nhờ AI sinh khung plugin mới bằng ezy.sh. Xem chi tiết tại trang hướng dẫn cài đặt EzyPlatform SDK chính thức.
Cài đặt IntelliJ IDEA
Ngoài VS Code để trò chuyện với AI, khi phát triển module bạn còn cần một IDE Java chuyên dụng để debug, xem cấu trúc project Maven và điều hướng mã nguồn Java thuận tiện hơn — IntelliJ IDEA là lựa chọn phổ biến nhất. Bản Community Edition miễn phí và mã nguồn mở là đủ dùng cho việc phát triển plugin EzyPlatform.
Lưu ý: kho github.com/JetBrains/intellij-community/releases chỉ đánh dấu (tag) các phiên bản mã nguồn mở của IntelliJ, không có sẵn file cài đặt (.exe, .dmg, .tar.gz). Để tải bộ cài đặt chạy được ngay, hãy dùng trang tải về chính thức của JetBrains.
Hãy hướng dẫn tôi tải và cài IntelliJ IDEA Community Edition (bản miễn phí) từ trang tải về chính thức của JetBrains cho máy {Windows / macOS / Linux} của tôi.
Sau khi cài xong, đưa tôi cách kiểm tra IntelliJ đã nhận diện đúng JDK 8 tôi đã cài ở phần trước hay chưa.
Nếu muốn tìm hiểu sâu hơn về chính mã nguồn của IntelliJ (không bắt buộc để phát triển plugin), bạn có thể tham khảo thêm kho intellij-community trên GitHub.
Hướng dẫn khởi tạo dự án mô đun
Sau khi đã có đủ JDK, MySQL, Maven, EzyPlatform, EzyPlatform SDK và IntelliJ IDEA, bạn đã sẵn sàng để sinh khung (scaffold) cho plugin đầu tiên bằng lệnh ezy.sh. Gồm 3 bước:
Bước 1: Sinh khung project bằng ezy.sh
Lệnh ezy.sh cp (create project) sinh sẵn cấu trúc project Maven cho plugin, gồm tên project, nhóm (group), phiên bản (version), và danh sách module cần có — chọn trong 5 loại: admin-plugin (màn hình quản trị), web-plugin (hiển thị phía website), theme (giao diện), socket-plugin và socket-app (xử lý realtime qua socket). Ví dụ tạo một project tên sweet-bakery, nhóm org.youngmonkeys, phiên bản 1.0.0, gồm 3 module admin-plugin, web-plugin và theme:
ezy.sh cp sweet-bakery -g org.youngmonkeys -v 1.0.0 -i admin-plugin,web-plugin,theme
Hãy giúp tôi tạo một plugin project EzyPlatform mới tên {tên-plugin-của-bạn} bằng ezy.sh, với group là {com.tencongty}, gồm các module {admin-plugin,theme,web-plugin} (chọn module phù hợp với plugin tôi muốn làm: {mô tả ngắn plugin của bạn}).
Giải thích cho tôi ý nghĩa của từng tham số trong lệnh trước khi chạy, và cấu trúc thư mục vừa được sinh ra gồm những gì.
Bước 2: Mở project và chạy thử bằng IntelliJ IDEA
Import project vừa sinh vào IntelliJ IDEA (đã cài ở phần trên), cấu hình JDK 8 làm Project SDK nếu IntelliJ chưa tự nhận diện đúng, chạy bash export.sh (hoặc export.bat trên Windows) để export các thành phần cần thiết, rồi tìm file StartupTest tương ứng trong thư mục src/test/ của từng module (ví dụ HelloWorldAdminPluginStartupTest cho module admin-plugin) để chạy thử ngay trong IDE.
Tôi vừa mở project plugin {tên-plugin-của-bạn} bằng IntelliJ IDEA. Hãy hướng dẫn tôi:
1. Kiểm tra và cấu hình JDK 8 làm Project SDK nếu IntelliJ chưa tự nhận đúng.
2. Chạy bash export.sh (hoặc export.bat) để export thành phần cần thiết.
3. Tìm đúng file StartupTest trong thư mục src/test/ của module admin-plugin.
4. Chạy file đó và truy cập http://localhost:9090 để kiểm tra plugin đã chạy đúng chưa.
Bước 3: Export plugin thành file cài đặt
Khi plugin đã chạy đúng như mong muốn, export lại thành một file zip hoàn chỉnh để cài vào EzyPlatform thật hoặc đóng gói lên Marketplace ở phần trước.
Plugin {tên-plugin-của-bạn} đã chạy thử ổn định. Hãy giúp tôi chạy bash export.sh để export plugin thành file zip trong thư mục target/project/, rồi hướng dẫn tôi cách cài file zip đó vào một EzyPlatform khác để kiểm tra lần cuối trước khi đăng Marketplace.
Xem hướng dẫn đầy đủ tại trang hướng dẫn khởi tạo plugin chính thức của EzyPlatform.
Cấu trúc dự án mô đun
Sau khi ezy.sh cp sinh project (ví dụ tên book-store), mỗi loại module bạn chọn ở Bước 1 trở thành một sub-module Maven riêng, đặt tên theo mẫu {project}-{loại-module}. Hiểu rõ vai trò từng module giúp bạn mô tả yêu cầu cho AI đúng chỗ hơn, thay vì để AI tự đoán nên đặt code ở đâu.
{project}-sdk— chứa các lớp dùng chung (model, service, config...) cho toàn bộ module còn lại.{project}-admin-plugin— chứa model, controller và file tĩnh cho màn hình quản trị (Admin).{project}-web-plugin— chứa model, controller và file tĩnh hiển thị phía Website.{project}-theme— module giao diện (theme) cho phần Website.{project}-socket-pluginvà{project}-socket-app— xử lý các tác vụ realtime qua socket.
sweet-bakery sau khi sinh bằng ezy.sh cp, mở trong IntelliJ IDEA — mỗi module nằm trong một thư mục Maven riêng.Bên trong mỗi module, cấu trúc thư mục theo chuẩn Maven:
src/main/java— mã nguồn Java: model, controller, service, class cấu hình...src/main/resources/static— file tĩnh: html, css, javascript, media.src/main/resources/templates— template Thymeleaf.src/test/java— mã kiểm thử, gồm cả fileStartupTestdùng để chạy thử module ngay trong IDE (đã nhắc ở phần trước).
Ở gốc mỗi module còn có pom.xml (cấu hình Maven), module.properties (định nghĩa tên, version, group của plugin), menus.properties (định nghĩa menu Admin, chỉ có ở module admin-plugin) và assembly.xml (cấu hình đóng gói). Ở gốc project còn có script export.sh/export.bat dùng để xuất bản plugin vào EzyPlatform cục bộ.
Khi export, mỗi module được sao chép vào đúng vị trí tương ứng trong EzyPlatform:
{project}-admin-plugin→ezyplatform/admin/plugins/{project}{project}-web-plugin→ezyplatform/web/plugins/{project}{project}-theme→ezyplatform/admin/themes/{project}{project}-socket-plugin→ezyplatform/socket/plugins/{project}{project}-socket-app→ezyplatform/socket/apps/{project}{project}-sdk→ thư mụclibcủa tất cả module trên (admin, socket, web) — vì đây là thư viện dùng chung cho cả plugin.
Tôi đang phát triển plugin {tên-plugin-của-bạn} với các module {admin-plugin,theme,web-plugin}.
Tôi muốn thêm tính năng: {mô tả tính năng}.
Hãy cho tôi biết phần này nên đặt code ở module nào (sdk, admin-plugin, web-plugin, theme...), theo đúng cấu trúc chuẩn của một plugin project EzyPlatform, trước khi bắt đầu viết code.
Xem chi tiết đầy đủ tại trang hướng dẫn cấu trúc plugin project chính thức.
Hướng dẫn khắc phục lỗi khi import dự án vào IntelliJ (nếu có)
Một số máy, đặc biệt trên Windows, IntelliJ đôi khi không nhận diện được các biến môi trường như EZYPLATFORM_HOME hay EZYPLATFORM_SDK dù đã khai báo đúng ở cấp hệ điều hành, khiến project báo lỗi build ngay khi import. Nếu gặp tình huống này, hãy thử lần lượt 4 bước sau — dừng lại ngay khi bước nào đó đã khắc phục được lỗi, không cần làm hết cả 4.
Bước 1: Kiểm tra lại biến môi trường ở cấp hệ điều hành
Đảm bảo biến (ví dụ EZYPLATFORM_HOME) đã khai báo đúng, trỏ đến thư mục chứa cli.sh/cli.bat, sau đó khởi động lại IntelliJ hoàn toàn (không chỉ đóng project) để IDE đọc lại biến môi trường mới.
Project plugin của tôi báo lỗi build khi import vào IntelliJ, nghi ngờ do không nhận được biến môi trường {EZYPLATFORM_HOME hoặc EZYPLATFORM_SDK}.
Hãy kiểm tra giúp tôi biến này đã khai báo đúng ở cấp hệ điều hành chưa, rồi hướng dẫn tôi khởi động lại IntelliJ đúng cách để nó nhận biến mới.
Bước 2: Khai báo Path Variables trong IntelliJ
Nếu vẫn lỗi, vào Settings → Appearance & Behavior → Path Variables, thêm thủ công biến (ví dụ EZYPLATFORM_HOME) trỏ đúng đường dẫn, rồi build lại project.
Bước kiểm tra biến môi trường cấp hệ điều hành chưa khắc phục được lỗi build. Hãy hướng dẫn tôi vào Settings → Path Variables của IntelliJ để khai báo thủ công biến {EZYPLATFORM_HOME} trỏ đến {đường dẫn thư mục EzyPlatform của bạn}, rồi build lại project để kiểm tra.
Bước 3: Thêm VM option cho Maven
Nếu vẫn chưa hết lỗi, vào Settings → Build, Execution, Deployment → Build Tools → Maven, thêm VM option -Denv.EZYPLATFORM_HOME=/đường/dẫn/của/bạn ở cả mục Importing và Runner — riêng mục Runner cần thêm cả biến môi trường tương ứng.
Hai bước trước vẫn chưa hết lỗi build. Hãy hướng dẫn tôi vào Settings → Build, Execution, Deployment → Build Tools → Maven của IntelliJ, thêm VM option -Denv.EZYPLATFORM_HOME=/đường/dẫn/của/bạn vào cả mục Importing và Runner, đồng thời thêm biến môi trường EZYPLATFORM_HOME ở mục Runner.
Bước 4: Cấu hình ở cấp Run Configuration
Nếu lỗi vẫn còn khi chạy thử (ví dụ chạy file StartupTest), thêm cùng VM option và biến môi trường vào chính Run Configuration của class khởi động đó.
Tôi vẫn gặp lỗi khi chạy file StartupTest trong IntelliJ. Hãy hướng dẫn tôi mở Run Configuration của file này, thêm VM option -Denv.EZYPLATFORM_HOME=/đường/dẫn/của/bạn và biến môi trường EZYPLATFORM_HOME tương ứng, rồi chạy lại để kiểm tra.
Xem hướng dẫn đầy đủ tại trang khắc phục lỗi PATH variable trong IntelliJ chính thức.
Sử dụng các phần trang (page fragment) để cài đặt theme
Mỗi khách hàng lại muốn một giao diện khác nhau cho website của mình — nếu mọi thay đổi nhỏ đều phải nhờ developer sửa code, chi phí và thời gian phát triển theme sẽ rất tốn kém. Page fragment giải quyết vấn đề này bằng cách "phân mảnh" giao diện thành từng phần (styles, scripts, header, footer, nội dung trang chủ...), để người dùng cuối tự chỉnh sửa ngay trong Admin mà không cần đụng vào code. Gồm 3 bước để khai báo:
Bước 1: Đăng ký danh sách page fragment
Trong module SDK dùng chung của plugin/theme, tạo một lớp cấu hình implement EzyBeanConfig để đăng ký tên các fragment, ví dụ nhóm common (styles, scripts, header, footer dùng chung mọi trang) hoặc home, blog_details (container HTML riêng cho từng loại trang).
Trong module {tên-sdk-module} của theme, hãy tạo một lớp cấu hình implement EzyBeanConfig để đăng ký các page fragment sau bằng pageFragmentManager.registerFragmentNames:
- Nhóm "common": styles, scripts, header, footer (dùng chung mọi trang).
- Nhóm "home": container (HTML trang chủ).
Giải thích cho tôi ý nghĩa của từng fragment trước khi tạo.
Bước 2: Mở rộng cấu hình cho module Web và Admin
Tại module web-plugin và admin-plugin, tạo lớp kế thừa từ config gốc ở Bước 1, đánh dấu @EzyConfigurationAfter để đảm bảo chạy sau cấu hình chung.
Hãy tạo giúp tôi lớp cấu hình kế thừa từ {tên-lớp-config-gốc-ở-bước-1} trong module web-plugin và admin-plugin, đánh dấu @EzyConfigurationAfter để nó chạy sau cấu hình gốc.
Bước 3: Hiển thị page fragment trong template
Cập nhật ViewDecorator để nạp fragment vào biến của view (ví dụ commonFragments), rồi trong page.html, render có điều kiện: nếu fragment đã được người dùng chỉnh sửa qua Admin thì lấy nội dung đó, nếu chưa thì giữ nguyên mặc định.
Hãy giúp tôi:
1. Cập nhật ViewDecorator để nạp các page fragment nhóm "common" vào biến commonFragments của view.
2. Sửa page.html: nếu commonFragments.get('styles') khác null và nội dung khác giá trị mặc định "styles" thì render nội dung đó bằng ezy:utext, ngược lại giữ nguyên style mặc định của theme.
Áp dụng tương tự cho scripts, header, footer.
Sau khi hoàn tất, người dùng có thể tự chỉnh sửa các phần trang này tại {đường-dẫn-admin-của-bạn}/ezyarticle/pages/fragments mà không cần nhờ developer. Xem chi tiết và ví dụ đầy đủ tại bài viết hướng dẫn page fragment chính thức.
Tạo giao diện cho theme
Nếu bạn (hoặc AI) đã có sẵn một giao diện HTML/CSS tĩnh — tải từ một template có sẵn, xuất từ Figma, hay do AI tạo riêng — bước tiếp theo là biến nó thành theme chạy được trên EzyPlatform bằng Thymeleaf, engine template mà EzyPlatform sử dụng. Gồm 4 bước.
Bước 1: Đưa file tĩnh vào đúng thư mục
Copy toàn bộ css, js, ảnh (thường nằm trong thư mục assets/ của giao diện gốc) vào src/main/resources/static/ của module theme, rồi sửa lại các đường dẫn tham chiếu trong HTML — ví dụ ./assets/css/style.css đổi thành /css/style.css — vì khi chạy, EzyPlatform tự ánh xạ thư mục static về gốc /.
Tôi có sẵn một giao diện HTML/CSS tĩnh tại thư mục {đường-dẫn-giao-diện-gốc}, gồm các file css/js/ảnh trong assets/.
Hãy giúp tôi copy toàn bộ các file tĩnh đó vào src/main/resources/static/ của module theme {tên-module-theme}, rồi sửa lại mọi đường dẫn tham chiếu trong HTML từ dạng ./assets/... sang /... cho đúng với cách EzyPlatform ánh xạ thư mục static.
Bước 2: Tách phần dùng chung thành fragment và layout
Header, footer thường lặp lại ở mọi trang nên tách thành fragment riêng (ví dụ fragments/header.html, fragments/footer.html) bằng thuộc tính th:fragment. Phần khung chung — đầu trang, cuối trang, và chỗ chèn nội dung riêng của từng trang — gom vào một file layout.html, dùng thư viện thymeleaf-layout-dialect với layout:fragment="content" đánh dấu vị trí nội dung sẽ được thay thế.
Từ file index.html tĩnh tôi vừa đưa vào theme, hãy giúp tôi: 1. Tách phần <header>...</header> thành fragments/header.html với th:fragment="header". 2. Tách phần <footer>...</footer> thành fragments/footer.html với th:fragment="footer". 3. Tạo layout.html dùng thymeleaf-layout-dialect, nhúng lại hai fragment trên bằng th:replace, và đánh dấu layout:fragment="content" ở chỗ nội dung chính sẽ được chèn vào.
Bước 3: Tạo template riêng cho từng trang
Mỗi trang cụ thể (trang chủ, trang chi tiết bài viết...) chỉ cần viết phần nội dung riêng (ví dụ phần <main>...</main> cũ) rồi khai báo layout:decorate để "mặc" lại layout.html, tránh phải lặp lại code header/footer ở mọi trang.
Hãy giúp tôi tạo home.html từ phần nội dung chính (thẻ main) trong index.html gốc, khai báo layout:decorate="~{layout}" để kế thừa layout.html vừa tạo ở Bước 2, và đặt nội dung vào đúng layout:fragment="content".
Bước 4: Chạy thử và kiểm tra giao diện
Chạy thử theme trong EzyPlatform (xem lại Bước 2 ở phần "Hướng dẫn khởi tạo dự án mô đun"), sau đó truy cập website để kiểm tra giao diện đã hiển thị đúng như bản gốc chưa — đặc biệt là css, js và ảnh có load đúng không.
Nếu giao diện gốc lấy từ một template miễn phí có sẵn, hãy giữ lại credit cho tác giả gốc (trong README hoặc footer theme) — đây vừa là phép lịch sự, vừa thường là điều kiện bắt buộc trong giấy phép sử dụng của template đó.
Xem hướng dẫn đầy đủ kèm ví dụ mã nguồn tại bài viết tạo giao diện cho theme blog đơn giản.
Ghép dữ liệu động cho theme
Giao diện dựng ở phần trước mới chỉ có dữ liệu giả (hardcode trong HTML). Bước tiếp theo là thay bằng dữ liệu thật lấy từ EzyPlatform — ví dụ danh sách bài viết blog đã xuất bản — kèm phân trang và định dạng ngày tháng dễ đọc. Gồm 4 bước.
Bước 1: Liên kết plugin EzyArticle vào theme
Theme cần truy vấn dữ liệu bài viết từ plugin EzyArticle, nên trước tiên phải liên kết (link) plugin đó vào project bằng ezy.sh — lệnh này tự thêm dependency SDK cần thiết vào pom.xml và cập nhật @ComponentsScan để quét đúng package của EzyArticle.
Hãy giúp tôi chạy lệnh ezy.sh link ezyarticle trong project theme {tên-module-theme} của tôi, rồi kiểm tra pom.xml và @ComponentsScan đã được cập nhật đúng để dùng được các service của EzyArticle chưa.
Bước 2: Tạo dữ liệu mẫu để kiểm thử
Trước khi đụng vào code, tạo sẵn vài bài viết mẫu qua trang Admin — tiêu đề, ảnh đại diện, nội dung, loại bài viết là "blog", trạng thái "đã xuất bản" — để có dữ liệu thật dùng kiểm tra kết quả ở các bước sau.
Bước 3: Lấy danh sách bài viết trong Controller
Sửa HomeController: tiêm (inject) WebPostControllerService, tạo bộ lọc DefaultPostFilter với postType = PostType.BLOG và postStatus = PostStatus.PUBLISHED, gọi getPostItemPagination() kèm ngôn ngữ, thứ tự sắp xếp và token phân trang, rồi đưa kết quả vào view qua biến pagination.
Hãy sửa HomeController của theme để lấy danh sách bài viết loại "blog" đã xuất bản, có phân trang, hiển thị ở trang chủ: 1. Tiêm WebPostControllerService vào controller. 2. Tạo DefaultPostFilter lọc postType = PostType.BLOG, postStatus = PostStatus.PUBLISHED. 3. Gọi getPostItemPagination() kèm ngôn ngữ hiện tại, thứ tự sắp xếp, nextPageToken/prevPageToken và limit (mặc định 12). 4. Đưa kết quả vào view qua biến "pagination".
Bước 4: Lặp và hiển thị danh sách trong template
Trong home.html, dùng th:each lặp qua pagination.items, mỗi phần tử render qua một fragment riêng (ví dụ fragments/blog-item) để tái sử dụng HTML của từng thẻ bài viết thay vì lặp code. Vì dữ liệu trả về là timestamp dạng Unix, thêm thư viện moment.js để chuyển thành ngày tháng dễ đọc ngay trong template.
Trong home.html, hãy giúp tôi: 1. Dùng th:each lặp qua pagination.items, mỗi bài viết render qua fragment fragments/blog-item (truyền biến blog vào fragment). 2. Tạo fragment blog-item hiển thị ảnh đại diện, tiêu đề, ngày đăng của bài viết. 3. Thêm thư viện moment.js và dùng nó để chuyển timestamp Unix của bài viết thành định dạng ngày tháng dễ đọc. 4. Thêm nút chuyển trang trước/sau dựa trên nextPageToken và prevPageToken của pagination.
Xem hướng dẫn đầy đủ kèm ví dụ mã nguồn tại bài viết ghép dữ liệu động cho theme blog đơn giản.
Lập trình đa ngôn ngữ cho theme
Nếu website của bạn phục vụ nhiều thị trường, theme cần hỗ trợ đa ngôn ngữ (i18n) ngay từ đầu, thay vì hardcode text trực tiếp trong HTML. EzyPlatform dùng cơ chế thay thế theo key: mỗi đoạn text trong giao diện ứng với một key, giá trị thật được lấy từ file properties riêng cho từng ngôn ngữ. Gồm 4 bước.
Bước 1: Thay text cứng bằng key
Trong template, thay các đoạn text tĩnh bằng cú pháp Thymeleaf [[#{key}]] — ví dụ <a>Liên hệ</a> đổi thành <a>[[#{contact}]]</a>. Đặt tên key theo quy ước chữ thường, các từ cách nhau bằng dấu gạch dưới, ví dụ contact, load_more, topic_title.
Trong các template của theme {tên-module-theme}, hãy giúp tôi thay toàn bộ text tĩnh (tiêu đề, nhãn nút, thông báo...) bằng cú pháp Thymeleaf [[#{key}]], đặt tên key theo quy ước chữ thường và gạch dưới.
Liệt kê cho tôi danh sách đầy đủ các key vừa tạo kèm text gốc tương ứng, để tôi dùng cho bước tạo file bản dịch tiếp theo.
Bước 2: Tạo file bản dịch cho từng ngôn ngữ
Trong src/main/resources/messages, tạo messages.properties (giá trị mặc định) và các file riêng theo ngôn ngữ như messages_vi.properties, messages_zh.properties, mỗi dòng theo dạng key=giá trị, ví dụ about_me=Về tôi.
Dựa trên danh sách key ở bước trước, hãy tạo giúp tôi các file trong src/main/resources/messages:
- messages.properties: giá trị mặc định bằng tiếng {ngôn ngữ mặc định của bạn}.
- messages_vi.properties: bản dịch tiếng Việt.
- messages_zh.properties: bản dịch tiếng Trung.
Mỗi file theo định dạng key=giá trị.
Bước 3: Cấu hình encoding trong IntelliJ để tránh lỗi font
Vì tiếng Việt và nhiều ngôn ngữ khác dùng ký tự có dấu, cần bật tùy chọn Transparent native-to-ascii conversion tại Settings → Editor → File Encodings của IntelliJ, để tránh chữ có dấu bị lưu sai thành ký tự lỗi trong file .properties.
Hãy hướng dẫn tôi bật "Transparent native-to-ascii conversion" trong Settings → Editor → File Encodings của IntelliJ, để các file messages_vi.properties, messages_zh.properties lưu đúng ký tự có dấu, không bị lỗi font.
Bước 4: Chạy thử và chuyển đổi ngôn ngữ
Khởi động lại project (chạy lại file StartupTest tương ứng), rồi truy cập website kèm tham số lang trên URL để kiểm tra từng ngôn ngữ, ví dụ http://localhost:8080/?lang=vi.
Hãy giúp tôi khởi động lại theme {tên-module-theme} (chạy lại StartupTest), rồi cho tôi biết cách truy cập trang chủ với các tham số ?lang=vi và ?lang=zh trên URL để kiểm tra bản dịch từng ngôn ngữ đã hiển thị đúng chưa.
Bài hướng dẫn gốc chỉ dừng ở việc chuyển ngôn ngữ qua tham số?lang=trên URL, chưa có sẵn nút chọn ngôn ngữ trên giao diện. Nếu muốn người dùng tự đổi ngôn ngữ bằng một menu, bạn cần tự thiết kế thêm — có thể nhờ AI tạo một dropdown chuyển hướng sang URL kèm đúng tham sốlangtương ứng.
Xem hướng dẫn đầy đủ tại bài viết lập trình đa ngôn ngữ với EzyPlatform.
Hướng dẫn cài đặt menu cho admin plugin
Mặc định, menu của plugin bạn tạo sẽ nằm trong nhóm "Phần mở rộng" (Extension) ở khu vực quản trị — nhưng bạn có thể tùy chỉnh vị trí và icon để menu nổi bật hơn, phù hợp mức độ quan trọng của plugin. Gồm 2 bước.
Bước 1: Hiểu 4 khu vực đặt menu
EzyPlatform chia menu Admin thành 4 khu vực:
- Top — dành cho plugin quan trọng, hiển thị ngay khu vực đầu của sidebar.
- System — dành cho các menu quản lý hệ thống.
- Admin — dành cho plugin phục vụ công việc quản trị.
- Extension — nhóm "Phần mở rộng", vị trí mặc định của mọi plugin mới tạo.
Bước 2: Khai báo và tùy chỉnh menu trong menu.properties
Menu được khai báo trong file src/main/resources/menu.properties của module admin-plugin, theo cú pháp {khu-vực}.{tên-plugin}=đường-dẫn cho mục cha và {khu-vực}.{tên-plugin}.{tên-mục-con}=đường-dẫn; icon cho mục con — icon dùng class Font Awesome hoặc Ionic, ví dụ:
extensions.personal=/ extensions.personal.dashboard=/dashboard; fas fa-tachometer-alt
Muốn chuyển menu lên nhóm Top và đổi icon, chỉ cần đổi tiền tố extensions thành top:
top.personal=/; fas fa-blog top.personal.dashboard=/dashboard; fas fa-tachometer-alt
Hãy mở file menu.properties trong module {tên-module-admin-plugin} của tôi, cho tôi xem cấu hình menu hiện tại.
Sau đó chuyển menu từ nhóm extensions sang nhóm {top / system / admin}, đổi icon thành {tên icon Font Awesome hoặc Ionic bạn muốn}, giữ nguyên các mục con và đường dẫn.
Cuối cùng hướng dẫn tôi export lại và chạy lại StartupTest để xem thay đổi có hiển thị đúng trên sidebar Admin không.
Hướng dẫn gốc chỉ dừng ở việc đổi vị trí và icon menu, chưa đề cập cách khai báo menu con nhiều cấp, nhãn đa ngôn ngữ hay phân quyền hiển thị theo role. Nếu plugin cần những tính năng menu nâng cao hơn, hãy nhờ AI đọc thêm mã nguồn các plugin có sẵn (ví dụ EzyArticle, EzySupport) để tham khảo cách họ khai báo, thay vì đoán cú pháp.
Xem hướng dẫn đầy đủ tại bài viết lập trình menu cho EzyPlatform admin plugin.
Tạo bảng mới
Khi plugin cần lưu trữ dữ liệu riêng ngoài các bảng có sẵn của EzyPlatform, bạn tự định nghĩa bảng mới ngay trong plugin. EzyPlatform dùng JPA/Hibernate với các annotation quen thuộc (@Entity, @Table, @Id, @Column) để ánh xạ entity Java với bảng MySQL. Gồm 3 bước.
Bước 1: Viết script SQL tạo bảng
Đặt file .sql trong thư mục src/main/resources/scripts của module admin-plugin — EzyPlatform tự quét và chạy một lần duy nhất khi khởi động. Luôn dùng CREATE TABLE IF NOT EXISTS để tránh lỗi khi chạy lại, và đặt tiền tố tên plugin trước tên bảng để tránh trùng với bảng của plugin khác.
Plugin {tên-plugin-của-bạn} cần lưu {mô tả dữ liệu cần lưu, ví dụ: số lượng từ trong mỗi bài viết}.
Hãy giúp tôi viết file SQL trong src/main/resources/scripts của module admin-plugin, dùng CREATE TABLE IF NOT EXISTS, đặt tên bảng có tiền tố {tên-plugin}_, khai báo đúng các cột, khóa chính và index cần thiết cho dữ liệu trên.
Bước 2: Khai báo Entity ánh xạ với bảng
Tạo class Java tương ứng, đánh dấu @Entity, @Table(name = ...), khai báo các cột bằng @Column và khóa chính bằng @Id, khớp đúng với bảng vừa tạo ở Bước 1.
Hãy tạo giúp tôi một class Entity Java ánh xạ với bảng vừa tạo ở Bước 1, dùng @Entity, @Table, @Id và @Column, đặt tên các trường tương ứng đúng với tên cột trong bảng.
Bước 3: Sinh Repository để CRUD dữ liệu
Thay vì viết tay, EzyPlatform cung cấp công cụ RepositoryClassesGenerator — chạy như một class Java bình thường trong IntelliJ — để tự sinh ra lớp repository tương ứng (ví dụ AdminPersonalPostWordCountRepository), có sẵn các phương thức CRUD như save(), findById(), delete().
Hãy giúp tôi tạo và chạy class RepositoryClassesGenerator để sinh repository cho Entity vừa tạo ở Bước 2, rồi viết cho tôi một đoạn code ví dụ dùng repository đó để save, findById và delete một bản ghi.
Sau này nếu cần thay đổi cấu trúc bảng đã tồn tại, tạo thêm một file riêng có chữ "alter" trong tên (ví dụ alter_add_column.sql) — EzyPlatform sẽ chạy các file này và bỏ qua lỗi nếu thay đổi đã được áp dụng trước đó, tránh việc phải sửa lại file tạo bảng gốc. Xem hướng dẫn đầy đủ tại bài viết tạo bảng trong EzyPlatform plugin.
Cài đặt batch (data appender)
Data Appender (Batch) là cơ chế xử lý dữ liệu hàng loạt chạy nền, phù hợp cho các nghiệp vụ tổng hợp dữ liệu nặng — ví dụ quét toàn bộ lịch sử bài viết để đếm số từ và lưu vào bảng thống kê riêng, thay vì tính toán ngay mỗi khi người dùng thao tác. Gồm 3 bước.
Bước 1: Chuẩn bị Repository đọc dữ liệu nguồn
Tạo repository đọc dữ liệu nguồn theo trang (page), ví dụ AdminPersonalPostHistoryRepository với phương thức findByIdGt(id) lấy các bản ghi có id lớn hơn id đã xử lý lần trước — đảm bảo duyệt tuần tự và không xử lý trùng.
Hãy tạo giúp tôi repository {tên-repository-nguồn}, có phương thức findByIdGt(id) lấy danh sách bản ghi {tên-entity-nguồn} có id lớn hơn id truyền vào, sắp xếp tăng dần theo id, giới hạn số bản ghi mỗi lần lấy.
Bước 2: Tạo lớp Data Appender
Tạo class kế thừa AdminDataAppender<Nguồn, Đích, KiểuPageToken> (ví dụ AdminDataAppender<PostHistory, PersonalPostWordCount, Long>), đánh dấu @EzySingleton để EzyPlatform tự nhận diện và quản lý vòng đời, rồi implement các phương thức chính: getValueList (lấy danh sách bản ghi cần xử lý), toDataRecord (chuyển entity nguồn sang entity đích), addDataRecord (lưu vào bảng đích), extractNewLastPageToken (lấy id bản ghi cuối để dùng cho lần chạy tiếp theo), và defaultStarted (trả về true để chạy ngay khi EzyPlatform khởi động).
Hãy tạo giúp tôi class {TênPluginPostWordCountDataAppender} kế thừa AdminDataAppender<PostHistory, {EntityĐích}, Long>, đánh dấu @EzySingleton, dùng repository {tên-repository-nguồn} ở Bước 1 và repository của {EntityĐích} ở phần Tạo bảng mới.
Implement getValueList, toDataRecord (đếm số từ trong nội dung bài viết), addDataRecord, extractNewLastPageToken (lấy id bản ghi cuối cùng) và defaultStarted trả về true.
Bước 3: Quản lý Data Appender qua Admin
Sau khi export và cài vào EzyPlatform, vào Cài đặt → Thông thường để thấy appender xuất hiện trong danh sách (ví dụ personal_post_word_count) với ba tùy chọn: Stop (dừng), Restart (chạy tiếp từ token hiện tại) và Reload (xóa token, quét lại toàn bộ từ đầu) — hữu ích khi cần chạy lại sau khi sửa logic tính toán.
Xem hướng dẫn đầy đủ tại bài viết tạo Batch Data Appender trong EzyPlatform plugin.
Tạo và xử lý sự kiện nội bộ
Để plugin dễ mở rộng, các phần bên trong nó không nên gọi trực tiếp lẫn nhau — thay vào đó, EzyPlatform cung cấp cơ chế sự kiện nội bộ (Internal Event): một nơi bắn sự kiện, nhiều nơi khác nhau có thể tự đăng ký lắng nghe và xử lý mà không cần biết đến nhau. Tiếp nối ví dụ Data Appender ở phần trên: thay vì tính luôn "thời gian đọc" ngay trong lúc đếm số từ, ta bắn một sự kiện sau khi đếm xong — một handler riêng sẽ lắng nghe và tự tính thời gian đọc. Gồm 3 bước.
Bước 1: Định nghĩa tên sự kiện
Khai báo một hằng số String đặt tên sự kiện, thường gom vào một class hằng số chung của plugin (ví dụ PersonalConstants), đặt tiền tố tên plugin để tránh trùng với sự kiện của plugin khác — ví dụ INTERNAL_EVENT_NAME_POST_WORD_COUNT = "personal_post_word_count".
Hãy thêm vào class {TênPlugin}Constants của tôi một hằng số String đặt tên sự kiện nội bộ cho việc {mô tả hành động cần thông báo, ví dụ: đã đếm xong số từ trong bài viết}, đặt tiền tố tên plugin để tránh trùng với plugin khác.
Bước 2: Bắn sự kiện tại nơi xảy ra hành động
Tại nơi cần thông báo (ví dụ ngay sau khi addDataRecord của Data Appender lưu xong bản ghi số từ), tiêm EventHandlerManager phù hợp (AdminEventHandlerManager cho phía admin, WebEventHandlerManager cho phía web) qua constructor hoặc @EzyAutoBind, rồi gọi handleEvent(tên-sự-kiện, dữ-liệu) kèm dữ liệu cần truyền, đóng gói bằng EzyMapBuilder.
Trong {tên-class-nơi-xảy-ra-hành-động}, hãy giúp tôi tiêm AdminEventHandlerManager, rồi ngay sau khi {mô tả hành động, ví dụ: lưu xong bản ghi số từ}, gọi eventHandlerManager.handleEvent với tên sự kiện đã định nghĩa ở Bước 1, dữ liệu đóng gói bằng EzyMapBuilder gồm các trường {liệt kê dữ liệu cần truyền, ví dụ: postId, wordCount}.
Bước 3: Đăng ký handler lắng nghe sự kiện
Tạo class implement EventHandler<D, R> (hoặc kế thừa AbstractEventHandler<D, R> nếu không cần trả kết quả), đánh dấu @EzySingleton để EzyPlatform tự nhận diện, override getEventName() trả về đúng tên sự kiện ở Bước 1 và processEventData()/doHandleEventData() để xử lý logic. Một sự kiện có thể có nhiều handler cùng lắng nghe, mỗi handler làm một việc độc lập, không ảnh hưởng lẫn nhau.
Hãy tạo giúp tôi class {TênPluginTimeToReadEventHandler} kế thừa AbstractEventHandler<Map<String, Object>, Void>, đánh dấu @EzySingleton.
Override getEventName() trả về hằng số sự kiện đã tạo ở Bước 1.
Override processEventData() để {mô tả logic xử lý, ví dụ: tính thời gian đọc dựa trên wordCount nhận được và lưu vào bảng tương ứng}.
Sự kiện nội bộ chỉ xử lý được trong cùng một thành phần (admin, web hoặc socket) và trên cùng một máy chủ — không thể dùng để giao tiếp giữa các thành phần khác nhau hoặc giữa nhiều máy chủ. Nếu cần điều đó, bạn sẽ cần một cơ chế khác (ví dụ message queue), nằm ngoài phạm vi của Internal Event.
Xem hướng dẫn đầy đủ tại bài viết tạo và xử lý sự kiện nội bộ trong EzyPlatform plugin.
📌 Tóm tắt chương
- Plugin là đơn vị mở rộng độc lập; Marketplace Item là plugin đã đóng gói để chia sẻ hoặc bán; Theme định hình toàn bộ diện mạo website.
- Môi trường phát triển cục bộ cần JDK 8, MySQL, Maven, EzyPlatform, EzyPlatform SDK và IntelliJ IDEA — mỗi bước đều có thể nhờ AI kiểm tra và cài đặt giúp.
- Dùng
ezy.shđể sinh khung project mới; mỗi loại module (sdk, admin-plugin, web-plugin, theme, socket-plugin, socket-app) là một sub-module Maven riêng với vai trò rõ ràng. - Xây theme gồm: dựng giao diện tĩnh bằng Thymeleaf, ghép dữ liệu động, lập trình đa ngôn ngữ, và cho phép người dùng tự chỉnh sửa qua page fragment.
- Xây plugin gồm: cấu hình menu Admin, tạo bảng dữ liệu riêng, xử lý dữ liệu hàng loạt bằng Data Appender, và giao tiếp nội bộ giữa các thành phần bằng Internal Event.
- Luôn kiểm thử kỹ với dữ liệu giả trước khi đưa module vào vận hành thật.
Young Monkeys - Founder