Cloudflare教程(二)- Wokers

Cloudflare教程(二)- Wokers

上一篇博客Cloudflare教程(一)-Wokers

需求背景

接着整我自己的ChinaHolidayAPI接口。

技术选型

  • Cloudflare的Wokers
  • ChinaHolidayAPI
  • D1 SQL数据库 – 这相当于MySQL
  • Workers KV数据库 – 这相当于redis
  • R2对象存储 – 这相当于OSS,我准备拿来存节假日的json文件

搭建过程

创建Wokers工程

创建一个GitHub的私有仓库仓库,用来存储我们的Wokers代码。当然这个过程你也可以没有,每次手动到cloudflare的网页端去更新文件也可以。不过我为了后续有可能增加其他功能,能持续的更新,还是准备了一个仓库。

参考cloudflare的Workers文档安装nodejs,安装命令行工具,在我们的仓库目录中新建一个workers项目。

注意cloudflare要求的node版本,我使用的是v22.12.0

1
npm create cloudflare@latest -- wooapi

我自己是准备后续做一个api的集合的,所以起了个名字:wooapi

创建的过程中,咱们就使用最简单的模板,从头开始写,依次选择:

  1. Hello World example
  2. Worker only
  3. JavaScript

等待npm安装完依赖,工具会提示你是否关联当前git仓库用于版本管理。我选择了yes,本来我也就打算之后用这个仓库关联到cloudflare,到时候自动更新部署。

接着会提示:是否现在部署?我选择现在部署。可以先直接看看效果。如果你没有使用过工具,会弹出网页让你登录你的cloudflare账号。登录之后,工具会继续执行部署操作。

等待部署完成之后,你可以看到workers的域名。可以直接访问看看。页面会直接返回:Hello World!

当然你也可以去cloudflare后台查看该workers的配置,同时添加自定义域名。我这里直接就添加了自定义域名,后续测试都使用该正式域名了。

wokers的部署完成之后,我们可以开始编写代码了。先看看cloudflare帮我们生成的项目结构:

项目结构里面,我们需要关心的只有两个地方:
src/index.js – 这是我们的代码
wrangler.jsonc – 这是wrokers的配置文件,包括绑定的数据库参数,项目入口文件等都在这里

我们修改一下index.js的内容,命令行进入wooapi目录,执行命令:

1
npx wrangler dev

工具输出:[wrangler:info] Ready on http://localhost:8787

此时,我们的workers已经在本地启动,访问http://localhost:8787便能看到我们修改的页面了。

我将代码改为:

1
2
3
4
5
export default {
async fetch(request, env, ctx) {
return new Response('Hello WorldAAA!');
},
};

可以看到工具输出:⎔ Reloading local server…
index.js被重新加载了。此时我们访问页面,看到的就是:Hello WorldAAA!

实现从D1数据库读取数据

查看文档:https://developers.cloudflare.com/workers/runtime-apis/bindings/
可以看到workers支持非常多的资源,都可以绑定到workers上。

如何绑定D1数据库

查看文档:https://developers.cloudflare.com/d1/get-started/#3-bind-your-worker-to-your-d1-database
文档说的非常清楚,你可以使用wrangler d1 create命令直接创建一个D1数据库,同时该命令也会将该数据库绑定到workers中(wrangler.jsonc自动添加D1数据库的配置信息)。
也可以手动添加配置,配置一个已经存在的D1数据库。由于我们之前已经收集过节假日信息,创建过一个D1数据库,所以这里我们选择手动添加配置。

1
2
3
4
5
6
7
8
9
{
"d1_databases": [
{
"binding": "你希望在代码里使用的变量名,这个必须符合js变量命名规范",
"database_name": "你的数据库名称",
"database_id": "你的数据库ID"
}
]
}
1
2
3
4
5
6
7
8
9
export default {
async fetch(request, env, ctx) {
const { results } = await env.n8n
.prepare("SELECT * FROM holidays limit ?;")
.bind("10")
.run();
return Response.json(results);
},
};

当你配置好之后,并且参考文档修改了代码,从D1读取数据,此时访问本地的服务,你会发现居然报错了。

我的第一反应是,代码写的有问题?然后检查代码,并且把SQL拿到D1后台去执行,发现是能正常查询到数据的。我便怀疑是不是本地启动服务,查询不了数据库?那我部署到线上试试看。

执行命令,进行部署。

1
npx wrangler deploy

等待部署完成。访问正式地址。nice!数据正常返回了,我们已经成功的将D1绑定到workers,并且在其中实现读取数据了。

实现根据规则查询数据

确定需求

首先要确定的是我们的需求,追求简单,我们就按照原仓库的接口来实现。我们仅支持GET请求。

此接口定义来源于开源仓库:https://github.com/Dreace/ChinaHolidayAPI

域名统一为:https://wooapi.664663.xyz/holidays – 出于扩展考虑,/holidays 为查询节假日接口

|接口样式|含义|备注|
|无参数|查询当日类型||
|?date=2025-09-09|查询指定日期类型||

实现代码

我们写一段测试代码,用来获取我们需要的参数。这段代码不涉及D1的查询,我们直接在本地就能测试。

1
2
3
4
5
6
7
8
9
10
export default {
async fetch(request, env, ctx) {
const { pathname, searchParams} = new URL(request.url);
// pathname 是 /holidays
// searchParams 是查询参数,是个object
// request.method 是请求方式,如:GET、POST等
return new Response(pathname+" "+searchParams.get("date") +" "+ request.method)
},
};

我们访问 http://localhost:8787/holidays?date=2025-09-09
可以看到接口返回的是:/holidays 2025-09-09 GET

那么我们就可以开始实现我们的代码了:

  1. 先判断是否**/holidays**,是的话就是查询节假日的请求;
  2. 再判断是否GET请求,是则继续执行,否则返回错误;
  3. 最后获取date参数,有效则查询指定日期,无效则查询当日类型;
  4. 根据查询的数据进行判断返回。

流程很简单,代码也不长,我也没有去优化。以下就是全部代码:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* Welcome to Cloudflare Workers! This is your first worker.
*
* - Run `npm run dev` in your terminal to start a development server
* - Open a browser tab at http://localhost:8787/ to see your worker in action
* - Run `npm run deploy` to publish your worker
*
* Learn more at https://developers.cloudflare.com/workers/
*/

export default {
async fetch(request, env, ctx) {
const { pathname, searchParams} = new URL(request.url);
// pathname 是 /holidays
// searchParams 是查询参数,是个object
// request.method 是请求方式,如:GET、POST等
if(pathname === '/holidays'){
if(request.method !== 'GET'){
return new Response(
JSON.stringify({ error: "仅支持GET请求" }),
{
headers: { "Content-Type": "application/json" },
status: 405 // 405 Method Not Allowed
}
);
}
var dateParam = searchParams.get('date');

if (!dateParam) {
// 查询当日数据
dateParam = this.getShanghaiDate()
}
// 格式验证(YYYY-MM-DD)
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
if (!dateRegex.test(dateParam)) {
return new Response(
JSON.stringify({ error: "无效格式,正确格式:YYYY-MM-DD" }),
{ headers: { "Content-Type": "application/json" }, status: 400 }
);
}

// 日期有效性验证
const [year, month, day] = dateParam.split('-').map(Number);
const date = new Date(year, month - 1, day);
if (
date.getFullYear() !== year ||
date.getMonth() + 1 !== month || // 注意:月份从0开始,需+1
date.getDate() !== day
) {
return new Response(
JSON.stringify({ error: "日期不存在(如2025-02-30)" }),
{ headers: { "Content-Type": "application/json" }, status: 400 }
);
}
// 默认数据
var result = {date:dateParam,isHoliday:false,note:"普通工作日",type:"工作日"}
// 日期校验OK,查询数据库
const { results } = await env.n8n
.prepare("SELECT * FROM holidays WHERE date = ?;")
.bind(dateParam)
.run();
if (Array.isArray(results) && results.length === 0) {
// 为空数组时说明不是假日或者补班日,判断其是否为周末进行数据返回
if(this.isWeekend(dateParam)){
result = {date:dateParam,isHoliday:true,note:"周末",type:"假日"}
}else{
result = {date:dateParam,isHoliday:false,note:"普通工作日",type:"工作日"}
}
return Response.json(result);
}
// 走到这里说明有数据,要返回查询回来的数据
const resultFirst = results[0]
if(resultFirst.type === "补班日"){
result = {date:dateParam,isHoliday:true,note:"补班日",type:"工作日"}
}else{
result = {date:dateParam,isHoliday:true,note:resultFirst.note,type:"假日"}
}
return Response.json(result);
}
return new Response("OK");
},
getShanghaiDate() {
// 创建当前时间对象
const now = new Date();

// 上海时区为 UTC+8,计算与 UTC 时间的偏移毫秒数
const utcOffset = 8 * 60 * 60 * 1000; // 8小时的毫秒数
const shanghaiTime = new Date(now.getTime() + utcOffset);

// 提取年、月、日(注意月份从0开始,需要+1)
const year = shanghaiTime.getUTCFullYear();
const month = String(shanghaiTime.getUTCMonth() + 1).padStart(2, '0');
const day = String(shanghaiTime.getUTCDate()).padStart(2, '0');

// 拼接为 YYYY-MM-DD 格式
return `${year}-${month}-${day}`;
},
isWeekend(dateStr) {
// 分割日期部分(已确保格式正确,无需再校验)
const [year, month, day] = dateStr.split('-').map(Number);

// 创建Date对象(月份从0开始,需减1)
const date = new Date(year, month - 1, day);

// getDay()返回0-6,0是周日,6是周六
const dayOfWeek = date.getDay();
return dayOfWeek === 0 || dayOfWeek === 6;
},
};

总结

至此,节假日查询接口就完成了,代码我没有特意去优化,现有的代码肯定是不能满足线上使用的需求的,比如缓存没有做,现在都是查询D1数据,效率不高。可以考虑从KV查询数据,并且将工作日的数据也存入KV,避免每次查询工作日,都请求到D1去。
还有诸如请求频率限制等等的问题,这里就不一一列举了。

后续我还会继续完善holidays接口,实现缓存,限制频率等。现阶段它还不能作为正式使用的接口,请勿使用。

测试地址:https://wooapi.664663.xyz/holidays?date=2025-05-05

同时,现在也还没有将GitHub仓库和cloudflare workers关联起来,所以代码修改后,需要手动执行npx wrangler deploy命令部署。后续我会一一实现这些功能。