目录
- 背景与需求
- 技术方案设计
- [Vue CLI 配置详解](#Vue CLI 配置详解 "#vue-cli-%E9%85%8D%E7%BD%AE%E8%AF%A6%E8%A7%A3")
- [OSS 工具安装与配置](#OSS 工具安装与配置 "#oss-%E5%B7%A5%E5%85%B7%E5%AE%89%E8%A3%85%E4%B8%8E%E9%85%8D%E7%BD%AE")
- 上传脚本编写
- 部署流程
- 注意事项与最佳实践
- 问题排查
背景与需求
业务场景
在大型前端项目的生产环境部署中,静态资源(JS、CSS、字体文件等)通常占据较大的体积。将这些资源部署到对象存储服务(OSS)并通过 CDN 加速,可以带来以下优势:
- 减轻服务器压力:静态资源不再占用应用服务器的带宽和存储
- 提升访问速度:CDN 边缘节点就近分发,降低延迟
- 降低服务器成本:减少服务器带宽和存储需求
- 提高可用性:CDN 的高可用性保障
需求分析
我们的项目需要实现以下功能:
- 环境区分:测试环境使用本地资源,生产环境使用 OSS CDN
- 选择性上传 :只上传
static/css/、static/fonts/、static/js/目录 - 路径处理 :
favicon.ico和styles/目录不上传 OSS,使用相对路径 - 自动化部署:通过脚本自动化完成上传流程
- 版本管理:通过文件 hash 实现缓存控制
技术方案设计
架构设计
scss
┌─────────────────┐
│ 本地开发环境 │ → 相对路径 (/)
└─────────────────┘
┌─────────────────┐
│ 测试环境构建 │ → 相对路径 (/) → 直接部署
└─────────────────┘
┌─────────────────┐
│ 生产环境构建 │ → CDN 路径 (https://cdn.example.com/)
└─────────────────┘
│
├─→ static/css/ → 上传 OSS
├─→ static/fonts/ → 上传 OSS
├─→ static/js/ → 上传 OSS
├─→ favicon.ico → 不上传(相对路径)
└─→ styles/ → 不上传(相对路径)
技术选型
- 构建工具:Vue CLI(基于 Webpack)
- OSS 工具:ossutil(阿里云官方命令行工具)
- 脚本语言:Bash Shell
- 路径处理:Webpack 插件 + 自定义 HTML 修改插件
Vue CLI 配置详解
1. publicPath 动态配置
publicPath 决定了构建后资源的引用路径。我们需要根据环境变量动态设置:
javascript
module.exports = {
publicPath: process.env.CDN_URL ||
(process.env.NODE_ENV === 'development' ? '/' : // 本地开发环境
process.env.VUE_APP_PATH_TYPE === 'gray' ? '/' : // 测试环境使用相对路径
'https://js-cdn.m.cc/'), // 生产环境使用 CDN
outputDir: 'dist',
assetsDir: 'static',
filenameHashing: true,
}
关键点说明:
assetsDir: 'static':所有资源输出到static/目录filenameHashing: true:启用文件名 hash,实现缓存控制- 环境判断逻辑:
- 开发环境:始终使用相对路径
- 测试环境(
VUE_APP_PATH_TYPE === 'gray'):使用相对路径 - 生产环境:使用 CDN 路径
2. 输出文件名配置
为了确保文件路径的一致性,需要显式设置输出文件名:
javascript
configureWebpack: config => {
// 统一使用 static/js/ 路径,与 assetsDir 保持一致
config.output.filename = 'static/js/[name].[hash:8].js'
config.output.chunkFilename = 'static/js/[name].[hash:8].js'
}
为什么需要显式设置?
虽然 assetsDir: 'static' 会自动处理路径,但显式设置可以:
- 确保所有环境路径一致
- 避免不同 Webpack 版本的差异
- 便于后续维护和理解
3. HTML 路径修改插件
生产环境需要特殊处理:静态资源使用 CDN,但 favicon.ico 和 styles/ 使用相对路径。
实现原理:
在 Webpack 的 emit 阶段(生成文件后,写入磁盘前),通过自定义插件修改 HTML 内容:
javascript
chainWebpack: config => {
// 只在生产环境(非测试环境)运行
if (process.env.NODE_ENV === 'production' && process.env.VUE_APP_PATH_TYPE !== 'gray') {
class ModifyHtmlPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('ModifyHtmlPlugin', (compilation, callback) => {
Object.keys(compilation.assets).forEach(filename => {
if (filename.endsWith('.html')) {
let html = compilation.assets[filename].source()
// 1. 先处理 BASE_URL 变量(如果还没被 Vue CLI 替换)
html = html.replace(/href="<%= BASE_URL %>favicon\.ico"/g, 'href="/favicon.ico"')
html = html.replace(/href="<%= BASE_URL %>styles\/([^"]+)"/g, 'href="/styles/$1"')
// 2. 替换其他 BASE_URL 变量为 CDN 路径
const baseUrl = process.env.CDN_URL || 'https://js-cdn.m.cc/'
html = html.replace(/<%= BASE_URL %>/g, baseUrl)
// 3. 最后处理已经被替换的 CDN 路径(将 favicon 和 styles 改回相对路径)
html = html.replace(/href="https:\/\/js-cdn\.modb\.cc\/favicon\.ico"/g, 'href="/favicon.ico"')
html = html.replace(/href="https:\/\/js-cdn\.modb\.cc\/styles\/([^"]+)"/g, 'href="/styles/$1"')
compilation.assets[filename] = {
source: () => html,
size: () => html.length
}
}
})
callback()
})
}
}
config.plugin('modify-html').use(ModifyHtmlPlugin)
}
}
处理顺序的重要性:
- 先处理
BASE_URL变量(模板阶段) - 替换为 CDN 路径
- 最后将特定文件改回相对路径
这样可以确保所有路径都被正确处理。
4. 代码分割优化
为了提升加载性能,我们配置了代码分割:
javascript
config.optimization.splitChunks = {
chunks: 'all',
minSize: 20000,
maxAsyncRequests: 20,
maxInitialRequests: 15,
cacheGroups: {
// Vue 核心库单独拆分
vueCore: {
name: 'chunk-vue-core',
test: /[\\/]node_modules[\\/](vue|vue-router|vuex|vue-i18n)[\\/]/,
priority: 40,
chunks: 'initial',
enforce: true
},
// 大型 UI 库单独拆分
elementPlus: {
name: 'chunk-element-plus',
test: /[\\/]node_modules[\\/]element-plus[\\/]/,
priority: 35,
chunks: 'initial',
enforce: true
},
// 其他配置...
}
}
优势:
- 大型库单独拆分,便于缓存
- 按需加载的库设置为
async,减少初始加载体积 - 公共代码提取,避免重复打包
OSS 工具安装与配置
1. 确定服务器架构
首先需要确定服务器的 CPU 架构,以选择正确的 ossutil 版本:
bash
uname -m
# 输出示例:
# x86_64 → 使用 ossutil64
# aarch64 → 使用 ossutilarm64
2. 下载 ossutil
根据架构下载对应版本(以阿里云 OSS 为例):
bash
# 创建目录
mkdir -p /root/oss
cd /root/oss
# 下载对应版本(示例:ARM64 架构)
wget https://gosspublic.alicdn.com/ossutil/1.7.13/ossutilarm64
# 或者使用 curl
curl -o ossutilarm64 https://gosspublic.alicdn.com/ossutil/1.7.13/ossutilarm64
# 添加执行权限
chmod +x ossutilarm64
# 验证安装
/root/oss/ossutilarm64 --version
3. 配置 OSS 凭证
ossutil 支持两种配置方式:
方式一:命令行配置(临时)
bash
/root/oss/ossutilarm64 config -e <endpoint> \
-i <accessKeyId> \
-k <accessKeySecret>
方式二:配置文件(推荐)
创建配置文件 ~/.ossutilconfig:
bash
cat > /root/.ossutilconfig << 'EOF'
[Credentials]
language=EN
endpoint=oss-cn-hangzhou.aliyuncs.com
accessKeyID=YOUR_ACCESS_KEY_ID
accessKeySecret=YOUR_ACCESS_KEY_SECRET
EOF
# 设置文件权限(安全考虑)
chmod 600 /root/.ossutilconfig
配置说明:
language=EN:使用英文输出,避免编码问题endpoint:OSS 区域节点地址accessKeyID和accessKeySecret:OSS 访问凭证
4. 测试连接
bash
# 测试连接
/root/oss/ossutilarm64 ls oss://your-bucket-name/
# 如果成功,会显示 OSS 上的文件列表
上传脚本编写
脚本结构设计
上传脚本需要包含以下功能:
- 环境检查:验证构建输出是否存在
- 连接测试:确保 OSS 连接正常
- 清理旧文件:删除 OSS 上的旧版本文件
- 上传新文件:上传指定的目录
- 验证上传:确认上传成功
完整脚本示例
bash
#!/bin/bash
set -e # 遇到错误立即退出
echo "Starting OSS upload for production environment..."
# ========== 1. OSS 配置 ==========
# 如果使用配置文件,可以跳过此步骤
# 如果需要动态配置,使用以下命令:
# /root/oss/ossutilarm64 config -e <endpoint> \
# -i <accessKeyId> \
# -k <accessKeySecret>
# ========== 2. 测试 OSS 连接 ==========
echo "Testing OSS connection..."
if /root/oss/ossutilarm64 ls oss://your-bucket-name/ 2>/dev/null > /dev/null; then
echo "OSS connection test passed"
else
echo "Warning: OSS connection test failed, but continuing..."
fi
# ========== 3. 设置构建路径 ==========
DIST_PATH="/u01/dist"
# ========== 4. 检查构建输出 ==========
if [ ! -d "$DIST_PATH" ]; then
echo "Error: $DIST_PATH not found"
exit 1
fi
echo "Build path: $DIST_PATH"
# 检查 static 目录
if [ ! -d "$DIST_PATH/static" ]; then
echo "Error: $DIST_PATH/static not found"
exit 1
fi
# 检查关键文件
if [ ! -f "$DIST_PATH/index.html" ]; then
echo "Error: $DIST_PATH/index.html not found"
exit 1
fi
if [ ! -f "$DIST_PATH/favicon.ico" ]; then
echo "Warning: $DIST_PATH/favicon.ico not found"
fi
if [ ! -d "$DIST_PATH/styles" ]; then
echo "Warning: $DIST_PATH/styles directory not found"
fi
# ========== 5. 清理 OSS 上的旧文件 ==========
echo ""
echo "Cleaning old files from OSS..."
# 删除 OSS 上的 css 目录(使用 -f 参数避免交互式提示)
echo " Deleting old CSS files..."
/root/oss/ossutilarm64 rm -r oss://your-bucket-name/static/css/ -f 2>/dev/null || echo " (CSS directory may not exist, skipping)"
# 删除 OSS 上的 fonts 目录
echo " Deleting old fonts files..."
/root/oss/ossutilarm64 rm -r oss://your-bucket-name/static/fonts/ -f 2>/dev/null || echo " (Fonts directory may not exist, skipping)"
# 删除 OSS 上的 js 目录
echo " Deleting old JS files..."
/root/oss/ossutilarm64 rm -r oss://your-bucket-name/static/js/ -f 2>/dev/null || echo " (JS directory may not exist, skipping)"
echo "Old files cleaned (if any existed)"
echo ""
# ========== 6. 上传新文件 ==========
# 上传 static/css/ 目录
if [ -d "$DIST_PATH/static/css" ]; then
echo "Uploading static/css/ directory..."
/root/oss/ossutilarm64 cp -r $DIST_PATH/static/css/ oss://your-bucket-name/static/css/ -f
if [ $? -eq 0 ]; then
echo "CSS directory uploaded successfully"
else
echo "Failed to upload CSS directory"
exit 1
fi
else
echo "Warning: $DIST_PATH/static/css not found, skipping..."
fi
# 上传 static/fonts/ 目录
if [ -d "$DIST_PATH/static/fonts" ]; then
echo "Uploading static/fonts/ directory..."
/root/oss/ossutilarm64 cp -r $DIST_PATH/static/fonts/ oss://your-bucket-name/static/fonts/ -f
if [ $? -eq 0 ]; then
echo "Fonts directory uploaded successfully"
else
echo "Failed to upload fonts directory"
exit 1
fi
else
echo "Warning: $DIST_PATH/static/fonts not found, skipping..."
fi
# 上传 static/js/ 目录
if [ -d "$DIST_PATH/static/js" ]; then
echo "Uploading static/js/ directory..."
/root/oss/ossutilarm64 cp -r $DIST_PATH/static/js/ oss://your-bucket-name/static/js/ -f
if [ $? -eq 0 ]; then
echo "JS directory uploaded successfully"
else
echo "Failed to upload JS directory"
exit 1
fi
else
echo "Error: $DIST_PATH/static/js not found (required)"
exit 1
fi
# ========== 7. 验证上传 ==========
echo ""
echo "Verifying upload..."
# 统计上传的文件数量
JS_COUNT=$(/root/oss/ossutilarm64 ls oss://your-bucket-name/static/js/ 2>/dev/null | wc -l)
CSS_COUNT=$(/root/oss/ossutilarm64 ls oss://your-bucket-name/static/css/ 2>/dev/null | wc -l)
FONTS_COUNT=$(/root/oss/ossutilarm64 ls oss://your-bucket-name/static/fonts/ 2>/dev/null | wc -l)
echo ""
echo "Upload completed!"
echo "Upload statistics:"
echo " JS files: $JS_COUNT"
echo " CSS files: $CSS_COUNT"
echo " Fonts files: $FONTS_COUNT"
# 检查关键文件是否存在
echo ""
echo "Checking key files..."
JS_CHECK=$(/root/oss/ossutilarm64 ls oss://your-bucket-name/static/js/app.*.js 2>/dev/null | head -1 | wc -l)
CSS_CHECK=$(/root/oss/ossutilarm64 ls oss://your-bucket-name/static/css/app.*.css 2>/dev/null | head -1 | wc -l)
if [ "$JS_CHECK" -gt 0 ] && [ "$CSS_CHECK" -gt 0 ]; then
echo "Key files app.js and app.css found in OSS"
echo "Upload verification passed!"
else
echo "Warning: Some key files may be missing"
fi
echo ""
echo "OSS upload process completed!"
脚本关键点说明
1. set -e 的作用
bash
set -e
遇到任何错误立即退出,避免继续执行可能导致的问题。
2. 使用 -f 参数
bash
ossutilarm64 rm -r oss://bucket/path/ -f
ossutilarm64 cp -r local/path/ oss://bucket/path/ -f
-f 参数的作用:
rm -r -f:强制删除,不询问确认cp -r -f:强制覆盖,不询问确认
为什么重要:在自动化脚本中,交互式提示会导致脚本挂起。
3. 错误处理
bash
command 2>/dev/null || echo "fallback message"
2>/dev/null:隐藏错误输出||:如果命令失败,执行后续命令- 适用于首次运行(目录可能不存在)的场景
4. 变量命名注意事项
避免使用可能被系统识别的变量名:
bash
# ❌ 不推荐(可能被某些系统识别为系统变量)
BUILD_PATH="/u01/dist"
# ✅ 推荐
DIST_PATH="/u01/dist"
5. 文件计数验证
bash
JS_COUNT=$(ossutilarm64 ls oss://bucket/static/js/ 2>/dev/null | wc -l)
使用 wc -l 统计文件数量,用于验证上传是否成功。
部署流程
测试环境部署
测试环境使用相对路径,不需要上传 OSS:
bash
# 1. 构建项目
npm run gray
# 2. 打包
tar -czf dist.gz dist/
# 3. 上传到服务器并解压
scp dist.gz user@server:/path/to/deploy/
ssh user@server "cd /path/to/deploy && tar -xzf dist.gz"
生产环境部署
生产环境需要上传资源到 OSS:
bash
# 1. 构建项目
npm run build
# 2. 打包
tar -czf dist.gz dist/
# 3. 上传到服务器
scp dist.gz user@server:/u01/
scp upload-to-oss-production.sh user@server:/u01/
# 4. 在服务器上执行部署脚本
ssh user@server << 'EOF'
cd /u01
rm -rf dist && tar -zxvf ./dist.gz
chmod +x upload-to-oss-production.sh
./upload-to-oss-production.sh
EOF
完整部署脚本示例
可以将解压和上传合并到一个脚本中:
bash
#!/bin/bash
set -e
# 解压构建文件
cd /u01
rm -rf dist && tar -zxvf ./dist.gz
# 执行 OSS 上传脚本
./upload-to-oss-production.sh
# 部署到 Web 服务器(Nginx 示例)
# cp -r dist/* /usr/share/nginx/html/
# systemctl reload nginx
注意事项与最佳实践
1. 安全性
凭证管理
- 不要在脚本中硬编码凭证
- 使用环境变量或配置文件
- 设置 配置文件权限为
600
bash
chmod 600 ~/.ossutilconfig
最小权限原则
为 OSS 访问密钥设置最小必要权限:
- 只允许上传到指定目录
- 禁止删除其他目录的文件
2. 性能优化
并行上传
对于大量文件,可以考虑并行上传:
bash
# 使用后台任务并行上传
/root/oss/ossutilarm64 cp -r $DIST_PATH/static/css/ oss://bucket/static/css/ -f &
/root/oss/ossutilarm64 cp -r $DIST_PATH/static/fonts/ oss://bucket/static/fonts/ -f &
/root/oss/ossutilarm64 cp -r $DIST_PATH/static/js/ oss://bucket/static/js/ -f &
# 等待所有任务完成
wait
增量上传
如果文件很多,可以使用 --update 参数进行增量上传:
bash
ossutilarm64 cp -r local/path/ oss://bucket/path/ --update
但需要注意:如果文件名包含 hash,每次构建都是新文件,增量上传意义不大。
3. 缓存策略
HTTP 缓存头
在 OSS 控制台或通过 API 设置缓存头:
arduino
Cache-Control: public, max-age=31536000
对于带 hash 的文件名,可以设置长期缓存。
CDN 缓存
如果使用 CDN,需要配置:
- 缓存规则:根据文件类型和路径
- 缓存刷新:更新后及时刷新 CDN 缓存
4. 监控与日志
上传日志
记录上传过程的关键信息:
bash
LOG_FILE="/var/log/oss-upload.log"
echo "$(date): Starting OSS upload" >> $LOG_FILE
# ... 上传过程 ...
echo "$(date): OSS upload completed" >> $LOG_FILE
监控告警
- 监控上传失败率
- 监控 OSS 存储使用量
- 监控 CDN 流量
5. 回滚策略
保留历史版本
在删除旧文件前,可以先备份:
bash
# 备份当前版本
BACKUP_DIR="backup/$(date +%Y%m%d_%H%M%S)"
/root/oss/ossutilarm64 cp -r oss://bucket/static/ oss://bucket/$BACKUP_DIR/ -f
快速回滚
如果需要回滚,可以从备份恢复:
bash
/root/oss/ossutilarm64 cp -r oss://bucket/$BACKUP_DIR/static/ oss://bucket/static/ -f
问题排查
1. 路径问题
问题:HTML 中的路径不正确
症状:浏览器控制台显示 404 错误
排查步骤:
- 检查
dist/index.html中的路径 - 确认
publicPath配置是否正确 - 验证
ModifyHtmlPlugin是否正常运行
解决方案:
bash
# 检查构建后的 HTML
cat dist/index.html | grep -E '(href|src)='
# 检查环境变量
echo $NODE_ENV
echo $VUE_APP_PATH_TYPE
2. OSS 连接问题
问题:ossutil 连接失败
症状 :ossutil: No such file or directory 或连接超时
排查步骤:
- 检查 ossutil 是否安装
- 检查网络连接
- 验证凭证是否正确
解决方案:
bash
# 检查 ossutil 是否存在
ls -la /root/oss/ossutilarm64
# 测试网络连接
ping oss-cn-hangzhou.aliyuncs.com
# 测试 OSS 连接
/root/oss/ossutilarm64 ls oss://bucket-name/
3. 上传失败
问题:上传过程中断或失败
症状:脚本执行失败,部分文件未上传
排查步骤:
- 检查磁盘空间
- 检查网络稳定性
- 查看 OSS 控制台的错误日志
解决方案:
bash
# 检查磁盘空间
df -h
# 检查网络
curl -I https://oss-cn-hangzhou.aliyuncs.com
# 重试上传
./upload-to-oss-production.sh
4. 文件权限问题
问题:ossutil 无执行权限
症状 :Permission denied
解决方案:
bash
chmod +x /root/oss/ossutilarm64
5. 脚本语法问题
问题:脚本执行报语法错误
症状 :syntax error: unexpected EOF 等
常见原因:
- 括号不匹配
- 引号未闭合
- 变量名包含特殊字符
解决方案:
bash
# 检查脚本语法
bash -n upload-to-oss-production.sh
# 使用 shellcheck 检查(如果已安装)
shellcheck upload-to-oss-production.sh
6. 环境变量问题
问题:构建时路径不正确
症状:测试环境使用了 CDN 路径,或生产环境使用了相对路径
排查步骤:
bash
# 检查 package.json 中的脚本
cat package.json | grep -A 5 '"scripts"'
# 确认环境变量设置
# 测试环境应该设置 VUE_APP_PATH_TYPE=gray
# 生产环境不应该设置此变量
总结
本文详细介绍了 Vue.js 项目将静态资源部署到 OSS 的完整实现过程,包括:
- 配置层面:通过 Vue CLI 配置实现不同环境的路径处理
- 工具层面:OSS 命令行工具的安装和配置
- 脚本层面:自动化上传脚本的编写
- 部署层面:完整的部署流程
关键要点
- 环境区分:测试环境使用相对路径,生产环境使用 CDN
- 选择性上传:只上传必要的静态资源目录
- 路径处理:通过自定义插件处理特殊文件的路径
- 自动化:通过脚本实现一键部署
- 安全性:妥善管理 OSS 凭证
- 可维护性:清晰的代码结构和完善的错误处理
扩展建议
- CI/CD 集成:将上传脚本集成到 CI/CD 流程中
- 监控告警:添加监控和告警机制
- 多环境支持:支持多个生产环境的配置
- 版本管理:实现更完善的版本管理和回滚机制
希望本文能帮助您成功实现静态资源的 OSS 部署!