标签归档:开发经验

silver mercedes benz emblem on blue surface

使用 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)
a computer screen with a bunch of text on it

又准备折腾博客了

我写博客经历过两个阶段 :

  1. 2012 ~ 2014 年:买虚拟主机、云主机,折腾性能和技术。在这个阶段,我的博客也经历过几轮变迁,也因为我自己没做好生产环境和测试环境的分离,导致数据丢了不少。目前的博客只能回溯到 2016 年便是因此。
  2. 2016 ~ 至今:不再折腾博客,开始专注于内容写作,并且保持每年都有更新,做一个活博客。

写了十年,我也算是没少搞和 WordPress 相关的事情,不过,时间久了,脑子里那些工程师的想法,难免重新冒出来 —— 我是不是应该自己写一个 Blog 系统。对于曾经的我来说,可能是不容易的,我并没有那么丰富的开发经验。但对于如今的我来说,确实是不那么困难的事情。

不过,写总是要有个目的和收益评估,不能「为了写而写」,而是应该有一个明确的目的,评估 ROI。

我为什么想写?

  • 觉得 WordPress 还是太臃肿,有不少我用不上的能力。
    • 那么我自己的 Blog 系统应该有一个明确的 Feature List。这样才能避免需求无限的膨胀。
  • 我的需求已经逐步稳定下来,不太需要新的主题/插件了。
    • 这些年的确主要是更新内容,博客的形式、内容啥的,基本上都是稳定不变了。

我为什么不能写?

  • 写 Blog 对于我来说没有什么特别的收益。
    • 毕竟这玩意没办法卖,除非写的过程中有别的收益,可能还好,不然大概率亏本。

我如果要写一个 Blog 系统,我需要哪些 Feature?

  • 文章系统:包含标题、描述、内容、Slug、目录、标签几个核心属性和实体。
    • Slug 需要自动翻译:我特别依赖这个功能。懒得自己翻译 Slug
  • 支持图片自动上传 S3
    • 我现在的图片都是用的外链,这样服务器自身的压力没那么大。
  • 支持评论
    • 评论还需要能够自动发送邮件更新。
    • 评论要能实现反垃圾。
    • 历史的 600+ 评论可导入
  • 简洁稳定的主题设计
    • 坦白来讲,这几年我很少做主题方面的变更了,基本上就是在几个景点主题上来回切换。
  • 支持 RSS Feed 等能力
  • 支持在线编写(可能非必需,最近开始逐步用 Ulyssess 写作,其实对于网页写文章的诉求越来越小了,maybe Hexo 是可接受的方案。)
a computer screen with a drawing of two people talking to each other

Solution as a Service, not Software as a Service

在软件领域,我们有非常经典的 IaaS、PaaS、SaaS 模型,我们使用这个模型定义着我们的产品。

但另一方面,这个定义也局限了很多人的想法 —— SaaS just Software as a Service。

实际上,如果你是一个独立开发者 / 直面用户的岗位,你需要深刻的明白 —— SaaS 更大的价值不应该是 Software ,而应该是 Solution —— 用户从来不关心你是不是有 Software,用户只关心你的 Solution 能不能解决你的问题。

如果你不知道你的 Software 是在解决谁的问题 —— 不妨想想,是不是你没有找到你的Software 到底应该如何放在 Solution 当中 ,以及那个 Solution 对应的问题到底是什么。

持续关注用户的问题,尝试提供靠谱的 Solution 去解决他的问题 —— 而不是关注你的 Software。除了你,没有人真正在意你的 Software。

brown and black round board

OKR 要长远,但迭代要敏捷

飞书执行季 OKR 已经很久了,相比于过去的双月 OKR,我认为这确实是一个好的事情。季度 OKR 可以让我们在一个更长期的事情上来完成我们要达成的目标而无需担心自己所做的事情要更加的长远和长期,也期待更多的团队和协作方可以享受到季 OKR 的带来的长期。

但从另外一个方向来看,即使我们使用了季度 OKR,也需要关注执行的迭代。

OKR周期是我们达成目标的周期,而做事的手段则应该尽可能的敏捷和快速。快速判断、快速执行、快速复盘、快速修正。

目标大和周期长是我们要着眼于更重要、更长期的事情。而迭代的敏捷,则可以帮助我们更好更快的抵达目标。

person holding sticky note

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)
34456427bc43e44f517b4eece861c6f5

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. 执行命令,扩容硬盘

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

black and white penguin toy

在你的 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)
blue red and green letters illustration

Thinking in Component Tree

在开发前端应用的时候,我比较推荐在真正开始写代码之前试着画一画组件树 / 状态树。

在很多时候,可能你的设计师已经帮你做好了组件树,但在某些场景下,你的设计时并不会帮你拆解组件树,或者是你是直接和产品经理对接,他不会帮你拆解组件树。

这个时候,相比于写代码,我更推荐你先拆解组件树,在完成组件树之后,再开始你的 Coding。

d2b5ca33bd970f64a6301fa75ae2eb22 5

Figma / Sketch 之类的软件提供的分组能力、图层的能力,可以帮助你将组件合理的拆解、分组、归类。当你完成树的建设之后,可以试试看将不同的模块拆解,每个模块是否可以独立正常的运转。如果不可以,则说明你的状态拆解的可能是有问题的。

当你完成拆解之后,只需要按照你拆解出来的树组织你的 Component 即可。

text

使用 idb-kayval 作为前端数据存储

在前端留存一些状态,是在前端场景下提升性能的常规操作。最近我有一个场景需要在前端留存一个状态,借着这个机会,试了试 IndexedDB 来作为数据存储,拓展一下新的方向。

关于 Indexed DB

Chrome 在中提供了多种不同的存储,按下 F12 ,打开 DevTools ,找到应用 - 存储,你就会看到目前 Chrome 支持的多种存储方式。常用的主要就是本次存储空间(Local Storage)、会话存储空间(Session Storage)和 Indexed DB。这次我用的便是 Indexed DB。

d2b5ca33bd970f64a6301fa75ae2eb22 1

开发使用建议

由于 Indexed DB 提供的 API 整体比较裸,在实际应用开发时,可能并不好用,你可以根据自己的需要,选择使用不同的第三方开发库来开发你的应用。在这篇文章中,我使用了 idb-keyval 来作为我的开发库。

d2b5ca33bd970f64a6301fa75ae2eb22 3

用法

首先,使用 yarn add idb-keyval 来安装依赖,安装完成后,可以参考如下代码来在你的项目中引入 indexedDB.

import { set,get,keys } from 'idb-keyval';


// 下面演示了一个 get_books 函数,会将内容存储在 IndexedDB 的 your-keys 当中。
// 如果存在缓存,则直接使用缓存,不存在,则进行数据获取
function get_books(){
   // 使用 keys 获取当前 IndexedDB 当中的所有 Key,用于判断当前是否有缓存结果。
   const exists_keys = await keys();
   if(exists_keys.indexOf('your-keys') !== -1){
    console.log("use cached glossary")
    return await get('your-keys');
   }

   // fetch data
   let data = fetch_data();
   
   await set('your-keys',data)
   return data;
}
Code language: PHP (php)

使用前后的效果

在性能上,使用 Indexed DB 之后,根据你的数据获取的难度,会有不同的性能提升。比如这里我不使用缓存,单次数据获取需要花费 800ms,借助于 Indexed DB,时间可以被控制在 10ms 以内,从而得到一个不错性能。

d2b5ca33bd970f64a6301fa75ae2eb22 2
person holding sticky note

FastAPI 在使用 pytest 加载不同的配置文件,以实现测试环境单独运行某些配置项目

引言

Fast API 作为一个新兴的 Python Web 框架,不少的开发者都在使用 FastAPI 来开发应用。我最近也在试着使用它。

在开发应用时,我习惯使用 TDD 的方式进行开发,特别是开发飞书机器人时,由于飞书给我提供的请求是可预测的,特别适合以 TDD 的方式来定义行为并实现。

同时,我在使用其他语言开发的时候,也会用到使用 .env.env.testing 这样的配置文件来在不同的环境下加载不同的配置文件,达到在不同环境使用不同的变量来完成任务。

内容大纲

  1. 实现读取 .env 文件
  2. 实现在测试环境读取 .env.testing 文件

模块详细内容

实现读取 .env 文件

想要实现在 FastAPI 中读取 .env 文件,首先,你需要引入 pydantic_settings 包,来完成基本的配置文件的读取。

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "Awesome API"

settings = Settings()
print(settings.app_name)

接下来你可以为这个 Settings 类新增 Config 配置,以实现读取本地的 .env 文件

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "Awesome API"

    class Config:
        env_file = ".env"

settings = Settings()
print(settings.app_name)

通过上述代码,你的配置项目就会自动读取 .env 文件来加载环境变量,从而实现更加简单的环境变量管理。

实现在测试环境读取 .env.testing 文件

在测试时,为了避免使用线上的数据,你可以在测试时加载不同的环境变量文件。

如果你只有一个测试环境,可以有一个非常简单的方式来实现:在配置 env file 时,传入一个元组,会自动加载多个文件。

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    app_name: str = "Awesome API"

    class Config:
        env_file = (".env",".env.testing")

settings = Settings()
print(settings.app_name)

需要注意,这里实现的实际上是使用 pydantic-settings 自己的加载顺序来实现。默认情况下,靠后的文件优先级会更高(覆盖了前面的内容)。这样的好处是如果你有些配置测试环生产是一样的,可以在 .env.testing 中加入不同的部分,相同的部分直接复用生产环境的配置项目即可。

但由于使用的是顺序加载多个问题,如果你发现表现和你配置的不一致时,就要看看是不是有优先级更高的环境变量文件覆盖了默认的 .env 文件。

结论

借助 pydantic-settings 的一些配置,你可以快速实现 .env.env.testing 的加载,相比与判断环境变量,这样会比较简单,且可以实现在 .env.testing 中只维护变量,和线上保持一致的部分可以直接继承。


附加部分