SpringBoot-Web开发

SpringBoot简介

Spring Boot 是一个流行的开源 Java 框架,它基于 Spring 框架并对其进行了扩展和优化,旨在简化 Spring 应用程序的初始化、配置和开发过程。以下列出了 Spring Boot 的主要概念和优势:

自动配置:

Spring Boot 提供了自动配置功能,可以根据项目类路径中的依赖自动配置 Spring 应用程序,减少了手动配置的工作。

starter依赖:

Spring Boot 通过 starter dependencies 简化了依赖管理。这些 starters 是一组预定义的依赖集合,包含了开发特定类型应用程序所需的所有库和依赖。

嵌入式服务器:

Spring Boot 默认使用嵌入式的 Servlet 容器(如 Tomcat、Jetty 或 Undertow),使得开发者可以直接运行生成的单个 JAR 文件作为独立的应用程序。

微服务支持:

Spring Boot 对微服务架构提供了良好的支持,可以方便地构建和管理分布式系统。
快速应用开发(RAD):
Spring Boot 的目标是提供一种快速、简洁的方式来创建独立、生产级别的基于 Spring 框架的应用程序。
优势:

简化初始化和开发过程:

Spring Boot 减少了项目的初始设置和配置工作,使得开发者能够更快地开始编写业务逻辑代码。

内置默认配置:

Spring Boot 提供了一系列默认配置,遵循“约定优于配置”的原则,避免了大量样板代码和配置文件的编写。

开箱即用的体验:

开发者只需要添加相应的 starter dependencies,就可以立即开始开发,无需处理复杂的依赖管理和配置问题。

易于部署和运行:

Spring Boot 应用程序可以打包为单个可执行的 JAR 或 WAR 文件,便于部署和运行在各种环境中。

广泛的集成:

Spring Boot 可以方便地与其他技术栈集成,包括数据库、模板引擎、NoSQL 数据库、消息队列等。
强大的开发工具支持:
Spring Boot 提供了 CLI 工具和 IDE 插件,进一步简化了项目的创建、构建和运行过程。

非功能性特性:

内置了一系列非功能性特性,如健康检查、指标监控、外部化配置、安全控制等,提高了应用程序的稳定性和可维护性。

Spring Boot Web 项目创建

1. 打开 IntelliJ IDEA:

启动 IntelliJ IDEA,如果你还没有安装,可以访问官方网站下载并安装。

2.新建项目:

在欢迎屏幕中,选择 “Create New Project”,或者在顶部菜单栏选择 “File” -> “New” -> “Project”。
在这里插入图片描述

3.选择项目类型:

在新窗口中,选择 “Spring Initializr”,然后点击 “Next”。
在这里插入图片描述

4.配置项目信息:

在 “New Project” 窗口中,填写以下信息:
Project SDK:选择你已经安装的 JDK 版本。
Project name:输入你的项目名称。
Project location:选择你的项目保存路径。

5.添加 starter dependency:

在 “Search for dependencies” 搜索框中,输入 “spring-boot-starter-web”,然后点击 “+” 或者双击搜索结果将其添加到依赖列表中。
其他依赖和设置:
根据你的项目需求,你可以添加其他starter依赖,如数据持久化(spring-boot-starter-data-jpa)、模板引擎(spring-boot-starter-thymeleaf)等。
可以调整项目的语言级别、构建工具(Maven 或 Gradle)等设置。
在这里插入图片描述

6.完成项目创建:

点击 “Next”,然后在下一个窗口中确认你的项目配置,无误后点击 “Finish”。

7.等待项目初始化:

IntelliJ IDEA 将会使用 Spring Initializr 初始化你的项目,下载所需的依赖并生成基本的项目结构。

8.查看项目结构:

初始化完成后,你可以在 “Project” 视图中看到项目的基本结构,包括 src/main/java(源代码目录)、src/main/resources(资源文件目录)等。
编写代码:
在 src/main/java 目录下,找到 com.example.yourprojectname 包下的 YourProjectNameApplication 类,这是 Spring Boot 的主类,包含了 main 方法。
在这里插入图片描述

你可以在该项目中开始编写你的 Spring Boot Web 应用程序代码。

Spring MVC 自动配置

Spring Boot 为 Spring MVC 提供了自动配置功能,这极大地简化了开发过程并减少了手动配置的工作。以下是如何为 Spring MVC 提供自动配置的解释,以及如何配置和自定义 Spring MVC 的行为

WebMvcAutoConfiguration 类的作用

WebMvcAutoConfiguration 是 Spring Boot 中的一个核心自动配置类,位于 org.springframework.boot.autoconfigure.web.servlet 包下。这个类负责配置和初始化 Spring MVC 的各种组件和特性。

  • 当项目中存在 spring-webmvc 依赖时,Spring Boot 会自动检测并应用 WebMvcAutoConfiguration。
  • 这个类包含了多个 @Configuration 子类和 @Bean 方法,用于配置视图解析器、静态资源处理、消息转换器、拦截器、格式化器等 Spring MVC 的关键组件。
自动配置的内容:
  • 视图解析器(View Resolver)
    默认使用 InternalResourceViewResolver,可以根据返回的逻辑视图名解析实际的 JSP 或 HTML 页面。
  • 静态资源处理
    配置默认的静态资源处理器,使得可以直接访问项目中的静态资源文件(如 CSS、JavaScript、图片等)。
  • 消息转换器(Message Converters)
    注册默认的消息转换器,支持将 HTTP 请求和响应体转换为 Java 对象和 JSON、XML 等格式。
  • 拦截器(Interceptors)
    可以通过实现 WebMvcConfigurer 接口并重写 addInterceptors 方法来添加自定义拦截器。
  • 格式化器(Formatters)
    注册默认的日期和其他类型格式化器,用于在控制器方法参数和返回值中进行数据绑定。
配置和自定义 Spring MVC 行为:
  • 视图解析器:
    创建自己的 ViewResolver 实现,并使用 @Bean 注解将其声明为一个 Bean。
    在 application.properties 或 application.yml 文件中配置视图解析器的相关属性。
  • 拦截器:
    创建自定义的拦截器类,实现 HandlerInterceptor 接口。
    在实现了 WebMvcConfigurer 接口的配置类中,重写 addInterceptors 方法,并将自定义拦截器添加到 InterceptorRegistry 中。
  • 消息转换器:
    创建自定义的消息转换器类,实现 HttpMessageConverter 接口。
    在实现了 WebMvcConfigurer 接口的配置类中,重写 extendMessageConverters 方法,并将自定义转换器添加到转换器列表中。
  • 格式化器:
    创建自定义的格式化器类,实现 Formatter 接口。
    在实现了 WebMvcConfigurer 接口的配置类中,重写 addFormatters 方法,并将自定义格式化器添加到 FormatterRegistry 中。

RESTful API 开发

在 Spring Boot 中开发 RESTful 风格的接口非常直观和方便。以下是如何使用 Spring Boot 开发 RESTful API 的一些关键步骤,以及如何使用 HTTP 动词、@RestController、@RequestMapping 和其他相关注解进行路由和控制器方法的配置:

HTTP 动词与资源操作的对应关系:

RESTful 风格的接口遵循以下 HTTP 动词与资源操作的一般对应关系:

  • GET:用于获取资源信息,不改变资源状态。
  • POST:用于创建新的资源。
  • PUT:用于更新整个资源。
  • PATCH:用于更新资源的部分属性。
  • DELETE:用于删除指定的资源。
使用 @RestController 和 @RequestMapping 注解:

在 Spring Boot 中,通常使用 @RestController 和 @RequestMapping 注解来定义 RESTful API 路由和控制器方法。

  • @RestController:
    这是一个组合了 @Controller 和 @ResponseBody 的特殊注解,表示该类中的所有方法都将返回直接写入 HTTP 响应体的对象。
  • @RequestMapping:
    可以在类级别或方法级别使用,用于映射 HTTP 请求到特定的处理方法。
    通过指定路径、HTTP 方法、请求参数等条件,可以灵活地控制请求的路由。
    以下是一个简单的 Spring Boot RESTful API 示例:
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
public class UserController {

    // GET /api/users
    @GetMapping
    public List<User> getAllUsers() {
        // 实现获取所有用户的功能
    }

    // GET /api/users/{id}
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        // 实现根据 ID 获取用户的功能
    }

    // POST /api/users
    @PostMapping
    public User createUser(@RequestBody User user) {
        // 实现创建新用户的功能
    }

    // PUT /api/users/{id}
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User updatedUser) {
        // 实现更新用户的功能
    }

    // DELETE /api/users/{id}
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        // 实现删除用户的功能
    }
}

在这个示例中,我们定义了一个名为 UserController 的 RESTful 控制器,它包含了处理用户资源的各种 CRUD 操作。每个方法都使用了相应的 HTTP 动词和 @RequestMapping 注解来指定请求的路由。

此外,还可以使用其他相关的注解来进一步定制和增强 RESTful API 的功能,如:

  • @PathVariable:用于从请求 URL 中提取路径变量值。
  • @RequestParam:用于从请求参数中提取值。
  • @RequestBody:用于将请求体内容绑定到方法参数对象。
  • @ResponseStatus:用于设置响应的状态码和原因短语。

异常处理

在 Spring Boot 中,异常处理是非常重要的一部分,它可以帮助我们优雅地处理程序中的错误和异常情况,并向客户端返回一致的错误响应格式。以下是如何在 Spring Boot 中实现全局和局部的异常处理机制,以及如何自定义异常处理器

全局异常处理:

全局异常处理是指为所有控制器方法统一处理异常的一种方式。在 Spring Boot 中,可以创建一个包含 @ControllerAdvice 注解的类来实现全局异常处理。

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = {Exception.class})
    public ResponseEntity<Object> handleException(Exception ex) {
        // 实现全局异常处理逻辑,例如记录异常日志、返回错误信息等
        String errorMessage = "An unexpected error occurred: " + ex.getMessage();
        return new ResponseEntity<>(errorMessage, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler(value = {CustomException.class})
    public ResponseEntity<Object> handleCustomException(CustomException ex) {
        // 实现自定义异常类型的处理逻辑
        String errorMessage = "A custom exception occurred: " + ex.getMessage();
        return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
    }
}

在这个示例中,我们创建了一个名为 GlobalExceptionHandler 的类,其中包含了两个 @ExceptionHandler 方法。第一个方法用于处理所有的异常类型,第二个方法专门处理自定义的 CustomException 类型。

当控制器方法抛出异常时,Spring Boot 会自动查找包含 @ControllerAdvice 注解的类,并调用匹配的 @ExceptionHandler 方法进行处理。

局部异常处理:

局部异常处理是指在特定的控制器方法中处理异常的一种方式。可以通过在方法内部使用 try-catch 语句或者在方法上添加 @ExceptionHandler 注解来实现局部异常处理。

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        try {
            // 实现根据 ID 获取用户的功能
            return userService.getUserById(id);
        } catch (UserNotFoundException ex) {
            // 实现局部异常处理逻辑
            String errorMessage = "User not found with ID: " + id;
            throw new CustomException(errorMessage, HttpStatus.NOT_FOUND);
        }
    }

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<Object> handleUserNotFoundException(UserNotFoundException ex) {
        // 实现局部异常处理逻辑
        String errorMessage = "User not found: " + ex.getMessage();
        return new ResponseEntity<>(errorMessage, HttpStatus.NOT_FOUND);
    }
}

在这个示例中,我们在 getUser 方法中使用了 try-catch 语句来捕获并处理 UserNotFoundException 异常。同时,我们也在 UserController 类中添加了一个针对 UserNotFoundException 的局部 @ExceptionHandler 方法。

通过以上方式,你可以根据需要在 Spring Boot 中实现全局和局部的异常处理机制,以确保程序在遇到错误和异常时能够提供一致且有用的错误响应给客户端。

安全功能

在 Spring Boot 中,安全功能是非常重要的一部分,它可以帮助我们保护应用程序免受各种安全威胁。以下是如何在 Spring Boot 中实现基本的安全配置,如 CSRF、XSS 防护和 HTTP 基础认证,以及如何集成 Spring Security 进行更复杂的权限管理和用户认证:

基本安全配置:

1.CSRF(跨站请求伪造)防护
Spring Boot 默认启用了 CSRF 防护。如果你使用的是 spring-boot-starter-security 依赖,那么 CSRF 防护是自动启用的。
如果你需要禁用 CSRF 防护,可以在 application.properties 文件中添加以下配置:

spring.security.csrf.enabled=false

2.XSS(跨站脚本攻击)防护:

  • Spring Boot 默认使用 Thymeleaf 或其他模板引擎时,会启用 XSS 防护。
  • 在 Thymeleaf 中,可以使用 th:utext 标签来防止 HTML 编码,以避免 XSS 攻击。

3.HTTP 基础认证:
要启用 HTTP 基础认证,需要在 WebSecurityConfigurerAdapter 的子类中配置 http 元素:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();
    }
}
  • 在运行应用程序时,浏览器将提示用户提供用户名和密码进行身份验证。
集成 Spring Security:

Spring Security 是一个强大的安全框架,可以用于实现更复杂的安全功能,如角色和权限管理、OAuth2 认证、JWT 签名等。

1.添加 Spring Security 依赖:
在你的 pom.xml 或 build.gradle 文件中添加 spring-boot-starter-security 依赖。
2.配置 Spring Security:
创建一个扩展自 WebSecurityConfigurerAdapter 的配置类,并覆盖其中的方法来配置 Spring Security 的行为。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

在这个示例中,我们配置了以下内容:

  • 允许所有 /api/public/** 路径的请求无需身份验证。
  • 其他所有请求都需要经过身份验证。
  • 使用 HTTP 基础认证作为认证机制。
  • 使用自定义的 UserDetailsService 和 PasswordEncoder。

3.实现 UserDetailsService 接口:
创建一个实现了 UserDetailsService 接口的类,用于从数据源加载用户信息并进行身份验证。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                AuthorityUtils.createAuthorityList(user.getRoles()));
    }
}

通过以上方式,你可以使用 Spring Boot 实现基本的安全配置,并集成 Spring Security 进行更复杂的安全管理。这包括但不限于角色和权限管理、多因素认证、访问控制列表(ACL)等高级功能。

案例分享

SpringBoot AOP实现统一日志处理

在基于Spring Boot的项目开发中,日志记录是一个非常重要的方面。为了规范和简化日志记录的过程,我们可以利用Spring Boot的AOP(面向切面编程)功能来实现统一的日志处理。

首先,我们需要创建一个切面类,并使用@Aspect和@Component注解进行标识。切面类的作用是定义切点和通知,以便在特定方法执行前后添加额外的日志逻辑。

@Aspect
@Component
public class LogAspect {
    // ...
}

接下来,我们定义一个切点,用于匹配带有@Log注解的方法,该注解用于标识需要记录日志的方法。


@Pointcut("@annotation(com.xiaoke.annotation.Log)")
public void pt() {
}

然后,我们使用@Around注解来实现环绕通知,该通知会在切点方法执行前后进行拦截,并在执行完切点方法后记录日志信息。在拦截到切点方法后,我们可以通过ProceedingJoinPoint对象获取方法的签名、注解内容、参数等信息。

@Around("pt()")
public Object log(ProceedingJoinPoint pjp) throws Throwable {
    long beginTime = System.currentTimeMillis();
    // 执行切点方法
    Object result = pjp.proceed();
    // 执行时长
    Long runTime = System.currentTimeMillis() - beginTime;
    // 记录日志
    handleLog(pjp, runTime);
    return result;
}

在handleLog方法中,我们可以进一步处理日志信息。通过ProceedingJoinPoint对象的getSignature()方法,我们可以获取方法的签名,进而获取注解内容。

private void handleLog(ProceedingJoinPoint pjp, Long runTime) {
    MethodSignature signature = (MethodSignature) pjp.getSignature();
    Method method = signature.getMethod();
    // 获取注解内容
    Log logAnnotation = method.getAnnotation(Log.class);
    // 获取模块
    String title = logAnnotation.title();
    // 获取业务类型
    BusinessType businessType = logAnnotation.businessType();
    // ...
}

除了获取注解内容外,我们还可以通过HttpServletRequest对象获取请求的方法、URL、IP地址等信息。


@Autowired
HttpServletRequest httpServletRequest;

// ...

private void handleLog(ProceedingJoinPoint pjp, Long runTime) {
    // ...
    // 请求方法
    String httpMethod = httpServletRequest.getMethod();
    // IP地址
    String ip = IPUtils.getIpAddr(httpServletRequest);
    // 请求URL
    StringBuffer requestURL = httpServletRequest.getRequestURL();
    // ...
}
最后,在handleLog方法中,我们可以根据需求对日志进行进一步处理,比如将日志存储到数据库或者输出到控制台。

private void handleLog(ProceedingJoinPoint pjp, Long runTime) {
    // ...
    // 封装日志对象
    SysLog sysLog = new SysLog(title, businessType, httpMethod, requestURL.toString(), ip, params, runTime);

    // 这里就是将日志对象输出了,你可以根据需求,把它存到数据库
    // 我这里就简单地打印出来啦!
    System.out.println(sysLog);
}

日志处理的使用用例:

@Log(title = "测试日志模块", businessType = BusinessType.OTHER)
@PostMapping("demo")
public R<TestEntity> demo(@RequestBody TestEntity testEntity) throws InterruptedException {
    return R.ok(testEntity);
}

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

在这个例子中,我们在demo方法上添加了@Log注解。其中,title参数表示日志模块的名称,businessType参数表示业务类型。通过这样的配置,我们可以在方法执行前后自动记录日志信息。

当请求到达/demo接口时,切面会拦截到该方法的执行,并根据注解的配置进行日志记录。在执行完方法后,我们可以获取到请求的方法、URL、IP地址等信息,并且可以根据@Log注解的内容进行进一步的日志处理。

完整代码:

@Aspect
@Component
public class LogAspect {

    @Autowired
    HttpServletRequest httpServletRequest;

    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 定义切面
     */
    @Pointcut("@annotation(com.xiaoke.annotation.Log)")
    public void pt() {
    }

    /**
     * 环绕切点
     * @param pjp join point for advice
     * @return result
     * @throws Throwable throws IllegalArgumentException
     */
    @Around("pt()")
    public Object log(ProceedingJoinPoint pjp) throws Throwable {
        long beginTime = System.currentTimeMillis();
        // 执行切点方法
        Object result = pjp.proceed();
        // 执行时长
        Long runTime = System.currentTimeMillis() - beginTime;
        // 记录日志
        handleLog(pjp,runTime);
        return result;
    }


    private void handleLog(ProceedingJoinPoint pjp,Long runTime) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        // 获取注解内容
        Log logAnnotation = method.getAnnotation(Log.class);
        // 获取模块
        String title = logAnnotation.title();
        // 获取业务类型
        BusinessType businessType = logAnnotation.businessType();

        Object[] args = pjp.getArgs();
        // 参数
        String params = JSON.toJSONString(args);
        // 请求方法
        String httpMenthod = httpServletRequest.getMethod();
        // ip
        String ip = IPUtils.getIpAddr(httpServletRequest);
        // 请求url
        StringBuffer requestURL = httpServletRequest.getRequestURL();
        // 封装日志对象
        SysLog sysLog = new SysLog(title, businessType, httpMenthod, requestURL.toString(), ip, params, runTime);

        // 这里就是将日志对象输出了,你可以根据需求,把它存到数据库
        // 我这里就简单地打印出来啦!
        System.out.println(sysLog);
    }
}

参考视频:哔哩哔哩

SpringBoot实现登录校验验证码

在开发Web应用程序时,用户登录功能是一个常见的需求。为了增加安全性,我们可以引入验证码来进行登录校验。本文将介绍如何使用Spring Boot框架实现登录校验验证码的功能。

首先引入依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--  参数验证包    -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <!-- 验证码依赖包  -->
        <dependency>
            <groupId>com.github.whvcse</groupId>
            <artifactId>easy-captcha</artifactId>
            <version>1.6.2</version>
        </dependency>
        <!-- SpringBoot Boot Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.34</version>
        </dependency>
    </dependencies>

编写登录接口DTO,这里用了DTO进行数据验证,具体参考:使用 Spring Boot 和 DTO 进行数据验证

/**
 * @Author xiaoke
 * @Description 登录接口请求参数
 * @Date 2023/7/24 13:13
 */
public class LoginDto {
    @NotBlank(message = "用户名不能为空")
    private String username;
    @NotBlank(message = "密码不能为空")
    private String password;
    @NotBlank(message = "验证码key不能为空")
    private String key;
    @NotBlank(message = "验证码结果不能为空")
    private String code;

    public LoginDto() {
    }

    public LoginDto(String username, String password, String key, String code) {
        this.username = username;
        this.password = password;
        this.key = key;
        this.code = code;
    }

    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;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getCode() {
        return code;
    }

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

编写控制器,这里有两个方法,login方法是登录,code方法是获取验证码

@RestController
@RequestMapping("/auth")
public class LoginController {

    @Autowired
    LoginService loginService;

    /**
     * 登录
     * @param dto 请求参数
     */
    @PostMapping("/login")
    public R<String> login(@RequestBody @Validated LoginDto dto) {
        return loginService.login(dto);
    }


    /**
     *
     * @return 验证码vo
     */
    @GetMapping("/code")
    public R<LoginCodeVo> code(){
        return loginService.code();
    }

}

服务层,首先根据用户名获取用户信息,然后判断用户名和密码是否匹配。如果匹配失败,则返回错误信息。接下来,调用verifyCode方法验证验证码是否正确。如果验证码不存在或者验证失败,则返回相应的错误信息。最后,删除已经验证成功的验证码,并返回登录成功的消息。

获取验证码,我们使用ArithmeticCaptcha生成了一个算术类型的验证码图片,并将验证码结果存储在Redis中,设置过期时间为60秒。同时,生成一个UUID作为验证码的key,并将验证码图片和key封装到LoginCodeVo对象中,返回给前端。

@Service
public class LogniServiceImpl  implements LoginService {

    @Autowired
    UserService userService;

    @Autowired
    RedisService redisService;

    @Override
    public R<String> login(LoginDto dto) {
        User user = userService.getByUserName(dto.getUsername());
        if(user == null || !user.getPassword().equals(dto.getPassword())) {
            return R.error("用户名或密码错误");
        }
        if(!verifyCode(dto.getKey(), dto.getCode())) {
            return R.error("验证码不存在或验证码错误");
        }
        //删除已经验证成功的验证码
        removeCode(dto.getKey());
        return R.ok("登录成功");
    }

    @Override
    public R<LoginCodeVo> code() {
        // 算术类型
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(111, 36);
        // 几位数运算,默认是两位
        captcha.setLen(2);
        // 获取运算的结果
        String result = "";
        try {
            result = new Double(Double.parseDouble(captcha.text())).intValue() + "";
        } catch (Exception e) {
            result = captcha.text();
        }
        //生成uuid做为验证码的key
        UUID key = UUID.randomUUID();
        //存储验证码信息到redis中,60秒过期
        redisService.setCacheObject(RedisConstants.LOGIN_CODE+key,result,RedisConstants.LOGIN_CODE_TIME, TimeUnit.SECONDS);
        return R.ok(new LoginCodeVo(captcha.toBase64(),key.toString()));
    }

    /**
     * 验证验证码是否正确
     * @param key 验证码的key
     * @param code 验证码的结果
     */
    private boolean verifyCode(String key,String code) {
        String result = redisService.getCacheObject(RedisConstants.LOGIN_CODE + key);
        //验证码不存在直接返回null
        if(result == null) {
            return false;
        }
        return result.equals(code);
    }

    /**
     * 删除验证码
     * @param key 验证码Key
     */
    private void removeCode(String key){
        redisService.deleteObject(RedisConstants.LOGIN_CODE + key);
    }

}
LoginVo

public class LoginCodeVo {
    private String img;
    private String key;
}

生成验证码接口
在这里插入图片描述
在这里插入图片描述
登录接口
在这里插入图片描述

Redis工具类

@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisService
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key)
    {
        return redisTemplate.getExpire(key);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection) > 0;
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }

    /**
     * 删除Hash中的某条数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}
Redis配置类

@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    }
}
Redis序列化配置类

public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;


    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}

​### Java实现JSON Web Token(JWT)的生成、解码和验证
JSON Web Token(JWT)是一种用于安全传输信息的开放标准。它可以用于认证和授权用户,以及在不同系统之间传输数据。在本文中,我们将介绍如何在 Java 中使用 jjwt 库来生成、解码和验证 JWT

引入 jjwt 库
首先,你需要在你的项目中引入 jjwt 库。如果你使用 Maven,可以在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

生成 JWT
以下是使用 jjwt 验证 JWT 的示例代码:

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class JwtGenerator {
    public static void main(String[] args) {
        // 设置过期时间为 1 天
        long expirationTime = System.currentTimeMillis() + 86400000;
        String token = Jwts.builder()
                .setSubject("user123") // 设置 subject
                .setExpiration(new Date(expirationTime)) // 设置过期时间
                .signWith(SignatureAlgorithm.HS256, "secret") // 设置签名
                .compact();
        System.out.println(token);
    }
}

验证 JWT
以下是使用 jjwt 验证 JWT 的示例代码:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;

public class JwtVerifier {
    public static void main(String[] args) {
        String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNjIwNzg3NzE3fQ.hXcJHdYvy3l0gI6HgNv6v-Tx_lL4FOWp65eGJ0YJtDk";
        try {
            Claims claims = Jwts.parser()
                    .setSigningKey("secret")
                    .parseClaimsJws(token)
                    .getBody();
            System.out.println("Subject: " + claims.getSubject());
        } catch (SignatureException e) {
            System.out.println("Invalid token.");
        }
    }
}

在这个例子中,我们使用 parser() 方法解析 JWT,并使用 setSigningKey() 方法设置签名密钥。如果 JWT 验证成功,我们可以从 Claims 对象中获取 subject 值。
参考视频:哔哩哔哩

使用 Spring Boot 和 DTO 进行数据验证

Spring Boot 是一个非常流行的 Java Web 应用程序开发框架,它提供了很多方便的功能,让开发者可以快速构建出高效、安全的 Web 应用。在开发 Web 应用程序时,数据验证是非常重要的一环。通过使用 DTO(Data Transfer Object)来验证数据,可以确保应用程序中输入的数据是有效和合法的。本文将介绍如何使用 Spring Boot 和 DTO 进行数据验证。

什么是DTO

DTO,即 Data Transfer Object,是用于数据传输的对象。它通常用于封装从数据库、文件或其他数据源中获取的数据,以便于在应用程序的不同层之间传输。DTO 对象应该包含与数据库表或 API 请求相对应的字段。

DTO验证规则

使用 DTO 对象来验证输入数据是一种很好的方式。可以使用 Bean Validation API 来为 DTO 对象添加验证规则。这些规则可以包括以下内容:是否为空、长度、正则表达式、范围等。在 DTO 类中使用注解来添加验证规则。以下是一些常用的注解:

  • @NotNull: 验证字段不能为空
  • @Size: 验证字段的长度是否在指定的范围内
  • @Pattern: 验证字段是否符合指定的正则表达式
  • @Min: 验证字段是否大于或等于指定的值
  • @Max: 验证字段是否小于或等于指定的值
    可以根据需要在 DTO 类中添加多个注解来验证字段。
DTO验证实现步骤

1.依赖引入
首先,在 pom.xml 文件中添加DTO验证的依赖:

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

该依赖引入了 Spring Boot 中的 Bean Validation API。

2.DTO对象

创建一个 DTO 对象,其中包含需要验证的字段。DTO 对象应该包含与数据库表或 API 请求相对应的字段。

public class UserDTO {
    @NotNull(message = "用户名不能为空")
    @Size(min = 6, max = 20, message = "用户名长度必须在6到20个字符之间")
    private String username;
 
    @NotNull(message = "密码不能为空")
    @Size(min = 6, max = 20, message = "密码长度必须在6到20个字符之间")
    private String password;
 
    // getter and setter
}
3.添加验证规则

在 DTO 类中使用 Bean Validation API 来添加验证规则。例如,在上面的 UserDTO 类中,@NotNull 和 @Size 注解分别用于验证 username 和 password 字段是否为空和长度是否符合要求。

4.控制器中使用 @Validated 注解来验证 DTO 对象

在控制器中使用 @Validated 注解来验证 DTO 对象。如果 DTO 对象不符合验证规则,将抛出 BindException异常

@RestController
@RequestMapping("/users")
public class UserController {
 
    @PostMapping
    public UserDTO createUser(@RequestBody @Validated UserDTO user) {
        // TODO: 创建用户
        return user;
    }
}
5,全局捕捉DTO验证异常

如果 DTO 对象不符合验证规则,使用 @ExceptionHandler 注解来处理异常,全局捕捉BindException,并将错误信息返回给客户端。

@RestControllerAdvice
public class GlobalExceptionHandler {
 
    /**
     * DTO参数异常验证处理
     *
     * @param e 异常类
     * @return 错误信息
     */
    @ExceptionHandler(BindException.class)
    public ResponseResult methodArgumentNotValidException(BindException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        List<String> messages = allErrors.stream().map(DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
        return ResponseResult.errorResult(400, messages.get(0));
    }
 
}

验证错误实例
在这里插入图片描述

在上面的代码中,@Validated 注解用于验证传入的 UserDTO 对象。如果 UserDTO 对象不符合验证规则,将抛出 BindException异常。在异常处理方法中,我们使用了 @RestControllerAdvice 注解标注了一个类,这个类可以处理所有控制器中抛出的异常。然后我们使用 @ExceptionHandler 注解捕获了 BindException 异常,并将其处理为一个 ResponseResult 类型的错误响应。我们使用了 BindingResult 对象获取了所有错误信息,并将其转换为了一个错误信息列表。最后,我们使用 ResponseResult 类创建了一个错误响应,并将错误信息添加到响应中返回给客户端。使用 @RestControllerAdvice 注解可以让我们在整个应用程序中集中处理异常,并将代码中的异常处理代码减少到最小。这可以使代码更加简洁和易于维护。

现在,我们已经成功地使用 DTO 对象和 Bean Validation API 对输入数据进行了验证。使用 DTO 对象来验证输入数据可以确保应用程序中输入的数据是有效和合法的,避免了一些潜在的问题。在实际开发中,可以根据需要对 DTO 对象添加不同的验证规则。
参考视频:哔哩哔哩

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: idea-springboot-projectes是指使用IDEA开发工具构建Spring Boot项目的过程。Spring Boot是一个流行的开源框架,可以帮助Java开发者快速构建高效的微服务应用。IDEA则是一个以Java为基础的集成开发环境,可以提供代码编辑、调试、测试和部署等功能。 在构建Spring Boot项目时,IDEA可以通过自带的Spring Initializr工具或者通过手动配置的方式来进行。Spring Initializr可以自动生成项目基础框架,包括依赖库、项目结构、POM文件等。而手动配置则需要开发者自行添加所需要的依赖库和配置文件。 在项目开发过程中,IDEA可以提供强大的代码提示和自动补全功能,包括快捷键、代码重构、调试等。此外,IDEA还支持各种测试框架和部署方式,方便开发者快速进行测试和部署。 总的来说,使用IDEA开发Spring Boot项目可以提高开发效率和代码质量,并且可以使用各种插件和扩展来增强开发体验。这是一个非常流行的Java开发模式,适用于各种类型的应用程序和系统。 ### 回答2: Idea-SpringBoot-Project是一个使用了Spring Boot框架的项目,有助于Java开发者轻松构建Web应用程序。Spring Boot是一个流行的Java框架,它可以帮助开发者更快地构建更好的应用程序。使用Idea-SpringBoot-Project,开发者可以轻松创建具有高可用性和可扩展性的Java Web应用程序。 Idea-SpringBoot-Project引入了许多方便的功能,如Spring容器管理、数据访问和Web MVC框架等。通过使用Spring Boot开发者可以在不需要手动配置的情况下快速构建应用程序。而使用Idea作为开发工具,则能帮助开发者更快地编写代码和进行调试。这个项目不仅可以在Windows和Linux平台上运行,还与许多其他大型Java库和框架兼容,如Spring Security和Hibernate等。 总之,Idea-SpringBoot-Project帮助开发者将更多的时间专注于应用程序逻辑和功能,而不是花费时间和精力去手动配置。通过这个项目,开发者可以构建出高性能、高度可用性和可扩展性的Java应用程序。 ### 回答3: idea-springboot-projectes是针对Spring Boot框架的项目管理功能的开发工具集成环境。它提供了一种方便快捷的方式来创建、维护和调试Spring Boot项目。 idea-springboot-projectes使开发人员能够在一个单一的界面中,管理不同的Spring Boot项目,包括应用程序、库和插件。它自动生成项目结构,提供依赖管理,支持代码重构、调试和测试等功能,同时也能够整合其他常用开发工具如Maven、Gradle等,进一步提升开发效率。 通过idea-springboot-projectes,开发人员可以快速创建Spring Boot应用程序。一旦项目创建完成,可以通过IDEA的自动配置机制,无需编写大量的代码即可完成基础设施的搭建和配置。同时,IDEA也提供了许多Spring Boot Starter库,这些库包含了大量常用的功能和组件,帮助开发人员轻松实现各种业务需求。 总之,idea-springboot-projectes是一款非常实用的开发工具,为Spring Boot开发提供了强大的项目管理和开发支持,同时提高了开发效率和代码质量,使得开发人员能够更专注于业务代码的编写。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值