Nest.js:做个图书借阅系统(1) 需求对接与技术准备

503 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情 

本系列文章将涉及到的技术有Nest.js、Typescript、Vite、Vue、Antdv、MySql,有兴趣的可以关注一波,本系列将一步步的从 客户需求->程序设计->全部功能开发,提供完整示例;我的观点就是实践出真知,你可能收获到的包括但不限于:需求分析,数据结构设计,Nest.js操作指南等,。本人也是个菜鸡前端,初涉全栈,第一次写文,希望各位大大多多包涵,有什么建议可以提出来互相交流。


-【前奏】

不久之前,前女友居然发来一条微信:

好久不见,能否找天出来见个面,有些事情想找你商量。

我们好多年没见了,我记得2年前还在朋友圈看到她结婚的消息。难不成跟老公相处不和谐,想找我诉诉苦?最后还是觉得我更合适一些?

我谨慎的询问了一下才知道,我果然还是想多了啊,原来她孩子都1岁多了,最近盘了个生意,是做社区儿童书店,主要是会员制以借阅绘本为主,想做个图书借阅系统,怎奈找人报价都太高了,创业初期想省点钱... 咳咳,不就是个图书借阅系统么,在学校就练习过了,一点都不难嘛,我趁机会赚点外快也是不错的呢,于是随口就答应了。

见面那天晚上,我们相约在一个西餐吧隔壁是希x顿酒店,那是我们以前经常去的地方,我站在门口等着,老远就看到了引人注目的她,她穿着一件黑色深v吊带裙,似乎是精心打扮了一番,虽然生了孩子但是身材一如当年。我们找了一个靠窗的位置坐下,这里可以欣赏美丽的夜色,我坐下来就开门见山问到项目的事情,而她伸出食指靠近了红唇,“嘘,待会再说,我们先开瓶酒叙叙旧吧...” “也好。”

不知道过了多久,我已经有点迷迷糊糊了...后面发生了什么也记不太清


-【准备工作 & 技术选择】

第二天一大早,我就爬起来翻开笔记本,上面已经写下了项目的需求。要做一个系统一定要首先弄清楚客户的需求,如下:

  1. 会员制,客户来了根据手机号开卡,他们只有有半年卡和年卡。规则:有这个卡就能借阅书籍,当然你也可以在现场看(一位家长陪同),借阅外出一次只能3本,有效期是15天,超过时间按购买算,不买就不能再借了。但是客户单次也是可以的,单次不用办卡,直接当天买门票费用即可,不允许外带食物,容易造成书籍损耗。
  2. 根据手机号查当前借阅情况,办理借阅归还,书有破损需要记录并补款。
  3. 根据书籍名称和编号查询书籍在库归还情况,并办理借出

虽然看起来功能并不多,她也认为功能并不多,但是有很多潜在的功能点,我们在下一篇设计数据库的时候再重新整理成程序需求。

这也就是互联网产品的工作,一般现场人员不懂技术,拿回需求之后就对接产品,产品把客户语言转换成程序语言

我们先选择技术栈,既然是做外快,一定要选择我们熟练好用的工具,前端就是Vite+Vue,但是我是第一次涉及后端,也只好用我们js的服务框架比较合适,虽然我不是很懂,我问过她们,理想状态社区小孩也不过几百人,对于性能安全要求不是很高的这些小应用来说,问题不大,能跑就行,不论是程序还是人!

对比 express \ egg \ koa \ Nest

我选择Nest,主要觉得Nest已经帮我们做了不少工作,路由控制器模块化,还有默认TypeScript,代码风格等等,省了不少事。于是后端就确定为 Nest.js + TypeOrm + MySql


-【Nest.js 快速上手试用】

我们按照官网的来试试看,首先装一个全局的nest cli

$ npm i -g @nestjs/cli 
$ nest new book-lending

等待安装完成之后会自动运行,不过我这里用的node是12的版本会有依赖装不上去,只好用nvm切换成14的版本,手动yarn来安装

$ cd book-lending
$ yarn
$ npm run start

现在打开 http://localhost:3000/ 就可以看到Hello World!

接下来是安装本地mysql,毕竟你需要先本地开发,这一步请自行百度,就不需要说太多了,接下来是安装TypeOrm.

$ yarn add --save @nestjs/typeorm typeorm mysql2

建议参考nest文档来安装,@nestjs/typeorm是必须的

使用TypeOrm主要是2个方面的原因,

  1. 他可以建立模型后与数据库结构同步,这样你前期经常改动就不用去直接管理数据库专心你的代码逻辑就好,到时候迁移到服务器也只用换个数据库连接即可
  2. 我们前端一开始不怎么会写sql语句,那么就直接用类似对象操作的语法,更容易上手(事实真的如此吗?),也帮我们规避一些基础的问题,比如sql注入。

安装好之后,我们来配置一下数据库的同步设置和数据库连接。 根据文档,我们修改app.module.ts文件

// app.module.ts 
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',  // 上线后替换成自己的服务器地址
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'booklending',  // 连接的数据库需要自己建一个哟
      entities: ['dist/**/*.entity{.ts,.js}'],
      entityPrefix: 'bl_',  // 表示给表添加前缀名
      synchronize: true,  // 开启自动同步
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}


注意:设置synchronize: true不应该在生产中使用,否则可能会丢失生产数据。

这时候还得加上一个模型类,先随便搞一个试试看

// -/src/entity/

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

@Entity('user')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ nullable: true })
  companyId: string;

  @Column({ nullable: true, length: 100, unique: true })
  username: string;

  @Column({ nullable: true })
  password: string;

  @Column({ nullable: true, length: 100, unique: true })
  name: string;

  @Column({ nullable: true })
  age: number;

  @Column({ nullable: true })
  sex: number;

  @Column({ nullable: true })
  remark: string;
}

运行后,打开数据库,就会看到多出来一个bl_user的表

QQ截图20220808170740.png

下面我们写一段查询,并尝试调用一下,就完成了我们的准备工作。 首先修改app.service.ts 内容如下

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from 'src/entity/user.entity';

@Injectable()
export class AppService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  getHello(): string {
    return 'Hello World!';
  }

  getUser(): Promise<User[]> {
    return this.usersRepository.find();
  }
}

这里使用了@InjectRepository()装饰器将 UsersRepository 注入到 appService 中:

同样的app.controller.ts 中也得加上一个api接口,@Get后面的参数其实就是api访问路径,这一点真的超级方便

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('users')
  getUser(): Promise<any> {
    return this.appService.getUser();
  }
}

这个时候发现运行不了,报错了,那是因为必须要用forFeature()对User实体进行注册

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entity/user.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'booklending',
      entities: ['dist/**/*.entity{.ts,.js}'],
      entityPrefix: 'bl_',
      synchronize: true,
    }),
    TypeOrmModule.forFeature([User]),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

这一点我觉得好繁琐,尽管可以分模块注册,但假设我开发时,每多个实体,我就要用TypeOrmModule.forFeature注册一个,不知道有没有自动的方法,或者启动时写个读取文件进行循环注册。

我们在下面添加了一个getUser方法,用来查询我们的bl_user表中所有的数据, 我们可以先手动添加几条数据

QQ截图20220808173934.png

然后访问http://localhost:3000/users就能看到数据了

QQ截图20220808174104.png

OK,Nest的初步尝试就到这里,本系列文章只是记录我设计和实践的过程,将不会对Nestjs一些基础概念进行科普,比如装饰器,依赖注入等,有需求的同学请自行看文档,或者这篇入门文章 学完这篇 Nest.js 实战,还没入门的来锤我!

下一篇我们设计数据库结构,并且同步调整梳理一下Nest的目录结构。吭哧吭哧研究了半个小时,突然想到一件事,昨天晚上到底聊了价格没有呢?