学生管理系统的登录模块
项目创建
同样的spring创建,勾选相应内容
1. Spring Web
- 用途:Spring Web 是用于构建基于HTTP的Web应用程序的基础依赖。它支持MVC架构,用于处理请求、路由、返回视图或数据。
- 作用:
- 处理登录和注册功能的API请求。
- 通过Spring MVC支持Controller、路由、处理POST/GET请求等。
- 为前端页面(Thymeleaf)提供数据,并通过Controller返回页面。
2. Spring Session
- 用途:Spring Session用于管理和存储用户的会话信息,支持Session存储在多个地方(如Redis、数据库、内存等)。
- 作用:
- 解决Session管理问题,特别是在分布式应用中,可以确保多个服务器节点之间共享会话。
- 将用户的登录信息存储在Session中,实现用户状态保持。
- 支持将Session存储到Redis中,与Spring Data Redis一起使用。
3. MyBatis Framework
- 用途:MyBatis是一个持久层框架,简化了Java对象和数据库之间的映射,支持自动生成SQL查询、插入、更新和删除等操作。
- 作用:
- 自动生成数据库访问层代码,通过Mapper接口与数据库交互。
- 将数据库中的用户信息(如用户名、密码、手机号)与Java对象进行映射,便于用户信息的管理和验证。
- 提供了自定义SQL的能力,适合于复杂的数据库查询。
4. Spring Data Redis (Access+Driver)
- 用途:Spring Data Redis是Spring提供的对Redis数据库的支持,它简化了与Redis数据库交互的工作。
- 作用:
- 用于将会话信息存储在Redis中,实现分布式环境下的会话共享。
- 支持在用户登录后,将用户信息存储到Redis,以便在整个分布式系统中共享Session。
- 通过Redis进行验证码存储、用户信息的缓存等操作,提升系统性能。
5. Lombok
- 用途:Lombok是一个Java库,旨在简化Java代码,自动生成
getter
、setter
、equals
、hashCode
、toString
等方法,减少样板代码。 - 作用:
- 通过注解自动生成Java实体类中的
getter
和setter
方法,减少手写代码的负担。 - 常用于
User
类等实体类的开发,简化代码的编写和维护。
- 通过注解自动生成Java实体类中的
6. Thymeleaf
- 用途:Thymeleaf是一个现代的服务器端模板引擎,允许在HTML页面中嵌入动态内容,并与Spring MVC集成,用于生成Web页面。
- 作用:
- 负责前端页面的渲染,可以通过简单的HTML模板生成登录、注册等表单页面。
- 与Spring MVC无缝集成,支持动态数据渲染。
- 简化前后端交互,避免了额外的JavaScript框架,如Vue或React,适合快速构建较简单的Web应用程序。
依赖整体解析:
- Spring Web:用于提供Web API和页面渲染。
- Spring Session:用于Session管理,确保用户登录状态可以在多个请求中保持。
- MyBatis Framework:用于与数据库交互,特别是持久化用户信息(如用户名、手机号、密码等)。
- Spring Data Redis:将Session存储到Redis中,支持分布式环境下的会话共享和高效数据缓存。
- Lombok:简化实体类开发,减少手写代码。
- Thymeleaf:负责前端HTML页面的动态渲染,特别是登录和注册表单的生成。
数据库连接与创建
mybatis-x生成结构
选好两个表,放好目录位置
完成后导入mybatis-plus依赖
加入后刷新maven,报错解决
<!--mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.7</version> <!-- 请根据实际版本修改 -->
</dependency>
导入knife4j
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
添加配置
# Knife4j API文档的基本配置
knife4j.enable=true
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
添加配置类
package com.example.xbb.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
@Configuration
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.xbb")) // 替换为你的包路径
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("API 文档")
.description("登录功能测试接口文档")
.version("1.0")
.build();
}
}
添加数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/student_manage?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
结构修改
为实现效果,选用工厂模式和测略模式,在service包下添加包
为启动类添加包扫描位置
@MapperScan(“com.example.xbb.mapper”) // 指定Mapper接口所在的包
创建策略接口和策略实现类
代码
工厂
package com.example.xbb.service.strategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class LoginStrategyFactory {
@Autowired
private UsernamePasswordLoginStrategy usernamePasswordLoginStrategy;
@Autowired
private PhoneCodeLoginStrategy phoneCodeLoginStrategy;
public LoginStrategy getStrategy(String type) {
if ("username".equals(type)) {
return usernamePasswordLoginStrategy;
} else if ("phone".equals(type)) {
return phoneCodeLoginStrategy;
}
throw new IllegalArgumentException("Unknown login type: " + type);
}
}
两种策略
package com.example.xbb.service.strategy;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.xbb.domain.User;
import com.example.xbb.domain.VerificationCode;
import com.example.xbb.mapper.UserMapper;
import com.example.xbb.mapper.VerificationCodeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PhoneCodeLoginStrategy implements LoginStrategy {
@Autowired
private UserMapper userMapper;
@Override
public boolean login(String phoneNumber, String password) {
// 使用 QueryWrapper 来构建查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("phone_number", phoneNumber); // 根据用户名查询
User user = userMapper.selectOne(queryWrapper); // 使用MyBatis-Plus的selectOne方法
return user != null && user.getPassword().equals(password);
}
}
package com.example.xbb.service.strategy;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.xbb.domain.User;
import com.example.xbb.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UsernamePasswordLoginStrategy implements LoginStrategy {
@Autowired
private UserMapper userMapper;
@Override
public boolean login(String username, String password) {
// 使用 QueryWrapper 来构建查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username); // 根据用户名查询
User user = userMapper.selectOne(queryWrapper); // 使用MyBatis-Plus的selectOne方法
return user != null && user.getPassword().equals(password);
}
}
配置controller完成接口
启动接口文档测试
访问http://localhost:8080/doc.html(笔者80占用,配置成了81)
数据库捏了假数据,进行测试
设计模式实现总结
集成详解
1. 策略模式 (Strategy Pattern)
定义:
策略模式定义了多个算法(或策略),将每个算法封装在独立的类中,使得它们可以互换使用。客户端可以在运行时选择使用哪种策略,而不需要修改具体的逻辑。
在项目中的应用:
登录功能的不同方式(如通过用户名密码或手机号密码登录)是不同的策略,通过策略模式将这些不同的登录方式进行解耦。
结构:
- 策略接口 (
LoginStrategy
):定义了所有策略的通用方法——login(String identifier, String credential)
,代表一种通用的登录行为。 - 具体策略实现类:
UsernamePasswordLoginStrategy
:实现了根据用户名和密码进行登录的逻辑。PhoneCodeLoginStrategy
:实现了根据手机号和密码进行登录的逻辑。
代码示例:
public interface LoginStrategy {
boolean login(String identifier, String credential);
}
@Service
public class UsernamePasswordLoginStrategy implements LoginStrategy {
@Autowired
private UserMapper userMapper;
@Override
public boolean login(String username, String password) {
User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
return user != null && user.getPassword().equals(password);
}
}
@Service
public class PhoneCodeLoginStrategy implements LoginStrategy {
@Autowired
private UserMapper userMapper;
@Override
public boolean login(String phoneNumber, String password) {
User user = userMapper.selectOne(new QueryWrapper<User>().eq("phone_number", phoneNumber));
return user != null && user.getPassword().equals(password);
}
}
如何工作:
当用户登录时,应用会根据传入的登录类型(loginType
),如用户名还是手机号,选择合适的策略执行具体的登录逻辑。这种设计使得添加新的登录方式(如第三方登录)变得非常简单,只需实现新的策略类即可。
2. 工厂模式 (Factory Pattern)
定义:
工厂模式提供了一种封装对象创建逻辑的机制。客户端不直接实例化对象,而是通过工厂类来创建和获取对象,这样可以将对象的创建过程与使用过程分离。
在项目中的应用:
工厂模式用于根据登录类型(如 username
或 phone
)动态选择并返回正确的策略实现。
结构:
- 工厂类 (
LoginStrategyFactory
):根据输入的登录类型,返回对应的策略实现类。
代码示例:
@Component
public class LoginStrategyFactory {
@Autowired
private UsernamePasswordLoginStrategy usernamePasswordLoginStrategy;
@Autowired
private PhoneCodeLoginStrategy phoneCodeLoginStrategy;
public LoginStrategy getStrategy(String loginType) {
if ("username".equals(loginType)) {
return usernamePasswordLoginStrategy;
} else if ("phone".equals(loginType)) {
return phoneCodeLoginStrategy;
}
throw new IllegalArgumentException("Unknown login type: " + loginType);
}
}
如何工作:
工厂模式根据传入的 loginType
,返回具体的策略实例(如 UsernamePasswordLoginStrategy
或 PhoneCodeLoginStrategy
)。客户端无需关心如何创建这些策略,只需要通过工厂获取即可。
3. 策略模式和工厂模式的结合
在你的项目中,工厂模式和策略模式是紧密结合在一起的。工厂模式用于动态选择具体的策略,而策略模式封装了不同的登录方式的具体逻辑。
整个流程:
-
用户发起登录请求:用户通过提供登录类型(用户名或手机号)、登录标识(用户名或手机号)以及凭证(密码)发起登录请求。
-
通过工厂选择策略:
UserService
调用LoginStrategyFactory
,根据传入的登录类型,选择正确的策略类(如用户名登录策略或手机号登录策略)。 -
策略执行登录逻辑:工厂返回相应的策略类,调用策略类的
login
方法执行登录逻辑(如用户名或手机号和密码验证)。
代码示例:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private LoginStrategyFactory loginStrategyFactory;
@Override
public boolean login(String loginType, String identifier, String credential) {
// 根据 loginType 获取具体的登录策略
LoginStrategy strategy = loginStrategyFactory.getStrategy(loginType);
// 调用策略的 login 方法
return strategy.login(identifier, credential);
}
}
工厂模式与策略模式的关系:
- 策略模式提供了多种可互换的登录方式(用户名登录和手机号登录),封装了不同的登录逻辑。
- 工厂模式则帮助根据具体的登录类型,返回合适的策略实现类。
通过这种结合,整个登录逻辑变得更加灵活和可扩展。如果未来需要添加新的登录方式(如通过电子邮件或第三方服务登录),只需添加新的策略类,并在工厂中加入选择逻辑,完全不需要修改现有的代码。
apiresult支持类比前文,套壳即可
前端界面实现
创建front文件夹进行前端工程
进入front 命令行通过vue create front 创建项目![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/79786ef7164c4ca7bf8366a0d32f1f75.png)
回车创建
使用vscode编辑项目
npm install axios安装axios支持前后端通信
修改app.vue代码
<template>
<div id="app">
<h1>Login</h1>
<!-- 登录类型选项卡 -->
<div class="tab-buttons">
<button
@click="loginType = 'username'"
:class="{ active: loginType === 'username' }"
>
Username
</button>
<button
@click="loginType = 'phone'"
:class="{ active: loginType === 'phone' }"
>
Phone
</button>
</div>
<!-- 登录表单 -->
<form @submit.prevent="login" class="login-form">
<label v-if="loginType === 'username'" for="identifier">Username:</label>
<label v-if="loginType === 'phone'" for="identifier">Phone Number:</label>
<input
type="text"
v-model="identifier"
id="identifier"
placeholder="Enter your username or phone"
class="input-box"
/>
<br />
<label for="password">Password:</label>
<input
type="password"
v-model="password"
id="password"
class="input-box"
/>
<br />
<button type="submit">Login</button>
</form>
<!-- 弹窗显示消息 -->
<div v-if="message" class="message-popup">
<p>{{ message }}</p>
<button @click="closeMessage">Close</button>
</div>
<!-- 登出按钮 -->
<button @click="logout" class="logout-btn">Logout</button>
</div>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
loginType: "username", // 默认登录类型为用户名
identifier: "", // 用户名或手机号
password: "", // 密码
message: "", // 用于显示后端返回的消息
};
},
methods: {
login() {
axios
.post("http://localhost:8081/login", null, {
params: {
loginType: this.loginType,
identifier: this.identifier,
credential: this.password,
},
})
.then((response) => {
this.message = response.data.msg; // 修改为从 msg 字段获取消息
})
.catch((error) => {
this.message = error.response
? error.response.data.msg
: "Login failed";
});
},
logout() {
axios
.post("http://localhost:8081/logout")
.then((response) => {
this.message = response.data.msg; // 同样修改为从 msg 字段获取消息
})
.catch((error) => {
this.message = error.response
? error.response.data.msg
: "Logout failed";
});
},
closeMessage() {
this.message = ""; // 关闭消息弹窗
},
},
};
</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;
}
.tab-buttons {
margin-bottom: 20px;
}
button {
margin: 5px;
padding: 10px 20px;
cursor: pointer;
}
button.active {
background-color: #42b983;
color: white;
}
.login-form {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 20px;
}
.input-box {
width: 300px;
height: 40px;
font-size: 16px;
margin: 10px 0;
padding: 5px;
}
button {
margin-top: 10px;
padding: 10px 20px;
font-size: 16px;
}
.logout-btn {
margin-top: 20px;
}
/* 弹窗样式:放大并居中 */
.message-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #f8f9fa;
padding: 40px; /* 加大 padding */
border: 2px solid #ccc;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
color: #333;
z-index: 1000;
font-size: 18px; /* 增大字体 */
width: 400px; /* 宽度调整为 400px */
text-align: center;
}
.message-popup button {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
}
</style>
重启后端,启动前端
运行测试