使用HTTPS 服务在浏览器端使用摄像头的方式解析
1.方式1
// 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'], // 添加您的IPcertDir: './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
// 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 插件
plugins: [vue(),basicSsl({name: 'test',domains: ['192.168.31.179', 'localhost'],certDir: './cert',}),
]
原理:
- 自动生成证书:插件会自动为指定的域名生成自签名 SSL 证书
- 证书存储:生成的证书存储在
./cert
目录中 - 多域名支持:可以为多个域名/IP 生成证书(如局域网 IP 和 localhost)
- 自动配置:插件会自动将证书配置到 Vite 的 HTTPS 服务器
工作流程:
- 首次运行时,插件检查证书是否存在
- 如果不存在,使用内置的证书生成器创建自签名证书
- 将证书自动注入到 Vite 的 server 配置中
- 后续运行时复用已生成的证书
方式二:手动配置证书
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):
# 生成私钥
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 后都可以使用以下代码访问摄像头:
navigator.mediaDevices.getUserMedia({ video: true, audio: true
})
.then(stream => {// 获取到媒体流videoElement.srcObject = stream;
})
.catch(err => {console.error('无法访问摄像头:', err);
});
注意事项
- 证书信任:两种方式生成的自签名证书都会导致浏览器警告,需要手动信任
- 局域网访问:方式一通过配置 IP 地址,可以让局域网内其他设备通过 HTTPS 访问
- 安全性:这些配置仅适用于开发环境,生产环境应使用正规 CA 签发的证书
总的来说,方式一更适合快速开发和原型验证,方式二则提供了更多的控制权和灵活性。选择哪种方式主要取决于项目需求和团队的技术偏好。
这是使用自签名证书进行本地开发时的常见问题。让我详细说明解决方案:
一、手机无法访问的问题排查
1. 确保网络连通性
// vite.config.js
server: {host: '0.0.0.0', // 允许外部访问port: 3000,https: true,
}
检查步骤:
- 确保手机和电脑在同一局域网
- 电脑防火墙允许 3000 端口
- 使用电脑的局域网 IP(如
https://192.168.31.179:3000
)
2. 防火墙设置
# 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 设备
- 生成包含正确信息的证书:
# 创建证书配置文件 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
# 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 并生成证书
# 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 配置
// 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 证书:
# 找到 CA 证书位置
mkcert -CAROOT
# 通常在:
# Windows: %LOCALAPPDATA%\mkcert
# macOS: ~/Library/Application Support/mkcert
# Linux: ~/.local/share/mkcert
- 将
rootCA.pem
发送到手机并安装
四、使用 ngrok(外网访问方案)
如果证书问题难以解决,可以使用 ngrok 提供的 HTTPS 隧道:
# 安装 ngrok
npm install -g ngrok# 启动本地服务后,创建隧道
ngrok http 3000# 会得到类似这样的地址:
# https://abc123.ngrok.io
优点:
- 自动 HTTPS,无需证书配置
- 可以外网访问
- 手机无需安装证书
五、开发环境最佳实践配置##
// 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}`)// 检查是否安装了 mkcerttry {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,// 配置 CORScors: 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 脚本
{"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
# 一次性设置,永久解决证书问题
brew install mkcert # macOS
mkcert -install
- 生成证书
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:
npx localtunnel --port 3000 --subdomain myapp
# 或
ngrok http 3000
这样可以获得一个临时的公网 HTTPS 地址,无需任何证书配置,手机可以直接访问。
主要缺点是需要依赖外部服务,并且速度可能较慢。但对于紧急测试和演示,这是最快的解决方案。