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