Daibor Cyber Space

学习笔记、一些思考和记录

使用 Express + Nginx + PM2 部署接口,完整实现生成小程序码的功能

Posted at — Jul 29, 2018

小程序最近开放了广告组件,但是有一个门槛就是UV需要达到1000,看着我那已经上线半年的小程序自然增长用户只有400多,感觉错失了一个亿,而能最快最有效带来用户增长的渠道就是扫码。作为一个半吊子前端,小程序的数据库和后台程序直接偷懒使用了 LeanCloud ,但是生成小程序码这件事情恐怕就得自己来了。

本文记录一下这个需求的解决过程,帮助小白们快速搭建小程序码的相关后台程序。JS 和 Nginx 写得用得都不多,恳请大神们指正哈~

获取小程序码逻辑和需求

微信文档 已经写得很清楚了,首先需要获取一个有效期 2 小时的 access_token ,使用该 token 再带上小程序码中需要包括的路径、参数向微信接口请求,微信会通过二进制文件流的形式返回小程序码。

比较坑的是出于安全需要,微信不允许直接通过前端请求获取小程序码接口,原因是 app_secret 是敏感字段,放在前端不安全。所以我们现在需要自己搭后端程序,通过服务器向微信请求到小程序码,再返回给前端,相当于一个中间层。从输入输出看我们这个接口,就是接受小程序传过来的参数,返回一个小程序图片或 URL 。

简单考虑,使用 Express (NodeJS的后端框架)作为路由,用来设置接口地址。服务端请求发送使用 request 库,为了减小延迟(省事儿)就直接把文件存在本地。二进制流发来发去估计也会比较慢,所以就直接返回本地的路径就好。PM2 是为了让程序能够持续稳定地运行而选择的一个库,没仔细看过文档,大概就是可以自动重启吧~

编写 Node 程序

整体目录结构如下:

使用方式:

下面放代码,一个一个来哈~

//index.js
const express = require('express');
const url = require('url');
const wechatqr = require('./wechatqr');

const app = express();

app.get('接口地址', function(req, res) {
//接收参数,自行换成你的
//接口效果如https://baidu.com/接口地址?objectId=123;
	let objectId = url.parse(req.url, true).query.objectId;
	if (typeof objectId !== 'string' || objectId.length == 0) {
		res.json({
			code: 0
		})
	} else {
		wechatqr.getQR(objectId, '扫码后打开的小程序页面路径').then((data) => {
			res.json({
				qrimg: data,
				code: 1
			})
		}).catch((err) => {
			console.log(err)
			res.json({
				code: 0
			})
		});
	}
});

var server = app.listen(3000, function() {

});
//wechatqr.js
const request = require("request");
const rp = require("request-promise-native");
const fs = require("fs");
const crypto = require('crypto');
const token = require('./wechattoken');

function sha1(message) {
	return crypto.createHash('sha1').update(message, 'utf8').digest('hex');
}

function checkFile(filePath) {
	return new Promise(function(resolve, reject) {
		fs.access(filePath, fs.constants.R_OK, (err) => {
			if (err)
				reject()
			else
				resolve()
		});
	})
}

function getQR(scene, path) {
	let fileName = sha1(scene),
		filePath = `../webpage/postcard/static/qrimg/${fileName}.png`,
		imgPath = `https://static.postcard.daibor.cn/qrimg/${fileName}.png`,
		readable;

	return new Promise(function(resolve, reject) {
		//检查文件是否存在
		checkFile(filePath).then((res) => {
			//无错误,有文件
			console.log('existed');
			resolve(imgPath);
		}).catch((err) => {
			//有错误,无文件
			console.log('new user');
			var saveQR = request({
				url: 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' + token.getToken(),
				method: 'POST',
				headers: {
					'content-type': 'application/json'
				},
				body: JSON.stringify({
					scene: scene,
					page: path,
					is_hyaline: true
				})
			}).pipe(fs.createWriteStream(filePath));
			saveQR.on('finish', function() {
				resolve(imgPath);
			})
		})
	})
}

module.exports = {
	getQR
}
//wechattoken.js
const cf = require('./conf');
const rp = require("request-promise-native");

var _TOKEN = '';
//请求access_token
function getAccessToken() {
	rp('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + cf.appid + '&secret=' + cf.secret).then((res) => {
		console.log(res);
		_TOKEN = JSON.parse(res).access_token;
	})
}

function getToken() {
	return _TOKEN;
}

getAccessToken();

//微信规定超时时间7200秒,不能频繁请求,因此设置每7000秒重新请求一次。
const _timer = setInterval(getAccessToken, 7000000);

module.exports = {
	getToken
}
//conf.js
module.exports = {
	appid: 你的小程序id,
	secret: 你的小程序secret
}

配置 Nginx 反向代理

只要加上这么一段在 server 的大括号里就妥了。

location  /  {
    proxy_pass    http://localhost:3000;        
}

在服务端启动代码

使用 PM2 作为 Node 进程的管理工具。对于这个项目来说不用了解太多 PM2 的用法(好吧其实是我懒)。用 FTP 把代码(4个 JS 和 package.json )传到服务器上,我用的是 FileZilla。呃……写完才发现这种方法一点都不 Geek ,还是过两天搭个仓库,直接 git clone 逼格高(并不)。

首先,全局安装该包

npm install -g pm2

然后进入到存放代码的目录,npm install 一下。国内速度一般比较慢,可以先配置下 CNPM ,然后就欢快的用 PM2 启动吧~

pm2 start index.js --watch -i 2
//这句的意思是:监听当前目录,启用两个实例用于负载均衡

如果没出什么问题的话,接下来就可以用 Postman (或者直接用小程序的 request ) 测试一下接口有没有正常工作。然后就欢快地为你的小程序加上小程序码的功能,收割朋友圈这个最大的流量入口吧~

作为一个后端能力近乎不存在的独立小程序开发者,我在生成带小程序码时直接把微信生成的图片返回到前端,使用 Canvas 搭配 PS 做好的图片模板生成海报,效果如下图,个人感觉还不错,过两天写篇文章分享下我生成图片的代码:

欢迎扫码体验啦 | center

comments powered by Disqus