英文網(wǎng)站模板源代碼免費(fèi)源碼下載網(wǎng)站
一、Egg.js 是什么
在當(dāng)今的 Web 開(kāi)發(fā)領(lǐng)域,Node.js 憑借其事件驅(qū)動(dòng)、非阻塞 I/O 的模型,在構(gòu)建高性能、可擴(kuò)展的網(wǎng)絡(luò)應(yīng)用方面展現(xiàn)出獨(dú)特的優(yōu)勢(shì) ,受到了廣大開(kāi)發(fā)者的青睞。它讓 JavaScript 不僅局限于前端,還能在服務(wù)器端大展身手,實(shí)現(xiàn)前后端技術(shù)棧的統(tǒng)一,大大提高了開(kāi)發(fā)效率。
而 Egg.js,作為基于 Koa 構(gòu)建的企業(yè)級(jí) Node.js Web 應(yīng)用框架,更是為 Node.js 開(kāi)發(fā)帶來(lái)了新的活力和便利。它就像是一位貼心的助手,為開(kāi)發(fā)者們提供了一套完善的解決方案,助力打造出穩(wěn)定、高效且易于維護(hù)的應(yīng)用程序。
Egg.js 具有諸多令人矚目的優(yōu)勢(shì)。其模塊化設(shè)計(jì),讓開(kāi)發(fā)者可以將應(yīng)用拆分成一個(gè)個(gè)獨(dú)立的模塊,每個(gè)模塊各司其職,獨(dú)立開(kāi)發(fā)、測(cè)試和部署。這不僅降低了代碼的復(fù)雜度,還使得應(yīng)用的可維護(hù)性和可擴(kuò)展性大幅提升,就像搭積木一樣,根據(jù)需求靈活組合各個(gè)模塊,輕松應(yīng)對(duì)各種業(yè)務(wù)場(chǎng)景的變化。
內(nèi)置中間件是 Egg.js 的又一亮點(diǎn)。路由處理、靜態(tài)文件服務(wù)、錯(cuò)誤處理等中間件一應(yīng)俱全,開(kāi)發(fā)者無(wú)需再花費(fèi)大量時(shí)間和精力去自行實(shí)現(xiàn)這些基礎(chǔ)功能,大大提高了開(kāi)發(fā)速度。以路由處理中間件為例,它能夠精準(zhǔn)地將不同的 URL 請(qǐng)求映射到對(duì)應(yīng)的處理函數(shù),確保請(qǐng)求的高效分發(fā)和處理,讓整個(gè)應(yīng)用的交互流程更加順暢。
此外,Egg.js 還擁有配置靈活、插件機(jī)制強(qiáng)大、支持多進(jìn)程等特性,為企業(yè)級(jí)應(yīng)用開(kāi)發(fā)提供了全方位的支持,使其在面對(duì)復(fù)雜業(yè)務(wù)需求和高并發(fā)場(chǎng)景時(shí)也能游刃有余 。
二、環(huán)境搭建與項(xiàng)目初始化
(一)安裝 Node.js 和 npm
在開(kāi)始使用 Egg.js 進(jìn)行項(xiàng)目開(kāi)發(fā)之前,我們首先需要安裝 Node.js 和 npm。Node.js 是 Egg.js 運(yùn)行的基礎(chǔ),而 npm(Node Package Manager)則是 Node.js 的包管理器,用于安裝和管理項(xiàng)目依賴。
我們可以從 Node.js 官方網(wǎng)站(https://nodejs.org/en/?)下載適合自己操作系統(tǒng)的安裝包。下載完成后,運(yùn)行安裝程序,按照提示進(jìn)行安裝即可。安裝過(guò)程中,記得勾選 “Add to PATH” 選項(xiàng),這樣就可以在命令行中直接使用 Node.js 和 npm 命令。
安裝完成后,打開(kāi)命令行工具,輸入以下命令驗(yàn)證安裝是否成功:
node -v npm -v |
如果成功輸出版本號(hào),說(shuō)明 Node.js 和 npm 已經(jīng)成功安裝在你的電腦上 。
(二)安裝 Egg.js
接下來(lái),我們需要安裝 Egg.js。Egg.js 提供了一個(gè)名為egg-init的命令行工具,用于快速初始化 Egg.js 項(xiàng)目。我們可以使用 npm 全局安裝egg-init:
npm install -g egg-init |
安裝完成后,我們就可以使用egg-init命令來(lái)創(chuàng)建 Egg.js 項(xiàng)目了。這個(gè)工具就像是一把神奇的鑰匙,為我們打開(kāi)了 Egg.js 開(kāi)發(fā)的大門(mén),讓我們能夠迅速搭建起項(xiàng)目的基本框架,開(kāi)啟高效的開(kāi)發(fā)之旅。
(三)創(chuàng)建 Egg.js 項(xiàng)目
使用egg-init命令創(chuàng)建 Egg.js 項(xiàng)目非常簡(jiǎn)單。在命令行中,進(jìn)入你想要?jiǎng)?chuàng)建項(xiàng)目的目錄,然后執(zhí)行以下命令:
egg-init my-egg-project --type=simple |
這里的my-egg-project是你為項(xiàng)目取的名字,你可以根據(jù)自己的喜好進(jìn)行修改。--type=simple表示創(chuàng)建一個(gè)簡(jiǎn)單的 Egg.js 項(xiàng)目模板,如果你想要?jiǎng)?chuàng)建更復(fù)雜的項(xiàng)目,還可以選擇其他類型的模板。
執(zhí)行完上述命令后,egg-init會(huì)在當(dāng)前目錄下創(chuàng)建一個(gè)名為my-egg-project的文件夾,并在其中生成項(xiàng)目的基本結(jié)構(gòu)。項(xiàng)目目錄結(jié)構(gòu)如下:
my-egg-project ├── app │ ??├── controller │ ??│ ??└── home.js │ ??├── service │ ??└── router.js ├── config │ ??├── config.default.js │ ??├── plugin.js │ ??└── config.prod.js ├── test │ ??├── app │ ??│ ??├── controller │ ??│ ??│ ??└── home.test.js │ ??│ ??└── service │ ??└── middleware ├── README.md └── package.json |
各目錄的作用如下:
app目錄:存放應(yīng)用的核心代碼,包括控制器(controller)、服務(wù)(service)和路由(router)等。
config目錄:存放項(xiàng)目的配置文件,如config.default.js是默認(rèn)配置文件,plugin.js用于配置插件,config.prod.js是生產(chǎn)環(huán)境的配置文件。
test目錄:存放測(cè)試用例,用于對(duì)應(yīng)用進(jìn)行單元測(cè)試和集成測(cè)試 。
package.json:項(xiàng)目的依賴管理文件,記錄了項(xiàng)目所依賴的包及其版本信息。
通過(guò)以上步驟,我們就完成了 Egg.js 項(xiàng)目的初始化,接下來(lái)就可以開(kāi)始在這個(gè)基礎(chǔ)上進(jìn)行應(yīng)用的開(kāi)發(fā)了。
三、Egg.js 核心概念與基礎(chǔ)用法
(一)路由與控制器
路由在 Egg.js 中扮演著非常重要的角色,它就像是一個(gè)交通樞紐的調(diào)度員,負(fù)責(zé)定義 URL 和處理邏輯之間的映射關(guān)系 。通過(guò)合理配置路由,我們能夠準(zhǔn)確地將不同的 URL 請(qǐng)求引導(dǎo)到對(duì)應(yīng)的處理函數(shù),確保請(qǐng)求的高效分發(fā)和處理。而控制器則是處理請(qǐng)求邏輯的核心場(chǎng)所,它負(fù)責(zé)接收客戶端發(fā)送的請(qǐng)求,對(duì)請(qǐng)求數(shù)據(jù)進(jìn)行處理,并調(diào)用相應(yīng)的服務(wù)層方法來(lái)完成業(yè)務(wù)邏輯的處理,最后將處理結(jié)果返回給客戶端。
在 Egg.js 項(xiàng)目中,路由規(guī)則通常在app/router.js文件中進(jìn)行定義。例如,我們想要定義一個(gè)簡(jiǎn)單的路由規(guī)則,當(dāng)用戶訪問(wèn)根路徑/時(shí),調(diào)用home控制器的index方法,可以這樣寫(xiě):
// app/router.js module.exports = app => { ??const { router, controller } = app; ??router.get('/', controller.home.index); }; |
在這個(gè)例子中,router.get表示定義一個(gè)處理 GET 請(qǐng)求的路由,第一個(gè)參數(shù)'/'是 URL 路徑,第二個(gè)參數(shù)controller.home.index指定了處理該請(qǐng)求的控制器方法。
控制器文件一般存放在app/controller目錄下。比如,我們?cè)?span style="background-color:#00ffff">app/controller/home.js中編寫(xiě)index方法的處理邏輯:
// app/controller/home.js const { Controller } = require('egg'); class HomeController extends Controller { ??async index() { ????this.ctx.body = 'Hello, Egg.js!'; ??} } module.exports = HomeController; |
在上述代碼中,HomeController類繼承自Controller,index方法通過(guò)this.ctx.body將響應(yīng)內(nèi)容設(shè)置為'Hello, Egg.js!',這樣當(dāng)用戶訪問(wèn)根路徑時(shí),就能看到這個(gè)響應(yīng)信息。
除了 GET 請(qǐng)求,Egg.js 也能輕松處理其他類型的請(qǐng)求,如 POST 請(qǐng)求。假設(shè)我們要處理一個(gè)用戶注冊(cè)的 POST 請(qǐng)求,在router.js中定義路由:
// app/router.js module.exports = app => { ??const { router, controller } = app; ??router.post('/register', controller.user.register); }; |
然后在app/controller/user.js中編寫(xiě)register方法:
// app/controller/user.js const { Controller } = require('egg'); class UserController extends Controller { ??async register() { ????const { ctx } = this; ????const { username, password } = ctx.request.body; ????// 這里可以進(jìn)行用戶注冊(cè)的邏輯處理,比如將用戶信息保存到數(shù)據(jù)庫(kù) ????ctx.body = { success: true, message: '用戶注冊(cè)成功' }; ??} } module.exports = UserController; |
在這個(gè)例子中,ctx.request.body用于獲取 POST 請(qǐng)求的參數(shù),通過(guò)解構(gòu)賦值獲取username和password,然后進(jìn)行相應(yīng)的業(yè)務(wù)處理,并返回注冊(cè)成功的響應(yīng)信息。
(二)服務(wù)(Service)層
服務(wù)層在 Egg.js 應(yīng)用中起著至關(guān)重要的作用,它就像是一個(gè)幕后的工作團(tuán)隊(duì),主要負(fù)責(zé)封裝業(yè)務(wù)邏輯,將復(fù)雜的業(yè)務(wù)操作抽象成一個(gè)個(gè)獨(dú)立的方法,提高代碼的復(fù)用性和可維護(hù)性 。當(dāng)控制器接收到請(qǐng)求后,通常會(huì)調(diào)用服務(wù)層的方法來(lái)完成具體的業(yè)務(wù)邏輯處理,這樣可以使控制器的代碼更加簡(jiǎn)潔,專注于請(qǐng)求和響應(yīng)的處理,而將業(yè)務(wù)邏輯的實(shí)現(xiàn)放在服務(wù)層中,實(shí)現(xiàn)了業(yè)務(wù)邏輯與控制器的分離,使得代碼結(jié)構(gòu)更加清晰。
在 Egg.js 項(xiàng)目中,服務(wù)層的文件通常存放在app/service目錄下。我們以一個(gè)簡(jiǎn)單的用戶管理功能為例,假設(shè)我們需要從數(shù)據(jù)庫(kù)中獲取用戶信息,就可以在服務(wù)層編寫(xiě)相應(yīng)的方法。首先,在app/service/user.js中創(chuàng)建一個(gè)UserService類,并編寫(xiě)獲取用戶信息的方法:
// app/service/user.js const { Service } = require('egg'); class UserService extends Service { ??async getUserById(id) { ????// 這里可以使用數(shù)據(jù)庫(kù)操作庫(kù),如Sequelize或Mongoose,來(lái)查詢數(shù)據(jù)庫(kù)獲取用戶信息 ????// 為了演示方便,這里假設(shè)從數(shù)據(jù)庫(kù)中查詢到的用戶信息如下 ????const user = { ??????id, ??????name: '張三', ??????age: 25, ??????email: 'zhangsan@example.com' ????}; ????return user; ??} } module.exports = UserService; |
在上述代碼中,UserService類繼承自Service,getUserById方法接受一個(gè)id參數(shù),用于查詢指定用戶的信息。在實(shí)際應(yīng)用中,這里會(huì)使用數(shù)據(jù)庫(kù)操作庫(kù)與數(shù)據(jù)庫(kù)進(jìn)行交互,獲取真實(shí)的用戶數(shù)據(jù),這里為了簡(jiǎn)化演示,直接返回了一個(gè)模擬的用戶對(duì)象。
接下來(lái),在控制器中調(diào)用服務(wù)層的方法來(lái)獲取用戶信息。在app/controller/user.js中編寫(xiě)如下代碼:
// app/controller/user.js const { Controller } = require('egg'); class UserController extends Controller { ??async info() { ????const { ctx } = this; ????const id = ctx.params.id; ????const user = await ctx.service.user.getUserById(id); ????ctx.body = user; ??} } module.exports = UserController; |
在這個(gè)控制器的info方法中,首先通過(guò)ctx.params.id獲取路由參數(shù)中的用戶 ID,然后調(diào)用ctx.service.user.getUserById(id)方法,從服務(wù)層獲取用戶信息,最后將用戶信息通過(guò)ctx.body返回給客戶端。通過(guò)這種方式,控制器和服務(wù)層相互協(xié)作,實(shí)現(xiàn)了用戶信息查詢的功能,同時(shí)也將業(yè)務(wù)邏輯和請(qǐng)求處理邏輯進(jìn)行了有效的分離,提高了代碼的可維護(hù)性和復(fù)用性。
(三)中間件(Middleware)
中間件在 Egg.js 的請(qǐng)求處理鏈中扮演著非常關(guān)鍵的角色,它就像是一個(gè)關(guān)卡的守衛(wèi),在請(qǐng)求到達(dá)控制器之前和響應(yīng)返回給客戶端之前,對(duì)請(qǐng)求和響應(yīng)進(jìn)行各種處理,比如日志記錄、身份驗(yàn)證、錯(cuò)誤處理等 。通過(guò)使用中間件,我們可以在不修改業(yè)務(wù)邏輯的前提下,方便地對(duì)應(yīng)用的功能進(jìn)行擴(kuò)展和增強(qiáng),使得應(yīng)用的功能更加豐富和完善。
Egg.js 提供了一些內(nèi)置中間件,如bodyParser用于解析請(qǐng)求體,static用于處理靜態(tài)文件服務(wù)等。這些內(nèi)置中間件為我們的開(kāi)發(fā)提供了很大的便利,減少了我們重復(fù)開(kāi)發(fā)基礎(chǔ)功能的工作量。例如,bodyParser中間件可以自動(dòng)解析請(qǐng)求體中的數(shù)據(jù),使得我們?cè)诳刂破髦锌梢灾苯油ㄟ^(guò)ctx.request.body獲取請(qǐng)求參數(shù),無(wú)需手動(dòng)解析。
除了內(nèi)置中間件,我們還可以根據(jù)項(xiàng)目的具體需求自定義中間件。自定義中間件通常存放在app/middleware目錄下。下面我們以一個(gè)簡(jiǎn)單的日志記錄中間件為例,展示如何自定義中間件。在app/middleware/log.js中編寫(xiě)如下代碼:
// app/middleware/log.js module.exports = () => { ??return async (ctx, next) => { ????console.log(`[${new Date().toISOString()}] ${ctx.method} ${ctx.url}`); ????await next(); ??}; }; |
在這個(gè)中間件中,首先打印出請(qǐng)求的時(shí)間、方法和 URL,然后通過(guò)await next()將控制權(quán)交給下一個(gè)中間件或控制器。當(dāng)所有中間件和控制器處理完成后,程序會(huì)回到這個(gè)中間件繼續(xù)執(zhí)行后續(xù)代碼。
要啟用中間件,需要在config.default.js配置文件中進(jìn)行配置。在config/config.default.js中添加如下代碼:
// config/config.default.js module.exports = appInfo => { ??const config = {}; ??config.middleware = ['log']; ??return config; }; |
在上述配置中,config.middleware數(shù)組中添加了'log',表示啟用log中間件。這樣,當(dāng)應(yīng)用接收到請(qǐng)求時(shí),log中間件就會(huì)對(duì)請(qǐng)求進(jìn)行處理,打印出相應(yīng)的日志信息。
(四)配置文件
Egg.js 的配置文件在整個(gè)項(xiàng)目中起著舉足輕重的作用,它就像是一個(gè)項(xiàng)目的指揮中心,用于存儲(chǔ)項(xiàng)目的各種配置信息,如數(shù)據(jù)庫(kù)連接配置、服務(wù)器端口設(shè)置、中間件配置、插件配置等 。通過(guò)合理配置這些信息,我們可以靈活地調(diào)整應(yīng)用的行為和功能,使其適應(yīng)不同的開(kāi)發(fā)環(huán)境和業(yè)務(wù)需求。
Egg.js 的配置文件主要位于config目錄下,其中config.default.js是默認(rèn)配置文件,它包含了應(yīng)用的基礎(chǔ)配置項(xiàng),這些配置項(xiàng)在所有環(huán)境下都會(huì)生效 。例如,我們可以在config.default.js中設(shè)置應(yīng)用的端口號(hào)、日志級(jí)別等:
// config/config.default.js module.exports = appInfo => { ??const config = {}; ??// 設(shè)置應(yīng)用端口號(hào) ??config.port = 7001; ??// 設(shè)置日志級(jí)別 ??config.logger = { ????level: 'info' ??}; ??return config; }; |
在上述代碼中,config.port設(shè)置了應(yīng)用運(yùn)行的端口號(hào)為7001,config.logger.level設(shè)置了日志級(jí)別為'info',這樣應(yīng)用在運(yùn)行時(shí)就會(huì)按照這些配置進(jìn)行工作。
除了config.default.js,Egg.js 還支持根據(jù)不同的環(huán)境設(shè)置特定的配置文件,如config.local.js用于本地開(kāi)發(fā)環(huán)境,config.prod.js用于生產(chǎn)環(huán)境等。這些環(huán)境特定的配置文件會(huì)覆蓋config.default.js中的相應(yīng)配置,從而實(shí)現(xiàn)不同環(huán)境下的差異化配置。例如,在生產(chǎn)環(huán)境中,我們可能需要修改數(shù)據(jù)庫(kù)連接配置,在config.prod.js中可以這樣寫(xiě):
// config/config.prod.js module.exports = appInfo => { ??const config = {}; ??// 生產(chǎn)環(huán)境數(shù)據(jù)庫(kù)配置 ??config.mysql = { ????client: { ??????host: 'prod-database-host', ??????port: 3306, ??????user: 'prod-user', ??????password: 'prod-password', ??????database: 'prod-database' ????}, ????connection: { ??????timeout: '3000ms' ????} ??}; ??return config; }; |
在這個(gè)例子中,config.prod.js中定義了生產(chǎn)環(huán)境下的數(shù)據(jù)庫(kù)連接配置,當(dāng)應(yīng)用在生產(chǎn)環(huán)境中運(yùn)行時(shí),會(huì)加載這些配置,而config.default.js中的數(shù)據(jù)庫(kù)配置則會(huì)被覆蓋,確保應(yīng)用在不同環(huán)境下都能正確連接到相應(yīng)的數(shù)據(jù)庫(kù)。通過(guò)這種靈活的配置方式,我們可以輕松地管理不同環(huán)境下的應(yīng)用配置,提高開(kāi)發(fā)和部署的效率。
四、實(shí)戰(zhàn)案例:構(gòu)建一個(gè)簡(jiǎn)單的博客系統(tǒng)
(一)功能需求分析
為了讓大家更深入地了解 Egg.js 在實(shí)際項(xiàng)目中的應(yīng)用,我們將以構(gòu)建一個(gè)簡(jiǎn)單的博客系統(tǒng)為例,一步步展示如何使用 Egg.js 實(shí)現(xiàn)一個(gè)完整的 Web 應(yīng)用。在開(kāi)始編碼之前,我們首先需要明確博客系統(tǒng)的功能需求。
這個(gè)博客系統(tǒng)主要包含以下幾個(gè)核心功能:
文章列表:展示所有文章的列表,包括文章標(biāo)題、簡(jiǎn)介、發(fā)布時(shí)間等信息,方便用戶快速瀏覽和選擇感興趣的文章 。用戶可以在這個(gè)頁(yè)面上看到最新發(fā)布的文章,以及文章的簡(jiǎn)要概述,從而決定是否深入閱讀。
文章詳情:點(diǎn)擊文章列表中的某篇文章,能夠查看該文章的詳細(xì)內(nèi)容,包括完整的文章正文、作者信息、評(píng)論區(qū)等 。文章詳情頁(yè)面為用戶提供了全面的閱讀體驗(yàn),讓用戶能夠深入了解文章的內(nèi)容,并與其他讀者進(jìn)行交流互動(dòng)。
創(chuàng)建文章:博主可以在后臺(tái)創(chuàng)建新的文章,填寫(xiě)文章標(biāo)題、正文、分類等信息 。創(chuàng)建文章功能是博主分享知識(shí)和觀點(diǎn)的重要途徑,確保了博客內(nèi)容的不斷更新和豐富。
更新文章:對(duì)于已發(fā)布的文章,博主可以進(jìn)行編輯和更新,修改文章的內(nèi)容、標(biāo)題、分類等信息 。這一功能使得博主能夠及時(shí)修正文章中的錯(cuò)誤,或者根據(jù)新的想法和觀點(diǎn)對(duì)文章進(jìn)行完善。
刪除文章:如果某篇文章不再需要,博主可以將其刪除 。刪除文章功能可以幫助博主清理博客內(nèi)容,保持博客的整潔和有序。
明確了這些功能需求后,我們就可以開(kāi)始進(jìn)行數(shù)據(jù)庫(kù)設(shè)計(jì)和項(xiàng)目的搭建了。
(二)數(shù)據(jù)庫(kù)設(shè)計(jì)
對(duì)于博客系統(tǒng),我們選擇 MySQL 數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)數(shù)據(jù)。根據(jù)功能需求,我們需要設(shè)計(jì)一個(gè)文章表,用于存儲(chǔ)文章的相關(guān)信息。以下是文章表的字段設(shè)計(jì):
CREATE TABLE articles ( ??id INT AUTO_INCREMENT PRIMARY KEY, ??title VARCHAR(255) NOT NULL, ??content TEXT NOT NULL, ??author VARCHAR(50) NOT NULL, ??create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ??update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); |
在這個(gè)建表語(yǔ)句中:
id?是文章的唯一標(biāo)識(shí),使用自增長(zhǎng)的整數(shù)類型 ,作為主鍵,確保每篇文章都有一個(gè)唯一的編號(hào),方便在數(shù)據(jù)庫(kù)中進(jìn)行查詢和操作。
title?用于存儲(chǔ)文章的標(biāo)題,最大長(zhǎng)度為 255 個(gè)字符 ,不能為空,文章標(biāo)題是吸引讀者的重要元素,所以需要明確且具有吸引力。
content?用于存儲(chǔ)文章的正文內(nèi)容,使用 TEXT?類型可以存儲(chǔ)大量的文本 ,滿足文章內(nèi)容多樣化的需求。
author?記錄文章的作者,最大長(zhǎng)度為 50 個(gè)字符 ,不能為空,明確文章的作者有助于責(zé)任追溯和讀者對(duì)作者的認(rèn)知。
create_time?記錄文章的創(chuàng)建時(shí)間,使用 TIMESTAMP?類型,默認(rèn)值為當(dāng)前時(shí)間 ,方便記錄文章的發(fā)布時(shí)間順序。
update_time?記錄文章的更新時(shí)間,同樣使用 TIMESTAMP?類型,默認(rèn)值為當(dāng)前時(shí)間,并且在文章更新時(shí)自動(dòng)更新為當(dāng)前時(shí)間 ,讓博主和讀者能夠了解文章的最新?tīng)顟B(tài)。
通過(guò)這樣的數(shù)據(jù)庫(kù)設(shè)計(jì),我們就為博客系統(tǒng)的數(shù)據(jù)存儲(chǔ)奠定了基礎(chǔ)。
(三)搭建項(xiàng)目框架
接下來(lái),我們按照前面介紹的步驟搭建 Egg.js 項(xiàng)目。在命令行中執(zhí)行以下命令創(chuàng)建一個(gè)新的 Egg.js 項(xiàng)目:
egg-init blog-system --type=simple cd blog-system npm install |
這里創(chuàng)建了一個(gè)名為 blog-system?的 Egg.js 項(xiàng)目,并進(jìn)入項(xiàng)目目錄安裝依賴。
為了連接 MySQL 數(shù)據(jù)庫(kù),我們需要安裝 egg-mysql?插件。在項(xiàng)目目錄下執(zhí)行以下命令進(jìn)行安裝:
npm install egg-mysql --save |
安裝完成后,在 config/plugin.js?文件中配置插件:
// config/plugin.js exports.mysql = { ??enable: true, ??package: 'egg-mysql' }; |
然后在 config/config.default.js?文件中配置數(shù)據(jù)庫(kù)連接信息:
// config/config.default.js config.mysql = { ??client: { ????host: 'localhost', ????port: '3306', ????user: 'root', ????password: '123456', ????database: 'blog_db' ??}, ??app: true, ??agent: false }; |
請(qǐng)根據(jù)實(shí)際情況修改數(shù)據(jù)庫(kù)的連接信息,確保能夠正確連接到 MySQL 數(shù)據(jù)庫(kù)。通過(guò)這些配置,我們就完成了項(xiàng)目框架的搭建和數(shù)據(jù)庫(kù)連接的配置,為后續(xù)的業(yè)務(wù)功能實(shí)現(xiàn)做好了準(zhǔn)備。
(四)實(shí)現(xiàn)業(yè)務(wù)功能
在完成項(xiàng)目框架搭建和數(shù)據(jù)庫(kù)配置后,我們開(kāi)始實(shí)現(xiàn)博客系統(tǒng)的各項(xiàng)業(yè)務(wù)功能。
首先是路由配置,在 app/router.js?文件中定義博客系統(tǒng)的路由規(guī)則:
// app/router.js module.exports = app => { ??const { router, controller } = app; ??// 文章列表 ??router.get('/articles', controller.article.list); ??// 文章詳情 ??router.get('/articles/:id', controller.article.detail); ??// 創(chuàng)建文章 ??router.post('/articles', controller.article.create); ??// 更新文章 ??router.put('/articles/:id', controller.article.update); ??// 刪除文章 ??router.delete('/articles/:id', controller.article.delete); }; |
上述代碼中,分別定義了獲取文章列表、文章詳情、創(chuàng)建文章、更新文章和刪除文章的路由。
接下來(lái)是控制器的實(shí)現(xiàn),在 app/controller/article.js?文件中編寫(xiě)控制器代碼:
// app/controller/article.js const { Controller } = require('egg'); class ArticleController extends Controller { ??// 獲取文章列表 ??async list() { ????const articles = await this.ctx.service.article.list(); ????this.ctx.body = articles; ??} ??// 獲取文章詳情 ??async detail() { ????const id = this.ctx.params.id; ????const article = await this.ctx.service.article.detail(id); ????if (article) { ??????this.ctx.body = article; ????} else { ??????this.ctx.status = 404; ??????this.ctx.body = { message: '文章未找到' }; ????} ??} ??// 創(chuàng)建文章 ??async create() { ????const { title, content, author } = this.ctx.request.body; ????const article = await this.ctx.service.article.create({ title, content, author }); ????if (article) { ??????this.ctx.status = 201; ??????this.ctx.body = article; ????} else { ??????this.ctx.status = 500; ??????this.ctx.body = { message: '文章創(chuàng)建失敗' }; ????} ??} ??// 更新文章 ??async update() { ????const id = this.ctx.params.id; ????const { title, content, author } = this.ctx.request.body; ????const article = await this.ctx.service.article.update(id, { title, content, author }); ????if (article) { ??????this.ctx.body = article; ????} else { ??????this.ctx.status = 500; ??????this.ctx.body = { message: '文章更新失敗' }; ????} ??} ??// 刪除文章 ??async delete() { ????const id = this.ctx.params.id; ????const result = await this.ctx.service.article.delete(id); ????if (result) { ??????this.ctx.body = { message: '文章刪除成功' }; ????} else { ??????this.ctx.status = 500; ??????this.ctx.body = { message: '文章刪除失敗' }; ????} ??} } module.exports = ArticleController; |
在控制器中,通過(guò)調(diào)用服務(wù)層的方法來(lái)實(shí)現(xiàn)具體的業(yè)務(wù)邏輯,并根據(jù)不同的業(yè)務(wù)場(chǎng)景返回相應(yīng)的響應(yīng)。
服務(wù)層的代碼在 app/service/article.js?文件中編寫(xiě):
// app/service/article.js const { Service } = require('egg'); class ArticleService extends Service { ??// 獲取文章列表 ??async list() { ????const sql = 'SELECT * FROM articles'; ????return await this.app.mysql.query(sql); ??} ??// 獲取文章詳情 ??async detail(id) { ????const sql = 'SELECT * FROM articles WHERE id =?'; ????return await this.app.mysql.query(sql, [id]); ??} ??// 創(chuàng)建文章 ??async create(article) { ????const result = await this.app.mysql.insert('articles', article); ????if (result.affectedRows === 1) { ??????return { id: result.insertId, ...article }; ????} ????return null; ??} ??// 更新文章 ??async update(id, article) { ????const result = await this.app.mysql.update('articles', { id, ...article }); ????if (result.affectedRows === 1) { ??????return { id, ...article }; ????} ????return null; ??} ??// 刪除文章 ??async delete(id) { ????const result = await this.app.mysql.delete('articles', { id }); ????return result.affectedRows === 1; ??} } module.exports = ArticleService; |
服務(wù)層主要負(fù)責(zé)與數(shù)據(jù)庫(kù)進(jìn)行交互,執(zhí)行具體的數(shù)據(jù)庫(kù)操作,如查詢、插入、更新和刪除等。通過(guò)這種分層的設(shè)計(jì),使得代碼結(jié)構(gòu)更加清晰,易于維護(hù)和擴(kuò)展。
(五)模板渲染與頁(yè)面展示
為了將博客系統(tǒng)的內(nèi)容展示給用戶,我們需要進(jìn)行模板渲染和頁(yè)面展示。這里我們使用 EJS 模板引擎,它是一種簡(jiǎn)潔、靈活的模板語(yǔ)言,可以幫助我們構(gòu)建動(dòng)態(tài)的 HTML 頁(yè)面 。
首先安裝 EJS 模板引擎相關(guān)的插件:
npm install egg-view-ejs --save |
安裝完成后,在 config/plugin.js?文件中配置 EJS 插件:
// config/plugin.js exports.ejs = { ??enable: true, ??package: 'egg-view-ejs' }; |
然后在 config/config.default.js?文件中配置視圖相關(guān)的信息:
// config/config.default.js config.view = { ??defaultViewEngine: 'ejs', ??mapping: { ????'.html': 'ejs' ??} }; |
接下來(lái),在控制器中進(jìn)行模板渲染。以文章列表頁(yè)面為例,在 app/controller/article.js?文件中修改 list?方法:
// app/controller/article.js async list() { ??const articles = await this.ctx.service.article.list(); ??await this.ctx.render('article/list.html', { articles }); } |
這里將獲取到的文章列表數(shù)據(jù)傳遞給 article/list.html?模板文件進(jìn)行渲染。
在 app/view/article?目錄下創(chuàng)建 list.html?模板文件,代碼如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> ??<meta charset="UTF-8"> ??<title>文章列表</title> </head> <body> ??<h1>文章列表</h1> ??<ul> ????<% articles.forEach(article => { %> ??????<li> ????????<a href="/articles/<%= article.id %>"><%= article.title %></a> - <%= article.author %> - <%= article.create_time %> ??????</li> ????<% }); %> ??</ul> </body> </html> |
在這個(gè)模板文件中,使用 EJS 的語(yǔ)法循環(huán)遍歷文章列表數(shù)據(jù),并生成相應(yīng)的 HTML 列表項(xiàng),每個(gè)列表項(xiàng)包含文章標(biāo)題、作者和創(chuàng)建時(shí)間,并通過(guò)鏈接跳轉(zhuǎn)到文章詳情頁(yè)面。
通過(guò)以上步驟,我們就完成了博客系統(tǒng)的模板渲染和頁(yè)面展示部分。當(dāng)用戶訪問(wèn)文章列表頁(yè)面時(shí),就能看到渲染后的 HTML 頁(yè)面,展示出文章的相關(guān)信息。這樣,一個(gè)簡(jiǎn)單的博客系統(tǒng)就基本完成了,通過(guò)這個(gè)實(shí)戰(zhàn)案例,希望大家能夠?qū)?Egg.js 的開(kāi)發(fā)流程和應(yīng)用有更深入的理解和掌握。
五、常見(jiàn)問(wèn)題與優(yōu)化
(一)常見(jiàn)錯(cuò)誤及解決方法
在使用 Egg.js 進(jìn)行開(kāi)發(fā)的過(guò)程中,我們可能會(huì)遇到各種各樣的錯(cuò)誤。以下是一些常見(jiàn)錯(cuò)誤及解決方法:
路由錯(cuò)誤:路由配置錯(cuò)誤是開(kāi)發(fā)中常見(jiàn)的問(wèn)題之一,比如路由路徑寫(xiě)錯(cuò)、請(qǐng)求方法不匹配等 。如果在訪問(wèn)某個(gè) URL 時(shí)出現(xiàn) “404 Not Found” 錯(cuò)誤,首先檢查app/router.js中的路由配置是否正確,確保路由路徑與請(qǐng)求的 URL 一致,并且請(qǐng)求方法(如 GET、POST 等)也匹配。例如,若在訪問(wèn)/user路徑時(shí)出現(xiàn) 404 錯(cuò)誤,而在router.js中定義的路由是router.get('/users', controller.user.list);,這里路徑就不一致,需要將路由路徑修改為router.get('/user', controller.user.list);。
數(shù)據(jù)庫(kù)連接失敗:在連接數(shù)據(jù)庫(kù)時(shí),可能會(huì)因?yàn)榕渲缅e(cuò)誤、數(shù)據(jù)庫(kù)服務(wù)未啟動(dòng)等原因?qū)е逻B接失敗 。以 MySQL 數(shù)據(jù)庫(kù)為例,如果在配置文件config/config.default.js中配置的數(shù)據(jù)庫(kù)連接信息有誤,如用戶名、密碼、主機(jī)地址或端口號(hào)錯(cuò)誤,就會(huì)導(dǎo)致連接失敗。此時(shí),需要仔細(xì)檢查配置信息,確保其與數(shù)據(jù)庫(kù)實(shí)際設(shè)置一致。另外,也要確保數(shù)據(jù)庫(kù)服務(wù)已經(jīng)正常啟動(dòng),可以通過(guò)命令行工具嘗試連接數(shù)據(jù)庫(kù)來(lái)驗(yàn)證。例如,使用mysql -u用戶名 -p密碼 -h主機(jī)地址 -P端口號(hào)命令進(jìn)行連接測(cè)試,如果連接失敗,根據(jù)錯(cuò)誤提示進(jìn)行相應(yīng)的排查和修復(fù)。
中間件配置錯(cuò)誤:中間件配置不當(dāng)也會(huì)引發(fā)問(wèn)題,如中間件未正確加載、中間件順序錯(cuò)誤等 。在config/config.default.js中配置中間件時(shí),要確保中間件名稱拼寫(xiě)正確,并且已經(jīng)在config/plugin.js中正確啟用。同時(shí),中間件的順序也很重要,因?yàn)橹虚g件是按照配置順序依次執(zhí)行的。例如,若先配置了一個(gè)用于解析請(qǐng)求體的中間件,再配置一個(gè)用于日志記錄的中間件,而實(shí)際需求是先記錄日志再解析請(qǐng)求體,就需要調(diào)整中間件的順序??梢詫⑷罩居涗浿虚g件放在前面,如config.middleware = ['log', 'bodyParser'];。
(二)性能優(yōu)化
為了提高 Egg.js 應(yīng)用的性能,我們可以從以下幾個(gè)方面進(jìn)行優(yōu)化:
代碼層面:
優(yōu)化算法:在編寫(xiě)業(yè)務(wù)邏輯代碼時(shí),選擇高效的算法和數(shù)據(jù)結(jié)構(gòu),避免使用復(fù)雜度過(guò)高的算法,以減少計(jì)算時(shí)間 。例如,在進(jìn)行數(shù)組查找時(shí),使用二分查找算法(前提是數(shù)組已排序)比普通的線性查找算法效率要高得多。
合理使用異步編程:充分利用 Node.js 的異步特性,使用async/await或Promise來(lái)處理異步操作,避免阻塞線程,提高應(yīng)用的并發(fā)處理能力 。比如在調(diào)用數(shù)據(jù)庫(kù)查詢方法或進(jìn)行文件讀取操作時(shí),這些操作通常是異步的,使用async/await可以使代碼看起來(lái)更加簡(jiǎn)潔和易讀,同時(shí)保證異步操作的正確執(zhí)行順序。例如:
async function getData() { ????const result1 = await someAsyncFunction1(); ????const result2 = await someAsyncFunction2(); ????return result1 + result2; } |
服務(wù)器層面:
啟用緩存:對(duì)于頻繁訪問(wèn)且數(shù)據(jù)變動(dòng)不大的內(nèi)容,可以使用緩存技術(shù),如內(nèi)存緩存(如node-cache)或分布式緩存(如 Redis),減少重復(fù)計(jì)算和數(shù)據(jù)庫(kù)查詢,提高響應(yīng)速度 。以文章列表數(shù)據(jù)為例,如果文章列表更新頻率不高,我們可以在服務(wù)啟動(dòng)時(shí)將文章列表數(shù)據(jù)緩存起來(lái),當(dāng)有請(qǐng)求到來(lái)時(shí),先從緩存中獲取數(shù)據(jù),如果緩存中有數(shù)據(jù),直接返回,無(wú)需再次查詢數(shù)據(jù)庫(kù),大大提高了響應(yīng)速度。例如,使用node-cache實(shí)現(xiàn)簡(jiǎn)單的緩存:
const NodeCache = require('node-cache'); const cache = new NodeCache(); async function getArticleList() { ????let articles = cache.get('articleList'); ????if (articles) { ????????return articles; ????} ????articles = await queryArticleListFromDatabase();// 從數(shù)據(jù)庫(kù)查詢文章列表的函數(shù) ????cache.set('articleList', articles); ????return articles; } |
負(fù)載均衡:在高并發(fā)場(chǎng)景下,使用負(fù)載均衡技術(shù)(如 Nginx)將請(qǐng)求分發(fā)到多個(gè)服務(wù)器實(shí)例上,減輕單個(gè)服務(wù)器的壓力,提高系統(tǒng)的整體性能和可用性 ??梢耘渲?Nginx 將請(qǐng)求按照一定的規(guī)則(如輪詢、IP 哈希等)分發(fā)到多個(gè) Egg.js 應(yīng)用實(shí)例上,確保每個(gè)實(shí)例都能合理地分擔(dān)負(fù)載。
數(shù)據(jù)庫(kù)層面:
優(yōu)化數(shù)據(jù)庫(kù)查詢:合理設(shè)計(jì)數(shù)據(jù)庫(kù)表結(jié)構(gòu)和索引,避免全表掃描,提高查詢效率 。例如,在查詢用戶信息時(shí),如果經(jīng)常根據(jù)用戶 ID 進(jìn)行查詢,就應(yīng)該為用戶 ID 字段創(chuàng)建索引,這樣可以大大加快查詢速度??梢允褂脭?shù)據(jù)庫(kù)管理工具(如 MySQL Workbench)來(lái)創(chuàng)建索引,在創(chuàng)建表時(shí)或者后期添加索引都可以,例如:
CREATE INDEX idx_user_id ON users(user_id); |
連接池配置:配置合適的數(shù)據(jù)庫(kù)連接池大小,避免頻繁創(chuàng)建和銷毀數(shù)據(jù)庫(kù)連接,提高數(shù)據(jù)庫(kù)連接的復(fù)用率 。在 Egg.js 中使用egg-mysql插件時(shí),可以在config/config.default.js中配置連接池參數(shù),如maxConnections(最大連接數(shù))和minConnections(最小連接數(shù))等,根據(jù)應(yīng)用的實(shí)際并發(fā)情況來(lái)調(diào)整這些參數(shù),以達(dá)到最佳的性能表現(xiàn)。例如:
config.mysql = { ????client: { ????????host: 'localhost', ????????port: '3306', ????????user: 'root', ????????password: '123456', ????????database: 'test', ????????// 連接池配置 ????????maxConnections: 10, ????????minConnections: 2 ????}, ????app: true, ????agent: false }; |
六、總結(jié)與展望
通過(guò)以上內(nèi)容,我們對(duì) Egg.js 從入門(mén)到實(shí)戰(zhàn)進(jìn)行了全面且深入的探索。從 Egg.js 的基礎(chǔ)概念,到環(huán)境搭建、核心概念的運(yùn)用,再到通過(guò)實(shí)戰(zhàn)案例構(gòu)建一個(gè)完整的博客系統(tǒng),以及在開(kāi)發(fā)過(guò)程中常見(jiàn)問(wèn)題的解決和性能優(yōu)化的方法,相信大家對(duì) Egg.js 已經(jīng)有了較為清晰的認(rèn)識(shí)和掌握。
Egg.js 作為基于 Koa 構(gòu)建的企業(yè)級(jí) Node.js Web 應(yīng)用框架,其模塊化設(shè)計(jì)、內(nèi)置中間件、靈活配置和強(qiáng)大插件機(jī)制等特性,為我們的開(kāi)發(fā)工作帶來(lái)了諸多便利,讓我們能夠高效地構(gòu)建穩(wěn)定、可擴(kuò)展的應(yīng)用程序 。
在未來(lái),隨著 Node.js 技術(shù)的不斷發(fā)展和應(yīng)用場(chǎng)景的持續(xù)拓展,Egg.js 也將迎來(lái)更多的機(jī)遇和挑戰(zhàn)。它有望在微服務(wù)架構(gòu)、Serverless 架構(gòu)等新興領(lǐng)域發(fā)揮更大的作用,進(jìn)一步提升其在企業(yè)級(jí)應(yīng)用開(kāi)發(fā)中的地位。同時(shí),Egg.js 的社區(qū)也在不斷壯大,更多優(yōu)秀的插件和工具將會(huì)涌現(xiàn),為開(kāi)發(fā)者提供更加豐富的資源和更強(qiáng)大的支持。
希望大家在今后的開(kāi)發(fā)工作中,能夠積極運(yùn)用 Egg.js,不斷探索和實(shí)踐,充分發(fā)揮其優(yōu)勢(shì),創(chuàng)造出更多優(yōu)秀的應(yīng)用。如果你在學(xué)習(xí)和使用 Egg.js 的過(guò)程中有任何問(wèn)題或心得,歡迎在評(píng)論區(qū)留言分享,讓我們一起交流進(jìn)步 。