微信公众号H5活动页面开发(踩坑)总结

593 阅读5分钟

前言

最近接了个活, 大概就是开发一个企业的周年庆的活动, 内容有抽奖, 游戏等, 自信满满的接了下来, 结果过程一言难尽, 今天特意来总结一下

开发准备

首先要确定客户的公众号是服务号还是订阅号, 你想要获取用户更多的信息, 比如头像, 地区, 昵称等, 只有服务号才能完成。 很多客户域名和服务器都是没有的, 从购买域名 -> 个人/企业认证备案 -> 购买服务器 -> 服务器域名备案, 没有一个月是下不来的

frp

公众号开发最头疼的一点就是你需要在本地开发, 但是你要调用微信api需要指定域名, 比如Oauth2网页授权, 你总不可能每次修改一点一次次打包到服务器, 所以这时候内网穿透的优势就体现出来了, 常见的有ngrok/frp/natapp, 这里我建议是frp, 因为配置比较简单而且是免费的, 但前提是你有域名和服务器,安装过程我就不赘述了,服务器需要配置frps.ini

# frps.ini
[common]
bind_port = 7000   
vhost_http_port = 80 // 这个是对应的frpc中custom_domain的端口号,最好是80, 其他端口的话, 展示形式就是custom_domain:vhost_http_port
# frpc.ini
[common]
server_addr = 180.76.169.181
server_port = 7000

[web]
type = http                      // 前端静态页面类型就是http  
local_ip = 0.0.0.0
local_port = 5419                // 脚手架热更新服务器端口 
custom_domains = you.domain.com  // 在线上暴露的域名

[api]
type = tcp                       // 后端接口就是tcp
local_ip = 0.0.0.0
local_port = 3000                // node 服务端口3000 
custom_domains = you1.domain.com // 这个域名一定要和上一个不同, 因为vhost_http_port只能指定一个, 

假如你的后端接口和前端页面都是部署在本地, 后端端口映射的type是tcp, 前端页面映射的type是http

获取微信用户头像

这次的需求是获取微信用户头像, 然后外面是一个企业周年庆的边框, 相当于DIY头像, 然后可以供用户下载, 这里我使用了html2canvas, 但是生成头像图片模糊不清, 同样的尺寸比原头像还要模糊, 我做了很多尝试,后来终于发现, 只是将周年庆边框从background-image转换为img, 整个图片清晰度一下子上来了, 可能html2canvas对img边框重绘更加友好, 这还不是最优的, 微信后台传过来的用户头像url最后会有一个/数字, 大多数就是132, 就是132px * 132px, 把路径/132去掉, 整个头像图片大小可以达到720 * 720, 这下可以达到高清标准了, 在开发的时候还遇到另外一个问题,就是你刚换成DIY头像, 立马又替换成自己的头像, 但微信后台传过来的头像还是DIY头像, 论坛上有说明,大概有4个小时的延迟,后台不会立即保存用户头像。

获取accoken_token

微信里有两种accoken_token, 在此我说的就是网页授权获取用户信息的accoken_token

const Koa = require("koa");
const Router = require("@koa/router");
const bodyParser = require("koa-bodyparser");
const axios = require("axios");

const app = new Koa();
const router = new Router();
app.use(bodyParser());

const appID = "xxxxxxxxxxx";     // 微信测试号的appid
const appSecret = "yyyyyyyyyyy"; // 微信测试号的appsecret

router.post("/getUserInfo", async (ctx, next) => {
  const { code } = ctx.request.body;

  try {
    const { data: resp } = await axios.get(
      `https://api.weixin.qq.com/sns/oauth2/access_token`,
      {
        params: {
          appid: appID,
          secret: appSecret,
          grant_type: "authorization_code",
          code,
        },
      }
    );

    const { access_token, openid } = resp;
    
    // data的JSON数据结构
    // {   
    //   "openid": "OPENID",
    //   "nickname": NICKNAME,
    //   "sex": 1,
    //   "province":"PROVINCE",
    //   "city":"CITY",
    //   "country":"COUNTRY",
    //   "headimgurl":"https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
    //   "privilege":[ "PRIVILEGE1" "PRIVILEGE2"],
    //   "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
    // }

    const { data } = await axios.get("https://api.weixin.qq.com/sns/userinfo", {
      params: {
        access_token,
        openid,
        lang: "zh_CN",
      },
    });

    ctx.body = data;
  } catch (error) {
    console.log(error);
  }
});

再谈access_token

再谈另一种access_token, 这种token是用来获取填充初始化jssdk参数的, 参数如下 前端代码

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

后端代码

const Koa = require("koa");
const Router = require("@koa/router");
const bodyParser = require("koa-bodyparser");
const sha1 = require("sha1");
const axios = require("axios");
const redis = require("redis");

app.use(bodyParser());
const client = redis.createClient({
  url: 'redis://alice:foobared@awesome.redis.server:6380'
});
const app = new Koa();
const router = new Router();
const appID = "xxxxxxxxxxx";     // 微信测试号的appid
const appSecret = "yyyyyyyyyyy"; // 微信测试号的appsecret

client.on("error", function (err) {
  console.log("Error " + err);
});

router.get("/getTicket", async (ctx, next) => {

  /**
   * 这里的access_token需要缓存, 7200秒后才会更新
   * 假如这段时期再次请求, 是不会在返回access_token参数
   * 所以用redis缓存access_token
   */
  let access_token = await client.get("access_token"); 
  if(!access_token){
     const { access_token: token } = await axios.get(
    `https://api.weixin.qq.com/cgi-bin/token`,
    {
      params: {
        appid: appID,
        secret: appSecret,
        grant_type: "client_credential",
      },
    }
   );
   access_token = token;
   // EX 过期时间7200秒  NX 只有键不存在时,才会操作
   await client.set("access_token", token, { EX: 7200, NX: true, });
  } 
   
 
  const { ticket } = await axios.get(
    `https://api.weixin.qq.com/cgi-bin/ticket/getticket`,
    {
      params: {
        access_token,
        type: "jsapi",
      },
    }
  );
  const timestamp = new Date().getTime();
  const noncestr = "jacklove";
  const str = `jsapi_ticket=${ticket}&noncestr=${noncestr}&timestamp=${timestamp}&url=http://you.domain.cn`;
  const signature = sha1(str);
  ctx.body = { signature, noncestr, signature, timestamp, appId: appID };
});

Termux

人不可能时刻守在电脑面前, 但手机是随身携带的, 对, 假如有个很急但又很小的需求或者你想监控一下服务器的状态, termux是个不错的选择, 相当于andriod的终端, 只需要输入下面代码, 就可以愉快的输入linux命令了,这块我用的是PM2来管理项目, 比如查看项目状态pm2 status, 重启服务pm2 reload all, 开启集群pm2 start app.js -i 4

1.pkg install openssh
2.sshd