引言

想法的产生是因为又将博客引擎切回了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格式。

部署

  1. clone本项目,按需修改docker-compose.yml文件,参照配置部分
  2. 运行docker-compose up -d或是docker compose up -d
  3. (可选)自行配置反代

配置

Basic

BASIC_AUTH_USERNAME:Basic验证携带的username

BASIC_AUTH_PASSWORD:Basic验证携带的原始密码的SHA256结果

S3

以Cloudflare R2为例,首先创建一个存储桶,假设取名为images

S3_BUCKET_NAME的值填写为imagesS3_REGIONauto

再创建一个存储桶,取名为temp,将S3_MANUAL_UPLOAD_BUCKET_NAME填写为temp

回到R2的主页,点击右上角的管理R2 API令牌,创建一个允许对象读写的,指定存储桶为imagestemp的令牌,将创建后的访问密钥 ID机密访问密钥填写为S3_ACCESS_IDS3_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的免费额度(太香了

仓库:random-img-cf-worker

部署

  1. Cloudflare - Workers&Pages - KV下新建一个KV,并复制它的ID

  2. 首先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"
    
  3. 在根目录下执行pnpm install,当然你可以用别的包管理工具

  4. 在根目录下执行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%),可不携带,默认为1
  • th - 图片宽度,可不携带

随机图片

发送GET请求到https://random.image.service/random

响应:302跳转到图片资源,并携带id

请求头:Referer 可不携带

参数:

  • postId - 标识资源,可不携带
  • quality - 图片质量,值为0(100%), 1(75%), 2(50%),可不携带
  • th - 图片宽度,可不携带

注意,对于quality,如果th与预设宽度一致,则无效,默认为1


参考资料:

ねぇ,あなたは何色になりたい