文章目录
使用技术
前端 React + Antd
后端 Nest + svg-captcha + express-session
代码示例
前端
import { FC, memo, useState } from "react";
import { Col, Row, Button, Form, Input, Image, message } from "antd";
const login: FC = memo(() => {
const [code, setCode] = useState("/api/user/code");
//提示框hooks
const [messageApi, contextHolder] = message.useMessage();
//表单类型
interface user {
username: string;
passwrd: number;
code: number;
}
//重置验证码
const resetCode = () => setCode(code + "?" + Math.random());
//点击登录按钮事件
const onsubmit = async (value: user) => {
await fetch("/api/user/login", {
method: "POST",
body: JSON.stringify(value),
headers: {
"content-type": "application/json",
},
})
.then((res) => res.json())
.then((res) => {
if (res.code === 200) {
messageApi.open({
type: "success",
content: res.msg,
});
} else {
messageApi.open({
type: "error",
content: res.msg,
});
}
});
};
//表单验证失败事件
const onFinishFailed = (errorInfo: any) => {
console.log("Failed:", errorInfo);
};
return (
<>
{contextHolder}
< Row align="middle" style={{ height: "500px" }} >
< Col className="gutter-row" span={6} offset={8} >
< Form
name="basic"
style={{ maxWidth: 600 }}
onFinish={onsubmit}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
< Form.Item
label="用户名"
name="username"
rules={[{ required: true, message: "请输入用户名!" }]}
>
< Input size="large" />
< />
< Form.Item
label=" 密码"
name="password"
rules={[{ required: true, message: "请输入密码!" }]}
>
< Input.Password size="large" />
< />
< div style={{ display: "flex" }}>
< Form.Item
label="验证码"
name="inputcode"
style={{ width: "80%" }}
rules={[{ required: true, message: "请输入验证码!" }]}
>
< Input size="large" />
< />
< Image
style={{ marginLeft: "10px" }}
width={100}
src={code}
preview={false}
onClick={() => resetCode()}
/>
< />
< Form.Item wrapperCol={{ offset: 8, span: 16 }}>
< Button type="primary" htmlType="submit">
登录
< />
< />
< />
< />
< /Row>
< />
);
});
export default login;
跨域配置 vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
server: {
proxy: {
"/api": {
target: "http://localhost:3000",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
});
后端
主文件 main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
//导出session包
import * as session from "express-session";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
//使用session
app.use(
session({
secret: "code", //生成服务端session 签名
rolling: true, //在每次请求时强行设置 cookie
})
);
await app.listen(3000);
}
bootstrap();
express-session 参数配置详解
secret | 生成服务端 session 签名 可以理解为加盐 |
---|---|
name | 生成客户端 cookie 的名字 默认 connect.sid |
cookie | 设置返回到前端 key 的属性,默认值为{ path: ‘/’, httpOnly: true, secure: false, maxAge: null }。 |
rolling | 在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false) |
模块文件 app.module.ts
@Module()
装饰器提供 Nest 用来组织应用结构的元数据。
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
服务模块 app.service.ts
服务将负责数据存储和检索,并设计为供 Controller
使用
import { Injectable } from "@nestjs/common";
import * as svgcaptcha from "svg-captcha";
@Injectable()
export class AppService {
createCode(res, session) {
//创建验证码
const { data, text } = svgcaptcha.create({
size: 4,
fontSize: 50,
width: 100,
height: 40,
background: "#cc9966",
});
//设置session
session.code = text;
//返回验证码
res.type("image/svg+xml");
res.send(data);
}
createUser(body, session) {
//验证码判断
if (
session.code.toLocaleLowerCase() === body.inputcode.toLocaleLowerCase()
) {
return {
code: 200,
msg: "验证码正确",
};
} else {
return {
code: 500,
msg: "验证码错误",
};
}
}
}
控制模块 app.controller.ts
使用 @Controller()
装饰器将指定的可选路由路径前缀。 在 @Controller()
装饰器中使用路径前缀可以让我们轻松地对一组相关路由进行分组,并最大限度地减少重复代码。
import { Controller, Body, Post, Get, Res, Session } from "@nestjs/common";
import { AppService } from "./app.service";
@Controller("user")
export class AppController {
constructor(private readonly appService: AppService) {}
@Get("code")
createCode(@Res() res, @Session() session) {
return this.appService.createCode(res, session);
}
@Post("login")
createUser(@Body() body, @Session() session) {
return this.appService.createUser(body, session);
}
}