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 的设备上运行。
基本架构
model层:数据持久化,并提共数据处理,持久化操作接口
control层:业务模块流程控制,调用service层接口
service层:业务操作实现类,调用model层接口**
中间件
:
中间件 | 作用特点 |
---|---|
koa-static | 静态资源路径 |
koa-logger | 日志打点中间件 |
koa2-cors | 跨域处理 |
koa-jwt | 主要提供路有权限控制的功能,它会对需要限制的资源请求进行检查 |
koa-body | 是一个可以帮助解析 http 中 body 的部分的中间件,包括 json、表单、文本、文件等。 |
Sequelize | Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。 |
koa-router | koa 的一个路由中间件,它可以将请求的URL和方法(如:GET 、 POST 、 PUT 、 DELETE 等) 匹配到对应的响应程序或页面 |
数据库相关
数据库配置
const config = { database: "data_base", // 数据库名称 username: "root", // 用户名 password: "xxxx", // 密码 host: "8.16.23.x", // 主机地址 port: "3306", // 端口号 dialect: "mysql", //数据库类型,支持: 'mysql', 'sqlite', 'postgres', 'mssql' // logging: true, // 是否启用日志 }
数据库链接
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。