微信公众号开发之基础框架搭建(基于SpringBoot)

一、概述

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)) {
                // 重复通知不作处理
                //System.err.println("重复通知不作处理");
                responseContent(response, "重复通知不作处理");
                return;
            } else {
                expireKey.add(key);
            }

            //logger.info(TokenManager.getDefaultToken());
            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);

//            String mediaId = "zYNNuHMhJ0dstBmlXEiBHmo69BZnjFKp6J6MNP8i9ZtLrJFnZKBYE9qEn3Bu2Xp8";
//			XMLImageMessage message2 = new XMLImageMessage(fromUserName, toUserName , mediaId);
//            this.responseMessage(message2, 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)) {
                // 重复通知不作处理
                //System.err.println("重复通知不作处理");
                responseContent(response, "重复通知不作处理");
                return;
            } else {
                expireKey.add(key);
            }

            //logger.info(TokenManager.getDefaultToken());
            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);

//            String mediaId = "zYNNuHMhJ0dstBmlXEiBHmo69BZnjFKp6J6MNP8i9ZtLrJFnZKBYE9qEn3Bu2Xp8";
//			XMLImageMessage message2 = new XMLImageMessage(fromUserName, toUserName , mediaId);
//            this.responseMessage(message2, 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;
    }
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QIFU

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值