【问题探讨】web前端小伙入坑nodejs实践

本文讲述有一定web前端基础的小伙伴,如何入坑nodejs。

项目环境:nodejs V12.x,egg V2.x

先列一下大纲:

一,通读nodejs文档
二,通读eggjs文档
三,搭建项目框架
四,文件目录约定
五,部署
六,debug调试方式
七,环境配置
八,参数校验
九,异常捕获
十,状态code拼装
十一,跨域配置

下面看一下正文:

一,通读nodejs文档
http://nodejs.cn/learn
二,通读eggjs文档
https://www.eggjs.org/zh-CN/index
三,搭建项目框架

$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i

四,文件目录约定

demo // 项目根目录
├── app.js // 统一的入口文件
├── package.json // 项目npm配置
├── app
|   ├── router.js // 用于配置 URL 路由规则
│   ├── controller // 用于解析用户的输入,处理后返回相应的结果(本工程约定为和接口一一对应)|   └── home.js
│   ├── service //  用于编写业务逻辑(比如用户service,则用户的增删改查,都放在这个service中)|   └── user.js
│   ├── middleware // 用于编写中间件|   └── formatResponseBody.js
│   ├── schedule (待启用) // 用于定时任务|   └── my_task.js
│   ├── public (待启用) // 用于放置静态资源|   └── reset.css
│   ├── view (待启用) // 用于放置模板文件|   └── home.tpl
│   └── extend (待启用) // 用于框架的扩展
│       ├── helper.js (待启用)
│       ├── request.js (待启用)
│       ├── response.js (待启用)
│       ├── context.js (待启用)
├── config
|   ├── plugin.js // 用于配置需要挂载的插件
|   ├── config.default.js // 默认的配置文件(开发环境的默认配置文件)
│   ├── config.prod.js // 生产环境特殊配置
|   ├── config.stg.js // 测试环境特殊配置
|   └── config.unittest.js // 单元测试环境特殊配置
└── test // 单元测试
    ├── middleware // (待启用)
    |   └── response_time.test.js
    └── controller // (待启用)
        └── home.test.js

五,部署
nodejs不需要编译,部署就是把本地工程压缩上传到服务器上,解压运行在nodejs环境中,就好了

手动部署过程参考:
1,本地文件压缩
2,登陆服务器,上传压缩文件到服务器
 通过ssh工具,MobalXterm或者ZenTermLite等
3,docker hub上搜索node镜像,选择需要的nodejs版本
 docker search node
5,拉取镜像,如果没有特别要求,可以直接拉取latest版本
 docker pull node:latest
6,建立并启动容器
 docker run -itd --name web-nodejs-server -p 8080:80 node:latest
 解释:
 a,-i 以交互模式运行容器,通常与 -t 同时使用;
 b,-t 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
 c,-d 后台运行容器,并返回容器ID;
 d,name 容器名称
 e,-p 8080:80: 端口进行映射,将本地 8080 端口映射到容器内部的 80 端口。
 f,最后是使用的 镜像名称
7,进入容器
   docker exec -it web-nodejs-server /bin/bash
8,建立项目运行文件夹
  cd /usr/src/
  mkdir server
9,退出容器,将项目文件全部转移到 以上目录中
  exit
  docker cp ./askbob-web-node-server web-nodejs-server:/usr/src/
10,进入容器,启动服务
  docker exec -it web-nodejs-server /bin/bash
  npm run start

六,调试方式

开启 VSCode 配置 Debug:
Auto Attach开启为on,然后在 Terminal 执行 npm run debug 即可。
操作步骤:
(1)vscode—setting---搜索 attach
(2)Debug › Node: Auto Attach
(3)开启为on
(4)重启vs code
(5)npm run debug

七,环境配置

// 运行开发环境
// 环境配置config.default.js, 环境标示app.config.env=local
$ npm run dev

// 运行调试环境, vs code断点有效,
// 环境配置config.default.js, 环境标示app.config.env=local
$ npm run debug 

// 运行测试环境,环境配置config.stg.js, 环境标示app.config.env=stg
$ npm run stg 

// 运行生产环境,环境配置config.prod.js, 环境标示app.config.env=prod
$ npm run prod 

├── config
|   ├── plugin.js // 用于配置需要挂载的插件
|   ├── config.default.js // 默认的配置文件(开发环境的默认配置文件)
│   ├── config.prod.js // 生产环境特殊配置
|   ├── config.stg.js // 测试环境特殊配置
|   └── config.unittest.js // 单元测试环境特殊配置

八,参数校验:使用egg-validate插件

// 安装依赖包
  npm i egg-validate

// 装载插件 路径:/config/plugin.js
  validate: {
    enable: true,
    package: 'egg-validate',
  },
// 配置插件 路径:/config/config.default.js
  config.validate = {
    // 会对入参进行转换
    // 举个例子,使用表单中默认的 submit 类型按钮提交表单时,
    // 提交过来的往往是序列化后的字符串,那些期望是数字类型的字段就会始终验证不过。
    // 而开启此项后,会对入参按希望的类型进行转换。
    convert: true,
    // 开启后,会把空字符串,NaN,null 这些转成 undefined,
    // 将这些异常的数据进行了统一,方便后续处理。
    widelyUndefined:true
    // 限制被验证值必须是一个对象
    // validateRoot: false,
  }
// 使用参数校验插件 路径: /app/controller/*.js
    // 定义参数校验规则
    const validateRules = {
      channel: {
        type: 'string',
        required: true,
        min: 3,
        max: 3,
      },
      // string?---字符串类型,可选,如果必须,去掉?
      phone: 'string?',
      eventId: 'string?',
    }
    // 对ctx.query中的参数进行校验 
    const errors = this.app.validator.validate(validateRules, ctx.query);
    if (errors) {
        // 如果校验失败,在这里做一些处理
    }

九,异常捕获
参考:https://www.eggjs.org/zh-CN/core/error-handling
(1)使用try catch
(2)框架层统一异常处理___项目自带的onerror插件
(3)onerror 插件支持自定义配置错误处理方法,本项目配置见如下代码

// 配置插件 路径:/config/config.default.js
  config.onerror = {
    all(err, ctx) {
      // 吸收程序中所有未catch的err,并在body中返回错误堆栈信息
      ctx.body = err.stack;
      ctx.status = 500;
    }
  }

十,状态code拼装___使用自定义的formatResponseBody中间件
说明:
(1) Egg 是基于 Koa 实现的,所以 Egg 的中间件形式和 Koa 的中间件形式是一样的,都是基于洋葱圈模型。每次我们编写一个中间件,就相当于在洋葱外面包了一层。
(2) 简单理解:以router中的使用为例,await next() 前面的代码在进入controller之前执行,await next()后面的代码在从controller回来之后执行;
参考文章:
(1) koa2中间件机制-洋葱圈模型 https://www.cnblogs.com/senjer/p/10621980.html
(2) nodejs中的中间件是什么意思
https://www.php.cn/website-design-ask-483598.html

// 使用方式1:路由中为单个接口配置
// 使用中间件 路径:/app/router.js
module.exports = app => {
  const { router, controller, middleware } = app;
  ......
  router.post(
    '/askbob-admin/updateTrackWhitelist', 
    middleware.formatResponseBody(),
    controller.updateTrackWhitelist.index);
};

// 使用方式2:全局使用
// 使用中间件 路径:config.default.js
// 数组顺序即为中间件的加载顺序
config.middleware = ['formatResponseBody'];

// 编写中间件 路径:/app/middleware/formatResponseBody.js
module.exports = () => {
  return async function formatResponseBody(ctx, next) {
    await next();
    // 定义生成searchI的方法
    function createRequestId(length) {
      let str = ''
      const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
      for (let i = 0; i < length; i++) {
        const pos = Math.round(Math.random() * (arr.length - 1))
        str += arr[pos]
      }
      return str
    }
    // 定义返回状态配置
    // 配置说明:配置同java服务端,目前使用了200和400
    const responseStatusMap = {
      'SUCCESS': {
        code: '200', 
        msg: '接口请求成功!'
      },
      'INVALID_PARAM': {
        code: '400', 
        msg: '字段校验非法!'
      },
      'INVALID_REQUEST_MSG': {
        code: '402', 
        msg: '请求数据非法!'
      },
      'SERVICE_NOT_FOUND': {
        code: '404', 
        msg: '服务未发现!'
      },
      'RUNTIME_EXCEPTION': {
        code: '500', 
        msg: '运行时异常!'
      },
      'BUSINESS_PROCESS_FAILED': {
        code: '501', 
        msg: '服务器繁忙!'
      },
      'GATEWAY_EXCEPTION': {
        code: '502', 
        msg: '网关繁忙!'
      },
      'CLIENT_ABORT': {
        code: '502', 
        msg: '请求超时!'
      },
      'INVALID_VIRTUAL_USER': {
        code: '940304', 
        msg: '非法虚拟用户!'
      },
      'INVALID_REQUEST': {
        code: '940405', 
        msg: '请求方式错误!'
      },
      'DIRECT_REQUEST_NOT_SUPPORT': {
        code: '940428', 
        msg: '不允许直接访问!'
      },
      'FLOW_LIMIT': {
        code: '950429', 
        msg: 'Blocked by Sentinel (flow limiting)!'
      }
    }
    // 依据status获取配置
    const responseStatus = responseStatusMap[ctx.body.status]
    // 将返回状态配置和requestId组装入返回体中
    // 右侧ctx.body内容约定:SUCCESS状态下controller/service返回data信息,其他状态下返回errors信息
    ctx.body = Object.assign(ctx.body, responseStatus, {requestId: createRequestId(19)})
  };
};

十一,跨域配置

// 安装依赖包
  npm i egg-cors

// 装载插件 路径:/config/plugin.js
  cors: {
    enable: true,
    package: 'egg-cors',
  },
// 配置插件 路径:/config/config.default.js
// 简单请求和复杂请求,参考博文 https://www.cnblogs.com/goloving/p/14525157.html
// 简单请求,只要设置如下相应头即可
// 1,origin: '*',
// 2,allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
// 复杂请求,必须指定origin为某个具体的请求源,以及处理其他导致请求成为复杂请求的问题
// 管理后台项目请求,有如下请求变动,导致变为复杂请求,需要处理
// 1,request header新增/改写Authorization和Content-Type,
// 所以需要明文指定allowHeaders: 'Authorization, Content-Type',
// 2,withCredentials: true 设置了携带cookie,所以响应需要设置credentials: true,
  config.cors = {
    origin:'http://127.0.0.1:8013',
    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
    allowHeaders: 'Authorization, Content-Type',
    credentials: true,
    // 设置max age,浏览器端会进行缓存,没有过期之前真对同一个请求只会发送一次预检请求
    maxAge: 86400,
  }

后面的计划(完成后 会继续更新本文):

1,测试用例编写
2,登陆认证

你可能感兴趣的:(问题探讨,前端,javascript,node.js)