普通视图

发现新文章,点击刷新页面。
昨天以前沈唁志

怎么申请开具中国税收居民身份证明?

作者 沈唁
2025年4月18日 17:04

之前因为 Google Adsense 要求进行新加坡税务信息填写,需要上传税务证明,但是实在找不到个人在哪里申请,于是就暂停了 Google Adsense 服务。

不过在 2025 年 4 月 1 日起施行了新规,申请《中国税收居民身份证明》不再困难了!

下面就来说说申请步骤:

  1. 访问自然人电子税务局官网,找到我要办税,点击中国税收居民身份证明开具

自然人电子税务局官网

  1. 点击申请开具《中国税收居民身份证明》。后续查询也是这里,点击旁边的查询。

申请开具《中国税收居民身份证明》

  1. 选择申请年度等信息,选择完成后下一步会要求你确认主管税务机关,这个是根据任职受雇单位自动匹配,只需要选择使用那个单位申报就可以。

选择申请年度

  1. 填写申请信息,这里的要求比较多,我下文直接提供了

填写申请信息

  • 对方纳税人名称:Google Asia Pacific Pte. Ltd.
  • 拟适用协定名称:中华人民共和国政府和新加坡共和国关于对所得避免双重征税和防止偷漏税的协定
  • 拟适用协定条款:独立个人劳务条款
  • 拟享受协定待遇收入金额(元):按实际填,比如 720
  • 预计减免税金额(元):按实际填,比如 72
  • 相关附件:使用 Google AdSense 的在线服务条款即可,从这里下载:AdSense 在线服务条款
  • 申请人信息会自动补全
  • 在中国境内是否有住所:是
  • 有住所个人提供因户籍、家庭、经济利益关系而在中国境内习惯性居住的证明材料或说明材料:多选,可以选户籍或者经济利益关系,选择后需要上传身份证、户口簿,我还补充了自己公司的营业执照

在中国境内是否有住所填写

  • 证明领取方式:电子领取

点击下一步提交确认等待主管机关审核即可。

审核通过后没有通知,需要自己去登录网站去查看,如果有问题的话,可能会被拒绝,或者主管机关会给你打电话。

目前我已经通过了,1 号政策开放的时候提交了一次,前几天主管机关下级给我打电话说没有下发成功,让我撤销重新提交一次,这次提交后两三天就通过了。

HMAC 签名编码的坑:Go 和 PHP 的不同处理方式

作者 沈唁
2025年3月6日 17:19

在开发过程中,我们经常使用 HMAC(散列消息认证码)对数据进行签名,以确保数据完整性和身份验证。

然而,不同编程语言在对签名数据进行编码时可能会有所不同,导致相同的 HMAC 计算在不同语言中产生不同的结果。

这篇文章也是因为我直接将 PHP 的签名算法扔给 ChatGPT 生成,并没有实际测试,导致客户反馈签名计算失败,测试后才发现的。

本文将以 Go 和 PHP 为例,探讨为什么直接对 HMAC 签名进行 Base64 编码与先转换为 16 进制字符串再编码的结果不同。

代码示例

Go 代码

package main

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/base64"
    "encoding/hex"
    "fmt"
)

func main() {
    data := "hello"
    password := "123456"
    h := hmac.New(sha1.New, []byte(password))
    h.Write([]byte(data))
    signatureBytes := h.Sum(nil)

    // 直接对 HMAC 结果进行 Base64 编码
    base64Signature := base64.StdEncoding.EncodeToString(signatureBytes)
    fmt.Println(base64Signature) // 输出:NYSQUfYBHG0EZ6pU+r+Iw4CvPIQ=

    // 先转换成 16 进制字符串,再进行 Base64 编码
    hexString := hex.EncodeToString(signatureBytes)
    base64OfHex := base64.StdEncoding.EncodeToString([]byte(hexString))
    fmt.Println(base64OfHex) // 输出:MzU4NDkwNTFmNjAxMWM2ZDA0NjdhYTU0ZmFiZjg4YzM4MGFmM2M4NA==
}

PHP 代码

<?php
$data = "hello";
$password = "123456";

// 直接对 HMAC 结果进行 Base64 编码
echo base64_encode(hash_hmac('sha1', $data, $password, true));
// 输出:NYSQUfYBHG0EZ6pU+r+Iw4CvPIQ=

echo "\n";

// 先转换成 16 进制字符串,再进行 Base64 编码
echo base64_encode(hash_hmac('sha1', $data, $password));
// 输出:MzU4NDkwNTFmNjAxMWM2ZDA0NjdhYTU0ZmFiZjg4YzM4MGFmM2M4NA==
?>

为什么结果不同?

表面上看,Go 和 PHP 代码的逻辑是相同的,但它们的 Base64 结果却不同。

其根本原因在于编码前的输入数据不同。

PHP 参数定义文档

hash_hmac(
    string $algo,
    string $data,
    #[\SensitiveParameter] string $key,
    bool $binary = false
): string

PHP 手册中也提到了:当 binary 设置为 true 输出原始二进制数据,设置为 false 输出小写 16 进制字符串。

原始二进制 vs. 16 进制字符串

  1. 原始二进制数据
    • 在 Go 代码中,signatureBytes 是 HMAC 计算出的二进制数据。
    • 在 PHP 代码中,hash_hmac('sha1', $data, $password, true) 也返回二进制数据。
    • 直接对这些二进制数据进行 Base64 编码,输出的是编码后的 HMAC 结果。
  2. 16 进制字符串转换
    • 在 PHP 中,hash_hmac('sha1', $data, $password) 默认返回 16 进制字符串,每个字节被转换成 2 个字符。
    • 在 Go 中,hex.EncodeToString(signatureBytes) 也会将二进制数据转换为 16 进制字符串。
    • 由于 16 进制字符串的长度是原始二进制数据的 2 倍,在进行 Base64 编码时,最终结果也会完全不同。

Base64 编码的作用

Base64 编码的主要作用是将二进制数据转换为文本格式,便于在 URL 或 JSON 等环境中传输。

它不会改变数据的内容,而是按照固定的方式将每 3 个字节转换为 4 个可打印字符。

因此,输入数据的不同会直接影响最终的编码结果。

如何保证一致性?

如果希望跨语言 HMAC 计算保持一致,建议:

  • 确保 Base64 编码前的数据格式一致,统一使用二进制数据进行编码。
  • 在 PHP 中,使用 hash_hmac('sha1', $data, $password, true) 以获取二进制结果。
  • 在 Go 中,直接使用 base64.StdEncoding.EncodeToString(signatureBytes),避免中间转换为 16 进制字符串。

结论

  • 直接对 HMAC 结果进行 Base64 编码,能保持原始数据格式,保证数据可还原。
  • 先转换为 16 进制字符串再进行 Base64 编码,会导致数据翻倍,最终的编码结果不同。
  • 在不同语言间使用 HMAC 签名时,务必保证编码方式的一致性,以避免验证失败。

希望这篇文章能帮助你理解 HMAC 签名在不同语言中的编码差异,并在开发中避免类似的问题!

Bitwarden Secrets Manager:简化 DevOps 的机密管理

作者 沈唁
2025年2月27日 14:42

在 DevOps 和开发流程中,如何安全高效地管理机密数据(如密码、API 密钥和认证信息)是一个重要话题。

Bitwarden 是一款开源密码管理工具,帮助用户存储、管理并共享敏感信息。Bitwarden 推出了新产品Secrets Manager,专为 DevOps 团队和开发人员提供简化的机密管理方案。

对于使用 GitHub Actions 等 CI/CD 工具的团队来说,Secrets 是一种存储机密信息的常见方式。

但是,GitHub Actions 中的 Secrets 一旦保存,就无法查看或修改,这使得本地保存机密变得繁琐且易出错。

而在团队环境中,个人和公司电脑之间的同步问题更是增加了额外的复杂性。

Bitwarden Secrets Manager 解决了这一难题,提供了安全、高效的机密存储与管理方式。

免费额度和在线服务

与传统的 Bitwarden 密码管理器类似,Secrets Manager 也支持 self-hosting,但需要授权才能进行。

为了简化流程(白嫖),也可以使用 Bitwarden 提供的在线服务,享受如下免费额度:

  • 无限量的 Secrets
  • 2 位用户
  • 3 个项目
  • 3 个机器账户

这些免费额度对于大多数小型或中型团队来说已足够使用。

如何集成 GitHub Actions?

Bitwarden Secrets Manager 便于与 GitHub Actions 等 CI/CD 服务进行集成。

以下是一个简单的示例,展示了如何在 GitHub Actions 中获取并使用存储在 Bitwarden 中的机密。

首先,在 GitHub Actions 的工作流 YAML 文件中,添加获取机密的步骤:

- name: Get Secrets
  uses: bitwarden/sm-action@v2
  with:
    access_token: ${{ secrets.BW_ACCESS_TOKEN }}
    base_url: https://vault.bitwarden.com
    secrets: |
      fc3a93f4-2a16-445b-b0c4-aeaf0102f0ff > SECRET_NAME_1
      bdbb16bc-0b9b-472e-99fa-af4101309076 > SECRET_NAME_2

在上面的示例中,fc3a93f4-2a16-445b-b0c4-aeaf0102f0ffbdbb16bc-0b9b-472e-99fa-af4101309076 是存储在 Bitwarden Secrets Manager 中的机密 ID,而 SECRET_NAME_1SECRET_NAME_2 是引用机密的名称,用于在后续步骤中进行使用。

接着,在后续步骤中,使用这些机密值:

- name: Use Secret
  run: SQLCMD -S MYSQLSERVER -U "$SECRET_NAME_1" -P "$SECRET_NAME_2"

完整示例:

- name: Get Secrets
  uses: bitwarden/sm-action@v2
  with:
    access_token: ${{ secrets.BW_ACCESS_TOKEN }}
    secrets: |
      fc3a93f4-2a16-445b-b0c4-aeaf0102f0ff > GITHUB_GPG_PRIVATE_KEY
      bdbb16bc-0b9b-472e-99fa-af4101309076 > GITHUB_GPG_PRIVATE_KEY_PASSPHRASE

- name: Import GPG key
  uses: crazy-max/ghaction-import-gpg@v6
  with:
    gpg_private_key: ${{ env.GITHUB_GPG_PRIVATE_KEY }}
    passphrase: ${{ env.GITHUB_GPG_PRIVATE_KEY_PASSPHRASE }}
    git_user_signingkey: true
    git_commit_gpgsign: true

更多集成方式请参考 官方文档

Secrets Manager CLI 使用

为了便于在本地查询和管理机密,Bitwarden 提供了强大的 Secrets Manager CLI 工具。可以通过它来创建、删除、编辑和列出机密。

GitHub Releases 下载适合操作系统的可执行文件,运行以下命令以查看帮助信息:

bws --help

使用 Secrets Manager CLI 时,需先配置访问令牌(Access Token),然后运行如下命令列出机密:

# 设置环境变量
export BWS_ACCESS_TOKEN=xxxxxx
bws secret list

# 或者从命令行传入
bws secret list --access-token xxxxxx

Alfred Workflow 集成

为了进一步提高效率,写了一个小工具,帮助在 macOS 上快速查询并复制 Secrets。

Alfred Workflow 效果

以下是完整的 PHP 脚本示例:

<?php

$tokenName = !empty($argv[1]) ? trim($argv[1]) : '';
$accessToken = getenv('BWS_ACCESS_TOKEN');
$iconPngUrl = 'icon.png';

$json = `bws secret list -t '{$accessToken}'`;
if (!$json) {
    echo json_encode(['items' => [['title' => 'Error: Failed to fetch secrets', 'valid' => false]]]);
    exit;
}

$list = json_decode($json, true);

$items = [];

foreach ($list as $item) {
    if (!empty($tokenName) && stripos($item['key'], $tokenName) === false) {
        continue;
    }

    $items[] = [
        'arg' => $item['value'],
        'title' => $item['key'],
        'subtitle' => $item['note'],
        'icon' => ['path' => $iconPngUrl],
        'valid' => true,
    ];
}

if (empty($items)) {
    $items[] = [
        'title' => 'No secrets found',
        'valid' => false
    ];
}

echo json_encode(['items' => $items]);
exit;

Bitwarden Secrets Manager 为 DevOps 团队提供了一种更加安全、便捷的方式来管理和集成机密信息。

无论是与 GitHub Actions 集成,还是使用 CLI 工具进行本地管理,Bitwarden 都提供了简洁而强大的功能,帮助提升工作效率并确保敏感数据的安全。

MySQL 字符集与大小写敏感性解析

作者 沈唁
2025年2月25日 14:24

在 MySQL 数据库中,UTF-8 及其变体是最常用的字符集。

不同的 UTF-8 编码可能对大小写敏感性产生影响,主要包括以下几种:

  • utf8:MySQL 早期的 UTF-8 实现,最多支持 3 字节,无法存储部分 Emoji 字符。
  • utf8mb4:MySQL 5.5+ 版本推荐使用的 UTF-8 编码,最多支持 4 字节,能够完整存储所有 Unicode 字符。

字符集与排序规则(Collation)

MySQL 字符集搭配不同的排序规则(Collation)可能会影响查询的大小写敏感性。

常见的排序规则包括:

  • utf8_general_ci / utf8mb4_general_ci:不区分大小写(Case Insensitive,ci 代表 Case Insensitive)。
  • utf8_bin / utf8mb4_bin:区分大小写(Binary,bin 代表按二进制存储,严格区分大小写)。
  • utf8_unicode_ci / utf8mb4_unicode_ci:更符合 Unicode 规范的排序方式,不区分大小写。

默认情况下,utf8_general_ciutf8mb4_general_ci 在搜索时是不区分大小写的。

MySQL 大小写搜索问题

当 MySQL 表的字符集设置为 utf8_general_ciutf8mb4_general_ci 时,使用 LIKE= 进行查询时,默认是不区分大小写的。

例如:

SELECT * FROM users WHERE username = 'admin';

如果数据库中存储了 AdminADMIN 等,查询会返回这些所有匹配项。

如果需要执行区分大小写的查询,则需要:

  1. 修改排序规则(Collation)
ALTER TABLE users MODIFY username VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;

这样查询就会严格区分 adminAdmin

  1. 使用 BINARY 关键字
SELECT * FROM users WHERE BINARY username = 'admin';

这样 admin 只会匹配完全相同的字符串,而不会匹配 AdminADMIN 等。

在 ThinkPHP 框架中使用 whereRaw 进行原生查询

在 ThinkPHP 框架中,默认的 where 方法不支持直接使用 BINARY 进行查询,但可以通过 whereRaw 方法执行 MySQL 原生查询。

$result = Db::table('users')
    ->whereRaw("BINARY username = ?", ['admin'])
    ->find();

$result = Db::table('users')
    ->whereRaw("BINARY username LIKE ?", ['%admin%'])
    ->select();

这种方法可以避免默认的大小写不敏感查询,让 MySQL 进行更严格的匹配。

总结

  • MySQL 的 utf8_general_ciutf8mb4_general_ci 默认不区分大小写。
  • 需要区分大小写时,可以修改排序规则(Collation)或使用 BINARY 关键字。
  • 在 ThinkPHP 框架中,可以使用 whereRaw 方法执行 MySQL 原生查询,确保大小写敏感匹配。

这样,你就可以在 ThinkPHP 框架中更灵活地处理 MySQL 字符集大小写敏感的问题。

在命令行中输出带颜色的日志

作者 沈唁
2025年1月15日 18:09

在命令行界面(CLI)中输出带颜色的日志不仅能提升可读性,还能帮助开发人员在调试时迅速区分不同类型的日志信息。

通过使用 ANSI 转义序列,我们可以很方便地控制输出文本的颜色、样式和其他显示效果,如加粗、下划线、反显等。

本文将详细介绍如何使用这些序列输出带颜色的日志。

什么是 ANSI 转义序列?

ANSI 转义序列是一种用于控制终端文本格式的字符序列。

它通常以 \033[\e[ 开头,后接不同的控制代码,最后以 m 结尾。

例如,\033[32m 表示设置文本颜色为绿色,\033[0m 用来重置样式。

利用 ANSI 转义序列,开发者可以灵活地在命令行中输出不同颜色和效果的文本。

常见的 ANSI 转义序列控制

  • \033[0m:关闭所有属性,恢复为默认设置
  • \033[1m:设置高亮度(加深显示)
  • \033[4m:设置下划线
  • \033[5m:设置闪烁
  • \033[7m:反显(替换背景色和前景色)
  • \033[8m:消隐(隐藏文本)
  • \033[30m\033[37m:设置前景色(字体颜色)
  • \033[40m\033[47m:设置背景色
  • \033[nA:光标上移 n 行
  • \033[nB:光标下移 n 行
  • \033[nC:光标右移 n 行
  • \033[nD:光标左移 n 行
  • \033[y;xH:设置光标位置为 y 行 x 列
  • \033[2J:清屏
  • \033[K:清除从光标到行尾的内容
  • \033[s:保存光标位置
  • \033[u:恢复光标位置
  • \033[?25l:隐藏光标
  • \033[?25h:显示光标

常见的颜色和效果

字体颜色(前景色)

  • 30: 黑色
  • 31: 红色
  • 32: 绿色
  • 33: 黄色
  • 34: 蓝色
  • 35: 紫色
  • 36: 深绿色
  • 37: 白色

背景颜色

  • 40: 黑色背景
  • 41: 红色背景
  • 42: 绿色背景
  • 43: 黄色背景
  • 44: 蓝色背景
  • 45: 紫色背景
  • 46: 深绿色背景
  • 47: 白色背景

显示效果

  • 0: 默认(无效果)
  • 1: 高亮(加深显示)
  • 2: 低亮(减弱显示)
  • 4: 下划线
  • 5: 闪烁
  • 7: 反显(替换前景色和背景色)
  • 8: 消隐(隐藏文本)

示例代码

  1. 简单的颜色输出

最简单的颜色控制是设置文本的前景色。例如,以下代码将输出绿色文本:

echo "\033[32m绿色\033[0m"

033[32m 设置文本为绿色,033[0m 用于重置所有属性,使后续输出恢复默认样式。

  1. 加粗和下划线

我们可以结合多种样式来增强文本的可读性。例如,下面的代码将输出一个带下划线的红色文本:

echo "\033[4;31m下划线红色\033[0m"

这里,4 表示下划线,31 表示红色。

  1. 发出声音提示

除了颜色和样式,ANSI 转义序列还可以控制终端的其他行为,比如发出声音。

这行命令会在终端发出一声铃声,同时输出一段普通文本:

echo "\007发出'咚~'一声\033[0m"

请注意,在某些终端环境下,铃声可能不会响起,尤其是在没有扬声器的设备上。

  1. 设置背景色和前景色

你还可以同时设置文本的前景色和背景色。例如,以下代码将输出一个白色背景和红色前景的文本:

echo "\033[47;31m白底红字\033[0m"

47 是背景色(白色),31 是前景色(红色)。

  1. 结合多个效果

通过组合多个效果,你可以创建更具视觉冲击力的输出。比如,以下代码将输出一个蓝色加粗下划线的文本:

echo "\033[1;4;34m蓝色加粗下划线\033[0m"

在这个示例中,1 表示加粗,4 表示下划线,34 表示蓝色。

  1. 光标控制和清屏

ANSI 转义序列还允许控制光标的位置和终端屏幕的清理。例如:

echo "\033[2J"  # 清屏
echo "\033[10;5H光标移动到第10行第5列\033[0m"
  1. 隐藏和显示光标

你还可以隐藏和显示光标:

echo "\033[?25l"  # 隐藏光标
echo "\033[?25h"  # 显示光标

实现效果

通过使用 ANSI 转义序列,我们可以轻松地为命令行中的输出添加颜色和样式。

这不仅能让调试日志变得更加易读,还能增强命令行工具的用户体验。

你可以根据需求结合不同的颜色、效果和光标控制,创建自定义的命令行输出,通过这些技术,命令行的输出变得更加生动和富有表现力,有助于开发人员快速识别关键信息。

PHP 中生成带毫秒的时间戳

作者 沈唁
2025年1月2日 12:05

今天在对接一个 API 的时候,发现需要生成高精度的时间戳,格式为yyyyMMddHH24mmssSSS

本文将介绍两种常见的实现方式,并讨论它们的优缺点。

时间格式解析

格式 yyyyMMddHH24mmssSSS 的含义如下:

  • yyyy:四位数的年份(例如:2025)。
  • MM:两位数的月份(01-12)。
  • dd:两位数的日期(01-31)。
  • HH24:两位数的小时(24 小时制,00-23)。
  • mm:两位数的分钟(00-59)。
  • ss:两位数的秒(00-59)。
  • SSS:三位数的毫秒(000-999)。

例如,时间 2025-01-02 11:30:45.123 的格式化结果为:20250102113045123

使用 DateTime 类实现

以下是使用 DateTime 类生成毫秒时间戳的代码示例:

<?php
$dateTime = new DateTime();
// 获取当前时间的微秒数并计算为毫秒
$milliseconds = intval($dateTime->format('u') / 1000);
// 格式化时间
$formattedTime = $dateTime->format("YmdHi") . $dateTime->format("s") . sprintf("%03d", $milliseconds);
echo $formattedTime;

代码解析

  1. $dateTime->format('u') 返回当前时间的微秒(6 位数,例如 123456)。
  2. intval($dateTime->format('u') / 1000) 将微秒转换为毫秒(3 位数,例如 123)。
  3. 使用 sprintf("%03d", $milliseconds) 确保毫秒部分始终为 3 位数(不足时补零)。

示例输出

假设当前时间为 2025-01-02 11:30:45.123456,输出结果为:

20250102113045123

使用 microtime 函数实现

另一种方法是结合 microtime()date() 函数:

<?php
$microtime = microtime(true);
// 格式化时间到秒
$formattedDate = date('YmdHis', floor($microtime));
// 获取毫秒部分
$milliseconds = sprintf('%03d', ($microtime - floor($microtime)) * 1000);
// 拼接毫秒
$formattedDate .= $milliseconds;
echo $formattedDate;

代码解析

  1. microtime(true) 返回当前 Unix 时间戳,包含秒和小数部分。
  2. floor($microtime) 获取整数秒部分。
  3. ($microtime - floor($microtime)) * 1000 提取小数部分并转换为毫秒。
  4. 最终拼接秒部分和毫秒部分,生成完整的时间戳。

示例输出

假设当前时间为 2025-01-02 15:30:45.123456,输出结果为:

20250102153045123

对比分析

特性 使用 DateTime 使用 microtime()
代码简洁性 更加现代化,语义清晰 较为传统,需要手动处理毫秒
精度 取决于系统支持的时间精度 依赖 microtime() 的实现
扩展性 更容易与其他 DateTime 操作结合 适合处理与 Unix 时间戳相关的逻辑

统一验证输出

为了验证两种方法的输出是否一致,可以添加以下代码:

if ($formattedTime === $formattedDate) {
    echo "两种方法的输出一致:$formattedTime\n";
} else {
    echo "两种方法的输出不一致:\n第一种方法:$formattedTime\n第二种方法:$formattedDate\n";
}

总结

  • 如果你的项目主要使用 DateTime 类,建议采用第一种方法,代码语义更加清晰。
  • 如果需要与 microtime() 或 Unix 时间戳直接交互,可以选择第二种方法。

选择哪种方式主要取决于项目需求和代码风格偏好。希望本文对你在生成带毫秒的时间戳方面有所帮助!

坚韧与成长的2024年

作者 沈唁
2025年1月1日 19:20

2024 年,对于我而言,是一段充满挑战与成长的旅程。

从工作中的挫折到家庭的变故,这一年让我更加体会到坚韧的意义,也在风雨中积累了成长的力量。

虽然充满波折,但我仍然选择在逆境中寻找方向,努力让自己变得更加强大。

挑战:逆境的洗礼

在 2024 年中生活与工作的双重挑战交织而来,让我猝不及防。

工作的波折与解脱

其实在去年的年终总结中也提到了,自己已经内耗很久了,而且大部分同事其实也是,职业道路面临着严峻考验,加上大环境不好,工作和家庭多方面带来的压力并不小。

内耗了大概有快一年吧,今年最终也是爆发了:我和一位关系较好的同事一起离职了。

主要原因也是多方面,一是内耗这么久了,不想耗了,世界这么大我想去看看;二是自己的直性格和新同事的背刺;三是公司内部种种事件的发生,使得原本热爱的工作逐渐成为负担。

内耗严重,消耗了我的热情与精力。经过一段时间的挣扎,我决定离开这个让我不再快乐的环境。

离职后的日子可能并不轻松,但也让我重新掌握了生活的主动权,离职后也有听到有几位关系好的同事也先后离开了公司,活成了吃瓜群众...

生活的波澜与坚守

因为房租还未到期,自己也选择了继续在青岛待上一段时间,跟关系好的同事们陆续告别,吃饭喝酒,侃侃而谈,大家也在聊着各自的看法和感受。

离职后,尝试让自己暂时放下压力,为未来的职业方向重新规划,也有疑问为什么不等到过年后再离职?答案还是上面提到的不想耗了,世界这么大我想去看看。

于是我也答应了跟我同一天离职的同事,帮她送兔子去北京,顺便溜达散心,从北京回来之后再起程回老家。

在北京我俩也是喝酒畅聊,还去了一趟雍和宫,在上班和上进之间选择了上香。时间仓促,第三天早上我便出发返程了,不料半路下雨,在出服务区时过了一个小水坑,结果车出现了问题(后来检查得知是电子转向机由于进水坏了),没办法只能一路坚持从高速开回了住处,找了地方维修车辆,避免耽误回老家。

由于转向机突然损坏,不得不承担一笔不小的维修费用,这让本不富裕的家庭雪上加霜,虽然看似是一次小意外,却让我深刻意识到,人生的计划往往会被突如其来的问题打断,我们能做的,是从容面对。

青岛的短暂停留给了我片刻喘息,与老同事的聚会让我感受到人情的温暖。然而,在房租到期后回到老家,奶奶的去世又一次让我面对人生的无常,同时也第一次在医院过夜。

这段时间,我更多地陪伴家人,感悟到亲情的重要性,也开始学会放慢脚步。

就在我努力整理心情,接受上海新工作的同时,命运再次掀起波澜:父亲突然离世,很急很急,我只能在视频中去医院之前见了最后一眼,没想到隔了不到几个小时就得到了人已经走了的消息。

这一噩耗让我深陷悲痛,当晚就请假第二天乘坐最早的飞机回家,奶奶去世时还有父亲在前面撑着,而父亲突然走了,让我手足无措,但我知道我不能垮掉,还有更多的事情在等着我处理。

这个家的生活还要继续,处理完父亲的身后事,直到三七结束,我也重返工作岗位。这段经历不仅考验了我的承受能力,也让我更加清楚地理解了责任的真正含义。

坚韧:在风雨中积累力量

生活和工作的波折让我意识到,坚韧不仅仅是一种态度,更是一种能力。

面对工作中的失落,我学会了取舍,明白了有时候“止损”是对自己最好的成全;面对生活中的离别,我选择直面悲伤,在亲情的回忆中汲取前行的力量。

这一年,尽管前路坎坷,但我始终没有停下脚步。

在适应新的工作节奏中,我不断调整状态,力求用最好的表现迎接挑战。生活的种种考验让我学会了平衡,懂得了如何在压力中找到一丝安慰,让自己变得更坚强、更从容。

成长:收获与前行

2024 年的经历让我深刻体会到,成长往往藏在不经意的每一个瞬间。

离职后的三个月是我重新审视自己的机会,除了本质工作以外,开源事业也是我发自内心热爱的方向。

Docsify 成为 Gitee 成为 GVP 的那一刻,我感受到努力被认可的喜悦,也让我更加坚定地投入到技术与开源社区中。

此外,成为 Apache Answer 的 PPMC Member 也是今年的一大收获。

这份荣誉不仅是一种认可,更是一份责任,促使我不断提升自己,为开源社区贡献更多价值。

无论是在工作还是生活中,我都在这些起起伏伏中找到了成长的契机。

从经历挫折到收获信心,这一年的每一步都让我离更好的自己更近了一些。

展望:迈向崭新的明天

回顾 2024 年,我经历了从失望到解脱,从低谷到重生的全过程。

这些经历让我更加明白,生活并不会因为我们的疲惫而停下脚步,而我们能做的就是在每一次风雨中找到支撑自己的力量。

展望未来,我希望自己能更加沉着应对工作中的挑战,也会投入更多精力到开源社区中,用技术赋能更多人,在社区活动中认识更多人;在生活中,我会更加珍惜与家人、朋友的每一刻,用心感受生活带来的温暖与感动。

这一年让我更加坚信,无论面对什么,只要心中有光,终能走出阴霾。

2025 年,希望是一个充满希望和新机遇的年份,我期待着更多的可能性,也期待着与成长同行,继续前行。

如何使用 Nginx 配置自定义日志并记录用户信息

作者 沈唁
2024年12月25日 10:40

在 Web 开发中,日志记录和 HTTP 头部信息的传递非常重要。

Nginx 和 PHP 作为常见的 Web 服务器和处理引擎,结合使用时可以提供强大的日志记录功能和灵活的头部管理。

本文将介绍如何通过 Nginx 配置自定义日志格式、隐藏特定的 HTTP 头信息,并在 PHP 端输出特殊的 Header 信息,以便在 Nginx 日志中记录详细的用户信息。

1. 新增自定义日志格式

通过修改 Nginx 配置,可以定义一个新的日志格式,用于记录详细的用户信息。这个配置将帮助记录 PHP 动态生成的用户信息(如用户 ID、账号等)。

配置步骤

在 Nginx 配置文件中,定义自定义的日志格式,并在访问日志中加入 X-User-Info 头部:

http {
    # 定义自定义日志格式
    log_format custom_log '$remote_addr - $remote_user [$time_local] "$request" '
                           '$status $body_bytes_sent "$http_referer" '
                           '"$http_user_agent" "$http_x_forwarded_for" '
                           '$request_time $upstream_response_time '
                           '$http_x_user_info';  # 记录 X-User-Info 头部信息

    # 使用自定义格式记录日志
    access_log /var/log/nginx/custom_access.log custom_log;
}

在这个配置中:

  • log_format custom_log 定义了日志格式,其中 $http_x_user_info 用来记录通过 HTTP 头传递的用户信息。
  • access_log 指令将日志输出到指定的文件 /var/log/nginx/custom_access.log,并使用定义的 custom_log 格式。

2. 隐藏特定的 HTTP 头信息

在某些情况下,可能不希望某些敏感的 HTTP 头暴露给客户端。Nginx 提供了 proxy_hide_header 指令来隐藏这些头部信息。

此功能特别有用,当需要阻止某些信息(如用户的详细信息)暴露给客户端时。

配置步骤

假设希望隐藏 X-User-Info 头部信息,可以在 location 配置中使用 proxy_hide_header 指令:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend_server;

        # 隐藏 X-User-Info 头部信息
        proxy_hide_header X-User-Info;
    }
}

此配置确保即使 X-User-Info 头部信息在请求中被发送到 Nginx,Nginx 也不会将该头部返回给客户端,从而保护敏感信息。

3. 在 PHP 端输出特殊的 HTTP 头

在 PHP 中,可以动态生成和输出特定的 HTTP 头部信息,并将其传递给 Nginx。比如,在响应头中输出用户信息(如用户 ID、账号等),然后在 Nginx 日志中记录这些信息。

PHP 代码示例

在 PHP 中,可以使用 header() 函数来输出自定义的 HTTP 头。

以下是一个示例,展示如何在 PHP 中生成并发送 X-User-Info 头部信息:

<?php
// 启动会话并获取用户信息
session_start();
$user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : 'guest';

// 构造用户信息字符串
$user_info = "user_id={$user_id};";

// 设置 HTTP 头部
header("X-User-Info: {$user_info}");

// 其他 PHP 逻辑...
?>

在这段 PHP 代码中:

  • 通过 session_start() 获取当前用户的 ID(假设它存储在会话中)。如果用户未登录,则默认为 'guest'
  • 获取了用户的 IP 地址、浏览器信息以及当前时间。
  • 使用 header() 函数,将这些信息作为 X-User-Info 头部发送到 Nginx。

4. 结合使用自定义日志格式和隐藏 HTTP 头

在 Nginx 中配置了自定义日志格式,并在 PHP 中输出了 X-User-Info 头部信息。接下来,可以将这两部分结合使用,确保详细的用户信息能够记录到日志中,同时确保这些信息不会暴露给客户端。

完整配置

http {
    # 定义自定义日志格式
    log_format custom_log '$remote_addr - $remote_user [$time_local] "$request" '
                           '$status $body_bytes_sent "$http_referer" '
                           '"$http_user_agent" "$http_x_forwarded_for" '
                           '$request_time $upstream_response_time '
                           '$http_x_user_info';  # 记录 X-User-Info 头部信息

    # 使用自定义格式记录日志
    access_log /var/log/nginx/custom_access.log custom_log;

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://backend_server;

            # 隐藏 X-User-Info 头部信息
            proxy_hide_header X-User-Info;
        }
    }
}

在这个完整配置中:

  • 定义了一个自定义的日志格式 custom_log,其中 $http_x_user_info 用来记录通过 PHP 生成的 X-User-Info 头部。
  • location 块中,使用 proxy_hide_header 隐藏了 X-User-Info 头部,确保敏感信息不会被返回给客户端。

5. 结论

通过结合 Nginx 和 PHP,可以实现以下目标:

  • 详细记录用户信息:通过 X-User-Info 头部将用户的 ID、账号等记录到 Nginx 日志中,便于后续的行为分析和问题排查。
  • 隐藏敏感信息:使用 proxy_hide_header 隐藏特定的 HTTP 头,确保敏感信息不被暴露给客户端。
  • 灵活控制日志内容:通过动态生成 HTTP 头部信息,可以根据需要灵活地定制日志内容,提供更多关于用户行为和请求的详细数据。

这种配置适用于需要详细日志记录的高流量站点,尤其是在需要跟踪用户活动、分析访问模式或保护敏感数据时。

通过合理的日志管理和头部处理,可以在保障系统性能和安全性的同时,提供更丰富的数据支持。

Nginx 配置证书时报错:SSL_CTX_use_PrivateKey failed

作者 沈唁
2024年12月13日 10:20

在配置 Nginx SSL 证书时,如果遇到以下错误:

nginx: [emerg] SSL_CTX_use_PrivateKey failed (SSL: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch)

这应该是 SSL 证书和私钥之间的公私钥对不匹配。这篇文章就展开地分析这个问题的原因和解决方案。

错误分析

该错误表示 Nginx 在加载私钥时,检测到证书和私钥之间不匹配。常见原因如下:

  1. 使用了错误的证书文件,如其他域名的证书。
  2. 在配置时不小心选择了错误的私钥文件。
  3. 证书或私钥文件被损坏。
  4. CSR 文件和私钥不匹配。

解决方案

  1. 检查证书和私钥是否匹配

通过 OpenSSL 命令检查证书和私钥的 MD5 值是否匹配:

查看私钥的 MD5 值:

openssl rsa -noout -modulus -in your_private.key | openssl md5

查看证书的 MD5 值:

openssl x509 -noout -modulus -in your_certificate.crt | openssl md5

CSR 的 MD5 值(如果有):

openssl req -noout -modulus -in your_request.csr | openssl md5

如果 MD5 值不匹配,请确保使用了正确的私钥和证书。

  1. 检查私钥是否有效

使用下列命令检查私钥是否有效:

openssl rsa -check -in your_private.key

如果私钥损坏,需重新生成私钥和证书。

  1. 检查证书链是否完整

为了确保证书链完整,可将证书和中间证书合并:

cat your_certificate.crt intermediate_certificate.crt > full_chain.crt

然后在 Nginx 配置文件中指向此合并证书:

ssl_certificate /path/to/full_chain.crt;
ssl_certificate_key /path/to/private.key;
  1. 重新生成 CSR 和私钥

如果不能确保证书和私钥是否一致,可考虑重新生成 CSR 和私钥:

生成新的私钥:

openssl genrsa -out new_private.key 2048

生成 CSR:

openssl req -new -key new_private.key -out new_request.csr

然后将 CSR 提交给 CA (证书授权机构),以获取新证书。

  1. 检查 Nginx 配置

确保配置文件中指向正确的证书和私钥路径,如在 Nginx 中的配置示例:

ssl_certificate /etc/nginx/ssl/full_chain.crt;
ssl_certificate_key /etc/nginx/ssl/private.key;

配置保存后,重启 Nginx:

sudo nginx -t
sudo systemctl reload nginx

这个错误通常是由于证书和私钥不匹配或配置错误导致。通过检查证书和私钥的 MD5 值、检查配置文件和确保证书链完整,可以解决这个问题。

PHP 8.4 发布!

作者 沈唁
2024年11月20日 14:05

PHP 8.4 是一个重要的版本,它带来了主要的新功能、对构建依赖项和底层库的几项更新,以及相当多的弃用,以消除旧版 PHP 中一些遗留的不良行为和功能。

它包含许多新功能,例如属性钩子、不对称可见性、更新的 DOM API、性能改进、错误修复和常规清理等。

属性钩子和不对称可见性

PHP 8.4 中最重要的功能之一是新增了 属性钩子 和为 get 和 set 操作分别声明可见性 的功能。

属性钩子

属性钩子允许在访问或设置属性时执行“钩子”逻辑:

class Locale
{
    public string $languageCode;

    public string $countryCode
    {
        set (string $countryCode) {
            $this->countryCode = strtoupper($countryCode);
        }
    }

    public string $combinedCode
    {
        get => \sprintf("%s_%s", $this->languageCode, $this->countryCode);
        set (string $value) {
            [$this->countryCode, $this->languageCode] = explode('_', $value, 2);
        }
    }

    public function __construct(string $languageCode, string $countryCode)
    {
        $this->languageCode = $languageCode;
        $this->countryCode = $countryCode;
    }
}

$brazilianPortuguese = new Locale('pt', 'br');
var_dump($brazilianPortuguese->countryCode); // BR
var_dump($brazilianPortuguese->combinedCode); // pt_BR

非对称可见性

非对称可见性允许为 getset 操作定义不同的作用域:

class PhpVersion
{
    public private(set) string $version = '8.4';
}

$phpVersion = new PhpVersion();
var_dump($phpVersion->version); // string(3) "8.4"
$phpVersion->version = 'PHP 8.3'; // Visibility error

改进的 HTML5 解析器

PHP 8.4 的 DOM 扩展也进行了一次重大功能更新。之前,DOM 扩展仅提供 libxml2 来解析 HTML,而 libxml2 并未跟上 HTML5 的进展。现在,DOM 扩展新增了 Dom\HTMLDocumentDom\XMLDocument 类,其中前者支持 HTML5 合规的解析。

在这一领域有很多新的改进,包括不仅仅是HTML5 解析支持,还包括DOM 规范合规性,以及若干小的增强,比如添加了对 CSS 选择器的支持。

BCMath 扩展新增 Number 类和新函数

PHP 8.4 中的 BCMath 扩展现在支持运算符重载,提供了新的类支持!

use BcMath\Number;

$num1 = new Number('22');
$num2 = new Number('7');
$num3 = new Number('100');

$result = ($num1 / $num2) + $num1 - $num2;
echo $result; // 18.1428571428

现在,不再需要使用 BCMath 函数如 bcaddbcsubbcdiv 等,可以直接使用标准运算符(+-/ 等)。

新的 BcMath\Number 类支持运算符重载,虽然用户自定义 PHP 类尚不支持此功能,但 BCMath 扩展已经实现了这一点,因此可以像使用常规数字一样使用这些对象。

BcMath\Number 类实现了 Stringable 接口,因此这些对象可以在需要字符串的地方使用(比如上例中的 echo 调用)。此外,该类实现了所有的 bc* 函数。例如,还可以调用 $num->add($num2)$num->add('5'),它会返回一个新的 BcMath\Number 对象,而不会修改原始对象,这使得这些对象是不可变的。

新增函数

PDO 驱动特定子类

PDO 驱动特定子类 RFC 已在 PHP 8.4 中实现。它曾在 PHP 8.3 中进行投票,但由于 8.3 的功能冻结,未能实现。

PHP 8.4 现在新增了 Pdo\MysqlPdo\PgsqlPdo\SqlitePdo\DbLibPdo\Firebird 类,这些类扩展了 PDO 类。现在可以在驱动特定子类中使用驱动特定的方法、属性和常量。驱动特定的子类还允许通过只接受/返回驱动特定的子类来使 API 更加明确和限制。

解耦扩展

IMAP、Pspell、OCI8 和 PDO_OCI8 扩展已从 PHP 核心中解耦,现在作为 PECL 扩展提供,用户可以通过 PIE 轻松安装这些扩展。

PHP 发布周期更新

2024 年 4 月,PHP 投票并通过了一项 RFC 提案,以更新 PHP 的发布周期政策。

此前,PHP 核心团队提供两年活跃支持,然后是一年的安全修复支持。

而现在从 PHP 8.1(2021 年 11 月发布)起,所有 PHP 版本将获得 两年安全修复支持,活跃支持期保持两年不变。

此外,活跃支持和生命周期终止(EOL)的日期将调整为日历年的 12 月 31 日,使这些日期更加可预测。

以下是当前 PHP 版本的更新支持和生命周期终止日期,变更部分用加粗标注:

PHP 版本 发布时间 活跃支持至 生命周期终止日期
PHP 8.1 2021-11-25 2023-11-25 2025-12-31
PHP 8.2 2022-12-08 2024-12-31 2026-12-31
PHP 8.3 2023-11-23 2025-12-31 2027-12-31
PHP 8.4 2024-11-21 2026-12-31 2028-12-31
PHP 8.5 2025-11 2027-12-31 2029-12-31

更多信息可以查看 PHP 版本发布页面

使用 acme.sh 自动申请并更新泛域名证书

作者 沈唁
2024年11月1日 15:49

在互联网快速发展的今天,网站安全愈发重要,而 SSL/TLS 证书成为了保护网站数据的基础。对于管理多个子域的用户来说,泛域名证书(Wildcard Certificate)是一种高效便捷的选择。

各家云厂商也开始割起来了域名证书的韭菜,泛域名证书收费昂贵,单域名证书免费但将有效期都调整为了三个月。

我之前也是宁愿花费一些小钱来解决这个问题,一年更新一次也不是不行,现在调制为三个月后就需要频繁更新了,正好之前的服务器过期,迁移之后顺便研究起来了证书部署的问题,于是有了这篇文章。

什么是 acme.sh?

ACME(自动证书管理环境)是一个互联网工程任务组维护的协议,它允许自动化 Web 服务器证书的部署,acme.sh 是支持 ACME 协议流行的客户端之一,可以通过其实现 SSL 证书的自动申请、续期等。

安装

在使用 acme.sh 前,需要先在服务器上安装它。acme.sh 的安装过程简单,只需执行几条命令即可。

curl https://get.acme.sh | sh -s email=my@example.com

my@example.com更换为自己的邮箱后执行,安装主要把 acme.sh 安装到你的 home 目录下,并且添加了一个 crontab 用于自动检测所有的证书,如果快过期了,需要更新,则会自动更新证书。

注册 ZeroSSL 账号

acme.sh 目前默认使用ZeroSSL.com,ZeroSSL 没有速率限制,可以颁发无限制、有效期为 90 天的 TLS/SSL 证书。

  1. 先去创建一个 ZeroSSL 账号

  2. 访问 https://app.zerossl.com/developer 生成你的 EAB 凭证,点击EAB Credentials for ACME Clients

  3. 执行命令,注册你的 EAB 凭证,将 xxxxxxxxx 替换为第二步获取到的内容

acme.sh --register-account --server zerossl \
        --eab-kid xxxxxxxxx  \
        --eab-hmac-key xxxxxxxxx

申请签发泛域名证书

我选择直接使用 DNS API 模式进行签发,还支持其他模式可以自己研究,我的域名都在腾讯云,所以通过 DNSPod 可以管理。

登录 DNSPod 控制台获取到 DNSPod Token:https://console.dnspod.cn/account/token/token

获取后将值存入到系统环境变量中:

vim ~/.bashrc

#写入
export DP_Id=
export DP_Key=

#保存后执行
source ~/.bashrc

保存后就可以开始申请了:

acme.sh --issue --dns dns_dp -d example.com -d *.example.com

运行后,acme.sh 将自动为example.com域名申请泛域名证书,如果没有出错,会将证书文件保存在~/.acme.sh/example.com/目录下,并且会自动为该域名创建配置,以便自动执行续期任务。

安装证书

acme.sh 不建议直接使用~/.acme.sh/目录下的证书文件,而是通过 acme.sh 提供的命令将证书安装到指定位置,以确保证书的正确使用和续期。

acme.sh --install-cert -d example.com \
        --key-file       /path/to/keyfile/in/nginx/key.pem  \
        --fullchain-file /path/to/fullchain/nginx/cert.pem \
        --reloadcmd     "service nginx force-reload"

将路径设置为自己的路径即可,在reloadcmd中执行重启 Nginx 的命令,到这一步本机的证书已经是没问题了,但是如果使用了 CDN 等服务的话,仍然需要更新对应服务的证书配置。

acme.sh 提供了阿里云 CDN 的脚本,没有腾讯云的,所以我自己顺手写了一个小工具。

Cert Manager

将本地通过 acme.sh 生成的证书上传到对应云服务的证书服务中,以便于在云服务中使用证书。

composer create-project sy-records/cert-manager
cd cert-manager
chmod +x tx-cert-manger

调用了腾讯云的获取证书列表和一键更新旧证书资源两个接口,可以直接更新对应的资源使用的证书,支持 clb、cdn、waf、live、ddos、teo、apigateway、vod、tke、tcb、tse、cos。

使用 composer 安装后,获取腾讯云的 SecretId 和 SecretKey,也放到环境变量中:

vim ~/.bashrc

#写入
export TX_SECRET_ID=
export TX_SECRET_KEY=

#保存后执行
source ~/.bashrc

config.example.php复制一份到config.php,并根据实际情况修改配置:

return [
    'example.com' => [
        'resourceTypes' => ['cdn'],
        'fullchain' => '/path/to/fullchain/nginx/cert.pem',
        'privkey' => '/path/to/keyfile/in/nginx/key.pem',
    ]
];

这样就可以使用tx-cert-manger命令来更新腾讯云的服务了。

# 更新所有配置文件中的证书
./tx-cert-manger

# 更新指定域名的证书
./tx-cert-manger example.com

配合 reloadcmd,就可以实现续期后自动更新证书文件到腾讯云,并重启本地的 Nginx 服务了。

acme.sh --install-cert -d example.com \
        --key-file       /path/to/keyfile/in/nginx/key.pem  \
        --fullchain-file /path/to/fullchain/nginx/cert.pem \
        --reloadcmd     "service nginx force-reload & /path/to/cert-manager/tx-cert-manger example.com"

acme.sh 是一个功能强大、配置灵活的 ACME 客户端,特别适合需要管理多个域名和子域名的用户。

通过 acme.sh,不仅可以快速申请泛域名证书,还能轻松实现自动更新,降低了 SSL 证书管理的复杂度。

如果你的站点涉及多个子域名,那么使用 acme.sh 申请泛域名证书是一个不错的选择。

PIE:PHP 扩展管理的未来,替代 PECL 的新选择

作者 沈唁
2024年10月10日 17:47

PHP 扩展开发和管理的流程在多年来通过 PECL(PHP 扩展社区库)得到了显著的优化。为了继续提升开发者体验,PIE 的出现为此带来了新的可能性。

在 23 年 12 月份,PHP 邮件列表收到了由 Derick 发送的一封新邮件:New "PECL"

在 PHP 基金会内部,已经讨论了一段时间如何处理 PECL 及其网站,PECL 的现状是代码老旧,难以维护,数据库中充满了乱码。查看邮件原文

什么是 PECL

PECL(PHP Extension Community Library)是一个专门用于托管和管理 PHP 扩展的社区库。PHP 扩展是一种用来增强 PHP 核心功能的模块,开发者可以通过这些扩展来实现额外的功能,比如数据库驱动、图像处理、缓存系统、加密工具等。

PECL 提供了一个集中化的目录,开发者可以浏览、下载和安装各种扩展。它还为开发者提供了扩展的开发和托管设施,使得社区能够参与扩展的维护和改进。许多 PHP 扩展的源代码都托管在 PECL 上,开发者可以在其基础上进行二次开发。

PECL 的打包和分发系统与 PEAR(PHP Extension and Application Repository)共享。PEAR 主要面向可重用的 PHP 库和组件,而 PECL 则专注于底层扩展。两者都使用相同的包管理工具来简化安装和管理过程。PECL 扩展可以通过 pecl 命令行工具直接安装,这极大地方便了开发者的使用。

通过 PECL,PHP 开发者可以快速找到并集成高质量的扩展,提升 PHP 应用的性能和功能。

前往 PECL 官网了解更多信息。

对于扩展开发者,发布版本的时候,需要登录 PECL 网站,并将打包好的 zip 包上传到 PECL,而且只能 lead角色上传,developer角色是无法上传的。

对于用户,PECL 也是一种过时的安装方式,而通过 Composer 处理用户空间代码要容易得多

所以才有了本篇文章要介绍的 PIE,用于替代 PECL

什么是 PIE

PIE(PHP Installer for Extensions) 是一个全新的工具,专为 PHP 开发者设计,目的是提供一个更轻量、更现代化的 PHP 扩展管理方式。它采用模块化设计,能够方便地添加、管理和部署 PHP 扩展。相比 PECL,PIE 的安装和使用过程更加流畅,具有更好的用户体验。

使用 PIE 的优势

与传统的 PECL 相比,PIE 更注重简洁和现代化的设计。其轻量级架构不仅减少了开发者的维护成本,还使得扩展的安装速度和稳定性得到了提升。

扩展开发者只需要在项目中增加composer.json,声明一些安装选项等,并提交到 Packagist 即可。

下载安装 PIE

需要 PHP 8.1 或更新版本才能运行 PIE,但 PIE 可以为任何已安装的 PHP 版本安装扩展。

wget https://github.com/php/pie/releases/download/0.1.0/pie.phar

sudo chmod +x pie.phar
sudo mv pie.phar /usr/local/bin/pie

两种命令,二选一即可。

sudo curl -L --output /usr/local/bin/pie https://github.com/php/pie/releases/download/0.1.0/pie.phar && sudo chmod +x /usr/local/bin/pie

目前发布了 0.1.0 的预览版,所以本文使用 0.1.0 的下载地址,等正式版本发布后,可以使用https://github.com/php/pie/releases/latest/download/pie.phar此链接下载。

下载、构建或安装扩展

PIE 可以:

  • 只下载一个扩展,使用pie download
  • 下载并构建扩展,使用pie build
  • 最常见的是:下载、构建和安装扩展,使用pie install

使用 PIE 安装扩展时,必须使用其 Composer 软件包名称,可以在 https://packagist.org/extensions 上找到与 PIE 兼容的软件包列表。

packageist 扩展列表

知道扩展名称后,就可以使用下面的命令进行安装:

pie install <vendor>/<package>

# 举个例子
pie install apcu/apcu

在为不同的 PHP 版本安装扩展时,可以通过指定php-config来进行:

pie install --with-php-config=/usr/bin/php-config7.4 apcu/apcu

版本约束和 composer 是一样的,了解更多可以查看:Composer 进阶使用之版本约束表达式的使用

pie install <vendor>/<package>:<version-constraint>

编译扩展时,有些扩展需要向 ./configure 命令传递额外的参数。

这些参数通常用于启用或禁用某些功能,或提供未自动检测到的库的路径。

为了确定扩展可用的配置选项,可以使用pie info <vendor>/<package>将返回列表,例如:

$ pie info apcu/apcu
You are running PHP 8.2.10
Target PHP installation: 8.2.10 nts, on Linux/OSX/etc x86_64 (from /usr/local/Cellar/php/8.2.10/bin/php)
Found package: apcu/apcu:v5.1.24 which provides ext-apcu
Extension name: apcu
Extension type: php-ext (PhpModule)
Composer package name: apcu/apcu
Version: v5.1.24
Download URL: https://api.github.com/repos/krakjoe/apcu/zipball/62e67989a35247263c370b5ecebb4e69b73b0709
Configure options:
    --disable-apcu-mmap  (Disable mmap, falls back on shm)
    --disable-apcu-rwlocks  (Disable rwlocks in APCu)
    --disable-valgrind-checks  (Disable Valgrind-based memory checks)
    --enable-apcu  (Enable APCu support)
    --enable-apcu-clear-signal  (Enable SIGUSR1 clearing handler)
    --enable-apcu-debug  (Enable APCu debugging)
    --enable-apcu-spinlocks  (Use spinlocks before flocks)
    --enable-coverage  (Include code coverage symbols (DEVELOPERS ONLY!!))

需要注意的是,目前,PIE 不会配置 INI 文件,但很快会进行改进,需要手动给对应的php.ini中添加extension=

这些内容对于普通用户来说已经够用了,而对于扩展开发者来说,需要给项目中添加composer.json来让 PIE 能够索引和下载,具体可以查看面向扩展维护者的 PIE

并且我也给 Swoole 添加了 PIE 支持:composer.json,但对于像 Swow 这种已经自己实现了安装脚本,并且也是使用 composer 进行管理的,应该并不会使用 PIE,或者可能存在冲突,所以还是先观望吧

pie install swow/swow

composer require swow/swow
./vendor/bin/swow-builder --install

结语

PIE 0.1.0 的发布标志着 PHP 扩展管理进入了一个新阶段。它为开发者提供了更加轻松的工具集,让扩展的管理、安装和部署更加高效。PIE 将逐步替代 PECL,成为 PHP 社区中的主流工具。

通过 PIE,PHP 开发者能够更加专注于代码本身,而不再需要为复杂的扩展管理流程而烦恼。

解决GoLand会自动删除import包的问题

作者 沈唁
2024年9月10日 11:16

最近在给 Apache Answer 处理安装时的多语言切换时,遇到了保存时 import 的声明会被自动删掉的问题。

拥有 @apache.org 邮箱以后会有 JetBrains 提供的 All Products Pack License,之前只用到了 PhpStorm,现在将 GoLand 也下载下来了。

在编码完成后需要进行构建,构建的时候提示:undefined: i18n,我还在纳闷我引入了呀,翻上去一看确实没有,被删掉了,撤回保存后还会被删除。

vim 编辑后可以正常启动服务,那就是这个 GoLand 编辑器的问题了。

打开Settings设置,输入optimize,将下面两个选项取消勾选:

  • Optimize imports on the fly

  • Optimize imports

这样恢复被删除的导入后,就不会再被自动删除了。

香港汇丰和中国银行开户经验分享

作者 沈唁
2024年9月6日 09:57

趁着离职后休假有空,想着去趟香港,开通一下香港的银行卡。

在 X 上看到了一个代理,了解了一下银行、费用以及需要的资料。

香港银行的个人户可以开渣打、汇丰、中银、华侨、花旗、恒生、星展、永隆和集友,渣打、汇丰、华侨和恒生可以当天拿卡,其他的都是邮寄卡片,费用大概到 1600-2300 不等。

办理港澳通行证

进香港需要港澳通行证,去年办理护照的时候没有办理港澳通行证,需要再去一趟政务服务中心办理。(此处提醒,如果没有办理护照和港澳通行证的同学,这两个证件可以一起办理)

办理港澳通行证的时间线:

  • 8.12 去办理,由于是外地户口,只能办理团队签证,且需要预计 20 天才能发证,我选择了快递。
  • 8.20 在移民局的小程序上查看显示正在制证中
  • 8.21 收到中国邮政的短信通知,证照邮件已寄出(在山东青岛申请的,从济南寄出)
  • 8.22 拿到手了证件

办理港澳通行证十天到手。

预约银行

可以花钱找中介,但是也是需要提前预约,不过中介会帮你提前预约所谓的客户经理。

在小红书上也搜索了一下,也发现中介居多,而且好多人都是直接 walk in 开户,看了一圈发现汇丰和中银合适,决定就开这两家。

为了不花冤枉钱,毕竟如果开两家的话,找中介也需要 4k 左右的费用了,这个费用都够机票酒店的钱了,那就自己动手。

提前下载好这三个 APP:HSBC HKBOCHK 中银香港BoC Pay

汇丰银行

汇丰银行可以直接在网上预约,前往汇丰网上取票,可以预约当天或未来最多 30 个营业日的分行服务以享优先服务。

进去以后填写信息和选时间就可以,我选择的是新界-元朗分行,地址:新界元朗青山公路 150-160 号元朗汇丰大厦 2 楼。

预约成功后有一个二维码,将它截图保存,现场会扫这个码取号。

这个比较轻松,现场开卡体验也很好,我 20 分钟就完事了!

中国银行(香港)

中国银行的预约比较特殊,每天的 0 点开始放号,需要提前 7 天预约,我定了闹钟,结果睡过了,睡醒一看只能预约到下午 14:50 的号了。

关注微信公众号:中银香港微服务,从菜单里选择我要预约,预约开立香港账户,选择中国居民身份证和一般账户即可,然后也是填信息和选分行,同样选择的元朗,元朗青山道分行

预约成功后会收到邮件,可以截图或者现场后看邮件原文,就知道你要干什么了。

元朗这两家银行是隔壁,隔了一条马路。

进入香港

我果然选了一个好日子,和台风摩羯撞上了,不过幸好没有影响我办卡,到香港的时候是晴空万里,热得要死。

我选择的是福田口岸进关,坐地铁直达,跟着人群一起走就可以,走访客通道,刷两次港澳通行证,拿到过关凭证(一个小白条),第二次刷港澳通行证的时候机器下面会出来,记得拿上。

过完关后也是跟着人群走,注意去元朗可以坐公交 B1 直达,不要跟着人群去坐了港铁,看到下图直走就行:

落马洲总站

下去跟着人群排队搭乘 B1 公交,可以扫支付宝或者微信的乘车码,我没有办理八达通,单趟的费用是 12 块左右。

在元朗大棠路下车就行,走一会就可以看到汇丰,面朝汇丰银行元朗分行,右手边过了马路就是中国银行元朗青山道分行。

汇丰开卡

汇丰我预约的是早上 9 点,到达的时候是 8:30,楼下已经有人在排队了,这里注意不要在楼下跟着他们排队,又热还没用,坐旁边的电梯,上到二楼,在这里排队或者进入即可。

汇丰元朗大厦

汇丰元朗大厦2楼

前台有人引导,拿出预约的二维码取号就可以,等叫号,会有人出来接你,都是单独的办公间。

办卡阶段,是一个小哥哥接待的我,用到了身份证和港澳通行证,别的都没用到,而且只问了办卡用途、工作薪资的信息。

办卡用途就是投资港股

问有没有相关经验,我回答1-2 年

问打算投资多少,我回答先看看

然后就开始给我办理了,全程都在手机上操作的,打开HSBC HK的 APP 进行,基本都是他操作的,期间问我带没带地址信息,我说就是身份证上的地址,小哥自己打了拼音,实际我在备忘录里准备了公司地址、身份证地址还有一个同事的地址,中文、英文和拼音的,也没用上。

中途让输入一下公司名称,给我自己输入了,使用拼音即可。

不到 20 分钟就办理完了,下楼存钱激活。

期间有意思的是,我问蓝狮子可以不可以申请,他说可以,你在 APP 上点就行,我问是不是要改中文地址,他说你们是不是攻略看多了改不了,你要申请就在 APP 上申请,如果收不到的话可以联系客服让给你寄 DHL,并帮我操作了申请蓝狮子。

我回到酒店后就自己修改了中文通讯地址,下一篇说这个。

汇丰到信用卡已经不给大陆内地申请了。

中银开卡

因为我预约的是下午,汇丰又早早就结束了,我去以后也是上二楼,给接待说了一下,帮我取了一个号,8 号,看现在还是 2 号,带我到旁边打开BOCHK 中银香港填信息了,看了身份证、港澳通行证和地址证明(我带了国内的驾照,没有的话可以信用卡账单等),填完以后就等着叫号了。

2 号完事以后到了 3 号,等着 3 号办完就叫 4、5、6、7,我看了半天都没人,于是就到我 8 号了,心想还挺快的,结果是我乐观了。

这个小哥是带实习生的,给我操作一步,他给我普通话沟通完,又用粤语给实习生讲一遍,我等的心里苦啊。

同样的,再次查看了身份证、港澳通行证、过关小白条和地址证明。

问了一些问题:

办卡用途:投资港股

有无投资经验:有 1-2 年,并让拿出了相关资料,我用东方财富 APP 给他看了

工作职位:后端研发工程师

薪资待遇:...

以及公司名称让我写给他了。

取号单我以为无用了,已经装到包里了,他找我要去了,上面的问题结果他都写在了取号单的背面,并写上了办理时间。

办理途中还问了已婚未婚,介绍了投资股票的费用等信息,总体下来耗时一个多小时。

如果不是必须也可以不办中行,或者直接线上申请后邮寄也行。

办完后又让他们其他同事协助绑定了BoC Pay,和更新限额,完事后就让下楼去提款机插卡查询更新卡片信息,再去存款机存钱了。

我提前准备了 2k 的港币,其实都用不上,有 200 就够了,一家银行存 100,至此,成功拿下两张香港银行卡!

了解PHP魔术方法:__toString()、__invoke()和__debugInfo()

作者 沈唁
2024年4月6日 11:19

最近同事在研究内部开发的组件时,发现了一个__debugInfo()的用法,突然问我,我一时也没有想起这是个什么用法,于是重新阅读下 PHP 手册。

插入一下,先说说 PHP 手册的用法,像这种魔术方法,是无法通过手册中的搜索来找到的,需要通过 URL 访问的方式,例如:

__toString()https://www.php.net/__tostring

__debuginfo()https://www.php.net/__debuginfo

或者你记得它应该是 OOP 中包含的内容,则可以通过 OOP 的首页列表(https://www.php.net/oop)来找到Magic Methods的入口。

同样的,类似于:@...use::???:??=<=> 这种都可以通过在官网后拼接的方式查看对应的手册内容。

什么是魔术方法?

魔术方法是一种特殊的方法,当对对象执行某些操作时会覆盖 PHP 的默认操作。

下列方法名被认为是魔术方法: __construct()__destruct()__call()__callStatic()__get()__set()__isset()__unset()__sleep()__wakeup()__serialize()__unserialize()__toString()__invoke()__set_state()__clone()__debugInfo()

PHP 保留所有以 __ 开头的方法名称。 因此,除非覆盖 PHP 的行为,否则不建议使用此类方法名称。除了 __construct()__destruct()__clone() 之外的所有魔术方法都必须声明为 public

本篇文章主要来看看__toString()__invoke()__debugInfo()这三个魔术方法。

__toString()

public __toString(): string

__toString() 方法用于一个类被当成字符串时应怎样回应。

class TestClass
{
    public function __toString()
    {
        return 'Hello World!';
    }
}

echo (new TestClass());

这个会输出Hello World!

__invoke()

__invoke( ...$values): mixed

当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。

class TestClass
{
    public function __invoke($foo, $bar)
    {
        var_dump($foo, $bar);
    }
}
$test = new TestClass();
$test('foo', 'bar');

这个就会打印出foobar

__debugInfo()

__debugInfo(): array

当通过 var_dump() 转储对象,获取应该要显示的属性的时候,该函数就会被调用。

class TestClass
{
    public $param1;
    private $param2;
    protected $param3;

    public function __construct($param1, $param2, $param3)
    {
        $this->param1 = $param1;
        $this->param2 = $param2;
        $this->param3 = $param3;
    }

    public function __debugInfo()
    {
        return [
            'param1' => $this->param1,
            'param2' => $this->param2,
            'param3' => $this->param3,
        ];
    }
}

$test = new TestClass('foo', 'bar', 'baz');
var_dump($test);

如果对象中没有定义该方法,那么将会展示所有的公有、受保护和私有的属性,且会显示对应的属性类型。

object(TestClass)#1 (3) {
  ["param1"]=>
  string(3) "foo"
  ["param2":"TestClass":private]=>
  string(3) "bar"
  ["param3":protected]=>
  string(3) "baz"
}

object(TestClass)#1 (3) {
  ["param1"]=>
  string(3) "foo"
  ["param2"]=>
  string(3) "bar"
  ["param3"]=>
  string(3) "baz"
}

这三个魔术方法,其实都是对于对象的一些操作,通过对 PHP 魔术方法的理解,我们可以更好地掌握对象的行为和调试技术。

深入了解这些方法将有助于提升我们在 PHP 开发中的技能和效率。

本文的示例代码可以通过 https://3v4l.org/NWqMh 查看。

不知道如何总结是好的2023

作者 沈唁
2023年12月29日 14:15

时光匆匆,2023 年也即将结束了,回头看,一地鸡毛。

苦中作乐

1 月 3 日,思否公布了2022 中国开源先锋 33 人之心尖上的开源人物榜单,有幸入选,成为了 2022 年中国开源先锋。

2022年中国开源先锋-鲁飞

前几天也参与了 2023 中国开源先锋 33 人的投票,期待着 2023 年的开源先锋榜单产生。

同天,Hyperf 3.0 版本发布,新时代来临,Hyperf 3.0 带来了很多非常有意思的新能力,其中一些新能力不乏是 PHP 领域里面前所未有的,当然这些新能力也脱离不了其他开源社区的积极发展,包括但不限于 PHPSwooleSwowPHPMicroDTMSeata 等开源社区。

1 月 17 日,腾讯云开发者社区 2022 年度回顾发布,获得了年度最佳作者

腾讯云开发者社区-2022年度最佳作者

腾讯云开发者社区由原来的腾讯云加社区改为现在的名字,自己参与的也越来越少了,不过社群中倒是有许多新面孔加入,估计今年我是没有排名了。

因为没有太多时间去写博客了,遇到比较难的技术问题也越来越少,不适合写博客,以至于 2023 年也就才写了 17 篇博客。

3 月 29 日,我正式加入了 Typecho 组织,4 月 1 日,发布了 Typecho 1.2.0 正式版,这不是一个愚人节玩笑,Typecho 确实回来了。

join-typecho

4 月初,第一次带女朋友回了家。

4 月 19 日,我把注册了好几年的公众号名称,修改为了真名,具体原因可以看以前的博客:正名示意,我将公众号名改成了真名。。也因为个人微信公众号支持认证了,虽然好像没有什么用,只是多了一个认证的标志,增加了一个认证。

欢迎关注我的个人公众号:

微信公众号

5 月 27 日,买了王国之泪,继续救公主,旷野之息还没玩完... 玩了几次之后又开始吃灰了。

8 月 12 日,小明哥哥结婚了,作为“娘家人”,和同事一起去了趟沈阳铁岭,感受一下东北的喜宴和烤肉。

10 月 13 日,人生中第一张罚单,违停。

12 月 12 日,姥爷去世了,走得很突然,请假赶回家见了最后一面,看着很安详,没有痛苦。

刻苦耐劳

在 2022 年年底时,公司成立了新的一个部门,由我带队的:鲁班巴

年初还为此专门写了一篇公众号:展望 2023:直面新挑战,解释了为什么部门名为鲁班巴等信息。

工作还是在稳步推进,该上线的迭代都是正常发布,就是偶尔会有背锅的时候。

到头来看,在浮躁的环境中,更要沉下心来做事情,不要被外界打扰,多提升自己。

唯有“搞钱”才能让一个人通透。

我们一路奋战,不是为了改变世界,而是为了不让世界改变我们。

PHP 中 trim 函数对多字节字符的使用和限制

作者 沈唁
2023年12月9日 18:46

先来判断下这段代码的输出是否一致?

$string = '沈唁 ';
var_dump(trim($string));
var_dump(preg_replace('/^[\s\0]+|[\s\0]+$/u', '', $string));

如果你觉得是一致的,那么就是大错特错了。

在日常工作中,经常需要处理字符串。其中一种常用的情况是,需要删除字符串两端的空白字符,这就是 trim() 函数原本的作用。

但是标准的 trim() 函数不能处理多字节字符。

什么是trim()函数?

#PHP#中, trim() 函数用于删除字符串的开头和结尾的空白字符。默认情况下,这些字符包括:

  • "\0" - NULL
  • "\t" - 制表符
  • "\n" - 换行
  • "\v" - 垂直制表符
  • "\r" - 回车
  • " " - 空格

mbstring 扩展

在很多语言中,每个必要字符都能一对一映射到 8 bit 的值,但也有一些语言需要非常多的字符来书面通讯,以至于它们的编码范围不能仅仅包含在一个字节里。

开发多字节字符编码方案是为了在基于字节的常规编码系统中表达超过 256 个字符。

在使用trimsplitsplice 等等操作多字节编码的字符串的时候,特别需要注意,由于在这种编码方案下,两个或多个连续字节可能只表达了一个字符,所以需要使用专门的函数。 否则,你可能会得到一个以乱码的字符串结尾。

mbstring 提供了针对多字节字符串的函数,能够帮开发者处理 PHP 中的多字节编码。

mbstring 扩展的使用和普通字符串操作函数一致,而且仅仅需要加上mb_前缀即可。

类似于:

  • split => mb_split
  • strlen => mb_strlen
  • substr => mb_substr

以此类推,trim 是不是可以直接改为调用mb_trim

答案是也不是。因为你可能会得到一个错误:

PHP Fatal error:  Uncaught Error: Call to undefined function mb_trim()

mb_trimmb_ltrimmb_rtrim

从 2022 年 8 月份就有人在 php-src 的 issue 进行了反馈,这三个函数也是前不久刚刚通过 RFC,合并到 PHP 内核中新增的。

https://github.com/php/php-src/commit/a80b6d7b99ae885cb450a563a788f57917cef74e

function mb_trim(string $string, string $characters = " \f\n\r\t\v\x00\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}"): string

function mb_ltrim(string $string, string $characters = " \f\n\r\t\v\x00\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", ?string $encoding = null): string

function mb_rtrim(string $string, string $characters = " \f\n\r\t\v\x00\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}", ?string $encoding = null): string

所以虽然 8.3 刚发布,但是 8.3 中确实没有这三个函数,可能需要在 8.3.1 中才能使用了。

不过 PHP 足够灵活,使我们能够根据需要创建自定义的函数,如多字节 mb_trim() 函数。

if (!function_exists('mb_trim')) {
    function mb_trim($string)
    {
        return preg_replace('/^[\s\0]+|[\s\0]+$/u', '', $string);
    }
}

快速添加SSH公钥进行免密登录

作者 沈唁
2023年11月23日 08:06

在需要连接其他人服务器或者其他的场景下,忘记密码或者不方便提供密码的时候可以通过 SSH 免密码进行登录。

生成 SSH 密钥

首先需要一个 SSH 密钥,已经有了的可以跳过。

ssh-keygen -t ed25519 -C "your_email@example.com"

如果你使用的是不支持 Ed25519 算法的旧系统,请使用以下命令:

 ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

默认可以一路回车,生成后会在~/.ssh/目录下生成id_ed25519id_ed25519.pub或者id_rsaid_rsa.pub两个密钥文件。

复制公钥到远程主机

需要将公钥复制到远程主机上,以便进行身份验证。有下面几种命令:

ssh-copy-id username@remote_host

ssh-copy-id 这种方式需要知道服务器的密码,不知道密码时不能使用。

这样的话就只能手动将~/.ssh/id_rsa.pub/~/.ssh/id_ed25519.pub公钥文件内容复制,并添加到服务器账户的 ~/.ssh/authorized_keys文件里。

当需要告诉不同的用户公钥文件,每次分发也是一个问题,但是 GitHub 可以直接显示用户配置的 SSH 和 GPG Keys。

比如我的 GitHub 账号是:sy-records

可以通过 https://github.com/sy-records.keys 获取到我的 SSH 公钥;
通过 https://github.com/sy-records.gpg 获取到我的 GPG 公钥。

这样只需要发一个地址,让对应的用户获取到公钥,把获取到的公钥加到服务器账户的 ~/.ssh/authorized_keys 文件里面,

如果服务器是 Debian 或者 Ubuntu,可以安装 ssh-import-id 来导入 GitHub 上的公钥:

# 推荐使用这个来进行导入
apt install ssh-import-id
ssh-import-id-gh username

# 添加我的公钥
ssh-import-id-gh sy-records

有所不同的是这个命令用的是 GitHub 的开放接口:https://api.github.com/users/sy-records/keys

但是本质上都是把公钥下载下来加入到 ~/.ssh/authorized_keys 这个文件中。

也可以通过下载 https://api.github.com/users/<username>/keys,到 ~/.ssh/authorized_keys

wget -O https://api.github.com/users/sy-records/keys | tee -a ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

测试连接

配置完成以后就可以通过 ssh 测试能否无需密码正常连接服务器了。

ssh username@remote_host

ssh username@remote_host -pport

等操作完成,再把添加的内容删掉,这样就不能再次登录了。

WordPress 文章内容审核增强版

作者 沈唁
2023年11月18日 15:14

基于百度文本审核技术来提供 WordPress 文章内容审核。

这是一个增强版插件,也有免费的 #内容审核# 插件。

增强功能

  • 支持检测历史文章(可以通过发布时间或者文章ID)
  • 支持有违规词时文章转草稿
  • 支持分类搜索过滤查看违规文章
  • 支持替换自定义文章中的违规内容

插件截图

插件配置

检测历史文章

插件售价

¥ 99

购买必读

  • 本插件仅在 qq52o.me 出售,未授权给任何第三方站点;
  • 插件仅允许购买者自己使用;
  • 不得倒卖、转发、共享给他人下载使用;
  • 如有以上行为,不再提供插件更新等售后服务;
  • 购买(支付)成功后,不提供退款/退货服务;

如何购买

支付成功后,请按以下格式用联系邮箱发送邮件到 order[at]qq52o.me

支付宝或微信扫码支付

主题:
购买 TextCensor Plus For Articles + [订单号](必填)
---
正文:
订单号 :(必填)
支付账号:(必填)
联系邮箱:(必须和发件人一致)
联系QQ:(选填)
网站域名:(必填,仅作记录)

我确认你的邮件后,会回复给你插件包,并把你的联系邮箱添加到邮件列表,以后插件发布更新会第一时间收到通知。

❌
❌