koa2+koa-generator+mysql快速搭建nodejs伺服器
阿新 • • 發佈:2021-02-02
# koa2+koa-generator+mysql快速搭建nodejs伺服器
> 用koa的腳手架koa-generator可以快速生成專案骨架,可以用於發開或者測試介面
> https://github.com/hellojinjin123/node-koa2-template
## 1. 全域性安裝koa-generator(不用全域性安裝koa)
專案名字fast-koa
```cmd
npm install koa-generator -g
koa2 fast-koa
cd fast-koa
npm install
```
> 目錄結構如下
-bin // www 專案啟動目錄 node ./www
-public // 靜態網站放置目錄 也就是vue dist程式碼放置的地 專案入口index.html
-routes // 路由
-views // 檢視 伺服器渲染使用的模板
-app.js // 專案入口
-packaga.json
## 2. 啟動專案
```json
// package.json
"scripts": {
"start": "node bin/www",
"dev": "./node_modules/.bin/nodemon bin/www",
"prd": "pm2 start bin/www",
"test": "echo \"Error: no test specified\" && exit 1"
}
```
執行npm run dev開啟伺服器
同時可以看到generator自帶了nodemon(Nodemon 是一款非常實用的工具,用來監控你 node.js 原始碼的任何變化和自動重啟你的伺服器)
如下圖:伺服器啟動了
![avatar1][base64str1]
## 3. 專案入口app.js
```js
// app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const logger = require('koa-logger')
const index = require('./routes/index')
const users = require('./routes/users')
// error handler
onerror(app)
// middlewares
app.use(bodyparser({
enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(logger())
app.use(require('koa-static')(path.resolve(__dirname, config.publicPath))))
app.use(views(__dirname + '/views', {
extension: 'pug'
}))
// logger
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())
// error-handling
app.on('error', (err, ctx) => {
console.error('server error', err, ctx)
});
module.exports = app
```
可以在根目錄路新增config.js 把一些公共的配置放入 比如資料庫資訊,埠,靜態資源路徑等
```js
// config.js
const path = require('path');
const config = {
// 專案啟動監聽的埠
port: 3000,
publicPath: 'public',
logPath: 'logs/koa-template.log',
// 資料庫配置
database: {
HOST: 'xxx', // 資料庫地址
USERNAME: 'xxx', // 使用者名稱
PASSWORD: 'xxx', // 使用者密碼
DATABASE: 'xxx', // 資料庫名
PORT: 3306 // 資料庫埠(預設: 3306)
}
};
module.exports = config;
```
## 4. koa-static 靜態資源中介軟體
`app.use(require('koa-static')(path.resolve(__dirname, config.publicPath))))`
koa-generator已經配置了靜態資源中介軟體,只要放入public目錄,靜態網站就可以執行
瀏覽http://localhost:3000/,伺服器會優先讀取public下的index.html
如果沒有index.html,伺服器會根據路由,判斷'/'是否有內容返回,沒有對應路由則返回404 not found
因為koa-generator預設設定了路由,所以伺服器執行返回了`Hello Koa 2!`
如下:
```js
router.get('/', async (ctx, next) => {
await ctx.render('index', {
title: 'Hello Koa 2!'
})
})
```
## 5. 新增models目錄
相當於伺服器資料層,存放資料庫模型(相當於建表),使用Sequelize進行mysql操作
```js
const { sequelize, Sequelize } = require('../config/db')
const { DataTypes, Model } = Sequelize
class Admin extends Model {
/**
* @description: 新增管理員
* @param {*} username
* @param {*} password
* @return {*} 返回新增的資料
*/
static async createAdmin({ username, password }) {
return await this.create({
username,
password
})
}
/**
* @description: 根據id修改管理員密碼
* @param {*} id
* @return {*} 返回修改的資料
*/
static async updatepwdById({id, password}) {
return await this.update({ password }, {
where: {
id
}
})
}
/**
* @description: 根據id刪除管理員
* @param {*} id
* @return {*}
*/
static async deleteById(id){
return await this.destroy({
where: {
id
}
})
}
}
// 初始化表結構
Admin.init(
{
id: {
type: DataTypes.INTEGER,
allowNull: false, //非空
autoIncrement: true, //自動遞增
primaryKey: true //主鍵
},
username: {
type: DataTypes.STRING,
field: "username",
allowNull: false,
unique: true // 唯一約束 使用者名稱不能重複
},
password: {
type: DataTypes.STRING,
allowNull: false
},
active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
}
}, {
underscored: true, //額外欄位以下劃線來分割
timestamps: true, //取消預設生成的createdAt、updatedAt欄位
createdAt: "created_at",
updatedAt: "updated_at",
freezeTableName: true, // Model 對應的表名將與model名相同
comment: "管理員表類",
// paranoid: true //虛擬刪除
sequelize, // 我們需要傳遞連線例項
// modelName: 'Admin', // 我們需要選擇模型名稱
// tableName: 'Admin' // 表名
}
)
// 建立表格
; (async () => {
await Admin.sync();
console.log("Admin表剛剛(重新)建立!");
// 這裡是程式碼
})()
// 定義的模型是類本身
// console.log(User === sequelize.models.User); // true
module.exports = Admin
```
## 6. mysql資料庫的使用(Sequelize stars 23.6k in github )
Sequelize 是一個基於 promise 的 Node.js ORM, 目前支援 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有強大的事務支援, 關聯關係, 預讀和延遲載入,讀取複製等功能。
Sequelize 遵從 語義版本控制。 支援 Node v10 及更高版本以便使用 ES6 功能。https://www.sequelize.com.cn/core-concepts/model-basics
安裝mysql&sequelize
```cmd
npm install --save mysql mysql2
npm install --save sequelize
```
安裝sequelize之後,在config目錄下建立db.js,進行資料庫連線設定
```js
const Sequelize = require('sequelize');
const db = require('./index').db
// 初始化資料庫
const sequelize = new Sequelize(db.database, db.username, db.password, {
host: db.host,
dialect: 'mysql',
pool: {
max: 5,
min: 0,
idle: 10000
}
})
//測試資料庫連結
sequelize.authenticate().then(function() {
console.log("資料庫連線成功");
}).catch(function(err) {
//資料庫連線失敗時列印輸出
console.error(err);
throw err;
});
module.exports = { sequelize, Sequelize }
```
## 7. 新增controllers目錄
有了模型層對操作資料庫的支援,就可以進行業務操作了,也就是控制器目錄(在這個層可以放心呼叫models層方法進行curd)
```js
// 匯入模型
const Admin = require('../models/admin')
module.exports = {
async getAllAdmins(ctx, next) {
try {
let data = await Admin.findAll()
ctx.body = { msg: 1001, data }
} catch (err) {
ctx.body = { code: -1, msg: 1000, }
}
await next();
},
async createAdmin(ctx, next) {
let req = ctx.request.body
if (!req.username || !req.password) {
ctx.body = { code: -1, msg: 1002 }
return await next();
}
try {
let data = await Admin.createAdmin(req)
ctx.body = { msg: 1003, data }
} catch (err) {
ctx.body = { code: -1, msg: 1000 }
}
await next();
},
async updatepwdById(ctx, next) {
let req = ctx.request.body
if (req.id && req.password) {
try {
await Admin.updatepwdById(req)
ctx.body = { msg: 1004 }
} catch (err) {
ctx.body = { code: -1, msg: 1000 }
}
} else {
ctx.body = { code: -1, msg: 1002 }
}
await next();
},
async deleteById(ctx, next) {
let query = ctx.request.query // 獲取get請求引數
if (query && query.id) {
try {
await Admin.deleteById(query.id)
ctx.body = { msg: 1005 }
} catch (err) {
ctx.body = { code: -1, msg: 1000 }
}
} else {
ctx.body = { code: -1, msg: 1002 }
}
await next();
}
}
```
## 8. 路由配置
```js
// app.js 中新增
// routes
const admin = require('./routes/admin')
app.use(admin.routes(), admin.allowedMethods())
```
到此為止,一個完整的請求(介面)就處理完成了
比如請求 http://localhost:3000/admin/getAllAdmins
koa經歷的簡單過程:
1. 瀏覽器發出請求 -> 中介軟體 ->路由中介軟體 -> 中介軟體 -> 中介軟體若干回撥 -> 瀏覽器收到響應
2. 路由:
- `router.get/post` -> `controllers.func` -> `models.func`-> mysql
- 請求行為 -> 業務邏輯 -> 模型支援 -> 入庫
## 9. 配置自定義的中介軟體處理日誌和response訊息
可以參考github程式碼
## 10. 附上一個很好理解的中介軟體原理的簡析
```js
// 根目錄下test.js
// koa2 中介軟體原理簡析
// 中介軟體的倉庫
const arr = [
async (next) => {
console.log(1);
await next();
console.log(2);
},
async (next) => {
console.log(3);
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(
console.log(4)
);
}, 3000);
}); // 非同步操作 await 會等待後面的promise resolve 後再向下執行
await next();
console.log(5);
},
async (next) => {
console.log(6);
},
async (next) => {
// 不會執行 因為上一個函式中沒有執行next
console.log(7);
await next();
console.log(8);
},
async (next) => {
// 不會執行 因為前面的函式中沒有執行next
console.log(9);
}
];
function fun(arr) {
function dispose(index) {
const currentFun = arr[index];
const next = dispose.bind(null, index + 1);
return currentFun(next); // 尾遞迴
}
dispose(0);
}
fun(arr); // 先列印 1 3 一秒後列印4 6 5 2
```
## 參考連結
- https://www.sequelize.com.cn/
- https://www.cnblogs.com/zjknb/p/12149420.html
[base64str1]:data:image/png;base64,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