EzyOA cho phép plugin khác gửi tin nhắn bằng cách lấy trực tiếp OAService từ service manager của runtime, sau đó gọi API gửi tin trên service đó. Cách này không sử dụng event handler và cho phép plugin nhận trực tiếp kết quả hoặc exception từ dịch vụ OA.

Thành phần cần sử dụng

EzyOA cung cấp hai service manager tương ứng với hai runtime:
  • Admin plugin: AdminOAServiceManager
  • Web plugin: WebOAServiceManager
Cả hai đều kế thừa OAServiceManager và cung cấp phương thức:
OAService getOAServiceByCode(String serviceCode);
Đối tượng trả về triển khai interface:
org.youngmonkeys.ezyoa.service.OAService
OAService là điểm vào chính để gửi tin nhắn, tin nhắn theo mẫu, thông báo và media.

Khai báo dependency

Plugin gọi cần sử dụng SDK và implementation tương ứng với runtime, bạn có thể tham khảo Hướng dẫn liên kết plugin để liên kết EzyOA với dự án của bạn hoặc bạn có thể thực hiện thủ công như dưới đây:
Ví dụ cho admin plugin:
<dependency>
    <groupId>org.youngmonkeys</groupId>
    <artifactId>ezyoa-sdk</artifactId>
    <version></version>
    <scope>system</scope>
    <systemPath>
        /admin/plugins/ezyoa/lib/ezyoa-sdk-.jar
    </systemPath>
</dependency>

<dependency>
    <groupId>org.youngmonkeys</groupId>
    <artifactId>ezyoa-admin-plugin</artifactId>
    <version></version>
    <scope>system</scope>
    <systemPath>
        /admin/plugins/ezyoa/lib/ezyoa-admin-plugin-.jar
    </systemPath>
</dependency>
Với web plugin, thay đường dẫn và artifact implementation bằng:
<dependency>
    <groupId>org.youngmonkeys</groupId>
    <artifactId>ezyoa-sdk</artifactId>
    <version></version>
    <scope>system</scope>
    <systemPath>
        /web/plugins/ezyoa/lib/ezyoa-sdk-.jar
    </systemPath>
</dependency>

<dependency>
    <groupId>org.youngmonkeys</groupId>
    <artifactId>ezyoa-web-plugin</artifactId>
    <version></version>
    <scope>system</scope>
    <systemPath>
        /web/plugins/ezyoa/lib/ezyoa-web-plugin-.jar
    </systemPath>
</dependency>
Phiên bản dependency phải tương thích với phiên bản EzyOA đang được cài trên EzyPlatform.

Gửi tin nhắn từ admin plugin

Inject AdminOAServiceManager vào service của plugin:
import lombok.AllArgsConstructor;
import org.youngmonkeys.ezyoa.admin.manager.AdminOAServiceManager;
import org.youngmonkeys.ezyoa.service.OAService;

@AllArgsConstructor
public class OrderOaNotificationService {

    private final AdminOAServiceManager oaServiceManager;

    public Object sendOrderCreatedMessage(
        String serviceCode,
        String recipientId,
        String orderCode
    ) throws Exception {
        OAService oaService = getOAServiceOrThrow(serviceCode);

        return oaService.sendMessage(
            recipientId,
            "Đơn hàng " + orderCode + " đã được tạo."
        );
    }

    private OAService getOAServiceOrThrow(String serviceCode) {
        OAService oaService =
            oaServiceManager.getOAServiceByCode(serviceCode);

        if (oaService == null) {
            throw new IllegalArgumentException(
                "Không tìm thấy dịch vụ OA: " + serviceCode
            );
        }
        return oaService;
    }
}

Gửi tin nhắn từ web plugin

Ở web runtime, cách sử dụng giống admin plugin, nhưng inject WebOAServiceManager:
import lombok.AllArgsConstructor;
import org.youngmonkeys.ezyoa.service.OAService;
import org.youngmonkeys.ezyoa.web.manager.WebOAServiceManager;

@AllArgsConstructor
public class UserOaMessageService {

    private final WebOAServiceManager oaServiceManager;

    public Object sendMessage(
        String serviceCode,
        String recipientId,
        String message
    ) throws Exception {
        OAService oaService =
            oaServiceManager.getOAServiceByCode(serviceCode);

        if (oaService == null) {
            throw new IllegalArgumentException(
                "Không tìm thấy dịch vụ OA: " + serviceCode
            );
        }

        return oaService.sendMessage(recipientId, message);
    }
}

Ý nghĩa của service code

serviceCode xác định cấu hình OA sẽ được sử dụng, ví dụ:
OAService oaService =
    oaServiceManager.getOAServiceByCode("zalo");
Service manager tìm kiếm mã dịch vụ không phân biệt chữ hoa, chữ thường đối với các service được đăng ký sẵn.
EzyOA cũng hỗ trợ các service được cấu hình dựa trên một handling service khác. Trong trường hợp đó, service manager có thể clone implementation gốc theo cấu hình của service code mới.
Nếu không tồn tại implementation hoặc cấu hình tương ứng, getOAServiceByCode trả về null.
Không nên tiếp tục gọi phương thức gửi khi kết quả trả về là null.

Gửi tin nhắn văn bản

Cách đơn giản nhất là gọi:
Object result = oaService.sendMessage(
    recipientId,
    message
);
Ví dụ:
Object result = oaService.sendMessage(
    "oa-user-123456",
    "Đơn hàng của bạn đang được giao."
);
Phương thức này mặc định:
  • Gửi qua OA API.
  • Không ghi nhận quản trị viên kích hoạt.
  • Không ghi nhận người dùng kích hoạt.
  • Trả về response gốc do implementation của dịch vụ OA cung cấp.
recipientId là ID người nhận trên nền tảng OA, không phải ID người dùng nội bộ của EzyPlatform.

Gửi tin và ghi nhận người thực hiện

Để ghi nhận người đã kích hoạt thao tác gửi, sử dụng overload đầy đủ:
Object result = oaService.sendMessage(
    byAdminId,
    byUserId,
    transportType,
    recipientId,
    message
);
Ví dụ gửi từ admin:
import static org.youngmonkeys.ezyoa.constant
    .OAMessageTransportType.API;

Object result = oaService.sendMessage(
    currentAdminId,
    0L,
    API.toString(),
    recipientId,
    "Đơn hàng đã được xác nhận."
);
Ví dụ gửi từ người dùng:
Object result = oaService.sendMessage(
    0L,
    currentUserId,
    API.toString(),
    recipientId,
    "Yêu cầu của bạn đã được tiếp nhận."
);
Trong đó:
  • byAdminId: ID quản trị viên kích hoạt việc gửi.
  • byUserId: ID người dùng kích hoạt việc gửi.
  • recipientId: ID người nhận trên dịch vụ OA.
  • transportType: phương thức vận chuyển tin nhắn.
Hai trường byAdminId và byUserId mô tả tác nhân gửi, không phải người nhận.

Chọn phương thức gửi

EzyOA định nghĩa hai transport type:
public enum OAMessageTransportType {
    API,
    EXTENSION
}
Gửi qua API:
oaService.sendMessage(
    byAdminId,
    byUserId,
    OAMessageTransportType.API.toString(),
    recipientId,
    message
);
Gửi qua extension:
oaService.sendMessage(
    byAdminId,
    byUserId,
    OAMessageTransportType.EXTENSION.toString(),
    recipientId,
    message
);
EXTENSION chỉ hoạt động khi dịch vụ OA tương ứng đã cung cấp implementation gửi qua extension và môi trường đã được cấu hình đầy đủ.
Overload rút gọn:
oaService.sendMessage(recipientId, message);
luôn sử dụng transport type API.

Gửi tin nhắn kèm media

Danh sách media được truyền vào overload thứ ba:
Object result = oaService.sendMessage(
    recipientId,
    message,
    medias
);
Ví dụ gửi ảnh:
import com.tvd12.ezyfox.util.EzyMapBuilder;

import java.util.Collections;
import java.util.List;
import java.util.Map;

List<Map<String, Object>> medias =
    Collections.singletonList(
        EzyMapBuilder.mapBuilder()
            .put("type", "image")
            .put("fileName", "payment-qr.png")
            .put("filePath", "images/payment-qr.png")
            .toMap()
    );

Object result = oaService.sendMessage(
    recipientId,
    "Mã QR thanh toán của bạn.",
    medias
);
Để gửi kèm thông tin tác nhân và transport type:
Object result = oaService.sendMessage(
    byAdminId,
    byUserId,
    OAMessageTransportType.API.toString(),
    recipientId,
    message,
    medias
);
Các thuộc tính media thực tế phụ thuộc vào implementation của từng dịch vụ OA. Plugin không nên giả định mọi nền tảng đều hỗ trợ cùng một loại media.

Gửi tin nhắn theo mẫu

Khác với sendMessage, phương thức sendTemplatedMessage nhận userId nội bộ của EzyPlatform thay vì recipientId.
oaService.sendTemplatedMessage(
    userId,
    templateType,
    templateName,
    parameters
);
Ví dụ:
import com.tvd12.ezyfox.util.EzyMapBuilder;

import java.util.Map;

Map<String, Object> parameters =
    EzyMapBuilder.mapBuilder()
        .put("customerName", customerName)
        .put("orderCode", orderCode)
        .put("totalAmount", totalAmount)
        .toMap();

oaService.sendTemplatedMessage(
    userId,
    "response_message",
    "order_created",
    parameters
);
EzyOA sẽ:
  1. Tìm người dùng EzyPlatform theo userId.
  2. Tìm tài khoản OA đã liên kết với người dùng đó.
  3. Lấy bộ gửi tương ứng với serviceCode, templateType và templateName.
  4. Kết hợp tham số được truyền vào với thông tin người dùng.
  5. Dựng nội dung từ mẫu.
  6. Gửi nội dung đến OA user.
Nếu không cần truyền tham số bổ sung:
oaService.sendTemplatedMessage(
    byAdminId,
    byUserId,
    userId,
    templateType,
    templateName
);
Phiên bản đầy đủ:
oaService.sendTemplatedMessage(
    byAdminId,
    byUserId,
    OAMessageTransportType.API.toString(),
    userId,
    templateType,
    templateName,
    parameters
);
Phương thức này trả về void. Khi gửi thất bại, exception được ném trực tiếp cho plugin gọi.

recipientId và userId

Hai loại ID không thể tùy ý thay thế cho nhau:
Phương thứcID người nhậnÝ nghĩa
sendMessageString recipientIdID người nhận do nền tảng OA cấp
sendTemplatedMessagelong userIdID người dùng nội bộ EzyPlatform
sendTemplatedNotification theo mẫu nội bộlong userIdID người dùng nội bộ EzyPlatform
sendTemplatedNotification theo mẫu OAString recipientIdID hoặc số điện thoại được OA hỗ trợ
Nếu plugin chỉ có userId nhưng muốn gửi nội dung văn bản tùy ý, plugin phải lấy OA user tương ứng trước khi gọi sendMessage. Không nên truyền String.valueOf(userId) làm recipientId.

Gửi đến một danh sách người nhận

Để gửi cùng một nội dung đến nhiều OA user đã biết:
import org.youngmonkeys.ezyoa.model.OASendMessageToUsersResultModel;
import org.youngmonkeys.ezyoa.model.SendOAMessageToAllUsersOptionsModel;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

Collection<String> failedRecipientIds = new ArrayList<>();

SendOAMessageToAllUsersOptionsModel options =
    SendOAMessageToAllUsersOptionsModel.builder()
        .sendToAccountsConnectedToUser(true)
        .build();

OASendMessageToUsersResultModel result =
    oaService.sendMessageToUsers(
        byAdminId,
        byUserId,
        OAMessageTransportType.API.toString(),
        recipientIds,
        message,
        Collections.emptyList(),
        options,
        failedRecipientIds
    );
Kết quả cung cấp:
long successCount = result.getSuccessCount();
long failureCount = result.getFailureCount();
Collection failedRecipientIds được dùng làm output để EzyOA bổ sung các ID gửi thất bại. Có thể truyền null nếu plugin không cần danh sách này.

Gửi đến tất cả người nhận

EzyOA cũng cung cấp phương thức gửi đến toàn bộ người nhận thuộc dịch vụ:
import org.youngmonkeys.ezyoa.model.OASendMessageToAllUsersResultModel;
import org.youngmonkeys.ezyoa.model.SendOAMessageToAllUsersOptionsModel;

SendOAMessageToAllUsersOptionsModel options =
    SendOAMessageToAllUsersOptionsModel.builder()
        .sendToAccountsConnectedToUser(true)
        .build();

OASendMessageToAllUsersResultModel result =
    oaService.sendMessageToAllUsers(
        message,
        options
    );
Kết quả gồm:
result.getTotalSend();
result.getSuccessCount();
result.getFailureCount();
Có thể gửi kèm media:
OASendMessageToAllUsersResultModel result =
    oaService.sendMessageToAllUsers(
        byAdminId,
        byUserId,
        OAMessageTransportType.API.toString(),
        message,
        medias,
        options
    );
Việc gửi hàng loạt phải tuân thủ hạn mức và chính sách của nền tảng OA. Plugin không nên gọi phương thức này trong request HTTP kéo dài nếu số lượng người nhận lớn.

Kết quả và exception

sendMessage trả về Object vì mỗi nền tảng OA có kiểu response khác nhau:
Object result = oaService.sendMessage(
    recipientId,
    message
);
Plugin chỉ nên ép kiểu khi biết chắc implementation của dịch vụ đang được sử dụng. Với code dùng chung cho nhiều OA, nên xem việc không có exception là kết quả thành công ở tầng gọi hoặc chuyển response thành kiểu dữ liệu riêng của plugin.
Khác với cách gửi thông qua event handler, khi gọi trực tiếp OAService:
  • Exception không được gom vào một result map.
  • Plugin gọi nhận exception trực tiếp.
  • Plugin tự quyết định ghi log, retry hoặc chuyển thành lỗi nghiệp vụ.
  • Kết quả không được tổng hợp theo nhiều service code.
Ví dụ:
public boolean trySend(
    String serviceCode,
    String recipientId,
    String message
) {
    OAService oaService =
        oaServiceManager.getOAServiceByCode(serviceCode);

    if (oaService == null) {
        return false;
    }

    try {
        oaService.sendMessage(recipientId, message);
        return true;
    } catch (Exception e) {
        logger.warn(
            "Send OA message failed, service: {}, recipient: {}",
            serviceCode,
            recipientId,
            e
        );
        return false;
    }
}
Không nên trả trực tiếp exception hoặc response gốc của OA qua public API vì chúng có thể chứa thông tin kỹ thuật hoặc dữ liệu nhạy cảm.

Service mẫu hoàn chỉnh

Ví dụ dưới đây dùng cho admin plugin:
import com.tvd12.ezyfox.util.EzyLoggable;
import lombok.AllArgsConstructor;
import org.youngmonkeys.ezyoa.admin.manager.AdminOAServiceManager;
import org.youngmonkeys.ezyoa.constant.OAMessageTransportType;
import org.youngmonkeys.ezyoa.service.OAService;

@AllArgsConstructor
public class OaMessageService extends EzyLoggable {

    private final AdminOAServiceManager oaServiceManager;

    public Object send(
        String serviceCode,
        String recipientId,
        String message,
        long byAdminId
    ) throws Exception {
        if (serviceCode == null || serviceCode.isEmpty()) {
            throw new IllegalArgumentException(
                "serviceCode is required"
            );
        }
        if (recipientId == null || recipientId.isEmpty()) {
            throw new IllegalArgumentException(
                "recipientId is required"
            );
        }

        OAService oaService =
            oaServiceManager.getOAServiceByCode(serviceCode);

        if (oaService == null) {
            throw new IllegalArgumentException(
                "Không tìm thấy dịch vụ OA: " + serviceCode
            );
        }

        return oaService.sendMessage(
            byAdminId,
            0L,
            OAMessageTransportType.API.toString(),
            recipientId,
            message
        );
    }
}
Điểm tích hợp khuyến nghị là đóng gói OAServiceManager và OAService trong một service riêng của plugin. Cách này giữ cho controller và logic nghiệp vụ không phụ thuộc trực tiếp vào chi tiết lựa chọn dịch vụ, transport type và xử lý lỗi của EzyOA.