引言
想法的产生是因为又将博客引擎切回了Halo,使用了 halo-theme-sakura 作为博客主题,而主题自带的随机图服务并不能很好的自定义图片资源,且使用他人的服务总感觉怪怪的,又看到了 这篇博客 ,就打算自己整一个。
后端
后端部分使用SpringMVC搭配Kotlin使用,由于Spring5.2之后对于Kotlin Coroutines的支持,使得可以使用协程+命令式的风格实现响应式,具体请看这篇文章:Going Reactive with Spring, Coroutines and Kotlin Flow。
后端早期使用WebFlux写过一个demo,功能实现后的代码实在是太丑陋了,于是作罢。
代码仓库:random-img
需要注意的是本项目没有提供可视化的UI,一切操作都通过直接发送请求进行,可以使用curl
, insomnia
, postman
等工具。
一些说明
设计之初只考虑个人使用,因此只是用了固定用户名密码的Basic验证,也就不区分上传者。
图片个人全部使用 PixivBatchDownloader 从Pixiv上获取,也就按照p站的uid/pid来进行区分。
由于demo实验后发现Cloudflare Worker免费版的性能确实不足以支持大量的图片尺寸转换,因此在后端上传时对图片进行了预处理,除原图外分别存储了1920px
, 1440px
, 128px
, 960px
, 640px
, 320px
这几种宽度的webp
格式。
部署
- clone本项目,按需修改
docker-compose.yml
文件,参照配置部分 - 运行
docker-compose up -d
或是docker compose up -d
- (可选)自行配置反代
配置
Basic
BASIC_AUTH_USERNAME
:Basic验证携带的username
BASIC_AUTH_PASSWORD
:Basic验证携带的原始密码的SHA256结果
S3
以Cloudflare R2为例,首先创建一个存储桶,假设取名为images
将S3_BUCKET_NAME
的值填写为images
,S3_REGION
为auto
再创建一个存储桶,取名为temp
,将S3_MANUAL_UPLOAD_BUCKET_NAME
填写为temp
回到R2的主页,点击右上角的管理R2 API令牌
,创建一个允许对象读写
的,指定存储桶为images
和temp
的令牌,将创建后的访问密钥 ID
和机密访问密钥
填写为S3_ACCESS_ID
和S3_ACCESS_TOKEN
,并将为 S3 客户端使用管辖权地特定的终结点
填写为S3_ENDPOINT
请参照Cloudflare Worker的部署部分,将wrangler.toml
文件的route - pattern
值设置为S3_CDN_URL
,如https://images.your.domain
PERSISTENCE_REFERER
这个设置项是一个String Array,如果请求携带完全相同的Referer,那么这个请求的PostId和随机图会被持久化到数据库中,保证之后的每次随机都是相同图片。
请填写去除所有:
的Referer(因为会作为Redis的key)。
由于compose file
的限制,如果有多个请用 ,
隔开,而不是使用yaml
的对应语法(未测试)。
例如:https//a.domain.haha, https//b.domain.haha
PROBABILITY
默认是没有使用的,无需管它,用法请见io.sakurasou.dao.ImageDAO#randomSelImage
Cloudflare Worker
本项目的后端会将上传后的图片进行处理,并上传到Cloudflare R2(任何实现Amazon S3 API的对象存储)下,由于R2不收取流量费,且B类操作每月有10M的免费额度(太香了
部署
-
在
Cloudflare - Workers&Pages - KV
下新建一个KV,并复制它的ID -
首先clone仓库,并在根目录下创建
wrangler.toml
文件填写并修改以下内容:
name = "images-handler" main = "src/index.js" compatibility_date = "2024-05-17" node_compat = true workers_dev = false route = { pattern = "images.your.domain/*", zone_name = "your.domain" } r2_buckets = [ { binding = "imagesR2", bucket_name = "images"}, ] kv_namespaces = [ { binding = "imagesCache", id = "your-image-cache-kv-id" } ] [placement] mode = "smart"
-
在根目录下执行
pnpm install
,当然你可以用别的包管理工具 -
在根目录下执行
pnpm wrangler deploy
需要注意的是默认代码使用
images.your.domain/uid/pid
来进行图像识别,如果使用别的pattern
,请自行修改代码实现
使用
假设后端的地址是https://random.image.service
有(Basic)的需要携带相应请求头
上传图片(Basic)
直接上传
如果和服务器传输效率高,可以使用这种方式
发送Post请求到https://random.image.service/
,携带Multipart类型参数,参数名为file
,文件类型为zip
注意:zip文件的组织形式如下:
image.zip
-----111111(uid)
-----111p0.png(pid)
-----222222
-----222p0.png
即zip下有多个uid文件夹,只有一个uid文件夹的话也是如此(压缩时压缩文件名和文件夹名不一致即可)
如果超过60s没有上传完,会被cf断开连接(如果是cf cdn),那么请采用远程上传
远程上传
进入配置项S3_MANUAL_UPLOAD_BUCKET_NAME
的存储桶,直接将uid文件夹的zip文件上传(可以用别的工具上传到此存储桶)
这里的文件组织形式要求和直接上传不一样,每一个uid对应一个zip:
111111.zip
-----111p0.png
完成后发送PUT请求到https://random.image.service/remote-upload
,可选携带参数num
,用于后端限制单次下载的文件数量,建议一次不超过3个。
注意:远程上传时请打开日志
docker logs -f random-img
,并关注线程名为image-upload-thread
的输出,如果想多线程上传的话,请修改Dockerfile的启动参数,否则容易OOM,并且线程池默认使用轮询处理,似乎存在顶掉前面已提交未完成的任务的情况。
删除图片(Basic)
可以根据uid或id删除
发送DELETE请求到https://random.image.service/
必须携带uid/id中的一个,id的优先级高
清空图片(Basic)
发送DELETE请求到https://random.image.service/delete-all
参数:token
- 必须携带,请求后会打印在日志,二次请求携带即可
s3单次最多删除1000个文件,需要重复请求此接口实现清空
删除缓存(Basic)
发送DELETE请求到
https://random.image.service/cache/select
- 指定图片缓存https://random.image.service/cache/random
- 随机图片缓存
删除文章图片映射(Basic)
发送DELETE请求到https://random.image.service/post_image
参数:
- origin - 即referer,必须携带
- postId - 文章资源标识,必须携带
物理删除(Basic)
以上所有删除操作,都是逻辑删除(对象存储上的图片是真删了),想要从数据库中物理删除所有已删除的元组,发送DELETE请求到https://random.image.service/clean-deleted
指定图片
发送GET请求到https://random.image.service/
参数:
id
- 图片id,必须携带quality
- 图片质量,值为0(100%)
,1(75%)
,2(50%)
,可不携带,默认为1th
- 图片宽度,可不携带
随机图片
发送GET请求到https://random.image.service/random
响应:302跳转到图片资源,并携带id
请求头:Referer 可不携带
参数:
postId
- 标识资源,可不携带quality
- 图片质量,值为0(100%)
,1(75%)
,2(50%)
,可不携带th
- 图片宽度,可不携带
注意,对于quality,如果th与预设宽度一致,则无效,默认为1
参考资料: