node+koa中轉層開發實踐總結
阿新 • • 發佈:2018-11-15
node中轉層的意義:
1.能解決前後端程式碼部署在不同伺服器下時的跨域問題。(實現)
2.合併請求,業務邏輯處理。(實現)
3.單頁應用的首屏服務端渲染。(暫未實現)
環境準備:
node: ^8.11.2
koa: ^2.6.1
koa-router: ^7.4.0
koa-bodyparser: ^4.2.1
在專案目錄下新建server目錄,新建app.js
const Koa = require('koa'); const bodyParser = require('koa-bodyparser'); const apiControler = require('./apiControler'); let app = new Koa(); global.hostname = "172.16.16.113"; global.port = 8070; app.use(async (ctx, next) => { await next(); console.log(`Process ${ctx.request.method} ${ctx.request.url}...`); }); //bodyParser必須在router之前註冊到app上 app.use(bodyParser()); //使用路由處理中介軟體 app.use(apiControler()); app.listen(8899);
在server目錄下新建業務介面目錄,本例目錄名為apiControlers,拿登入模組為例,新建一個login.js,裡面包含登入模組所需要的所有介面。(獲取驗證碼、登入、獲取選單許可權)
login.js
const http = require('http'); const hostname = global.hostname; const port = global.port; let tokenStr = ""; /*獲取圖形驗證碼*/ let getAuthCoedFn = async (ctx, next) => { let data = await asyncGetAuthCode(); ctx.set("Content-Type", "application/json"); ctx.body = JSON.parse(data); await next(); }; function asyncGetAuthCode() { return new Promise((resolve, reject)=> { let authCodedData = ""; let req = http.request({ path: '/api/backstage/authCode', port: port, method: 'GET', hostname: hostname },(res)=> { res.on('data', (chunk)=> { authCodedData += chunk }); res.on('end', ()=> { authCodedData = JSON.stringify(authCodedData) resolve(authCodedData) }) }); req.on("error", (e)=> { console.log("api:/backstage/authCode error") reject(e.message) }); req.end(); }) } /*登入*/ let loginFn = async (ctx, next) => { let param = ctx.request.body; let authcodekey = ctx.request.header.authcodekey; let postData = { userName: param.userName, authCode: param.authCode, password: param.password }; let loginData = await asyncPostLogin(authcodekey, JSON.stringify(postData)); ctx.set("Content-Type", "application/json"); ctx.set("Connection", "keep-alive"); ctx.body = JSON.parse(loginData); next() }; function asyncPostLogin(authcodekey, postData) { return new Promise((resolve, reject)=> { let loginData = ""; let req = http.request({ path: '/api/backstage/login', port: port, method: 'POST', hostname: hostname, headers: { 'Content-Type': 'application/json', 'authCodeKey': authcodekey } },(res)=> { res.on('data', (chunk)=> { loginData += chunk }).on('end', ()=> { loginData = JSON.stringify(loginData); tokenStr = res.headers['set-cookie']; resolve(loginData) }) }); req.on('error', (e)=> { console.log("api:/backstage/login error"); reject(e.message) }); req.write(postData); req.end(); }) } /*獲取選單及許可權列表*/ let getPowerListFn = async (ctx, next) => { let menuList = await asyncGetPowerList(); ctx.body = JSON.parse(menuList); next() }; function asyncGetPowerList() { return new Promise((resolve, reject)=> { let listData = ""; let req = http.request({ path: '/api/backstage/getPowerList', method: 'get', port: port, hostname: hostname, headers: { 'Cookie': tokenStr.toString() } },(res)=> { res.on('data', (chunk)=> { listData += chunk; }).on('end', ()=> { listData = JSON.stringify(listData); resolve(listData) }) }); req.on("error", (e)=> { console.log("api: /backstage/getPowerList error"); reject(e.message) }); req.end() }) } module.exports = { 'GET/api/backstage/authCode': getAuthCoedFn, 'POST/api/backstage/login': loginFn, 'GET/api/backstage/getPowerList': getPowerListFn }
以介面功能宣告一個函式,在此函式中通過node的http模組傳送請求。需要注意的是http.request請求獲取響應頭cookie的方式是tokenStr = res.headers['set-cookie']。
每一個業務功能js最後暴露出內部所有以介面請求方式+介面地址為key,以對應功能函式為value的物件。
在server目錄下新建一個apiControler.js中介軟體(有返回值的函式)。此中介軟體的功能一是讀取apiControlers目錄下的所有業務js,並引入;二是設定介面請求方式與執行函式的對映關係。
最後暴露出一個函式返回所有請求介面路徑的集合。
apiControler.js
const fs = require("fs"); function readApiFiles(router, dir = '/apiControlers') { fs.readdirSync(__dirname + dir).filter((f)=> { return f.endsWith('.js') }).forEach(f => { console.log(`process controller: ${f}...`); let mapping = require(__dirname + dir + '/' + f); addMapping(router, mapping) }); } function addMapping(router, mapping) { for(let url in mapping) { if(url.startsWith('GET')) { let path = url.substring(3); router.get(path, mapping[url]); }else if(url.startsWith('POST')) { let path = url.substring(4); router.post(path, mapping[url]); }else{ router.get(url, mapping[url]); console.log(`無效的URL: ${url}`); } } } module.exports = function (dir) { let controllers_dir = dir || '/apiControlers'; let router = require('koa-router')(); readApiFiles(router, controllers_dir); return router.routes(); };
最後回到app.js,引入apiControler.js中介軟體並註冊到app上。需要注意的是bodyParser中介軟體必須在router之前註冊到app上。
後續
此例目前只能用作介面轉發、合併請求和解決跨域問題,終極目標是能解決SPA(單頁應用的)首屏服務端渲染問題。
持續折騰中...