# 中北检测项目 **Repository Path**: AdminRDC/zhongbei-testing-projects ## Basic Information - **Project Name**: 中北检测项目 - **Description**: 中北检测项目代码存放 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-03 - **Last Updated**: 2026-09-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 汽车发动机积碳程度检测系统 —— 前端 前端项目信息:技术栈、目录结构、启动方式、路由与权限、请求封装与 Mock、API 清单、状态管理、工具函数、布局组件、页面视图、后端对接约定。 --- ## 1. 项目概述 - **name**:`engine-carbon-detection-system`(汽车发动机积碳程度检测系统前端) - **功能**:登录/注册、单部位检测、多部位融合检测、手持设备检测、历史记录(预览/下载/批量导出/删除)、上传报告、模型配置、汽车品牌管理、用户管理、审计日志、工作台统计。 - **合作单位**:中北大学、深圳市捷易检测服务有限责任公司、昆山杰普软件科技有限公司、山西省信息产业技术研究院有限公司。 ## 2. 技术栈 | 分类 | 依赖 | 版本 | | --- | --- | --- | | 框架 | Vue | ^3.5.12 | | UI | Element Plus | ^2.8.4 | | 图标 | @element-plus/icons-vue | ^2.3.1 | | 状态 | Pinia | ^2.2.4 | | 路由 | Vue Router | ^4.4.5 | | HTTP | Axios | ^1.7.7 | | 构建 | Vite | ^5.4.10 | - `type: "module"`;Element Plus 使用中文语言包 `zh-cn`。 ## 3. 目录结构 ``` frontend/ ├── .env.development / .env.production ├── index.html / package.json / vite.config.js ├── public/favicon.svg └── src/ ├── main.js # 入口:注册 Pinia/Router/ElementPlus/图标 ├── App.vue # 根组件,仅 router-view + fade 过渡 ├── api/ # request.js + auth/detect/record/model/user/audit/dashboard/brand/upload ├── assets/styles/main.css # 全局样式 ├── components/ # ImageUploader.vue、CameraCapture.vue、ExportTypeSelect.vue ├── layouts/MainLayout.vue # 主布局(侧边菜单+页眉+内容) ├── router/index.js # 路由与权限守卫 ├── stores/ # user.js、config.js、detect.js ├── utils/ # constants/fileExport/imageValidate/permission/pagination └── views/ ├── login/Login.vue ├── home/Index.vue ├── detect/SingleDetect.vue、MultiDetect.vue、HandheldDetect.vue ├── record/History.vue、UploadReport.vue └── manage/UserManage.vue、ModelManage.vue、BrandManage.vue、AuditLog.vue ``` ## 4. 环境变量 | 变量 | 开发 | 生产 | 说明 | | --- | --- | --- | --- | | `VITE_API_BASE_URL` | `http://localhost:8888` | `https://carbon-detect.nuc.edu.cn` | 后端地址(Axios baseURL 与代理目标) | | `VITE_USE_MOCK` | `false` | 未设置 | 仅等于字符串 `"false"` 才走真实接口 | | `VITE_APP_TITLE` | 系统标题 | 系统标题 | 备用 | > 生产未设置 `VITE_USE_MOCK`,默认走 Mock;如需生产对接真实后端需显式加 `VITE_USE_MOCK=false`。 ## 5. 命令 ```bash npm install # 安装依赖 npm run dev # 开发(0.0.0.0:5173) npm run build # 构建到 dist/ npm run preview # 预览构建 ``` ## 6. Vite 配置 - 别名 `@` → `./src`。 - 开发代理:`/api` → `VITE_API_BASE_URL`(默认 `http://localhost:8888`),`changeOrigin: true`。 - 构建:`outDir: dist`、`sourcemap: false`、`chunkSizeWarningLimit: 1500`。 - 手动分包:`element-plus`(element-plus + icons)、`vue-vendor`(vue + vue-router + pinia)。 ## 7. 入口初始化(main.js) 1. 创建 app → 2. 全局注册所有 Element Plus 图标组件 → 3. 注册 Pinia → 4. 注册 Router → 5. 注册 Element Plus(zh-cn)→ 6. 引入样式 → 7. 挂载 `#app`。 ## 8. 请求封装与 Mock(api/request.js) - Axios 实例:`baseURL = VITE_API_BASE_URL || '/api'`,`timeout 30s`,默认 JSON 头。 - **请求拦截**:注入 `Authorization: Bearer `。 - **响应拦截**:`code` 为 0 或 200 视为成功并直接返回响应体;否则弹错并 reject。 - 401 → 清空用户并跳登录;403/404/500 → 对应提示;超时/网络错误 → 提示或 `console.warn`(网络错误不弹窗,便于后端未启动时联调)。 - **Mock 开关**:`USE_MOCK = VITE_USE_MOCK !== 'false'`;`mockResponse(data, delay=400)` 返回 `{code:0, data, message:'success'}`。 - 各 api 模块均为「`if (USE_MOCK)` 走本地 localStorage → 否则走真实接口」双分支。 ### localStorage 键 | Key | 用途 | | --- | --- | | `carbon_user_state` | 登录态(token/username/role/...) | | `mock_user_db` | Mock 用户库 | | `mock_records` | Mock 检测记录 | | `mock_model_config` | Mock 模型槽位配置 | | `mock_audit_logs` | Mock 审计日志 | | `mock_brand_db` | Mock 汽车品牌/车型库 | | `mock_upload_reports` | Mock 上传报告列表 | ## 9. API 清单 ### auth.js | 函数 | 真实端点 | | --- | --- | | `login(data)` | `POST /api/auth/login` | | `register(data)` | `POST /api/auth/register` | | `logout()` | `POST /api/auth/logout` | Mock 默认管理员 `admin/admin123`。 ### detect.js | 函数 | 说明 | 真实端点 | | --- | --- | --- | | `detectSingle(image, part, vehicleInfo)` | 单部位检测(FormData) | `POST /api/detect/single`(60s) | | `detectMulti(images, vehicleInfo)` | 多部位检测 | `POST /api/detect/multi`(120s) | | `detectHandheld(image, part, vehicleInfo)` | 手持设备检测(流程与单部位一致) | `POST /api/detect/handheld` | | `exportReport(taskId, type)` | 多格式导出(Blob,`type`:pdf/txt/word/excel,缺省 pdf) | `GET /api/report/export/:taskId?type=` | | `getReportDetail(taskId)` | 报告详情(预览) | `GET /api/report/detail/:taskId` | | `getImageBlob(imageId)` | 鉴权拉取图像(Blob) | `GET /api/image/:imageId` | 表单字段:单部位 `image/part/vehicleBrand/plateNumber/vehicleModel`;多部位 `images[i]/vehicleBrand/plateNumber/vehicleModel`(后端自动识别各图部位)。多部位融合:同部位取平均后按部位权重重新归一化加权,整体等级取加权结果四舍五入,置信度取均值。图像用 `getImageBlob` 单独拉取是因 `` 无法带 token。 ### record.js | 函数 | 真实端点 | | --- | --- | | `listRecords(params)` | `GET /api/record/list` | | `deleteRecords(ids)` | `DELETE /api/record/delete` | `listRecords` 参数含 `currentUser`(普通用户数据隔离)。 ### model.js | 函数 | 真实端点 | | --- | --- | | `uploadModel(formData)` | `POST /api/model/upload`(multipart,超时 600s) | | `getModelList(params)` | `GET /api/model/list` | | `deleteModel(modelId)` | `POST /api/model/delete` | | `getModelConfig()` | `GET /api/model/config` | | `updateModelBinding(detectType, partKey, modelId)` | `POST /api/model/update` | 模型结构:`{id, name, version, partKey, partName, functionType, functionName, fileName, fileSize, modifiedAt, createdAt, isActive}`。 上传字段:`file / version / functionType(region|pollution) / partKey`(污染识别时必填)。 `getModelList(params)` 参数:分页 `page`/`pageSize`,筛选 `functionType`(`region`/`pollution`)、`partKey`、`status`(`active`/`inactive`)、`keyword`(名称/版本模糊);传分页参数时返回 `{ total, list }`,否则返回全量数组。 ### user.js | 函数 | 真实端点 | | --- | --- | | `listUsers()` | `GET /api/user/list` | | `deleteUser(userId)` | `DELETE /api/user/delete` | | `resetPassword(userId, newPassword)` | `POST /api/user/reset-password` | ### audit.js | 函数 | 真实端点 | | --- | --- | | `listAuditLogs(params)` | `GET /api/audit/list` | | `appendMockAuditLog(entry)` | 仅 Mock(登录/注销调用) | ### dashboard.js | 函数 | 真实端点 | | --- | --- | | `getDashboardStats()` | `GET /api/dashboard/stats` | 返回:`{recordCount, reportCount, successCount, failureCount, levelDistribution[], partDistribution[], recentRecords[]}`。 ### brand.js | 函数 | 真实端点 | | --- | --- | | `getBrandList()` | `GET /api/brand/list`(登录可读,返回品牌及其下车型) | | `createBrand(name)` | `POST /api/brand/create` | | `updateBrand(brandId, name)` | `POST /api/brand/update` | | `deleteBrand(brandId)` | `POST /api/brand/delete`(级联删除旗下车型) | | `createVehicleModel(brandId, name)` | `POST /api/brand/model/create` | | `updateVehicleModel(modelId, name)` | `POST /api/brand/model/update` | | `deleteVehicleModel(modelId)` | `POST /api/brand/model/delete` | 品牌/车型增删改仅管理员;列表供管理页与检测页共用。 ### upload.js | 函数 | 真实端点 | | --- | --- | | `uploadReportByTask(taskId, type)` | `POST /api/upload/generate`(按任务重新生成并上传,`type`:pdf/txt/word/excel) | | `uploadReportFile(file)` | `POST /api/upload/file`(上传本地报告文件,multipart) | | `listUploadedReports(params)` | `GET /api/upload/list`(分页,仅 admin) | | `downloadUploadedReport(id)` | `GET /api/upload/download/:id`(Blob,仅 admin) | | `previewUploadedReport(id)` | 复用 `downloadUploadedReport`,前端按格式自行渲染 | | `deleteUploadedReport(id)` | `DELETE /api/upload/delete/:id`(仅 admin) | ## 10. Pinia stores ### user.js 状态:`username/userId/role/token/loginTime`;getter:`isLoggedIn/isAdmin/isUser`;方法:`login/register/logout/clearUser`。初始化从 `carbon_user_state` 恢复,登录后持久化。 ### config.js 状态:`models/singleBindings/multiBindings/loading/modelsLoading`;方法:`loadConfig/loadModels/updateBinding/configuredCount`。 ### detect.js - 单部位:`currentImage/currentImageUrl/singleResult/singleLoading/selectedPart/taskId` - 多部位:`multiImages/multiResults/overallResult/multiLoading/multiTaskId` - 车辆:`vehicleInfo`;标识:`hasUnsavedResult` - getter:`validMultiImageCount/canMultiDetect`;大量 set/clear/reset 方法。 ## 11. 工具函数 ### constants.js - `CARBON_LEVELS`:积碳 5 级(0 轻微积碳 ~ 4 严重积碳),含颜色/描述/建议;`getCarbonMeta(value)`。 - `ROLES/ROLE_LABELS`:`user/admin` 与中文名。 - `ENGINE_PARTS`:5 部位(AIRINLET 进气道、CYLINDER 缸壁、CROWN 活塞顶、INLETVALVE 进气阀、NOZZLE 喷油嘴);`getPartName`。 - `DETECT_TYPE_LABELS`(single/multi/handheld 中文名);`getDetectTypeName`。 - `MODEL_PART_LABELS`:模型部位标签(含 ALL 部位识别)。 - `DEFAULT_MODEL_VERSION='DEFAULT'`、`isDefaultVersion/versionLabel`。 - `MODEL_SLOTS_SINGLE` / `MODEL_SLOTS_MULTI`(均 6 槽,含部位识别 ALL);`MODEL_SLOTS_POLLUTION`(污染识别 5 部位,不含 ALL)。 - `MODEL_FUNCTIONS`(region 部位识别 / pollution 污染识别)。 - `IMAGE_EXTENSIONS/IMAGE_ACCEPT/MAX_IMAGE_SIZE`(10MB);`MODEL_EXTENSIONS`(`.h5`)。 - `AUDIT_OPERATION_TYPES`:24 种操作类型(含登录/注销/检测/记录/报告/模型/品牌车型管理等)。 ### fileExport.js - `EXPORT_TYPES` / `EXPORT_TYPE_LIST`:单条导出支持的格式元数据(pdf/txt/word/excel,含 `label/ext/description`,与后端 `EXPORT_RENDERERS` 对齐)。 - `DETECT_TYPE_NAMES`:检测类型中文名映射(`single`→单部位检测、`multi`→多部位检测、`handheld`→手持设备检测,与后端 `_DETECT_TYPE_NAMES` 对齐)。 - `getExportExt(type)`:按格式返回文件扩展名(未知回退 pdf)。 - `buildExportFilename({ type, detectType, detectTime })`:生成统一导出文件名「检测报告_检测类型_yyyyMMddHHmmss.扩展名」,与后端 `build_export_filename` 规则一致,供四视图 `` 使用。 - `buildReportContent`:拼装 UTF-8 文本报告。 - `exportTxtReport`:Blob + `` 下载。 - `saveAsTxtReport`:优先 File System Access API 弹「另存为」,降级 `exportTxtReport`。 - `downloadBlob(blob, filename)`:下载二进制(PDF 等)。 - `exportBatchReports(records)`:批量打包导出多份报告为单个 txt(历史记录「备份/批量备份」按钮实际调用,已不涉及后端备份接口)。 ### imageValidate.js `isValidImageExtension / isImageSizeValid / validateImageFile / fileToDataURL / formatFileSize`。 ### permission.js `getHomeRoute(role)`:角色对应的首页路由(统一返回 `/home`,Dashboard 内部按角色渲染)。 ### pagination.js `getPageSize(key, defaultSize=10) / setPageSize(key, size)`:分页每页条数持久化工具,写入 localStorage(键前缀 `carbon_pagination_`),刷新后保持用户偏好。 ## 12. 布局与组件 ### MainLayout.vue 左侧可折叠菜单(220px/64px)+ 顶部页眉(折叠、面包屑、返回工作台、用户下拉)+ `el-main` 内容区。菜单按角色过滤。 菜单:工作台 `/home`、单部位检测 `/detect/single`、多部位检测 `/detect/multi`、手持设备检测 `/detect/handheld`、上传报告 `/record/upload`、历史记录 `/record/history`(以上 user/admin)、用户管理 `/manage/user`、模型管理 `/manage/model`、汽车品牌管理 `/manage/brand`、审计日志 `/manage/audit`(仅 admin)。 ### ImageUploader.vue 封装 `el-upload` 为本地预览;Props `modelValue/file/label/disabled/height/aspectRatio`;Emits `update:modelValue/update:file/change`;校验格式与大小,预览图 + 更换/清除按钮。 ### CameraCapture.vue 摄像头采集组件(手持设备检测用):开启/关闭摄像头、拍照、切换设备、预览、重新拍摄;基于 `getUserMedia`,受浏览器安全上下文(HTTPS/本机回环)限制。 ### ExportTypeSelect.vue 导出格式选择弹窗:基于 `EXPORT_TYPE_LIST` 渲染四个格式卡片(可选中高亮,`CircleCheckFilled` 图标);Props `modelValue/title`;Emits `update:modelValue/confirm`(`confirm` 携带所选格式 key)。 ## 13. 页面视图 | 视图 | 说明 | | --- | --- | | Login.vue | 登录/注册;演示账号一键填充 `admin/admin123`;注册默认普通用户 | | home/Index.vue | 统计卡片、等级/部位分布、快捷入口、系统模型状态、最近记录 | | detect/SingleDetect.vue | 部位选择、图像上传、结果评估、车辆信息、导出报告(多格式) | | detect/MultiDetect.vue | 5 槽位上传(≥2 张)、融合评级、导出报告(多格式) | | detect/HandheldDetect.vue | 手持设备图像采集(摄像头拍照/上传)、结果评估、车辆信息、导出报告(多格式) | | record/History.vue | 检索、表格、预览/下载报告(多格式)/批量导出/删除、批量操作、预览弹窗(含图像) | | record/UploadReport.vue | 上传报告(按任务重新生成上传 + 本地文件上传)、上传列表、下载/预览/删除 | | manage/UserManage.vue | 用户列表、修改密码、删除(admin 与当前账号不可删) | | manage/ModelManage.vue | 模型上传(管理员)、所有模型表格、删除模型、单/多部位槽位绑定、选择模型弹窗 | | manage/BrandManage.vue | 汽车品牌/车型两级管理(增删改,品牌删除级联车型) | | manage/AuditLog.vue | 审计日志检索与展示 | > **报告下载结果提示**:四个下载入口(历史记录、单部位检测、多部位检测、手持设备检测结果页)的导出回调统一按以下规则提示: > > - 浏览器下载成功 → `ElMessage.success('报告下载成功')`; > - 浏览器下载失败(后端返回错误状态码)→ `ElMessage.error('报告下载失败')`; > - 下载行为被其他下载软件(迅雷/IDM 等)接管 → `ElMessage.warning('下载可能已被下载工具接管,请到下载工具的下载记录中查看')`。 > > 当浏览器安装了下载工具并接管下载时,浏览器侧的导出请求会被中断(无 HTTP `response`),但文件通常已由下载工具保存成功;下载回调的 `catch` 通过 `e.response` 是否存在区分「真失败」与「被下载工具接管」,避免误报。 ## 14. 路由与权限守卫 路由表:`/login`(public)、`/home`、`/detect/single`、`/detect/multi`、`/detect/handheld`、`/record/upload`、`/record/history`(user/admin)、`/manage/user|model|brand|audit`(admin);`/home/user`、`/home/admin` 重定向 `/home`;兜底重定向 `/login`。 守卫逻辑: 1. 设置 `document.title`。 2. 公开页:已登录访问 `/login` 跳 `/home`。 3. 未登录访问受保护页 → 提示并跳 `/login`。 4. 角色不匹配 → 提示「权限不足」并跳 `/home`。 ## 15. 全局样式(main.css) - `:root` 定义积碳 5 级配色变量与 `--font-mono` 等宽字体。 - 全局 `box-sizing`、字体(中文 PingFang SC/Microsoft YaHei)、自定义滚动条。 - 修复 Element Plus 弹层锁滚动导致的背景宽度抖动(`body[class*="parent--hidden"]`)。 - 工具类:`.page-section-title`、`.font-mono`、`.text-ellipsis`。 ## 16. 后端对接约定 - 响应结构:`{code, data, message}`,成功 `code` 为 0 或 200。 - 认证:登录返回 token,前端存 localStorage,请求头 `Authorization: Bearer ` 携带。 - 权限:后端接口分 `token_required`(登录可访问)与 `admin_required`(仅管理员);前端角色 `user/admin` 与之对应。 - 图像:检测图像需鉴权,通过 `getImageBlob` 拉取 Blob 后转 objectURL 预览,而非 `` 直连。 - 报告:生成(`POST /report/generate`)、多格式导出(`GET /report/export/:id?type=pdf|txt|word|excel`)、详情预览(`GET /report/detail/:id`)。 ## 17. 局域网部署时摄像头无法打开(安全上下文 / HTTPS) > **一键部署提示**:若使用项目根目录 `deploy.sh` 部署,本系统已内置 HTTPS(80/443 均可访问),并默认用 `mkcert` 自动签发证书——站点证书在 `./data/certs/`,根证书在 `./data/mkcert/rootCA.pem`。你只需把 `rootCA.pem` 下载并导入到浏览器所在客户端即可,无需手动执行下方步骤。 ### 17.1 问题现象 将系统部署到服务器后,通过局域网 `http://192.168.x.x:端口` 访问时: - 点击「开启摄像头」无反应; - 控制台可能出现 `navigator.mediaDevices is undefined` 或 `getUserMedia` 调用被拒绝; - 但本机通过 `http://localhost:端口` 访问却一切正常。 ### 17.2 根因 浏览器只在「安全上下文(Secure Context)」下才开放摄像头 API(`getUserMedia` / `MediaDevices`)。安全上下文必须满足以下任一条件: 1. **HTTPS** 协议访问; 2. 或通过 **`localhost` / `127.0.0.1`** 这类本地回环地址访问(浏览器对本地回环网开一面)。 局域网访问使用的是「IP + HTTP」地址(如 `http://192.168.1.100:8080`),既不是 HTTPS,也不是回环地址,因此属于**非安全上下文**,浏览器会直接禁用摄像头能力。这是浏览器的硬性安全策略,与前端代码无关。 --- ### 17.3 解决方案一:给内网服务配置 HTTPS(推荐,最彻底) 通过 Nginx 做 HTTPS 反向代理,并给内网 IP 颁发证书。证书推荐使用 `mkcert` 生成,可做到局域网内客户端**无警告**信任。 #### 步骤 1:安装 mkcert(服务器上) - **Ubuntu / Debian**: ```bash sudo apt update sudo apt install -y libnss3-tools curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" chmod +x mkcert-v*-linux-amd64 sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert ``` - **CentOS / RHEL**: ```bash sudo yum install -y nss-tools curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" chmod +x mkcert-v*-linux-amd64 sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert ``` - **Windows(PowerShell,管理员)**: ```powershell choco install mkcert # 或使用 scoop scoop install mkcert ``` #### 步骤 2:安装本地 CA ```bash mkcert -install ``` > 该命令会在本机安装一个本地 CA(根证书),`mkcert` 后续签发的证书都会被本机浏览器信任。查看根证书所在目录可用: ```bash mkcert -CAROOT # 输出示例:/root/.local/share/mkcert # 该目录下的 rootCA.pem 就是需要分发到客户端的根证书 ``` #### 步骤 3:为内网 IP 签发证书 ```bash # 换成你服务器的实际内网 IP(如需多个地址可并列) mkcert 192.168.1.100 # 生成结果(示例) # 192.168.1.100.pem —— 证书 # 192.168.1.100-key.pem —— 私钥 ``` #### 步骤 4:配置 Nginx HTTPS 反向代理 ```nginx server { listen 443 ssl; server_name 192.168.1.100; # 你的内网 IP ssl_certificate /etc/nginx/certs/192.168.1.100.pem; ssl_certificate_key /etc/nginx/certs/192.168.1.100-key.pem; # 前端静态资源 location / { root /path/to/dist; # 前端 build 产物目录 index index.html; try_files $uri $uri/ /index.html; # 支持前端路由 history 模式 } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/; # 换成后端实际监听地址/端口 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_pass` 端口需与后端服务实际监听端口一致。 校验并重载 Nginx: ```bash nginx -t # 校验配置 systemctl reload nginx ``` #### 步骤 5:把根证书分发到每一台访问设备(关键) `mkcert -CAROOT` 目录下的 `rootCA.pem`(根证书)必须安装到**每一台要通过 HTTPS 访问的客户端**,否则浏览器仍会提示「不安全」,摄像头依旧无法打开。 - **Windows 电脑**:双击 `rootCA.pem` → 「安装证书」→ 「本地计算机」→ 「将所有证书放入下列存储」→ 选择「受信任的根证书颁发机构」。 - **macOS 电脑**:双击安装后,在「钥匙串访问」中找到该证书,将信任改为「始终信任」。 - **Android 手机**:将 `rootCA.pem` 传到手机 → 设置 → 安全 → 加密与凭据 → 安装证书 → CA 证书。 - **iOS 手机**:将证书发送到手机(或托管下载)→ 打开安装描述文件 → 再到「设置 → 通用 → 关于本机 → 证书信任设置」中开启完全信任。 > 也可以把根证书文件放到一个内网可访问的下载地址,让用户自行下载安装。 #### 备选:使用 openssl 自签名证书 若不方便使用 `mkcert`,可用 `openssl` 生成自签名证书(浏览器首次访问仍会警告,需手动点「继续访问」或信任该证书): ```bash openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \ -keyout 192.168.1.100-key.pem \ -out 192.168.1.100.pem \ -subj "/CN=192.168.1.100" \ -addext "subjectAltName=IP:192.168.1.100" ``` 其余 Nginx 配置同上。 --- ### 17.4 解决方案二:Chrome 临时白名单(仅限电脑调试) 在**访问端电脑**的 Chrome 浏览器地址栏打开: ``` chrome://flags/#unsafely-treat-insecure-origin-as-secure ``` 操作步骤: 1. 在「Insecure origins treated as secure」输入框中填入完整访问地址,例如 `http://192.168.1.100:8080`; 2. 将右侧选项切换为 `Enabled`; 3. 点击右下角 `Relaunch` 重启浏览器; 4. 重新访问该地址,摄像头即可正常打开。 **限制说明**: - 仅对配置了白名单的**这台电脑的 Chrome** 生效,换电脑/换浏览器都要重新配置; - 手机浏览器无法使用此方法; - 该 Flag 可能随 Chrome 版本更新而改名或被移除,属于临时调试手段,不适合正式使用; - 若需要同时白名单多个地址,多个地址用英文逗号分隔。 --- ### 17.5 解决方案三:有域名就走域名 + HTTPS 如果有**域名**(公网域名或内网 DNS 解析的域名),最省事的做法是给域名配置证书后走 HTTPS 访问。 #### 场景 A:有公网域名 1. 用 `certbot`(Let's Encrypt)给域名签发受信任证书: ```bash sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d your-domain.com ``` 2. Nginx 配置中 `server_name` 使用域名,前端静态托管与后端反代同「方案一」第 4 步。 #### 场景 B:内网 DNS / 域名解析 1. 在路由器或内网 DNS 服务器上,把某个域名(如 `carbon.local`)解析到服务器内网 IP; 2. 用 `mkcert` 给该域名签发证书: ```bash mkcert carbon.local ``` 3. 把 `rootCA.pem` 分发到客户端(同「方案一」第 5 步),之后用 `https://carbon.local` 访问即可。 #### 场景 C:公网域名反代到内网(异地访问) 1. 服务器通过 frp / 内网穿透 / 云负载均衡等方式,将公网域名流量转发到内网服务; 2. 公网侧配置 HTTPS 证书(Let's Encrypt 或云厂商证书); 3. 内网侧保持 HTTP 即可,客户端统一通过公网 HTTPS 域名访问。 --- ### 17.6 方案对比与选择建议 | 方案 | 适用场景 | 是否彻底 | 客户端成本 | | --- | --- | --- | --- | | 方案一:内网 HTTPS(mkcert/自签名 + Nginx) | 纯局域网、无域名 | 是 | 需给每台客户端装根证书(一次性) | | 方案二:Chrome 白名单 | 单机临时调试 | 否 | 每台电脑手动配置,手机不可用 | | 方案三:域名 + HTTPS | 有域名 / 内网 DNS / 需异地访问 | 是 | 无(受信任证书)或仅装根证书 | **建议**: - 正式交付:优先选**方案一**(无域名)或**方案三**(有域名); - 开发联调:可临时用**方案二**快速验证,但最终交付必须切换为 HTTPS。