体系结构课程实验

在这里插入图片描述

学生管理系统的登录模块

在这里插入图片描述

项目创建

同样的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代码,自动生成gettersetterequalshashCodetoString等方法,减少样板代码。
  • 作用
    • 通过注解自动生成Java实体类中的gettersetter方法,减少手写代码的负担。
    • 常用于User类等实体类的开发,简化代码的编写和维护。

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)

定义:

工厂模式提供了一种封装对象创建逻辑的机制。客户端不直接实例化对象,而是通过工厂类来创建和获取对象,这样可以将对象的创建过程与使用过程分离。

在项目中的应用:

工厂模式用于根据登录类型(如 usernamephone)动态选择并返回正确的策略实现。

结构:
  • 工厂类 (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,返回具体的策略实例(如 UsernamePasswordLoginStrategyPhoneCodeLoginStrategy)。客户端无需关心如何创建这些策略,只需要通过工厂获取即可。

3. 策略模式和工厂模式的结合

在你的项目中,工厂模式策略模式是紧密结合在一起的。工厂模式用于动态选择具体的策略,而策略模式封装了不同的登录方式的具体逻辑。

整个流程:
  1. 用户发起登录请求:用户通过提供登录类型(用户名或手机号)、登录标识(用户名或手机号)以及凭证(密码)发起登录请求。

  2. 通过工厂选择策略UserService 调用 LoginStrategyFactory,根据传入的登录类型,选择正确的策略类(如用户名登录策略或手机号登录策略)。

  3. 策略执行登录逻辑:工厂返回相应的策略类,调用策略类的 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 创建项目在这里插入图片描述

回车创建

使用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>


重启后端,启动前端

在这里插入图片描述

运行测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值