file-storage-sdk项目开发中的踩坑记录

文章目录

file-storage-sdk项目开发中的踩坑记录

问题1:项目启动报错:Attribute "@click" appears more than once in element

  • 问题背景 :在编写前端代码时启动报错org.attoparser.ParseException: (Line = 66, Column = 74) Malformed markup: Attribute "@click" appears more than once in element
  • 问题原因:Thymeleaf 和 Vue 混用导致的
  • 问题解决 :将@click替换成v-on:click

参考文章

问题2:前端对话框被遮挡

  • 问题背景:我在实现上传文件的SDK时,我需要在上传之后弹出对话框展示上传文件列表,用户点击确认之后就进行上传,但是发现对话框居然被遮罩层给遮挡了

  • 问题原因 :这个是由于modal-append-to-body默认是true,导致默认遮罩层是遮挡父元素的

    将他设置为fasle,结果发现遮挡了表格,这是由于append-to-body默认属性是false导致的,导致弹窗默认是插入父元素,并不会插入body元素上,然后将它设置为 true

  • 问题解决

    html 复制代码
            <el-dialog
                    title="是否确认上传"
                    :visible.sync="dialogVisible"
                    width="30%"
                    :modal-append-to-body="false"
                    :append-to-body="true">

参考文章

问题3:@RequestBody无法接收表单数据

  • 问题背景 :在使用 vue-simple-uploder 上传文件数据时,后端直接报错 WARN 26176 --- [io-10086-exec-9] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data;boundary=----WebKitFormBoundaryCA6Bc2bnpcwGa9eq;charset=UTF-8' not supported]
  • 问题原因@RequestBody只能接收请求体中的 JSON 数据,无法接收表单数据
  • 问题解决 :讲后端标记的 @RequestBody 给去掉即可

参考文章

问题4:文件上传失败

  • 问题背景:在实现 file-stroage-sdk 项目时,前端分片上传,每一片5MB,结果出错

    org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field file exceeds its maximum permitted size of 5242880 bytes.

  • 问题原因:SpringBoot内置的Tomcat默认只接受 1MB 以内的文件,超过 1MB 会直接抛出上诉异常

  • 问题解决:添加如下配置文件

    yaml 复制代码
    spring:
      servlet:
        multipart:
          max-file-size: 10MB # 单个上传文件的最大限制大小
          max-request-size: 20MB # 单次请求的最大限制大小

    参考文章

问题5:Java代码编译后方法的参数名发生了改变

  • 问题背景:在开发 file-storage-sdk 时,经过 mvn install 命令编译打包下载到本地Maven仓库,在另一个 file-storage-sdk-demo 中引用,结果发现方法参数名发生了改变,本来的方法参数名是可以见名知义的,但是全都变为了 var1、var2、var3......

  • 问题原因:这是由于 编译优化 的原因,我们平常写的 Java 代码,在编译时会对代码进行一个编译优化

  • 问题解决

    最开始我通过配置 IDEA 的设置(相关操作可以直接看下面提供的链接),保障编译之后保留方法参数名,但是发现并没有用,我于是就通过配置 Maven 插件保障编译之后能够保留方法参数名

    xml 复制代码
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                        <!-- 确保编译之后方法参数命不发生改变 -->
                        <compilerArgs>
                            <arg>-parameters</arg>
                        </compilerArgs>
                    </configuration>
                </plugin>
            </plugins>
        </build>

注意:这个只能在 Java8 之后才能解决,Java8 之前的代码是无法做到的

参考文章

问题6:前端接收的data一直为null

  • 问题背景:在实现 查询文件列表时,后端查询数据成功了,并且页成功响应给前端了,前端也成功接收到数据了,结果发现 axios 发送请求后的成功回调中 response 的 data 数据项一直为null

  • 问题原因:首先我说一下这个问题的原因,由于我的ResposneResult 中没有对 data 进行赋值。

    这个问题相当的低级,但是我真的被卡了将近 30 min ,我实在没想过居然是我的 ResponseResult 出错了,我也没有动这个 ResponseResult 类啊?我一般是直接 CV 以前的来用,结果排查判断,我把错误归咎到前端,硬是排查了 30多分钟,结果无耐,一步一步点进去看,结果发现是 ResponseResult 中没有对 data 进行赋值😫真的服了

  • 问题解决:在ResponseResult中赋值data ,即 this.data = data;

问题7:axios中的this指向问题、event无法传递

  • 问题背景:想要绑定一个加载图片的事件,结果在axios内部调用this,发现报错了一个 未定义的错误
  • 问题原因
  • 问题解决

已废弃,问题7和问题8是一起的,这两个问题我已经通过换一种思路实现了,之前我展示 云存储上私有空间的图片,我是直接请求下载接口,将图片下载下来,然后传递给前端,前端将二进制流解析成图片展示,但是发现这种方式太麻烦了,

展示阿里云OSS上私有空间的图片,可以使用两种方式:直接请求后端接口获取授权URL,或者通过请求后端接口下载OSS中的图片并传递给前端。

  1. 直接请求后端接口获取授权URL :这种方式较为常见。当需要展示私有空间的图片时,前端向后端请求授权URL,后端验证用户身份和权限,并生成一个带有签名的临时URL。然后,前端将这个授权URL直接作为图片的 src 属性值,浏览器会发送请求到OSS服务器加载图片。这种方式不需要后端将图片下载并传递给前端,而是直接由浏览器请求加载。
  2. 通过请求后端接口下载OSS中的图片并传递给前端:这种方式在某些情况下也可行。前端向后端发送请求,后端验证用户身份和权限,并从OSS中下载对应的图片。然后,后端将图片以二进制流的形式返回给前端,前端再通过解析二进制流生成图片进行展示。这种方式可以用于一些特殊需求,例如需要对图片进行二次处理或保护图片真实地址等情况。

一般来说,直接请求后端接口获取授权URL是更常见和推荐的方式。这样可以避免后端多余的数据传输和资源开销,并且利用了OSS的访问控制机制,提高了安全性。

问题8:无法展示二进制流的图片

  • 方式一:responseType: 'arraybuffer'

    js 复制代码
                arrayBufferToBase64(buffer) {
                    let binary = '';
                    let bytes = new Uint8Array(buffer);
                    let len = bytes.byteLength;
                    for (let i = 0; i < len; i++) {
                        binary += String.fromCharCode(bytes[i]);
                    }
                    return window.btoa(binary);
                },
                // 图片加载失败时将其替换为默认加载失败图片
                handleImageError(event) {
                    var _this = this;
                    var image = event.target
                    axios.post('/file/download', {fileUrl: event.target.currentSrc}, {responseType: 'arraybuffer'})
                        .then(resp => {
                            debugger
                            image.src = `data:image/jpeg;base64,${_this.arrayBufferToBase64(resp)}`
                            // event.target.src = `data:image/jpeg;base64,${_this.arrayBufferToBase64(resp)}`
                        }).catch(error => {
                        debugger
                        event.target.src = "images/file_error.jpg";
                    });
                }
  • 方式二:responseType: 'blob'

    js 复制代码
    function convertBlobToImage(blob) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onloadend = function() {
          const imgSrc = reader.result;
          const imgElement = document.createElement('img');
          imgElement.src = imgSrc;
          resolve(imgElement);
        };
        reader.onerror = reject;
        reader.readAsDataURL(blob);
      });
    }
    
    axios.post('/file/download', { fileUrl: 'https://example.com/image.jpg' }, { responseType: 'blob' })
      .then(response => {
        const blob = response.data;
        return convertBlobToImage(blob);
      })
      .then(imgElement => {
        const imageContainer = document.getElementById('imageContainer');
        imageContainer.appendChild(imgElement);
      })
      .catch(error => {
        console.error(error);
      });

问题9:ElementUI的img组件无法展示阿里云图片链接

html 复制代码
<el-image
        style="width: 80px; height: 80px"
        :src="https://file-storage-sdk.oss-cn-guangzhou.aliyuncs.com/test/image/2023/09/01/1693575185713.jpg?Expires=1693628500&OSSAccessKeyId=LTAI5tGwejaWwLbmcNBEjrBo&Signature=SeEvQmzCbgm2nId9egqT%2FjeYzRU%3D"
        @error="handleImageError"
        :preview-src-list="[scope.row.fileUrl]">
</el-image>

无法直接展示,这是由于 src属性 无法解析链接中的查询参数,这样写,前端不会报错,但是会导致整个页面处于空白状态

解决方法:

1)方式一:使用模板字符串``

模板字符串``能够直接让整个字符串是真正的字符串,告诉 src 属性这个 URL 不需要解析

html 复制代码
<el-image
        style="width: 80px; height: 80px"
        :src="`https://file-storage-sdk.oss-cn-guangzhou.aliyuncs.com/test/image/2023/09/01/1693575185713.jpg?Expires=1693628500&OSSAccessKeyId=LTAI5tGwejaWwLbmcNBEjrBo&Signature=SeEvQmzCbgm2nId9egqT%2FjeYzRU%3D`"
        @error="handleImageError"
        :preview-src-list="[scope.row.fileUrl]">
</el-image>

2)方式二:在 data 中定义URL

这种方式的原理和模板字符串是一样的

js 复制代码
<el-image
        style="width: 80px; height: 80px"
        :src="url"
        @error="handleImageError"
        :preview-src-list="[scope.row.fileUrl]">
</el-image>

        data() {
            return {
                url:'https://file-storage-sdk.oss-cn-guangzhou.aliyuncs.com/test/image/2023/09/01/1693575185713.jpg?Expires=1693628500&OSSAccessKeyId=LTAI5tGwejaWwLbmcNBEjrBo&Signature=SeEvQmzCbgm2nId9egqT%2FjeYzRU%3D'
			}
	`	}

3)方式三:逻辑转换

直接修改传递 index属性,然后更新 tableData

注意更新不能直接使用 = 进行赋值,可以使用 this.$set()

问题10:阿里云OSS分片上传失败

  • bug1 :上传直接报错The upload ID may be invalid, or the upload may have been aborted or completed

    java 复制代码
    <Error>
      <Code>NoSuchUpload</Code>
      <Message>The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.</Message>
      <RequestId>64F2FE89FBA3A939312E6EAE</RequestId>
      <HostId>file-storage-sdk.oss-cn-guangzhou.aliyuncs.com</HostId>
      <UploadId>8A2877C216304F1286F8796BB32CFB42</UploadId>
      <EC>0042-00000104</EC>
    </Error>

    结果经过测试,发现 uploadId 创建时的 key 和 每一个分片的 key 必须是一一致的,这样才能分片上传成功

    问题解决:我的思路,是直接使用一个 Map 存储第一个分片的 key,由于我前端计算了 md5 值对于所有分片而言是唯一的,所以我每次只需要获取一次对应分片的 key,没有 key 时 才进行新建,从而确保每一个分片的 key 是唯一的

    注意:由于我使用了Map,Map是基于服务器内存的,所以同时 set 是很容易出现并发安全问题的,基于这两点,我直接

  • bug2 :使用阿里云OSS分片上传时,每次到最后一片时,都无法成功,并且报错java.net.SocketTimeoutException: Read timed out

    问题原因:通过层层排查,发现居然问题处在一个超级细节上,原因是我前端切片,在传递切片大小时都是 chunkSize,而我在使用阿里云OSS分片上传时,每次都是直接使用 chunkSize 作为当前块的大小,由于文件最后一片的大小大概率是要小于 chunkSize 的,所以这就导致 在上传最后一片时,由于我设置了 chunkSize ,而最后一片的大小是小于 chunkSize 的,所以导致最后一片数据不够,OssClient 一直处于等待状态,这就导致链接超时了,最终直接被强制断开

    问题解决:每次设置 分片大小,都以当前文件流的大小为准

    java 复制代码
    uploadFile.getSize()

问题11:启动出现循环依赖问题

我在 fileChunkServiceImpl 注入了 fileInfoServiceImpl 对象,然后再 fileInfoServiceImpl 又注入了 fileChunkServiceImpl 对象,从而导致循环依赖问题

Description:

The dependencies of some of the beans in the application context form a cycle:

   fileController
┌─────┐
|  fileChunkServiceImpl
↑     ↓
|  fileInfoServiceImpl
└─────┘

这个是最经典,也是简单的循环依赖问题了,直接将 fileChunkServiceImpl 的逻辑统一放到 fileInfoServiceImpl 中就可以了

备注:循环依赖问题是一个很常见的问题,网上也有很多文章,我打算之后抽出一段时间仔细研究一下,然后总结一篇文章

问题12:使用ant desing的upload组件上传文件后端包格式转换错误

  • 问题背景 : 我后端单独使用一个FileDTO对象接收前端的文件数据类型,结果报错org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:388)

  • 问题原因:文件数据不支持 json 格式传递,仅支持 表单 格式传递

  • 解决方法

    js 复制代码
            formData.append('chunkNumber', '1');
            formData.append('chunkSize', file.size);
            formData.append('currentChunkSize', file.size);
            formData.append('totalSize', file.size);
            formData.append('identifier', "123");
            formData.append('filename', file.filename);
            formData.append('relativePath', file.relativePath);
            formData.append('totalChunks', '1');
            formData.append('file', file);

问题13:使用RabbitMQ结果报错

这个bug并不是 file-storage-sdk项目的,是另一个项目的,这里就一起记录到这个文章中了,也懒得单独拿一篇文章记录一个bug

项目启动循环报错,一直出现下面的异常信息,我一开始还以为是我配置问题,后面翻译了以下,然后结合这个 DispatcherServlet经典类,我就注意到可能是SpringMVC的问题,我之前从没出现过这个问题,后面想了一下可能是 队列监视方法不能写在Controller标注的类中,因为我图方便,就一股脑将所有的逻辑写在了Controller中,结果遇到这个坑了

java 复制代码
Caused by: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

由:java.lang.IllegalStateException: No thread-bound request found:您是在实际web请求之外引用请求属性,还是在原始接收线程之外处理请求?如果你实际上是在一个web请求中操作,并且仍然收到这个消息,你的代码可能在DispatcherServlet之外运行:在这种情况下,使用RequestContextListener或RequestContextFilter来公开当前请求。
  • 问题原因 :在Spring MVC中,@RabbitListener注解默认在一个单独的线程中执行,而不是在Web请求的上下文中。(具体原因是也不是很清楚,这是GPT的回答,很懵逼,感觉这个应该涉及到底层知识了吧,我一个初学者,对这些东西是真的不懂,如果有大佬知道的话,恳请告诉我出现这个问题的原因,在此谢过了(●'◡'●))

  • 问题解决

    @RabbitListener标记的方法,放到一个不是Controller注解标记的类,比如放在 @Component@Configuration注解标记的类

总结

bug踩多了,经验就丰富了,这个文章中的许多 bug 我觉得还是比较常见的,但是发现好多bug都是在于莫方面知识的缺乏,比如,接连遇到几个前端问题,当然前端有很多问题 其实我之前就遇到过了,这一次遇到可能以下就发现了,但是我还是把它记录下来了,为了让更多人看到,少走过我踩过的坑

其中有一些bug的原因,我可能是解释有问题,或者说您有更好、更正确的解释,欢迎告诉博主,博主不胜感激,当然,最后,如果大家感兴趣的可以,添加一下下方本人的微信公众号,这个公众号是上个月开通的,我会在公众号上分享一些我的有趣的工具,或者是一些我珍藏的笔记(●ˇ∀ˇ●)

相关推荐
aloha_7894 分钟前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java34 分钟前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山34 分钟前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
睡觉谁叫~~~38 分钟前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
程序媛小果1 小时前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
小屁孩大帅-杨一凡2 小时前
java后端请求想接收多个对象入参的数据
java·开发语言
java1234_小锋2 小时前
使用 RabbitMQ 有什么好处?
java·开发语言
TangKenny2 小时前
计算网络信号
java·算法·华为
肘击鸣的百k路2 小时前
Java 代理模式详解
java·开发语言·代理模式
城南vision2 小时前
Docker学习—Docker核心概念总结
java·学习·docker