当前位置: 首页 > news >正文

前端文件上传实践与后端处理——文件分块上传

文件上传是现代Web应用程序中常见的功能之一。在这篇博客中,我们将探讨一个简单但完整的前端文件上传实践,同时提供一个后端示例,演示如何处理上传的文件。我们将使用JavaScript作为前端语言,并结合Node.js作为后端环境。让我们开始吧!

前端文件上传

我们首先关注前端部分。我们将使用HTML、CSS和JavaScript来创建一个简单的文件上传表单,然后通过AJAX将文件传递给后端服务器。这里我们假设你已经具备了基本的前端开发知识。

1. HTML结构

首先,我们创建一个HTML结构,包含一个文件上传输入框、上传按钮和用于显示上传进度和信息的元素:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文件上传示例</title>
</head>
<body><input type="file" id="videoUploader" accept="video/mp4, video/ogg"><button id="uploadBtn">上传</button><p id="uploadInfo"></p><progress id="uploadProgress" value="0" max="100"></progress>
</body>
</html>

2. CSS样式(可选)

你可以根据需要添加一些CSS样式来美化页面。

3. JavaScript逻辑

接下来,我们将使用JavaScript来实现文件上传逻辑。我们从一个配置文件开始,然后实现上传函数和相关的辅助函数:

// config.js
// 导出一些常量,包括上传信息、允许上传的文件类型、块大小和上传接口的URL
// 这些常量将在前端和后端代码中使用
const BASE_URL = 'http://localhost:8000/';export const UPLOAD_INFO = {'NO_FILE': '请先选择文件','INVALID_TYPE': '不支持该类型文件上传','UPLOAD_FAILED': '上传失败','UPLOAD_SUCCESS': '上传成功'
}export const ALLOWED_TYPE = {'video/mp4': 'mp4','video/ogg': 'ogg'
}export const CHUNK_SIZE = 64 * 1024;export const API = {UPLOAD_VIDEO: BASE_URL + 'upload_video'
}// index.js
// 在闭包内部,定义一个立即执行的匿名函数,接受document作为参数
((doc) => {// 获取页面上的一些元素const oProgress = doc.querySelector('#uploadProgress'); // 上传进度条const oUploader = doc.querySelector('#videoUploader'); // 文件上传输入框const oInfo = doc.querySelector('#uploadInfo'); // 用于显示上传信息const oBtn = doc.querySelector('#uploadBtn'); // 上传按钮let uploadedSize = 0; // 已上传的文件大小,用于控制分块上传// 初始化函数const init = () => {bindEvent(); // 绑定事件}// 绑定事件函数function bindEvent() {oBtn.addEventListener('click', uploadVideo, false); // 点击上传按钮触发上传函数}// 异步上传函数async function uploadVideo() {// 获取文件对象const { files: [ file ] } = oUploader;if (!file) {// 检查是否选择了文件,如果没有则显示提示信息oInfo.innerText = UPLOAD_INFO['NO_FILE'];return;}if (!ALLOWED_TYPE[file.type]) {// 检查文件类型是否被允许,如果不允许则显示提示信息oInfo.innerText = UPLOAD_INFO['INVALID_TYPE'];return;}// 获取文件的一些基本信息const { name, type, size } = file;const fileName = new Date().getTime() + '_' + name;let uploadedResult = null;oProgress.max = size;oInfo.innerText = '';while (uploadedSize < size) {// 使用slice方法将文件切割成小块,实现分块上传const fileChunk = file.slice(uploadedSize, uploadedSize + CHUNK_SIZE);// 创建FormData对象,用于传递文件块和相关信息const formData = createFormData({name, type,size,fileName,uploadedSize,file: fileChunk});try {// 使用axios库发送POST请求,将文件块上传到后端uploadedResult = await axios.post(API.UPLOAD_VIDEO, formData);} catch (e) {// 捕获异常,如果上传失败,则显示提示信息oInfo.innerText = `${ UPLOAD_INFO['UPLOAD_FAILED'] }(${ e.message })`;return; }// 更新已上传的文件大小和进度条uploadedSize += fileChunk.size;oProgress.value = uploadedSize;}// 上传成功后,显示上传成功的提示信息,并将文件输入框置空oInfo.innerText = UPLOAD_INFO['UPLOAD_SUCCESS'];oUploader.value = null;// 根据后端返回的视频URL,创建一个视频元素并展示在页面上createVideo(uploadedResult.data.video_url);}// 创建FormData函数,将文件块和相关信息添加到FormData对象中function createFormData ({name, type,size,fileName,uploadedSize,file}) {const fd = new FormData();fd.append('name', name);fd.append('type', type);fd.append('size', size);fd.append('fileName', fileName);fd.append('uploadedSize', uploadedSize);fd.append('file', file);return fd;}// 创建视频元素函数,用于在页面上展示上传成功的视频function createVideo(src) {const oVideo = document.createElement('video');oVideo.controls = true;oVideo.width = '500';oVideo.src = src;document.body.appendChild(oVideo);}init(); // 初始化,执行绑定事件函数
})(document);
  1. 引入依赖项:

    • config.js:导出一些常量,包括上传信息、允许上传的文件类型、块大小和上传接口的URL。
    • axios:一个用于发起HTTP请求的库,用于将文件块上传到后端。
  2. 创建一个立即执行的匿名函数:

    • 接受document作为参数,并使用doc来代表document对象。
    • 这种方式可以避免全局变量污染,确保代码运行在独立的作用域中。
  3. 获取页面上的元素:

    • oProgress:代表上传进度条的<progress>元素。
    • oUploader:代表文件上传输入框的<input type="file">元素。
    • oInfo:用于显示上传信息的<p>元素。
    • oBtn:代表上传按钮的<button>元素。
  4. 初始化函数:

    • 调用bindEvent()函数,用于绑定点击上传按钮时的事件处理函数。
  5. 绑定事件函数:

    • 使用addEventListener()方法,为上传按钮添加一个点击事件监听器。
    • 当点击上传按钮时,将触发uploadVideo()函数进行文件上传。
  6. 异步上传函数uploadVideo()

    • 获取文件对象file,这是通过文件上传输入框oUploader获取的。
    • 检查是否选择了文件,如果没有则显示提示信息,并返回。
    • 检查文件类型是否被允许上传,如果不允许则显示提示信息,并返回。
    • 获取文件的一些基本信息,如文件名、文件类型和文件大小等。
    • 使用while循环,对文件进行分块上传:
      • 使用file.slice()方法将文件切割成小块(块大小由常量CHUNK_SIZE定义)。
      • 创建FormData对象,将文件块和相关信息添加到其中,用于传递给后端。
      • 使用axios.post()方法,将文件块上传到后端指定的URL(由常量API.UPLOAD_VIDEO定义)。
      • 如果上传失败,捕获异常并显示提示信息,并返回。
      • 更新已上传的文件大小和进度条的值。
    • 上传完成后,显示上传成功的提示信息,并将文件输入框的值置空。
    • 根据后端返回的视频URL,调用createVideo()函数在页面上创建视频元素并展示上传成功的视频。
  7. 创建FormData函数createFormData()

    • 将文件块和相关信息添加到FormData对象中,用于传递给后端。
  8. 创建视频元素函数createVideo()

    • 用于在页面上创建视频元素,并设置视频URL为上传成功后后端返回的视频URL。
  9. 初始化函数init()

    • 执行bindEvent()函数,将上传按钮和事件处理函数绑定在一起。
  10. 最后,通过调用init()函数来启动整个前端代码。

 

我们在前端代码中引入了axios库,这是一个用于发起HTTP请求的常用工具。在实际项目中,你需要确保在HTML文件中引入axios库,或者使用其他类似功能的库。

以上代码中,我们首先定义了一些常量,如允许上传的文件类型、块大小等。然后我们通过事件监听捕获“上传”按钮的点击事件,执行上传函数。在上传函数中,我们通过分块上传的方式,将文件切割为多个小块,并逐个发送给后端进行处理。上传过程中,我们还会实时更新上传进度条和显示上传信息。

后端文件处理

现在让我们关注后端处理部分。我们将使用Node.js和Express来搭建一个简单的后端服务器,接收前端传递的文件块,并将它们合并成完整的文件。

1. 安装依赖

首先,在项目目录下创建一个package.json文件,然后运行以下命令安装依赖:

以上代码使用Express框架搭建了一个简单的服务器,并设置了文件上传的路由。我们可以通过/upload_video接口来上传文件块,后端根据上传的信息,将文件块保存在服务器端,直到所有块都接收完成后,合并成完整的文件。

npm init -y
npm install express body-parser express-fileupload

2. 后端代码

接下来,我们创建一个server.js文件,编写后端代码:

const express = require('express');
const bodyParser = require('body-parser');
const uploader = require('express-fileupload');
const { extname, resolve } = require('path');
const { existsSync, appendFileSync, writeFileSync } = require('fs');const app = express();
const PORT = 8000;// 设置中间件,解析请求的JSON数据和文件上传
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(uploader());// 设置静态资源目录,用于存放上传的临时文件块
app.use('/', express.static('upload_temp'));// 允许跨域访问
app.all('*', (req, res, next) => {res.header('Access-Control-Allow-origin', '*');res.header('Access-Control-Allow-Methods', 'POST,GET');next();
});// 处理文件上传的POST请求,对应的URL为/upload_video
app.post('/upload_video', (req, res) => {// 从请求体中获取传递的文件信息和文件块const { name, type, size, fileName, uploadedSize } = req.body;const { file } = req.files;if (!file) {// 检查是否传递了文件块,如果没有则返回错误信息res.send({code: 1001,msg: 'No file uploaded'});return;}if (!ALLOWED_TYPE[type]) {// 检查文件类型是否被允许,如果不允许则返回错误信息res.send({code: 1002,msg: 'The type is not allowed for uploading.'});return;}const filename = fileName + extname(name);const filePath = resolve(__dirname, './upload_temp/' + filename);if (uploadedSize !== '0') {if (!existsSync(filePath)) {// 检查文件是否存在,如果不存在则返回错误信息res.send({code: 1003,msg: 'No file exists'});return;}// 文件已存在,则将当前文件块追加到已有的文件中appendFileSync(filePath, file.data);// 返回成功信息和视频URLres.send({code: 0,msg: 'Appended',video_url: 'http://localhost:8000/' + filename});return;}// 第一个文件块,创建一个新文件并写入当前块的数据writeFileSync(filePath, file.data);// 返回成功信息res.send({code: 0,msg: 'File is created'});
});// 启动服务,监听指定端口
app.listen(PORT, () => {console.log('Server is running on ' + PORT);
});
  1. 引入依赖项:

    • express:Express框架,用于构建Web服务器。
    • body-parser:解析请求体中的JSON数据。
    • express-fileupload:处理文件上传请求。
    • path:Node.js内置模块,用于处理文件路径。
    • fs:Node.js内置模块,用于文件系统操作。
  2. 创建Express应用:

    const app = express();
    const PORT = 8000;
    

  3. 使用中间件:

    • bodyParser中间件用于解析请求体中的JSON数据和表单数据。
    • uploader中间件用于处理文件上传请求。
    • 设置静态资源目录'/upload_temp',用于存放上传的临时文件块。
  4. 设置跨域访问: 使用app.all()方法设置允许跨域请求的响应头。

  5. 处理文件上传请求:

    • 使用app.post('/upload_video', ...)定义处理文件上传的POST请求,对应的URL是/upload_video
    • 从请求体中获取传递的文件信息和文件块,包括文件名、文件类型、文件大小、文件块的上传起始位置等。
    • 检查文件块是否存在,如果不存在则返回错误信息。
    • 检查文件类型是否被允许,如果不允许则返回错误信息。
    • 根据文件名和文件类型生成新的文件名,并计算文件的存储路径。
    • 如果不是第一个文件块,将当前文件块追加到已有的文件中。
    • 如果是第一个文件块,创建一个新文件并写入当前块的数据。
    • 返回成功信息,并包含上传成功后的视频URL。
  6. 启动服务: 使用app.listen()方法启动服务器,监听指定的端口(在此例中为8000)。

 

运行项目

现在,我们已经完成了前端和后端的代码编写。接下来,我们需要在本地运行项目,以测试文件上传的功能。

1. 创建上传临时目录

在项目根目录下创建一个名为upload_temp的文件夹,用于保存上传的临时文件块。

2. 启动服务器

运行以下命令启动后端服务器:

node server.js

3. 启动前端

将前端代码(HTML、CSS和JavaScript)放置在一个文件夹中,然后在该文件夹下运行一个HTTP服务器(使用npm和yarn也没问题, npm install——npm run dev/yarn——yarn dev),比如使用http-server

npx http-server

现在,通过浏览器访问前端页面(通常是http://localhost:8080)。选择一个支持的视频文件(.mp4或.ogv格式),点击上传按钮,你将看到上传进度条显示上传进度。上传完成后,页面将显示上传成功的信息,并在页面上展示上传的视频文件。 

结束语

在本篇博客中,我们学习了如何实现一个简单的前端文件上传功能,并结合Node.js和Express搭建了一个后端服务器来处理文件上传。在实际项目中,你可以根据需求对文件上传的功能进行扩展和优化。希望这篇博客能够帮助你理解文件上传的基本原理和实践方法。谢谢阅读!

 

 

如果你对前端文件上传还有疑问,或者对其他技术主题感兴趣,欢迎加入讨论群或者关注!

 

相关文章:

前端文件上传实践与后端处理——文件分块上传

文件上传是现代Web应用程序中常见的功能之一。在这篇博客中&#xff0c;我们将探讨一个简单但完整的前端文件上传实践&#xff0c;同时提供一个后端示例&#xff0c;演示如何处理上传的文件。我们将使用JavaScript作为前端语言&#xff0c;并结合Node.js作为后端环境。让我们开…...

SFP6012A-ASEMI代理海矽美快恢复二极管参数、尺寸、规格

编辑&#xff1a;ll SFP6012A-ASEMI代理海矽美快恢复二极管参数、尺寸、规格 型号&#xff1a;SFP6012A 品牌&#xff1a;ASEMI 封装&#xff1a;TO-247AC 恢复时间&#xff1a;100ns 正向电流&#xff1a;60A 反向耐压&#xff1a;1200V 芯片大小&#xff1a;102MIL*2…...

githack的安装步骤+一次错误体验

一.githack的安装步骤 1.要在Kali Linux上安装GitHack工具&#xff0c;您可以按照以下步骤操作&#xff1a; 打开终端并使用以下命令克隆GitHack存储库&#xff1a; git clone https://github.com/lijiejie/GitHack.git2.进入GitHack目录&#xff1a; cd GitHack3.安装依赖项…...

【Spring框架】SpringBoot创建和使用

目录 什么是SpringBoot&#xff1f;SpringBoot优点创建SpringBootSpringBoot使用 什么是SpringBoot&#xff1f; Spring 的诞⽣是为了简化 Java 程序的开发的&#xff0c;⽽ Spring Boot 的诞⽣是为了简化 Spring 程序开发的。 SpringBoot优点 1.起步依赖(创建的时候就可以方…...

【C语言项目】多臂井径电子测井成像项目(一)

目录 1、目的和意义2、本章概述3、串口R2324、OpenGL5、开发环境6、环境配置6.1、VS安装OpenGL6.2、虚拟串口生成工具 7、成品速览参考文献 1、目的和意义 本项目为获取矿藏地层的油气当量和及时精确地测量含油、含气层的压力及温度值的需求&#xff0c;辅助生产管理人员完成对…...

力扣 56. 合并区间

题目来源&#xff1a;https://leetcode.cn/problems/merge-intervals/description/ C题解&#xff1a;根据左区间排序&#xff0c;更新每一段的右区间最大值&#xff0c;直到间断。 class Solution { public:static bool cmp(vector<int> & a, vector<int> &a…...

前端开发Vue3.0 标签setup语法『UI组件库』之『模态框』【业务提升必备】

封装模态框需要定义的参数 title //弹窗标题 show // 是否显示弹窗 width // 弹窗宽度 height // 弹窗高度 borderRadius // 弹窗圆角 headerColor // 弹窗顶部颜色 contentText // 内容文本 contentTextCorder //内容文本颜色 position // 标题的位置 …...

在CSDN学Golang云原生(Kubernetes二开)

一&#xff0c;通过client-go管理集群资源 Kubernetes提供了client-go库&#xff0c;该库可以让开发人员使用Golang编写的应用程序与Kubernetes API进行交互。通过client-go&#xff0c;你可以创建、更新和删除Kubernetes资源&#xff0c;并查询集群状态等信息。 以下是一个示…...

chatglm-6b量化推理指标记录

chatglm量化推理指标对比&#xff0c;单卡显存32G, 保持batchsize为64不变。通过不同的量化可以节省显存进而提升提升batch size&#xff0c;加快全量数据的推理速度。当然通过量化可以降低大模型的显存使用门槛。...

Android kotlin系列讲解之最佳的UI体验 - Material Design 实战

目录 一、什么是Material Design二、Toolbar三、滑动菜单1、DrawerLayout2、NavigationView 四、悬浮按钮和可交互提示1、FloatingActionButton2、Snackbar3、CoordinatorLayout 五、卡片式布局1、MaterialCardView2、AppBarLayout 六、可折叠式标题栏1、CollapsingToolbarLayo…...

链表基础知识

一、什么是链表 链表是一种物理存储结构上非连续&#xff0c;非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 链表的结构是多式多样的&#xff0c;当时通常用的也就是两种&#xff1a; &#xff08;1&#xff09;第一种是无头非循环单向…...

process.env.npm_config_argv的值3个参数remain、cooked、original什么含义

在使用Webpack进行打包时&#xff0c;判断process.env.npm_config_argv的值通常是为了根据命令行参数来决定打包的行为。process.env.npm_config_argv是一个环境变量&#xff0c;保存了当前运行的npm命令和其参数。 具体而言&#xff0c;process.env.npm_config_argv的值是一个…...

【飞书】飞书导出md文档 | 飞书markdown文档导出 | 解决飞书只能导出pdf word

一、飞书导出markdown github地址&#xff1a;https://github.com/Wsine/feishu2md 这是一个下载飞书文档为 Markdown 文件的工具&#xff0c;使用 Go 语言实现。 请看这里&#xff1a;招募有需求和有兴趣的开发者&#xff0c;共同探讨开发维护&#xff0c;有兴趣请联系。 二、…...

零信任网络架构与实现技术的研究与思考

目前&#xff0c;国外已有较多有关零信任网络的研究与实践&#xff0c;包括谷歌的 BeyondCorp、BeyondProd&#xff0c;软件定义边界&#xff08;Software Defined Perimeter&#xff0c;SDP&#xff09; 及盖特提出的“持续自适应风险与信任评估”等。国内也有不少安全厂商积极…...

Unity 性能优化二:内存问题

目录 策略导致的内存问题 GFX内存 纹理资源 压缩格式 Mipmap 网格资源 Read/Write 顶点数据 骨骼 静态合批 Shader资源 Reserved Memory RenderTexture 动画资源 音频资源 字体资源 粒子系统资源 Mono堆内存 策略导致的内存问题 1. Assetbundle 打包的时候…...

JavaScript与TypeScript的区别

JavaScript和TypeScript是两种不同的编程语言&#xff0c;在一些方面有一些区别。 1. 类型系统&#xff1a;JavaScript是一种动态类型语言&#xff0c;变量的类型是在运行时确定的&#xff0c;并且可以随时更改。而TypeScript引入了静态类型系统&#xff0c;可以在编译时检查代…...

【NetCore】05-使用Autofac增强容器能力

文章目录 1.什么情况下需要引入第三方容器组件2.如何集成Autoface 1.什么情况下需要引入第三方容器组件 基于名称的注入属性注入子容器基于动态代理的AOP 核心扩展点&#xff1a;IServiceProviderFactory 第三方注入容器均使用这个类作为扩展点&#xff0c;将其注入到框架中…...

sparksql参数

Spark参数场景配置 参数类型 参数 参数说明 平台默认值 场景与建议 资源申请 spark.executor.memory Executor Java进程的堆内存大小 即Executor Java进程的Xmx值 2g 默认设置,或者同时等比例增大,最高不超过默认值的3倍,超过的单独拿出来看下 (注意作业是否数据倾斜&…...

STM32读写内部Flash

参考&#xff1a;https://blog.csdn.net/Caramel_biscuit/article/details/131925715 参考&#xff1a;https://blog.csdn.net/qq_36075612/article/details/124087574?spm1001.2014.3001.5502 目录 内存映射内部Flash的构成对内部Flash的写入过程查看工程内存的分布ROM加载空…...

golang文件锁,目录锁,syscall包的使用

先说结论 1. golang提供了syscall包来实现文件/目录的加锁&#xff0c;解锁 2. syscall包属于文件锁&#xff0c;是比较底层的技术&#xff0c;并不能在所有操作系统上完全实现&#xff0c;linux上实现了&#xff0c;windows下面就没有 3. 加锁时调用syscall.Flock(fd&#…...

数据库数据恢复-Syabse数据库存储页底层数据杂乱的数据恢复案例

数据库恢复环境&#xff1a; Sybase版本&#xff1a;SQL Anywhere 8.0。 数据库故障&#xff1a; 数据库所在的设备意外断电后&#xff0c;数据库无法启动。 错误提示&#xff1a; 使用Sybase Central连接后报错&#xff1a; 数据库故障分析&#xff1a; 经过北亚企安数据恢复…...

移远通信推出新一代高算力智能模组SG885G-WF,为工业和消费级IoT应用带来全新性能标杆

2023年7月24日&#xff0c;全球领先的物联网整体解决方案供应商移远通信宣布&#xff0c;正式推出其新一代旗舰级安卓智能模组SG885G-WF。该智能模组具有高达48 TOPS 的AI综合算力、强大性能及丰富的多媒体功能&#xff0c;非常适用于需要高处理能力和多媒体功能的工业和消费者…...

微信小程序开发,小程序类目符合,线上版本无权限申请wx.getLocation接口

我开发 的小程序类目符合wx.getLocation接口的申请标准 但是却还是显示无权限申请 后来研究好久才发现&#xff0c;小程序需要在发布线上版本时提交用户隐私保护指引 如未设置也可以在 设置-服务内容声明-用户隐私保护指引-声明处理用户信息项并补充填写后提交用户隐私协议审核…...

vue2企业级项目(五)

vue2企业级项目&#xff08;五&#xff09; 页面适配、主题切换 1、适配 项目下载插件 npm install --save-dev style-resources-loader vue-cli-plugin-style-resources-loader修改vue.config.js部分内容 const path require("path");module.exports {pluginOpt…...

【HTML5】拖放详解及实现案例

文章目录 效果预览代码实现 效果预览 代码实现 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>一颗不甘坠落的流星</title><style>#div1,#div2 {float: left;width: 100px;height: 27px;margin: 10px;paddin…...

Codeforces Round 888 (Div. 3)(视频讲解全部题目)

[TOC](Codeforces Round 888 (Div. 3)&#xff08;视频讲解全部题目&#xff09;) Codeforces Round 888 (Div. 3)&#xff08;A–G&#xff09;全部题目详解 A Escalator Conversations #include<bits/stdc.h> #define endl \n #define INF 0x3f3f3f3f using namesp…...

MySQL之深入InnoDB存储引擎——物理文件

文章目录 一、参数文件二、日志文件三、表结构定义文件四、InnoDB 存储引擎文件1、表空间文件2、重做日志文件 一、参数文件 当 MySQL 实例启动时&#xff0c;数据库会先去读一个配置参数文件&#xff0c;用来寻找数据库的各种文件所在位置以及指定某些初始化参数。在默认情况…...

Jquery操作html常用函数

1. text() 获取元素的文本内容&#xff1a;$("#element").text(); 设置元素的文本内容&#xff1a;$("#element").text("New Text"); 2. html() 获取元素的 HTML 内容&#xff1a;$("#element").html(); 设置元素的 HTML 内容&am…...

【Lua学习笔记】Lua进阶——Table,迭代器

文章目录 官方唯一指定数据结构--tabletable的一万种用法字典和数组 迭代器ipairs()pairs() 回到Table 在【Lua学习笔记】Lua入门中我们讲到了Lua的一些入门知识点&#xff0c;本文将补充Lua的一些进阶知识 官方唯一指定数据结构–table 在上篇文章的最后&#xff0c;我们指出…...

重庆市北斗新型智慧城市政府项目

技术栈&#xff1a;使用vue2JavaScriptElementUIvuexaxiosmapboxcesium 项目描述&#xff1a;重庆市北斗新型智慧城市政府项目是基于千寻孪界开发的一款智慧城市项目&#xff0c;包含车辆实时位置定位&#xff0c;智能设备的报警&#xff0c;基础设施的部设等等功能 工作内容&a…...