前言

本文章介绍使用nest.js框架(基于nodejs)搭建后端服务,介绍对mysql数据的增删改查操作,并且配置swagger接口文档,本地文件上传到服务器。


一、nest是什么?

Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,内置并完全支持 TypeScript(但仍然允许开发人员使用纯 JavaScript 编写代码)并结合了 OOP(面向对象编程),FP(函数式编程)和 FRP(函数式响应编程)的元素。

在底层,Nest使用强大的 HTTP Server 框架,如 Express(默认)和 Fastify。Nest 在这些框架之上提供了一定程度的抽象,同时也将其 API 直接暴露给开发人员。这样可以轻松使用每个平台的无数第三方模块。

nest官网

二、使用步骤

1.使用

 npm i -g @nestjs/cli // 将nest安装到全局
 nest new 项目名 // 使用nest-cli创建项目
 npm start // 运行本地项目

2.目录结构

并未列出所有目录,主要目录已列出

├─dist                       编译后的文件
├─static       静态文件
├─src						 代码文件
	├─server						接口文件夹
		├─config							网站配置的增删改查
			├─config.controller.ts			控制器,实现接口
			├─config.entity.ts				实体类,搭配typeorm实现对数据库表的创建,对数据库表的增删改查
			├─config.entityDao.ts			接收入参,根据情况而定
			├─config.module.ts				入口
			└─config.service.ts				服务层,逻辑实现
		└─upload						上传文件
			├─upload.controller.ts			控制器,实现接口
			├─upload.entity.ts				实体类,搭配typeorm实现对数据库表的创建,对数据库表的增删改查
			├─upload.module.ts			入口
			└─upload.service.ts			服务层,逻辑实现
	├─utils						公用方法
		└─result.ts        		返回给前端的数据格式
	├─app.module.ts  		服务层入口
	└─main.ts        		入口文件
├─test          测试文件
├─nest-cli.json     nest-cli配置文件
├─package.json   依赖文件
└─tsconfig.json    ts配置文件

3.改造初始项目,使其能够连接mysql数据库

1.连接数据库

使用typeorm的的方式连接的数据库,连接数据库使用到了几个依赖,引入相关依赖

 // mysql只能连接低版本数据库,由于我安装的最新mysql,安装mysql依赖会报错,mysql2不会出现这样的问题
 npm i @nestjs/typeorm typeorm mysql2 -S

在app.module.ts文件中导入数据库配置,以及将config和upload子模块导入,代码如下

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
// 子模块加载
import { UploadModule } from './server/upload/upload.module';
import { ConfigModule } from './server/config/config.module';
@Module({
  imports: [
    // 加载连接数据库
    TypeOrmModule.forRoot({
      type: 'mysql', // 数据库类型
      host: '***.***.***.***', // 数据库ip地址
      port: 3306, // 端口
      username: 'test', // 登录名
      password: '****', // 密码
      database: 'test', // 数据库名称
      entities: [__dirname + '/**/*.entity{.ts,.js}'], // 扫描本项目中.entity.ts或者.entity.js的文件
      synchronize: true, // 定义数据库表结构与实体类字段同步(这里一旦数据库少了字段就会自动加入,根据需要来使用)
    }),
    UploadModule,
    ConfigModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

2.实现文件上传

我是将上传后的文件链接存入了一遍数据库,所以增加了数据库的新增操作
upload.entity.ts //存入数据库的字段名定义

import { Column, Entity, PrimaryGeneratedColumn, BaseEntity } from 'typeorm';

@Entity()
export class Upload extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;
  // 文件名
  @Column()
  name: string;
  // 文件相对位置
  @Column()
  path: string;
  // 文件在服务器的绝对位置
  @Column({ default: null, name: 'path_local' })
  pathLocal: string;
  // 文件的外部可访问地址
  @Column({ default: null, name: 'path_url' })
  pathUrl?: string;
  // 文件大小
  @Column({ default: null })
  size?: number;
  // 文件种类  img 图片,file 文件
  @Column({ default: null })
  type?: string;
  // 文件创建时间
  @Column({ type: 'datetime', name: 'create_time' })
  createTime: string;
}

存储文件,分日期进行存储,我使用的是多文件存储传入一个文件数据,用到的依赖

npm i mz-modules moment fs path -S

upload.service

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { join } from 'path';
import * as fs from 'fs';
import * as moment from 'moment';
import { mkdirp } from 'mz-modules';
import { Repository } from 'typeorm';
import { Upload } from './upload.entity';

@Injectable()
export class UploadService {
  // 使用InjectRepository装饰器并引入Repository这样就可以使用typeorm的操作了
  constructor(
    @InjectRepository(Upload)
    private readonly upoladRepository: Repository<Upload>,
  ) {}

  // 获取所有文件
  async findAll(): Promise<Upload[]> {
    return await this.upoladRepository.find();
  }

  // 存储文件
  async create(files: Array<any>) {
    const fileArr = [];
    for (const file of files) {
      console.log(file);
      console.log(file.filename, file.originalname);
      // 文件类型为img则存储img文件夹,否则存在file文件夹
      const file_type = file.mimetype;
      const imgReg = /image/gi;
      // 文件夹,判断是图片还是其他文件
      const upload_dir_type = imgReg.test(file_type) ? 'img' : 'file';
      // 当天日期
      const today = moment().format('YYYY-MM-DD');
      // 相对路径
      const relative_dir_path = `/static/${upload_dir_type}/${today}/`;
      // 生成本地路径
      const target_dir_path = join(__dirname, '../../..', relative_dir_path);
      // 若是没有某天的文件夹,mkdirp会创建出该文件夹
      await mkdirp(target_dir_path);
      // 文件相对路径
      let file_path = relative_dir_path + file.originalname;
      // 文件本地路径
      let target_file_path = target_dir_path + file.originalname;
      let clientID = '';
      // 判断文件夹中是否已经存在该文件,只有重复的文件才会执行这一步
      // 若是已经存在,会随机生成一个id,然后拼接到文件名的前面
      const pathBool = await fs.existsSync(target_file_path);
      if (pathBool) {
        // clientID 随机生成一个8位数的id
        const possible =
          'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        for (let i = 0; i < 8; i++) {
          clientID += possible.charAt((Math.random() * possible.length) | 0);
        }
        file_path = relative_dir_path + clientID + file.originalname;
        target_file_path = target_dir_path + clientID + file.originalname;
      }
      // 存储文件
      const writeMusicCover = fs.createWriteStream(target_file_path);
      writeMusicCover.write(file.buffer);
      // 存数据库
      const uploadData = new Upload();
      uploadData.name = pathBool
        ? clientID + file.originalname
        : file.originalname;
      uploadData.type = upload_dir_type;
      uploadData.path = file_path;
      uploadData.pathLocal = target_file_path;
      // http://node.wisdoms.xin域名根据自己的来
      uploadData.pathUrl = 'http://node.wisdoms.xin' + file_path;
      uploadData.size = file.size;
      uploadData.createTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
      await this.upoladRepository.save(uploadData);
      // 删除是为了不将这几个参数返回给前端
      delete uploadData.pathLocal;
      delete uploadData.type;
      fileArr.push(uploadData);
    }
    // 单个文件直接返回对象,多个文件会返回数组
    return files.length > 1 ? fileArr : fileArr[0];
  }
}

获取前端传入的文件,配置swagger
upload.controller.ts

import {
  Controller,
  Post,
  UseInterceptors,
  UploadedFiles,
} from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
// swagger的展示配置
import { ApiTags, ApiProperty, ApiConsumes, ApiBody } from '@nestjs/swagger';
import { UploadService } from './upload.service';
import { IHttpData } from '../../utils/relust';

class FilesUploadDto {
  // swagger配置项
  @ApiProperty({ type: 'array', items: { type: 'string', format: 'binary' } })
  file: any[];
}

// swagger该模块的标题
@ApiTags('文件接口')
@Controller('upload')
export class UploadController {
  constructor(private readonly uploadService: UploadService) {}

  // 支持上传多个文件
  @Post()
  // @UseInterceptors(AnyFilesInterceptor())
  // AnyFilesInterceptor定义任意字段的名称
  @UseInterceptors(FilesInterceptor('file')) // file对应HTML表单的name属性
  // swagger入参配置
  @ApiConsumes('multipart/form-data')
  @ApiBody({
    description: '选择文件',
    type: FilesUploadDto,
  })
  async UploadedFile(@UploadedFiles() files): Promise<IHttpData> {
    // 接口严格要求以这个形式返回给前端
    const result: IHttpData = {
      code: 0,
      data: null,
      msg: '',
    };
    // 若传入的文件为空,直接返回
    if (!files || files.length === 0) {
      result.code = -1;
      result.msg = '未选择文件';
      return result;
    }
    // 调用service的存储文件方法,传入前端传来的文件数组
    // 成功后会将文件信息返回给前端
    const data = await this.uploadService.create(files);
    result.code = 0;
    result.data = data;
    result.msg = '上传成功';
    return result;
  }
}

导出上传的配置项
upload.module.ts

import { Module } from '@nestjs/common';
import { UploadController } from './upload.controller';
import { UploadService } from './upload.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Upload } from './upload.entity';

@Module({
  // 必须导入该实体类,不然数据库不会创建upload数据表,之后的操作也没有用处
  imports: [TypeOrmModule.forFeature([Upload])],
  controllers: [UploadController],
  providers: [UploadService],
})
export class UploadModule {}

到目前为止上传文件,存储文件以及完成了,可以自己运行测试,不懂得在下方留言哦

3. 对数据库的增删改查操作

看了以上上传操作后,大家应该知道怎么进行新增操作了吧,删除和修改操作和以上类似我就不详细进行介绍了,config.entity,config.entityDao类我就不贴出来了,和上传里面类似,看代码吧,偷偷懒。
config.server.ts

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import * as moment from 'moment';
import { Repository } from 'typeorm';
import { Config } from './config.entity';
import { configDao } from './config.entityDao';

@Injectable()
export class ConfigService {
  // 使用InjectRepository装饰器并引入Repository这样就可以使用typeorm的操作了
  constructor(
    @InjectRepository(Config)
    private readonly configRepository: Repository<Config>,
  ) {}

  // 获取所有配置信息
  async findAll(): Promise<Config[]> {
    return await this.configRepository.find();
  }

  // 根据管理id查询配置信息, 默认1
  async getById(id = 1) {
    return await this.configRepository.findOne(id);
  }

  // 创建配置信息
  async add(data: configDao) {
    const configData = new Config();
    configData.websiteName = data.websiteName;
    configData.logo = data.logo;
    configData.notice = data.notice;
    configData.about = data.about;
    configData.createTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
    this.configRepository.save(configData);
  }

  // 修改配置信息
  async update(data: configDao) {
    const configData = await this.getById(data.id);
    configData.websiteName = data.websiteName;
    configData.logo = data.logo;
    configData.notice = data.notice;
    configData.about = data.about;
    configData.updateTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
    this.configRepository.save(configData);
  }

  // 删除配置信息
  async delete(id = 1) {
    const configData = await this.configRepository.findOne(id);
    this.configRepository.remove(configData);
  }
}

我对接口的返回没有做错误判断,以下代码无论成功与否都会返回相应的信息,要想返回错误请自行修改
config.controller.ts

import { Controller, Get, Post, Delete, Body, Param } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ApiTags, ApiParam, ApiBody } from '@nestjs/swagger';
import { IHttpData } from '../../utils/relust';
import { configDao } from './config.entityDao';

@ApiTags('配置接口')
@Controller('config')
export class ConfigController {
  constructor(private readonly configService: ConfigService) {}

  // 查询所有配置信息
  @Get('list')
  async findAll(): Promise<IHttpData> {
    const data: any = await this.configService.findAll();
    const result: IHttpData = {
      code: 0,
      data,
      msg: '获取成功',
    };
    return result;
  }

  // 根据id查询配置信息
  @Get(':id')
  @ApiParam({
    name: 'id',
    description: '这是配置id',
  })
  async getById(@Param('id') id?: number): Promise<IHttpData> {
    const data: any = await this.configService.getById(id);
    const result: IHttpData = {
      code: 0,
      data,
      msg: '获取成功',
    };
    return result;
  }

  // 新增配置信息
  @Post('add')
  @ApiBody({ type: configDao })
  async addConfig(@Body() configDao: configDao): Promise<IHttpData> {
    this.configService.add(configDao);
    const result: IHttpData = {
      code: 0,
      data: null,
      msg: '新增成功',
    };
    return result;
  }

  // 修改配置信息
  @Post('update')
  async updateConfig(@Body() configDao: configDao): Promise<IHttpData> {
    this.configService.update(configDao);
    const result: IHttpData = {
      code: 0,
      data: null,
      msg: '修改成功',
    };
    return result;
  }

  // 删除用户用户信息
  @Delete(':id')
  @ApiParam({
    name: 'id',
    description: '这是配置id',
  })
  async deleteConfig(@Param('id') id: number): Promise<IHttpData> {
    this.configService.delete(id);
    const result: IHttpData = {
      code: 0,
      data: null,
      msg: '删除成功',
    };
    return result;
  }
}

3.main.ts文件处理

处理跨域问题
引入swagger,实现接口文档,需要安装相关依赖

 npm i @nestjs/swagger swagger-ui-express -S

配置默认文件夹(文件上传后可通过域名直接进行访问上传的文件)

import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import * as serveStatic from 'serve-static';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);

  // 处理跨域
  app.enableCors();

  // '/static' 是路由名称,即你访问的路径为:host/static
  // serveStatic 为 serve-static 导入的中间件,其中'../static' 为本项目相对于src目录的绝对地址
  app.use(
    '/static',
    serveStatic(join(__dirname, '../static'), {
      maxAge: '1d',
      extensions: ['jpg', 'jpeg', 'png', 'gif'], // 可以访问的文件类型
    }),
  );

  // swagger配置
  const options = new DocumentBuilder()
    .setTitle('Nodejs + Vuejs 全栈项目-后台管理API') // 标题
    .setDescription('供后台管理界面调用的服务端API') // 简述
    .setVersion('1.0') // 版本
    // .addTag('cats')
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup('/', app, document{
    swaggerOptions: {
      docExpansion: 'none' // 默认不展开
    }
  });

  await app.listen(3000);
}
bootstrap();

4.package配置文件

{
  "name": "demo",
  "version": "0.0.1",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "pm2": "pm2 start --name nest dist/main.js",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@nestjs/common": "^7.5.1",
    "@nestjs/core": "^7.5.1",
    "@nestjs/platform-express": "^7.5.1",
    "@nestjs/swagger": "^4.7.9",
    "@nestjs/typeorm": "^7.1.5",
    "fs": "0.0.1-security",
    "moment": "^2.29.1",
    "mysql2": "^2.2.5",
    "mz-modules": "^2.1.0",
    "reflect-metadata": "^0.1.13",
    "rimraf": "^3.0.2",
    "rxjs": "^6.6.3",
    "swagger-ui-express": "^4.1.6",
    "typeorm": "^0.2.29"
  },
  "devDependencies": {
    "@nestjs/cli": "^7.5.1",
    "@nestjs/schematics": "^7.1.3",
    "@nestjs/testing": "^7.5.1",
    "@types/express": "^4.17.8",
    "@types/jest": "^26.0.15",
    "@types/node": "^14.14.6",
    "@types/supertest": "^2.0.10",
    "@typescript-eslint/eslint-plugin": "^4.6.1",
    "@typescript-eslint/parser": "^4.6.1",
    "eslint": "^7.12.1",
    "eslint-config-prettier": "7.1.0",
    "eslint-plugin-prettier": "^3.1.4",
    "jest": "^26.6.3",
    "prettier": "^2.1.2",
    "supertest": "^6.0.0",
    "ts-jest": "^26.4.3",
    "ts-loader": "^8.0.8",
    "ts-node": "^9.0.0",
    "tsconfig-paths": "^3.9.0",
    "typescript": "^4.0.5"
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

三.发布到服务器

打包发布服务器后,我使用的pm2守护进程运行的项目
将整个项目上传到服务器,服务器需要安装node环境,npm环境,nest环境,pm2环境
然后根目录运行一下代码

npm i
npm build// 运行后再运行下面的代码
npm run pm2 // 使用pm2守护该进程

然后就可以进行访问了,我使用nginx配置进行了设置,设置了域名
这样就可以直接使用以下接口进行上传操作了,美滋滋
http://node.wisdoms.xin/upload // 上传接口

使用以下链接就可以访问到swagger接口文档了`http://node.wisdoms.xin/api

gitee仓库地址
swagger接口文档
apiPost接口文档

0/500
评论列表
Java电商系统2023-10-09 16:12:16 回复

感谢分享