无限极服务
146.43MB · 2025-10-24
在Vue项目根目录下创建不同的环境配置文件:
# .env.development - 开发环境配置文件
NODE_ENV=development # 设置Node.js环境为开发环境
VUE_APP_API_URL=http://localhost:3000 # 开发环境的后端API地址
VUE_APP_CURRENTMODE=development # 当前环境模式标识
VUE_APP_VERSION=1.0.0-dev # 开发版本号
# .env.test - 测试环境配置文件
NODE_ENV=production # 设置为生产环境(测试环境也使用生产模式构建)
VUE_APP_API_URL=http://test.api.example.com # 测试环境的后端API地址
VUE_APP_CURRENTMODE=test # 当前环境模式标识
outputDir=dist-test # 测试环境构建输出目录名
VUE_APP_VERSION=1.0.0-test # 测试版本号
# .env.production - 生产环境配置文件
NODE_ENV=production # 设置Node.js环境为生产环境
VUE_APP_API_URL=http://api.example.com # 生产环境的后端API地址
VUE_APP_CURRENTMODE=production # 当前环境模式标识
outputDir=dist # 生产环境构建输出目录名
VUE_APP_VERSION=1.0.0 # 生产版本号
环境变量使用说明:
// 在Vue组件中使用环境变量
const apiUrl = process.env.VUE_APP_API_URL; // 获取API地址
const currentMode = process.env.VUE_APP_CURRENTMODE; // 获取当前环境模式
const version = process.env.VUE_APP_VERSION; // 获取版本号
// 根据环境执行不同逻辑
if (process.env.NODE_ENV === 'development') {
// 开发环境特定逻辑
console.log('当前处于开发环境');
}
{
"scripts": {
"serve": "vue-cli-service serve", // 启动开发服务器
"build:test": "vue-cli-service build --mode test", // 构建测试环境版本
"build": "vue-cli-service build", // 构建生产环境版本(默认)
"build:analyze": "vue-cli-service build --mode analyze", // 构建并分析包大小
"preview": "vite preview --port 4173", // 预览生产构建
"deploy:test": "npm run build:test && node ./scripts/deploy-test.js", // 部署到测试环境
"deploy:prod": "npm run build && node ./scripts/deploy-prod.js", // 部署到生产环境
"test:unit": "vitest", // 运行单元测试
"test:e2e": "cypress run" // 运行端到端测试
}
}
创建或修改vue.config.js文件:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
// 部署应用包时的基本URL
// 如果你打算将项目部署到子路径(如:https://www.example.com/my-app/),需要设置这个值
// 开发环境通常为 '/',生产环境根据实际部署路径设置
publicPath: process.env.NODE_ENV === 'production' ? '/your-project/' : '/',
// 构建输出目录(打包后文件存放的文件夹)
// 默认是 'dist',可以通过环境变量动态设置
outputDir: process.env.outputDir || 'dist',
// 放置生成的静态资源(js、css、img、fonts)的目录
// 相对于 outputDir 的路径
assetsDir: 'static',
// 是否在生产环境构建中生成 source map
// true:会生成,便于调试但文件更大
// false:不生成,生产环境推荐关闭以提高性能和安全性
productionSourceMap: false,
// 配置CSS相关选项
css: {
// 是否将组件中的CSS提取到独立的CSS文件中
// 生产环境建议true,开发环境建议false(以便支持热重载)
extract: true,
// 是否为CSS开启source map
// 生产环境建议关闭以提高性能
sourceMap: false,
// 向CSS loader传递选项
loaderOptions: {
sass: {
// 全局注入sass变量和mixins,这样在所有组件中都可以使用
additionalData: `@import "@/styles/variables.scss";`
}
}
},
// 开发服务器配置
devServer: {
port: 8080, // 开发服务器端口
host: 'localhost', // 开发服务器主机名
open: true, // 启动后是否自动打开浏览器
proxy: { // 配置代理,解决开发环境跨域问题
'/api': {
target: 'http://localhost:3000', // 后端API地址
changeOrigin: true, // 允许跨域
pathRewrite: {
'^/api': '' // 重写路径,去掉/api前缀
}
}
}
},
// 配置Webpack
configureWebpack: {
// 配置解析别名
resolve: {
alias: {
'@': path.resolve(__dirname, 'src') // 将@指向src目录
}
},
// 生产环境特定配置
...(process.env.NODE_ENV === 'production' ? {
// 生产环境优化配置
optimization: {
splitChunks: {
chunks: 'all', // 对所有类型的chunk进行分割
cacheGroups: {
vendor: {
name: 'chunk-vendors', // 第三方库打包成的chunk名
test: /[\/]node_modules[\/]/, // 从node_modules引入的库
priority: 10, // 优先级,数值越大优先级越高
chunks: 'initial' // 只打包初始依赖
},
common: {
name: 'chunk-common', // 公共代码chunk名
minChunks: 2, // 至少被2个chunk引用的模块
priority: 5, // 优先级
chunks: 'initial' // 只打包初始依赖
}
}
}
}
} : {})
// 开发环境使用默认配置
},
// 链式操作Webpack配置(更细粒度的控制)
chainWebpack: config => {
// 配置HTML模板
config.plugin('html').tap(args => {
args[0].title = '我的Vue应用' // 设置HTML标题
args[0].minify = { // 生产环境HTML压缩配置
removeComments: true, // 移除注释
collapseWhitespace: true, // 折叠空白字符
removeAttributeQuotes: true // 移除属性引号
}
return args
})
}
})
创建vite.config.js文件:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
// 插件配置
plugins: [vue()],
// 项目根目录
root: process.cwd(),
// 开发服务器配置
server: {
port: 3000, // 开发服务器端口
host: 'localhost', // 开发服务器主机
open: true, // 启动时自动打开浏览器
cors: true, // 允许跨域
proxy: { // 代理配置
'/api': {
target: 'http://localhost:8080', // 后端API地址
changeOrigin: true, // 允许跨域
rewrite: (path) => path.replace(/^/api/, '') // 路径重写
}
}
},
// 构建配置
build: {
outDir: 'dist', // 输出目录
assetsDir: 'assets', // 静态资源目录
sourcemap: false, // 是否生成source map
minify: 'terser', // 代码压缩工具
terserOptions: { // terser压缩配置
compress: {
drop_console: true, // 移除console.log
drop_debugger: true // 移除debugger
}
},
rollupOptions: { // Rollup打包配置
output: {
// 代码分割配置
manualChunks: {
vue: ['vue', 'vue-router', 'pinia'], // Vue相关库打包到一起
utils: ['lodash', 'axios'] // 工具库打包到一起
},
// 入口文件配置
entryFileNames: 'js/[name]-[hash].js',
chunkFileNames: 'js/[name]-[hash].js',
assetFileNames: (assetInfo) => {
// 静态资源分类输出
const extType = assetInfo.name.split('.')[1]
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
return 'img/[name]-[hash][extname]'
}
return 'assets/[name]-[hash][extname]'
}
}
}
},
// 解析配置
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'), // 路径别名
'~': path.resolve(__dirname, 'public') // 公共文件别名
}
},
// 环境变量前缀
envPrefix: 'VUE_APP_',
// CSS配置
css: {
preprocessorOptions: {
scss: {
additionalData: '@import "@/styles/variables.scss";' // 全局SCSS变量
}
}
}
}))
创建scripts/serve-local.js文件:
const express = require('express')
const path = require('path')
const history = require('connect-history-api-fallback')
const app = express()
// 使用history模式中间件,解决Vue Router history模式刷新404问题
app.use(history())
// 设置静态文件目录(指向打包后的dist目录)
app.use(express.static(path.join(__dirname, '../dist')))
// 添加安全头
app.use((req, res, next) => {
// 防止XSS攻击
res.setHeader('X-XSS-Protection', '1; mode=block')
// 防止点击劫持
res.setHeader('X-Frame-Options', 'DENY')
// 防止MIME类型嗅探
res.setHeader('X-Content-Type-Options', 'nosniff')
next()
})
// 启动服务器
const port = process.env.PORT || 4399
app.listen(port, () => {
console.log(`本地测试服务器已启动`)
console.log(`访问地址: http://localhost:${port}`)
console.log(`静态文件目录: ${path.join(__dirname, '../dist')}`)
})
创建vitest.config.js单元测试配置:
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
// 测试配置
test: {
globals: true, // 启用全局API
environment: 'jsdom', // 测试环境(模拟浏览器环境)
// 覆盖率配置
coverage: {
reporter: ['text', 'json', 'html'], // 覆盖率报告格式
exclude: [ // 排除不需要测试覆盖的文件
'node_modules/',
'tests/',
'**/*.d.ts'
]
},
// 测试文件匹配模式
include: ['src/**/*.{test,spec}.{js,ts}'],
// 测试超时时间(毫秒)
testTimeout: 10000,
// 设置全局测试变量
setupFiles: ['./tests/setup.js']
},
// 解析配置
resolve: {
alias: {
'@': new URL('./src', import.meta.url).pathname // 路径别名
}
}
})
创建Cypress端到端测试配置cypress.config.js:
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:4399', // 测试服务器地址
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', // 测试文件模式
supportFile: 'cypress/support/e2e.js', // 支持文件
fixturesFolder: 'cypress/fixtures', // 测试数据文件夹
downloadsFolder: 'cypress/downloads', // 下载文件文件夹
screenshotsFolder: 'cypress/screenshots', // 截图文件夹
videosFolder: 'cypress/videos', // 视频文件夹
// 配置测试任务
setupNodeEvents(on, config) {
// 在这里可以注册事件监听器
// 环境变量配置
config.env = {
...config.env,
test_mode: process.env.NODE_ENV || 'development'
}
return config
}
},
// 组件测试配置
component: {
devServer: {
framework: 'vue', // 使用的框架
bundler: 'vite' // 使用的打包工具
}
},
// 视口设置
viewportWidth: 1280, // 视口宽度
viewportHeight: 720, // 视口高度
// 截图配置
screenshotOnRunFailure: true, // 测试失败时自动截图
trashAssetsBeforeRuns: true // 运行测试前清理资源
})
创建完整的Nginx配置文件nginx.conf:
# 运行Nginx的用户,通常设置为nginx或者www-data
user nginx;
# 工作进程数,通常设置为CPU核心数
worker_processes auto;
# 错误日志文件路径和日志级别
error_log /var/log/nginx/error.log warn;
# PID文件路径(存储Nginx主进程ID)
pid /var/run/nginx.pid;
# 事件模块配置
events {
# 每个工作进程的最大连接数
worker_connections 1024;
# 使用epoll事件模型(Linux系统高效模型)
use epoll;
# 允许同时接受多个网络连接
multi_accept on;
}
# HTTP服务器配置
http {
# 包含MIME类型定义
include /etc/nginx/mime.types;
# 默认MIME类型
default_type application/octet-stream;
# 日志格式定义
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 访问日志路径和格式
access_log /var/log/nginx/access.log main;
# 基础性能优化配置
sendfile on; # 启用高效文件传输
tcp_nopush on; # 在sendfile模式下,保证数据包被完整发送
tcp_nodelay on; # 禁用Nagle算法,提高响应速度
keepalive_timeout 65; # 保持连接超时时间
types_hash_max_size 2048; # types哈希表大小
# Gzip压缩配置
gzip on; # 启用Gzip压缩
gzip_vary on; # 根据请求头Vary: Accept-Encoding
gzip_min_length 1024; # 最小压缩文件大小
gzip_comp_level 6; # 压缩级别(1-9,数字越大压缩率越高但CPU消耗越大)
gzip_types # 需要压缩的MIME类型
application/javascript
application/json
application/xml
text/css
text/javascript
text/plain
text/xml;
# 上游服务器配置(如果需要代理到后端)
upstream backend {
server 127.0.0.1:8080; # 后端服务器地址
keepalive 32; # 保持的连接数
}
# HTTP服务器配置(80端口,重定向到HTTPS)
server {
listen 80; # 监听80端口
server_name yourdomain.com www.yourdomain.com; # 域名配置
# 重定向所有HTTP请求到HTTPS
return 301 https://$server_name$request_uri;
}
# HTTPS服务器配置(443端口,主要服务)
server {
listen 443 ssl http2; # 监听443端口,启用SSL和HTTP/2
server_name yourdomain.com www.yourdomain.com;
# SSL证书配置
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# SSL安全配置
ssl_protocols TLSv1.2 TLSv1.3; # 允许的SSL协议版本
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; # 加密套件
ssl_prefer_server_ciphers off; # 优先使用服务器加密套件
ssl_session_cache shared:SSL:10m; # SSL会话缓存
ssl_session_timeout 10m; # SSL会话超时时间
# 安全头配置
add_header X-Frame-Options DENY always; # 防止点击劫持
add_header X-Content-Type-Options nosniff always; # 防止MIME类型嗅探
add_header X-XSS-Protection "1; mode=block" always; # XSS保护
add_header Strict-Transport-Security "max-age=63072000" always; # HSTS
# 根路径配置 - Vue应用主入口
location / {
root /var/www/your_project/dist; # Vue打包文件所在目录
index index.html index.htm; # 默认索引文件
# 对于Vue Router的history模式,需要配置这个
# 当请求的文件不存在时,返回index.html,让Vue处理路由
try_files $uri $uri/ /index.html;
# 缓存静态资源
location ~* .(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y; # 缓存1年
add_header Cache-Control "public, immutable";
}
}
# API代理配置 - 将API请求转发到后端服务器
location /api/ {
proxy_pass http://backend/; # 转发到上游服务器
proxy_http_version 1.1; # 使用HTTP/1.1
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# 代理超时配置
proxy_connect_timeout 60s; # 连接超时时间
proxy_send_timeout 60s; # 发送超时时间
proxy_read_timeout 60s; # 读取超时时间
}
# 静态资源缓存配置
location /static/ {
root /var/www/your_project/dist;
expires 1y; # 长期缓存
add_header Cache-Control "public, immutable";
access_log off; # 关闭访问日志,减少IO
}
# 安全配置 - 隐藏敏感文件
location ~ /.(git|htaccess|htpasswd) {
deny all; # 拒绝访问
return 404;
}
# 健康检查端点
location /health {
access_log off; # 关闭日志
return 200 "healthyn"; # 返回健康状态
add_header Content-Type text/plain;
}
}
}
创建scripts/deploy-prod.js文件:
const { execSync } = require('child_process')
const fs = require('fs')
const path = require('path')
const scpClient = require('scp2')
const ora = require('ora')
const chalk = require('chalk')
// 服务器配置
const serverConfig = {
host: '192.168.1.100', // 服务器IP地址
port: 22, // SSH端口,默认22
username: 'root', // 服务器用户名
password: 'your_password', // 服务器密码(建议使用密钥认证)
path: '/var/www/your_project/' // 服务器上项目部署路径
}
// 备份配置
const backupConfig = {
enabled: true, // 是否启用备份
backupDir: '/var/www/backups/', // 备份目录
keepBackups: 5 // 保留的备份数量
}
// 部署状态跟踪
class Deployment {
constructor() {
this.startTime = new Date()
this.steps = []
}
addStep(step, status, message) {
this.steps.push({
step,
status,
message,
timestamp: new Date()
})
}
logSuccess(step, message) {
this.addStep(step, 'success', message)
console.log(chalk.green(` ${step}: ${message}`))
}
logError(step, message) {
this.addStep(step, 'error', message)
console.log(chalk.red(` ${step}: ${message}`))
}
logInfo(step, message) {
this.addStep(step, 'info', message)
console.log(chalk.blue(`ℹ ${step}: ${message}`))
}
generateReport() {
const duration = new Date() - this.startTime
console.log(chalk.cyan('n=== 部署报告 ==='))
console.log(`开始时间: ${this.startTime.toLocaleString()}`)
console.log(`持续时间: ${(duration / 1000).toFixed(2)} 秒`)
console.log(`执行步骤: ${this.steps.length}`)
const successSteps = this.steps.filter(s => s.status === 'success').length
const errorSteps = this.steps.filter(s => s.status === 'error').length
console.log(chalk.green(`成功: ${successSteps}`))
console.log(chalk.red(`失败: ${errorSteps}`))
if (errorSteps > 0) {
console.log(chalk.red('n部署过程中出现错误,请检查以上错误信息!'))
process.exit(1)
} else {
console.log(chalk.green('n部署成功完成!'))
}
}
}
// 创建部署实例
const deployment = new Deployment()
// 主部署函数
async function deploy() {
try {
deployment.logInfo('初始化', '开始生产环境部署流程')
// 步骤1: 检查当前Git状态
deployment.logInfo('Git检查', '检查Git工作区状态')
try {
const gitStatus = execSync('git status --porcelain').toString()
if (gitStatus) {
deployment.logError('Git检查', '工作区有未提交的更改,请先提交更改')
process.exit(1)
}
deployment.logSuccess('Git检查', '工作区干净')
} catch (error) {
deployment.logError('Git检查', 'Git命令执行失败')
process.exit(1)
}
// 步骤2: 安装依赖
deployment.logInfo('依赖安装', '开始安装项目依赖')
try {
execSync('npm install', { stdio: 'inherit' })
deployment.logSuccess('依赖安装', '依赖安装完成')
} catch (error) {
deployment.logError('依赖安装', '依赖安装失败')
process.exit(1)
}
// 步骤3: 运行测试
deployment.logInfo('测试', '运行单元测试和E2E测试')
try {
execSync('npm run test:unit', { stdio: 'inherit' })
execSync('npm run test:e2e', { stdio: 'inherit' })
deployment.logSuccess('测试', '所有测试通过')
} catch (error) {
deployment.logError('测试', '测试失败,部署中止')
process.exit(1)
}
// 步骤4: 构建项目
deployment.logInfo('构建', '开始构建生产版本')
try {
execSync('npm run build', { stdio: 'inherit' })
// 检查构建结果
const distPath = path.join(__dirname, '../dist')
if (!fs.existsSync(distPath)) {
throw new Error('构建输出目录不存在')
}
const files = fs.readdirSync(distPath)
if (files.length === 0) {
throw new Error('构建输出目录为空')
}
deployment.logSuccess('构建', `构建完成,生成 ${files.length} 个文件`)
} catch (error) {
deployment.logError('构建', `构建失败: ${error.message}`)
process.exit(1)
}
// 步骤5: 上传到服务器
deployment.logInfo('上传', '开始上传文件到生产服务器')
const spinner = ora('上传文件中...').start()
try {
await new Promise((resolve, reject) => {
scpClient.scp('./dist/', serverConfig, (err) => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
spinner.succeed('文件上传成功')
deployment.logSuccess('上传', '所有文件已上传到服务器')
} catch (error) {
spinner.fail('文件上传失败')
deployment.logError('上传', `上传失败: ${error.message}`)
process.exit(1)
}
// 步骤6: 生成部署报告
deployment.generateReport()
} catch (error) {
deployment.logError('部署流程', `部署过程出现未知错误: ${error.message}`)
deployment.generateReport()
process.exit(1)
}
}
// 执行部署
deploy()
创建scripts/deploy-test.js文件:
const { execSync } = require('child_process')
const chalk = require('chalk')
console.log(chalk.blue('开始测试环境部署...'))
try {
// 1. 安装依赖
console.log(chalk.yellow('1. 安装依赖...'))
execSync('npm install', { stdio: 'inherit' })
// 2. 构建测试版本
console.log(chalk.yellow('2. 构建测试版本...'))
execSync('npm run build:test', { stdio: 'inherit' })
// 3. 运行测试
console.log(chalk.yellow('3. 运行测试...'))
execSync('npm run test:unit', { stdio: 'inherit' })
// 4. 部署到测试服务器
console.log(chalk.yellow('4. 部署到测试服务器...'))
// 这里可以添加测试服务器部署逻辑
console.log(chalk.green(' 测试环境部署完成!'))
} catch (error) {
console.log(chalk.red(' 测试环境部署失败:', error.message))
process.exit(1)
}
创建scripts/health-check.js:
const https = require('https')
const chalk = require('chalk')
// 健康检查配置
const endpoints = [
{
name: '生产环境主站',
url: 'https://yourdomain.com',
timeout: 5000
},
{
name: '生产环境API',
url: 'https://yourdomain.com/api/health',
timeout: 3000
},
{
name: '测试环境',
url: 'https://test.yourdomain.com',
timeout: 5000
}
]
// 执行健康检查
async function healthCheck() {
console.log(chalk.blue('开始健康检查...n'))
const results = []
for (const endpoint of endpoints) {
const startTime = Date.now()
try {
const result = await checkEndpoint(endpoint)
results.push({
...result,
responseTime: Date.now() - startTime
})
} catch (error) {
results.push({
name: endpoint.name,
status: 'down',
error: error.message,
responseTime: Date.now() - startTime
})
}
}
// 输出检查结果
printResults(results)
}
// 检查单个端点
function checkEndpoint(endpoint) {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('请求超时'))
}, endpoint.timeout)
https.get(endpoint.url, (res) => {
clearTimeout(timeout)
if (res.statusCode === 200) {
resolve({
name: endpoint.name,
status: 'up',
statusCode: res.statusCode
})
} else {
resolve({
name: endpoint.name,
status: 'down',
statusCode: res.statusCode
})
}
}).on('error', (error) => {
clearTimeout(timeout)
reject(error)
})
})
}
// 打印检查结果
function printResults(results) {
results.forEach(result => {
if (result.status === 'up') {
console.log(chalk.green(` ${result.name}`))
console.log(` 状态: 运行正常`)
console.log(` 响应时间: ${result.responseTime}ms`)
console.log(` 状态码: ${result.statusCode}n`)
} else {
console.log(chalk.red(` ${result.name}`))
console.log(` 状态: 服务异常`)
console.log(` 错误: ${result.error}`)
console.log(` 响应时间: ${result.responseTime}msn`)
}
})
// 总结
const upServices = results.filter(r => r.status === 'up').length
const totalServices = results.length
console.log(chalk.cyan('=== 健康检查总结 ==='))
console.log(`总服务数: ${totalServices}`)
console.log(`健康服务: ${upServices}`)
console.log(`异常服务: ${totalServices - upServices}`)
if (upServices === totalServices) {
console.log(chalk.green(' 所有服务运行正常!'))
} else {
console.log(chalk.yellow(' 有服务出现异常,请及时检查!'))
process.exit(1)
}
}
// 执行健康检查
healthCheck()