开启左侧

企业微信第三方服务商运用开发及上架教程

[复制链接]
在线会员 wx_IS6bQV7iVRV1 发表于 2023-4-27 17:12:24 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
以前不竭不写那篇文章,是以为企微的效劳商使用绝对简朴。第两个启事是近来正在搞钉钉的ISV上架,以是时间没有是很充沛。邪题开端……

第一章效劳 商进驻

一、使用办理员登录效劳商办理背景
企业微疑-效劳商背景-登录地点
企业微信第三方服务商运用开发及上架教程

https://open.work.weixin.qq.com/wwopen/login二、输出根本疑息及认证
企业微疑第三圆效劳商使用开辟及上架学程-2.png


企业微疑第三圆效劳商使用开辟及上架学程-3.png


第两章 使用设置

一、登录后,间接创立创立网页使用,以下图。
企业微疑第三圆效劳商使用开辟及上架学程-4.png


二、使用概略-使用设置-参考上面的学程。主页地点是牢固的,只要供写进自己的appid战redirect_uri的地点便止。那里需要留神,可托域名必需设置一下,需要留神的是那个一级域名一朝使用的效劳商使用,那末自修使用是没法正在使用那个域名的,即使是差别的两级域名也不成以。那个仍是比力坑,招致咱们从头恳求了一个一级域名去效劳自修使用。
企业微疑第三圆效劳商使用开辟及上架学程-5.png


三、可托域名设置,使用设置-面打【编纂】,而后面打【校验可托域名回属】,而后下载那个文献到nginx设置的域名文献夹下,只要颠末步调2的地点能够会见到便算考证颠末。nginx的设置参考上面的那个文章。
nginx设置websocket或者https的转收学程_renkai721的专客-CSDN专客_nginx websocket转收nginx设置http,https,ssl,websocket转收

https://blog.csdn.net/renkai721/article/details/125991270
企业微疑第三圆效劳商使用开辟及上架学程-7.png


四、数据回调的设置
企业微疑第三圆效劳商使用开辟及上架学程-8.png


第三章 使用开辟

一、pom.xml中增加剖析XML格局实质
  1.                 <!--XML 剖析包-->
  2.                 <dependency>
  3.                         <groupId>org.jdom</groupId>
  4.                         <artifactId>jdom2</artifactId>
  5.                         <version>2.0.6</version>
  6.                 </dependency>
  7.                 <dependency>
  8.                         <groupId>co妹妹ons-codec</groupId>
  9.                         <artifactId>co妹妹ons-codec</artifactId>
  10.                         <version>1.15</version>
  11.                 </dependency>
复造代码
二、properties文献,没有需要那末多,定名更具自己的喜好,那一瞅即是参考了gitee的binarywang/weixin-java-cp-demo,那个demo假设入门者能够瞅瞅,而后自己启拆。
企业微疑第三圆效劳商使用开辟及上架学程-9.png


三、中心解稀controller.java
  1. package cn.renkai721.controller;
  2. import cn.renkai721.bean.*;
  3. import cn.renkai721.configuration.QywxProperties;
  4. import cn.renkai721.service.*;
  5. import cn.renkai721.util.HttpUtil;
  6. import cn.renkai721.util.MsgUtil;
  7. import cn.renkai721.util.WxUtil;
  8. import cn.renkai721.wechataes.WXBizMsgCrypt;
  9. import com.alibaba.druid.util.StringUtils;
  10. import com.alibaba.fastjson.JSON;
  11. import lombok.extern.slf4j.Slf4j;
  12. import org.redisson.api.RBucket;
  13. import org.redisson.api.RedissonClient;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.http.ResponseEntity;
  16. import org.springframework.scheduling.annotation.EnableAsync;
  17. import org.springframework.web.bind.annotation.*;
  18. import org.springframework.web.client.RestTemplate;
  19. import javax.annotation.Resource;
  20. import javax.servlet.ServletInputStream;
  21. import javax.servlet.http.HttpServletRequest;
  22. import javax.servlet.http.HttpServletResponse;
  23. import java.io.BufferedReader;
  24. import java.io.InputStreamReader;
  25. import java.util.Map;
  26. @EnableAsync
  27. @RestController
  28. @RequestMapping("/d3f")
  29. @Slf4j
  30. public class D3f2Controller {
  31.     @Resource
  32.     private RedissonClient redissonClient;
  33.     @Autowired
  34.     private RestTemplate restTemplate;
  35.     @Autowired
  36.     private D3fService d3fService;
  37.     @GetMapping(produces = "text/plain;charset=utf-8")
  38.     public void d3fGet(@RequestParam(name = "msg_signature", required = false) String signature,
  39.                           @RequestParam(name = "timestamp", required = false) String timestamp,
  40.                           @RequestParam(name = "nonce", required = false) String nonce,
  41.                           @RequestParam(name = "echostr", required = false) String echostr,
  42.                           HttpServletResponse response) throws Exception  {
  43.         response.setContentType("text/html;charset=utf-8");
  44.         response.setStatus(HttpServletResponse.SC_OK);
  45.         WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(
  46.                 MsgUtil.val("wechat.cp.appConfigs[0].token"),
  47.                 MsgUtil.val("wechat.cp.appConfigs[0].aesKey"),
  48.                 MsgUtil.val("wechat.cp.corpId"));
  49.         // 需要前去的明文
  50.         String sEchoStr = "";
  51.         try {
  52.             sEchoStr = wxcpt.VerifyURL(signature, timestamp, nonce, echostr);
  53.             log.info("resp sEchoStr={}",sEchoStr);
  54.             response.getWriter().print(sEchoStr);
  55.             return;
  56.         } catch (Exception e) {
  57.             // 考证URL失利,毛病启事请检察非常
  58.             e.printStackTrace();
  59.         }
  60.         response.getWriter().print("不法恳求");
  61.         return;
  62.     }
  63.     @PostMapping(produces = "application/xml; charset=UTF-8")
  64.     public void d3fPost(@RequestParam("msg_signature") String signature,
  65.                      @RequestParam("timestamp") String timestamp,
  66.                      @RequestParam("nonce") String nonce,
  67.                      HttpServletResponse response,
  68.                      HttpServletRequest request) throws Exception {
  69.         String success = "success";
  70.         String type = request.getParameter("type");
  71.         String corpid = request.getParameter("corpid");
  72.         log.info("领受d3f post恳求:[signature=[{}], timestamp=[{}], nonce=[{}], type=[{}], corpid=[{}] ]",
  73.                 signature, timestamp, nonce, type, corpid);
  74.         try{
  75.             response.setContentType("text/html;charset=utf-8");
  76.             response.setStatus(HttpServletResponse.SC_OK);
  77.             String id = "";
  78.             //拜访 使用战企业回调传差别的ID
  79.             if("data".equals(type)){
  80.                 // 企微背景树立【数据回调URL】的链交为https://wx.naturobot.com/qywx/d3f?type=data&corpid=$CORPID$
  81.                 id = corpid;
  82.             } else {
  83.                 id = MsgUtil.val("suite_id");
  84.             }
  85.             WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(MsgUtil.val(
  86.                     "wechat.cp.appConfigs[0].token"),
  87.                     MsgUtil.val("wechat.cp.appConfigs[0].aesKey"),
  88.                     id);
  89.             // 稀文,对于应POST恳求的数据
  90.             String postData = "";
  91.             // 获得减稀的恳求消息:使用输出流得到减稀恳求消息postData
  92.             ServletInputStream in = request.getInputStream();
  93.             BufferedReader reader = new BufferedReader(new InputStreamReader(in));
  94.             // 动作输出字符串的临时串,用于鉴别可否读与结束
  95.             String tempStr = "";
  96.             while(null != (tempStr=reader.readLine())){
  97.                 postData += tempStr;
  98.             }
  99.             String suiteXml = wxcpt.DecryptMsg(signature, timestamp, nonce, postData);
  100.             Map suiteMap = WxUtil.transferXmlToMap(suiteXml);
  101.             log.info("\n req map={}", suiteMap);
  102.             if("suite_ticket".equals(suiteMap.get("InfoType"))){
  103.                 // https://developer.work.weixin.qq.com/document/10975#%E8%8E%B7%E5%8F%96%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%94%E7%94%A8%E5%87%AD%E8%AF%81
  104.                 // 主动拉收SuiteTicket间接写进
  105.                 // 每一十分钟革新一次
  106.                 //suite_ticket理论有用期为30分钟,
  107.                 String suite_ticket_value = (String) suiteMap.get("SuiteTicket");
  108.                 String SuiteId = (String) suiteMap.get("SuiteId");
  109.                 log.info("suite_ticket={},SuiteId={}",suite_ticket_value,SuiteId);
  110.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.suite_ticket_key);
  111.                 idBucket.set(suite_ticket_value);
  112.                 // 挪用企业微疑交心
  113.                 d3fService.get_suite_access_token();
  114.             }else if("create_auth".equals(suiteMap.get("InfoType"))){
  115.                 String authCode = (String) suiteMap.get("AuthCode");
  116.                 // SuiteId代表一个企业,相称于suite_id
  117.                 String SuiteId = (String) suiteMap.get("SuiteId");
  118.                 log.info("第三圆使用尝试上线,AuthCode={},SuiteId={}",authCode,SuiteId);
  119.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.authCode_key+"_"+SuiteId);
  120.                 idBucket.set(authCode);
  121.                 // 获得企业永久受权码
  122.                 idBucket = redissonClient.getBucket(QywxProperties.suite_access_token_key);
  123.                 String suite_access_token = idBucket.get();
  124.                 String url1 = "https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token="+suite_access_token;
  125.                 PermanentReqBean permanentReqBean = new PermanentReqBean();
  126.                 permanentReqBean.setAuth_code(authCode);
  127.                 ResponseEntity<PermanentRespBean> postForEntity1 = restTemplate.postForEntity(url1, permanentReqBean, PermanentRespBean.class);
  128.                 log.info("get_permanent_code={}",postForEntity1.getBody());
  129.                 if(postForEntity1.getBody().getExpires_in() != null){
  130.                     String authCorpId = postForEntity1.getBody().getAuth_corp_info().getCorpid();
  131.                     log.info("永久受权码中获得的第三圆使用的authCorpId={}",authCorpId);
  132.                     String userIdD3f = postForEntity1.getBody().getAuth_user_info().getUserid();
  133.                     // 间接与第一个
  134.                     String agentId = postForEntity1.getBody().getAuth_info().getAgent().get(0).getAgentid();
  135.                     String permanent_code_access_token = postForEntity1.getBody().getAccess_token();
  136.                     String permanent_code = postForEntity1.getBody().getPermanent_code();
  137.                     log.info("permanent_code={}",permanent_code);
  138.                     String open_userid = postForEntity1.getBody().getAuth_user_info().getOpen_userid();
  139.                     // 能够树立企业的容许主动激活形状
  140. // 那里面的工具需要保留下来,否则前面使用的时候不了便垮台了
  141. // 那里面的工具需要保留下来,否则前面使用的时候不了便垮台了
  142. // 那里面的工具需要保留下来,否则前面使用的时候不了便垮台了
  143.                 }else{
  144.                     log.error("get_permanent_code api is error");
  145.                 }
  146.             }else if("cancel_auth".equals(suiteMap.get("InfoType"))){
  147.                 String AuthCorpId = (String) suiteMap.get("AuthCorpId");
  148.                 log.info("打消定阅cancel_auth AuthCorpId={}",AuthCorpId);
  149.             }
  150.             if("unlicensed_notify".equals(suiteMap.get("Event"))){
  151.                 // 该用户帐号已受权
  152.                 String AgentID = (String) suiteMap.get("AgentID");
  153.                 String ToUserName = (String) suiteMap.get("ToUserName");
  154.                 String FromUserName = (String) suiteMap.get("FromUserName");
  155.                 log.info("用户帐号不保守受权,需要受权");
  156.             }else if("change_app_admin".equals(suiteMap.get("Event"))){
  157.                 String AgentID = (String) suiteMap.get("AgentID");
  158.                 // ToUserName=corpId
  159.                 String ToUserName = (String) suiteMap.get("ToUserName");
  160.                 log.info("第三圆使用change_app_admin,ToUserName={},AgentID={}",ToUserName,AgentID);
  161.             }else if("subscribe".equals(suiteMap.get("Event"))){
  162.                 log.info("新用户存眷,user={}",suiteMap);
  163.                 // 复兴感谢存眷
  164.                 String ToUserName = (String) suiteMap.get("ToUserName");
  165.                 String FromUserName = (String) suiteMap.get("FromUserName");
  166.                 String AgentID = (String) suiteMap.get("AgentID");
  167.                 // 获得临时受权码
  168.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.suite_access_token_key);
  169.                 String suite_access_token = idBucket.get();
  170.                 String url1 = "https://qyapi.weixin.qq.com/cgi-bin/service/get_pre_auth_code?suite_access_token=" + suite_access_token;
  171.                 String postData1 = HttpUtil.sendGet(url1);
  172.                 log.info("get_pre_auth_code={}", postData1);
  173.                 String subscribe_pre_auth_code = JSON.parseObject(postData1).getString("pre_auth_code");
  174.                 String expires_in = JSON.parseObject(postData1).getString("expires_in");
  175.                 if(!StringUtils.isEmpty(expires_in)){
  176.                     // 树立受权设置
  177.                     url1 = "https://qyapi.weixin.qq.com/cgi-bin/service/set_session_info?suite_access_token=" + suite_access_token;
  178.                     SessionInfoReqBean sessionInfoReqBean = new SessionInfoReqBean();
  179.                     sessionInfoReqBean.setPre_auth_code(subscribe_pre_auth_code);
  180.                     SessionInfoBean sessionInfoBean = new SessionInfoBean();
  181.                     sessionInfoBean.setAppid(new Integer[0]);
  182.                     sessionInfoBean.setAuth_type(Integer.parseInt(MsgUtil.val("authType")));
  183.                     sessionInfoReqBean.setSession_info(sessionInfoBean);
  184.                     log.info("sessionInfoReqBean={}", JSON.toJSONString(sessionInfoReqBean));
  185.                     ResponseEntity<SessionInfoRespBean> postForEntity = restTemplate.postForEntity(url1, sessionInfoReqBean, SessionInfoRespBean.class);
  186.                     log.info("树立受权设置={}", postForEntity.getBody());
  187.                 }else{
  188.                     log.error("get_pre_auth_code api is error");
  189.                 }
  190.                 // 收收XML消息给用户
  191.                 String Title = "感谢装置该使用";
  192.                 String Description = "咱们的使用很佳用,假设有成就请拨挨德律风021-12345";
  193.                 String Url = MsgUtil.val("poster.freeUrl");
  194.                 String PicUrl = "https://wx.naturobot.com/qywx/image/bg1.png";
  195.                 log.info("Title={},Description={},Url={},PicUrl={},",Title,Description,Url,PicUrl);
  196.                 String xmlOutMsg = wxcpt.getXmlNewsMessage(FromUserName,ToUserName,Title,Description,Url,PicUrl);
  197.                 success = wxcpt.EncryptMsg(xmlOutMsg, timestamp, nonce);
  198.             }else if("enter_agent".equals(suiteMap.get("Event"))){
  199.                 // 用户翻开使用的工作
  200.             }
  201.             if("text".equals(suiteMap.get("MsgType"))){
  202.                 // 用户收收了文原消息给使用
  203.                 String ToUserName = (String) suiteMap.get("ToUserName");
  204.                 String FromUserName = (String) suiteMap.get("FromUserName");
  205.                 String AgentID = (String) suiteMap.get("AgentID");
  206.                 String xmlOutMsg = wxcpt.getXmlTextMessage(FromUserName,ToUserName,"久已启开谈天功用。");
  207.                 success = wxcpt.EncryptMsg(xmlOutMsg, timestamp, nonce);
  208.             }
  209.         } catch (Exception e) {
  210.             e.printStackTrace();
  211.         }
  212.         response.getWriter().print(success);
  213.         return;
  214.     }
  215. }
复造代码
四、解稀东西WXBizMsgCrypt.java
  1. /**
  2. * 对于企业微疑收收给企业背景的消息减解稀示例代码.
  3. *
  4. * @copyright Copyright (c) 1998-2014 Tencent Inc.
  5. */
  6. // ------------------------------------------------------------------------
  7. /**
  8. * 针对于org.apache.co妹妹ons.codec.binary.Base64,
  9. * 需要导进架包co妹妹ons-codec-1.9(或者co妹妹ons-codec-1.8等其余版原)
  10. * 民间下载地点:http://co妹妹ons.apache.org/proper/co妹妹ons-codec/download_codec.cgi
  11. */
  12. package cn.renkai721.wechataes;
  13. import org.apache.co妹妹ons.codec.binary.Base64;
  14. import javax.crypto.Cipher;
  15. import javax.crypto.spec.IvParameterSpec;
  16. import javax.crypto.spec.SecretKeySpec;
  17. import java.nio.charset.Charset;
  18. import java.util.Arrays;
  19. import java.util.Random;
  20. /**
  21. * 供给领受战拉收给企业微疑消息的减解稀交心(UTF8编码的字符串).
  22. * <ol>
  23. *         <li>第三圆复兴减稀消息给企业微疑</li>
  24. *         <li>第三圆支到企业微疑收收的消息,考证消息的宁静性,并对于消息截至解稀。</li>
  25. * </ol>
  26. *说明 :非常java.security.InvalidKeyException:illegal Key Size的处置计划
  27. * <ol>
  28. *         <li>正在民间网站下载JCE无限造权力战略文献(JDK7的下载地点:
  29. *      http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html</li>
  30. *         <li>下载后解压,能够瞅到local_policy.jar战US_export_policy.jar和readme.txt</li>
  31. *         <li>假设装置了JRE,将二个jar文献搁到%JRE_HOME%\lib\security目次下笼盖本来的文献</li>
  32. *         <li>假设装置了JDK,将二个jar文献搁到%JDK_HOME%\jre\lib\security目次下笼盖本来文献</li>
  33. * </ol>
  34. */
  35. public class WXBizMsgCrypt {
  36.         static Charset CHARSET = Charset.forName("utf-8");
  37.         Base64 base64 = new Base64();
  38.         byte[] aesKey;
  39.         String token;
  40.         String receiveid;
  41.         /**
  42.          *结构 函数
  43.          * @param token 企业微疑背景,开辟者树立的token
  44.          * @param encodingAesKey 企业微疑背景,开辟者树立的EncodingAESKey
  45.          * @param receiveid, 差别场景寄义差别,详睹文档
  46.          *
  47.          * @throws AesException 施行失利,请检察该非常的毛病码战具体的毛病疑息
  48.          */
  49.         public WXBizMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException {
  50.                 if (encodingAesKey.length() != 43) {
  51.                         throw new AesException(AesException.IllegalAesKey);
  52.                 }
  53.                 this.token = token;
  54.                 this.receiveid = receiveid;
  55.                 aesKey = Base64.decodeBase64(encodingAesKey + "=");
  56.         }
  57.         // 天生4个字节的收集字节序
  58.         byte[] getNetworkBytesOrder(int sourceNumber) {
  59.                 byte[] orderBytes = new byte[4];
  60.                 orderBytes[3] = (byte) (sourceNumber & 0xFF);
  61.                 orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF);
  62.                 orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF);
  63.                 orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF);
  64.                 return orderBytes;
  65.         }
  66.         //复原 4个字节的收集字节序
  67.         int recoverNetworkBytesOrder(byte[] orderBytes) {
  68.                 int sourceNumber = 0;
  69.                 for (int i = 0; i < 4; i++) {
  70.                         sourceNumber <<= 8;
  71.                         sourceNumber |= orderBytes[i] & 0xff;
  72.                 }
  73.                 return sourceNumber;
  74.         }
  75.         // 随机天生16位字符串
  76.         String getRandomStr() {
  77.                 String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  78.                 Random random = new Random();
  79.                 StringBuffer sb = new StringBuffer();
  80.                 for (int i = 0; i < 16; i++) {
  81.                         int number = random.nextInt(base.length());
  82.                         sb.append(base.charAt(number));
  83.                 }
  84.                 return sb.toString();
  85.         }
  86.         /**
  87.          * 对于明文截至减稀.
  88.          *
  89.          * @param text 需要减稀的明文
  90.          * @return 减稀后base64编码的字符串
  91.          * @throws AesException aes减稀失利
  92.          */
  93.         String encrypt(String randomStr, String text) throws AesException {
  94.                 ByteGroup byteCollector = new ByteGroup();
  95.                 byte[] randomStrBytes = randomStr.getBytes(CHARSET);
  96.                 byte[] textBytes = text.getBytes(CHARSET);
  97.                 byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length);
  98.                 byte[] receiveidBytes = receiveid.getBytes(CHARSET);
  99.                 // randomStr + networkBytesOrder + text + receiveid
  100.                 byteCollector.addBytes(randomStrBytes);
  101.                 byteCollector.addBytes(networkBytesOrder);
  102.                 byteCollector.addBytes(textBytes);
  103.                 byteCollector.addBytes(receiveidBytes);
  104.                 // ... + pad: 使用自界说的添补方法对于明文截至补位添补
  105.                 byte[] padBytes = PKCS7Encoder.encode(byteCollector.size());
  106.                 byteCollector.addBytes(padBytes);
  107.                 //取得 终极的字撙节, 已减稀
  108.                 byte[] unencrypted = byteCollector.toBytes();
  109.                 try {
  110.                         // 树立减稀情势为AES的CBC情势
  111.                         Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  112.                         SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
  113.                         IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
  114.                         cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
  115.                         // 减稀
  116.                         byte[] encrypted = cipher.doFinal(unencrypted);
  117.                         // 使用BASE64对于减稀后的字符串截至编码
  118.                         String base64Encrypted = base64.encodeToString(encrypted);
  119.                         return base64Encrypted;
  120.                 } catch (Exception e) {
  121.                         e.printStackTrace();
  122.                         throw new AesException(AesException.EncryptAESError);
  123.                 }
  124.         }
  125.         /**
  126.          * 对于稀文截至解稀.
  127.          *
  128.          * @param text 需要解稀的稀文
  129.          * @return 解稀获得的明文
  130.          * @throws AesException aes解稀失利
  131.          */
  132.         String decrypt(String text) throws AesException {
  133.                 byte[] original;
  134.                 try {
  135.                         // 树立解稀情势为AES的CBC情势
  136.                         Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
  137.                         SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES");
  138.                         IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
  139.                         cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);
  140.                         // 使用BASE64对于稀文截至解码
  141.                         byte[] encrypted = Base64.decodeBase64(text);
  142.                         // 解稀
  143.                         original = cipher.doFinal(encrypted);
  144.                 } catch (Exception e) {
  145.                         e.printStackTrace();
  146.                         throw new AesException(AesException.DecryptAESError);
  147.                 }
  148.                 String xmlContent, from_receiveid;
  149.                 try {
  150.                         // 来除补位字符
  151.                         byte[] bytes = PKCS7Encoder.decode(original);
  152.                         //别离 16位随机字符串,收集字节序战receiveid
  153.                         byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20);
  154.                         int xmlLength = recoverNetworkBytesOrder(networkOrder);
  155.                         xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
  156.                         from_receiveid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length),
  157.                                         CHARSET);
  158.                 } catch (Exception e) {
  159.                         e.printStackTrace();
  160.                         throw new AesException(AesException.IllegalBuffer);
  161.                 }
  162.                 // receiveid没有差异的情况
  163.                 System.out.println("------ from_receiveid="+from_receiveid+", receiveid="+receiveid);
  164.                 if (!from_receiveid.equals(receiveid)) {
  165.                         throw new AesException(AesException.ValidateCorpidError);
  166.                 }
  167.                 return xmlContent;
  168.         }
  169.         /**
  170.          * 将企业微疑复兴用户的消息减稀挨包.
  171.          * <ol>
  172.          *         <li>对于要收收的消息截至AES-CBC减稀</li>
  173.          *         <li>天生宁静署名</li>
  174.          *         <li>将消息稀文战宁静署名挨包成xml格局</li>
  175.          * </ol>
  176.          *
  177.          * @param replyMsg 企业微疑待复兴用户的消息,xml格局的字符串
  178.          * @param timeStamp时间 戳,能够自己天生,也能够用URL参数的timestamp
  179.          * @param nonce 随机串,能够自己天生,也能够用URL参数的nonce
  180.          *
  181.          * @return 减稀后的能够间接复兴用户的稀文,包罗msg_signature, timestamp, nonce, encrypt的xml格局的字符串
  182.          * @throws AesException 施行失利,请检察该非常的毛病码战具体的毛病疑息
  183.          */
  184.         public String EncryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException {
  185.                 // 减稀
  186.                 String encrypt = encrypt(getRandomStr(), replyMsg);
  187.                 // 天生宁静署名
  188.                 if (timeStamp == "") {
  189.                         timeStamp = Long.toString(System.currentTimeMillis());
  190.                 }
  191.                 String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt);
  192.                 // System.out.println("收收给仄台的署名是: " + signature[1].toString());
  193.                 // 天生收收的xml
  194.                 String result = XMLParse.generate(encrypt, signature, timeStamp, nonce);
  195.                 System.out.println("call wechat xml message=["+result+"]");
  196.                 return result;
  197.         }
  198.         /**
  199.          * 查验消息的实在性,而且获得解稀后的明文.
  200.          * <ol>
  201.          *         <li>使用支到的稀文天生宁静署名,截至署名考证</li>
  202.          *         <li>若考证颠末,则提炼xml中的减稀消息</li>
  203.          *         <li>对于消息截至解稀</li>
  204.          * </ol>
  205.          *
  206.          * @param msgSignature 署名串,对于应URL参数的msg_signature
  207.          * @param timeStamp时间 戳,对于应URL参数的timestamp
  208.          * @param nonce 随机串,对于应URL参数的nonce
  209.          * @param postData 稀文,对于应POST恳求的数据
  210.          *
  211.          * @return 解稀后的本文
  212.          * @throws AesException 施行失利,请检察该非常的毛病码战具体的毛病疑息
  213.          */
  214.         public String DecryptMsg(String msgSignature, String timeStamp, String nonce, String postData)
  215.                         throws AesException {
  216.                 // 稀钥,公家帐号的app secret
  217.                 // 提炼稀文
  218.                 Object[] encrypt = XMLParse.extract(postData);
  219.                 // 考证宁静署名
  220.                 String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString());
  221.                 // 战URL中的署名比力可否相称
  222.                 // System.out.println("第三圆支到URL中的署名:" + msg_sign);
  223.                 // System.out.println("第三圆校验署名:" + signature);
  224.                 if (!signature.equals(msgSignature)) {
  225.                         throw new AesException(AesException.ValidateSignatureError);
  226.                 }
  227.                 // 解稀
  228.                 String result = decrypt(encrypt[1].toString());
  229.                 return result;
  230.         }
  231.         /**
  232.          * 考证URL
  233.          * @param msgSignature 署名串,对于应URL参数的msg_signature
  234.          * @param timeStamp时间 戳,对于应URL参数的timestamp
  235.          * @param nonce 随机串,对于应URL参数的nonce
  236.          * @param echoStr 随机串,对于应URL参数的echostr
  237.          *
  238.          * @return 解稀以后的echostr
  239.          * @throws AesException 施行失利,请检察该非常的毛病码战具体的毛病疑息
  240.          */
  241.         public String VerifyURL(String msgSignature, String timeStamp, String nonce, String echoStr)
  242.                         throws AesException {
  243.                 String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr);
  244.                 if (!signature.equals(msgSignature)) {
  245.                         throw new AesException(AesException.ValidateSignatureError);
  246.                 }
  247.                 String result = decrypt(echoStr);
  248.                 return result;
  249.         }
  250.         public String getXmlTextMessage(String FromUserName,String ToUserName, String sendMsgText){
  251.                 // 文原消息
  252.                 String timestamp = Long.toString(System.currentTimeMillis()/1000L);
  253.                 return "<xml>" +
  254.                                 "  <ToUserName><![CDATA["+FromUserName+"]]></ToUserName>" +
  255.                                 "  <FromUserName><![CDATA["+ToUserName+"]]></FromUserName>" +
  256.                                 "  <CreateTime>"+timestamp+"</CreateTime>" +
  257.                                 "  <MsgType><![CDATA[text]]></MsgType>" +
  258.                                 "  <Content><![CDATA["+sendMsgText+"]]></Content>" +
  259.                                 "</xml>";
  260.         }
  261.         public String getXmlNewsMessage(String FromUserName,String ToUserName, String Title, String Description, String Url, String PicUrl){
  262.                 // 图文消息
  263.                 String timestamp = Long.toString(System.currentTimeMillis()/1000L);
  264.                 return "<xml>" +
  265.                                 "  <ToUserName><![CDATA["+FromUserName+"]]></ToUserName>" +
  266.                                 "  <FromUserName><![CDATA["+ToUserName+"]]></FromUserName>" +
  267.                                 "  <CreateTime>"+timestamp+"</CreateTime>" +
  268.                                 "  <MsgType><![CDATA[news]]></MsgType>" +
  269.                                 "  <Content><![CDATA[]]></Content>" +
  270.                                 "  <ArticleCount>1</ArticleCount>" +
  271.                                 "  <Articles>" +
  272.                                 "          <item>" +
  273.                                 "                  <Title><![CDATA["+Title+"]]></Title>" +
  274.                                 "                  <Description><![CDATA["+Description+"]]></Description>" +
  275.                                 "                  <Url><![CDATA["+Url+"]]></Url>" +
  276.                                 "                  <PicUrl><![CDATA["+PicUrl+"]]></PicUrl>" +
  277.                                 "          </item>" +
  278.                                 "  </Articles>" +
  279.                                 "  <FuncFlag>0</FuncFlag>" +
  280.                                 "</xml>";
  281.         }
  282. }
复造代码
五、WxUtil.java
  1. package cn.renkai721.util;
  2. import org.jdom2.Document;
  3. import org.jdom2.Element;
  4. import org.jdom2.JDOMException;
  5. import org.jdom2.input.SAXBuilder;
  6. import java.io.ByteArrayInputStream;
  7. import java.io.IOException;
  8. import java.io.InputStream;
  9. import java.util.*;
  10. public class WxUtil {
  11.     /**
  12.      * 将 Map 转移为 XML
  13.      *
  14.      * @param map
  15.      * @return
  16.      */
  17.     public static String transferMapToXml(SortedMap<String, Object> map) {
  18.         StringBuffer sb = new StringBuffer();
  19.         sb.append("<xml>");
  20.         for (String key : map.keySet()) {
  21.             sb.append("<").append(key).append(">")
  22.                     .append(map.get(key))
  23.                     .append("</").append(key).append(">");
  24.         }
  25.         return sb.append("</xml>").toString();
  26.     }
  27.     /**
  28.      * 将 XML 转移为 map
  29.      *
  30.      * @param strxml
  31.      * @return
  32.      * @throws IOException
  33.      */
  34.     public static Map transferXmlToMap(String strxml) throws IOException {
  35.         strxml = strxml.replaceFirst("encoding=".*"", "encoding="UTF-8"");
  36.         if (null == strxml || "".equals(strxml)) {
  37.             return null;
  38.         }
  39.         Map m = new HashMap();
  40.         InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
  41.         SAXBuilder builder = new SAXBuilder();
  42.         Document doc = null;
  43.         try {
  44.             doc = builder.build(in);
  45.         } catch (JDOMException e) {
  46.             throw new IOException(e.getMessage()); //分歧 转移为 IO 非常输出
  47.         }
  48.         // 剖析 DOM
  49.         Element root = doc.getRootElement();
  50.         List list = root.getChildren();
  51.         Iterator it = list.iterator();
  52.         while (it.hasNext()) {
  53.             Element e = (Element) it.next();
  54.             String k = e.getName();
  55.             String v = "";
  56.             List children = e.getChildren();
  57.             if (children.isEmpty()) {
  58.                 v = e.getTextNormalize();
  59.             } else {
  60.                 v = getChildrenText(children);
  61.             }
  62.             m.put(k, v);
  63.         }
  64.         //封闭流
  65.         in.close();
  66.         return m;
  67.     }
  68.     //辅佐 transferXmlToMap办法 递回提炼子节面数据
  69.     private static String getChildrenText(List<Element> children) {
  70.         StringBuffer sb = new StringBuffer();
  71.         if (!children.isEmpty()) {
  72.             Iterator<Element> it = children.iterator();
  73.             while (it.hasNext()) {
  74.                 Element e = (Element) it.next();
  75.                 String name = e.getName();
  76.                 String value = e.getTextNormalize();
  77.                 List<Element> list = e.getChildren();
  78.                 sb.append("<" + name + ">");
  79.                 if (!list.isEmpty()) {
  80.                     sb.append(getChildrenText(list));
  81.                 }
  82.                 sb.append(value);
  83.                 sb.append("</" + name + ">");
  84.             }
  85.         }
  86.         return sb.toString();
  87.     }
  88. }
复造代码
六、XmlUtil.java
  1. package cn.renkai721.util;
  2. import javax.xml.bind.JAXBContext;
  3. import javax.xml.bind.Unmarshaller;
  4. import java.io.StringReader;
  5. public class XmlUtil {
  6.         /**
  7.          * 剖析XMl实质,变换为POJO类
  8.          *
  9.          * @param clazz 要剖析的工具
  10.          * @param xml 剖析的xml字符串
  11.          * @return 剖析完毕的工具
  12.          */
  13.         public static Object xmlStrToObject(Class clazz, String xml) {
  14.             Object xmlObject = null;
  15.             try {
  16.                 JAXBContext context = JAXBContext.newInstance(clazz);
  17.                 // 退行将Xml转成工具的中心交心
  18.                 Unmarshaller unmarshaller = context.createUnmarshaller();
  19.                 StringReader sr = new StringReader(xml);
  20.                 xmlObject = unmarshaller.unmarshal(sr);
  21.             } catch (Exception e) {
  22.                 e.printStackTrace();
  23.             }
  24.             return xmlObject;
  25.         }
  26. }
复造代码
七、AesException.java
  1. package cn.renkai721.wechataes;
  2. @SuppressWarnings("serial")
  3. public class AesException extends Exception {
  4.         public final static int OK = 0;
  5.         public final static int ValidateSignatureError = -40001;
  6.         public final static int ParseXmlError = -40002;
  7.         public final static int ComputeSignatureError = -40003;
  8.         public final static int IllegalAesKey = -40004;
  9.         public final static int ValidateCorpidError = -40005;
  10.         public final static int EncryptAESError = -40006;
  11.         public final static int DecryptAESError = -40007;
  12.         public final static int IllegalBuffer = -40008;
  13.         //public final static int EncodeBase64Error = -40009;
  14.         //public final static int DecodeBase64Error = -40010;
  15.         //public final static int GenReturnXmlError = -40011;
  16.         private int code;
  17.         private static String getMessage(int code) {
  18.                 switch (code) {
  19.                 case ValidateSignatureError:
  20.                         return "署名考证毛病";
  21.                 case ParseXmlError:
  22.                         return "xml剖析失利";
  23.                 case ComputeSignatureError:
  24.                         return "sha减稀天生署名失利";
  25.                 case IllegalAesKey:
  26.                         return "Sy妹妹etricKey不法";
  27.                 case ValidateCorpidError:
  28.                         return "corpid校验失利";
  29.                 case EncryptAESError:
  30.                         return "aes减稀失利";
  31.                 case DecryptAESError:
  32.                         return "aes解稀失利";
  33.                 case IllegalBuffer:
  34.                         return "解稀后获得的buffer不法";
  35. //                case EncodeBase64Error:
  36. //                        return "base64减稀毛病";
  37. //                case DecodeBase64Error:
  38. //                        return "base64解稀毛病";
  39. //                case GenReturnXmlError:
  40. //                        return "xml天生失利";
  41.                 default:
  42.                         return null; // cannot be
  43.                 }
  44.         }
  45.         public int getCode() {
  46.                 return code;
  47.         }
  48.         AesException(int code) {
  49.                 super(getMessage(code));
  50.                 this.code = code;
  51.         }
  52. }
复造代码
8、ByteGroup.java
  1. package cn.renkai721.wechataes;
  2. import java.util.ArrayList;
  3. public class ByteGroup {
  4.         ArrayList<Byte> byteContainer = new ArrayList<Byte>();
  5.         public byte[] toBytes() {
  6.                 byte[] bytes = new byte[byteContainer.size()];
  7.                 for (int i = 0; i < byteContainer.size(); i++) {
  8.                         bytes[i] = byteContainer.get(i);
  9.                 }
  10.                 return bytes;
  11.         }
  12.         public ByteGroup addBytes(byte[] bytes) {
  13.                 for (byte b : bytes) {
  14.                         byteContainer.add(b);
  15.                 }
  16.                 return this;
  17.         }
  18.         public int size() {
  19.                 return byteContainer.size();
  20.         }
  21. }
复造代码
九、D3fService.java
  1. package cn.renkai721.biz;
  2. import cn.renkai721.bean.*;
  3. import cn.renkai721.configuration.QywxProperties;
  4. import cn.renkai721.util.HttpUtil;
  5. import cn.renkai721.util.MsgUtil;
  6. import com.alibaba.druid.util.StringUtils;
  7. import com.alibaba.fastjson.JSON;
  8. import com.alibaba.fastjson.JSONObject;
  9. import lombok.extern.slf4j.Slf4j;
  10. import org.redisson.api.RBucket;
  11. import org.redisson.api.RedissonClient;
  12. import org.springframework.http.ResponseEntity;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.web.client.RestTemplate;
  15. import javax.annotation.Resource;
  16. import java.util.*;
  17. import java.util.concurrent.TimeUnit;
  18. @Slf4j
  19. @Component
  20. public class D3fBiz {
  21.         @Resource
  22.         private RedissonClient redissonClient;
  23.         @Resource
  24.         private RestTemplate restTemplate;
  25.         public String get_suite_ticket(){
  26.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.suite_ticket_key);
  27.                 String get_suite_ticket = idBucket.get();
  28.                 return get_suite_ticket;
  29.         }
  30.         public String get_suite_access_token(){
  31.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.suite_access_token_key);
  32.                 String suite_access_token = idBucket.get();
  33.                 log.info("suite_access_token={}",suite_access_token);
  34.                 if(StringUtils.isEmpty(suite_access_token)){
  35.                         String suite_ticket = this.get_suite_ticket();
  36.                         //假设 上线后,不最新的suite,脚动正在企微掌握台面打革新ticket
  37.                         // 颠末原交心获得的suite_access_token有用期为2小时,开辟者需要截至慢存,不成频仍获得。
  38.                         // 参照地点=https://work.weixin.qq.com/api/doc/90001/90143/90600
  39.                         String url1= "https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token";
  40.                         Map<String, Object> paramMap1 = new HashMap<>();
  41.                         paramMap1.put("suite_id", MsgUtil.val(QywxProperties.suite_id_key));
  42.                         paramMap1.put("suite_secret", MsgUtil.val(QywxProperties.suite_secret_key));
  43.                         paramMap1.put("suite_ticket", suite_ticket);
  44.                         String postData1 = HttpUtil.sendPost(url1, JSONObject.toJSONString(paramMap1));
  45.                         log.info("get_suite_token={}",postData1);
  46.                         suite_access_token = JSON.parseObject(postData1).getString(QywxProperties.suite_access_token_key);
  47.                         String expires_in = JSON.parseObject(postData1).getString("expires_in");
  48.                         if(!StringUtils.isEmpty(expires_in)){
  49.                                 idBucket.set(suite_access_token,Integer.parseInt(expires_in), TimeUnit.SECONDS);
  50.                         }else{
  51.                                 log.error("get_suite_token api is error");
  52.                         }
  53.                 }
  54.                 return suite_access_token;
  55.         }
  56.         public String get_access_token(String corpId){
  57.                 String suite_access_token = this.get_suite_access_token();
  58.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.corpId_suiteId_agentId+"_"+corpId);
  59.                 String corpIdAndAgentId = idBucket.get();
  60.                 log.info("corpIdAndAgentId={}",corpIdAndAgentId);
  61.                 String permanent_code = corpIdAndAgentId.split(";")[3];
  62.                 Map paramMap1 = new HashMap<>();
  63.                 // 获得企业access_token
  64.                 idBucket = redissonClient.getBucket(QywxProperties.access_token_key+"_"+corpId);
  65.                 String access_token = idBucket.get();
  66.                 log.info("access_token={}",access_token);
  67.                 if(StringUtils.isEmpty(access_token)){
  68.                         String url1 = "https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token?suite_access_token="+suite_access_token;
  69.                         paramMap1 = new HashMap<>();
  70.                         paramMap1.put("auth_corpid", corpId);
  71.                         paramMap1.put("permanent_code", permanent_code);
  72.                         String postData1 = HttpUtil.sendPost(url1, JSONObject.toJSONString(paramMap1));
  73.                         log.info("get_corp_token={}",postData1);
  74.                         access_token = JSON.parseObject(postData1).getString("access_token");
  75.                         String expires_in = JSON.parseObject(postData1).getString("expires_in");
  76.                         if(!StringUtils.isEmpty(expires_in)){
  77.                                 idBucket.set(access_token,Integer.parseInt(expires_in), TimeUnit.SECONDS);
  78.                         }else{
  79.                                 log.error("get_corp_token is error");
  80.                         }
  81.                 }
  82.                 return access_token;
  83.         }
  84.         public void sendD3fTextMsg(String corpId, String toUser, String message){
  85.                 log.info("sendD3fTextMsg corpId={},toUser={},message={}"
  86.                                 ,corpId,toUser,message);
  87.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.corpId_suiteId_agentId+"_"+corpId);
  88.                 String corpIdAndAgentId = idBucket.get();
  89.                 log.info("corpIdAndAgentId={}",corpIdAndAgentId);
  90.                 String agentId = corpIdAndAgentId.split(";")[2];
  91.                 String access_token = this.get_access_token(corpId);
  92.                 String msgUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="+access_token;
  93.                 MsgRequestDTO requestData = new MsgRequestDTO();
  94.                 requestData.setAgentid(Integer.parseInt(agentId));
  95.                 requestData.setTouser(toUser);
  96.                 requestData.setMsgtype("text");
  97.                 Map<String,String> text = new HashMap<>();
  98.                 text.put("content", message);
  99.                 requestData.setText(text);
  100.                 log.info("sendD3fTextMsg requestData={}",requestData);
  101.                 ResponseEntity<MsgResult> postForEntity = restTemplate.postForEntity(msgUrl, requestData, MsgResult.class);
  102.                 log.info("sendD3fTextMsg postForEntity={}",postForEntity);
  103.         }
  104.         public void sendD3fNewsMsg(String corpId, String toUser, String Title,
  105.                                                            String Description, String Url, String PicUrl){
  106.                 log.info("sendD3fNewsMsg corpId={},toUser={},Title={},Description={},Url={},PicUrl={},"
  107.                                 ,corpId,toUser,Title,Description,Url,PicUrl);
  108.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.corpId_suiteId_agentId+"_"+corpId);
  109.                 String corpIdAndAgentId = idBucket.get();
  110.                 log.info("corpIdAndAgentId={}",corpIdAndAgentId);
  111.                 String agentId = corpIdAndAgentId.split(";")[2];
  112.                 String access_token = this.get_access_token(corpId);
  113.                 String msgUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="+access_token;
  114.                 Map<String, Object> body = new HashMap<>();
  115.                 body.put("touser",toUser);
  116.                 body.put("msgtype","news");
  117.                 body.put("agentid",Integer.parseInt(agentId));
  118.                 Map<String, Object> news = new HashMap<>();
  119.                 List articles = new ArrayList();
  120.                 Map<String, Object> article = new HashMap<>();
  121.                 article.put("title",Title);
  122.                 if(!StringUtils.isEmpty(Description)){
  123.                         article.put("description",Description);
  124.                 }
  125.                 if(!StringUtils.isEmpty(Url)){
  126.                         article.put("url",Url);
  127.                 }
  128.                 article.put("picurl",PicUrl);
  129.                 articles.add(article);
  130.                 news.put("articles",articles);
  131.                 body.put("news",news);
  132.                 JSONObject jsonObject = new JSONObject(body);
  133.                 log.info("sendNewsMsg body={},",jsonObject);
  134.                 ResponseEntity<MsgResult> postForEntity = restTemplate.postForEntity(msgUrl, jsonObject, MsgResult.class);
  135.                 log.info("sendNewsMsg postForEntity={}",postForEntity);
  136.         }
  137.         public void sendMarkdownMsg(String corpId,String toUser,String message) {
  138.                 log.info("sendMarkdownMsg corpId={},toUser={},message={}"
  139.                                 ,corpId,toUser,message);
  140.                 RBucket<String> idBucket = redissonClient.getBucket(QywxProperties.corpId_suiteId_agentId+"_"+corpId);
  141.                 String corpIdAndAgentId = idBucket.get();
  142.                 log.info("corpIdAndAgentId={}",corpIdAndAgentId);
  143.                 String agentId = corpIdAndAgentId.split(";")[2];
  144.                 String access_token = this.get_access_token(corpId);
  145.                 String msgUrl = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="+access_token;
  146.                 Map<String, Object> body = new HashMap<>();
  147.                 body.put("touser",toUser);
  148.                 body.put("msgtype","markdown");
  149.                 body.put("agentid",agentId);
  150.                 Map<String,String> markdown = new HashMap<>();
  151.                 markdown.put("content", message);
  152.                 body.put("markdown",markdown);
  153.                 JSONObject jsonObject = new JSONObject(body);
  154.                 log.info("sendMarkdownMsg body={},",jsonObject);
  155.                 ResponseEntity<MsgResult> postForEntity = restTemplate.postForEntity(msgUrl, jsonObject, MsgResult.class);
  156.                 log.info("sendMarkdownMsg={}",postForEntity);
  157.         }
  158. }
复造代码
十、开辟代码尝试的时候,忘患上把效劳器IP增加到利剑名单,使用办理员登录效劳商背景,面打企业疑息,而后输出IP。
第四章 使用上架

一、开辟完毕后,登录到企业效劳商办理背景,一般的办理员也能够操纵。
二、【使用战模板上线】-【提接上线】-选一个要上线的使用-【肯定】。
企业微疑第三圆效劳商使用开辟及上架学程-10.png


三、假设失利了,效劳商背景的消息会支到报告,胜利也会支到报告。
四、上线胜利后,能够树立使用商场可搜刮的设置,公布上线仍是要挖写一点儿工具,包罗图片,需要好工制作特地格局的图片才能够。
第五章 企微民间交心及别的参照文章

企业微佩服务商-开辟前必读 - 交心文档
企业微疑第三圆效劳商使用开辟及上架学程-11.jpg

https://developer.work.weixin.qq.com/document/path/91201企微效劳商仄台免费交心连接学程_renkai721的专客-CSDN专客_企微效劳商媒介一、从前的过程是用户增加第三圆使用,而后登录,而后操纵。二、现在的过程是用户增加第三圆使用,而后效劳商购置账号,效劳商正在用户增加第三圆使用时或者用户登录时或者领受到【unlicensed_notify】交心容许生效报告时,受权激该死用户,而后用户登录,而后操纵。企微民间文档里背效劳商截至仄台免费情势调解的分析仄台交心容许付费企微效劳商背景办理操纵学程一、用户正在企微使用商场搜刮效劳商开辟的第3圆使用,假设使用名字【气候帮忙】。而后面打装置。二、这时候分效劳商的背景效劳会支到腾讯服

https://blog.csdn.net/renkai721/article/details/124970456解读:企微里背效劳商截至仄台免费情势调解的分析_renkai721的专客-CSDN专客媒介一、从前的过程是用户增加第三圆使用,而后登录,而后操纵。二、现在是效劳商购置账号,效劳商正在用户增加第三圆使用时或者用户登录时受权激该死用户,而后用户登录,而后操纵。企微民间文档里背效劳商截至仄台免费情势调解的分析仄台交心容许付费1、假设没有购置【根底帐号】,那末【身份考证】【女伶 href="https://www.taojin168.com/cloud/" target="_blank">小法式登录】【收收使用消息】那3个交心没法挪用。表示进去的场景为:一、第三圆使用战小法式的用户是没法登录的。二、也不克不及挪用交心API收收消息给用户。2、假设没有购置【互通帐号】,那末【获得.

https://blog.csdn.net/renkai721/article/details/124675211
您需要登录后才可以回帖 登录 | 立即注册 qq_login

本版积分规则

发布主题
阅读排行更多+
用专业创造成效
400-778-7781
周一至周五 9:00-18:00
意见反馈:server@mailiao.group
紧急联系:181-67184787
ftqrcode

扫一扫关注我们

Powered by 职贝云数A新零售门户 X3.5© 2004-2025 职贝云数 Inc.( 蜀ICP备2024104722号 )