优化项目结构

This commit is contained in:
eafonyang
2026-08-13 14:23:01 +08:00
parent e40d9d6b27
commit d01b0c8f47
29 changed files with 620 additions and 872 deletions
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env bash
# ============================================================
# 统一部署脚本:将本地产物同步到正式服务器(rsync over ssh
#
# 覆盖两类目标(统一 ssh 别名 luxsin-api2,按顶层目录自动路由远程路径):
# dashboard/ -> /data/project/dashboard/ frontend / backend / compose
# www/ -> /data/project/dashboard/www/ (默认仅 .output/nginx.conf / docker-compose.yml 需显式指定)
#
# 用法:./deploy/upload.sh [选项] [目标|路径...]
# ============================================================
set -euo pipefail
# ===== 连接(统一使用 ssh 别名) =====
SERVER="luxsin-api2"
REMOTE_ROOT="/data/project/dashboard"
# www 静态产物位于 ${REMOTE_ROOT}/www
WWW_REMOTE_ROOT="${REMOTE_ROOT}/www"
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
FRONTEND_PATHS=(
./dashboard/frontend/dist/
)
BACKEND_PATHS=(
./dashboard/backend/dist/
./dashboard/backend/package.json
./dashboard/backend/pnpm-lock.yaml
./dashboard/backend/pnpm-workspace.yaml
)
COMPOSE_PATHS=(
./dashboard/docker-compose.yml
)
WWW_PATHS=(
./www/.output/
)
DEFAULT_PATHS=(
"${FRONTEND_PATHS[@]}"
"${BACKEND_PATHS[@]}"
"${COMPOSE_PATHS[@]}"
)
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $1"; }
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
show_help() {
cat <<EOF
用法: $(basename "$0") [选项] [目标|路径...]
将本地代码同步到远程服务器(rsync)。
远程(统一 ssh 别名 ${SERVER},按顶层目录自动路由):
dashboard/ -> ${SERVER}:${REMOTE_ROOT}/
www/ -> ${SERVER}:${WWW_REMOTE_ROOT}/
选项:
-n 虚拟执行,不实际传输或删除远程文件
-h, --help 显示此帮助信息
目标:
frontend, f 同步 dashboard 前端 dist/(默认跳过 uploads/
backend, b 同步 dashboard 后端 dist/、package.json、pnpm-lock.yaml
compose, c 同步 dashboard docker-compose.yml
www, w 同步 www 构建产物 .output/nginx.conf / docker-compose.yml 需显式指定路径)
all, a 同步 frontend + backend + compose(不含 www
(路径) 同步指定文件或目录,可传多个
默认不同步 nginx.conf 与 Dockerfiledashboard),需要时请显式指定,例如:
./dashboard/frontend/nginx.conf
./dashboard/backend/Dockerfile
www 同步前需先本地构建: cd www && pnpm generate
示例:
$(basename "$0") 同步 dashboard 默认内容
$(basename "$0") frontend 仅同步 dashboard 前端
$(basename "$0") www 仅同步 www .output/
$(basename "$0") www ./www/nginx.conf ./www/docker-compose.yml 同步 www 配置
$(basename "$0") backend www 同步后端与 www
$(basename "$0") all www 同步全部(dashboard + www
$(basename "$0") -n www 虚拟执行,预览 www 同步
EOF
}
resolve_preset_paths() {
local preset="$1"
case "$preset" in
frontend|f) printf '%s\n' "${FRONTEND_PATHS[@]}" ;;
backend|b) printf '%s\n' "${BACKEND_PATHS[@]}" ;;
compose|c) printf '%s\n' "${COMPOSE_PATHS[@]}" ;;
www|w) printf '%s\n' "${WWW_PATHS[@]}" ;;
all|a) printf '%s\n' "${DEFAULT_PATHS[@]}" ;;
*)
echo "$preset"
;;
esac
}
# www 静态产物同步前校验本地已构建(nuxt generate
ensure_www_built() {
local idx="${PROJECT_ROOT}/www/.output/public/index.html"
if [[ ! -f "$idx" ]]; then
echo "❌ 本地缺少 ${idx},请先执行:"
echo " cd ${PROJECT_ROOT}/www && pnpm generate"
exit 1
fi
}
rsync_item() {
local item="$1"
local rel local_full
if [[ "$item" == /* ]]; then
if [[ "$item" == "$PROJECT_ROOT"/* ]]; then
rel="${item#"$PROJECT_ROOT"/}"
local_full="$item"
else
warn "路径不在项目内,跳过: $item"
return 1
fi
else
rel="${item#./}"
rel="${rel%/}"
local_full="${PROJECT_ROOT}/${rel}"
fi
# 依据顶层目录映射远程路径(dashboard/ 内容在远程根,www/ 内容在远程 www 子目录)
local sub
if [[ "$rel" == dashboard/* ]]; then
sub="${rel#dashboard/}"
elif [[ "$rel" == www/* ]]; then
sub="$rel"
else
warn "路径不在 dashboard/ 或 www/ 下,跳过: $item"
return 1
fi
local remote_path="${REMOTE_ROOT}/${sub}"
if [[ ! -e "$local_full" ]]; then
warn "路径不存在: ${local_full}"
return 1
fi
local rsync_opts=(-avz --progress)
if [[ "$DRY_RUN" = true ]]; then
rsync_opts+=(-n)
fi
# dashboard 前端 dist 同步默认跳过 uploads/(服务器挂载的上传目录,避免 --delete 误删)
if [[ "$sub" == "frontend/dist" ]]; then
rsync_opts+=(--exclude '/uploads/')
fi
if [[ -d "$local_full" ]]; then
if [[ "$DRY_RUN" = true ]]; then
info "预览目录: ${rel}/ -> ${SERVER}:${remote_path}/"
else
info "同步目录: ${rel}/ -> ${SERVER}:${remote_path}/"
fi
rsync "${rsync_opts[@]}" --delete \
-e ssh \
"${local_full}/" "${SERVER}:${remote_path}/"
else
if [[ "$DRY_RUN" = true ]]; then
info "预览文件: ${rel} -> ${SERVER}:${remote_path}"
else
info "同步文件: ${rel} -> ${SERVER}:${remote_path}"
ssh "$SERVER" "mkdir -p $(dirname "$remote_path")"
fi
rsync "${rsync_opts[@]}" \
-e ssh \
"$local_full" "${SERVER}:${remote_path}"
fi
}
sync_paths() {
local paths=("$@")
local item
cd "$PROJECT_ROOT"
for item in "${paths[@]}"; do
rsync_item "$item"
done
}
DRY_RUN=false
ARGS=()
for arg in "$@"; do
case "$arg" in
-n)
DRY_RUN=true
;;
-h|--help)
show_help
exit 0
;;
*)
ARGS+=("$arg")
;;
esac
done
PATHS=()
if [[ ${#ARGS[@]} -eq 0 ]]; then
PATHS=("${DEFAULT_PATHS[@]}")
else
for arg in "${ARGS[@]}"; do
while IFS= read -r path; do
PATHS+=("$path")
done < <(resolve_preset_paths "$arg")
done
fi
# 若包含 www .output 产物,先校验本地已构建
for p in "${PATHS[@]}"; do
rel="${p#./}"
if [[ "$rel" == www/.output || "$rel" == www/.output/* ]]; then
ensure_www_built
break
fi
done
if [[ "$DRY_RUN" = true ]]; then
info "虚拟同步(dashboard -> ${SERVER}:${REMOTE_ROOT}/ www -> ${SERVER}:${WWW_REMOTE_ROOT}/"
else
info "同步(dashboard -> ${SERVER}:${REMOTE_ROOT}/ www -> ${SERVER}:${WWW_REMOTE_ROOT}/"
fi
sync_paths "${PATHS[@]}"
# www 部署后续提示
for p in "${PATHS[@]}"; do
rel="${p#./}"
if [[ "$rel" == www/* ]]; then
echo ""
ok "www 产物已上传,下一步(在服务器上执行):"
echo " cd ${WWW_REMOTE_ROOT} && docker compose up -d"
break
fi
done
echo ""
ok "完成!"