实用pb_hooks模板
实用pb_hooks模板
创建自定义Payload的JWT
/// <reference path="../pb_data/types.d.ts" />
routerAdd(
"GET",
"/api/customAuth",
(e) => {
// 常规读取请求url中的参数
// let name = e.request?.pathValue("name");
// 读取环境变量密钥-用trim避免ubuntu下额外的换行符
const JWT_SIGNING_KEY = $os.getenv("SECRET_PB_ROLE_JWT").trim();
// jwt密钥
if (!JWT_SIGNING_KEY) {
return e.json(500, {
code: 500,
message: "服务端未配置自定义认证密钥",
});
}
if (JWT_SIGNING_KEY.length !== 32) {
return e.json(500, {
code: 500,
message: "服务端自定义认证密钥配置错误,长度必须是32字节",
});
}
// 如果前端在请求头Authorization传递token,则此处会返回用户完整数据
let userInfo = e.auth;
// 初始化日期对象
const date = new Date();
// 当前服务器时间(时间戳格式)
const curTimestamp = date.getTime();
// 当前服务器时间(ISO8601格式)
const curTime = date.toISOString();
// 设置JWT有效期(单位:秒)
const JWT_SEC_DURATION = 604800;
// 计算出JWT过期时间(ISO8601格式)
const jwtExpireTime = new Date(
curTimestamp + JWT_SEC_DURATION * 1000
).toISOString();
// jwt自定义载荷
const JWT_PAYLOAD = {
id: userInfo?.get("id"),
role: userInfo?.get("role"),
};
// 生成自定义JWT
const roleJwt = $security.createJWT(
JWT_PAYLOAD,
JWT_SIGNING_KEY,
JWT_SEC_DURATION
);
return e.json(200, {
code: 200,
message: "获取权限JWT成功",
data: {
// userInfo,
token: roleJwt,
expireTime: jwtExpireTime,
},
curTime: curTime,
});
},
// 需要在请求头携带users表token才允许访问
$apis.requireAuth("users")
);AES加密
/// <reference path="../pb_data/types.d.ts" />
// 路由-字符串加密/解密
routerAdd(
"POST",
"/api/crypto",
(e) => {
// 读取环境变量密钥-用trim避免ubuntu下额外的换行符
const SECRET_AES = $os.getenv("SECRET_AES_KEY").trim();
// 服务端AES密钥
if (!SECRET_AES) {
return e.json(500, {
code: 500,
message: "服务端未配置自定义认证密钥",
});
}
if (SECRET_AES.length !== 32) {
return e.json(500, {
code: 500,
message: "服务端密钥配置错误,长度必须是32字节",
});
}
// 请求体读取示例(无自动类型校验)
// const reqBody = e.requestInfo().body;
// 请求体读取示例(自动类型校验)
let reqBody;
reqBody = new DynamicModel({
type: "",
data: "",
});
e.bindBody(reqBody);
// console.log("请求体:", toString(reqBody));
// 加密字符串数据
if (reqBody.type === "encrypt") {
try {
const ciphertext = $security.encrypt(reqBody.data, SECRET_AES);
return e.json(200, {
code: 200,
message: "操作成功",
result: ciphertext,
});
} catch (err) {
return e.json(500, {
code: 500,
message: `服务端错误: ${err.message}`,
});
}
}
// 解密字符串数据
else if (reqBody.type === "decrypt") {
try {
const payloadStr = $security.decrypt(reqBody.data, SECRET_AES);
return e.json(200, {
code: 200,
message: "操作成功",
result: payloadStr,
});
} catch (err) {
return e.json(500, {
code: 500,
message: `服务端错误: ${err.message}`,
});
}
} else {
return e.json(400, {
code: 400,
message: "操作类型错误",
});
}
},
// 需要在请求头携带users表token才允许访问
// $apis.requireAuth("users")
);SMTP自定义邮件
/// <reference path="../pb_data/types.d.ts" />
routerAdd(
"GET",
"/api/mail",
(e) => {
// console.log("test", $app.settings());
if (!$app.settings().smtp.enabled) {
return e.json(400, {
code: 400,
message: "未开启邮件功能",
});
}
/** 从查询参数读取收件人邮箱列表 */
const recipients = e.request?.url?.query().get("recipients");
if (!recipients) {
return e.json(400, {
code: 400,
message: "未填写邮箱列表",
});
}
const mailConfig = new MailerMessage({
from: {
address: e.app.settings().meta.senderAddress,
name: e.app.settings().meta.senderName,
},
to: [{ address: recipients }],
subject: "YOUR_SUBJECT...",
html: "YOUR_HTML_BODY...",
// bcc, cc and custom headers are also supported...
});
e.app.newMailClient().send(mailConfig);
return e.json(200, {
code: 200,
message: "发送邮件成功",
});
},
// 需要在请求头携带users表token才允许访问
$apis.requireAuth("users")
);规则触发邮件通知
/// <reference path="../pb_data/types.d.ts" />
onRecordCreate((e) => {
const recipients = "sos@unmei.fun";
const userId = e.record?.get("sender");
const userLocation = e.record?.get("location");
const userMsgContent = e.record?.get("msgContent");
if (e.record?.tableName() === "msgBoard") {
const mailConfig = new MailerMessage({
from: {
address: e.app.settings().meta.senderAddress,
name: e.app.settings().meta.senderName,
},
to: [{ address: recipients }],
subject: "新的待审核留言",
html: `
<p>用户${userId}(${userLocation})提交了一条新留言:</p>
<p>${userMsgContent}</p>
`,
// bcc, cc and custom headers are also supported...
});
e.app.newMailClient().send(mailConfig);
}
e.next();
});IP日志
/// <reference path="../pb_data/types.d.ts" />
routerAdd("GET", "/api/visit", (e) => {
// ---ip转地名请求部分
// ip转地名结果
let location = "";
const getIPReq = {
url: "https://ip.unmei.fun/api/region",
method: "POST",
// 请求体json示例 JSON.stringify({"test": 123})
// 请求体表单示例 new FormData()
body: JSON.stringify([e.realIP()]),
// 测试解析ip地址请求
// body: JSON.stringify(["121.34.33.120"]),
headers: {
Authorization: "4444",
"content-type": "application/json",
},
// 超时时间(单位:秒)
timeout: 120,
};
// console.log("请求参数", JSON.stringify(getIPReq));
// 发出请求给ip地址解析接口,可抛出超时或网络连接错误
const locationReq = $http.send(getIPReq);
// 取出第一个数据体结构
const locationRes = locationReq.json.data[0].region;
// 第一个数据体中,如果存在'region'字段则判断为解析成功
if (locationRes) {
// console.log("成功解析地名", location);
location = `${locationRes.country}${locationRes.province}${locationRes.city}`;
} else {
console.log("无法解析地名");
}
// ---ip转地名请求部分
// 1. 获取请求信息
const data = new DynamicModel({
ip: e.realIP(),
location: location,
url: e.request?.url?.path || "",
uid: e.auth?.id || "",
fringerPrint: e.request?.header.get("X-Fingerprint") || "",
created: new DateTime(),
});
// console.log("请求数据", JSON.stringify(data));
const table = $app.db();
// 2. 创建一条新记录并保存到 'logs' 表
try {
table
.newQuery(
"INSERT INTO logs (ip,location, url, uid, fringerPrint,created) VALUES ({:ip}, {:location}, {:url}, {:uid}, {:fringerPrint}, {:created})"
)
.bind(data)
.execute();
} catch (error) {
console.log("Failed to create visit record: " + error);
// 即使记录创建失败,我们仍然尝试返回总数
}
// 3. 查询 'logs' 表中的总记录数
const result = new DynamicModel({ count: 0 }); // 用于接收查询结果
try {
$app
.db()
.newQuery("SELECT COUNT(*) as count FROM logs") // SQL 查询语句
.one(result); // 将查询结果的第一行填充到 result 对象中[6](@ref)
} catch (queryError) {
$app.logger().error("Failed to query total logs: " + queryError);
// 如果查询失败,返回一个错误响应
return e.json(500, {
error: "Internal server error: could not retrieve count",
});
}
// 4. 返回总记录数
return e.json(200, {
total_visit: result, // 返回总计数
});
});返回日志数据
/// <reference path="../pb_data/types.d.ts" />
routerAdd("GET", "/api/visit", (e) => {
const total_visit = new DynamicModel({ count: 1 });
$app
.logQuery()
.select("count(*) as count")
.andWhere($dbx.exp("json_extract(data, '$.url') = '/api/visit'"))
.one(total_visit);
return e.json(200, {
total_visit: total_visit,
});
});