nest连接Mongodb
mac中,直接使用brew install mongodb-community
安装MongoDB,然后启动服务brew services start mongodb-community
查看服务已经启动ps aux | grep mongo
Nestjs中操作Mongodb数据库可以使用Nodejs封装的DB库,也可以使用Mongoose。
// https://docs.nestjs.com/techniques/mongodb
npm install --save @nestjs/mongoose mongoose
npm install --save-dev @types/mongoose
在app.module.ts中配置数据库连接
// app.module.ts
import { ConfigModule, ConfigService } from 'nestjs-config';
import { MongooseModule } from '@nestjs/mongoose';
import { MongodbModule } from '../examples/mongodb/mongodb.module';
@Module({
imports: [
// 加载配置文件目录
ConfigModule.load(resolve(__dirname, 'config', '**/!(*.d).{ts,js}')),
// mongodb
MongooseModule.forRootAsync({
useFactory: async (configService: ConfigService) =>
configService.get('mongodb'),
inject: [ConfigService],
}),
MongodbModule,
],
controllers: [],
providers: [],
})
export class AppModule implements NestModule {}
// mongodb配置
// src/config/mongodb.ts
export default {
uri: 'mongodb://localhost:27017/nest', // 指定nest数据库
};
配置Schema
// article.schema
import * as mongoose from 'mongoose';
export const ArticleSchema = new mongoose.Schema({
title: String,
content:String,
author: String,
status: Number,
});
在控制器对应的Module中配置Model
// mongodb.module.ts
import { Module } from '@nestjs/common';
import { MongodbService } from './mongodb.service';
import { MongodbController } from './mongodb.controller';
import { ArticleSchema } from './schemas/article.schema';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forFeature([
{
name: 'Article', // schema名称对应
schema: ArticleSchema, // 引入的schema
collection: 'article', // 数据库名称
},
]),
],
controllers: [MongodbController],
providers: [MongodbService],
})
export class MongodbModule {}
在服务里面使用@InjectModel 获取数据库Model实现操作数据库
// mongodb.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
@Injectable()
export class MongodbService {
// 注入模型
constructor(@InjectModel('Article') private readonly articleModel) {}
async findAll() {
return await this.articleModel.find().exec();
}
async findById(id) {
return await this.articleModel.findById(id);
}
async create(body) {
return await this.articleModel.create(body);
}
async update(body) {
const { id, ...params } = body;
return await this.articleModel.findByIdAndUpdate(id, params);
}
async delete(id) {
return await this.articleModel.findByIdAndDelete(id);
}
}
浏览器测试 http://localhost:9000/api/mongodb/list
typeORM操作Mysql数据库
mac中,直接使用brew install mysql
安装mysql,然后启动服务brew services start mysql
查看服务已经启动ps aux | grep mysql
Nest 操作Mysql官方文档:docs.nestjs.com/techniques/…
npm install --save @nestjs/typeorm typeorm mysql
配置数据库连接地址
// src/config/typeorm.ts
const { MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE } =
process.env;
const config = {
type: 'mysql',
host: MYSQL_HOST,
port: MYSQL_PORT,
username: MYSQL_USER,
password: MYSQL_PASSWORD,
database: MYSQL_DATABASE,
synchronize: process.env.NODE_ENV !== 'production', // 生产环境不要开启
autoLoadEntities: true, // 如果为true,将自动加载实体(默认:false)
keepConnectionAlive: true, // 如果为true,在应用程序关闭后连接不会关闭(默认:false)
retryDelay: 3000, // 两次重试连接的间隔(ms)(默认:3000)
retryAttempts: 10, // 重试连接数据库的次数(默认:10)
dateStrings: 'DATETIME', // 转化为时间
timezone: '+0800', // +HHMM -HHMM
// 自动需要导入模型
entities: ['dist/**/*.entity{.ts,.js}'],
};
export default config;
// app.module.ts中配置
import { resolve, join } from 'path';
import { ConfigModule, ConfigService } from 'nestjs-config';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
// 加载配置文件目录
ConfigModule.load(resolve(__dirname, 'config', '**/!(*.d).{ts,js}')),
// 连接mysql数据库
TypeOrmModule.forRootAsync({
useFactory: (config: ConfigService) => config.get('typeorm'),
inject: [ConfigService],
}),
],
controllers: [],
providers: [],
})
export class AppModule implements NestModule {}
配置实体entity
// photo.entity.ts
import {
Column,
Entity,
ManyToMany,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { PostsEntity } from './post.entity';
@Entity('photo')
export class PhotoEntity {
// @PrimaryGeneratedColumn()
// id: number; // 标记为主列,值自动生成
@PrimaryGeneratedColumn('uuid')
id: string; // 该值将使用uuid自动生成
@Column({ length: 50 })
url: string;
// 多对一关系,多个图片对应一篇文章
@ManyToMany(() => PostsEntity, (post) => post.photos)
posts: PostsEntity;
}
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { PhotoEntity } from './photo.entity';
export type UserRoleType = 'admin' | 'editor' | 'ghost';
export type postStatus = 1 | 2 | 3;
// mysql的列类型: type
/**
* int, tinyint, smallint, mediumint, bigint, float, double, dec, decimal,
* numeric, date, datetime, timestamp, time, year, char, varchar, nvarchar,
* text, tinytext, mediumtext, blob, longtext, tinyblob, mediumblob, longblob, enum,
* json, binary, geometry, point, linestring, polygon, multipoint, multilinestring,
* multipolygon, geometrycollection
*/
/**
* ColumnOptions中可用选项列表:
* length: number - 列类型的长度。 例如,如果要创建varchar(150)类型,请指定列类型和长度选项。
width: number - 列类型的显示范围。 仅用于MySQL integer types(opens new window)
onUpdate: string - ON UPDATE触发器。 仅用于 MySQL (opens new window).
nullable: boolean - 在数据库中使列NULL或NOT NULL。 默认情况下,列是nullable:false。
update: boolean - 指示"save"操作是否更新列值。如果为false,则只能在第一次插入对象时编写该值。 默认值为"true"。
select: boolean - 定义在进行查询时是否默认隐藏此列。 设置为false时,列数据不会显示标准查询。 默认情况下,列是select:true
default: string - 添加数据库级列的DEFAULT值。
primary: boolean - 将列标记为主要列。 使用方式和@ PrimaryColumn相同。
unique: boolean - 将列标记为唯一列(创建唯一约束)。
comment: string - 数据库列备注,并非所有数据库类型都支持。
precision: number - 十进制(精确数字)列的精度(仅适用于十进制列),这是为值存储的最大位数。仅用于某些列类型。
scale: number - 十进制(精确数字)列的比例(仅适用于十进制列),表示小数点右侧的位数,且不得大于精度。 仅用于某些列类型。
zerofill: boolean - 将ZEROFILL属性设置为数字列。 仅在 MySQL 中使用。 如果是true,MySQL 会自动将UNSIGNED属性添加到此列。
unsigned: boolean - 将UNSIGNED属性设置为数字列。 仅在 MySQL 中使用。
charset: string - 定义列字符集。 并非所有数据库类型都支持。
collation: string - 定义列排序规则。
enum: string[]|AnyEnum - 在enum列类型中使用,以指定允许的枚举值列表。 你也可以指定数组或指定枚举类。
asExpression: string - 生成的列表达式。 仅在MySQL (opens new window)中使用。
generatedType: "VIRTUAL"|"STORED" - 生成的列类型。 仅在MySQL (opens new window)中使用。
hstoreType: "object"|"string" -返回HSTORE列类型。 以字符串或对象的形式返回值。 仅在Postgres中使用。
array: boolean - 用于可以是数组的 postgres 列类型(例如 int [])
transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType } - 用于将任意类型EntityType的属性编组为数据库支持的类型DatabaseType。
注意:大多数列选项都是特定于 RDBMS 的,并且在MongoDB中不可用
*/
@Entity('posts')
export class PostsEntity {
// @PrimaryGeneratedColumn()
// id: number; // 标记为主列,值自动生成
@PrimaryGeneratedColumn('uuid')
id: string; // 该值将使用uuid自动生成
@Column({ length: 50 })
title: string;
@Column({ length: 18 })
author: string;
@Column({ type: 'longtext', default: null })
content: string;
@Column({ default: null })
cover_url: string;
@Column({ default: 0 })
type: number;
@Column({ type: 'text', default: null })
remark: string;
@Column({
type: 'enum',
enum: [1, 2, 3],
default: 1,
})
status: postStatus;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
create_time: Date;
@Column({
type: 'timestamp',
default: () => 'CURRENT_TIMESTAMP',
})
update_time: Date;
@Column({
type: 'enum',
enum: ['admin', 'editor', 'ghost'],
default: 'ghost',
select: false, // 定义在进行查询时是否默认隐藏此列
})
role: UserRoleType;
// 一对多关系,一篇文章对应多个图片
// 在service中查询使用 .find({relations: ['photos]}) 查询文章对应的图片
@OneToMany(() => PhotoEntity, (photo) => photo.posts)
photos: [];
}
参数校验
Nest 与 class-validator 配合得很好。这个优秀的库允许您使用基于装饰器的验证。装饰器的功能非常强大,尤其是与 Nest 的 Pipe 功能相结合使用时,因为我们可以通过访问 metatype
信息做很多事情,在开始之前需要安装一些依赖。
npm i --save class-validator class-transformer
// posts.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
export class CreatePostDto {
@IsNotEmpty({ message: '文章标题必填' })
readonly title: string;
@IsNotEmpty({ message: '缺少作者信息' })
readonly author: string;
readonly content: string;
readonly cover_url: string;
@IsNotEmpty({ message: '缺少文章类型' })
readonly type: number;
readonly remark: string;
}
在控制器对应的Module中配置Model
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { PostsEntity } from './entities/post.entity';
@Module({
imports: [TypeOrmModule.forFeature([PostsEntity])],
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {}
在服务里面使用@InjectRepository获取数据库Model实现操作数据库
// posts.services.ts
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Not, Between, Equal, Like, In } from 'typeorm';
import * as dayjs from 'dayjs';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { PostsEntity } from './entities/post.entity';
import { PostsRo } from './interfaces/posts.interface';
@Injectable()
export class PostsService {
constructor(
@InjectRepository(PostsEntity)
private readonly postsRepository: Repository<PostsEntity>,
) {}
async create(post: CreatePostDto) {
const { title } = post;
const doc = await this.postsRepository.findOne({ where: { title } });
console.log('doc', doc);
if (doc) {
throw new HttpException('文章标题已存在', HttpStatus.BAD_REQUEST);
}
return {
data: await this.postsRepository.save(post),
message: '创建成功',
};
}
// 分页查询列表
async findAll(query = {} as any) {
// eslint-disable-next-line prefer-const
let { pageSize, pageNum, orderBy, sort, ...params } = query;
orderBy = query.orderBy || 'create_time';
sort = query.sort || 'DESC';
pageSize = Number(query.pageSize || 10);
pageNum = Number(query.pageNum || 1);
console.log('query', query);
const queryParams = {} as any;
Object.keys(params).forEach((key) => {
if (params[key]) {
queryParams[key] = Like(`%${params[key]}%`); // 所有字段支持模糊查询、%%之间不能有空格
}
});
const qb = await this.postsRepository.createQueryBuilder('post');
// qb.where({ status: In([2, 3]) });
qb.where(queryParams);
// qb.select(['post.title', 'post.content']); // 查询部分字段返回
qb.orderBy(`post.${orderBy}`, sort);
qb.skip(pageSize * (pageNum - 1));
qb.take(pageSize);
return {
list: await qb.getMany(),
totalNum: await qb.getCount(), // 按条件查询的数量
total: await this.postsRepository.count(), // 总的数量
pageSize,
pageNum,
};
}
// 根据ID查询详情
async findById(id: string): Promise<PostsEntity> {
return await this.postsRepository.findOne({ where: { id } });
}
// 更新
async update(id: string, updatePostDto: UpdatePostDto) {
const existRecord = await this.postsRepository.findOne({ where: { id } });
if (!existRecord) {
throw new HttpException(`id为${id}的文章不存在`, HttpStatus.BAD_REQUEST);
}
// updatePostDto覆盖existRecord 合并,可以更新单个字段
const updatePost = this.postsRepository.merge(existRecord, {
...updatePostDto,
update_time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
});
return {
data: await this.postsRepository.save(updatePost),
message: '更新成功',
};
}
// 删除
async remove(id: string) {
const existPost = await this.postsRepository.findOne({ where: { id } });
if (!existPost) {
throw new HttpException(`文章ID ${id} 不存在`, HttpStatus.BAD_REQUEST);
}
await this.postsRepository.remove(existPost);
return {
data: { id },
message: '删除成功',
};
}
}
nest统一处理数据库操作的查询结果
操作数据库时,如何做异常处异常? 比如id不存在,用户名已经存在?如何统一处理请求失败和请求成功?
处理方式:
- 在nest中,一般是在service中处理异常,如果有异常,直接抛出错误,由过滤器捕获,统一格式返回,如果成功,service把结果返回,controller直接return结果即可,由拦截器捕获,统一格式返回
- 失败:过滤器统一处理
- 成功:拦截器统一处理
- 当然你也可以在
controller
处理
// user.controller.ts
import {
Controller,
Get,
Post,
Body,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) { }
@Post()
@HttpCode(HttpStatus.OK) //创建成功返回的是201状态码,这里重置为200,需要用到的可以使用HttpCode设置
async create(@Body() user) {
return await this.userService.create(user);
}
@Get(':id')
async findOne(@Param('id') id: string) {
return await this.userService.findOne(id);
}
}
// user.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersEntity } from './entities/user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>
) { }
async create(user) {
const { username } = user;
const result = await this.usersRepository.findOne({ username });
if (result) { //如果用户名已经存在,抛出错误
throw new HttpException(
{ message: '请求失败', error: '用户名已存在' },
HttpStatus.BAD_REQUEST,
);
}
return await this.usersRepository.save(user);
}
async findOne(id: string) {
const result = await this.usersRepository.findOne(id);
if (!result) { //如果用户id不存在,抛出错误
throw new HttpException(
{ message: '请求失败', error: '用户id不存在' },
HttpStatus.BAD_REQUEST,
);
}
return result;
}
}
可以将
HttpException
再简单封装一下,或者使用继承,这样代码更简洁一些
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
@Injectable()
export class ToolsService {
static fail(error, status = HttpStatus.BAD_REQUEST) {
throw new HttpException(
{
message: '请求失败',
error: error,
},
status,
);
}
}
简洁代码
// user.service.ts
import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersEntity } from './entities/user.entity';
import { ToolsService } from '../../utils/tools.service';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>
) { }
async create(user) {
const { username } = user;
const result = await this.usersRepository.findOne({ username });
if (result) ToolsService.fail('用户名已存在');
return await this.usersRepository.save(user);
}
async findOne(id: string) {
const result = await this.usersRepository.findOne(id);
if (!result) ToolsService.fail('用户id不存在');
return result;
}
}
全局使用filter过滤器
// src/common/filters/http-execption.ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from '@nestjs/common';
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
const exceptionRes: any = exception.getResponse();
const { error, message } = exceptionRes;
const msgLog = {
status,
message,
error,
path: request.url,
timestamp: new Date().toISOString(),
};
response.status(status).json(msgLog);
}
}
全局使用interceptor拦截器
// src/common/inteptors/transform.interceptor.ts
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => {
return {
status: 200,
message: '请求成功',
data: data,
};
}),
);
}
}
// main.ts
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
async function bootstrap() {
// 创建实例
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 全局过滤器
app.useGlobalFilters(new HttpExceptionFilter());
// 全局拦截器
app.useGlobalInterceptors(new TransformInterceptor());
// 启动端口
const PORT = process.env.PORT || 9000;
await app.listen(PORT, () =>
Logger.log(`服务已经启动 http://localhost:${PORT}`),
);
}
bootstrap();
失败
成功
数据库实体设计与操作
typeorm的数据库实体如何编写?
数据库实体的监听装饰器如何使用?
实体设计
简单例子:下面讲解
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn} from "typeorm";
@Entity({ name: 'users' })
export class User {
@PrimaryGeneratedColumn()
id: number; // 默认是int(11)类型
@Column()
username: string; // 默认是varchar(255)类型
@Column()
password: string;
@Column()
status: boolean;
@CreateDateColumn()
created_at:date;
@UpdateDateColumn()
updated_at:date;
@DeleteDateColumn()
deleted_at:date;
}
装饰器说明
Entity
实体声明,程序运行时,自动创建的数据库表,@Entity({ name: 'users' })
,name
则是给该表命名,否则自动命名PrimaryColumn
设置主键,没有自增PrimaryGeneratedColumn
设置主键和自增,一般是id
Column
设置数据库列字段,在下面说明CreateDateColumn
创建时间,自动填写UpdateDateColumn
更新时间,自动填写DeleteDateColumn
删除时间,自动填写
列字段参数
// 写法:
@Column("int")
@Column("varchar", { length: 200 })
@Column({ type: "int", length: 200 }) // 一般采用这种
// 常用选项参数:
@Column({
type: 'varchar', // 列的数据类型,参考mysql
name: 'password', // 数据库表中的列名,string,如果和装饰的字段是一样的可以不指定
length: 30, // 列类型的长度,number
nullable: false, // 是否允许为空,boolean,默认值是false
select:false, // 查询数据库时是否显示该字段,boolean,默认值是true,密码一般使用false
comment: '密码' // 数据库注释,stirng
})
password:string;
@Column({
type:'varchar',
unique: true, // 将列标记为唯一列,唯一约束,比如账号不能有相同的
})
username:string;
@Column({
type:'tinyint',
default: () => 1, // 默认值,创建时自动填写的值
comment: '0:禁用,1:可用'
})
status:number;
@Column({
type: 'enum',
enum: ['male', 'female'], // 枚举类型,只能是数组中的值
default: 'male' 默认值
})
gender:string;
完整例子
import {
Column,
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({ name: 'users' })
export class UsersEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({
type: 'varchar',
length: 30,
nullable: false,
unique: true,
})
username: string;
@Column({
type: 'varchar',
name: 'password',
length: 100,
nullable: false,
select: false,
comment: '密码',
})
password: string;
@Column({
type: 'varchar',
length: 11,
select: false,
nullable: true,
comment: '手机号码',
})
mobile: string;
@Column({
type: 'varchar',
length: 50,
select: false,
nullable: true,
comment: '邮箱',
})
email: string;
@Column({
type: 'enum',
enum: ['male', 'female'],
default: 'male',
})
gender: string;
@Column({
type: 'tinyint',
default: () => 1,
comment: '0:禁用,1:可用',
})
status: number;
@CreateDateColumn({
type: 'timestamp',
nullable: false,
name: 'created_at',
comment: '创建时间',
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp',
nullable: false,
name: 'updated_at',
comment: '更新时间',
})
updatedAt: Date;
@DeleteDateColumn({
type: 'timestamp',
nullable: true,
name: 'deleted_at',
comment: '删除时间',
})
deletedAt: Date;
}
抽离部分重复的字段:使用继承
baseEntity
:将id,创建时间,更新时间,删除时间抽离成BaseEntity
import {
Entity,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
} from 'typeorm';
@Entity()
export class BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn({
type: 'timestamp',
nullable: false,
name: 'created_at',
comment: '创建时间',
})
createdAt: Date;
@UpdateDateColumn({
type: 'timestamp',
nullable: false,
name: 'updated_at',
comment: '更新时间',
})
updatedAt: Date;
@DeleteDateColumn({
type: 'timestamp',
nullable: false,
name: 'deleted_at',
comment: '删除时间',
})
deletedAt: Date;
}
users
表继承自baseEntity
,就不需要写创建时间,修改时间,自增ID
等重复字段了。其他的表也可以继承自baseEntity
,减少重复代码
import { Column,Entity } from 'typeorm';
import { BaseEntity } from './user.baseEntity';
@Entity({ name: 'users' })
export class UsersEntity extends BaseEntity { // 继承
@Column({
type: 'varchar',
length: 30,
nullable: false,
unique: true,
})
username: string;
@Column({
type: 'varchar',
name: 'password',
length: 100,
nullable: false,
select: false,
comment: '密码',
})
password: string;
@Column({
type: 'varchar',
length: 11,
select: false,
nullable: true,
comment: '手机号码',
})
mobile: string;
@Column({
type: 'varchar',
length: 50,
select: false,
nullable: true,
comment: '邮箱',
})
email: string;
@Column({
type: 'enum',
enum: ['male', 'female'],
default: 'male',
})
gender: string;
@Column({
type: 'tinyint',
default: () => 1,
comment: '0:禁用,1:可用',
})
status: number;
}
实体监听装饰器
- 其实是typeorm在操作数据库时的生命周期,可以更方便的操作数据
- 查找后:
@AfterLoad
- 插入前:
@BeforeInsert
- 插入后:
@AfterInsert
- 更新前:
@BeforeUpdate
- 更新后:
@AfterUpdate
- 删除前:
@BeforeRemove
AfterLoad
例子:其他的装饰器是一样的用法
import {
Column,
Entity,
AfterLoad,
} from 'typeorm';
@Entity({ name: 'users' })
export class UsersEntity extends BaseEntity {
// 查找后,如果age小于20,让age = 20
@AfterLoad() // 装饰器固定写
load() { // 函数名字随你定义
console.log('this', this);
if (this.age < 20) {
this.age = 20;
}
}
@Column()
username: string;
@Column()
password: string;
@Column({
type: 'tinyint',
default: () => 18,
})
age: number;
}
// 使用生命周期前是18,查找后就变成了20
{
"status": 200,
"message": "请求成功",
"data": {
"id": 1,
"username": "admin",
"age": 20,
}
}
typeorm增删改查操作
访问数据库的方式有哪些?
typeorm增删改查操作的方式有哪些?
多种访问数据库的方式
第一种:Connection
import { Injectable } from '@nestjs/common';
import { Connection } from 'typeorm';
import { UsersEntity } from './entities/user.entity';
@Injectable()
export class UserService {
constructor(
private readonly connection: Connection,
) { }
async test() {
// 使用封装好方法:
return await this.connection
.getRepository(UsersEntity)
.findOne({ where: { id: 1 } });
// 使用createQueryBuilder:
return await this.connection
.createQueryBuilder()
.select('user')
.from(UsersEntity, 'user')
.where('user.id = :id', { id: 1 })
.getOne();
}
}
第二种:Repository
,需要@nestjs/typeorm
的InjectRepository
来注入实体
import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { UsersEntity } from './entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity) 注入实体
private readonly usersRepository: Repository<UsersEntity>,
) { }
async test() {
// 使用封装好方法:
return await this.usersRepository.find({ where: { id: 1 } });
// 使用createQueryBuilder:
return await this.usersRepository
.createQueryBuilder('user')
.where('id = :id', { id: 1 })
.getOne();
}
}
第三种:getConnection()
:语法糖,是Connection
类型
import { Injectable } from '@nestjs/common';
import { getConnection } from 'typeorm';
import { UsersEntity } from './entities/user.entity';
@Injectable()
export class UserService {
async test() {
// 使用封装好方法:
return await getConnection()
.getRepository(UsersEntity)
.find({ where: { id: 1 } });
// 使用createQueryBuilder:
return await getConnection()
.createQueryBuilder()
.select('user')
.from(UsersEntity, 'user')
.where('user.id = :id', { id: 1 })
.getOne();
}
}
第四种:getRepository
:语法糖
import { Injectable } from '@nestjs/common';
import { getRepository } from 'typeorm';
import { UsersEntity } from './entities/user.entity';
@Injectable()
export class UserService {
async test() {
// 使用封装好方法:
return await getRepository(UsersEntity).find({ where: { id: 1 } });
// 使用createQueryBuilder:
return await getRepository(UsersEntity)
.createQueryBuilder('user')
.where('user.id = :id', { id: 1 })
.getOne();
}
}
第五种:getManager
import { Injectable } from '@nestjs/common';
import { getManager } from 'typeorm';
import { UsersEntity } from './entities/user.entity';
@Injectable()
export class UserService {
async test() {
// 使用封装好方法:
return await getManager().find(UsersEntity, { where: { id: 1 } });
// 使用createQueryBuilder:
return await getManager()
.createQueryBuilder(UsersEntity, 'user')
.where('user.id = :id', { id: 1 })
.getOne();
}
}
简单总结
使用的方式太多,建议使用:2,4
,比较方便
Connection核心类:
connection
等于getConnection
connection.manager
等于getManager
, 等于getConnection.manager
connection.getRepository
等于getRepository
, 等于getManager.getRepository
connection.createQueryBuilder
使用QueryBuilder
connection.createQueryRunner
开启事务
EntityManager
和Repository
都封装了操作数据的方法,注意:两者的使用方式是不一样的,(实在不明白搞这么多方法做什么,学得头大)
getManager
是EntityManager
的类型,getRepository
是Repository
的类型- 都可以使用
createQueryBuilder
,但使用的方式略有不同
增删改查的三种方式
- 第一种:使用sql语句,适用于sql语句熟练的同学
- 第二种:
typeorm
封装好的方法,增删改 + 简单查询 - 第三种:
QueryBuilder
查询生成器,适用于关系查询,多表查询,复杂查询 - 其实底层最终都会生成
sql
语句,只是封装了几种方式而已,方便人们使用。
第一种:sql语句
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
) { }
async findAll() {
return await this.usersRepository.query('select * from users'); // 在query中填写sql语句
}
}
第二种:typeorm封装好的api方法
这里使用第二种访问数据库的方式
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
) { }
async findAll() {
return await this.usersRepository.findAndCount(); // 封装好的方法
}
}
api方法
增
save(user) 创建:返回该数据的所有字段
insert(user) 快速插入一条数据,插入成功:返回插入实体,与save方法不同的是,它不执行级联、关系和其他操作。
删
remove(user) 删除:返回该数据的可见字段
softRemove(user); 拉黑:返回该数据的可见字段,该删除实体必须拥有@DeleteDateColumn()字段,被拉黑的用户还存在数据库中,但无法被find查找到,会在@DeleteDateColumn()字段中添加删除时间,可使用recover恢复
改
update(id, user) 更新:返回更新实体,不是该数据的字段
恢复
recover({ id }) 恢复:返回id,将被softRemove删除(拉黑)的用户恢复,恢复成功后可以被find查找到
查找全部
find()
find({id:9}) 条件查找,写法一,找不到返回空对象
find({where:{id:10}}) 条件查找,写法二,找不到返回空对象
findAndCount() 返回数据和总的条数
查找一个
findOne(id); 根据ID查找,找不到返回undefined
findOne({ where: { username } }); 条件查找,找不到返回undefined
根据ID查找一个或多个
findByIds([1,2,3]); 查找n个,全部查找不到返回空数组,找到就返回找到的
其他
hasId(new UsersEntity()) 检测实体是否有合成ID,返回布尔值
getId(new UsersEntity()) 获取实体的合成ID,获取不到返回undefined
create({username: 'admin12345', password: '123456',}) 创建一个实体,需要调用save保存
count({ status: 1 }) 计数,返回数量,无返回0
increment({ id }, 'age', 2); 增加,给条件为id的数据的age字段增加2,成功返回改变实体
decrement({ id }, 'age', 2) 减少,给条件为id的数据的age字段增加2,成功返回改变实体
谨用
findOneOrFail(id) 找不到直接报500错误,无法使用过滤器拦截错误,不要使用
clear() 清空该数据表,谨用!!
find更多参数
this.userRepository.find({
select: ["firstName", "lastName"], 要的字段
relations: ["photos", "videos"], 关系查询
where: { 条件查询
firstName: "Timber",
lastName: "Saw"
},
where: [{ username: "li" }, { username: "joy" }], 多个条件or, 等于:where username = 'li' or username = 'joy'
order: { 排序
name: "ASC",
id: "DESC"
},
skip: 5, 偏移量
take: 10, 每页条数
cache: 60000 启用缓存:1分钟
});
find进阶选项
TypeORM 提供了许多内置运算符,可用于创建更复杂的查询
import { Not, Between, In } from "typeorm";
return await this.usersRepository.find({
username: Not('admin'),
});
将执行以下查询:
SELECT * FROM "users" WHERE "username" != 'admin'
return await this.usersRepository.find({
likes: Between(1, 10)
});
SELECT * FROM "users" WHERE "likes" BETWEEN 1 AND 10
return await this.usersRepository.find({
username: In(['admin', 'admin2']),
});
SELECT * FROM "users" WHERE "title" IN ('admin', 'admin2')
第三种:QueryBuilder
查询生成器
使用链式操作
QueryBuilder增,删,改
// 增加
return await this.usersRepository
.createQueryBuilder()
.insert() 声明插入操作
.into(UsersEntity) 插入的实体
.values([ 插入的值,可插入多个
{ username: 'Timber', password: '123456' },
{ username: 'Timber2', password: '123456' },
])
.execute(); 执行
// 修改
return this.usersRepository
.createQueryBuilder()
.update(UsersEntity)
.set({ username: 'admin22' })
.where('id = :id', { id: 2 })
.execute();
// 删除
return this.usersRepository
.createQueryBuilder()
.delete()
.from(UsersEntity)
.where('id = :id', { id: 8 })
.execute();
// 处理异常:请求成功会返回一个对象, 如果raw.affectedRows != 0 就是成功
"raw": {
"fieldCount": 0,
"affectedRows": 2,
"insertId": 13,
"serverStatus": 2,
"warningCount": 0,
"message": "&Records: 2 Duplicates: 0 Warnings: 0",
"protocol41": true,
"changedRows": 0
}
查询
简单例子
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
) { }
async findAll() {
return await this.usersRepository
.createQueryBuilder('user') 创建生成器,参数:别名
.where('user.id = :id', { id: id }) 条件
.innerJoinAndSelect('user.avatar', 'avatar') 关系查询
.addSelect('user.password') 添加显示字段
.getOne(); 获取一条数据
}
}
QueryBuilder查询生成器说明
查询单表
访问数据库的方式不同:
方式一:没有指定实体,需要使用from指定实体
return await getConnection()
.createQueryBuilder()
.select('user.username') ‘user’:全部字段,‘user.username’:只获取username
.from(UsersEntity, 'user') 参1:连接的实体, 参2:别名
.where('user.id = :id', { id: 1 })
.getOne();
方式二:指定实体:默认获取全部字段
return await getConnection()
.createQueryBuilder(UsersEntity, 'user') 指定实体
.where('user.id = :id', { id: 1 })
.getOne();
方式三: 已经在访问时指定了实体:默认获取全部字段
return await this.usersRepository
.createQueryBuilder('user') 别名
.where('user.id = :id', { id: 1 })
.getOne();
获取结果
.getSql(); 获取实际执行的sql语句,用于开发时检查问题
.getOne(); 获取一条数据(经过typeorm的字段处理)
.getMany(); 获取多条数据
.getRawOne(); 获取一条原数据(没有经过typeorm的字段处理)
.getRawMany(); 获取多条原数据
.stream(); 返回流数据
如:经过typeorm的字段处理,获取到的就是实体设计时的字段
{
"status": 200,
"message": "请求成功",
"data": {
"id": 1,
"username": "admin",
"gender": "male",
"age": 18,
"status": 1,
"createdAt": "2021-04-26T09:58:54.469Z",
"updatedAt": "2021-04-28T14:47:36.000Z",
"deletedAt": null
}
}
如:没有经过typeorm的字段处理,将数据库的字段原生不动的显示出来
{
"status": 200,
"message": "请求成功",
"data": {
"user_id": 1,
"user_username": "admin",
"user_gender": "male",
"user_age": 18,
"user_status": 1,
"user_created_at": "2021-04-26T09:58:54.469Z",
"user_updated_at": "2021-04-28T14:47:36.000Z",
"user_deleted_at": null
}
}
查询部分字段
.select(["user.id", "user.name"])
实际执行的sql语句:SELECT user.id, user.name FROM users user;
添加隐藏字段:实体中设置select为false时,是不显示字段,使用addSelect会将字段显示出来
.addSelect('user.password')
where
条件
.where("user.name = :name", { name: "joy" })
等于
.where("user.name = :name")
.setParameter("name", "Timber")
实际执行的sql语句:SELECT * FROM users user WHERE user.name = 'joy'
多个条件
.where("user.firstName = :firstName", { firstName: "Timber" })
.andWhere("user.lastName = :lastName", { lastName: "Saw" });
实际执行的sql语句:SELECT * FROM users user WHERE user.firstName = 'Timber' AND user.lastName = 'Saw'
in
.where("user.name IN (:...names)", { names: [ "Timber", "Cristal", "Lina" ] })
实际执行的sql语句:SELECT * FROM users user WHERE user.name IN ('Timber', 'Cristal', 'Lina')
or
.where("user.firstName = :firstName", { firstName: "Timber" })
.orWhere("user.lastName = :lastName", { lastName: "Saw" });
实际执行的sql语句:SELECT * FROM users user WHERE user.firstName = 'Timber' OR user.lastName = 'Saw'
子句
const posts = await connection
.getRepository(Post)
.createQueryBuilder("post")
.where(qb => {
const subQuery = qb
.subQuery()
.select("user.name")
.from(User, "user")
.where("user.registered = :registered")
.getQuery();
return "post.title IN " + subQuery;
})
.setParameter("registered", true)
.getMany();
实际执行的sql语句:select * from post where post.title in (select name from user where registered = true)
having
筛选
.having("user.firstName = :firstName", { firstName: "Timber" })
.andHaving("user.lastName = :lastName", { lastName: "Saw" });
实际执行的sql语句:SELECT ... FROM users user HAVING user.firstName = 'Timber' AND user.lastName = 'Saw'
orderBy
排序
.orderBy("user.name", "DESC")
.addOrderBy("user.id", "asc");
等于
.orderBy({
"user.name": "ASC",
"user.id": "DESC"
});
实际执行的sql语句:SELECT * FROM users user order by user.name asc, user.id desc;
group
分组
.groupBy("user.name")
.addGroupBy("user.id");
关系查询(多表)
1参:你要加载的关系,2参:可选,你为此表分配的别名,3参:可选,查询条件
左关联查询
.leftJoinAndSelect("user.profile", "profile")
右关联查询
.rightJoinAndSelect("user.profile", "profile")
内联查询
.innerJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", { isRemoved: false })
例子:
const result = await this.usersRepository
.createQueryBuilder('user')
.leftJoinAndSelect("user.photos", "photo")
.where("user.name = :name", { name: "joy" })
.andWhere("photo.isRemoved = :isRemoved", { isRemoved: false })
.getOne();
实际执行的sql语句:
SELECT user.*, photo.*
FROM users user
LEFT JOIN photos photo ON photo.user = user.id
WHERE user.name = 'joy' AND photo.isRemoved = FALSE;
const result = await this.usersRepository
.innerJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", { isRemoved: false })
.where("user.name = :name", { name: "Timber" })
.getOne();
实际执行的sql语句:
SELECT user.*, photo.* FROM users user
INNER JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber';
多个关联
const result = await this.usersRepository
.createQueryBuilder("user")
.leftJoinAndSelect("user.profile", "profile")
.leftJoinAndSelect("user.photos", "photo")
.leftJoinAndSelect("user.videos", "video")
.getOne();
typeorm使用事务的3种方式
typeorm
使用事务的方式有哪些?如何使用?
事务
- 在操作多个表时,或者多个操作时,如果有一个操作失败,所有的操作都失败,要么全部成功,要么全部失
- 解决问题:在多表操作时,因为各种异常导致一个成功,一个失败的数据错误。
例子:银行转账
如果用户1向用户2转了100元,但因为各种原因,用户2没有收到,如果没有事务处理,用户1扣除的100元就凭空消失了
如果有事务处理,只有用户2收到100元,用户1才会扣除100元,如果没有收到,则不会扣除。
应用场景
多表的增,删,改操作
nest-typrorm事务的使用方式
- 使用装饰器,在
controller
中编写,传递给service
使用 - 使用
getManager
或getConnection
,在service
中编写与使用 - 使用
connection
或getConnection
,开启queryRunner
,在service
中编写与使用
方式一:使用装饰器
controller
import {
Controller,
Post,
Body,
Param,
ParseIntPipe,
} from '@nestjs/common';
import { Transaction, TransactionManager, EntityManager } from 'typeorm'; 开启事务第一步:引入
import { UserService } from './user.service-oto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) { }
@Post(':id')
@Transaction() 开启事务第二步:装饰接口
async create(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager, 开启事务第三步:获取事务管理器
) {
return await this.userService.create(id, maneger); 开启事务第四步:传递给service,使用数据库时调用
}
}
service
- 这里处理的是1对1关系:保存头像地址到
avatar
表,同时关联保存用户的id
- 如果你不会1对1关系,请先去学习对应的知识
import { Injectable } from '@nestjs/common';
import { Repository, EntityManager } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersEntity } from './entities/user.entity';
import { AvatarEntity } from './entities/avatar.entity';
import { ToolsService } from '../../utils/tools.service';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
@InjectRepository(AvatarEntity)
private readonly avatarRepository: Repository<AvatarEntity>,
) { }
async create(id: number, manager: EntityManager) {
const urlObj = {
url: `http://www.dmyxs.com/images/${id}.png`,
};
const user = await this.usersRepository.findOne({ id }); 先查找用户,因为要保存用户的id
if (!user) ToolsService.fail('用户id不存在'); 找不到用户抛出异常
const avatarEntity = this.avatarRepository.create({ url: urlObj.url }); 创建头像地址的实体
const avatarUrl = await manager.save(avatarEntity); 使用事务保存副表
user.avatar = avatarUrl; 主表和副表建立关系
await manager.save(user); 使用事务保存主表
return '新增成功'; 如果过程出错,不会保存
}
}
方式二:使用getManager 或 getConnection
service
import { Injectable } from '@nestjs/common';
import { Connection, Repository, getManager } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersEntity } from './entities/user.entity';
import { AvatarEntity } from './entities/avatar.entity';
import { ToolsService } from '../../utils/tools.service';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
private readonly connection: Connection,
) { }
async test(id: string) {
const urlObj = {
url: `http://www.dmyxs.com/images/${id}.png`,
};
const user = await this.usersRepository.findOne(id); 先查找用户
if (!user) ToolsService.fail('用户id不存在'); 找不到用户抛出异常
//getConnection的方式:await getConnection().transaction(manager=> {});
//getManager的方式:
const result = await getManager().transaction(async (manager) => {
const avatarEntity = manager.create(AvatarEntity, { url: urlObj.url }); 创建头像地址的实体
const avatarUrl = await manager.save(AvatarEntity, avatarEntity); 使用事务保存副表
user.avatar = avatarUrl; 创建关联
return await manager.save(UsersEntity, user); 使用事务保存主表,并返回结果
});
return result;
}
}
{
"status": 200,
"message": "请求成功",
"data": {
"id": 1,
"createdAt": "2021-04-26T09:58:54.469Z",
"updatedAt": "2021-04-28T14:47:36.000Z",
"deletedAt": null,
"username": "admin",
"gender": "male",
"age": 18,
"status": 1,
"avatar": {
"url": "http://www.dmyxs.com/images/1.png",
"id": 52
}
}
}
方式三:使用 connection 或 getConnection
service
import { Injectable } from '@nestjs/common';
import { Connection, Repository, getManager } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersEntity } from './entities/user.entity';
import { AvatarEntity } from './entities/avatar.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
private readonly connection: Connection,
) { }
async test(id: string) {
const urlObj = {
url: `http://www.test.com/images/${id}.png`,
};
const user = await this.usersRepository.findOne(id); 先查找用户
if (!user) ToolsService.fail('用户id不存在'); 找不到用户抛出异常
const queryRunner = this.connection.createQueryRunner(); 获取连接并创建新的queryRunner
await queryRunner.connect(); 使用我们的新queryRunner建立真正的数据库连
await queryRunner.startTransaction(); 开始事务
const avatarEntity = new AvatarEntity(); 创建实体:要保存的数据
avatarEntity.url = urlObj.url;
try {
const result = await queryRunner.manager 使用事务保存到副表
.getRepository(AvatarEntity)
.save(avatarEntity);
user.avatar = result; 主表和副表建立连接
const userResult = await queryRunner.manager 使用事务保存到副表
.getRepository(UsersEntity)
.save(user);
await queryRunner.commitTransaction(); 提交事务
return userResult; 返回结果
} catch (error) {
console.log('创建失败,取消事务');
await queryRunner.rollbackTransaction(); 出错回滚
} finally {
await queryRunner.release(); 释放
}
}
}
typeorm 一对一关系设计与增删改查
实体如何设计一对一关系?如何增删改查?
一对一关系
- 定义:一对一是一种 A 只包含一个 B ,而 B 只包含一个 A 的关系
- 其实就是要设计两个表:一张是主表,一张是副表,查找主表时,关联查找副表
- 有外键的表称之为副表,不带外键的表称之为主表
- 如:一个账户对应一个用户信息,主表是账户,副表是用户信息
- 如:一个用户对应一张用户头像图片,主表是用户信息,副表是头像地址
一对一实体设计
主表:
- 使用
@OneToOne()
来建立关系- 第一个参数:
() => AvatarEntity
, 和谁建立关系? 和AvatarEntity
建立关系- 第二个参数:
(avatar) => avatar.user)
,和哪个字段联立关系?avatar
就是AvatarEntity
的别名,可随便写,和AvatarEntity
的userinfo
字段建立关系- 第三个参数:
RelationOptions
关系选项
import {
Column,
Entity,
PrimaryGeneratedColumn,
OneToOne,
} from 'typeorm';
import { AvatarEntity } from './avatar.entity';
@Entity({ name: 'users' })
export class UsersEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@OneToOne(() => AvatarEntity, (avatar) => avatar.userinfo)
avatar: AvatarEntity;
}
副表
参数:同主表一样
主要:根据@JoinColumn({ name: ‘user_id’ })
来分辨副表,name
是设置数据库的外键名字,如果不设置是userId
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from 'typeorm';
import { UsersEntity } from './user.entity';
@Entity({ name: 'avatar' })
export class AvatarEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar' })
url: string;
@OneToOne(() => UsersEntity, (user) => user.avatar)
@JoinColumn({ name: 'userinfo_id' })
userinfo: UsersEntity;
}
一对一增删改查
- 注意:只要涉及两种表操作的,就需要开启事务:同时失败或同时成功,避免数据不统一
- 在这里:创建,修改,删除都开启了事务
- 注意:所有数据应该是由前端传递过来的,这里为了方便,直接硬编码了(写死)
// user.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Query,
Param,
Delete,
HttpCode,
HttpStatus,
ParseIntPipe,
} from '@nestjs/common';
import { Transaction, TransactionManager, EntityManager } from 'typeorm'; 开启事务第一步:引入
import { UserService } from './user.service-oto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) { }
@Get()
async findAll() {
const [data, count] = await this.userService.findAll();
return { count, data };
}
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id: number) {
return await this.userService.findOne(id);
}
@Post(':id')
@HttpCode(HttpStatus.OK)
@Transaction() 开启事务第二步:装饰接口
async create(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager, 开启事务第三步:获取事务管理器
) {
return await this.userService.create(id, maneger); 开启事务第四步:传递给service,使用数据库时调用
}
@Patch(':id')
@Transaction()
async update(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager,
) {
return await this.userService.update(id, maneger);
}
@Delete(':id')
@Transaction()
async remove(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager,
) {
return await this.userService.remove(id, maneger);
}
}
// user.service.ts
import { Injectable } from '@nestjs/common';
import { Repository, Connection, UpdateResult, EntityManager } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersEntity } from './entities/user.entity';
import { AvatarEntity } from './entities/avatar.entity';
import { ToolsService } from '../../utils/tools.service';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
@InjectRepository(AvatarEntity)
private readonly avatarRepository: Repository<AvatarEntity>,
private connection: Connection,
) { }
一对一增删改查
查找全部
async findAll() {
使用封装好的方式
// return await this.usersRepository.findAndCount({ relations: ['avatar'] });
使用QueryBuilder的方式
const list = await this.usersRepository
.createQueryBuilder('UsersEntity')
.leftJoinAndSelect('UsersEntity.avatar', 'AvatarEntity.userinfo')
.getManyAndCount();
return list;
}
根据主表id查找一对一
async findOne(id: number) {
const result = await this.usersRepository.findOne(id, {
relations: ['avatar'],
});
if (!result) ToolsService.fail('用户id不存在');
return result;
}
根据主表id创建一对一
async create(id: number, manager: EntityManager) {
const urlObj = {
url: `http://www.dmyxs.com/images/${id}.png`,
};
const user = await this.usersRepository.findOne({ id }); 先查找用户
if (!user) ToolsService.fail('用户id不存在'); 如果没找到,抛出错误,由过滤器捕获错误
创建实体的两种方式:new 和 create,new的方式方便条件判断
创建实体方式一:
const avatarEntity = this.avatarRepository.create({ url: urlObj.url }); 创建实体
创建实体方式二:
//const avatarEntity = new AvatarEntity();
//avatarEntity.url = urlObj.url;
const avatarUrl = await manager.save(avatarEntity); 使用事务保存副表
user.avatar = avatarUrl; 主表和副表建立关系
await manager.save(user); 使用事务保存主表
return '新增成功'; 如果过程出错,不会保存
}
根据主表id更改一对一
要更改的副表id,会从前端传递过来
async update(id: number, manager: EntityManager) {
const urlObj = {
id: 18,
url: `http://www.dmyxs.com/images/${id}-update.jpg`,
};
const user = await this.usersRepository.findOne( { id } ); 先查找用户
if (!user) ToolsService.fail('用户id不存在'); 如果没找到id抛出错误,由过滤器捕获错误
const avatarEntity = this.avatarRepository.create({ url: urlObj.url }); 创建要修改的实体
使用事务更新方法:1参:要修改的表,2参:要修改的id, 3参:要更新的数据
await manager.update(AvatarEntity, urlObj.id, avatarEntity);
return '更新成功';
}
根据主表id删除一对一
async remove(id: number, manager: EntityManager): Promise<any> {
const user = await this.usersRepository.findOne(id);
if (!user) ToolsService.fail('用户id不存在');
只删副表的关联数据
await manager.delete(AvatarEntity, { user: id });
如果连主表用户一起删,加下面这行代码
//await manager.delete(UsersEntity, id);
return '删除成功';
}
}
typeorm 一对多和多对一关系设计与增删改查
实体如何设计一对多与多对一关系,如何关联查询
一对多关系,多对一关系
定义:一对多是一种一个 A 包含多个 B ,而多个B只属于一个 A 的关系
其实就是要设计两个表:一张是主表(一对多),一张是副表(多对一),查找主表时,关联查找副表
有外键的表称之为副表,不带外键的表称之为主表
如:一个用户拥有多个宠物,多个宠物只属于一个用户的(每个宠物只能有一个主人)
如:一个用户拥有多张照片,多张照片只属于一个用户的
如:一个角色拥有多个用户,多个用户只属于一个角色的(每个用户只能有一个角色)
一对多和多对一实体设计
一对多
使用
@OneToMany()
来建立一对多关系
第一个参数:() => PhotoEntity
, 和谁建立关系? 和PhotoEntity
建立关系
第二个参数:(user) => user.photo
,和哪个字段联立关系?user
就是PhotoEntity
的别名,可随便写,和PhotoEntity
的userinfo
字段建立关系
第三个参数:RelationOptions
关系选项
import {
Column,
Entity,
PrimaryGeneratedColumn,
OneToOne,
} from 'typeorm';
import { AvatarEntity } from './avatar.entity';
@Entity({ name: 'users' })
export class UsersEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@OneToMany(() => PhotoEntity, (avatar) => avatar.userinfo)
photos: PhotoEntity;
}
多对一
使用
@ManyToOne()
来建立多对一关系,参数如同上
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { UsersEntity } from './user.entity';
@Entity({ name: 'photo' })
export class PhotoEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar' })
url: string;
@ManyToOne(() => UsersEntity, (user) => user.photos)
@JoinColumn({ name: 'userinfo_id' })
userinfo: UsersEntity;
}
一对多和多对一增删改查
只要涉及两种表操作的,就需要开启事务:同时失败或同时成功,避免数据不统一
注意:所有数据应该是由前端传递过来的,这里为了方便,直接硬编码了(写死)
比较复杂的是更新操作
user.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Query,
Param,
Delete,
HttpCode,
HttpStatus,
ParseIntPipe,
} from '@nestjs/common';
import { Transaction, TransactionManager, EntityManager } from 'typeorm'; 开启事务第一步:引入
import { UserService } from './user.service-oto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) { }
@Get()
async findAll() {
const [data, count] = await this.userService.findAll();
return { count, data };
}
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id: number) {
return await this.userService.findOne(id);
}
@Post(':id')
@HttpCode(HttpStatus.OK)
@Transaction() 开启事务第二步:装饰接口
async create(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager, 开启事务第三步:获取事务管理器
) {
return await this.userService.create(id, maneger); 开启事务第四步:传递给service,使用数据库时调用
}
@Patch(':id')
@Transaction()
async update(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager,
) {
return await this.userService.update(id, maneger);
}
@Delete(':id')
@Transaction()
async remove(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager,
) {
return await this.userService.remove(id, maneger);
}
}
user.service.ts
令人头大的地方:建立关系和查找使用实体,删除使用实体的id,感觉设计得不是很合理,违背人的常识
import { Injectable } from '@nestjs/common';
import { Repository, EntityManager } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersEntity } from './entities/user.entity';
import { PhotoEntity } from './entities/photo.entity';
import { ToolsService } from '../../utils/tools.service';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
@InjectRepository(PhotoEntity)
private readonly photoRepository: Repository<PhotoEntity>,
) { }
一对多增删改查
async findAll() {
// return await this.usersRepository.findAndCount({ relations: ['photos'] });
const list = await this.usersRepository
.createQueryBuilder('UsersEntity')
.leftJoinAndSelect('UsersEntity.photos', 'PhotoEntity.userinfo')
.getManyAndCount();
return list;
}
根据主表id查找一对多
async findOne(id: number) {
查询一个用户有多少张照片(一对多)
const result = await this.usersRepository.findOne(id, {
relations: ['photos'],
});
if (!result) ToolsService.fail('用户id不存在');
return result;
查询这张照片属于谁(多对一)
// const result = await this.photoRepository.findOne(id, {
// relations: ['userinfo'],
// });
// if (!result) ToolsService.fail('图片id不存在');
// return result;
}
根据主表id创建一对多
async create(id: number, manager: EntityManager) {
const urlList = [
{
url: `http://www.dmyxs.com/images/${id}.png`,
},
{
url: `http://www.dmyxs.com/images/${id}.jpg`,
},
];
const user = await this.usersRepository.findOne({ id });
if (!user) ToolsService.fail('用户id不存在');
遍历传递过来的数据
if (urlList.length !== 0) {
for (let i = 0; i < urlList.length; i++) {
创建实体的两种方式:new 和 create,new的方式方便条件判断
// const photo = new PhotoEntity();
// photo.url = urlList[i].url;
// photo.user = user;
// await manager.save(PhotoEntity, photo);
const photoEntity = this.photoRepository.create({
url: urlList[i].url,
userinfo: user, 注意:这里是使用实体建立关系,而不是实体id
});
await manager.save(photoEntity);
}
}
return '新增成功';
}
根据主表id更改一对多
示例:删除一张,修改一张(修改的有id),新增一张
先使用创建,创建两张photo
async update(id: number, manager: EntityManager) {
const urlList = [
{
id: 22,
url: `http://www.dmyxs.com/images/${id}-update.png`,
},
{
url: `http://www.dmyxs.com/images/${id}-create.jpeg`,
},
];
const user = await this.usersRepository.findOne({ id });
if (!user) ToolsService.fail('用户id不存在');
如果要修改主表,先修改主表用户信息,后修改副表图片信息
修改主表
const userEntity = this.usersRepository.create({
id,
username: 'admin7',
password: '123456',
});
await manager.save(userEntity);
修改副表
如果前端附带了图片list
if (urlList.length !== 0) {
查询数据库已经有的图片
const databasePhotos = await manager.find(PhotoEntity, { userinfo: user });
如果有数据,则进行循环判断,先删除多余的数据
if (databasePhotos.length >= 1) {
for (let i = 0; i < databasePhotos.length; i++) {
以用户传递的图片为基准,数据库的图片id是否在用户传递过来的表里,如果不在,就是要删除的数据
const exist = urlList.find((item) => item.id === databasePhotos[i].id);
if (!exist) {
await manager.delete(PhotoEntity, { id: databasePhotos[i].id });
}
}
}
否则就是新增和更改的数据
for (let i = 0; i < urlList.length; i++) {
const photoEntity = new PhotoEntity();
photoEntity.url = urlList[i].url;
如果有id则是修改操作,因为前端传递的数据是从服务端获取的,会附带id,新增的没有
if (!!urlList[i].id) {
修改则让id关联即可
photoEntity.id = urlList[i].id;
await manager.save(PhotoEntity, photoEntity);
} else {
否则是新增操作,关联用户实体
photoEntity.userinfo = user;
await manager.save(PhotoEntity, photoEntity);
}
}
} else {
如果前端把图片全部删除,删除所有关联的图片
await manager.delete(PhotoEntity, { userinfo: id });
}
return '更新成功';
}
根据主表id删除一对多
async remove(id: number, manager: EntityManager): Promise<any> {
const user = await this.usersRepository.findOne(id);
if (!user) ToolsService.fail('用户id不存在');
只删副表的关联数据
await manager.delete(PhotoEntity, { userinfo: id });
如果连主表用户一起删,加下面这行代码
//await manager.delete(UsersEntity, id);
return '删除成功';
}
}
typeorm 多对多关系设计与增删改查
实体如何设计多对多关系?如何增删改查?
多对多关系
定义:多对多是一种 A 包含多个 B,而 B 包含多个 A 的关系
如:一个粉丝可以关注多个主播,一个主播可以有多个粉丝
如:一篇文章属于多个分类,一个分类下有多篇文章
比如这篇文章,可以放在nest目录,也可以放在typeorm目录或者mysql目录
实现方式
第一种:建立两张表,使用装饰器
@ManyToMany
建立关系,typeorm
会自动生成三张表
第二种:手动建立3张表
这里使用第一种
实体设计
这里将设计一个用户(粉丝) 与 明星的 多对多关系
用户(粉丝)可以主动关注明星,让users
变为主表,加入@JoinTable()
使用
@ManyToMany()
来建立多对多关系
第一个参数:() => StarEntity
, 和谁建立关系? 和StarEntity
建立关系
第二个参数:(star) => star.photo
,和哪个字段联立关系?star
就是StarEntity
的别名,可随便写,和PhotoEntity
的followers
字段建立关系
用户(粉丝)表:follows关注/跟随
import {
Column,
Entity,
PrimaryGeneratedColumn,
ManyToMany,
JoinTable,
} from 'typeorm';
import { AvatarEntity } from './avatar.entity';
@Entity({ name: 'users' })
export class UsersEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@ManyToMany(() => StarEntity, (star) => star.followers)
@JoinTable()
follows: StarEntity[]; 注意这里是数组类型
}
明星表:followers跟随者
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm';
import { UsersEntity } from './user.entity';
@Entity({ name: 'star' })
export class StarEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar' })
name: string;
@ManyToMany(() => UsersEntity, (user) => user.follows)
followers: UsersEntity;
}
注意:
程序运行后,将会默认在数据库中生成三张表,users,star,users_follows_star,users_follows_star是中间表,用于记录users和star之间的多对多关系,它是自动生成的。
为了测试方便,你可以在users表和star表创建一些数据:这些属于单表操作
多对多增删改查
只要涉及两种表操作的,就需要开启事务:同时失败或同时成功,避免数据不统一
注意:所有数据应该是由前端传递过来的,这里为了方便,直接硬编码了(写死)
user.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Query,
Param,
Delete,
HttpCode,
HttpStatus,
ParseIntPipe,
} from '@nestjs/common';
import { Transaction, TransactionManager, EntityManager } from 'typeorm'; 开启事务第一步:引入
import { UserService } from './user.service-oto';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) { }
@Get()
async findAll() {
const [data, count] = await this.userService.findAll();
return { count, data };
}
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id: number) {
return await this.userService.findOne(id);
}
@Post(':id')
@HttpCode(HttpStatus.OK)
@Transaction() 开启事务第二步:装饰接口
async create(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager, 开启事务第三步:获取事务管理器
) {
return await this.userService.create(id, maneger); 开启事务第四步:传递给service,使用数据库时调用
}
@Patch(':id')
@Transaction()
async update(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager,
) {
return await this.userService.update(id, maneger);
}
@Delete(':id')
@Transaction()
async remove(
@Param('id', new ParseIntPipe()) id: number,
@TransactionManager() maneger: EntityManager,
) {
return await this.userService.remove(id, maneger);
}
}
user.service.ts
import { Injectable } from '@nestjs/common';
import { Repository, EntityManager } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersEntity } from './entities/user.entity';
import { StarEntity } from './entities/star.entity';
import { ToolsService } from '../../utils/tools.service';
@Injectable()
export class UserService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
@InjectRepository(StarEntity)
private readonly starRepository: Repository<StarEntity>,
) { }
一对多增删改查
async findAll() {
// return await this.usersRepository.findAndCount({ relations: ['follows'] });
const list = await this.usersRepository
.createQueryBuilder('UsersEntity')
.leftJoinAndSelect('UsersEntity.follows', 'StarEntity.followers')
.getManyAndCount();
return list;
}
根据主表id查找多对多
async findOne(id: number) {
查询一个用户关注了哪些明星
// const result = await this.usersRepository.findOne(id, {
// relations: ['follows'],
// });
// if (!result) ToolsService.fail('用户id不存在');
// return result;
查询一个明星有多少粉丝
const result = await this.starRepository.findOne(id, {
relations: ['followers'],
});
if (!result) ToolsService.fail('明星id不存在');
return result;
}
根据主表id创建多对多
粉丝关注明星
async create(id: number, manager: EntityManager) {
要关注的明星id数组
const willFollow = [3, 4];
const user = await this.usersRepository.findOne({ id });
if (!user) ToolsService.fail('用户id不存在');
if (willFollow.length !== 0) {
const followList = [];
for (let i = 0; i < willFollow.length; i++) {
const star = await manager.findOne(StarEntity, {
id: willFollow[i],
});
if (!star) ToolsService.fail('主播id不存在');
followList.push(star);
}
const userEntity = new UsersEntity();
重点:
不指定id是创建新的用户,还需要填写username和password等必填的字段
指定id就是更新某些字段:只关注明星,不创建新的用户,同样可用于修改
userEntity.id = id;
userEntity.follows = followList; 建立关联,数据表会自动更新
await manager.save(userEntity);
}
return '新增成功';
}
根据主表id更改多对多
假设:某用户关注了id为[3, 4]的明星, 现在修改为只关注[2]
逻辑和创建一样
async update(id: number, manager: EntityManager) {
const willFollow = [2];
const user = await this.usersRepository.findOne({ id });
if (!user) ToolsService.fail('用户id不存在');
if (willFollow.length !== 0) {
const followList = [];
for (let i = 0; i < willFollow.length; i++) {
const listOne = await manager.findOne(StarEntity, {
id: willFollow[i],
});
if (!listOne) ToolsService.fail('主播id不存在');
followList.push(listOne);
}
const userEntity = new UsersEntity();
userEntity.id = id;
userEntity.follows = followList;
await manager.save(userEntity);
}
return '更新成功';
}
根据主表id删除多对多
多种删除
async remove(id: number, manager: EntityManager): Promise<any> {
const user = await this.usersRepository.findOne(id, {
relations: ['follows'],
});
if (!user) ToolsService.fail('用户id不存在');
根据id删除一个:取消关注某个明星,明星id应由前端传递过来,这里写死
需要获取当前用户的的follows,使用关系查询
const willDeleteId = 2;
if (user.follows.length !== 0) {
过滤掉要删除的数据,再重新赋值
const followList = user.follows.filter((star) => star.id != willDeleteId);
const userEntity = new UsersEntity();
userEntity.id = id;
userEntity.follows = followList;
await manager.save(userEntity);
}
全部删除关联数据,不删用户
// const userEntity = new UsersEntity();
// userEntity.id = id;
// userEntity.follows = [];
// await manager.save(userEntity);
如果连用户一起删,会将关联数据一起删除
// await manager.delete(UsersEntity, id);
return '删除成功';
}
}
nest连接Redis
Redis 字符串数据类型的相关命令用于管理 redis 字符串值
- 查看所有的key:
keys *
- 普通设置:
set key value
- 设置并加过期时间:
set key value EX 30
表示30秒后过期 - 获取数据:
get key
- 删除指定数据:
del key
- 删除全部数据:
flushall
- 查看类型:
type key
- 设置过期时间:
expire key 20
表示指定的key5
秒后过期
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
- 列表右侧增加值:
rpush key value
- 列表左侧增加值:
lpush key value
- 右侧删除值:
rpop key
- 左侧删除值:
lpop key
- 获取数据:
lrange key
- 删除指定数据:
del key
- 删除全部数据:
flushall
- 查看类型:
type key
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。它和列表的最主要区别就是没法增加重复值
- 给集合增数据:
sadd key value
- 删除集合中的一个值:
srem key value
- 获取数据:
smembers key
- 删除指定数据:
del key
- 删除全部数据:
flushall
Redis hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
- 设置值hmset :
hmset zhangsan name "张三" age 20 sex “男”
- 设置值hset :
hset zhangsan name "张三"
- 获取数据:
hgetall key
- 删除指定数据:
del key
- 删除全部数据:
flushall
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
// 发布
client.publish('publish', 'message from publish.js');
// 订阅
client.subscribe('publish');
client.on('message', function(channel, msg){
console.log('client.on message, channel:', channel, ' message:', msg);
});
Nestjs中使用redis
Nestjs Redis 官方文档:github.com/kyknow/nest…
npm install nestjs-redis --save
如果是nest8需要注意该问题:github.com/skunight/ne…
// app.modules.ts
import { RedisModule } from 'nestjs-redis';
import { RedisTestModule } from '../examples/redis-test/redis-test.module';
@Module({
imports: [
// 加载配置文件目录
ConfigModule.load(resolve(__dirname, 'config', '**/!(*.d).{ts,js}')),
// redis连接
RedisModule.forRootAsync({
useFactory: (configService: ConfigService) => configService.get('redis'),
inject: [ConfigService],
}),
RedisTestModule,
],
controllers: [],
providers: [ ],
})
export class AppModule implements NestModule {}
// src/config/redis.ts 配置
export default {
host: '127.0.0.1',
port: 6379,
db: 0,
password: '',
keyPrefix: '',
onClientReady: (client) => {
client.on('error', (err) => {
console.log('-----redis error-----', err);
});
},
};
创建一个cache.service.ts 服务 封装操作redis的方法
// src/common/cache.service.ts
import { Injectable } from '@nestjs/common';
import { RedisService } from 'nestjs-redis';
@Injectable()
export class CacheService {
public client;
constructor(private redisService: RedisService) {
this.getClient();
}
async getClient() {
this.client = await this.redisService.getClient();
}
//设置值的方法
async set(key: string, value: any, seconds?: number) {
value = JSON.stringify(value);
if (!this.client) {
await this.getClient();
}
if (!seconds) {
await this.client.set(key, value);
} else {
await this.client.set(key, value, 'EX', seconds);
}
}
//获取值的方法
async get(key: string) {
if (!this.client) {
await this.getClient();
}
const data = await this.client.get(key);
if (!data) return;
return JSON.parse(data);
}
// 根据key删除redis缓存数据
async del(key: string): Promise<any> {
if (!this.client) {
await this.getClient();
}
await this.client.del(key);
}
// 清空redis的缓存
async flushall(): Promise<any> {
if (!this.client) {
await this.getClient();
}
await this.client.flushall();
}
}
使用redis服务
redis-test.controller
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { CacheService } from 'src/common/cache/redis.service';
@Controller('redis-test')
export class RedisTestController {
// 注入redis服务
constructor(private readonly cacheService: CacheService) {}
@Get('get')
async get(@Query() query) {
return await this.cacheService.get(query.key);
}
@Post('set')
async set(@Body() body) {
const { key, ...params } = body as any;
return await this.cacheService.set(key, params);
}
@Get('del')
async del(@Query() query) {
return await this.cacheService.del(query.key);
}
@Get('delAll')
async delAll() {
return await this.cacheService.flushall();
}
}
redis-test.module.ts
import { Module } from '@nestjs/common';
import { RedisTestService } from './redis-test.service';
import { RedisTestController } from './redis-test.controller';
import { CacheService } from 'src/common/cache/redis.service';
@Module({
controllers: [RedisTestController],
providers: [RedisTestService, CacheService], // 注入redis服务
})
export class RedisTestModule {}
redis-test.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class RedisTestService {}
集成redis实现单点登录
在要使用的controller或service中使用redis
- 这里以实现
token
存储在redis
为例子,实现单点登陆 - 需要在
passport
的login
中,存储token
,如果不会passport
验证
单点登陆原理
- 一个账户在第一个地方登陆,登陆时,JWT生成token,保存token到redis,同时返回token给前端保存到本地
- 同一账户在第二个地方登陆,登陆时,JWT生成新的token,保存新的token到redis。(token已经改变)
此时,第一个地方登陆的账户在请求时,使用的本地token就会和redis里面的新token不一致(注意:都是有效的token)
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { compareSync, hashSync } from 'bcryptjs';
import { UsersEntity } from '../user/entities/user.entity';
import { ToolsService } from '../../utils/tools.service';
import { CreateUserDto } from '../user/dto/create-user.dto';
import { CacheService } from '../../common/db/redis-ceche.service';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(UsersEntity)
private readonly usersRepository: Repository<UsersEntity>,
private readonly jwtService: JwtService,
private readonly redisService: CacheService,
) { }
async create(user: CreateUserDto) {
const { username, password } = user;
const transformPass = hashSync(password);
user.password = transformPass;
const result = await this.usersRepository.findOne({ username });
if (result) ToolsService.fail('用户名已存在');
return await this.usersRepository.insert(user);
}
async validateUser(userinfo): Promise<any> {
const { username, password } = userinfo;
const user = await this.usersRepository.findOne({
where: { username },
select: ['username', 'password', 'id'],
});
if (!user) ToolsService.fail('用户名或密码不正确');
//使用bcryptjs验证密码
if (!compareSync(password, user.password)) {
ToolsService.fail('用户名或密码不正确');
}
return user;
}
async login(user: any): Promise<any> {
const { id, username } = user;
const payload = { id, username };
const access_token = this.jwtService.sign(payload);
await this.redisService.set(`user-token-${id}`, access_token, 60 * 60 * 24); 在这里使用redis
return access_token;
}
}
验证token
import { Strategy, ExtractJwt, StrategyOptions } from 'passport-jwt';
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { jwtConstants } from './constants';
import { CacheService } from '../../common/db/redis-ceche.service';
import { Request } from 'express';
import { ToolsService } from '../../utils/tools.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private redisService: CacheService) {
super({
jwtFromRequest: ExtractJwt.fromHeader('token'), //使用ExtractJwt.fromHeader从header获取token
ignoreExpiration: false, //如果为true,则不验证令牌的过期时间。
secretOrKey: jwtConstants.secret, //使用密钥解析,可以使用process.env.xxx
passReqToCallback: true,
} as StrategyOptions);
}
//token验证, payload是super中已经解析好的token信息
async validate(req: Request, payload: any) {
console.log('payload', payload);
const { id } = payload;
const token = ExtractJwt.fromHeader('token')(req);
const cacheToken = await this.redisService.get(`user-token-${id}`); 获取redis的key
//单点登陆验证
if (token !== JSON.parse(cacheToken)) {
ToolsService.fail('您账户已经在另一处登陆,请重新登陆', 401);
}
return { username: payload.username };
}
}