微信公众号对接记录java版本

微信公众号对接记录java版本

点点

2021-04-28 21:43 阅读 280 喜欢 0

开发过程中使用的测试号进行开发,需要先配置一些地址信息等。 这里开发需要使用外网,用的是frp.

相关地址

测试号: http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login&token=217603174&lang=zh_CN 微信官网:http://mp.weixin.qq.com 开发者文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432

流程介绍

URL 为微信调用的地址,在这里,微信服务器会发送信息到这个地址中,我们要做的就是正确的返回微信的token校验。 微信的token校验为GET访问,示例代码如下: WeChatController.java

 /***
 * 验证wechat token 地址,根据接受的数据进行字典加密后如果正确返回echostr
 * @url /wechat/validateToken
 * @param signature
 * @param timestamp
 * @param nonce
 * @param echostr
 * @return
 */
@GetMapping("/api")
@ResponseBody
public String validateToken(String signature,String timestamp,String nonce,String echostr) {
    try {
        if(SignUtil.checkSignature(signature, timestamp, nonce)) {
            return echostr;
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "token validate failed!";
}

验证token的工具类,SignUtil.java.

 /** 
 * 验证签名 
 *  
 * @param signature 
 * @param timestamp 
 * @param nonce 
 * @return 
 */  
public static boolean checkSignature(String signature, String timestamp, String nonce) {  
    String[] arr = new String[] { ConstantUtil.WECHAT_TOKEN/**该值为上方我们自己填写的token内容**/, timestamp, nonce };  
    // 将token、timestamp、nonce三个参数进行字典序排序  
    Arrays.sort(arr);  
    StringBuilder content = new StringBuilder();  
    for (int i = 0; i < arr.length; i++) {  
        content.append(arr[i]);  
    }  
    MessageDigest md = null;  
    String tmpStr = null;  

    try {  
        md = MessageDigest.getInstance("SHA-1");  
        // 将三个参数字符串拼接成一个字符串进行sha1加密  
        byte[] digest = md.digest(content.toString().getBytes());  
        tmpStr = byteToStr(digest);  
    } catch (NoSuchAlgorithmException e) {  
        e.printStackTrace();  
    }  

    content = null;  
    // 将sha1加密后的字符串可与signature对比,标识该请求来源于微信  
    return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;  
} 
/** 
 * 将字节数组转换为十六进制字符串 
 *  
 * @param byteArray 
 * @return 
 */  
private static String byteToStr(byte[] byteArray) {  
    String strDigest = "";  
    for (int i = 0; i < byteArray.length; i++) {  
        strDigest += byteToHexStr(byteArray[i]);  
    }  
    return strDigest;  
}  

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

    String s = new String(tempArr);  
    return s;  
}  

启动我们的项目,同时启动frp,在浏览器中访问对应的域名地址后校验token就可以啦。

js安全域名

这个域名是我们用来开发web的时候微信校验使用的,暂时先填写一个就可以,需要注意的地方是:该域名只写域名不带协议,也就是http https 等都不用写入,例如:demo.chrunlee.cn 即可。

token获取

接下来,我们就要开始获取token,通过token来调用微信的接口获取数据。关于如何保存token这个方法比较多,存文件、session、数据库等都可以,只要尽可能的保证token有效即可。

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。 我们可以做定时刷新,也可以在使用的时候校验超期时间来刷新,总之token的获取次数每天是有限的,如果超过后,当天就无法获取了哦,测试的时候一定要注意了。

token的获取很简单,按照官方文档调去API接口即可。 WeixinUtil.java

  // 获取access_token的接口地址(GET) 限200(次/天)
public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

/**
 * 获取access_token
 * 
 * @param appid 凭证
 * @param appsecret 密钥
 * @return
 */
public static AccessToken getAccessToken(String appid, String appsecret) {
    AccessToken accessToken = null;

    String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
    JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
    // 如果请求成功
    if (null != jsonObject) {
        try {
            accessToken = new AccessToken();
            accessToken.setToken(jsonObject.getString("access_token"));
            accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
        } catch (JSONException e) {
            accessToken = null;
            // 获取token失败
            log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
        }
    }
    return accessToken;
}
/**
 * 发起https请求并获取结果
 * 
 * @param requestUrl 请求地址
 * @param requestMethod 请求方式(GET、POST)
 * @param outputStr 提交的数据
 * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
 */
public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
    JSONObject jsonObject = null;
    StringBuffer buffer = new StringBuffer();
    try {
        // 创建SSLContext对象,并使用我们指定的信任管理器初始化
        TrustManager[] tm = { new MyX509TrustManager() };
        SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
        sslContext.init(null, tm, new java.security.SecureRandom());
        // 从上述SSLContext对象中得到SSLSocketFactory对象
        SSLSocketFactory ssf = sslContext.getSocketFactory();

        URL url = new URL(requestUrl);
        HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
        httpUrlConn.setSSLSocketFactory(ssf);

        httpUrlConn.setDoOutput(true);
        httpUrlConn.setDoInput(true);
        httpUrlConn.setUseCaches(false);

        // 设置请求方式(GET/POST)
        httpUrlConn.setRequestMethod(requestMethod);

        if ("GET".equalsIgnoreCase(requestMethod))
            httpUrlConn.connect();

        // 当有数据需要提交时
        if (null != outputStr) {
            OutputStream outputStream = httpUrlConn.getOutputStream();
            // 注意编码格式,防止中文乱码
            outputStream.write(outputStr.getBytes("UTF-8"));
            outputStream.close();
        }


        // 将返回的输入流转换成字符串
        InputStream inputStream = httpUrlConn.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        String str = null;
        while ((str = bufferedReader.readLine()) != null) {
            buffer.append(str);
        }
        bufferedReader.close();
        inputStreamReader.close();
        // 释放资源
        inputStream.close();
        inputStream = null;
        httpUrlConn.disconnect();
        jsonObject = JSONObject.fromObject(buffer.toString());
    } catch (ConnectException ce) {
        log.error("Weixin server connection timed out.");
    } catch (Exception e) {
        log.error("https request error:{}", e);
    }
    return jsonObject;
}

接受消息

当用户在公众号发送消息的时候,微信会将消息内容转发到我们的服务器地址上,地址则是第一步我们配置的接口地址。 微信发送的数据是以xml的格式来的,所以我们要解析xml数据,具体格式参考:微信官方文档#消息管理。

这里只是做了普通文本消息的整理,其他的暂未用到,可以参考下。 MessageUtil.java

package com.boyuyun.wechat.util;

import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.net.nntp.Article;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.boyuyun.wechat.resp.ImageMessage;
import com.boyuyun.wechat.resp.TextMessage;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;


/**
* 消息处理工具类
* 
*/
public class MessageUtil {

// 请求消息类型:文本
    public static final String REQ_MESSAGE_TYPE_TEXT = "text";
    // 请求消息类型:图片
    public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
    // 请求消息类型:语音
    public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
    // 请求消息类型:视频
    public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
    // 请求消息类型:地理位置
    public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
    // 请求消息类型:链接
    public static final String REQ_MESSAGE_TYPE_LINK = "link";

    // 请求消息类型:事件推送
    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";

    // 响应消息类型:文本
    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";

    /*   新加变量    */
    public static final String RESP_MESSAGE_TYPE_VIEW = "view";

    /**
     * 扩展xstream使其支持CDATA
     */
    /*private static XStream xstream = new XStream(new XppDriver() {
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                // 对所有xml节点的转换都增加CDATA标记
                boolean cdata = true;

                public void startNode(String name, Class clazz) {
                    super.startNode(name, clazz);
                }

                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("<![CDATA[");
                        writer.write(text);
                        writer.write("]]>");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });*/
    /**
     * 解析微信发来的请求(XML)
     * 
     * @param request
     * @return Map<String, String>
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
        // 将解析结果存储在HashMap中
        Map<String, String> map = new HashMap<String, String>();

        // 从request中取得输入流
        InputStream inputStream = request.getInputStream();
        // 读取输入流
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        // 得到xml根元素
        Element root = document.getRootElement();
        // 得到根元素的所有子节点
        List<Element> elementList = root.elements();

        // 遍历所有子节点
        for (Element e : elementList)
            map.put(e.getName(), e.getText());

        // 释放资源
        inputStream.close();
        inputStream = null;

        return map;
    }



    /**
     * 文本消息对象转换成xml
     * 
     * @param textMessage 文本消息对象
     * @return xml
     */
    public static String messageToXml(TextMessage textMessage) {
        XStream xstream = new XStream();
        xstream.alias("xml", textMessage.getClass());
        return xstream.toXML(textMessage);
    }

    /**
     * 图片消息对象转换成xml
     * 
     * @param imageMessage 图片消息对象
     * @return xml
     */
    public static String messageToXml(ImageMessage imageMessage) {
        XStream xstream = new XStream();
        xstream.alias("xml", imageMessage.getClass());
        return xstream.toXML(imageMessage);
    }
 }

包括用户发送的消息、用户关注或取消关注都会触发调用该接口。 返回消息的时候,只需要对照官网文档中的xml结构返回数据即可。 关于封装可以参考MessageUtil.java中的messageToXml函数。

获取用户信息

在用户关注我们的公众号的时候,就可以通过接口获取到用户的基本信息啦。当然,后续的网页请求也会让用户去授权,同时也可以拿到用户信息。 以下为根据用户的openId 和 token 来获得用户的基本信息。 WeixinUtil.java

 //根据openId 获取微信用户基本信息
public static String SERVER_USERINFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=${tokenStr}&openid=${openId}&lang=zh_CN";

/***
 * 根据用户的openId 和 token 获取用户基本信息
 * @param token
 * @param openId
 * @return
 */
public static WeiLoginInfoPo getUserInfoByServer(String token,String openId) {
    String requestUrl = SERVER_USERINFO_URL.replace("${tokenStr}", token).replace("${openId}", openId);
    JSONObject jo = httpRequest(requestUrl, "GET", null);
    //将json转成对象返回。
    System.out.println(jo.toString());
    JSONArray ja = jo.getJSONArray("tagid_list");
    String tagIdStr = "";
    for (Iterator iterator = ja.iterator(); iterator.hasNext();) {
        String object = (String) iterator.next();
        tagIdStr += object;
    }
    WeiLoginInfoPo info = new WeiLoginInfoPo();
    info.setSubscribe(jo.getInt("subscribe"));
    info.setOpenId(jo.getString("openid"));
    info.setNickName(jo.getString("nickname"));
    info.setSex(jo.getInt("sex"));
    info.setLanguage(jo.getString("language"));
    info.setCity(jo.getString("city"));
    info.setProvince(jo.getString("province"));
    info.setCountry(jo.getString("country"));
    info.setHeadimgurl(jo.getString("headimgurl"));
    info.setSubscribe_time(jo.getLong("subscribe_time"));
    info.setRemark(jo.getString("remark"));
    info.setGroupid(jo.getInt("groupid"));
    info.setTagidList(tagIdStr);
    info.setSubscribe_scene(jo.getString("subscribe_scene"));
    info.setQr_scene(jo.getInt("qr_scene"));
    info.setQr_scene_str(jo.getString("qr_scene_str"));
    return info;
}

具体官方文档可参考:微信官方文档#获取用户基本信息 用户基本信息获取后可以保存在数据库中,用于后续的身份校验或记录。

数据走向

接下来用户就开始进入我们的应用界面啦,大体的流程如下:

用户点击链接进入我们的系统后台 后台去跳转oauth2地址,去校验用户认证信息 微信调用回调地址到我们系统中,此时我们就可以获取web token数据,然后获取js ticket。 获得ticket后进行存储,返回到页面端,提供给weixin.js来使用。 此时用户访问的页面中,我们就可以通过js sdk来进行调用微信的接口函数啦。

网页对接

关于网页授权(与服务器授权基本类似)可以参考微信官方文档#网页授权

我这边的代码会掺杂一些我的业务代码,仅供参考:

//通过accesstoken 获取js-sdk 开发的ticket
public static String WEB_JS_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${ACCESS_TOKEN}&type=jsapi";
/***
 * 通过token获得js-sdk 的ticket,以accessToken的形式返回
 * @param token
 * @return
 */
public AccessToken getTicketByToken(String token) {
    String requestUrl = WEB_JS_TICKET_URL.replace("${ACCESS_TOKEN}", token);
    JSONObject jo = httpRequest(requestUrl, "GET", null);
    //将json转成对象返回。
    System.out.println(jo.toString());
    String ticket = jo.getString("ticket");
    Integer expireIn = jo.getInt("expires_in");
    AccessToken accessToken = new AccessToken();
    accessToken.setExpiresIn(expireIn);
    accessToken.setToken(ticket);
    System.out.println("---用户进入页面获得ticket : "+ticket);
    return accessToken;
}
/**
 * 根据传递过来的schoolWeixin 数据产生ticket并返回
 * @return
 */
public JsTicket getTicketBySchool(SchoolWeixin school,String url) {
    /**
     * 1.校验ticket是否过期,如果过期,通过token生成并更新
     * 2.拿到ticket,然后随机生成数据,进行前面
     * 3.返回数据内容
     */
    String ticket = school.getTicket();
    Date ticketdate = school.getTicketdate();
    Date now = new Date();
    JsTicket jsTicket = new JsTicket();
    if(null == ticketdate || ticketdate.getTime() < now.getTime()) {
        //重新拿ticket
        String token = getTokenStr(school);
        AccessToken tokenPo = getTicketByToken(token);
        //更新token
        Date now2 = new Date();
        Date ticketdate2 = new Date(now2.getTime() + tokenPo.getExpiresIn()* 1000);
        school.setTicket(tokenPo.getToken());
        school.setTicketdate(ticketdate2);
        weService.updateSchoolTicket(school);
        jsTicket.setTicket(tokenPo.getToken());
    }else {
        jsTicket.setTicket(ticket);
    }

    String noncestr = getRandomStr(6);//随机字符串四五位
    Long timestamp = new Date().getTime();
    String timestampstr = timestamp.toString();
    //计算signature
    String str = "jsapi_ticket="+jsTicket.getTicket()+"&noncestr="+noncestr+"&timestamp="+timestampstr+"&url="+url;

    MessageDigest md = null;
    String tmpStr = "";
    try {
        md = MessageDigest.getInstance("SHA-1");
        // 将三个参数字符串拼接成一个字符串进行sha1加密  
        byte[] digest = md.digest(str.getBytes());  
        tmpStr = byteToStr(digest);
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    }
    jsTicket.setAppId(school.getAppId());
    jsTicket.setSignature(tmpStr);
    jsTicket.setNoncestr(noncestr);
    jsTicket.setTimestamp(timestampstr);
    jsTicket.setUrl(url);

    return jsTicket;
}

网页分享/界面自定义

这几个都是前端的东西,直接按照文档写就可以了 微信官方文档#JS-SDK

 //引入weixin.js
wx.config({
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出      来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅  在pc端时才会打印。
 appId: '', // 必填,公众号的唯一标识
 timestamp: , // 必填,生成签名的时间戳
 nonceStr: '', // 必填,生成签名的随机串
 signature: '',// 必填,签名
 jsApiList: [] // 必填,需要使用的JS接口列表
});

安全

总之,在开发过程中一定要注意token的保持和保护,所有的校验和生成都放在后端来做,尽量保证安全。

转载请注明出处: http://sdxlp.cn/article/jiru.html


如果对你有用的话,请赏给作者一个馒头吧 ...或帮点下页面底部的广告,感谢!!

赞赏支持
提交评论
评论信息(请文明评论)
暂无评论,快来快来写想法...
推荐
为实现华为手机与电脑的文件传输与共享,以下需要在手机和电脑上所做的设置是一次性的,在最初使用前设置好即可,以后使用时无需再次设置。
智能手机成为普遍存在后,年轻人很多都喜欢刷B站,那么手机B站缓存的视频在哪个文件夹呢?今天小编为小伙伴们讲解一下。
最近小编在各大的平台都看到很多的小伙伴们都在找副业,也有小伙伴找小编想给小编的网站啊做运营,也有私信小编的,有时间小编会一一回复,在平台上的大多数都是想收割韭菜的,副业大部分还是需要自己静下心来慢慢做,声音,图片,绘画,面部表情,写作等等都是可以的,主要是找准方向,持续更新,刚开始都是难的后期就会容易,前期也是没有毛毛拿的,需要小伙伴们,有耐心,坚持。
作为微软比较稳的操作系统,越来越多小伙伴们都升级或重装了Windows10,不过也有部分小伙伴们遇到了一些比较棘手的问题,比如今天要分享的win10开机黑屏时间长,通常都是开机比较慢,然后是黑屏,用户需要等半分钟,甚至一分钟才能进入系统。那么,win10开机黑屏时间长怎么办?
很多小伙伴们既想删好友但又舍不得聊天记录!小编也一样 ,下面就告诉小伙伴们如何删好友但保留聊天记录的方法。
现在手机版微信软件有很多小伙伴们在使用,用来聊天,支付等,有的小伙伴在使用该软件时,想要赠送亲属卡给他人,来支付他人的支出,但是却不知道如何赠送,那么小编就来为小伙伴们介绍一下吧。
智能手机成为我们生活中必不可少的生活用品,当我们在使用手机的时候,可以下载微信app来与好友取得联系,但是有时候微信会看不到,又遇到特别急的事情,那怎么在是好友状态不知道手机号的情况下,给其打电话。在微信内,一般用户都会绑定自己的手机号码,那么如何通过微信号来查询对应的手机号呢?接下来就由点点来告诉小伙伴们。
微信怎么设置主题背景?很多小伙伴们长时间的使用微信会觉得微信的界面和主题会有一点的单调,想要丰富一下自己的微信界面,但是很多小伙伴不知道微信怎么设置主题背景?那么下面就让点点给小伙伴们介绍一下。