从这一篇开始,开始从实际项目探索企业微信开发。
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-天赐我爱
。写好了测试文件,现在的项目结构是:
重新debugWxCpDemoApplication.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