我是个菜鸡,所以去B站看了视频,然后写了这个博客
去往:微信文档地址
去往:微信测试号申请
我们默认你已经拥有测试账号了,下面就开始
声明:我也是把文档的东西搬过来加上代码一起看
其实文档写的很清楚了,我们第一步就是去验证URL有效性成功后即接入生效,成为开发者。
在我们本地测试的时候,需要将我们的本地地址映射到公网,我们使用一个免费且非常方便的工具:natapp(注意的是这个免费的地址经常会变化,测试开发的时候一定注意,说多了都是泪啊)
第一步:填写服务器配置
测试号不需要(EncodingAESKey)可以先忽略
第二步:验证消息的确来自微信服务器
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
/**
* @author yhh
* @Description:
* @date 2020/5/13 14:24
*/
public class WxServlet extends HttpServlet {
/**
* 测试号-接口配置信息(连接)
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/**
* signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
* timestamp 时间戳
* nonce 随机数
* echostr 随机字符串
*/
String signature = req.getParameter("signature");
String timestamp = req.getParameter("timestamp");
String nonce = req.getParameter("nonce");
String echostr = req.getParameter("echostr");
//校验请求
if(WxService.check(timestamp,nonce,signature)){
System.out.println("校验成功");
//原样返回
PrintWriter writer = resp.getWriter();
writer.print(echostr);
writer.flush();
writer.close();
}else {
System.out.println("校验失败");
}
}
}
public static boolean check(String timestamp, String nonce, String signature) {
//1) 将token、timestamp、nonce三个参数进行字典序排序
String[] strs = new String[]{timestamp, nonce, TOKEN};
Arrays.sort(strs);
//2)将三个参数字符串拼接成一个字符串进行sha1加密
String str = strs[0] + strs[1] + strs[2];
String mySign = sha1(str);
//3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
return mySign.equals(signature);
}
private static String sha1(String str) {
try {
//获取一个加密对象
MessageDigest md = MessageDigest.getInstance("sha1");
//加密
byte[] digest = md.digest(str.getBytes());
char[] chars = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
StringBuilder sb = new StringBuilder();
//处理加密结果,一个byte分别 处理 高四位和低四位,范围都是0-15(相当于16进制的数),所以分开处理
for (byte b : digest) {
//高四位向右移动4位,让他与15,得到一个0-15的数字,然后转换成16进制的
sb.append(chars[(b >> 4) & 15]);
//处理低四位
sb.append(chars[b & 15]);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
我们的项目是Springboot的项目,所以定义的servlet需要被Spring管理起来
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.yhhcompany.servlet.WxServlet;
@SpringBootApplication
public class WechatApplication {
public static void main(String[] args) {
SpringApplication.run(WechatApplication.class, args);
}
@Bean //一定要加,不然这个方法不会运行
public ServletRegistrationBean getServletRegistrationBean() {
//一定要返回ServletRegistrationBean
ServletRegistrationBean bean = new ServletRegistrationBean(new WxServlet());
//放入自己的Servlet对象实例
bean.addUrlMappings("/wx"); //访问路径值
return bean;
}
}
到此就已经接入成功了
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上
所以我们继续完善我们的servlet
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
/**
* @author yhh
* @Description:
* @date 2020/5/13 14:24
*/
public class WxServlet extends HttpServlet {
/**
* 接受消息和事件推送
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//这里是避免中文乱码问题
req.setCharacterEncoding("utf8");
resp.setCharacterEncoding("utf8");
//接受的消息体
Map<String,String> requestMap = WxService.parseRequest(req.getInputStream());
System.out.println(requestMap);
//准备回复的数据包
String xml = WxService.getResonse(requestMap);
PrintWriter writer = resp.getWriter();
writer.print(xml);
writer.flush();
writer.close();
}
解析微信请求的XML,并转换成map
public static Map<String, String> parseRequest(ServletInputStream inputStream) {
SAXReader saxReader = new SAXReader();
HashMap<String, String> map = new HashMap<>();
try {
Document read = saxReader.read(inputStream);
//获取根节点
Element root = read.getRootElement();
List<Element> elements = root.elements();
for (Element element : elements) {
map.put(element.getName(), element.getStringValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
收到用户发送的消息,然后可以做自己的逻辑处理,注意返回给微信也是XML的数据包
/**
* 用于处理所有的事件和消息回复
* 返回的是xml数据包
*
* @param requestMap
* @return
*/
public static String getResonse(Map<String, String> requestMap) {
String msgType = requestMap.get("MsgType");
BaseMessage msg = null;
//不同的消息類型,需要轉換成xml數據包返回
switch (msgType) {
case "text":
/**
*
*
*
* 12345678
*
*
*
*/
msg = processingText(requestMap);
break;
case "image":
break;
case "voice":
break;
case "music":
break;
case "video":
break;
case "news":
break;
case "event":
msg = dealEvent(requestMap);
break;
default:
break;
}
String s = objToXml(msg);
return s;
}
对象转XML,我们引入的是Xstream的jar包
private static String objToXml(BaseMessage msg) {
XStream xStream = new XStream();
xStream.processAnnotations(TextMessage.class);
// xStream.processAnnotations(TextMessage.class);
// xStream.processAnnotations(TextMessage.class);
// xStream.processAnnotations(TextMessage.class);
// xStream.processAnnotations(TextMessage.class);
String s = xStream.toXML(msg);
String trim = s.trim();
return trim;
}
这里就采用面向对象的思想封装了返回对象
private static BaseMessage processingText(Map<String, String> requestMap) {
//这里的返回内容你想写什么就写什么
TextMessage tm = new TextMessage(requestMap,"老铁666");
return tm;
}
在贴着两个对象
@Data
@ToString
@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 ;
}
}
/**
* @author yhh
* @Description:
* @date 2020/5/13 17:42
*/
@Data
@XStreamAlias("xml")
public class BaseMessage {
/**
* 参数 是否必须 描述
* ToUserName 是 接收方帐号(收到的OpenID)
* FromUserName 是 开发者微信号
* CreateTime 是 消息创建时间 (整型)
* MsgType 是 消息类型,文本为text
*/
@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 = requestMap.get("FromUserName");
this.fromUserName = requestMap.get("ToUserName");
this.createTime = System.currentTimeMillis()/1000+"";
}
}
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
先定义一个对象用来临时存储access_token
import lombok.Data;
/**
* @author yhh
* @Description:
* @date 2020/5/15 14:25
*/
@Data
public class AccessToken {
private String accessToken;
//好久过期
private long expireDate;
//这里传token和过期时间
public AccessToken(String accessToken, String expireIn) {
this.accessToken = accessToken;
this.expireDate = System.currentTimeMillis()+Integer.parseInt(expireIn)*1000;
}
/**
* 判断token是否过期
* @return
*/
public Boolean isExpire(){
return System.currentTimeMillis()>expireDate;
}
}
获取access_token,过期的话重新获取,并将access_token 存储起来
public class WxService {
//token 是我们在测试号自己随便写的
private static final String TOKEN = "youhonghan";
//获取access_token的地址
private static final String ACCESSTOKEN_URL="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
//测试号给的
private static final String APPID="xx";
private static final String APPSECRET="xx";
//z暂存access_token
private static AccessToken at ;
//发送post请求封装自己的菜单
private static final String MUNE_URL="https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
private static void getToken() {
String url=ACCESSTOKEN_URL.replace("APPID",APPID).replace("APPSECRET",APPSECRET);
String access = get(url);
JSONObject object = JSONObject.parseObject(access);
String accessToken = object.getString("access_token");
String expiresIn = object.getString("expires_in");
//获取accesstoken 并存起来
at = new AccessToken(accessToken, expiresIn);
}
/**
* 向外暴露,判断access_token是否过期
* @return
*/
public static String getAccessToken(){
if(at==null||at.isExpire()){
getToken();
}
String accessToken = at.getAccessToken();
return accessToken;
}
}