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

常山网站建设设计公司网站设计

常山网站建设,设计公司网站设计,个人能做网站吗,宇舶手表网站文章目录一、引言#xff1a;问题背景二、技术选型与项目架构三、核心设计与实现1. 初始化上传 (/init)2. 上传分块 (/chunk)3. 完成上传与合并 (/complete)4. 查询上传进度 (/progress)四、断点续传工作流程五、方案优势总结六、拓展优化七、方案优势对比一、引言#xff1a… 文章目录一、引言问题背景二、技术选型与项目架构三、核心设计与实现1. 初始化上传 (/init)2. 上传分块 (/chunk)3. 完成上传与合并 (/complete)4. 查询上传进度 (/progress)四、断点续传工作流程五、方案优势总结六、拓展优化七、方案优势对比一、引言问题背景 在医疗问诊等需要高可靠性数据记录的场景中我们的一项关键功能是医生与患者问答填写问卷时系统需要实时录制音频并与问卷绑定以保证数据的真实性和有效性。然而直接上传完整的录音文件尤其是长时间问诊产生的大文件面临两个严峻的技术挑战 网络稳定性医院网络环境复杂长时间上传大文件极易因网络波动而中断导致整个上传失败用户体验极差。文件与服务器压力大文件上传占用服务器连接时间过长接口响应慢且服务端一次性处理大文件对内存和IO压力巨大容易导致文件损坏或上传失败。 为了解决这些问题我们放弃了传统的一次性上传方案转而采用 分块上传与断点续传 相结合的技术方案。本文将详细介绍如何基于 Spring Boot 3.0、MyBatis-Plus 和 MinIO 对象存储来实现这一稳健的上传流程。 二、技术选型与项目架构 后端框架: Spring Boot 3.0ORM 框架: MyBatis-Plus 3.5对象存储: MinIO 8.2.2状态缓存: Redis (用于存储上传状态和分块索引)核心思路: 将大文件分割成多个小块分别上传。利用 MinIO 的分块上传能力和 Redis 的记录功能实现上传中断后可从中断点继续上传而非重新开始。 三、核心设计与实现 我们通过四个清晰的 RESTful 接口来串联整个上传流程 init: 初始化上传获取本次上传的唯一ID。chunk: 上传单个文件分块。complete: 通知服务端所有分块已上传完毕执行合并操作。progress: 查询当前上传进度。 **上传任务状态流转图**本图描述了一个上传任务可能处于的各种状态及其转换条件。任务从“初始化”状态进入“上传中”子状态并随着每个分块的上传成功而逐步推进。核心在于当任务因异常进入“已中断”状态后可以通过查询进度重新回到“上传中”状态继续上传剩余分块从而实现“续传”。超时未完成的任务会被系统清理。 #mermaid-svg-JvFH8TP9iqqT9NuW {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JvFH8TP9iqqT9NuW .error-icon{fill:#552222;}#mermaid-svg-JvFH8TP9iqqT9NuW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JvFH8TP9iqqT9NuW .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-JvFH8TP9iqqT9NuW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JvFH8TP9iqqT9NuW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JvFH8TP9iqqT9NuW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JvFH8TP9iqqT9NuW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JvFH8TP9iqqT9NuW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JvFH8TP9iqqT9NuW .marker.cross{stroke:#333333;}#mermaid-svg-JvFH8TP9iqqT9NuW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JvFH8TP9iqqT9NuW defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-JvFH8TP9iqqT9NuW g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-JvFH8TP9iqqT9NuW g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-JvFH8TP9iqqT9NuW g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-JvFH8TP9iqqT9NuW g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-JvFH8TP9iqqT9NuW g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-JvFH8TP9iqqT9NuW .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-JvFH8TP9iqqT9NuW .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-JvFH8TP9iqqT9NuW .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-JvFH8TP9iqqT9NuW .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-JvFH8TP9iqqT9NuW .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-JvFH8TP9iqqT9NuW .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-JvFH8TP9iqqT9NuW .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-JvFH8TP9iqqT9NuW .edgeLabel .label text{fill:#333;}#mermaid-svg-JvFH8TP9iqqT9NuW .label div .edgeLabel{color:#333;}#mermaid-svg-JvFH8TP9iqqT9NuW .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-JvFH8TP9iqqT9NuW .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-JvFH8TP9iqqT9NuW .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-JvFH8TP9iqqT9NuW .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-JvFH8TP9iqqT9NuW .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-JvFH8TP9iqqT9NuW .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JvFH8TP9iqqT9NuW .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JvFH8TP9iqqT9NuW #statediagram-barbEnd{fill:#333333;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JvFH8TP9iqqT9NuW .cluster-label,#mermaid-svg-JvFH8TP9iqqT9NuW .nodeLabel{color:#131300;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-JvFH8TP9iqqT9NuW .note-edge{stroke-dasharray:5;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-note text{fill:black;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram-note .nodeLabel{color:black;}#mermaid-svg-JvFH8TP9iqqT9NuW .statediagram .edgeLabel{color:red;}#mermaid-svg-JvFH8TP9iqqT9NuW #dependencyStart,#mermaid-svg-JvFH8TP9iqqT9NuW #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-JvFH8TP9iqqT9NuW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}上传流程开始调用/init清理状态结束超时未完成清理状态结束开始上传分块所有分块成功调用/complete超时未完成网络中断等异常查询进度后续传未完成分块IdleInitializedUploading上传成功上传成功...上传成功Chunk_0Chunk_1Chunk_2...Chunk_NCompletedAbortedInterrupted1. 初始化上传 (/init) 客户端在上传前首先需要调用初始化接口。 Controller: Operation(summary 初始化分片上传) PostMapping(/init) public CommonResultFileUploadDTO.UploadInitResponse initUpload(RequestParam(fileName) String fileName,RequestParam(fileSize) long fileSize) {return CommonResult.SUCCESS(minioSysFileServiceImpl.initUpload(fileName, fileSize)); }Service: Override public FileUploadDTO.UploadInitResponse initUpload(String fileName, long fileSize) {// 1. 生成唯一上传ID用于标识本次上传任务String uploadId UUID.randomUUID().toString();// 2. 根据预设的分块大小计算总分块数int totalChunks (int) Math.ceil((double) fileSize / chunkSize);// 3. 创建上传状态对象并存入Redis设置过期时间以防僵尸任务UploadStatus status new UploadStatus(uploadId, fileName, fileSize, totalChunks);redisTemplate.opsForValue().set(RedisKeyUtil.getUploadKey(uploadId), status, 24, TimeUnit.HOURS);// 4. 返回给客户端uploadId 和 总分块数return new FileUploadDTO.UploadInitResponse(uploadId, totalChunks, chunkSize); }此接口的核心是生成一个全局唯一的 uploadId并将文件元信息名称、大小、分块数存入 Redis为后续的分块上传和续传奠定基础。 2. 上传分块 (/chunk) 客户端根据初始化接口返回的 totalChunks将文件切分并循环调用此接口上传每一个分块。 Controller: Operation(summary 上传分片) PostMapping(/chunk) public CommonResultVoid uploadChunk(RequestParam(uploadId) String uploadId,RequestParam(chunkNumber) int chunkNumber,MultipartFile chunk) {minioSysFileServiceImpl.uploadChunk(uploadId, chunkNumber, chunk);return CommonResult.SUCCESS(); }Service: Override public void uploadChunk(String uploadId, int chunkNumber, MultipartFile chunk){// 1. 为当前分块生成在MinIO中的唯一对象名称// 格式如chunks/{uploadId}/{chunkNumber}String objectName chunkObjectName(uploadId, chunkNumber);try (InputStream inputStream chunk.getInputStream()) {// 2. 调用MinIO SDK上传分块PutObjectArgs args PutObjectArgs.builder().bucket(minioConfig.getBucketName()).object(objectName).stream(inputStream, chunk.getSize(), -1).contentType(chunk.getContentType()).build();minioClient.putObject(args);} catch (Exception e) {log.error(上传分块 {} 失败: {}, chunkNumber, e.getMessage());throw new BaseException(ResultCode.CHUNK_UPLOAD_FAILED); // 抛出自定义异常由全局异常处理器处理}// 3. 上传成功后在Redis中标记该分块已完成markChunkUploaded(uploadId, chunkNumber); }private void markChunkUploaded(String uploadId, int chunkNumber) {// 使用Set结构存储已上传的分块编号redisTemplate.opsForSet().add(RedisKeyUtil.getUploadKey(uploadId) :chunks, chunkNumber); }每个分块都是独立上传的一个分块的失败不会影响其他分块。成功上传后其编号会被记录到 Redis 的 Set 中用于后续的进度查询和完成校验。 3. 完成上传与合并 (/complete) 当所有分块都上传完毕后客户端调用此接口。 Controller: Operation(summary 完成上传并合并文件) PostMapping(/complete) public CommonResultString completeUpload(RequestParam(uploadId) String uploadId){return CommonResult.SUCCESS(minioSysFileServiceImpl.completeUpload(uploadId)); }Service: Override public String completeUpload(String uploadId) {// 1. 从Redis获取上传状态并检查所有分块是否已上传完成UploadStatus status getUploadStatus(uploadId);if (!status.isComplete()) {throw new BaseException(ResultCode.CHUNK_UPLOAD_NOT_COMPLETE);}// 2. (业务逻辑)准备文件记录String originalFilename status.getFileName();FileRecord record ... // 创建或更新文件记录逻辑try {String finalObjectName ... // 生成最终在MinIO中存储的文件名// 3. 核心合并分块// 构建一个源分块列表ListComposeSource sources IntStream.range(0, status.getTotalChunks()).mapToObj(i - ComposeSource.builder().bucket(minioConfig.getBucketName()).object(chunkObjectName(uploadId, i)) // 指向每个分块.build()).collect(Collectors.toList());// 调用MinIO的composeObject API合并文件// 此操作在MinIO服务端进行高效且不耗费应用服务器资源minioClient.composeObject(ComposeObjectArgs.builder().bucket(minioConfig.getBucketName()).object(finalObjectName).sources(sources).build());// 4. 合并成功后清理临时分块文件for (int i 0; i status.getTotalChunks(); i) {minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioConfig.getBucketName()).object(chunkObjectName(uploadId, i)).build());}String fullUrl minioConfig.getUrl() / minioConfig.getBucketName() / finalObjectName;record.setUrl(fullUrl);} catch (Exception e) {log.error(文件合并失败{}, e.getMessage());throw new BaseException(文件合并失败);} finally {// 5. 清理Redis状态redisTemplate.delete(RedisKeyUtil.getUploadKey(uploadId));redisTemplate.delete(RedisKeyUtil.getUploadKey(uploadId) :chunks);}fileRecordService.saveOrUpdate(record);return record.getUrl(); // 返回最终文件的访问地址 }这是最精妙的一步。合并操作 (composeObject) 是在 MinIO 服务端完成的它只是将各个分块文件的元数据组合起来形成一个逻辑上的完整文件而不需要在应用服务器上进行耗时的二进制流合并因此速度极快资源消耗极低。 4. 查询上传进度 (/progress) 在上传过程中客户端可以定时调用此接口来获取上传进度用于前端显示进度条。 Service: Override public FileUploadDTO.UploadProgressResponse getUploadProgress(String uploadId) {UploadStatus status getUploadStatus(uploadId);return new FileUploadDTO.UploadProgressResponse(status.getUploadedChunks().size(), // 已上传数status.getTotalChunks(), // 总数status.getUploadedChunks() // 已上传的编号集合); }private UploadStatus getUploadStatus(String uploadId) {// 从Redis获取基础状态UploadStatus status (UploadStatus) redisTemplate.opsForValue().get(RedisKeyUtil.getUploadKey(uploadId));if (status null) {throw new BaseException(ResultCode.CHUNK_ID_NOT_EXIST);}// 从Redis Set中获取已上传的分块编号并设置到状态对象中SetObject uploadedChunks redisTemplate.opsForSet().members(RedisKeyUtil.getUploadKey(uploadId) :chunks);if (uploadedChunks ! null) {status.setUploadedChunks(uploadedChunks.stream().map(o - Integer.parseInt(o.toString())) // 注意类型转换.collect(Collectors.toSet()));}return status; }四、断点续传工作流程 此方案如何实现断点续传流程如下 客户端首次上传文件前调用 /init 获取 uploadId。开始上传分块。假设在上传到第 50 个分块时网络中断。网络恢复后客户端可以先调用 /progress?uploadIdxxx 查询进度。接口返回 {uploadedChunks: 49, totalChunks: 100, uploadedChunkNumbers: [0,1,2,...,48]}。客户端得知前 50 个块0-49中第 49 块索引从0开始还未成功上传于是从第 49 块开始继续上传而不需要重传 0-48 块。所有分块上传完成后调用 /complete 完成合并。 五、方案优势总结 提升稳定性网络中断后可从断点继续避免重复劳动和流量浪费。减轻服务器压力分块上传变小请求降低服务器内存和IO压力。MinIO 服务端合并效率极高。提升用户体验前端可以实时显示精确的上传进度条。清晰的责任分离四个接口职责单一逻辑清晰易于维护和扩展。 六、拓展优化 分块大小需要根据实际网络情况和文件大小调整 chunkSize例如 5MB 或 10MB在减少请求次数和降低单次失败成本之间取得平衡。异常处理与重试在 uploadChunk 方法中可以实现更强大的重试机制例如最多重试 3 次。过期清理需要有一个定时任务清理 Redis 中超过一定时间如 24 小时仍未完成的 UploadStatus 以及 MinIO 中对应的临时分块文件避免存储资源浪费。安全性可以对 uploadId 进行校验确保用户只能操作自己发起的上传任务。 七、方案优势对比 传统方案 vs 分块断点续传方案 方面传统单次上传 (Traditional)分块断点续传 (Chunked Resumable)网络中断完全失败需从头开始重传从中断点继续仅需传剩余分块进度反馈难以实现精确的进度条实时精确的进度反馈服务器压力长时间占用连接内存IO压力大分块小请求压力分散服务端合并高效用户体验差失败成本高优可控且可靠
http://www.zqtcl.cn/news/660398/

相关文章:

  • 可信网站 认证规则山东网站建设代理
  • 网站怎么谈设计常用的软件开发文档有哪些
  • 该怎么给做网站的提页面需求焦作做网站公司
  • 自己做的网站找不到了制作网站问题和解决方法
  • 5118站长平台cento安装wordpress
  • 政务大厅网站建设管理制度wordpress商城移动端
  • 提供中小企业网站建设北京企业网站建设公司哪家好
  • 做海报找图片的网站黑群晖按照wordpress
  • 网站建设与运营市场开拓方案网站首页策划
  • 做国外网站什么好网站快速优化排名排名
  • 如东做网站专注高密网站建设
  • dw网页设计作品简单宁波seo排名方案
  • 网站做微信接口吗小说网站首页模板
  • 网站正在建设中html个人站长做网站需要多少钱
  • 做推广便宜的网站有哪些数据网站建设哪家好
  • 中介网站制度建设wordpress genesis
  • 广东贸易网站开发用数据库做学校网站论文
  • 关于省钱的网站名字东莞哪些网络公司做网站比较好
  • net网站建设多少前MAC怎么做网站
  • 创建网站流程图国内高清图片素材网站推荐
  • 淄博住房和城乡建设局网站建设外贸网站哪家好
  • dede网站地图路径密云区免费网站建设
  • 男女做那事是什 网站软文网
  • 安徽建海建设工程有限公司网站活动推广宣传方案
  • 镇江市建设审图网站关键词优化过程
  • 广州个人网站备案要多久手机软件界面设计
  • 网站建设成都公司哪家好wordpress悬浮代码
  • 制作网站服务公司wordpress文章添加关注公众号
  • 陶瓷企业 瓷砖地板公司网站建设视频解析wordpress
  • 城乡建设厅网站首页wordpress模板汉化教程视频