大体思路:
- web 登录页从服务端获取一个带有 scene 的小程序码。
- 微信扫码,进入小程序,获得唯一参数 scene,小程序进行授权获取用户信息(头像、昵称)。
- 授权登录,把 scene, opendid 和用户信息一起保存到数据库。
- 先根据 openid 查询用户列表,如果存在则更新用户信息 和 scene, 如果不存在则添加用户。
- 登录页轮询或 websocket 请求后端获取扫码状态,扫码成功后登录进去。
- 带着 scene 值轮询,如果查询到用户信息,则根据用户信息,secret, 生成 token 返回给 web登录页。
- web 登录页,拿到 token, 保存 token, 并跳转到首页, 以后每次请求请求头带上 token。
- 获取机器人登录的二维码,扫码登录
server 端的代码
token.js
async function getAccessToken() {
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${secret}`;
try {
const currentTime = Date.now();
var contentText = fs.readFileSync(
path.resolve(__dirname, "./access_token.json"),
"utf-8"
);
accessTokenJson = JSON.parse(contentText);
if (
!accessTokenJson.access_token ||
accessTokenJson.expires_time < currentTime
) {
// token 过期了,则重新请求生成
let { access_token, expires_in } = await superagent.req({
url,
method: "GET",
});
fs.writeFileSync(
path.resolve(__dirname, "./access_token.json"),
JSON.stringify({
access_token,
expires_time: Date.now() + (expires_in - 200) * 1000,
}),
"utf-8"
);
return access_token;
} else {
// token 未过期,直接用
return accessTokenJson.access_token;
}
} catch (err) {
console.log("获取接口失败", err);
}
}
qrCode.js
function getQrCode() {
return new Promise(async function (resolve, reject) {
const access_token = await getAccessToken();
const createQrUrl = `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${access_token}`;
const scene = Date.now();
ajax
.post(createQrUrl)
.send({
path: "page/index/index", // 小程序扫码后展示的页面
width: 430,
scene, // 唯一性的参数
is_hyaline: true,
})
.set("X-API-Key", "foobar")
.set("accept", "json")
.end(function (err, res) {
var base64 = res.body.toString("base64");
resolve({
base64,
scene,
});
});
});
// return qrData;
}
server.js
// 通过该请求,登录页获取小程序码
router.post("/createQrCode", async (ctx, next) => {
const { base64, scene } = await getQrCode();
const base64Str = `data:image/png;base64,${base64}`;
ctx.body = {
code: 20000,
base64Str,
scene,
};
});
小程序端代码
index.js
onLogin(scene) {
wx.login({
success: (res) => {
if (res.code) {
wx.request({
url: "https://www.moluoyingxiong.tech/bot-server/getOpenId",
data: {
code: res.code,
},
success: (user) => {
const { openid } = user.data;
const alreadyInfo = this.data;
this.setData({
userInfo: {
...alreadyInfo,
openid,
scene,
},
});
},
});
} else {
console.log("登录失败!" + res.errMsg);
}
},
});
},
saveUserInfo(userInfo) {
wx.request({
url: "https://www.moluoyingxiong.tech/bot-server/saveUserInfo",
method: "post",
data: userInfo,
success(res) {},
});
},
getUserProfile() {
// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
wx.getUserProfile({
desc: "展示用户信息", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
success: (res) => {
const { userInfo } = res;
const alreadyInfo = this.data.userInfo;
this.setData({
avatarUrl: userInfo.avatarUrl,
userInfo: {
...alreadyInfo,
...userInfo,
},
});
this.saveUserInfo({
userInfo: { ...alreadyInfo, ...userInfo },
});
},
fail(e) {
console.log(e, "======");
},
});
},
web 端代码 完整代码
login.vue
mounted() {
this.getQrcode(); // 登录页初始化出小程序码
},
methods: {
getQrcode() {
getQrcode().then((res) => {
const { base64Str, scene } = res;
this.qrcode = base64Str;
this.checkIsScan(scene); // 获取小程序码后开始轮询检查是否扫码
});
},
checkIsScan(scene) {
const startTime = Date.now();
if (this.hasScan) {
clearInterval(this.timer);
this.timer = null;
} else {
this.timer = setInterval(() => {
checkScene({ scene }).then((res) => {
const curTime = Date.now();
if (curTime - startTime > 30 * 1000) { // 半分钟后停止检查
clearInterval(this.timer);
this.timer = null;
}
const { hasScan, token } = res;
if (hasScan) { // 已经扫码,则保存 token ,跳转到首页或 redirect 页
clearInterval(this.timer);
this.timer = null;
this.$store.commit("user/SET_TOKEN", token);
setToken(token);
this.$router.push({ path: this.redirect || "/" });
}
this.hasScan = hasScan;
});
}, 3000);
}
},
}
微信小助手 - 娜美 这个项目可以在终端显示登陆二维码,为了实现多个小助手,其实就是在 server 端生成多个机器人实例。
bot.js
function initBot() {
return new Promise(function (resolve) {
const bot = new Wechaty({
name: "nami",
});
bot.on("scan", (qrcode, status) => {
// require('qrcode-terminal').generate(qrcode); // 在console端显示二维码
const qrcodeImageUrl = [
"https://api.qrserver.com/v1/create-qr-code/?data=",
encodeURIComponent(qrcode),
].join("");
resolve({ qrcodeImageUrl }); // 把二维码返回给前端,直接扫描登录
});
bot.on("login", (user) => {
onLogin(user, bot);
});
bot.on("logout", () => {
bot.logout()
bot.stop()
});
bot.on("message", (msg) => {
onMessage(msg, bot);
});
bot
.start()
.then(() => {
console.log("开始登陆微信");
})
.catch((e) => console.error(e));
});
}
server.js
// koa server
const Koa = require("koa");
const Router = require("@koa/router");
const app = new Koa();
const router = new Router();
router.get("/bot/generate", async (ctx, next) => {
deleteMemory();
const { qrcodeImageUrl } = await bot.botInit();
ctx.body = {
code: 20000,
data: { qrcodeImageUrl }, // 生成二维码,扫码登录机器人
};
});
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, () => {
console.log("listening localhost:3000");
});