SpringBoot接入微信公众号【服务号】

SpringBoot接入微信公众号【服务号】

一、服务号注册

注册地址:https://mp.weixin.qq.com/cgi-bin/registermidpage?action=index&lang=zh_CN
在这里插入图片描述
注册流程参考:https://kf.qq.com/touch/faq/150804UVr222150804quq6B7.html?platform=15

二、服务号配置

基本配置

用于服务配置
在这里插入图片描述
在这里插入图片描述

访问及服务配置

在这里插入图片描述

网页授权配置

在这里插入图片描述

三、核心代码

  • 基于WxJava - 微信开发 Java SDK
  • 实现关注、取关逻辑
  • 实现消息被动回复
  • 实现个人信息网页授权
  • 提供基本业务功能

微信开发者文档:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html

WxJava开源地址:https://gitee.com/binary/weixin-java-tools?_from=gitee_search

引入pom依赖

<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-mp</artifactId>
    <version>4.5.0</version>
</dependency>

yml配置文件

# 微信公众号配置
wx:
  mp:
    appId: wx1111111
    appSecret: 111111111111
    # 配置消息回调地址接入公众号时需要的token
    token: add2222
    aesKey: xxxx
    server: https://www.baidu.com/

属性配置类

package com.qiangesoft.wechat.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 微信公众号相关配置属性
 *
 * @author qiangesoft
 * @date 2023-09-07
 */
@Data
@Component
@ConfigurationProperties(WxMpProperties.PREFIX)
public class WxMpProperties {

    public static final String PREFIX = "wx.mp";

    /**
     * 公众号开发信息:appId
     */
    private String appId;

    /**
     * 公众号开发信息:appSecret
     */
    private String appSecret;

    /**
     * 服务器配置:token
     */
    private String token;

    /**
     * 服务器配置:消息加解密密钥EncodingAESKey
     */
    private String aesKey;

    /**
     * 服务器配置:微信服务器地址
     */
    private String server;

}

bean配置类

package com.qiangesoft.wechat.config;

import com.qiangesoft.wechat.core.handler.LogHandler;
import com.qiangesoft.wechat.core.handler.event.SubscribeHandler;
import com.qiangesoft.wechat.core.handler.event.UnsubscribeHandler;
import com.qiangesoft.wechat.core.handler.message.ImageHandler;
import com.qiangesoft.wechat.core.handler.message.LocationHandler;
import com.qiangesoft.wechat.core.handler.message.MessageHandler;
import com.qiangesoft.wechat.core.handler.message.VoiceHandler;
import com.qiangesoft.wechat.core.interceptor.MessageInterceptor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 微信公众号配置
 *
 * @author qiangesoft
 * @date 2023-09-07
 */
@Slf4j
@Configuration
@RequiredArgsConstructor
public class WxMpConfiguration {

    /**
     * 微信公众号配置信息
     */
    private final WxMpProperties wxMpProperties;
    /**
     * 日志处理器
     */
    private final LogHandler logHandler;

    /**
     * 订阅事件处理器
     */
    private final SubscribeHandler subscribeHandler;
    /**
     * 取消订阅事件处理器
     */
    private final UnsubscribeHandler unsubscribeHandler;

    /**
     * 语音消息处理器
     */
    private final VoiceHandler voiceHandler;
    /**
     * 文本消息处理器
     */
    private final MessageHandler messageHandler;
    /**
     * 图片消息处理器
     */
    private final ImageHandler imageHandler;
    /**
     * 位置消息处理器
     */
    private final LocationHandler locationHandler;

    /**
     * 消息拦截器
     */
    private final MessageInterceptor messageInterceptor;

    /**
     * 声明实例
     *
     * @return
     */
    @Bean
    public WxMpService wxMpService() {
        WxMpService wxMpService = new WxMpServiceImpl();
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
        return wxMpService;
    }

    /**
     * 配置存储方式
     *
     * @return
     */
    @Bean
    public WxMpConfigStorage wxMpConfigStorage() {
        WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();
        // 公众号appId
        configStorage.setAppId(wxMpProperties.getAppId());
        // 公众号appSecret
        configStorage.setSecret(wxMpProperties.getAppSecret());
        // 公众号Token
        configStorage.setToken(wxMpProperties.getToken());
        // 公众号EncodingAESKey
        configStorage.setAesKey(wxMpProperties.getAesKey());
        return configStorage;
    }

    /**
     * 配置公众号的事件路由
     * <p>
     * 1.用户发消息
     * 2.关注事件
     * 3.取消关注事件
     * ......
     */
    @Bean
    public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
        WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
        // 记录所有消息事件的日志
        newRouter.rule().async(false).handler(this.logHandler).next();


        // 文本消息
        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.TEXT).interceptor(this.messageInterceptor).handler(this.messageHandler).end();

        // 语音消息
        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.VOICE).handler(this.voiceHandler).end();

        // 图片消息
        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.IMAGE).handler(this.imageHandler).end();

        // 位置消息
        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION).handler(this.locationHandler).end();


        // 关注事件
        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.SUBSCRIBE).handler(this.subscribeHandler).end();

        // 取消关注事件
        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.UNSUBSCRIBE).handler(this.unsubscribeHandler).end();

        return newRouter;
    }
}

网页授权接入配置

下载验证文件放于项目目录
在这里插入图片描述
在这里插入图片描述

package com.qiangesoft.wechat.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * 微信接入控制器
 * ps:(JS接口安全域名、网页授权域名需要获取MP_verify_eerrereeee.txt)
 * <a href="https://mp.weixin.qq.com/cgi-bin/settingpage?t=setting/function&action=function&token=854866943&lang=zh_CN">...</a>
 *
 * @author qiangesoft
 * @date 2023-09-11
 */
@Slf4j
@Api(tags = "微信接入")
@RestController
@RequestMapping("/wechat")
public class WxAccessController {


    /**
     * 获取安全凭证
     * ps:例如[http://主机:端口/wechat/MP_verify_eerrereeee.txt]
     *
     * @param fileName
     * @param response
     */
    @ApiOperation(value = "安全凭证")
    @GetMapping("/{fileName}")
    public void security(@PathVariable String fileName, HttpServletResponse response) {
        InputStream is = null;
        OutputStream os = null;
        try {
            // 获取文件
            ClassPathResource classPathResource = new ClassPathResource(fileName);
            String filename = classPathResource.getFilename();
            is = classPathResource.getInputStream();
            byte[] bytes = is.readAllBytes();

            response.setCharacterEncoding("UTF-8");
            response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8));
            response.addHeader(HttpHeaders.CONTENT_LENGTH, bytes.length + "");
            response.setContentType("application/octet-stream");
            os = response.getOutputStream();
            os.write(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

微信请求验证及被动消息回复

package com.qiangesoft.wechat.controller;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
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.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * 微信消息控制器
 * ps:请求验证、接收消息被动回复等
 *
 * @author qiangesoft
 * @date 2023-09-07
 */
@Slf4j
@Api(tags = "微信消息")
@RestController
@RequestMapping("/wechat/message")
public class WxMessageController {

    @Autowired
    private WxMpService wxMpService;
    @Autowired
    private WxMpMessageRouter wxMpMessageRouter;

    @ApiOperation(value = "验证接口")
    @GetMapping
    public String validate(@RequestParam String signature,
                           @RequestParam String timestamp,
                           @RequestParam String nonce,
                           @RequestParam String echostr) {
        // 校验消息是否来自微信
        if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
            throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
        }
        return echostr;
    }

    @ApiOperation(value = "接收消息")
    @PostMapping(produces = "application/xml; charset=UTF-8")
    public String handleMessage(@RequestBody String requestBody,
                                @RequestParam String signature,
                                @RequestParam String timestamp,
                                @RequestParam String nonce,
                                @RequestParam String openid,
                                @RequestParam(name = "encrypt_type", required = false) String encType,
                                @RequestParam(name = "msg_signature", required = false) String msgSignature) {
        // 校验消息是否来自微信
        if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
            throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
        }

        // 消息处理【被动消息回复】
        String message = null;
        if (encType == null) {
            // 明文传输
            WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
            WxMpXmlOutMessage outMessage = wxMpMessageRouter.route(inMessage);
            if (outMessage == null) {
                return null;
            }
            message = outMessage.toXml();
        } else if ("aes".equalsIgnoreCase(encType)) {
            // aes加密
            WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxMpService.getWxMpConfigStorage(), timestamp, nonce, msgSignature);
            WxMpXmlOutMessage outMessage = wxMpMessageRouter.route(inMessage);
            if (outMessage == null) {
                return null;
            }
            message = outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage());
        }
        return message;
    }
}

用户信息网页授权

package com.qiangesoft.wechat.controller;

import com.qiangesoft.wechat.config.WxMpProperties;
import com.qiangesoft.wechat.dto.UserDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.service.WxOAuth2Service;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

/**
 * 微信用户控制器
 * ps:获取用户信息、授权网页用户信息等
 *
 * @author qiangesoft
 * @date 2023-09-11
 */
@Slf4j
@Api(tags = "微信用户")
@Controller
@RequestMapping("/wechat/user")
public class WxUserController {
    @Autowired
    private WxMpProperties wxMpProperties;
    @Autowired
    private WxMpService wxMpService;

    @ApiOperation(value = "个人信息")
    @GetMapping("/mine")
    public String mine() {
        WxOAuth2Service oAuth2Service = wxMpService.getOAuth2Service();
        // 构建授权url
        String authorizationUrl = oAuth2Service.buildAuthorizationUrl(wxMpProperties.getServer() + "wechat/user/callback", WxConsts.OAuth2Scope.SNSAPI_USERINFO, null);
        return "redirect:" + authorizationUrl;
    }

    @ApiOperation(value = "个人信息网页授权回调")
    @GetMapping("/callback")
    public String callback(String code, Model model) throws WxErrorException {
        WxOAuth2UserInfo userInfo = this.getUserInfo(code);
        String openid = userInfo.getOpenid();

        // todo 通过openid拿取业务系统详细用户信息 IWxUserService

        UserDTO userDTO = new UserDTO();
        userDTO.setOpenId(openid);
        userDTO.setNickName(userInfo.getNickname());
        model.addAttribute("user", userDTO);
        return "mine";
    }

    /**
     * 获取微信用户信息
     *
     * @param code
     * @return
     * @throws WxErrorException
     */
    private WxOAuth2UserInfo getUserInfo(String code) throws WxErrorException {
        WxOAuth2Service oAuth2Service = wxMpService.getOAuth2Service();

        // 利用code获取accessToken
        WxOAuth2AccessToken accessToken = oAuth2Service.getAccessToken(code);

        // 利用accessToken获取用户信息
        WxOAuth2UserInfo userInfo = oAuth2Service.getUserInfo(accessToken, null);
        return userInfo;
    }
}

消息拦截器

package com.qiangesoft.wechat.core.interceptor;

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

import java.util.Map;

/**
 * 对微信公众号消息进行预处理、过滤等操作,根据具体业务需求决定是否允许继续执行后面的路由处理方法
 * <p>
 * 1.如果要中止消息的继续处理,需要返回 false
 * 2.在执行完当前拦截器操作后,允许消息的继续处理,返回 true
 *
 * @author qiangesoft
 * @date 2023-09-07
 */
@Component
public class MessageInterceptor implements WxMpMessageInterceptor {

    @Override
    public boolean intercept(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
        String msgType = wxMessage.getMsgType();
        String content = wxMessage.getContent();
        // 文本处理
        if (msgType.equals(WxConsts.XmlMsgType.TEXT)) {
            if (content.contains("混蛋")) {
                String newContent = content.replace("混蛋", "***");
                wxMessage.setContent(newContent);
            }
            return true;
        }
        // todo 音频、视频处理
        return true;
    }
}

消息事件处理器

1.关注事件处理器
package com.qiangesoft.wechat.core.handler.event;

import com.qiangesoft.wechat.core.builder.TextOutMessageBuilder;
import com.qiangesoft.wechat.entity.WxUser;
import com.qiangesoft.wechat.service.IWxUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
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;

/**
 * 微信公众号用户关注处理器
 *
 * @author qiangesoft
 * @date 2023-09-07
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class SubscribeHandler implements WxMpMessageHandler {

    private final IWxUserService wxUserService;

    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
        if (!wxMessage.getMsgType().equals(WxConsts.EventType.SUBSCRIBE)) {
            return null;
        }

        String openId = wxMessage.getFromUser();

        // 微信关注用户
        WxUser wxUser = wxUserService.getByOpenId(openId);
        if (wxUser == null) {
            wxUser = new WxUser();
            wxUser.setOpenId(openId);
            wxUser.setAttentionFlag(true);
            wxUserService.save(wxUser);
        } else {
            wxUser.setAttentionFlag(true);
            wxUserService.updateById(wxUser);
        }

        String outContent = "您好,欢迎关注xxx!";
        return new TextOutMessageBuilder().build(outContent, wxMessage, wxMpService);
    }
}
2.取关事件处理器
package com.qiangesoft.wechat.core.handler.event;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.qiangesoft.wechat.core.builder.TextOutMessageBuilder;
import com.qiangesoft.wechat.entity.WxUser;
import com.qiangesoft.wechat.service.IWxUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
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;

/**
 * 微信公众号用户取消关注处理器
 *
 * @author qiangesoft
 * @date 2023-09-07
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class UnsubscribeHandler implements WxMpMessageHandler {

    private final IWxUserService wxUserService;

    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
        if (!wxMessage.getMsgType().equals(WxConsts.EventType.UNSUBSCRIBE)) {
            return null;
        }

        String openId = wxMessage.getFromUser();

        // 取消关注
        LambdaUpdateWrapper<WxUser> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(WxUser::getOpenId, openId)
                .set(WxUser::getAttentionFlag, false);
        wxUserService.update(updateWrapper);

        String outContent = "已取消关注xxx,再见!";
        return new TextOutMessageBuilder().build(outContent, wxMessage, wxMpService);
    }
}

3.文本消息处理器
package com.qiangesoft.wechat.core.handler.message;

import com.qiangesoft.wechat.core.builder.TextOutMessageBuilder;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
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;

/**
 * 文本消息处理器
 *
 * @author qiangesoft
 * @date 2023-09-07
 */
@Slf4j
@Component
public class MessageHandler implements WxMpMessageHandler {
    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
        if (!wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) {
            return null;
        }

        // 接收的消息内容
        String content = wxMessage.getContent();

        log.info("Received wechat text message, content [{}] !", content);

        String outContent = "您好,欢迎访问xxx!";
        if (content.contains("你好") || content.contains("您好")) {
            outContent = "您好";
        }

        return new TextOutMessageBuilder().build(outContent, wxMessage, wxMpService);
    }
}
4.日志记录处理器
package com.qiangesoft.wechat.core.handler;

import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
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;

/**
 * 微信公众号日志记录处理器
 *
 * @author qiangesoft
 * @date 2023-09-07
 */
@Slf4j
@Component
public class LogHandler implements WxMpMessageHandler {

    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
        log.info("Received wechat message, content is [{}]", JSONObject.toJSONString(wxMessage));
        return null;
    }
}

四、源码地址

地址:https://gitee.com/qiangesoft/boot-business/tree/master/boot-business-wechat

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

PG_强哥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值