1.方式1
bash
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import basicSsl from '@vitejs/plugin-basic-ssl'
export default defineConfig({
plugins: [
vue(),
basicSsl({
name: 'test',
domains: ['192.168.15.166', 'localhost'], // 添加您的IP
certDir: './cert',
}),
],
server: {
host: '0.0.0.0',
port: 3000,
https: true,
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]',
},
},
},
})
2.方式2
bash
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import fs from 'fs'
import path from 'path'
export default defineConfig({
plugins: [
vue(),
],
server: {
https: {
key: fs.readFileSync(path.resolve(__dirname, 'cert/key.pem')),
cert: fs.readFileSync(path.resolve(__dirname, 'cert/cert.pem'))
},
host: '0.0.0.0',
port: 3000,
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true
},
},
},
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
},
})
这两个 Vite 配置文件都实现了 HTTPS 服务,这是浏览器访问摄像头等敏感设备的必要条件。以下详细解释它们的原理和差异。
为什么需要 HTTPS
现代浏览器出于安全考虑,要求在以下情况下才能访问摄像头、麦克风等敏感设备:
- 使用 HTTPS 协议(安全连接)
- 或者是
localhost
域名(本地开发特例)
这是因为 getUserMedia()
API 被归类为"强大功能"(Powerful Features),需要安全上下文(Secure Context)。
方式一:使用 @vitejs/plugin-basic-ssl 插件
javascript
plugins: [
vue(),
basicSsl({
name: 'test',
domains: ['192.168.31.179', 'localhost'],
certDir: './cert',
}),
]
原理:
- 自动生成证书:插件会自动为指定的域名生成自签名 SSL 证书
- 证书存储 :生成的证书存储在
./cert
目录中 - 多域名支持:可以为多个域名/IP 生成证书(如局域网 IP 和 localhost)
- 自动配置:插件会自动将证书配置到 Vite 的 HTTPS 服务器
工作流程:
- 首次运行时,插件检查证书是否存在
- 如果不存在,使用内置的证书生成器创建自签名证书
- 将证书自动注入到 Vite 的 server 配置中
- 后续运行时复用已生成的证书
方式二:手动配置证书
javascript
server: {
https: {
key: fs.readFileSync(path.resolve(__dirname, 'cert/key.pem')),
cert: fs.readFileSync(path.resolve(__dirname, 'cert/cert.pem'))
},
}
原理:
- 手动管理证书:需要预先生成或获取 SSL 证书文件
- 文件读取 :使用 Node.js 的
fs
模块直接读取证书文件 - 直接配置:将证书内容直接传递给 Vite 的 HTTPS 配置
证书生成方式(通常使用 OpenSSL):
bash
# 生成私钥
openssl genrsa -out key.pem 2048
# 生成证书
openssl req -new -x509 -key key.pem -out cert.pem -days 365
关键差异对比
特性 | 方式一(plugin-basic-ssl) | 方式二(手动配置) |
---|---|---|
便利性 | 高 - 自动生成和管理 | 低 - 需要手动创建证书 |
灵活性 | 中等 - 插件预设配置 | 高 - 完全自定义 |
证书类型 | 自签名证书 | 可以使用任何证书(自签名或CA签发) |
多域名支持 | 内置支持,配置简单 | 需要生成包含 SAN 的证书 |
首次设置 | 零配置,自动完成 | 需要手动生成证书 |
团队协作 | 每个开发者自动生成 | 需要共享证书文件 |
证书更新 | 删除旧证书即可重新生成 | 手动重新生成和替换 |
依赖性 | 需要安装额外插件 | 仅需 Node.js 内置模块 |
实际应用场景
方式一适合:
- 本地开发环境
- 快速原型开发
- 需要在局域网内多设备测试
- 团队成员各自开发,不共享证书
方式二适合:
- 需要使用特定证书(如公司内部 CA 签发)
- 生产环境的本地模拟
- 需要精确控制证书配置
- 已有证书管理流程的团队
浏览器访问摄像头的实现
无论使用哪种方式,配置 HTTPS 后都可以使用以下代码访问摄像头:
javascript
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
.then(stream => {
// 获取到媒体流
videoElement.srcObject = stream;
})
.catch(err => {
console.error('无法访问摄像头:', err);
});
注意事项
- 证书信任:两种方式生成的自签名证书都会导致浏览器警告,需要手动信任
- 局域网访问:方式一通过配置 IP 地址,可以让局域网内其他设备通过 HTTPS 访问
- 安全性:这些配置仅适用于开发环境,生产环境应使用正规 CA 签发的证书
总的来说,方式一更适合快速开发和原型验证,方式二则提供了更多的控制权和灵活性。选择哪种方式主要取决于项目需求和团队的技术偏好。
这是使用自签名证书进行本地开发时的常见问题。让我详细说明解决方案:
一、手机无法访问的问题排查
1. 确保网络连通性
javascript
// vite.config.js
server: {
host: '0.0.0.0', // 允许外部访问
port: 3000,
https: true,
}
检查步骤:
- 确保手机和电脑在同一局域网
- 电脑防火墙允许 3000 端口
- 使用电脑的局域网 IP(如
https://192.168.31.179:3000
)
2. 防火墙设置
bash
# Windows - 以管理员运行
netsh advfirewall firewall add rule name="Vite Dev Server" dir=in action=allow protocol=TCP localport=3000
# macOS
sudo pfctl -d # 临时关闭防火墙测试
# Linux
sudo ufw allow 3000/tcp
二、手机端安装证书的正确方法
iOS 设备
- 生成包含正确信息的证书:
bash
# 创建证书配置文件 cert.conf
cat > cert.conf <<EOF
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = CN
ST = State
L = City
O = Organization
CN = 192.168.31.179
[v3_req]
keyUsage = critical, digitalSignature, keyAgreement
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 192.168.31.179
IP.2 = 127.0.0.1
EOF
# 生成证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout key.pem -out cert.pem -config cert.conf
- 安装证书到 iOS:
- 将
cert.pem
文件通过邮件/AirDrop 发送到手机 - 点击证书文件,选择"安装"
- 设置 → 通用 → 关于本机 → 证书信任设置
- 开启对该证书的完全信任
Android 设备
- Android 安装步骤:
- 将
cert.pem
重命名为cert.crt
- 传输到手机
- 设置 → 安全 → 加密与凭据 → 安装证书 → CA 证书
- 选择证书文件安装
三、使用 mkcert 工具(推荐方案)
mkcert 可以创建本地受信任的证书,避免所有警告:
安装 mkcert
bash
# Windows (使用 Chocolatey)
choco install mkcert
# macOS
brew install mkcert
# Linux
apt install libnss3-tools
wget -O mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
chmod +x mkcert
sudo mv mkcert /usr/local/bin/
配置本地 CA 并生成证书
bash
# 1. 安装本地 CA
mkcert -install
# 2. 生成证书(包含所有需要的域名和 IP)
mkcert -cert-file cert.pem -key-file key.pem localhost 127.0.0.1 192.168.31.179 ::1
# 3. 为手机生成 CA 证书
mkcert -CAROOT # 显示 CA 证书位置
更新 Vite 配置
javascript
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import fs from 'fs'
import path from 'path'
export default defineConfig({
plugins: [vue()],
server: {
https: {
key: fs.readFileSync('./key.pem'),
cert: fs.readFileSync('./cert.pem')
},
host: '0.0.0.0',
port: 3000,
}
})
手机端信任 mkcert CA
- 获取 CA 证书:
bash
# 找到 CA 证书位置
mkcert -CAROOT
# 通常在:
# Windows: %LOCALAPPDATA%\mkcert
# macOS: ~/Library/Application Support/mkcert
# Linux: ~/.local/share/mkcert
- 将
rootCA.pem
发送到手机并安装
四、使用 ngrok(外网访问方案)
如果证书问题难以解决,可以使用 ngrok 提供的 HTTPS 隧道:
bash
# 安装 ngrok
npm install -g ngrok
# 启动本地服务后,创建隧道
ngrok http 3000
# 会得到类似这样的地址:
# https://abc123.ngrok.io
优点:
- 自动 HTTPS,无需证书配置
- 可以外网访问
- 手机无需安装证书
五、开发环境最佳实践配置##
javascript
// vite.config.js - 完整的 HTTPS 配置方案
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import fs from 'fs'
import path from 'path'
import { execSync } from 'child_process'
// 获取本机局域网 IP
function getLocalIP() {
const os = require('os')
const interfaces = os.networkInterfaces()
for (const name of Object.keys(interfaces)) {
for (const iface of interfaces[name]) {
if (iface.family === 'IPv4' && !iface.internal) {
return iface.address
}
}
}
return '127.0.0.1'
}
// 自动生成证书函数
function ensureCertificates() {
const certDir = path.resolve(__dirname, 'cert')
const keyPath = path.join(certDir, 'key.pem')
const certPath = path.join(certDir, 'cert.pem')
// 检查证书是否存在
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
console.log('✅ 使用现有证书')
return { key: keyPath, cert: certPath }
}
// 创建证书目录
if (!fs.existsSync(certDir)) {
fs.mkdirSync(certDir, { recursive: true })
}
const localIP = getLocalIP()
console.log(`📱 局域网 IP: ${localIP}`)
// 检查是否安装了 mkcert
try {
execSync('mkcert -version', { stdio: 'ignore' })
console.log('🔧 使用 mkcert 生成受信任的证书...')
// 安装本地 CA(如果还没安装)
execSync('mkcert -install', { stdio: 'inherit' })
// 生成证书
execSync(
`mkcert -cert-file ${certPath} -key-file ${keyPath} localhost 127.0.0.1 ${localIP} ::1`,
{ stdio: 'inherit' }
)
console.log('✅ mkcert 证书生成成功!')
console.log('📱 手机端请安装 CA 证书:')
const caRoot = execSync('mkcert -CAROOT').toString().trim()
console.log(` ${path.join(caRoot, 'rootCA.pem')}`)
} catch (e) {
console.log('⚠️ mkcert 未安装,使用 OpenSSL 生成自签名证书...')
// 创建 OpenSSL 配置
const configPath = path.join(certDir, 'openssl.cnf')
const opensslConfig = `
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = CN
ST = State
L = City
O = Dev Organization
CN = ${localIP}
[v3_req]
keyUsage = critical, digitalSignature, keyAgreement
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = *.localhost
IP.1 = 127.0.0.1
IP.2 = ${localIP}
IP.3 = ::1
`
fs.writeFileSync(configPath, opensslConfig)
// 生成证书
try {
execSync(
`openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ${keyPath} -out ${certPath} -config ${configPath}`,
{ stdio: 'inherit' }
)
console.log('✅ 自签名证书生成成功!')
console.log('⚠️ 浏览器会显示安全警告,需要手动信任证书')
} catch (error) {
console.error('❌ 证书生成失败:', error)
process.exit(1)
}
}
return { key: keyPath, cert: certPath }
}
// 生成或获取证书
const certificates = ensureCertificates()
export default defineConfig({
plugins: [vue()],
server: {
https: {
key: fs.readFileSync(certificates.key),
cert: fs.readFileSync(certificates.cert)
},
host: '0.0.0.0', // 允许外部访问
port: 3000,
// 显示二维码方便手机扫描
open: false,
// 配置 CORS
cors: true,
// 代理配置
proxy: {
'/api': {
target: 'http://localhost:5000',
changeOrigin: true,
secure: false,
}
}
},
// 构建配置
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js',
entryFileNames: 'js/[name]-[hash].js',
assetFileNames: '[ext]/[name]-[hash].[ext]',
}
}
},
// 优化配置
optimizeDeps: {
exclude: ['vue']
}
})
// 启动后打印访问信息
process.nextTick(() => {
const localIP = getLocalIP()
console.log('\n🚀 服务已启动:')
console.log(` 本地: https://localhost:3000`)
console.log(` 局域网: https://${localIP}:3000`)
console.log('\n📱 手机访问提示:')
console.log(' 1. 确保手机与电脑在同一 WiFi')
console.log(' 2. 如有证书警告,点击"高级"→"继续访问"')
console.log(' 3. iOS 需在"设置"→"通用"→"关于本机"→"证书信任设置"中信任证书\n')
})
六、配套的 package.json 脚本
javascript
{
"scripts": {
"dev": "vite",
"dev:host": "vite --host",
"dev:network": "vite --host 0.0.0.0",
"dev:https": "node scripts/setup-https.js && vite",
"cert:install": "mkcert -install",
"cert:generate": "mkcert -cert-file cert/cert.pem -key-file cert/key.pem localhost 127.0.0.1 $(ipconfig getifaddr en0 || hostname -I | awk '{print $1}') ::1",
"cert:trust": "mkcert -install && npm run cert:generate",
"build": "vite build",
"preview": "vite preview",
"tunnel": "ngrok http 3000"
},
"devDependencies": {
"@vitejs/plugin-basic-ssl": "^1.0.1",
"@vitejs/plugin-vue": "^4.2.3",
"vite": "^4.3.9",
"vue": "^3.3.4"
},
"dependencies": {
"qrcode": "^1.5.3"
}
}
七、快速解决方案总结
最简单的方案(推荐)
- 安装 mkcert
bash
# 一次性设置,永久解决证书问题
brew install mkcert # macOS
mkcert -install
- 生成证书
bash
mkcert -cert-file cert.pem -key-file key.pem localhost 192.168.xxx.xxx
- 手机端信任
- 将
~/.local/share/mkcert/rootCA.pem
(或对应系统路径)发送到手机 - 安装为受信任的 CA 证书
故障排查清单
✅ 网络连接
- 手机和电脑同一 WiFi?
- 防火墙允许 3000 端口?
- 使用正确的 IP 地址?
✅ 证书配置
- 证书包含局域网 IP?
- 证书已安装到手机?
- iOS 已在"证书信任设置"中启用?
✅ Vite 配置
host: '0.0.0.0'
已设置?https: true
或手动配置证书?- 端口没有被占用?
终极备用方案
如果以上都不行,使用 localtunnel 或 ngrok:
bash
npx localtunnel --port 3000 --subdomain myapp
# 或
ngrok http 3000
这样可以获得一个临时的公网 HTTPS 地址,无需任何证书配置,手机可以直接访问。
主要缺点是需要依赖外部服务,并且速度可能较慢。但对于紧急测试和演示,这是最快的解决方案。