在上一篇文章中,我们完成了变量的配置。一步一步完成下来实属不易,恭喜你坚持到这里。
接下来,我们需要把整个项目部署到 Cloudflare 上。
如果你有自定义的需求,可以先参照篇末的「自定义」一节,对代码修改后再部署。
登录到 Wrangler
由于 App Store Connect API 需要实时签发 JWT 进行认证,所以源代码中引入了一些加密算法库,不能直接复制源代码来通过网页部署到 Cloudflare。
所以我们要使用 Cloudflare Wrangler,这是 Clouflare 发布的用来部署 Worker 的命令行程序。假如你还没有安装,可以查看这个官方文档来安装 Wrangler。
使用命令 wrangler login
,它会在你的浏览器打开一个页面认证你的 Cloudflare 账号,认证成功后即登录 Wrangler 成功。
创建和初始化数据库
我们使用 Cloudflare 免费的 D1 数据库来与 Worker 无缝结合。
用终端打开项目路径,运行以下指令:
wrangler d1 create <DATABASE_NAME>
把<DATABASE_NAME>
替换为你想要在 Cloudflare 面板上显示的数据库名称,例如:
wrangler d1 create numpkin-edu-offer
运行成功之后,终端会出现类似如下的输出:
wrangler d1 create <DATABASE_NAME>
✅ Successfully created DB '<DATABASE_NAME>'
# 复制输出中的以下内容
[[d1_databases]]
binding = "DB"
database_name = "<DATABASE_NAME>"
database_id = "<unique-ID-for-your-database>"
请复制输出中 [[d1_databases]]
及之后的内容,替换掉 wrangler.toml
中 [[d1_databases]]
对应的字段。
这样就完成了数据库的绑定。
接下来我们对数据库进行建表等初始化。
在项目路径下打开终端,运行以下建表指令:
wrangler d1 execute <DATABASE_NAME> --file=./schema.sql
这里的 <DATABASE_NAME>
与刚刚创建的一致。
个性化名称和邮件模版
在配置文件 wrangler.toml
的第一行,你还可以更改这个 Worker 的名称。例如,对于我的 app Numpkin,它的名称是 numpkin-edu-offer-sender
,这个名称会显示在你的 Cloudflare 面板。
我在代码中预置了发送邮件的模版,你可以把它替换为自己设计的 HTML 格式的邮件。如果要直接使用我的模板,需要针对你的 app 进行一些修改(比如邮件应该以你 app 而不是 Numpkin 的名义发送):
打开/src/worker.ts
,搜索 Email Related
,在该行注释之后,有以下几个变量,它们的含义如下,根据你的信息修改即可。
// appName:app 的名称,如 Numpkin
const appName = "Numpkin"
// serviceName:要给予教育优惠的服务名称
const serviceName = "Numpkin Pro"
// contactEmail:用于联系你的邮箱,如 Numpkin 支持的联系邮箱是 [email protected]
const contactEmail = "[email protected]"
// redeemPath:告知用户在哪里使用你的兑换代码
const redeemPath = "Numpkin 设置 -> 订阅 Numpkin Pro -> 兑换代码。"
//senderEmail:给用户发送邮件的发信人。请让该发信人与你在 Resend 中绑定的域名相符。例如在 Resend 中绑定了 mail.numpk.in
//那么就可以填写 [email protected]
const senderEmail = "[email protected]"
修改完成之后,邮件就会以你 app 的名义发出了。
导入依赖并部署代码分发 Worker
为了安装 Worker 需要的依赖,请在文件路径下运行 npm install
,等待拉取依赖(可能需要几十秒到一分钟)。
然后仍然在项目目录,使用终端运行 wrangler deploy
,稍等片刻后,项目就会被部署到云上。
(第一次部署可能会需要一些配置,但应该就是给自己设置一个名称/域名等等,根据提示就可以)
如果部署成功,wrangler 会输出以下几行
Uploaded <worker-title> (2.51 sec)
Published <worker-title> (3.92 sec)
https://<worker-title>.<zone>.workers.dev
最后一行以 worker.dev 结尾的链接,这就是唤醒代码分发 Worker 的链接。请保留它,即将进行的最后一步将会用到。
最后一步!设置邮件处理和转发
我们已经成功部署了代码分发 Worker,最后需要设置邮件路由和邮件处理 Worker,让新邮件传入时唤醒代码分发 Worker。
登录到 Cloudflare,选择你想要用于接收用户邮件的域名,并进入「邮件路由 (Email Routing)」标签。
根据提示,添加 DNS 记录并启用邮件路由。
如上篇文章介绍,如果你也想实现接收邮件来触发,你需要有一个闲置或者已经绑定 Cloudflare Email Routing 的域名。该服务只允许绑定一级域名(如 numpkin.app)来接收邮件,不允许绑定 mail.numpkin.app 这样的二级域名。
然后,在「电子邮件 Workers」下点击创建,
设置电子邮件 Worker 的名称,方便以后参考。模板可以任意选择一个(反正之后都会改),我选择了最后一个,然后创建。
创建完成之后,在本地打开/src/emailworker.js
,然后分别将数字 1 处 URL 修改为上一步获得的以 worker.dev 结尾的链接,2 处修改为上一节中设置的 EMAILWORKER_AUTHKEY
字段的值(如果忘记了可以重新打开 wrangler.toml
查看),3 处修改为你自己的邮箱,这会让 Email Worker 对该邮件执行转发操作(必须执行这个操作,Email Routing 才会给发送方的邮件服务器返回接收状态,才不会报错,否则用户的发信服务器会不断重试投递)。
完成之后,在「路由」分区下,创建一个自定义地址。我想让用户发送到 [email protected] 来获取教育优惠代码,因此这里填写了 edu。「目标位置」请选择刚刚创建的 Email Worker,完成后保存。
稍微向下滑动,点击「添加目标地址」,添加并验证你刚刚在代码中修改的数字 3 处的邮箱。这样才能成功在代码中调用转发函数,不设置会导致邮件无法发送,Worker 会抛出错误停止执行。
如果你不想自己的收件箱被转发来的邮件打扰,可以在自己的邮件服务里设置过滤规则(转发来的邮件收件人不会改变,在这个例子里你可以直接过滤收件人为 [email protected] 的邮件)。
大功告成!测试一下吧
如果每一步都顺利完成了(这确实是很漫长的道路…),那么现在向你刚刚设置的自定义地址(在我的例子里,是 [email protected])发送邮件就可以根据教育资格获得响应了。
如果你手上没有教育机构的邮件地址,可以在 /src/worker.ts
文件中修改eduSuffixes 常量,把你的邮件地址添加进去(从而把它当做是一个合法的教育邮箱),重新执行 wrangler deploy
来部署。这样部署之后不会改变 Worker 地址,可以直接重发邮件测试。
恭喜你,现在你可以公开这个教育优惠邮箱的获取途径了!
这个过程确实挺繁琐的,但希望你享受了这种探索和折腾最后实现目标的过程。
安全性
Cloudflare 会对传入的邮件进行 SPF、DMARC、DKIM 的检查(但我还没有测试过)。在发送代码时,我们也是直接向一个已经通过验证的教育邮箱地址发送,而不是回复原邮件。因此不会存在被伪造发件人骗走代码的情况。
为了保护在上篇文章获取的 API 密钥,你可以在部署完成 Worker 后,在 Cloudflare 面板中将它们加密,保存并部署后,就无法再在 Cloudflare 面板中查看此密钥。
主要的安全隐患:如果有人恶意向你发送大量的优惠代码申请请求,Resend 会很快超过的每日免费邮件额度。这可以通过取消报错邮件通知、升级 Resend 方案、对同一地址邮件进行速率限制等方法来解决。由于时间有限,我没能实现相关的逻辑。
自定义
部分读者可能有自定义的需求,我们来介绍一下worker.ts
文件中各函数的功能,并且给出可能的自定义建议。
createJWT
App Store Connect API 要求使用 API 密钥签发 JWT 用于验证,因此我们使用这个函数来签发一个有效期为 60 秒的JWT。
getCodesAndSaveToDB
App Store Connect 要求一次性最低生成 500 个代码,因此我们需要一次申请,保存到数据库,之后每次遇到请求后再取出使用。App Store Connect API 并没有指明最长能生成的代码有效期,经测试这个有效期在半年左右。这个函数就是向 App Store Connect 申请 500 个指定优惠的、截止有效期为当前日期后 177 天的一次性代码,并把代码、创建日期、截止日期等信息保存到数据库中。
可能的自定义选项:如果你不是 App Store 生态的开发者,可以修改这个函数,使用你自己的优惠代码生成逻辑。
fetch
接收传入的请求,进行授权认证是否请求来自于 Email Worker,未通过则返回错误。
判断是否是教育邮箱,是则调用redeemCode
函数,否则向用户发送不符合资格的邮件通知。
如果在过程中出现了错误,则捕获错误并使用notifyDeveloperOfError
函数来通知开发者。
可能的自定义选项:如果你想从前端直接调用此 API 而不是只能通过 Email Worker,可以去掉检查 Authorization
头的逻辑。但这样可能会因为 API 被滥用而导致邮件发送额度迅速用完,也可能会导致你的服务被用于发送垃圾邮件。你可以增加一些必要的速率限制、验证逻辑来规避这些问题。
redeemCode
传入一个邮件地址,判断是否在一年内兑换过,如果没有,则从数据库获取一个有效期七天以上、未被分发过的代码。
可能的自定义选项:你可以修改速率(默认需要距离上次兑换 1 年)和预留有效期(默认有效期至少 7 天)的限制。
notifyDeveloperOfError
在程序抛出错误时通知开发者。源文件中使用飞书自定义机器人通知。
可能的自定义选项:你可以修改为其他通知方式,或者禁用通知功能。在这两种情况下,你可能需要注释掉对这个函数的引用,或者修改函数内部的实现,并删除环境变量中的 FEISHU_ROBOT_URL
。
限制
我没有 TypeScript 的开发经验,所有代码都由 GPT 生成,经由我修改。可能有一些语法上的问题,欢迎指出。
(再一次)免责声明
这篇分享的所有步骤和代码都是公开的,尽管我已尽量保证安全性,但仍然可能存在没有预见的隐患。请阅读代码并理解各个步骤,自行判断得失、合规性、安全性后再部署。作者不对因此可能带来的任何风险和事故负任何责任。