新增华为云短信

This commit is contained in:
阿沐 2022-08-20 19:11:09 +08:00
parent 0b580d2fe2
commit 29cfc30459
3 changed files with 218 additions and 19 deletions

View File

@ -20,18 +20,18 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.HashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* 短信平台 * 短信平台
* *
* @author 阿沐 babamu@126.com * @author 阿沐 babamu@126.com
*/ */
@RestController @RestController
@RequestMapping("message/sms/platform") @RequestMapping("message/sms/platform")
@Tag(name="短信平台") @Tag(name = "短信平台")
@AllArgsConstructor @AllArgsConstructor
public class SmsPlatformController { public class SmsPlatformController {
private final SmsPlatformService smsPlatformService; private final SmsPlatformService smsPlatformService;
@ -40,7 +40,7 @@ public class SmsPlatformController {
@GetMapping("page") @GetMapping("page")
@Operation(summary = "分页") @Operation(summary = "分页")
@PreAuthorize("hasAuthority('sms:platform:page')") @PreAuthorize("hasAuthority('sms:platform:page')")
public Result<PageResult<SmsPlatformVO>> page(@Valid SmsPlatformQuery query){ public Result<PageResult<SmsPlatformVO>> page(@Valid SmsPlatformQuery query) {
PageResult<SmsPlatformVO> page = smsPlatformService.page(query); PageResult<SmsPlatformVO> page = smsPlatformService.page(query);
return Result.ok(page); return Result.ok(page);
@ -49,7 +49,7 @@ public class SmsPlatformController {
@GetMapping("{id}") @GetMapping("{id}")
@Operation(summary = "信息") @Operation(summary = "信息")
@PreAuthorize("hasAuthority('sms:platform:info')") @PreAuthorize("hasAuthority('sms:platform:info')")
public Result<SmsPlatformVO> get(@PathVariable("id") Long id){ public Result<SmsPlatformVO> get(@PathVariable("id") Long id) {
SmsPlatformEntity entity = smsPlatformService.getById(id); SmsPlatformEntity entity = smsPlatformService.getById(id);
return Result.ok(SmsPlatformConvert.INSTANCE.convert(entity)); return Result.ok(SmsPlatformConvert.INSTANCE.convert(entity));
@ -58,7 +58,7 @@ public class SmsPlatformController {
@PostMapping @PostMapping
@Operation(summary = "保存") @Operation(summary = "保存")
@PreAuthorize("hasAuthority('sms:platform:save')") @PreAuthorize("hasAuthority('sms:platform:save')")
public Result<String> save(@RequestBody SmsPlatformVO vo){ public Result<String> save(@RequestBody SmsPlatformVO vo) {
smsPlatformService.save(vo); smsPlatformService.save(vo);
return Result.ok(); return Result.ok();
@ -67,13 +67,13 @@ public class SmsPlatformController {
@PostMapping("send") @PostMapping("send")
@Operation(summary = "发送短信") @Operation(summary = "发送短信")
@PreAuthorize("hasAuthority('sms:platform:update')") @PreAuthorize("hasAuthority('sms:platform:update')")
public Result<String> send(@RequestBody SmsSendVO vo){ public Result<String> send(@RequestBody SmsSendVO vo) {
SmsPlatformEntity entity = smsPlatformService.getById(vo.getId()); SmsPlatformEntity entity = smsPlatformService.getById(vo.getId());
SmsConfig config = SmsPlatformConvert.INSTANCE.convert2(entity); SmsConfig config = SmsPlatformConvert.INSTANCE.convert2(entity);
// 短信参数 // 短信参数
Map<String, String> params = new HashMap<>(); Map<String, String> params = new LinkedHashMap<>();
if(!StringUtils.isAnyBlank(vo.getParamKey(), vo.getParamValue())) { if (StringUtils.isNotBlank(vo.getParamValue())) {
params.put(vo.getParamKey(), vo.getParamValue()); params.put(vo.getParamKey(), vo.getParamValue());
} }
@ -85,7 +85,7 @@ public class SmsPlatformController {
smsService.saveLog(config, vo.getMobile(), params, null); smsService.saveLog(config, vo.getMobile(), params, null);
return Result.ok(); return Result.ok();
}catch (Exception e) { } catch (Exception e) {
// 保存日志 // 保存日志
smsService.saveLog(config, vo.getMobile(), params, e); smsService.saveLog(config, vo.getMobile(), params, e);
@ -96,7 +96,7 @@ public class SmsPlatformController {
@PutMapping @PutMapping
@Operation(summary = "修改") @Operation(summary = "修改")
@PreAuthorize("hasAuthority('sms:platform:update')") @PreAuthorize("hasAuthority('sms:platform:update')")
public Result<String> update(@RequestBody @Valid SmsPlatformVO vo){ public Result<String> update(@RequestBody @Valid SmsPlatformVO vo) {
smsPlatformService.update(vo); smsPlatformService.update(vo);
return Result.ok(); return Result.ok();
@ -105,7 +105,7 @@ public class SmsPlatformController {
@DeleteMapping @DeleteMapping
@Operation(summary = "删除") @Operation(summary = "删除")
@PreAuthorize("hasAuthority('sms:platform:delete')") @PreAuthorize("hasAuthority('sms:platform:delete')")
public Result<String> delete(@RequestBody List<Long> idList){ public Result<String> delete(@RequestBody List<Long> idList) {
smsPlatformService.delete(idList); smsPlatformService.delete(idList);
return Result.ok(); return Result.ok();

View File

@ -0,0 +1,195 @@
package net.maku.message.sms;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.CharsetUtil;
import lombok.Data;
import net.maku.framework.common.exception.FastException;
import net.maku.framework.common.utils.JsonUtils;
import net.maku.message.sms.config.SmsConfig;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import javax.net.ssl.*;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 华为云短信
*
* @author 阿沐 babamu@126.com
*/
public class HuaweiSmsStrategy implements SmsStrategy {
// 无需修改用于格式化鉴权头域"X-WSSE"参数赋值
private static final String WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\"";
// 无需修改用于格式化鉴权头域"Authorization"参数赋值
private static final String AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\"";
private final SmsConfig smsConfig;
public HuaweiSmsStrategy(SmsConfig smsConfig) {
this.smsConfig = smsConfig;
}
@Override
public void send(String mobile, Map<String, String> params) {
// APP接入地址(在控制台"应用管理"页面获取)+接口访问URI
String url = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1";
// 有参数则设置
String templateParas = null;
if (MapUtil.isNotEmpty(params)) {
templateParas = JsonUtils.toJsonString(params.values().toArray(new String[0]));
}
// 请求Body,不携带签名名称时,signature请填null
String body = buildRequestBody(smsConfig.getSenderId(), "+86" + mobile, smsConfig.getTemplateId(), templateParas, null, smsConfig.getSignName());
if (StringUtils.isBlank(body)) {
throw new FastException("body is null.");
}
// 请求Headers中的X-WSSE参数值
String wsseHeader = buildWsseHeader(smsConfig.getAccessKey(), smsConfig.getSecretKey());
if (StringUtils.isBlank(wsseHeader)) {
throw new FastException("wsse header is null.");
}
try {
// 使用 https
trustAllHttpsCertificates();
URL realUrl = new URL(url);
HttpsURLConnection connection = (HttpsURLConnection) realUrl.openConnection();
HostnameVerifier hv = (hostname, session) -> true;
connection.setHostnameVerifier(hv);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(true);
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Authorization", AUTH_HEADER_VALUE);
connection.setRequestProperty("X-WSSE", wsseHeader);
connection.connect();
IoUtil.writeUtf8(connection.getOutputStream(), true, body);
int status = connection.getResponseCode();
if (status == HttpStatus.OK.value()) {
String response = IoUtil.read(connection.getInputStream(), CharsetUtil.CHARSET_UTF_8);
HuaweiSmsResult result = JsonUtils.parseObject(response, HuaweiSmsResult.class);
// 短信是否发送成功
assert result != null;
if (!"000000".equals(result.code)) {
throw new FastException(result.description);
}
} else { //400 401
throw new FastException(IoUtil.read(connection.getErrorStream(), CharsetUtil.CHARSET_UTF_8));
}
} catch (Exception e) {
throw new FastException("短信发送失败:", e);
}
}
/**
* 构造请求Body体
*
* @param signature | 签名名称,使用国内短信通用模板时填写
*/
static String buildRequestBody(String sender, String receiver, String templateId, String templateParas,
String statusCallBack, String signature) {
if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty()
|| templateId.isEmpty()) {
throw new FastException("buildRequestBody(): sender, receiver or templateId is null.");
}
Map<String, String> map = new HashMap<>();
map.put("from", sender);
map.put("to", receiver);
map.put("templateId", templateId);
if (null != templateParas && !templateParas.isEmpty()) {
map.put("templateParas", templateParas);
}
if (null != statusCallBack && !statusCallBack.isEmpty()) {
map.put("statusCallback", statusCallBack);
}
if (null != signature && !signature.isEmpty()) {
map.put("signature", signature);
}
StringBuilder sb = new StringBuilder();
String temp = "";
for (String s : map.keySet()) {
try {
temp = URLEncoder.encode(map.get(s), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
sb.append(s).append("=").append(temp).append("&");
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
/**
* 构造X-WSSE参数值
*/
static String buildWsseHeader(String appKey, String appSecret) {
if (null == appKey || null == appSecret || appKey.isEmpty() || appSecret.isEmpty()) {
throw new FastException("buildWsseHeader(): appKey or appSecret is null.");
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
String time = sdf.format(new Date());
String nonce = UUID.randomUUID().toString().replace("-", "");
MessageDigest md;
byte[] passwordDigest = null;
try {
md = MessageDigest.getInstance("SHA-256");
md.update((nonce + time + appSecret).getBytes());
passwordDigest = md.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
String passwordDigestBase64Str = Base64.getEncoder().encodeToString(passwordDigest);
return String.format(WSSE_HEADER_FORMAT, appKey, passwordDigestBase64Str, nonce, time);
}
static void trustAllHttpsCertificates() throws Exception {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
}
@Data
static class HuaweiSmsResult {
// code为000000表示成功
private String code;
private String description;
private List<Object> result;
}
}

View File

@ -15,11 +15,15 @@ public class SmsContext {
private final SmsStrategy smsStrategy; private final SmsStrategy smsStrategy;
public SmsContext(SmsConfig config) { public SmsContext(SmsConfig config) {
if(config.getPlatform() == SmsPlatformEnum.ALIYUN.getValue()) { if (config.getPlatform() == SmsPlatformEnum.ALIYUN.getValue()) {
this.smsStrategy = new AliyunSmsStrategy(config); this.smsStrategy = new AliyunSmsStrategy(config);
}else if(config.getPlatform() == SmsPlatformEnum.QCLOUD.getValue()) { } else if (config.getPlatform() == SmsPlatformEnum.QCLOUD.getValue()) {
this.smsStrategy = new QcloudSmsStrategy(config); this.smsStrategy = new QcloudSmsStrategy(config);
}else { } else if (config.getPlatform() == SmsPlatformEnum.QINIU.getValue()) {
this.smsStrategy = new QiniuSmsStrategy(config);
} else if (config.getPlatform() == SmsPlatformEnum.HUAWEI.getValue()) {
this.smsStrategy = new HuaweiSmsStrategy(config);
} else {
throw new FastException("未知的短信平台"); throw new FastException("未知的短信平台");
} }
} }