news 2026/4/27 18:45:31

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

作者头像

张小明

前端开发工程师

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

UniApp + Node.js 全栈实战:从零构建家庭安防监控系统

1. 项目概述与技术选型

想象一下,当你外出旅行时,只需打开手机就能实时查看家中的情况;或者当快递员按门铃时,远程确认对方身份——这些场景都可以通过自建安防监控系统实现。本教程将带你用UniApp和Node.js构建一个完整的远程监控解决方案,涵盖从摄像头调用到云端存储的全流程。

为什么选择这套技术栈?UniApp的跨平台特性让我们一套代码适配iOS、Android和Web,而Node.js轻量高效的特点非常适合处理实时视频流。相比市面上封闭的监控系统,自建方案具有以下优势:

  • 数据自主可控:所有视频流经自己的服务器,避免隐私泄露风险
  • 成本低廉:利用现有手机/平板作为监控设备,无需购买专业硬件
  • 高度定制:可根据需求灵活调整功能,如添加AI人脸识别

技术架构分为三个核心层:

  1. 客户端:UniApp调用设备摄像头,采集并编码视频流
  2. 传输层:WebSocket实现低延迟传输,HTTP用于文件上传
  3. 服务端:Node.js处理流媒体转发与存储,Express提供REST API

2. 客户端开发: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 class="container"> <camera ref="camera" device-position="back" flash="off" @error="onCameraError" style="width: 100%; height: 70vh;" /> <view class="control-panel"> <button @tap="switchCamera">切换镜头</button> <button :type="isRecording ? 'warn' : 'primary'" @tap="toggleRecording"> {{ 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 联调关键问题解决

常见联调问题及解决方案:

  1. 跨域问题

    • 服务端启用CORS中间件
    • 开发环境配置代理
    // vue.config.js module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } } }
  2. 视频格式兼容性

    • 客户端统一使用MP4格式录制
    camera.startRecord({ format: 'mp4', // ...其他参数 })
  3. 大文件上传中断

    • 实现分片上传
    • 添加重试机制
    // 客户端分片上传示例 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:contact@example.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 module="tf" lang="renderjs"> 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) } } } </script>

5.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 class="device-grid"> <view v-for="device in devices" :key="device._id" class="device-card" @click="selectDevice(device)" > <image :src="device.thumbnail || '/static/default-camera.png'" /> <text>{{ device.name }}</text> <view class="status" :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>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 18:28:21

函数-方法的定义与调用:代码复用的基础

在编程的世界里&#xff0c;函数&#xff08;或方法&#xff09;是代码复用的基石。无论是解决复杂问题还是简化重复操作&#xff0c;函数都能将零散的代码逻辑封装成独立的模块&#xff0c;让程序更清晰、更高效。想象一下&#xff0c;如果每次需要完成相同任务时都要重写代码…

作者头像 李华
网站建设 2026/4/16 18:27:03

从串口通信到图像处理:移位寄存器在FPGA中的5种实战用法(Verilog版)

从串口通信到图像处理&#xff1a;移位寄存器在FPGA中的5种实战用法&#xff08;Verilog版&#xff09; 在FPGA开发中&#xff0c;移位寄存器就像瑞士军刀一样多功能且不可或缺。它不仅是数字电路的基础构建块&#xff0c;更是连接底层硬件与复杂算法的桥梁。本文将带您超越教科…

作者头像 李华
网站建设 2026/4/16 18:23:32

AWS注册流程中的三大验证难题及解决方案

1. AWS注册流程中的电话验证失败问题 第一次注册AWS账户时&#xff0c;电话验证环节往往是最容易卡壳的地方。我自己刚开始用AWS时就遇到过这种情况&#xff0c;明明输入了正确的手机号码&#xff0c;却迟迟收不到验证码。后来才发现&#xff0c;原来问题出在国家/地区代码的选…

作者头像 李华
网站建设 2026/4/16 18:21:52

Python 入门(四)- Openpyxl 操作 Excel 教程

以往我们处理 Excel 文件&#xff0c;只能手动打开 Excel 文件进行操作。如果碰到大量且重复性高的任务&#xff0c;一个个编辑文件就特别费时费力。这时我们可以借助 Python 读取和写入 Excel 文件的库 来进行文件的批量以及自动化处理。Python 提供了好几个能够操作 Excel 的…

作者头像 李华