最近工作开发我写了一个文件上传的接口,接收MultipartFile。以下文件上传代码加最后补充 过滤器 代码 可以直接使用。如下代码:
//伪代码
@RestController
//跨域注解
@CrossOrigin
@RequestMapping("/test")
public class TestController {
@RequestMapping("/queryData")
public void queryData(){
System.out.println("查询方法生效");
}
@RequestMapping("/upload")
public void upload(@RequestPart("filesData")MultipartFile multipartFile){
System.out.println("文件上传方法生效");
}
}
拦截器与配置代码如下:
//拦截器类
public class WebInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//TODO:判断token逻辑自行处理
String token = request.getHeader("token");
if (token == null) {
return false;
}
return true;
}
}
//配置接口路径使用拦截器类
@Configurable
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new WebInterceptor())
.addPathPatterns("/**");
}
}
前端代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<input type="file" id="fileInput" multiple accept=".jpg,.jpeg,.png" />
<button type="button" onclick="uploadFiles()">上传文件</button>
<button type="button" onclick="queryData()">测试查询</button>
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
function queryData(){
let config = {
headers: {'token':'123'}
}
axios.post('http://localhost:8081/test/queryData',{},config).then(res => {
console.log('查询数据成功:', res);
})
}
function uploadFiles() {
const fileInput = document.getElementById('fileInput');
const files = fileInput.files;
// 创建 FormData 对象
const formData = new FormData();
// 将每个文件添加到 FormData 对象中
for (let i = 0; i < files.length; i++) {
formData.append('filesData', files[i]);
}
let config = {
headers:
{
'Content-Type': 'multipart/form-data;charset=utf-8',
'token':'123'
}
}
axios.post('http://localhost:8081/test/upload',formData,config)
.then(response => {
alert('上传成功');
})
.catch(error => {
alert('上传失败,请重试');
});
}
</script>
</html>
前端在Hbuilder中运行的。本地且有自己的端口(模拟前后端分离跨域问题)
流程: 通过axios使用 multipart/form-data 方式请求上传接口。后端实现一个拦截器(HandlerInterceptor) 对请求中请求头的token进行验证是否正确。正确就可以通过拦截器进行请求。后端接口接收到文件二进制数据。然后就上传文件时出现了问题。
1.发现问题
前端8848端口请求后端upload接口导致跨域问题。
2.解决问题
因为工作中出现的问题,我为了快速解决,然后去做其它的事情想到了注解 @CrossOrigin 去快速解决跨域。在前面贴的代码中我也加上了。并且在这个复现过程中我也加上了。我就奇怪了。还是报错跨域问题。第一反应就是 “难不成这个注解因为可以快速解决问题所以设计的不完善又或者是因为使用快速开发框架的原因里面有某些配置有隐藏bug给拦住了?”。然后就想到了用全局配置类解决。于是乎我又配置了一个百度上随便摘抄了一个 跨域配置类;
//跨域注解配置类
@Configuration
public class GlobalCorsConfig {
@Bean
public CorsFilter corsFilter() {
//1. 添加 CORS配置信息
CorsConfiguration config = new CorsConfiguration();
// 放行哪些原始域
config.addAllowedOriginPattern("*");
// 是否发送 Cookie
config.setAllowCredentials(true);
// 放行哪些请求方式
config.addAllowedMethod("*");
// 放行哪些原始请求头部信息
config.addAllowedHeader("*");
// 暴露哪些头部信息
config.addExposedHeader("*");
//2. 添加映射路径
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**",config);
//3. 返回新的CorsFilter
return new CorsFilter(corsConfigurationSource);
}
}
Copy一段上述代码后我重新开始了测试。不仅上传文件导致跨域问题未解决后台还出现了新报错:
When allowCredentials is true, allowedOrigins cannot contain the special value “*” since that cannot be set on the “Access-Control-Allow-Origin” response header. To allow credentials to a set of origins, list them explicitly or consider using “allowedOriginPatterns” instead.
于是乎我以为是这个报错导致的跨域没生效,紧接着我又去查找这个问题产生的原因:
解决上述问题博客
弄好后重新测试,发现正常了。于是我把跨域配置类放到自己的项目中。在自己项目环境中测试一下文件上传。依旧出现了跨域问题。
到这里已经百思不得其解了。找了Axios问题,找了Spring问题,找了前端问题。搜了很久。最后搜到了答案。
链接中回答有一句话是这么说的:
后端不允许user-token这个报头,因此OPTIONS预检发现目标到达不了,跟axios没啥关系。
根据这个答案让跨域允许接收这个请求头;实际我项目中设置的是 “*” 号,也就是放开所有。可是最后还是出现了跨域问题。我这里应该是有上述的问题,但是快速开发框架估计底层还是有一些拦截之类的?。同样出现了跨域问题。
3.过滤器解决跨域
最后我在Spring中实现了一个过滤器,因为这个过滤器实现跨域代码看着多加上偏向于更底层,我就一直不咋用这个配置,都是用的跨域配置类解决。在里面放开token才允许访问。除了这里已经放开的token以及浏览器请求默认允许的一些请求头允许。其它自定义的都需要放开才允许访问,不然哪天就会因为找这个问题开始头疼了。
//过滤器方式解决跨域
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
// 允许所有来源进行跨域请求
response.setHeader("Access-Control-Allow-Origin", "*");
// 允许发送 cookie
response.setHeader("Access-Control-Allow-Credentials", "true");
// 允许的请求方法
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
// 允许的请求头字段,在这里允许token请求解决
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, token");
// 预检请求的有效期,单位为秒
response.setHeader("Access-Control-Max-Age", "3600");
chain.doFilter(req, res);
}
}
以至于现在我都没明白为啥我的项目里写了跨域配置类就是不行,怎么样写都不行。像过滤器类代码中一样编写的跨域配置类,不使用 “*” 号还是一样不行。有知道的小伙伴可以在评论区为我解答一下~