package com.ruoyi.project.utils; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.ruoyi.common.utils.http.HttpUtils; import com.taobao.api.ApiException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; /** * 钉钉API代理客户端 * 通过Nginx代理访问钉钉API,解决内网访问外网的问题 * * @author * @date 2025-01-01 */ @Slf4j @Component public class DingTalkProxyClient { @Value("${dingtalk.proxy.enabled:false}") private boolean proxyEnabled; @Value("${dingtalk.proxy.url:https://oapi.dingtalk.com}") private String proxyUrl; @Value("${dingAppid}") private String dingAppid; @Value("${dingAppSecret}") private String dingAppSecret; private String cachedAccessToken; private long tokenExpireTime; /** * 获取访问令牌 */ public String getAccessToken() throws ApiException { // 检查缓存的token是否仍然有效(预留30秒缓冲时间) if (cachedAccessToken != null && System.currentTimeMillis() < tokenExpireTime - 30000) { return cachedAccessToken; } String url; if (proxyEnabled) { url = proxyUrl + "/gettoken"; } else { url = "https://oapi.dingtalk.com/gettoken"; } Map params = new HashMap<>(); params.put("appkey", dingAppid); params.put("appsecret", dingAppSecret); params.put("grant_type", "client_credentials"); String paramString = buildParamString(params); String response = HttpUtils.sendGet(url, paramString); JSONObject result = JSON.parseObject(response); if (result.getInteger("errcode") == 0) { cachedAccessToken = result.getString("access_token"); // token通常有效期为7200秒,这里设置为7000秒后过期 tokenExpireTime = System.currentTimeMillis() + 7000 * 1000L; return cachedAccessToken; } else { throw new ApiException("获取钉钉访问令牌失败: " + result.getString("errmsg")); } } /** * 将Map参数转换为URL编码的字符串 */ private String buildParamString(Map params) { StringBuilder paramString = new StringBuilder(); if (params != null) { for (Map.Entry entry : params.entrySet()) { if (paramString.length() > 0) { paramString.append("&"); } try { paramString.append(URLEncoder.encode(entry.getKey(), "UTF-8")) .append("=") .append(URLEncoder.encode(entry.getValue() != null ? entry.getValue() : "", "UTF-8")); } catch (UnsupportedEncodingException e) { // 这种情况不应该发生,因为UTF-8是标准编码 paramString.append(entry.getKey()) .append("=") .append(entry.getValue() != null ? entry.getValue() : ""); } } } return paramString.toString(); } /** * 执行GET请求 */ public String executeGet(String apiPath, Map params) throws ApiException { String url; if (proxyEnabled) { url = proxyUrl + apiPath; } else { url = "https://oapi.dingtalk.com" + apiPath; } String paramString = buildParamString(params); return HttpUtils.sendGet(url, paramString); } /** * 执行POST请求,支持JSON Content-Type */ public String executePost(String apiPathWithParams, String params) throws ApiException { String url; if (proxyEnabled) { url = proxyUrl + apiPathWithParams; } else { url = "https://oapi.dingtalk.com" + apiPathWithParams; } return sendJsonPost(url, params); } /** * 发送JSON格式的POST请求 */ private String sendJsonPost(String url, String jsonData) throws ApiException { HttpURLConnection connection = null; try { URL obj = new URL(url); connection = (HttpURLConnection) obj.openConnection(); // 设置请求方法 connection.setRequestMethod("POST"); // 设置请求头 connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); connection.setRequestProperty("Accept", "application/json"); connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); // 发送POST请求 connection.setDoOutput(true); try (OutputStream os = connection.getOutputStream()) { byte[] input = jsonData.getBytes("utf-8"); os.write(input, 0, input.length); } // 读取响应 int responseCode = connection.getResponseCode(); StringBuilder response = new StringBuilder(); try (BufferedReader br = new BufferedReader( new InputStreamReader(connection.getInputStream(), "utf-8"))) { String responseLine; while ((responseLine = br.readLine()) != null) { response.append(responseLine.trim()); } } return response.toString(); } catch (Exception e) { throw new ApiException("POST请求失败: " + e.getMessage()); } finally { if (connection != null) { connection.disconnect(); } } } /** * 执行带访问令牌的GET请求 */ public String executeGetWithToken(String apiPath, Map params) throws ApiException { if (params == null) { params = new HashMap<>(); } params.put("access_token", getAccessToken()); return executeGet(apiPath, params); } /** * 执行带访问令牌的POST请求 */ public String executePostWithToken(String apiPath, String params) throws ApiException { String fullParams; if (params.contains("access_token=")) { fullParams = params; } else { // 对于JSON格式的请求体,access_token应该已经包含在params中 fullParams = params; } return executePost(apiPath, fullParams); } public boolean isProxyEnabled() { return proxyEnabled; } }