Spring Boot+Vue(前后端分离开发)

开发环境准备

  • 开发工具: IntelliJ IDEA 2020.1.2 x64

  • node.js : v12.13.0

    1
    2
    3
    4
    5
    6
    7
    # 1. Vue的脚手架是依赖于node.js,因此我们要先安装node.js
    C:\Users\L15096000421>node -v # 查看node版本
    C:\Users\L15096000421>v12.13.0
    C:\Users\L15096000421>npm -v # node.js的包管理工具
    C:\Users\L15096000421>6.13.2
    # 2. 使用淘宝NPM 镜像
    C:\Users\L15096000421>npm install -g cnpm --registry=https://registry.npm.taobao.org
  • Vue Cli 脚手架 : 如果没安装可以看我的Vue-Cli详解

前端实现(Vue)

创建Vue前端项目

通过vue提供的初始化命令创建脚手架vue init webpack my-project,注:这里是Vue Cli2初始化项目,和Vue Cli3初始化项目有区别,我的开发工具使用的是IDEA进行开发的,一般推荐使用WebStorm 2020.1.2 x64,这里为了方便直接使用同一个开发工具进行前后端的开发,方便切换;导入项目,IDEA会智能提示你下载Vue.js插件,如果没有提示,可以在File-->Settings-->Plugins中搜索下载,以后创建.vue文件时,会帮助你智能写入vue的三段式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
G:\book_springboot\vue_springboot>vue init webpack my-project
# 会根据vue-cli2-test名称创建一个文件夹,存放之后的项目内容,该名称也会作为默认的项目名称,但是不能包含大写字母
? Project name my-project
# 项目名称,不能包含大写
? Project description A Vue.js project
#项目描述
? Author zysheep <zysheep@126.com>
# 作者的信息,默认在git中读取信息
? Vue build runtime
# 选择第二个
? Install vue-router? No
# 是否需要路由
? Use ESLint to lint your code? No
# ESlint检测代码规范
? Set up unit tests No
# 单元测试
? Setup e2e tests with Nightwatch? No
#e2e测试
? Should we run `npm install` for you after the project has been created? (recommended) npm
# 选择npm安装
vue-cli · Generated "vue-cli2-test".

启动项目:用cd命令进入项目目录,用npm install 安装项目所需依赖,npm run dev启动项目即可。

页面

首先下载前端开发需要的依赖

1
2
3
cnpm install -g vue-router
cnpm install -g js-cookie # 一个简单,轻巧的JavaScript API,用于处理Cookie
cnpm isntall -g axios

Login.vue

src/components目录下创建

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
<template>
<div >

用户名:<input type="text" v-model="userName" placeholder="请输入用户名" />
<br><br>
密码:<input type="password" v-model="password" placeholder="请输入密码" />
<br><br>
<button @click="login()">登录</button>
<br><br>

<router-link to='/Register'>
<span style="text-align:center;font-size: 14px;">账号注册</span>
</router-link>

<router-link to='/ChangePwd'>
<span style="text-align:center;font-size: 14px; color:red;">修改密码</span>
</router-link>

</div>
</template>

<script>
import Cookies from 'js-cookie';

export default {
name: 'Login',
data() {
return {
userName: "",
password: "",

}
},
created() {

},
methods: {
login: function() {
let fd = new FormData();
fd.append("userName", this.userName);
fd.append("passwd", this.password);
// console.log(fd.get("passwd"));

let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}

this.$axios.post("user/login", fd, config).then(res => {
alert(res.data.msg)
if (res.data.code === 200) {
Cookies.set('userName', fd.get('userName'));
this.$router.push({
path: '/success'
})
} else {

}
}).catch(res => {
alert(res.data.msg)
})
}

}
}
</script>

<style scoped>

</style>

Register.vue

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
<template>
<div>
用户名: <input id="userName" type="text" v-model="userName" placeholder="请输入用户名" />
<br><br>
密码: <input id="password" type="password" v-model="password" placeholder="请输入密码" />
<br><br>
确认密码: <input id="password2" type="password" v-model="password2" placeholder="请再次输入密码" />
<br><br>
<button @click="register()">注册</button>
<br><br>

<router-link to='/'>
<span style="text-align:center;font-size: 14px; color:red;">现在登录</span>
</router-link>

</div>
</template>

<script>
export default {
name: 'Register',
data() {
return {
userName : "",
password2: ""
}
},
created() {

},
methods: {
register: function() {
let fd = new FormData();
fd.append("userName", this.userName);
fd.append("passwd", this.password);

let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}

if (this.password === this.password2) {
this.$axios.post("user/register", fd, config).then(res => {
alert(res.data.msg)
if (res.data.code === 200) {
// 回到登录界面
this.$router.push({
path: '/'
})
}
}).catch(res => {
alert(res.data.msg)
})
} else {
alert("两次输入的密码不同")
}

}

}
}
</script>

<!-- 添加“scoped”属性以将CSS仅限于此组件 -->
<style scoped>

</style>

ChangePwd.vue

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
<template>
<div>
<div class="top_div">当前登录的用户是:{{ LoginuserName }}</div>
<br><br>
用户名: <input id="Username" type="text" v-model="LoginuserName" disabled="disabled" />
<br><br>
原密码: <input id="oldpassword" type="password" v-model="oldpassword" placeholder="请输入原密码" />
<br><br>
新密码: <input id="newpassword" type="password" v-model="newpassword" placeholder="请输入新密码" />
<br><br>
确认新密码: <input id="newpassword2" type="password" v-model="newpassword2" placeholder="请再次输入新密码" />
<br><br>
<button @click="ChangePwd()">确定修改</button>
</div>
</template>

<script>
import Cookies from 'js-cookie';
export default {
name: 'ChangePwd',
data() {
return {
oldpassword: "",
newpassword: "",
newpassword2: ""
}
},
created() {

},
computed: {
LoginuserName() {
return Cookies.get('userName');
}
},
methods: {
ChangePwd: function() {
let fd = new FormData();
fd.append("username", this.LoginuserName);
fd.append("oldpassword", this.oldpassword);
fd.append("newpassword", this.newpassword);

let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}

if (this.oldpassword.length === 0 || this.newpassword.length === 0 || this.newpassword2.length === 0) {
alert("密码不能为空");
return;
} else if (this.newpassword === this.newpassword2) {
this.$axios.post("user/changepwd", fd, config).then(res => {
alert(res.data.msg)
if (res.data.code === 200) {
// 回到登录界面
this.$router.push({
path: '/'
})
}
}).catch(res => {
alert(res.data.msg);
})
} else {
alert("两次输入的新密码不同")
}
}
}
}
</script>


<!-- 添加“scoped”属性以将CSS仅限于此组件 -->
<style scoped>

</style>

Success.vue

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
<template>
<div style="text-align:center">
<h2>欢迎你 {{ username }}登录成功!</h2>
<img src="">
<router-link to='/'>
<span>返回登录界面</span>
</router-link>
</div>
</template>

<script>
import Cookies from 'js-cookie';
export default{
computed:{
username(){
return Cookies.get('userName');
}
}
}
</script>
<!-- 添加“scoped”属性以将CSS仅限于此组件 -->
<style scoped>


</style>

页面路由的配置

src\router目录下创建index.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
29
30
31
32
33
34
35
import Vue from 'vue'
import Router from 'vue-router'
import Register from '../components/Register'
import Login from '../components/Login'
import Success from '../components/Success'
import ChangePwd from '../components/ChangePwd'

Vue.use(Router)

export default new Router({
// 修改为History路由模式
mode: 'history',
routes: [
{
path: '/',
name: '登录',
component: Login
},
{
path: '/Register',
name: '注册',
component: Register
},
{
path: '/Success',
name: '登录成功',
component: Success
},
{
path: '/ChangePwd',
name: '修改密码',
component: ChangePwd
},
]
})

将路由添加到程序入口,设置反向代理:修改main.js

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


var axios = require('axios')

axios.defaults.baseURL = 'http://localhost:8002';
Vue.prototype.HOST='/user'
Vue.prototype.$axios = axios

Vue.config.productionTip = false

new Vue({
el: '#app',
router,
render: h => h(App)
})

修改App.vue,把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
<template>
<div id="app">
<router-view>
<Login/>
</router-view>
</div>
</template>

<script>
import HelloWorld from './components/HelloWorld'
import Login from './components/Login'


export default {
name: 'App',
components: {
Login
}
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

后端实现(SpringBoot)

后端的实现主要采用的技术栈

  • SpringBoot 2.2.2
  • JDK1.8
  • Maven 3.6.1
  • MySQl 5.7.1
  • mybatis-spring-boot-starter 2.1.3

entity

1
2
3
4
5
6
7
8
9
10
11
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

private Integer userId;
private String userName;
private String passwd;
private String status;
private String registTime;
}

mapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Mapper
public interface UserMapper {

@Select("select * from t_user where userName = #{userName}")
public User getUserByName(String userName);


@Insert("insert into t_user (userName,passwd,status,registTime) values (#{userName}, #{passwd}, #{status}, #{registTime})")
public int addUser(User user);


@Update("update t_user set passwd = #{newpassword} where userName = #{username}")
public int updateUser(String username, String newpassword);
}

service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class UserService {

@Autowired
private UserMapper userMapper;

public User selectByName(String userName){
return userMapper.getUserByName(userName);
}


public int insert(User user){
return userMapper.addUser(user);
}


public int updatePwd(String username,String newpassword) {
return userMapper.updateUser(username,newpassword);
}

}

controller

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
@RequestMapping("/user")
@RestController
public class UserController {


@RequestMapping({"/","/index"})
public String index() {
return "hello world!";
}

@Autowired
private UserService userService;

//注册
@RequestMapping(value = "/register", method = RequestMethod.POST)
@CrossOrigin
public Result register(@RequestParam String userName,
@RequestParam String passwd) {
Result result = new Result();

if (StringUtils.isEmpty(userName) || StringUtils.isEmpty(passwd)) {
result.setMsg("用户名和密码不能为空");
return result;
}
// 验证用户名是否已经注册
User exsitUser = userService.selectByName(userName);
if (exsitUser != null) {
result.setMsg("该用户名已存在");

return result;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = sdf.format(new Date());
User user = new User();
user.setUserName(userName);
user.setRegistTime(format);
user.setStatus("1");
// Spring 框架的工具类对一些重要的密码进行加密可以使用MD5,其工具类是DigestUtils;
//比如对密码进行 md5 加密
user.setPasswd(DigestUtils.md5DigestAsHex(passwd.getBytes()));
int count = userService.insert(user);
System.out.println(count);
if (count != 1) {
result.setMsg("注册失败");

return result;
}
result.setMsg("注册成功");
result.setCode(200);
return result;
}

// 登录
@RequestMapping(value = "/login", method = RequestMethod.POST)
@CrossOrigin
public Result login(@RequestParam String userName,
@RequestParam String passwd,
HttpSession session,
HttpServletResponse response) {
Result result = new Result();
User exsitUser = userService.selectByName(userName);
if (exsitUser == null) {
result.setMsg("该用户未注册");
result.setCode(400);
return result;
}
if (!exsitUser.getPasswd().equals(DigestUtils.md5DigestAsHex(passwd.getBytes()))) {
result.setMsg("密码错误,请重新输入");
result.setCode(400);
return result;
}
session.setAttribute("username", userName);
result.setCode(200);
result.setMsg("登录成功");
return result;
}

// 修改密码
@RequestMapping(value = "/changepwd", method = RequestMethod.POST)
@CrossOrigin
public Result changepwd(@RequestParam String username, @RequestParam String oldpassword, @RequestParam String newpassword, HttpSession session) {
Result result = new Result();
User exsitUser = userService.selectByName(username);

if (!DigestUtils.md5DigestAsHex(oldpassword.getBytes()).equals(exsitUser.getPasswd())) {
System.out.println(oldpassword);
result.setMsg("原密码输入错误");
return result;
}else {
int count = userService.updatePwd(username, DigestUtils.md5DigestAsHex(newpassword.getBytes()));
if (count>0) {
result.setMsg("密码修改成功");
result.setCode(200);
return result;
}
result.setMsg("密码修改错误");
return result;
}
}
}

###常见的加密算法分类

常见的加密算法可以分成三类,对称加密算法,非对称加密算法和Hash算法

对称加密

指加密和解密使用相同密钥的加密算法。对称加密算法的优点在于加解密的高速度和使用长密钥时的难破解性。假设两个用户需要使用对称加密方法加密然后交换数据,则用户最少需要2个密钥并交换使用,如果企业内用户有n个,则整个企业共需要n×(n-1) 个密钥,密钥的生成和分发将成为企业信息部门的恶梦。对称加密算法的安全性取决于加密密钥的保存情况,但要求企业中每一个持有密钥的人都保守秘密是不可能的,他们通常会有意无意的把密钥泄漏出去——如果一个用户使用的密钥被入侵者所获得,入侵者便可以读取该用户密钥加密的所有文档,如果整个企业共用一个加密密钥,那整个企业文档的保密性便无从谈起。

常见的对称加密算法:DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6和AES

非对称加密

指加密和解密使用不同密钥的加密算法,也称为公私钥加密。假设两个用户要加密交换数据,双方交换公钥,使用时一方用对方的公钥加密,另一方即可用自己的私钥解密。如果企业中有n个用户,企业需要生成n对密钥,并分发n个公钥。由于公钥是可以公开的,用户只要保管好自己的私钥即可,因此加密密钥的分发将变得十分简单。同时,由于每个用户的私钥是唯一的,其他用户除了可以可以通过信息发送者的公钥来验证信息的来源是否真实,还可以确保发送者无法否认曾发送过该信息。非对称加密的缺点是加解密速度要远远慢于对称加密,在某些极端情况下,甚至能比非对称加密慢上1000倍。

常见的非对称加密算法:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)

Hash算法

Hash算法特别的地方在于它是一种单向算法,用户可以通过Hash算法对目标信息生成一段特定长度的唯一的Hash值,却不能通过这个Hash值重新获得目标信息。因此Hash算法常用在不可还原的密码存储、信息完整性校验等。

常见的Hash算法:MD2、MD4、MD5、HAVAL、SHA、SHA-1、HMAC、HMAC-MD5、HMAC-SHA1

加密算法的效能通常可以按照算法本身的复杂程度、密钥长度(密钥越长越安全)、加解密速度等来衡量。上述的算法中,除了DES密钥长度不够、MD2速度较慢已逐渐被淘汰外,其他算法仍在目前的加密系统产品中使用。

加密算法的选择

那我们在实际使用的过程中究竟该使用哪一种比较好呢?

我们应该根据自己的使用特点来确定,由于非对称加密算法的运行速度比对称加密算法的速度慢很多,当我们需要加密大量的数据时,建议采用对称加密算法,提高加解密速度。

对称加密算法不能实现签名,因此签名只能非对称算法。

由于对称加密算法的密钥管理是一个复杂的过程,密钥的管理直接决定着他的安全性,因此当数据量很小时,我们可以考虑采用非对称加密算法。

在实际的操作过程中,我们通常采用的方式是:采用非对称加密算法管理对称算法的密钥,然后用对称加密算法加密数据,这样我们就集成了两类加密算法的优点,既实现了加密速度快的优点,又实现了安全方便管理密钥的优点。

如果在选定了加密算法后,那采用多少位的密钥呢?一般来说,密钥越长,运行的速度就越慢,应该根据的我们实际需要的安全级别来选择,一般来说,RSA建议采用1024位的数字,ECC建议采用160位,AES采用128为即可。

Spring 提供的工具类DigestUtils

sha256的使用

1
2
String encodekey = DigestUtils.sha256Hex("测试SHA256");
System.out.println(encodekey);

运行结果:

1
6c6c0785899852543f360d1270d400f709179fc03b62bada26aa0f69a8ec7dea

md5的使用

1
2
String encodekey = DigestUtils.md5Hex("测试MD5");
System.out.println(encodekey);

运行结果:

1
c2dbb895a66c3ca924ccdbea49fa6884

response

1
2
3
4
5
6
7
8
9
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
private int code;
private String msg;
private Object data;

}

config

解决跨域问题:这里使用的是用配置类的方式。也可以在后端相关Controller添加跨域注解@CrossOrigin实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class CrosConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许的请求
.allowedOrigins("*") //代表所有域名都可访问
.allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS") // 允许的请求方式
.allowCredentials(true)
.maxAge(3600) //飞行前响应的缓存持续时间的最大年龄,简单来说就是Cookie的有效期 单位为秒
.allowedHeaders("*");
}
}

application.yml

1
2
3
4
5
6
7
8
server:
port: 8002
spring:
datasource:
url: jdbc:mysql://localhost:3306/vue_springboot?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root

Application主类

1
2
3
4
5
6
7
8
9

@SpringBootApplication
public class VueSpringbootApiApplication {

public static void main(String[] args) {
SpringApplication.run(VueSpringbootApiApplication.class, args);
}

}

SQL

1
2
3
4
5
6
7
8
CREATE TABLE `t_user` (
`userId` int(11) NOT NULL AUTO_INCREMENT,
`userName` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`passwd` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`status` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`registTime` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`userId`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin