|
@@ -0,0 +1,198 @@
|
|
|
+package com.nokia.common.dingtalk;
|
|
|
+
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import org.springframework.core.io.FileSystemResource;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.http.HttpEntity;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
+import org.springframework.util.LinkedMultiValueMap;
|
|
|
+import org.springframework.util.MultiValueMap;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import com.nokia.common.dingtalk.exception.DingTalkApiException;
|
|
|
+
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+
|
|
|
+/*
|
|
|
+ * 钉钉API封装
|
|
|
+ * 采用RestTemplate调用API接口的方式进行封装
|
|
|
+ * 官方SDK分为新版与旧版,使用起来比较复杂
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+public class DingTalkUtil {
|
|
|
+
|
|
|
+ private static final String API_ACCESS_TOKEN_REDIS_KEY = "API_DING_TALK_ACCESS_TOKEN";
|
|
|
+ private static final String OAPI_ACCESS_TOKEN_REDIS_KEY = "OAPI_DING_TALK_ACCESS_TOKEN";
|
|
|
+
|
|
|
+ private final String appKey;
|
|
|
+ private final String appSecret;
|
|
|
+ private final String conversationId;
|
|
|
+ private final RestTemplate restTemplate;
|
|
|
+ private final RedisTemplate<String, Object> redisTemplate;
|
|
|
+ private final ObjectMapper objectMapper;
|
|
|
+
|
|
|
+ public DingTalkUtil(String appKey, String appSecret, String conversationId, RestTemplate restTemplate,
|
|
|
+ RedisTemplate<String, Object> redisTemplate, ObjectMapper objectMapper) {
|
|
|
+ this.appKey = appKey;
|
|
|
+ this.appSecret = appSecret;
|
|
|
+ this.conversationId = conversationId;
|
|
|
+ this.restTemplate = restTemplate;
|
|
|
+ this.redisTemplate = redisTemplate;
|
|
|
+ this.objectMapper = objectMapper;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 上传文件,返回mediaid
|
|
|
+ */
|
|
|
+ public String upload(String filePath) throws DingTalkApiException {
|
|
|
+ String url = "https://oapi.dingtalk.com/media/upload?access_token={0}";
|
|
|
+ HttpHeaders httpHeaders = new HttpHeaders();
|
|
|
+ httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
|
|
|
+
|
|
|
+ MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
|
|
|
+ FileSystemResource fileSystemResource = new FileSystemResource(filePath);
|
|
|
+ multiValueMap.add("media", fileSystemResource);
|
|
|
+ multiValueMap.add("type", "image");
|
|
|
+
|
|
|
+ HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(multiValueMap, httpHeaders);
|
|
|
+
|
|
|
+ ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class,
|
|
|
+ getOapiToken());
|
|
|
+ if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
|
|
+ try {
|
|
|
+ JsonNode node = objectMapper.readTree(responseEntity.getBody());
|
|
|
+ int errcode = node.get("errcode").asInt(-1);
|
|
|
+ if (errcode != 0) {
|
|
|
+ throw new DingTalkApiException(node.get("errmsg").asText());
|
|
|
+ } else {
|
|
|
+ return node.get("media_id").asText();
|
|
|
+ }
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ throw new DingTalkApiException(e.getMessage());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new DingTalkApiException(
|
|
|
+ "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 使用钉钉机器人发送消息
|
|
|
+ */
|
|
|
+ public void sendMsgWithRobot(Object msgParam) throws DingTalkApiException {
|
|
|
+ String url = "https://api.dingtalk.com/v1.0/robot/groupMessages/send";
|
|
|
+
|
|
|
+ HttpHeaders httpHeaders = new HttpHeaders();
|
|
|
+ httpHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ httpHeaders.set("x-acs-dingtalk-access-token", getApiToken());
|
|
|
+
|
|
|
+ Map<String, Object> request = new HashMap<>();
|
|
|
+ try {
|
|
|
+ request.put("msgParam", objectMapper.writeValueAsString(msgParam));
|
|
|
+ request.put("msgKey", "sampleImageMsg");
|
|
|
+ request.put("openConversationId", conversationId);
|
|
|
+ request.put("robotCode", appKey);
|
|
|
+
|
|
|
+ HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<>(request, httpHeaders);
|
|
|
+
|
|
|
+ ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
|
|
|
+
|
|
|
+ if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
|
|
+ JsonNode node = objectMapper.readTree(responseEntity.getBody());
|
|
|
+ if (node.get("processQueryKey") != null) {
|
|
|
+ return;
|
|
|
+ } else {
|
|
|
+ throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ throw new DingTalkApiException("入参格式错误--" + msgParam.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 获取api access token
|
|
|
+ * 钉钉的access token的有效期是2个小时,并且对获取token的接口有熔断机制
|
|
|
+ * 这里使用redis缓存token,缓存有效期设置为90分钟。
|
|
|
+ * 优先从redis获取,redis中不存在的情况下从
|
|
|
+ */
|
|
|
+ public String getApiToken() throws DingTalkApiException {
|
|
|
+ Object token = redisTemplate.opsForValue().get(API_ACCESS_TOKEN_REDIS_KEY);
|
|
|
+ if (token == null) {
|
|
|
+ // 调用api获取accessToken
|
|
|
+ String url = "https://api.dingtalk.com/v1.0/oauth2/accessToken";
|
|
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+
|
|
|
+ Map<String, Object> request = new HashMap<>();
|
|
|
+ request.put("appKey", appKey);
|
|
|
+ request.put("appSecret", appSecret);
|
|
|
+ HttpEntity<Map<String, Object>> httpEntity = new HttpEntity<Map<String, Object>>(request, headers);
|
|
|
+
|
|
|
+ // 发送请求
|
|
|
+ ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
|
|
|
+ log.info("请求新的api accessToken--{}", responseEntity.getStatusCode());
|
|
|
+ if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
|
|
+ try {
|
|
|
+ JsonNode node = objectMapper.readTree(responseEntity.getBody());
|
|
|
+ if (node.get("accessToken") != null) {
|
|
|
+ // 添加到缓存,缓存时间比返回的时间短10秒
|
|
|
+ token = node.get("accessToken").asText();
|
|
|
+ redisTemplate.boundValueOps(API_ACCESS_TOKEN_REDIS_KEY)
|
|
|
+ .set(token, Duration.ofSeconds(node.get("expireIn").asLong(3600L) - 10L));
|
|
|
+ } else {
|
|
|
+ throw new DingTalkApiException("api返回格式有误--" + responseEntity.getBody());
|
|
|
+ }
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ throw new DingTalkApiException(e.getMessage());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new DingTalkApiException(
|
|
|
+ "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return (String) token;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 获取oapi access token
|
|
|
+ */
|
|
|
+ public Object getOapiToken() throws DingTalkApiException {
|
|
|
+ Object token = redisTemplate.opsForValue().get(OAPI_ACCESS_TOKEN_REDIS_KEY);
|
|
|
+
|
|
|
+ if (token == null) {
|
|
|
+ String url = "https://oapi.dingtalk.com/gettoken?appkey={0}&appsecret={1}";
|
|
|
+
|
|
|
+ ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, appKey, appSecret);
|
|
|
+ log.info("请求新的oapi accessToken--{}--{}", responseEntity.getStatusCode(), responseEntity.getBody());
|
|
|
+ if (responseEntity.getStatusCode().is2xxSuccessful()) {
|
|
|
+ try {
|
|
|
+ JsonNode node = objectMapper.readTree(responseEntity.getBody());
|
|
|
+ int code = node.get("errcode").asInt(-1);
|
|
|
+ if (code == 0) {
|
|
|
+ // 添加到缓存
|
|
|
+ token = node.get("access_token").asText();
|
|
|
+ redisTemplate.boundValueOps(OAPI_ACCESS_TOKEN_REDIS_KEY)
|
|
|
+ .set(token, Duration.ofSeconds(node.get("expires_in").asLong(3600L) - 10L));
|
|
|
+ } else {
|
|
|
+ throw new DingTalkApiException(node.get("errmsg").asText());
|
|
|
+ }
|
|
|
+ } catch (JsonProcessingException e) {
|
|
|
+ throw new DingTalkApiException(e.getMessage());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ throw new DingTalkApiException(
|
|
|
+ "钉钉api调用失败--" + responseEntity.getStatusCodeValue() + "--" + responseEntity.getBody());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return (String) token;
|
|
|
+ }
|
|
|
+}
|