Files
file-transfer-go/build-fullstack.sh

556 lines
15 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# =============================================================================
# 全栈应用构建脚本
#
# 功能:
# 1. 构建 Next.js SSG 静态文件
# 2. 将静态文件复制到 Go 嵌入目录
# 3. 构建 Go 二进制文件,包含嵌入的前端文件
# 4. 生成单一可部署的二进制文件
#
# 使用方法:
# ./build-fullstack.sh [options]
#
# 选项:
# --clean 清理所有构建文件
# --frontend-only 只构建前端
# --backend-only 只构建后端
# --dev 开发模式构建
# --verbose 显示详细输出
# --help 显示帮助信息
# =============================================================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'
# 配置变量
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$SCRIPT_DIR"
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"
# 标志变量
CLEAN=false
FRONTEND_ONLY=false
BACKEND_ONLY=false
DEV_MODE=false
VERBOSE=false
# 打印函数
print_header() {
echo -e "${PURPLE}========================================${NC}"
echo -e "${PURPLE}🚀 $1${NC}"
echo -e "${PURPLE}========================================${NC}"
}
print_step() {
echo -e "${BLUE}📋 $1${NC}"
}
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
print_info() {
echo -e "${CYAN} $1${NC}"
}
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
}
# 检查依赖
check_dependencies() {
print_step "检查构建依赖..."
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
if [ ${#missing_deps[@]} -gt 0 ]; then
print_error "缺少必要的依赖: ${missing_deps[*]}"
print_info "请安装缺少的依赖后重试"
exit 1
fi
print_verbose "Node.js 版本: $(node --version)"
print_verbose "Yarn 版本: $(yarn --version)"
print_verbose "Go 版本: $(go version)"
print_success "依赖检查完成"
}
# 清理函数
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 "清理完成"
fi
}
# 构建前端
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
fi
cd "$FRONTEND_DIR"
# 安装依赖
print_verbose "安装前端依赖..."
if [ "$VERBOSE" = true ]; then
yarn install
else
yarn install --silent
fi
# 执行 SSG 构建
print_verbose "执行 SSG 构建..."
# 临时移除 API 目录
api_backup_name=""
if [ -d "src/app/api" ]; then
api_backup_name="next-api-backup-$(date +%s)-$$"
mv src/app/api "/tmp/$api_backup_name" 2>/dev/null || true
print_verbose "API 目录已备份到: /tmp/$api_backup_name"
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
fi
rm -f build.log
fi
# 恢复 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"
# 验证构建结果
if [ ! -d "$FRONTEND_OUT_DIR" ] || [ ! -f "$FRONTEND_OUT_DIR/index.html" ]; then
print_error "前端构建失败:输出文件不存在"
exit 1
fi
print_success "前端构建完成"
}
# 复制前端文件到嵌入目录
copy_frontend_files() {
if [ "$BACKEND_ONLY" = true ]; then
print_info "跳过前端文件复制 (--backend-only)"
return
fi
print_step "复制前端文件到嵌入目录..."
# 确保嵌入目录存在
mkdir -p "$FRONTEND_EMBED_DIR"
# 清理现有文件(除了 .gitkeep
find "$FRONTEND_EMBED_DIR" -type f ! -name ".gitkeep" -delete 2>/dev/null || true
# 复制所有文件
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 "未知")
print_verbose "复制了 $file_count 个文件,总大小: $total_size"
print_success "前端文件复制完成"
else
print_error "前端输出目录不存在: $FRONTEND_OUT_DIR"
exit 1
fi
}
# 构建后端
build_backend() {
if [ "$FRONTEND_ONLY" = true ]; then
print_info "跳过后端构建 (--frontend-only)"
return
fi
print_step "构建 Go 后端..."
cd "$PROJECT_ROOT"
# 构建参数
local build_args=()
if [ "$DEV_MODE" = true ]; then
build_args+=("-gcflags" "all=-N -l") # 禁用优化,启用调试
print_verbose "开发模式构建(包含调试信息)"
else
build_args+=("-ldflags" "-s -w") # 移除调试信息和符号表
print_verbose "生产模式构建(移除调试信息)"
fi
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 构建失败"
exit 1
fi
fi
# 验证构建结果
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 "后端构建完成"
}
# 验证最终结果
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+=("前端嵌入目录不存在")
fi
embedded_files=$(find "$FRONTEND_EMBED_DIR" -type f ! -name ".gitkeep" | wc -l)
if [ "$embedded_files" -eq 0 ]; then
errors+=("没有嵌入的前端文件")
fi
if [ ${#errors[@]} -gt 0 ]; then
print_error "构建验证失败:"
for error in "${errors[@]}"; do
echo " - $error"
done
exit 1
fi
print_success "构建验证通过"
}
# 显示构建摘要
show_summary() {
print_header "构建完成"
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
if [ -d "$FRONTEND_EMBED_DIR" ]; then
embedded_files=$(find "$FRONTEND_EMBED_DIR" -type f ! -name ".gitkeep" | wc -l)
echo " - 嵌入的前端文件: $embedded_files"
fi
echo ""
print_info "🚀 部署说明:"
echo " 1. 只需部署单个二进制文件: $BINARY_NAME"
echo " 2. 运行命令: ./$BINARY_NAME"
echo " 3. 访问地址: http://localhost:8080"
echo ""
print_info "💡 特性:"
echo " ✅ 前端界面完全嵌入"
echo " ✅ 无需额外的静态文件服务器"
echo " ✅ 支持 SPA 路由"
echo " ✅ 自动处理 API 代理"
echo ""
if [ "$DEV_MODE" = true ]; then
print_warning "⚠️ 这是开发模式构建,包含调试信息,不适合生产部署"
fi
}
# 错误处理
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)
# 优先恢复当前进程的备份
if [ -n "$current_process_backups" ]; then
for backup in $current_process_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
}
# 主函数
main() {
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
build_frontend
copy_frontend_files
build_backend
verify_build
show_summary
}
# 如果脚本被直接执行
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi