一、概述
1.1 开发者工具
- 在开发者工具中可以找到“测试账号、接口调试工具、相关文档”等。
- 官方地址:https://mp.weixin.qq.com/cgi-bin/frame?t=advanced/dev_tools_frame&nav=10049&token=705684025&lang=zh_CN
1.1 测试账号及接口
- 测试账号是用于开发时测试的账号,里面可以找到测试 appID、appsecret 和微信支持的接口地址。还有测试二维码,通过扫描测试二维码关注测试账号进行测试。
- 官方地址:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
1.2 接口调试工具
- 接口调试工具可以用来测试微信公众号支持的接口。
- 官方地址:https://mp.weixin.qq.com/debug?token=705684025&lang=zh_CN
二、三方依赖
- 引入微信 JAVA SDK,是微信平台(公众平台、开放平台、商户平台、服务商平台)接口服务的JAVA 实现,开发 严格按照官方技术文档,合理划分包名、定义字段及方法,能胜任任何微信相关的业务。这是一个开源的工具包,开源地址:https://github.com/liyiorg/weixin-popular
<dependency>
<groupId>com.github.liyiorg</groupId>
<artifactId>weixin-popular</artifactId>
<version>2.8.32</version>
</dependency>
三、签名验证控制器
package top.yiqifu.weixin.offiaccount.controller;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import top.yiqifu.weixin.offiaccount.WeixinConfig;
import weixin.popular.bean.message.EventMessage;
import weixin.popular.bean.xmlmessage.XMLImageMessage;
import weixin.popular.bean.xmlmessage.XMLMessage;
import weixin.popular.bean.xmlmessage.XMLTextMessage;
import weixin.popular.support.ExpireKey;
import weixin.popular.support.TokenManager;
import weixin.popular.support.expirekey.DefaultExpireKey;
import weixin.popular.util.SignatureUtil;
import weixin.popular.util.XMLConverUtil;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
@Controller()
@RequestMapping("/weixin")
public class WeixinController {
private static ExpireKey expireKey = new DefaultExpireKey();
@Autowired
WeixinConfig config;
@RequestMapping("/signature")
@ResponseBody
public void checkSignature(@RequestParam Map<String, String> param ,
HttpServletRequest request, HttpServletResponse response){
String signature = param.get("signature");
String timestamp = param.get("timestamp");
String nonce = param.get("nonce");
String echostr = param.get("echostr");
if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp)) {
responseContent(response, "非法请求,关键信息丢失");
return;
}
if (StringUtils.isNotEmpty(echostr)) {
responseContent(response, echostr);
return;
}
String token = config.getToken();
if (!signature.equals(SignatureUtil.generateEventMessageSignature(token, timestamp, nonce))) {
responseContent(response, "非法请求,签名验证失败");
return;
}
try {
this.messageHandler(request, response);
} catch (IOException e) {
e.printStackTrace();
responseContent(response, "处理消息失败");
}
}
private void messageHandler(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
ServletOutputStream outputStream = response.getOutputStream();
if (inputStream != null) {
EventMessage eventMessage = XMLConverUtil.convertToObject(EventMessage.class, inputStream);
String key = eventMessage.getFromUserName() + "__" + eventMessage.getToUserName() + "__" + eventMessage.getMsgId() + "__" + eventMessage.getCreateTime();
if (expireKey.exists(key)) {
responseContent(response, "重复通知不作处理");
return;
} else {
expireKey.add(key);
}
String fromUserName = eventMessage.getFromUserName();
String toUserName = eventMessage.getToUserName();
String content = eventMessage.getContent();
String text = "收到消息"+content+" <a href='https://www.baidu.com/?s="+content+"'>百度一下</a>";
XMLTextMessage message1 = new XMLTextMessage(fromUserName, toUserName, text);
this.responseMessage(message1, outputStream);
}
}
private void responseMessage(XMLMessage message, ServletOutputStream outputStream){
message.outputStreamWrite(outputStream);
}
private void responseContent(HttpServletResponse response, String message){
try {
PrintWriter writer = response.getWriter();
writer.print(message);
} catch (IOException e) {
e.printStackTrace();
}
}
@RequestMapping("test")
@ResponseBody
public String test(){
return "111";
}
}
四、应用参数配置
- 在application.yaml中配置微信相关参数
weixin:
offiaccount:
appid: wx56b01298d347f42f
appsecret: 1e6b4ed052c6897f4a4101af8ebb149f
token: test-weixin-offiaccount
package top.yiqifu.weixin.offiaccount;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class WeixinConfig {
@Value("${weixin.offiaccount.appid}")
private String appID;
@Value("${weixin.offiaccount.appsecret}")
private String appsecret;
@Value("${weixin.offiaccount.token}")
private String token;
public String getAppID() {
return appID;
}
public String getAppsecret() {
return appsecret;
}
public String getToken() {
return token;
}
}
五、测试公众号配置
- 配置地址:https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
六、会话保持
6.1 Ticket
package top.yiqifu.weixin.offiaccount.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.yiqifu.weixin.offiaccount.config.WeixinConfig;
import weixin.popular.support.TicketManager;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@Component
@WebListener
public class TicketManagerListener implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(TicketManagerListener.class);
@Autowired
WeixinConfig config;
@Override
public void contextInitialized(ServletContextEvent sce) {
logger.info("------------------TicketManagerListener----contextInitialized---------------");
TicketManager.init(config.getAppID(), 15, 60 * 119);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
logger.info("------------------TicketManagerListener----contextInitialized---------------");
TicketManager.destroyed();
}
}
6.2 Token
package top.yiqifu.weixin.offiaccount.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import top.yiqifu.weixin.offiaccount.config.WeixinConfig;
import weixin.popular.support.TokenManager;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@Component
@WebListener
public class TokenManagerListener implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(TokenManagerListener.class);
@Autowired
WeixinConfig config;
@Override
public void contextInitialized(ServletContextEvent sce) {
logger.info("------------------TokenManagerListener----contextInitialized---------------");
TokenManager.init(config.getAppID(), config.getAppsecret());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
logger.info("------------------TokenManagerListener----destroyed---------------");
TokenManager.destroyed();
}
}
七、消息处理
- 消息处理的作用的是当你在公众号中发送消息时,服务器端可以根据消息内容做出相应的响应。
private void messageHandler(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
ServletOutputStream outputStream = response.getOutputStream();
if (inputStream != null) {
EventMessage eventMessage = XMLConverUtil.convertToObject(EventMessage.class, inputStream);
String key = eventMessage.getFromUserName() + "__" + eventMessage.getToUserName() + "__" + eventMessage.getMsgId() + "__" + eventMessage.getCreateTime();
if (expireKey.exists(key)) {
responseContent(response, "重复通知不作处理");
return;
} else {
expireKey.add(key);
}
String fromUserName = eventMessage.getFromUserName();
String toUserName = eventMessage.getToUserName();
String content = eventMessage.getContent();
String text = "收到消息"+content+" <a href='https://www.baidu.com/?s="+content+"'>百度一下</a>";
XMLTextMessage message1 = new XMLTextMessage(fromUserName, toUserName, text);
this.responseMessage(message1, outputStream);
}
}
private void responseMessage(XMLMessage message, ServletOutputStream outputStream){
message.outputStreamWrite(outputStream);
}
八、自定义菜单
- 在公众号的底部添加菜单(下面是通过访问/weixin/test/menu地址来添加)
package top.yiqifu.weixin.offiaccount.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import weixin.popular.api.MenuAPI;
import weixin.popular.bean.BaseResult;
import weixin.popular.support.TokenManager;
@Controller()
@RequestMapping("/weixin/test")
public class TestController {
@RequestMapping("menu")
@ResponseBody
public BaseResult test(){
String menuText = "{\n" +
" \"button\": [\n" +
" {\n" +
" \"name\": \"扫码\",\n" +
" \"sub_button\": [\n" +
" {\n" +
" \"type\": \"scancode_waitmsg\",\n" +
" \"name\": \"扫码带提示\",\n" +
" \"key\": \"rselfmenu_0_0\",\n" +
" \"sub_button\": [ ]\n" +
" },\n" +
" {\n" +
" \"type\": \"scancode_push\",\n" +
" \"name\": \"扫码推事件\",\n" +
" \"key\": \"rselfmenu_0_1\",\n" +
" \"sub_button\": [ ]\n" +
" }\n" +
" ]\n" +
" },\n" +
" {\n" +
" \"name\": \"发图\",\n" +
" \"sub_button\": [\n" +
" {\n" +
" \"type\": \"pic_sysphoto\",\n" +
" \"name\": \"系统拍照发图\",\n" +
" \"key\": \"rselfmenu_1_0\",\n" +
" \"sub_button\": [ ]\n" +
" },\n" +
" {\n" +
" \"type\": \"pic_photo_or_album\",\n" +
" \"name\": \"拍照或者相册发图\",\n" +
" \"key\": \"rselfmenu_1_1\",\n" +
" \"sub_button\": [ ]\n" +
" },\n" +
" {\n" +
" \"type\": \"pic_weixin\",\n" +
" \"name\": \"微信相册发图\",\n" +
" \"key\": \"rselfmenu_1_2\",\n" +
" \"sub_button\": [ ]\n" +
" }\n" +
" ]\n" +
" },\n" +
" {\n" +
" \"name\": \"发送位置\",\n" +
" \"sub_button\": [\n" +
" {\n" +
" \"name\": \"发送位置\",\n" +
" \"type\": \"location_select\",\n" +
" \"key\": \"rselfmenu_2_0\"\n" +
" }\n" +
" ]\n" +
" }\n" +
"\n" +
"\n" +
" ]\n" +
"}";
BaseResult result = MenuAPI.menuCreate(TokenManager.getDefaultToken(), menuText);
return result;
}
}