创作背景

众所周知,本网站导航栏中有一「能效」栏目。该栏目中的三个页面,都是旧网站中没有的,是我在新网站创立之初灵机一动想到的。为了更明显地区分它们与主站,我特意选择了不同的背景图源 API,这在「自述江湖」中有提到。

但是,我在寻找“二次元风景壁纸 API”时发现这些 API 大部分都难以令我满意,费尽周折才选出这一个,但是图库比较小。恰好我手里有一些图片,于是就萌生了自制 API 的想法。

项目可以部署在 netlify functions 上。这款服务是免费提供的——至少是够用的。
当我询问 netlify AI chatbot 时,它这样回答我:

根据提供的知识来源,Netlify Functions 的免费额度如下:

  • 每月 125,000125,000 次函数调用
  • 每月 300300 分钟的运行时间

这些信息来自Netlify的免费计划介绍博客文章

此外,值得注意的是:

  1. 免费计划有使用限制。当达到限制时,您会收到电子邮件通知(在使用量达到 50%50\%75%75\%90%90\%100%100\% 时)。
  2. 如果达到构建限制,您的网站仍然可以访问,但新的构建将被禁用。
  3. 对于其他计量功能,如果达到 100%100\% 的限制,新的构建将被禁用,并且您账户下的所有网站都将被暂停。
  4. 要恢复服务,您需要添加付款方式并升级到付费计划。

这些额外信息来自Netlify的计费FAQ文档

如果您需要更高的限额或有其他具体需求,建议查看Netlify的付费计划或联系他们的销售团队。

所以,对于中小型网站,比如本网站,是完全够用的。如果你不放心,可以手搓负载均衡

然而,本网站新栏目现在使用的是另外一种形式的 PicFlare。不过本质上是一样的,问题不大。
另外,有些文章没有封面,为了优化 UI,它们的默认封面很可能是 PicFlare 提供的喔!~

效果展示

我制作了一个项目,托管在 Github 上。即:https://github.com/pandaoxi/picflare

DEMO 见:https://picflare.netlify.app/

核心 API 接口:https://picflare.netlify.app/api/picflare

当然,如果你乐意,你当然可以把 API 利用在 Markdown 中。

1
![图:Markdown 中的 PicFlare API](https://picflare.netlify.app/api/picflare)

图:Markdown 中的 PicFlare API

原理

具体的操作方法见仓库中的使用说明。我主要说说原理。

结构

1
2
3
4
5
6
7
8
9
10
11
12
13
.
│ netlify.toml
│ README.md

├─functions
│ picflare.js

└─public
│ index.html

└─images
wallhaven-yjr3lk.webp
wallhaven-z8jvqw.webp

JS 实现

我们要从 JS 中获取图库文件夹,需要协调函数与静态资源的位置关系。这需要设置 netlify.toml

1
2
3
4
5
6
7
8
9
10
11
12
[build]
publish = "public" # 静态资源

[functions]
directory = "functions" # 函数目录
included_files = ["public/**"]

[[redirects]]
from = "/api/*" # 来自 /api/* 下的访问
to = "/.netlify/functions/:splat" # 被重定向到 functions
status = 200
force = true

然后可以通过 JS 访问文件夹,随机获取一张图片并返回。这些操作都可以在一个 picflare.js 内完成。具体地,JS 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// PicFlare by PanDaoxi.

const fs = require("fs"); // 引入文件系统模块
const path = require("path"); // 引入路径模块

exports.handler = async (event) => { // 定义并导出一个异步函数作为处理程序
const imagesDir = path.resolve(__dirname, "..", "public", "images"); // 解析图像目录的绝对路径
const imageFiles = fs.readdirSync(imagesDir); // 同步读取图像目录下的所有文件名
const randomImageFile = imageFiles[Math.floor(Math.random() * imageFiles.length)]; // 随机选择一个文件名
const imagePath = path.join(imagesDir, randomImageFile); // 构造选中图像文件的完整路径
const imageData = fs.readFileSync(imagePath); // 同步读取图像文件的数据
const headers = { // 定义响应头
"Content-Type": "image/webp", // 设置内容类型为webp图像
};
return { // 返回响应对象
statusCode: 200, // 设置状态码为200(成功)
headers, // 包含响应头
isBase64Encoded: true, // 指定响应体是否为base64编码,这里设置为true
body: imageData.toString("base64"), // 将图像数据转换为base64字符串并作为响应体
};
};

本网站为了减轻负担,每天对应一张图片。JS 可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const fs = require("fs");
const path = require("path");

exports.handler = async (event) => {
const imagesDir = path.resolve(__dirname, "..", "public", "images");
const imageFiles = fs.readdirSync(imagesDir);
const today = new Date();
const dayOfYear = getDayOfYear(today);
const imageIndex = (dayOfYear - 1) % imageFiles.length;
const randomImageFile = imageFiles[imageIndex];
const imagePath = path.join(imagesDir, randomImageFile);
const imageData = fs.readFileSync(imagePath);
const headers = {
"Content-Type": "image/webp",
};
return {
statusCode: 200,
headers,
isBase64Encoded: true,
body: imageData.toString("base64"),
};
};

function getDayOfYear(date) {
const start = new Date(date.getFullYear(), 0, 0);
const diff = date - start;
const oneDay = 1000 * 60 * 60 * 24;
return Math.ceil(diff / oneDay);
}

因此,PicFlare 并不是一个复杂的项目。欢迎贡献!