一. 架构设计
微服务模块所有的架构设计都是分成 API 与 SVC 两个包来设计。
API 接口模块内会包含对外的接口及接口中会使用的数据模型。
而 SVC 模块中则包含项目的主要代码。
本 SMS 模块负责短信发送的处理。所以,只有一个主要的发送方法。结构较为基本而清晰。
二. 代码分析
2.1 sms-api
SmsClient 内只含有一个 send 方法,参数为服务类型数据和 SmsRequest 对象。
返回一个 BaseResponse 对象(内涵一个 message(String),一个 code(ResultCode:判断返回状态)及一个 isSuccess 方法)。绑定在 /queue_send 路径上。
负责发送短信。
@PostMapping(path = "/queue_send")
BaseResponse send(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid SmsRequest smsRequest);
此方法将在 bot 模块的 helperService 中被调用。
而 SmsRequest 中主要包含三个参数,发给谁,发送状态,发什么
@NotBlank(message = "Please provide a phone number")
private String to;
@NotBlank(message = "Please provide a template code")
private String templateCode;
private String templateParam;
2.2 sms-svc
从底层向上分析。
首先是 AppProps 这个 SVC 中的底层数据封装对象。里面是一些要用到的配置数据,包括 阿里云的密码等。
@NotNull private String aliyunAccessKey;
@NotNull private String aliyunAccessSecret;
@NotNull private String aliyunSmsSignName;
private boolean whiteListOnly;
private String whiteListPhoneNumbers;
private int concurrency;
然后是 AppConfig 类。
其中导入并生成了操作阿里云模块的 IAcsClient 对象来与阿里云进行交互。
并生成了一个异步线程池,准备操作的处理。
@Bean
public IAcsClient acsClient(@Autowired SentryClient sentryClient) {
IClientProfile profile = DefaultProfile.getProfile(SmsConstant.ALIYUN_REGION_ID, appProps.getAliyunAccessKey(), appProps.getAliyunAccessSecret());
try {
DefaultProfile.addEndpoint(SmsConstant.ALIYUN_SMS_ENDPOINT_NAME, SmsConstant.ALIYUN_REGION_ID, SmsConstant.ALIYUN_SMS_PRODUCT, SmsConstant.ALIYUN_SMS_DOMAIN);
} catch (ClientException ex) {
sentryClient.sendException(ex);
logger.error("Fail to create acsClient ", ex);
}
IAcsClient client = new DefaultAcsClient(profile);
return client;
}
@Bean(name=ASYNC_EXECUTOR_NAME)
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(appProps.getConcurrency());
executor.setMaxPoolSize(appProps.getConcurrency());
executor.setQueueCapacity(SmsConstant.DEFAULT_EXECUTOR_QUEUE_CAPACITY);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("AsyncThread-");
executor.initialize();
return executor;
}
接着是 SmsSendService 类,属于 Service 层。
因为没有数据库调用,所以没有 Dao 层,直接到 Service 层中。
关键步骤是生成 request 请求对象,发送并得到 Response,下面展示关键语句。
SendSmsRequest request = new SendSmsRequest();
request.setPhoneNumbers(smsRequest.getTo());
request.setSignName(appProps.getAliyunSmsSignName());
request.setTemplateCode(smsRequest.getTemplateCode());
request.setTemplateParam(smsRequest.getTemplateParam());
try {
SendSmsResponse response = acsClient.getAcsResponse(request);
.....
但是代码中还是有很长一段的,是返回参数的校验与错误处理及日志记录,这都应该是一个正规项目应该有的部分。
大致相同,贴一部分。
Context sentryContext = sentryClient.getContext();
sentryContext.addTag("to", smsRequest.getTo());
sentryContext.addTag("template_code", smsRequest.getTemplateCode());
sentryClient.sendException(ex);
logger.error("failed to make aliyun sms request ", ex);
最后是 Controller 层的 SmsController 类。
就一个 send 的方法,要注意的点也是参数校验。
一个是头部的参数校验,发送过来的服务是否符合操作的权限(company,account,bot 等)
再一个是判断传过来的电话号是否都能够发送(数据非空且被包含在白名单中),最后发送目标。
@PostMapping(path = "/queue_send")
@Authorize({
AuthConstant.AUTHORIZATION_COMPANY_SERVICE,
AuthConstant.AUTHORIZATION_ACCOUNT_SERVICE,
AuthConstant.AUTHORIZATION_BOT_SERVICE
})
public BaseResponse send(@RequestBody @Valid SmsRequest smsRequest) {
if (appProps.isWhiteListOnly()) {
String whiteList = appProps.getWhiteListPhoneNumbers();
boolean allowedToSend = !StringUtils.isEmpty(whiteList)
&& whiteList.contains(smsRequest.getTo());
if (!allowedToSend) {
String msg = String.format("prevented sending to number %s due to whitelist", smsRequest.getTo());
logger.warn(msg);
return BaseResponse.builder().code(ResultCode.REQ_REJECT).message(msg).build();
}
}
smsSendService.sendSmsAsync(smsRequest);
String msg = String.format("sent message to %s. async", smsRequest.getTo());
logger.debug(msg);
return BaseResponse.builder().message(msg).build();
}
三. 小关键点
展示一下 lombok 的基本注解
// 自动生成构建类对象的方法
@Builder
// 自动生成无参构造器
@NoArgsConstructor
// 自动生成全参构造器
@AllArgsConstructor
// 必须非空
@NotNull
Spring 自带的数据校验注解
// 校验启动
@Validated
// 在方法参数中加
@Valid
@RequestBody