|
|
|
@ -1,11 +1,12 @@ |
|
|
|
package com.project.ding.utils; |
|
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil; |
|
|
|
import com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketResponse; |
|
|
|
import com.aliyun.teautil.models.RuntimeOptions; |
|
|
|
import com.fasterxml.jackson.core.type.TypeReference; |
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
|
import com.github.tingyugetc520.ali.dingtalk.api.DtService; |
|
|
|
import com.github.tingyugetc520.ali.dingtalk.bean.department.DtDepart; |
|
|
|
|
|
|
|
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtCorpConversationMessage; |
|
|
|
import com.github.tingyugetc520.ali.dingtalk.bean.message.DtMessage; |
|
|
|
import com.github.tingyugetc520.ali.dingtalk.error.DtErrorException; |
|
|
|
@ -13,26 +14,49 @@ import com.google.common.collect.Lists; |
|
|
|
import com.google.gson.JsonObject; |
|
|
|
import com.jayway.jsonpath.JsonPath; |
|
|
|
import com.project.appeal.domain.dto.AppealDTO; |
|
|
|
import com.project.ding.config.DingProperties; |
|
|
|
import com.project.ding.domain.dto.DepartmentDTO; |
|
|
|
import com.project.ding.domain.dto.DingUserDTO; |
|
|
|
import com.project.ding.domain.dto.UserDTO; |
|
|
|
import io.vavr.control.Try; |
|
|
|
import jakarta.servlet.http.HttpServletRequest; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.data.redis.core.StringRedisTemplate; |
|
|
|
import org.springframework.scheduling.annotation.Async; |
|
|
|
import org.springframework.stereotype.Component; |
|
|
|
import com.aliyun.dingtalkoauth2_1_0.Client; |
|
|
|
import com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketHeaders; |
|
|
|
|
|
|
|
import java.net.URLDecoder; |
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.security.MessageDigest; |
|
|
|
import java.time.LocalDateTime; |
|
|
|
import java.time.format.DateTimeFormatter; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Date; |
|
|
|
import java.util.List; |
|
|
|
import java.util.*; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.stream.Collectors; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Component |
|
|
|
public class DingUtil { |
|
|
|
@Autowired |
|
|
|
private DtService dtService; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private StringRedisTemplate redisTemplate; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private Client dingTalkClient; // 之前创建好的新版 SDK Client
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
private DingProperties dingTalkProperties; |
|
|
|
|
|
|
|
|
|
|
|
private static final String TICKET_KEY = "dingtalk:jsapi_ticket"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public List<DepartmentDTO> getAllDepartment() throws Exception { |
|
|
|
List<DtDepart> list = dtService.getDepartmentService().list(null, true); |
|
|
|
@ -42,10 +66,112 @@ public class DingUtil { |
|
|
|
BeanUtil.copyProperties(dtDepart , departmentDTO); |
|
|
|
res.add(departmentDTO); |
|
|
|
} |
|
|
|
|
|
|
|
return res; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public String getJsapiTicket(String url) throws Exception { |
|
|
|
// 1. 先从 Redis 拿
|
|
|
|
String ticket = redisTemplate.opsForValue().get(TICKET_KEY + url); |
|
|
|
if (ticket != null) { |
|
|
|
return ticket; |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 如果 Redis 没了,加锁去钉钉查,防止高并发下多个请求同时冲击钉钉接口
|
|
|
|
synchronized (TICKET_KEY + url) { |
|
|
|
// 二次检查
|
|
|
|
ticket = redisTemplate.opsForValue().get(TICKET_KEY + url); |
|
|
|
if (ticket != null) return ticket; |
|
|
|
|
|
|
|
// 3. 获取 AccessToken (也建议通过 Redis 拿,逻辑同下)
|
|
|
|
String accessToken = dtService.getAccessToken(); |
|
|
|
|
|
|
|
// 4. 调用新版 SDK 获取 Ticket
|
|
|
|
CreateJsapiTicketHeaders createJsapiTicketHeaders = new CreateJsapiTicketHeaders(); |
|
|
|
createJsapiTicketHeaders.xAcsDingtalkAccessToken = accessToken; |
|
|
|
String jsapiTicket = ""; |
|
|
|
try { |
|
|
|
CreateJsapiTicketResponse jsapiTicketResponse = dingTalkClient.createJsapiTicketWithOptions(createJsapiTicketHeaders, new RuntimeOptions()); |
|
|
|
// 5. 存入 Redis,设置 7000 秒过期(留出 200 秒冗余)
|
|
|
|
jsapiTicket = jsapiTicketResponse.getBody().getJsapiTicket(); |
|
|
|
redisTemplate.opsForValue().set(TICKET_KEY + url, jsapiTicket |
|
|
|
, 7000, TimeUnit.SECONDS); |
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
// 如果 V2 接口报错,部分企业应用可能需要降级调用 V1 接口(钉钉目前的过渡期特征)
|
|
|
|
throw new Exception("获取JsapiTicket失败: " + e.getMessage()); |
|
|
|
} |
|
|
|
|
|
|
|
return jsapiTicket; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public Map<String, String> getConfig(HttpServletRequest request) { |
|
|
|
String urlString = request.getRequestURL().toString(); |
|
|
|
String queryString = request.getQueryString(); |
|
|
|
|
|
|
|
String queryStringEncode = null; |
|
|
|
String url; |
|
|
|
if (queryString != null) { |
|
|
|
queryStringEncode = URLDecoder.decode(queryString); |
|
|
|
url = urlString + "?" + queryStringEncode; |
|
|
|
} else { |
|
|
|
url = urlString; |
|
|
|
} |
|
|
|
|
|
|
|
String nonceStr = "abcdefg"; |
|
|
|
long timeStamp = System.currentTimeMillis() / 1000; |
|
|
|
String signedUrl = url; |
|
|
|
String ticket = null; |
|
|
|
String signature = null; |
|
|
|
String agentid = null; |
|
|
|
|
|
|
|
try { |
|
|
|
String path = request.getServletContext().getRealPath("/"); |
|
|
|
ticket = getJsapiTicket(path); |
|
|
|
signature = sign(ticket, nonceStr, timeStamp, signedUrl); |
|
|
|
agentid = dingTalkProperties.getAgentId(); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
// TODO Auto-generated catch block
|
|
|
|
e.printStackTrace(); |
|
|
|
} |
|
|
|
Map<String,String> resMap = new HashMap<>(); |
|
|
|
resMap.put("jsticket" , ticket); |
|
|
|
resMap.put("signature" , signature); |
|
|
|
resMap.put("nonceStr" , nonceStr); |
|
|
|
resMap.put("corpId" , dingTalkProperties.getCorpId()); |
|
|
|
resMap.put("agentid" , agentid); |
|
|
|
|
|
|
|
return resMap; |
|
|
|
} |
|
|
|
|
|
|
|
public static String sign(String jsticket, String nonceStr, long timeStamp, String url) throws Exception { |
|
|
|
// 1. 拼接字符串。注意:顺序必须固定,参数名必须全小写
|
|
|
|
// 提示:url 建议直接用前端传过来的原始值(去掉 # 之后的部分)
|
|
|
|
String plain = "jsapi_ticket=" + jsticket + |
|
|
|
"&noncestr=" + nonceStr + |
|
|
|
"×tamp=" + timeStamp + |
|
|
|
"&url=" + url; |
|
|
|
|
|
|
|
try { |
|
|
|
// 2. 钉钉 JSAPI 签名必须使用 SHA-1
|
|
|
|
MessageDigest sha1 = MessageDigest.getInstance("SHA-1"); |
|
|
|
|
|
|
|
// 3. 计算哈希值
|
|
|
|
byte[] digest = sha1.digest(plain.getBytes(StandardCharsets.UTF_8)); |
|
|
|
|
|
|
|
// 4. JDK 17 优雅写法:将 byte 数组转为 16 进制字符串
|
|
|
|
return HexFormat.of().formatHex(digest); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
throw new RuntimeException("计算钉钉签名失败", e); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public String getUserIdByCode(String code) { |
|
|
|
return Try.of(() -> dtService.getOauth2Service().getUserInfo(code).getUserId()) |
|
|
|
.getOrElse(""); |
|
|
|
|