package com.ruoyi.common.utils.sms;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.annotation.JSONField;
|
import com.ruoyi.common.utils.HttpUtil;
|
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.http.HttpUtils;
|
import com.ruoyi.common.utils.sign.Md5Utils;
|
import lombok.Data;
|
import lombok.extern.slf4j.Slf4j;
|
|
import java.nio.charset.StandardCharsets;
|
import java.util.Base64;
|
import java.util.Date;
|
import java.util.HashMap;
|
import java.util.Map;
|
|
/**
|
* 短信发送工具类
|
*
|
* @author ruoyi
|
*/
|
@Slf4j
|
public final class SmsUtils {
|
|
private SmsUtils() {
|
}
|
|
/**
|
* 云MAS 每批次最多手机号数量
|
*/
|
private static final int YUNMAS_MAX_MOBILES = 5000;
|
|
/**
|
* 云MAS 返回码说明
|
*/
|
private static final Map<String, String> YUNMAS_RSPCOD_DESC = new HashMap<>();
|
|
static {
|
YUNMAS_RSPCOD_DESC.put("success", "数据验证通过");
|
YUNMAS_RSPCOD_DESC.put("IllegalMac", "mac校验不通过");
|
YUNMAS_RSPCOD_DESC.put("IllegalSignId", "无效的签名编码");
|
YUNMAS_RSPCOD_DESC.put("InvalidMessage", "非法消息,请求数据解析失败");
|
YUNMAS_RSPCOD_DESC.put("InvalidUsrOrPwd", "非法用户名/密码");
|
YUNMAS_RSPCOD_DESC.put("NoSignId", "未匹配到对应的签名信息");
|
YUNMAS_RSPCOD_DESC.put("TooManyMobiles", "手机号数量超限(>5000)");
|
}
|
|
/**
|
* 云MAS 接口配置。
|
* <p>
|
* 注意:secretKey 只参与 mac 计算,不能放进请求报文。
|
*/
|
@Data
|
public static class YunMasConfig {
|
/**
|
* 接口地址
|
*/
|
private String url;
|
/**
|
* 企业名称
|
*/
|
private String ecName;
|
/**
|
* 接口账号用户名
|
*/
|
private String apId;
|
/**
|
* 密钥(仅参与 mac 计算)
|
*/
|
private String secretKey;
|
/**
|
* 签名编码
|
*/
|
private String sign;
|
/**
|
* 扩展码:精确匹配填空字符串;模糊匹配可填空字符串或自定义扩展码(与服务代码合计不超过20位)
|
*/
|
private String addSerial;
|
|
public String getAddSerial() {
|
return addSerial == null ? "" : addSerial;
|
}
|
|
/**
|
* 丽水妇幼默认配置(未在 sys_config 中配置时使用)
|
*/
|
public static YunMasConfig defaultLishui() {
|
YunMasConfig config = new YunMasConfig();
|
config.setUrl("http://9.208.5.35:1992/sms/norsubmit");
|
config.setEcName("丽水市妇幼保健院");
|
config.setApId("dxjkkf");
|
config.setSecretKey("1q2w3e4r5t6y");
|
config.setSign("D9gKbwdt7");
|
config.setAddSerial("");
|
return config;
|
}
|
|
/**
|
* 根据配置内容(json)构建,未配置的字段沿用默认值
|
*
|
* @param configJson 形如 {"url":"...","ecName":"...","apId":"...","secretKey":"...","sign":"...","addSerial":""}
|
*/
|
public static YunMasConfig of(String configJson) {
|
YunMasConfig config = defaultLishui();
|
if (StringUtils.isEmpty(configJson)) {
|
return config;
|
}
|
JSONObject json;
|
try {
|
json = JSON.parseObject(configJson);
|
} catch (Exception e) {
|
log.error("【云MAS】配置解析失败,使用默认配置,配置内容:{}", configJson, e);
|
return config;
|
}
|
if (json == null) {
|
return config;
|
}
|
if (StringUtils.isNotEmpty(json.getString("url"))) {
|
config.setUrl(json.getString("url"));
|
}
|
if (StringUtils.isNotEmpty(json.getString("ecName"))) {
|
config.setEcName(json.getString("ecName"));
|
}
|
if (StringUtils.isNotEmpty(json.getString("apId"))) {
|
config.setApId(json.getString("apId"));
|
}
|
if (StringUtils.isNotEmpty(json.getString("secretKey"))) {
|
config.setSecretKey(json.getString("secretKey"));
|
}
|
if (StringUtils.isNotEmpty(json.getString("sign"))) {
|
config.setSign(json.getString("sign"));
|
}
|
if (json.containsKey("addSerial")) {
|
config.setAddSerial(json.getString("addSerial"));
|
}
|
return config;
|
}
|
|
/**
|
* 关键参数是否齐全
|
*/
|
public boolean isValid() {
|
return StringUtils.isNotEmpty(url) && StringUtils.isNotEmpty(ecName)
|
&& StringUtils.isNotEmpty(apId) && StringUtils.isNotEmpty(secretKey)
|
&& StringUtils.isNotEmpty(sign);
|
}
|
}
|
|
/**
|
* 短信发送结果
|
*/
|
@Data
|
public static class SmsResult {
|
/**
|
* 平台返回的状态码(success 表示校验通过)
|
*/
|
private String rspcod;
|
/**
|
* 消息批次号,失败时为空
|
*/
|
private String msgGroup;
|
/**
|
* 平台返回的数据校验结果
|
*/
|
private boolean success;
|
/**
|
* 本地异常/失败描述,便于排查
|
*/
|
private String message;
|
|
/**
|
* 是否发送成功(平台校验通过)
|
*/
|
@JSONField(serialize = false, deserialize = false)
|
public boolean isOk() {
|
return success && "success".equals(rspcod);
|
}
|
|
public static SmsResult fail(String message) {
|
SmsResult result = new SmsResult();
|
result.setSuccess(false);
|
result.setMessage(message);
|
return result;
|
}
|
}
|
|
/**
|
* 杭州市一医院吴山院区我短信接口
|
*
|
* @param url 短信URL
|
* @param userName 短信帐号
|
* @param passWord 短信密码
|
* @param phone 手机号组合如 13500000001,13500000002
|
* @param content 短信内容
|
* @return 发送结果
|
*/
|
public static String sendSms(String url, String userName, String passWord, String phone, String content) {
|
long timestamp = new Date().getTime();
|
String sign = Md5Utils.hash(userName + timestamp + Md5Utils.hash(passWord));
|
content = "【杭州市肿瘤医院】" + content;
|
String jsonMsg = "{" +
|
"\"userName\": \"" + userName + "\"," +
|
"\"content\": \"" + content + "\"," +
|
"\"phoneList\": [" + phone + "]," +
|
"\"timestamp\": " + timestamp + "," +
|
"\"sign\": \"" + sign + "\"" +
|
"}";
|
Boolean sendMsg = HttpUtil.postJsonRequest(url, jsonMsg);
|
return sendMsg.toString();
|
|
}
|
|
|
/**
|
* 杭州市一医院湖滨院区我短信接口
|
*
|
* @param url 短信URL
|
* @param userName 短信帐号
|
* @param passWord 短信密码
|
* @param phone 手机号组合如 13500000001,13500000002
|
* @param content 短信内容
|
* @return 发送结果
|
*/
|
public static String sendSms1(String url, String userName, String passWord, String phone, String content) {
|
long timestamp = new Date().getTime();
|
String sign = Md5Utils.hash(userName + timestamp + Md5Utils.hash(passWord));
|
content = "【杭州市第一人民医院】" + content;
|
String jsonMsg = "{" +
|
"\"userName\": \"" + userName + "\"," +
|
"\"content\": \"" + content + "\"," +
|
"\"phoneList\": [" + phone + "]," +
|
"\"timestamp\": " + timestamp + "," +
|
"\"sign\": \"" + sign + "\"" +
|
"}";
|
Boolean sendMsg = HttpUtil.postJsonRequest(url, jsonMsg);
|
return sendMsg.toString();
|
|
}
|
|
/**
|
* 中国移动云MAS 普通短信发送(一对一/多对一模式)
|
* <p>
|
* 接口:POST {url},报文为 json 的 base64 编码。
|
* <ul>
|
* <li>mac 按 ecName、apId、secretKey、mobiles、content、sign、addSerial 顺序拼接后取 MD5(32位小写)</li>
|
* <li>secretKey 只参与 mac 计算,不放进请求报文</li>
|
* <li>签名由平台根据 sign 编码自动追加,content 中不要再手工拼【签名】</li>
|
* </ul>
|
*
|
* @param config 云MAS 配置
|
* @param mobiles 收信手机号,英文逗号分隔,如 13800138000,13800138001,每批不超过 5000 个
|
* @param content 短信内容(mac 用最终内容计算,计算后不得再修改)
|
* @return 发送结果,通过 {@link SmsResult#isOk()} 判断是否成功
|
*/
|
public static SmsResult sendYunMasSms(YunMasConfig config, String mobiles, String content) {
|
if (config == null || !config.isValid()) {
|
return SmsResult.fail("云MAS配置不完整");
|
}
|
if (StringUtils.isEmpty(mobiles)) {
|
return SmsResult.fail("收信手机号不能为空");
|
}
|
if (StringUtils.isEmpty(content)) {
|
return SmsResult.fail("短信内容不能为空");
|
}
|
if (mobiles.split(",").length > YUNMAS_MAX_MOBILES) {
|
return SmsResult.fail("手机号数量超限(>" + YUNMAS_MAX_MOBILES + ")");
|
}
|
|
String addSerial = config.getAddSerial();
|
// 1. mac 必须用报文里最终发送的内容计算
|
String mac = Md5Utils.hash(config.getEcName() + config.getApId() + config.getSecretKey()
|
+ mobiles + content + config.getSign() + addSerial);
|
|
// 2. 用 json 组装报文,避免内容中的引号/反斜杠破坏报文;secretKey 不落报文
|
JSONObject body = new JSONObject(true);
|
body.put("ecName", config.getEcName());
|
body.put("apId", config.getApId());
|
body.put("mobiles", mobiles);
|
body.put("content", content);
|
body.put("sign", config.getSign());
|
body.put("addSerial", addSerial);
|
body.put("mac", mac);
|
|
String requestBody = Base64.getEncoder()
|
.encodeToString(body.toJSONString().getBytes(StandardCharsets.UTF_8));
|
|
try {
|
String resp = HttpUtil.postJsonRequestForString(config.getUrl(), requestBody);
|
log.info("【云MAS】发送短信,手机号:{},响应:{}", mobiles, resp);
|
if (StringUtils.isEmpty(resp)) {
|
return SmsResult.fail("云MAS响应为空");
|
}
|
SmsResult result = JSON.parseObject(resp, SmsResult.class);
|
if (result == null) {
|
return SmsResult.fail("云MAS响应解析失败:" + resp);
|
}
|
if (!result.isOk()) {
|
String desc = YUNMAS_RSPCOD_DESC.getOrDefault(result.getRspcod(), "未知错误");
|
result.setMessage(desc);
|
log.error("【云MAS】发送失败,手机号:{},rspcod:{}({}),响应:{}",
|
mobiles, result.getRspcod(), desc, resp);
|
}
|
return result;
|
} catch (Exception e) {
|
log.error("【云MAS】发送异常,手机号:{},原因:{}", mobiles, e.getMessage(), e);
|
return SmsResult.fail("云MAS发送异常:" + e.getMessage());
|
}
|
}
|
|
/**
|
* 杭州市一医院吴山院区微信接口
|
*
|
* @param url 微信URL
|
* @param phone 手机号 13500000001
|
* @param idCard 身份证
|
* @return 发送结果
|
*/
|
public static String sendChat(String url, String phone, String idCard) {
|
String urlTemp = "http://192.200.54.14:5003/sfjk/SendMessage?sfzh="
|
+ idCard
|
+ "&title=您好,邀请您填写出院调查表,请点击填写。&content=待随访&phone=" + phone
|
+ "&url=" + url + "&key=ff76f8904f5f32b5ee1739e8ea46e60g";
|
String sendMsg = "";
|
try {
|
log.info("【sendChat】市一微信公众号请求参数:{}", urlTemp);
|
sendMsg = HttpUtils.sendPost(urlTemp);
|
log.info("【sendChat】市一微信公众号返回值:{}", sendMsg);
|
} catch (Exception ex) {
|
sendMsg = ex.getMessage();
|
log.error(ex.getMessage());
|
}
|
return sendMsg;
|
}
|
}
|