feat: 更新全栈构建脚本,支持多平台构建并优化清理和构建流程

This commit is contained in:
MatrixSeven
2025-08-02 15:04:30 +08:00
parent afa94c1e9e
commit 0ff3e67694

View File

@@ -7,18 +7,10 @@
# 1. 构建 Next.js SSG 静态文件
# 2. 将静态文件复制到 Go 嵌入目录
# 3. 构建 Go 二进制文件,包含嵌入的前端文件
# 4. 生成单一可部署的二进制文件
# 4. 生成多平台静态二进制文件 (Windows/macOS/Linux)
#
# 使用方法:
# ./build-fullstack.sh [options]
#
# 选项:
# --clean 清理所有构建文件
# --frontend-only 只构建前端
# --backend-only 只构建后端
# --dev 开发模式构建
# --verbose 显示详细输出
# --help 显示帮助信息
# ./build-fullstack.sh
# =============================================================================
set -e
@@ -39,15 +31,16 @@ FRONTEND_DIR="$PROJECT_ROOT/chuan-next"
FRONTEND_OUT_DIR="$FRONTEND_DIR/out"
GO_WEB_DIR="$PROJECT_ROOT/internal/web"
FRONTEND_EMBED_DIR="$GO_WEB_DIR/frontend"
BINARY_NAME="file-transfer-server"
BINARY_PATH="$PROJECT_ROOT/$BINARY_NAME"
DIST_DIR="$PROJECT_ROOT/dist"
# 标志变量
CLEAN=false
FRONTEND_ONLY=false
BACKEND_ONLY=false
DEV_MODE=false
VERBOSE=false
# 平台配置
PLATFORMS=(
"windows/amd64:file-transfer-server.exe"
"darwin/amd64:file-transfer-server-macos-amd64"
"darwin/arm64:file-transfer-server-macos-arm64"
"linux/amd64:file-transfer-server-linux-amd64"
"linux/arm64:file-transfer-server-linux-arm64"
)
# 打印函数
print_header() {
@@ -77,77 +70,7 @@ print_info() {
}
print_verbose() {
if [ "$VERBOSE" = true ]; then
echo -e "${CYAN}[VERBOSE]${NC} $1"
fi
}
# 显示帮助
show_help() {
cat << EOF
全栈应用构建脚本
此脚本将构建 Next.js 前端和 Go 后端,并将前端静态文件嵌入到 Go 二进制中。
使用方法:
$0 [选项]
选项:
--clean 清理所有构建文件和缓存
--frontend-only 只构建前端部分
--backend-only 只构建后端部分(需要前端已构建)
--dev 开发模式构建(包含调试信息)
--verbose 显示详细构建过程
--help 显示此帮助信息
示例:
$0 # 完整构建
$0 --clean # 清理后完整构建
$0 --frontend-only # 只构建前端
$0 --backend-only # 只构建后端
$0 --dev --verbose # 开发模式详细构建
输出:
构建成功后会生成 '$BINARY_NAME' 可执行文件,包含完整的前后端功能。
EOF
}
# 解析命令行参数
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--clean)
CLEAN=true
shift
;;
--frontend-only)
FRONTEND_ONLY=true
shift
;;
--backend-only)
BACKEND_ONLY=true
shift
;;
--dev)
DEV_MODE=true
shift
;;
--verbose)
VERBOSE=true
shift
;;
--help)
show_help
exit 0
;;
*)
print_error "未知选项: $1"
show_help
exit 1
;;
esac
done
echo -e "${CYAN}[INFO]${NC} $1"
}
# 检查依赖
@@ -156,17 +79,14 @@ check_dependencies() {
local missing_deps=()
# 检查 Node.js
if ! command -v node &> /dev/null; then
missing_deps+=("Node.js")
fi
# 检查 yarn
if ! command -v yarn &> /dev/null; then
missing_deps+=("Yarn")
fi
# 检查 Go
if ! command -v go &> /dev/null; then
missing_deps+=("Go")
fi
@@ -186,51 +106,27 @@ check_dependencies() {
# 清理函数
clean_all() {
if [ "$CLEAN" = true ]; then
print_step "清理构建文件..."
# 清理前端构建
if [ -d "$FRONTEND_DIR/.next" ]; then
rm -rf "$FRONTEND_DIR/.next"
print_verbose "已删除 $FRONTEND_DIR/.next"
fi
if [ -d "$FRONTEND_OUT_DIR" ]; then
rm -rf "$FRONTEND_OUT_DIR"
print_verbose "已删除 $FRONTEND_OUT_DIR"
fi
# 清理嵌入的前端文件
if [ -d "$FRONTEND_EMBED_DIR" ]; then
find "$FRONTEND_EMBED_DIR" -name "*.html" -o -name "*.js" -o -name "*.css" -o -name "*.json" -o -name "*.png" -o -name "*.jpg" -o -name "*.svg" -o -name "*.ico" | xargs rm -f 2>/dev/null || true
print_verbose "已清理嵌入的前端文件"
fi
# 清理 Go 构建
if [ -f "$BINARY_PATH" ]; then
rm -f "$BINARY_PATH"
print_verbose "已删除 $BINARY_PATH"
fi
# 清理 Go 模块缓存(可选)
if [ "$VERBOSE" = true ]; then
go clean -modcache
fi
print_success "清理完成"
print_step "清理构建文件..."
# 清理前端构建
[ -d "$FRONTEND_DIR/.next" ] && rm -rf "$FRONTEND_DIR/.next"
[ -d "$FRONTEND_OUT_DIR" ] && rm -rf "$FRONTEND_OUT_DIR"
# 清理嵌入的前端文件
if [ -d "$FRONTEND_EMBED_DIR" ]; then
find "$FRONTEND_EMBED_DIR" -name "*.html" -o -name "*.js" -o -name "*.css" -o -name "*.json" -o -name "*.png" -o -name "*.jpg" -o -name "*.svg" -o -name "*.ico" | xargs rm -f 2>/dev/null || true
fi
# 清理输出目录
[ -d "$DIST_DIR" ] && rm -rf "$DIST_DIR"
print_success "清理完成"
}
# 构建前端
build_frontend() {
if [ "$BACKEND_ONLY" = true ]; then
print_info "跳过前端构建 (--backend-only)"
return
fi
print_step "构建 Next.js 前端..."
# 检查前端目录
if [ ! -d "$FRONTEND_DIR" ]; then
print_error "前端目录不存在: $FRONTEND_DIR"
exit 1
@@ -240,14 +136,7 @@ build_frontend() {
# 安装依赖
print_verbose "安装前端依赖..."
if [ "$VERBOSE" = true ]; then
yarn install
else
yarn install --silent
fi
# 执行 SSG 构建
print_verbose "执行 SSG 构建..."
yarn install --silent
# 临时移除 API 目录
api_backup_name=""
@@ -258,33 +147,25 @@ build_frontend() {
fi
# 构建
build_success=true
if [ "$VERBOSE" = true ]; then
NEXT_EXPORT=true yarn build || build_success=false
else
NEXT_EXPORT=true yarn build > build.log 2>&1 || build_success=false
if [ "$build_success" = false ]; then
print_error "前端构建失败,查看 $FRONTEND_DIR/build.log"
cat build.log
# 恢复 API 目录后再退出
if [ -n "$api_backup_name" ] && [ -d "/tmp/$api_backup_name" ]; then
mv "/tmp/$api_backup_name" src/app/api 2>/dev/null || true
print_verbose "已恢复 API 目录"
fi
exit 1
print_verbose "执行 SSG 构建..."
if ! NEXT_EXPORT=true yarn build > build.log 2>&1; then
print_error "前端构建失败,查看 $FRONTEND_DIR/build.log"
cat build.log
# 恢复 API 目录后再退出
if [ -n "$api_backup_name" ] && [ -d "/tmp/$api_backup_name" ]; then
mv "/tmp/$api_backup_name" src/app/api 2>/dev/null || true
fi
rm -f build.log
exit 1
fi
rm -f build.log
# 恢复 API 目录
if [ -n "$api_backup_name" ] && [ -d "/tmp/$api_backup_name" ]; then
mv "/tmp/$api_backup_name" src/app/api 2>/dev/null || true
print_verbose "已恢复 API 目录"
elif [ -n "$api_backup_name" ]; then
print_warning "API 目录备份丢失,无法恢复: /tmp/$api_backup_name"
fi
# 清理历史备份文件保留最近1小时的
# 清理历史备份文件
find /tmp -name "next-api-backup-*" -mmin +60 -exec rm -rf {} \; 2>/dev/null || true
cd "$PROJECT_ROOT"
@@ -300,14 +181,8 @@ build_frontend() {
# 复制前端文件到嵌入目录
copy_frontend_files() {
if [ "$BACKEND_ONLY" = true ]; then
print_info "跳过前端文件复制 (--backend-only)"
return
fi
print_step "复制前端文件到嵌入目录..."
# 确保嵌入目录存在
mkdir -p "$FRONTEND_EMBED_DIR"
# 清理现有文件(除了 .gitkeep
@@ -317,7 +192,6 @@ copy_frontend_files() {
if [ -d "$FRONTEND_OUT_DIR" ]; then
cp -r "$FRONTEND_OUT_DIR"/* "$FRONTEND_EMBED_DIR/" 2>/dev/null || true
# 统计复制的文件
file_count=$(find "$FRONTEND_EMBED_DIR" -type f ! -name ".gitkeep" | wc -l)
total_size=$(du -sh "$FRONTEND_EMBED_DIR" 2>/dev/null | cut -f1 || echo "未知")
@@ -329,100 +203,76 @@ copy_frontend_files() {
fi
}
# 构建后端
# 构建多平台后端
build_backend() {
if [ "$FRONTEND_ONLY" = true ]; then
print_info "跳过后端构建 (--frontend-only)"
return
fi
print_step "构建 Go 后端..."
print_step "构建多平台 Go 后端..."
cd "$PROJECT_ROOT"
# 创建输出目录
mkdir -p "$DIST_DIR"
# 构建参数
local build_args=()
local ldflags="-s -w -extldflags '-static'"
if [ "$DEV_MODE" = true ]; then
build_args+=("-gcflags" "all=-N -l") # 禁用优化,启用调试
print_verbose "开发模式构建(包含调试信息)"
else
build_args+=("-ldflags" "-s -w") # 移除调试信息和符号表
print_verbose "生产模式构建(移除调试信息)"
fi
print_verbose "构建参数: $ldflags"
build_args+=("-o" "$BINARY_NAME" "./cmd")
# 执行构建
print_verbose "执行 Go 构建: go build ${build_args[*]}"
if [ "$VERBOSE" = true ]; then
go build "${build_args[@]}"
else
go build "${build_args[@]}" 2>&1
if [ $? -ne 0 ]; then
print_error "Go 构建失败"
# 为每个平台构建
for platform_config in "${PLATFORMS[@]}"; do
IFS=':' read -r platform binary_name <<< "$platform_config"
IFS='/' read -r goos goarch <<< "$platform"
output_path="$DIST_DIR/$binary_name"
print_verbose "构建 $platform -> $binary_name"
# 设置环境变量并构建
if ! env CGO_ENABLED=0 GOOS="$goos" GOARCH="$goarch" go build \
-ldflags "$ldflags" \
-o "$output_path" \
./cmd; then
print_error "构建 $platform 失败"
exit 1
fi
fi
# 验证构建结果
if [ ! -f "$output_path" ]; then
print_error "构建验证失败: $output_path 不存在"
exit 1
fi
binary_size=$(du -sh "$output_path" | cut -f1)
print_verbose "$binary_name ($binary_size)"
done
# 验证构建结果
if [ ! -f "$BINARY_PATH" ]; then
print_error "Go 构建失败:二进制文件不存在"
exit 1
fi
# 显示二进制文件信息
if command -v file &> /dev/null; then
file_info=$(file "$BINARY_PATH")
print_verbose "二进制文件信息: $file_info"
fi
binary_size=$(du -sh "$BINARY_PATH" | cut -f1)
print_verbose "二进制文件大小: $binary_size"
print_success "后端构建完成"
print_success "多平台后端构建完成"
}
# 验证最终结果
verify_build() {
print_step "验证构建结果..."
if [ "$FRONTEND_ONLY" = true ]; then
if [ -d "$FRONTEND_OUT_DIR" ] && [ -f "$FRONTEND_OUT_DIR/index.html" ]; then
print_success "前端构建验证通过"
else
print_error "前端构建验证失败"
exit 1
fi
return
fi
if [ "$BACKEND_ONLY" = true ]; then
if [ -f "$BINARY_PATH" ]; then
print_success "后端构建验证通过"
else
print_error "后端构建验证失败"
exit 1
fi
return
fi
# 完整构建验证
local errors=()
if [ ! -f "$BINARY_PATH" ]; then
errors+=("二进制文件不存在")
fi
# 检查前端嵌入文件
if [ ! -d "$FRONTEND_EMBED_DIR" ]; then
errors+=("前端嵌入目录不存在")
else
embedded_files=$(find "$FRONTEND_EMBED_DIR" -type f ! -name ".gitkeep" | wc -l)
if [ "$embedded_files" -eq 0 ]; then
errors+=("没有嵌入的前端文件")
fi
fi
embedded_files=$(find "$FRONTEND_EMBED_DIR" -type f ! -name ".gitkeep" | wc -l)
if [ "$embedded_files" -eq 0 ]; then
errors+=("没有嵌入的前端文件")
fi
# 检查所有平台的二进制文件
for platform_config in "${PLATFORMS[@]}"; do
IFS=':' read -r platform binary_name <<< "$platform_config"
binary_path="$DIST_DIR/$binary_name"
if [ ! -f "$binary_path" ]; then
errors+=("$platform 二进制文件不存在: $binary_name")
fi
done
if [ ${#errors[@]} -gt 0 ]; then
print_error "构建验证失败:"
@@ -439,36 +289,10 @@ verify_build() {
show_summary() {
print_header "构建完成"
echo -e "${GREEN}🎉 全栈应用构建成功!${NC}"
echo -e "${GREEN}🎉 多平台全栈应用构建成功!${NC}"
echo ""
if [ "$FRONTEND_ONLY" = true ]; then
print_info "📁 前端文件输出目录: $FRONTEND_OUT_DIR"
if [ -d "$FRONTEND_OUT_DIR" ]; then
file_count=$(find "$FRONTEND_OUT_DIR" -type f | wc -l)
dir_size=$(du -sh "$FRONTEND_OUT_DIR" | cut -f1)
echo " - 文件数量: $file_count"
echo " - 总大小: $dir_size"
fi
return
fi
if [ "$BACKEND_ONLY" = true ]; then
print_info "📦 后端二进制文件: $BINARY_PATH"
if [ -f "$BINARY_PATH" ]; then
binary_size=$(du -sh "$BINARY_PATH" | cut -f1)
echo " - 文件大小: $binary_size"
fi
return
fi
# 完整构建摘要
print_info "📦 单一二进制文件: $BINARY_PATH"
if [ -f "$BINARY_PATH" ]; then
binary_size=$(du -sh "$BINARY_PATH" | cut -f1)
echo " - 文件大小: $binary_size"
fi
print_info "<EFBFBD> 构建输出目录: $DIST_DIR"
if [ -d "$FRONTEND_EMBED_DIR" ]; then
embedded_files=$(find "$FRONTEND_EMBED_DIR" -type f ! -name ".gitkeep" | wc -l)
@@ -476,43 +300,48 @@ show_summary() {
fi
echo ""
print_info "🚀 部署说明:"
echo " 1. 只需部署单个二进制文件: $BINARY_NAME"
echo " 2. 运行命令: ./$BINARY_NAME"
print_info "<EFBFBD> 生成的二进制文件:"
for platform_config in "${PLATFORMS[@]}"; do
IFS=':' read -r platform binary_name <<< "$platform_config"
binary_path="$DIST_DIR/$binary_name"
if [ -f "$binary_path" ]; then
binary_size=$(du -sh "$binary_path" | cut -f1)
echo "$binary_name ($platform) - $binary_size"
else
echo "$binary_name ($platform) - 构建失败"
fi
done
echo ""
print_info "<22> 部署说明:"
echo " 1. 选择对应平台的二进制文件进行部署"
echo " 2. 运行命令: ./二进制文件名"
echo " 3. 访问地址: http://localhost:8080"
echo ""
print_info "💡 特性:"
print_info "🌟 特性:"
echo " ✅ 前端界面完全嵌入"
echo " ✅ 无需额外的静态文件服务器"
echo " ✅ 支持 SPA 路由"
echo " ✅ 自动处理 API 代理"
echo ""
if [ "$DEV_MODE" = true ]; then
print_warning "⚠️ 这是开发模式构建,包含调试信息,不适合生产部署"
fi
echo " ✅ 静态编译,无外部依赖"
echo " ✅ 支持多平台部署"
echo " ✅ 单文件部署"
}
# 错误处理
error_cleanup() {
print_error "构建过程中发生错误"
# 尝试恢复 API 目录 - 查找所有可能的备份
local current_process_backups=$(ls /tmp/next-api-backup-*-$$ 2>/dev/null || true)
local other_backups=$(ls /tmp/next-api-backup-* 2>/dev/null | grep -v "\-$$" | head -1 || true)
# 尝试恢复 API 目录
local api_backups=$(ls /tmp/next-api-backup-*-$$ 2>/dev/null || true)
# 优先恢复当前进程的备份
if [ -n "$current_process_backups" ]; then
for backup in $current_process_backups; do
if [ -n "$api_backups" ]; then
for backup in $api_backups; do
if [ -d "$backup" ] && [ -d "$FRONTEND_DIR" ]; then
mv "$backup" "$FRONTEND_DIR/src/app/api" 2>/dev/null || true
print_verbose "已恢复 API 目录: $backup"
break
fi
done
elif [ -n "$other_backups" ] && [ -d "$FRONTEND_DIR" ]; then
mv "$other_backups" "$FRONTEND_DIR/src/app/api" 2>/dev/null || true
print_verbose "已恢复 API 目录: $other_backups"
fi
exit 1
@@ -520,25 +349,11 @@ error_cleanup() {
# 主函数
main() {
print_header "全栈应用构建脚本"
print_header "多平台全栈应用构建"
# 设置错误处理
trap error_cleanup ERR INT TERM
# 解析参数
parse_args "$@"
# 显示构建配置
if [ "$VERBOSE" = true ]; then
print_info "构建配置:"
echo " - 清理模式: $CLEAN"
echo " - 仅前端: $FRONTEND_ONLY"
echo " - 仅后端: $BACKEND_ONLY"
echo " - 开发模式: $DEV_MODE"
echo " - 详细输出: $VERBOSE"
echo ""
fi
# 执行构建步骤
check_dependencies
clean_all