Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc49152f1b |
@@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# 存量头像迁移:把旧命名(<uid>.jpg,历史方案)的用户头像迁入统一 files 表
|
||||
# (type='avatars')并用 SHA-256 哈希命名(公开链接 /uploads/avatars/<哈希>)。
|
||||
#
|
||||
# 背景:头像上传自 2026-08 起走统一上传引擎(files 表 + 内容寻址);历史头像
|
||||
# 文件仍以 <uid>.jpg 命名、未被 files 表登记。本脚本按用户逐个迁移:
|
||||
# 1. 取头像文件内容 SHA-256 作为新存储名(内容字节不变,不重新编码);
|
||||
# 2. 复制为 avatars/<哈希> 并登记 files 行(type=avatars);
|
||||
# 3. 更新 users.avatar;
|
||||
# 4. 删除旧的 <uid>.jpg。
|
||||
# 幂等:已是 64 位十六进制哈希名的用户自动跳过;无遗留文件时无操作。
|
||||
#
|
||||
# 用法:
|
||||
# MYSQL_HOST=127.0.0.1 MYSQL_PORT=3306 MYSQL_USER=blog_go MYSQL_PASSWORD=xxx \
|
||||
# MYSQL_DB=blog_go STORAGE_DIR=/srv/blog_go [SVC_USER=blog_go] \
|
||||
# bash scripts/migrate_legacy_avatars.sh
|
||||
set -euo pipefail
|
||||
|
||||
: "${MYSQL_HOST:=127.0.0.1}"
|
||||
: "${MYSQL_PORT:=3306}"
|
||||
: "${MYSQL_USER:?请设置 MYSQL_USER}"
|
||||
: "${MYSQL_PASSWORD:?请设置 MYSQL_PASSWORD}"
|
||||
: "${MYSQL_DB:?请设置 MYSQL_DB}"
|
||||
: "${STORAGE_DIR:?请设置 STORAGE_DIR(存储根目录,头像位于其 avatars/ 子目录)}"
|
||||
: "${SVC_USER:=blog_go}"
|
||||
|
||||
AVATAR_DIR="$STORAGE_DIR/avatars"
|
||||
|
||||
mysqlq() { mysql -h"$MYSQL_HOST" -P"$MYSQL_PORT" -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DB" -N -B -e "$1"; }
|
||||
|
||||
# 前置检查:files 表与头像目录必须存在。
|
||||
if ! mysqlq "SHOW TABLES LIKE 'files'" | grep -q files; then
|
||||
echo "错误:数据库 $MYSQL_DB 中不存在 files 表(需先部署 files 表版本)" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d "$AVATAR_DIR" ]; then
|
||||
echo "错误:头像目录不存在:$AVATAR_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
migrated=0
|
||||
skipped=0
|
||||
while IFS=$'\t' read -r uid avatar; do
|
||||
[ -n "$avatar" ] || continue
|
||||
if [[ "$avatar" =~ ^[0-9a-f]{64}$ ]]; then
|
||||
echo "跳过 $uid:已是哈希命名($avatar)"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
src="$AVATAR_DIR/$avatar"
|
||||
if [ ! -f "$src" ]; then
|
||||
echo "跳过 $uid:磁盘文件缺失 $src(不影响 URL,仅提示)"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
hash=$(sha256sum "$src" | awk '{print $1}')
|
||||
dst="$AVATAR_DIR/$hash"
|
||||
if [ ! -f "$dst" ]; then
|
||||
cp "$src" "$dst"
|
||||
chown "$SVC_USER:$SVC_USER" "$dst"
|
||||
fi
|
||||
# 登记 files 行(幂等:同类型同哈希已存在则跳过)。
|
||||
mysqlq "INSERT INTO files (type, article_id, session_token, uploader_id, filename, stored_name, ext, mime, size, category, created_at, updated_at, deleted_at)
|
||||
SELECT 'avatars', 0, '', $uid, '$avatar', '$hash', '.jpg', 'image/jpeg', $(stat -c%s "$src"), 'image', NOW(3), NOW(3), NULL
|
||||
WHERE NOT EXISTS (SELECT 1 FROM files WHERE type='avatars' AND stored_name='$hash')"
|
||||
mysqlq "UPDATE users SET avatar='$hash' WHERE id=$uid AND avatar='$avatar'"
|
||||
rm -f "$src"
|
||||
echo "迁移 $uid:$avatar -> $hash"
|
||||
migrated=$((migrated + 1))
|
||||
done < <(mysqlq "SELECT id, avatar FROM users WHERE avatar <> ''")
|
||||
|
||||
echo "完成:迁移 $migrated 个,跳过 $skipped 个。"
|
||||
@@ -58,7 +58,7 @@
|
||||
<!-- Avatar Dropdown (click to toggle) -->
|
||||
<div class="relative" id="avatarDropdown">
|
||||
<button type="button" id="avatarBtn" class="flex items-center gap-2 hover:opacity-80 transition-opacity cursor-pointer" onclick="toggleDropdown()">
|
||||
<div class="w-8 h-8 rounded-full overflow-hidden border-2 border-gray-300 hover:border-blue-500 transition-colors flex items-center justify-center bg-gray-200 text-gray-600 font-bold text-sm">
|
||||
<div id="headerAvatarWrap" class="w-8 h-8 rounded-full overflow-hidden border-2 border-gray-300 hover:border-blue-500 transition-colors flex items-center justify-center bg-gray-200 text-gray-600 font-bold text-sm">
|
||||
{{if .Avatar}}
|
||||
<img src="/uploads/avatars/{{.Avatar}}" alt="avatar" class="w-full h-full object-cover">
|
||||
{{else}}
|
||||
|
||||
@@ -212,10 +212,11 @@
|
||||
previewContainer.innerHTML =
|
||||
'<img src="' + avatarUrl + '" alt="avatar" class="w-full h-full object-cover">';
|
||||
|
||||
// Update nav bar avatar
|
||||
var navAvatarBtn = document.getElementById('avatarBtn');
|
||||
if (navAvatarBtn) {
|
||||
navAvatarBtn.innerHTML =
|
||||
// Update nav bar avatar:只替换头像圆形容器内的内容,
|
||||
// 保留按钮结构与用户名文本,避免样式被破坏。
|
||||
var navAvatarWrap = document.getElementById('headerAvatarWrap');
|
||||
if (navAvatarWrap) {
|
||||
navAvatarWrap.innerHTML =
|
||||
'<img src="' + avatarUrl + '" alt="avatar" class="w-full h-full object-cover">';
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user