背景

之前为了学习windows的bat脚本,我在AI的帮助下写了一个将前端包上传到服务器的部署脚本,使用起来还算方便,相比于 GitHub Actions 在代码推送时强制触发的自动化流程,我这套“半自动”方案可以自由控制什么时候发新版本,具有更高的灵活性。

由于最近在脚本中集成了刷新cdn的js脚本,考虑到前端开发者的技术栈偏好与代码的可维护性,于是想着把整套部署流程全部用nodejs脚本实现,毕竟作为一个前端开发者,js是我更熟悉的语言,具有更好的可读性。本文的目的只是在这里将现有的bat脚本存个档,或许它在未来的某一天会发挥它的价值也说不定。

代码

deploy.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
@echo off
setlocal enabledelayedexpansion

:: ==========================================
:: 前端部署脚本 (交互模式版)
:: ==========================================

cd /d "%~dp0"

echo ==========================================
echo 开始执行前端部署...
echo ==========================================
echo [提示] 如果出现 "Store key in cache?" 询问,请输 y 并回车。
echo ==========================================

:: --- 1. 环境检查 ---
where tar >nul 2>nul
if %errorlevel% neq 0 (
echo [错误] 未找到 tar 命令。
pause
exit /b 1
)

:: --- 2. 读取配置 ---
set "configFile=config.ini"
if not exist "%configFile%" (
echo [错误] 找不到配置文件: %configFile%
pause
exit /b 1
)

:: 初始化变量
set "server_ip="
set "username="
set "local_dir="
set "remote_dir="
set "private_key="

:: 解析 INI 文件
for /f "usebackq tokens=1,* delims==" %%a in ("%configFile%") do (
set "key=%%a"
set "val=%%b"
for /f "tokens=* delims= " %%k in ("!key!") do set "key=%%k"

if /i "!key!"=="server_ip" set "server_ip=!val!"
if /i "!key!"=="username" set "username=!val!"
if /i "!key!"=="local_dir" set "local_dir=!val!"
if /i "!key!"=="remote_dir" set "remote_dir=!val!"
if /i "!key!"=="private_key" set "private_key=!val!"
if /i "!key!"=="cdn_secret_id" set "cdn_secret_id=!val!"
if /i "!key!"=="cdn_secret_key" set "cdn_secret_key=!val!"
)

:: 清洗变量 (去引号、去斜杠)
call :CleanVar server_ip
call :CleanVar username
call :CleanVar local_dir
call :CleanVar remote_dir
call :CleanVar private_key

:: --- 3. 验证配置 ---
if "%server_ip%"=="" goto ConfigError
if "%username%"=="" goto ConfigError
if "%local_dir%"=="" goto ConfigError
if "%remote_dir%"=="" goto ConfigError
if "%private_key%"=="" goto ConfigError

echo ------------------------------------------
echo 服务器IP : %server_ip%
echo 本地源码 : %local_dir%
echo 远程路径 : %remote_dir%
echo ------------------------------------------
echo 按下 Enter 键开始部署...
pause >nul

:: --- 4. 打包 ---
echo.
echo [1/4] 正在压缩文件...

if not exist "%local_dir%" (
echo [错误] 本地目录不存在: %local_dir%
pause
exit /b 1
)

set "distFile=%~dp0dist.tar.gz"
if exist "%distFile%" del "%distFile%"

tar -czf "%distFile%" -C "%local_dir%" .
if %errorlevel% neq 0 (
echo [错误] 压缩失败!
pause
exit /b 1
)
echo OK.

:: --- 5. 清理远程目录 (交互式) ---
echo.
echo [2/4] 清理远程目录...
if "%remote_dir%"=="/" (
echo [错误] 禁止删除根目录
pause
exit /b 1
)

rem 注意:这里去掉了 -batch,如果有询问,请在窗口输入 y
plink -i "%private_key%" %username%@%server_ip% "mkdir -p %remote_dir% && rm -rf %remote_dir%/*"

if %errorlevel% neq 0 (
echo [错误] 清理失败!
pause
exit /b 1
)
echo OK.

:: --- 6. 上传 (交互式) ---
echo.
echo [3/4] 上传文件...

rem 注意:这里去掉了 -batch
pscp -i "%private_key%" "%distFile%" %username%@%server_ip%:%remote_dir%/dist.tar.gz

if %errorlevel% neq 0 (
echo [错误] 上传失败!
pause
exit /b 1
)
echo OK.

:: --- 7. 解压 (交互式) ---
echo.
echo [4/4] 远程解压...

rem 注意:这里去掉了 -batch
plink -i "%private_key%" %username%@%server_ip% "cd %remote_dir% && tar -xzf dist.tar.gz && rm -f dist.tar.gz"

if %errorlevel% neq 0 (
echo [错误] 解压失败!
pause
exit /b 1
)
echo OK.
:: 完成

:: --- 8. 刷新CDN缓存 ---
echo.
echo [5/5] 刷新CDN缓存...

:: 检查密钥是否存在
if "%cdn_secret_id%"=="" (
echo [警告] 未配置 secret_id,跳过 CDN 刷新。
goto :SkipCDN
)
if "%cdn_secret_key%"=="" (
echo [警告] 未配置 secret_key,跳过 CDN 刷新。
goto :SkipCDN
)

:: 设置临时环境变量 (仅对当前 session 有效,不会污染系统变量)
:: 注意:变量名必须与 refreshCDN.js 中 process.env.后使用的名称一致
set "TENCENTCLOUD_SECRET_ID=%cdn_secret_id%"
set "TENCENTCLOUD_SECRET_KEY=%cdn_secret_key%"

call node "%~dp0refreshCDN.js"
if %errorlevel% neq 0 (
echo [错误] CDN刷新失败!
pause
exit /b 1
)
echo OK.
:: 完成

if exist "%distFile%" del "%distFile%"
echo.
echo ==========================================
echo 部署成功!
echo ==========================================
pause
exit /b 0

:: --- 子程序 ---
:CleanVar
set "tmpVal=!%1!"
if "!tmpVal!"=="" goto :eof
set "tmpVal=!tmpVal:"=!"
if "!tmpVal:~-1!"=="\" set "tmpVal=!tmpVal:~0,-1!"
if "!tmpVal:~-1!"=="/" set "tmpVal=!tmpVal:~0,-1!"
set "tmpVal=!tmpVal: =!"
set "%1=!tmpVal!"
goto :eof

:ConfigError
echo [错误] 配置文件变量缺失。
pause
exit /b 1

refreshCDN.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// Depends on tencentcloud-sdk-nodejs version 4.0.3 or higher

const tencentcloud = require("tencentcloud-sdk-nodejs-cdn")

const CdnClient = tencentcloud.cdn.v20180606.Client

// 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
// 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性
// 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等
// 请参见:https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
const clientConfig = {
credential: {
secretId: process.env.TENCENTCLOUD_SECRET_ID,
secretKey: process.env.TENCENTCLOUD_SECRET_KEY,
},
region: "ap-chengdu",
profile: {
httpProfile: {
endpoint: "cdn.tencentcloudapi.com",
},
},
}

// 实例化要请求产品的client对象,clientProfile是可选的
const client = new CdnClient(clientConfig)
const params = {
Paths: ["https://cyanfish.site/"],
FlushType: "flush",
}
client.PurgePathCache(params).then(
(data) => {
console.log(data)
},
(err) => {
console.error("error", err)
}
)

js代码来自腾讯云sdk官方文档: https://console.cloud.tencent.com/api/explorer?Product=cdn&Version=2018-06-06&Action=PurgePathCache