头图

记一次个人开发者如何让H5婚礼请帖在微信里「体面」的流转!

我的公众号原文地址 📖

⚠️ 很关键的前提 ⚠️
❗️个人开发者使用wx-jssdk的权限受限❗️(无转发给好友、分享朋友圈等权限),可先去「设置与开发-接口权限」查看权限详情。
🤷‍♂️文末是我说的「体面」❗️

公众号设置

a7f18ba8b66cb9e4e5abf46a5efcd77f.png

8c2b97d50deff3a35e72b9aa507bb7b3.png

服务端代码

(基于Koa2开发,省略服务器搭建代码,仅提供关键步骤代码)
1.编写「服务器配置-服务器地址(URL)」所需要的接口地址
https://yourserver.com/api/ch...
let sha1 = require("sha1");

/**
 * 微信公众号-指定服务器地址-验证接口
 * @param {*} ctx
 */
checkToken: async (ctx) => {
  const { signature, timestamp, nonce, echostr } = ctx.query;
  //ctx.query获取请求中携带的参数
  let { token } = wxconfig;
  //将Token,timestamp,nonce按字典排序,排序后链接成一个字符串
  let str = [token, timestamp, nonce].sort().join("");
  //使用sha1模块进行sha1加密
  let sha1Str = sha1(str);

  //判断加密后的字符串与请求中signature是否相等
  if (sha1Str === signature) {
    ctx.body = echostr;
  } else {
    ctx.body = "Token check failed.";
  }
};

这一步OK之后,在「微信公众号后台就能成功开启服务器了」。接下来就是编写H5在微信里调用wx-jssdk相关功能所需要的授权过程。其实主要就是拿到wxconfig所需要的一些必要参数

编写wx.config所需数据的接口
https://yourserver.com/api/wx...需授权网站的url

生成wxconfig数据的大致流程:获取access_token,通过access_token获取jsapi_ticket;然后拼接access_tokenjsapi_ticketnoncestrtimestamp要授权的页面的url生成签名signature,最终将appIdtimestampnonceStrsignature返回给前端,前端调用wx.config进行注册,成功后就可以在wx.ready中调用相关js接口了。

wxConfig.js文件代码
!!!这里部分代码借鉴来的,懒得改了大致流程都是这样!
const sha1 = require("sha1");
const request = require("request");
const { wxconfig } = require("./wxConfig");

// 为了应对缓存压力,不要每次刷新token,访问量高会带来很大问题
// 因为获取access_token的接口,一天最多调用2000次,每次有效期是两个小时
let CACHE = {
  ticket: "",
  ticketTimeout: 0, //ticket过期时间
  ticketTime: 0, //获取ticket时间

  accessToken: "",
  accessTokenTimeout: 0, //token过期时间
  accessTokenTime: 0, //获取token时间
};

class wxModel {
  /**
   * 刷新access_token
   */
  static async refreshAccessToken() {
    return new Promise((resolve, reject) => {
      const tokenUrl = `${wxconfig.getAccessTokenUrl}?grant_type=client_credential&appid=${wxconfig.appId}&secret=${wxconfig.appSecret}`;
      request(tokenUrl, (error, response, body) => {
        if (typeof body === "string") {
          try {
            body = JSON.parse(body);
          } catch (e) {
            body = {
              errcode: "-1000",
              body,
            };
          }
        }

        if (body && (!body.errcode || body.errcode == 0)) {
          CACHE.accessToken = body.access_token;
          CACHE.accessTokenTimeout = body.expires_in * 500;
          CACHE.accessTokenTime = new Date();
          resolve(CACHE.accessToken);
        } else if (body) {
          reject(body.errmsg);
        } else {
          reject("未知异常");
        }
      });
    });
  }

  /**
   * 刷新ticket
   * @param {*} access_token
   * @param {*} callback
   */
  static async refreshJsapiTicket(access_token) {
    // Jsapi_ticket
    return new Promise((resolve, reject) => {
      let ticketUrl = `${wxconfig.getJsapiTicketUrl}?access_token=${access_token}&type=jsapi`;
      request(ticketUrl, function (err, response, content) {
        content = JSON.parse(content);
        if (content && (content.errcode == 0 || !content.errcode)) {
          CACHE.ticket = content.ticket;
          CACHE.ticketTimeout = content.expires_in * 500;
          CACHE.accessTokenTime = new Date();
          resolve(CACHE.ticket); // ticket
        } else if (content) {
          reject(content.errmsg);
        } else {
          reject("未知异常");
        }
      });
    });
  }

  /**
   * 获取wxconfig
   * @param {*} url
   * @returns
   */
  static async geneWxConfig(url) {
    // 获取access_token
    let access_token = CACHE.accessToken;
    let ticket = CACHE.ticket;

    if (
      !access_token ||
      new Date() - CACHE.accessTokenTime > CACHE.accessTokenTimeout
    ) {
      access_token = await this.refreshAccessToken();
      ticket = await this.refreshJsapiTicket(access_token);
    }

    let nonceStr = this.createNonceStr();
    let timestamp = this.createTimestamp();
    let signature = this.createSign({
      jsapi_ticket: ticket,
      nonceStr,
      timestamp,
      url,
    });

    return {
      appId: wxconfig.appId,
      access_token,
      ticket,
      timestamp,
      nonceStr,
      signature,
    };
  }

  /**
   * 随机字符串
   */
  static createNonceStr() {
    return Math.random().toString(36).substr(2, 15);
  }
  /**
   * 时间戳
   */
  static createTimestamp() {
    return parseInt(new Date().getTime() / 1000).toString();
  }

  /**
   * 生成签名
   * ⚠️ 只对url#前面部分加密
   * ⚠️ noncestr全部小写
   * @param {*} config
   */
  static createSign(config) {
    let ret = {
      jsapi_ticket: config.jsapi_ticket,
      nonceStr: config.nonceStr,
      timestamp: config.timestamp,
      url: config.url,
    };
    let url = ret.url.split("#")[0];
    let string = `jsapi_ticket=${ret.jsapi_ticket}&noncestr=${ret.nonceStr}&timestamp=${ret.timestamp}&url=${url}`;
    let shaObjs = sha1(string);
    return shaObjs;
  }
}

module.exports = wxModel;
wxConfig.js文件内容
const wxconfig = {
  appId: "wx9xxxxxxx",
  appSecret: "xxxxxxxxxxxxxxxxxxxxx",
  token: "hxxxt", //公众号后台自行配置的token

  getAccessTokenUrl: "https://api.weixin.qq.com/cgi-bin/token",
  getJsapiTicketUrl: "https://api.weixin.qq.com/cgi-bin/ticket/getticket",
};

module.exports = {
  wxconfig,
};

前端代码

1.引入wx-jssdk
let jssdkUri = `${window.location.protocol}//res.wx.qq.com/open/js/jweixin-1.6.0.js`;

loadJs() {
  return new Promise((resolve, reject) => {
    if (window.wx) {
      return resolve(window.wx);
    }

    let script = document.createElement("script");
    script.type = "text/javascript";
    script.src = this.jssdkUri;
    window.document.getElementsByTagName("head")[0].appendChild(script);

    script.onload = () => {
      resolve(window.wx);
    };
    script.onerror = reject;
  });
}
2.请求 /api/wxconfig 接口,拿到必要数据后调用 wx.config
let url = window.location.href;
let data = await this.getWxconfig(url);

wx.config({
  debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
  appId: data.appId, // 必填,公众号的唯一标识
  timestamp: data.timestamp, // 必填,生成签名的时间戳
  nonceStr: data.nonceStr, // 必填,生成签名的随机串
  signature: data.signature, // 必填,签名
  jsApiList: [
    "hideMenuItems",
    "updateAppMessageShareData",
    "updateTimelineShareData",
  ], // 必填,需要使用的JS接口列表
});
3.在wx.ready中编写「分享、转发」和其它业务需要的方法
share({ title, desc, link, imgUrl }) {
  this.shareData = { title, desc, link, imgUrl };

  if (!this.isReady) {
    console.error("wx jssdk is not ready.");
    return;
  }
  wx.ready(() => {
    wx.updateAppMessageShareData({
      title,
      desc,
      link,
      imgUrl, // 分享图标
      success: (res) => {},
      fail: (err) => this.onError(err),
    });

    wx.updateTimelineShareData({
      title,
      link,
      imgUrl, // 分享图标
      success: (res) => {},
      fail: (err) => this.onError(err),
    });
  });
}

hideMenu() {
  if (!this.isReady) {
    console.error("wx jssdk is not ready.");
    return;
  }

  wx.ready(() => {
    wx.hideMenuItems({
      menuList: [
        "menuItem:share:appMessage",
        "menuItem:share:timeline",
        "menuItem:share:qq",
        "menuItem:share:weiboApp",
        "menuItem:share:QZone",
        "menuItem:copyUrl",
      ],
    });
  });
}
如何让H5在微信里活的「体面」!

最后想说一下,作为一个个人开发者无法调用wx.updateAppMessageShareDatawx.updateTimelineShareData等一些接口,我是怎么利用有限的功能让我的H5网页尽量「体面」的吧!

既然不能分享,那干脆干掉微信点击右上角功能按钮后弹出来的「转发给朋友」和「分享到朋友圈」,甚至干掉「复制链接」功能菜单。

这样尽量减少转发出去只剩下裸露的难看的url链接的情况发生。

(请看对比图)
58a24c96f299986f1377ca1d00d2c9e7.png
ffc56d3b7ec7e1bb3739228dcbb3bd72.png

至于如何传播这个H5网页?给H5生成一个二维码,贴在一张好看的图片上,转发给朋友吧哈哈哈!

😕顺便附上一个Amazing的免费在线 二维码生成网站

无法忍受尘世间的丑 便看不到尘世间的美

36 声望
0 粉丝
0 条评论
推荐阅读
threejs角色移动平滑路线规划
一开始学threejs时,角色移动都是通过一个点直线移动到另一个点,但是现在情况不一样了,若将地图网格化处理后,利用以上技术点,将玩家移动的路线从「直来直往」进化到「平滑过渡」不是梦😄

Believer1阅读 341

封面图
JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy49阅读 7.4k评论 12

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 7k评论 12

封面图
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs32阅读 3.6k评论 5

封面图
安全地在前后端之间传输数据 - 「3」真的安全吗?
在「2」注册和登录示例中,我们通过非对称加密算法实现了浏览器和 Web 服务器之间的安全传输。看起来一切都很美好,但是危险就在哪里,有些人发现了,有些人嗅到了,更多人却浑然不知。就像是给门上了把好锁,还...

边城29阅读 6.4k评论 5

封面图
2022大前端总结和2023就业分析
我在年前给掘金平台分享了《2022年热点技术盘点》的前端热点,算是系统性的梳理了一下我自己对前端一整年的总结。年后,在知乎上看到《前端的就业行情怎么样?》,下面都是各种唱衰前端的论调,什么裁员,外包化...

i5ting27阅读 2.4k评论 4

封面图
深入理解React Diff算法
fiber上的updateQueue经过React的一番计算之后,这个fiber已经有了新的状态,也就是state,对于类组件来说,state是在render函数里被使用的,既然已经得到了新的state,那么当务之急是执行一次render,得到持有新...

nero31阅读 11.8k评论 3

无法忍受尘世间的丑 便看不到尘世间的美

36 声望
0 粉丝
宣传栏