前言:
现在姐妹们对“js获取transform”大体比较关注,朋友们都需要学习一些“js获取transform”的相关知识。那么小编在网上汇集了一些对于“js获取transform””的相关知识,希望同学们能喜欢,我们一起来学习一下吧!前言
nestjs 为后台的一门技术框架,是前端转向全栈非常好的一个切入点,其完美支持 typescript,也可以使用JavaScript,且nestjs具备渐进增强的能力,可以边开发边学习,且使用入门简单,几天下来就可以连学带写一个小项目,可以说非常受欢迎,这篇文章就介绍他基础入门吧
配置项目
// 全局安装Nest,理解为安装nest环境npm i -g @nestjs/cli// 使用nest命令创建项目,后面基本就靠他了nest new project-name
我们可以选择自己爱好的包管理器(个人比较喜欢 yarn)
就这样一个项目就创建完成了
我们可以看到上面默认创建了一个目录,主要有下面几个(实际上更多),后续用到简介
main:main 函数,不多说,程序入口
conroller:程序路由,也就是接口来源地,一般用来分发功能,具体实现交个一个至多个 service 编写
service:提供服务的文件 service 也叫 provider,我们的业务逻辑基本上都在这里写,无论是对数据库的增删改查,还是业务逻辑的编写都在这里(当然一部分公共库还是要提取的,跟其他开发一样)
module:模块管理器,我们的应用由一个根模块和多个子模块组成,首先是根模块,然后是子模块,当然特别小的程序可能只有一个跟模块,这里比较推荐根据功能分割成多个子模块,日后方便管理
扩展: 当然后面还有什么 entity(数据库映射表)、dto(接口参数规范)、guard(鉴权)等,后面会介绍到的(也许当前文章没有)
扩展2: IOC机制,在 controller、service 中引入内容相应功能时,可以参考 module 中注入的内容,不需要 new,直接使用即可
nest指令
我们配置完成后,就可以使用 nest 指令了,如果不记得也没事,很简单,调用 -help 即可
//查看有哪些命令nest -help
个人感觉,开始时用的最多的就是 res、s、gu了,当然也看个人开发习惯
下面创建一个 user 模块吧,我们使用 res 命令
//创建一个 user resource 仓库nest g res user
其中里面会出现一堆 .spec.ts,这是做单元测试用了,自学阶段嫌乱,可以直接删了(也可以根据情况写自测代码)
路由
指的就是 controller 文件在做的事情,我们的功能通常会被分成多个模块,每个模块都会有一个自己的controller,他可以帮我们定义接口的名字和位置
作为路由,主要是分发功能使用,实际业务实现逻辑要分发到其他 service 中编写,否则 controller 会很肿胀,如果不按规范,项目比较大,合作开发时,无论是自己看以前代码,还比别人看自己代码都会很麻烦
从这里开始就会用到各种各样的装饰器,如果介绍的不够多,可以的到这里参考
controller 头部介绍
@ApiTags('user') //设置swagger文档用的,标记路由所属模块,用于区分api,后面也会介绍@Controller('user') //设置该模块根路由位置,默认会有export class UserController { constructor(private readonly userService: UserService) { }}网络请求
常见的网络请求有 get、post、put、patch、delete、headers 等,也就 rest 风格多一点,现在业务比较杂,很多功能并不是那么单一,因此一般就 get + post 就全部拿下了(业务简单,公司有要求,可以改成 rest 风格,其实都不差太多)
这里通过ts装饰来标记我们的请求类型,例如:
//声明接口的请求类型,@Get()@Post()@Delete()@Put()//在模块根路由上增加一个get类型接口,通过该模块跟路由访问//这样编写一个模块只能调用一个 get 方法//例如:...api/user?id=123@Get() getUserInfo(...) { return ...}//命名追加到url路由上,这样可以区分不同的get类型//例如:...api/user/getUserInfo?id=123@Get('getUserInfo') //给接口追加子路由getUserInfo(...) { return ...}
其他的不多说,下面会稍微详细一点介绍一下 get 和 post
get请求值 params类型参数**
这种请求以前使用的比较多,现在也是比较少用的,基本都改成 query 类型参数了,但是也要了解
params类型参数会直接将参数值 拼到路由上传给服务器,如下所示
//使用params类型,根据id查询//.../api/id_value //id的值会传递在url上@Get(':id')getUserFriends( @Param('id') id: string //声明一个 param 类型 id,类型为 string) { return { name: 'friend1', age: 20 }}get请求query类型参数
现在 get 请求基本上都是使用 query 的形式传递,这样不会污染我们的路由,参数位置一目了然
其中 query 类型如下所示,仍然是 get 特色,参数内容都在 url 上,会暴露参数,接口容易被利用,适合一些公共无安全问题的读取信息接口,例如:排行榜、说明书等
// 使用query类型// .../api?id=value&...@Get('getUserInfo') //命名追加到url路径上getUserInfo( @Query('id') id: string //声明一个 query 类型 id,类型为string) { return { name: '哈哈', age: 22, }}post请求与body
post请求是最受大家喜欢的接口了,url 信息暴露问题解决了,参数都放到了 body(data)中,因此外部看不到,一般用来做创建、更新、删除等操作
@Post('updateUserInfo') //声明post接口类型,路由为updateUserInfo@APIResponse(UserDto)updateUserInfo( @Headers() headers: any, //可以获取headers中的内容,例如版本号平台 @Body() userInfo: UserDto //定义body体类型dto,规范参数类型) { return { message: 'ok' }}参数dto与pile校验
dto(Data Transfer Object),其实这里就是用来定义参数,规范文档和使用的一个类型罢了,除了规范参数使用,也可以用来校验和生成参数文档
pile就是内容校验通道,我们可以通过引入 class-validator
//使用 yarn 引入 class-validator yarn add class-validator
如下所示,使用ts装饰配置一些校验器即可,类型不对,会在 message 以一个数组的方式返回所有出现的参数错误提示,有很多相关判断,可以点进装饰器,看目录就能了解很多装饰器
export class UserDto { //api属性备注,必填 @IsNotEmpty({ message: '名称不能为空' }) //可以返回指定message,返回为数组 readonly name: string //可选参数 @IsNotEmpty() //返回默认 message,且为字符串数组,毕竟可能存在多个为空的 readonly age: number readonly mobile: string //自定义校验类型 @Validate((val: number) => val >=0 && val <= 1) readonly sex: number @IsBoolean() isMarry: boolean}
除了上面还要在 main 函数加入下面代码,将其设为全局
app.useGlobalPipes(new ValidationPipe());
如果不填写,会报如下错误(当然可以通过拦截器,统一一下最后的错误处理,避免返回数据格式和自己定下的类型不一致,后面会讲)
当然 get 的 query 也可以设置,只不过没有那么便利,或者那么校验了,只能使用默认的几种,例如:下面的 ParseIntPipe 判断是否是 int 类型,还可以 float、bool、array 等
@Get('getUserInfo') //命名追加到url路径上getUserInfo( @Query('id', new ParseIntPipe()) //如果不是int类型会报错 // @Query('id', new ParseIntPipe({ // errorHttpStatusCode: HttpStatus.NOT_FOUND, //也可以指定httpcode类型,但一般都是用自己的错误类型 // })) id: number //声明一个 query 类型 id,类型为number)
一般存在校验的,推荐 dto + pile校验 这种,像 get 这种,一般都是参数极为简单或者没参数,可以直接使用上面的,或者直接手动抛出异常即可
post上传文件form/data
上传 form-data类型数据时, 客户端需要指定 content type 为 multipart/form-data(有些固定的调用不需要)
//上传单个文件@Post('file')@UseInterceptors(FileInterceptor('file'))uploadFiles( @UploadedFile() file: Express.Multer.File,) { console.log(files);}//上传多个文件@Post('file')@UseInterceptors(FilesInterceptor('file'))uploadFiles( @UploadedFiles() files: Array<Express.Multer.File>,) { console.log(files);}//上传带其他参数的文件@Post('file')@UseInterceptors(AnyFilesInterceptor({ dest: 'uploads/', }))uploadFile( @Body() objDto: ObjDto, @UploadedFiles() files: Array<Express.Multer.File>,) { console.log(files); console.log(objDto)}typeorm连接mysql数据库
typeorm 是数据库的一个映射工具,会将我们创建的数据库类型、数据库操作映射到 mysql 上,是一个封装后的mysql简化操作工具,减少了直接写 sql语句 操作数据库中的很多麻烦
因此我们需要进行如下步骤,才可以完成数据库的成功连接:
安装配置mysql并打开数据库服务 -> 连接并创建数据库database -> 配置nest的typeorm映射关系 -> 开始我们的数据库操作
分布式、微服务简介
经过上面步骤,在配置的过程,可能也会颠覆扩展一些小白前端的认知,前端和移动端基本上就是直接创建数据库然后操作即可,基本可以理解都在一个设备上
后台不太一样,其很有可能是分布式的,即:后台连接的数据库可能部署在后台本地,也可能部署在远端服务器,那样后台就和我们前端一样,主要就负责写业务了。需要读写数据时,除了本地服务器,需要连接远端一台或者多台服务器读写数据,这就是后台常见的分布式部分概念了,另外,当我们的项目功能比较复杂,可能会吧一个项目的多个模块,分割成多个小项目独立运行,他们共同访问属于自己的数据库服务器或者公共数据库服务器,分割多个子项目独立运行作用整体,其就是微服务架构,错综复杂的整个系统就是分布式系统
这下相信也可以理解,为何一个后台项目可以使用多套技术栈来运行了吧,那就是将业务分割成多个服务,整体形成一个庞大的分布式系统,项目越大内容越杂,整个分布式系统也会看着越繁琐
安装mysql(mac端)
mysql下载地址,我们到这里直接下载 dmg 即可,要是服务器一般为远端 linux, 直接进入服务器,按照别人的步骤来下载配置即可
安装完毕后,在系统偏好找打 mysql,然后启动即可,忘记密码也可以点进去配置,比以前方便太多了
ps:我自动自动后,基本都是开机自启,基本不用管了(甚至时间久了都会忘了流程,毕竟太简单了)
连接数据库
到这里数据库服务我们开启了,但我们还没创建数据库,因此需要创建数据库(nest只会创建表和读写,不能创建数据库)
创建前我们需要下载一些工具来操作和查看数据库,可以已使用 appstore 上面的一些收费的软件,其看起来比较舒服,但需要马内,也可以使用vscode插件等,个人倾向 vscode插件,毕竟免费,丑点无所谓
打开 vscode 搜索 database client,然后下载,下载后我们打开,会出现这个界面,直接输入我们的密码和数据库类型名称即可,数据库填写 mysql,不要填写我们自定义的 database 名字,如下所示填写完成后,点,连接成功
到这里我们还需要创建我们的数据库 database,因此需要命令,我们新建一个,会出现下面命令,我们在 CREATE DATABASE后面加上我们自定义的 database 名字即可,这里叫 nest_demo,当然也可以创建连接多个数据库
到这里就完成了,后面只需要使用 typeorm 库连接我们的数据库,并配置好数据库字段映射操作即可,配置完成后,下次运行便会出现我们的表了(虾米那会介绍怎么连接和映射)
typeorm配置与环境变量
话不多说,先安装下面是三个库 typeorm、mysql
yarn add @nestjs/typeorm typeorm mysql2
然后配置 app.module,可以发现使用了 TypeOrmModule.forRoot,在里面可以直接写入自己的地址、端口号、密码等,这里面没有直接代码写死,是因为使用环境变量的方式,方便后期部署时随时更改地址
@Module({ imports: [ TypeOrmModule.forRoot({ type: 'mysql', host: envConfig.host, port: Number(envConfig.port), username: envConfig.username, password: envConfig.password, database: envConfig.database, synchronize: true, //自动同步创建数据库表 retryDelay: 500, retryAttempts: 10, autoLoadEntities: true, //自动查找entity实体 }) ], controllers: [AppController], providers: [AppService],})
创建了一个 .env 的环境变量文件,在里面填写我们用到的一些相关变量
创建一个 config 文件,然后用于获取转化我们的环境变量,方便使用即可(注意:本地服务可能和数据库不在一个,那是服务的 host、port 自己要区分开)
import * as dotenv from 'dotenv';class ConfigEnv { secret: string; host: string; port: string; //mysql username: string; password: string; database: string; constructor(envConfig: any) { this.secret = envConfig.APP_SECRET this.host = envConfig.DB_HOST this.port = envConfig.DB_PORT this.username = envConfig.DB_USER this.password = envConfig.DB_PASSWORD this.database = envConfig.DB_DATABASE }}const envConfig = new ConfigEnv(dotenv.config().parsed)export { envConfig };
这样基础配置好了,但还没完事,我们还没有建立数据库映射,因此还无法自动新建数据库
crud映射
设置数据库映射 entity 表,这里设置完毕后,会自动映射出数据库 table 表
还记得我们前面 nest g res user 创建的用户模块么,里面有一个 user.entity.ts 文件,我们在里面映射我们的表即可
如下所示,我们建立了一个简单的用户表,方便介绍
@Entity() //默认带的 entityexport class User { //作为主键且创建时自动生成,默认自增 @PrimaryGeneratedColumn() id: number //默认数据库的列,会根据 ts 类型,自动创建自定类型,默认字符串 255 byte,也就是255个unicode字符 @Column() name: string //可以设置唯一值,参数可以点进去看详情 @Column({unique: true}) wxId: string //设置默认值 @Column({default: null}) age: number //设置枚举,实际推荐数字 + 文档即可,方便又实惠 @Column('simple-enum', {enum: ['man', 'woman', 'unknow']}) sex: string @Column({default: null}) //默认最大字符串255字节,能储存255个unicode字符 mobile: string @Column({ select: false, length: 30}) //查询时隐藏此列,可以设置长度30个字节 password: string //默认都是可变字节,如果设置最大长度比较小,但内容比较大,也能写入,但是效率可能会变低 //默认最大字节数比较大,65535为text,另一个更大,也可以根据自行设置大小 // @Column('mediumtext', {default: null}) @Column('text', {default: null}) desc: string //伪/软删除,用户误操作可以恢复,对于重要/敏感信息,不能真删除 @Column({ select: false}) //查询时隐藏此列 isDelete: boolean @VersionColumn() //自动记录内容更新次数,某些计次场景会用到 version: number //下面是创建内容自动生成,和更新时自动更新的时间戳,分别代表该条记录创建时间和上次更新时间 @CreateDateColumn({ type: 'timestamp' }) createTime: Date @UpdateDateColumn({ type: 'timestamp' }) updateTime: Date}
其他还有需要的功能,自己点进装饰器看就可以了,我也是功能不够用时,点进去找的
service服务
我们的 controller 主要是充当路由,而数据提取、业务逻辑等都在 service 中编写,因此其很重重要,有时候根据业务和功能,一个小模块会分成好多 service
编写 service 时,如果出现问题错误返回给外面,直接 throw 即可,正常我们一般会包装一层固定结构返回,后面介绍swagger时一起介绍
@Injectable()export class UserService { constructor( @InjectRepository(User) private userRepository: Repository<User>, @InjectRepository(Account) private accountRepository: Repository<Account>, ) {} getUserInfo(id: number) { //nest的查询语句,这句意思和 findOne 一样,根据表当中的某个字段获取一个,可以点进去查看 //复杂的查询逻辑,还是需要对数据库多学习了解的 return this.userRepository.findOneBy({id}) }}全局拦截器
全局拦截器的文件可以通过 nest 指令,也可以直接直接创建编写,下面直接编写即可,为了方便可以使用
ps1:仅个人感觉来说,两个都不是很好用,尤其是成功后的拦截器,其有点鸡肋(可能是我用的方式不对哈),因为我们只能修改里面的 data 数据结构,外层的还得按照 http 协议的走,失败的倒是还不错。我们可以让用户按照我们的显示,不过客户端还没到服务器阶段产生的网络错误,还得按照他自己的逻辑走,并且使用时我们要占用一个 http 状态码,并且一些是我们请求成功了,但是不符合业务要求的错误,抛出异常会到这里,这时状态码用着有点不太舒服,最好在成功里面,因此不使用全局,(自定义一个类,返回最好)
ps2:后面单独介绍文档 swagger 时,会一起讲解一下个人思路
错误过滤拦截
创建一个 http-exception.filter.ts 文件
import { ArgumentsHost, Catch, ExceptionFilter, HttpException } from '@nestjs/common';@Catch(HttpException)export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); // 获取请求上下文 const response = ctx.getResponse(); // 获取请求上下文中的 response对象 const status = exception.getStatus(); // 获取异常状态码 let message: string; let code: number; if (status === 401) { code = status; message = '未授权'; }else { code = -1; // message = exception.message message = '网络请求失败'; } // 设置返回的状态码, 请求头,发送错误信息 response.status(status); // response.header('Content-Type', 'application/json; charset=utf-8'); response.send({ msg: message, code, }); }}成功格式拦截
创建一个 transform.interceptor 文件
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';import { map, Observable } from 'rxjs';@Injectable()export class TransformInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( map((data) => { return { data, code: 200, msg: '请求成功', }; }), ); }}配置swagger配置前的口嗨
这一步也是很重要的一个步骤,可以说不会写、不写文档的后台不是一个合格的后台
ps:前端平生最恨两种后台,一个是文档全靠嘴,接口说改就改,最后找前端要文档,另一个就是一运行就报错,对错全靠前端测
因此,写文档是必要的,接口写完可以先拿 postman 等自测一下吧,不谈功能对不对,运行先不报错500吧
配置文档安装swagger
先使用 npm、yarn 导入 swagge相关
yarn add @nestjs/swagger swagger-ui-express配置main函数
然后再 main函数开启文档,当我们项目运行的时候,文档就能看到了
const options = new DocumentBuilder() .setTitle('nest demo api') .setDescription('This is nest demo api') .setVersion('1.0') .build();const document = SwaggerModule.createDocument(app, options);SwaggerModule.setup('api-docs', app, document);//这个地址本地看得话就是 了
main 函数配置的就是,swagger顶部那些,如下所示,下面的也会介绍
配置路由、dto文档
配置模块名称 @ApiTags,也就是给一个模块添加一个大标题索引,方便快速区分api功能的
@ApiTags('user')@Controller('user')export class UserController { ......}
配置接口路由备注 @ApiOperation,可以设置单个接口备注,上图就可以看出来
@ApiOperation({ summary: '修改用户信息2',})@Post('updateUserInfo')updateUserInfo(//可以获取headers中的内容,例如版本号平台 @Body() userInfo: UserDto) { ......}
前面给 @Body 后续前面设置了 dto,dto也可以配置上传参数的标签,以便于用户设置,再加上以前的参数验证pipe,如下所示
下面 description参数描述, example 为参数案例,@ApiProperty表示必填,@ApiPropertyOptional表示可选属性
export class UserDto { //api属性备注,必填 @ApiProperty({description: '名字', example: '迪丽热巴'}) //设置了 IsNotEmpty 就是必填属性了,文档也会根据该验证来显示是否必填 @IsNotEmpty({ message: 'name不能为空' })//可以返回指定message,返回为数组 // @IsNotEmpty()//返回默认message,默认为为原字段的英文提示 readonly name: string //可选参数 @ApiPropertyOptional({description: '年龄', example: 20}) readonly age: number @ApiPropertyOptional({description: '手机号', example: '133****3333'}) readonly mobile: string @ApiPropertyOptional({description: '性别 1男 2女 0未知', example: 1}) @Validate((val: number) => !val || (val > 0 && val <= 1)) readonly sex: number @ApiPropertyOptional({description: '是否已婚', example: false}) @IsNotEmpty() marry: number}
body 传参如下所示,可以点击 schema 查看必填项
后面还会增加 swagger 更加详细的内容,这篇就介绍到这里了(文档很重要,单独搞一篇出来,保证能做出来一个相对比较好的 http 文档)
设置api路由前缀
有时为了使接口api更加清晰化,或预留位置等情况,我们开发时,会给我们的项目添加一个全局路由
app.setGlobalPrefix('api');这样接口的基础地址就会变成 文档还是原来那个不受影响 /api-docs设置跨域支持
前端开发不可避免的会遇到跨域问题,如果是测试阶段还需要使用代理,为了方便调试后端可以关闭跨域,如下所示
//设置跨域支持app.enableCors();最后
本篇文章主要是入门,篇幅太大就比较难读了,到这里基本上就能开始写了,不会的也知道从哪里查了,后续也会略微完善一点
测试案例demo ------ 最后附上是因为里面还有后面的其他功能,相对比较杂,有些功能会有不小改动,直接拿 demo 学习对一些人会有影响
ps:我们在开发中,可能会给前端写接口,移动端,尤其是对于移动端来说,更新较慢,因此当我们上线应用时,更改原有功能时,有较大改动时(数据结构发生改变),可以新增接口或者添加版本判断等,不要轻易直接动原接口,否则可能会导致线上应用显示异常、崩溃等,毕竟短时间内用户不一定会升级应用。因此,一些必要的措施是要做的,此外,我们应用可以加上一个版本统计功能,这样等老接口都退休无人访问的时候,就可以真的退休了,另外一定要注释好了
祝大家学习愉快!
标签: #js获取transform #js变量命名bool