【nest mongo】

965 阅读4分钟

1. 初始化

安装

npm i -g @nestjs/cli
nest new nest-mongo

删除测试

  • 删除package.json测试相关
// 以下是删除的代码
{
    "scripts": {
        "test": "jest",
        "test:watch": "jest --watch",
        "test:cov": "jest --coverage",
        "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
        "test:e2e": "jest --config ./test/jest-e2e.json"
    },
    "devDependencies": {
        "@nestjs/testing": "^7.6.15",
        "@types/supertest": "^2.0.10",
        "@types/jest": "^26.0.22",
        "supertest": "^6.1.3",
        "ts-jest": "^26.5.4",
    },
    "jest": { ... }
}
  • 删除 spec 文件

  • 安装运行

$ npm install
$ npm run start

# Hello World! 成功

定制格式化

// .pretterrc
{
    "jsxSingleQuote": true,
    "jsxBracketSameLine": true,
    "printWidth": 120,
    "singleQuote": true,
    "trailingComma": "none",
    "useTabs": true,
    "tabWidth": 2,
    "semi": false,
    "arrowParens": "avoid"
}
$ npm run lint

2. 登录接口

配置好mongodb用户表users connections,添加几个用户

接入Mongo

$ npm install --save @nestjs/mongoose mongoose
// app.module.ts
import { MongooseModule } from '@nestjs/mongoose'

@Module({
    imports: [
        MongooseModule.forRoot(
                'mongodb://<path-to-mongodb>?authSource=admin&retryWrites=true&w=majority',
                { useFindAndModify: false }
                // 消除警告(早期mongoose使用了mongodb后期才有的方法,现在做同步)
        )
    ],
    ... 
})

users 模块

# 新建users目录,新建模块三件套
src/users/users.module.ts # 引入app.module.ts
src/users/users.controller.ts # 加一个 /user/login 接口
src/users/users.service.ts 
// src/users/users.controller.ts
import { Controller, Post } from '@nestjs/common'
import { UsersService } from './users.service'

@Controller('user')
export class UsersController {
    constructor(private usersService: UsersService) {}

    @Post('login')
    login() {
        return 'login api'
    }
}

访问数据库

// user 模块注入mongodb
// src/users/users.module.ts
import { MongooseModule } from '@nestjs/mongoose'
import { UserSchema } from './schemas/user.schema'

@Module({
    imports: [MongooseModule.forFeature([{ name: 'users', schema: UserSchema }])],
})
// user 结构
// src/users/schemas/user.schema.ts
import { Prop, SchemaFactory, Schema } from '@nestjs/mongoose'
import { Document } from 'mongoose'

@Schema()
export class User extends Document {
    @Prop()
    ...
}
export const UserSchema = SchemaFactory.createForClass(User)
// user 数据服务
// scr/users/users.service.ts
import { Injectable } from '@nestjs/common'
import { LeanDocument, Model } from 'mongoose'
import { InjectModel } from '@nestjs/mongoose'
import { User } from './schemas/user.schema'

Injectable()
export class UsersService {
    constructor(@InjectModel('users') private readonly usersModel: Model<User>) {}

    async validateUser(username: string, password: string): Promise<Omit<LeanDocument<User>, 'password'> | void> {
        const user = await this.usersModel.findOne({ username }).exec()
        if (user && user.password === password) {
            const { password, ...result } = user.toObject({ getters: true })
            return result
        }
    }
}
// user login路由
import { Controller, Post, Body } from '@nestjs/common'
import { UsersService } from './users.service'

@Controller('user')
export class UsersController {
    constructor(private usersService: UsersService) {}

    @Post('login')
    async login(@Body() loginDto: { username: string; password: string }) {
        const user = await this.usersService.validateUser(loginDto.username, loginDto.password)
        if (user) return user
        else return '请输入'
    }
}

jwt 生成 token

$ npm install @nestjs/jwt passport-jwt
$ npm install @types/passport-jwt --save-dev # passport-jwt还没有ts声明文件
// user 模块注入jwt模块
// src/users/users.module.ts
import { JwtModule } from '@nestjs/jwt'
import { jwtContants } from './contants' // 新建常量文件,方便其它文件引用

@Module({
    imports: [
        JwtModule.register({
            secret: jwtContants.secret,
            signOptions: { expiresIn: '1d' }
        })
    ],
    ...
// user 登录服务
// src/users/users.service.ts
import { JwtService } from '@nestjs/jwt'

Injectable()
export class UsersService {
    constructor(
        @InjectModel('users') private readonly usersModel: Model<User>,
        private readonly jwtService: JwtService
    ) {}
    ...
    login(user: LeanDocument<Omit<User, 'password'>>): string {
        const payload = { username: user.username, id: user._id }
        return 'Bearer ' + this.jwtService.sign(payload)
    }
}
// src/users/users.controller.ts
@Controller('user')
    @Post('login')
    async login(@Body() loginDto: { username: string; password: string }) {
        const user = await this.usersService.validateUser(loginDto.username, loginDto.password)
        if (user) return this.usersService.login(user)
    }

用户认证

// jwt 策略文件
// src/users/strategies/jwt.strategy.ts
import { Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { Strategy, ExtractJwt } from 'passport-jwt'
import { jwtContants } from '../constants'

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
    constructor() {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: jwtContants.secret
        })
    }

    validate(payload) {
        return { id: payload.id, username: payload.username }
    }
}
// user 模块注入jwt策略
// src/users/users.module.ts
import { JwtStrategy } from './strategies/jwt.strategy'
@Module({
    providers: [UsersService, JwtStrategy],
    ...
})
export class UsersModule {}
// user 查询用户数据服务
// src/users/users.service.ts
Injectable()
export class UsersService {
    ...
    async findById(id: string): Promise<Partial<User> | void> {
        const user = this.usersModel.findById(id).exec()
        if (user) {
            const { password, id, _id, ...result } = (await user).toObject({ getters: true })
            return result
        }
    }
}
// 查询用户路由
// src/users/users.controller.ts
@Controller('user')
export class UsersController {
    ...
    @UseGuards(AuthGuard('jwt'))
    @Get('profile')
    async getProfile(@Request() req) {
        const user = await this.usersService.findById(req.user.id)
        if (user) return user
    }
}

包裹异常

// 如果token验证未通过,nest jwt自动返回401(如下)
{
    "statusCode": 401,
    "message": "Unauthorized"
}

// statusCode 字段并不是我想要的,需要将其换为常用的 code
// 创建一个异常过滤器
// src/common/HttpExceptionFilter.ts
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common'
import { Response } from 'express'

@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
    catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp()
        const response = ctx.getResponse<Response>()
        const status = exception.getStatus()

        response.status(status).json({
            code: status,
            message: exception.message
        })
    }
}
// 全局绑定
// src/main.ts
import { HttpExceptionFilter } from './common/HttpExceptionFilter'
...
    app.useGlobalFilters(new HttpExceptionFilter())
...

传输验证管道

$ npm install class-validator class-transformer
// src/users/dto/login.dto
import { IsDefined, IsString, Length } from 'class-validator'

export class LoginDto {
    ...
}
// 绑定验证
// src/users/users.controller.ts
import { LoginDto } from './dto/login.dto'
...
    async login(@Body() loginDto: LoginDto) {
...

返回错误数据没能正确显示,修改一下HttpExceptionFilter

// src/common/HttpExceptionFilter.ts
...
    const resOfException: string | { message?: string } = exception.getResponse()

    response.status(status).json({
        code: status,
        message: typeof resOfException === 'string' ? resOfException : resOfException.message
    })
...

设置返回结构

// src/common/types/return.ts
export interface Return {
    code: number

    message: string

    date: any
}

3. 日记路由

新建文件

# 建立diaries模块

src/diaries/diaries.module.ts
src/diaries/diaries.service.ts
src/diaries/diaries.controller.ts

# 注意链接模块树 app => diaries => service + controller
// 测试路由
import { Controller, Get } from '@nestjs/common'

@Controller('diaries')
export class DiariesController {
    @Get()
    getDiaries() {
        return 'get diaries'
    }
}

链接数据表

// 新建数据结构
// src/diaries/schemas/diary.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'
import { Document } from 'mongoose'

@Schema()
export class Diary extends Document {
...
}

export const DiarySchema = SchemaFactory.createForClass(Diary)
// 模块链接diaries表
// src/diaries/diaries.module.ts
import { MongooseModule } from '@nestjs/mongoose'
import { DiarySchema } from './schemas/diary.schema'
@Module({
    imports: [MongooseModule.forFeature([{ name: 'diaries', schema: DiarySchema }])],
})
export class DiariesModule {}
// 数据服务
// src/diaries/diaries.service.ts
import { Injectable } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { LeanDocument, Model } from 'mongoose'
import { Diary } from './schemas/diary.schema'

@Injectable()
export class DiariesService {
    constructor(@InjectModel('diaries') private readonly diariesModel: Model<Diary>) {}

    async findAll(pageSize = 10, pageNum = 1): Promise<LeanDocument<Diary>[]> {
        const diaries = await this.diariesModel
            .find()
            .limit(pageSize)
            .skip((pageNum - 1) * pageSize)
            .exec()
        const diariesObject = diaries.map(diary => diary.toObject())
        return diariesObject
    }
}
// 路由返回数据
// src/diaries/diaries.controller.ts

@Controller('diaries')
export class DiariesController {
    ...
    @Get()
    async getDiaries() {
        const diaries = this.diariesService.findAll()
        return diaries
    }
...
  • 登录限制
// src/diaries/diaries.controller.ts

@UseGuards(AuthGuard('jwt'))
@Controller('diaries')
export class DiariesController {
    ...
    @Get()
    async getDiaries(@Request() req): Promise<ReturnType> {
        const diaries = this.diariesService.findAll(req.user.id)
        ...
    }
}
  • 数据筛选
// src/diaries/diaries.service.ts
...
async findAll(id: string, pageSize = 10, pageNum = 1): Promise<LeanDocument<Diary>[]> {
    ...
    .find({ author: id })
    ...
}