真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

怎么搭建Koa后端項(xiàng)目腳手架

這篇文章主要介紹怎么搭建Koa后端項(xiàng)目腳手架,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

專注于為中小企業(yè)提供成都做網(wǎng)站、網(wǎng)站設(shè)計(jì)、外貿(mào)營銷網(wǎng)站建設(shè)服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)左云免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了超過千家企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。

一、背景

結(jié)合當(dāng)前的node比較火的三大框架,Express、Koa、egg。筆者之前用的Express,后面發(fā)現(xiàn)回調(diào)把我搞死了,實(shí)在太無奈了。終于有一天去嘗試了Koa來進(jìn)行開發(fā),發(fā)現(xiàn)實(shí)在太舒服了。ES6語法支持很多,同步模式也很到位,但是在學(xué)習(xí)koa的過程中,發(fā)現(xiàn)基本的基礎(chǔ)知識都了解了,也按照官方的文檔走了一遍,但發(fā)現(xiàn)好像無從下手。感覺開發(fā)過程中,分層不太明顯,業(yè)務(wù)邏輯簡單還好,一多麻煩就來了。查看了資料后,有一個(gè)koa的腳手架叫做 koa-generator ,馬上嘗試后發(fā)現(xiàn)不是我想要的模板??磥韌ithub已經(jīng)有2年沒有維護(hù)了,koa2一些新特性也沒有加上,感覺有點(diǎn)快落伍了。于是結(jié)合其他人的模式,也避免后面自己過多的重復(fù)造輪子。編寫一個(gè)Koa項(xiàng)目的初始模板。這個(gè)模板主要的功能集成了Logger、Router、JWT、Mongoose、redis、PM2等模塊,還有部分的中間件集合,該模板對于簡單的后臺項(xiàng)目來說基本夠用了,沒有考慮高并發(fā)處理,后期會繼續(xù)完善。對于初學(xué)者來說,可以快速的新項(xiàng)目開發(fā),在開始之前先好好看下面的解讀。

二、目錄結(jié)構(gòu)

下面的目錄是該模板基礎(chǔ)目錄結(jié)構(gòu),后面的章節(jié)會對每一個(gè)目錄的配置進(jìn)行介紹,讓大家在開發(fā)中對項(xiàng)目的結(jié)構(gòu)比較清晰,出了問題容易定位。

├─.gitignore  // 忽略文件配置
├─app.js   // 應(yīng)用入口
├─config.js   // 公共配置文件
├─ecosystem.config.js // pm2配置文件
├─package.json  // 依賴文件配置
├─README.md   // README.md文檔
├─routes   // 路由
| ├─private.js  // 校驗(yàn)接口
| └public.js   // 公開接口
├─models   // 數(shù)據(jù)庫配置及模型
| ├─index.js   // 數(shù)據(jù)庫配置
| └user.js   // 用戶的schema文件
├─middlewares  // 中間件
| ├─cors.js  // 跨域中間件
| ├─jwt.js   // jwt中間件
| ├─logger.js  // 日志打印中間件
| └response.js  // 響應(yīng)及異常處理中間件
├─logs   // 日志目錄
| ├─koa-template.log
| └koa-template.log-2019-05-28
├─lib   // 工具庫
| ├─error.js   // 異常處理
| └MongoDB.js   // mongoDB配置
├─controllers  // 操作數(shù)據(jù)庫及業(yè)務(wù)邏輯
| ├─index.js  // 配置
| ├─login.js  // 登錄
| └test.js   // 測試
├─bin   // 啟動目錄
| └www    // 啟動文件配置

怎么搭建Koa后端項(xiàng)目腳手架

bin文件

bin文件目錄中,只有一個(gè)文件,即為www,因?yàn)槲覀兒蠖说捻?xiàng)目基本上是在Linux上進(jìn)行運(yùn)行的,其實(shí)我們不必去擔(dān)心文件的后綴是什么,只需知道該文件是可執(zhí)行文件還是不可執(zhí)行文件就行了。這個(gè)文件有什么用呢?其實(shí)我們這個(gè)文件是用來部署的時(shí)候可以啟動我們一整個(gè)后端程序,也就是我們前端中的集成的運(yùn)行環(huán)境。我們的運(yùn)行、關(guān)閉、重啟都在這文件進(jìn)行即可?;敬a如下:

#!/usr/bin/env node

/**
 * Module dependencies.
 */

const app = require('../app')
const http = require('http')
const config = require('../config')

/**
 * Get port from environment and store in Express.
 */

const port = normalizePort(process.env.PORT || config.port)
// app.set('port', port);

/**
 * Create HTTP server.
 */

const server = http.createServer(app.callback())

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port)
server.on('error', onError)
server.on('listening', onListening)

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val) {
 const port = parseInt(val, 10)

 if (isNaN(port)) {
 // named pipe
 return val
 }

 if (port >= 0) {
 // port number
 return port
 }

 return false
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
 if (error.syscall !== 'listen') {
 throw error
 }

 const bind = typeof port === 'string'
 ? 'Pipe ' + port
 : 'Port ' + port

 // handle specific listen errors with friendly messages
 switch (error.code) {
 case 'EACCES':
 console.error(bind + ' requires elevated privileges')
 process.exit(1)
 break
 case 'EADDRINUSE':
 console.error(bind + ' is already in use')
 process.exit(1)
 break
 default:
 throw error
 }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
 const addr = server.address()
 const bind = typeof addr === 'string'
 ? 'pipe ' + addr
 : 'port ' + addr.port
 console.log('Listening on ' + bind)
}

相信用過koa-generator對這個(gè)代碼斌并不陌生,這其實(shí)就是他里面的代碼,express項(xiàng)目的www文件也基本差不多。還是希望大家可以把這里面的代碼過一遍,它的基本思路就是利用了node.js中的http模塊,讓http暴露你的端口并進(jìn)行監(jiān)聽,這個(gè)端口是在配置文件config.js中引入的。

app.js

先簡單的看一下代碼

'use strict'

const Koa = require('koa')
const bodyParser = require('koa-bodyparser')()
const staticCache = require('koa-static-cache')

const config = require('./config')
const publicRouter = require('./routes/public')
const privateRouter = require('./routes/private')
const { loggerMiddleware } = require('./middlewares/logger')
const { errorHandler, responseHandler } = require('./middlewares/response')

const app = new Koa()

// Logger
app.use(loggerMiddleware)

// Error Handler
app.use(errorHandler)

// Global Middlewares
app.use(bodyParser)
app.use(staticCache(config.publicDir))

// Routes
app.use(publicRouter.routes(), publicRouter.allowedMethods())
app.use(privateRouter.routes(), privateRouter.allowedMethods())

// Response
app.use(responseHandler)

module.exports = app

這個(gè)文件中,我們可以看到較多的中間件,中間件的執(zhí)行順序是從外到內(nèi),再從內(nèi)到外,也就是洋蔥模式。如果還不大了解中間的小伙伴可以去查找相關(guān)資料。中間件的執(zhí)行過程是依靠 app.use() 進(jìn)行傳遞的,你可以簡單的理解為自己編寫的函數(shù),依次去執(zhí)行即可。每一個(gè)中間件會在app調(diào)用是傳入2個(gè)參數(shù),分別為: ctxnext

ctx:

Koa Context 將 node 的 request 和 response 對象封裝在一個(gè)單獨(dú)的對象里面,其為編寫 web 應(yīng)用和 API 提供了很多有用的方法。

這些操作在HTTP服務(wù)器開發(fā)中經(jīng)常使用,因此其被添加在上下文這一層,而不是更高層框架中,因此將迫使中間件需要重新實(shí)現(xiàn)這些常用方法。

next:

下一個(gè)中間件函數(shù),也就是每一個(gè)中間件如果要往下走必須寫上這個(gè),否則無法執(zhí)行。

可以理解為前端的vue-Router中的路由守衛(wèi)中的next(), 執(zhí)行下一步或者進(jìn)行傳參。

middlewares

在這個(gè)項(xiàng)目主要用到了幾個(gè)中間件,一個(gè)是 logger.js 、 response.jsjwt.js 等其他中間件。

logger.js

大家可以想一下,如果我們項(xiàng)目在開發(fā)中,或者上線了,我們要看我們執(zhí)行的日志或者請求的參數(shù)以及報(bào)錯(cuò)等信息,如果沒有再每一個(gè)請求中體現(xiàn)出來,那么遇到問題我們會很難定位到是前端的問題還是后端。而logger這個(gè)中間件就是用來對這些情況進(jìn)行處理的,原來的koa模板中,只是簡單的進(jìn)行l(wèi)og的打印而已,這個(gè)中間件是用了log4js模塊進(jìn)行封裝的。詳細(xì)使用方法查看官方文檔,這個(gè)中間件會在控制臺或者日志中打印出固定的格式,http請求方法、返回狀態(tài)、請求url、IP地址、請求時(shí)間等,而且我們也可以很好的利用log4js中的配置,來打印出自定義的日志??梢源?console.log() 使用,在使用這個(gè)中間件的時(shí)候,必須放在第一個(gè)中間件,才能保證所以的請求及操作會先經(jīng)過logger進(jìn)行記錄再到下一個(gè)中間件。其代碼如下:

'use strict'

const fs = require('fs')
const path = require('path')
const log4js = require('log4js')
const config = require('../config')

const logsDir = path.parse(config.logPath).dir
if (!fs.existsSync(logsDir)) {
 fs.mkdirSync(logsDir)
}

log4js.configure({
 appenders: {
 console: { type: 'console' },
 dateFile: { type: 'dateFile', filename: config.logPath, pattern: '-yyyy-MM-dd' }
 },
 categories: {
 default: {
 appenders: ['console', 'dateFile'],
 level: 'info'
 }
 }
})

const logger = log4js.getLogger('[Default]')

const loggerMiddleware = async (ctx, next) => {
 const start = new Date()
 await next()
 const ms = new Date() - start

 const remoteAddress = ctx.headers['x-forwarded-for'] || ctx.ip || ctx.ips ||
 (ctx.socket && (ctx.socket.remoteAddress || (ctx.socket.socket && ctx.socket.socket.remoteAddress)))
 let logText = `${ctx.method} ${ctx.status} ${ctx.url} 請求參數(shù): ${JSON.stringify(ctx.request.body)} 響應(yīng)參數(shù): ${JSON.stringify(ctx.body)} - ${remoteAddress} - ${ms}ms`
 logger.info(logText)
}

module.exports = {
 logger,
 loggerMiddleware
}

response.js

這個(gè)中間件主要是用來對返回前端的響應(yīng)進(jìn)行處理,基礎(chǔ)的koa模板中,我們可以用 ctx.body 進(jìn)行返回前端,但是發(fā)現(xiàn)有些東西經(jīng)常重復(fù)寫,還不如提出來進(jìn)行封裝,而且還不用擔(dān)心返回的格式會不一致。 先看看代碼:

'use strict'

const { logger } = require('./logger')

// 這個(gè)middleware用于將ctx.result中的內(nèi)容最終回傳給客戶端
// 回傳的格式遵循這樣的格式:{ code: 0, msg: any data: any }
const responseHandler = (ctx) => {
 if (ctx.result !== undefined) {
 ctx.type = 'json'
 ctx.body = {
 code: 200,
 msg: ctx.msg || '',
 data: ctx.result
 }
 }
}

// 這個(gè)middleware處理在其它middleware中出現(xiàn)的異常
// 并將異常消息回傳給客戶端:{ code: '錯(cuò)誤代碼', msg: '錯(cuò)誤信息' }
const errorHandler = (ctx, next) => {
 return next().catch(err => {
 if (err.code == null) {
 logger.error(err.stack)
 }
 ctx.body = {
 code: err.code || -1,
 data: null,
 msg: err.message.trim()
 }

 ctx.status = 200 // 保證返回狀態(tài)是 200, 這樣前端不會拋出異常
 return Promise.resolve()
 })
}

module.exports = {
 responseHandler,
 errorHandler
}

代碼的后面會暴露出 responseHandlererrorHandler , responseHandler 正確響應(yīng),我們在業(yè)務(wù)中,只需要對 ctx.result 進(jìn)行寫入即可。這個(gè)中間件可以放在所有中間件的最后面,這樣可以保證前面中間件都需要經(jīng)過它,再返回前端。 errorHandler 錯(cuò)誤響應(yīng),這個(gè)主要是用來進(jìn)行出錯(cuò)或者異常的捕獲,可以返回響應(yīng)給前端,要不前端會出現(xiàn)一直padding的狀態(tài)直到超時(shí)。

jwt.js

'use strict'

const koaJwt = require('koa-jwt')
const jwt = require('jsonwebtoken')
const config = require('../config')
const jwtMiddleware = koaJwt({ secret: config.secret })

module.exports = function (ctx, next) {
 // 將 token 中的數(shù)據(jù)解密后存到 ctx 中
 try {
 if (typeof ctx.request.headers.authorization === 'string') {
 const token = ctx.request.headers.authorization.slice(7)
 ctx.jwtData = jwt.verify(token, config.secret)
 } else {
 throw {code: 401, message: 'no authorization'}
 }
 } catch (err) {
 throw {code: 401, message: err.message}
 }
 next()
}

其實(shí)這個(gè)中間件是對 koa-jwt 進(jìn)行封裝的,JWT我們用來生成token,用來判斷用戶的唯一性,每次登錄后返回前端,前端每一個(gè)需要鑒權(quán)的api都需要進(jìn)行token驗(yàn)證,我們利用了 koa-jwt 進(jìn)行token的生成,但是怎樣才能在每一個(gè)接口中獲取到token解析后的用戶呢。這個(gè)中間件就起到很大的關(guān)鍵作用。會結(jié)合在需要鑒權(quán)的 router 中,驗(yàn)證通過后保存信息到ctx中,可以供全局使用。

cors.js

在前后端接口請求中,由于瀏覽器的限制,會出現(xiàn)跨域的情況。常用的跨域方案有:

1、JSONP跨域

2、nginx反向代理

3、服務(wù)器端修改heade

4、document.domain

5、window.name

6、postMessage

7、后臺配置運(yùn)行跨域

koa中如何設(shè)置跨域

先看看koa中如何設(shè)置跨域,cors具體的實(shí)現(xiàn)過程,具體的詳細(xì)介紹,已經(jīng)在代碼中進(jìn)行注釋了。先看一下原生的配置,后面直接使用中間件即可,不過還是需要了解一下具體實(shí)現(xiàn)方式,萬一出了問題,能快熟的排查。

app.use(async (ctx, next) => {
 // 允許來自所有域名請求
 ctx.set("Access-Control-Allow-Origin", "*");
 // 這樣就能只允許 http://localhost:8080 這個(gè)域名的請求了
 // ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); 

 // 設(shè)置所允許的HTTP請求方法
 ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE");

 // 字段是必需的。它也是一個(gè)逗號分隔的字符串,表明服務(wù)器支持的所有頭信息字段.
 ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");

 // 服務(wù)器收到請求以后,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,確認(rèn)允許跨源請求,就可以做出回應(yīng)。

 // Content-Type表示具體請求中的媒體類型信息
 ctx.set("Content-Type", "application/json;charset=utf-8");

 // 該字段可選。它的值是一個(gè)布爾值,表示是否允許發(fā)送Cookie。默認(rèn)情況下,Cookie不包括在CORS請求之中。
 // 當(dāng)設(shè)置成允許請求攜帶cookie時(shí),需要保證"Access-Control-Allow-Origin"是服務(wù)器有的域名,而不能是"*";
 ctx.set("Access-Control-Allow-Credentials", true);

 // 該字段可選,用來指定本次預(yù)檢請求的有效期,單位為秒。
 // 當(dāng)請求方法是PUT或DELETE等特殊方法或者Content-Type字段的類型是application/json時(shí),服務(wù)器會提前發(fā)送一次請求進(jìn)行驗(yàn)證
 // 下面的的設(shè)置只本次驗(yàn)證的有效時(shí)間,即在該時(shí)間段內(nèi)服務(wù)端可以不用進(jìn)行驗(yàn)證
 ctx.set("Access-Control-Max-Age", 300);

 /*
 CORS請求時(shí),XMLHttpRequest對象的getResponseHeader()方法只能拿到6個(gè)基本字段:
 Cache-Control、
 Content-Language、
 Content-Type、
 Expires、
 Last-Modified、
 Pragma。
 */
 // 需要獲取其他字段時(shí),使用Access-Control-Expose-Headers,
 // getResponseHeader('myData')可以返回我們所需的值
 //https://www.rails365.net/articles/cors-jin-jie-expose-headers-wu
 ctx.set("Access-Control-Expose-Headers", "myData");
 
 await next();
})

相對用得較多是的大神封裝好得koa-cors中間件,可以自行查看npm上得文檔,在這個(gè)項(xiàng)目中用的就是koa-cors的中間件,基本的配置寫在cors.js里面了,再通過中間件進(jìn)行引用。注意要寫在router前面,避免在沒有進(jìn)行跨域配置前就去請求接口。

app.js中的引用,記得安裝引入koa-cors

// cors
app.use(cors(corsHandler))
'use strict'

const corsHandler = {
 origin: function (ctx) {
 if (ctx.url === '/test') {
  // 這里可以配置不運(yùn)行跨域的接口地址
  return false;
 }
 return '*';
 },
 exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
 maxAge: 5,
 credentials: true,
 allowMethods: ['GET', 'POST', 'DELETE'],
 allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}


module.exports = {
 corsHandler
}

koa-helmet 中間件

koa-helmet 可以幫助你的 app 抵御一些比較常見的安全 web 安全隱患,它其實(shí)是將 9 個(gè)安全中間件集中到了一起,做了合并,大部分都是對于 http header 的操作,下圖為默認(rèn)開啟的功能。

怎么搭建Koa后端項(xiàng)目腳手架

在項(xiàng)目中使用先安裝該中間件, npm i koa-helmet --save ,該項(xiàng)目中直接引用默認(rèn)配置即可,如果有需要,可以看官方文檔自己進(jìn)行配置。

const helmet = require("koa-helmet")

// Helmet
app.use(helmet())

其他中間件

koa的中間件可以說很多大神給我們做好了輪子,我們直接可以拿來用就行,例如: bodyParser 、 koa-session 、將將中間件轉(zhuǎn)換成koa2可以使用的中間件 koa-convert 、EJS模板使用 koa-ejs ,大家根據(jù)自己需要進(jìn)行引用,由于是基礎(chǔ)模板,暫時(shí)沒有加上過多中間件,減少體積。

lib文件

這個(gè)文件夾主要是用來做存放工具類的文件夾,一些全局的工具處理文件可以放到這邊來,目前這個(gè)項(xiàng)目中只有2個(gè)文件, error.jsmongoDB.js

error.js 中主要是在中間件中拋出異常,由于前面我們已經(jīng)加入了異常捕獲的中間件,在中間件操作過程中,如果有錯(cuò)誤,我們可以直接拋出異常,這個(gè)方法就是為了方便我們配置所用的。文件中的方法是 CodedError 方法繼承了Error, ForbiddenErrorInvalidQueryError 是繼承了 CodedError ,記得在使用的時(shí)候得實(shí)例化一下該構(gòu)造函數(shù)。如果小伙伴對ES6的繼承還不熟悉,可以先看一下文檔再來看該工具類。

'use strict'

class CodedError extends Error {
 constructor (message = '未知錯(cuò)誤', code = -1) {
 super(message)
 this.code = code
 }
}

module.exports = {
 CodedError,
 /**
 * 拒絕訪問構(gòu)造函數(shù)
 */
 ForbiddenError: class ForbiddenError extends CodedError {
 constructor (message = '拒絕訪問') {
 super(message, 403)
 }
 },
 /**
 * 無效的參數(shù)構(gòu)造函數(shù)
 */
 InvalidQueryError: class InvalidQueryError extends CodedError {
 constructor (message = '無效的參數(shù)') {
 super(message, 400)
 }
 }
}

mongoDB.js 文件是對mongoDB的鏈接配置,后續(xù)在models中會講到。

models文件

該項(xiàng)目中是使用 mongoosemongoDB 對數(shù)據(jù)庫進(jìn)行操作, mongoose 語法簡單,需要過多的學(xué)習(xí)成本。按照官方文檔的配置以及api操作,即可對 mongoBD 進(jìn)行靈活性存儲。 mongoose 的配置包括三大部分: connectModelsSchema

connect :用于創(chuàng)建數(shù)據(jù)庫連接及監(jiān)聽

Schema :Schema主要用于定義MongoDB中集合Collection里文檔document的結(jié)構(gòu),可以理解為mongoose對表結(jié)構(gòu)的定義(不僅僅可以定義文檔的結(jié)構(gòu)和屬性,還可以定義文檔的實(shí)例方法、靜態(tài)模型方法、復(fù)合索引等),每個(gè)schema會映射到mongodb中的一個(gè)collection,schema不具備操作數(shù)據(jù)庫的能力,簡單理解是對字段的定義,操作數(shù)據(jù)庫必須按照這些字段進(jìn)行,否在會報(bào)錯(cuò)。

Models : Model是由Schema編譯而成的假想(fancy)構(gòu)造器,具有抽象屬性和行為。Model的每一個(gè)實(shí)例(instance)就是一個(gè)document,document可以保存到數(shù)據(jù)庫和對數(shù)據(jù)庫進(jìn)行操作。簡單說就是model是由schema生成的模型,可以對數(shù)據(jù)庫的操作。

在我們項(xiàng)目中,我們把它全局集合在models文件中進(jìn)行配置。 index.js 文件里面操作了 connect 、 Models 這兩個(gè)步驟。先看代碼:

const fs = require('fs');
const path = require('path');
const mongoose = require('mongoose'); //引用mongoose模塊
const config = require('../config')
const { logger } = require('../middlewares/logger')

let url = "mongodb://" + config.mongoDB.host + ":" + config.mongoDB.port + "/" + config.mongoDB.database;
var mongo = mongoose.createConnection(url); //創(chuàng)建一個(gè)數(shù)據(jù)庫連接

let db = {
 mongoose: mongoose,
 mongo: mongo,
 models: {}
};
// 錯(cuò)誤
mongo.on('error', function (err) {
 logger.error(new Error(err));
});
// 開啟
mongo.once('open', function () {
 logger.info("mongo is opened");
});
// 整合models文件下的其他js文件
fs.readdirSync(__dirname)
 .filter(function (file) {
 return (file.indexOf(".") !== 0) && (file !== "index.js");
 }).forEach(function (file) {
 var modelFile = require(path.join(__dirname, file));
 var schema = new mongoose.Schema(modelFile.schema);

 db.models[modelFile.name] = mongo.model(modelFile.name, schema, modelFile.name);
});
// 根據(jù)name選擇model
db.getModel = function (name) {
 return this.models[name];
};

module.exports = db;

代碼中的鏈接部分一看基本就明白了,可是 models 部分怎么看不出所以然。其實(shí)是模塊化開發(fā)的一部分,這里是為了整合models文件下的其他js文件,方便開發(fā)者使用,不用每寫一個(gè)文件就要進(jìn)行引入和導(dǎo)出。

初始情況下, models 引入只需 mongoose.model('名稱', schema); 并將其暴露出去,即可對數(shù)據(jù)庫進(jìn)行操作。

fs.readdirSync(__dirname)
 .filter(function (file) {
 return (file.indexOf(".") !== 0) && (file !== "index.js");
 }).forEach(function (file) {
 var modelFile = require(path.join(__dirname, file));
 var schema = new mongoose.Schema(modelFile.schema);

 db.models[modelFile.name] = mongo.model(modelFile.name, schema, modelFile.name);
});

在這個(gè)文件內(nèi),我們做了這樣一件事:讀取 models 目錄下所有文件名不為 index.js 且以 .js 為后綴名的文件,使用 require 進(jìn)行引用,并將其整合為一個(gè) schema對象后再引入到models并且暴露出去給操作數(shù)據(jù)庫。這樣子做的好處是,在項(xiàng)目越來越龐大以后,如果我們需要添加新的 schema ,只需直接在 models 目錄下新建 .js 文件即可,則不用再進(jìn)行引入的關(guān)系操作

由于有了上一步的操作,我們后面直接新增一個(gè)schema的配置文件即可。index.js會自動的引入并暴露出model

'use strict'

module.exports = {
 name: "user",
 schema: {
 uuid: String, // UUID
 userName: String, // 用戶名
 password: String, // 密碼
 }
};

我們使用時(shí)可以這樣操作,

const User = require('../models/index').getModel('user')
const user = await User.findOne({userName: userName})

PM2配置

PM2是可以用于生產(chǎn)環(huán)境的Nodejs的進(jìn)程管理工具,并且它內(nèi)置一個(gè)負(fù)載均衡。它不僅可以保證服務(wù)不會中斷一直在線,并且提供0秒reload功能,還有其他一系列進(jìn)程管理、監(jiān)控功能。并且使用起來非常簡單。pm2的官方文檔已經(jīng)進(jìn)行詳細(xì)的配置說明,在這里就不進(jìn)行一一簡述,主要講的時(shí)我的koa項(xiàng)目怎樣配合PM2進(jìn)行相關(guān)管理或者說部署。PM2常用命令需要用的時(shí)候可以進(jìn)行查看,沒必要去背,用多就熟悉了。也可以結(jié)合在package.json里面,用自定義命令運(yùn)行。我們在 package.jsonscript 配置和初始化文件 ecosystem.config.js 進(jìn)行了多環(huán)境運(yùn)行的配置,我們可以根據(jù)需要進(jìn)行切換環(huán)境。

package.json 文件添加如下:

 "scripts": {
 "start": "node ./bin/www",
 "dev": "pm2 start ecosystem.config.js --env dev",
 "test": "pm2 start ecosystem.config.js --env test",
 "pro": "pm2 start ecosystem.config.js --env pro",
 "logs": "pm2 logs",
 "stop": "pm2 stop ecosystem.config.js"
 },

其中的

npm run start: 直接跑www文件,可用于調(diào)試
npm run dev: 開發(fā)環(huán)境
npm run test:測試環(huán)境
npm run pro:生產(chǎn)環(huán)境
npm run logs: 查看pm2的日志
npm run stop: 停止pm2服務(wù)

新增 ecosystem.config.js 文件:

module.exports = {
 apps : [{
 name: 'API',
 script: './bin/www',

 // Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/
 args: 'one two',
 instances: 1,
 autorestart: true,
 watch: true,
 ignore_watch: [    // 不用監(jiān)聽的文件
 'node_modules',
 'logs'
 ],
 max_memory_restart: '1G',
 env_pro: {
 "NODE_ENV": "production",
 "REMOTE_ADDR": ""
 },
 env_dev: {
 "NODE_ENV": "development",
 "REMOTE_ADDR": ""
 },
 env_test: {
 "NODE_ENV": "test",
 "REMOTE_ADDR": ""
 }
 }]
};

這個(gè)文件主要是對pm2的基本配置,不用每次都進(jìn)行配置,直接在文件進(jìn)行改動即可。我們需要關(guān)注的是可以在 env 中,增加我們需要的環(huán)境及變量即可,文件中的 watch 屬性是可以配置監(jiān)聽文件改動后,自動重啟項(xiàng)目,比較好用。如果想忽略某一個(gè)文件夾的變動可以 ignore_watch ,更多的配置如果有興趣的小伙伴可以查看官方文檔的文檔說明。

路由配置

該目錄下存放路由基本配置,有 privatepublic 兩個(gè)文件,引入路由后,我們對其前綴做了處理, router.prefix('/api') 在每一個(gè)請求的時(shí)候都需要帶上這個(gè)前綴,抽出來也是為了服務(wù)目錄的改變,可以直接更改即可,做了全局的操作。每一個(gè)路由都必須暴露出去,這樣在app.js文件中使用該中間件。 publicRouter.allowedMethods() 根據(jù) ctx.status 設(shè)置 response 響應(yīng)頭

// Routes
app.use(publicRouter.routes(), publicRouter.allowedMethods())
app.use(privateRouter.routes(), privateRouter.allowedMethods())

private :該文件下的路由是需要通過jwt驗(yàn)證的,才能進(jìn)行訪問。前面我們做了jwt的中間件,我們直接引入即可 router.use(jwtMiddleware) 記得要放在請求路由的前面,才能保證每次都經(jīng)過它。

'use strict'

const Router = require('koa-router')
const controllers = require('../controllers')
const jwtMiddleware = require('../middlewares/jwt')

const router = new Router()
router.prefix('/api')
router.use(jwtMiddleware)

router.get('/test', controllers.test.test)

module.exports = router

public :該文件與上面相反,主要用來不進(jìn)行登錄的校驗(yàn),也就是我們常用的登錄、注冊等不需要驗(yàn)證的接口。

'use strict'

const Router = require('koa-router')
const controllers = require('../controllers')

const router = new Router()
router.prefix('/api')

router.post('/login', controllers.login.login)

module.exports = router

為什么我們沒在這里處理業(yè)務(wù)邏輯呢?其實(shí)這里是遵循了MVC的思想,進(jìn)行了分離。把數(shù)據(jù)庫的操作放到了controllers文件中。這如果我們接口一多,不會顯示得特別混亂。下面我們就來講這個(gè)文件。

controllers文件

為了讓整個(gè)項(xiàng)目更為模塊化,該目錄下主要是處理對應(yīng)的路由的回調(diào)函數(shù),一般我們不會在router文件中去操作數(shù)據(jù)庫和邏輯操作等步驟,這里采用 routes 和 controller 分開,在方便代碼的查看同時(shí),也方便代碼的維護(hù)和開發(fā)。

index.js文件:

該文件與models中的index.js文件中的集合該目錄下的文件類似,這里是將其他文件導(dǎo)出統(tǒng)一到index暴露出去。

'use strict'

const fs = require('fs')

const files = fs.readdirSync(__dirname).filter(file => file !== 'index.js')

const controllers = {}
for (const file of files) {
 if (file.toLowerCase().endsWith('js')) {
 const controller = require(`./${file}`)
 controllers[`${file.replace(/\.js/, '')}`] = controller
 }
}

module.exports = controllers

其他文件的編寫可以按照下面基本框架進(jìn)行,在這里會用到前面封裝好的業(yè)務(wù),例如數(shù)據(jù)庫操作、響應(yīng)、jwt等操作。大家可以認(rèn)真看以下代碼分析一下。

'use strict'

const jwt = require('jsonwebtoken')
const config = require('../config')
const User = require('../models/index').getModel('user')
const login = {}

login.login = async (ctx, next) => {
 const {userName, password} = ctx.request.body
 const user = await User.findOne({userName: userName})
 if (!user) {
 ctx.result = ''
 ctx.msg = '用戶不存在'
 } else {
 ctx.result = jwt.sign({
  data: user._id,
  // 設(shè)置 token 過期時(shí)間
  exp: Math.floor(Date.now() / 1000) + (60 * 60), // 60 seconds * 60 minutes = 1 hour
 }, config.secret)
 }
 return next()
}

module.exports = login

config.js文件

該文件主要用來存放全局的配置,如果一個(gè)項(xiàng)目中沒有全局的配置,那么一個(gè)地方改動牽動的其他地方很多,這樣很不利于工作效率,在開發(fā)過程中,我們一般會把常用的都放在這個(gè)文件,例如:數(shù)據(jù)庫參數(shù),端口,密鑰,全局變量等??醋约旱男枨筮m當(dāng)?shù)母摹T撐募⒆兞窟M(jìn)行了暴露,引用時(shí)進(jìn)行require即可。

'use strict'

const path = require('path')

module.exports = {
 port: '3001',
 secret: 'secret',
 publicDir: path.resolve(__dirname, './public'),
 logPath: path.resolve(__dirname, './logs/koa-template.log'),
 mongoDB: {
 database: 'mall',
 username: 'root',
 password: 'root',
 host: '127.0.0.1',
 port: 27017
 }
}

package.json文件

每個(gè)Nodejs項(xiàng)目的根目錄下面,一般都會有一個(gè)package.json文件。該文件可以由npm init生成,定義了項(xiàng)目所需要的各種模塊,以及項(xiàng)目的配置信息(比如名稱、版本、許可證等元數(shù)據(jù))。 package.json文件內(nèi)部就是一個(gè)JSON對象,該對象的每一個(gè)成員就是當(dāng)前項(xiàng)目的一項(xiàng)設(shè)置。我們也可在里面配置我們的 npm run XXX 的命令,大家可以根據(jù)需求進(jìn)行配置。這是這項(xiàng)目需要用到的package.json文件。

{
 "name": "koa-template",
 "version": "0.1.0",
 "author": "bayi",
 "private": true,
 "scripts": {
 "start": "node ./bin/www",
 "dev": "pm2 start ecosystem.config.js --env dev",
 "test": "pm2 start ecosystem.config.js --env test",
 "pro": "pm2 start ecosystem.config.js --env pro",
 "logs": "pm2 logs",
 "stop": "pm2 stop ecosystem.config.js"
 },
 "dependencies": {
 "koa": "^2.6.2",
 "koa-bodyparser": "^4.2.1",
 "koa-helmet": "^4.1.0",
 "koa-jwt": "^3.5.1",
 "koa-router": "^7.4.0",
 "koa-static-cache": "^5.1.2",
 "koa2-cors": "^2.0.6",
 "log4js": "^3.0.6",
 "mongoose": "^5.5.5"
 }
}

以上是“怎么搭建Koa后端項(xiàng)目腳手架”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!


標(biāo)題名稱:怎么搭建Koa后端項(xiàng)目腳手架
URL標(biāo)題:http://weahome.cn/article/jpjejs.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部