| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.common.utils.sms; |
| | | |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONArray; |
| | | 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.io.BufferedReader; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InputStreamReader; |
| | | import java.io.OutputStream; |
| | | import java.net.HttpURLConnection; |
| | | import java.net.URL; |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.Base64; |
| | | import java.util.Date; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | /** |
| | | * çä¿¡åéå·¥å
·ç±» |
| | | * |
| | | * @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; |
| | | } |
| | | |
| | | /** |
| | | * 丽水微信 app-api åç¼ |
| | | */ |
| | | private static final String LS_WX_APP_PREFIX = "/app-api"; |
| | | |
| | | /** |
| | | * ä¸½æ°´å¾®ä¿¡å¼æ¾æ¥å£åç¼ |
| | | */ |
| | | private static final String LS_WX_API_PREFIX = LS_WX_APP_PREFIX + "/cross/open-api"; |
| | | |
| | | /** |
| | | * è·å访é®ä»¤ç |
| | | */ |
| | | private static final String LS_WX_TOKEN_PATH = "/token"; |
| | | |
| | | /** |
| | | * è·åæ¶æ¯æ¨¡æ¿ |
| | | */ |
| | | private static final String LS_WX_TEMPLATE_PATH = "/getTemplate"; |
| | | |
| | | /** |
| | | * åéæ¶æ¯ |
| | | */ |
| | | private static final String LS_WX_SEND_PATH = "/sendMessage"; |
| | | |
| | | /** |
| | | * 访é®ä»¤ç请æ±å¤´ |
| | | */ |
| | | private static final String LS_WX_TOKEN_HEADER = "openApiToken"; |
| | | |
| | | /** |
| | | * 令çç¼åï¼keyï¼baseUrl|appIdï¼ |
| | | */ |
| | | private static final Map<String, LsWxToken> LS_WX_TOKEN_CACHE = new ConcurrentHashMap<>(); |
| | | |
| | | /** |
| | | * 令çæåå·æ°çç§æ°ï¼é¿å
临çè¿æ |
| | | */ |
| | | private static final long LS_WX_TOKEN_REFRESH_AHEAD_SECONDS = 60L; |
| | | |
| | | /** |
| | | * 丽水微信æ¥å£é
ç½® |
| | | */ |
| | | @Data |
| | | public static class LsWxConfig { |
| | | /** |
| | | * åºç¨åç§° |
| | | */ |
| | | private String appName; |
| | | /** |
| | | * æ¥å£æ ¹å°åï¼å¦ http://127.0.0.1:8080 |
| | | * <p> |
| | | * å¯ä»¥åªå¡« scheme://host:portï¼ä¹å¯ä»¥å¡«å° /app-api æ /app-api/cross/open-apiã |
| | | */ |
| | | private String baseUrl; |
| | | /** |
| | | * åºç¨ID |
| | | */ |
| | | private String appId; |
| | | /** |
| | | * åºç¨å¯é¥ |
| | | */ |
| | | private String appSecret; |
| | | /** |
| | | * é»è®¤æ¶æ¯æ¨¡æ¿IDï¼æ¶æ¯éæªæå®æ¶ä½¿ç¨ |
| | | */ |
| | | private Long templateId; |
| | | /** |
| | | * é»è®¤æ¶æ¯æ¨¡æ¿åç§°ï¼æªé
置模æ¿IDæ¶éè¿ getTemplate æåç§°æ¥æ¾ |
| | | */ |
| | | private String templateName; |
| | | /** |
| | | * è¿æ¥è¶
æ¶ï¼åä½ï¼æ¯«ç§ |
| | | */ |
| | | private Integer connectTimeout = 5000; |
| | | /** |
| | | * 读åè¶
æ¶ï¼åä½ï¼æ¯«ç§ |
| | | */ |
| | | private Integer readTimeout = 10000; |
| | | |
| | | /** |
| | | * 丽水é»è®¤é
ç½® |
| | | */ |
| | | public static LsWxConfig defaultLishui() { |
| | | LsWxConfig config = new LsWxConfig(); |
| | | config.setAppName("æ£è
é访"); |
| | | config.setBaseUrl("https://wmini.lsfby.com.cn/app-api"); |
| | | config.setAppId("kpzEHgZSteCO4lz74"); |
| | | config.setAppSecret("HKUDVMJSVTKQADIXV7XXRLNBQM8HNNL7"); |
| | | // æ¶æ¯æ¨¡æ¿ï¼è¯åé访æéï¼æªæå®æ¨¡æ¿IDæ¶æåç§°ä» getTemplate æ¥è¯¢ï¼ |
| | | config.setTemplateName("è¯åé访æé"); |
| | | return config; |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®é
ç½®å
容ï¼jsonï¼æå»ºï¼æªé
ç½®çåæ®µæ²¿ç¨é»è®¤å¼ |
| | | * |
| | | * @param configJson å½¢å¦ |
| | | * {"appName":"æ£è
é访","baseUrl":"https://wmini.lsfby.com.cn/app-api","appId":"...","appSecret":"...","templateId":16,"templateName":"è¯åé访æé"} |
| | | */ |
| | | public static LsWxConfig of(String configJson) { |
| | | LsWxConfig config = defaultLishui(); |
| | | if (StringUtils.isEmpty(configJson)) { |
| | | return config; |
| | | } |
| | | JSONObject json; |
| | | try { |
| | | json = JSON.parseObject(configJson); |
| | | } catch (Exception e) { |
| | | log.error("ã丽水微信ãé
置解æå¤±è´¥ï¼ä½¿ç¨é»è®¤é
ç½®ï¼é
ç½®å
容ï¼{}", configJson, e); |
| | | return config; |
| | | } |
| | | if (json == null) { |
| | | return config; |
| | | } |
| | | if (StringUtils.isNotEmpty(json.getString("appName"))) { |
| | | config.setAppName(json.getString("appName")); |
| | | } |
| | | if (StringUtils.isNotEmpty(json.getString("baseUrl"))) { |
| | | config.setBaseUrl(json.getString("baseUrl")); |
| | | } else if (StringUtils.isNotEmpty(json.getString("url"))) { |
| | | config.setBaseUrl(json.getString("url")); |
| | | } else if (StringUtils.isNotEmpty(json.getString("address"))) { |
| | | config.setBaseUrl(json.getString("address")); |
| | | } |
| | | if (StringUtils.isNotEmpty(json.getString("appId"))) { |
| | | config.setAppId(json.getString("appId")); |
| | | } |
| | | if (StringUtils.isNotEmpty(json.getString("appSecret"))) { |
| | | config.setAppSecret(json.getString("appSecret")); |
| | | } |
| | | if (json.containsKey("templateId")) { |
| | | config.setTemplateId(json.getLong("templateId")); |
| | | } else if (json.containsKey("id")) { |
| | | config.setTemplateId(json.getLong("id")); |
| | | } |
| | | if (StringUtils.isNotEmpty(json.getString("templateName"))) { |
| | | config.setTemplateName(json.getString("templateName")); |
| | | } else if (StringUtils.isNotEmpty(json.getString("tmpName"))) { |
| | | config.setTemplateName(json.getString("tmpName")); |
| | | } |
| | | if (json.containsKey("connectTimeout")) { |
| | | config.setConnectTimeout(json.getInteger("connectTimeout")); |
| | | } |
| | | if (json.containsKey("readTimeout")) { |
| | | config.setReadTimeout(json.getInteger("readTimeout")); |
| | | } |
| | | return config; |
| | | } |
| | | |
| | | /** |
| | | * å
³é®åæ°æ¯å¦é½å
¨ |
| | | */ |
| | | public boolean isValid() { |
| | | return StringUtils.isNotEmpty(baseUrl) && StringUtils.isNotEmpty(appId) |
| | | && StringUtils.isNotEmpty(appSecret); |
| | | } |
| | | |
| | | /** |
| | | * æ¼æ¥å¼æ¾æ¥å£å°å |
| | | */ |
| | | private String url(String path) { |
| | | String prefix = baseUrl == null ? "" : baseUrl.trim(); |
| | | while (prefix.endsWith("/")) { |
| | | prefix = prefix.substring(0, prefix.length() - 1); |
| | | } |
| | | if (prefix.endsWith(LS_WX_API_PREFIX)) { |
| | | return prefix + path; |
| | | } |
| | | if (prefix.endsWith(LS_WX_APP_PREFIX)) { |
| | | return prefix + "/cross/open-api" + path; |
| | | } |
| | | return prefix + LS_WX_API_PREFIX + path; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä¸½æ°´å¾®ä¿¡æ¶æ¯åæ°ï¼åæ®µä¸ sendMessage æ¥å£ä¸è´ |
| | | */ |
| | | @Data |
| | | public static class LsWxMessage { |
| | | /** |
| | | * æ¶æ¯æ¨¡æ¿IDï¼ä¸ºç©ºæ¶ä½¿ç¨é
ç½®ä¸çæ¨¡æ¿ |
| | | */ |
| | | private Long id; |
| | | /** |
| | | * æ£è
ç¼å· |
| | | */ |
| | | private String patientId; |
| | | /** |
| | | * æ£è
å§å |
| | | */ |
| | | private String patientName; |
| | | /** |
| | | * è¯ä»¶å· |
| | | */ |
| | | private String idCard; |
| | | /** |
| | | * ææºå·ç |
| | | */ |
| | | private String mobile; |
| | | /** |
| | | * å°±è¯å¡å· |
| | | */ |
| | | private String medCardNo; |
| | | /** |
| | | * éç¥æ é¢ |
| | | */ |
| | | private String titleName; |
| | | /** |
| | | * éç¥å
容 |
| | | */ |
| | | private String content; |
| | | /** |
| | | * 龿¥å°å |
| | | */ |
| | | private String url; |
| | | } |
| | | |
| | | /** |
| | | * 访é®ä»¤çç¼å对象 |
| | | */ |
| | | private static class LsWxToken { |
| | | private final String token; |
| | | private final long expireAt; |
| | | |
| | | private LsWxToken(String token, long expireAt) { |
| | | this.token = token; |
| | | this.expireAt = expireAt; |
| | | } |
| | | |
| | | private String getToken() { |
| | | return token; |
| | | } |
| | | |
| | | private long getExpireAt() { |
| | | return expireAt; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä¸½æ°´å¾®ä¿¡æ¶æ¯åéï¼å
è·å/å¤ç¨è®¿é®ä»¤çï¼åè°ç¨ sendMessageï¼ |
| | | * <p> |
| | | * æ¥å£æµç¨ï¼ |
| | | * <ol> |
| | | * <li>GET /app-api/cross/open-api/token?appId=...&appSecret=... è·å openApiToken</li> |
| | | * <li>POST /app-api/cross/open-api/sendMessageï¼è¯·æ±å¤´æºå¸¦ openApiToken</li> |
| | | * </ol> |
| | | * |
| | | * @param config 丽水微信é
ç½® |
| | | * @param message æ¶æ¯åæ° |
| | | * @return æ¥å£åå§ååºï¼æ¬å°æ ¡éªå¤±è´¥æ¶è¿å {"code":-1,"msg":"失败åå "} |
| | | */ |
| | | public static String sendChatLs(LsWxConfig config, LsWxMessage message) { |
| | | if (config == null || !config.isValid()) { |
| | | return lsWxFail("丽水微信é
ç½®ä¸å®æ´ï¼è¯·é
ç½® baseUrlãappIdãappSecret"); |
| | | } |
| | | if (message == null) { |
| | | return lsWxFail("ä¸½æ°´å¾®ä¿¡æ¶æ¯åæ°ä¸è½ä¸ºç©º"); |
| | | } |
| | | if (StringUtils.isEmpty(message.getMobile()) && StringUtils.isEmpty(message.getIdCard()) |
| | | && StringUtils.isEmpty(message.getPatientId()) && StringUtils.isEmpty(message.getMedCardNo())) { |
| | | return lsWxFail("ææºå·ç ãè¯ä»¶å·ãæ£è
ç¼å·ãå°±è¯å¡å·ä¸è½å
¨é¨ä¸ºç©º"); |
| | | } |
| | | try { |
| | | String token = getLsWxToken(config); |
| | | if (StringUtils.isEmpty(token)) { |
| | | return lsWxFail("è·å丽水微信访é®ä»¤ç失败"); |
| | | } |
| | | |
| | | // 模æ¿ä¼å
çº§ï¼æ¶æ¯æå® > é
ç½®æå® > ææ¨¡æ¿åç§°æ¥è¯¢ |
| | | Long templateId = message.getId(); |
| | | if (templateId == null) { |
| | | templateId = config.getTemplateId(); |
| | | } |
| | | if (templateId == null) { |
| | | templateId = findLsWxTemplateId(config, token, config.getTemplateName()); |
| | | if (templateId == null && StringUtils.isNotEmpty(config.getTemplateName())) { |
| | | // å¯è½æ¯ç¼åç令ç已失æï¼å·æ°ä»¤çåéè¯ä¸æ¬¡ |
| | | LS_WX_TOKEN_CACHE.remove(tokenCacheKey(config)); |
| | | token = getLsWxToken(config); |
| | | if (StringUtils.isNotEmpty(token)) { |
| | | templateId = findLsWxTemplateId(config, token, config.getTemplateName()); |
| | | } |
| | | } |
| | | } |
| | | if (templateId == null) { |
| | | return lsWxFail("æªæ¾å°ä¸½æ°´å¾®ä¿¡æ¶æ¯æ¨¡æ¿ï¼è¯·æå®æ¶æ¯æ¨¡æ¿IDææ¨¡æ¿åç§°"); |
| | | } |
| | | |
| | | JSONObject body = new JSONObject(true); |
| | | body.put("id", templateId); |
| | | body.put("patientId", nvl(message.getPatientId())); |
| | | body.put("patientName", nvl(message.getPatientName())); |
| | | body.put("idCard", nvl(message.getIdCard())); |
| | | body.put("mobile", message.getMobile()); |
| | | body.put("medCardNo", nvl(message.getMedCardNo())); |
| | | body.put("titleName", nvl(message.getTitleName())); |
| | | body.put("content", nvl(message.getContent())); |
| | | body.put("url", nvl(message.getUrl())); |
| | | String requestBody = body.toJSONString(); |
| | | |
| | | Map<String, String> headers = new HashMap<>(2); |
| | | headers.put(LS_WX_TOKEN_HEADER, token); |
| | | String sendUrl = config.url(LS_WX_SEND_PATH); |
| | | log.info("ã丽水微信ãåéæ¶æ¯ï¼å°åï¼{}ï¼åæ°ï¼{}", sendUrl, requestBody); |
| | | String resp = lsWxRequest("POST", sendUrl, requestBody, headers, config); |
| | | log.info("ã丽水微信ãåéç»æï¼{}", resp); |
| | | |
| | | // 令çå¤±ææ¶æ¸
é¤ç¼åå¹¶éè¯ä¸æ¬¡ |
| | | if (isLsWxTokenInvalid(resp)) { |
| | | LS_WX_TOKEN_CACHE.remove(tokenCacheKey(config)); |
| | | token = getLsWxToken(config); |
| | | if (StringUtils.isNotEmpty(token)) { |
| | | headers.put(LS_WX_TOKEN_HEADER, token); |
| | | resp = lsWxRequest("POST", sendUrl, requestBody, headers, config); |
| | | log.info("ã丽水微信ã令ç失æéè¯ç»æï¼{}", resp); |
| | | } |
| | | } |
| | | return resp; |
| | | } catch (Exception e) { |
| | | log.error("ã丽水微信ãåéå¼å¸¸ï¼ææºå·ï¼{}ï¼åå ï¼{}", message.getMobile(), e.getMessage(), e); |
| | | return lsWxFail("丽水微信åéå¼å¸¸ï¼" + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ä¸½æ°´å¾®ä¿¡æ¶æ¯åéï¼å¸¸ç¨éè®¿åæ°ï¼ |
| | | * |
| | | * @param config 丽水微信é
ç½® |
| | | * @param mobile ææºå·ç |
| | | * @param idCard è¯ä»¶å· |
| | | * @param patientName æ£è
å§å |
| | | * @param titleName éç¥æ é¢ |
| | | * @param content éç¥å
容 |
| | | * @param url 龿¥å°å |
| | | * @return æ¥å£åå§ååºï¼æ¬å°æ ¡éªå¤±è´¥æ¶è¿å {"code":-1,"msg":"失败åå "} |
| | | */ |
| | | public static String sendChatLs(LsWxConfig config, String mobile, String idCard, String patientName, |
| | | String titleName, String content, String url) { |
| | | LsWxMessage message = new LsWxMessage(); |
| | | message.setMobile(mobile); |
| | | message.setIdCard(idCard); |
| | | message.setPatientName(patientName); |
| | | message.setTitleName(titleName); |
| | | message.setContent(content); |
| | | message.setUrl(url); |
| | | return sendChatLs(config, message); |
| | | } |
| | | |
| | | /** |
| | | * ä¸½æ°´å¾®ä¿¡æ¶æ¯åéï¼é
ç½®ç´æ¥ä¼ sys_config ä¸ç jsonï¼ |
| | | * |
| | | * @param configJson 丽水微信é
ç½® json |
| | | * @param mobile ææºå·ç |
| | | * @param idCard è¯ä»¶å· |
| | | * @param patientName æ£è
å§å |
| | | * @param titleName éç¥æ é¢ |
| | | * @param content éç¥å
容 |
| | | * @param url 龿¥å°å |
| | | * @return æ¥å£åå§ååºï¼æ¬å°æ ¡éªå¤±è´¥æ¶è¿å {"code":-1,"msg":"失败åå "} |
| | | */ |
| | | public static String sendChatLs(String configJson, String mobile, String idCard, String patientName, |
| | | String titleName, String content, String url) { |
| | | return sendChatLs(LsWxConfig.of(configJson), mobile, idCard, patientName, titleName, content, url); |
| | | } |
| | | |
| | | /** |
| | | * è·å丽水微信访é®ä»¤çï¼ä¼å
使ç¨ç¼å |
| | | */ |
| | | private static String getLsWxToken(LsWxConfig config) throws IOException { |
| | | String cacheKey = tokenCacheKey(config); |
| | | long now = System.currentTimeMillis(); |
| | | LsWxToken cached = LS_WX_TOKEN_CACHE.get(cacheKey); |
| | | if (cached != null && cached.getExpireAt() > now) { |
| | | return cached.getToken(); |
| | | } |
| | | |
| | | String url = config.url(LS_WX_TOKEN_PATH) |
| | | + "?appId=" + encode(config.getAppId()) |
| | | + "&appSecret=" + encode(config.getAppSecret()); |
| | | String resp = lsWxRequest("GET", url, null, null, config); |
| | | log.info("ã丽水微信ãè·åtokenååºï¼{}", resp); |
| | | if (StringUtils.isEmpty(resp)) { |
| | | return null; |
| | | } |
| | | JSONObject json = JSON.parseObject(resp); |
| | | if (json == null || json.getIntValue("code") != 0) { |
| | | log.error("ã丽水微信ãè·åtoken失败ï¼{}", resp); |
| | | return null; |
| | | } |
| | | JSONObject data = json.getJSONObject("data"); |
| | | String token = data == null ? null : data.getString("token"); |
| | | if (StringUtils.isEmpty(token)) { |
| | | log.error("ã丽水微信ãè·åtoken失败ï¼ååºä¸æ²¡ætokenï¼{}", resp); |
| | | return null; |
| | | } |
| | | |
| | | long expiresIn = data.getLongValue("expiresIn"); |
| | | if (expiresIn <= 0) { |
| | | expiresIn = 3000L; |
| | | } |
| | | long expireAt = now + Math.max(expiresIn - LS_WX_TOKEN_REFRESH_AHEAD_SECONDS, 1L) * 1000L; |
| | | LS_WX_TOKEN_CACHE.put(cacheKey, new LsWxToken(token, expireAt)); |
| | | return token; |
| | | } |
| | | |
| | | /** |
| | | * æ ¹æ®æ¨¡æ¿åç§°æ¥è¯¢æ¨¡æ¿ID |
| | | */ |
| | | private static Long findLsWxTemplateId(LsWxConfig config, String token, String templateName) throws IOException { |
| | | if (StringUtils.isEmpty(templateName)) { |
| | | return null; |
| | | } |
| | | String url = config.url(LS_WX_TEMPLATE_PATH) + "?appId=" + encode(config.getAppId()); |
| | | Map<String, String> headers = new HashMap<>(2); |
| | | headers.put(LS_WX_TOKEN_HEADER, token); |
| | | String resp = lsWxRequest("GET", url, null, headers, config); |
| | | log.info("ã丽水微信ãè·å模æ¿ååºï¼{}", resp); |
| | | if (StringUtils.isEmpty(resp)) { |
| | | return null; |
| | | } |
| | | JSONObject json = JSON.parseObject(resp); |
| | | if (json == null || json.getIntValue("code") != 0) { |
| | | log.error("ã丽水微信ãè·å模æ¿å¤±è´¥ï¼{}", resp); |
| | | return null; |
| | | } |
| | | JSONArray data = json.getJSONArray("data"); |
| | | if (data == null) { |
| | | return null; |
| | | } |
| | | for (int i = 0; i < data.size(); i++) { |
| | | JSONObject item = data.getJSONObject(i); |
| | | if (item != null && templateName.equals(item.getString("tmpName"))) { |
| | | return item.getLong("id"); |
| | | } |
| | | } |
| | | log.error("ãä¸½æ°´å¾®ä¿¡ãæªæ¾å°æ¨¡æ¿ï¼{}", templateName); |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 夿ååºæ¯å¦å±äºä»¤ç失æ |
| | | */ |
| | | private static boolean isLsWxTokenInvalid(String resp) { |
| | | if (StringUtils.isEmpty(resp)) { |
| | | return false; |
| | | } |
| | | try { |
| | | JSONObject json = JSON.parseObject(resp); |
| | | if (json == null) { |
| | | return false; |
| | | } |
| | | int code = json.getIntValue("code"); |
| | | if (code == 401 || code == 403) { |
| | | return true; |
| | | } |
| | | String msg = json.getString("msg"); |
| | | if (StringUtils.isEmpty(msg)) { |
| | | return false; |
| | | } |
| | | String lower = msg.toLowerCase(); |
| | | return (lower.contains("token") || msg.contains("令ç")) |
| | | && (lower.contains("expire") || lower.contains("invalid") |
| | | || msg.contains("失æ") || msg.contains("è¿æ")); |
| | | } catch (Exception e) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * åé丽水微信 HTTP è¯·æ± |
| | | */ |
| | | private static String lsWxRequest(String method, String url, String body, |
| | | Map<String, String> headers, LsWxConfig config) throws IOException { |
| | | HttpURLConnection connection = null; |
| | | try { |
| | | connection = (HttpURLConnection) new URL(url).openConnection(); |
| | | connection.setRequestMethod(method); |
| | | connection.setConnectTimeout(config.getConnectTimeout() == null ? 5000 : config.getConnectTimeout()); |
| | | connection.setReadTimeout(config.getReadTimeout() == null ? 10000 : config.getReadTimeout()); |
| | | connection.setRequestProperty("Accept", "*/*"); |
| | | if (headers != null) { |
| | | for (Map.Entry<String, String> entry : headers.entrySet()) { |
| | | connection.setRequestProperty(entry.getKey(), entry.getValue()); |
| | | } |
| | | } |
| | | if (body != null) { |
| | | connection.setDoOutput(true); |
| | | connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); |
| | | try (OutputStream os = connection.getOutputStream()) { |
| | | os.write(body.getBytes(StandardCharsets.UTF_8)); |
| | | } |
| | | } |
| | | |
| | | int status = connection.getResponseCode(); |
| | | InputStream inputStream = status >= 200 && status < 300 |
| | | ? connection.getInputStream() : connection.getErrorStream(); |
| | | String resp = readStream(inputStream); |
| | | if (status < 200 || status >= 300) { |
| | | log.warn("ã丽水微信ã请æ±è¿åé200ï¼å°åï¼{}ï¼ç¶æç ï¼{}ï¼ååºï¼{}", url, status, resp); |
| | | } |
| | | return resp; |
| | | } finally { |
| | | if (connection != null) { |
| | | connection.disconnect(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 读åååºæµ |
| | | */ |
| | | private static String readStream(InputStream inputStream) throws IOException { |
| | | if (inputStream == null) { |
| | | return ""; |
| | | } |
| | | StringBuilder result = new StringBuilder(); |
| | | try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | result.append(line); |
| | | } |
| | | } |
| | | return result.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 令çç¼åkey |
| | | */ |
| | | private static String tokenCacheKey(LsWxConfig config) { |
| | | return config.getBaseUrl() + "|" + config.getAppId(); |
| | | } |
| | | |
| | | /** |
| | | * URLåæ°ç¼ç |
| | | */ |
| | | private static String encode(String value) { |
| | | if (value == null) { |
| | | return ""; |
| | | } |
| | | try { |
| | | return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); |
| | | } catch (Exception e) { |
| | | return value; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * null转空å符串 |
| | | */ |
| | | private static String nvl(String value) { |
| | | return value == null ? "" : value; |
| | | } |
| | | |
| | | /** |
| | | * æé æ¬å°å¤±è´¥ååº |
| | | */ |
| | | private static String lsWxFail(String msg) { |
| | | JSONObject json = new JSONObject(true); |
| | | json.put("code", -1); |
| | | json.put("msg", msg); |
| | | return json.toJSONString(); |
| | | } |
| | | } |