对公众号还没有一定的了解的,请先阅读此博客 使用springboot进行微信公众号开发的准备!_码上编程的博客-CSDN博客_springboot微信公众号开发 之后再来阅读此博客
此博客将手把手教你从0开始用springboot进行微信公众号的开发。当然了,这里只是介绍初步的使用以及学习方法,详细使用或者更深层次的使用,则详细阅读微信公众号官方文档, 官方文档就是最好的教材!!
官方文档: 微信公众平台开发概述 | 微信开放文档
本项目的源代码在gitee中,使用git clone https://gitee.com/liu-wenxin/gongzhonghao.git即可下载
1、读取流的方式接受用户消息
公众号发送对应的消息,详细信息类型请看官方文档
目标结果 可以发现是xml数据包
代码实现
public class WeixinService {
/**
* 读取公众号发送过来的消息
* @param request
* @return
* @throws IOException
*/
public static String getWeixinMessage(HttpServletRequest request) throws IOException {
ServletInputStream is = request.getInputStream();
byte[] b=new byte[1024];
int len;
StringBuilder sb=new StringBuilder();
while((len=is.read(b))!=-1){
sb.append(new String(b,0,len));
}
return sb.toString();
}
}
@RestController
public class TestweixinController {
@RequestMapping(value="/testWeixin",method = {RequestMethod.POST,RequestMethod.GET})
public void testWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
if(Utils.check(timestamp,nonce,signature)){
//校验正确并返回echostr,才能正式成为一名公众号开发者,即填写的URL和TOKEN才能生效
//out.print(echostr);
//查看用户从公众号发送过来的信息
String result = WeixinService.getWeixinMessage(request);
System.out.println(result);
}else{
//校验失败
out.print("---请到公众号执行相应操作---");
}
out.flush();
out.close();
}
}
2、将读取的xml数据映射成map数据格式
上述方式读取公众号发送过来的信息也没毛病,但是, 当我想要获取某个节点的值就显得不是那么灵活了,并且也不方便,能不能将xml数据映射出map数据,通过操作map方式去操作xml数据
答案是: 可以的!!!
需要导入的依赖:
<!--解析xml数据-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
代码实现:
public class WeixinService {
//以Map形式读取公众号的所有消息
public static Map<String, String> parseRequest(InputStream is) throws IOException {
//将输入流解析成Map
Map<String, String> map = new HashMap<>();
try {
//读取输入流获取文档对象
SAXReader reader = new SAXReader();
Document document = reader.read(is);
//根据文档对象获取根节点
Element root = document.getRootElement();
//获取所有的子节点
List<Element> elements = root.elements();
for (Element e : elements) {
map.put(e.getName(), e.getStringValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
}
@RestController
public class TestweixinController {
@RequestMapping(value="/testWeixin",method = {RequestMethod.POST,RequestMethod.GET})
public void testWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
if(Utils.check(timestamp,nonce,signature)){
//校验正确并返回echostr,才能正式成为一名公众号开发者,即填写的URL和TOKEN才能生效
//out.print(echostr);
//查看用户从公众号发送过来的信息
Map<String, String> requestMap=WeixinService.parseRequest(request.getInputStream());
System.out.println(requestMap);
}else{
//校验失败
out.print("---请到公众号执行相应操作---");
}
out.flush();
out.close();
}
}
目标结果:
3、xml的字符串形式发送消息给用户
来而不往非礼也,用户发送信息了,可能焦急的等待公众号的回复! 通过阅读官方文档可得知,返回的数据类型也是xml数据包格式,每种类型都有严格的格式,比如:
回复文本消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
回复图片消息
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[media_id]]></MediaId>
</Image>
</xml>
代码实现
public class WeixinService {
//以Map形式读取公众号的所有消息
public static Map<String, String> parseRequest(InputStream is) throws IOException {
//将输入流解析成Map
Map<String, String> map = new HashMap<>();
try {
//读取输入流获取文档对象
SAXReader reader = new SAXReader();
Document document = reader.read(is);
//根据文档对象获取根节点
Element root = document.getRootElement();
//获取所有的子节点
List<Element> elements = root.elements();
for (Element e : elements) {
map.put(e.getName(), e.getStringValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
}
这里有个坑,发送的xml数据不能有空格 , 还有发送方与接收方的逻辑, 原来的发送方现在要变成接收方,原来的接收方要变成发送方
@RestController
public class TestweixinController {
@RequestMapping(value="/testWeixin",method = {RequestMethod.POST,RequestMethod.GET})
public void testWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
if(Utils.check(timestamp,nonce,signature)){
//校验正确并返回echostr,才能正式成为一名公众号开发者,即填写的URL和TOKEN才能生效
//out.print(echostr);
//查看用户从公众号发送过来的信息
Map<String, String> requestMap=WeixinService.parseRequest(request.getInputStream());
System.out.println(requestMap);
//返回 收到了!!!给用户
String respXml="<xml>\n" +
"<ToUserName><![CDATA["+requestMap.get("FromUserName")+"]]></ToUserName>\n" +
"<FromUserName><![CDATA["+requestMap.get("ToUserName")+"]]></FromUserName>\n" +
"<CreateTime>12345678</CreateTime>\n" +
"<MsgType><![CDATA[text]]></MsgType>\n" +
"<Content><![CDATA["+"收到了!!!"+"]]></Content>\n" +
"</xml>\n";
out.print(respXml);
}else{
//校验失败
out.print("---请到公众号执行相应操作---");
}
out.flush();
out.close();
}
}
目标结果
4、 将发送的数据解析成xml数据格式,通过操作对象的方式操作xml待返回的数据
上述方式也可以实现发送数据给用户,就是不灵活还有不方便,如果能通过操作对象的方式操作xml数据,那该有多好呀, 答案是可以实现的!!
导入的依赖
<!--将信息封装成xml数据-->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.8</version>
</dependency>
<dependency>
<groupId>xmlpull</groupId>
<artifactId>xmlpull</artifactId>
<version>1.1.3.1</version>
</dependency>
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3_min</artifactId>
<version>1.1.4c</version>
</dependency>
每种消息类型都含有 <ToUserName> <FromUserName> <CreateTime> <MsgType>
BaseMessage.java , 让所有的衍生类继承它,@XStreamAlias("xml")和@XStreamAlias("ToUserName") 解决映射成对象小写的问题
/*
* 所有消息的父类
* */
@Data
@NoArgsConstructor
@XStreamAlias("xml")
public class BaseMessage {
@XStreamAlias("ToUserName")
private String toUserName;
@XStreamAlias("FromUserName")
private String fromUserName;
@XStreamAlias("CreateTime")
private String createTime;
@XStreamAlias("MsgType")
private String msgType;
public BaseMessage(Map<String,String> requestMap){
this.toUserName= (String) requestMap.get("FromUserName");
this.fromUserName= (String) requestMap.get("ToUserName");
this.createTime=System.currentTimeMillis()/1000+"";
}
}
TextMessage.java
@Data
@XStreamAlias("xml")
public class TextMessage extends BaseMessage {
@XStreamAlias("Content")
private String content;
public TextMessage(Map<String, String> requestMap, String content){
//调用父类构造方法
super(requestMap);
//设置类型为文本类型
this.setMsgType("text");
this.content=content;
}
}
ImageMessage.java
@Data
@XStreamAlias("xml")
public class ImageMessage extends BaseMessage {
@XStreamAlias("Image")
private Image image;
public ImageMessage(Map<String,String> requestMap,Image image){
super(requestMap);
this.setMsgType("image");
this.image=image;
}
}
Image.java
@XStreamAlias("Image")
@AllArgsConstructor
@Data
public class Image {
@XStreamAlias("MediaId")
private String mediaId;
}
public class WeixinService {
//以Map形式读取公众号的所有消息
public static Map<String, String> parseRequest(InputStream is) throws IOException {
//将输入流解析成Map
Map<String, String> map = new HashMap<>();
try {
//读取输入流获取文档对象
SAXReader reader = new SAXReader();
Document document = reader.read(is);
//根据文档对象获取根节点
Element root = document.getRootElement();
//获取所有的子节点
List<Element> elements = root.elements();
for (Element e : elements) {
map.put(e.getName(), e.getStringValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
/*
*封装公众号返回信息,用于处理所有的事件和消息的回复,返回的是xml数据包
* */
public static String getResponse(Map<String, String> requestMap) {
BaseMessage msg = null;
//获取用户发送消息的类型
String msgType = (String) requestMap.get("MsgType");
switch (msgType) {
case "text":
//处理文本类型
msg = dealTextMessage(requestMap);
//将图文信息转成xml数据包
//msg=dealNewsMessage(requestMap);
//System.out.println("---返回文字类型数据---");
break;
case "image":
//处理图片信息
msg = dealImageMessage(requestMap);
break;
default:
break;
}
if (msg != null) {
//将信息转成xml数据包
return beanToXml(msg);
}
return null;
}
/*
* 把消息对象转成xml数据
* */
private static String beanToXml(BaseMessage msg) {
XStream stream = new XStream();
/*
* 使对应类上的注解生效
* */
stream.processAnnotations(BaseMessage.class);
stream.processAnnotations(TextMessage.class);
stream.processAnnotations(ImageMessage.class);
String xml = stream.toXML(msg);
return xml;
}
/*
* 专门处理文本消息
* */
private static BaseMessage dealTextMessage(Map<String, String> requestMap) {
TextMessage textMessage=new TextMessage(requestMap,"成功获取消息!!!");
return textMessage;
}
/*
*
*专门处理回复图片消息 requestMap是从用户从公众号发过来的数据
* */
private static BaseMessage dealImageMessage(Map<String, String> requestMap) {
return null;
}
}
@RestController
public class TestweixinController {
@RequestMapping(value="/testWeixin",method = {RequestMethod.POST,RequestMethod.GET})
public void testWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
if(Utils.check(timestamp,nonce,signature)){
//校验正确并返回echostr,才能正式成为一名公众号开发者,即填写的URL和TOKEN才能生效
//out.print(echostr);
//查看用户从公众号发送过来的信息
Map<String, String> requestMap=WeixinService.parseRequest(request.getInputStream());
//返回信息给用户
String resXml = WeixinService.getResponse(requestMap);
out.print(resXml);
}else{
//校验失败
out.print("---请到公众号执行相应操作---");
}
out.flush();
out.close();
}
}
目标效果
5、获取access_token
有了access_token才能进行其它操作,比如素材管理、自定义菜单
记得将TOKEN、APPID、APPSECRET改成自己的值
代码部分
public class Utils {
public static final String TOKEN="aabbccdd"; //TOKEN就是自己填写的
public static final String APPID="wx18d322f947cdf313";
public static final String APPSECRET="addf7e78f784c940ce69557cbe58b31b";
//用于存储access_token
private static AccessToken at;
/*
* 获取accessToken,有效期2小时
* */
private static void getToken() {
String url = " https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "&appid=" + APPID + "&secret=" + APPSECRET;
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();
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(
connection.getInputStream(),"utf-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
Gson gson = new Gson();
HashMap hashMap = gson.fromJson(result, HashMap.class);
String access_token = (String) hashMap.get("access_token");
Double expires_in = (Double) hashMap.get("expires_in");
//创建access_token,并存起来
at = new AccessToken(access_token, expires_in);
}
/*
* 获取access_token的方法 直接HttpRequest.getAccessToken()即可返回access_token值
* */
public static String getAccessToken() {
if (at == null || at.isExpired()) {
//过期则创建新的access_token
getToken();
}
//返回access_token
return at.getAccess_token();
}
/*
* 验证签名
* */
public static boolean check(String timestamp, String nonce, String signature) {
//1、将token、timestamp、nonce进行字典排序
String[] strs = new String[]{TOKEN, timestamp, nonce};
Arrays.sort(strs);
//2、将三个参数字符串拼接成一个字符串进行sha1加密
String str = strs[0] + strs[1] + strs[2];
String mysignature = sha1(str);
//返回对比结果
return mysignature.equalsIgnoreCase(signature);
}
/*
* 加密方法
* */
private static String sha1(String src) {
try {
//获取一个加密对象
MessageDigest md = MessageDigest.getInstance("sha1");
//加密
byte[] digest = md.digest(src.getBytes());
char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
StringBuffer sb = new StringBuffer();
//处理结果集
for (byte b : digest) {
sb.append(chars[(b >> 4) & 15]);
sb.append(chars[b & 15]);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
@SpringBootTest
class GongzhonghaoApplicationTests {
@Test
void contextLoads() {
System.out.println("---access_token---\n"+ Utils.getAccessToken());
}
}
目标结果
6、自定义菜单
无论是点击菜单、二级菜单、视图菜单,都有一个name的属性, 菜单的刷新需要你先取消关注公众号,然后在重新关注才能看到效果
@Data
@AllArgsConstructor
public abstract class AbstractButon {
private String name;
}
官网要求的菜单类型数据
{
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"name":"菜单",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"miniprogram",
"name":"wxa",
"url":"http://mp.weixin.qq.com",
"appid":"wx286b93c14bbf93aa",
"pagepath":"pages/lunar/index"
},
{
"type":"click",
"name":"赞一下我们",
"key":"V1001_GOOD"
}]
}]
}
@Data
public class Button {
private List<AbstractButon> button=new ArrayList<>();
}
点击菜单
public class ClickButton extends AbstractButon {
private String type="click";
private String key;
public ClickButton(String name,String key){
super(name);
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;
}
}
二级菜单
public class SubButton extends AbstractButon {
private List<AbstractButon> sub_button=new ArrayList<>();
public SubButton(String name){
super(name);
}
public List<AbstractButon> getSub_button() {
return sub_button;
}
public void setSub_button(List<AbstractButon> sub_button) {
this.sub_button = sub_button;
}
}
发送post请求的方法:
/**
* 发送post请求
* @param url 地址
* @param data 参数
* @return
*/
public static String post(String url,String data){
BufferedReader in = null;
String result="";
try {
URL urlObj=new URL(url);
URLConnection connection=urlObj.openConnection();
//设置可发送状态
connection.setDoOutput(true);
connection.setDoInput(true);
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)");
//获取输出流
OutputStream os=connection.getOutputStream();
//写出数据
os.write(data.getBytes("UTF-8"));
os.close();
//获取输入流
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
in.close();
os.close();
return result.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
测试
@SpringBootTest
class GongzhonghaoApplicationTests {
@Test
void contextLoads() {
//一级菜单
Button btn = new Button();
btn.getButton().add(new ClickButton("一级菜单","1"));
//二级菜单
SubButton sb2=new SubButton("二级菜单");
sb2.getSub_button().add(new ClickButton("1","21"));
sb2.getSub_button().add(new ClickButton("2","22"));
btn.getButton().add(sb2);
Gson gson = new Gson();
//向指定的url发送post请求,并携带上access_token和菜单数据
String url="https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
url=url.replace("ACCESS_TOKEN",Utils.getAccessToken());
//返回结果集
String result = Utils.post(url, gson.toJson(btn));
System.out.println("---result---\n"+result);
}
}
目标结果
7、菜单事件
上述菜单虽然有了,但是点击却没有任何响应,这时需要为它添上事件处理! 菜单事件类型有click(点击事件)、view(跳转链接事件)、scancode_push(扫码推事件)、view_miniprogram(跳转小程序事件)等等,详情请看官方文档。
代码部分
public class WeixinService {
//以Map形式读取公众号的所有消息
public static Map<String, String> parseRequest(InputStream is) throws IOException {
//将输入流解析成Map
Map<String, String> map = new HashMap<>();
try {
//读取输入流获取文档对象
SAXReader reader = new SAXReader();
Document document = reader.read(is);
//根据文档对象获取根节点
Element root = document.getRootElement();
//获取所有的子节点
List<Element> elements = root.elements();
for (Element e : elements) {
map.put(e.getName(), e.getStringValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
/*
*封装公众号返回信息,用于处理所有的事件和消息的回复,返回的是xml数据包
* */
public static String getResponse(Map<String, String> requestMap) {
BaseMessage msg = null;
//获取用户发送消息的类型
String msgType = (String) requestMap.get("MsgType");
switch (msgType) {
case "text":
//处理文本类型
msg = dealTextMessage(requestMap);
//将图文信息转成xml数据包
//msg=dealNewsMessage(requestMap);
//System.out.println("---返回文字类型数据---");
break;
case "image":
//处理图片信息
msg = dealImageMessage(requestMap);
break;
case "event":
//处理事件
msg = dealEvent(requestMap);
break;
default:
break;
}
if (msg != null) {
//将信息转成xml数据包
return beanToXml(msg);
}
return null;
}
/**
* 判断菜单栏事件
* @param requestMap
* @return
*/
private static BaseMessage dealEvent(Map<String, String> requestMap) {
//判断什么事件
String event = requestMap.get("Event");
switch (event){
case "CLICK":
//详细处理点击事件
return dealClick(requestMap);
default:
//其它事件
break;
}
return null;
}
/**
* 专门处理点击事件
* @param requestMap
* @return
*/
private static BaseMessage dealClick(Map<String, String> requestMap) {
//获取创建菜单时填的key值
String key = requestMap.get("EventKey");
switch (key){
case "1":
//点击一级菜单
return new TextMessage(requestMap,"点击了一级菜单");
case "21":
//点击二级菜单的1
return new TextMessage(requestMap,"点击二级菜单的1");
case "22":
//点击二级菜单的2
return new TextMessage(requestMap,"点击二级菜单的2");
default:
break;
}
return null;
}
/*
* 把消息对象转成xml数据
* */
private static String beanToXml(BaseMessage msg) {
XStream stream = new XStream();
/*
* 使对应类上的注解生效
* */
stream.processAnnotations(BaseMessage.class);
stream.processAnnotations(TextMessage.class);
stream.processAnnotations(ImageMessage.class);
String xml = stream.toXML(msg);
return xml;
}
/*
* 专门处理文本消息
* */
private static BaseMessage dealTextMessage(Map<String, String> requestMap) {
TextMessage textMessage=new TextMessage(requestMap,"成功获取消息!!!");
return textMessage;
}
/*
*
*专门处理回复图片消息 requestMap是从用户从公众号发过来的数据
* */
private static BaseMessage dealImageMessage(Map<String, String> requestMap) {
return null;
}
}
@RestController
public class TestweixinController {
@RequestMapping(value="/testWeixin",method = {RequestMethod.POST,RequestMethod.GET})
public void testWeixin(HttpServletRequest request, HttpServletResponse response) throws IOException {
request.setCharacterEncoding("utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
if(Utils.check(timestamp,nonce,signature)){
//校验正确并返回echostr,才能正式成为一名公众号开发者,即填写的URL和TOKEN才能生效
//out.print(echostr);
//查看用户从公众号发送过来的信息
Map<String, String> requestMap=WeixinService.parseRequest(request.getInputStream());
//返回信息给用户
String resXml = WeixinService.getResponse(requestMap);
out.print(resXml);
}else{
//校验失败
out.print("---请到公众号执行相应操作---");
}
out.flush();
out.close();
}
}
目标结果
微信公众号基础开发已经结束了!!! 不要光看,需要多练多看官方文档!!
本项目的源代码在gitee中,使用git clone https://gitee.com/liu-wenxin/gongzhonghao.git即可下载