【微服务】Staffjoy 项目源码解析(四)—— Bot 模块

一. 架构设计

在这里插入图片描述
上面的图片在 API 处有省略数个不同 数据模型,具体请自行查看源码

二. 代码分析

首先,是 BotClient 这个唯一的接口。
接口将三个 Controller 的方法综合防在了一起,对外提供接口调用。
相同的 Controller 的方法大同小异,各举一个例子

    @PostMapping(path="sms_greeting")
    BaseResponse sendSmsGreeting(@RequestBody @Validated GreetingRequest request);

    @PostMapping(path="onboard_worker")
    BaseResponse onboardWorker(@RequestBody @Validated OnboardWorkerRequest request);

    @PostMapping(path="alert_new_shift")
    BaseResponse alertNewShift(@RequestBody @Validated AlertNewShiftRequest request);

可以看出。都没有头部的验证,只是将数据模型作为参数传递。

数据模型方面,每个数据模型都较为细致且基础。
主要是 userId ,shift(ShiftDto 封装对象),companyId 等

不展示了。

然后是老规矩,代码自底向上分析。
Bot 模块的代码可以被认为分成四层。具体可以在上面我展示的架构图中可以看出

AppProps 提供一个布尔值,判断是发送 email 还是 sms

private boolean forceEmailPreference;

AppConfig 也不复杂,就提供一个异步线程池,在 HelperService 中被调用

    @Bean(name=ASYNC_EXECUTOR_NAME)
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setTaskDecorator(new ContextCopyingDecorator());
        executor.setCorePoolSize(3);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(100);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setThreadNamePrefix("AsyncThread-");
        executor.initialize();
        return executor;
    }

然后是 DispatchPreference,他含三个标签值,判断时 SMS,Email,还是发不出去

enum DispatchPreference {
    DISPATCH_SMS,
    DISPATCH_EMAIL,
    DISPATCH_UNAVAILABLE
}

接着是重头戏 HelperService。它可以说是整个模块的根基。主要的功能实现代码就在此处。

比如,异步发送短信

    @Async(AppConfig.ASYNC_EXECUTOR_NAME)
    void smsGreetingAsync(String phoneNumber) {
        String templateCode = BotConstant.GREETING_SMS_TEMPLATE_CODE;
        String templateParam = "";
        this.sendSms(phoneNumber, templateCode, templateParam);
    }

    void sendSms(String phoneNumber, String templateCode, String templateParam) {
        SmsRequest smsRequest = SmsRequest.builder()
                .to(phoneNumber)
                .templateCode(templateCode)
                .templateParam(templateParam)
                .build();

        BaseResponse baseResponse = null;
        try {
            baseResponse = smsClient.send(AuthConstant.AUTHORIZATION_BOT_SERVICE, smsRequest);
        } catch (Exception ex) {
            String errMsg = "could not send sms";
            logger.error(errMsg, ex);
            sentryClient.sendException(ex);
            throw new ServiceException(errMsg, ex);
        }
        if (!baseResponse.isSuccess()) {
            logger.error(baseResponse.getMessage());
            sentryClient.sendMessage(baseResponse.getMessage());
            throw new ServiceException(baseResponse.getMessage());
        }
    }

发送 email 的方法大同小异,所以只对该方法进行详细分析。

方法和之前的相同点在于。都会生成一个封装好的数据对象。
将这个对象当做参数传递,得到反馈(以 BaseResponse 类型)。
但有些不同的是,本方法使用的 Sms 模块 中封装的 SmsRequest 对象
且,使用 smsClient 接口就行代码调用。从而实现了不同模块间的通信。

Email 相关方法也是如此。

再到具体方法的执行。

首先是最多的 AlertService 类。
里面的方法是对 shift 这一班次的增删查改进行处理。

方法大同小异,择一分析。

    public void alertNewShift(AlertNewShiftRequest req) {
        String companyId = req.getNewShift().getCompanyId();
        String teamId = req.getNewShift().getTeamId();

        AccountDto account = helperService.getAccountById(req.getUserId());
        DispatchPreference dispatchPreference = helperService.getPreferredDispatch(account);
        if (dispatchPreference == DispatchPreference.DISPATCH_UNAVAILABLE) {
            return;
        }
        CompanyDto companyDto = helperService.getCompanyById(companyId);
        TeamDto teamDto = this.getTeamByCompanyIdAndTeamId(companyId, teamId);

        String newShiftMsg = this.printShiftSmsMsg(req.getNewShift(), teamDto.getTimezone());
        String jobName = this.getJobName(companyId, teamId, req.getNewShift().getJobId());

        // Format name with leading space
        if (!StringUtils.isEmpty(jobName)) {
            jobName = " " + jobName;
        }

        String greet = HelperService.getGreet(account.getName());
        String companyName = companyDto.getName();

        if (dispatchPreference == DispatchPreference.DISPATCH_EMAIL) {
            String htmlBody = String.format(BotConstant.ALERT_NEW_SHIFT_EMAIL_TEMPLATE,
                    greet, companyName, jobName, newShiftMsg);
            String subject = "New Shift Alert";
            String email = account.getEmail();
            String name = account.getName();

            helperService.sendMail(email, name, subject, htmlBody);
        } else { // sms

            String templateParam = Json.createObjectBuilder()
                    .add("greet", greet)
                    .add("company_name", companyName)
                    .add("job_name", jobName)
                    .add("shift_msg", newShiftMsg)
                    .build()
                    .toString();
            String phoneNumber = account.getPhoneNumber();

            // TODO crate sms template on aliyun then update constant
//        String msg = String.format("%s Your %s manager just published a new%s shift for you: \n%s",
                greet, company.getName(), jobName, newShiftMsg);
            helperService.sendSms(phoneNumber, BotConstant.ALERT_NEW_SHIFT_SMS_TEMPLATE_CODE, templateParam);
        }
    }

代码量不低,但逻辑并不复杂。

通过封装好传过来的 Request 对象取出其中的 companyId 和 teamId
在根据对象中的 userId 找到对应的 account 对象
通过这个对象,判断他支持发送邮件还是短信
然后通过之前取出的俩 id 找到对应的 company 和 team
在进一步得到新的 班次信息(newShift)和工作信息(jobName)
接着通过 helperService.getGreet 通知用户。
是发送短信还是邮件,根据权限(DispatchPreference)处理。

然后是俩 Controller,都只有一个方法

GreetingController 中的 sendSmsGreeting 方法在 account 模块中被运用到通知激活等信息

    public void greeting(String userId) {
        AccountDto account = helperService.getAccountById(userId);

        DispatchPreference dispatchPreference = helperService.getPreferredDispatch(account);
        switch (dispatchPreference) {
            case DISPATCH_SMS:
                helperService.smsGreetingAsync(account.getPhoneNumber());
                break;
            case DISPATCH_EMAIL:
                helperService.mailGreetingAsync(account);
                break;
            default:
                logger.info("Unable to send greeting to user %s - no comm method found", userId);
        }
    }

传入参数 userId 即可,便会择情处理。

另一边的 OnBoardingService 中的 onboardWorker 则在 company 模块中被用来通知班次的创建和修改

    public void onboardWorker(OnboardWorkerRequest req) {
        AccountDto account = helperService.getAccountById(req.getUserId());
        CompanyDto companyDto = helperService.getCompanyById(req.getCompanyId());

        DispatchPreference dispatchPreference = helperService.getPreferredDispatch(account);
        switch (dispatchPreference) {
            case DISPATCH_SMS:
                helperService.smsOnboardAsync(account, companyDto);
                break;
            case DISPATCH_EMAIL:
                helperService.mailOnBoardAsync(account, companyDto);
                break;
            default:
                logger.info("Unable to onboard user %s - no comm method found", req.getUserId());
        }
    }

参数相对复杂
因为不仅需要 userId 还要 companyId ,从而同时找到用户和公司
然后进行处理。

Controller 层就中规中矩,没什么特别的,各展示一个范例,收工。

    @PostMapping(value = "/sms_greeting")
    BaseResponse sendSmsGreeting(@RequestBody @Validated GreetingRequest request) {
        greetingService.greeting(request.getUserId());
        return BaseResponse.builder().message("greeting sent").build();
    }
    
    @PostMapping(value = "alert_changed_shifts")
    public BaseResponse alertChangedShifts(@RequestBody @Validated AlertChangedShiftRequest request) {
        alertService.alertChangedShift(request);
        return BaseResponse.builder().message("changed shifts alerted").build();
    }

    @PostMapping(value = "/onboard_worker")
    public BaseResponse onboardWorker(@RequestBody @Validated OnboardWorkerRequest request) {
        onBoardingService.onboardWorker(request);
        return BaseResponse.builder().message("onboarded worker").build();
    }

三. 小关键点

使用 lombok 插件生成后的构建方法,可以清晰且方便的构建封装数据对象。

        SmsRequest smsRequest = SmsRequest.builder()
                .to(phoneNumber)
                .templateCode(templateCode)
                .templateParam(templateParam)
                .build();

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值