REQ-2211: 获取钉钉config

This commit is contained in:
yangchen 2024-04-07 17:21:22 +08:00
parent 075f64ca2c
commit 9d21a6c713
8 changed files with 282 additions and 0 deletions

View File

@ -0,0 +1,17 @@
package cn.axzo.riven.client.feign;
import cn.axzo.riven.client.req.GetDingDingConfigReq;
import cn.axzo.riven.client.res.GetDingDingConfigRes;
import cn.azxo.framework.common.model.CommonResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
/**
* @author yangchen@axzo.cn
*/
@FeignClient(name = "riven", url = "${axzo.service.riven:http://riven:8080}")
public interface DingDingApi {
@PostMapping("/api/getDingDingConfig")
CommonResponse<GetDingDingConfigRes> getDingDingConfig(@RequestBody GetDingDingConfigReq req);
}

View File

@ -0,0 +1,32 @@
package cn.axzo.riven.client.req;
import lombok.Data;
import java.util.List;
/**
* @author yangchen@axzo.cn
*/
@Data
public class GetDingDingConfigReq {
/**
* 目前后端写死
*/
private Long ouId;
/**
* 当前网页的URL不包含#及其后面部分
*/
private String url;
/**
* 企业ID
*/
private String appId;
/**
* 选填0表示微应用的jsapi,1表示服务窗的jsapi不填默认为0该参数从dingtalk.js的0.8.3版本开始支持
*/
private Integer type;
/**
* 必填需要使用的jsapi列表注意不要带dd
*/
private List<String> jsApiList;
}

View File

@ -0,0 +1,40 @@
package cn.axzo.riven.client.res;
import lombok.Data;
import java.util.List;
/**
* @author yangchen@axzo.cn
*/
@Data
public class GetDingDingConfigRes {
/**
* 授权企业的agentid
*/
private String appId;
/**
* 企业ID
*/
private String corpId;
/**
* 生成签名的时间戳
*/
private Long timeStamp;
/**
* 自定义固定字符串
*/
private String nonceStr;
/**
* 签名
*/
private String signature;
/**
* 0表示微应用的jsapi,1表示服务窗的jsapi不填默认为0该参数从dingtalk.js的0.8.3版本开始支持
*/
private Integer type;
/**
* 需要使用的jsapi列表注意不要带dd
*/
private List<String> jsApiList;
}

View File

@ -8,5 +8,9 @@ public class RedisKeyConstant {
* 企业内部应用的accessToken * 企业内部应用的accessToken
*/ */
public static final String DING_DING_V2_ACCESS_TOKEN_KEY = "DING:DING:V2:ACCESS_TOKEN_KEY:%s"; public static final String DING_DING_V2_ACCESS_TOKEN_KEY = "DING:DING:V2:ACCESS_TOKEN_KEY:%s";
/**
* 获取jsapiTicket
*/
public static final String DING_DING_V2_JSAPI_TICKET = "DING:DING:V2:JSAPI_TICKET:%s";
} }

View File

@ -0,0 +1,87 @@
package cn.axzo.riven.common.util;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Formatter;
import java.util.Random;
/**
* @author yangchen@axzo.cn
*/
public class DdConfigSign {
/**
* 计算dd.config的签名参数
*
* @param jsticket 通过微应用appKey获取的jsticket
* @param nonceStr 自定义固定字符串
* @param timeStamp 当前时间戳
* @param url 调用dd.config的当前页面URL
* @return
* @throws Exception
*/
public static String sign(String jsticket, String nonceStr, long timeStamp, String url) throws Exception {
String plain = "jsapi_ticket=" + jsticket + "&noncestr=" + nonceStr + "&timestamp=" + timeStamp
+ "&url=" + decodeUrl(url);
try {
MessageDigest sha1 = MessageDigest.getInstance("SHA-256");
sha1.reset();
sha1.update(plain.getBytes(StandardCharsets.UTF_8));
return byteToHex(sha1.digest());
} catch (Exception e) {
throw new Exception(e.getMessage());
}
}
/**
* 字节数组转化成十六进制字符串
*/
private static String byteToHex(final byte[] hash) {
Formatter formatter = new Formatter();
for (byte b : hash) {
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
/**
* 因为ios端上传递的url是encode过的android是原始的url开发者使用的也是原始url,
* 所以需要把参数进行一般urlDecode
*
* @param url
* @return
* @throws Exception
*/
private static String decodeUrl(String url) throws Exception {
URL urler = new URL(url);
StringBuilder urlBuffer = new StringBuilder();
urlBuffer.append(urler.getProtocol());
urlBuffer.append(":");
if (urler.getAuthority() != null && urler.getAuthority().length() > 0) {
urlBuffer.append("//");
urlBuffer.append(urler.getAuthority());
}
if (urler.getPath() != null) {
urlBuffer.append(urler.getPath());
}
if (urler.getQuery() != null) {
urlBuffer.append('?');
urlBuffer.append(URLDecoder.decode(urler.getQuery(), "utf-8"));
}
return urlBuffer.toString();
}
public static String getRandomStr(int count) {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < count; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
}

View File

@ -0,0 +1,23 @@
package cn.axzo.riven.controller;
import cn.axzo.riven.client.feign.DingDingApi;
import cn.axzo.riven.client.req.GetDingDingConfigReq;
import cn.axzo.riven.client.res.GetDingDingConfigRes;
import cn.axzo.riven.service.DingDingService;
import cn.azxo.framework.common.model.CommonResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
/**
* @author yangchen@axzo.cn
*/
@RestController
@RequiredArgsConstructor
public class DingDingController implements DingDingApi {
private final DingDingService dingDingService;
@Override
public CommonResponse<GetDingDingConfigRes> getDingDingConfig(GetDingDingConfigReq req) {
return CommonResponse.success(dingDingService.getDingDingConfig(req));
}
}

View File

@ -4,10 +4,13 @@ import cn.axzo.riven.common.constants.RedisKeyConstant;
import cn.axzo.riven.common.util.Throws; import cn.axzo.riven.common.util.Throws;
import cn.azxo.framework.common.utils.LogUtil; import cn.azxo.framework.common.utils.LogUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketResponse;
import com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketResponseBody;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest; import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenRequest;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse; import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponse;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponseBody; import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponseBody;
import com.aliyun.tea.TeaException; import com.aliyun.tea.TeaException;
import com.aliyun.teautil.models.RuntimeOptions;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
@ -53,6 +56,28 @@ public class DingDingNewSdkManger {
return res.getBody(); return res.getBody();
} }
/**
* 获取jsapiTicket
*/
public CreateJsapiTicketResponseBody createJsapiTicket(String appKey, String accessToken) {
String key = String.format(RedisKeyConstant.DING_DING_V2_JSAPI_TICKET, appKey);
String tokenStr = redisTemplate.opsForValue().get(key);
if (StringUtils.hasText(tokenStr)) {
return JSONUtil.toBean(tokenStr, CreateJsapiTicketResponseBody.class);
}
com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketHeaders createJsapiTicketHeaders = new com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketHeaders();
createJsapiTicketHeaders.xAcsDingtalkAccessToken = accessToken;
try {
CreateJsapiTicketResponse res = dingTalkOauthClient.createJsapiTicketWithOptions(createJsapiTicketHeaders, new RuntimeOptions());
String data = JSONUtil.toJsonStr(res.getBody());
redisTemplate.opsForValue().set(key, data, res.getBody().expireIn - 200L, TimeUnit.SECONDS);
return res.getBody();
} catch (Exception e) {
logErr("createJsapiTicket", e);
throw Throws.bizDefaultException();
}
}
public void logErr(String api, Exception e) { public void logErr(String api, Exception e) {
TeaException err = new TeaException(e.getMessage(), e); TeaException err = new TeaException(e.getMessage(), e);
if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) { if (!com.aliyun.teautil.Common.empty(err.code) && !com.aliyun.teautil.Common.empty(err.message)) {

View File

@ -0,0 +1,54 @@
package cn.axzo.riven.service;
import cn.axzo.framework.domain.web.code.BaseCode;
import cn.axzo.riven.client.req.GetDingDingConfigReq;
import cn.axzo.riven.client.res.GetDingDingConfigRes;
import cn.axzo.riven.common.util.DdConfigSign;
import cn.axzo.riven.common.util.Throws;
import cn.axzo.riven.manger.DingDingNewSdkManger;
import cn.axzo.riven.repository.entity.ThirdPartyCredential;
import cn.axzo.riven.repository.service.ThirdPartyCredentialDao;
import cn.azxo.framework.common.utils.LogUtil;
import com.aliyun.dingtalkoauth2_1_0.models.CreateJsapiTicketResponseBody;
import com.aliyun.dingtalkoauth2_1_0.models.GetAccessTokenResponseBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author yangchen@axzo.cn
*/
@Service
public class DingDingService {
@Autowired
private ThirdPartyCredentialDao thirdPartyCredentialDao;
@Autowired
private DingDingNewSdkManger dingDingNewSdkManger;
public GetDingDingConfigRes getDingDingConfig(GetDingDingConfigReq req) {
ThirdPartyCredential dingTalk = thirdPartyCredentialDao.getByOuIdAndChannel(req.getOuId(), "DingTalk");
if (dingTalk == null) {
throw Throws.bizException(BaseCode.FORBIDDEN, "企业未配置钉钉");
}
GetAccessTokenResponseBody accessToken = dingDingNewSdkManger.getAccessToken(dingTalk.getAppKey(), dingTalk.getAppSecret());
CreateJsapiTicketResponseBody jsapiTicket = dingDingNewSdkManger.createJsapiTicket(dingTalk.getAppKey(), accessToken.getAccessToken());
String randomStr = DdConfigSign.getRandomStr(10);
long ts = System.currentTimeMillis() / 1000;
String sign;
try {
sign = DdConfigSign.sign(jsapiTicket.jsapiTicket, randomStr, ts, req.getUrl());
} catch (Exception e) {
LogUtil.error(LogUtil.ErrorLevel.P0, LogUtil.ErrorType.ERROR_THIRD_SERVICE, "获取钉钉配置计算签名异常", e);
throw Throws.bizDefaultException();
}
GetDingDingConfigRes getDingDingConfigRes = new GetDingDingConfigRes();
getDingDingConfigRes.setAppId(req.getAppId());
getDingDingConfigRes.setCorpId(dingTalk.getAppKey());
getDingDingConfigRes.setTimeStamp(ts);
getDingDingConfigRes.setNonceStr(randomStr);
getDingDingConfigRes.setSignature(sign);
getDingDingConfigRes.setType(req.getType());
getDingDingConfigRes.setJsApiList(req.getJsApiList());
return getDingDingConfigRes;
}
}