diff --git a/YLErpWeb/YLErpWeb.csproj b/YLErpWeb/YLErpWeb.csproj index 8eb389eb..3a096f2e 100644 --- a/YLErpWeb/YLErpWeb.csproj +++ b/YLErpWeb/YLErpWeb.csproj @@ -232,10 +232,12 @@ - - + + <_RebuildScript>$(MSBuildProjectDirectory)\rebuild-bundles.ps1 <_RebuildScriptSh>$(MSBuildProjectDirectory)/rebuild-bundles.sh @@ -246,6 +248,21 @@ Command="bash "$(_RebuildScriptSh)"" /> + + + + <_RebuildScript>$(MSBuildProjectDirectory)\rebuild-bundles.ps1 + <_RebuildScriptSh>$(MSBuildProjectDirectory)/rebuild-bundles.sh + + + + + diff --git a/YLErpWeb/rebuild-bundles.ps1 b/YLErpWeb/rebuild-bundles.ps1 index ee7187ea..9f7766b8 100644 --- a/YLErpWeb/rebuild-bundles.ps1 +++ b/YLErpWeb/rebuild-bundles.ps1 @@ -35,11 +35,38 @@ $bundles = $configJson | ConvertFrom-Json $utf8NoBom = [System.Text.UTF8Encoding]::new($false) $mismatches = @() +# CI 环境检测:CI 下 git checkout 后所有文件 mtime 几乎相同,增量检查不可靠 +# 因此 CI 环境强制全量重建,本地开发才走增量 +$isCI = -not [string]::IsNullOrEmpty($env:CI) ` + -or -not [string]::IsNullOrEmpty($env:JENKINS_HOME) ` + -or -not [string]::IsNullOrEmpty($env:BUILD_NUMBER) ` + -or -not [string]::IsNullOrEmpty($env:GITHUB_ACTIONS) + foreach ($bundle in $bundles) { $outputRel = $bundle.outputFileName $outputPath = Join-Path $projectDir $outputRel $isMinified = $bundle.minify.enabled + # 增量检查:非 Verify、非 CI、非 minified,且 bundle 产物已存在时, + # 若所有源文件都不比 bundle 产物新,则跳过此 bundle + # 目的:让 target 挂到 Build 之前时,后端-only 改动不会触发重建,IDE F5 不变慢 + # CI 环境强制全量:git checkout 后 mtime 不可靠,必须重建保证部署产物正确 + if (-not $Verify -and -not $isCI -and -not $isMinified -and (Test-Path $outputPath)) { + $outputMtime = (Get-Item $outputPath).LastWriteTime + $stale = $false + foreach ($inputRel in $bundle.inputFiles) { + $inputPath = Join-Path $projectDir $inputRel + if ((Test-Path $inputPath) -and (Get-Item $inputPath).LastWriteTime -gt $outputMtime) { + $stale = $true + break + } + } + if (-not $stale) { + Write-Host ("[UP-TO-DATE] {0}" -f $outputRel) -ForegroundColor DarkGray + continue + } + } + Write-Host ("[{0}] {1}" -f $(if ($isMinified) { 'SKIP-MIN' } else { 'BUILD' }), $outputRel) # Check input files exist + no BOM @@ -73,9 +100,15 @@ foreach ($bundle in $bundles) { continue } if ($bomInputs.Count -gt 0) { - Write-Host " [ERROR] Input files have BOM:" -ForegroundColor Red + # Verify 模式:BOM 是源文件问题,报错阻止提交 + # 重建模式:BOM 会在读取时自动剥离(ReadAllText 处理),只警告不 fail,避免阻塞 CI 部署 + $level = $(if ($Verify) { 'ERROR' } else { 'WARN' }) + $color = $(if ($Verify) { 'Red' } else { 'Yellow' }) + Write-Host " [$level] Input files have BOM (will be stripped on rebuild):" -ForegroundColor $color foreach ($b in $bomInputs) { Write-Host " - $b" } - $mismatches += "$outputRel : input BOM ($($bomInputs -join ', '))" + if ($Verify) { + $mismatches += "$outputRel : input BOM ($($bomInputs -join ', '))" + } } # Pure concatenation with LF separator between files (matches committed bundle artifacts) diff --git a/YLErpWeb/rebuild-bundles.py b/YLErpWeb/rebuild-bundles.py index d6a439bb..ae4dce27 100644 --- a/YLErpWeb/rebuild-bundles.py +++ b/YLErpWeb/rebuild-bundles.py @@ -97,9 +97,67 @@ def build_all(): return results +def is_stale(out, input_files): + """增量检查:任一源文件比 bundle 产物新(或产物不存在)则返回 True。""" + dest = os.path.join(ROOT, out) + if not os.path.exists(dest): + return True + out_mtime = os.path.getmtime(dest) + for f in input_files: + p = os.path.join(ROOT, f) + if os.path.exists(p) and os.path.getmtime(p) > out_mtime: + return True + return False + + +def is_ci_environment(): + """检测是否在 CI 环境运行。 + CI 下 git checkout 后所有文件 mtime 几乎相同,增量检查不可靠, + 因此 CI 环境强制全量重建。 + """ + ci_vars = ('CI', 'JENKINS_HOME', 'BUILD_NUMBER', 'GITHUB_ACTIONS', + 'GITLAB_CI', 'TF_BUILD') + return any(os.environ.get(v) for v in ci_vars) + + def main(): verify = '--verify' in sys.argv[1:] - results = build_all() + force_rebuild = is_ci_environment() # CI 环境强制全量重建 + # 读取配置用于增量检查 + cfg_path = os.path.join(ROOT, 'bundleconfig.json') + raw = open(cfg_path, 'rb').read() + raw = re.sub(rb'//[^\n]*', b'', raw) + cfg = json.loads(raw.decode('utf-8-sig')) + + # 增量过滤:非 verify、非 CI 时,跳过源文件未变更的 bundle + # CI 环境(force_rebuild=True)强制全量重建,因为 git checkout 后 mtime 不可靠 + stale_bundles = [] + for b in cfg: + out = b['outputFileName'] + if verify or force_rebuild or is_stale(out, b['inputFiles']): + stale_bundles.append(b) + else: + print(f" [UP-TO-DATE] {out}") + + # 只重建过期的 bundle + results = [] + for b in stale_bundles: + out = b['outputFileName'] + iscss = out.endswith('.css') + parts = [] + missing = [] + for f in b['inputFiles']: + p = os.path.join(ROOT, f) + if not os.path.exists(p): + missing.append(f) + continue + content = normalize(open(p, 'rb').read()) + if iscss: + content = rewrite_css_urls(content, f, out) + parts.append(content) + joined = b'\n'.join(parts) + results.append((out, joined, missing)) + ok = True for out, joined, missing in results: dest = os.path.join(ROOT, out) diff --git a/deploy/jenkins-build.sh b/deploy/jenkins-build.sh new file mode 100644 index 00000000..4f04bada --- /dev/null +++ b/deploy/jenkins-build.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# ============================================================================= +# Jenkins CI 构建脚本(参考副本) +# ============================================================================= +# 此文件为 Jenkins 节点上实际执行的构建脚本的参考副本。 +# 实际执行以 Jenkins 节点上的脚本为准;本副本用于: +# 1. 让团队了解 CI 构建流程(大小写修正、publish、部署、重启) +# 2. 修改 CI 流程时有参考依据 +# 3. 排查部署问题时可对照实际行为 +# +# 注意:本文件不参与 CI 执行。修改此文件不会影响 Jenkins 构建。 +# 如需修改 CI 流程,请联系运维同步修改 Jenkins 节点上的脚本。 +# ============================================================================= + +set -euo pipefail + +export DOTNET_ROOT=/usr/local/dotnet +export PATH=$PATH:$DOTNET_ROOT +export JENKINS_NODE_COOKIE=dontKillMe +export DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false +export LANG=zh_CN.UTF-8 +export LC_ALL=zh_CN.UTF-8 + +WS="$WORKSPACE" + +# ============================================================ +# 1. 文件名大小写修正(Linux 区分大小写) +# ============================================================ +# Windows 不区分大小写,但 Linux 区分。 +# 以下文件在仓库里的命名与 Linux 期望不一致,需要重命名。 +echo "Step 1: Fix filename casing..." + +safe_rename() { + local src="$1" dst="$2" + [[ "$src" == "$dst" ]] && return + if [[ -d "$src" ]]; then + mkdir -p "$dst" + rsync -a "$src"/ "$dst"/ + rm -rf "$src" + elif [[ -f "$src" ]]; then + mkdir -p "$(dirname "$dst")" + mv -f "$src" "$dst" + fi +} + +safe_rename "$WS/Framework/YLErp.Resources/Dictionary" "$WS/Framework/YLErp.Resources/dictionary" +safe_rename "$WS/YLErpDAL/Resources" "$WS/YLErpDAL/resources" +safe_rename "$WS/YLErpDAL/resources/clientEditConfig.js" "$WS/YLErpDAL/resources/clienteditconfig.js" + +# ============================================================ +# 2. 清理 & 发布 +# ============================================================ +# dotnet publish 会触发 csproj 中的 GenerateBundlesBeforeBuild target(挂在 Build 之前), +# 按 bundleconfig.json 增量重建前端 bundle 产物。 +# CI 环境(JENKINS_HOME 存在)下脚本会强制全量重建,不依赖 git checkout 后的 mtime。 +echo "Step 2: Clean & publish..." + +rm -rf "$WS/otc" "$WS/RealTimeCalcPositionService" + +publish() { + local name="$1" path="$2" output="$3" + echo " Publishing $name..." + cd "$path" + dotnet publish "${name}.csproj" -c Release -o "$output" --nologo +} + +publish "YLErpWeb" "$WS/YLErpWeb" "$WS/otc" +publish "RealTimeCalcPositionService" "$WS/YLWinSer/RealTimeCalcPositionService" "$WS/RealTimeCalcPositionService" +publish "YLErp.Plugins.GuoLian" "$WS/Plugins/YLErp.Plugins.GuoLian" "$WS/otc/App_Plugin" + +# ============================================================ +# 3. 移除敏感文件 +# ============================================================ +echo "Step 3: Remove sensitive configs..." + +rm -f "$WS/RealTimeCalcPositionService/appsettings"* "$WS/otc/appsettings"* +rm -f "$WS/RealTimeCalcPositionService/st"* "$WS/otc/st"* + +# ============================================================ +# 4. 同步 & 保留特定插件文件 +# ============================================================ +echo "Step 4: Sync to deploy directory..." + +rsync -a "$WS/otc/" "/home/glms/YLErpWeb/" +rsync -a "$WS/RealTimeCalcPositionService/" "/home/glms/RealTimeCalcPositionService/" + +# App_Docs 移到上级目录后删掉原位置 +rsync -a --delete "$WS/otc/App_Docs/" "$WS/App_Docs/" +rm -rf "$WS/otc/App_Docs" + +# App_Plugin 只保留 GuoLian 相关 +find "$WS/otc/App_Plugin" -mindepth 1 -maxdepth 1 \ + ! -name "YLErp.Plugins.GuoLian.deps.json" \ + ! -name "YLErp.Plugins.GuoLian.dll" \ + ! -name "YLErp.Plugins.GuoLian.pdb" \ + -exec rm -rf {} + + +# ============================================================ +# 5. 重启服务(延迟脱离 Jenkins 进程树) +# ============================================================ +echo "Step 5: Schedule restart..." +echo "cd /home/glms/RealTimeCalcPositionService && sh server-ctl.sh restart" | at now + 1 minute +echo "cd /home/glms/YLErpWeb && sh server-ctl.sh restart" | at now + 2 minute + +echo "Done."