从这一篇开始,开始从实际项目探索企业微信开发。
1、企业微信开发第一步
看一下企业微信的开发者文档: 企业微信开发者文档.

在开发文档的首页中,我们看到:企业&定制化服务商开发流程

1. 获取企业号CorpID&Secret: 企业管理员建立管理组,获取CorpID&Secret
2. 开发对接相关接口: 开发测试应用,对接企业号接口,接口文档qydev.weixin.qq.com

企业号的CorpID和Secret,在上一篇已经讲解了,这里不赘述。对接接口的内容,在后面也会使用,暂时按下不表。

看【主动调用】,文档中有个醒目的获取AccessToken而且备注是:你应该审慎配置管理组的权限,够用即好,权限过大会增加误操作可能性及信息安全隐患。对于我们开发者而言:做好缓存、注意有时间限制7200s。下面还说频率的问题等等,这个也做到心中有数吧。

我们先把关注点集中在获取access_token上。在项目中,使用get请求获取access_token,请求地址为:https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=id&corpsecret=secrect。

【回调模式】在开发中用的也很多,使用的时候按照文档来吧。

我在一个项目中,写了一个微信开发的WeixinUtil,java写的未必那么完美。它的作用就是基于主动调用的,来看一下,这里一个推送的实现:

/**
* 企业号推送文章
*/
sentPushMsg(List<String> userIds , PushMsgEntity pushMsg) {
String appid = sysConfigService.getWeixinAppid();
String secret = sysConfigService.getWeixinSecret();
String agentId = sysConfigService.getParamValue(“agentId”);

String qytoken = apiRedis.getWeAccessToken(appid); //从缓存中获取token
if(qytoken == null) {
TokenResponse tokenResponse = WeixinUtil.token(appid,secret);
try {
qytoken = tokenResponse.getAccessToken();
apiRedis.setWeAccessToken(appid , qytoken);
}catch(Exception e) {
return R.error(ErrorCode.WEIXIN_API_GET_TOKEN_ERROR, “微信获取AccessToken失败,请重新获取!”);
}
}
MessageCustomArticle article = new MessageCustomArticle();

article.setTitle(pushMsg.getTitle());
article.setDescription(pushMsg.getMessage());
article.setUrl(pushMsg.getUrl());
article.setPicurl(pushMsg.getPicurl());

MessageCustomSendRequest messageCustomSendRequest = WeixinUtil.getMessageCustomSendRequest(userIds , article);
if(!StringUtil.isBlank(agentId)) {
messageCustomSendRequest.setAgentid(new Integer(agentId));
}

Response res = WeixinUtil.messageCustomSend(qytoken , messageCustomSendRequest);
if(res == null) {
//….略去了业务代码
return R.error( ErrorCode.SENT_TEMPLATE_MSG_ERROR, “消息发送失败”);
}else {
//….略去了业务代码
return R.ok(res.getErrMsg()).put(“code”, res.getErrCode());
}

}

大家在自己的项目中,也一定会有自己封装的方式。

接着上一篇,这里用的是什么呢?我在上一篇里说到,引入weixin-java-cp的Jar包了,这个jar包有非常强悍的作用。

2、weixin-java-cp-demo-master
1)、引入thymeleaf改造项目
在pom.xml中引入spring-boot-starter-thymeleaf

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

引入之后,application.yml中配置解析html:

spring:
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mode: HTML5
encoding: UTF-8
servlet:
content-type: text/html
cache: false

稍微解释一下,前缀prefix是地址,工程的相对路径,这边是404错误的根据地了吧,哈哈哈,得注意编译和请求得路径啊;后缀suffix需要spring解析的模板后缀,这里是html。mode、encoding、wevlet、cache就不说了哦。

在thymeleaf目录下,新建一个文件夹,thymeleaf,在文件夹中,新建一个first.html,

 

<!DOCTYPE HTML>
<html xmlns:th=”http://www.thymeleaf.org”>
<head>
<title>first</title>
<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″/>
</head>
<body>
<p th:text=”‘Url:’ + ${Url}”/>
<p th:text=”‘Error:’ + ${Error}”/>
<p th:text=”‘Message:’ + ${Message}”/>
<p th:text=”‘Exception:’ + ${Exception}”/>
<p th:text=”‘Path:’ + ${Path}”/>
<p th:text=”‘Status:’ + ${Status}”/>
<p th:text=”‘Timestamp:’ + ${Timestamp}”/>
</body>
</html>

看一下文件目录结构:

 

 

 

新建一个ThemleafController.java,内容是:

package com.github.binarywang.demo.wx.cp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping(“/thymeleaf”)
public class ThemleafController {

@GetMapping(value = “/first”)
public String first(Model model) {
model.addAttribute(“Url”, “thymeleaf/first”);
model.addAttribute(“Error”, “0”);
model.addAttribute(“Path”, “”);
model.addAttribute(“Message”, “”);
model.addAttribute(“Exception”, “”);
model.addAttribute(“Status”, “”);
model.addAttribute(“Timestamp”, System.currentTimeMillis());
return “/thymeleaf/first”;
}

}

改一手application.yml

server: port: 8080

debug模式启动WxCpDemoApplication.java
在浏览器中输入http://localhost:8080/thymeleaf/first

 

 

 

出来它就可以了。

2)增加Configuration

springboot在很多配置项简洁得让人摸不着头脑,这里是基于springboot不得不说的就是这个,先上代码,然后解释:

package com.github.binarywang.demo.wx.cp.config;

import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

public class WebMvcConfig implements WebMvcConfigurer{
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler(“/static/**”).addResourceLocations(“classpath:/static/”);
}
}

这个Java文件的意思是以静态资源的形式访问/static/文件夹下的所有文件。
/static/**使用了**作为通配符。但是,并没有static文件夹,对吗?建立一个static文件夹,看一下项目结构:

 

 

在里面放个txt文件文件名叫who_love_who.txt,文件里面写上自己喜欢的人的名字,文件名的话用我们的代码来试试看TA对你是否有意,哈哈哈。我的文件里面写的是:天赐我爱-love-天赐我爱。写好了测试文件,现在的项目结构是:

 

 

重新debug
WxCpDemoApplication.java
启动好了之后,在浏览器输入:
http://localhost:8080/who_love_who.txt
我这边的是:

 

 

看到是乱码,我就放心了,她是爱谁谁,与我这单身技术狗无关!差不多了哦!配置起作用了。

3)回到企业号

回到企业号。找到上篇手把手教你springboot企业微信开发(一)中的应用配置页:

 

 

 

拉到下面:
【开发者接口】–》点开【网页授权及JS-SDK】

 

 

点,【 下载文件】的链接,下载了一个txt文件吧?好!这个文件,放到我之前说的那个static文件夹!现在的static文件夹:

 

 

是否疑问,我在大费周章在搞thymeleaf、txt静态资源是为了什么呢?下面细细说这个。

4)穿透内外网

访问链接: https://natapp.cn/login.
注册~~~购买,不不不申请免费隧道:

 

 

 

天底下最好的东西,一定的免费的,阳光是免费的、空气是免费的,爱也是免费的。点开刚刚申请的隧道:

 

 

端口用个8080;authtoken复制备用。下载客户端:

 

 

自己选择吧。
下载完了,自己创建一个文件夹,就叫natapp得了,在里面放个文件,文件名config.ini在里面的配置:

#将本文件放置于natapp同级目录 程序将读取 [default] 段
#在命令行参数模式如 natapp -authtoken=xxx 等相同参数将会覆盖掉此配置
#命令行参数 -config= 可以指定任意config.ini文件
[default]
authtoken=myauthtoken #对应一条隧道的authtoken
clienttoken= #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy= #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空

刚才我说复制的authtoken,粘贴在这里。打开natapp.exe有木有什么?

 

 

有类似这个吗?如果是那行的意思是:现在这个域名,已经穿透到了你本地的8080端口。
好,把我画线的部分复制到浏览器:
是不是有这样的画面:

 

 

有的话,说明穿透成功了。把http://域名/thymeleaf/first复制下来,编码:
链接: url编码.把编码后的这段复制备用。把域名(不带http://的)复制下来备用。

5)完善

在pom.xml中引入jar包:

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>

引入这个fastjson是为了解析返回的字符串。

自动注入:

@Autowired
WxCpProperties wxCpProperties;

修改first方法:

@GetMapping(value = “/first”)
public String first(ServletRequest request, ServletResponse response, Model model) {

HttpServletRequest servletRequest = ((HttpServletRequest) request);
String userAgent = servletRequest.getHeader(“user-agent”);

final WxCpService wxCpService = WxCpConfiguration
.getCpService(wxCpProperties.getAppConfigs().get(0).getAgentId());

String queryString = servletRequest.getQueryString();
String backString = “”;
try {
backString = HttpRequest.sendGet(“https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=”
+ wxCpService.getAccessToken() + “&” + queryString, null);
} catch (WxErrorException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JSONObject json_test = JSONObject.parseObject(backString);

model.addAttribute(“Url”, “thymeleaf/first”);
model.addAttribute(“Error”, “0”);
model.addAttribute(“Path”, “”);
model.addAttribute(“Message”, “”);
model.addAttribute(“Exception”, “”);
model.addAttribute(“Status”, “”);
model.addAttribute(“Timestamp”, System.currentTimeMillis());
return “/thymeleaf/first”;
}

这里用的HttpRequest.java是我自己写的:

package com.github.binarywang.demo.wx.cp.utils;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HttpRequest {

public static Logger logger = LoggerFactory.getLogger(HttpRequest.class);

/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, Map params) {
return sendGet(url,””, params);
}
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url,String interfaceName, Map params) {
String result = “”;
BufferedReader in = null;
StringBuffer strBufParamsSet = new StringBuffer();

if(params!=null) {
Set<String> paramsSet = params.keySet();

for(String parma:paramsSet) {
if(strBufParamsSet.length()>0) {
strBufParamsSet.append(“&”).append(parma).append(“=”).append(params.get(parma));
}else {
strBufParamsSet.append(“?”).append(parma).append(“=”).append(params.get(parma));
}
}
}else {
strBufParamsSet.append(“”);
}

try {
String urlNameString = “”;
if(interfaceName==null || “”.contains(interfaceName.trim())) {
urlNameString = url + strBufParamsSet.toString();
}else {
urlNameString = url +”/”+ interfaceName + strBufParamsSet.toString();
}
urlNameString = urlNameString.replaceAll(“\r\n”, “”);
trustAllHttpsCertificates();
logger.info(“请求路径:” + urlNameString);
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty(“accept”, “*/*”);
connection.setRequestProperty(“connection”, “Keep-Alive”);
connection.setRequestProperty(“Accept-Charset”, “UTF-8”);
connection.setRequestProperty(“user-agent”, “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)”);
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// // 遍历所有的响应头字段
// for (String key : map.keySet()) {
// System.out.println(key + “—>” + map.get(key));
// }
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
logger.debug(“发送 get请求出现异常!获取文件流失败:” + e);

}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
logger.debug(“发送 get请求出现异常!获取文件流失败:” + e2);
}
}
return result;
}

 

private static void trustAllHttpsCertificates() throws Exception {
javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
javax.net.ssl.TrustManager tm = new miTM();
trustAllCerts[0] = tm;
javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
.getInstance(“SSL”);
sc.init(null, trustAllCerts, null);
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
.getSocketFactory());

HostnameVerifier hv = new HostnameVerifier() {
public boolean verify(String urlHostName, SSLSession session) {
System.out.println(“Warning: URL Host: ” + urlHostName + ” vs. “
+ session.getPeerHost());
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hv);
}

static class miTM implements javax.net.ssl.TrustManager,
javax.net.ssl.X509TrustManager {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}

public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) {
return true;
}

public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) {
return true;
}

public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}

public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
}
}

到了这一步,把
再回企业微信应用配置的页面,复制这个:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=appIdf&redirect_uri=编码后的域名&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
复制到:【工作台应用主页】里面去。
复制域名到【网页授权及JS-SDK】:

 

 

勾选【已上传域名归属校验文件】,【确定】!
点:【企业微信授权登录】已启用–》Web网页–》

 

 

编辑填入域名。
好了之后,debug重启:WxCpDemoApplication.java并且在这行打断点:

 

 

在企业微信中打开:

 

 点进去自己的应用,如果进断点,看backString,是不是与我类似:

 

 

如果看到返回值与我类似的,恭喜,已经完成了!!!如果有疑问或者需要讨论,或者报错的不好解决的话,给我留言即可。
附上我这边最终的画面:

 

 

另外,稍微看一下natapp的情况,那里有请求的情况:

 

 

转自:https://blog.csdn.net/miaozhicheng1990/article/details/106193474