能源化工企业网页应用,JAVA如何实现大文件的分块与断点续传?

大文件上传系统开发指南(基于原生JS+SpringBoot)

项目概述

大家好,我是一个陕西的Java程序员,最近接了个"刺激"的外包项目 - 要开发一个支持20G文件上传下载的系统,还得兼容IE9这种古董浏览器。客户要求用原生JS实现(不能借jQuery的力),还要支持文件夹上传、加密传输、断点续传等高级功能。预算只有100元?没问题,咱们程序员最擅长的就是用爱发电!

技术选型分析

经过深思熟虑(和几根头发的牺牲),我决定采用以下技术方案:

  1. 前端:Vue3 CLI + 原生JS实现WebUploader功能(兼容IE9)
  2. 后端:SpringBoot + 阿里云OSS
  3. 数据库:MySQL(主要存用户信息和文件元数据)
  4. 加密:前端SM4(国密) + 后端AES双重加密
  5. 断点续传:基于文件分片和本地存储记录

前端实现(Vue3 + 原生JS)

1. 兼容IE9的文件夹上传组件

export default { name: 'FileUploader', data() { return { fileList: [], chunkSize: 5 * 1024 * 1024, // 5MB分片 concurrent: 3 // 并发上传数 } }, methods: { triggerFileInput() { document.getElementById('fileInput').click(); }, handleFileChange(e) { const files = e.target.files; if (!files.length) return; // 处理文件夹结构 const fileTree = this.buildFileTree(files); this.prepareUpload(fileTree); }, // 构建文件树结构(保留文件夹层级) buildFileTree(files) { const tree = {}; for (let i = 0; i < files.length; i++) { const file = files[i]; const path = file.webkitRelativePath || file.relativePath || file.name; const parts = path.split('/'); let currentLevel = tree; for (let j = 0; j < parts.length - 1; j++) { const dir = parts[j]; if (!currentLevel[dir]) { currentLevel[dir] = { __files__: [] }; } currentLevel = currentLevel[dir]; } // 添加文件信息 currentLevel.__files__.push({ file: file, relativePath: path, size: file.size, loaded: 0, progress: 0, chunks: Math.ceil(file.size / this.chunkSize), uploadedChunks: 0 }); } return tree; }, // 准备上传队列 prepareUpload(fileTree) { const flattenFiles = []; // 扁平化文件树(保留路径信息) const traverse = (node, parentPath = '') => { for (const key in node) { if (key === '__files__') { node[key].forEach(fileItem => { flattenFiles.push({ ...fileItem, relativePath: parentPath ? `${parentPath}/${fileItem.relativePath}` : fileItem.relativePath }); }); } else { const newPath = parentPath ? `${parentPath}/${key}` : key; traverse(node[key], newPath); } } }; traverse(fileTree); this.fileList = flattenFiles; // 开始上传 this.startUpload(); }, // 开始上传(带并发控制) startUpload() { let activeUploads = 0; const uploadNext = () => { if (activeUploads >= this.concurrent) return; const fileItem = this.fileList.find(f => f.progress < 100); if (!fileItem) { if (activeUploads === 0) { this.$emit('upload-complete'); } return; } activeUploads++; this.uploadFile(fileItem).finally(() => { activeUploads--; uploadNext(); }); // 立即检查下一个 uploadNext(); }; // 初始启动 for (let i = 0; i < this.concurrent; i++) { uploadNext(); } }, // 分片上传文件 async uploadFile(fileItem) { const file = fileItem.file; const totalChunks = Math.ceil(file.size / this.chunkSize); // 检查断点续传信息 const uploadInfo = this.getUploadInfo(fileItem.relativePath); let startChunk = uploadInfo ? uploadInfo.uploadedChunks : 0; for (let i = startChunk; i < totalChunks; i++) { const start = i * this.chunkSize; const end = Math.min(start + this.chunkSize, file.size); const chunk = file.slice(start, end); // 读取分片内容(兼容IE9) const chunkData = await this.readFileAsArrayBuffer(chunk); // SM4加密(前端加密) const encryptedChunk = this.sm4Encrypt(chunkData); // 上传分片 const formData = new FormData(); formData.append('file', new Blob([encryptedChunk]), file.name); formData.append('chunkIndex', i); formData.append('totalChunks', totalChunks); formData.append('relativePath', fileItem.relativePath); formData.append('fileSize', file.size); formData.append('fileMd5', await this.calculateMD5(chunk)); try { const response = await fetch('/api/upload/chunk', { method: 'POST', body: formData }); if (!response.ok) throw new Error('Upload failed'); // 更新进度 fileItem.uploadedChunks = i + 1; fileItem.loaded = end; fileItem.progress = Math.round((fileItem.uploadedChunks / totalChunks) * 100); // 保存上传进度(使用localStorage) this.saveUploadInfo(fileItem.relativePath, { uploadedChunks: fileItem.uploadedChunks, totalChunks: totalChunks, fileSize: file.size }); this.$forceUpdate(); } catch (error) { console.error('Chunk upload failed:', error); // 失败后重试当前分片 i--; await new Promise(resolve => setTimeout(resolve, 1000)); } } // 所有分片上传完成,通知合并 if (fileItem.uploadedChunks === totalChunks) { await fetch('/api/upload/merge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ relativePath: fileItem.relativePath, fileSize: file.size, totalChunks: totalChunks }) }); // 清除本地存储的上传信息 this.clearUploadInfo(fileItem.relativePath); } }, // 兼容IE9的文件读取方法 readFileAsArrayBuffer(file) { return new Promise((resolve, reject) => { if (typeof FileReader === 'undefined') { // IE9 fallback const reader = new ActiveXObject("Scripting.FileSystemObject"); const stream = new ActiveXObject("ADODB.Stream"); stream.Type = 1; // binary stream.Open(); stream.LoadFromFile(file); const arrayBuffer = stream.Read(); stream.Close(); resolve(arrayBuffer); } else { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsArrayBuffer(file); } }); }, // 简化的SM4加密(实际项目中应该使用成熟的加密库) sm4Encrypt(data) { // 这里应该是真正的SM4加密实现 // 为了示例,我们只是返回原始数据(实际项目中不要这样做!) console.warn('实际项目中请替换为真正的SM4加密实现'); return data; }, // 计算MD5(用于分片校验) calculateMD5(file) { return new Promise((resolve) => { // 实际项目中应该使用真正的MD5计算 // 这里简化为固定值(实际项目中不要这样做!) resolve('dummy-md5-hash'); }); }, // 断点续传相关方法(使用localStorage) getUploadInfo(relativePath) { const key = `upload_progress_${relativePath}`; const data = localStorage.getItem(key); return data ? JSON.parse(data) : null; }, } }

后端实现(SpringBoot)

1. 文件上传控制器

@RestController@RequestMapping("/api/upload")publicclassFileUploadController{@AutowiredprivateFileChunkServicefileChunkService;@AutowiredprivateOSSClientossClient;@Value("${oss.bucketName}")privateStringbucketName;// 上传分片@PostMapping("/chunk")publicResponseEntityuploadChunk(@RequestParam("file")MultipartFilefile,@RequestParam("chunkIndex")intchunkIndex,@RequestParam("totalChunks")inttotalChunks,@RequestParam("relativePath")StringrelativePath,@RequestParam("fileSize")longfileSize,@RequestParam("fileMd5")StringfileMd5){try{// AES解密(后端解密)byte[]decryptedBytes=AesUtil.decrypt(file.getBytes(),"your-secret-key-123");// 保存分片到临时目录StringtempDir=System.getProperty("java.io.tmpdir")+"/upload_chunks/"+fileMd5;FilechunkFile=newFile(tempDir+"/"+chunkIndex);Files.createParentDirs(chunkFile);Files.write(decryptedBytes,chunkFile);// 记录分片信息到数据库FileChunkchunk=newFileChunk();chunk.setFileMd5(fileMd5);chunk.setChunkIndex(chunkIndex);chunk.setTotalChunks(totalChunks);chunk.setRelativePath(relativePath);chunk.setFileSize(fileSize);chunk.setUploadTime(newDate());fileChunkService.save(chunk);returnResponseEntity.ok().build();}catch(Exceptione){returnResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}}// 合并分片@PostMapping("/merge")publicResponseEntitymergeChunks(@RequestBodyMergeRequestrequest){try{StringfileMd5=request.getFileMd5();StringtempDir=System.getProperty("java.io.tmpdir")+"/upload_chunks/"+fileMd5;// 检查所有分片是否已上传Listchunks=fileChunkService.findByFileMd5(fileMd5);if(chunks.size()!=request.getTotalChunks()){returnResponseEntity.badRequest().body("Not all chunks uploaded");}// 创建临时合并文件FilemergedFile=newFile(tempDir+"/merged_"+System.currentTimeMillis());try(FileOutputStreamfos=newFileOutputStream(mergedFile);BufferedOutputStreammergingStream=newBufferedOutputStream(fos)){// 按顺序合并分片for(inti=0;i<request.getTotalChunks();i++){FilechunkFile=newFile(tempDir+"/"+i);Files.copy(chunkFile,mergingStream);}}// 计算合并后文件的MD5(校验用)StringactualMd5=DigestUtils.md5DigestAsHex(newFileInputStream(mergedFile));if(!actualMd5.equals(fileMd5)){returnResponseEntity.badRequest().body("File MD5 mismatch");}// 上传到OSS(保留路径结构)StringossKey="uploads/"+request.getRelativePath();ossClient.putObject(bucketName,ossKey,mergedFile);// 保存文件元数据到数据库FileInfofileInfo=newFileInfo();fileInfo.setRelativePath(request.getRelativePath());fileInfo.setFileSize(request.getFileSize());fileInfo.setOssKey(ossKey);fileInfo.setUploadTime(newDate());fileInfo.setLastModified(newDate());fileInfoService.save(fileInfo);// 清理临时文件FileUtils.deleteDirectory(newFile(tempDir));returnResponseEntity.ok().build();}catch(Exceptione){returnResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}}}

2. 文件下载控制器

@RestController@RequestMapping("/api/download")publicclassFileDownloadController{@GetMapping("/info")publicResponseEntity>getFileInfo(@RequestParamStringpath){Listfiles=fileInfoService.findByPathPrefix(path);returnResponseEntity.ok(files.stream().map(this::convertToDTO).collect(Collectors.toList()));}// 分片下载文件(大文件支持)@GetMapping("/file")publicResponseEntitydownloadFile(@RequestParamStringossKey,@RequestParam(required=false)Longstart,@RequestParam(required=false)Longend){try{// 从OSS获取文件对象OSSObjectossObject=ossClient.getObject(bucketName,ossKey);// 如果请求了范围下载if(start!=null&&end!=null){InputStreaminputStream=ossObject.getObjectContent();longcontentLength=end-start+1;// 跳过前面的字节inputStream.skip(start);// 创建限制长度的输入流InputStreamlimitedStream=newLimitedInputStream(inputStream,contentLength);returnResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\""+URLEncoder.encode(ossKey.substring(ossKey.lastIndexOf('/')+1),"UTF-8")+"\"").header(HttpHeaders.CONTENT_RANGE,"bytes "+start+"-"+end+"/*").header(HttpHeaders.ACCEPT_RANGES,"bytes").contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(contentLength).body(newInputStreamResource(limitedStream));}else{// 完整文件下载returnResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\""+URLEncoder.encode(ossKey.substring(ossKey.lastIndexOf('/')+1),"UTF-8")+"\"").contentType(MediaType.APPLICATION_OCTET_STREAM).body(newInputStreamResource(ossObject.getObjectContent()));}}catch(Exceptione){returnResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();}}}

数据库设计

-- 文件信息表CREATETABLEfile_info(idBIGINTAUTO_INCREMENTPRIMARYKEY,relative_pathVARCHAR(1000)NOTNULLCOMMENT'文件相对路径(保留层级结构)',file_sizeBIGINTNOTNULLCOMMENT'文件大小(字节)',oss_keyVARCHAR(500)NOTNULLCOMMENT'OSS存储key',upload_timeDATETIMENOTNULLCOMMENT'上传时间',last_modifiedDATETIMENOTNULLCOMMENT'最后修改时间',UNIQUEKEYuk_relative_path(relative_path(255)))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;-- 文件分片表(用于断点续传)CREATETABLEfile_chunk(idBIGINTAUTO_INCREMENTPRIMARYKEY,file_md5VARCHAR(32)NOTNULLCOMMENT'文件MD5(作为唯一标识)',chunk_indexINTNOTNULLCOMMENT'分片索引',total_chunksINTNOTNULLCOMMENT'总分片数',relative_pathVARCHAR(1000)NOTNULLCOMMENT'文件相对路径',file_sizeBIGINTNOTNULLCOMMENT'文件总大小',upload_timeDATETIMENOTNULLCOMMENT'上传时间',INDEXidx_file_md5(file_md5))ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;

项目部署说明

  1. 前端部署

    • 使用Vue CLI构建生产版本:npm run build
    • 将生成的dist目录内容部署到Nginx或Apache
  2. 后端部署

    • 使用Maven打包:mvn clean package
    • 生成JAR文件后上传到阿里云ECS
    • 使用java -jar命令运行,或配置为系统服务
  3. Nginx配置示例(支持大文件上传):

server { listen 80; server_name your-domain.com; client_max_body_size 102400m; # 100GB proxy_read_timeout 600s; proxy_send_timeout 600s; location / { root /path/to/frontend/dist; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

兼容性处理要点

  1. IE9兼容性

    • 使用ActiveXObject替代FileReader
    • 避免使用ES6+语法
    • 使用fetch的polyfill或改用XMLHttpRequest
  2. 文件夹上传

    • 利用webkitdirectory属性(Chrome等)
    • IE中使用``并手动解析路径
  3. 加密处理

    • 前端使用SM4(国密算法)加密
    • 后端使用AES二次加密
    • 提供加密开关配置

开发建议

  1. 分阶段开发

    • 第一阶段:实现基本文件上传下载
    • 第二阶段:添加文件夹支持
    • 第三阶段:实现断点续传
    • 第四阶段:添加加密功能
  2. 测试要点

    • 大文件上传(>5GB)
    • 网络中断后恢复上传
    • 文件夹层级结构验证
    • 跨浏览器兼容性测试
  3. 性能优化

    • 分片大小调整(5MB-10MB比较合适)
    • 并发上传数控制(3-5个并发)
    • 使用Web Worker处理加密计算

完整项目结构

large-file-upload/ ├── frontend/ # 前端Vue3项目 │ ├── src/ │ │ ├── components/ │ │ │ └── FileUploader.vue │ │ ├── App.vue │ │ └── main.js │ ├── public/ │ └── package.json ├── backend/ # 后端SpringBoot项目 │ ├── src/ │ │ ├── main/ │ │ │ ├── java/com/example/upload/ │ │ │ │ ├── controller/ │ │ │ │ ├── service/ │ │ │ │ ├── model/ │ │ │ │ └── Application.java │ │ │ └── resources/ │ │ │ ├── application.yml │ │ │ └── application-dev.yml │ └── pom.xml ├── docs/ # 开发文档 │ ├── api.md │ ├── deployment.md │ └── compatibility.md └── README.md

最后的话

兄弟,这个项目确实有点挑战性,特别是100元预算还要兼容IE9。不过咱们程序员不就是喜欢挑战吗?我建议:

  1. 先实现核心功能(文件上传下载)
  2. 再逐步添加高级功能
  3. 重点测试断点续传和文件夹结构保留
  4. 加密功能可以先用简化版,后续再完善

我已经提供了核心代码框架,你可以基于这个继续开发。如果遇到具体问题,欢迎加入我们的QQ群(374992201)交流,群里大佬众多,说不定能找到帮你调试的兄弟。

记住,咱们虽然预算有限,但志气不能限!用爱发电,照亮代码之路!💪🔥

导入项目

导入到Eclipse:点南查看教程
导入到IDEA:点击查看教程
springboot统一配置:点击查看教程

工程

NOSQL

NOSQL示例不需要任何配置,可以直接访问测试

创建数据表

选择对应的数据表脚本,这里以SQL为例

修改数据库连接信息

访问页面进行测试

文件存储路径

up6/upload/年/月/日/guid/filename

效果预览

文件上传

文件刷新续传

支持离线保存文件进度,在关闭浏览器,刷新浏览器后进行不丢失,仍然能够继续上传

文件夹上传

支持上传文件夹并保留层级结构,同样支持进度信息离线保存,刷新页面,关闭页面,重启系统不丢失上传进度。

下载示例

点击下载完整示例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1205097.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2026硬硅酸钙石保温板源头优选,这几家实力出众,高密度硅酸钙板/碳纤维增强硅酸钙板,硬硅酸钙石保温板源头厂家找哪家

引言 硬硅酸钙石保温板凭借其优异的耐高温、低导热及抗腐蚀性能,已成为冶金、玻璃、电力等高温工业领域保障安全生产、改善作业环境的核心材料。随着国内工业升级进程加速,该产品需求量持续增长,但市场存在质量参差…

TCP三次握手与四次挥手:两个“社恐”程序的破冰与告别仪式

在网络世界里,TCP协议绝对是“严谨派”代表——不像UDP那样“发完就跑”,TCP要让两个设备传数据,非得先走一套“确认三连”的破冰流程;而聊完收场时,也得按规矩来一套“告别四步走”,绝不敷衍离场。这两套流程,…

算竞代码设计与技巧解析

本文使用了ai辅助,旨在更好的帮助大家理解一些技巧 邮递员送信_牛客题霸_牛客网 以这一题为例,需要对节点 1 求 两次 dijkstra,怎么使得代码写的简洁? ac 代码如下,我们来一一解析: void solve() {int n(q_), m(…

vue3基于python的流浪猫爱心救助系统

目录 Vue3与Python结合的流浪猫爱心救助系统摘要 项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 Vue3与Python结合的流浪猫爱心救助系统摘要 技术架构 前端采用Vue3框架实现响应式用户界面&#xff0c;搭…

PPT内容粘贴到CKEDITOR为何动画失效?

教育行业文档导入功能开发记录 一、需求分析与技术选型 作为项目组核心开发成员&#xff0c;我负责实现后台试卷发布模块的文档导入功能&#xff0c;需支持Word/Excel/PPT/PDF四种格式的解析&#xff0c;并保留原始样式与图片。经过技术评估&#xff0c;决定采用以下技术栈&a…

金融保险行业网页,JAVA如何处理多附件的分块上传功能?

大文件传输系统技术方案&#xff08;源码版&#xff09; 作为甘肃IT行业软件公司项目负责人&#xff0c;我深度理解您对大文件传输系统的核心诉求&#xff1a;高稳定性、强兼容性、可扩展加密、无缝集成现有系统。结合贵司200项目规模与信创要求&#xff0c;我团队基于JSP/Spr…

小程序python实验室预约排课系统

目录系统概述核心功能技术实现优势与扩展性应用场景项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作系统概述 Python实验室预约排课系统是一款基于微信小程序和Python后端开发的工具&#xff0c;旨在简化实…

2026年市面上热门的升降机生产厂家联系电话,自行走升降平台/液压升降平台/移动登车桥,升降机生产厂家联系方式

随着城市化进程加速与工业自动化需求提升,自行走升降机作为高空作业、物流搬运的核心设备,其市场呈现技术迭代加速、场景细分深化的趋势。据行业调研机构统计,2025年全球自行走升降机市场规模突破120亿美元,其中中…

python智能水务巡检预警应急调度与决策系统的设计与实现

目录智能水务巡检预警应急调度与决策系统的设计与实现摘要项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作智能水务巡检预警应急调度与决策系统的设计与实现摘要 该系统基于Python技术栈&#xff0c;结合物…

归一化说明

在 AI 算法训练中&#xff0c;归一化&#xff08;Normalization&#xff09; 是一种数据预处理技术&#xff0c;核心是将不同量纲、不同分布范围的特征数据缩放到统一的数值区间&#xff08;最常见为 [0,1][0,1][0,1]&#xff09;&#xff0c;消除数据间的量级差异对模型训练的…

小程序python康养旅游服务系统 计划管理APP 功能多

目录 康养旅游服务系统功能设计技术实现要点 项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 康养旅游服务系统功能设计 用户管理模块 注册登录&#xff08;手机号/微信授权&#xff09;、个人资料编辑、…

2026 年 1 月条码设备与耗材厂家推荐排行榜:条码打印机/扫描枪/标签/碳带/序列化/追溯系统,专业高效条码解决方案源头厂家精选

2026 年 1 月条码设备与耗材厂家推荐排行榜:条码打印机/扫描枪/标签/碳带/序列化/追溯系统,专业高效条码解决方案源头厂家精选 在数字化转型浪潮席卷全球的今天,条码技术作为数据采集与信息传递的基石,其重要性日益…

【实战项目】 大语言模型轻量化后的下游任务适配性分析

运行效果:https://lunwen.yeel.cn/view.php?id=5974 大语言模型轻量化后的下游任务适配性分析摘要:随着大语言模型的快速发展,其轻量化技术逐渐成为研究热点。然而,大语言模型轻量化后,如何适配下游任务成为一个…

FONE受邀加入华东人工智能应用联盟,获评“首届人工智能应用最佳伙伴”

1月17日&#xff0c;"临平CXO年会暨华东人工智能应用联盟成立大会”在杭州成功举办。本次大会以“智能新发展”为主题&#xff0c;共同探讨人工智能技术的前沿趋势与产业赋能路径。会上&#xff0c;FONE荣获“首届人工智能应用最佳伙伴”&#xff0c;并作为核心成员正式加…

大数据基于Python的电商用户消费行为分析

目录 大数据环境下电商用户消费行为分析的Python实现核心分析流程典型分析场景技术栈组合示例 项目技术支持可定制开发之功能亮点源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 大数据环境下电商用户消费行为分析的Python实现 电商平台通…

前端处理Excel:从导入导出到数据处理全攻略

在前端开发中&#xff0c;处理Excel文件是高频需求——无论是后台管理系统的批量数据导入、报表导出&#xff0c;还是用户上传数据的解析校验&#xff0c;掌握操作Excel的技能都能大幅提升产品体验。本文将从实用角度出发&#xff0c;手把手教你用React实现Excel的导入、解析、…

《优雅应对失败:JavaScript异步重试模式详解》

《优雅应对失败:JavaScript异步重试模式详解》一个函数解决90%的异步请求稳定性问题* 异步重试函数* @param {Function} asyncFn - 要执行的异步函数* @param {number} maxRetries - 最大重试次数(包括首次调用)* @…

交易·社交·管理一体化,开源可商用的二手平台小程序源码系统

温馨提示&#xff1a;文末有资源获取方式在共享经济与循环消费理念日益深入人心的今天&#xff0c;一个功能完备、体验流畅的二手交易平台已成为连接供需双方的重要桥梁。源码获取方式在源码闪购网。本系统源码采用主流且稳定的技术栈开发&#xff0c;确保了系统的性能与可靠性…

基于PLC的室内温度控制系统设计(设计源文件+万字报告+讲解)(支持资料、图片参考_相关定制)_文章底部可以扫码

基于PLC的室内温度控制系统设计(设计源文件万字报告讲解)&#xff08;支持资料、图片参考_相关定制&#xff09;_文章底部可以扫码电子资料包含:程序&#xff0c;程序仿真&#xff0c;组态仿真&#xff0c;设计报告]本文主要采用温度传感器对温度进行采集&#xff0c;并通过pid…

Docker run 命令详解(-a、-d、-e、-h、-i、-m、-p、-t、-v、--cpuset、--dns、--env-file、--expose、--link、--name、--net)

Docker run 命令详解&#xff1a;这些常用选项&#xff0c;用熟了效率翻倍-d&#xff1a;后台运行容器&#xff0c;不占用命令行-it&#xff1a;交互模式运行&#xff0c;直接进入容器终端-p / -P&#xff1a;端口映射&#xff0c;让外部能访问容器-p&#xff1a;指定端口映射&…