本文最后更新于:2024年8月21日 凌晨
前面三篇小记分别讲述了图床的整体架构 、用 Workers 构建 Restful API 和 自动更新部署 SSL 证书 ,这一篇c处理由此带来的图片上传问题,主要是要为 Typora 编写自动上传脚本,并为博客原有的图片进行迁移。
自动上传脚本 主要还是给 Typora 用,实现这种效果
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 #!/bin/bash HOST="upload.example.com" CDN_HOST="cdn.example.com" UPLOAD_PATH="uploads/$(date +%Y/%m/%d) " AUTH_TOKEN="1145141919810" webp=false markdown=false force=false keep=false while getopts ":mwfkp:" opt; do case $opt in m|markdown) markdown=true ;; w|webp) webp=true ;; f|force) force=true ;; k|keep) keep=true ;; p|path) UPLOAD_PATH=$OPTARG ;; \?) echo "Invalid option: -$OPTARG " ;; esac done shift $((OPTIND - 1 )) UPLOAD_URL="https://$HOST /$UPLOAD_PATH " if [[ "$UPLOAD_URL " == */ ]]; then UPLOAD_URL="${UPLOAD_URL%?} " fi for image in "$@ " ; do if [ "$webp " = true ]; then cwebp -quiet "$image " -o "${image%.*} .webp" image="${image%.*} .webp" fi if [ "$keep " = true ]; then FILENAME=$(basename "$image " ) else FILENAME="$(md5sum $image | cut -c 1-13) .$(basename $image | cut -d. -f2) " fi if [ "$force " = true ]; then UPLOAD_RESPONSE=$(curl -s -X PUT "${UPLOAD_URL} /$FILENAME " \ -w "%{http_code}" \ --data-binary @"$image " \ -H "X-Custom-Auth-Key: $AUTH_TOKEN " \ -H "Overwrite: true" \ ) else UPLOAD_RESPONSE=$(curl -s -X PUT "${UPLOAD_URL} /$FILENAME " \ -w "%{http_code}" \ --data-binary @"$image " \ -H "X-Custom-Auth-Key: $AUTH_TOKEN " \ ) fi UPLOAD_HTTP_CODE=$(echo "$UPLOAD_RESPONSE " | tail -n1) if [ -n "$UPLOAD_PATH " ]; then CDN_URL="https://$CDN_HOST /$UPLOAD_PATH /$FILENAME " else CDN_URL="https://$CDN_HOST /$FILENAME " fi if [ "$UPLOAD_HTTP_CODE " != "200" ]; then echo "上传失败: $UPLOAD_RESPONSE " continue fi if [ "$markdown " = true ]; then echo "![](${CDN_URL} )" else echo "${CDN_URL} " fi done
这一次使用 Cloudflare Workers 构建的 Restful API 很有意思,使用了 GET
、PUT
和 DELETE
三个请求类型。GET
请求很常见,是用来获取图片的,PUT
和 DELETE
在 web 开发就不如 GET
和 POST
常见了,这一次也是让我体会到了这两个 http verb 在 Storage Bucket 操作中是有多么形象了。
PUT
- 从直观上来讲,就是将某个文件放到目标位置
打个比方,我向 https://cdn.example.com/img/avatar.webp
打了一个请求,并带上了要上传的文件,那就意味着我将这个文件放到了 Storage Bucket 的 /img/avatar.webp
这个位置,所以我在上传后,应该就能用 GET
请求我刚才 PUT
的那个 URL 获取我刚才上传的东西。如果那个路径存在文件,那么默认行为是直接覆盖。
DELETE
- 删除目标路径的文件
和 PUT
一样,我在请求对应 URL 后,Storage Bucket 中对应 URL 路径的资源应该被删除。
PUT
和 DELETE
这两个 Http Verb 让我们更像是在对一个真实的文件系统进行操作,而非那种传统的使用 POST
上传的图床那样,我们并不通过 POST 请求上传一个文件,然后获取资源最终被放置位置的 URL —— 我们自己决定资源被存放的位置。
在这个 Shell 脚本中,引入了四个可选选项
1 2 3 4 5 m|markdown) markdown=true ;; w|webp) webp=true ;; f|force) force=true ;; k|keep) keep=true ;; p|path) UPLOAD_PATH=$OPTARG ;;
markdown 选项决定返回值是否以 ![]()
这种 URL 格式返回
webp 决定上传过程中是否将图片转为 webp 后再上传
force 决定如果遇到文件路径冲突,是否强制覆盖云端的文件
keep 决定是否保留文件原有的文件名进行上传
path 决定文件具体被存放的路径(或者使用默认的路径)
HOST
是图床用于上传的地址,CDN_HOST
是图床用于被方可访问的地址。
由于急着用,也没考虑协程的处理方式,等等看后期有没有时间用 Python 重写吧。
博客图床迁移脚本 因为只用一次,所以也没使用协程或者多线程的方式去上传文件——毕竟图片不多,也就两三百张。
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 import osimport reimport requests file_extension = [ '.md' , '.yml' , '.html' ] pic_urls = [] _files = [] pattern = r'https://cdn.example.com/\d{4}/\d{2}/\d{2}/[a-z0-9]{13}\.[a-z]{3,4}' def upload (url ): """ 此处的返回值应该是新的 url """ for root, dirs, files in os.walk("." ): for file in files: if file.endswith(tuple (file_extension)): file_name = os.path.join(root, file) with open (file_name, 'r' ) as f: content = f.read() urls = re.findall(pattern, content) if urls: pic_urls.extend(urls) _files.append(file_name) pic_urls = list (set (pic_urls))print ("共找到图片:" , len (pic_urls)) url_dict = {}for i,u in enumerate (pci_urls, start=1 ): for t in range (1 ,4 ): try : new_u = upload(u) continue except : if t == 3 : new_u = u print (f"{u} 无法上传:{e} " ) url_dict[u] = new_u print (f"{i} / {len (pic_urls)} " )for file in _files: with open (file, 'r' ) as f: content = f.read() for k, v in url_dict.items(): content = content.replace(k, v) with open (file, 'w' ) as f: f.write(content) print ("完成替换:" , file)