一、申请微信公众号账号

1、先去微信公众平台 申请一个账号,账号类型,如果是个人,可以选择订阅号,如果是企业、单位,可以申请服务号(注意:服务号是要收费的),订阅号不收费,但是接口权限较少,服务号收费,但提供的功能多。

2、根据个人来申请订阅号,也可以实现部分有限功能,申请完登录进去,

找到设置与开发,点击基本配置,打开如下页面

可以把个人使用的IP白名单填写进去,多个IP换行填写。

在服务器配置处,注意

服务器(URL)填写规则:域名/个人的微信回调地址,我个人的地址是http://hvdkdx.natappfree.cc/weChat/confirmFromWx

令牌(Token):随意填写

消息加解密密钥:随机生成

举例如下:

/**
 * 文件名: WeChatController
 * 描述: 微信公众号controller
 */
@Slf4j
@RequestMapping("/weChat")
@Controller
public class WeChatController {
    @Autowired
    private WeChatService weChatService;
    @Autowired
    private WeChatConfig weChatConfig;

    /**
     * 验证消息是否来自微信服务器
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "/confirmFromWx", method = {RequestMethod.GET, RequestMethod.POST})
    public void confirmFromWx(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("****************来自微信服务器的请求:{}", request.getMethod().toUpperCase());
        //微信服务器POST请求时,用的是UTF-8编码,在接收时也要用同样的编码,否则中文乱码
        request.setCharacterEncoding("UTF-8");
        //响应消息时,也要设置同样的编码
        response.setCharacterEncoding("UTF-8");
        //判断请求方式是否是post
        boolean isPost = Objects.equals("POST", request.getMethod().toUpperCase());
        if (isPost) {
            log.info("从微信服务器发过来POST请求,准备处理......");
            //xml转map
            try {
                Map<String, String> map = WeChatUtil.xmlToMap(request);
                String msgType = map.get("MsgType");// 消息类型,有:event
                String xml;//返回的xml
                if (MessageType.REQ_MESSAGE_TYPE_EVENT.equals(msgType)) {//事件类型
                    xml = weChatService.parseEvent(map);
                } else {//消息类型
                    xml = weChatService.parseMessage(map);
                }
                response.setContentType("text/xml");
                if (StringUtils.isNotBlank(xml)) {
                    response.getWriter().write(xml);
                } else {
                    response.getWriter().write("");
                }
            } catch (Exception e) {
                e.printStackTrace();
                response.getWriter().write("");
            }
            log.info("从微信服务器发过来POST请求,处理结束......");
        } else {
            log.info("********************验证微信服务器信息开始********************");
            //微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
            String signature = request.getParameter("signature");
            //时间戳
            String timestamp = request.getParameter("timestamp");
            //随机数
            String nonce = request.getParameter("nonce");
            //随机字符串
            String echostr = request.getParameter("echostr");
            if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce) || StringUtils.isEmpty(echostr)) {
                return;
            }
            log.info("weChat: signature is: " + signature + ",timestamp is: " + timestamp + ",nonce is:" + nonce + ",echostr is:" + echostr);
            String check = weChatService.checkSignature(signature, timestamp, nonce, echostr);
            if (StringUtils.isNotBlank(check)) {
                log.info("********************校验成功,确实来自微信服务器,验证结束********************");
                response.getWriter().write(check);
            }
            log.info("********************验证微信服务器信息结束********************");
            response.getWriter().write("");
        }
    }
}

service实现类

@Service
@Slf4j
public class WeChatServiceImpl implements WeChatService {
    @Autowired
    private WeChatConfig weChatConfig;

    @Override
    public String checkSignature(String signature, String timestamp, String nonce, String echostr) {
        String sign_token = weChatConfig.getSign_token();
        // 1.将token、timestamp、nonce三个参数进行字典序排序
        log.info("signature:{},sign_token:{},timestamp:{},nonce:{}", signature, sign_token, timestamp, nonce);
        String sha1 = WeChatUtil.getSha1(sign_token, timestamp, nonce);
        // 2.进行对比
        log.info("随机字符串echostr:{}", echostr);
        log.info("sha1算法得到的字符串:{}", sha1);
        if (sha1.equals(signature.toUpperCase())) {
            return echostr;
        }
        return null;
    }
}

MessageType类如下:

/**
 * 文件名: MessageType
 * 描述: 消息类型
 */
public class MessageType {

    /**
     * 文本消息
     */
    public static final String TEXT_MESSAGE = "text";
    /**
     * 图片消息
     */
    public static final String IMAGE_MESSAGE = "image";
    /**
     * 语音消息
     */
    public static final String VOICE_MESSAGE = "voice";
    /**
     * 视频消息
     */
    public static final String VIDEO_MESSAGE = "video";
    /**
     * 小视频消息
     */
    public static final String SHORTVIDEO_MESSAGE = "shortvideo";
    /**
     * 地理位置消息
     */
    public static final String POSOTION_MESSAGE = "location";
    /**
     * 链接消息
     */
    public static final String LINK_MESSAGE = "link";
    /**
     * 音乐消息
     */
    public static final String MUSIC_MESSAGE = "music";
    /**
     * 图文消息
     */
    public static final String IMAGE_TEXT_MESSAGE = "news";


    /**
     * 请求消息类型:事件推送
     */
    public static final String REQ_MESSAGE_TYPE_EVENT = "event";
    /**
     * 事件类型:subscribe(订阅)
     */
    public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
    /**
     * 事件类型:unsubscribe(取消订阅)
     */
    public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
    /**
     * 事件类型:scan(用户已关注时的扫描带参数二维码)
     */
    public static final String EVENT_TYPE_SCAN = "scan";
    /**
     * 事件类型:LOCATION(上报地理位置)
     */
    public static final String EVENT_TYPE_LOCATION = "location";
    /**
     * 事件类型:CLICK(自定义菜单)
     */
    public static final String EVENT_TYPE_CLICK = "click";

    /**
     * 事件类型: VIEW(点击菜单跳转链接时的事件推送)
     */
    public static final String EVENT_TYPE_VIEW = "view";


    /**
     * 响应消息类型:文本
     */
    public static final String RESP_MESSAGE_TYPE_TEXT = "text";
    /**
     * 响应消息类型:图片
     */
    public static final String RESP_MESSAGE_TYPE_IMAGE = "image";
    /**
     * 响应消息类型:语音
     */
    public static final String RESP_MESSAGE_TYPE_VOICE = "voice";
    /**
     * 响应消息类型:视频
     */
    public static final String RESP_MESSAGE_TYPE_VIDEO = "video";
    /**
     * 响应消息类型:音乐
     */
    public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
    /**
     * 响应消息类型:图文
     */
    public static final String RESP_MESSAGE_TYPE_NEWS = "news";

    /**
     * 公众号回复消息样式
     * @return
     */
    public static String menuText() {
        StringBuilder sb = new StringBuilder();
        sb.append("感谢关注");
        sb.append("该公众号已实现以下功能:\n");
        sb.append("如您在使用该公众号时,有宝贵意见,欢迎反馈!\n\n");
        sb.append("反馈邮箱:XXXXXX");
        return sb.toString();
    }
}

WeChatUtil工具类如下:

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 文件名: WeChatUtil
 * 描述: 微信工具类
 */
@Slf4j
public class WeChatUtil {
    public static void main(String[] args) {
        long currentSeconds = DateUtil.currentSeconds();
        System.out.println("当前时间戳是:"+currentSeconds);

    }

    /**
     * 用sha-1算法验证token
     *
     * @param sign_token 与公众号中配置的签名token保持一致
     * @param timestamp
     * @param nonce
     * @return
     */
    public static String getSha1(String sign_token, String timestamp, String nonce) {
        String[] strings = new String[]{sign_token, timestamp, nonce};
        Arrays.sort(strings);
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < strings.length; i++) {
            stringBuilder.append(strings[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            //将三个字符串拼到一起,进行sha1加密
            byte[] digest = md.digest(stringBuilder.toString().getBytes());
            tmpStr = byteArrToHexStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            log.error("错误信息:{}", e.getMessage());
        }
        return tmpStr;
    }

    /**
     * 将字节转换成十六进制字符串
     *
     * @param b
     * @return
     */
    private static String byteToHexStr(byte b) {
        char[] chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempChar = new char[2];
        tempChar[0] = chars[(b >>> 4) & 0X0F];
        tempChar[1] = chars[b & 0X0F];
        return new String(tempChar);
    }

    /**
     * 将字节数组转换成十六进制字符串
     *
     * @param byteArr
     * @return
     */
    private static String byteArrToHexStr(byte[] byteArr) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < byteArr.length; i++) {
            stringBuilder.append(byteToHexStr(byteArr[i]));
        }
        return stringBuilder.toString();
    }

    /**
     * xml转map
     *
     * @param request
     * @return
     * @throws IOException
     * @throws DocumentException
     */
    public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException, DocumentException {
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<>();
        // 从request中取得输入流
        InputStream inputStream = request.getInputStream();
        // 读取输入流
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        // 得到xml根元素
        Element element = document.getRootElement();
        // 得到根元素的所有子节点
        List<Element> list = element.elements();
        // 遍历所有子节点
        for (Element e : list) {
            map.put(e.getName(), e.getText());
        }
        // 释放资源
        inputStream.close();
        return map;
    }
}
WeChatConfig配置类如下:
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 文件名: WeChatConfig
 * 描述: 微信配置类
 */
@Component
@Data
public class WeChatConfig {

    private String appId;

    private String appSecret;

    private String sign_token;

    private String server_url;

    @Value("${weChat.appId}")
    public void setAppId(String appId) {
        this.appId = appId;
    }

    @Value("${weChat.appSecret}")
    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }

    @Value("${weChat.sign_token}")
    public void setSign_token(String sign_token) {
        this.sign_token = sign_token;
    }
    @Value("${weChat.server_url}")
    public void setServer_url(String server_url) {
        this.server_url = server_url;
    }

    /**
     * 1. 获取access_token.
     */
    String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?" +
            "grant_type=client_credential&appid=APPID&secret=APPSECRET";
    /**
     * 2. 获得各种类型的ticket.
     */
    String TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=";
    /**
     * 3. 长链接转短链接接口.
     */
    String SHORTURL_API_URL = "https://api.weixin.qq.com/cgi-bin/shorturl";
    /**
     * 4. 语义查询接口.
     */
    String SEMANTIC_SEMPROXY_SEARCH_URL = "https://api.weixin.qq.com/semantic/semproxy/search";
    /**
     * 5. 用code换取oauth2的access token.
     */
    String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
            "appid=APPID&secret=APPSECRET&code=CODE&grant_type=authorization_code";
    /**
     * 6. 刷新oauth2的access token.
     */
    String OAUTH2_REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?" +
            "appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
    /**
     * 7. 用oauth2获取用户信息.
     */
    String OAUTH2_USERINFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=LANG";
    /**
     * 8. 验证oauth2的access token是否有效.
     */
    String OAUTH2_VALIDATE_TOKEN_URL = "https://api.weixin.qq.com/sns/auth?access_token=ACCESS_TOKEN&openid=OPENID";
    /**
     * 9. 获取微信服务器IP地址.
     */
    String CALLBACK_IP_URL = "https://api.weixin.qq.com/cgi-bin/getcallbackip";
    /**
     * 10. 第三方使用网站应用授权登录的url.
     */
    String QRCONNECT_URL = "https://open.weixin.qq.com/connect/qrconnect?" +
            "appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
    /**
     * 11. oauth2授权的url连接.
     */
    String CONNECT_OAUTH2_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
            "appid=APPID&redirect_uri=REDIRECT_URL&response_type=code&scope=SCOPE&" +
            "state=STATE#wechat_redirect";

    /**
     * 12. 获取公众号的自动回复规则.
     */
    String CURRENT_AUTOREPLY_INFO_URL = "https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info";

    /**
     * 13.公众号调用或第三方平台帮公众号调用对公众号的所有api调用(包括第三方帮其调用)次数进行清零.
     */
    String CLEAR_QUOTA_URL = "https://api.weixin.qq.com/cgi-bin/clear_quota";
    /**
     * 14.获取微信公众号用户的基本信息(UnionID机制)
     */
    String USERINFO_URL="https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
    /**
     * 15.生成带参数的二维码
     */
    String qrCode_URL="https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN";
    /**
     * 16.显示二维码图片的URL
     */
    String showQrCode_URL="https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET";
    /**
     * 17.微信公众号长链接转短链接接口,POST请求
     */
    String shortURL="https://api.weixin.qq.com/cgi-bin/shorturl?access_token=ACCESS_TOKEN";
    /**
     * 18.自定义菜单查询接口
     */
    String findMenu_URL="https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
    /**
     *  19.删除自定义菜单接口
     */
    String deleteMenu_URL="https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
    /**
     * 20.微信 新增临时素材
     */
    String uploadTempMedia_URL="https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE";
}


application.yml配置微信的如下
weChat:
  appId: xxxxxxxx  #appId
  appSecret: xxxxx #appsecret
  sign_token: xxxx #与公众号中配置的签名sign_token保持一致
  server_url: xxxxx #服务器域名

3、如果个人不想申请,可以申请测试号即可,地址是微信公众平台 ,测试号的接口权限也很多,能满足大部分需要。

4、下面是部分接口权限表截图

二、有域名,没有域名的处理方法

上面提到测试号填写的URL中包含域名,如果个人有域名,则直接填上去即可,如果没有域名的解决办法,可以使用内网穿透来实现,我个人使用的 NATAPP来实现的,地址是NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 ,具体可以查看NATAPP文档,来获取域名,此处不再赘述。如果有不明白的地方,可以私我。

Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐