摘要:項(xiàng)目都很小,但為了進(jìn)一步了解,特意選擇了作為框架基礎(chǔ)開發(fā)后端服務(wù)。能將請(qǐng)求限制在同源網(wǎng)站,即只有擁有專有令牌的網(wǎng)站發(fā)送請(qǐng)求才會(huì)正確響應(yīng)。項(xiàng)目生產(chǎn)靜默部署,啟動(dòng)使用,停止使用。不足工具函數(shù)的訪問需要自己手動(dòng)添加擴(kuò)展另沒有寫測(cè)試,希望下次補(bǔ)上。
前言
這段時(shí)間,用Eggjs作為后端服務(wù)框架開發(fā)了幾個(gè)項(xiàng)目。項(xiàng)目都很小,但為了進(jìn)一步了解Eggjs,特意選擇了Eggjs作為框架基礎(chǔ)開發(fā)后端服務(wù)。期間也遇到過一些問題和坑,還有幾個(gè)值得注意的點(diǎn),下面來講一下我這段時(shí)間開發(fā)的總結(jié)。
Egg.js 為企業(yè)級(jí)框架和應(yīng)用而生 ,我們希望由 Egg.js 孕育出更多上層框架,幫助開發(fā)團(tuán)隊(duì)和開發(fā)人員降低開發(fā)和維護(hù)成本。
這個(gè)是Eggjs文檔對(duì)Eggjs的解釋,關(guān)于Eggjs的詳細(xì)介紹和使用請(qǐng)點(diǎn)解前面的地址;相對(duì)于Egg.js 1.x版本的文檔,已經(jīng)有很大的改進(jìn)了,很多關(guān)鍵的地方都可以比較完整講解和帶有代表性的實(shí)例。
步驟 開始用的Egg.js版本是2.2.1,對(duì)環(huán)境有一定的要求,本人用的配置如下:
操作系統(tǒng):macOS
運(yùn)行環(huán)境:v9.8.0
使用腳手架快速創(chuàng)建項(xiàng)目:
$ npm i egg-init -g $ egg-init egg-example --type=simple $ cd egg-example $ npm i
項(xiàng)目安裝完畢,啟動(dòng)項(xiàng)目:
$ npm run dev $ open localhost:7001
至此,項(xiàng)目順利建立及啟動(dòng)完畢。
項(xiàng)目結(jié)構(gòu):(摘自文檔)
egg-project ├── package.json ├── app.js (可選) ├── agent.js (可選) ├── app | ├── router.js │ ├── controller │ | └── home.js │ ├── service (可選) │ | └── user.js │ ├── middleware (可選) │ | └── response_time.js │ ├── schedule (可選) │ | └── my_task.js │ ├── public (可選) │ | └── reset.css │ ├── view (可選) │ | └── home.tpl │ └── extend (可選) │ ├── helper.js (可選) │ ├── request.js (可選) │ ├── response.js (可選) │ ├── context.js (可選) │ ├── application.js (可選) │ └── agent.js (可選) ├── config | ├── plugin.js | ├── config.default.js │ ├── config.prod.js | ├── config.test.js (可選) | ├── config.local.js (可選) | └── config.unittest.js (可選) └── test ├── middleware | └── response_time.test.js └── controller └── home.test.js
上述目錄也是一個(gè)給開發(fā)者一個(gè)目錄創(chuàng)建的指南,但按照文檔建立的項(xiàng)目目錄結(jié)構(gòu)沒有那么全,基本上標(biāo)注為“可選”的都是初始沒有的,在/config目錄里也只有plugin.js和config.default.js兩個(gè)文件,其他文件要自己根據(jù)需求創(chuàng)建。
建立控制器Controller初始項(xiàng)目里會(huì)有一個(gè)示例Controller,在創(chuàng)建一個(gè)新的Controller可以參考/app/controller/home.js的示例,一般而言,推薦使用module.exports暴露出一個(gè)類或者參數(shù)為app返回一個(gè)類的函數(shù)(文檔示例中為箭頭函數(shù),其他方式?jīng)]試過不清楚),類里面包含著這塊業(yè)務(wù)的一些操作,下面在控制器文件目錄/app/controller/里新建一個(gè)文件名為user.js的控制器文件:
// 繼承egg的控制器 const Controller = require("egg").Controller; class UserController extends Controller { async index() { const { ctx } = this; const { name } = ctx.request.body; ctx.body = `hi, ${name}`; } async getUserById() { const { userId } = this.ctx.request.body; // 使用業(yè)務(wù)函數(shù)查詢用戶信息 const userInfo = await this.service.user.findById(userId); this.ctx.body = { msgCode: 0, message: "成功", data: userInfo }; } } // 注意:一定要將控制器暴露出去,否則請(qǐng)求的時(shí)候會(huì)報(bào)找不到該controller的錯(cuò)誤; module.exports = UserController;添加路由
路由代碼在/app目錄之下,文件名router.js,添加路由的代碼如下:
// 參數(shù)app為全局應(yīng)用的對(duì)象 module.exports = app => { const { router, controller, middleware } = app; // 在這里controller相當(dāng)于app下的controller文件目錄,user為user.js,index為控制器類的index方法 router.get("/", controller.user.index); };編寫業(yè)務(wù)
通常,controller主要處理數(shù)據(jù)的結(jié)構(gòu)和處理返回的結(jié)果,具體的涉及的業(yè)務(wù)由service業(yè)務(wù)類方法完成,編寫service,在目錄/app/service/下建立user.js文件,并編寫代碼:
// 同樣要繼承egg的Service類 const Service = require("egg").Service; class UserService extends Service { // 根據(jù)用戶id查找用戶 async findUserById(id) { const mysql = this.app.mysql; const result = await mysql.get("users", { id }); return result; } } module.exports = UserService;添加插件
eggjs simple 版本旨在根據(jù)業(yè)務(wù)需求添加eggjs的插件來搭建上層框架。在本人開發(fā)過程中,用到的一些插件做簡(jiǎn)要說明。
插件安裝:
$ npm i --save egg-pluginName
在文件/config/plugin.js添加配置:
exports.pluginName = { enable: true, package: "egg-pluginName", };
需要插件初始化配置的情況下,修改/config/config.default.js:
config.pluginName = { // 配置項(xiàng) };egg-mysql
egg-redis因使用mysql數(shù)據(jù)庫,需要一個(gè)nodejs對(duì)mysql的操作庫,基于eggjs選擇了egg-mysql ,操作文檔點(diǎn)擊這里?;镜臄?shù)據(jù)庫增刪查改都能操作,寫起來還挺方便,但是有個(gè)需求,編寫某個(gè)接口,返回當(dāng)前用戶某一段時(shí)間的數(shù)據(jù),這就比較蛋疼了,找了很久,就連egg-mysql封裝的庫ali-rds查看了源碼也找不到這類方法,無奈之下,只能通過組織原生的mysql查詢語句去動(dòng)態(tài)拼湊,雖然不推薦,不過如果找到更好的方法,還是愿意改寫的。
查詢指定日期數(shù)據(jù)的mysql相關(guān)參考資料:
http://www.jb51.net/article/1...
https://www.cnblogs.com/benef...
https://www.cnblogs.com/softi...
redis相當(dāng)于基于內(nèi)存的一個(gè)微型數(shù)據(jù)庫,其存取速度非常快,代碼執(zhí)行的時(shí)候幾乎感覺不到阻塞,這里使用egg-redis作為項(xiàng)目對(duì)redis的操作庫,文檔點(diǎn)擊這里。文檔說明解析了基本的存取和設(shè)置操作,對(duì)于較為復(fù)雜的操作只能通過查看redis的官方文檔,對(duì)應(yīng)的命令小寫即為方法名:
redis命令DECR,對(duì)應(yīng)的方法使用:
// /app/controller/user.js const value = await this.app.redis.decr(keyName);擴(kuò)展內(nèi)置對(duì)象
添加過幾個(gè)插件之后,發(fā)現(xiàn)其中的源代碼都是以擴(kuò)展內(nèi)置對(duì)象的方式去掛載相關(guān)的庫或者插件的。
ctx文檔原文:“一般來說屬性的計(jì)算在同一次請(qǐng)求中只需要進(jìn)行一次,那么一定要實(shí)現(xiàn)緩存,否則在同一次請(qǐng)求中多次訪問屬性時(shí)會(huì)計(jì)算多次,這樣會(huì)降低應(yīng)用性能。
推薦的方式是使用 Symbol + Getter 的模式?!?/p>
const jwt = require("jsonwebtoken"); const JWT = Symbol("Context#jwt"); module.exports = { get jwt() { if (!this[JWT]) { this[JWT] = jwt; } return this[JWT]; } };
第一次擴(kuò)展cxt對(duì)象的時(shí)候,不明白為何要使用Symbol + Getter的模式,后來基于這個(gè)問題,查找資料,發(fā)現(xiàn)這種方式更能避免和其他屬性名發(fā)生沖突,上述代碼中,ctx的jwt定義為只讀方式。在方便維護(hù)的同時(shí),生成一個(gè)帶有命名空間(Context#jwt)字符串描述的Symbol實(shí)例數(shù)據(jù), 作為ctx的屬性,通過只讀屬性jwt來獲取內(nèi)部的JWT屬性。
PS:
ctx.JWT == ctx[JWT] // false
關(guān)于Symbol的介紹和使用請(qǐng)參考阮一峰的ES6。
app在擴(kuò)展app對(duì)象的時(shí)候,遇到個(gè)問題,就是如果需要獲取ctx怎么辦?
查找文檔,找到了在擴(kuò)展app對(duì)象時(shí),只需要在函數(shù)體里添加一句代碼:
const ctx = app.createAnonymousContext();
就可以獲取ctx對(duì)象,這對(duì)于使用其他函數(shù)提供了一道橋梁。
編寫中間件eggjs的中間件處理流程遵循koa的洋蔥式請(qǐng)求模型
中間件的寫法:
module.exports = options => { return async function middleWareFunctionName (ctx, next) { // 控制器之前業(yè)務(wù)處理代碼 // ... await next(); //控制器之后業(yè)務(wù)處理代碼 // ... } }
中間件以返回一個(gè)處理業(yè)務(wù)的函數(shù)為主體,函數(shù)接收兩個(gè)參數(shù):ctx、next。ctx則是請(qǐng)求級(jí)別的對(duì)象,next()方法可以讓請(qǐng)求進(jìn)入下一個(gè)步驟。特別注意的是:在一個(gè)控制器中,有對(duì)請(qǐng)求到達(dá)下一步之前做一些操作的,可以控制next()在代碼流程中的位置,其后也可以處理請(qǐng)求之后的操作。
制定定時(shí)任務(wù)在eggjs寫定時(shí)任務(wù)也是非常簡(jiǎn)單的,關(guān)注于業(yè)務(wù)代碼,加以簡(jiǎn)單的配置,即可使用定時(shí)任務(wù)。
下面是一個(gè)簡(jiǎn)單的定時(shí)統(tǒng)計(jì)業(yè)務(wù)數(shù)據(jù)的定時(shí)任務(wù):
const Subscription = require("egg").Subscription; class Statistics extends Subscription { // 通過 schedule 屬性來設(shè)置定時(shí)任務(wù)的執(zhí)行間隔等配置 static get schedule() { return { cron: "00 59 23 * * *", // 秒 分 時(shí) 日 月 年 // interval: "10s", // 設(shè)置時(shí)間間隔觸發(fā),單位s為秒,ms為毫秒 type: "worker", // all 指定所有的 worker 都需要執(zhí)行, worker 為某一個(gè) worker 執(zhí)行 }; } // subscribe 是真正定時(shí)任務(wù)執(zhí)行時(shí)被運(yùn)行的函數(shù) async subscribe() { // 定時(shí)任務(wù)業(yè)務(wù)代碼 // ... } } module.exports = Statistics;
在程序啟動(dòng)的時(shí)候,就會(huì)在配置的指定時(shí)機(jī)執(zhí)行相關(guān)的業(yè)務(wù)代碼。
配置(待補(bǔ)充) csrf的討論eggjs在v2.x版本之后默認(rèn)開啟了csrf插件,已確?;?b>cookie存儲(chǔ)驗(yàn)證信息的網(wǎng)站信息安全。
csrf能將請(qǐng)求限制在同源網(wǎng)站,即只有擁有“專有令牌”的網(wǎng)站發(fā)送請(qǐng)求才會(huì)正確響應(yīng)。此處容易與jwt的作用混淆,可以看看這篇文章。
跨域使用egg-cors;前后端分離用戶驗(yàn)證
使用jwt驗(yàn)證
jwt則在認(rèn)證方式上跟csrf上有所不同,jwt可以在不使用cookie的情況下,以token的方式在前后端交互數(shù)據(jù)的body里傳輸,也可以在header里設(shè)置相關(guān)信息,詳細(xì)可以參考這篇文章。
日志(待補(bǔ)充)類的寫法遠(yuǎn)程機(jī)開發(fā)部署
文檔中,有《應(yīng)用部署》一文,里面介紹的很詳細(xì)的。使用egg-script插件啟動(dòng)生產(chǎn)環(huán)境中的應(yīng)用程序。項(xiàng)目生產(chǎn)靜默部署,啟動(dòng)使用npm start,停止使用npm stop。另,在開發(fā)環(huán)境里想要使用pm2管理進(jìn)程后臺(tái)啟動(dòng),--watch會(huì)不斷打印控制臺(tái)日志,原因不清楚。生產(chǎn)環(huán)境部署
啟動(dòng)命令:
$ npm install --production $ npm start
停止命令:
$ npm stop總結(jié) 優(yōu)點(diǎn)
使用eggjs開發(fā)企業(yè)級(jí)應(yīng)用還是相當(dāng)方便的,雖然說要根據(jù)需求裝,但安裝和配置步驟非常簡(jiǎn)單,很多有用的業(yè)務(wù)配置都能夠很方便快速配置好,還可以區(qū)分環(huán)境,項(xiàng)目結(jié)構(gòu)和調(diào)用方式很合理。不足
工具函數(shù)的訪問需要自己手動(dòng)添加擴(kuò)展另
沒有寫測(cè)試,希望下次補(bǔ)上。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/95471.html
摘要:今天就簡(jiǎn)要說說下的實(shí)現(xiàn)。主要的原因是的文檔寫的不太清楚,方便新人快速上手。導(dǎo)致我們一目十行去掃文檔的時(shí)候,有時(shí)總會(huì)覺得有些莫名,的實(shí)現(xiàn)就是其中之一。其實(shí)這和我本身對(duì)的了解不深入有關(guān),但是也沒有文檔和我說實(shí)現(xiàn)啊。 這兩天真的是宅的骨頭都發(fā)霉了,春困秋乏夏打盹,也是醉了。今天就簡(jiǎn)要說說eggjs下Restful API的實(shí)現(xiàn)。主要的原因是egg的文檔寫的不太清楚,方便新人快速上手。話說eg...
摘要:最近學(xué)習(xí),學(xué)習(xí)過程中使用官方推薦的庫,感覺官方庫不太好用,基礎(chǔ)的沒問題。介紹這個(gè)輪子其實(shí)是很早以前就造好的,主要參考的數(shù)據(jù)庫操作方式。將設(shè)置表名設(shè)置查詢字段聯(lián)表等操作進(jìn)行鏈?zhǔn)讲僮鳎o人一種語義化操作數(shù)據(jù)庫的感覺。 最近學(xué)習(xí)eggjs,學(xué)習(xí)過程中使用官方推薦的MySQL庫,感覺官方庫不太好用,基礎(chǔ)的CURD沒問題。但是復(fù)雜點(diǎn)的操作就不行了,雖然官方還有一個(gè)egg-sequelize,但是...
摘要:總所周知,的策略讓每次都要發(fā)送碼驗(yàn)證,為了方便,我在的里作了前置攔截。結(jié)果不幸從此發(fā)生最開始沒有看官方文檔,以為應(yīng)該加在里面,又沒有考慮到要上傳格式的文檔,所以直接結(jié)果發(fā)送的是。這很正常,閱讀源碼知為時(shí)會(huì)自動(dòng)添加的頭。不加又以上傳了。 總所周知,egg的csrf策略讓post每次都要發(fā)送token碼驗(yàn)證,為了方便,我在axios的interceptor里作了前置攔截。 結(jié)果不幸從此發(fā)生...
摘要:前兩天將一個(gè)部署到服務(wù)器時(shí),用就是啟動(dòng)不了,錯(cuò)誤信息為無權(quán)限創(chuàng)建目錄。查看工作目錄權(quán)限,當(dāng)前用戶是有權(quán)限的,看了源碼,原來是用的包的問題。首先,在生產(chǎn)環(huán)境下的啟動(dòng)是通過啟動(dòng)的。 前兩天將一個(gè)egg部署到服務(wù)器時(shí),用npm start就是啟動(dòng)不了,錯(cuò)誤信息為無權(quán)限創(chuàng)建log目錄。查看工作目錄權(quán)限,當(dāng)前用戶是有權(quán)限的,看了源碼,原來是用的npm包的問題。這里簡(jiǎn)單記錄下解決過程。 首先,在生...
閱讀 1745·2021-11-22 15:33
閱讀 2135·2021-10-08 10:04
閱讀 3575·2021-08-27 13:12
閱讀 3448·2019-08-30 13:06
閱讀 1492·2019-08-29 16:43
閱讀 1418·2019-08-29 16:40
閱讀 814·2019-08-29 16:15
閱讀 2774·2019-08-29 14:13