1

nodejs+koa+Sequelize实践

  • Node.js® 是一个开源、跨平台的 JavaScript 运行时环境。
  • Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。
  • Sequelize是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。
  • pkg可以将 Node.js 项目打包为可执行文件,甚至可以在未安装 Node.js 的设备上运行。

基本架构

image.png

model层:数据持久化,并提共数据处理,持久化操作接口
control层:业务模块流程控制,调用service层接口
service层:业务操作实现类,调用model层接口**

中间件

中间件作用特点
koa-static静态资源路径
koa-logger日志打点中间件
koa2-cors跨域处理
koa-jwt主要提供路有权限控制的功能,它会对需要限制的资源请求进行检查
koa-body是一个可以帮助解析 http 中 body 的部分的中间件,包括 json、表单、文本、文件等。
SequelizeSequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。
koa-routerkoa 的一个路由中间件,它可以将请求的URL和方法(如:GET 、 POST 、 PUT 、 DELETE 等) 匹配到对应的响应程序或页面

数据库相关

  1. 数据库配置

    const config = {
     database: "data_base", // 数据库名称
     username: "root", // 用户名
     password: "xxxx", // 密码
     host: "8.16.23.x", // 主机地址
     port: "3306", // 端口号
     dialect: "mysql", //数据库类型,支持: 'mysql', 'sqlite', 'postgres', 'mssql'
     // logging: true, // 是否启用日志
    }
  2. 数据库链接

    const config = require('./dbconfig') // 引入数据库配置信息
    const { Sequelize, DataTypes ,Op} = require("sequelize") // 引入sequelize依赖
    
    const sequelize = new Sequelize(
     config.database,
     config.username,
     config.password,
     {
       dialect: config.dialect,
       dialectOptions: {
         dateStrings: true,
         typeCast: true
       },
       host: config.host,
       port: config.port,
       logging: config.logging,
       pool: { // 连接池配置
         min: 0, // 最小连接数
         max: 5, // 最大链接数
         idle: 30000,
         acquire: 60000,
       },
       define: {
         // 字段以下划线(_)来分割(默认是驼峰命名风格)
         underscored: true
       },
       timezone: '+08:00'
     }
    )

    3、定义表结构

    const { sequelize, DataTypes } = require("../config/connect")
    const alliances = sequelize.define(
     "aw_table",
     {
       id: {
         type: DataTypes.INTEGER(11),
         allowNull: false, // 是否允许为空
         autoIncrement: true,
         primaryKey: true, // 是否主键
       },
       title: {
         type: DataTypes.STRING,
         allowNull: false,
         comment: '名称'
       },
     }, {
       timestamps: true,
       
       // 不想要 createdAt
       createdAt: 'create_time',
       
       // 想要 updatedAt 但是希望名称叫做 updateTimestamp
       updatedAt: 'update_time'
     }
    )
    // alliances.sync({force:true}) // 是否自动创建表

服务层

1、业务操作实现类,调用model层接口

const { daoModel } = require("../model/index");

class Service {
    async getList(params) {
        const { pagenum = 1, pagesize = 10 } = params;
        return daoModel.findAndCountAll({
            limit: parseInt(pagesize),
            // 跳过实例数目
            offset: (pagenum - 1) * parseInt(pagesize),
        });
    }
    async getDetail(id) {
        return daoModel.findOne({
            where: {
                id: id,
            },
        });
    }
}

module.exports = new Service();

控制层

1、control层:业务模块流程控制,调用service层接口

const Service = require("../services/newsService");
module.exports = {
    getList: async (ctx) => {
        const params = ctx.query;
        const { count, rows } = await Service.getList(params);
        if (count > 0) {
            const list = [];
            for (let i = 0; i < rows.length; i++) {
                const data = rows[i].dataValues;
                list.push(data);
            }
            ctx.body = {
                status: 200,
                msg: "获取成功",
                data: {
                    list: list,
                    total: count,
                    limit: 1,
                },
            };
        } else {
            ctx.body = {
                status: "error",
                msg: "获取失败",
                data: null,
            };
        }
    },
    getDetail: async (ctx) => {
        const matchArr = ctx.url.split("/");
        const id = matchArr[matchArr.length - 1];
        // 截取出文章详情ID
        const result = await Service.getDetail(id);
        if (result) {
            ctx.body = {
                status: 200,
                msg: "获取成功",
                data: {
                    detail: result.dataValues,
                },
            };
        }
    },
};

接口路由

1、服务模块化

 const Router = require('koa-router')
 const router = new Router()
 const Controller = require('../../controllers/admin/fileController')
 const routers = router
   .post('/alliance', Controller.add)
   .get('/alliance', Controller.getList)
   .get('/alliance/:id', Controller.getDetail)
   .delete('/alliance/:id', Controller.delete)
   .put('/alliance/:id', Controller.update)
 module.exports = routers

2、服务路由入库

const Router = require('koa-router')
const router = new Router()

const Routes = require('./routes')
const upload = require('./upload')
const article = require('./article')

router.use(article.routes(), article.allowedMethods())
router.use(article.routes(), article.allowedMethods())

module.exports = router

权限处理

const { varifyToken } = require('../utils/utils')
const RolesService = require('../services/admin/role/rolesService')
module.exports = function () {
  return async (ctx, next) => {
    const url = ctx.path
    // 对前端展示、登录、注册等路由进行放行
    if (url.substring(0, 11) === '/api/v1/web'
      || url === '/api/v1/admin/login'
      || url === '/api/v1/admin/register'
      || url === '/api/v1/admin/logout') {
      await next()
    } else {
      // 判断headers 中是否存在 authorization
      if (ctx.headers && ctx.headers.authorization === undefined) {
        ctx.status = 401
        ctx.body = {
          status: 401,
          msg: '无效token,没有访问权限'
        }
      } else {
        try {
          // 若存在,验证 token 是否等于当前登录用户的用户名,等于的话,再判断此用户的角色表中的 permission 字段
          // 是否存在 ctx.url ,是的话 next(),否则未授权
          // 在else中再深入判断它是否能够访问该接口的权限就是啦{验证token,判断用户是否有权限能访问此接口路径}
          const token = ctx.headers.authorization
          // 解密token
          const payload = await varifyToken(token)
          const userInfo = payload.userInfo
          // roles:['管理员'],转为字符串
          const roleName = userInfo.roles.toString()
          const result = await RolesService.getRolePermission(roleName)
          const permissionApi = []
          for (let i = 0; i < result.length; i++) {
            const tmp = {
              // 拼接api路径
              path: '/api/v1/admin' + result[i].path,
              method: result[i].method
            }
            permissionApi.push(tmp)
          }
          // console.log(permissionApi)
          const res = permissionApi.filter(item => {
            const index = item.path.indexOf(':')
            if (index !== -1) {
              // console.log(index)
              // 根据 :id等 动态拼接api路径
              item.path = item.path.substring(0, index) + `${ctx.url.substring(index)}`
              // console.log('index: '+index+' '+item.path)
            }
            // 过滤出当前访问的api接口
            return new RegExp(item.path, 'g').test(ctx.url) && item.method.toUpperCase() === ctx.request.method.toUpperCase()
          })
          // 返回当前访问的api接口列表
          // console.log(res)
          if (res.length === 0) {
            ctx.status = 401
            ctx.body = {
              code: 401,
              msg: '您的用户没有该访问权限!'
            }
          } else {
            await next()
          }
        } catch (err) {
          // 捕获 jwt 的异常信息
          if (err.message === 'jwt expired') {
            ctx.status = 50014
            ctx.body = {
              code: 50014,
              msg: 'token 过期'
            }
          } else if (err.message === 'jwt malformed') {
            ctx.status = 50008
            ctx.body = {
              code: 50008,
              msg: 'token 无效'
            }
          } else {
            ctx.status = 500
            ctx.body = {
              code: 500,
              msg: err.message
            }
          }
        }
      }
    }
  }
}

pkg打包

1、相关配置

 pkg [options] <input>

 Options:

   -h, --help           output usage information
   -v, --version        output pkg version
   -t, --targets        comma-separated list of targets (see examples)
   -c, --config         package.json or any json file with top-level config
   --options            bake v8 options into executable to run with them on
   -o, --output         output file name or template for several files
   --out-path           path to save output one or more executables
   -d, --debug          show more information during packaging process [off]
   -b, --build          donot download prebuilt base binaries, build them
   --public             speed up and disclose the sources of top-level project
   --public-packages    force specified packages to be considered public
   --no-bytecode        skip bytecode generation and include source files as plain js
   -C, --compress       [default=None] compression algorithm = Brotli or GZip

 Examples:

 – Makes executables for Linux, macOS and Windows
   $ pkg index.js
 – Takes package.json from cwd and follows 'bin' entry
   $ pkg .
 – Makes executable for particular target machine
   $ pkg -t node14-win-arm64 index.js
 – Makes executables for target machines of your choice
   $ pkg -t node12-linux,node14-linux,node14-win index.js
 – Bakes '--expose-gc' and '--max-heap-size=34' into executable
   $ pkg --options "expose-gc,max-heap-size=34" index.js
 – Consider packageA and packageB to be public
   $ pkg --public-packages "packageA,packageB" index.js
 – Consider all packages to be public
   $ pkg --public-packages "*" index.js
 – Bakes '--expose-gc' into executable
   $ pkg --options expose-gc index.js
 – reduce size of the data packed inside the executable with GZip
   $ pkg --compress GZip index.js

1、 pkg .,意思就是它会寻找指定目录下的package.json文件,然后再寻找bin字段作为入口文件。
2、-t 用来指定打包的目标平台和Node版本,如-t node12-win-x64,node12-linux-x64,node12-macos-x64,可以同时打包3个平台的可执行程序;
3、--out-path 用来指定输出的目录地址;后面的"=dist/"就是指定的目录地址,也可以这样写"--out-path dist/",用空格替代"="

相关配置

"bin": "./app.js",
    "pkg": {
        "assets": [
            "static/**/*"
        ],
        "targets": [
            "node16"
        ],
        "outputPath": "dist"
    }

测试pkg

image.png

image.png


西安小哥
1.3k 声望88 粉丝

thinking、doing、do better、do much better than today。exchange 、sharing、improve as quickly as possible。