node+ts 入门小实例

2,248 阅读7分钟

前言

对于正在学习前端的我,相信很多的同学也跟我一样,特别怕与数据库打交道,但写项目是无法避免数据的存储,使用到数据库的。所以我们还是要撸起袖子加油干,不仅要做干饭人,还要做个光荣的打工人。通过下面这个实例,希望可以帮助你对node的理解,试着自己做一个小实例,会发现数据库也没有那么可怕啦。

image.png

MVC 概念

官方概念:M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。

下面这张图可以理解的更清楚

image.png

数据库是独立于应用程序之外的,数据库模块比较复杂,有一个专门的端口,MySQL的默认端口:3306

下面这张图来看一下从用户到数据库中数据的走向流程:

image.png

启动服务器

首先启动一个项目,安装:

yarn add typescript tsc-watch
yarn add express @type/express

1.在项目下创建一个src文件夹,在src文件夹中新建一个main.ts 文件

//项目的启动

import app from './app' //模块化的极致就是一个文件只做一件事


//APP 路由,上传文件,json化....  将给app 去做
//数据库的连接
app.listen(1234,()=>{
    console.log('服务器已经启动')
})

2. 在src文件夹创建子文件夹app,app文件夹中新建index.ts

// APP的创建以及基本配置  中间件
// es6 module typescript => js
import express, { urlencoded } from 'express'

const app = express()
export default app

3.打开终端启动项目

image.png

controller设置restful路径

restful定义:

REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。

客户端使用GET、POST、PUT、DELETE4个表示操作方式的动词对服务端资源进行操作。这里刚好讲一下POST和PUT的区别:
POST:用来新建资源,也可以更新资源。
PUT:只能用来更新资源

单点入口:

从main.js 进入->app->post
在APP中的index.js 启用路由中间件

  • 代码如下
// APP的创建以及基本配置  中间件
// es6 module typescript => js
import express, { urlencoded } from 'express'
import postRouter from '../post/post.router'
import {defaultErrorHandler} from './app.middleware'
import userRouter from '../user/user.router'

const app = express()
const bodyParser = require('body-parser')
// app 处于伺服状态eventEmitter
// 等
//body 加中间件  bodyParser 
app.use(bodyParser.urlencoded())

// 中间件来打理  函数
//所有的路由都在这里汇集
app.use( //函数
    //文章模块的路由 增删改查
    postRouter,
    userRouter,
    //用户路由
    //.....
)
//处理各种错误
app.use(defaultErrorHandler)

export default app

express 中的next() 方法

当我们在express 中间件中定义一个next() 参数,next()一般是第三个参数,作用是把控制权交给中间件,只有中间件请求完成,才会执行下面的请求,否则请求被挂起,后面定义的中间件没有执行的机会。具体看下面这段代码:

export const authGard =(request:Request,response:Response,next:NextFunction)=>{
  next(new Error('UNAUTHORIZED'))
}

剖析具体代码

我们先在每一个实现功能的文件夹分四个业务模块分别完成四个功能,分别是:router、middleware、controller、service。通过下面这种图,更加清晰的表示目前项目中的文件。

image.png

app 模块

  • app.config.ts
    首先在app 文件夹下的app.config.ts中配置MySQL中需要用到的字段,我们使用了.env,让代码更安全,譬如上传带GitHub时不会让别人拿到数据库的账号和密码;
import dotenv from 'dotenv'
dotenv.config()
// nginx
export const {APP_PORT}=process.env
export const {
    MYSQL_HOST,
    MYSQL_PORT,
    MYSQL_USER,
    MYSQL_PASSWORD,
    MYSQL_DATABASE
  
} = process.env
  • app->database->mysql.ts 写入启动服务器需要用到的数据信息,并且输出模块 connection
    import mysql from 'mysql2' //mysql 配置
      import {
          MYSQL_HOST,
          MYSQL_PORT,
          MYSQL_USER,
          MYSQL_PASSWORD,
          MYSQL_DATABASE
      } from '../app.config'
      export const connection = mysql.createConnection({
          host:MYSQL_HOST,
          port:parseInt(MYSQL_PORT),
          user:MYSQL_USER,
          password:MYSQL_PASSWORD,
          database:MYSQL_DATABASE
      })
    
  • app.middleware.ts
    我们要一个中间件,在启用MySQL时可能发生的错误做出反应的模块。
    import {Request,Response,NextFunction} from 'express'
    
    export const defaultErrorHandler =(
      error:any,
      request:Request,
      response:Response,
      next:NextFunction
      )=>{
        if(error.message){
            console.log(error.message,'----------')
        }
        let statusCode:number,message:string;
        /**
         * 处理异常
         */
        switch(error.message){
              case 'NAME_IS_REQUIRED':
                  statusCode=400;
                  message='请提供用户名';
                  break;
              case 'PASSWORD_IS_REQUIRED':
                  statusCode=400;
                  message="请输入密码";
                  break;
              case 'USER_ALREADY_EXSIT':
                  statusCode=409;
                  message="用户名已被占用";
                  break;
              case 'USER_DOES_NOT_EXIT':
                  statusCode=400;
                  message="用户名不存在";
              case 'UNAUTHORIZED':
                  statusCode=401;
                  message="请先登录";
              case 'USER_DOES_NOT_OWN_RESOURCE':
                  statusCode=403;
                  message="你不能处理内容";
                  break;
              case 'NOT_FOUND':
                  statusCode=404;
                  message="没找到";
    
          default:
              statusCode=500;
              message='运维小哥哥正在....'
              break;
    }
    response.status(statusCode).send({message})
    }
    
    注意: 这里就用到了 next()方法,当发生错误时,不会进行下面的操作,并且会返回错误信息,这里我们需要对状态码比较熟悉。

post 模块

这个模块负责的业务是:发送文章,用户发送文章存入数据库中。

  • post.router.ts

//文章模块的路由
import express from 'express'
import * as postController from './post.controller'
// import {authGard} from './auth/auth.middleware'
import {authGard} from '../auth/auth.middleware'
const router= express.Router()
// GET 获得


/**
 * 
 * 创建内容
 */
//检查有没有登录  authGard:检测身份登录  next 
router.post('/posts',authGard,postController.store)

/**
 * 
 *获取文章列表
 */
// router.get('./posts')
export default router
  • post.model.ts 对数据库的模型定义,进行类型约束,
export class PostModel {
    id?:number;
    title:string;
    content?:string

}
  • post.controller.ts
import { Request,Response} from 'express'  //@types/express
import { createPost } from './post.service'

export const store = async(
    request:Request,
    response:Response
    )=>{
        console.log(request.body,'//////////')
        const { title,content  }=request.body
        // 存 -> 数据存储服务service
        const data=await createPost({title,content})
    response.status(201).send('保存成功')    
}
  • post.service.ts
import {PostModel} from './post.model'
import {connection} from '../app/database/mysql'

/**
 * 新增文章
 */

export const createPost = async(post:PostModel)=>{
    const statement=`
    INSERT INTO post 
    SET ?
    `
    console.log(statement);
    //每次用户来访问 走数据库
    //保存操作  
    // list select redis 
    const [data] =await connection.promise().query(statement,post)
    return data
}
  • MySQL中查看数据存储成功 image.png

user 模块

这个业务模块我们需要做的是:

  1. 当用户创建一个账号,我们要查看数据库中是否已经创建过了如果该用户已经存在,就不能创建,并且告知用户。
  2. 需要保护用户的隐私,将用户的密码进行加密,加密后存入数据库中。

bcrypt

Bcrypt是单向Hash加密算法,不可反向破解生成明文。

  • user.router.ts 引入 /user.middleware/user.controller 操作。
import express from 'express'

import {
    validataUserData,
    hashPassword
} from './user.middleware'
import * as userController from './user.controller'
const router = express.Router()
/**
 *创建用户
 */
router.post('/users',validataUserData,hashPassword, userController.store)

export default router
  • user.middleware.ts 该模块的功能:检测是否有存在的用户名,不存在则可以注册。这里面使用了 bcrypt 对密码进行加密。需要在终端进行安装,指令:yarn add bcrypt
import {Request,Response,NextFunction, request} from 'express'
import * as userService from './user.service'
import bcrypt from 'bcrypt'

export const hashPassword =async(
    request:Request,
    response:Response,
    next:NextFunction
)=>{
    const {password}= request.body
    request.body.password =await bcrypt.hash(password,10)
    next()
}

/**
 * 
 * 验证用户信息
 */
export const validataUserData = async(
    request:Request,
    response:Response,
    next:NextFunction
)=>{
 // 1. 验证数据合法
    // 1.1 用户名为空
    // 1.2 密码为空
    // 重名

    console.log('验证用户数据')
    const {name,password}=request.body
    // console.log(name,password)
    //如果用户名为空,交给处理为空的错误处理函数去做
    if(!name){
        next(new Error('NAME_IS_REQUIRED'))
    }
    if(!password){
        next(new Error('PASSWORD_IS_REQUIRED'))
    }
    
    
    //验证用户名是否存在
    const user = await userService.getUserByName(name)
    if(user) return next(new Error('USER_ALREADY_EXSIT'))
    next()
}
  • user.controller.ts 该模块功能:要判断用户名或密码不能为空,如果为空绝对不能存的,会带来数据库的问题,这个文件相当于一个控制器,从用户端去管理数据库。
import {Response,Request,NextFunction} from 'express'
import * as userService from './user.service'
export const store =async( 
    request:Request,
   response:Response,
    next:NextFunction

    )=>{
    const {name,password}= request.body
    console.log(name,password)
    try{
        const data=await userService.createUser({
            name,
            password
        })
        response.status(201).send(data)
    }catch(error){
        console.log('有错误',error.message)
    }

}
  • user.service.ts 该模块功能:连接数据库进行存入数据。
import { connection } from '../app/database/mysql';
/**
 * 创建用户
 */
 export const createUser=async(user)=>{
    const statement = `
    INSERT INTO user
    SET ?
    `
    const [data]= await connection.promise().query(statement, user);
    return data
 }

export const getUser = (condition: string) => {
  return async (param:string | number)=> {
    // console.log(param, '-----');
    const statement = `
      SELECT
        user.id,
        user.name
      FROM
        user
      WHERE
        ${condition} = ?
    `;

    const [data]= await connection.promise().query(statement, param);
    console.log(data);
    return data[0]?data[0]:null
  }
}

export const getUserByName = getUser('user.name');
export const getUserById = getUser('user.id');
  • 用户名和密码存储成功,并且对密码进行了加密

image.png

总结

在这个实例中把功能细分,每一个功能分成许多的小模块,去完成每一个模块应该完成的业务,通过画思维导图去理清楚思路。这里面使用ts会让代码更加的严谨,不容易出错误,node+ts的学习是许多大厂要求的,也是我们要学习的重点和难点。如果有写的不好的地方,欢迎指出,一起学习呀。
本人大三学生正在准备春招,有一起学习的小伙伴欢迎加WX:chen0105123

image.png