浏览器异常:TypeError

问题描述

封装好的request.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import axios from 'axios'

export function request(config) {
// 1.创建axios的实例
const instance = axios.create({
baseURL: 'http://127.0.0.1:8181',
timeout: 5000
})

// 2.axios的拦截器
// 2.1.请求拦截的作用
instance.interceptors.request.use(config => {
return config
}, err => {
console.log(err);
})

// 2.2.响应拦截
instance.interceptors.response.use(res => {
// console.log(res);
return res.data
}, err => {
console.log(err);
})

// 3.发送真正的网络请求
return instance(config)
}

axios发送请求代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import {request} from "@/network/request";
export default {
name: "Blogs",
data() {
return {
currentPage: 1,
total: 0,
size: 4,
pages: 0,
blogs: []
}
},
methods: {
getBlogs(currentPage, size) {
request({
url: '/getBlogs?currentPage=' + currentPage + '&size=' + size
}).then(res =>{
this.total = res.data.data.total
this.size = res.data.data.size
this.pages = res.data.data.pages
this.blogs = res.data.data.records
// console.log(this)
})
},
},

解决思路

  • 起初我以为是total这个属性读取不到,将this赋值给_this,在request里再使用 _this.total访问,还是报错;

  • 于是去百度,看到别人报这个错是因为使用Vue.use(axios),改为原型全局注册后就不报错了:Vue.prototype.$http = axios

  • 因为暂时想不出解决方法,所以我放弃使用封装的request.js,改用下面这种方法来使用axios:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from 'axios'

Vue.prototype.$http = axios
axios.defaults.baseURL = 'http://localhost:8181'
axios.defaults.withCredentials = true

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
1
2
3
4
this.$http.get('http://localhost:8181/blogs'
).then(res => {
console.log(res)
})
  • 项目完成结束后,我回来重新测试这个问题,发现是我axios的拦截器响应拦截后给我返回的是只有数据部分return res.data;所以要赋值的话应为:this.total = res.data.total

  • 所以在使用axios的拦截器时,应注意放行拦截结果return configreturn res


请求400

问题描述

后端:

前端:

1
2
3
4
5
6
7
8
9
10
11
12
13
data() {
return {
ruleForm: {
username: '',
password: '',
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$http.post('/login', this.ruleForm).then((res) => {

登录页面提交表单后:

解决方法

可以看到图中的Content-Type:application/json

  • 如果前端传入的是json数据(Content-Type:application/json),那么后端使用@RequestBody HashMap<String, String> map或者@RequestBody User user进行接收。

  • 如果前端传入的是简单类型数据(Content-Type:application/x-www-form-urlencoded),那么后端可以使用@RequestParam("id") String id

spring boot一些注解的详解


跨域与拦截器导致跨域失败

拦截器和跨域的配置代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package top.nanzx.blog.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import top.nanzx.blog.entity.Admin;
import top.nanzx.blog.service.AdminService;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class AdminLoginInterceptor implements HandlerInterceptor {

@Autowired
AdminService adminService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Cookie[] cookies = request.getCookies();
Admin admin = adminService.getOne(new QueryWrapper<Admin>().eq("id", 1));
String token = admin.getToken();
for (Cookie c : cookies) {
if (c.getName().equals("token")) {
return token.equals(c.getValue());
}
}
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package top.nanzx.blog.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class Config implements WebMvcConfigurer {

@Autowired
AdminLoginInterceptor adminLoginInterceptor;


@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "HEAD", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true).allowedOrigins()
.maxAge(3600)
.allowedHeaders("*");
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(adminLoginInterceptor).excludePathPatterns("/getBlogs","/getBlog/*","/login","/getAdminMess");
}

}

产生问题

  • 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。
  • 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
  • 预检请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

在非简单请求的时候,总是报跨域访问的错误:

预检请求被拦截器拦截后,服务器端没有给浏览器返回必要的跨域指示信息,浏览器没收到指示信息就认为服务器不允许跨域请求,就会报错。

解决

拦截器截取到请求时先进行判断,如果是option请求的话,则放行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package top.nanzx.blog.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import top.nanzx.blog.entity.Admin;
import top.nanzx.blog.service.AdminService;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class AdminLoginInterceptor implements HandlerInterceptor {

@Autowired
AdminService adminService;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if("OPTIONS".equals(request.getMethod().toUpperCase())) {
System.err.println("OP:OK");
return true;
}

Cookie[] cookies = request.getCookies();
Admin admin = adminService.getOne(new QueryWrapper<Admin>().eq("id", 1));
String token = admin.getToken();
for (Cookie c : cookies) {
if (c.getName().equals("token")) {
return token.equals(c.getValue());
}
}
return false;
}
}

参考文章:跨域与拦截器导致跨域失败

response响应异常

1
java.lang.IllegalStateException: Cannot call sendError() after the response has been committed

下载文件接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* @Author: Nan
* @Param: [courseId, fileName, response]
* @Return: top.nanzx.dto.JsonResult
* @Date: 12:38 2021/2/14
* @Description: 下载文件接口
*/
@Override
public JsonResult download(int courseId, String fileName, HttpServletResponse response) {
Means means = meansDao.queryFile(fileName, courseId);
File file = new File(means.getFilePath() +'\\'+ means.getFileName());

if (!file.exists()) {
return new JsonResult(1, "文件不存在!", null);
}
response.setContentType("application/force-download");
response.addHeader("Content-Disposition", "attachment;fileName=" + new String(fileName.getBytes(StandardCharsets.UTF_8), Charset.forName("ISO8859-1")));

byte[] buffer = new byte[1024];
try {
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);

OutputStream os = response.getOutputStream();

int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
fis.close();
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(1, "文件下载失败。", null);
}
return new JsonResult(0, "文件下载成功。", null);
}

出现这个错误,应该是多次response导致的,可以这么理解,http server发送response后就关闭了socket,这个时候再次发送response给http client就会出现这个问题。而我代码中传输完文件后,流一关闭,socket也就关闭了。

解决办法:如果下载出现问题,返回 JsonResult 让前台知道问题详情,下载成功的话,直接return null 就Ok了。

修改代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* @Author: Nan
* @Param: [courseId, fileName, response]
* @Return: top.nanzx.dto.JsonResult
* @Date: 12:38 2021/2/14
* @Description: 下载文件接口
*/
@Override
public JsonResult download(int courseId, String fileName, HttpServletResponse response) {
Means means = meansDao.queryFile(fileName, courseId);
File file = new File(means.getFilePath() +'\\'+ means.getFileName());

if (!file.exists()) {
return new JsonResult(1, "文件不存在!", null);
}
response.setContentType("application/force-download");
response.addHeader("Content-Disposition", "attachment;fileName=" + new String(fileName.getBytes(StandardCharsets.UTF_8), Charset.forName("ISO8859-1")));

byte[] buffer = new byte[1024];
try {
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);

OutputStream os = response.getOutputStream();

int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
fis.close();
} catch (Exception e) {
e.printStackTrace();
return new JsonResult(1, "文件下载失败。", null);
}
return null;
}

前端Json数据解析异常

1
2
3
4
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: 
JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token;
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_ARRAY token
at [Source: (PushbackInputStream); line: 1, column: 101] (through reference chain: java.util.HashMap["object"])]

出错原因:前端传值类型与后端接收值的类型不一致,无法解析

这是我前端传输的数据:

1
2
3
4
5
6
7
8
9
10
11
<script>
export default {
data() {
return {
ruleForm: {
courseName: '',
classes: [],
},
}
}
}

这是我后端对应的接口:

1
2
3
4
@PostMapping("/createCourse")
public JsonResult createCourse(@RequestBody HashMap<String, String> map) {
return teacherService.createCourse(map);
}

前端数组类型,后端无法用string类型接收。

解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
前端=>后端:

//格式化数组--转字符串
this.tag = this.tag.join(',');
后端=>前端:

//格式化字符串--转数组
for(var i in data){
if(data[i].tag!==""){
data[i].tag = data[i].tag.split(',');
}
}

项目代码解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script>
export default {
name: "AddCourse",
data() {
return {
ruleForm: {
courseName: '',
classes: [],
},
};
},
computed: {
formData() {
return {
courseName: this.ruleForm.courseName,
classes: this.ruleForm.classes.join(','),
}
}
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$http.post('/teacher/createCourse', this.formData).then((res) => {
...
}
</script>