1 微信公众号介绍
账号分为服务号
、订阅号
、小程序
服务号和订阅号开发类似,但是申请服务号必须是企业,所以学习的话申请一个订阅号+测试账号即可
2 注册订阅号
第一步:访问:微信公众平台 点击立即注册
按钮
第二步:注册类型页面选择订阅号
第三步:填写相关信息,点击注册即可
3 注册测试号
因为订阅号的接口权限是有限的,为了熟悉更多的微信公众号接口,所以需要申请一个测试号。
第一步:用注册的订阅号登录
第二步:在目录中【设置与开发】—>【开发者工具】下选择公众平台测试账号,点击进入后申请即可。
申请成功之后,就可以配置相关信息进行开发了,具体怎么配置后面再解释
4 搭建开发环境
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- 阿里云小蜜-自动回复机器人 -->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-chatbot</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.2</version>
</dependency>
<!-- xml操作相关依赖 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.11.1</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.0.0</version>
</dependency>
<!-- 阿里json解析 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
<!-- 这个是编码解码的 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<!--httpClient需要的依赖-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<!--//httpclient缓存-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient-cache</artifactId>
<version>4.5</version>
</dependency>
<!--//http的mime类型都在这里面-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.3.2</version>
</dependency>
</dependencies>
开发接入
接入之后微信服务器和我们自己的项目就接通了。那么如何接入呢?
- 第一步:登录微信公众测试号的管理界面,填写好相关信息
上图中的url就是自己电脑的项目
点击上图的提交按钮之后,微信会向上图中的url发送一个get请求,请求参数如下:
参数 | 描述 |
---|---|
signature | 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 随机字符串 |
- 第二步:编写代码校验,用代码实现下面的逻辑
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信,如果比对成功,请原样返回echostr参数内容
需要用到的加解密工具类
import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SecurityUtil {
protected static char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
protected static MessageDigest messageDigest = null;
protected static String convert="SHA1";
static{
try{
// 拿到一个MD5转换器(如果想要SHA1参数换成”SHA1”)
messageDigest = MessageDigest.getInstance(convert);
}catch (NoSuchAlgorithmException e) {
System.err.println(SecurityUtil.class.getName()+"初始化失败,MessageDigest不支持MD5Util.");
e.printStackTrace();
}
}
private static String bufferToHex(byte bytes[], int m, int n) {
StringBuffer stringbuffer = new StringBuffer(2 * n);
int k = m + n;
for (int l = m; l < k; l++) {
appendHexPair(bytes[l], stringbuffer);
}
return stringbuffer.toString();
}
private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
char c0 = hexDigits[(bt & 0xf0) >> 4];
char c1 = hexDigits[bt & 0xf];
stringbuffer.append(c0);
stringbuffer.append(c1);
}
private static String bufferToHex(byte bytes[]) {
return bufferToHex(bytes, 0, bytes.length);
}
/**
* 字符串的md5加密
* @param input
* @return
*/
public static String stringMD5(String input) {
// 输入的字符串转换成字节数组
byte[] inputByteArray = input.getBytes();
// inputByteArray是输入字符串转换得到的字节数组
messageDigest.update(inputByteArray);
// 转换并返回结果,也是字节数组,包含16个元素
byte[] resultByteArray = messageDigest.digest();
// 字符数组转换成字符串返回
return bufferToHex(resultByteArray);
}
/**
* 文件的md5加密
* @param inputFile
* @return
* @throws IOException
*/
public static String fileMD5(String inputFile) throws IOException {
// 缓冲区大小(这个可以抽出一个参数)
int bufferSize = 256 * 1024;
FileInputStream fileInputStream = null;
DigestInputStream digestInputStream = null;
try {
// 使用DigestInputStream
fileInputStream = new FileInputStream(inputFile);
digestInputStream = new DigestInputStream(fileInputStream,messageDigest);
// read的过程中进行MD5处理,直到读完文件
byte[] buffer =new byte[bufferSize];
byte[] resultByteArray =null;
while (digestInputStream.read(buffer) > 0) {
// 获取最终的MessageDigest
messageDigest= digestInputStream.getMessageDigest();
// 拿到结果,也是字节数组,包含16个元素
resultByteArray= messageDigest.digest();
// 同样,把字节数组转换成字符串
}
return bufferToHex(resultByteArray);
} finally {
try {
digestInputStream.close();
} catch (Exception e) {
}
try {
fileInputStream.close();
} catch (Exception e) {
}
}
}
}
微信调用的接口
/**
* 进行公众号开发配置,联通服务器与微信
* @return
*/
@GetMapping("/getWXSign")
public String getWXSign(String timestamp,String nonce,String echostr,String signature) {
String token = "120"; //token 必须跟自己设置的一致
// 1)将token、timestamp、nonce三个参数进行字典序排序
String[] arr={token,timestamp,nonce};
Arrays.sort(arr);
// 2)将三个参数字符串拼接成一个字符串进行sha1加密
StringBuilder scs=new StringBuilder();
for (String temp:arr){
scs.append(temp);
}
//获得自己的加密
String mySignature= SecurityUtil.stringMD5(scs.toString());
// 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if(mySignature.equals(signature)){
System.out.println("接入成功");
return echostr;
}
System.out.println("接入失败");
return null;
}
★access token的获取
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存.access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效
目前access_token的有效期通过返回的expire_in
来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token
总结:调用很多接口需要access_token,获取access_token之后需要保存起来,过期了再重新获取,而不是每次都重新获取。
接口调用请求说明
https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 获取access_token填写client_credential |
appid | 是 | 第三方用户唯一凭证 |
secret | 是 | 第三方用户唯一凭证密钥,即appsecret |
返回说明
正常情况下,微信会返回下述JSON数据包给公众号:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数说明
参数 | 说明 |
---|---|
access_token | 获取到的凭证 |
expires_in | 凭证有效时间,单位:秒 |
★封装请求工具类
因为需要发送请求给微信服务器,所以需要有请求的工具类。罗老师用的是java自带的请求类,相对来说比较繁琐。所以我这里采用的是Apache HttpClient,这个用起来更加的简单。
基于Apache HttpClient
封装HttpUtils
工具类,我封装了4个方法,可以支持get请求和post请求。后面很多需要用的地方直接调用即可。
public class HttpUtils {
public static void main(String[] args) {
// 1.测试get请求
/*
String getUrl = "http://localhost:8080/user/searchPage?pageNum=1&pageSize=2";
System.out.println(sendGet(getUrl));
*/
// 2.测试post请求 携带x-www-form-urlencoded数据格式
/*String postUrlForm = "http://localhost:8080/user";
Map paramMap = new HashMap();
paramMap.put("name", "杰克");
paramMap.put("age", "20");
paramMap.put("gender", "1");
System.out.println(sendPost(postUrlForm, paramMap));*/
//3.测试post请求 携带json数据格式
/*String postUrlJson = "http://localhost:8080/user";
String jsonParam = "{\"name\":\"jack\",\"age\":\"18\",\"gender\":\"2\"}";
System.out.println(sendPost(postUrlJson,jsonParam));*/
//4 测试post 携带文件
String postUrlFile = "http://localhost:8080/user/upload";
Map paramMap = new HashMap();
paramMap.put("name", "tom");
String localFile = "d:\\logo.png";
String fileParamName = "file";
System.out.println(sendPost(postUrlFile, paramMap,localFile,fileParamName));
}
// 1.httpClient发送get请求
public static String sendGet(String url) {
String result = "";
CloseableHttpResponse response = null;
try {
// 根据地址获取请求
HttpGet request = new HttpGet(url);// 这里发送get请求
// 获取当前客户端对象
CloseableHttpClient httpClient = HttpClients.createDefault();
// 通过请求对象获取响应对象
response = httpClient.execute(request);
// 判断网络连接状态码是否正常(0--200都数正常)
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
result = EntityUtils.toString(response.getEntity(), "utf-8");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != response) {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
// 2.httpClient发送post请求 携带x-www-form-urlencoded数据格式
public static String sendPost(String url, Map<String, String> map) {
CloseableHttpResponse httpResponse = null;
String result = "";
try {
// 1、创建一个httpClient客户端对象
CloseableHttpClient httpClient = HttpClients.createDefault();
// 2、创建一个HttpPost请求
HttpPost httpPost = new HttpPost(url);
// 设置请求头
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); // 设置传输的数据格式
// 携带普通的参数params的方式
List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
Set<String> keys = map.keySet();
for (String key : keys) {
params.add(new BasicNameValuePair(key, map.get(key)));
}
String str = EntityUtils.toString(new UrlEncodedFormEntity(params, Consts.UTF_8));
// 这里就是:username=kylin&password=123456
System.out.println(str);
// 放参数进post请求里面 从名字可以知道 这个类是专门处理x-www-form-urlencoded 添加参数的
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
// 7、执行post请求操作,并拿到结果
httpResponse = httpClient.execute(httpPost);
// 获取结果实体
HttpEntity entity = httpResponse.getEntity();
if (entity != null) {
result = EntityUtils.toString(entity, "UTF-8");
} else {
EntityUtils.consume(entity); 如果entity为空,那么直接消化掉即可
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != httpResponse) {
try {
httpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
// 3.httpClient发送post请求 携带json数据格式
public static String sendPost(String url, String jsonStr) {
CloseableHttpResponse httpResponse = null;
String result = "";
try {
// 1.创建httpClient
CloseableHttpClient httpClient = HttpClients.createDefault();
// 2.创建post请求方式实例
HttpPost httpPost = new HttpPost(url);
// 2.1设置请求头 发送的是json数据格式
httpPost.setHeader("Content-type", "application/json;charset=utf-8");
httpPost.setHeader("Connection", "Close");
// 3.设置参数---设置消息实体 也就是携带的数据
/*
* 比如传递: { "username": "aries", "password": "666666" }
*/
//String jsonStr = " {\"username\":\"aries\",\"password\":\"666666\"}";
StringEntity entity = new StringEntity(jsonStr.toString(), Charset.forName("UTF-8"));
entity.setContentEncoding("UTF-8"); // 设置编码格式
// 发送Json格式的数据请求
entity.setContentType("application/json");
// 把请求消息实体塞进去
httpPost.setEntity(entity);
// 4.执行http的post请求
// 4.执行post请求操作,并拿到结果
httpResponse = httpClient.execute(httpPost);
// 获取结果实体
HttpEntity httpEntity = httpResponse.getEntity();
if (httpEntity != null) {
result = EntityUtils.toString(httpEntity, "UTF-8");
} else {
EntityUtils.consume(httpEntity); 如果httpEntity为空,那么直接消化掉即可
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (null != httpResponse) {
try {
httpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return result;
}
// 4.httpClient发送post请求 携带文件
public static String sendPost(String url, Map<String, String> map,String localFile, String fileParamName) {
HttpPost httpPost = new HttpPost(url);
CloseableHttpClient httpClient = HttpClients.createDefault();
String resultString = "";
CloseableHttpResponse response = null;
try {
// 把文件转换成流对象FileBody
FileBody bin = new FileBody(new File(localFile));
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
// 相当于<input type="file" name="fileParamName"/> 其中fileParamName以传进来的为准
builder.addPart(fileParamName, bin);
// 相当于<input type="text" name="userName" value=userName>
/*builder.addPart("filesFileName",
new StringBody(fileParamName, ContentType.create("text/plain", Consts.UTF_8)));*/
if (map != null) {
for (String key : map.keySet()) {
builder.addPart(key,
new StringBody(map.get(key), ContentType.create("text/plain", Consts.UTF_8)));
}
}
HttpEntity reqEntity = builder.build();
httpPost.setEntity(reqEntity);
// 发起请求 并返回请求的响应
response = httpClient.execute(httpPost, HttpClientContext.create());
resultString = EntityUtils.toString(response.getEntity(), "utf-8");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (response != null)
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
}
调用时需设置白名单,不然调用不了
创建AccessToken类
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Data
@Slf4j
public class AccessToken {
private String token;
private long expiresTime;//过期时间
public AccessToken(String token, String expiresIn) {
super();
this.token = token;
//当前时间+有效期 = 过期时间
this.expiresTime = System.currentTimeMillis()+Integer.parseInt(expiresIn);
}
/**
* 判断token是否过期
* @return
*/
public boolean isExpire() {
return System.currentTimeMillis() > expiresTime;
}
//get and set ...
private static AccessToken at;//token获取的次数有限,有效期也有限,所以需要保存起来
private static String GET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
private static String APPID = "wx1e953fbcxxxxxxxx";
private static String APPSECRET = "103854f56cf397e1bd2xxxxxxxxx";
/**
* 发送get请求获取AccessToken
*/
private static String getToken() {
String url = GET_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
String tokenStr = HttpUtils.sendGet(url);//调用工具类发get请求
System.out.println(tokenStr);
JSONObject jsonObject = JSONObject.parseObject(tokenStr);
String token = jsonObject.getString("access_token");
String expiresIn = jsonObject.getString("expires_in");
at = new AccessToken(token, expiresIn);
return at.token;
}
/**
* todo 获取AccessToken 向外提供 调用的ip必须在公众号设置白名单。不然获取不到token!!!!!!!!!!
*/
public static String getAccessToken() {
//过期了或者没有值再去发送请求获取
if(at == null || at.isExpire()) {
getToken();
}
return at.getToken();
}
public static void main(String[] args) {
String accessToken = getAccessToken();
System.out.println("accessToken = " + accessToken);
}
}
自定义菜单
请注意:
- 自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
- 一级菜单最多4个汉字,二级菜单最多8个汉字,多出来的部分将会以“…”代替。
- 创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
自定义菜单接口可实现多种类型按钮,如下:
- click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
- view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
- scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
- scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
- pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
- pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
- pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
- location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
- media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
- view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
接口调用请求说明
http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
url中的ACCESS_TOKEN就是之前获取的,调用这个接口需要带上
请求需携带json参数
{
"button":[
{
"type":"click",
"name":"一级点击",
"key":"1"
},
{
"type":"view",
"name":"个人博客",
"url":"https://heliufang.gitee.io/"
},
{
"name":"有子菜单",
"sub_button":[
{
"type":"click",
"name":"三一点击",
"key":"31"
},
{
"type":"view",
"name":"码云博客",
"url":"https://heliufang.gitee.io/"
},
{
"type":"pic_photo_or_album",
"name":"拍照或发图",
"key":"33"
}
]
}
]
}
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
button | 是 | 一级菜单数组,个数应为1~3个 |
sub_button | 否 | 二级菜单数组,个数应为1~5个 |
type | 是 | 菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型 |
name | 是 | 菜单标题,不超过16个字节,子菜单不超过60个字节 |
key | click等点击类型必须 | 菜单KEY值,用于消息接口推送,不超过128字节 |
url | view、miniprogram类型必须 | 网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。 |
media_id | media_id类型和view_limited类型必须 | 调用新增永久素材接口返回的合法media_id |
appid | miniprogram类型必须 | 小程序的appid(仅认证公众号可配置) |
pagepath | miniprogram类型必须 | 小程序的页面路径 |
返回结果
正确时的返回JSON数据包如下:
{"errcode":0,"errmsg":"ok"}
错误时的返回JSON数据包如下(示例为无效菜单名长度):
{"errcode":40018,"errmsg":"invalid button name size"}
和前面xml的类似,我们需要对着请求的json数据封装按钮类,这样后面操作起来就比较方便,而且也方便维护。
封装菜单类
<1>AbstractButton类
//所有菜单(按钮)的父类
public abstract class AbstractButton {
private String name;//按钮标题
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public AbstractButton(final String name) {
this.name = name;
}
}
<2>Button类
//一级菜单对象
public class Button {
private List<AbstractButton> button;
public Button() {
this.button = new ArrayList<AbstractButton>();
}
public List<AbstractButton> getButton() {
return this.button;
}
public void setButton(final List<AbstractButton> button) {
this.button = button;
}
}
<3>ClickButton类
//点击类型的菜单
public class ClickButton extends AbstractButton {
private String type;
private String key;
public String getType() {
return this.type;
}
public void setType(final String type) {
this.type = type;
}
public String getKey() {
return this.key;
}
public void setKey(final String key) {
this.key = key;
}
public ClickButton(final String name, final String key) {
super(name);
this.type = "click";//点击类型
this.key = key;
}
}
<4>ViewButton类
//网页类型的菜单
public class ViewButton extends AbstractButton {
private String type;
private String url;
public String getType() {
return this.type;
}
public void setType(final String type) {
this.type = type;
}
public String getUrl() {
return this.url;
}
public void setUrl(final String url) {
this.url = url;
}
public ViewButton(final String name, final String url) {
super(name);
this.type = "view";//网页类型
this.url = url;
}
}
<5> PhotoOrAlbumButton
//拍照或传图菜单
public class PhotoOrAlbumButton extends AbstractButton{
private String type;
private String key;
public PhotoOrAlbumButton(String name,String key) {
super(name);
this.type = "pic_photo_or_album";//拍照获取传图
this.key = key;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
<6>SubButton
import java.util.ArrayList;
import java.util.List;
//二级菜单对象
public class SubButton extends AbstractButton {
private List<AbstractButton> sub_button;
public List<AbstractButton> getSub_button() {
return this.sub_button;
}
public void setSub_button(final List<AbstractButton> sub_button) {
this.sub_button = sub_button;
}
public SubButton(final String name) {
super(name);
this.sub_button = new ArrayList<AbstractButton>();
}
}
测试
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.web.jxxt.util.wxGz.*;
import java.util.ArrayList;
import java.util.List;
/**
* 进行设置三级菜单进行跳转功能页面
*/
public class AAWxGzh {
public static void main(String[] args) {
//创建一级菜单
Button button = new Button();
//在第三个菜单中创建二级菜单
SubButton subButton = new SubButton("驾校点击");
List<AbstractButton> list2 = new ArrayList();
list2.add(new ViewButton("一刀999", "http://jiaxiaoh5.heiwangke.cn/"));
list2.add(new ViewButton("百度点击", "https://www.baidu.com/"));
list2.add(new PhotoOrAlbumButton("拍照或发图","33"));
subButton.setSub_button(list2);
//在第一个模块中进行创建二级菜单
SubButton subButton1 = new SubButton("第一个");
List<AbstractButton> list21 = new ArrayList();
list21.add(new ClickButton("三一点击", "31"));
list21.add(new ViewButton("百度点击", "https://www.baidu.com/"));
list21.add(new PhotoOrAlbumButton("拍照或发图","33"));
subButton1.setSub_button(list21);
//在一级菜单中添加三个按钮,
List<AbstractButton> list = new ArrayList();
list.add(subButton1);
list.add(new ViewButton("个人博客", "https://blog.csdn.net/Java_Mr_Jin?type=blog"));
list.add(subButton);
button.setButton(list);
//转成json格式字符串
String jsonString = JSONObject.toJSONString(button);
//System.out.println(jsonString);
//发送请求
String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
url = url.replace("ACCESS_TOKEN", AccessToken.getAccessToken());//把token带上
String result = HttpUtils.sendPost(url, jsonString);
System.out.println(result);
}
}
测试结果:
获取已关注的用户信息
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
获取用户基本信息(包括UnionID机制)
开发者可通过OpenID来获取用户基本信息。请使用https协议。
接口调用请求说明 http请求方式: GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
参数说明
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 调用接口凭证 |
openid | 是 | 普通用户的标识,对当前公众号唯一 |
lang | 否 | 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 |
openid可以登录测试号管理界面获取,对应关注者的微信号
@ApiOperation("获取用户token")
@PostMapping("/getToken")
public R getToken(String openId){
String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
url = url.replace("ACCESS_TOKEN", AccessToken.getAccessToken());
url = url.replace("OPENID", openId);
String string = HttpUtils.sendGet(url);
System.out.println(string);//这里就可以看到打印的用户信息了
return R.ok().data("one",string);
}
网页授权 获取未关注的用户信息
可以获取未关注的用户信息,这部分需要有域名才能测试。
@RestController
@RequestMapping("/jx/getUserInfo")
@Api(tags = "公众号登录")
public class GetUserInfoServlet {
private static final long serialVersionUID = 1L;
private static String APPID = "wxde22cf3xxxxxxbea";
private static String APPSECRET = "e8186658cb4b33xxxxxxxxxxxx";
@ApiOperation("/用户登录")
@PostMapping("/doGets")
public R doGets(String code){
//1.用户同意授权,获取code
//2.通过code获取网页授权的access_token
String url = " https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
url = url.replace("APPID", APPID).replace("CODE", code).replace("SECRET", APPSECRET);
String string = HttpUtils.sendGet(url);
JSONObject jsonObject = JSONObject.parseObject(string);
//{"openid":"okUsy6TJldDxcpNa0SKmxacVP6J0","nickname":"刘亚方","sex":0,"language":"","city":"","province":"","country":"","headimgurl":"https:\/\/thirdwx.qlogo.cn\/mmopen\/vi_32\/DYAIOgq83eqOicpjaCCDfU5RcHBJAjNNCRg65RLjdNanjWgwB7Ria7T9FrnLqWicibjlQacI0FeVVHXppTeLCD1Vfw\/132","privilege":[]}
String accessToken = jsonObject.getString("access_token");
String openid = jsonObject.getString("openid");
//3.刷新access_token(如果需要)
//4.通过token获取用户信息
String getUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
getUserInfoUrl = getUserInfoUrl.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openid);
String userInfoJsonStr = HttpUtils.sendGet(getUserInfoUrl);
System.out.println(userInfoJsonStr);
JSONObject jsonObject1 = JSONObject.parseObject(userInfoJsonStr);//获取姓名 和头像
String nickname = jsonObject1.getString("nickname");
String headimgurl = jsonObject1.getString("headimgurl");
...
return R.ok();
}
}
微信公众号开发框架
前面的开发都是原生的写法,github上有很多现成的公众号开发框架。
比如这个基于springboot的公众号开发框架:
仓库:GitHub - binarywang/weixin-java-mp-demo: 基于Spring Boot 和 WxJava 实现的微信公众号Java后端Demo,支持多公众号
文档:公众号开发文档 · Wechat-Group/WxJava Wiki · GitHub
最后多说一句只有把原生的基础打好了,才能更好的理解和使用框架,所以建议先学原生的公众号开发,再上手框架。