文章目录
- file-storage-sdk项目开发中的踩坑记录
-
- [问题1:项目启动报错:`Attribute "@click" appears more than once in element`](#问题1:项目启动报错:
Attribute "@click" appears more than once in element
) - 问题2:前端对话框被遮挡
- 问题3:@RequestBody无法接收表单数据
- 问题4:文件上传失败
- 问题5:Java代码编译后方法的参数名发生了改变
- 问题6:前端接收的data一直为null
- 问题7:axios中的this指向问题、event无法传递
- 问题8:无法展示二进制流的图片
- 问题9:ElementUI的img组件无法展示阿里云图片链接
- 问题10:阿里云OSS分片上传失败
- 问题11:启动出现循环依赖问题
- [问题12:使用ant desing的upload组件上传文件后端包格式转换错误](#问题12:使用ant desing的upload组件上传文件后端包格式转换错误)
- 问题13:使用RabbitMQ结果报错
- [问题1:项目启动报错:`Attribute "@click" appears more than once in element`](#问题1:项目启动报错:
- 总结
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 会直接抛出上诉异常
-
问题解决:添加如下配置文件
yamlspring: 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中的图片并传递给前端。
- 直接请求后端接口获取授权URL :这种方式较为常见。当需要展示私有空间的图片时,前端向后端请求授权URL,后端验证用户身份和权限,并生成一个带有签名的临时URL。然后,前端将这个授权URL直接作为图片的
src
属性值,浏览器会发送请求到OSS服务器加载图片。这种方式不需要后端将图片下载并传递给前端,而是直接由浏览器请求加载。 - 通过请求后端接口下载OSS中的图片并传递给前端:这种方式在某些情况下也可行。前端向后端发送请求,后端验证用户身份和权限,并从OSS中下载对应的图片。然后,后端将图片以二进制流的形式返回给前端,前端再通过解析二进制流生成图片进行展示。这种方式可以用于一些特殊需求,例如需要对图片进行二次处理或保护图片真实地址等情况。
一般来说,直接请求后端接口获取授权URL是更常见和推荐的方式。这样可以避免后端多余的数据传输和资源开销,并且利用了OSS的访问控制机制,提高了安全性。
问题8:无法展示二进制流的图片
-
方式一:
responseType: 'arraybuffer'
jsarrayBufferToBase64(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'
jsfunction 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 一直处于等待状态,这就导致链接超时了,最终直接被强制断开
问题解决:每次设置 分片大小,都以当前文件流的大小为准
javauploadFile.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 格式传递,仅支持 表单 格式传递
-
解决方法:
jsformData.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的原因,我可能是解释有问题,或者说您有更好、更正确的解释,欢迎告诉博主,博主不胜感激,当然,最后,如果大家感兴趣的可以,添加一下下方本人的微信公众号,这个公众号是上个月开通的,我会在公众号上分享一些我的有趣的工具,或者是一些我珍藏的笔记(●ˇ∀ˇ●)