前言
萌新做的一个毕设,边学边做,使用Nest.js的原因是他原生支持TypeScript,仿写的BBBUG 音乐聊天室,肯定没人家原版写的好,大部分功能已经完成
个人gitee地址后端地址 前端地址
- 文件上传模块
- 使用中间件对用户身份验证
- 数据库:TypeORM x Mysql
- 暴露静态资源目录
- websocket实现数据推送
- 管道dto验证
前端
前端部分采用React来实现,大部分都有TS类型提示
后端
业务接口
- 文件上传:multer 静态资源暴露静态资源目录
- 用户:注册 修改密码 邮箱发送验证码
- 聊天室:WebSocket
- 音乐数据:axios 请求酷我接口
基础模块
- 鉴权:设置middware相应的处理
- 数据库:TypeORM
- 参数校验:管道-类验证器,进行请求参数校验
ws验证
import { WebSocketGateway, WebSocketServer } from "@nestjs/websockets";
import { Socket, Server } from "socket.io";
import { SongService } from "src/song/song.service";
import { decrypt, getTimeStamp, queryString } from "src/utils/common";
import { CacheRedisService } from "src/cache-redis/cache-redis.service";
@WebSocketGateway({
allowEIO3: true,
cors: {
origin: "*",
credentials: true,
},
transports: ["websocket"],
})
export class WsGateway {
constructor(private song: SongService, private redis: CacheRedisService) {
setInterval(async () => {
const res = await song.execute();
//如果接收到的值是空,那么就不会广播
for (let i = 0; i < res.length; i++) {
this.server.to(res[i].room_id.toString()).emit("playSong", {
song: res[i].song,
sysTime: getTimeStamp(),
});
}
}, 6000);
}
@WebSocketServer()
server: Server;
async send(room_id: string, data: any) {
this.server.to(room_id).emit("text", data);
}
async handleConnection(client: Socket, ...args: any[]) {
const str = decrypt(client.handshake.query.tick as string);
const obj = queryString(str) as any;
if (obj.timeStamp) {
if (getTimeStamp() > Number(obj.timeStamp) + 300) {
client.disconnect();
} else {
client.join(obj.room_id as any);
const res = await this.song.getNowPlayingSong(obj.room_id);
if (res) {
client.emit("playSong", { sysTime: getTimeStamp(), song: res });
}
}
}
}
}
文件上传
我这里只是进行了一个图片上传,为了获取到图片的真实类型,用了一个image-type进行一个类型的判断(原理的文件的数据,在前多少位来着有一个标记,具体可以自己搜索一下)
@Post("upload")
@UseInterceptors(FileInterceptor("file"))
uploadImage(
@UploadedFile() file: Express.Multer.File,
@Req() request: Request
) {
const ret = imageType(file.buffer);
//限制大小
if (file.size > 1024 * 1024) {
throw new HttpException({ code: 601, msg: "文件大小最大为1MB" }, 200);
}
//限制文件类型
if (!ret || !mimeTypes.has(ret.ext)) {
throw new HttpException(
{ code: 602, msg: "只允许上传jpg、png、jpeg" },
200
);
}
const data = this.userService.uploadHead(
file.buffer,
`${v4()}.${ret.ext}`,
request.headers.token as string
);
if (data) {
return success("上传成功", { path: data });
}
throw new HttpException({ code: 603, msg: "文件生成失败" }, 200);
}
中间件
为了省事不再二次查询,就将数据挂载到body上了
import {
HttpException,
Inject,
Injectable,
NestMiddleware,
} from "@nestjs/common";
import { Request, Response, NextFunction } from "express";
import { CacheRedisService } from "src/cache-redis/cache-redis.service";
@Injectable()
export class UserMiddleware implements NestMiddleware {
constructor(
@Inject(CacheRedisService) private readonly redis: CacheRedisService
) {}
async use(req: Request, res: Response, next: NextFunction) {
const token = req.headers.token ?? "";
if (!token) {
throw new HttpException({ code: 501, msg: "令牌不存在" }, 200);
}
const data = await this.redis.get(token.toString());
if (data === null) {
throw new HttpException({ code: 502, msg: "令牌异常" }, 200);
}
const user = data;
Object.assign(req.body, { user });
next();
}
}