Vue 2x+SpringBoot+MySQL前后端分离登

文章版权所有:中国移动通信集团 – 山东有限公司 – 烟台分公司 – 市场经营部 – 业务支撑中心

如果要转载或者使用请联系wechat:mickeywang1998,本项目是借助网上开源的代码二次开发而成,本项目及其文档是单一来源付费的开源项目,请尊重作者创作!

项目Demo展示

为什么要做这个教程?

在你看之前:事先说明,教程很简单,是入门教程,不喜勿喷,出门右拐,喷子视情况删评论的嗷!

本人普通一本毕业,本科计算机专业,入职某国企,毕业之后希望自己能利用所学在企业施展一些拳脚,但是技术所限,这个教程是给所有的初学者,也是给我自己,好脑子不如烂笔头,希望这个教程能够帮助到更多像我一样的人。

很简单的一个项目,其实说简单也简单,说困难也困难,对于初学者,我相信大家踩了不少坑,也绕了不少的弯路。

网上大部分基于vue和springboot的项目都是非常成熟的项目,具体成熟到什么地步呢,可以这么说,项目和框架体量大的,可以大到初学者一眼看不出相互逻辑的,框架的API文档也看不懂,怎么办呢,把项目git下来自己摸索着改一下吧,发现根本改不动。

登录项目固然非常简单,但是对于初学前后端分离开发的同学或者想要转行计算机的人士来说,迫切需要一个非常基础的前后端框架逻辑及其要点的一个教程,我就是基于这个想法来做这个教程的。

界面展示

本地环境搭建

前置:nodejs安装

nodejs就是运行在服务端的JavaScript,你的JS速度越快,那么对于我们的这个登录项目来说,我们前端的用户交互和提交速度也就越快。

学过Html5+CSS+JavaScript这一套基本前端的开发流程的同学应该都知道

这样一来,有了nodejs,我们的vue项目就可以很好的运行了。下面来介绍一下nodejs安装步骤:

nodejs安装步骤

  1. Node.js 安装包及源码下载地址为:点我去官网下载nodejs
  2. win10-64bit系统用户点击下载Windows Installer (.msi)64-bit

  1. 进行安装,要记住下面图片的安装目录,防止后期环境变量PATH中没有nodejs我们还要去文件管理器中找目录

  1. 安装完毕之后要win+R运行CMD,显示如下:
1
2
3
4
5
Java复制代码Microsoft Windows [版本 10.0.18363.1316]
(c) 2019 Microsoft Corporation。保留所有权利。

C:\Users\w1248>path
PATH=F:\Programs\Python\Python38\Scripts\;F:\Programs\Python\Python38\;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0\bin;C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0\libnvvp;C:\Windows\System32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;D:\Program Files\Polyspace\R2019b\runtime\win64;D:\Program Files\Polyspace\R2019b\bin;D:\Program Files\Polyspace\R2019b\polyspace\bin;F:\Program Files\IBM\SPSS\Statistics\25\JRE\bin;D:\Program Files\nodejs\;C:\ProgramData\chocolatey\bin;D:\mysql\mysql-8.0.21-winx64\bin;E:\Maven\apache-maven-3.6.3\bin;F:\Program Files\Git\cmd;F:\texlive\2020\bin\win32;C:\Users\w1248\AppData\Local\Microsoft\WindowsApps;F:\Program Files\Microsoft VS Code\bin;F:\Program Files\JetBrains\PyCharm Community Edition 2020.1.4\bin;F:\Program Files\JetBrains\PyCharm 2020.1.4\bin;C:\Users\w1248\AppData\Local\GitHubDesktop\bin;C:\Users\w1248\AppData\Roaming\npm;D:\Program Files\JetBrains\WebStorm 2020.2.3\bin;

插播:PATH没有nodejs看这里!!!环境变量

在CMD的PATH路径下面没有看见nodejs路径的看这里!!!

首先,win10环境下面,右击“此电脑”,点击“属性”

其次,点击“高级系统设置”,再点击“环境变量”

最后,我们需要在第二个,系统变量这一栏新建变量NODE_PATH,将刚才的安装路径/bin复制进去,一路保存就可以了!

好的,我们继续刚才的话题,下面是第5步:

  1. 随后检查nodejs的版本,如何用CMD命令行查看nodejs的版本?输入在CMD中,代码如下:
1
Java复制代码node -v

或者

1
Java复制代码node --version

安装版本为V14.12.0

前置:npm安装

npm的作用

npm是一个在nodejs下面的包管理器,他在nodejs安装完毕时就已经完成安装了,只不过有可能不是最新版本。顾名思义,没有这个包管理器,你的所有后续工作都无法进行。

NPM能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种:

  1. 允许用户从NPM服务器下载别人编写的第三方包到本地使用。
  2. 允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。
  3. 允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。

查看npm版本

其实nodejs安装完了,npm也会自动完成安装,如何用CMD命令行查看npm的版本?

1
Java复制代码npm -v

npm的全局安装和本地安装

  1. 全局安装

这里是全局安装可以看到在根目录下都有cmd后缀的文件,通过这些文件,我们就可以直接在命令行中使用了。 如使用 xxx –version等等都是有效的,且不管你是在哪个目录文件下运行的。

也就是说,全局安装的意思,就是在计算机的任何一个角落,任何一个目录下,你都可以通过CMD等方式,找到安装好的组件

  1. 局部安装

但是局部安装并不是这样,往往是在一个项目中安装之后装入 ./node_nodules 目录下面,这样的局限在于,只能是在本项目中使用。

特别的是,webpack和babel这样的工具可以在各种情况、各种项目中使用。

升级npm/利用淘宝镜像升级

  1. 如果你想升级的话,可以在CMD输入
1
Java复制代码npm install npm -g
  1. 配置淘宝镜像源,随后利用淘宝镜像源的资源来升级npm,方法如下:

首先,修改npm的镜像站点配置

1
Java复制代码npm install -g cnpm --registry=https://registry.npm.taobao.org

其次,检查我们的npm

1
2
Java复制代码C:\Users\w1248>npm config get registry
https://registry.npm.taobao.org/

最后,通过淘宝镜像源更新npm

1
Java复制代码C:\Users\w1248>npm install npm -g

屏幕显示如下:

1
2
3
4
Java复制代码C:\Users\w1248\AppData\Roaming\npm\npm -> C:\Users\w1248\AppData\Roaming\npm\node_modules\npm\bin\npm-cli.js
C:\Users\w1248\AppData\Roaming\npm\npx -> C:\Users\w1248\AppData\Roaming\npm\node_modules\npm\bin\npx-cli.js
+ npm@6.14.11
added 435 packages from 889 contributors in 10.398s

可选:cnpm安装

其实在安装完npm之后,我们可以利用npm包管理器的作用来完成cnpm的安装

  1. 打开CMD,输入命令(其实刚才配置了镜像源的话,npm install -g cnpm就可以了)
1
Java复制代码npm install -g cnpm --registry=https://registry.npm.taobao.org
  1. 检查cnpm的版本,输入
1
Java复制代码cnpm -v

显示如下(这段显示是我的计算机没有配置环境变量为nodejs的node_modules目录,如果你在我的上面文章介绍中配置了,那你的会和我不一样)

1
2
3
4
5
6
7
Java复制代码cnpm@6.1.1 (C:\Users\w1248\AppData\Roaming\npm\node_modules\cnpm\lib\parse_argv.js)
npm@6.14.8 (C:\Users\w1248\AppData\Roaming\npm\node_modules\cnpm\node_modules\npm\lib\npm.js)
node@14.12.0 (D:\Program Files\nodejs\node.exe)
npminstall@3.27.0 (C:\Users\w1248\AppData\Roaming\npm\node_modules\cnpm\node_modules\npminstall\lib\index.js)
prefix=C:\Users\w1248\AppData\Roaming\npm
win32 x64 10.0.18363
registry=https://r.npm.taobao.org

vue安装

vue.js安装

我们之前已经安装好了nodejs和npm,要安装vue.js,CMD输入

1
Java复制代码npm install vue -g

vue-router安装

我们之前已经安装好了nodejs和npm,要安装vue-router,CMD输入

1
Java复制代码npm install vue-router -g

vue-cli 3 脚手架安装(内置webpack)

现在我们来安装vue-cli的开发脚手架,CMD输入

1
Java复制代码npm install vue-cli -g

利用CMD命令行,输入下面的命令,对vue的版本进行查看

1
Java复制代码vue -V

环境变量配置

上述nodejs配置,我们已经解决了环境变量的配置

Vue前端页面开发

前端开发用到的IDE:Visual Studio Code

前端项目目录:

vue项目初始构建(附录:VSCode加右键菜单)

创建VUE项目

  1. 创建 Vue 项目(进入自己想创建的文件夹位置,我放在 D:\teaching),创建语句是 vue create vue-spring-loginwork,方向键上下可以你的选择创建方式,我选择默认default Vue 2(因为Vue 3的一些语法改变我不是很熟悉,我没有选择3)
1
Java复制代码vue create vue-spring-loginwork

创建完成之后是这样的

  1. 进入到创建的 Vue 项目目录,添加依赖框架,在cmd中代码如下:
1
2
3
4
5
Java复制代码cd vue-spring-loginwork
vue add element
npm install axios
npm install vuex --save
npm install vue-router

(1) 进入目录cd vue-spring-loginwork

(2) 添加饿了么团队研发的element-ui组件,我们在vue中会用到一些el-form等等样式(这个时候要等待一会比较慢)

这个地方要选择Fully import,y,zh-CN,切记

完成截图:

(3) 安装 axios,用于网络请求

(4) 安装 Vuex,用于管理前端的状态

(5) 安装vue框架路由,用于实现vue页面的跳转

(6) 这个时候就基本框架搞定了,我们先做个小测试

打开项目,我们的目录是这样的,如图

这个时候我们打开VSCode下方的终端,输入npm run serve测试:

浏览器输入http://localhost:8080/ 发现页面,说明成功!(这个页面说明我们的element和vue都已经安装好了)

将VSCODE加入右键菜单,方便我们打开项目!

PS:在这里,教给大家如何将VSCode加入我们的右键菜单当中,方便我们打开项目

首先,桌面创建一个aaa.txt文件,将下面的代码copy进去

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
Java复制代码Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\*\shell\VSCode]
@="Open with Code"
"Icon"="D:\\Program Files\\Microsoft VS Code\\Code.exe"

[HKEY_CLASSES_ROOT\*\shell\VSCode\command]
@="\"D:\\Program Files\\Microsoft VS Code\\Code.exe\" \"%1\""

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\shell\VSCode]
@="Open with Code"
"Icon"="D:\\Program Files\\Microsoft VS Code\\Code.exe"

[HKEY_CLASSES_ROOT\Directory\shell\VSCode\command]
@="\"D:\\Program Files\\Microsoft VS Code\\Code.exe\" \"%V\""

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\VSCode]
@="Open with Code"
"Icon"="D:\\Program Files\\Microsoft VS Code\\Code.exe"

[HKEY_CLASSES_ROOT\Directory\Background\shell\VSCode\command]
@="\"D:\\Program Files\\Microsoft VS Code\\Code.exe\" \"%V\""

其次,将里面所有的路径,改为你的Code.exe路径,查看自己的VSCODE安装路径,我的Code.exe路径为”D:\Program Files\Microsoft VS Code\Code.exe”(自己文件路径不是双斜线的一定要自己改成双斜线\,例如,D:\Program Files改成D:\Program Files,否则会失败的

最后,将txt命名为aaa.reg,执行文件,完成!

修改index.html,App.vue和main.js

初始前端Vue项目显示逻辑

项目建立完毕之后,页面的显示逻辑是:

  1. 浏览器访问项目,最先访问的是index.html文件
  2. 上面有一个id为app的挂载点,之后我们的Vue根实例就会挂载到该挂载点上

  1. main.js作为vue项目的入口文件,在main.js中,新建了一个Vue实例,在Vue实例中,通过下图告诉该实例要挂载的地方

  1. 所以Vue这个实例就是战士的是App.vue这个组件的内容。

显示结果:在网页的Title部分,加载了index.html中定义的Title,而在正文部分,加载了App.vue中定义的部分。(但是需要注意的是,在浏览器打开的瞬间,浏览器中正文部分会瞬间显示index.html中定义的正文部分)

在做这一步之前,我们需要往项目的src目录当中,先添加几个文件夹目录,他们分别是

  1. api (网络请求接口)
  2. router (路由配置)
  3. store (Vuex 状态管理)
  4. utils (项目的工具包)
  5. views (视图包,存放页面代码,可根据功能模块进行相应分包)

如图所示:

下面我们来修改基本的入口App.vue和main.js

修改index.html

  1. public目录下的index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Html复制代码<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- <title><%= htmlWebpackPlugin.options.title %></title> -->
<title>前后端分离项目演示demo</title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

修改App.vue

  1. App.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Html复制代码<template>
<div>
本页面是路由初始页面
<!-- 路由的出入口,路由的内容将被显示在这里 -->
<router-view/>
</div>
</template>

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

<style lang="stylus">

</style>

修改main.js

  1. main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
javascript复制代码import Vue from 'vue'
import App from './App.vue'
import './plugins/element.js'
import router from './router'
import store from './store'


Vue.config.productionTip = false


new Vue({
render: h => h(App),//render翻译:递交,将App.vue这个组件通过new的这个实例递交上去
router,
store
}).$mount('#app') //mount英文翻译:挂载,将实例装载到index.html中的id为app的位置

编写error,login,success的vue界面

修改完基本的项目入口御三家之后,我们开始编写登录页面组件,以及登陆之后的反馈页面组件error和success

login.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
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
Html复制代码<template>
<div>
<el-card class="login-form-layout">
<el-form
autocomplete="on"
:model="loginForm"
ref="loginForm"
label-position="left"
>
<div style="text-align: center">
<svg-icon icon-class="login-mall" style="width: 56px;height: 56px;color: #409EFF"></svg-icon>
</div>
<h2 class="login-title color-main">element-ui登录器</h2>
<el-form-item prop="username">
<el-input
name="username"
type="text"
v-model="loginForm.username"
autocomplete="on"
placeholder="请输入用户名"
>
<span slot="prefix">
<svg-icon icon-class="user" class="color-main"></svg-icon>
</span>
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
name="password"
:type="pwdType"
@keyup.enter.native="handleLogin"
v-model="loginForm.password"
autocomplete="on"
placeholder="请输入密码"
>
<span slot="prefix">
<svg-icon icon-class="password" class="color-main"></svg-icon>
</span>
<span slot="suffix" @click="showPwd">
<svg-icon icon-class="eye" class="color-main"></svg-icon>
</span>
</el-input>
</el-form-item>
<el-form-item style="margin-bottom: 60px">
<el-button
style="width: 100%"
type="primary"
:loading="loading"
@click.native.prevent="handleLogin"
>登录</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>

<script>
export default {
name: "login",
data() {
return {
loginForm: {
username: "juejin",
password: "123456"
},
loading: false,
pwdType: "password",
};
},
methods: {
showPwd() {
if (this.pwdType === "password") {
this.pwdType = "";
} else {
this.pwdType = "password";
}
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
//调用store下的Login方法
this.$store
.dispatch("Login", this.loginForm)//读取loginForm里面的username和password
.then(response => {//声明response变量,类型是json
this.loading = false;
let code = response.data.code;//后端传给前端的值为data,code,message,传值给code
if (code == 200) {
this.$router.push({
path: "/success",
query: { data: response.data.data }
});
} //this.$router.push根据路由将query里面的信息push给/success
else {
this.$router.push({
path: "/error",
query: { message: response.data.message }
});
}//this.$router.push根据路由将query里面的信息push给/error
})
.catch(() => {
this.loading = false;
});
} else {
// eslint-disable-next-line no-console
console.log("参数验证不合法!");
return false;
}
});
}
}
};
</script>

<style scoped>
.login-form-layout {
position: absolute;
left: 0;
right: 0;
width: 360px;
margin: 140px auto;
border-top: 10px solid #409eff;
}

.login-title {
text-align: center;
}

.login-center-layout {
background: #409eff;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
margin-top: 200px;
}
</style>

success.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Html复制代码<template>
<div>
<h1>登陆成功:{{msg}}</h1>
</div>
</template>

<script>
export default {
data(){
return{
msg:this.$route.query.data//login.vue里面根据路由route传过来的query里面的data
};
}
}
</script>

<style>

</style>

error.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Html复制代码<template>
<div>
<h1>登陆错误:{{msg}}</h1>
</div>
</template>

<script>
export default {
data(){
return{
msg:null
};
},
created(){
this.msg=this.$route.query.message;//login.vue里面根据路由route传过来的query里面的message
}
}
</script>

<style>

</style>

注册路由vue-router

这一步我们需要将我们我们写好的页面配置到路由里面,这样我们的router/index.js需要配置我们的views里面页面组件

router/index.js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
javascript复制代码import Vue from 'vue' //引入 Vue
import VueRouter from 'vue-router' //引入 Vue 路由

Vue.use(VueRouter); //安装插件

export const constantRouterMap = [
//配置默认的路径,默认根据app.vue的router-view跳转显示登录页
{ path: '/', component: () => import('@/views/login')},

//配置登录成功页面,使用时需要使用 path 路径来实现跳转
{ path: '/success', component: () => import('@/views/success')},

//配置登录失败页面,使用时需要使用 path 路径来实现跳转
{ path: '/error', component: () => import('@/views/error'), hidden: true }
]

export default new VueRouter({
// mode: 'history', //后端支持可开
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap //指定路由列表
})

this里面的内容(也就是data里面的)

利用postman进行后端–前端测试

发现返回的数据如下

1
2
3
4
5
Java复制代码{
"code": 200,
"message": "操作成功",
"data": "我是后端controller @ResponseBody通过CommonResult API返回的success"
}

这就已经和代码对应上了

使用 Vuex + Axios 方式进行网络请求(本章需先看后端再来)

这个地方就是前端项目的重中之重了,我会重点来讲解,他们这个前后端到底是如何交互的。

其实这个教程我是写反了了的,正常是先写好后端,才能写前端,因为前端他不能架空来写,他是需要后端和数据库来做沟通交流的。

重点:前端和后端的传值字段

我们的json里面是包含两个字段的,也就是:

1
2
3
4
Java复制代码{
"username": "juejin",
"password": "123456"
}

前端是通过访问了页面之后,向http://localhost:8088/admin/login POST username和password。

这个从src/api/login.js里面可以看到.

之后呢?后端反馈的是什么呢?

  1. 如果成功的话,返回的是:
1
2
3
4
5
Java复制代码{
"code": 200,
"message": "操作成功",
"data": "我是后端controller @ResponseBody通过CommonResult API返回的success"
}
  1. 如果失败的话,返回的是:
1
2
3
4
5
Java复制代码{
"code": 404,
"message": "我是后端controller @ResponseBody通过CommonResult API返回的error",
"data": null
}

你会发现成功的时候,提示信息是放在data里面的,然而,失败是放在message里面的。

这是因为后端的controller里面调用的CommomResult的缘故:

在src/main/java/cn.eli.vue.api里面的CommonResult方法写到:

仔细看一下顺序,和传参类型:

  1. 如果成功的话:

方法是读进来一个泛型的data,返回的是code,message(这两个在枚举里面都写了),还有一个泛型data。

  1. 如果失败的话:

方法是读进来一个字符串的message,返回的是code,刚才读进来的message,还有一个泛型data(这个data已经被置为空)。

这个时候我们看看枚举里面怎么写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java复制代码
SUCCESS(200, "操作成功"),
FAILED(500, "操作失败"),
VALIDATE_FAILED(404, "参数检验失败"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "没有相关权限");

private long code;
private String message;

private ResultCode(long code, String message) {
this.code = code;
this.message = message;
}

public long getCode() {
return code;
}

public String getMessage() {
return message;
}

这就可以看出我们传值的类型了。

重点:本项目如何前端向后端传json的,以及网络请求逻辑

前面解释清楚了前端和后端传值的类型和具体名字,我们在这里讨论一下,前端向后端的POST流程:

页面的逻辑没有什么好说的,一个入口,页面写完,路由配置好,这样静态页面就能一个一个访问了。

关键的是vuex和axios的部分:

  1. 首先,先运行main.js,要引入store(为啥不引入axios呢,后面在utils里面的request会用到),进而通过store进行网络请求(store文件夹是vuex的状态管理器)
  2. 在store里面的index.js,在我们的项目里面是一个组装模块并且导出store的地方,主要写法:const store=new Vuex.Store(option),是一个vuex固定写法

(1) 对于options这个const里面的参数可以是state, getters(key: string),mutations[type: string],actions,[type: string],modules(store里面的模块),plugins(Array<Function(store)>,自定义插件),strict, (Boolean,默认false),devtools,(Boolean,默认false)

(2) 对于每一个module都可以随意创建一个对应模块语义化的.js文件(如test.js),然后export输出

(3) 把store,拆分成多个子模块(module),以方便开发维护。

使用方法:在new Vuex.Store({modules:{m1,m2,m3}}),子模块(module)就是一个普通的对象。

(4) module里面js写法:

export default {
namespaced:true,//为了解决不同模块命名冲突的问题
state:{},//规定变量名并且初始化值
getters:{},// 获取用户信息
mutations:{},//将信息可以通过mutations里面的方法放进state
actions:{}//动作
}

(5)讲一下插件plugins,plugins实际上是函数,它接收 store 作为唯一参数

1
2
3
4
Java复制代码const store = new Vuex.Store({
// ...
plugins: [myPlugin]
})

那么plugins的肚子里都是什么东西呢?

1
2
3
4
5
6
7
Java复制代码const myPlugin = store => {
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// 每次 mutation 之后调用
// mutation 的格式为 { type, payload }
})
}

首先,在插件内提交 Mutation,生成 State 快照,快照就是像一个瞬间曝光的照片,可以比较所拍事物的前后变化,在这里大体说一下,我们项目里没有用到,官方详细讲plugins的文档,vuex.vuejs.org/zh/guide/pl…

  1. store通过user.js这个模块进行网络请求,actions里面定义Login方法,这个方法在login.vue组件中使用,语法是this.$store.dispatch(“Login”)

user.js里面的逻辑其实网上很多大佬已经写好了,包括之前login.vue的登录逻辑,废话不说看一下代码,我从user.js文件中拿出来的,我把第一句import和最后一句export删了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Java复制代码const user = {
actions: {
//登录
//定义Login方法,在组件中使用语法this.$store.dispatch("Login")
Login({ commit }, userInfo) {
//commit为了后面提交一个token,我们这里没有设置
//const username=userInfo.username.trim()//处理传递过来的数据
const { username, password } = userInfo

//封装一个 Promise
return new Promise((resolve, reject) => {
//使用 login 接口进行网络请求
login(username, password).then(response => {
commit('') //提交一个 mutation,通知状态改变
resolve(response) //将结果封装进 Promise
}).catch(error => {
reject(error)
})
})
},
}
}

这里上面const username=userInfo.username.trim(),其实可以不做处理改成const { username, password } = userInfo。commit为了后面提交一个token,我们这里没有设置,token就是在本地的一个验证的东西。

网上有一些不良代码写的很误导人,虽然我写的也不是很好,但是我有讲解,没有讲解的话真的很容易误导别人。

  1. 随后user.js在return Promise里面利用api/login.js接口的login来发网络请求
  2. 之后你会发现,login接口里面用到的request是utils/request.js

utils/request.js里面对axios基本配置,创建axios实例:

1
2
3
4
5
6
7
8
9
Javascript复制代码import axios from 'axios';
import baseUrl from "@/api/baseUrl";

const service = axios.create({
baseURL:baseUrl,// api的base_url
timeout:15000 // 请求超时时间
})

export default service

export语法声明用于导出函数、对象、指定文件(或模块)的原始值
import花括号里面的变量与export后面的变量一一对应

这时,api/login.js里面就可以import request from ‘@/utils/request’

直接把实例化axios引入,也就是说,在api/login.js里面的request这个量,可以使用axios的get,post等等,也可以通过向axios传递相关配置来创建请求

请看官网写法:axios.create([config])

写一个实例,之后往这个实例里面,我们可以有axios([config])这种写法(这种就是请求方法的写法了)

之后还有“别名”写法

为方便起见,为所有支持的请求方法提供了别名

axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])

  1. 访问逻辑顺序:main.js调用store/index.js调用user.js调用api/login.js调用request.js调用api/baseUrl.js

Vuex + Axios部分所有源文件代码

api/baseUrl.js

1
2
3
4
5
6
7
8
9
10
11
12
13
Javascript复制代码let baseUrl = "";
let NODE_ENV='dev';
switch (NODE_ENV) { //注意变量名是自定义的
//process.env.NODE_ENV
case 'dev':
baseUrl = "http://localhost:8088/" //开发环境url
break
case 'serve':
baseUrl = "http://localhost:8089/" //生产环境url
break
}

export default baseUrl;

api/login.js

1
2
3
4
5
6
7
8
9
10
11
12
13
Javascript复制代码import request from '@/utils/request' //引入封装好的 axios 请求

export function login(username, password) { //登录接口

return request({ //使用封装好的 axios 进行网络请求
url: '/admin/login',
method: 'post',
data: { //提交的数据
username,
password
}
})
}

store/modules/user.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Javascript复制代码import { login } from '@/api/login'

const user = {
actions: {
//登录
//定义Login方法,在组件中使用语法this.$store.dispatch("Login")
Login({ commit }, userInfo) {
//const username=userInfo.username.trim()//处理传递过来的数据
const { username, password } = userInfo
//封装一个 Promise
return new Promise((resolve, reject) => {
//使用 login 接口进行网络请求
login(username, password).then(response => {
commit('') //提交一个 mutation,通知状态改变
resolve(response) //将结果封装进 Promise
}).catch(error => {
reject(error)
})
})
},
}
}
export default user

store/index.js

1
2
3
4
5
6
7
8
9
Javascript复制代码import Vue from 'vue'
import Vuex from 'vuex'
import user from "./modules/user"

Vue.use(Vuex)

const store=new Vuex.Store({modules:{user},plugins:[]})

export default store

utils/request.js

1
2
3
4
5
6
7
8
9
10
Javascript复制代码import axios from 'axios';
import baseUrl from "@/api/baseUrl";
//import baseUrl from '../api/baseUrl'使用环境变量 + 模式的方式定义基础URL

const service = axios.create({
baseURL:baseUrl,
timeout:15000,
})

export default service

SpringBoot后端接口逻辑开发

后端的IDE用的是:Intellij IDEA 2020.2

后端项目目录:

第二个图接上面的test

前置:Java注解开发

这个问题,可以说是从大学的汇编语言,C,Java,数据结构,编译原理算法导论等等一系列软件底层技术学完之后,在开发上遇到的第一个算是挺困惑的问题,在介绍后端开发之前,我们先来介绍这个后端开发最为基本的问题。

什么是注解(annotation)

Java 注解是在 JDK5 时引入的新特性,注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。

注解类型定义指定了一种新的类型,一种特殊的接口类型。

既然注解是一种特殊的接口,那么关键词@interface的写法也就表示注解的特殊性,来区分注解的定义和普通的接口声明。

程序可以通过反射来获取指定程序元素的注解对象,然后通过注解对象来获取注解里面的元数据。注解可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。

通俗的说,通过反射得到类里的各种注解信息,比如注解下的字段和属性方法,然后就可以根据这些信息执行特定的代码。

在框架里面使用注解的多,因为注解中的代码都已经实现过了,知道注解含义拿来用就完事了

项目携带信息的方式

  1. 通过注解来携带信息,然后通过反射来读取信息
  2. 通过文件来携带信息,然后通过IO来读取信息

元注解(meta-annotation)与自定义注解

我们还可以自定义注解,在JDK 1.5中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为元注解。

我们可以使用这4个元注解来对我们自定义的注解类型进行注解,接下来,我们挨个对这4个元注解的作用进行介绍。

  1. @Target注解:描述注解的使用范围,说明被Target修饰的注解可以用在什么地方
  2. @Retention注解:描述注解保留的时间范围(被Retention描述的注解在它所修饰的类中可以被保留到何时) 。
  3. @Documented注解:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息
  4. @Inherited注解:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)

对于元注解的应用,在这里我们就不过多介绍,这里只是说一下元注解的定义和作用意义,想要学习元注解怎么用的可以去自行学习一下。

注解的作用

  1. 提供信息给编译器:编译器可以利用注解来探测错误和警告信息,@Override(编译器给你验证@Override接下来后面的方法名是否是你父类中所有的,如果没有则报错)、@Deprecated(表示接下来的方法不推荐使用,因为还有更好的方法可以调用)。
  2. 编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html 文档或者做其它相应处理,如 @Param(@Param是org.apache.ibatis.annotations.Param提供的,作为实体类entity/Dao层的注解,作用是用于传递参数,从而可以与SQL中的的字段名相对应,一般在2=<参数数<=5时使用最佳)、@Return(生成JavaDoc的说明)、@Author(文件开头注释生成作者信息)。
  3. 运行时的处理: 某些注解可以在程序运行的时候接受代码的提取,值得注意的是,注解不是代码本身的一部分。Spring 2.5开始注解配置,减少了配置。

注解的特殊性

通过 @interface 定义注解后,该注解也不能继承其他的注解或接口,注解是不支持继承(extends)的。

几种特殊的注解

下面介绍几种常用注解,因为4级标题没有目录,下面我都换成了3级标题,目录有了也好方便查看!

@Override

重写方法需要的注解,举个例子:

@RequestMapping

@RequestMapping其实在功能上来说,是@PostMapping和@GetMapping的组合,如果怕在映射的时候出现这种不必要的书写错误,不知道后面的方法是GET还是POST的时候,可以统一写@RequestMapping
(<=>表示等价于)

@RequestMapping(method = RequestMethod.GET) <=> @GetMapping

@RequestMapping(method = RequestMethod.POST) <=> @PostMapping

常用的@RequestMapping包含七种请求方式,他们是:

GET(请求指定页面信息),POST(向指定资源提交数据),HEAD,(前面三种是HTTP1.0就已经规定好的)PUT(客户端->服务器),DELETE(服务器删除指定页面),CONNECT(连接改为管道方式),OPTIONS(客户端查看服务器性能)

@ResponseBody和@RequestBody

  1. @ResponseBody

将java对象转为json格式的数据,作用在方法上
将ajax(datas)发出的请求写入 User 对象中

如果返回值是字符串,那么直接将字符串写到客户端;如果是一个对象,会将对象转化为json串,然后写到客户端。

  1. @RequestBody

如下面例子演示,@ResponseBody和@RequestBody配合这样就不会再被解析为跳转路径,而是直接将user对象写入 HTTP 响应正文中

例如:

1
2
3
4
5
6
7
Java复制代码@RequestMapping(value = "user/login")
@ResponseBody

public User login(@RequestBody User user) {

return user;
}

@RestController和@Controller

@Controller 控制器(注入服务)
用于标注控制层,相当于struts中的action层

@Autowired

后面结合@Repository讲到,在这列出,但是不做赘述

@Service

@Service 服务(注入dao)
用于标注服务层,主要用来进行业务的逻辑处理

@Mapper和@Repository

  1. 相同点

@Mapper和@Repository都是作用在实体层(dao/entity)接口,使得其生成代理对象bean,交给spring容器管理
对于mybatis来说,都可以不用写mapper.xml文件

@Repository(实现实体层(dao/entity)访问)
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件

  1. 不同点

(1)@Mapper不需要配置扫描地址,通过mapper.xml里面的namespace里面的接口地址,生成了Bean后注入到Service层中。

@Mapper不需要配置扫描地址,可以单独使用,如果有多个mapper文件的话,可以在项目启动类中加入@MapperScan(“mapper文件所在包”),这样就不需要每个mapper文件都加@Mapper注解了

(2)@Repository需要在Spring中配置扫描地址,然后生成Dao层的Bean才能被注入到Service层中。

@Repository不可以单独使用,否则会报错误找不到bean,这是因为项目启动的时候没有去扫描使用@Repository注解的文件,所以使用@Repository需要配置扫描地址;

在idea中,使用@Repository可以消除在业务层中注入mapper对象时的错误;

如果你没有在mapper接口类(class)的头上标注@Repository注解,并配置value=’地址’,那么你必须在serviceImpl的中引用接口时在头上标注@Autowired,如下图所示:

@component和@MapperScan

一旦使用关于Spring的注解出现在类里,例如我在实现类中用到了@Autowired注解,被注解的这个类是从Spring容器中取出来的,那调用的实现类也需要被Spring容器管理,加上@Component

@Component (把普通pojo实例化到spring容器中,相当于配置文件)泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Service等的时候),我们就可以使用@Component来标注这个类。

Q:为啥UserMapper上面不用写@Component?

mapper接口怎么被spring管理的?

  1. 通过 @MapperScan 扫描到对应的接口
  2. 对接口进行动态代理,代理类做到的事情就是拿到sql,连接数据库,执行sql
  3. 通过spring扩展机制FactoryBean将代理对象交给spring管理

综上所述,所以mapper使用的时候,只需要在启动类Application.class里面的主类头上标注@MapperScan,下图,注意:在引号里写包目录,要精确到mapper包(com.xxx.mapper)

项目初始化

点击左上角File->New->Project,按照箭头点击

Next之后,再按照箭头点击,注意Java Version选择8

到下图页面就是选择基本的依赖了,
我一般是选择下列这些作为基本的依赖,如果自己想要的包这些栏目里面没有的话,后期可以在pom.xml自己添加的:

  1. Developer Tools里面的Spring Boot DevTools
  2. Developer Tools里面的Lombok
  3. Web里面的Spring Web
  4. Security里面的Spring Security
  5. SQL里面的Spring Data JDBC

最后呢就到了工程项目路径选择啦,选一个放我们的项目文件,最好不要C盘呦,C盘满了的话就很头疼啦!之后点个Finish!

下面就正式进入后端文件的编写了

pom.xml编写

如果后端项目,你是按照我上面的步骤下来的,那么在pom.xml文件里面一定会出现下面几个依赖(dependency),他们的artifactId分别是

  1. spring-boot-starter-parent:这个依赖的版本,我的是2.4.1,他是 Spring Boot 的父级依赖(总依赖),这样代表当前的项目就是 Spring Boot 项目了。spring-boot-starter-parent 是一个特殊的 starter,它用来提供相关的 Maven 默认依赖。
  2. properties,声明我们Java JDK版本,一般是1.8
  3. dependencies里面,spring-boot-starter-web:项目启动的时候,使用嵌入式的tomcat作为web容器提供HTTP服务,提供注解@EnableWebMvc的@configuration配置类完全接管所有SpringMVC的相关配置,Converter和Formatter注册到IOC容器

pom.xml的目的:

POM是项目对象模型(Project Object Model)的简称,它是Maven项目中的文件,使用XML表示,名称叫做pom.xml。该文件用于管理:源代码、配置文件、开发者的信息和角色、问题追踪系统、组织信息、项目授权、项目的url、项目的依赖关系等等。事实上,在Maven世界中,project可以什么都没有,甚至没有代码,但是必须包含pom.xml文件。

pom.xml可以多写,但是一定不能少写

本项目的pom.xml如下:

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
112
113
Java复制代码<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.eli.vue</groupId>
<artifactId>vue-login-java</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>vue-login-java</name>
<description>Vue Login Java</description>

<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>

<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>

<!--mp代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.2.0</version>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>

<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yml</include>
</includes>
<filtering>false</filtering>
</resource>

<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>

</resources>

</build>

</project>

下面来解释一下pom文件
有一部分的依赖是在Spring Initializr
大标签project部分表示:本工程的依赖

在声明的时候,version标签可以不写,为什么可以不写呢?
这是parent标签里面的spring-boot-starter-parent会为我们提供常用jar包版本,其实不是不用指定,是人家为我们指定好了。自己指定版本号也可以,会覆盖官方版本,自己用maven helper查看一下有没有冲突即可。

本工程的依赖也不是特别多,列举几个重要的:
(本项目中spring-boot-devtools,lombok,spring-boot-starter-data-jdbc没有用到,我一般是都引入的)

  1. mybatis-plus-boot-starter

mybatis必需的包

  1. mybatis-plus-generator

mybatis必需的包

  1. spring-boot-starter-web:这个在上面我们已经解释过了。
  2. spring-boot-devtools:devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现),实现类文件热部署(类文件修改后不会立即生效),实现对属性文件的热部署。
  3. lombok:根据代码生成抽象语法树AST(参见《数据结构》)

举个简单的例子:

我们建一个实体类的时候,往往要自动生成他的所有基本操作方法。一开始我们肯定是这么写

1
2
3
4
5
Java复制代码public class Mountain{
private String name;
private double longitude;
private String country;
}

现在有了lombok之后,我们加一个@data注解

1
2
3
4
5
6
Java复制代码@Data
public class Mountain{
private String name;
private double longitude;
private String country;
}

我们会发现

贼jb方便对不对!

对于lombok中注解的使用,介绍几个常用的,如下。

@Getter/@Setter:作用在类上,生成所有成员变量的getter/setter方法

@ToString:作用于类,覆盖默认toString()方法,可以通过of属性限定显示某些字段

@EqualsAndHashCode:作用于类,覆盖默认的equals和hashCode方法

@NonNull:主要作用于成员变量和参数中,标识不能为空,否则抛出空指针异常

@Data:作用于类上,是以下注解的集合:@ToString @EqualsAndHashCode @Getter @Setter @RequiredArgsConstructor

  1. spring-boot-starter-data-jdbc:
    Spring Data 包含对 JDBC 的存储库支持,并将自动为 CrudRepository 上的方法生成 SQL。当 classpath 上存在必要的依赖项时,Spring Boot 将自动配置 Spring Data 的 JDBC 存储库。

这个包引入之后,可以通过方法的方式来操作数据库(这里我不讲,想学的自己查资料,因为我不常用,项目里面也没有用!),当然,你也可以不用,后面有xml操作数据库的使用介绍

我个人认为,这个包存在的目的是为了给出数据源的配置语法:
在 src/main/resource 目录下添加 application.properties 配置文件,内容如下:

1
2
3
4
Java复制代码spring.datasource.url = jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver

懂得都懂,这就是yml里面那一坨。

  1. mysql-connector-java:

JAVA通过jdbc访问mySQL数据库时需要该包支持,

首先在这里说一下这个包的版本问题,自己曾经因为mysql8和mysql5的版本问题项目出了问题没有搞定,贼烦,你在写项目之前首先要搞明白你自己的项目要用的是5还是8

(1)mysql-connector-java与Mysql对应版本:

(2)mysql-connector-java与Java对应版本:

  1. spring-boot-starter-test:

项目自动生成的时候,会带着一个测试类,我就没删,讲道理是可以删的

定义返回类result和API操作码

通用的返回对象,API接口的操作码,这些网上有统一的格式规范,建议同学们搞懂一个写法之后一直用就可以了。

在这里我讲一下我用的一套写法,这东西是配套的,要用就用一套,不要去东配西凑,很容易出岔子。

返回类result和API操作码,这些东西我放在了一个叫api的包下面,目录如下:

OK,现在开始放代码嗷!

  1. CommonResult.java(类)
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
112
113
114
115
116
117
118
119
120
Java复制代码package cn.eli.vue.api;

/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/
public class CommonResult<T> {
private long code;
private String message;
private T data;

protected CommonResult() {

}

protected CommonResult(long code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}

/**
* 成功返回结果
*
* @param data 获取的数据
*/
public static <T> CommonResult<T> success(T data) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
}

/**
* 成功返回结果
*
* @param data 获取的数据
* @param message 提示信息
*/
public static <T> CommonResult<T> success(T data, String message) {
return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
}

/**
* 失败返回结果
*
* @param errorCode 错误码
*/
public static <T> CommonResult<T> failed(IErrorCode errorCode) {
return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
}

/**
* 失败返回结果
*
* @param message 提示信息
*/
public static <T> CommonResult<T> failed(String message) {
return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
}

/**
* 失败返回结果
*/
public static <T> CommonResult<T> failed() {
return failed(ResultCode.FAILED);
}

/**
* 参数验证失败返回结果
*/
public static <T> CommonResult<T> validateFailed() {
return failed(ResultCode.VALIDATE_FAILED);
}

/**
* 参数验证失败返回结果
*
* @param message 提示信息
*/
public static <T> CommonResult<T> validateFailed(String message) {
return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
}

/**
* 未登录返回结果
*/
public static <T> CommonResult<T> unauthorized(T data) {
return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
}

/**
* 未授权返回结果
*/
public static <T> CommonResult<T> forbidden(T data) {
return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
}

public long getCode() {
return code;
}

public void setCode(long code) {
this.code = code;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}
}
  1. IErrorCode.java(接口)
1
2
3
4
5
6
7
8
9
10
11
Java复制代码package cn.eli.vue.api;

/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/
public interface IErrorCode {
long getCode();
String getMessage();
}
  1. ResultCode.java(类)
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
Java复制代码package cn.eli.vue.api;

/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/
public enum ResultCode implements IErrorCode {
SUCCESS(200, "操作成功"),
FAILED(500, "操作失败"),
VALIDATE_FAILED(404, "参数检验失败"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "没有相关权限");
private long code;
private String message;

private ResultCode(long code, String message) {
this.code = code;
this.message = message;
}

public long getCode() {
return code;
}

public String getMessage() {
return message;
}
}

后端对跨域请求的解决CorsConfig

对于跨域请求的处理,我们这里在后端进行处理,代码目录如下:

跨域请求的详细原理请自行百度,在这里我只说明怎么用

文件:CorsConfig.java

代码:

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
Java复制代码package cn.eli.vue.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/
@Configuration
public class CorsConfig {
private CorsConfiguration buildConfig() {
System.out.println("222222222222");
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); // 1
corsConfiguration.addAllowedHeader("*"); // 2
corsConfiguration.addAllowedMethod("*"); // 3
return corsConfiguration;
}

@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4
return new CorsFilter(source);
}
}

编写实体类entity中的user

大家都知道springboot的业务逻辑:

实体类User从controller中根据service中的逻辑在mapper接口中和数据库交互这个逻辑

我们,先写实体类Dao/entity,规定好用户实体的基本方法,随后根据登录功能开发对应的mapper接口和数据库操作即为mapper对应的xml,写好之后,再写基本的service层里面的service接口以及对service的实现(即为Impl),最后,写登录的controller,说简单也简单,说困难也困难。

User.java实体类很简单,目录逻辑如下:

User.java代码如下,里面用不着任何注解,都是基本的get和set,如果自己不想写,可以去用我上面介绍的lombok包自动实现一下。

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
Java复制代码package cn.eli.vue.entity;
//import lombok.Data;
/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/
public class User {

private int id;

private String username;

private String password;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}
}

mapper层和service层开发

mapper层和service层的目录逻辑如下:

mapper层

  1. UserMapper.java(用户接口)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Java复制代码package cn.eli.vue.mapper;


import cn.eli.vue.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
//import org.springframework.stereotype.Component;
//import org.springframework.stereotype.Repository;

/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/

@Mapper
public interface UserMapper {
public User Login(@Param("username") String username, @Param("password") String password);
//public User Login(String username,String password);
}
  1. UserMapper.xml(用户操作数据库的sql)
1
2
3
4
5
6
7
8
9
10
11
Java复制代码<?xml version="1.0" encoding="UTF-8"?>
<!-- @author: 实验室-->
<!-- @Date: 2021-1-15 11:33:26-->
<!-- @Description:-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="cn.eli.vue.mapper.UserMapper">
<select id="Login" resultType="cn.eli.vue.entity.User">
select * from userlink where username = #{username} and password = #{password}
</select>
</mapper>

service层

  1. service层的接口:UserService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Java复制代码package cn.eli.vue.service;

import cn.eli.vue.entity.User;


/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/

public interface UserService {
public User Login(String username,String password);
}
  1. 对service层接口的实现:UserServiceImpl.java
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
Java复制代码package cn.eli.vue.service.impl;

import cn.eli.vue.entity.User;
import cn.eli.vue.mapper.UserMapper;
import cn.eli.vue.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/

@Service("UserService")
public class UserServiceImpl implements UserService{
@Autowired
UserMapper userMapper;

@Override
public User Login(String username,String password){

return userMapper.Login(username,password);
}

}

controller层业务逻辑开发

controller层目录逻辑:

LoginController.java

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
Java复制代码package cn.eli.vue.controller;

import cn.eli.vue.api.CommonResult;
import cn.eli.vue.entity.User;
import cn.eli.vue.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/
@ResponseBody
@RestController
public class LoginController {
@Autowired
UserService userService;

@RequestMapping(value = "/admin/login")
public CommonResult login(@RequestBody User user) {

//String username= request.getParameter("username");
//String password= request.getParameter("password");
String username=user.getUsername();
String password=user.getPassword();

//输出测试
System.out.println("11111111");
System.out.println(username);
System.out.println(password);

user=userService.Login(username,password);
if(user!=null){
return CommonResult.success("admin");
}
else{
return CommonResult.validateFailed("1");
}
}
}

修改VueLoginJavaApplication

目录逻辑:

VueLoginJavaApplication.java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Java复制代码package cn.eli.vue;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author: 实验室
* @Date: 2021-1-15 11:33:26
* @Description:
*/
@SpringBootApplication
@MapperScan("cn.eli.vue.mapper")
public class VueLoginJavaApplication {

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

}

项目总体配置application.yml编写

项目总体配置目录逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
Java复制代码server:
port: 8088

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# url: jdbc:mysql://localhost:3306/denglu_sp?serverTimezone=GMT%2B8
url: jdbc:mysql://localhost:3306/denglu_sp?serverTimezone=UTC
username: root
password: wcq980220

#热部署生效
spring.devtools.restart.enabled: true

postman进行后端的数据POST测试

先用postman进行我们后端的测试

maven中遇到的坑,详解(欢迎向我投稿,我会在这里继续做补充)

首先说明,这是我自己在整理这个项目的时候遇到的一些小问题,很简单,大佬勿喷!

  1. maven中的依赖(dependency)会出现红色波浪线,怎么办?

(1) 注释掉pom.xml的所有依赖(即为dependencies标签中的所有东西)

(2) 点击Reload All Maven Projects(那个“循环”的图标)

(3) 此时你会发现依赖文件夹直接消失了,说明注释成功;

(4) 这个时候,再把注释取消掉,点击Reload All Maven Projects(那个“循环”的图标),让依赖重新导入一遍就可以了

  1. 用maven打包完成的target文件总是运行报错,检查语法是正确的,怎么办?

(1) 检查mapper里面的xml有没有

(2) 检查application.yml有没有

(3) 解决措施:在pom.xml里面要求打包的时候强制带上后缀为yml和xml文件(这个我上面的源代码已经带上了,大家可以自己去看)

本地JavaWeb应用拉起,项目运行(视频录制编写!!)

云服务器上线本机JavaWeb应用

基于Linux的CentOS环境配置

拉起redis,nginx,mysql

部署jar和前端页面

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%