README
Wallpaper时光机
一个精美的 Bing 每日壁纸网站项目,前端站点与壁纸 API 已合并为同一个 Cloudflare Worker 部署单元:Worker 既负责 Bing 壁纸的抓取入库与接口服务,又在边缘对 WebStudio 生成的静态页面做数据注入(SSR),同时提供 Android / Windows 客户端下载跳转。支持网页版、Android 和 Windows 应用程序。
功能特性
网站前端
- 🎨 精美的壁纸展示界面,基于 WebStudio SSG 构建
- 📱 响应式设计,支持移动端
- � 首页支持关键词搜索与分页(
search/page/limit/size查询参数透传) - 🖼️ 详情页支持按
enddate浏览任意一天的壁纸,缺省展示当天壁纸 - 🖼️ 集成 Viewer.js 图片查看器(构建时自动安装并本地化到
/npm/viewerjs) - �📦 提供 Android APK、Windows NSIS 安装版与免安装版下载页(307 跳转至 GitCode Releases)
- 🚀 边缘 SSR:Worker 在响应 HTML / 入口 JS 时实时注入 Supabase 数据,静态产物无需重新构建即可展示最新壁纸
壁纸 API
- 从 Bing 每日图片接口聚合两个时间窗口(
idx=0与idx=7)的数据,自动去重并按日期降序排列 - 使用 Supabase(PostgreSQL)存储壁纸信息,按
enddate增量入库(upsert + 忽略重复) - 提供搜索、详情、浏览量原子自增、手动更新等接口,均支持 GET / POST
- 提供
/api/bing-hp实时聚合接口(不落库),支持通过mkt切换市场区域 - 支持 Cron 定时触发自动更新
- 全接口开放跨域(CORS),内置静态资源托管与 404 页面
- 📐 提供 OpenAPI 3.0 架构文件
wallpaper-api-schema.yaml,可直接上传 Cloudflare API Shield 进行请求结构校验(详见 OpenAPI 架构文件)
项目结构
wallpaper/
├── public/ # 静态资源与 Worker 源文件
│ ├── _worker.js # 合并后的 Worker:API + 边缘 SSR + 静态资源缓存
│ ├── _headers # 静态资产缓存头配置(仅对不经 Worker 的静态资产生效)
│ ├── 404.html # 404 页面
│ ├── wallpaper/404.html # api 404 页面
│ ├── app.html # Android(5+ WebView)客户端壳页面模板
│ ├── android-apk.html # Android APK 下载页({{androidVersion}} 占位)
│ ├── windows-nsis-exe.html # Windows NSIS 安装版下载页({{windowsVersion}} 占位)
│ └── windows-portable-exe.html # Windows 免安装版下载页({{windowsVersion}} 占位)
├── scripts/
│ ├── pages.js # 构建编排脚本(版本文件、路由、WebStudio 构建等)
│ └── workers.sh # 生成 wrangler.jsonc 并上传 SUPABASE_ANON_KEY 密钥
├── wallpaper-api-schema.yaml # OpenAPI 3.0 接口契约(上传 Cloudflare API Shield 用)
├── .gitignore
├── package.json
└── LICENSE
构建过程中还会生成以下产物(不入库):public/version.json、public/_routes.json、public/_redirects、public/npm/viewerjs/、.webstudio/、dist/、wrangler.jsonc。
整体架构与请求路由
部署到 Cloudflare Workers 时,scripts/workers.sh 会在生成的 wrangler.jsonc 中通过 assets.run_worker_first 精确声明以下优先进入 Worker 的路径,其余路径全部由静态资源(ASSETS)直接响应。Cloudflare Pages 部署时则由 scripts/pages.js 生成的 _routes.json 声明白名单,路径列表与下方完全一致:
/ /detail /detail/
/assets/entries/<routing> /assets/entries/<index> /assets/entries/<detail>
/api/search /api/detail /api/view /api/update /api/bing-hp
/wallpaper/search /wallpaper/detail /wallpaper/view /wallpaper/update /wallpaper/bing-hp
其中 <routing>、<index>、<detail> 三个入口文件名由 workers.sh 在构建产物 dist/client/assets/entries/ 中自动探测(entry-server-routing.*.js、pages_index.*.js、pages_detail.*.js)。
Worker(public/_worker.js)内部的处理顺序:
OPTIONS预检直接返回204;- 命中 10 个 API 路径之一 → 走 Bing / Supabase 接口逻辑(见 API 接口);
- 命中
/、/detail、入口 JS 等 6 个前端路径 → 从 ASSETS 取静态产物,实时替换/注入数据后返回(见边缘 SSR 机制); - 其余请求回退
env.ASSETS.fetch(request),并统一附加静态资源长期缓存头。
环境变量
构建期环境变量(scripts/pages.js 读取)
| 变量名 | 说明 | 是否必需 |
|---|---|---|
VERSION |
版本信息 JSON,格式见下文 | 是 |
BACKGROUND_IMAGE |
APP 壳页面背景图片文件名(拼接为 /assets/<文件名>) |
是 |
WEBSTUDIO_LINK |
WebStudio 项目链接,用于 webstudio link |
是 |
VIEWERJS_URL |
构建前 data.json 中需要替换为本地 /npm/viewerjs 的 Viewer.js 地址 |
是 |
DOMAIN |
网站域名,写入 APP 壳页面的 WebView 目标地址及数据接口地址 | 是 |
FAVICON |
favicon 源文件名(位于 public/assets/,复制为 public/favicon.ico) |
是 |
F404PAGE |
构建产物中 404 页面的相对路径,默认 404/index.html |
是 |
ROUTING_JS / INDEX_JS / DETAIL_JS |
三个入口 JS 的文件名;缺失时脚本仅告警,实际文件名由 workers.sh 自动探测 |
建议配置 |
运行期环境变量(写入 wrangler.jsonc 的 vars)
| 变量名 | 说明 |
|---|---|
SUPABASE_URL |
Supabase 项目 URL,例如 https://xxxx.supabase.co |
SUPABASE_TABLE_NAME |
存储壁纸数据的表名,例如 bing_wallpaper_data |
ROUTING_JS / INDEX_JS / DETAIL_JS |
Worker 运行时识别入口 JS 所用的文件名(自动探测写入) |
密钥(secret)
| 变量名 | 说明 |
|---|---|
SUPABASE_ANON_KEY |
Supabase 匿名密钥(anon key)。workers.sh 通过 wrangler versions secret put 上传,不会写入配置文件 |
版本信息格式
VERSION 环境变量为 JSON 字符串,用于生成 version.json、替换下载页版本号、生成 _redirects 下载地址:
{
"website": { "name": "1.0.0", "code": 1 },
"android": { "name": "1.0.0", "code": 1 },
"windows": { "name": "1.0.0", "code": 1 }
}
未设置或解析失败时使用默认值 0.0.0 / code 0。
构建与部署
1. Cloudflare Pages 构建
npm run pages
scripts/pages.js 依次完成:
- 生成
public/version.json、public/_routes.json(Pages Functions 路由白名单,包含 6 个前端路径与 10 个 API 路径,与 Workers 部署的run_worker_first列表保持一致); - 生成
public/_redirects:三个客户端下载路径 307 跳转至 GitCode Releases(地址随VERSION中的版本号变化); - 替换下载页与
app.html中的版本号、背景图、域名占位符; - 安装 Viewer.js 并复制到
public/npm/viewerjs; - 执行
webstudio link/webstudio sync,替换 data.json 中的 Viewer.js 地址后执行webstudio build --template ssg; - 复制 favicon、处理
pages/下+data.ts的接口域名、执行项目自身的npm run build; - 复制 404 页面,并安装运行期依赖
@supabase/supabase-js。
2. Cloudflare Workers 构建
先导出 Shell 环境变量,再执行:
export SUPABASE_URL="https://xxxx.supabase.co"
export SUPABASE_ANON_KEY="你的-anon-key"
export SUPABASE_TABLE_NAME="bing_wallpaper_data"
# 以及 pages.js 所需的 VERSION、DOMAIN、WEBSTUDIO_LINK 等构建变量
npm run workers
该命令等价于 node scripts/pages.js && bash scripts/workers.sh:先完成上述 Pages 构建,再生成 wrangler.jsonc、写入 dist/client/.assetsignore(内容为 _worker.js,使其作为 Worker 入口而非静态资源暴露),并上传 SUPABASE_ANON_KEY 密钥。随后执行:
npx wrangler deploy
3. 定时任务(可选)
Worker 导出了 scheduled 处理器,触发时执行与 /api/update 相同的增量更新逻辑。生成的 wrangler.jsonc 默认不含 Cron 触发器,需要手动补充,例如每天 UTC 00:07 执行一次:
{
// ...其余配置
"triggers": {
"crons": ["7 0 * * *"]
}
}
API 接口
所有接口同时提供 /api/* 与 /wallpaper/* 两套等价路径;GET 参数走 Query String,POST 参数走 JSON Body。未知路径返回 404 与 { "error": "API不存在" }。
接口总览
| 功能 | 路径(两套等价) | 方法 |
|---|---|---|
| 搜索壁纸 | /api/search、/wallpaper/search |
GET / POST |
| 壁纸详情 | /api/detail、/wallpaper/detail |
GET / POST |
| 更新浏览量 | /api/view、/wallpaper/view |
GET / POST |
| 手动更新数据 | /api/update、/wallpaper/update |
GET / POST |
| 实时聚合图片 | /api/bing-hp、/wallpaper/bing-hp |
GET / POST |
1. 搜索壁纸
分页搜索标题、版权信息或日期,结果按 enddate 降序返回。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
search |
string | "" |
关键词,模糊匹配 title、copyright、enddate;为空时返回全部 |
page |
number | 1 |
页码,最小为 1 |
limit |
number | 30 |
每页数量,范围 1–100 |
curl "https://你的域名/api/search?search=山&page=1&limit=10"
{
"Previous_Page": 1,
"Page": 1,
"Next_Page": 1,
"output": [
{
"enddate": "20261017",
"url": "https://cn.bing.com/th?id=OHR.xxx_UHD.jpg",
"copyright": "……",
"title": "……",
"views": 0
}
]
}
当返回条数不足一页时 Next_Page 等于当前页,表示没有下一页。
2. 获取壁纸详情
按 enddate 精确查询单条记录。
- 参数:
enddate(必填),8 位日期字符串,如20261017
curl "https://你的域名/api/detail?enddate=20261017"
响应结构同搜索接口,output 为最多 1 条的数组;查询失败返回 400。
3. 更新浏览量
通过数据库函数 increment_wallpaper_views 将指定壁纸的 views 原子自增 1(单条 UPDATE ... SET views = views + 1,无并发竞态),并返回更新后的值。使用前需先在 Supabase 中创建该函数,见数据库表结构。
- 参数:
enddate(必填)
curl -X POST "https://你的域名/api/view" \
-H "Content-Type: application/json" \
-d '{"enddate":"20261017"}'
{ "output": [{ "views": 1 }] }
enddate 不存在时返回 404:{ "error": "No data found for the given enddate" }。
4. 手动更新壁纸数据
实时从 Bing 拉取最新壁纸并增量写入 Supabase,无需等待定时任务。无参数。
{
"inserted": 2,
"skipped": 13,
"errors": []
}
inserted:新插入条数skipped:因enddate已存在而跳过的条数errors:查重或插入过程中的错误信息数组;抓取异常时返回500
5. 实时聚合 Bing 图片(不落库)
直接聚合 Bing 两个时间窗口的图片返回,不读写数据库,适合前端直接展示最新图库。
| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
mkt |
string | zh-CN |
市场区域;zh-CN 时图片域名为 cn.bing.com,其他区域为 www.bing.com |
curl "https://你的域名/api/bing-hp?mkt=en-US"
{
"images": [
{
"enddate": "20261017",
"urlbase": "/th?id=OHR.xxx",
"copyright": "……",
"title": "……",
"uhdurl": "https://cn.bing.com/th?id=OHR.xxx_UHD.jpg",
"whurl": "https://cn.bing.com/th?id=OHR.xxx_UHD.jpg&w=3840&h=2160&rs=1&c=1"
}
],
"totalImages": 15,
"mkt": "zh-CN",
"sourceUrls": [
"https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=zh-CN",
"https://www.bing.com/HPImageArchive.aspx?format=js&idx=7&n=8&mkt=zh-CN"
],
"timestamp": 1789000000000
}
每张图片在 Bing 原始字段基础上额外补充:
uhdurl:UHD 高清图地址whurl:4K(3840×2160)分辨率地址
6. OpenAPI 架构文件(wallpaper-api-schema.yaml)
仓库根目录的 wallpaper-api-schema.yaml 是本 API 的 OpenAPI 3.0 契约文件,可直接上传到 Cloudflare API Shield Schema Validation,由边缘按声明的参数与请求体结构校验入站请求:
- 覆盖范围:10 条路径(
/api/*与/wallpaper/*两套等价路径)× GET / POST,共 20 个 operation,每个 operation 带有唯一operationId;servers声明api.mcyin.com、wallpaper.mcyin.com两个绝对 URL 主机名; - 校验内容:Query 参数的类型与取值范围(
page ≥ 1、limit为 1–100)、enddate必须匹配^\d{8}$、POST JSON body(application/json)的字段结构; - 最小化设计:Schema Validation 仅校验请求侧、不校验 responses,因此文件只保留参数与请求体定义,所有响应统一为占位的
200;全部$ref均为#/components/...本地引用(外部引用会导致上传失败),无uniqueItems、参数content/anyOf等不支持的写法; - 上传方式:Cloudflare Dashboard → Web Assets → Schema validation → Add validation,上传该 YAML 后会自动创建 operation 与校验配置,检测默认仅记录(Log),需另行配置 WAF Custom Rule 才会拦截违规请求;
- 本地校验:修改文件后可执行
npx @apidevtools/swagger-cli validate wallpaper-api-schema.yaml验证 OpenAPI 合法性。
边缘 SSR 机制
合并版 Worker 在纯 API 能力之上,增加了对 WebStudio SSG 产物的边缘数据注入,站点无需客户端二次请求即可获得完整数据:
/(首页):透传完整查询字符串到首页入口 JS 与路由 JS,保证搜索 / 分页参数在资源引用间一致;/detail、/detail/(详情页):缺省enddate时按 UTC+8 计算当天日期;查询 Supabase 取得壁纸数据后,将 HTML 中的占位内容(示例标题、示例查看次数、示例版权文本、示例日期、示例图片 URL)整体替换为真实数据;无参请求缓存 24 小时,带enddate的请求长期缓存(详见缓存策略);pages_index.*.js(首页入口):实时查询搜索结果,将入口 JS 中内置的占位数据结构整体替换为 Supabase 返回值,并同步改写search / limit / size参数;该响应使用no-store缓存;pages_detail.*.js(详情入口):按enddate注入单条壁纸数据,同样使用no-store缓存;entry-server-routing.*.js(路由入口):替换其中首页 / 详情入口的引用地址以透传查询字符串。
缓存策略
Worker 通过 serveTransformedAsset 的 cache 参数统一管理经边缘处理的响应,共三档策略;三档均移除静态资源原有的 ETag / Last-Modified:
| 策略 | Cache-Control | 适用内容 |
|---|---|---|
long(默认) |
public, max-age=31536000, immutable |
内容固定不变的静态产物 |
daily |
public, max-age=86400 |
当天有效、次日可能变化的页面 |
none |
private, no-store, max-age=0 |
每次请求实时注入数据的动态响应 |
各路径的实际策略:
| 路径 | 策略 | 说明 |
|---|---|---|
| 普通静态资源(ASSETS 透传兜底) | long |
未命中 Worker 专有逻辑的所有静态资源 |
/ 首页 HTML |
long |
页面骨架固定,实时数据由入口 JS 注入 |
/assets/entries/<routing> 路由 JS |
long |
仅改写入口引用上的查询字符串 |
/detail、/detail/(不带 enddate) |
daily |
缺省展示当天壁纸,内容随日期切换,缓存 24 小时 |
/detail?enddate=...(带 enddate) |
long |
历史日期的壁纸数据固定不变,可长期强缓存 |
/assets/entries/<index>、<detail> 入口 JS |
none |
响应体中实时注入 Supabase 搜索 / 详情数据 |
判断依据是原始请求是否携带 enddate:无参请求在服务端补全为当天日期后注入 HTML,因此只给 24 小时缓存,避免跨天后仍展示前一天壁纸;带参请求对应确定的历史记录,故与静态资源同级缓存。
API 响应
API 路径(/api/*、/wallpaper/*)的所有 JSON 响应(含 400 / 404 / 500 错误响应)统一经 addCorsHeaders 输出,在附加 CORS 头的同时设置 Cache-Control: private, no-store, max-age=0,浏览器与 Workers Cache 均不缓存,每次请求都实时执行(搜索查询、浏览量自增、数据更新等)。这些路径全部位于 run_worker_first / _routes.json 白名单中,该缓存头由 Worker 代码自行给出,_headers 规则不会作用于它们。OPTIONS 预检响应为无实体的 204,仅携带 CORS 头,不设置缓存头。
_headers 的作用范围
public/_headers 的规则只对由平台直接输出的静态资产响应生效。根据 Cloudflare 官方文档,无论 Workers 部署(配置了 assets.run_worker_first)还是 Pages 部署(Pages Functions / advanced mode _worker.js),_headers 规则均不会应用于 Worker 代码生成的响应。因此文件中各规则的实际效果为:
/*(public, max-age=31536000, immutable):作用于不经 Worker 的静态资产(图片、CSS、字体、favicon、/npm/viewerjs等),与 Worker 兜底透传资产时设置的长缓存等价;/assets/entries/pages*、/api/*、/wallpaper/*(private, no-store, max-age=0):对已列入白名单的三个入口 JS 与 10 个 API 路径不生效——它们的 no-store 头由 Worker 代码自行设置(见上文三档策略与 API 响应小节);该规则仅可能匹配白名单之外的其他pages_*静态资产。
Workers Cache(边缘缓存)
生成的 wrangler.jsonc 默认开启 Workers Cache("cache": { "enabled": true }):请求到达后先查边缘缓存,命中时直接返回、不执行 Worker。是否缓存完全由响应的 Cache-Control 按 RFC 9111 判定,且仅 GET / HEAD 请求参与:long 与 daily 响应会被边缘缓存对应时长,none 策略的入口 JS 与显式 no-store 的 API 响应均不缓存、每次实时执行;带 Set-Cookie 或请求带 Authorization 时自动绕过。缓存键默认包含 Worker 版本,重新部署后旧缓存自然失效。此外 wrangler.jsonc 默认开启完整可观测性日志。
参考文档:Workers Static Assets — Headers、Pages — Headers、Workers Cache。
数据库表结构
Supabase 表至少包含以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
enddate |
text | 8 位日期,主键,非空 |
url |
text | UHD 壁纸地址,可为空 |
copyright |
text | 版权信息,可为空 |
title |
text | 标题,可为空 |
views |
bigint | 浏览量,可为空;Worker 写入新记录时默认填 0 |
建表 SQL 示例:
create table public.bing_wallpaper_data (
copyright text null,
enddate text not null,
title text null,
url text null,
views bigint null,
constraint bing_wallpaper_data_pkey primary key (enddate)
) TABLESPACE pg_default;
浏览量原子自增函数(/api/view 依赖;函数以调用者权限执行,因此表上仍需配置允许 anon 角色 UPDATE 的 RLS 策略):
create or replace function public.increment_wallpaper_views(p_enddate text)
returns table(views bigint)
language plpgsql
set search_path = ''
as $$
begin
return query
with upd as (
update public.bing_wallpaper_data
set views = coalesce(bing_wallpaper_data.views, 0) + 1
where enddate = p_enddate
returning bing_wallpaper_data.views
)
select upd.views from upd;
end;
$$;
CORS 与错误处理
- 所有 API 响应(含错误响应)均附带:
Access-Control-Allow-Origin: *,允许方法GET, POST, OPTIONS,预检缓存 86400 秒,并设置Cache-Control: private, no-store禁止缓存 OPTIONS预检请求直接返回204- 参数或数据库错误返回
400,记录不存在返回404,上游 Bing 接口异常返回500 - SSR 处理异常返回
500与{ error, message, context },便于定位出错环节
技术栈
- Cloudflare Workers(Fetch 处理器 + Scheduled Cron 处理器 + Static Assets)
- WebStudio(SSG 站点构建)
- Viewer.js(图片查看器)
- Supabase(PostgreSQL)+ @supabase/supabase-js
许可证
MIT License
Copyright (c) 2026 mcyin
一个精美的 Bing 每日壁纸网站项目,前端站点与壁纸 API 已合并为同一个 Cloudflare Worker 部署单元:Worker 既负责 Bing 壁纸的抓取入库与接口服务,又在边缘对 WebStudio 生成的静态页面做数据注入(SSR),同时提供 Android / Windows 客户端下载跳转。支持网页版、Android 和 Windows 应用程序。
Wallpaper时光机 - Android客户端
Wallpaper时光机,收集每日壁纸。这是一个基于 Electron 开发的 Windows 桌面客户端。
