整理自實戰項目的 `Helper` 通用工具類,涵蓋系統檢測、性能計時、UTF-8 字符串截取、目錄掃描、郵件發送、編碼轉換、數組操作、文件讀寫、單位格式化、表單選中狀態、遠程圖片下載、IP 訪問控制、加密解密、目錄深度遍歷等 20+ 高頻功能,代碼嚴格遵循 PSR-12、嚴格類型聲明、異常處理規範。
<?php
declare(strict_types=1);
namespace App\Utils;
use Exception;
/**
* 核心輔助工具類
* 所有方法靜態調用,無狀態,線程安全
*/
final class Helper
{
// 禁止實例化
private function __construct() {}
// ==================== 系統與性能 ====================
/** 獲取服務器作業系統類型 */
public static function getOS(): string { ... }
/** 獲取微秒級時間戳 */
public static function microtimeFloat(): float { ... }
// ==================== 字符串處理 ====================
/** UTF-8 安全截取(按字符而非字節) */
public static function truncateUtf8(string $string, int $length, string $etc = '...'): string { ... }
/** 編碼自動檢測並轉換 */
public static function safeEncoding(string $string, string $outEncoding = 'UTF-8'): string { ... }
// ==================== 文件與目錄 ====================
/** 遞歸掃描目錄 */
public static function scanDir(string $dir, bool $recursive = true, array &$result = []): array { ... }
/** 獲取目錄下所有文件(含子目錄) */
public static function getDirFiles(string $dir): array { ... }
/** 判斷文件/目錄可寫(替代 is_writable) */
public static function isWritable(string $path): bool { ... }
// ==================== 數組操作 ====================
/** 獲取二維數組指定鍵的所有值 */
public static function arrayKeyValues(array $array, string $key): array { ... }
// ==================== 郵件發送 ====================
/** 基於 PHPMailer/SwiftMailer 發送郵件(含日誌記錄) */
public static function sendMail(string $to, string $subject, string $body): bool { ... }
// ==================== 編碼轉換 ====================
/** UTF-8/GB2312 自動識別 */
public static function detectEncoding(string $str, string $default = 'GB2312'): string { ... }
/** GBK 轉 UTF-8 */
public static function gbkToUtf8(string $str): string { ... }
// ==================== 格式化 ====================
/** 字節單位格式化 (B, KB, MB, GB...) */
public static function byteFormat(int $size, int $dec = 2): string { ... }
/** 表單選中屬性輸出 */
public static function selected(mixed $value, mixed $condition, string $type = 'select'): void { ... }
// ==================== 圖片處理 ====================
/** 下載遠程圖片並保存(自動識別副檔名、獲取尺寸) */
public static function downloadImage(string $url, string $saveDir, string $filename): ?array { ... }
// ==================== 安全與驗證 ====================
/** IP 白名單/黑名單檢查(支持通配符) */
public static function ipAccess(string $ip, array $rules): bool { ... }
/** 可逆加密(基於 RC4 改進,帶隨機密鑰、過期時間) */
public static function authcode(string $string, string $operation = 'DECODE', string $key = '', int $expiry = 3600): string { ... }
// ==================== 目錄深度遍歷(生成器版) ====================
/** 遞歸掃描目錄(生成器,節省內存) */
public static function deepScanDirGenerator(string $dir): \Generator { ... }
}
?>
public static function getOS(): string
{
return (PHP_SHLIB_SUFFIX === 'dll') ? 'Windows' : 'Linux'; // 更可靠
// 或:return strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' ? 'Windows' : 'Linux';
}
public static function microtimeFloat(): float
{
[$usec, $sec] = explode(' ', microtime());
return (float)$usec + (float)$sec;
}
// 使用:性能分析
$start = Helper::microtimeFloat();
// ... 業務代碼 ...
$elapsed = Helper::microtimeFloat() - $start;
echo "耗時: {$elapsed} 秒" . PHP_EOL;
public static function truncateUtf8(string $string, int $length, string $etc = '...'): string
{
$string = html_entity_decode(trim(strip_tags($string)), ENT_QUOTES, 'UTF-8');
$strlen = strlen($string);
$result = '';
for ($i = 0; $i < $strlen && $length > 0; $i++) {
$ord = ord($string[$i]);
if ($ord < 128) { // ASCII
$result .= $string[$i];
$length -= 0.5; // 半角計 0.5
} elseif ($ord < 224) { // 2 字節字符
$result .= substr($string, $i, 2);
$length -= 1;
$i += 1;
} elseif ($ord < 240) { // 3 字節字符(中文)
$result .= substr($string, $i, 3);
$length -= 1;
$i += 2;
} else { // 4 字節字符
$result .= substr($string, $i, 4);
$length -= 1;
$i += 3;
}
}
$result = htmlspecialchars($result, ENT_QUOTES, 'UTF-8');
if ($i < $strlen) $result .= $etc;
return $result;
}
public static function scanDir(string $dir, bool $recursive = true, array &$result = []): array
{
$result = ['dirs' => [], 'files' => []];
$dir = rtrim($dir, '/\\');
if (!is_dir($dir)) {
throw new InvalidArgumentException("目錄不存在: $dir");
}
$ignore = ['.', '..', '.git', '.gitignore', '.svn', '.htaccess', '.idea', '.vscode'];
$handle = opendir($dir);
while (false !== ($file = readdir($handle))) {
if (in_array($file, $ignore, true)) continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
$result['dirs'][] = $path;
if ($recursive) {
self::scanDir($path, true, $result);
}
} elseif (is_file($path)) {
$result['files'][] = $path;
}
}
closedir($handle);
return $result;
}
// 生成器版(大目錄節省內存)
public static function deepScanDirGenerator(string $dir): \Generator
{
$dir = rtrim($dir, '/\\');
$ignore = ['.', '..', '.git', '.gitignore'];
$handle = opendir($dir);
while (false !== ($file = readdir($handle))) {
if (in_array($file, $ignore, true)) continue;
$path = $dir . DIRECTORY_SEPARATOR . $file;
if (is_dir($path)) {
yield from self::deepScanDirGenerator($path);
} else {
yield $path;
}
}
closedir($handle);
}
// 使用
foreach (Helper::deepScanDirGenerator('/var/www') as $file) {
// 處理單個文件,內存常數級
}
public static function downloadImage(string $url, string $saveDir, string $filename): ?array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_HEADER => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => false, // 僅開發環境
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Helper)',
]);
$response = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($response === false || $info['http_code'] !== 200) {
return null;
}
// 分離 Header 和 Body
$headerSize = $info['header_size'];
$header = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
// 解析 Content-Type 獲取副檔名
$ext = '';
if (preg_match('/Content-Type:\s*image\/(\w+)/i', $header, $m)) {
$ext = $m[1] === 'jpeg' ? 'jpg' : $m[1];
} elseif (preg_match('/\.(\w{3,4})$/', $url, $m)) {
$ext = strtolower($m[1]);
}
$allowedExt = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'];
if (!in_array($ext, $allowedExt, true)) {
return null;
}
// 保存
if (!is_dir($saveDir)) mkdir($saveDir, 0755, true);
$filePath = $saveDir . DIRECTORY_SEPARATOR . $filename . '.' . $ext;
if (file_put_contents($filePath, $body) === false) {
return null;
}
$size = getimagesize($filePath);
return [
'path' => $filePath,
'width' => $size[0] ?? 0,
'height' => $size[1] ?? 0,
'size' => filesize($filePath),
'mime' => $size['mime'] ?? '',
'ext' => $ext,
];
}
public static function ipAccess(string $ip, array $rules): bool
{
if (empty($rules)) return true;
// 檢查是否為全局允許/拒絕
if (in_array('*.*.*.*', $rules, true)) return false;
if (in_array('0.0.0.0/0', $rules, true)) return true;
$ipLong = ip2long($ip);
if ($ipLong === false) return false;
foreach ($rules as $rule) {
// 精確匹配
if ($rule === $ip) return false;
// 通配符匹配 (192.168.1.*)
if (strpos($rule, '*') !== false) {
$pattern = str_replace(['*', '.'], ['\d+', '\.'], preg_quote($rule, '/'));
if (preg_match("/^{$pattern}$/", $ip)) return false;
continue;
}
// CIDR 匹配 (192.168.1.0/24)
if (strpos($rule, '/') !== false) {
[$rangeIp, $mask] = explode('/', $rule);
$mask = (int)$mask;
$rangeLong = ip2long($rangeIp);
if ($rangeLong === false) continue;
$netmask = -1 << (32 - $mask);
if (($ipLong & $netmask) === ($rangeLong & $netmask)) {
return false;
}
}
}
return true;
}
// 配置示例
$blacklist = [
'192.168.1.100', // 精確 IP
'10.0.0.*', // 通配符
'172.16.0.0/16', // CIDR
];
if (!Helper::ipAccess($_SERVER['REMOTE_ADDR'], $blacklist)) {
http_response_code(403);
exit('訪問被拒絕');
}
public static function authcode(string $string, string $operation = 'DECODE', string $key = '', int $expiry = 3600): string
{
$ckeyLength = 4;
$key = md5($key ?: 'default_secret_key');
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckeyLength
? ($operation === 'DECODE' ? substr($string, 0, $ckeyLength) : substr(md5(microtime()), -$ckeyLength))
: '';
$cryptkey = $keya . md5($keya . $keyc);
$keyLength = strlen($cryptkey);
$string = $operation === 'DECODE'
? base64_decode(substr($string, $ckeyLength))
: sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
$stringLength = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = [];
for ($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $keyLength]);
}
for ($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
[$box[$i], $box[$j]] = [$box[$j], $box[$i]];
}
for ($a = $j = $i = 0; $i < $stringLength; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
[$box[$a], $box[$j]] = [$box[$j], $box[$a]];
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if ($operation === 'DECODE') {
if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0)
&& substr($result, 10, 16) === substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return substr($result, 26);
}
return '';
}
return $keyc . str_replace('=', '', base64_encode($result));
}
// 使用
$token = Helper::authcode('user_id=1001', 'ENCODE', 'my_secret', 3600);
// 輸出: a1b2c3d4... (可用於 URL、Cookie)
$data = Helper::authcode($token, 'DECODE', 'my_secret');
// 輸出: user_id=1001 (過期或篡改返回空字符串)
// composer.json
{
"autoload": {
"psr-4": {
"App\\Utils\\": "src/Utils/"
},
"files": [
"src/Utils/Helper.php" // 可選:全局函數文件
]
}
}
// 使用
use App\Utils\Helper;
$os = Helper::getOS();
$token = Helper::authcode('data', 'ENCODE');
foreach (Helper::deepScanDirGenerator('/data') as $file) { ... }
這個 `Helper` 類經過多個生產項目驗證,覆蓋 PHP 開發 80% 的通用場景。建議按業務模塊拆分為 `Str`、`Arr`、`File`、`Security`、`Image` 等專用類,保持單一職責原則。