nodejs企业级开发框架nest学习总结 - 4.NestJS入门Authentication认证,TypeORM数据库连接

NestJS入门Authentication认证,TypeORM数据库连接

官网API点击这里跳转

1.Authentication依赖包安装(passport-jwt)

yarn add @nestjs/passport passport passport-local @types/passport-local @nestjs/jwt passport-jwt @types/passport-jwt --save-dev

2.书写加密类,继承内置类、重写validate方法

// jwt.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
/**
 * * 我们遵循了之前描述的所有Passport策略。在我们使用passport-local的用例中,没有配置选项,所以我们的构造函数只是调用super(),没有选项对象。
 */
/**
 * 把用户信息存储到user中的类,继承内置类PassportStrategy、重写validate方法
 */
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'encry') { // 继承PassportStrategy方法抛出的类,传递一个Strategy ,第二个参数是自定义的加密的字符串
    constructor(private readonly authService: AuthService) { // 依赖注入服务
        super(); // 并且调用父类的构造函数
    }
    public async validate(username: string, password: string): Promise<any> {
        const user = await this.authService.validateUser(username, password); // (模拟)去数据库 验证是否成功
        if (!user) {
            throw new UnauthorizedException(); // 抛出未授权异常
        }
        return user; // validate()方法返回的值自动创建一个对象,并将其分配给Request对象:获取例如:req.user
    }
}
validate方法的两个参数,当使用守卫装饰路由后自动在body中注入成形参,然后提供给validate方法内部使用,当验证没有发生错误的情况下,数据在返回的时候被自动注入到request请求对象中,增加了一个user属性,里面包含了返回的数据,类型不限

validate由于继承了内置的类,所以只需要书写守卫装饰器,该方法会被系统自动调用

3.书写认证服务,模拟实现账号密码验证,登录成功生成token

// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; // 引入jwt
/**
 * 验证登录账号密码正确性,并产生token
 * @function validateUser 验证登录账号密码正确性
 * @function login 产生token
 */
@Injectable()
export class AuthService {
    constructor(
        // private readonly usersService: UsersService,
        private readonly jwtService: JwtService,
    ) { }
    /**
     * 模拟数据库匹配账号密码
     * @param username 用户名
     * @param pass 密码
     */
    public async validateUser(username: string, pass: string): Promise<any> {
		// 模拟数据
        const user = {
            id: '12fDAA267CCFa9932c',
            username: 'liu',
            password: '1111',
        };
        // 模拟查询数据库,判断密码什么的是否正确
        if (user && user.password === pass) {
            const { password, ...result } = user;
            return result; // 然后返回除了密码的数据,给local.strategy.ts那边使用
        }
        return null;
    }

    public async login(user: any) { // 登录,创建一个登录的token并返回
        const { username, id } = user;
        const payload = { username, id };
        return {
           // 调用内置的nestjs的jwt生成token的对象方法
            token: 'Bearer ' + this.jwtService.sign(payload),
        };
    }
}

JwtService这个jwt内置服务,会提供生成token的对象方法,当然还需要在模块中配置一些对应的才能生效

4.创建一个路由控制器类,用于使用jwt安全认证,登录成功后生成token

// auth.controller.ts
import { Controller, Request, Post, UseGuards, Get } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
/**
 * @UseGuards(AuthGuard('local'))我们使用的AuthGuard是@nestjs/passport自动设置
 */
@Controller('/auth')
export class AuthController {
	// 前面也提到过的,注入服务,提供给整个路由控制器类使用
    public constructor(private readonly authService: AuthService) { }
    /**
     * 使用jwt安全认证,登录成功后生成token
     */
    @UseGuards(AuthGuard('encry')) // encry 自定义的,默认是local,在local.strategy.ts文件中修改
    @Post('/login')
    public async login(@Request() req): Promise<{ access_token: string }> { // 登录验证在authService里面的validateUser进行
        return this.authService.login(req.user); // 发送登录请求,获取token
    }
}
AuthGuard守卫,内置安全认证的守卫,通过传递的字符串来辨别是验证数据还是token解析

1.默认情况下local字符串是对数据进行一系列操作,执行local.strategy.ts方法的validate

2.而后面使用的jwt字符串就是解析token的操作,会在Authentication字段中提取出token并解析出之前生成token放入的数据
3.默认情况下的这字符串可以修改,例如local.strategy.ts的PassportStrategy方法的第二个参数,就是自定义的字符串,然后使用也就用相应的字符串

这时候访问http://localhost:3000/auth/login是没有用的,因为我们还没配置对应的模块

5.配置模块,用于注册控制器,服务,内置jwt加解密模块

// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { LocalStrategy } from './local.strategy';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
@Module({
    imports: [
        JwtModule.register({
            secret: jwtConstants.secret, // 设置secret
            signOptions: { expiresIn: '36000s' }, // 设置token的属性,时间为3600*10就是十小时,其余配置可以看jwt的一些相关配置
        }),
    ],
    controllers: [AuthController], // 注册控制器
    providers: [AuthService, LocalStrategy], // 把AuthService,LocalStrategy注册成提供者
    exports: [AuthService], // 把这个服务抛出,给其他模块使用
})
export class AuthModule { }
constants.ts里面是一个常量对象,创建即可,密钥可以自定义
/**
 * jwt的密匙
 */
export const jwtConstants = {
    secret: 'secretKey',
};
这样就注册了一个登录时候生成token的配置,也有设置了密钥和有效期

6.解析token,创建一个解析jwt的类

// jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';
/**
 * 解析token的类
 * * 命名策略,实现策略时,可以通过向PassportStrategy函数传递第二个参数来为其提供名称。如果你不这样做,每个策略都有一个默认名称(例如,jwt-strategy的'jwt'
 */
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy/*, 'myjwt'*/) {// 自定义成myjwt后,解析也要使用myjwt
    constructor() {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: jwtConstants.secret,
        });
    }

    async validate({id, username}: any) {
        return { id , username };
    }
}

区别不大,就是调用父类的时候,传递了三个参数

  • jwtFromRequest:接收一个passport-jwt对象的ExtractJwt的fromAuthHeaderAsBearerToken方法执行的结果(JwtFromRequestFunction类型)
  • ignoreExpiration 接收一个布尔类型,是否忽略过期时间,正常是false,不忽略过期
  • secretOrKey 就是加密的时候的密钥,需要一致才能解密
    validate,然后重写父类的这个方法会接收到一个token解析后的payload数据,和生成token所用的payload一致

7.解析token的服务配置到模块中

// auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
@Module({
    imports: [
        // PassportModule,使用@nestjs/passport里面的PassportModule模块
        // 使用该register()方法可以以相同的方式传递任何标准的Passport自定义选项。可用选项取决于正在实施的策略。
        PassportModule.register({ defaultStrategy: 'jwt'/*, session: true */ }), // 默认策略,之后在装饰器中使用就不需要传递
        JwtModule.register({
            secret: jwtConstants.secret, // 设置secret
            signOptions: { expiresIn: '36000s' }, // 设置token的属性,时间为3600*10就是十小时,其余配置可以看jwt的一些相关
        }),
    ],
    controllers: [AuthController], // 注册控制器
    providers: [AuthService, LocalStrategy, JwtStrategy], // 把AuthService,LocalStrategy,JwtStrategy注册成提供者
    exports: [AuthService], // 把这个服务抛出,给其他模块使用
})
export class AuthModule { }
/**
 * 在第一种情况下(用户未登录),我们需要执行两个不同的功能:
 * * 限制未经身份验证的用户可以访问的路由(即拒绝访问受限制的路由)。我们将以熟悉的方式使用Guards来处理此功能,方法是在受保护的路由上放置一个Guard。
 * * 正如您所预料的那样,我们将在此Guard中检查是否存在有效的JWT,因此我们将在稍后成功发布JWT后继续使用此Guard。
 */
增加了PassportModule.register模块和JwtStrategy这个自定义继承实现的服务

使用该register()方法可以以相同的方式传递任何标准的Passport自定义选项。可用选项取决于正在实施的策略。

PassportModule.register({ defaultStrategy: ‘jwt’/*, session: true */ }), // 默认策略,之后在装饰器中使用就不需要传递,相当于提取出来AuthGuard()传递的字符串’jwt’,默认就是jwt,也可以在jwt.strategy.ts里面设置PassportStrategy的第二个参数

8.在控制器路由中使用,加上以下路由方法

    /**
     * 解析token并返回数据
     */
    // @UseGuards(AuthGuard('jwt'))
    @UseGuards(AuthGuard()) // 设置了默认策略之后
    @Get('me')
    public getProfile(@Request() req): any {
        return req.user;
    }
@UseGuards(AuthGuard('jwt'))方式是在注册的时候没有配置 defaultStrategy: 'jwt'的情况下使用的,配置了就不需要传递'jwt'字符串
这样访问http://localhost:3000/auth/me 并在请求头设置Authentication是登录生成的token即可

TypeORM连接使用Mysql数据库

8.TypeORM结合Nest的依赖库安装

yarn add @nestjs/typeorm typeorm mysql --save

9.创建数据库连接,单个数据库连接

// app.module.ts
import { Module } from '@nestjs/common';
// 使用typeorm
import { TypeOrmModule } from '@nestjs/typeorm';
import { Connection } from 'typeorm';
@Module({
  imports: [TypeOrmModule.forRoot(
    { // 注意:使用ts或者es6以上的方式,不支持ormconfig.json的方式
      type: 'mysql', // 类型
      host: 'localhost', // 本地host
      port: 3306, // 3306 mysql 端口号
      username: 'root', // mysql数据库账号
      password: 'password', // 数据库密码
      database: 'test', // 对应的数据库
      entities: [
        __dirname + '/**/entity/*.entity{.ts,.js}',
      ],
      synchronize: true,
    }),
  ],
})
export class AppModule {
  // 完成此操作后,TypeORM Connection和EntityManager对象将可用于**在整个项目中进行注入**(无需导入任何模块),
  public constructor(private readonly connection: Connection) { }
}
引入,配置数据库连接,entities是typeORM扫描的实体路径,当数据库中不存在该表/集合会创建

10.抽离出配置到ormconfig.json文件中,放置在和package.json文件同等目录下(记得不是src,是dist运行自动打包的项目目录)

{
    "type": "mysql",
    "host": "localhost",
    "port": 3306,
    "username": "root",
    "password": "test",
    "database": "test",
    "entities": [
       "dist/**/entity/*.entity{.ts,.js}"
    ],
    "synchronize": true
}

11.创建实例类,与数据库字段一致

// entity/users.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Users {
    @PrimaryGeneratedColumn()
    id: number;

    @Column({ length: 100, update: false })
    username: string;

    @Column({ length: 100 })
    password: string;

    @Column({
        length: 10,
        default: 'admin',
        update: false,
     })
    type: string;

    @Column({ length: 255 })
    header: string;

    @Column({ length: 100 })
    post: string;

    @Column({ length: 100 })
    info: string;

    @Column({ length: 100 })
    company: string;

    @Column({ length: 100 })
    salary: string;
}

typeorm的装饰器的一些使用,可以观看官方文档或者,本人写的恶补typeorm
Nest恶补Typeorm - Typeorm快速入门学习(结合typescript)

TypeOrm官方API文档,跳转

12.创建服务,用于注入查找数据

// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from './entity/users.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(Users) // 注入实体,传递实体类参数
    private readonly usersRepository: Repository<Users>, // 类型为Repository的泛型类
  ) { }

  public findAll(): Promise<Users[]> {
    return this.usersRepository.find();
  }
}

13.创建路由控制器

// users.controller.ts
import { Controller, Get, Post, Param } from '@nestjs/common';
import { Users } from './entity/users.entity';
import { UsersService } from './users.service';
@Controller('/user')
export class UsersController {
    public constructor(private readonly usersService: UsersService) { }
    @Get()
    public findAll(): Promise<Users[]> {
        return this.usersService.findAll(); // get请求查询全部user数据
    }
}

14.创建模块,然后imports注入到app模块中

// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { Users } from './entity/users.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Users/*, 'name' */])], // 加入功能模块,为一个数组的实体类 , 第二个参数为一个字符串,表示你在连接数据库设置的name,也是标识多个数据库连接的name
  providers: [UsersService],
  controllers: [UsersController],
})
export class UsersModule { }

TypeOrmModule.forFeature(参数1,参数2) 加入功能模块,为一个数组的实体类 , 第二个参数为一个字符串,表示你在连接数据库设置的name,也是标识多个数据库连接的

引入模块到app.module.ts中
// app.module.ts
import { Module } from '@nestjs/common';
// 使用typeorm
import { TypeOrmModule } from '@nestjs/typeorm';
import { Connection } from 'typeorm';
import { UsersModule } from './users.module'
@Module({
  imports: [TypeOrmModule.forRoot(
    {
      type: 'mysql', // 类型
      host: 'localhost', // 本地host
      port: 3306, // 3306 mysql 端口号
      username: 'root', // mysql数据库账号
      password: 'password', // 数据库密码
      database: 'test', // 对应的数据库
      entities: [
        __dirname + '/**/entity/*.entity{.ts,.js}',
      ],
      synchronize: true,
    }),
    UsersModule
  ],
})
export class AppModule {
  // 完成此操作后,TypeORM Connection和EntityManager对象将可用于**在整个项目中进行注入**(无需导入任何模块),
  public constructor(private readonly connection: Connection) { }
}
这样就可以使用typeOrm进行对数据库的操作了
public constructor(private readonly connection: Connection) {}

这个构造函数的目的是可以让Connection和EntityManager对象将可用于**在整个项目中进行注入**(无需导入任何模块)

15.使用Connection和EntityManager对象

// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository, InjectConnection, InjectEntityManager } from '@nestjs/typeorm';
import { Repository, EntityManager, Connection } from 'typeorm';
import { Users } from './entity/users.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(Users) // 注入实体,传递实体类参数
    private readonly usersRepository: Repository<Users>, // 类型为Repository的泛型类,生成两个参数,manager和metadata
    // 或者把连接数据库设置的name传递连接注入Connection或EntityManager
    @InjectConnection(/*'personsConnection'*/) // 获取对应的连接
    private readonly connection: Connection,
    @InjectEntityManager(/*'personsConnection'*/) // 获取注入的实体管理
    private readonly entityManager: EntityManager,
  ) { }

  public findAll(): Promise<Users[]> {
    return this.entityManager.find(Users);
    // or
    // return this.connection.manager.find(Users);
    // return this.usersRepository.find();
  }
}
上面就使用了Connection对象和EntityManager对象,替换了前面的Repository泛型类的操作

16.自定义存储库,定制findById查询单个数据操作等

// custom.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { Users } from './entity/users.entity';

/**
 * 定制存储库
 * * TypeORM提供了一个称为自定义存储库的功能。自定义存储库允许您扩展基本存储库类,并使用几种特殊方法对其进行丰富
 * * 下一步是将实例化责任委派给Nest。为此,我们必须将AuthorRepository类传递给TypeOrm.forFeature()方法。
 * * 之后在imports里面使用的TypeOrmModule.forFeature([Users] 替换成TypeOrmModule.forFeature([UsersRepository]
 */
@EntityRepository(Users) // 扩展了基本的存储库操作等
export class UsersRepository extends Repository<Users> {
    constructor( // 使用Object.assign对this上面添加连接成功后的操作对象
        connection: Connection,
    ) {
        super();
        Object.assign(this, connection.getRepository(Users));
    }
  // 扩展基本库,增加以下方法,通过id查找单条数据
  public findById(id: number): Promise<Users> {
    return this.findOne({ id });
  }
  // 扩展基本库,增加add这个增加数据的操作
  public add(users: Users): Promise<Users> {
    return this.save(users);
  }
}
在存储库扩展的情况下,继承Repository 这个泛型类,可以使用先前默认的find,findOne....等等等增删改查的方法
替换掉全部使用Users实体的地方(users.module.ts和users.service.ts)
users.module.ts
// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { UsersRepository } from './custom.repository';

@Module({
  // imports: [TypeOrmModule.forFeature([Users/*, 'name' */])], // 加入功能模块,为一个数组的实体类 , 第二个参数为一个字符串,表示你在连接数据库设置的name,也是标识多个数据库连接的name
  imports: [TypeOrmModule.forFeature([UsersRepository])], // 注入自定义扩展存储库后的实体
  providers: [UsersService],
  controllers: [UsersController],
})
export class UsersModule { }
users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectConnection, InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager, Connection } from 'typeorm';
import { Users } from './entity/users.entity';
import { UsersRepository } from './custom.repository';

@Injectable()
export class UsersService {
  constructor(
    private readonly usersRepository: UsersRepository, // 类型为Repository的泛型类,生成两个参数,manager和metadata
    // 或者把连接数据库设置的name传递连接注入Connection或EntityManager
    @InjectConnection(/*'personsConnection'*/) // 获取对应的连接
    private readonly connection: Connection,
    @InjectEntityManager(/*'personsConnection'*/) // 获取注入的实体管理
    private readonly entityManager: EntityManager,
  ) { }

  public findAll(): Promise<Users[]> {
    // return this.entityManager.find(Users);
    // or
    // return this.connection.manager.find(Users);
    return this.usersRepository.find();
  }
  public getFindOne(id: number): Promise<Users> {
    return this.usersRepository.findById(id); // 使用自定义的存储库操作
  }
  public addUser(user: Users): Promise<Users> { // 添加一条数据的方法
    return this.userRepository.add(user);
  }
}
使用了扩展的UsersRepository存储库后,会增加两个自定义的方法,findById和add

17.增加路由控制器一些新操作尝试扩展的方法

// users.controller.ts
import { Controller, Get, Post, Param, Body } from '@nestjs/common';
import { Users } from './entity/users.entity';
import { UsersService } from './users.service';
@Controller('/user')
export class UsersController {
    public constructor(private readonly usersService: UsersService) { }
    @Get()
    public findAll(): Promise<Users[]> {
        return this.usersService.findAll();
    }
    @Post('/select/:id') // 通过id查询单个用户信息
    public findOne(@Param('id') id: number): Promise<Users> {
        return this.usersService.getFindOne(id);
    }
    @Post('/add') // 通过id查询单个用户信息
    public addUser(@Body() user: Users): Promise<Users> {
        return this.usersService.addUser(user);
    }
}
这样扩展了存储库,也可以进行一些复杂的对数据库的操作封装,本人只是简单使用

18.异步配置连接数据库模块

// async.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeOrmConfigService } from './typeOrmConfig.service';

/**
 * 异步配置存储库
 * * 异步传递存储库模块选项而不是静态传递。在这种情况下,请使用该forRootAsync()方法,该方法提供了几种处理异步配置的方法。
 * * 1.工厂功能
 */
@Module({
    imports: [TypeOrmModule.forRootAsync({
    	// 方式1,直接使用useFactory方法配置,返回一个连接对象
        useFactory: () => ({
            type: 'mysql',
            host: 'localhost',
            port: 3306,
            username: 'root',
            password: 'root',
            database: 'test',
            entities: [__dirname + '/**/*.entity{.ts,.js}'],
            synchronize: true,
        }),
        // 方式2 使用以下useClass语法,配置一个创建连接的服务,
        // useClass: TypeOrmConfigService,
    })],
})
export class AsyncModule { }
// typeOrmConfig.service.ts
import { Injectable } from '@nestjs/common';
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from '@nestjs/typeorm';

@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory { // 实现选项factory工厂
    createTypeOrmOptions(): TypeOrmModuleOptions { // 返回一个模块选项
        return {
            type: 'mysql',
            host: 'localhost',
            port: 3306,
            username: 'root',
            password: 'root',
            database: 'test',
            entities: [__dirname + '/**/*.entity{.ts,.js}'],
            synchronize: true,
        };
    }
}
暂时了解异步配置即可,也可以在正常项目中使用
其它的部分请前往首页查看,或者直接官方查看

本博客主的首页地址(点击跳转)

你可能感兴趣的:(TypeORM,TypeScript,Nest)