Nest 二维码扫描
场景
- PC 端生成二维码页面,移动端扫描二维码,PC 端获取扫描结果
流程如下
前端
对应两个页面
一个是生成二维码页面
一个是扫描二维码后跳转的页面
后端
对应五个接口
- 生成二维码接口
- 轮询查询二维码结果
- 扫描二维码接口 等待结果
- 扫描二维码后,点击了确认
- 扫描二维码后,点击了取消
后端
安装包
bash
npm install qrcode @types/qrcode
npm install base62控制器
ts
import { Controller, Get, Query } from "@nestjs/common";
import { QrcodeService } from "./qrcode.service";
import * as qrcode from "qrcode";
const base62 = require("base62/lib/ascii");
@Controller("qrcode")
export class QrcodeController {
constructor(private readonly qrcodeService: QrcodeService) {}
// 生成一个不会重复的字符串
async setduanliancode(len) {
console.log("进来了啊");
console.log(len);
let str = "";
for (let i = 0; i < len; i++) {
const num = Math.floor(Math.random() * 62);
str += base62.encode(num);
}
return str;
}
// 生成二维码
@Get("generate")
async findAll() {
const url = "https://www.uiyin.com"; // 你自己的跳转的网址
// 生成一个不会重复的字符串
let code = "";
for (let i = 0; i < url.length; i++) {
const num = Math.floor(Math.random() * 62);
code += base62.encode(num);
}
console.log(code);
const options: qrcode.QRCodeToDataURLOptions = {
width: 300, // 二维码宽度
margin: 2, // 二维码边距
color: {
dark: "#000000", // 二维码点的颜色
light: "#FFFFFF", // 背景颜色
},
// errorCorrectionLevel: 'H', // 错误纠正级别 'L', 'M', 'Q', 'H'
};
// 结果
const qrcodedata = await this.qrcodeService.setqrcode(url, code, options);
return {
data: {
id: code, // 通过id 来轮询查询二维码状态
img: qrcodedata,
},
};
}
// 增加一个检查接口,用于检查二维码状态
@Get("check")
async check(@Query() query: any) {
console.log(query);
// 我这里就没有验证了,你可以根据你的需求来验证
const result = await this.qrcodeService.checkqrcode(query.id);
console.log(result);
return {
data: {
...result, // 返回二维码状态
},
};
}
// 二维码确认
// 在手机页面点击确认按钮 一定要传入qrcode_id 和token
// 我这里为了测试就不写token了
@Get("confirm")
async confirm(@Query() query: any) {
// 我这里就没有验证了,你可以根据你的需求来验证
const result: any = await this.qrcodeService.confirmqrcode(
query.id,
query.token
);
return {
data: {
result, // 返回二维码状态
},
};
}
// 二维码取消
// 在手机页面点击取消按钮 一定要传入qrcode_id 和token
// 我这里为了测试就不写token了
@Get("cancle")
async cancle(@Query() query: any) {
// 我这里就没有验证了,你可以根据你的需求来验证
const result = await this.qrcodeService.cancelqrcode(query.id, query.token);
console.log("控制器");
console.log(result);
return {
data: {
result, // 返回二维码状态
},
};
}
// 用户扫码后,跳转到页面仅仅跳转没点击确认和取消
// 进入手机页面 一定要传入qrcode_id 和token
// 我这里为了测试就不写token了
@Get("waiting")
async waiting(@Query() query: any) {
// 我这里就没有验证了,你可以根据你的需求来验证
const result = await this.qrcodeService.waitingqrcode(
query.id,
query.token
);
return {
data: {
result, // 返回二维码状态
},
};
}
}服务
ts
import { Inject, Injectable } from "@nestjs/common";
import * as qrcode from "qrcode";
import { RedisService } from "../redis/redis.service";
enum qrcodestatus {
NOSCAN = "noscan", // 未扫描
SCANWAITCONFIRM = "scanwaitconfirm", // 等待扫描
SCANCONFIRM = "scanconfirm", // 已扫描
SCANCANCEL = "scancancel", // 用户取消扫描
EXPIRED = "expired", // 已过期
}
@Injectable()
export class QrcodeService {
@Inject()
private readonly redisService: RedisService;
// 生成二维码
async setqrcode(
url: string,
code: string,
options?: qrcode.QRCodeToDataURLOptions
) {
// 使用 toDataURL 生成 base64 格式的图片,然后转换为 Buffer
// toDataURL 的 options 类型就是 QRCode.QRCodeToDataURLOptions
const dataUrl = await qrcode.toDataURL(url, options);
// 保存redis 状态
let redisdata = { status: qrcodestatus.NOSCAN, token: "" };
this.redisService.sethash(code, redisdata);
return dataUrl;
}
// 检查二维码
async checkqrcode(text: string) {
console.log(text);
const status: any = await this.redisService.gethash(text);
if (status === null) {
return {
status: "初始化",
token: "",
};
} else {
if (status.status === qrcodestatus.SCANCONFIRM) {
return {
status: status.status,
token: status.token,
};
} else {
return {
status: status.status,
token: "",
};
}
}
}
// 确认二维码
async confirmqrcode(qrcode_id: string, token: string) {
// 这里获取到token,知道是谁了。然后进行业务处理
// 我这里为了业务测试 就不写token了
console.log(token);
const status: any = await this.redisService.gethash(qrcode_id);
if (!status) {
return "二维码有问题";
}
// 设置二维码状态
let redisdata = {
status: qrcodestatus.SCANCONFIRM,
token: token,
};
this.redisService.sethash(qrcode_id, redisdata);
return "success";
}
// 取消二维码
async cancelqrcode(qrcode_id: string, token: string) {
// 这里获取到token,知道是谁了。然后进行业务处理
// 我这里为了业务测试 就不写token了
console.log(token);
const status: any = await this.redisService.gethash(qrcode_id);
if (!status) {
return "二维码有问题";
}
// 设置二维码状态
let redisdata = {
status: qrcodestatus.SCANCANCEL,
token: token,
};
this.redisService.sethash(qrcode_id, redisdata);
return "cancle";
}
// 等待确认二维码
async waitingqrcode(qrcode_id: string, token: string) {
// 这里获取到token,知道是谁了。然后进行业务处理
// 我这里为了业务测试 就不写token了
console.log(token);
const status: any = await this.redisService.gethash(qrcode_id);
if (!status) {
return "二维码有问题";
}
// 设置二维码状态
// 设置二维码状态
let redisdata = {
status: qrcodestatus.SCANWAITCONFIRM,
token: token,
};
this.redisService.sethash(qrcode_id, redisdata);
return "waiting";
}
}前端
扫描后的页面我就不写了.调用那三个接口即可
这个我写的是获取二维码的页面
获取二维码
轮询查询二维码状态
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>扫码登录</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/axios@1.5.0/dist/axios.min.js"></script>
</head>
<body>
<img id="img" src="" alt="" />
<div id="info"></div>
<script>
let id = "";
axios.get("http://localhost:5000/qrcode/generate").then((res) => {
console.log(res.data.data);
document.getElementById("img").src = res.data.data.img;
id = res.data.data.id;
});
// 循环查询状态
function queryStatus(id) {
axios.get("http://localhost:5000/qrcode/check?id=" + id).then((res) => {
const result = res.data.data;
let content = "";
switch (result.status) {
case "noscan":
content = "未扫码";
break;
case "scanwaitconfirm":
content = "已扫码,等待确认";
break;
case "scanconfirm":
content = "已确认";
console.log("获取到返回的token");
console.log(result.token);
console.log("就应该跳转到登录页");
break;
case "scancancel":
content = "已取消";
break;
}
document.getElementById("info").textContent = content;
});
}
setInterval(() => {
queryStatus(id);
}, 3000);
</script>
</body>
</html>