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