|
|
@@ -18,15 +18,20 @@ import com.fs.qw.param.QwAutoRulesTagsParams;
|
|
|
import com.fs.qw.service.*;
|
|
|
import com.fs.qwApi.Result.QwGroupChatDetailsResult;
|
|
|
import com.fs.qwApi.Result.QwOpenidResult;
|
|
|
+import com.fs.qwApi.Result.QwPermanentCodeResult;
|
|
|
import com.fs.qwApi.domain.QwResult;
|
|
|
import com.fs.qwApi.param.QwEditUserTagParam;
|
|
|
import com.fs.qwApi.param.QwOpenidByUserParams;
|
|
|
+import com.fs.qwApi.param.QwPermanentCodeParam;
|
|
|
+import com.fs.qwApi.param.QwSuiteTokenParams;
|
|
|
import com.fs.qwApi.util.AesException;
|
|
|
+import com.fs.qwApi.util.XMLParse;
|
|
|
import com.fs.voice.utils.StringUtil;
|
|
|
import com.google.gson.JsonObject;
|
|
|
import com.google.gson.JsonParser;
|
|
|
import com.tencent.wework.Finance;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang.StringUtils;
|
|
|
import org.json.JSONObject;
|
|
|
import org.redisson.api.RLock;
|
|
|
import org.redisson.api.RedissonClient;
|
|
|
@@ -37,11 +42,15 @@ import org.springframework.stereotype.Service;
|
|
|
import org.w3c.dom.Document;
|
|
|
import org.w3c.dom.Element;
|
|
|
import org.w3c.dom.NodeList;
|
|
|
+import org.xml.sax.InputSource;
|
|
|
|
|
|
import javax.crypto.Cipher;
|
|
|
+import javax.xml.parsers.DocumentBuilder;
|
|
|
+import javax.xml.parsers.DocumentBuilderFactory;
|
|
|
import java.io.File;
|
|
|
import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
|
+import java.io.StringReader;
|
|
|
import java.nio.charset.StandardCharsets;
|
|
|
import java.security.KeyFactory;
|
|
|
import java.security.PrivateKey;
|
|
|
@@ -97,12 +106,125 @@ public class QwDataCallbackService {
|
|
|
@Autowired
|
|
|
private ILeadService leadService;
|
|
|
|
|
|
+ /**
|
|
|
+ * 代开发模板默认 suite_id(回调 XML 里通常也会带 <SuiteId>,优先用 XML)
|
|
|
+ */
|
|
|
+ private static final String DEFAULT_SUITE_ID = "dk348a71ab9e97e43a";
|
|
|
+ /**
|
|
|
+ * 代开发模板 suite_secret(与服务商后台一致)
|
|
|
+ */
|
|
|
+ private static final String DEFAULT_SUITE_SECRET = "bUdYYGwWPSz4Kg11O5qQfrsIFwlGRh4W-JsJrlVy3A0";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 使用临时授权码 auth_code 调用 get_permanent_code,回写当前 qw_company 的 permanent_code。
|
|
|
+ * 适用于:首次安装 create_auth、重置永久授权码 reset_permanent_code 等带 AuthCode 的指令回调。
|
|
|
+ */
|
|
|
+ private void syncPermanentCodeByAuthCode(QwCompany qwCompany, String corpId, String authCode, String suiteIdFromXml) {
|
|
|
+ if (StringUtils.isBlank(authCode)) {
|
|
|
+ logger.warn("syncPermanentCodeByAuthCode authCode 为空, corpId={}", corpId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String effectiveSuiteId = StringUtils.isNotBlank(suiteIdFromXml) ? suiteIdFromXml.trim() : DEFAULT_SUITE_ID;
|
|
|
+
|
|
|
+ QwSuiteTokenParams qwSuiteTokenParams = new QwSuiteTokenParams();
|
|
|
+ qwSuiteTokenParams.setSuite_id(effectiveSuiteId);
|
|
|
+ qwSuiteTokenParams.setSuite_secret(DEFAULT_SUITE_SECRET);
|
|
|
+ String cacheKey = "qw_suite_ticket:" + corpId;
|
|
|
+ String suiteTicket = redisCache.getCacheObject(cacheKey);
|
|
|
+ if (StringUtils.isBlank(suiteTicket)) {
|
|
|
+ logger.error("syncPermanentCodeByAuthCode 未找到 suite_ticket(Redis key={}),无法换取 suite_access_token,请确认 suite_ticket 回调已正常写入", cacheKey);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ qwSuiteTokenParams.setSuite_ticket(suiteTicket);
|
|
|
+
|
|
|
+ String suiteAccessToken = qwApiService.getSuiteToken(qwSuiteTokenParams);
|
|
|
+ if (StringUtils.isBlank(suiteAccessToken)) {
|
|
|
+ logger.error("syncPermanentCodeByAuthCode suite_access_token 为空, corpId={}, suiteId={}", corpId, effectiveSuiteId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QwPermanentCodeParam qwPermanentCodeParam = new QwPermanentCodeParam();
|
|
|
+ qwPermanentCodeParam.setAuth_code(authCode.trim());
|
|
|
+ QwPermanentCodeResult permanentCode = qwApiService.getPermanentCode(qwPermanentCodeParam, suiteAccessToken);
|
|
|
+ if (permanentCode == null) {
|
|
|
+ logger.error("getPermanentCode 返回 null, corpId={}", corpId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (permanentCode.getErrcode() != 0) {
|
|
|
+ logger.error("getPermanentCode 失败 corpId={}, errcode={}, errmsg={}", corpId, permanentCode.getErrcode(), permanentCode.getErrmsg());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(permanentCode.getPermanent_code())) {
|
|
|
+ logger.error("getPermanentCode 成功但 permanent_code 为空, corpId={}", corpId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ QwCompany map = new QwCompany();
|
|
|
+ map.setId(qwCompany.getId());
|
|
|
+ map.setPermanentCode(permanentCode.getPermanent_code());
|
|
|
+ int rows = qwCompanyMapper.updateQwCompany(map);
|
|
|
+ logger.info("已更新 qw_company 永久授权码, corpId={}, qwCompanyId={}, rows={}", corpId, qwCompany.getId(), rows);
|
|
|
+ }
|
|
|
+
|
|
|
@Async
|
|
|
public void dataCallback( Document document,String corpId,QwCompany qwCompany) throws Exception {
|
|
|
|
|
|
|
|
|
Element root = document.getDocumentElement();
|
|
|
+
|
|
|
+ // 先判断是否是 suite_ticket 类型的回调
|
|
|
+ NodeList infoTypeNode = root.getElementsByTagName("InfoType");
|
|
|
+ if (infoTypeNode.getLength() > 0) {
|
|
|
+ String infoType = infoTypeNode.item(0).getTextContent();
|
|
|
+ if ("suite_ticket".equals(infoType)) {
|
|
|
+ // 处理 suite_ticket 推送
|
|
|
+ NodeList suiteTicketNode = root.getElementsByTagName("SuiteTicket");
|
|
|
+ String suiteTicket = suiteTicketNode.item(0).getTextContent();
|
|
|
+ // 保存 suite_ticket,用于获取永久授权码等
|
|
|
+ String cacheKey = "qw_suite_ticket:" + corpId;
|
|
|
+ // 企微约每 10~30 分钟推送;适当延长避免 reset_permanent_code 等场景下 ticket 已过期
|
|
|
+ redisCache.setCacheObject(cacheKey, suiteTicket, 6, TimeUnit.HOURS);
|
|
|
+ logger.info("收到 suite_ticket: {},corpId:{}", suiteTicket,corpId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 重置永久授权码 / 企业首次授权安装:用 AuthCode 换 permanent_code 并更新库
|
|
|
+ if ("reset_permanent_code".equals(infoType) || "create_auth".equals(infoType)) {
|
|
|
+ NodeList authCodeList = root.getElementsByTagName("AuthCode");
|
|
|
+ if (authCodeList.getLength() == 0) {
|
|
|
+ logger.error("{} 回调缺少 AuthCode, corpId={}", infoType, corpId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String authCode = authCodeList.item(0).getTextContent();
|
|
|
+ NodeList suiteIdNodes = root.getElementsByTagName("SuiteId");
|
|
|
+ String suiteIdXml = suiteIdNodes.getLength() > 0 ? suiteIdNodes.item(0).getTextContent() : null;
|
|
|
+ logger.info("收到 {} 回调, corpId={}, suiteId={}", infoType, corpId, suiteIdXml);
|
|
|
+ syncPermanentCodeByAuthCode(qwCompany, corpId, authCode, suiteIdXml);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ NodeList authCodeNode = root.getElementsByTagName("AuthCode");
|
|
|
+ if (authCodeNode.getLength() > 0) {
|
|
|
+ String authCodeTemp = authCodeNode.item(0).getTextContent();
|
|
|
+ if (StringUtils.isNotBlank(authCodeTemp)) {
|
|
|
+ NodeList suiteIdNodes = root.getElementsByTagName("SuiteId");
|
|
|
+ String suiteIdXml = suiteIdNodes.getLength() > 0 ? suiteIdNodes.item(0).getTextContent() : null;
|
|
|
+ syncPermanentCodeByAuthCode(qwCompany, corpId, authCodeTemp, suiteIdXml);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
NodeList msgTypeNode = root.getElementsByTagName("MsgType");
|
|
|
+
|
|
|
+ // 如果连 MsgType 都没有,说明是其他类型回调,直接返回
|
|
|
+ if (msgTypeNode.getLength() == 0) {
|
|
|
+ logger.error("未知回调类型,无 MsgType");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
String msgType = msgTypeNode.item(0).getTextContent();
|
|
|
if(msgType.equals("event")){
|
|
|
NodeList eventNode = root.getElementsByTagName("Event");
|
|
|
@@ -147,7 +269,7 @@ public class QwDataCallbackService {
|
|
|
qwUser.setCorpId(corpId);
|
|
|
|
|
|
if (qw==null){
|
|
|
- String serverQwUserName = qwApiService.getServerQwUserName(corpId, qwCompany.getOpenSecret(), userID);
|
|
|
+ String serverQwUserName = qwApiService.getServerQwUserName(corpId, qwCompany.getOpenSecret(), userID,qwCompany.getPermanentCode());
|
|
|
qwUser.setQwUserId(userID);
|
|
|
qwUser.setDepartment(root.getElementsByTagName("Department").item(0).getTextContent().toString());
|
|
|
qwUser.setQwUserName(serverQwUserName);
|
|
|
@@ -874,4 +996,152 @@ public class QwDataCallbackService {
|
|
|
return s;
|
|
|
}
|
|
|
|
|
|
+ public String dataCallbackServerAuth(String sVerifyMsgSig,String sVerifyTimeStamp,String sVerifyNonce,String sData,String qwCorpID) {
|
|
|
+
|
|
|
+ QwCompany qwCompany= qwCompanyMapper.selectQwCompanyByCorpId(qwCorpID);
|
|
|
+ String token = qwCompany.getToken();
|
|
|
+ String encodingAESKey = qwCompany.getEncodingAesKey();
|
|
|
+ String result = "error";
|
|
|
+ com.fs.qwApi.util.WXBizMsgCrypt wxcpt = null;
|
|
|
+ try {
|
|
|
+ Object[] encrypt = XMLParse.extract(sData);
|
|
|
+ String sSuiteid = (String) encrypt[2];
|
|
|
+ wxcpt = new com.fs.qwApi.util.WXBizMsgCrypt(token, encodingAESKey, sSuiteid);
|
|
|
+ }catch (AesException E){
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ try{
|
|
|
+ String sMsg = wxcpt.DecryptMsg(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, sData);
|
|
|
+// logger.info("企业微信回调事件:ServerAuth"+sMsg);
|
|
|
+ // 加密成功
|
|
|
+ // TODO: 解析出明文xml标签的内容进行处理
|
|
|
+ // For example:
|
|
|
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
|
|
+ DocumentBuilder db = dbf.newDocumentBuilder();
|
|
|
+ StringReader sr = new StringReader(sMsg);
|
|
|
+ InputSource is = new InputSource(sr);
|
|
|
+ Document document = db.parse(is);
|
|
|
+
|
|
|
+ Element root = document.getDocumentElement();
|
|
|
+ String msgType = root.getElementsByTagName("InfoType").item(0).getTextContent();
|
|
|
+
|
|
|
+ //10分钟推送一次的suite_ticket
|
|
|
+ if(msgType.equals("suite_ticket")){
|
|
|
+
|
|
|
+ String SuiteTicket =root.getElementsByTagName("SuiteTicket").item(0).getTextContent();
|
|
|
+ String SuiteId =root.getElementsByTagName("SuiteId").item(0).getTextContent();
|
|
|
+
|
|
|
+ switch (SuiteId) {
|
|
|
+ case "dk348a71ab9e97e43a":
|
|
|
+ //代开发应用模板信息
|
|
|
+ redisCache.setCacheObject("qw_suite_ticket:" + qwCorpID,SuiteTicket);
|
|
|
+ break;
|
|
|
+ case "ww0551047d6db89f0a":
|
|
|
+ //登录授权
|
|
|
+// redisTemplate.opsForValue().set("qwServer:signServer_suite_ticket",SuiteTicket);
|
|
|
+ redisCache.setCacheObject("qwServer:signServer_suite_ticket",SuiteTicket);
|
|
|
+ break;
|
|
|
+ case "ww378fbd25f8e3e9f8":
|
|
|
+ // 通讯录编辑授权
|
|
|
+// redisTemplate.opsForValue().set("qwServer:bookServer_suite_ticket",SuiteTicket);
|
|
|
+ redisCache.setCacheObject("qwServer:bookServer_suite_ticket",SuiteTicket);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //取消授权事件(先放着)
|
|
|
+ if (msgType.equals("cancel_auth")){
|
|
|
+//
|
|
|
+// String SuiteId = root.getElementsByTagName("SuiteId").item(0).getTextContent();
|
|
|
+// String AuthCorpId = root.getElementsByTagName("AuthCorpId").item(0).getTextContent();
|
|
|
+ }
|
|
|
+ //授权
|
|
|
+ if(msgType.equals("create_auth")||msgType.equals("reset_permanent_code")){
|
|
|
+ //第三方应用的SuiteId或者代开发应用模板id
|
|
|
+ String SuiteId = root.getElementsByTagName("SuiteId").item(0).getTextContent();
|
|
|
+ //临时授权码
|
|
|
+ String AuthCode = root.getElementsByTagName("AuthCode").item(0).getTextContent();
|
|
|
+
|
|
|
+ QwSuiteTokenParams qwSuiteTokenParams=new QwSuiteTokenParams();
|
|
|
+
|
|
|
+ switch (SuiteId) {
|
|
|
+ case "dk348a71ab9e97e43a":
|
|
|
+ //代开发应用模板信息
|
|
|
+ NodeList suiteIdNodes = root.getElementsByTagName("SuiteId");
|
|
|
+ String suiteIdXml = suiteIdNodes.getLength() > 0 ? suiteIdNodes.item(0).getTextContent() : null;
|
|
|
+ syncPermanentCodeByAuthCode(qwCompany, qwCorpID, AuthCode, suiteIdXml);
|
|
|
+ break;
|
|
|
+ case "ww0551047d6db89f0a":
|
|
|
+ //登录授权(暂时不做)
|
|
|
+// String signServer_suite_ticket = (String) redisTemplate.opsForValue().get("qwServer:signServer_suite_ticket");
|
|
|
+
|
|
|
+ break;
|
|
|
+ case "ww378fbd25f8e3e9f8":
|
|
|
+ // 通讯录编辑授权
|
|
|
+// String bookServer_suite_ticket = (String) redisTemplate.opsForValue().get("qwServer:bookServer_suite_ticket");
|
|
|
+ String bookServer_suite_ticket = (String) redisCache.getCacheObject("qwServer:bookServer_suite_ticket");
|
|
|
+
|
|
|
+ QwCompany qwBookServerConfig= qwCompanyMapper.selectQwCompanyByCorpId(qwCorpID);
|
|
|
+
|
|
|
+ qwSuiteTokenParams.setSuite_ticket(bookServer_suite_ticket);
|
|
|
+// qwSuiteTokenParams.setSuite_id(qwBookServerConfig.getSuiteId());
|
|
|
+// qwSuiteTokenParams.setSuite_secret(qwBookServerConfig.getSecret());
|
|
|
+//
|
|
|
+// String bookSuiteToken = qwApiService.getSuiteToken(qwSuiteTokenParams);
|
|
|
+// //临时授权码
|
|
|
+// QwPermanentCodeParam qwBookPermanentCodeParam=new QwPermanentCodeParam();
|
|
|
+// qwBookPermanentCodeParam.setAuth_code(AuthCode);
|
|
|
+//
|
|
|
+// //通过临时授权码以及代开发应用通用suite_ticket获取永久授权码及授权的企业信息
|
|
|
+// QwPermanentCodeResult bookPermanentCode = qwApiService.getPermanentCode(qwBookPermanentCodeParam, bookSuiteToken);
|
|
|
+//
|
|
|
+// //密文企业ID
|
|
|
+// String bookCorpid = bookPermanentCode.getAuth_corp_info().getCorpid();
|
|
|
+//
|
|
|
+// //各自的企业微信配置
|
|
|
+//// CompanyConfig bookConfig = companyConfigService.selectCompanyConfigByServerKeyAndCorpid("sys:qw:config",bookCorpid);
|
|
|
+// QwCompany qwBookConfig= qwCompanyMapper.selectQwCompanyByCorpId(bookCorpid);
|
|
|
+//
|
|
|
+// //永久授权码
|
|
|
+// qwBookConfig.setPermanentCode(bookPermanentCode.getPermanent_code());
|
|
|
+//
|
|
|
+// qwCompanyMapper.updateQwCompany(qwBookConfig);
|
|
|
+
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+// if(msgType.equals("auth_external_contact")){
|
|
|
+// NodeList AdminMobileLocationList = root.getElementsByTagName("AdminMobileLocation");
|
|
|
+// NodeList SuiteIdList = root.getElementsByTagName("SuiteId");
|
|
|
+// NodeList AuthExternalContactCodeList = root.getElementsByTagName("AuthExternalContactCode");
|
|
|
+// NodeList AuthCorpIdList = root.getElementsByTagName("AuthCorpId");
|
|
|
+// String AdminMobileLocation = AdminMobileLocationList.item(0).getTextContent();
|
|
|
+// String SuiteId = SuiteIdList.item(0).getTextContent();
|
|
|
+// String AuthExternalContactCode = AuthExternalContactCodeList.item(0).getTextContent();
|
|
|
+// String AuthCorpId = AuthCorpIdList.item(0).getTextContent();
|
|
|
+//
|
|
|
+// logger.info("AdminMobileLocationServerAuth:"+AdminMobileLocation);
|
|
|
+// logger.info("SuiteIdServerAuth:"+SuiteId);
|
|
|
+// logger.info("AuthExternalContactCodeServerAuth:"+AuthExternalContactCode);
|
|
|
+// logger.info("AuthCorpIdServerAuth:"+AuthCorpId);
|
|
|
+// }
|
|
|
+
|
|
|
+ }
|
|
|
+ catch(Exception e)
|
|
|
+ {
|
|
|
+ e.printStackTrace();
|
|
|
+ // 加密失败
|
|
|
+ logger.error("加密失败:"+result);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ result = "success";
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public String dataCallbackServer(String msgSignature, String timestamp, String nonce, String requestBody, String corpId) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
}
|