vue_ssm_project_test

家里蹲

小项目练习

介绍

SSM + Vue 前后端分离的一个知识点整合,简单的对登录业务进行梳理

项目环境

Windows 10/Jdk 11/Tomcat 9.0/Maven 3.6/Mysql 8.0/Redis 7.0

所用技术

Spring/Spring Mvc/Mybatis/Vue/Element Plus/Axios

主要模块

登录的处理以及信息的CRUD

项目意义

个人所学知识点整合

介绍一下项目搭建过程

前端搭建

  • Vue Cli

vue的一个脚手架 一键搭建前端项目框架

1
2
3
4
5
6
7
8
9
10
{
"dependencies": {
"axios": "^0.27.2",
"core-js": "^3.8.3",
"element-plus": "^2.2.12",
"vue": "^3.2.13",
"vue-router": "4",
"vuex": "^4.0.2"
},
}
  • vue-router

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
// 导入用来创建路由和确定路由模式的两个方法
import store from '@/store';
import storage from '@/util/storage';
import {
createRouter,
createWebHistory
} from 'vue-router'

/**
* 定义路由信息
*
*/
const routes = [
{
path: '/',
component: () => import('@/components/login/LoginShow.vue'),
},
{
name: 'login',
path: '/login',
component: () => import('@/components/login/LoginShow.vue'),
},
{
name: 'main',
path: '/main',
component: () => import('@/components/main/IndexShow.vue'),
children: [
{
name: 'user',
path: '/user',
component: () => import('@/components/system/user/userShow.vue'),
}
]
}
]

// 创建路由实例并传递 `routes` 配置
// 我们在这里使用 html5 的路由模式,url中不带有#,部署项目的时候需要注意。
const router = createRouter({
history: createWebHistory(),
routes,
})
// 全局的路由守卫 会在每次路由跳转的时候执行 to:你要去哪个路由 from:你从哪个路由来
router.beforeEach((to) => {
// console.log(to)
//1、如果去的是登陆页面,就放行
if (to.name === 'login') {
return true
}

if (!store.getters.isLogin) {
if (!storage.getSessionObject("loginUser")) {
router.push({ name: 'login' })
} else {
store.dispatch("RECOVERY_USER");
store.dispatch("GET_INFO");
}
}
// 3、没有登陆就跳转到登陆页面
return true
})

// 讲路由实例导出
export default router
  • vuex

vuex存储前端数据

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
import { login, logout,getInfo } from '@/api/user';

import storage from '@/util/storage.js';

const user = {
state: {
username: "",
nickname: "",
token: "",
roles: [],
permissions: []
},
getters: {

isLogin(state){
return state.username !== '' && state.token !== '';
},
permissions(state) {
return state.permissions;
},
roles(state) {
return state.roles;
},

},
mutations: {

SAVE_USERNAME(state, username) {
state.username = username
},

SAVE_NICKNAME(state, nickname) {
state.nickname = nickname;
},

SAVE_TOKEN(state, token) {
state.token = token;
},
SAVE_ROLES(state, roles) {
state.roles = roles;
},
SAVE_PERMISSIONS(state, permissions) {
state.permissions = permissions;
}
},
actions: {

LOGIN({ commit }, user) {

return new Promise(function (resolve) {
login(user).then(res => {
commit("SAVE_USERNAME", res.data.ydlUser.userName);
commit("SAVE_NICKNAME", res.data.ydlUser.nickName);
commit("SAVE_TOKEN", res.data.token);
storage.saveSessionObject("loginUser", res.data);
resolve(res);
})
})
},
LOGOUT({ commit }) {

return new Promise(function (resolve) {
logout(user).then(res => {
commit("SAVE_USERNAME", '');
commit("SAVE_NICKNAME", '');
commit("SAVE_TOKEN", '');
storage.remove("loginUser");
resolve(res);
})
})
},
RECOVERY_USER({ commit }){

let loginUser = storage.getSessionObject("loginUser");
if(loginUser){
commit("SAVE_USERNAME", loginUser.ydlUser.userName);
commit("SAVE_NICKNAME", loginUser.ydlUser.nickName);
commit("SAVE_TOKEN", loginUser.token);
}
},
GET_INFO({ commit }) {
return new Promise(resolve => {
getInfo().then(res => {
commit("SAVE_ROLES", res.data.roles);
commit("SAVE_PERMISSIONS", res.data.perms);
resolve();
})
})
},


}
}

export default user
  • axios

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
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
import request from "@/api";

//登录请求
export function login(data) {
return request({
url: '/login',
method: 'post',
data: data
})
}
//登出请求
export function logout() {
return request({
url: '/logout',
method: 'get',
})
}

//获取用户信息列表请求
export function listUser(data) {
return request({
url: '/ydlUser',
method: 'get',
params: data
})
}

//获取权限请求
export function getInfo() {
return request({
url: '/ydlUser/getInfo',
method: 'get',
})
}

//根据id发送请求
export function getById(id) {
return request({
url: '/ydlUser/' + id,
method: 'get',
})
}
//删除用户信息的请求
export function deleteUser(id) {
return request({
url: '/ydlUser/' + id,
method: 'delete',
})
}
// 添加用户的请求
export function add(data) {
return request({
url: '/ydlUser',
method: 'post',
data: data
})
}
//更新用户的请求
export function update(data) {
return request({
url: '/ydlUser',
method: 'put',
data: data
})
}

项目结构

SSM+vue前后端分离_前端项目结构

后端搭建

  • 使用Maven脚手架搭建

搭建后端框架结构

  • 配置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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.dream.xiaobo</groupId>
<artifactId>ssm-pro</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>system-admin</module>
</modules>

<name>ruoyi</name>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<javax.servlet.version>4.0.1</javax.servlet.version>
<spring.version>5.2.18.RELEASE</spring.version>
<spring-data-commons.version>2.6.0</spring-data-commons.version>
<aspectjweaver.version>1.9.6</aspectjweaver.version>
<lombok.version>1.18.22</lombok.version>
<jackson.version>2.13.1</jackson.version>
<validation-api.version>2.0.1.Final</validation-api.version>
<hibernate-validator>6.0.9.Final</hibernate-validator>
<logback-classic.version>1.2.6</logback-classic.version>
<commons-fileupload.version>1.3.3</commons-fileupload.version>
<druid.version>1.2.8</druid.version>
<mysql-connector-java.version>8.0.26</mysql-connector-java.version>
<mybatis.version>3.5.5</mybatis.version>
<mybatis-spring.version>2.0.6</mybatis-spring.version>
<redis.version>4.0.1</redis.version>
<commons-lang3.version>3.1</commons-lang3.version>
</properties>

<!--依赖管理-->
<dependencyManagement>
<dependencies>
<!--servlet-api-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${javax.servlet.version}</version>
</dependency>

<!--spring-webmvc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>${spring-data-commons.version}</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>

<!--jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>

<!--数据校验-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>${validation-api.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator}</version>
</dependency>

<!--logback日志-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback-classic.version}</version>
</dependency>


<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>

<!-- 数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- 数据区驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>

<!-- mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>

<!-- 整合spring和mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>

<!-- redis连接驱动 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${redis.version}</version>
</dependency>

<!-- User-Agent,获得浏览器版本以及操作系统等信息-->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>

</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
</build>

<!-- <repositories>-->
<!-- <repository>-->
<!-- <id>public</id>-->
<!-- <name>aliyun nexus</name>-->
<!-- <url>https://maven.aliyun.com/repository/public</url>-->
<!-- <releases>-->
<!-- <enabled>true</enabled>-->
<!-- </releases>-->
<!-- </repository>-->
<!-- </repositories>-->

</project>
  • 配置web.xml

web项目所需的设置

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<!--容器初始化时,负责启动spring容器-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</context-param>

<!--注册DispatcherServlet,这是springmvc的核心,就是个servlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:application.xml</param-value>
</init-param>
<!--加载时先启动-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--/ 匹配所有的请求;(不包括.jsp)-->
<!--/* 匹配所有的请求;(包括.jsp)-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<filter>

<filter-name>encodingFilter</filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>

<init-param>

<param-name>encoding</param-name>

<param-value>UTF-8</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>encodingFilter</filter-name>

<url-pattern>/*</url-pattern>

</filter-mapping>

<filter>
<filter-name>xssFilter</filter-name>
<filter-class>com.dream.xiaobo.filter.XssFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>xssFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

</web-app>
  • 配置jdbc.properties数据库连接信息文件

连接数据库所需的设置

1
2
3
4
user=root
password=xiaobo
url=jdbc:mysql://localhost:3306/db_ssm-pro?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
driverName=com.mysql.cj.jdbc.Driver
  • 配置application.xml

配置spring/spring mvc/mybatis所需的设置

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd
http://mybatis.org/schema/mybatis-spring
https://mybatis.org/schema/mybatis-spring.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 自动扫包 -->
<context:component-scan base-package="com.dream.xiaobo"/>

<!-- 开启注解-->
<aop:aspectj-autoproxy />

<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>

<!-- 处理映射器 -->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- 处理器适配器 -->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- 视图解析器,本项目可以不使用视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/page/"/>
<!-- 后缀 -->
<property name="suffix" value=".jsp"/>
<property name="order" value="10"/>
</bean>

<bean id="customObjectMapper" class="com.dream.xiaobo.configuration.CustomObjectMapper"/>

<!--全局异常处理-->
<!-- <bean class="com.ydlclass.handler.GlobalExceptionResolver"/>-->

<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<!-- 自定义Jackson的objectMapper -->
<property name="objectMapper" ref="customObjectMapper"/>
<!-- 自定义Jackson的objectMapper -->
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>


<!-- 拦截器配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--
mvc:mapping:拦截的路径
/**:是指所有文件夹及其子孙文件夹
/*:是指所有文件夹,但不包含子孙文件夹
/:Web项目的根目录
-->
<mvc:mapping path="/**"/>
<!-- mvc:exclude-mapping:不拦截的路径,不拦截登录路径 -->
<mvc:exclude-mapping path="/login"/>
<!--class属性就是我们自定义的拦截器-->
<bean id="loginInterceptor" class="com.dream.xiaobo.interceptor.LoginInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean id="repeatSubmitInterceptor" class="com.dream.xiaobo.interceptor.RepeatSubmitInterceptor"/>
</mvc:interceptor>

</mvc:interceptors>

<!--处理静态资源,前后分离项目不需要处理静态资源-->


<!--扫描mapper文件-->
<!-- 让springmvc自带的注解生效 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<mybatis:scan base-package="com.dream.xiaobo.dao"/>

<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${url}"/>
<property name="driverClassName" value="${driverName}"/>
<property name="username" value="${user}"/>
<property name="password" value="${password}"/>
</bean>

<!-- 整个整合就是在围绕sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/**/*.xml"/>
<property name="configuration">
<bean class="org.apache.ibatis.session.Configuration">
<property name="mapUnderscoreToCamelCase" value="true"/>
<property name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl" />
<property name="logPrefix" value="ssm-pro_"/>
</bean>
</property>
</bean>

<!-- 注入事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

<!-- 声明式事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
<tx:method name="select*" read-only="true" propagation="SUPPORTS"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="update*" read-only="false" propagation="REQUIRED"/>
<tx:method name="delete*" read-only="false" propagation="REQUIRED"/>
<tx:method name="insert*" read-only="false" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>


<!-- redis连接池 -->
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="0.0.0.0"/>
<constructor-arg name="port" value="6379"/>
<property name="minIdle" value="20" />
<property name="maxIdle" value="30" />
<property name="maxTotal" value="200"/>
</bean>

<!-- 开启异步任务注解-->
<task:annotation-driven executor="ydl-logger"/>
<task:executor id="ydl-logger" pool-size="10-20" keep-alive="120"
rejection-policy="ABORT" queue-capacity="500"/>

</beans>
  • 配置logback.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
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志存放路径 -->
<property name="log.path" value="D://logs" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>

<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/sys-error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/sys-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>

<!-- 系统模块日志级别控制 -->
<logger name="com.dream.xiaobo" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />

<root level="info">
<appender-ref ref="console" />
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>

</configuration>
  • 创建CustomObjectMapper类来配置jackson的序列化和反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class CustomObjectMapper extends ObjectMapper {

public CustomObjectMapper() {
super();
//去掉默认的时间戳格式
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//设置为东八区
setTimeZone(TimeZone.getTimeZone("GMT+8"));
//设置日期转换yyyy-MM-dd HH:mm:ss
setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
// 设置输入:禁止把POJO中值为null的字段映射到json字符串中
configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
// 空值不序列化
setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 反序列化时,属性不存在的兼容处理
getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// 序列化枚举是以toString()来输出,默认false,即默认以name()来输出
configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
}
}

项目结构

SSM+vue前后端分离_后端项目结构

数据库设计

简单的设计了六个表、并不复杂、主要学习登录的逻辑以及整合的过程

  • table_user(用户表)

table_user

  • table_role(角色表)
    table_role

  • table_user_role(用户角色关联表)
    table_user_role

  • table_menu(菜单表)
    table_menu

  • table_role_menu(角色菜单关联表)
    table_role_menu

  • table_log(日志表)
    table_log

介绍一下项目处理问题的过程

跨域问题

有两种解决方案,一种在后端解决,一种在前端解决

后端解决方案

这种方案是前端发送请求直接到达后端服务器,所以这个时候需要在后端进行处理,我们后端需要使用一个注解来进行处理
@CrossOrigin

配置文件进行全局处理

1
2
3
<mvc:cors>
<mvc:mapping path="/*" allowed-methods="*" allowed-origins="*"/>
</mvc:cors>

前端解决方案

这种方案是前端发送请求到前端服务器,前端服务器作为一个代理,然后在通过前端服务器发送请求到后端服务器,因为服务器之间不存在跨域问题

修改vue.config.js

1
2
3
4
5
6
module.exports = {
devServer: {
port: 80,
proxy: "http://localhost:8088"
}
}

配置axios.js

1
2
3
4
5
6
7
8
9
// 创建axios实例
const request = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: 'http://localhost:80/',
// 超时
timeout: 10000,
// 设置Content-Type,规定了前后端的交互使用json
headers: {'Content-Type': 'application/json;charset=utf-8'}
})

登录的过程(只介绍主要逻辑)

前端处理

  • loginShow.vue

写出登录页面,data()进行数据双向绑定以及数据简单校验

method: 处理一些事件点击方法,方法进行相应的逻辑处理

doLogin() 前端的登录逻辑,校验user中的validate是否存在,如果存在在store中取出结果集,在对其结果集中status状态码进行校验,如果为200,则证明访问成功,成功的时候需要进行对应的权限认证相关获取,然后进行对应的路由跳转,否则访问失败

这里介绍一下store

  • state

这个就相当于Java中的定义变量

  • mutations

这个相当于Java中的set方法

  • getters

这个相当于Java中的get方法

  • actions

对外提供方法,并进行逻辑处理

  • 全局路由守护

全局的路由守卫 会在每次路由跳转的时候执行 to:你要去哪个路由 from:你从哪个路由来

我们在这进行校验,如果去的是登陆页面,就放行,在此进行校验,校验用户名和token是否为空,如果不为空则继续进行校验,校验是否拿存储对象,进行相应的处理

后端处理

登录处理

后端首先进行controller处理,用@Validated,BindingResult进行校验处理,然后进行登录业务逻辑的续写

登录的service首先根据用户名进行查询拿到对象,将其对象进行校验,进行相应的自定义异常处理,在对其密码的正确性进行校验,也进行自定义异常的处理,生成Token,根据UUID进行生成,在进行request和User-Agent的获取,根据request拿到IP,通过IP拿到该用户的各个信息,在通过拿到的信息进行JSON序列化,拿到IP地址的省市区,通过创建者模式创建登录的用户信息,根据用户名生成一个key前缀token:username:查询token:userName:下的所有的key,然后再清除之前的Key保证最新的Token,并且唯一并且存储到Redis中,并返回对象

登出处理

获取到Request和头部信息中的AUTHORIZATION,根据token: * :获取到的token进行查询,如果查询到了在清空Redis中的数据

登录拦截器

根据AUTHORIZATION拿到信息校验是否存在,如果存在,在Redis中查看是否存在Token信息,如果存在给Redis中的Token信息续命,重新设置Token过期时间,然后放行,反之全部进行拦截

RBAC管理

RBAC

RBAC(Role-Based Access Control:基于角色的访问控制)就是,用户有哪些角色,该角色有哪些权限

RBAC组成

  • 用户

每个用户都有唯一的UID识别,并被授予不同的角色

  • 角色

不同角色具有不同的权限

  • 权限

访问权限

RBAC通过定义角色的权限,并对用户授予某个角色从而来控制用户的权限,实现了用户和权限的逻辑分离

鉴权

关键SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT
u.user_id user_id,
username,
nick_name,
r.role_id,
r.role_name,
r.role_tag,
m.menu_id,
m.menu_name,
m.perms
FROM
table_user u
LEFT JOIN `table_user_role` ur ON u.user_id = ur.user_id
LEFT JOIN table_role r ON ur.role_id = r.role_id
LEFT JOIN table_role_menu rm ON r.role_id = rm.role_id
LEFT JOIN table_menu m ON rm.menu_id = m.menu_id
where u.del_flag = 0 and r.del_flag = 0 and u.user_id = #{userId}

后端鉴权

拿到登录的用户信息,根据id进行查询用户对应的权限,进行截取,拿到prems: [system:user:add,system:user:update]格式的信息,roleTags:[admin,hr,user]格式信息,然后以perm:token:prems,roles:token:prems格式以此存入到Redis中进行保存,然后自定义两个注解即可,然后通过AOP即可使用注解完成切入

日志输出

自定义一个日志注解,然后可以自定义哪个接口需要进行监听即可对其进行日志的输出,然后通过AOP进行处理,拿到相关的信息依次存入到数据库即可,但有一个要注意的点是日志不是主要的业务逻辑,所以大量的日志输出不太优化,会影响主业务,所以我们在此要进行异步处理,要开启spring的异步处理,需要在application.xml进行相应的配置,然后就是用到线程来处理,开启一个线程池,让主业务是一个线程,为日志开启另一个线程,来解决这个问题

表单重复提交

主要的表单是不允许重复提交的,所以要进行处理,这里自定义一个注解,然后设置一个拦截器拦截

xss攻击

就是在表单里直接输入标签来进行脚本攻击,vue的本身做了防御机制,很多框架都做了这个防御机制

解决xss攻击的核心方法就是处理掉那些可能产生问题的标签,或者通过转义,或者将半角的尖括号转化为全角,在或者直接干掉一些特殊标签

声明

此项目只有登录业务和用户信息的CRUD

源码自取,公众号回复ssm_vue_project_test

自己学习整理,如有侵权,通知即删

正确的开始 微小的长进 然后持续 嘿 我是小博 带你一起看我目之所及的世界……

-------------本文结束 感谢您的阅读-------------

本文标题:vue_ssm_project_test

文章作者:小博

发布时间:2022年09月08日 - 16:06

最后更新:2022年09月08日 - 16:08

原始链接:https://codexiaobo.github.io/posts/3820866932/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。