抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前后端分离已经成为现今的主流开发模式,本篇将通过 demo 报道登记管理系统介绍如何搭建一个前后端分离的 web 应用。

1、报道登记管理系统介绍

后台技术栈:

spring + spring mvc + mybatis + mysql8.0

前台技术:

vue + vuex + element-ui

主要功能介绍

1、用户登录(包括自动登录)、退出、权限拦截

2、学生报道登记管理(列表、新增、编辑、删除、按条件查询、文件上传)

2、页面展示

  • 管理员的主页显示

  • 填报界面

  • 用户管理界面

3、前后端项目结构

目录及构图

  • 后端目录结构图:

  • 前端目录结构图

前后端分离搭建项目时的要点:

  • 前端

    前端还是使用较为经典的 vue2 项目,通过 vue-cli 搭建。

  • 后端

    搭建时,注意还是搭建 maven 的 web 项目,这样才能建立后端服务,也尝试搭建过 maven 的普通 Java 项目,但是后边发现把它放在 tomcat 服务器里边无法为前端提供服务。

4、解决前后端分离时的跨域问题

4.1、为什么会产生跨域问题

跨域问题是因为浏览器的同源策略引起的,所谓同源是指 " 协议 + 域名 + 端口 " 三者相同,即便两个不同的域名指向同一个 ip 地址,也非同源。同源策略 /SOP(Same origin policy)是一种约定,由 Netscape 公司 1995 年引入浏览器,它是浏览器最核心也最基本的安全功能,现在所有支持 JavaScript 的浏览器都会使用这个策略。如果缺少了同源策略,浏览器很容易受到 XSS、 CSFR 等攻击

4.2、如何解决跨域

4.2.1、前端解决

前端项目如果是 vue 项目,可以在 vue 的全局配置文件vue.config.js 中进行配置,将需要访问的后端地址进行代理,从安全方面考虑,推荐这样做,具体配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const define = require("./src/util/define")
// define 是自己定义的一些全局配置变量,便于管理
module.exports = {
// 去除代码规范
lintOnSave: false,
devServer: {
proxy: {
"/api": {
target: define.APIURL, // define.APIURL 就是你后端服务的地址
ws: true,
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}

4.2.2、后端解决

后端我使用的 spring 版本是 4.0 以上的,所以配置跨域很简单,只需要在相应的控制器或方法上加上注解 @CrossOrigin(origins = {"http://localhost:8080"})。

你也可以直接使用 @CrossOrgin 来进行配置,但是这样所有的访问地址都可以访问到你的服务,不安全。同时,这个注解也可以放在方法上,表示这个方法单独可以被访问

关于后端解决跨域的方式还有其他途径,比如全局配置拦截器,这里再不做细讲。

5、ssm 重要的配置文件

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
<?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.cxsw</groupId>
<artifactId>rmsService</artifactId>
<version>1.0</version>
<packaging>war</packaging>

<name>rmsService</name>
<!--FIXME change it to the project's website-->
<url>http://localhost:3000</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>

<dependencies>
<!-- 测试相关 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.22</version>
</dependency>
<!-- spring 相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.22</version>
</dependency>
<!-- 事务相关 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
<!-- 数据层相关 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<!-- 实体代码生成工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- 整合 mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.3</version>
</dependency>
<!-- 文件上传相关 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>3000</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>

ApplicationContext.xml (spring 核心配置文件)

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
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!-- 组建扫描 -->
<context:component-scan base-package="com.cxsw.rms">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

<!-- 配置数据源 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${druid.jdbc.driver}"/>
<property name="url" value="${druid.jdbc.url}"/>
<property name="username" value="${druid.jdbc.username}"/>
<property name="password" value="${druid.jdbc.password}"/>
</bean>

<!-- 整合 mybtis -->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
<property name="mapperLocations" value="classpath:/mappers/*.xml"/>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactoryBean"/>
</bean>
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.cxsw.rms.repository"/>
</bean>
<!-- 配置声明式事务控制 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="insert*"/>
<tx:method name="delete*"/>
<tx:method name="update*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.cxsw.rms.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>
</aop:config>

</beans>

SpringMVC.xml (springMVC 的核心配置文件)

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
<?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:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

<!-- 配置组建扫描 -->
<context:component-scan base-package="com.cxsw.rms.controller" />
<!-- 配置注解驱动 -->
<mvc:annotation-driven />

<bean id="dateConverter" class="com.cxsw.rms.convert.DateConverter" />

<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
<property name="converters" ref="dateConverter" />

</bean>
<!-- 时间转换驱动 -->
<mvc:annotation-driven conversion-service="conversionService"/>

<!-- 配置文件上传解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="52128800" />
<property name="maxUploadSizePerFile" value="52128800"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>

<mvc:default-servlet-handler />

</beans>

Mybatis 配置文件(ssm 集成之后可以不需要此配置文件,个人习惯而已)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<!-- 替换全限定名 -->
<typeAliases>
<typeAlias type="com.cxsw.rms.entity.User" alias="user"/>
<typeAlias type="com.cxsw.rms.entity.Student" alias="student"/>
</typeAliases>

</configuration>

数据库的配置文件

1
2
3
4
druid.jdbc.driver=com.mysql.cj.jdbc.Driver
druid.jdbc.url=jdbc:mysql://localhost:3306/rms?useUnicode=true&characterEncoding=utf-8
druid.jdbc.username=root
druid.jdbc.password=wb7446032001

Mapper 文件(示例)

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
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.cxsw.rms.repository.StudentMapper">

<resultMap id="studentMap" type="student">
<id property="sid" column="SID" />
<result property="name" column="NAME" />
<result property="nation" column="NATION" />
<result property="idCard" column="ID_CARD" />
<result property="region" column="REGION" />
<result property="education" column="EDUCATION" />
<result property="school" column="SCHOOL" />
<result property="major" column="MAJOR" />
<result property="educationType" column="EDUCATION_TYPE" />
<result property="email" column="EMAIL" />
<result property="phone" column="PHONE" />
<result property="employeeUnit" column="EMPLOYEE_UNIT" />
<result property="registerBook" column="REGISTER_BOOK" />
<result property="diploma" column="DIPLOMA" />
<result property="creatTime" column="CREAT_TIME" />
</resultMap>

<insert id="insertStudent" parameterType="student">
INSERT INTO STUDENTS (`SID`, `NAME`, NATION, ID_CARD, REGION, EDUCATION, SCHOOL, MAJOR, EDUCATION_TYPE,
EMAIL, PHONE, EMPLOYEE_UNIT, REGISTER_BOOK, DIPLOMA, CREAT_TIME) VALUE
(
#{sid}, #{name}, #{nation}, #{idCard}, #{region}, #{education}, #{school}, #{major},
#{educationType}, #{email}, #{phone}, #{employeeUnit}, #{registerBook}, #{diploma},
#{creatTime}
)
</insert>

</mapper>

6、后端三层架构示例

持久层和实体类

映射数据库字段的实体个人建议使用 Lombok 代码生成器,这样你在修改字段的时候就不用来回手动生成 get set tostring …… 等方法了。

用法如下

  • 在你的依赖文件中导入 Lombok 的依赖坐标。

1
2
3
4
5
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
  • 在你 idea 中的插件商店里边下载 Lombok 插件,不然代码生成不起作用,切记!!!

  • 在你的实体类里边配置注解,注解具体解释如下:

通过上图就可以发现,在单独写了实体字段的情况下,通过注解就可以自动生成代码。

持久层的接口:这里以新增数据的某个接口示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 信息登记数据层
* @author zhangsan
* @date 2022-08-25
*/
public interface StudentMapper {

/**
* 新增信息登记
* @param student 信息登记对象
* @return 0 代表新增失败 反之成功
*/
int insertStudent(Student student);
}

对应的 mapper(由于新增字段有点多,没有使用 mybatis 的注解来写 sql)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.cxsw.rms.repository.StudentMapper">

<!-- 这里的 student 是我在 mybtais 中配置的别名 -->
<insert id="insertStudent" parameterType="student">
INSERT INTO STUDENTS (`SID`, `NAME`, NATION, ID_CARD, REGION, EDUCATION, SCHOOL, MAJOR, EDUCATION_TYPE,
EMAIL, PHONE, EMPLOYEE_UNIT, REGISTER_BOOK, DIPLOMA, CREAT_TIME) VALUE
(
#{sid}, #{name}, #{nation}, #{idCard}, #{region}, #{education}, #{school}, #{major},
#{educationType}, #{email}, #{phone}, #{employeeUnit}, #{registerBook}, #{diploma},
#{creatTime}
)
</insert>

</mapper>

业务层(由于业务不是很复杂,就是简单的数据处理,所以业务层就是调用持久层的接口返回数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 信息填报接口业务逻辑层
*@author zhangsan
*@date 2022-08-26
*/
public interface StudentService {

/**
* 新增信息登记
* @param student 信息登记对象
* @return 0 代表新增失败 反之成功
*/
String insertStudent(Student student);
}

实现类:

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
/**
* 信息填报业务层实现
* @author zhangsan
* @date 2022-08-26
*/
@Service
public class StudentServiceImpl implements StudentService {

@Resource
private StudentMapper studentMapper;

/**
* 新增信息登记
* @param student 信息登记对象
* @return 0 代表新增失败 反之成功
*/
@Override
public String insertStudent(Student student) {

Date date = new Date(new java.util.Date(System.currentTimeMillis()).getTime());
student.setCreatTime(date);
studentMapper.insertStudent(student);
List<Student> students = studentMapper.listStudents(student.getName(), "");
User user = new User();
user.setUsername(student.getName());
user.setSid(students.get(0).getSid());
int res = userMapper.updateUserForeignKey(user);
if(res == 0){
return " 新增失败 ";
}else{
return " 新增成功 ";
}
}
}

控制层

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
/**
* 信息填报接口
* @author zhangsan
* date 2022-08-26
*/
@RestController
@RequestMapping(value = "/student")
public class StudentController {

@Resource
private StudentService studentService;

/**
* 新增信息填报接口
* @param student 学生对象
* @return result 对象
*/
@PostMapping
public Result createStudentList(@RequestBody Student student){

if(student == null){
return new Result(500, " 上报信息不全 ", null);
}else{
String msg = studentService.insertStudent(student);
return new Result(200, " 操作成功 ", msg);
}

}

}

✅ 😁这就完成后端的一个接口了,紧接着测试完就可以将接口交给前端了,在这里,个人建议写接口写类的时候一定要写好 javadoc 文档注释 ,这样方便后来者查看,我写代码的时候一定会这样想: 代码是写给别人看的,不是写给自己看的,所以一定要简洁明了

7、前端使用接口

✅ 前端我使用的是 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
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
import axios from "axios";
// message 是自己对 element-ui 总的消息提示框进行的封装,防止其重复弹出
import { message } from "@/util/message"

// 拦截响应
axios.interceptors.response.use(
response => {
if (!response?.data) return;
const res = response.data;
if (res.code !== 200) {
message({
message: res.msg || ' 请求出错,请重试 ',
type: "error",
duration: 1500,
onClose: () => {
// 消息框退出时需要处理的逻辑
}
})
} else {
return res;
}
},
error => {
message({
message: " 请求出错,请重试 ",
type: "error",
duration: 1500
})
// 返回错误
return Promise.reject(error)
}
)

// 封装请求
const request = async ({ url, method, data, headers }) => {

const type = method.toLocaleUpperCase();

if (type === 'GET') {
return await new Promise((res, rej) => {
getRequest(url, data, res, rej)
})
} else if (type === 'POST') {
return await new Promise((res, rej) => {
postRequest(url, data, headers, res, rej)
})
} else if (type === 'PUT') {
return await new Promise((res, rej) => {
putReqeust(url, data, res, rej)
})
} else {
return await new Promise((res, rej) => {
deleteRequest(url, res, rej)
})
}
}
// get 请求
const getRequest = (url, datas, res, rej) => {
axios.get(url, { params: datas }).then((data) => {
res(data)
}).catch((error) => {
rej(error)
})
}

// put 请求
const putReqeust = (url, datas, res, rej) => {
axios.put(url, datas).then((data) => {
res(data)
}).catch((error) => {
rej(error)
})
}
// delete 请求
const deleteRequest = (url, res, rej) => {
axios.delete(url).then((data) => {
res(data)
}).catch((error) => {
rej(error)
})
}
// post 请求
const postRequest = (url, datas, headers, res, rej) => {
axios.post(url, datas, headers).then((data) => {
res(data)
}).catch((error) => {
rej(error)
})
}

export default request;

message 的封装如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Message } from 'element-ui'

let messageInstance = null;
// 防止重复弹出消息提示
const resetMessage = (options) => {
if (messageInstance) {
messageInstance.close()
}
messageInstance = Message(options)
};
let tipType = ["error", "info", "success", "warning"];
tipType.forEach(type => {
resetMessage[type] = options => {
if (typeof options === "string") {
options = {
message: options
}
}
options.type = type
return resetMessage(options)
}
})
export const message = resetMessage

在 src 下构建目录 api 用于统一管理接口:

1
2
3
4
5
6
7
8
9
10
import request from "@/util/request";

// * 新增信息登记
export const addStudnetMsg = (data) => {
return request({
url: "/api/student",
method: "POST",
data
})
}

在相应的页面中调用接口:

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
// 导入 api
import { updateStudentMsg, addStudnetMsg } from "@/api/student"

// 表单提交方法
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {

// 根据你实际的页面 调用接口
const formMethod = this.ruleForm.sid ? updateStudentMsg : addStudnetMsg;
this.ruleForm.region = this.ruleForm.region[0] + "-" + this.ruleForm.region[1] + "-" + this.ruleForm.region[2]
formMethod(this.ruleForm).then((data) => {
if (data?.code === 200) {
message({
type: "success",
message: data.msg,
duration: 1500,
onClose: () => {
this.ruleForm.registerBook = ''
this.ruleForm.diploma = ''
this.$router.push("/");
// 更新用户信息
if (!this.ruleForm.sid) {
let params = { username: sessionUtils.get('token').username };
listUsers(params).then((data) => {
if (data?.code === 200) {
data.data[0].password = "";
sessionUtils.set("token", data.data[0], 1000 * 60 * 60 * 6)
// 将数据存储到 vuex 中
this.$store.dispatch("userInfo/getUserInfo", data.data[0]);
}
})
}
}
})
}
})

} else {
return false;
}
});

😜至此,一个接口通了。

8、源码地址

前端 gitee 地址:https://gitee.com/wenjingxin/rms.git

后端 gitee 地址:https://gitee.com/wenjingxin/ssm.git

😁 分享不易,希望多多支持 🤝

评论区:

分享你的观点