浏览器异常: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一些注解的详解


在Vue中如何使用markdown编辑器

安装:npm install mavon-editor --save

在main.js中全局注册:

1
2
3
4
5
6
// 全局注册
import Vue from 'vue'
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
// use
Vue.use(mavonEditor)

在页面使用组件即可:

1
<mavon-editor v-model="ruleForm.content"></mavon-editor>

效果如下:

对于后台请求过来的markdown格式文章如何解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template中使用该组件: :value是要展示的markdown格式内容
<mavon-editor
:value="this.blog.content"
:subfield="prop.subfield"
:defaultOpen="prop.defaultOpen"
:toolbarsFlag="prop.toolbarsFlag"
:editable="prop.editable"
:scrollStyle="prop.scrollStyle"
></mavon-editor>

script中添加该组件相关的计算属性:
computed: {
prop () {
let data = {
subfield: false,// 单双栏模式
defaultOpen: 'preview',//edit: 默认展示编辑区域 , preview: 默认展示预览区域
editable: false,
toolbarsFlag: false,
scrollStyle: true
}
return data
}
},

更多配置看mavonEditor的Github


MyBatis-Plus的代码生成器

注意版本差异:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package top.nanzx.blog;

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class CodeGenerator {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}

public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();

// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("nan");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
gc.setServiceName("%sService");
mpg.setGlobalConfig(gc);

// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/blog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);

// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(null);
pc.setParent("top.nanzx.blog");
mpg.setPackageInfo(pc);

// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};

// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";

// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/"
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});

cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);

// 配置模板
TemplateConfig templateConfig = new TemplateConfig();

templateConfig.setXml(null);
mpg.setTemplate(templateConfig);

// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}

Vue中使用Cookie

安装:npm install js-cookie --save

在main.js中全局注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import jsCookie from 'js-cookie'

Vue.prototype.$cookie = jsCookie;

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
1
this.$cookie.set("token",res.data.data)//关闭浏览器自动失效

注意:axios默认不携带cookie,需设置axios.defaults.withCredentials = true


element UI导航菜单组件双击报错解决

1
2
3
4
5
6
7
8
9
10
11
12
13
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

//导航菜单组件双击报错解决
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}

前端路由权限拦截

  • 我们后台的操作界面需要登录后才可以访问,所以我们在路由映射的meta里配置该路由是否需要权限才能访问:requireAuth: true/false
  • 用户登录成功时通过设置sessionStorage(存储客户端临时信息的对象)标志该用户已登录:sessionStorage.setItem('isLogin',true)
  • 一旦所有窗口或标签页被关闭,那么所有通过sessionStorage存储的数据也就被清空了。
  • 通过全局前置守卫,判断每次将要进入的路由是否需要权限,如果不需要权限或需要权限且已经登录,那么直接放行,否则重定向到登录页面。
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
const routes = [
...
{
path: '/login',
component: () => import("../views/Login"),
meta: {
title: "管理员登录"
}
},
{
path: '/manage',
component: () => import("../views/Manage"),
children: [
{
path: '',
redirect: 'editTable'
},
{
path: 'editTable',
component: () => import("../components/EditTable"),
meta: {
title: "后台管理",
requireAuth: true
}
]
}
]

const router = new VueRouter({
routes,
mode: 'history'
})

//路由权限拦截
router.beforeEach((to, from, next) => {
if (to.meta.requireAuth) {
const isLogin = sessionStorage.getItem("isLogin")
if (isLogin) {
next()
} else {
next({
path: '/login'
})
}
} else {
next()
}
})

为了优化用户体验,避免多次登录,我们可以设置跳转登录页面的方法为:

1
2
3
4
5
if (sessionStorage.getItem("isLogin")){
this.$router.push('/manage')
}else {
this.$router.push('/login')
}

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

拦截器和跨域的配置代码

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;
}
}

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