UniApp + Node.js 保姆级教程:手把手教你搭建一个简易的远程监控App(含完整前后端代码)

张开发
2026/4/16 18:28:38 15 分钟阅读

分享文章

UniApp + Node.js 保姆级教程:手把手教你搭建一个简易的远程监控App(含完整前后端代码)
UniApp Node.js 全栈实战从零构建家庭安防监控系统1. 项目概述与技术选型想象一下当你外出旅行时只需打开手机就能实时查看家中的情况或者当快递员按门铃时远程确认对方身份——这些场景都可以通过自建安防监控系统实现。本教程将带你用UniApp和Node.js构建一个完整的远程监控解决方案涵盖从摄像头调用到云端存储的全流程。为什么选择这套技术栈UniApp的跨平台特性让我们一套代码适配iOS、Android和Web而Node.js轻量高效的特点非常适合处理实时视频流。相比市面上封闭的监控系统自建方案具有以下优势数据自主可控所有视频流经自己的服务器避免隐私泄露风险成本低廉利用现有手机/平板作为监控设备无需购买专业硬件高度定制可根据需求灵活调整功能如添加AI人脸识别技术架构分为三个核心层客户端UniApp调用设备摄像头采集并编码视频流传输层WebSocket实现低延迟传输HTTP用于文件上传服务端Node.js处理流媒体转发与存储Express提供REST API2. 客户端开发UniApp摄像头集成2.1 基础环境搭建首先创建UniApp项目并安装必要依赖# 通过HBuilderX创建项目 vue create -p dcloudio/uni-preset-vue monitor-app # 安装摄像头插件 npm install dcloudio/uni-camera --save在pages.json中配置摄像头页面权限{ pages: [ { path: pages/camera/index, style: { navigationBarTitleText: 监控视图, app-plus: { permissions: [camera] } } } ] }2.2 视频采集核心代码创建pages/camera/index.vue实现多摄像头切换与流媒体控制template view classcontainer camera refcamera device-positionback flashoff erroronCameraError stylewidth: 100%; height: 70vh; / view classcontrol-panel button tapswitchCamera切换镜头/button button :typeisRecording ? warn : primary taptoggleRecording {{ isRecording ? 停止监控 : 开始监控 }} /button /view /view /template script export default { data() { return { isRecording: false, cameraPosition: back, recorder: null } }, methods: { async toggleRecording() { if (this.isRecording) { this.stopRecording() } else { await this.startRecording() } }, async startRecording() { try { const camera this.$refs.camera this.recorder await camera.startRecord({ quality: high, success: (res) { console.log(录制开始, res) } }) this.isRecording true } catch (err) { uni.showToast({ title: 启动失败: err.message, icon: none }) } }, async stopRecording() { const camera this.$refs.camera const { tempFilePath } await camera.stopRecord() this.uploadVideo(tempFilePath) this.isRecording false }, switchCamera() { this.cameraPosition this.cameraPosition back ? front : back this.$refs.camera.stopRecord() this.$nextTick(() { this.$refs.camera.startRecord() }) }, async uploadVideo(filePath) { uni.uploadFile({ url: https://your-server.com/api/upload, filePath, name: video, success: (res) { console.log(上传成功, res.data) } }) } } } /script提示真机调试时务必在manifest.json中配置摄像头权限声明iOS还需要在Xcode中启用相机权限3. 服务端开发Node.js视频处理3.1 Express服务器基础架构创建服务端项目并安装核心依赖mkdir monitor-server cd monitor-server npm init -y npm install express multer socket.io ffmpeg-static cors构建支持视频上传和实时转发的服务// server.js const express require(express) const multer require(multer) const path require(path) const { exec } require(child_process) const app express() const server require(http).createServer(app) const io require(socket.io)(server, { cors: { origin: * } }) // 文件存储配置 const storage multer.diskStorage({ destination: (req, file, cb) { cb(null, uploads/) }, filename: (req, file, cb) { cb(null, Date.now() path.extname(file.originalname)) } }) const upload multer({ storage }) // REST API端点 app.post(/api/upload, upload.single(video), (req, res) { const file req.file if (!file) return res.status(400).send(No file uploaded) // 转码为HLS格式 const outputPath streams/${file.filename.split(.)[0]} const cmd ffmpeg -i ${file.path} \ -profile:v baseline -level 3.0 \ -start_number 0 -hls_time 10 -hls_list_size 0 \ -f hls ${outputPath}.m3u8 exec(cmd, (error) { if (error) return res.status(500).send(转码失败) res.json({ url: /stream/${file.filename.split(.)[0]}.m3u8 }) }) }) // WebSocket实时通信 io.on(connection, (socket) { console.log(客户端连接:, socket.id) socket.on(stream, (data) { // 广播给其他客户端 socket.broadcast.emit(stream, data) }) socket.on(disconnect, () { console.log(客户端断开:, socket.id) }) }) server.listen(3000, () { console.log(服务运行在 http://localhost:3000) })3.2 视频处理优化方案针对不同场景我们提供三种视频处理方案方案类型延迟适用场景实现复杂度存储需求实时转发低(1s)即时监控中无HLS存储中(10s)历史回放高大帧快照低移动侦测低小实现帧提取与移动侦测的示例代码// motion-detection.js const Jimp require(jimp) const fs require(fs) async function detectMotion(currentFrame, prevFrame) { const threshold 0.1 // 变化阈值 const current await Jimp.read(currentFrame) const prev await Jimp.read(prevFrame) let diffCount 0 current.scan(0, 0, current.bitmap.width, current.bitmap.height, (x, y, idx) { const currPixel Jimp.intToRGBA(current.getPixelColor(x, y)) const prevPixel Jimp.intToRGBA(prev.getPixelColor(x, y)) const diff Math.abs(currPixel.r - prevPixel.r) / 255 Math.abs(currPixel.g - prevPixel.g) / 255 Math.abs(currPixel.b - prevPixel.b) / 255 if (diff threshold) diffCount }) const changeRatio diffCount / (current.bitmap.width * current.bitmap.height) return changeRatio 0.05 // 5%以上区域变化视为移动 }4. 前后端联调与部署4.1 联调关键问题解决常见联调问题及解决方案跨域问题服务端启用CORS中间件开发环境配置代理// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } }视频格式兼容性客户端统一使用MP4格式录制camera.startRecord({ format: mp4, // ...其他参数 })大文件上传中断实现分片上传添加重试机制// 客户端分片上传示例 const chunkSize 1024 * 1024 // 1MB const uploadChunk async (file, start, end) { const chunk file.slice(start, end) const formData new FormData() formData.append(chunk, chunk) formData.append(start, start) formData.append(total, file.size) return uni.uploadFile({ url: /api/upload-chunk, filePath: chunk, name: chunk, formData: { start, total: file.size } }) }4.2 生产环境部署指南服务器部署方案对比平台优点缺点适合场景云主机完全控制维护成本高高频访问Serverless自动扩展冷启动延迟间歇使用边缘计算低延迟价格较高多地分布推荐使用PM2进行Node.js进程管理# 全局安装PM2 npm install pm2 -g # 启动服务 pm2 start server.js --name monitor-server # 设置开机启动 pm2 startup pm2 save性能优化配置# Nginx配置示例 server { listen 80; server_name yourdomain.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; } location /stream { alias /path/to/streams; add_header Cache-Control no-cache; types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } } }5. 功能扩展与进阶优化5.1 实时通知系统集成WebPush实现移动端通知// 服务端推送逻辑 const webpush require(web-push) const vapidKeys { publicKey: YOUR_PUBLIC_KEY, privateKey: YOUR_PRIVATE_KEY } webpush.setVapidDetails( mailto:contactexample.com, vapidKeys.publicKey, vapidKeys.privateKey ) // 当检测到移动时 function onMotionDetected(subscription) { webpush.sendNotification(subscription, JSON.stringify({ title: 移动警报, body: 检测到可疑移动, icon: /assets/alert.png })).catch(err console.error(推送失败:, err)) }5.2 视频分析增强使用TensorFlow.js实现基础人脸检测!-- 在UniApp中通过renderjs使用TF.js -- script moduletf langrenderjs import * as tf from tensorflow/tfjs import * as facemesh from tensorflow-models/facemesh export default { async mounted() { this.model await facemesh.load() this.detectFrame() }, methods: { async detectFrame() { const video document.getElementById(camera-video) const predictions await this.model.estimateFaces(video) if (predictions.length 0) { this.$ownerInstance.callMethod(onFaceDetected) } requestAnimationFrame(this.detectFrame) } } } /script5.3 多设备管理方案实现设备绑定与切换的数据库设计// models/Device.js const mongoose require(mongoose) const DeviceSchema new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: User }, name: String, type: { type: String, enum: [camera, sensor] }, lastActive: Date, streamUrl: String, settings: { resolution: { type: String, default: 720p }, motionSensitivity: { type: Number, default: 5 } } }) module.exports mongoose.model(Device, DeviceSchema)设备状态管理界面建议布局template view classdevice-grid view v-fordevice in devices :keydevice._id classdevice-card clickselectDevice(device) image :srcdevice.thumbnail || /static/default-camera.png / text{{ device.name }}/text view classstatus :class{ online: device.isOnline }/view /view /view /template style .device-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; padding: 15px; } .device-card { position: relative; border-radius: 8px; overflow: hidden; } .status { position: absolute; top: 5px; right: 5px; width: 10px; height: 10px; border-radius: 50%; background: #ccc; } .status.online { background: #4CAF50; } /style

更多文章