SpringBoot接口开发入门基础,以Controller、Service和Mapper三层结构开发注册、登录、信息修改等业务接口,包括多个常见问题的解决及PostMan的使用。


1 概述

1.1 关于本文

本文主要用于记录总结我学习Spring Boot过程遇到的重要知识,我学习的课程是B站黑马程序员的SpringBoot课程。希望此博客可以帮助到大家,也能方便我快速回看相关知识。


2 SpringBoot三层结构

在这里插入图片描述

2.1 Controller层(表现层):

  • 作用:Controller层是应用程序与用户或其他客户端交互的入口点。它负责处理用户的请求和响应。
  • 职责:
    • 接收用户的HTTP请求。
    • 调用Service层的业务逻辑。
    • 将Service层返回的数据转换为视图模型(View Model)或直接返回JSON/XML等格式的数据。
    • 处理异常,并将异常信息返回给用户。
  • 特点:
    • 使用@RestController注解定义RESTful风格的控制器。
    • 使用@RequestMapping注解映射请求路径到处理方法。
    • 使用@RequestParam、@RequestBody、@PathVariable等注解获取请求参数。
    • 可以返回ResponseEntity对象,提供更灵活的响应控制。

2.2 Service层(业务逻辑层):

  • 作用:Service层是应用程序的核心,负责实现业务逻辑。
  • 职责:
    • 定义业务操作的抽象和实现。
    • 调用Mapper层的方法,获取或更新数据。
    • 处理业务规则和业务流程。
    • 可以包含事务管理逻辑。
  • 特点:
    • 通常使用接口(Interface)定义业务操作,实现类(Implementation)实现这些操作。
    • 可以使用@Service注解标记服务类。
    • 可以注入其他Service或Mapper层的组件。

2.3 Mapper层(数据访问层):

  • 作用:Mapper层负责与数据库进行交互,执行CRUD(创建、读取、更新、删除)操作。
  • 职责:
    • 定义数据访问接口。
    • 实现数据访问逻辑,如SQL查询。
    • 将数据库结果映射到Java对象。
  • 特点:
    • 在Spring Boot中,通常使用MyBatis或JPA等ORM框架来实现数据访问。
    • 使用@Mapper注解标记Mapper接口。
    • 可以注入DataSource或JdbcTemplate等数据源组件。
    • 可以使用XML或注解方式定义SQL语句。

3 一个接口的完整实现(以注册接口为例)

3.1 Controller层

package org.bigevent.controller;

import org.bigevent.pojo.Result;
import org.bigevent.pojo.User;
import org.bigevent.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    // 为了调用Service层的方法需要一个service成员
    private UserService userService;

    @PostMapping("/register")
    public Result register(String username, String password) {
        User user = userService.findByUsername(username);
        if (user == null) {
            userService.register(username, password);
            return Result.success();
        } else {
            return Result.error("用户名已存在");
        }
    }
}

3.2 Service层

接口:

package org.bigevent.service;

import org.bigevent.pojo.User;

public interface UserService {

    User findByUsername(String username);

    void register(String username, String password);
}

实现类:

package org.bigevent.service.impl;

import org.bigevent.mapper.UserMapper;
import org.bigevent.pojo.User;
import org.bigevent.service.UserService;
import org.bigevent.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    // 为了调用Mapper层的方法需要一个mapper成员
    private UserMapper userMapper;

    @Override
    public User findByUsername(String username) {
        User u = userMapper.findByUsername(username);
        return u;
    }

    @Override
    public void register(String username, String password) {
        String md5Pwd = Md5Util.getMD5String(password);
        userMapper.addUser(username, md5Pwd);
    }
}

3.3 Mapper层

package org.bigevent.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.bigevent.pojo.User;

@Mapper
public interface UserMapper {
    @Select("select * from user where username = #{username}")
    User findByUsername(String username);

    @Insert("insert into user(username,password,create_time,update_time) values (#{username},#{md5Pwd},now(),now())")
    void addUser(String username, String md5Pwd);
}

4 JWT令牌相关知识及使用

4.1 JWT介绍

  • JWT全称JSON Web Token,是一种用于在网络应用环境间安全传输信息的简洁的、URL安全的令牌格式。你可以把它想象成网络世界中的“电子护照”。
  • JWT的主要特点:
    • 紧凑:JWT通常以字符串形式存在,这使得它在网络中传输非常方便。
    • 自包含:JWT自身包含了所有必要的信息,不需要依赖外部数据库来验证其内容。
    • 可验证:通过使用数字签名,JWT可以被验证其真实性和完整性。
  • JWT的结构:(JWT由三部分组成,用点.分隔)
    • Header(头部):通常包含令牌的类型(即JWT)和所使用的加密算法,如HMAC SHA256或RSA。
    • Payload(负载):包含所要传递的信息。这些信息通常包括一些声明(Claims),例如用户ID、用户名、令牌的发行者、令牌的过期时间等。这部分通过Base64编码,不能包含任何私密信息
    • Signature(签名):用于验证消息的发送者是否是合法的发送者,并且确保令牌在传输过程中没有被篡改。
  • JWT的工作原理:
    用户登录:用户通过输入用户名和密码登录系统。
    服务器验证:服务器验证用户的凭据。
    生成JWT:一旦验证通过,服务器生成一个包含用户信息的JWT,并使用一个密钥对其进行签名。
    发送JWT:服务器将JWT发送给用户。
    存储JWT:用户将JWT保存在本地,通常是浏览器的Cookie或LocalStorage。
    后续请求:用户每次请求资源时,都会将JWT发送给服务器。
    服务器验证:服务器验证JWT的有效性,如果验证通过,就允许用户访问资源。

4.2 JWT生成

用户登录时生成一个jwt。

public void genToken(){
    // 添加自定义信息,即第二部分,不能存放敏感信息
    Map<String,Object> claims = new HashMap<>();
    claims.put("userId",1);
    claims.put("username","admin");

    String token = JWT.create()
            .withClaim("user1", claims)
            .withExpiresAt(
                    new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)  // 过期时间
            )
            .sign(Algorithm.HMAC256("secret")); // 密钥及加密方式

    System.out.println(token);
}

4.3 JWT验证

用户发送其他请求时应携带jwt。服务器首先进行jwt验证,通过后说明用户处于合法的登录状态,可以响应请求。

public void verifyToken(){
	// 模拟客户端传入的jwt
    String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7InVzZXJJZCI6MSwidXNlcm5hbWUiOiJhZG1pbiJ9LCJleHAiOjE3Mjc3ODI2OTZ9.55L_zM5V2UZ8n4-uzVsv5oCqYO9C4Kcx2YiXnri9FcU";
    JWTVerifier v = JWT.require(Algorithm.HMAC256("secret")).build();
    DecodedJWT jwt = v.verify(token);
    Map<String, Claim> claims = jwt.getClaims();
    System.out.println(claims.get("user1"));
}

5 一些常见问题的解决

5.1 使用Spring Validation对接口的参数进行合法性校验

  1. 引入Spring Validation起步依赖
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
  1. 在参数的前面添加@Pattern注解
    在这里插入图片描述
  2. 在Controller类上添加@Validated注解
    在这里插入图片描述

5.2 对参数校验失败结果进行异常处理(同样以Result形式返回客户端)

在exception包下新建一个类负责全局异常处理器

package org.bigevent.exception;

import org.bigevent.pojo.Result;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)	// 对Exception类进行处理
    public Result handleException(Exception e) {
        e.printStackTrace();	// 打印到控制台
        return Result.error(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "操作失败:未知错误");
    }
}

5.3 使用拦截器自动验证登录

  1. 实现拦截器类(interceptor包)
@Component// 将拦截器交给spring管理
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        try{
            JwtUtil.parseToken(token);
            return true;    // 放行
        }catch (Exception e){
            response.setStatus(401);
            return false;   // 拦截
        }
    }
}
  1. 通过配置类添加拦截器(config包)
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login", "/user/register");
    }
}

5.4 Json转换时不转换某些字段

使用JsonIgnore注解
在这里插入图片描述

5.5 数据库下划线命名到Java对象的驼峰命名自动转换

配置mybatis属性
在这里插入图片描述

5.6 PostMan请求预处理脚本添加请求Token

在这里插入图片描述

5.7 使用ThreadLocal减少token解析次数

  • 简介:ThreadLocal 是 Java 提供的一种线程局部变量机制,它可以让每个使用该变量的线程都有独立的变量副本,避免了多线程间共享全局变量时的同步问题。
  • 实现思路:每次请求只在拦截器进行一次解析获得claims,然后将claims存入ThreadLocal,后续的访问从ThreadLocal获取变量值,使用之后释放。
  • 实现步骤:
  1. 在拦截器中把解析结果放进ThreadLocal
@Component// 将拦截器交给spring管理
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        try{
            // 改进前:
            // JwtUtil.parseToken(token);
            // return true;
            
            // 改进后:
            Map<String, Object> claims = JwtUtil.parseToken(token);
            ThreadLocalUtil.set(claims);
            return true;    // 放行
        }catch (Exception e){
            response.setStatus(401);
            return false;   // 拦截
        }
    }
}

  1. 在使用处直接从ThreadLocal处获取
	// 改进前:
//    @GetMapping("/userInfo")
//    public Result<User> userInfo(@RequestHeader(name = "Authorization") String token){
//        // 从请求头中获取token
//        Map<String, Object> claims = JwtUtil.parseToken(token);
//        String username = claims.get("username").toString();
//
//        User user = userService.findByUsername(username);
//        return Result.success(user);
//    }

    // 改进后:
    @GetMapping("/userInfo")
    public Result<User> userInfo(){
        Map<String, Object> claims = ThreadLocalUtil.get();
        String username = claims.get("username").toString();

        User user = userService.findByUsername(username);
        return Result.success(user);
    }
  1. 使用之后释放ThreadLocal防止内存泄漏(在拦截器中重写afterCompletion函数)
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ThreadLocalUtil.remove();
    }

5.8 Spring Validation实现对实体类某一属性进行合法性校验

  • 用处:当Controller接收到的请求发来的是直接转换为实体类时,无法在函数参数列表中直接校验,则需要对实体类的具体属性进行校验
  • 实现方法
  1. 对实体类的对应属性加上注解
@Data
public class User {
    @NotNull
    private Integer id;//主键ID
    private String username;//用户名
    @JsonIgnore
    private String password;//密码
    @NotEmpty
    @Pattern(regexp = "^\\S{1,10}$")
    private String nickname;//昵称
    @NotEmpty
    @Email
    private String email;//邮箱
    private String userPic;//用户头像地址
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//更新时间
}
  1. 在Controller的接口参数处加上Validated注解
    在这里插入图片描述

其他知识

类和接口

  • 接口(Interface):

    • 定义:接口是一种完全抽象的结构,它规定了方法的签名,但不提供实现。接口可以被类实现(implement),实现接口的类必须提供接口中所有方法的具体实现
    • 作用:接口是一种规范,它定义了一组方法,这些方法可以被不同的类以不同的方式实现。接口的目的是让不同的类遵循相同的方法签名,从而实现多态性
    • 特点:
      • 接口中的方法默认是public的。
      • 接口中的字段默认是public static final的,即常量。
      • 从Java 8开始,接口可以包含默认方法(default method)。
  • 类(Class):

    • 定义:类是具有状态和行为的对象的模板。类可以包含字段(属性)、方法(行为)和构造函数。类可以被实例化(instantiated)以创建对象。
    • 作用:类是面向对象编程中的基本构建块,它封装了数据和操作这些数据的方法。
    • 特点:
      • 类可以是抽象的(abstract),这意味着它不能被实例化,并且可以包含未实现的方法。
      • 类可以是具体的(concrete),这意味着它可以被实例化,并且必须提供所有方法的具体实现。
      • 类可以继承另一个类,从而复用代码。
  • 接口和类的联系:

    • 实现/继承:类可以实现一个或多个接口,这意味着类必须提供接口中定义的所有方法的具体实现。类也可以继承另一个类,从而获得父类的状态和行为。
    • 多态性:接口和抽象类允许Java实现多态性。多态性是指同一个方法调用,根据被调用对象的实际类型,可以有不同的行为。例如,不同的类可以实现同一个接口,并且提供不同的方法实现。
    • 设计模式:接口和类在设计模式中扮演着重要角色。例如,策略模式、工厂模式、观察者模式等都涉及到接口和类的使用。

注解解释

  • RestController:@RestController 是 Spring 框架中的一个注解,用于定义 RESTful Web 服务。当你在一个类上使用这个注解时,它表明该类中的所有方法都是 RESTful 服务方法,并且返回的数据将自动序列化为 JSON 或 XML 格式(取决于请求的接受头)
    • 自动序列化:使用 @RestController 注解的类中的方法返回的对象会被自动序列化为 JSON 或 XML 格式,发送给客户端。
    • 默认请求处理:它处理所有的 HTTP 请求类型(GET、POST、PUT、DELETE 等)。

  • RequestMapping:@RequestMapping 是 Spring 框架中用于处理 HTTP 请求的注解,它将 HTTP 请求映射到处理器方法上。这个注解可以用于类或方法级别,提供了灵活的方式来处理不同类型的 HTTP 请求。
    • 请求映射:可以将一个或多个 HTTP 请求映射到一个特定的方法上。
    • 方法级别的注解:可以用于方法上,用于指定该方法处理的请求路径和请求类型。
    • 类级别的注解:可以用于类上,用于指定类中所有方法的请求路径的前缀。
    • 支持复合注解:如 @GetMapping、@PostMapping、@PutMapping、@DeleteMapping 等,它们是 @RequestMapping 的特定快捷方式。

  • AutoWired:@Autowired 是 Spring 框架中一个非常重要的注解,用于实现依赖注入(Dependency Injection,DI)。依赖注入是一种设计模式,用于减少代码之间的耦合度,提高模块化和代码的可测试性。
    • 自动装配:Spring 容器在创建对象时,会自动注入依赖的组件。
    • 类型驱动:Spring 通过组件的类型来自动匹配和注入依赖。
    • 可选性:可以指定某些依赖是可选的,如果找不到依赖则不注入。

  • PostMapping:@PostMapping 是 Spring 框架中的一个注解,用于映射 HTTP POST 请求到特定的处理方法上。它是 @RequestMapping 注解的一个特定快捷方式,专门用于处理 POST 请求。

  • GetMapping:@GetMapping 是 Spring Framework 中的一个注解,用于映射 HTTP GET 请求到特定的处理方法上。

  • Service:@Service 通常被用作一种约定俗成的注解,**用于标记服务层(Service Layer)的组件。**服务层通常负责业务逻辑的实现,它位于控制器(Controller)和数据访问层(Repository/DAO)之间。

  • Override:在 Java 编程语言中,@Override 注解用于表示某个方法是重写的父类方法。当一个子类的方法与父类的方法具有相同的方法名、返回类型和参数列表时,可以使用 @Override 注解来明确表示该方法是对父类方法的重写。
    • 编译时检查:使用 @Override 注解可以帮助编译器检查是否正确地重写了父类的方法。如果父类中没有对应的方法,或者签名不匹配,编译器会报错。
    • 提高代码可读性:通过使用 @Override 注解,可以清晰地表明某个方法是重写自父类的方法,提高代码的可读性和可维护性。
    • 避免错误:如果没有使用 @Override 注解,可能会在不经意间重写方法,而没有意识到这一点,导致潜在的错误。

  • Mapper:@Mapper 在 MyBatis 框架中是一个非常重要的概念,它通常指的是与数据库操作相关的接口。这些接口通过 XML 文件或注解与 SQL 语句相映射,从而实现对数据库的 CRUD 操作(创建、读取、更新、删除)。
    • 映射 Java 对象:Mapper 可以将查询结果映射到 Java 对象上,实现 ORM 操作。
    • 与 XML 或注解结合:Mapper 接口的方法可以通过 XML 映射文件或注解与 SQL 语句关联。
    • 简化数据库操作:开发者无需编写具体的 JDBC 代码,通过定义接口方法和对应的 SQL 映射即可操作数据库。

  • Select:@Select 是一个注解,用于在 Mapper 接口的方法上直接定义 SQL 查询语句。使用 @Select 注解可以避免编写 XML 映射文件,使得代码更加简洁和集中。

  • Insert:@Insert 注解用于在 Mapper 接口的方法上直接定义 SQL 插入语句。使用 @Insert 注解可以避免编写 XML 映射文件,使得代码更加简洁和集中。

  • validated:@Validated 是 Spring Framework 中的一个注解,用于开启基于 JSR-303/JSR-349 标准的验证(也称为 Bean Validation)。这个注解是 @Valid 注解的增强版本,提供了更多的功能,包括分组验证。
    • 分组验证:@Validated 支持分组验证,允许你定义不同的验证组,以适应不同的验证场景。
    • 方法级别的验证:可以用于方法参数上,也可用于整个类或特定的方法上。
    • 结合 BindingResult 使用:通常与 BindingResult 对象一起使用,以便于处理验证错误。
    • 自动抛出异常:如果验证失败,Spring 会抛出 MethodArgumentNotValidException 异常,可以通过全局异常处理器进行统一处理。

  • Pattern:@Pattern 是 Java 中的一个注解,用于标注一个字符串属性的校验规则。它可以在编译期和运行期对被注解的字符串属性进行校验。@Pattern 注解可以用于验证 String 类型的属性或字段,确保其值符合指定的正则表达式模式。例如,可以用来校验邮箱格式、手机号码、日期等。

  • RestControllerAdvice:@RestControllerAdvice 是 Spring Framework 中的一个注解,用于定义全局异常处理器和数据绑定器(DataBinder),它通常用于处理整个应用程序的异常。通过使用 @RestControllerAdvice,你可以在一个集中的地方处理多个控制器抛出的异常。

    • 全局异常处理:可以捕获和处理控制器抛出的异常。
    • 数据绑定定制:可以定制数据绑定过程,例如,自定义属性编辑器。
    • @ExceptionHandler:使用 @ExceptionHandler 注解定义异常处理方法。
    • @InitBinder:使用 @InitBinder 注解定义数据绑定的定制方法。
    • basePackages:通过 basePackages 或 basePackageClasses 属性指定注解的适用范围。

  • ExceptionHandler:@ExceptionHandler 是 Spring 框架中一个非常有用的注解,它允许你定义异常处理方法,这些方法可以捕获并处理控制器(Controller)中抛出的异常。通常与 @ControllerAdvice 或 @RestControllerAdvice 注解一起使用,以实现全局或局部的异常处理。

    • 指定异常类型:通过 @ExceptionHandler 注解的方法可以指定一个或多个异常类型,当这些异常被抛出时,相应的处理方法将被调用。
    • 灵活性:可以针对不同的异常返回不同的响应类型,如视图名称、JSON、XML 等。
    • 易于使用:直接在方法上使用注解,无需额外配置。

  • Test:@Test 是JUnit测试框架中的一个注解,用于标记一个方法为测试方法。当你使用JUnit进行单元测试时,@Test 注解告诉JUnit这个特定的方法包含了一个测试用例,应该被执行。

  • JsonIgnore:@JsonIgnore 是 Jackson 库中的一个注解,用于在序列化和反序列化过程中忽略 Java 对象的某些属性。当一个字段被标记为 @JsonIgnore 后,该字段将不会出现在序列化后的 JSON 字符串中,同时在反序列化时也会忽略该字段。可以对密码等字段进行忽略。

  • PutMappring:@PutMapping 是 Spring MVC 中用于映射 HTTP PUT 请求到控制器方法的注解。它是 @RequestMapping 注解的一个特定变体,专门用于处理 HTTP PUT 请求。使用 @PutMapping 可以让代码更加简洁明了,并且直接表达了方法的用途,即处理 PUT 请求。

  • PatchMapping:@PatchMapping 是 Spring Framework 中用于映射 HTTP PATCH 请求到特定处理器方法的注解。这个注解是 @RequestMapping 的一个合成注解,它提供了一个快捷方式,专门用于处理 PATCH 请求,这通常用于对资源进行部分更新。与 PUT 请求不同,PATCH 请求不需要提供资源的完整表示,而只需要提供要更新的部分



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值