@@ -80,116 +80,118 @@ if (-not $SkipConfirm) {
8080}
8181
8282# ------------------------------------------------------------------
83- # 4. 安装 nvm-windows
83+ # 4. 统一预检 & 安装(仅调整顺序,不改动原实现)
8484# ------------------------------------------------------------------
85+ $needRestart = $false
86+
87+ # ---------- nvm ----------
8588if (-not (Test-Cmd " nvm" )) {
8689 Write-Host " 正在安装 nvm-windows..." - ForegroundColor Green
8790 $nvmSetup = " $env: TEMP \nvm-setup.exe"
8891 Invoke-WebRequestWithProxy " https://github.com/coreybutler/nvm-windows/releases/latest/download/nvm-setup.exe" $nvmSetup
8992 Start-Process - Wait - FilePath $nvmSetup - ArgumentList " /silent"
9093 Remove-Item $nvmSetup
91- Write-Host " "
92- Write-Host " --------------------------------------------------" - ForegroundColor Yellow
93- Write-Host " nvm 已安装完毕,但当前 PowerShell 会话尚未识别到它。" - ForegroundColor Yellow
94- Write-Host " 请执行以下任意一步后再继续:" - ForegroundColor Cyan
95- Write-Host " 1) 关闭本窗口,重新打开一个『管理员』PowerShell后,再次执行脚本;" - ForegroundColor Cyan
96- Write-Host " 2) 或者再次右键选PS运行本脚本。" - ForegroundColor Cyan
97- Write-Host " --------------------------------------------------" - ForegroundColor Yellow
98- Read-Host " 按 Enter 键退出本窗口"
99- exit
94+ $needRestart = $true
10095} else {
10196 Write-Host " 已检测到 nvm,跳过安装" - ForegroundColor Green
10297}
10398
104- # -------------------------------------------------
105- # 5. 安装/切换 Node
106- # -------------------------------------------------
107- $needNode = $false
108- if (Test-Cmd " node" ) {
109- $nodeVer = (node - v) -replace ' ^v' , ' ' -split ' \.' | ForEach-Object { [int ]$_ }
110- $current = $nodeVer [0 ]* 10000 + $nodeVer [1 ]* 100 + $nodeVer [2 ]
111- $require = 20 * 10000 + 18 * 100 + 3 # 20.18.3
112- if ($current -ge $require ) {
113- Write-Host " 已检测到 Node v$ ( $nodeVer -join ' .' ) ≥20.18.3,跳过安装" - ForegroundColor Green
114- } else {
115- Write-Host " Node 版本低于 20.18.3,将使用 nvm 安装/切换到 20.18.3" - ForegroundColor Yellow
116- $needNode = $true
117- }
118- } else {
119- Write-Host " 未检测到 Node,准备安装" - ForegroundColor Yellow
120- $needNode = $true
121- }
122- if ($needNode ) {
123- nvm install 20.18 .3
124- nvm use 20.18 .3
125- }
126-
12799# -------------------------------------------------
128100# 6. 安装 Python 3.11(优先 winget)
129101# -------------------------------------------------
130- $pyNeed = $false
102+ $pythonOk = $false
131103try {
132104 $ver = (python - V 2> $null ) -replace ' Python ' , ' '
133- if ($ver -match ' ^3\.11' ) {
134- Write-Host " 已检测到 Python 3.11 ($ver ),跳过安装" - ForegroundColor Green
135- } else {
136- Write-Host " 检测到非 3.11 版本,准备覆盖安装 3.11" - ForegroundColor Yellow
137- $pyNeed = $true
105+ if ([version ]$ver -ge [version ]" 3.10" ) {
106+ Write-Host " 已检测到 Python $ver ≥ 3.10,跳过安装" - ForegroundColor Green
107+ $pythonOk = $true
138108 }
139- } catch {
140- Write-Host " 未检测到 Python,准备安装 3.11" - ForegroundColor Yellow
141- $pyNeed = $true
142- }
143- if ($pyNeed ) {
109+ } catch {}
110+ if (-not $pythonOk ) {
144111 Install-Winget
145- Write-Host " 正在通过 winget 安装 Python 3.11..." - ForegroundColor Green
146- winget install -- id Python.Python.3.11 - e -- accept- source- agreements -- accept- package- agreements
147- $env: Path = [System.Environment ]::GetEnvironmentVariable(" Path" , " Machine" ) + " ;" + [System.Environment ]::GetEnvironmentVariable(" Path" , " User" )
112+ $latestId = (winget search -- id Python.Python.* -- exact -- source winget |
113+ Select-String ' ^Python\.Python\.\d+' |
114+ ForEach-Object { $_.Matches.Value } |
115+ Sort-Object { [version ]($_ -replace ' Python\.Python\.' , ' ' ) } |
116+ Select-Object - Last 1 )
117+ if (-not $latestId ) { $latestId = " Python.Python.3.12" }
118+ Write-Host " 准备通过 winget 安装 $latestId ..." - ForegroundColor Green
119+ winget install -- id $latestId - e -- accept- source- agreements -- accept- package- agreements
120+ $needRestart = $true
148121}
149122
150123# -------------------------------------------------
151124# 7. 安装 Git:winget 优先,失败自动离线
152125# -------------------------------------------------
153- if (-not (Test-Cmd " git" )) {
154- # 1) winget 交互式安装
126+ $gitOk = $false
127+ if (Test-Cmd " git" ) {
128+ Write-Host " 已检测到 Git,跳过安装" - ForegroundColor Green
129+ $gitOk = $true
130+ }
131+ if (-not $gitOk ) {
132+ # 1) winget 交互
155133 Install-Winget
156134 if (Test-Cmd winget) {
157135 Write-Host " 正在通过 winget 安装 Git(交互模式)..." - ForegroundColor Green
158136 try {
159137 winget install -- id Git.Git - e -- source winget
138+
139+ # 重新加载 PATH(关键)
140+ $env: Path = [System.Environment ]::GetEnvironmentVariable(" Path" , " Machine" ) +
141+ " ;" +
142+ [System.Environment ]::GetEnvironmentVariable(" Path" , " User" )
143+
144+ # 再检测一次
160145 if (Test-Cmd git) {
161146 Write-Host " Git 安装成功(winget)" - ForegroundColor Green
162- $env: Path = [System.Environment ]::GetEnvironmentVariable(" Path" , " Machine" ) + " ;" + [System.Environment ]::GetEnvironmentVariable(" Path" , " User" )
163- continue
147+ $gitOk = $true # 这里才会真正阻止后面的离线安装
164148 }
165149 } catch {
166150 Write-Host " winget 安装失败,将使用离线包..." - ForegroundColor Yellow
167151 }
168152 }
169153
170- # 2) winget 失败 → 离线安装
171- Write-Host " 正在解析 Git 最新版本..." - ForegroundColor Green
172- try {
173- $latestUri = (Invoke-WebRequest - Uri " https://github.com/git-for-windows/git/releases/latest" - MaximumRedirection 0 - ErrorAction SilentlyContinue).Headers.Location
174- $ver = if ($latestUri ) { $latestUri -replace ' .*/tag/v([0-9.]+).*$' , ' $1' } else { " 2.51.0" }
175- } catch {
176- $ver = " 2.51.0"
154+ # 2) winget 失败 → 离线包
155+ if (-not $gitOk ) {
156+ Write-Host " 正在解析 Git 最新版本..." - ForegroundColor Green
157+ try {
158+ $latestUri = (Invoke-WebRequest - Uri " https://github.com/git-for-windows/git/releases/latest" - MaximumRedirection 0 - ErrorAction SilentlyContinue).Headers.Location
159+ $ver = if ($latestUri ) { $latestUri -replace ' .*/tag/v([0-9.]+).*$' , ' $1' } else { " 2.51.0" }
160+ } catch {
161+ $ver = " 2.51.0"
162+ }
163+
164+ Write-Host " 正在下载 Git $ver ..." - ForegroundColor Green
165+ $gitSetup = " $env: TEMP \Git-$ver -64-bit.exe"
166+ $gitUrl = " https://github.com/git-for-windows/git/releases/download/v$ver .windows.1/Git-$ver -64-bit.exe"
167+ Invoke-WebRequestWithProxy $gitUrl $gitSetup
168+ Start-Process - Wait - FilePath $gitSetup - ArgumentList " /VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS"
169+ Remove-Item $gitSetup - Force
170+ $needRestart = $true
177171 }
172+ }
178173
179- Write-Host " 正在下载 Git $ver ..." - ForegroundColor Green
180- $gitSetup = " $env: TEMP \Git-$ver -64-bit.exe"
181- $gitUrl = " https://github.com/git-for-windows/git/releases/download/v$ver .windows.1/Git-$ver -64-bit.exe"
182- Invoke-WebRequestWithProxy $gitUrl $gitSetup
183- Start-Process - Wait - FilePath $gitSetup - ArgumentList " /VERYSILENT /NORESTART /NOCANCEL /SP- /CLOSEAPPLICATIONS /RESTARTAPPLICATIONS"
184- Remove-Item $gitSetup - Force
185- $env: Path = [System.Environment ]::GetEnvironmentVariable(" Path" , " Machine" ) + " ;" + [System.Environment ]::GetEnvironmentVariable(" Path" , " User" )
186- } else {
187- Write-Host " 已检测到 Git,跳过安装" - ForegroundColor Green
174+ # ---------- 统一重启提示 ----------
175+ if ($needRestart ) {
176+ Write-Host " --------------------------------------------------" - ForegroundColor Yellow
177+ Write-Host " 依赖已安装/更新完成,但需重新加载环境变量。" - ForegroundColor Yellow
178+ Write-Host " 请关闭本窗口,重新打开『管理员』PowerShell 后再运行本脚本继续后续步骤。" - ForegroundColor Cyan
179+ Write-Host " --------------------------------------------------" - ForegroundColor Yellow
180+ Read-Host " 按 Enter 键退出"
181+ exit
188182}
189183
190- # -------------------------------------------------
191- # 8. 安装全局 npm 工具
192- # -------------------------------------------------
184+ # ------------------------------------------------------------------
185+ # 5. 阶段二:Node / yarn / pm2 / 克隆 / 配置
186+ # ------------------------------------------------------------------
187+ # 此时 nvm 已生效
188+ if (-not (Test-Cmd " node" ) -or -not (node - v).StartsWith(" v20." )) {
189+ Write-Host " 正在安装/切换到 Node 20.18.3 ..." - ForegroundColor Green
190+ nvm install 20.18 .3
191+ nvm use 20.18 .3
192+ }
193+
194+ # 安装 yarn / pm2
193195$tools = @ {
194196 yarn = { npm install - g yarn }
195197 pm2 = { npm install - g pm2 }
@@ -241,84 +243,123 @@ Use-ProxyIfNeeded -Script {
241243 [System.IO.File ]::WriteAllLines($configJson , $jsonText , $utf8NoBom )
242244 }
243245
244- # 生成 .env(UTF-8 无 BOM,不乱码 )
245- $envFile = Join-Path $projectPath " .env"
246- if (-not (Test-Path $envFile )) {
246+ # 生成 .env(UTF-8 无 BOM)
247+ $envFile = Join-Path $projectPath " .env"
248+ if (-not (Test-Path $envFile )) {
247249 # 如果仓库没带模板,就写一份最小模板(同样无 BOM)
248- $template = Join-Path $projectPath " .env.development"
249- if (-not (Test-Path $template )) {
250- @"
250+ $template = Join-Path $projectPath " .env.development"
251+ if (-not (Test-Path $template )) {
252+ @"
251253NODE_ENV=development
252254COOKIE_AUTH_CODE=drpys
253255API_AUTH_NAME=admin
254256API_AUTH_CODE=drpys
255257API_PWD=dzyyds
256258"@ | Out-File $template - Encoding UTF8
257- }
258-
259+ }
259260 # 复制模板
260- Copy-Item $template $envFile
261+ Copy-Item $template $envFile
261262
262263 # 依次输入
263- $cookieAuth = (Read-Host " 网盘入库密码(默认 drpys)" ).Trim()
264- $apiUser = (Read-Host " 登录用户名(默认 admin)" ).Trim()
265- $apiPass = (Read-Host " 登录密码(默认 drpys)" ).Trim()
266- $apiPwd = (Read-Host " 订阅PWD值(默认 dzyyds)" ).Trim()
264+ $cookieAuth = (Read-Host " 网盘入库密码(默认 drpys)" ).Trim()
265+ $apiUser = (Read-Host " 登录用户名(默认 admin)" ).Trim()
266+ $apiPass = (Read-Host " 登录密码(默认 drpys)" ).Trim()
267+ $apiPwd = (Read-Host " 订阅PWD值(默认 dzyyds)" ).Trim()
267268
268269 # 空值兜底
269- if ([string ]::IsNullOrWhiteSpace($cookieAuth )) { $cookieAuth = ' drpys' }
270- if ([string ]::IsNullOrWhiteSpace($apiUser )) { $apiUser = ' admin' }
271- if ([string ]::IsNullOrWhiteSpace($apiPass )) { $apiPass = ' drpys' }
272- if ([string ]::IsNullOrWhiteSpace($apiPwd )) { $apiPwd = ' dzyyds' }
270+ if ([string ]::IsNullOrWhiteSpace($cookieAuth )) { $cookieAuth = ' drpys' }
271+ if ([string ]::IsNullOrWhiteSpace($apiUser )) { $apiUser = ' admin' }
272+ if ([string ]::IsNullOrWhiteSpace($apiPass )) { $apiPass = ' drpys' }
273+ if ([string ]::IsNullOrWhiteSpace($apiPwd )) { $apiPwd = ' dzyyds' }
273274
274275 # 逐行替换,最后统一 UTF-8 无 BOM 写回
275- $utf8NoBom = [System.Text.UTF8Encoding ]::new($false )
276- $lines = [System.IO.File ]::ReadAllLines($template , $utf8NoBom )
277-
278- for ($i = 0 ; $i -lt $lines.Count ; $i ++ ) {
279- if ($lines [$i ] -match ' ^\s*COOKIE_AUTH_CODE\s*=' ) {
280- $lines [$i ] = " COOKIE_AUTH_CODE = $cookieAuth "
281- }
282- elseif ($lines [$i ] -match ' ^\s*API_AUTH_NAME\s*=' ) {
283- $lines [$i ] = " API_AUTH_NAME = $apiUser "
284- }
285- elseif ($lines [$i ] -match ' ^\s*API_AUTH_CODE\s*=' ) {
286- $lines [$i ] = " API_AUTH_CODE = $apiPass "
287- }
288- elseif ($lines [$i ] -match ' ^\s*API_PWD\s*=' ) {
289- $lines [$i ] = " API_PWD = $apiPwd "
276+ $utf8NoBom = [System.Text.UTF8Encoding ]::new($false )
277+ $lines = [System.IO.File ]::ReadAllLines($template , $utf8NoBom )
278+ for ($i = 0 ; $i -lt $lines.Count ; $i ++ ) {
279+ if ($lines [$i ] -match ' ^\s*COOKIE_AUTH_CODE\s*=' ) {
280+ $lines [$i ] = " COOKIE_AUTH_CODE = $cookieAuth "
281+ } elseif ($lines [$i ] -match ' ^\s*API_AUTH_NAME\s*=' ) {
282+ $lines [$i ] = " API_AUTH_NAME = $apiUser "
283+ } elseif ($lines [$i ] -match ' ^\s*API_AUTH_CODE\s*=' ) {
284+ $lines [$i ] = " API_AUTH_CODE = $apiPass "
285+ } elseif ($lines [$i ] -match ' ^\s*API_PWD\s*=' ) {
286+ $lines [$i ] = " API_PWD = $apiPwd "
287+ }
290288 }
289+ [System.IO.File ]::WriteAllLines($envFile , $lines , $utf8NoBom )
291290 }
292291
293- [System.IO.File ]::WriteAllLines($envFile , $lines , $utf8NoBom )
294- }
292+ # ---------- Node 依赖 ----------
293+ function Invoke-YarnWithRetry {
294+ param ([int ]$MaxRetry = 3 )
295+ $mirrors = @ (
296+ ' https://registry.npmmirror.com/' ,
297+ ' https://registry.yarnpkg.com' ,
298+ ' https://registry.npmjs.org'
299+ )
300+ $attempt = 0
301+ while ($attempt -lt $MaxRetry ) {
302+ $attempt ++
303+ $mirror = $mirrors [$attempt - 1 ]
304+ Write-Host " 尝试使用镜像 $mirror 安装 Node 依赖(第 $attempt /$MaxRetry 次)..." - ForegroundColor Cyan
305+ yarn config set registry $mirror | Out-Null
306+ try {
307+ yarn -- frozen- lockfile
308+ if ($LASTEXITCODE -eq 0 ) { return }
309+ } catch {}
310+ }
311+ Write-Host " [ERROR] 所有镜像均失败,请手动执行 yarn" - ForegroundColor Red
312+ }
295313
296- # Node 依赖
297314 if (-not (Test-Path " node_modules" )) {
298315 Write-Host " 首次安装 Node 依赖..." - ForegroundColor Yellow
299- yarn config set registry https:// registry.npmmirror.com /
300- yarn
301- } elseif ((git diff HEAD^ HEAD -- name- only 2> $null ) -match [regex ]::Escape(" yarn.lock" )) {
316+ Invoke-YarnWithRetry
317+ } elseif ((git diff HEAD~1 HEAD -- name- only 2> $null ) -match [regex ]::Escape(" yarn.lock" )) {
302318 Write-Host " 检测到 yarn.lock 变动,更新 Node 依赖..." - ForegroundColor Yellow
303- yarn install -- registry https: // registry.npmmirror.com /
319+ Invoke-YarnWithRetry
304320 }
305321
306- # Python 虚拟环境 & 依赖
322+ # ---------- Python 虚拟环境 & 依赖 ----------
307323 $venvActivate = Join-Path $projectPath " .venv\Scripts\Activate.ps1"
308324 if (-not (Test-Path " .venv\pyvenv.cfg" )) {
309325 Write-Host " 首次创建 Python 虚拟环境..." - ForegroundColor Yellow
310326 python - m venv .venv
311327 }
312328 & $venvActivate
313329 python - m pip install -- upgrade pip - q
314- pip install - r spider\py\base\requirements.txt - i https:// mirrors.cloud.tencent.com / pypi/ simple - q
330+ Write-Host " 虚拟环境创建完成" - ForegroundColor Green
331+ function Invoke-PipWithRetry {
332+ param (
333+ [string ]$ReqFile ,
334+ [int ]$MaxRetry = 3
335+ )
336+ $mirrors = @ (
337+ ' https://mirrors.cloud.tencent.com/pypi/simple' ,
338+ ' https://pypi.tuna.tsinghua.edu.cn/simple' ,
339+ ' https://pypi.org/simple'
340+ )
341+ $attempt = 0
342+ while ($attempt -lt $MaxRetry ) {
343+ $attempt ++
344+ $mirror = $mirrors [$attempt - 1 ]
345+ Write-Host " 尝试使用镜像 $mirror 安装 Python 依赖(第 $attempt /$MaxRetry 次)..." - ForegroundColor Cyan
346+ try {
347+ pip install - r $ReqFile - i $mirror -- no- warn- script- location - q
348+ Write-Host " pip install 完成" - ForegroundColor Green
349+ if ($LASTEXITCODE -eq 0 ) { return }
350+ } catch {}
351+ }
352+ Write-Host " [ERROR] 所有镜像均失败,请手动执行 pip install" - ForegroundColor Red
353+ }
354+
355+ Invoke-PipWithRetry " spider\py\base\requirements.txt"
315356
316- if ((git diff HEAD^ HEAD -- name- only 2> $null ) -match [regex ]::Escape(" spider\py\base\requirements.txt" )) {
357+ if ((git diff HEAD~ 1 HEAD -- name- only 2> $null ) -match [regex ]::Escape(" spider\py\base\requirements.txt" )) {
317358 Write-Host " 检测到 requirements.txt 变动,更新 Python 依赖..." - ForegroundColor Yellow
318- pip install - r spider\py\base\requirements.txt - i https: // mirrors.cloud.tencent.com / pypi / simple - q
359+ Invoke-PipWithRetry " spider\py\base\requirements.txt"
319360 }
320361
321- # PM2
362+ # ---------- PM2 ----------
322363 if (-not (pm2 list | Select-String " drpyS.*online" )) {
323364 Write-Host " 首次启动 PM2 进程..." - ForegroundColor Yellow
324365 pm2 start index.js -- name drpyS -- update-env
0 commit comments