Quản lý các mẫu (template) ví dụ mẫu thư (email), mẫu thông báo, mẫu tập tin hay bất kỳ mẫu nào là một trong những nghiệp vụ hết sức cơ bản và gần như phần mềm nào cũng có, chính vì vậy mà EzyPlatform đã đóng gói lại phần này để hỗ trợ các nhà phát triển không cần phải khởi tạo lại nữa.

Thiết kế cơ sở dữ liệu

Cũng không có gì phức tạp, EzyPlatform sử dụng một bảng có tên ezy_content_templates để quản lý toàn bộ các mẫu. Bảng này có mã nguồn SQL như sau:

CREATE TABLE IF NOT EXISTS `ezy_content_templates` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `template_type` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL,
  `template_name` varchar(300) COLLATE utf8mb4_unicode_520_ci NOT NULL,
  `title_template` varchar(1200) COLLATE utf8mb4_unicode_520_ci NOT NULL,
  `content_template` varchar(12000) COLLATE utf8mb4_unicode_520_ci NOT NULL,
  `creator_id` bigint unsigned NOT NULL,
  `status` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'DRAFT',
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `key_template_type_name` (`template_type`, `template_name`),
  INDEX `index_template_type` (`template_type`),
  INDEX `index_template_name` (`template_name`),
  INDEX `index_creator_id` (`creator_id`),
  INDEX `index_status` (`status`),
  INDEX `index_created_at` (`created_at`),
  INDEX `index_updated_at` (`updated_at`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

Ý nghĩa của một số trường đó là:

  1. template_type: Loại mẫu, ví dụ MAIL, LETTER, NOTIFICATION.
  2. template_name: Là tên của mẫu, ví dụ: new_orderslà mẫu để thông báo có đơn hàng mới qua email cho quản trị viên.
  3. title_template: Là tiêu đề của mẫu.
  4. content_template: Là nội dung của mẫu.
  5. creator_id: Là id của admin đã tạo ra mẫu.
  6. status: Trạng thái của mẫu có thể là DRAFT (đang nháp) hay COMPLETED (đã hoàn thành, có thể đưa vào sử dụng).

Bạn cần lưu ý rằng trong một loại mẫu sẽ không chấp nhận tên trùng nhau, nhưng trong nhiều loại mẫu thì có thể.

Thiết kế lớp

Từ bảng ezy_content_templates EzyPlatform sẽ thiết kế các lớp như sau:

EzyPlatform qua ly cac mau the nao.png
  1. ContentTemplate: Là lớp entity ánh xạ với bảng ezy_content_templates.
  2. ContentTemplateRepository: Là giao diện repository để tương tác với cơ sở dữ liệu.
  3. ContentTemplateService: Là giao diện cơ sở chứa một số hàm cơ bản để lấy dữ liệu mẫu.
  4. DefaultContentTemplateService: Là hàm cài đặt ContentTemplateService và sử dụng ContentTemplateRepository lưu hoặc lấy dữ liệu mẫu từ cơ sở dữ liệu.
  5. AdminContentTemplateService: Là lớp bạn sẽ dùng khi làm việc với admin plugin.
  6. WebContentTemplateService: Là lớp bạn sẽ dùng khi làm việc với web plugin.

Sử dụng

Trong dự án thực tế thường bạn sẽ trải qua các bước sau để sử dụng các mẫu:

  1. Là định nghĩa sẵn mẫu.
  2. Cài đặt logic sử dụng mẫu.
  3. Cho phép người sử dụng tuỳ chỉnh mẫu nếu họ muốn.

Định nghĩa sẵn mẫu

Giả sử bạn đang cần một mẫu để gửi thông báo cho admin khi có đơn hàng mới, bạn có thể tạo một tập tin có tên mail_template_new_orders_for_managers.html trong thứ mục admin plugin/resources của bạn với nội dung như sau:

<!DOCTYPE html>
<html>
<body>
<p>Hi managers,</p>
<p>There are ${newOrders} new orders and ${waitingForConfirmationOrders} orders need to confirm.<p>
<p>Please access <a href="${adminUrl}/ecommerce/orders">here</a> to review.<p>
<p>Thank you!</p>
</body>
</html>

Sau đó bạn có thể tạo ra một lớp config để đọc và lưu mẫu này vào cơ sở dữ liệu như sau:

@AllArgsConstructor
@EzyConfigurationAfter
public class AdminEcommerceMailConfig implements EzyBeanConfig {

    private final EzyInputStreamLoader inputStreamLoader;
    private final AdminContentTemplateService contentTemplateService;

    @Override
    public void config() {
        addMailTemplates();
    }

    @SuppressWarnings("MethodLength")
    private void addMailTemplates() {
        contentTemplateService.addTemplateIfAbsent(
            ContentTemplateType.MAIL.toString(),
            TEMPLATE_NEW_ORDERS_FOR_MANAGERS,
            () -> AddContentTemplateModel.builder()
                .templateName(TEMPLATE_NEW_ORDERS_FOR_MANAGERS)
                .titleTemplate("${orders} new orders need to review")
                .contentTemplate(
                    readMailTemplateContentOrDefault(
                        TEMPLATE_NEW_ORDERS_FOR_MANAGERS,
                        "${orders} new orders need to review"
                    )
                )
                .status(ContentTemplateStatus.COMPLETED.toString())
                .build()
        );
    }

    private String readMailTemplateContentOrDefault(
        String templateName,
        String defaultContent
    ) {
        try {
            String templateFullName = getMailTemplateFullName(
                templateName
            );
            return EzyInputStreams.toStringUtf8(
                inputStreamLoader.load(
                    "ecommerce/" + templateFullName + ".html"
                )
            );
        } catch (IOException e) {
            return defaultContent;
        }
    }
}

Cài đặt logic sử dụng mẫu

Giải sử bạn có một đối tượng lập lịch để định kỳ kiểm tra có đơn hàng mới hay không sau đó gửi mail, bạn có thể cài đặt mã nguồn như sau:

@EzySingleton
public class AdminNewOrdersMailAppender extends
    AdminDataAppender<Order, Order, Void> {

    private final AdminMailServiceProxy mailServiceProxy;
    private final AdminEventHandlerManager eventHandlerManager;
    private final AdminEcommerceSettingService ecommerceSettingService;
    private final AdminSettingService settingService;
    private final AdminOrderRepository orderRepository;
    private final AtomicLong lastSendMailTime = new AtomicLong(
        System.currentTimeMillis()
    );

    public AdminNewOrdersMailAppender(
        AdminMailServiceProxy mailServiceProxy,
        AdminEventHandlerManager eventHandlerManager,
        ObjectMapper objectMapper,
        AdminEcommerceSettingService ecommerceSettingService,
        AdminSettingService settingService,
        AdminOrderRepository orderRepository
    ) {
        super(objectMapper, settingService);
        this.mailServiceProxy = mailServiceProxy;
        this.eventHandlerManager = eventHandlerManager;
        this.ecommerceSettingService = ecommerceSettingService;
        this.settingService = settingService;
        this.orderRepository = orderRepository;
    }

    @Override
    protected void doAppend() {
        int period = ecommerceSettingService.getNotifyNewOrdersPeriod();
        long sendMailTime = lastSendMailTime.get() + period * 60 * 1000L;
        long now = System.currentTimeMillis();
        if (sendMailTime > now) {
            return;
        }
        long lastOrderId = ecommerceSettingService
            .getLastNotifiedNewOrderId();
        if (lastOrderId == 0) {
            Order lastOrder = orderRepository.findLast();
            lastOrderId = lastOrder != null ? lastOrder.getId() : 0L;
        }
        List<Order> orders = orderRepository.findByIdGtOrStatus(
            lastOrderId,
            OrderStatus.WAITING_FOR_CONFIRMATION.toString(),
            Next.limit(LIMIT_300_RECORDS)
        );
        if (orders.isEmpty()) {
            return;
        }
        long waitingForConfirmationOrders = orders
            .stream()
            .filter(it -> it
                .getStatus()
                .equals(OrderStatus.WAITING_FOR_CONFIRMATION.toString())
            )
            .count();
        int ordersSize = orders.size();
        long newOrders = ordersSize - waitingForConfirmationOrders;
        Map<String, Object> parameters = EzyMapBuilder.mapBuilder()
            .put("orders", ordersSize)
            .put("newOrders", newOrders)
            .put("waitingForConfirmationOrders", waitingForConfirmationOrders)
            .put("adminUrl", settingService.getAdminUrl())
            .toMap();
        Collection<String> orderManagementEmails = ecommerceSettingService
            .getOrderManagementEmails();
        if (orderManagementEmails.size() > 0) {
            mailServiceProxy.send(
                AdminMailModel.builder()
                    .templateName(TEMPLATE_NEW_ORDERS_FOR_MANAGERS)
                    .to(orderManagementEmails)
                    .parameters(parameters)
                    .build()
            );
        }
        eventHandlerManager.handleEvent(
            INTERNAL_EVENT_NAME_NEW_ORDERS_APPEND,
            parameters
        );
        lastSendMailTime.set(now);
        ecommerceSettingService.setLastNotifiedNewOrderId(now);
        long newLastOrderId = last(orders).getId();
        if (newLastOrderId > lastOrderId) {
            ecommerceSettingService.setLastNotifiedNewOrderId(
                newLastOrderId
            );
        }
    }

    @Override
    protected List<Order> getValueList(Void unused) {
        throw new UnsupportedOperationException("unused");
    }

    @Override
    protected Void extractNewLastPageToken(List<Order> list, Void unused) {
        throw new UnsupportedOperationException("unused");
    }

    @Override
    protected String getAppenderNamePrefix() {
        return TEMPLATE_NEW_ORDERS_FOR_MANAGERS;
    }

    @Override
    protected Void defaultPageToken() {
        throw new UnsupportedOperationException("unused");
    }

    @Override
    protected Class<Void> pageTokenType() {
        return Void.class;
    }
}

Cho phép người dùng tuỳ chỉnh

Bạn có thể cài đặt EzySupport plugin, nó sẽ cung cấp giao diện cho phép quản trị viên quản lý và thay đổi các mẫu theo ý muốn.

Screenshot 2024-12-10 at 17.15.18.png

Tổng kết

Quản lý mẫu là một trong những tính năng cơ bản nhưng cực kỳ quan trọng đối với bất kỳ dự án phần mềm nào. Với EzyPlatform các nhà phát triển sẽ không cần phải tạo lại tính năng này nữa mà sẽ chỉ cần tập trung vào sử dụng với nghiệp vụ tuỳ ý của mình.