nest实现扫码登录
首先初始化一个 nest 项目
nest n qrcode-login
安装下需要使用到的包
pnpm install qrcode @types/qrcode
在前一帖中提到过,实现扫码登录是需要后端提供三个接口
- 获取二维码key
- 依据二维码key生成二维码的
url - 检测二维码的状态
添加一个路由生成图片返回 key 和二维码的 url
这里为了简单一点就全部返回了
使用postman 测试返回数据了
初始化一个 前端项目,把二维码展示出来
pnpm create vite
import { FC, useEffect, useState } from "react";
import { HomeWrapper } from "./style";
import { getCodeImg } from "@/apis";
const Home: FC = () => {
const [codeImg, setCodeImg] = useState<string>("");
const initCodeImg = async () => {
const { code_url } = await getCodeImg();
setCodeImg(code_url);
};
useEffect(() => {
initCodeImg();
}, []);
return (
<HomeWrapper>
<div className="home">
<img src={codeImg} alt="" />
</div>
</HomeWrapper>
);
};
export default Home;
这里就简单一点直接使用 img 标签展示
可以看到成功展示出来了(如果发现发送了两次请求,去到根组件把 react 的严格模式去掉即可解决)
使用手机扫码之后出现的字符就是随机生成的 uuid
其实我们使用二维码解析工具解析出来的是一个网址
使用不同的软件扫描出现的界面不一样,本软件是确认登录,浏览器或是微信扫描出来的是软件的下载界面或是提示你使用软件扫描才能登录
这也是正常的不然随便一个软件都能扫描二维码登录了谁还会下载你的软件呢?
那么是怎么做到的呢?
其实就是访问的一个确认登录的页面
我们修改一下前端的页面,添加一个路由
const routes: RouteObject[] = [
{
path: "/",
element: <Layout />,
children: [
{
path: "",
element: <Home />,
},
{
path: 'confirm',
element: <Confirm />,
}
],
},
{
path: "*",
element: <NotFount />,
},
];
简单的写一下这个页面
import { memo, useEffect } from "react";
import type { FC, ReactNode } from "react";
import { Button, message } from "antd";
import ConfirmWrapper from "./style";
import { codeUpdate, confirmLogin, cancelLogin as cancelLoginApi } from "@/apis";
interface IProps {
children?: ReactNode;
}
const params = new URLSearchParams(window.location.search.slice(1));
const uid = params.get("uid");
const Confirm: FC<IProps> = () => {
const [messageApi, contextHolder] = message.useMessage();
useEffect(() => {
// 初始进入确认界面说明已经完成扫码
updateCodeStatus();
}, []);
const toLogin = () => {
confirmLogin(uid);
messageApi.open({
content: "登录成功",
type: "success",
duration: 1,
});
};
const cancelLogin = () => {
cancelLoginApi(uid);
messageApi.open({
content: "登录已取消",
type: "error",
duration: 1,
});
};
const updateCodeStatus = () => codeUpdate(uid);
return (
<ConfirmWrapper>
<div className="flex flex-col justify-end h-screen">
{contextHolder}
<div className="flex-1 flex-center text-2xl text-[#1E80FF]">
确认登录***网站吗?
</div>
<div className="px-3">
<Button type="primary" block onClick={toLogin}>
确认登录
</Button>
<Button block onClick={cancelLogin} className="my-4">
取消
</Button>
</div>
</div>
</ConfirmWrapper>
);
};
export default memo(Confirm);
我们在手机上如何预览电脑上的界面呢?
webpack 或是 vite 项目都可以配置一下开发服务器的选项
手机扫码效果
我们看下效果
下面就是当我们点击登录的时候,拿到这边的登录状态,取出用户信息
后端 通过 jwt 实现,我们给它加上
安装jwt 包
pnpm install @nestjs/jwt
在 app.module.ts 里面导入一下
设置一下密钥和 token 的有效时长
然后在 controller 里面加上两个接口,一个用于登录,一个登陆后依据 token 获取用户信息
然后我们在确认界面加上登录逻辑
import { memo, useEffect } from "react";
import type { FC, ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import { Button, message } from "antd";
import ConfirmWrapper from "./style";
import { codeUpdate, confirmLogin, cancelLogin as cancelLoginApi, login } from "@/apis";
interface IProps {
children?: ReactNode;
}
const params = new URLSearchParams(window.location.search.slice(1));
const uid = params.get("uid");
const Confirm: FC<IProps> = () => {
const [messageApi, contextHolder] = message.useMessage();
const navigate = useNavigate();
useEffect(() => {
// 初始进入确认界面说明已经完成扫码
updateCodeStatus();
}, []);
const toLogin = async () => {
const params = {
username: "admin",
password: 123456,
};
const {
data: { token },
} = await login(params);
// debugger;
localStorage.setItem("user-token", token);
navigate("/result");
confirmLogin(uid);
messageApi.open({
content: "登录成功",
type: "success",
duration: 1,
});
};
const cancelLogin = () => {
cancelLoginApi(uid);
messageApi.open({
content: "登录已取消",
type: "error",
duration: 1,
});
};
const updateCodeStatus = () => codeUpdate(uid);
return (
<ConfirmWrapper>
<div className="flex flex-col justify-end h-screen">
{contextHolder}
<div className="flex-1 flex-center text-2xl text-[#1E80FF]">
确认登录***网站吗?
</div>
<div className="px-3">
<Button type="primary" block onClick={toLogin}>
确认登录
</Button>
<Button block onClick={cancelLogin} className="my-4">
取消
</Button>
</div>
</div>
</ConfirmWrapper>
);
};
export default memo(Confirm);
然后在添加一跳转的结果页面
新建一个result 页面(别忘了,添加路由)
import { memo, useEffect, useState } from "react";
import type { FC, ReactNode } from "react";
interface IProps {
children?: ReactNode;
}
import ResultWrapper from "./style";
import { getUserInfo } from "@/apis";
const Result: FC<IProps> = () => {
const [info, setInfo] = useState<any>();
useEffect(() => {
getUserInfo().then((res) => {
const {
data: { username },
} = res;
setInfo(username);
});
});
return (
<ResultWrapper>
欢迎回来<br></br>当前用户为:
<span className="font-900 text-[25px] color-red">{info}</span>
</ResultWrapper>
);
};
export default memo(Result);
当我们在点击登录的时候调用 login 的登录,这时候会返回用户的 token ,我们存到本地
然后跳转到结果页面的时候,取出来查询用户信息,展示出来
在登录的时候添加上对应用户的 token,这里的
axios 的封装借鉴于 王红元(coderwhy) 老师使用的 class 类进行的封装,这里涉及到了主机的本地地址和局域网下手机的请求,主机上的生成二维码和查看二维码状态的基地址是本地的,而手机上点击确认取消是访问的主机的ipv4地址
使用类来封装的好处就体现出来了,可以对应的 new 两个不同的请求实例,它们分别设置不同的请求基地址
最终效果