浏览器异常: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 config return 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>

String格式转JSON格式输出到文本

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class JsonTool {
//是否采用制表符
private final boolean isTab = false;

public String stringToJSON(String strJson) {
//换行多少个退位
int tabNum = 0;
StringBuilder jsonFormat = new StringBuilder();
int length = strJson.length();
for (int i = 0; i < length; i++) {
char c = strJson.charAt(i);
if (c == '{') {
tabNum++;
jsonFormat.append(c).append("\n");
jsonFormat.append(getSpaceOrTab(tabNum));
} else if (c == '}') {
tabNum--;
jsonFormat.append("\n");
jsonFormat.append(getSpaceOrTab(tabNum));
jsonFormat.append(c);
} else if (c == ',') {
jsonFormat.append(c).append("\n");
jsonFormat.append(getSpaceOrTab(tabNum));
} else if ((c == ':' && strJson.charAt(i - 1) == '"' && strJson.charAt(i + 1) == '"')
|| (c == ':' && strJson.charAt(i + 1) == '[')
|| (c == ':' && strJson.charAt(i + 1) == '{')
|| (c == ':' && Character.isDigit(strJson.charAt(i + 1)) && strJson.charAt(i - 1) == '"')) {
jsonFormat.append(c);
jsonFormat.append(" ");
} else if (c == '[' && strJson.charAt(i + 1) != ']') {
tabNum++;
jsonFormat.append(c).append("\n");
jsonFormat.append(getSpaceOrTab(tabNum));
} else if (c == ']' && strJson.charAt(i - 1) == '"') {
tabNum--;
jsonFormat.append("\n");
jsonFormat.append(getSpaceOrTab(tabNum));
jsonFormat.append(c);
} else {
jsonFormat.append(c);
}
}
return jsonFormat.toString();
}


/**
* @Description:追加换行后的退格
* @Param: [tabNum]
* @Return: java.lang.String
* @author: Nan-ZN.Xu@aia.com
* @date: 6/9/2021
* @Time: 3:01 PM
*/
public String getSpaceOrTab(int tabNum) {
StringBuilder sbTab = new StringBuilder();
for (int i = 0; i < tabNum; i++) {
if (isTab) {
sbTab.append('\t');
} else {
sbTab.append(" ");
}
}
return sbTab.toString();
}
}

将DTO保存到指定路径

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* @Description:调用API接口并将参数和返回结果等封装成DTO保存到指定路径
* @Param: [method, url, params, filePath]
* @Return: boolean
* @author: Nan-ZN.Xu@aia.com
* @date: 6/8/2021
* @Time: 2:18 PM
*/
public static boolean requestAndSave(String method, String url, Map<String, String> params, String filePath) {
boolean flag = false;

ApiRequest apiRequest;//详见另一篇博客(HttpClient的使用)的封装
try {
apiRequest = request(method, url, params);//详见另一篇博客(HttpClient的使用)的封装
} catch (Exception e) {
e.printStackTrace();
return false;
}

//获取接口名
String[] urlStrings = url.split("/");
String interfaceName = urlStrings[urlStrings.length - 1];
//获取当前时间
Date date = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
String dataTime = formatter.format(date);
//设置文件保存路径以及文件名
filePath = filePath + "\\" + interfaceName + "-" + dataTime + ".json";

FileOutputStream fileOutputStream;
OutputStreamWriter outputStreamWriter;
BufferedWriter bufferedWriter = null;
try {
File file = new File(filePath);
if (!file.getParentFile().exists()) {
flag = file.getParentFile().mkdirs();
}
if (file.exists()) {
flag = file.delete();
}

//利用JsonTool工具(上文)将DTO对象转为json格式输出到文本
String ojString = JSONObject.toJSONString(apiRequest);
JsonTool tool = new JsonTool();
String text = tool.stringToJSON(ojString);

//利用流写入文件
fileOutputStream = new FileOutputStream(file);
outputStreamWriter = new OutputStreamWriter(fileOutputStream, CHARSET);
bufferedWriter = new BufferedWriter(outputStreamWriter);

bufferedWriter.write(text);
} catch (Exception e) {
flag = false;
e.printStackTrace();
} finally {
//关闭资源
if (bufferedWriter != null) {
try {
bufferedWriter.flush();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return flag;
}

从JSON文件中读取json并转换成DTO

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
38
39
40
41
42
43
44
45
46
47
/**
* @Description:从JSON文件中读取json并转换成DTO对象
* @Param: [filePath]
* @Return: aia.cn.oeServiceCommon.dto.ApiRequest
* @author: Nan-ZN.Xu@aia.com
* @date: 6/8/2021
* @Time: 5:28 PM
*/
public static ApiRequest readFromJsonFile(String filePath) {
ApiRequest apiRequest;//详见另一篇博客(HttpClient的使用)的封装

File file = new File(filePath);
if (!file.exists()) {
return null;
}

String jsonStr = null;

FileInputStream fis;
BufferedInputStream bis = null;
try {
fis = new FileInputStream(file);
bis = new BufferedInputStream(fis);

int ch;
StringBuilder str = new StringBuilder();
while ((ch = bis.read()) != -1) {
str.append((char) ch);
}

jsonStr = str.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//将JSON文本串转为对象
apiRequest = JSONArray.parseObject(jsonStr, ApiRequest.class);
return apiRequest;
}

将请求结果封装成指定DTO

1
2
3
4
5
6
7
8
9
10
11
12
13

public static <T> T getResultDto(String method, String url, Map<String, String> params, Class<T> clazz) {
ApiRequest apiRequest;//详见另一篇博客(HttpClient的使用)的封装
try {
apiRequest = request(method, url, params);
} catch (Exception e) {
e.printStackTrace();
return null;
}
Object result = apiRequest.getResult();
String jsonString = JSON.toJSONString(result);
return JSONObject.parseObject(jsonString, clazz);
}