支持 2G+ 大批量安全压缩(65% 质量、最大宽 800px),含全量快照、校验与恢复能力。 Co-authored-by: Cursor <cursoragent@cursor.com>
124 行
3.7 KiB
Bash
124 行
3.7 KiB
Bash
#!/usr/bin/env bash
|
||
# 批量压缩 public/uploads 图片(2G+ 安全模式)
|
||
#
|
||
# 用法:
|
||
# bash deploy/compress-uploads.sh --dry-run # 预览
|
||
# bash deploy/compress-uploads.sh --safe # 推荐:rsync 快照 + 压缩
|
||
# bash deploy/compress-uploads.sh --safe --log=deploy/logs/compress.log
|
||
set -euo pipefail
|
||
|
||
DEPLOY_PATH="${DEPLOY_PATH:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||
PHP_BIN="${PHP_BIN:-$(command -v php || true)}"
|
||
SCRIPT="${DEPLOY_PATH}/deploy/compress-uploads.php"
|
||
UPLOADS="${DEPLOY_PATH}/public/uploads"
|
||
SNAPSHOTS="${UPLOADS}/.compress_backup/snapshots"
|
||
LOG_DIR="${DEPLOY_PATH}/deploy/logs"
|
||
|
||
if [[ -z "$PHP_BIN" || ! -x "$PHP_BIN" ]]; then
|
||
echo "ERROR: 找不到可执行的 PHP,请设置 PHP_BIN"
|
||
exit 1
|
||
fi
|
||
|
||
if [[ ! -f "$SCRIPT" ]]; then
|
||
echo "ERROR: 脚本不存在: $SCRIPT"
|
||
exit 1
|
||
fi
|
||
|
||
if [[ ! -d "$UPLOADS" ]]; then
|
||
echo "ERROR: uploads 目录不存在: $UPLOADS"
|
||
exit 1
|
||
fi
|
||
|
||
# 解析 --safe 模式
|
||
SAFE_MODE=0
|
||
PASSTHRU=()
|
||
for arg in "$@"; do
|
||
if [[ "$arg" == "--safe" ]]; then
|
||
SAFE_MODE=1
|
||
else
|
||
PASSTHRU+=("$arg")
|
||
fi
|
||
done
|
||
|
||
echo "项目: ${DEPLOY_PATH}"
|
||
echo "PHP: ${PHP_BIN}"
|
||
echo ""
|
||
|
||
if [[ "$SAFE_MODE" -eq 1 ]]; then
|
||
SNAPSHOT_ID="$(date +%Y%m%d_%H%M%S)"
|
||
SNAPSHOT_DIR="${SNAPSHOTS}/${SNAPSHOT_ID}"
|
||
|
||
# 磁盘空间检查
|
||
UPLOADS_SIZE=$(du -sb "$UPLOADS" 2>/dev/null | awk '{print $1}' || echo 0)
|
||
FREE_SPACE=$(df -B1 "$UPLOADS" 2>/dev/null | awk 'NR==2{print $4}' || echo 0)
|
||
NEED=$(( UPLOADS_SIZE + UPLOADS_SIZE / 10 ))
|
||
|
||
echo "========== 安全模式 =========="
|
||
echo "uploads 大小: $(numfmt --to=iec "$UPLOADS_SIZE" 2>/dev/null || echo "${UPLOADS_SIZE}B")"
|
||
echo "磁盘可用: $(numfmt --to=iec "$FREE_SPACE" 2>/dev/null || echo "${FREE_SPACE}B")"
|
||
echo "快照 ID: ${SNAPSHOT_ID}"
|
||
echo ""
|
||
|
||
if [[ "$FREE_SPACE" -gt 0 && "$FREE_SPACE" -lt "$NEED" ]]; then
|
||
echo "ERROR: 磁盘空间不足,需要约 $(numfmt --to=iec "$NEED" 2>/dev/null || echo "${NEED}B")"
|
||
echo "请清理磁盘,或将 SNAPSHOTS 目录挂载到其他盘"
|
||
exit 1
|
||
fi
|
||
|
||
mkdir -p "$SNAPSHOT_DIR"
|
||
echo "正在创建全量快照(rsync,约 2G 需数分钟)..."
|
||
echo "快照目录: ${SNAPSHOT_DIR}"
|
||
echo ""
|
||
|
||
if command -v rsync >/dev/null 2>&1; then
|
||
rsync -a --info=progress2 \
|
||
--exclude='.compress_backup/' \
|
||
"${UPLOADS}/" "${SNAPSHOT_DIR}/"
|
||
else
|
||
echo "WARN: 未找到 rsync,使用 cp(较慢)"
|
||
find "$UPLOADS" -mindepth 1 -maxdepth 1 ! -name '.compress_backup' -exec cp -a {} "${SNAPSHOT_DIR}/" \;
|
||
fi
|
||
|
||
# 写入元数据
|
||
IMAGE_COUNT=$("$PHP_BIN" -r "
|
||
\$n=0; \$ext=['jpg','jpeg','png','webp','gif'];
|
||
\$it=new RecursiveIteratorIterator(new RecursiveDirectoryIterator('${UPLOADS}', FilesystemIterator::SKIP_DOTS));
|
||
foreach(\$it as \$f){ if(\$f->isFile() && in_array(strtolower(\$f->getExtension()),\$ext)) \$n++; }
|
||
echo \$n;
|
||
")
|
||
cat > "${SNAPSHOT_DIR}/.snapshot_meta.json" <<META
|
||
{
|
||
"created_at": "$(date -Iseconds 2>/dev/null || date)",
|
||
"image_count": ${IMAGE_COUNT},
|
||
"total_bytes": ${UPLOADS_SIZE},
|
||
"method": "rsync",
|
||
"max_width": 800,
|
||
"quality": 65
|
||
}
|
||
META
|
||
|
||
echo ""
|
||
echo "快照完成!"
|
||
echo "若压缩后有问题,一键恢复:"
|
||
echo " bash deploy/restore-uploads.sh --restore=${SNAPSHOT_ID}"
|
||
echo " bash deploy/restore-uploads.sh --latest"
|
||
echo ""
|
||
echo "========== 开始压缩 =========="
|
||
echo ""
|
||
|
||
mkdir -p "$LOG_DIR"
|
||
DEFAULT_LOG="${LOG_DIR}/compress_${SNAPSHOT_ID}.log"
|
||
HAS_LOG=0
|
||
for arg in "${PASSTHRU[@]}"; do
|
||
[[ "$arg" == --log=* ]] && HAS_LOG=1
|
||
done
|
||
if [[ "$HAS_LOG" -eq 0 ]]; then
|
||
PASSTHRU+=("--log=${DEFAULT_LOG}")
|
||
fi
|
||
|
||
# 已有快照,不再让 PHP 重复快照
|
||
exec "$PHP_BIN" -d memory_limit=512M "$SCRIPT" "${PASSTHRU[@]}"
|
||
fi
|
||
|
||
exec "$PHP_BIN" -d memory_limit=512M "$SCRIPT" "$@"
|