分类目录归档:技术

如何快速清空七牛的存储空间中的所有文件,并删除存储空间

如果你和我一样,有很多历史的文件存储在七牛上,但如今已经不再需要使用,那么就可以考虑删除七牛的存储空间,来节省费用。

但七牛为了保证安全,所以要求必须删除所有的文件后才能删除空间,以避免误删除,所以需要一个个删除所有的文件。为了快速删除七牛存储空间的文件,我写了个简单的脚本,帮助你快速删除七牛空间下的所有文件。

具体操作可参考如下脚本,你只需要

  1. 在本地安装七牛 SDK :pip install qiniu
  2. 创建一个新文件 run.py 并复制下方的代码,修改其中的访问密钥和存储空间名称
  3. 执行 python run.py 就可以了。
# -*- coding: utf-8 -*-
# 导入七牛云 SDK 所需的模块
from qiniu import Auth
from qiniu import BucketManager, build_batch_delete
# 七牛云账号的访问密钥
access_key = '你的 ACCESS Key'
secret_key = '你的 Secret Key'
# 要清理的存储空间名称
bucket_name = '你要清空的空间名称'


# 使用 AK、SK 初始化授权对象
q = Auth(access_key, secret_key)
# 初始化存储空间管理器
bucket = BucketManager(q)

# 设置每次列举的最大条目数
limit = 1000

# 循环列举并删除存储空间中的文件
while True:
    # 列举存储空间中的文件
    # ret: 包含文件信息的字典
    # eof: 是否已列举完所有文件
    # info: 请求的状态信息
    ret, eof, info = bucket.list(bucket=bucket_name)
    # 从返回结果中提取文件名列表
    keys = [item['key'] for item in ret['items']]
    # 构建批量删除操作
    ops = build_batch_delete(bucket_name, keys)
    # 执行批量删除操作
    ret, info = bucket.batch(ops)
    # 检查删除操作是否成功
    if info.status_code == 200:
        print(f"success delete {len(keys)} files!")
    
    # 判断是否已经列举完所有文件
    if eof:
        break
    else:
        continue
# 输出清理完成的提示信息
print(f"delete all files in {bucket_name}")
Code language: Python (python)

执行成功后,你会看到如下面这样的命令,接下来等他自动执行即可,你就不用做任何事情了。

image

当工具提示你 delete all files in 你的 kodo 名时,你就可以回到七牛控制台,删除掉空的 kodo 了。

image

使用 Cloudflare 配置 301 转发

我的 newsletter 一部分使用 Ghost.js 进行托管,还有的在使用 Quail 来进行托管,而对应的域名则放在 Cloudflare 上进行解析。最近给 Quail 的 newsletter 绑定了域名,因为其只支持绑定一个域名,所以我就在 Cloudflare 上配置了 301 转发,来确保我的 @ 和 www 域名都访问同一部分内容。

这里我设定是 www 域名是真实绑定了 Quail 的后台,并通过 Cloudflare 完成相应的 CNAME 配置,确保网页可以正常访问,并要实现在 Cloudflare 上配置 301 转发,能够将根域名转发到 www 域名上。

image

在 Cloudflare 上配置域名解析,并开启代理模式

在 Cloudflare 上新增一个 CNAME 域名配置,配置你要转发的域名,目标可以选择你的 www 域名,或者是你的 www 域名的对应的 cname 域名。然后再打开代理

image

配置规则

在 Cloudflare 左侧侧边栏找到「规则」,进入规则页面,以配置规则。

image

在规则页面,创建一个新的规则,这里模板你可以选择「从 www 重定向到根」(如果你和我相反,是先建设好的根域名,将 www 转发过来,那直接使用这个规则就行。)

image

在弹出的规则配置页面设置你的转发规则,比如下图这样的设置就行。

image

这样当你的规则生效后,如果用户访问 AIStarter.dev/xxx,就会自动转发到 www.aistarte.dev/xxx。从而帮助你完成相关的配置能力。配置完成后,保存稍等片刻就可以生效了。这个时候你就可以使用 curl -I xxxx.xxx 来看你的域名转发情况,是否能正常返回 301 response。

image

如何修改 Advanced Media Offloader 使其可以批量将历史文件上传至对象存储?

Advanced Media Offloader 提供了 Bulk Offload 功能,可以实现将历史的文件上传到对象存储中,从而降低本地的存储压力,使得站点自身变得无状态。

但其默认的 Bulk Offload 功能每次只能加载 50 个图片,如果附件太多,则需要点击 N 次,十分麻烦。

image 6


不过,可以通过简单的修改,来实现一次上传,将多个图片进行 Offload。

在 WordPress 后台的插件管理器中,找到 Advanced Media Offloader 插件,并将includes/BulkOffloadHandler.php文件打开,找到其中的 get_unoffloaded_attachments 函数,修改函数定义中的 $batch_size = 50 为你想要的大小即可使其一次批量加载多个文件了。

image 8
修改位置

修改后效果:

image 7

WordPress 使用 Caddy 完成静态化缓存

使用 Caddy 处理 WordPress 当中,我提到在用 Caddy 处理 WordPress,且为了性能做了很多优化。

我的博客经历了三重优化:最基础的 PHP OpCache + Redis 数据查询缓存 + 静态化缓存。

其中一个比较有效的,便是在整个站点上加入静态化缓存,绝大多数游客看到的其实是预先生成好的静态页面,从而减少了数据库加载、渲染、计算的成本。

而想要实现静态化,则需要借助于 Cache Enabler 插件和 Caddy 配置来完成。

安装插件并启用

安装 Cache Enabler 插件,并启用插件,启用后,在后台设置中,配置过期时间和对应的清除策略,并保存。这个时候,Cache Enabler 就会自动帮你去生成不同的页面了。

image 5

配置 Caddy 路由转发

首先,你应该在你的 php_fastcgi unix//run/php/php-fpm.sock 前面加入缓存的代码并重启 Caddy,具体如下

image 4

缓存配置如下

     @cache {
		not header_regexp Cookie "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in"
		not path_regexp "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(index)?.xml|[a-z0-9-]+-sitemap([0-9]+)?.xml)"
		not method POST
		not expression {query} != ''
    }

    route @cache {
        try_files /wp-content/cache/cache-enabler/{host}{uri}/https-index.html /wp-content/cache/cache-enabler/{host}{uri}/index.html {path} {path}/index.php?{query}
    }
       
Code language: JavaScript (javascript)

这部分配置先定义了一个 @cache 块,用于后续判断,并在其中加入了多种条件判断,说明了不使用缓存的情况:

  • 如果用户有以下 Cookie,就不使用缓存:
    • comment_author(评论作者)
    • wordpress_[a-f0-9]+ (WordPress 的会话 Cookie)
    • wp-postpass(密码保护文章的 Cookie)
    • wordpress_logged_in(登录状态的 Cookie)
  • 如果当前请求命中了以下路径则不缓存
    • /wp-admin/(后台管理页面)
    • /xmlrpc.php(XML-RPC 接口)
    • 所有 wp-*.php 文件(WordPress 系统文件)
    • /feed/(RSS 订阅)
    • sitemap 相关文件
  • POST 请求不缓存(比如评论)
  • 带查询参数的缓存不请求。

随后,通过 route @cache 定义了命中缓存部分的查找顺序:

  1. 先找 HTTPS 版本的缓存:/wp-content/cache/cache-enabler/{host}{uri}/https-index.html
  2. 再找普通缓存:/wp-content/cache/cache-enabler/{host}{uri}/index.html
  3. 如果找不到缓存,就尝试原始路径:{path}
  4. 最后尝试 PHP 文件:{path}/index.php?{query}

查看效果

打开一个无痕窗口,访问你的网站,如果在 html 底部看到 <!-- Cache Enabler by KeyCDN @ Sat, 04 Jan 2025 03:05:34 GMT (https-index.html) --> ,则说明你已经成功启用静态化缓存了!

参考资料

使用 Caddy 处理 WordPress

在用了很久的 Docker 托管 WordPress 后, 近期我把 Blog 迁移到了腾讯云的香港轻量云主机上,以获得更快的访问体验。在这次迁移后,出于 Hack 方便的目的,我将 Nginx 替换成了 Caddy。你目前访问的站点便是一个基于 Caddy 托管的 WordPress 站点。

安装 Caddy

安装 Caddy 的过程不需要太过赘述,遵循 Caddy 官方安装文档当中的指南安装即可。

安装 PHP

完成了 Caddy 的安装后,则是安装 PHP,这里我使用的是 ondrej 维护的仓库

sudo add-apt-repository ppa:ondrej/php
sudo apt update
Code language: Bash (bash)

执行上述命令安装 PPA 仓库,就可以继续执行 apt install php 来安装 php & 对应的版本。此外,记得安装相关的依赖包

apt-get install php-fpm php-mysql php-curl php-gd php-mbstring php-common php-xml php-xmlrpc -y
Code language: Bash (bash)

配置 Caddy

完成安装后,就可以正常来配置 Caddy 。得益于社区的集成和 Caddy 官方的支持,Caddy 配置 WordPress 的支持非常简单,可以直接使用 Caddyfile 格式来撰写。

example.ixiqin.com { # 这个配置是给 example.ixiqin.com

    root * /data/caddy/example.ixiqin.com # 我的网站文件都放在 /data/caddy/xxx 下,/data 是我挂载的数据盘

    log { #日志配置
        output file /var/log/caddy/example.ixiqin.com.log  # 日志路径
        format console # 日志格式
    }

    request_body { # 请求体大小
        max_size 20MB # 最大 20MB
    }

    encode zstd gzip # 支持 gzip 和 zstd 压缩
    file_server # 直接提供静态文件(比如图片啥的)
    php_fastcgi unix//run/php/php-fpm.sock # 使用 php_fastcgi 调用 php-fpm 来处理动态 php 文件。
}
Code language: PHP (php)

只需要这样的配置,你就可以完成一个最基础的 WordPress 的站点的配置。

其他配置

对静态文件提供单独的 404 返回

按照上面的配置,其实所有的请求都会转发给 php-fpm 来处理,从而造成额外的 PHP 资源浪费。因此,可以在配置当中加入如下代码,来让 Caddy 直接返回,从而避免对 PHP 性能的浪费。

@static_404 {  
  path_regexp \.(jpg|jpeg|png|webp|gif|avif|ico|svg|css|js|gz|eot|ttf|otf|woff|woff2|pdf)$  
  not file  
}  

respond @static_404 "Not Found" 404 {  
  close  
}

Code language: JavaScript (javascript)

配置缓存头

除了静态文件的 404 处理,你还可以在 Caddy 当中配置静态文件的缓存,从而让浏览器更多的应用缓存,减少服务器的流量,提升加载速度。

@static {  
  path_regexp \.(jpg|jpeg|png|webp|gif|avif|ico|svg|css|js|gz|eot|ttf|otf|woff|woff2|pdf)$  
}  
header @static Cache-Control "max-age=31536000,public,immutable"

Code language: JavaScript (javascript)

解决因为 SSL 导致的 WordPress 后台无限 Redirect 的问题

在使用 CapRover 并配置域名为 HTTPs 域名时,在你访问管理后台时,可能会导致触发Chrome 自己的无限 Redirect 的问题。

之所以出现这个问题,是因为 CapRover 的架构导致的:CapRover 在最外层是一个 Nginx,SSL 证书也是在这一层完成的。而 CapRover 的默认配置,在将请求向后转发时,透传的域名会是不含 HTTPs 的协议标识的,导致 WordPress 认为发来的请求是非加密的。

d2b5ca33bd970f64a6301fa75ae2eb22 18

而 WordPress 识别到你的请求未加密,就会返回 302 让你进入 HTTPS的链接。但新的请求并不会带上 HTTPs 的标识,导致进入无限循环。

解决这个问题的一个简单处理的方式是 — 在你的 wp-config.php 中加入如下代码,来告诉 WordPress,这个请求已经是 HTTPs 保护的了,你直接处理就好。

/* for ssl in docker */

define('FORCE_SSL_LOGIN',true);

define('FORCE_SSL_ADMIN',true);

$_SERVER['HTTPS'] = 'on';
Code language: PHP (php)

Chinese-Calendar: 一个帮助你判断今天是不是工作日的 Pypi 包

在开发过程中,你可能会需要实现某些和工作日相关的特性(比如,工作日才发某些通知 /推送),这个时候,你可以借助于 chinese_calendar 这个包,来查看当前是否是工作日,你可以引入 chinese_calendar 这个包,来实现判断今天是否是工作日。

可以参考如下代码,is_workday_today 返回 True 时,就是工作日,就需要执行某些特定的逻辑。

from datetime import datetime
from chinese_calendar import  is_workday

# https://github.com/LKI/chinese-calendar
def is_workday_today():
    today = datetime.now();
    return is_workday(today)
Code language: Python (python)

CapRover 如何停止服务,并进行硬盘扩容/维护

在一开始使用 CapRover 时,我使用的是一个 10 GB 的数据盘,但在部署了诸多应用后,10GB 的数据盘已经无法满足我的需求,于是我就对其进行了扩容,扩容至 20GB。在完成扩容 & 重启后,仍需要执行 Linux 的扩容命令 resize2fs 来扩容硬盘。

但由于 CapRover 中运行的服务跑在这个数据盘上,并没有办法直接在这个数据盘上进行扩容(进程会持续读取文件),因此,需要先将 CapRover 上的服务暂停,暂停后进行扩容,并重新启动服务。

CapRover 底层是使用 Docker Swarm + Nginx 来进行的,因此,我们只需要使用 Docker Swarm 的命令,来停止服务运行即可。

1. 获取服务名称

首先,你需要先获取到当前所有在跑的服务,以便于稍后去暂停。执行 docker service ls 来获取到具体的服务名称。

d2b5ca33bd970f64a6301fa75ae2eb22 13

2. 拼接所需的命令

在 Docker Swarm 当中,并没有直接的 Start or Stop 概念,而是通过将 Replica 设置为 0 来实现关闭的能力。这个命令可以通过 docker service scale 服务名=服务数 来实现。因此,你需要将对应的服务设置为 0 来解决这个问题。你可以先行把开启和停止的命令拼接好,从而实现快速的启动和关闭,尽可能的减少宕机时间。

如果是有多个服务,可以直接拼接在后面,从而实现一次关闭 / 开启多个服务。

# docker service scale service_name=1 service_name_2=0
# 停止命令
docker service scale srv-captain--blog-ixiqin-com=0 srv-captain--mysql-8-production-db=0 srv-captain--pgsql-16-production=0 srv-captain--redis-server-production=0
# 启动命令
docker service scale srv-captain--blog-ixiqin-com=1 srv-captain--mysql-8-production-db=1 srv-captain--pgsql-16-production=1 srv-captain--redis-server-production=1
Code language: Bash (bash)

3. 执行命令,扩容硬盘

你可以先执行停止命令,然后执行扩容命令。完成扩容后,重新启动,即可完成整体的扩容。

使用 CapRover WebHook 获得类 Vercel 部署体验

我在开发前端应用的时候,基本上使用的都是 Vercel ,究其原因,主要是以下几个点:

  1. Vercel 可以方便的与 Github 整合,提供简单易用的部署方式:写完代码,测试完成后推送到 Github ,就会自动部署到线上。对于小型项目来说,可以简化部署的流程。
  2. Vercel 提供了自定义域名和自动配置的 SSL,提供了简单的配置方式:在现在 SSL 成为标配的模式下,在 Vercel 你只需要把域名解析到 Vercel ,并在你的 Project 当中绑定域名,就会自动完成域名绑定和 SSL 申请和续期。
  3. Vercel 提供了 FaaS 环境:写应用的时候,很多时候不只有前端的需求,这个时候, Vercel 自身的提供的 FaaS 环境可以帮助你完成基本逻辑的编写。

但 Vercel 毕竟是以前端为主,且函数运行时长也有限制,对于一些比较重的场景下,Vercel 还是不太够用。刚好最近我把服务部署从传统的 LAMP 换成了 Docker Based PaaS,我使用的 CapRover 提供了类似的体验。

使用 Cap Rover 你能获得的体验:

  • 上传代码后,自动部署到 Production
  • 绑定域名后,自动配置 SSL 证书,且可以配置其他域名转发到主域名

具体操作步骤见下:

安装 CapRover

CapRover 的安装我就不再赘述,跟随官方的说明安装即可。

绑定根域名

当你登录 CapRover 时,CapRover 会让你绑定一个泛域名解析,你可以根据自己的需要,绑定一个二级或者三级域名,然后在 DNS 解析一个 * 到这个服务器上。这样后续部署的服务就会自动解析一个 服务名.你的域名 ,用于服务的初步访问(类似于 xxx.vercel.app)。

d2b5ca33bd970f64a6301fa75ae2eb22 6

上传代码至 GitHub

在 Github 上创建一个代码仓库,并把你自己的项目部署上去。如果你有已经写好的 Dockerfile,可以一并上传上去。如果没有的话,则可以选择参考 CapRover 提供的 Sample App ,里面提供了常见语言的部署参考。

创建容器并配置环境

完成代码上传后,你可以进入到 CapRover 后台,创建一个新的 App。这里可以输入你喜欢的名字,方便后续查找即可。

d2b5ca33bd970f64a6301fa75ae2eb22 7

创建完成后,点击下方列表中的应用名称,进入应用的配置页面,并切换至 Deployment 页面。

d2b5ca33bd970f64a6301fa75ae2eb22 8

在这个页面,可以找到 Method 3 : Deploy From GitHub/ Bitbucket/Gitlab,填写你的仓库信息、分支名、用户名。密码你可以选择直接使用你的密码,也可以选择创建一个 Personal Access Token ,或者是在仓库里配置一个 Deploy SSH key 均可。

d2b5ca33bd970f64a6301fa75ae2eb22 9

配置完成后,会自动给你生成一个 Webhook 地址,复制这个 Webhook 地址。

d2b5ca33bd970f64a6301fa75ae2eb22 10

配置 Github 上的 WebHook

复制上方的 Webhook 地址,并进入到 Github 你的仓库 – Settings – webhooks 页面,新增一个 Webhook。

d2b5ca33bd970f64a6301fa75ae2eb22 11

粘贴你刚刚复制的 URL,Content Type 选择 application JSON,并在下方选择触发部署的时机。

d2b5ca33bd970f64a6301fa75ae2eb22 12

点击报错。

等待自动部署

接下来你就可以通过提交代码,来让其自动完成部署,从而享受类似于 Vercel 的推送即部署的体验~。

在你的 Github Actions 中添加一个 PostgreSQL 用于测试

在开发应用的时候,我们会选择使用 PostgreSQL 作为数据库进行开发,但在 Github Actions 环境下,默认是没有 PostgreSQL 作为数据库后端的,这个时候如果你想要测试一些和数据库相关的逻辑,就不得不面临两个选择:

  1. 使用一个和生产环境无关的数据库,比如 SQLite。
  2. 在 Github Actions 当中添加一个 PostgreSQL。

前者是大多数常规的做法,大概率也不会出现什么问题(毕竟作为 CURD 仔,我们用的大部分时候都是一些 ORM,很少裸写 SQL),不过依然存在一些概率是你写了一些 PostgreSQL Only 的 Query 无法覆盖到测试。

另外就是本文的核心了:在你的 Github Actions 当中添加一个 PostgreSQL

Github Actions Service

想要实现这个效果,我们依赖了 Github Actions Service Containers 这个能力。

服务容器是 Docker 容器,以简便、可携带的方式托管您可能需要在工作流程中测试或操作应用程序的服务。 例如,您的工作流程可能必须运行需要访问数据库和内存缓存的集成测试。

您可以为工作流程中的每个作业配置服务容器。 GitHub 为工作流中配置的每个服务创建一个新的 Docker 容器,并在作业完成后销毁该服务容器。 作业中的步骤可与属于同一作业的所有服务容器通信。 但是,你不能在组合操作中创建和使用服务容器。

GitHub

你可以选择你需要运行测试的环境中,找到对应的 Job,并在 Job 下新增一个 services ,即可为你的 job 设定一个依赖的服务容器,它可能是数据库 、 缓存之类的。比如我这里用的就是 PostgreSQL。

我的 Github Actions 完整参考:

  • services 是我运行的服务容器。
  • steps 是我的真正的测试流程。
name: Django CI

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

env:
  DEBUG: true
  SECRET_KEY: django-insecure-github-actions
  DB_NAME: postgres
  DB_USER: postgres
  DB_PASSWORD: postgres
  DB_HOST: localhost
  DB_PORT: 5432

jobs:
  build:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres
        env:
          POSTGRES_PASSWORD: postgres
        # Set health checks to wait until postgres has started
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.12]

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v3
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Run Tests
      run: |
        python manage.py test
Code language: PHP (php)