微信h5卡片链接分享开发

这段时间有个需求,大概是老板看到别人发的产品链接是卡片形式展示的,而我们分享出去就是个http链接,感觉不太靠谱。

微信官方文档地址: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html

于是就改呗。这里做下开发记录,以备后需

1. 卡片链接的必要条件
  • 必须要有微信公众号
  • 使用jssdk进行开发即可
3. 开发流程
  1. 公众号获取appid,AppSecret, 并且配置Ip白名单。设置与开发-基本配置
  2. h5中引入微信的jssdk
  3. 公众号配置js安全域名设置与开发-公众号设置-功能设置-JS接口安全域名
  4. 开发后端签名代码, 参考下文
  5. h5端注册响应事件,参考下文

上述流程后在微信打开链接,点击分享给朋友,分享出去就是卡片形式啦。其实还是很简单的。

以下代码复制后即可使用。

h5端示例代码:

// js 这里需要每次请求后端生成分享的签名参数信息
   
    fetch(`/api/wxshareinfo?q=${encodeURIComponent(location.href)}`).then(res => res.json()).then(res => {

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

        // 通过error接口处理失败验证
        wx.error(function (errres) {
            console.info("失败:", errres);
        });

        wx.ready(function () {   //需在用户可能点击分享按钮前就先调用
            var sharedata = {
                title: 'hello', // 分享标题
                desc: `hello!`, // 分享描述
                link: res.url, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
                imgUrl: location.protocol + "//" + location.host+ "/logo.png", // 分享图标链接,png格式
                success: function () {
                    // 设置成功
                }
            }

            wx.updateAppMessageShareData(sharedata);
            wx.updateTimelineShareData(sharedata);


        });
    });

后端代码:

   @GetMapping(value = "/api/wxshareinfo")
    @ResponseBody
    public Map<String, String> wxshareinfo(@RequestParam("q") String url) throws UnsupportedEncodingException {
        String jsApiTicket = null;
        try {
            jsApiTicket = jsapi_ticketGetUtil.getJSApiTicket();
            Map<String, String> sign = jsapi_ticketGetUtil.sign(jsApiTicket, url);
            return sign;
        } catch (IOException e) {
            logger.error("wxshare error", e);
        }
        return null;
    }

工具类代码,这里需要啰嗦一句:
里面的access_token,和 jsticket这两个需要做缓存的,官方7200秒失效,我这里缓存了7000秒。

package com.springboot.demo.zhifu;

import com.alibaba.fastjson.JSON;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.springboot.demo.controller.BatchUpdateAgreeController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;

public class jsapi_ticketGetUtil {
    public static final String APPID = "xxxx";
    public static final String APPSecret = "xxxxxxxxx";
    private  static Logger logger = LoggerFactory.getLogger(jsapi_ticketGetUtil.class);

    public static final Cache<String, String> cache = CacheBuilder
            .newBuilder().expireAfterWrite(6000, TimeUnit.SECONDS).build();
    /***
     * 获取jsapiTicket
     * @return
     */
    public static String getJSApiTicket() throws FileNotFoundException, IOException {

        String getJSApiTicket = cache.getIfPresent("getJSApiTicket");
        if(Objects.nonNull(getJSApiTicket)) {
            return getJSApiTicket;
        }

        //获取token
        String acess_token= getAccessToken();
        String urlStr = //"http://gy7tgr.natappfree.cc//wxapi/weixinMall/index.jsp";
                "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token="+acess_token+"&type=jsapi";
        String backData=jsapi_ticketGetUtil.sendGet(urlStr, "utf-8", 10000);
        logger.info("jsapiticket 远程返回 {}", backData);

        String ticket = (String) JSON.parseObject(backData).getString("ticket");

        cache.put("getJSApiTicket", ticket);
        return  ticket;

    }
    /***
     * 获取acess_token
     * @return
     */
    public static String getAccessToken() throws FileNotFoundException, IOException{
        String getAccessToken = cache.getIfPresent("getAccessToken");
        if(Objects.nonNull(getAccessToken)) {
            return getAccessToken;
        }

        String appid = APPID;
        String appSecret = APPSecret;
        String url ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appid+"&secret="+appSecret+"";
        String backData=jsapi_ticketGetUtil.sendGet(url, "utf-8", 10000);
        logger.info("accesstoken 远程返回 {}", backData);
        String accessToken = (String) JSON.parseObject(backData).getString("access_token");

        cache.put("getAccessToken", accessToken);
        return accessToken;
    }
    public static Map<String, String> sign(String jsapi_ticket, String url) throws FileNotFoundException, IOException {
        Map<String, String> ret = new HashMap<String, String>();
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";


        //注意这里参数名必须全部小写,且必须有序
        string1 = "jsapi_ticket=" + jsapi_ticket +
                "&noncestr=" + nonce_str +
                "&timestamp=" + timestamp +
                "&url=" + url;
        System.out.println(string1);

        try
        {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }

        //自动获取WxTokenUtil.properties中appid属性,避免appid改动时重复更改
        String appId = APPID;


        ret.put("url", url);
        //注意这里 要加上自己的appId
        ret.put("appId", appId);
        ret.put("jsapi_ticket", jsapi_ticket);
        ret.put("nonceStr", nonce_str);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);

        return ret;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash)
        {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }



    /***
     * 模拟get请求
     * @param url
     * @param charset
     * @param timeout
     * @return
     */
    public static String sendGet(String url, String charset, int timeout)
    {
        String result = "";
        try
        {
            URL u = new URL(url);
            try
            {
                URLConnection conn = u.openConnection();
                conn.connect();
                conn.setConnectTimeout(timeout);
                BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), charset));
                String line="";
                while ((line = in.readLine()) != null)
                {

                    result = result + line;
                }
                in.close();
            } catch (IOException e) {
                return result;
            }
        }
        catch (MalformedURLException e)
        {
            return result;
        }

        return result;
    }
}
4. 常见问题
  1. debug模式打开后, 页面打开没有任何提示。 这里检查下jssdk是否正确引入。config代码是否执行
  2. wx.ready 方法不执行。这里重点检查下config方法是否执行
  3. 弹出提示签名错误,这里主要检查appid,和secrekey是否正确。可以使用 官方签名工具来验证签名是否正确
  4. 所有配置都正确但是提示授权失败,这里需要检查公众号性质。个人注册的公众号没有也无法获取分享功能接口使用的。在设置开发-接口权限那里可以看到
  5. 链接发送出去后是链接形式,不是卡片形式。这是由于微信要求链接只能通过公众号菜单分享,或者将链接生成二维码,用户扫码后再分享,这两种情况下分享出去才是卡片。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值