背景
和同事一起有一個(gè)公司內(nèi)部平臺(tái)的項(xiàng)目愁憔,平臺(tái)需要對(duì)于用戶上傳的圖片,視頻等資源進(jìn)行管理和存儲(chǔ)。
在項(xiàng)目一期而涉,由于申請(qǐng)DB資源的流程比較復(fù)雜,所以我們僅僅將用戶上傳的內(nèi)容記錄存儲(chǔ)在了localStorage
中联予,當(dāng)然這是很不安全的啼县,非常容易丟失材原,所以在二期的時(shí)候,我們開(kāi)始了接入DB的工作季眷。
技術(shù)選型
下面的整篇文章都會(huì)和這部分描述的技術(shù)棧相關(guān)余蟹,當(dāng)然,sequelize
在實(shí)際業(yè)務(wù)場(chǎng)景的使用相關(guān)內(nèi)容子刮,其實(shí)本身和技術(shù)棧的關(guān)系并不是非常大威酒,如果你在sequelize
的使用過(guò)程中遇到了問(wèn)題,這里或許會(huì)有解答~
mysql
采用mysql作為數(shù)據(jù)存儲(chǔ)引擎话告,其實(shí)本身也是無(wú)奈之舉兼搏,因?yàn)镈BA告訴我們目前MongoDB的資源不足,并且對(duì)于這種結(jié)構(gòu)化數(shù)據(jù)的存儲(chǔ)沙郭,mysql對(duì)于未來(lái)將平臺(tái)擴(kuò)展到整個(gè)公司使用佛呻,甚至對(duì)外開(kāi)放,則是必須的病线。
egg
作為一款比較成熟的node.js開(kāi)發(fā)框架吓著,公司toC端很多業(yè)務(wù)架構(gòu)都使用了基于egg的ReactSSR,egg對(duì)于sequelize
的支持還是比較好的送挑,提供了專門(mén)的插件來(lái)輔助使用sequelize
進(jìn)行DB接入绑莺。
sequelize
這才是本文的核心,sequelize
目前可以說(shuō)是目前最為成熟的node.js ORM框架了惕耕。CRUD操作不可能完全使用SQL語(yǔ)句進(jìn)行纺裁,這樣很容易出現(xiàn)各種SQL漏洞,一個(gè)成熟的ORM框架可以幫我們避免掉這些風(fēng)險(xiǎn)司澎,并且將CRUD操作封裝成對(duì)象函數(shù)方法之后欺缘,操作起來(lái)也更加方便,但是這樣會(huì)提升一定的開(kāi)發(fā)學(xué)習(xí)成本挤安。
開(kāi)始
整體的方案都出來(lái)了谚殊,剩下的就是爬坑。由于以前還是做過(guò)一些和數(shù)據(jù)庫(kù)有關(guān)的工作蛤铜,SQL語(yǔ)句和部分ORM的實(shí)現(xiàn)還有過(guò)一點(diǎn)接觸嫩絮,但是。围肥。剿干。我依然在坑里栽了很久,長(zhǎng)成了參天大樹(shù)穆刻,說(shuō)起參怨愤,我就想到西游記里面的人參果。蛹批。撰洗。文體兩開(kāi)花篮愉。
數(shù)據(jù)模型設(shè)計(jì)
接入DB,首先需要考慮就是如何設(shè)計(jì)數(shù)據(jù)模型差导。當(dāng)然這對(duì)于前端來(lái)說(shuō)试躏,還是有一些難度的。于是請(qǐng)教了最近在合作的后端大哥设褐。
關(guān)聯(lián)
一般來(lái)說(shuō)颠蕴,一個(gè)系統(tǒng)都需要有特定的用戶進(jìn)行登錄。又需要對(duì)于用戶進(jìn)行分組助析,一個(gè)用戶可以加入多個(gè)組犀被,一個(gè)組可以有多個(gè)用戶。n:m的關(guān)聯(lián)關(guān)系需要在設(shè)計(jì)數(shù)據(jù)模型的時(shí)候就體現(xiàn)出來(lái)外冀。
生產(chǎn)環(huán)境中寡键,n:m的關(guān)聯(lián)是需要中間表來(lái)輔助的,來(lái)存儲(chǔ)例如用戶和用戶組之間的映射關(guān)系雪隧。
外鍵還是邏輯
關(guān)聯(lián)可以通過(guò)設(shè)置foreignKey
來(lái)進(jìn)行關(guān)聯(lián)西轩,但是被后端大哥批斗了,為了保證數(shù)據(jù)庫(kù)的性能脑沿,一般很少采用外鍵關(guān)聯(lián)兩個(gè)數(shù)據(jù)模型藕畔,而是采用邏輯關(guān)聯(lián),通過(guò)開(kāi)發(fā)者人工保證寫(xiě)入和刪除的順序庄拇。
索引
索引是必不可少的注服,我們?cè)谔峤籇BA工單的時(shí)候,必須要建立索引措近,尤其是有些數(shù)據(jù)表會(huì)存儲(chǔ)非常多的記錄祠汇,這時(shí)對(duì)于主鍵建立索引,可以大幅提高查找的效率熄诡。
根據(jù)上面的三個(gè)重點(diǎn),完成了我的數(shù)據(jù)表設(shè)計(jì)诗力。這里可以給出一個(gè)簡(jiǎn)單的栗子數(shù)據(jù)庫(kù)模型凰浮。后面的實(shí)現(xiàn)也是根據(jù)這個(gè)栗子進(jìn)行的。
<figcaption></figcaption>
CREATE TABLE `group` (
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`db_create_time` DATETIME,
`db_update_time` DATETIME
);
CREATE TABLE `user` (
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`db_create_time` DATETIME,
`db_update_time` DATETIME
);
CREATE TABLE `group_users` (
`user` INTEGER NOT NULL,
`group` INTEGER NOT NULL,
CONSTRAINT `pk_group_users` PRIMARY KEY (`user`, `group`)
);
CREATE INDEX `idx_group_users` ON `group_users` (`group`);
復(fù)制代碼
Egg-Sequelize實(shí)踐
egg框架本身提供了很多即插即用的plugin
苇本,官方最基本的插件集中就有egg-sequelize
插件袜茧。插件配置起來(lái)非常簡(jiǎn)單。
下面可能不會(huì)說(shuō)的非常詳細(xì)瓣窄,我主要講一講自己在進(jìn)行開(kāi)發(fā)時(shí)候遇到的各種坑笛厦。
項(xiàng)目結(jié)構(gòu)
|-- app // node服務(wù)端相關(guān)代碼
|-- controller
|-- api // node端接口controller
|-- group.js // 組相關(guān)controller
|-- user.js // 用戶相關(guān)controller
|-- extend
|-- helper.js // helper擴(kuò)展
|-- middleware
|-- model // sequelize數(shù)據(jù)模型
|-- user.js
|-- group.js
|-- group_user.js
|-- service // 可復(fù)用的數(shù)據(jù)處理及查詢方法
|-- utils // service中拿不到helper,部分utils放在這里
|-- router.js // 路由
|-- build // 構(gòu)建代碼
|-- client // 客戶端相關(guān)代碼
|-- config // 配置文件
復(fù)制代碼
啟動(dòng)插件
// config.local.js
module.exports = {
sequelize: {
// 數(shù)據(jù)庫(kù)類型
dialect: 'mysql',
// 數(shù)據(jù)庫(kù)名
database: 'swiss',
// 數(shù)據(jù)庫(kù)IP和端口
host: '127.0.0.1',
port: '3306',
// 數(shù)據(jù)庫(kù)連接的用戶和密碼
username: 'root',
password: '123',
// 是否自動(dòng)進(jìn)行下劃線轉(zhuǎn)換(這里是因?yàn)镈B默認(rèn)的命名規(guī)則是下劃線方式俺夕,而我們使用的大多數(shù)是駝峰方式)
underscored: true,
// 時(shí)區(qū)裳凸,sequelize有很多自動(dòng)時(shí)間的方法贱鄙,都是和時(shí)區(qū)相關(guān)的,記得設(shè)置成東8區(qū)(+08:00)
timezone: '+08:00',
},
}
復(fù)制代碼
各種配置項(xiàng)一目了然姨谷,記得要設(shè)置好timezone
逗宁,否則你所有默認(rèn)為當(dāng)前時(shí)間的值都會(huì)出錯(cuò)。underscored
表示自動(dòng)將駝峰表示法轉(zhuǎn)換為mysql的下劃線表示法(當(dāng)然后面會(huì)說(shuō)到梦湘,他的轉(zhuǎn)換機(jī)制有些時(shí)候讓我感覺(jué)費(fèi)解瞎颗,希望了解的大佬們可以幫我解釋一下~)。
直接將啟動(dòng)項(xiàng)配置寫(xiě)死在配置文件里面不是不可以捌议,但是如果需要和同事一起合作開(kāi)發(fā)的話哼拔,這樣寫(xiě)死可能不夠靈活“曷可以將某些配置項(xiàng)提取出來(lái)倦逐,通過(guò)命令行傳入?yún)?shù),來(lái)進(jìn)行開(kāi)發(fā)環(huán)境的動(dòng)態(tài)配置弄捕。
~ npm run dev -- --u=root --p=123
// config.local.js
let DB_USER = 'root';
let DB_PASSWORD = '123';
const ARGV_2 = JSON.parse(process.argv[2] || {});
DB_USER = (ARGV_2 && ARGV_2.u) || 'root';
DB_PASSWORD = (ARGV_2 && ARGV_2.p) || '123';
module.exports = {
sequelize: {
// ....
username: `${DB_USER}`,
password: `${DB_PASSWORD}`,
// ....
}
};
復(fù)制代碼
model
egg-sequelize會(huì)自動(dòng)將sequelize
實(shí)例掛載到app.model
上面僻孝,然后靜態(tài)方法和屬性則會(huì)直接被綁定到app
上,通過(guò)app.Sequelize
進(jìn)行獲取守谓。
model
層作為MVC的最底層穿铆,需要注意到數(shù)據(jù)模型的pure,model
文件也應(yīng)該是純凈的斋荞,這個(gè)文件里面應(yīng)該是和數(shù)據(jù)庫(kù)中的表一一對(duì)應(yīng)荞雏,一個(gè)model
文件對(duì)應(yīng)一個(gè)DB中的表,這個(gè)文件中不應(yīng)該包含任何和邏輯相關(guān)的代碼平酿,應(yīng)該完全是數(shù)據(jù)模型的定義凤优。
// app/model/user.js
module.exports = app => {
// egg-sequelize插件會(huì)將Sequelize類綁定到app上線,從里面可以取到各種靜態(tài)類型
const { TEXT, INTEGER, NOW } = app.Sequelize;
const User = app.model.define(
'user',
{
name: TEXT,
createAt: {
type: DATE,
// 可以重寫(xiě)某個(gè)字段的字段名
field: 'db_create_time',
allowNull: false,
defaultValue: NOW,
},
updateAt: {
type: DATE,
field: 'db_update_time',
allowNull: false,
defaultValue: NOW,
},
},
{
timestamps: false,
freezeTableName: true,
tableName: 'users',
underscored: true,
}
);
// 定義關(guān)聯(lián)關(guān)系
User.associate = () => {
// 定義多對(duì)多關(guān)聯(lián)
User.belongsToMany(app.model.Groups, {
// 中間表的model
through: app.model.groupUser,
// 進(jìn)行關(guān)聯(lián)查詢時(shí)蜈彼,關(guān)聯(lián)表查出來(lái)的數(shù)據(jù)模型的alias
as: 'project',
// 是否采用外鍵進(jìn)行物理關(guān)聯(lián)
constraints: false,
});
// 這里如果一個(gè)模型和多個(gè)模型都有關(guān)聯(lián)關(guān)系的話筑辨,關(guān)聯(lián)關(guān)系需要統(tǒng)一定義在這里
};
return User;
};
復(fù)制代碼
上面的代碼有非常多需要注意的地方,我們通過(guò)這個(gè)文件定義了一個(gè)數(shù)據(jù)模型幸逆,這個(gè)模型可以映射到數(shù)據(jù)庫(kù)中的某一個(gè)表棍辕,這里就是映射到了users
表,用來(lái)存儲(chǔ)用戶信息还绘。
- 在默認(rèn)情況下楚昭,
id
字段會(huì)被設(shè)置為主鍵,并且是AUTO_INCREMENT
的拍顷,不需要我們自己聲明抚太; -
timestamps
字段可以表示是否采用默認(rèn)的createAt
和updateAt
字段,我們通過(guò)field
字段重寫(xiě)了這兩個(gè)字段的字段名; -
associate
字段可以用來(lái)設(shè)置數(shù)據(jù)模型的關(guān)聯(lián)關(guān)系尿贫,如果一個(gè)數(shù)據(jù)模型關(guān)聯(lián)了多個(gè)數(shù)據(jù)模型电媳,那么這個(gè)方法里面也可以定義多個(gè)關(guān)系; -
belongsToMany
表示n:m的關(guān)系映射帅霜,這個(gè)在官方文檔中描述的非常清楚了匆背; -
as
可以為這個(gè)映射設(shè)置別名,這樣在進(jìn)行查詢的時(shí)候身冀,得到的結(jié)果就是以別名來(lái)標(biāo)識(shí)的钝尸; -
constraints
:這個(gè)屬性非常重要,可以用來(lái)表示這個(gè)關(guān)聯(lián)關(guān)系是否采用外鍵關(guān)聯(lián)搂根。在大多數(shù)情況下我們是不需要通過(guò)外鍵來(lái)進(jìn)行數(shù)據(jù)表的物理關(guān)聯(lián)的珍促,直接通過(guò)邏輯進(jìn)行關(guān)聯(lián)即可; -
through
:這個(gè)屬性表示關(guān)聯(lián)表的數(shù)據(jù)模型剩愧,也就是保存關(guān)聯(lián)關(guān)系的數(shù)據(jù)庫(kù)表的模型猪叙。
上面的這些屬性,在開(kāi)發(fā)過(guò)程中多多少少都消耗了我一些時(shí)間-1s仁卷,模型的設(shè)置和數(shù)據(jù)庫(kù)表之間的關(guān)系非常緊密穴翩,一定要保證你的數(shù)據(jù)模型和數(shù)據(jù)表之間沒(méi)有歧義。
同樣地锦积,我們可以定義到關(guān)聯(lián)表和中間表的模型:
// app/model/group.js
module.exports = app => {
const { TEXT, INTEGER, NOW } = app.Sequelize;
const Group = app.model.define(
'group',
{
name: TEXT,
createAt: {
type: DATE,
field: 'db_create_time',
allowNull: false,
defaultValue: NOW,
},
updateAt: {
type: DATE,
field: 'db_update_time',
allowNull: false,
defaultValue: NOW,
},
},
{
timestamps: false,
freezeTableName: true,
tableName: 'groups',
underscored: true,
}
);
// 定義關(guān)聯(lián)關(guān)系
Group.associate = () => {
Group.belongsToMany(app.model.User, {
through: app.model.groupUser,
as: 'partner',
constraints: false,
});
};
return Group;
};
// app/model/group_user.js
// 中間表不需要定義關(guān)聯(lián)關(guān)系
module.exports = app => {
const { INTEGER } = app.Sequelize;
const GroupUser = app.model.define(
'group_user',
{
user_id: INTEGER,
group_id: INTEGER,
},
{
timestamps: false,
freezeTableName: true,
tableName: 'group_user',
underscored: true,
}
);
return GroupUser;
};
復(fù)制代碼
controller
在egg中芒帕,controller
模塊的作用類似于MVC模式中的控制器,進(jìn)行從model
到view
的轉(zhuǎn)換,而在提供接口的時(shí)候,controller
負(fù)責(zé)的是提供從model
到api
的轉(zhuǎn)換豌研,經(jīng)過(guò)model
從數(shù)據(jù)庫(kù)中查詢出來(lái)的結(jié)果,將在controller
里面進(jìn)行包裝带膀,然后返回給接口的調(diào)用者。
在進(jìn)行數(shù)據(jù)訪問(wèn)的時(shí)候橙垢,很多的接口請(qǐng)求都可以拆分為幾個(gè)類似的CRUD操作垛叨,比如:
- 我想查一個(gè)用戶的注冊(cè)時(shí)間;
- 我想查一個(gè)用戶的用戶名柜某; 這樣類似的操作都可以通過(guò)一樣的數(shù)據(jù)庫(kù)操作拿到嗽元,然后再進(jìn)行單獨(dú)處理,這些可復(fù)用的邏輯莺琳,根據(jù)egg的建議,都可以寫(xiě)到
service
里面载慈。而controller
只負(fù)責(zé)請(qǐng)求的響應(yīng)處理惭等。
當(dāng)一個(gè)接口請(qǐng)求跨過(guò)了middleware
的處理,經(jīng)過(guò)了router
的分發(fā)之后:
// app/router.js
module.exports = app => {
app.get('/api/user/get', app.controller.api.user.get);
app.post('/api/group/set', app.controller.api.group.set);
}
復(fù)制代碼
會(huì)被轉(zhuǎn)發(fā)到對(duì)應(yīng)的controller
進(jìn)行處理办铡。
// app/controller/user.js
module.exports = class UserController extends Controller {
async get = () => {
const { uuid } = this.ctx.session;
if (!uuid) {
ctx.body = {
code: 401,
message: 'unauthorized',
};
return;
}
const userInfo = await this.ctx.service.user.getUserById({ id: uuid });
if (userInfo) {
ctx.body = {
code: 200,
message: 'success',
data: userInfo
}
} else {
ctx.body = {
code: 500,
message: 'error',
}
}
}
}
復(fù)制代碼
service
egg官方文檔對(duì)于service的描述是這樣的:
簡(jiǎn)單來(lái)說(shuō)辞做,Service 就是在復(fù)雜業(yè)務(wù)場(chǎng)景下用于做業(yè)務(wù)邏輯封裝的一個(gè)抽象層琳要,提供這個(gè)抽象有以下幾個(gè)好處:
- 保持 Controller 中的邏輯更加簡(jiǎn)潔。
- 保持業(yè)務(wù)邏輯的獨(dú)立性秤茅,抽象出來(lái)的 Service 可以被多個(gè) Controller 重復(fù)調(diào)用稚补。
- 將邏輯和展現(xiàn)分離,更容易編寫(xiě)測(cè)試用例框喳。
也就是controller
中要盡量保持clean课幕,然后,可以復(fù)用的業(yè)務(wù)邏輯被統(tǒng)一抽出來(lái)五垮,放到service
中乍惊,被多個(gè)controller
進(jìn)行復(fù)用。
我們將CRUD操作放仗,全部提取到service
中润绎,封裝成一個(gè)個(gè)通用的CRUD方法,來(lái)提供給其他service
進(jìn)行嵌套的時(shí)候調(diào)用诞挨,或者提供給controller
進(jìn)行業(yè)務(wù)邏輯調(diào)用莉撇。
比如:讀取用戶信息的過(guò)程:
// app/service/user.js
module.exports = class UserService extends Service {
// 通過(guò)id獲取用戶信息
async getUserById = ({
id,
}) => {
const { ctx } = this;
let userInfo = {};
try {
userInfo = await ctx.model.User.findAll({
where: {
id,
},
// 查詢操作的時(shí)候,加入這個(gè)參數(shù)可以直接拿到對(duì)象類型的查詢結(jié)果惶傻,否則還需要通過(guò)方法調(diào)用解析
raw: true,
});
} catch (err) {
ctx.logger.error(err);
}
return userInfo;
}
}
復(fù)制代碼
sequelize
事務(wù)
之前有說(shuō)到棍郎,在建立模型的時(shí)候,我們建立了User
和Group
之間的關(guān)聯(lián)關(guān)系达罗,并且通過(guò)了一個(gè)關(guān)聯(lián)表進(jìn)行兩者之間的關(guān)聯(lián)坝撑。
由于我們沒(méi)有建立兩者之間的外鍵關(guān)聯(lián),所以在寫(xiě)入的時(shí)候粮揉,我們要進(jìn)行邏輯的關(guān)聯(lián)寫(xiě)入巡李。
如果我們需要新建一個(gè)用戶,并且為這個(gè)用戶新建一個(gè)默認(rèn)的group
扶认,由于組和用戶有著多對(duì)多的關(guān)系侨拦,所以這里我們采用belongsToMany
來(lái)建立關(guān)系。一個(gè)用戶可以屬于多個(gè)組辐宾,并且一組也可以包含多個(gè)用戶狱从。
在建立的時(shí)候,需要按照一定的順序叠纹,寫(xiě)入三張表季研,一旦某個(gè)寫(xiě)入操作失敗之后,需要對(duì)于之前的寫(xiě)入操作進(jìn)行回滾誉察,防止DB中產(chǎn)生垃圾數(shù)據(jù)与涡。這里需要用到事務(wù)機(jī)制進(jìn)行寫(xiě)入控制,并且人工保證寫(xiě)入順序。
// app/service/user.js
module.exports = class UserService extends Service {
async setUser = ({
name,
}) => {
const { ctx } = this;
let transaction;
try {
// 這里需要注意驼卖,egg-sequelize會(huì)將sequelize實(shí)例作為app.model對(duì)象
transaction = await ctx.model.transaction();
// 創(chuàng)建用戶
const user = await ctx.model.User.create({
name,
}, {
transaction,
});
// 創(chuàng)建默認(rèn)組
const group = await ctx.model.Group.create({
name: 'default',
}, {
transaction,
});
const userId = user && user.getDataValue('id');
const groupId = group && group.getDataValue('id');
if (!userId || !groupId) {
throw new Error('創(chuàng)建用戶失敗');
}
// 創(chuàng)建用戶和組之間的關(guān)聯(lián)
const associate = await ctx.mode.GroupUser.create({
user_id: userId,
group_id: groupId,
}, {
transaction,
});
await transaction.commit();
return userId;
} catch (err) {
ctx.logger.error(err);
await transaction.rollback();
}
}
}
復(fù)制代碼
通過(guò)sequelize
提供的事務(wù)功能氨肌,可以將串聯(lián)寫(xiě)入過(guò)程中的錯(cuò)誤進(jìn)行回滾,保證了每次寫(xiě)入操作的原子性酌畜。
關(guān)聯(lián)查詢
既然我們已經(jīng)創(chuàng)建了關(guān)聯(lián)關(guān)系怎囚,那么如果通過(guò)關(guān)聯(lián)關(guān)系,查詢到對(duì)應(yīng)的數(shù)據(jù)庫(kù)內(nèi)容呢桥胞?
在多對(duì)多的關(guān)聯(lián)條件下恳守,如果我們要查詢某個(gè)用戶的所有分組信息,需要通過(guò)用戶id來(lái)查詢其關(guān)聯(lián)的所有group
埠戳。
// service
async getGroupByUserId = ({
id,
}) => {
const { ctx } = this;
const group = await ctx.model.User.findAll({
attributes: ['project.id', 'project.name'],
include: [
{
model: ctx.model.Group,
as: 'project',
// 指定關(guān)聯(lián)表查詢屬性井誉,這里表示不需要任何關(guān)聯(lián)表的屬性
attributes: [],
through: {
// 指定中間表的屬性,這里表示不需要任何中間表的屬性
attributes: []
}
}
],
where: {
id,
},
raw: true,
// 這個(gè)需要和上面的中間表屬性配合整胃,表示不忽略include屬性中的attributes參數(shù)
includeIgnoreAttributes: false,
});
}
復(fù)制代碼
通過(guò)上面的關(guān)聯(lián)查詢方法颗圣,可以得到這樣的一條SQL語(yǔ)句:
SELECT `project`.`id`, `project`.`name` FROM `users` AS `user` LEFT OUTER JOIN ( `group_user` AS `project->group_user` INNER JOIN `groups` AS `project` ON `project`.`id` = `project->group_user`.`group_id`) ON `user`.`id` = `project->group_user`.`user_id` WHERE `user`.`id` = 1;
復(fù)制代碼
對(duì)應(yīng)的查詢結(jié)果:
[ { id: 1, name: 'default' } ]
復(fù)制代碼
而在一對(duì)多和多對(duì)一的關(guān)系下,其本質(zhì)和多對(duì)多基本上是一致的屁使,是在多的方向存儲(chǔ)一個(gè)冗余字段在岂,來(lái)保存其對(duì)應(yīng)的唯一元素的主鍵,無(wú)論是何種關(guān)系蛮寂,其默認(rèn)在sequelize
中實(shí)現(xiàn)的數(shù)據(jù)模型蔽午,都是范式化的,如果需要反范式來(lái)提高數(shù)據(jù)庫(kù)效率酬蹋,還是需要自己去做冗余的及老。
hooks
在數(shù)據(jù)庫(kù)查詢的過(guò)程中,難免需要在真正的CRUD前后進(jìn)行一些數(shù)據(jù)的處理范抓。
考慮到這樣的一個(gè)場(chǎng)景:
在客戶端骄恶,我們前端存儲(chǔ)的用戶名并不是通過(guò)name
來(lái)表示的,而是通過(guò)nickName
字段來(lái)進(jìn)行表示的匕垫,在每次進(jìn)行讀寫(xiě)操作之前僧鲁,例如這里允許用戶自己修改自己的名字,當(dāng)請(qǐng)求發(fā)送到服務(wù)端之后象泵,交予service
進(jìn)行處理寞秃。
// app/model/user
const User = app.model.define({
// ...
}, {
hooks: {
beforeUpdate: (user, options) => {
const name = user.nickName;
delete user.nickName;
user.name = name;
}
}
});
復(fù)制代碼
我們?cè)诙x模型的時(shí)候,直接定義好這個(gè)hook
偶惠,beforeUpdate
會(huì)在User
模型每次調(diào)用update
之前春寿,調(diào)用這個(gè)hook。這個(gè)hook會(huì)傳入update操作傳入的參數(shù)實(shí)例忽孽,可以直接對(duì)這個(gè)實(shí)例進(jìn)行修改绑改,保證實(shí)際update
操作的實(shí)例是正確的馋缅。
hooks
可以使用的地方很多,這里只是簡(jiǎn)單介紹一下使用的方法绢淀,hooks
中間也可以包含異步操作,但是要注意瘾腰,如果包含異步操作的話皆的,需要返回一個(gè)Promise
。我們還可以在進(jìn)行具有副作用的操作之前蹋盆,對(duì)于用戶權(quán)限進(jìn)行校驗(yàn)费薄。
hooks
的使用是需要了解到其功能,然后根據(jù)自己的業(yè)務(wù)場(chǎng)景栖雾,靈活地進(jìn)行使用的楞抡。
總結(jié)
egg提供了非常多的可擴(kuò)展空間,除了使用其作為前端頁(yè)面的部署環(huán)境之外析藕,還可以承擔(dān)一些model
層的工作召廷,有興趣的小伙伴可以試下通過(guò)egg實(shí)現(xiàn)前后端分離的全棧開(kāi)發(fā)工作~
在實(shí)際業(yè)務(wù)場(chǎng)景的實(shí)踐過(guò)程中,sequelize
的很多解決方案都要從官方文檔中一個(gè)字一個(gè)字的查找账胧,有些問(wèn)題甚至需要去翻issue
才能找到對(duì)應(yīng)的處理方法竞慢,不知道為什么官方文檔會(huì)有那么多版的中文翻譯。治泥。實(shí)踐的方案卻特別少筹煮,前端的小伙伴們大部分還是熱衷于MongoDB。確實(shí)關(guān)系型數(shù)據(jù)的操作相較于NoSQL還是比較復(fù)雜的居夹。不過(guò)解決問(wèn)題的過(guò)程雖然煩惱败潦,但是結(jié)果總還是愉悅的。
sequelize
還有很多需要挖掘的地方准脂,它本身提供的很多功能在這次迭代的過(guò)程中都沒(méi)有用到劫扒。比如scope
、migration
意狠,有機(jī)會(huì)可以嘗試下一些新的功能和實(shí)現(xiàn)方案粟关。