若依代码生成自动部署脚本开发实践

本文记录了为若依框架开发代码生成自动部署脚本的完整过程,包括遇到的问题、解决方案和最佳实践。

背景

若依(RuoYi)框架提供了强大的代码生成功能,可以快速生成前后端 CRUD 代码。但每次生成后需要手动解压 ZIP 包,然后按照目录结构逐个复制文件到项目中,这个过程非常繁琐且容易出错。

目标:开发一个自动化脚本,实现一键部署生成的代码

需求分析

  1. 自动解压 ZIP 文件
  2. 智能识别目录结构(兼容不同版本的若依代码生成格式)
  3. 自动复制文件到正确的项目位置
  4. 友好提示部署进度和结果
  5. 支持拖拽操作,提升用户体验

使用方法

先下载压缩包,解压后修改deploy-core文件中的backendRoot和frontendRoot变量为自己的项目目录

方式一:拖拽运行(推荐)

  1. 双击 deploy.bat
  2. 将 ZIP 文件拖拽到窗口
  3. 按回车键
  4. 等待自动完成

方式二:命令行参数

deploy.bat C:\path\to\article.zip

方式三:直接拖拽到 BAT 文件

直接将 ZIP 文件拖拽到 deploy.bat 图标上即可自动执行。


技术方案演进

方案一:纯 PowerShell 脚本(初版)

实现思路

使用 PowerShell 编写单个脚本文件,包含所有逻辑。

遇到的问题

问题 1:中文字符编码错误

# 错误示例 - 保存为 UTF-8 无 BOM 时
Write-Host "临时目录保留在: $tempDir"

# 报错信息
所在位置 C:\deploy.ps1:106 字符: 1
+ } catch {
+ ~
表达式或语句中包含意外的标记"}"

原因分析

  • PowerShell 对文件编码敏感
  • 中文字符在不同编码下会被错误解析
  • Windows 记事本默认可能保存为 GBK 编码

解决方案

  1. 使用英文避免编码问题
  2. 或使用 UTF-8 with BOM 编码保存
  3. 或在脚本开头强制设置编码
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8

问题 2:拖拽功能不工作

# 这种方式在某些环境下不支持拖拽
$zipFile = Read-Host "请输入路径"

原因分析

  • Read-Host 在某些 PowerShell 版本中对拖拽支持不完善
  • 控制台输入处理方式导致

尝试的解决方案

  1. 使用 [System.Console]::ReadLine() - 部分有效
  2. 从剪贴板读取路径 - 需要额外权限
  3. 使用命令行参数 - 最稳定但不友好

问题 3:目录结构识别不准确

初始脚本假设 ZIP 解压后的结构是:

ruoyi/
├── main/java
└── ruoyi-ui/src/api

但实际若依代码生成的结构是:

article/
├── main/java
├── vue/api
└── articleMenu.sql

解决方案:增加智能探测逻辑

# 检测多种可能的路径
$possiblePaths = @(
    (Join-Path $sourceRoot "main/java"),
    (Join-Path $sourceRoot "ruoyi/main/java"),
    (Join-Path $sourceRoot "src/main/java")
)

foreach ($path in $possiblePaths) {
    if (Test-Path $path) {
        # 找到正确的路径
        break
    }
}

方案二:批处理 + PowerShell 双文件架构(最终方案)✅

经过多次尝试,最终采用双文件分离架构,完美解决了所有问题。

架构设计
deploy.bat              ← 用户界面层(中文,支持拖拽)
    ↓ 调用
deploy-core.ps1         ← 核心逻辑层(英文,无编码问题)
文件 1:deploy.bat(入口文件)
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion

echo ========================================
echo   若依代码生成自动部署工具
echo ========================================
echo.

REM 获取 ZIP 文件路径(支持拖拽)
if "%~1"=="" (
    echo 提示: 可以直接拖拽 ZIP 文件到窗口中
    echo       或者手动输入完整路径
    echo.
    set /p ZIP_PATH="请输入 ZIP 文件路径: "
) else (
    set ZIP_PATH=%~1
)

REM 清理路径中的引号
set ZIP_PATH=%ZIP_PATH:"=%

REM 验证文件
if not exist "%ZIP_PATH%" (
    echo.
    echo 错误: 文件不存在: %ZIP_PATH%
    pause
    exit /b 1
)

if /i not "%ZIP_PATH:~-4%"==".zip" (
    echo.
    echo 错误: 不是有效的 ZIP 文件
    pause
    exit /b 1
)

echo.
echo ZIP 文件: %ZIP_PATH%
echo.

REM 调用 PowerShell 核心脚本
powershell -ExecutionPolicy Bypass -File "%~dp0deploy-core.ps1" "%ZIP_PATH%"

echo.
pause

优势

  • ✅ 批处理原生支持拖拽
  • ✅ 中文界面无编码问题
  • ✅ 可以作为 .bat 直接双击运行
  • ✅ 支持命令行参数 %~1

文件 2:deploy-core.ps1(核心逻辑)
# RuoYi Auto Deploy Core Script
param(
    [string]$ZipFile
)

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# 配置路径
$backendRoot = "C:\Users\15154\Desktop\ruoyi\RuoYi-Vue-fast"
$frontendRoot = "C:\Users\15154\Desktop\ruoyi\RuoYi-Vue3"

# 验证输入
if (-not $ZipFile) {
    Write-Host "Error: No ZIP file specified" -ForegroundColor Red
    exit 1
}

$ZipFile = $ZipFile.Trim('"').Trim()

if (-not (Test-Path $ZipFile)) {
    Write-Host "Error: File does not exist" -ForegroundColor Red
    exit 1
}

# 创建临时目录
$tempDir = Join-Path $env:TEMP "ruoyi-deploy-$(Get-Date -Format 'yyyyMMddHHmmss')"
New-Item -ItemType Directory -Path $tempDir | Out-Null

try {
    # 1. 解压
    Write-Host "[1/6] Extracting..." -ForegroundColor Yellow
    Expand-Archive -Path $ZipFile -DestinationPath $tempDir -Force
    Write-Host "      OK" -ForegroundColor Green
    
    # 2. 检测目录结构
    Write-Host "[2/6] Detecting structure..." -ForegroundColor Yellow
    $sourceRoot = $tempDir
    $subdirs = Get-ChildItem -Path $tempDir -Directory
    if ($subdirs.Count -eq 1) {
        $sourceRoot = $subdirs[0].FullName
    }
    
    # 3. 部署后端
    Write-Host "[3/6] Deploying backend..." -ForegroundColor Yellow
    $mainJavaPath = Join-Path $sourceRoot "main/java"
    $mainResourcesPath = Join-Path $sourceRoot "main/resources"
    
    if (Test-Path $mainJavaPath) {
        Copy-Item -Path "$mainJavaPath/*" -Destination (Join-Path $backendRoot "src/main/java") -Recurse -Force
        Write-Host "      Java files copied" -ForegroundColor Green
    }
    
    if (Test-Path $mainResourcesPath) {
        Copy-Item -Path "$mainResourcesPath/*" -Destination (Join-Path $backendRoot "src/main/resources") -Recurse -Force
        Write-Host "      Resources copied" -ForegroundColor Green
    }
    
    # 复制 SQL 文件
    $sqlFiles = Get-ChildItem -Path $sourceRoot -Filter "*.sql" -File
    if ($sqlFiles.Count -gt 0) {
        foreach ($sqlFile in $sqlFiles) {
            Copy-Item -Path $sqlFile.FullName -Destination (Join-Path $backendRoot "sql") -Force
            Write-Host "      SQL: $($sqlFile.Name)" -ForegroundColor Green
        }
    }
    
    # 4. 部署前端
    Write-Host "[4/6] Deploying frontend..." -ForegroundColor Yellow
    $vueApiPath = Join-Path $sourceRoot "vue/api"
    $vueViewsPath = Join-Path $sourceRoot "vue/views"
    
    if (Test-Path $vueApiPath) {
        Copy-Item -Path "$vueApiPath/*" -Destination (Join-Path $frontendRoot "src/api") -Recurse -Force
        Write-Host "      API files copied" -ForegroundColor Green
    }
    
    if (Test-Path $vueViewsPath) {
        Copy-Item -Path "$vueViewsPath/*" -Destination (Join-Path $frontendRoot "src/views") -Recurse -Force
        Write-Host "      View files copied" -ForegroundColor Green
    }
    
    # 5. 清理
    Write-Host "[5/6] Cleaning up..." -ForegroundColor Yellow
    Remove-Item -Path $tempDir -Recurse -Force
    Write-Host "      OK" -ForegroundColor Green
    
    # 6. 完成
    Write-Host ""
    Write-Host "========================================" -ForegroundColor Cyan
    Write-Host "  Deployment Completed!" -ForegroundColor Green
    Write-Host "========================================" -ForegroundColor Cyan
    Write-Host ""
    Write-Host "Next steps:" -ForegroundColor Yellow
    Write-Host "  1. Restart backend application" -ForegroundColor White
    Write-Host "  2. Refresh frontend or run npm run dev" -ForegroundColor White
    Write-Host "  3. Execute SQL in sql/ directory" -ForegroundColor White
    Write-Host "  4. Assign menu permissions" -ForegroundColor White
    Write-Host ""
    
} catch {
    Write-Host ""
    Write-Host "ERROR: $_" -ForegroundColor Red
    Write-Host "Temp dir kept: $tempDir" -ForegroundColor Yellow
    exit 1
}

优势

  • ✅ 纯英文无编码问题
  • ✅ 通过 param() 接收参数
  • ✅ 完整的错误处理
  • ✅ 详细的调试输出

核心技术要点

1. 智能目录结构探测

# 支持多种若依代码生成格式
$detectionRules = @{
    "vue/api" = "新版本的若依代码生成"
    "ruoyi-ui/src/api" = "旧版本的若依代码生成"
    "main/java" = "标准后端结构"
    "ruoyi/main/java" = "带前缀的后端结构"
}

foreach ($rule in $detectionRules.Keys) {
    $testPath = Join-Path $sourceRoot $rule
    if (Test-Path $testPath) {
        Write-Host "Detected: $($detectionRules[$rule])"
        # 使用这个路径
        break
    }
}

2. 安全的文件复制

# 复制前检查源路径是否存在
if (Test-Path $sourcePath) {
    # 使用 -Force 覆盖已存在的文件
    # 使用 -Recurse 递归复制子目录
    Copy-Item -Path "$sourcePath/*" -Destination $destPath -Recurse -Force
    
    # 列出复制的文件用于确认
    $files = Get-ChildItem -Path $sourcePath -Recurse -File
    Write-Host "Copied $($files.Count) files"
}

3. 临时文件管理

# 创建带时间戳的临时目录(避免冲突)
$tempDir = Join-Path $env:TEMP "ruoyi-deploy-$(Get-Date -Format 'yyyyMMddHHmmss')"

try {
    # 执行部署逻辑
    ...
} catch {
    # 出错时保留临时目录用于调试
    Write-Host "Temp dir kept: $tempDir" -ForegroundColor Yellow
    exit 1
} finally {
    # 成功时清理临时文件
    if ($success) {
        Remove-Item -Path $tempDir -Recurse -Force
    }
}

4. 友好的用户反馈

# 使用颜色区分不同类型的消息
Write-Host "[1/6] Processing..." -ForegroundColor Yellow   # 进行中
Write-Host "      ✓ Done" -ForegroundColor Green            # 成功
Write-Host "      ⚠ Warning" -ForegroundColor Yellow        # 警告
Write-Host "      ✗ Error" -ForegroundColor Red             # 错误
Write-Host "      Debug info" -ForegroundColor DarkGray     # 调试信息

总结

学到的经验

  1. 编码问题是 PowerShell 脚本的最大陷阱

    • 尽量避免在 PowerShell 中使用非 ASCII 字符
    • 如果必须使用,确保文件编码为 UTF-8 with BOM
    • 或者使用批处理作为外壳
  2. 拖拽功能的实现技巧

    • 批处理(.bat)原生支持拖拽(通过 %~1
    • PowerShell 的 Read-Host 对拖拽支持不稳定
    • 双文件架构可以兼顾两者优势
  3. 目录结构探测的重要性

    • 不要硬编码路径
    • 使用多种规则匹配,提高兼容性
    • 提供详细的调试输出
  4. 错误处理的最佳实践

    • 使用 try-catch 捕获异常
    • 出错时保留临时文件用于调试
    • 提供清晰的错误信息和解决建议

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐