陈昶聿
2 天以前 933846091c0b8f06158fcab23cfa0b59e8514f9b
ruoyi-common/src/main/java/com/ruoyi/common/utils/sms/SmsUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,336 @@
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;
    }
}