java开发微信公众号

参考

  • gitee:https://gitee.com/binary/weixin-java-tools
  • 微信官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

申请微信测试号

  • 网址:
    https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Requesting_an_API_Test_Account.html
    java开发微信公众号_第1张图片
  • url后面要填到如下未知
    java开发微信公众号_第2张图片

内网转发

  • 下载ngrok:https://ngrok.com/download
  • cmd进入到当前目录,并输入如下命令:(80表示映射本机的80端口,可修改为别的,需要和springboot项目server.port一致)
    ngrok.exe http 80

在这里插入图片描述

  • 下图中的网址就是你在公网中的地址
    java开发微信公众号_第3张图片
  • 配置测试号url:http://0938-111-18-95-182.ngrok.io/message,token自己填,相当于一个密钥,可以改;
    java开发微信公众号_第4张图片

基本配置

pom

<dependency>
  <groupId>com.github.binarywanggroupId>
  <artifactId>(不同模块参考下文)artifactId>
  <version>4.2.0version>
dependency>
  • 微信小程序:weixin-java-miniapp
  • 微信支付:weixin-java-pay
  • 微信开放平台:weixin-java-open
  • 公众号(包括订阅号和服务号):weixin-java-mp
  • 企业号/企业微信:weixin-java-cp

例如:使用微信公众号(测试号):


<dependency>
    <groupId>com.github.binarywanggroupId>
    <artifactId>wx-java-mp-spring-boot-starterartifactId>
    <version>4.2.0version>
dependency>

yml

wx:
  mp:
    app-id: appid
    secret: secret
    token: token # 配置消息回调地址接入公众号时需要的token
server:
  port: 80
  • app-id、secret、token请替换为自己的
    java开发微信公众号_第5张图片
    java开发微信公众号_第6张图片

java开发微信公众号_第7张图片

验证

注意,controller使用@RestController注解!
注意,controller使用@RestController注解!
注意,controller使用@RestController注解!

方法一(推荐使用第二个方法)

https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:

1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

		...
		 // 1)将token、timestamp、nonce三个参数进行字典序排序
        String[] strs=new String[]{token,timestamp,nonce};
        Arrays.sort(strs);
        System.out.println(Arrays.toString(strs));
        // 2)将三个参数字符串拼接成一个字符串进行sha1加密
        StringBuilder sb=new StringBuilder();
        for (String str : strs) {
            sb.append(str);
        }
        String str_sha1= Sha1Util.sha1(sb.toString());
        // 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
        if (str_sha1.equals(signature)){
                return echostr;
        }
        ...

方法二

	@GetMapping("message")
    @ResponseBody
    public String configAccess(String signature,String timestamp,String nonce,String echostr) {
        // 校验签名
        if (wxMpService.checkSignature(timestamp, nonce, signature)){
            // 校验成功原样返回echostr
            return echostr;
        }
        // 校验失败
        return null;
    }

接收、处理消息

  • 实现如下接口
package me.chanjar.weixin.mp.api;

import java.util.Map;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;

public interface WxMpMessageHandler {
    WxMpXmlOutMessage handle(WxMpXmlMessage var1, Map<String, Object> var2, WxMpService var3, WxSessionManager var4) throws WxErrorException;
}

详细解释:
java开发微信公众号_第8张图片

原样返回消息

package com.ljy.wechat_02.component;

import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class TextHandler implements WxMpMessageHandler {
    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
        // 接收的消息内容
        String inContent = wxMessage.getContent();
        
        // 构造响应消息对象,原样返回
        return WxMpXmlOutMessage
                .TEXT()
                .content(inContent)
                .fromUser(wxMessage.getToUser())
                .toUser(wxMessage.getFromUser())
                .build();
    }
}

配置自己的规则

package com.ljy.wechat_02.config;

import com.ljy.wechat_02.component.TextHandler;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WxJavaConfig {
    @Autowired
    private WxMpService wxMpService;

    @Autowired
    private TextHandler textHandler;

    @Bean
    public WxMpMessageRouter messageRouter() {
        // 创建消息路由
        final WxMpMessageRouter router = new WxMpMessageRouter(wxMpService);
        // 添加文本消息路由
        router.rule().async(false).msgType(WxConsts.XmlMsgType.TEXT).handler(textHandler).end();
        return router;
    }
}

使用规则

在controller中加入以下内容:

	...
    @Autowired
    WxMpMessageRouter wxMpMessageRouter;
	...
    @PostMapping(value = "message", produces = "application/xml; charset=UTF-8")
    public String handleMessage(@RequestBody String requestBody,
                                @RequestParam("signature") String signature,
                                @RequestParam("timestamp") String timestamp,
                                @RequestParam("nonce") String nonce) {
        // 校验消息是否来自微信
        if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
            throw new IllegalArgumentException("非法请求!");
        }
        // 解析消息体,封装为对象
        WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
        WxMpXmlOutMessage outMessage;
        try {
            // 将消息路由给对应的处理器,获取响应
            outMessage = wxMpMessageRouter.route(inMessage);
        } catch (Exception e) {
            outMessage = null;
        }
        // 将响应消息转换为xml格式返回
        log.info("响应消息:{}",outMessage);
        return outMessage == null ? "" : outMessage.toXml();
    }

事件推送

关注事件

  1. 添加handler
// 关注处理器
@Component
@Slf4j
public class SubscribeHandler implements WxMpMessageHandler {
    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
        log.info("SubscribeHandler调用");
        return WxMpXmlOutMessage.TEXT().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
                .content("欢迎关注").build();
    }
}
  1. 在配置类中增加自己的规则
		...
	    //关注
        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
                .event(WxConsts.EventType.SUBSCRIBE)
                .handler(subscribeHandler).end();

自定义菜单

微信官方文档

https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
java开发微信公众号_第9张图片

菜单

  • 通过以下类定义菜单
    java开发微信公众号_第10张图片

按钮

  • 通过以下类定义按钮
@Data
public class WxMenuButton implements Serializable {
  private static final long serialVersionUID = -1070939403109776555L;

  /**
   * 
   * 菜单的响应动作类型.
   * view表示网页类型,
   * click表示点击类型,
   * miniprogram表示小程序类型
   * 
*/
private String type; /** * 菜单标题,不超过16个字节,子菜单不超过60个字节. */ private String name; /** *
   * 菜单KEY值,用于消息接口推送,不超过128字节.
   * click等点击类型必须
   * 
*/
private String key; /** *
   * 网页链接.
   * 用户点击菜单可打开链接,不超过1024字节。type为miniprogram时,不支持小程序的老版本客户端将打开本url。
   * view、miniprogram类型必须
   * 
*/
private String url; /** *
   * 调用新增永久素材接口返回的合法media_id.
   * media_id类型和view_limited类型必须
   * 
*/
@SerializedName("media_id") private String mediaId; /** *
   * 小程序的appid.
   * miniprogram类型必须
   * 
*/
@SerializedName("appid") private String appId; /** *
   * 小程序的页面路径.
   * miniprogram类型必须
   * 
*/
@SerializedName("pagepath") private String pagePath; @SerializedName("sub_button") private List<WxMenuButton> subButtons = new ArrayList<>(); @Override public String toString() { return WxGsonBuilder.create().toJson(this); } }

其中有该方法,可用作设置子按钮
在这里插入图片描述

  • 按钮的类型包括:
  public static class MenuButtonType {
    /**
     * 点击推事件.
     */
    public static final String CLICK = "click";
    /**
     * 跳转URL.
     */
    public static final String VIEW = "view";
    /**
     * 跳转到小程序.
     */
    public static final String MINIPROGRAM = "miniprogram";
    /**
     * 扫码推事件.
     */
    public static final String SCANCODE_PUSH = "scancode_push";
    /**
     * 扫码推事件且弹出“消息接收中”提示框.
     */
    public static final String SCANCODE_WAITMSG = "scancode_waitmsg";
    /**
     * 弹出系统拍照发图.
     */
    public static final String PIC_SYSPHOTO = "pic_sysphoto";
    /**
     * 弹出拍照或者相册发图.
     */
    public static final String PIC_PHOTO_OR_ALBUM = "pic_photo_or_album";
    /**
     * 弹出微信相册发图器.
     */
    public static final String PIC_WEIXIN = "pic_weixin";
    /**
     * 弹出地理位置选择器.
     */
    public static final String LOCATION_SELECT = "location_select";
    /**
     * 下发消息(除文本消息).
     */
    public static final String MEDIA_ID = "media_id";
    /**
     * 跳转图文消息URL.
     */
    public static final String VIEW_LIMITED = "view_limited";
  }

例子

	@GetMapping("createMenu")
    public String createMenu() throws WxErrorException {
        // 创建菜单对象
        WxMenu menu = new WxMenu();
        // 创建按钮1
        WxMenuButton button1 = new WxMenuButton();
        button1.setType(WxConsts.MenuButtonType.CLICK);
        button1.setName("1");
        button1.setKey("1");
        // 创建按钮2
        WxMenuButton button2 = new WxMenuButton();
        button2.setName("2");
        // 创建按钮2的子按钮1
        WxMenuButton button21 = new WxMenuButton();
        button21.setType(WxConsts.MenuButtonType.VIEW);
        button21.setName("2.1");
        button21.setUrl("https://www.baidu.com/");
        // 创建按钮2的子按钮2
        WxMenuButton button22 = new WxMenuButton();
        button22.setType(WxConsts.MenuButtonType.VIEW);
        button22.setName("2.2");
        button22.setUrl("https://v.qq.com/");
        // 将子按钮添加到按钮2
        button2.getSubButtons().add(button21);
        button2.getSubButtons().add(button22);
        // 将按钮1和按钮2添加到菜单
        menu.getButtons().add(button1);
        menu.getButtons().add(button2);
        // 创建按钮
        return wxMpService.getMenuService().menuCreate(menu);
    }
  • 写好该GetMapping后,公众号依然没有菜单,可先拿浏览器访问该请求,例如我的是:http://localhost/createMenu,访问后重新进入公众号。
  • click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
  • view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。

你可能感兴趣的:(java,微信,spring,boot)