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 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 接口配置。 *

* 注意: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 普通短信发送(一对一/多对一模式) *

* 接口:POST {url},报文为 json 的 base64 编码。 *

* * @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 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 *

* 可以只填 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) *

* 接口流程: *

    *
  1. GET /app-api/cross/open-api/token?appId=...&appSecret=... 获取 openApiToken
  2. *
  3. POST /app-api/cross/open-api/sendMessage,请求头携带 openApiToken
  4. *
* * @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 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 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 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 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(); } }