Action 提供一個 visibility
屬性來控制服務的動作是否可視及呼叫。
可用的值:
null
預設值,效果等同於 published
。published
公開並可發布的。可以由本地、遠端呼叫也可被發佈到 API Gateway。public
公開的。可以由本地、遠端呼叫,但是不能發佈到 API Gateway。protected
受保護的。只能由本地服務呼叫。private
私有的。只能透過內部呼叫 (例如: this.actions.qqq()
)。Action Hook 是可插入及重用的中間件函數,可以在 Action 中註冊 before
、 after
或 errors
的 Hook。Hook 可以是函數或字串,如果是字串則必須有對應的 method
名稱。
Before Hooks 可以在執行 Action handler 前對 ctx
做修改,包含 ctx.params
、 ctx.meta
以及在 ctx.locals
中加入自定義的變數。假如發生異常可以 throw Error,但是不能進一步的中斷或跳過 hooks 或 Action handler。
用途:參數清理、參數驗證、查詢實體、授權驗證
After Hooks 可以在執行 Action handler 後對 ctx
及 response
做修改,可以對響應的內容進行操作或是完全變更,但最後必須返回響應的內容。
用途:添加屬性、資料清理、將響應包裝成物件、轉換響應的結構
Error hooks 可以捕獲 Action 呼叫期間所產生的 Error
,它接收 ctx
、 err
資訊,並且可以拋出一個錯誤資訊,或者是以自定義的方式進行 fallback
響應。
用途:捕獲錯誤、將錯誤包裝成其它、 fallback
響應
Before hooks 範例:
const DbService = require("moleculer-db");
module.exports = {
name: "posts",
mixins: [DbService]
hooks: {
before: {
// 定義全域的 Hook
// 此 Hook 將會呼叫 `resolveLoggedUser` 方法
"*": "resolveLoggedUser",
// 對 `remove` Action 定義多個 Hooks
remove: [
function isAuthenticated(ctx) {
if (!ctx.user)
throw new Error("Forbidden");
},
function isOwner(ctx) {
if (!this.checkOwner(ctx.params.id, ctx.user.id))
throw new Error("Only owner can remove it.");
}
],
// 適配 "create-" 前綴名稱的所有 Action
"create-*": [
async function (ctx){}
],
// 適配 "-user" 後綴名稱的所有 Action
"*-user": [
async function (ctx){}
],
}
},
methods: {
async resolveLoggedUser(ctx) {
if (ctx.meta.user)
ctx.user = await ctx.call("users.get", { id: ctx.meta.user.id });
}
}
}
After hooks 與 Error hooks 範例:
const DbService = require("moleculer-db");
module.exports = {
name: "users",
mixins: [DbService]
hooks: {
after: {
// 定義全域的 Hook 來消除敏感資訊
"*": function(ctx, res) {
// Remove password
delete res.password;
// 最後必須要返回 response (也可以重建響應物件)
return res;
},
get: [
// 新增一個虛擬欄位值給實體 (你沒有朋友!?)
async function (ctx, res) {
res.friends = await ctx.call("friends.count", { query: { follower: res._id }});
return res;
},
// 添加 `referrer` 欄位
async function (ctx, res) {
if (res.referrer)
res.referrer = await ctx.call("users.get", { id: res._id });
return res;
}
],
// 適配 "create-" 前綴名稱的所有 Action
"create-*": [
async function (ctx, res){}
],
// 適配 "-user" 後綴名稱的所有 Action
"*-user": [
async function (ctx, res){}
],
},
error: {
// 捕獲全域錯誤
"*": function(ctx, err) {
this.logger.error(`Error occurred when '${ctx.action.name}' action was called`, err);
// 拋出錯誤
throw err;
},
// 適配 "create-" 前綴名稱的所有 Action
"create-*": [
async function (ctx, err){}
],
// 適配 "-user" 後綴名稱的所有 Action
"*-user": [
async function (ctx, err){}
],
}
}
};
Hooks 也可以註冊在 Action 中。
broker.createService({
name: "greeter",
actions: {
hello: {
hooks: {
before(ctx) {
broker.logger.info("Before action hook");
},
after(ctx, res) {
broker.logger.info("After action hook"));
return res;
}
},
handler(ctx) {
broker.logger.info("Action handler");
return `Hello ${ctx.params.name}`;
}
}
}
});
Hooks 具有執行的順序,當 Hooks 註冊在服務或是 Actions 時,將會根據下列的規則依序執行(由前者優先執行):
before
hooks: 全域(*) → 服務 → Actionsafter
hooks: Actions → 服務 → 全域(*)範例:
broker.createService({
name: "greeter",
hooks: {
before: {
"*"(ctx) {
broker.logger.info(chalk.cyan("Before all hook"));
},
hello(ctx) {
broker.logger.info(chalk.magenta(" Before hook"));
}
},
after: {
"*"(ctx, res) {
broker.logger.info(chalk.cyan("After all hook"));
return res;
},
hello(ctx, res) {
broker.logger.info(chalk.magenta(" After hook"));
return res;
}
},
},
actions: {
hello: {
hooks: {
before(ctx) {
broker.logger.info(chalk.yellow.bold(" Before action hook"));
},
after(ctx, res) {
broker.logger.info(chalk.yellow.bold(" After action hook"));
return res;
}
},
handler(ctx) {
broker.logger.info(chalk.green.bold(" Action handler"));
return `Hello ${ctx.params.name}`;
}
}
}
});
範例輸出:
INFO - Before all hook
INFO - Before hook
INFO - Before action hook
INFO - Action handler
INFO - After action hook
INFO - After hook
INFO - After all hook
要有效重用 Hooks 最好的方式是將函數分離至單一文件,再利用 mixin 機制來導入,如此一來,Hooks 就可以很容易的在多個 Actions 間分享。
authorize.mixin.js
module.exports = {
methods: {
checkIsAuthenticated(ctx) {
if (!ctx.meta.user)
throw new Error("Unauthenticated");
},
checkUserRole(ctx) {
if (ctx.action.role && ctx.meta.user.role != ctx.action.role)
throw new Error("Forbidden");
},
checkOwner(ctx) {
// 確認實體擁有者
}
}
}
posts.service.js
const MyAuthMixin = require("./authorize.mixin");
module.exports = {
name: "posts",
mixins: [MyAuthMixin]
hooks: {
before: {
"*": ["checkIsAuthenticated"],
create: ["checkUserRole"],
update: ["checkUserRole", "checkOwner"],
remove: ["checkUserRole", "checkOwner"]
}
},
actions: {
find: {
// 無需角色
handler(ctx) {}
},
create: {
role: "admin",
handler(ctx) {}
},
update: {
role: "user",
handler(ctx) {}
}
}
};
ctx.locals
可以用於儲存其它的資料,並將其傳遞給 Actions 的處理程序,可以提供 hooks 更多的靈活性。
範例:
module.exports = {
name: "user",
hooks: {
before: {
async get(ctx) {
const entity = await this.findEntity(ctx.params.id);
ctx.locals.entity = entity;
}
}
},
actions: {
get: {
params: {
id: "number"
},
handler(ctx) {
this.logger.info("Entity", ctx.locals.entity);
}
}
}
}
[1] Actions, https://moleculer.services/docs/0.14/actions.html