前言
在大概 1 年前接触了 typescript 之后, 日渐被它所吸引. 甚至一个简单的本地测试文件 node ./test.js 有时也会切到 ts-node ./test.ts. 在同样的时间节点之前, 还是会不时地去学学 node, mongodb 相关的. 可是, 由于懒 (需) 惰(求), 在很久没碰之后, 很多知识点都忘了!
综上, 于是就有了今天这个话题:
论 如何在工作时间之余完成自己的个人项目并实现按时上床睡觉
答案是: 不存在的
项目简介
项目会不断维护. 无论是 client 端还是 server 端, 都只提供简单的模板式的功能.
地址
- client https://github.com/YDJ-FE/ts-react-webpack
- server https://github.com/jackple/showcase
线上体验 https://starter.jackple.com/#/
依赖
typescript 是两端的基调
- client
- webpack-4.x
- typescript-3.0.x
- react-16.4.x
- mobx-5.x
- ant design
- ...
详看 https://github.com/YDJ-FE/ts-react-webpack#characteristicspackages
server
centos 上 mongodb 的官网安装教程 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/ , 其他系统请自行查阅.
- nestjs
- dotenv
- jsonwebtoken
- mongodb(mongoose)
- ...
需要讲一下我为什么选了 https://github.com/nestjs/nest :
nestjs 将 typeScript 引入并基于 express 封装. 意味着, 它与绝大部分 express 插件的兼容性都很好.
nestjs 的核心概念是提供一种体系结构, 它帮助开发人员实现层的最大分离, 并在应用程序中增加抽象.
此外, 它对测试是非常友好的...
也需要声明的是, nestjs 的依赖注入特性是受到了 angular 框架的启发, 相信做 angular 开发的对整个程序体系会更容易看懂.
查看中文文档 https://docs.nestjs.cn/5.0/firststeps
具体实现
server
简单介绍下几个主流程模块
main.ts
我是用 nest-cli 工具初始化项目的, 一切从 https://github.com/jackple/showcase/blob/bc9906044694ea2f63489d5f9857c119fc547015/src/main.ts 开始
- import { NestFactory } from '@nestjs/core'
- import * as dotenv from 'dotenv'
- import { DOTENV_PATH } from 'config'
- // 优先执行, 避免引用项目模块时获取环境变量失败
- dotenv.config({ path: DOTENV_PATH })
- import { AppModule } from './app.module'
- async function bootstrap() {
- const app = await NestFactory.create(AppModule)
- // 支持跨域
- app.enableCors()
- await app.listen(9999)
- }
- bootstrap()
复制代码
同样地, 我们可以提供一个 express 实例到 NestFactory.create:
- const server = express();
- const app = await NestFactory.create(ApplicationModule, server);
复制代码
这样我们就可以完全控制 express 实例生命周期, 比如官方 FAQ 中说到的创建几个同时运行的服务器 https://docs.nestjs.cn/5.0/faq?id=https-和多服务器
在我本地开发的时候, 根目录上还有一个. dev.env, 这是未提交到 github 的, 因为里面包含了我个人的 mongodb 远程 ip 地址 其他内容与 github 上的 https://github.com/jackple/showcase/blob/master/.env 一致, 因为我本地并不想再安装一遍 mongodb, 如果是想把项目拉下来就跑起来的, 无论如何你都需要一个 mongodb 服务, 当然你是可以本地安装就好了.
还需要提及到一点就是调试:
以前在 vscode 上调试 node 程序都需要在调试栏新增配置, 然后利用该配置去跑起应用才能实现断点调试, 新版的 vscode 支持 autoAttach 功能, 使用 Command + Shift + P 唤起设置功能面板
启动它!
这样, 在项目的. vscode/setting.json 里面会多了一个选项: "debug.node.autoAttach": "on", 在我们的启动 script 里面加上 --inspect-brk 就可以实现 vscode 的断点调试了. 对应地, npm run start:debug 是我的启动项, 可参考 https://github.com/jackple/showcase/blob/master/nodemon.debug.json
- app.module.ts
- import { Module } from '@nestjs/common'
- import { MongooseModule } from '@nestjs/mongoose'
- import { DB_CONN } from 'config/db'
- import { AppController } from './app.controller'
- import { AppService } from './app.service'
- import modules from 'routers'
- @Module({
- imports: [
- MongooseModule.forRoot(DB_CONN, {
- useNewUrlParser: true,
- }),
- ...modules,
- ],
- controllers: [AppController],
- providers: [AppService],
- })
- export class AppModule {}
复制代码
每个 Nest 应用程序至少有一个模块, 即根模块. 根模块是 Nest 开始安排应用程序树的地方. 事实上, 根模块可能是应用程序中唯一的模块, 特别是当应用程序很小时, 但是对于大型程序来说这是没有意义的. 在大多数情况下, 您将拥有多个模块, 每个模块都有一组紧密相关的功能. 当然, 模块间也可以共享.
概念 | 解释 |
---|---|
providers | 由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享 |
controllers | 必须创建的一组控制器 |
imports | 导入模块所需的导入模块列表 |
exports | 此模块提供的提供者的子集, 并应在其他模块中使用 |
参考 module 的文档 https://docs.nestjs.cn/5.0/modules
AppController 在这个程序当中只是为了测试能返回 Hello World!!!, 其实它不是必须的, 我们可以把它直接干掉, 把全部接口, 全部逻辑放到各个 module 中实现, 以 modules/user 为例, 接着往下看.
modules/user
目录结构
user
dto -------------- 数据传输对象
index.ts --------- UserModule, 概念同 AppModule
controller.ts ---- 传统意义的控制器, `Nest` 会将控制器映射到相应的路由
interface.ts ----- 类型声明
schema.ts -------- mongoose schema
service.ts ------- 处理逻辑
复制代码
有必要讲讲 controller.ts 和 service.ts, 这是 nestjs 的概念中很重要的部分
- controller.ts
- import { Get, Post, Body, Controller } from '@nestjs/common'
- import UserService from './service'
- import CreateDto from './dto/create.dto'
- @Controller('user')
- export default class UserController {
- constructor(private readonly userService: UserService) {}
- @Get()
- findAll() {
- return this.userService.findAll()
- }
- @Post('create')
- create(@Body() req: CreateDto) {
- return this.userService.create(req)
- }
- }
复制代码
装饰器路由为每个路由声明了前缀, 所以 Nest 会在这里映射每个 / user 的请求
@Get()装饰器告诉 Nest 创建此路由路径的端点
同样地, @Post()也是如此, 并且这类 Method 装饰器接收一个 path 参数, 如 @Post('create'), 那么我们就可以实现 post 到路径 / user/create
到此, 往后的逻辑交给 service 实现
- service.ts
- import { Injectable } from '@nestjs/common'
- import { InjectModel } from '@nestjs/mongoose'
- import { Model } from 'mongoose'
- import logger from 'utils/logger'
- import { cryptData } from 'utils/common'
- import ServiceExt from 'utils/serviceExt'
- import { IUser } from './interface'
- import CreateDto from './dto/create.dto'
- @Injectable()
- export default class UserService extends ServiceExt {
- constructor(@InjectModel('User') private readonly userModel: Model<IUser>) {
- super()
- }
- async create(createDto: CreateDto) {
- if (!createDto || !createDto.account || !createDto.password) {
- logger.error(createDto)
- return this.createResData(null, '参数错误!', 1)
- }
- const isUserExist = await this.isDocumentExist(this.userModel, {
- account: createDto.account,
- })
- if (isUserExist) {
- return this.createResData(null, '用户已存在!', 1)
- }
- const createdUser = new this.userModel({
- ...createDto,
- password: cryptData(createDto.password),
- })
- const user = await createdUser.save()
- return this.createResData(user)
- }
- async findUserByAccount(account: string) {
- const user = await this.userModel.findOne({ account })
- return user
- }
- async findAll() {
- const users = await this.userModel.find({})
- return this.createResData(users)
- }
- }
复制代码
至此, 我们运行 npm run start:dev 启动一下服务:
直接在浏览器端访问 http://localhost:9999/#/
没错, 的确失败了!!! 因为我们使用了 jsonwebtoken, 在 https://github.com/jackple/showcase/tree/master/src/modules/auth 可以看到它的实现.
现在我们在 postman 中登录了再试试吧!
- bingo!!!
- (如果是想拉下来跑的话, 也可以照着 schema 的格式用 postman 先伪造条用户数据, 把系统打通!!!)
- client
关于 client 端的实现我不会细讲, 可以看项目 github https://github.com/YDJ-FE/ts-react-webpack , 和我之前的文章(typescript-react-webpack4 起手与踩坑), 项目结构会有改动.
讲一下接入了真实服务器之后 http 请求对于 token 的一些处理, 查看 http.ts https://github.com/YDJ-FE/ts-react-webpack/blob/master/src/services/http.ts
首先是创建 axios 实例时需要在 header 处把 token 带上
- const axiosConfig: AxiosRequestConfig = {
- method: v,
- url,
- baseURL: baseUrl || DEFAULTCONFIG.baseURL,
- headers: { Authorization: `Bearer ${getCookie(COOKIE_KEYS.TOKEN)}` }
- }
- const instance = axios.create(DEFAULTCONFIG)
复制代码
token 也可以存放在 localStorage
另外一点是, 对应服务端返回的 token 错误处理
- const TOKENERROR = [401, 402, 403]
- let authTimer: number = null
- ...
- if (TOKENERROR.includes(error.response.status)) {
- message.destroy()
- message.error('用户认证失败! 请登录重试...')
- window.clearTimeout(authTimer)
- authTimer = window.setTimeout(() => {
- location.replace('/#/login')
- }, 300)
- return
- }
复制代码
总结
两端项目都是简单的模板项目, 不存在什么繁杂的业务, 属于比较初级的学习实践. 对 nestjs 的掌握程度有限, 只是拿来练练手. 可能后续会基于这篇文章继续深入地去讲讲, 比如部署之类的, 两个项目也会不断去维护. 后续也有计划会合二为一. 看时间吧!
来源: https://juejin.im/post/5b89e47f51882542c062651f