Hướng dẫn tích hợp để cho phép bình luận dữ liệu
Back to ezyarticleSau khi cài đặt EzyArticle, một màn hình quản trị có thể tham gia hệ thống bình luận dữ liệu bằng cách khai báo thực thể đang được hiển thị. Để có trải nghiệm đầy đủ hơn, plugin quản lý dữ liệu nên cung cấp thêm bộ tải thông tin thực thể để EzyArticle lấy tên, biểu tượng và đường dẫn của dữ liệu khi hiển thị danh sách thảo luận.
Quá trình tích hợp gồm hai phần chính:
- Khai báo thực thể hiện tại trên giao diện quản trị.
- Đăng ký bộ tải thông tin thực thể ở phía máy chủ.
Phần thứ nhất là điều kiện tối thiểu để tạo và xem bình luận. Phần thứ hai giúp các cuộc thảo luận hiển thị thân thiện và hoạt động tốt trong phạm vi toàn hệ thống.
Điều kiện trước khi tích hợp
Ứng dụng cần đáp ứng các điều kiện sau:
- EzyArticle đã được cài đặt và kích hoạt trong trang quản trị.
- Trang sử dụng layout quản trị có khu vực thanh công cụ bên phải.
- Người dùng đã đăng nhập.
- Người dùng có quyền sử dụng tính năng quản lý bình luận.
- Dữ liệu cần bình luận đã được lưu và có mã định danh dạng số dương.
EzyArticle tự nạp giao diện, stylesheet, JavaScript và các thông điệp đa ngôn ngữ cần thiết vào trang quản trị. Plugin tích hợp không cần tự xây dựng hộp thoại bình luận hay gọi các API thảo luận.
Chọn mã loại dữ liệu
Mỗi loại dữ liệu cần có một mã nhận diện ổn định, ví dụ:
inventory_products sales_orders support_tickets
Mã loại dữ liệu được sử dụng để:
- Liên kết cuộc thảo luận với dữ liệu.
- Lọc bình luận của dữ liệu hiện tại.
- Tìm bộ tải thông tin tương ứng.
- Khôi phục tên và đường dẫn của dữ liệu khi xem thảo luận toàn hệ thống.
Nên sử dụng một hằng số dùng chung giữa phía máy chủ và giao diện:
public final class EntityTypes { public static final String PRODUCT = "inventory_products"; private EntityTypes() {} }
Không nên dùng tên hiển thị như
Product hoặc Sản phẩm, vì tên đó có thể thay đổi theo ngôn ngữ. Mã loại dữ liệu phải ổn định trong toàn bộ vòng đời ứng dụng.Khai báo dữ liệu hiện tại trên giao diện
EzyArticle đọc dữ liệu hiện tại từ biến JavaScript:
ezyadmin.taggableEntity
Tên biến này được dùng chung cho các tính năng làm việc với thực thể trên thanh công cụ quản trị. Để cho phép bình luận một sản phẩm, trang chi tiết hoặc trang chỉnh sửa có thể khai báo:
<script> ezyadmin.taggableEntity = { id: product.id, code: product.code, displayName: product.name, type: 'inventory_products', url: '/inventory/products/' + product.id + '/edit' }; </script>
Trong template được render phía máy chủ, dữ liệu thực tế có thể được đưa vào như sau:
<script th:inline="javascript"> ezyadmin.taggableEntity = { id: /*[[]]*/ 0, code: /*[[]]*/ '', displayName: /*[[]]*/ '', type: 'inventory_products', url: /*[[@{/inventory/products/{id}/edit(id=)}]]*/ '' }; </script>
Các thuộc tính có ý nghĩa như sau:
| Thuộc tính | Bắt buộc | Ý nghĩa |
|---|---|---|
id | Có | Mã định danh dạng số dương của dữ liệu |
type | Có | Mã loại dữ liệu, phải khớp với phía máy chủ |
displayName | Nên có | Tên thân thiện hiển thị trên giao diện |
code | Không | Mã nghiệp vụ như SKU hoặc mã đơn hàng |
url | Nên có | Đường dẫn mở trang chi tiết hoặc chỉnh sửa |
EzyArticle cũng chấp nhận
entityType thay cho type, và có thể đọc đường dẫn từ một trong các thuộc tính url, link, uri hoặc href. Tuy nhiên, nên thống nhất sử dụng type và url để mã tích hợp dễ đọc.Sau khi biến này được gán, công cụ bình luận sẽ tự động:
- Lấy số cuộc thảo luận đang mở của dữ liệu.
- Hiển thị số lượng trên huy hiệu.
- Tải danh sách thảo luận khi người dùng mở công cụ.
- Cho phép tạo chủ đề mới.
- Gửi
typevàidkhi lưu chủ đề. - Hiển thị tên và liên kết xem dữ liệu.
Chỉ khai báo dữ liệu đã tồn tại
Không nên bật bình luận trên màn hình tạo mới khi dữ liệu chưa được lưu. Khi chưa có mã định danh, hãy đặt thực thể hiện tại thành
null:ezyadmin.taggableEntity = null;
Sau khi thao tác lưu thành công, ứng dụng có thể chuyển sang trang chỉnh sửa của bản ghi vừa tạo. Tại đó,
taggableEntity được khai báo bằng mã định danh chính thức.Không nên dùng mã tạm, số âm hoặc giá trị
0. Máy chủ chỉ chấp nhận mã định danh lớn hơn 0.Tích hợp trên trang danh sách
Trang danh sách không có một thực thể cố định. Trong trường hợp này, ứng dụng có thể cập nhật dữ liệu hiện tại khi người dùng chọn một dòng:
function setCurrentProduct(product) { ezyadmin.taggableEntity = { id: product.id, code: product.code, displayName: product.name, type: 'inventory_products', url: '/inventory/products/' + product.id + '/edit' }; }
Ví dụ với checkbox:
$('.product-checkbox').on('change', function() { var productId = Number(this.value); if (this.checked) { setCurrentProduct(productById[productId]); return; } var checkedIds = $('.product-checkbox:checked') .map(function() { return Number(this.value); }) .get(); if (!checkedIds.length) { ezyadmin.taggableEntity = null; return; } setCurrentProduct( productById[checkedIds[checkedIds.length - 1]] ); });
Nếu có nhiều dòng được chọn, ứng dụng cần xác định rõ thực thể nào là thực thể hiện tại. Một cách đơn giản là chọn dòng được đánh dấu gần nhất.
Việc thay đổi
taggableEntity không tự mở hộp thoại. Khi người dùng bấm biểu tượng bình luận, EzyArticle sẽ đọc giá trị mới nhất và tải cuộc thảo luận của thực thể tương ứng.Đăng ký bộ tải thông tin thực thể
Khai báo phía trình duyệt đủ để bình luận trên trang hiện tại. Tuy nhiên, khi người dùng xem các phạm vi “Toàn hệ thống”, “Do tôi tạo” hoặc “Nhắc đến tôi”, EzyArticle cần dựng lại thông tin của nhiều loại dữ liệu khác nhau.
Plugin nên triển khai
CommonEntityFetcher để cung cấp thông tin đó:import org.youngmonkeys.ezyplatform.fetcher.CommonEntityFetcher; import org.youngmonkeys.ezyplatform.model.CommonEntityModel; import java.util.Collection; import java.util.Map; public class ProductEntityFetcher implements CommonEntityFetcher { private final ProductService productService; public ProductEntityFetcher(ProductService productService) { this.productService = productService; } @Override public String getEntityType() { return EntityTypes.PRODUCT; } @Override public CommonEntityModel getEntityById(long entityId) { ProductModel product = productService.getProductById(entityId); if (product == null) { return CommonEntityModel .defaultEntity(entityId, getEntityType()); } return toEntityModel(product); } @Override public Map<Long, CommonEntityModel> getEntityMapByIds( Collection<Long> entityIds ) { return productService .getProductMapByIds(entityIds) .entrySet() .stream() .collect( java.util.stream.Collectors.toMap( Map.Entry::getKey, entry -> toEntityModel(entry.getValue()) ) ); } private CommonEntityModel toEntityModel(ProductModel product) { return CommonEntityModel.builder() .id(product.getId()) .code(product.getCode()) .name(getEntityType()) .displayName(product.getName()) .url( "/inventory/products/" + product.getId() + "/edit" ) .icon("fas fa-box") .build(); } }
Fetcher cần được đăng ký thành singleton trong container của plugin quản trị, chẳng hạn:
@EzySingleton public class AdminProductEntityFetcher extends ProductEntityFetcher { public AdminProductEntityFetcher(ProductService productService) { super(productService); } }
Khi khởi tạo, nền tảng tìm tất cả singleton triển khai
CommonEntityFetcher và lập chỉ mục theo loại dữ liệu. Vì vậy, giá trị trả về từ getEntityType() phải trùng hoàn toàn với type đã khai báo trên trình duyệt.Thông tin mà fetcher nên trả về
CommonEntityModel có thể chứa các trường:
CommonEntityModel.builder()
.id(entityId)
.code(entityCode)
.name(entityType)
.displayName(displayName)
.url(detailsUrl)
.icon(iconClass)
.properties(additionalProperties)
.build();
Trong đó:
-
idlà mã định danh của dữ liệu. -
codelà mã nghiệp vụ dễ nhận biết. -
namethường được dùng để chứa loại dữ liệu. -
displayNamelà tên hiển thị cho người dùng. -
urllà đường dẫn đến trang quản trị của dữ liệu. -
iconlà lớp CSS của biểu tượng. -
propertieschứa thông tin mở rộng nếu các tính năng khác cần sử dụng.
Đối với bình luận dữ liệu,
displayName và url là hai thông tin quan trọng nhất. Nếu không có displayName, giao diện phải quay về cách hiển thị kỹ thuật dựa trên loại và mã định danh.
Tối ưu phương thức lấy dữ liệu theo lô
Khi tải danh sách thảo luận, EzyArticle nhóm các mã định danh theo loại dữ liệu rồi gọi:
getEntityMapByIds(Collection<Long> entityIds)
Vì vậy, không nên triển khai phương thức này bằng cách truy vấn cơ sở dữ liệu riêng cho từng mã:
// Không nên: có thể tạo ra N truy vấn.
for (long id : entityIds) {
productService.getProductById(id);
}
Thay vào đó, nên sử dụng một truy vấn theo tập mã định danh:
Map<Long, ProductModel> productById =
productService.getProductMapByIds(entityIds);
Cách này tránh vấn đề N+1 khi một trang có nhiều cuộc thảo luận.
Bản đồ kết quả không bắt buộc phải chứa dữ liệu cho mọi mã được yêu cầu. Nếu một thực thể đã bị xóa, có thể bỏ qua nó hoặc trả về mô hình mặc định:
CommonEntityModel.defaultEntity(
entityId,
EntityTypes.PRODUCT
);
Cuộc thảo luận vẫn được giữ lại, nhưng giao diện có thể không còn tên và đường dẫn đầy đủ của dữ liệu cũ.
Trường hợp một fetcher hỗ trợ nhiều loại dữ liệu
Nếu các loại dữ liệu có cùng cách tải, fetcher có thể khai báo nhiều loại:
@Override public String[] getEntityTypes() { return new String[] { "inventory_products", "inventory_product_variants" }; }
Khi đó, phương thức tải cần phân biệt được loại dữ liệu theo thiết kế của ứng dụng. Nếu hai loại có repository, đường dẫn hoặc mô hình khác nhau đáng kể, nên tạo fetcher riêng để mã nguồn rõ ràng hơn.
Khi có nhiều fetcher cùng đăng ký một loại, nền tảng sử dụng mức ưu tiên để chọn:
@Override public int getPriority() { return 10; }
Chỉ nên thay đổi mức ưu tiên khi plugin thực sự cần ghi đè cách cung cấp thông tin của một loại dữ liệu đã tồn tại.
Kiểm tra quyền truy cập
Các API bình luận yêu cầu phiên đăng nhập quản trị và quyền sử dụng chức năng quản lý bình luận. Tuy nhiên, plugin sở hữu dữ liệu vẫn cần quan tâm đến quyền truy cập đối tượng.
Ví dụ, nếu một quản trị viên chỉ được xem sản phẩm thuộc một kho cụ thể, plugin không nên cung cấp đường dẫn hoặc thông tin nhạy cảm của sản phẩm ngoài phạm vi đó.
Cần kiểm tra quyền ở ít nhất hai nơi:
- Controller của trang chi tiết hoặc chỉnh sửa dữ liệu.
- Service hoặc fetcher trả thông tin thực thể cho hệ thống.
Việc một người dùng biết
entityType và entityId không nên đồng nghĩa với quyền xem toàn bộ dữ liệu tương ứng.
Luồng hoạt động sau khi tích hợp
Khi người dùng mở một trang dữ liệu, luồng xử lý diễn ra như sau:
- Trang khai báo
ezyadmin.taggableEntity. - Giao diện EzyArticle đọc loại và mã định danh.
- Hệ thống lấy số cuộc thảo luận đang mở.
- Người dùng mở công cụ bình luận.
- Các chủ đề gắn với dữ liệu được tải theo trang.
- Khi tạo chủ đề, trình duyệt gửi loại dữ liệu, mã định danh, nội dung và danh sách người được nhắc đến.
- EzyArticle lưu chủ đề và liên kết với dữ liệu.
- Khi hiển thị danh sách toàn hệ thống, fetcher cung cấp tên, biểu tượng và đường dẫn cho từng thực thể.
Plugin tích hợp không cần tự quản lý trạng thái mở, đã giải quyết, danh sách phản hồi hay thông báo nhắc tên. Những phần này do EzyArticle xử lý.
Những lỗi tích hợp thường gặp
Biểu tượng bình luận xuất hiện nhưng không tạo được chủ đề
Kiểm tra
taggableEntity.id. Giá trị phải là số dương và dữ liệu phải được lưu trước.Không thấy bình luận đã tạo trên dữ liệu hiện tại
Kiểm tra
type có được sử dụng nhất quán hay không. Chỉ một khác biệt nhỏ như inventory_product và inventory_products cũng tạo thành hai mục tiêu khác nhau.Danh sách toàn hệ thống chỉ hiện loại và mã dữ liệu
Fetcher chưa được đăng ký, trả về thiếu
displayName, hoặc getEntityType() không khớp với mã phía giao diện.Liên kết mở dữ liệu không hoạt động
Kiểm tra trường
url trong taggableEntity và trong CommonEntityModel. Với loại dữ liệu tùy chỉnh, nên cung cấp URL rõ ràng thay vì phụ thuộc vào đường dẫn mặc định.Danh sách thảo luận tải chậm
Kiểm tra
getEntityMapByIds. Phương thức này nên sử dụng truy vấn theo lô, không nên tải từng bản ghi trong vòng lặp.Người dùng không gọi được API bình luận
Kiểm tra trạng thái đăng nhập và quyền quản lý bình luận của tài khoản quản trị.
Danh sách kiểm tra tích hợp
Trước khi hoàn tất, hãy xác nhận:
- EzyArticle đang hoạt động trong module quản trị.
- Trang sử dụng layout có thanh công cụ bên phải.
- Mỗi loại dữ liệu có một mã ổn định.
-
taggableEntity.idlà số dương. -
taggableEntity.typekhớp vớigetEntityType(). - Màn hình tạo mới đặt
taggableEntitythànhnull. - Trang danh sách cập nhật thực thể khi lựa chọn thay đổi.
- Fetcher được đăng ký thành singleton.
- Fetcher trả về
displayNamevàurl. - Phương thức lấy nhiều thực thể sử dụng truy vấn theo lô.
- Controller và service vẫn thực thi chính sách phân quyền của plugin.
Kết luận
Để một loại dữ liệu có thể được bình luận trong EzyArticle, tích hợp tối thiểu chỉ cần khai báo mã định danh và loại dữ liệu trên giao diện. Tuy nhiên, việc bổ sung
CommonEntityFetcher là bước nên làm để tên, biểu tượng và liên kết của dữ liệu được khôi phục chính xác trong mọi phạm vi thảo luận.Mô hình tích hợp này không giới hạn ở bài viết hay chuyên mục. Sản phẩm, đơn hàng, yêu cầu hỗ trợ hoặc bất kỳ dữ liệu quản trị nào có mã định danh ổn định đều có thể tham gia cùng một hệ thống trao đổi nội bộ.