🎉 欢迎,我的Github主页.
实用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,
  });
});