主题移植小记:给Typecho添加短代码功能

本文介绍了在Typecho博客系统中通过修改主题的functions.php文件来添加类似WordPress的短代码功能。核心是实现一个解析器,将文章内容中形如[post id="10"]的短代码自动替换为包含指定文章标题、摘要、链接和缩略图的卡片式HTML结构。文中提供了完整的PHP实现代码,包括短代码匹配、数据库查询、链接生成和摘要截取等关键步骤。
阅读更多

基于PHP-GD实现的单文件图片缩略图API

基于PHP实现的缩略图API在Github上有现成的,但过于老旧,遂用DeepSeek写了一个,基于PHP的GD扩展实现,支持域名白名单,本地缓存,临时文件隔离,过期缓存文件清理,需要在配置中修改域名白名单及缓存文件存放目录。

考虑到所使用服务器存储空间有限,故加入了缓存清理机制,缓存逻辑:在调用api时,有1%的概率触发缓存清理流程,会自动清理/cache/目录下留存时间大于30天的文件,同时加入了容错机制,如果当前请求传入的图片链接被清除,则会重新生成。

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<?php
// 配置部分
define('ALLOWED_DOMAINS', ['carefu.link', 'static.carefu.link']); // 允许的域名白名单
define('CACHE_DIR', __DIR__ . '/cache/'); // 缓存目录
define('TMP_DIR', __DIR__ . '/temp/'); // 临时文件目录
define('MAX_CACHE_AGE', 2592000); // 30天缓存有效期(秒)
define('CACHE_CLEAN_PROBABILITY', 1); // 1% 的清理概率
define('MAX_IMAGE_SIZE', 5242880); // 最大图片尺寸5MB

// 初始化目录
@mkdir(CACHE_DIR, 0755, true);
@mkdir(TMP_DIR, 0755, true);

try {
// 验证参数
$url = $_GET['url'] ?? null;
$width = isset($_GET['w']) ? intval($_GET['w']) : null;
$height = isset($_GET['h']) ? intval($_GET['h']) : null;

// 基础参数验证
if (!$url || !$width || !$height) {
throw new Exception('Missing parameters', 400);
}

// 验证尺寸参数
if ($width <= 0 || $height <= 0 || $width > 4096 || $height > 4096) {
throw new Exception('Invalid dimensions', 400);
}

// 验证URL合法性
$parsedUrl = parse_url($url);
if (!$parsedUrl || !isset($parsedUrl['host'])) {
throw new Exception('Invalid URL', 400);
}

// 域名白名单验证
if (!in_array($parsedUrl['host'], ALLOWED_DOMAINS)) {
throw new Exception('Domain not allowed', 403);
}

// 生成缓存文件名
$cacheKey = md5($url . $width . $height);
$extension = pathinfo($parsedUrl['path'], PATHINFO_EXTENSION);
$cacheFile = CACHE_DIR . $cacheKey . '.' . ($extension ?: 'jpg');

if (file_exists($cacheFile)) {
// 概率性触发缓存清理(不影响当前请求)
if (rand(1, 100) <= CACHE_CLEAN_PROBABILITY) {
cleanCache($cacheKey); // 修改清理函数避免删除当前文件
}

// 再次检查缓存文件是否存在
if (file_exists($cacheFile)) {
sendImage($cacheFile);
exit;
}
// 如果文件被清理,继续生成流程
}

// 下载远程文件到临时目录
$tmpFile = downloadImage($url);

// 生成缩略图
generateThumbnail($tmpFile, $cacheFile, $width, $height);

// 清理临时文件
@unlink($tmpFile);

// 发送生成的图片
sendImage($cacheFile);

} catch (Exception $e) {
http_response_code($e->getCode() ?: 500);
header('Content-Type: application/json');
echo json_encode(['error' => $e->getMessage()]);
exit;
}

// 辅助函数

function downloadImage($url) {
$context = stream_context_create([
'http' => [
'timeout' => 15,
'header' => "User-Agent: ThumbnailGenerator/1.0\r\n"
]
]);

$data = file_get_contents($url, false, $context);
if (!$data) {
throw new Exception('Failed to download image', 500);
}

if (strlen($data) > MAX_IMAGE_SIZE) {
throw new Exception('Image too large', 413);
}

$tmpFile = tempnam(TMP_DIR, 'img_');
file_put_contents($tmpFile, $data);
return $tmpFile;
}

function generateThumbnail($srcPath, $destPath, $width, $height) {
list($srcWidth, $srcHeight, $type) = getimagesize($srcPath);

// 创建图像资源
switch ($type) {
case IMAGETYPE_JPEG:
$image = imagecreatefromjpeg($srcPath);
break;
case IMAGETYPE_PNG:
$image = imagecreatefrompng($srcPath);
break;
case IMAGETYPE_GIF:
$image = imagecreatefromgif($srcPath);
break;
default:
throw new Exception('Unsupported image type', 415);
}

// 计算比例并进行居中裁剪
$ratio = max($width/$srcWidth, $height/$srcHeight);
$cropWidth = $width / $ratio;
$cropHeight = $height / $ratio;

$src_x = ($srcWidth - $cropWidth) / 2;
$src_y = ($srcHeight - $cropHeight) / 2;
$thumb = imagecreatetruecolor($width, $height);

// 处理透明背景
if ($type == IMAGETYPE_PNG || $type == IMAGETYPE_GIF) {
imagecolortransparent($thumb, imagecolorallocatealpha($thumb, 0, 0, 0, 127));
imagealphablending($thumb, false);
imagesavealpha($thumb, true);
}

imagecopyresampled(
$thumb, $image,
0, 0,
$src_x, $src_y,
$width, $height,
$cropWidth, $cropHeight
);

// 保存图像
imagejpeg($thumb, $destPath, 100);
imagedestroy($image);
imagedestroy($thumb);
}

function sendImage($path) {
if (!file_exists($path)) {
throw new Exception('Image not found', 404);
}

$mime = mime_content_type($path);
$lastModified = filemtime($path);
$etag = md5_file($path);

header('Content-Type: ' . $mime);
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
header('ETag: ' . $etag);
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + MAX_CACHE_AGE) . ' GMT');
readfile($path);
exit;
}

// 缓存清理函数
function cleanCache($excludeKey = null) {
$now = time();
foreach (glob(CACHE_DIR . '*') as $file) {
// 排除当前正在使用的缓存文件
if ($excludeKey && strpos($file, $excludeKey) !== false) {
continue;
}

if (is_file($file) && ($now - filemtime($file)) > MAX_CACHE_AGE) {
@unlink($file);
}
}
}
?>

调用格式:[api地址]?url=[图片地址]&w=[宽度]&h=[宽度],示例:

1
https://api.carefu.link/thumbnail.php?url=https://carefu.link/upload/images/thumbnail.jpg&w=840&h=420

给Docker中的PHP-FPM镜像安装GD扩展

本文介绍了在Docker的PHP-FPM镜像中安装GD扩展的步骤。首先,为了加速下载,将Alpine的软件源更换为阿里云镜像。接着,安装GD扩展所需的依赖包,包括libpng-dev、libjpeg-turbo-dev等。最后,通过docker-php-ext-configure和docker-php-ext-install命令成功安装GD扩展,安装后需重启容器并通过phpinfo验证。
阅读更多

在Alpine环境下使用包管理部署Typecho博客

本文介绍了在Alpine环境下使用包管理部署Typecho博客的完整流程。首先更换国内镜像源以加速访问,然后安装必要的软件包如Caddy2、PHP-FPM及Sqlite等。接着配置PHP-FPM和Caddy2,设置开机自启动并启动相关服务。最后调整文件夹权限以确保Typecho安装顺利进行,适用于存储空间有限的云服务器环境。
阅读更多

手动在CentOS 8下安装Seafile,搭建私有云

本文详细介绍了在CentOS 8系统下手动安装和配置Seafile 8.0.7私有云服务的全过程。主要内容包括:下载并解压Seafile安装包、安装Python3等必要依赖、创建专用用户并运行安装脚本、初始化Seafile服务。文章还重点说明了如何配置Nginx反向代理以实现外部访问,并提供了完整的Nginx配置文件示例。最后,指导用户设置Seafile的管理后台地址,并创建systemd服务单元文件以实现开机自启动。整个过程旨在帮助用户搭建一个稳定、安全的个人云存储环境。
阅读更多

Hello World

这篇文章是Hexo博客框架的入门指南,介绍了如何创建第一篇博客文章。文章内容涵盖了Hexo的基本操作,包括创建新文章、启动本地服务器、生成静态文件以及部署到远程站点。对于初次使用Hexo的用户,本文提供了快速上手的步骤和官方文档的指引。
阅读更多