SpringBoot 创建及登录、拦截器

1.创建

这里jdk1.8推荐使用java21

pom文件:

XML 复制代码
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.qcby</groupId>
    <artifactId>SpringBootDemoTest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBootDemoTest</name>
    <description>SpringBootDemoTest</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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

        <!--加载web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--加载Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!--加载mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope> runtime</scope>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

</project>

2.登录和拦截器

HTTP协议本身是无状态的,这意味着服务器不会保存任何与客户端交互的状态信息。每个HTTP请求都是独立的,服务器在处理完当前请求后不会记住之前的请求信息。这种设计是HTTP协议的核心特性之一。

这种无状态特性带来以下具体表现:

  1. 每个请求必须包含服务器处理该请求所需的全部信息
  2. 服务器不会基于之前的请求来推断当前请求的上下文
  3. 默认情况下,服务器不会记录客户端的历史访问记录

无状态设计的优缺点: 优点:

  • 简化服务器设计,降低服务器资源消耗
  • 提高可扩展性,服务器可以更容易地处理大量并发请求
  • 减少服务器故障时的数据丢失风险

缺点:

  • 无法直接支持需要保持状态的Web应用(如购物车、用户登录等)
  • 需要额外机制(如Cookie、Session)来实现状态管理

为了解决无状态带来的限制,Web开发中常采用以下技术:

  1. Cookie:在客户端存储少量数据
  2. Session:在服务器端保存用户状态
  3. Token:通过令牌验证用户身份

典型应用场景对比:

  • 静态网页:无状态完全适用
  • 动态网页:需要额外状态管理机制
  • Web应用:必须实现某种形式的状态保持

2.1 session

Session(会话)是Web开发中用于跟踪用户状态的重要机制。以下是关于session的详细说明:

  1. 基本概念
  • 服务器端存储的用户状态信息
  • 每个用户拥有独立的session数据
  • 通常通过cookie中的session ID来识别用户
  1. 工作原理
  1. 客户端首次访问时,服务器创建session
  2. 服务器生成唯一session ID并返回给客户端
  3. 客户端后续请求携带session ID
  4. 服务器根据ID查找对应的session数据
  1. 典型应用场景
  • 用户登录状态维护
  • 购物车功能实现
  • 个性化设置存储
  • 表单数据跨页面暂存

2.1.1 登录

java 复制代码
@GetMapping("/login")
@ResponseBody
public String login(Users users, HttpServletRequest request) {
    List<Users> list = usersDao.login(users);
   if(list.size()==1){
        request.getSession().setAttribute("users", list.get(0));//保存用户信息
        return "login success";
    }else {
        return "login fail";
    }
}

jsessionid每次登录都不一样

2.1.2 session拦截器

java 复制代码
package com.qcby.springbootdemotest.interceptor;
import com.qcby.springbootdemotest.model.Users;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
登录拦截器
HandlerInterceptor 该接口是springmvc提供的一个接口,拦截器的主要接口*/@Componentpublic class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 Users users = (Users) request.getSession().getAttribute("users");// 从session中获取用户信息
 if(users != null){
     return true;// true 表示放行,false 表示不放行
 }else {
     response.getWriter().append("please login");
     return false;
 }

}
}

添加拦截器:

java 复制代码
package com.qcby.springbootdemotest.config;
import com.qcby.springbootdemotest.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class LoginConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loginInterceptor)//添加拦截器
            .addPathPatterns("/**")         //拦截所有请求
            .excludePathPatterns("/users/login");//放行接口
}

}

2.2 token

token 说明

token 是服务端生成的一串字符串,目的是作为客户端进行请求的一个令牌。当第一次登录后,服务器生成一个 token (一串字符串),并将此 token 返回给客户端,此后页面接收到请求后,只需要找到 token 即可获取信息,无需再输入登录名和密码。

session 和 token 的区别

session 和 token 的区别:但 session 有一个缺陷:如果 web 服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候 session 会丢失。而 token 最大特点就是支持跨平台操作,不论是在 App 还是在 PC 端,token 都可以保留。

2.2.1 常建工具类用于创建token和解析token密钥

java 复制代码
package com.qcby.springbootdemotest.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
/**
JWT工具类/public class JwtUtil {//有效期为public static final long JWT_TTL = 6060*1000L;//60 * 60 *1000 一个小时//设置密钥明文public static final String JWT_KEY = "qcby";
/**
生成jwt
@param id
@param subject
@param ttlMillis
@return*/public static String createJWT(String id, String subject, Long ttlMillis){SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;long nowMillis = System.currentTimeMillis();Date now = new Date(nowMillis);if(ttlMillis==null){ttlMillis = JwtUtil.JWT_TTL;}long expMillis = nowMillis + ttlMillis;Date expDate = new Date(expMillis);SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id)      // jwt的唯一标识,根据业务需要,可以设置为一个不重复的值
.setSubject(subject)// 主题 可以是JSON数据
.setIssuer("wd") // 签发者
.setIssuedAt(now)  // 签发时间
.signWith(signatureAlgorithm, secretKey) //使用HS256签名算法和密钥
.setExpiration(expDate); // 设置过期时间
return builder.compact();
}
/**
生成加密后的密钥 secretKey
@return*/public static SecretKey generalKey(){byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");return key;}
/**
解密jwt
@param jwt
@return
@throws Exception*/public static Claims parseJWT(String jwt) throws Exception{SecretKey secretKey = generalKey();return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}
public static void main(String[] args) {
String token = JwtUtil.createJWT(UUID.randomUUID().toString(),"qd", null);
System.out.println(token);
}
}

导入依赖

XML 复制代码
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

2.2.2 修改登录

java 复制代码
@GetMapping("/login")
@ResponseBody
public String login(Users users, HttpServletRequest request) {
    List<Users> list = usersDao.login(users);
   if(list.size()==1){
        String token = JwtUtil.createJWT(list.get(0).getId().toString(), list.get(0).getUsername(), null);
        return token;
    }else {
        return "login fail";
    }
}

token优化:

java 复制代码
@PostMapping("/login")
@ResponseBody
public ResponseResult login(@RequestBody Users users, HttpServletRequest request) {
    List<Users> list = usersDao.login(users);
    Map<String,Object> map;
   if(list.size()==1) {
        map = new HashMap<>();
        //生成token
        String token = JwtUtil.createJWT(list.get(0).getId().toString(), list.get(0).getUsername(), null);
        map.put("token", token);
        return new ResponseResult(200,"success",map);
    }
    return new ResponseResult(400,"fail");
}

拦截器

java 复制代码
package com.qcby.springbootdemotest.interceptor;

import com.qcby.springbootdemotest.model.Users;
import com.qcby.springbootdemotest.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * 登录拦截器
 * HandlerInterceptor 该接口是springmvc提供的一个接口,拦截器的主要接口
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");//获取请求头中的token
        if(!StringUtils.hasText(token)){//token为空
            throw new RuntimeException("请登录");
        }
        //解析 token
        try{
            Claims claims = JwtUtil.parseJWT(token);//解析token
        }catch (Exception e){
            e.printStackTrace();
            response.sendError(401);//响应错误
            throw new RuntimeException("请登录");
        }
        return true;
    }
}

指定拦截异常类

java 复制代码
package com.qcby.springbootdemotest.expection;

import com.qcby.springbootdemotest.model.ResponseResult;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class MyControllerAdvice {
    @ResponseBody
    @ExceptionHandler(RuntimeException.class)//指定拦截的异常
    public ResponseResult handleException(Exception e){
        String message = e.getMessage();//获取异常信息
        ResponseResult result = new ResponseResult(400,message);
        return result;
    }
}

3.springboot统一的响应格式

springboot统一的响应格式好处:

标准化接口设计

  • 统一的数据结构使前后端协作更高效,前端开发者可以预期固定的响应格式
  • 示例:所有接口返回{"code":200,"message":"success","data":{...}}格式
  • 避免了不同开发者各自定义响应格式导致的混乱

错误处理规范化

  • 统一错误码和错误信息格式,便于客户端识别和处理异常
  • 可预定义常见的HTTP状态码(200,400,401,403,404,500等)
  • 示例:{"code":401,"message":"未授权访问","data":null}

简化客户端处理

  • 前端无需为不同接口编写不同的响应解析逻辑
  • 移动端应用可以复用统一的响应解析模块
  • 示例:可以封装通用的响应拦截器处理所有API响应

增强可维护性

  • 统一修改响应格式时只需修改一处代码
  • 方便添加全局的响应拦截和日志记录
  • 便于实现响应数据的统一加密或签名

监控和统计

  • 统一的格式便于收集和分析接口调用情况
  • 可以基于响应码统计系统健康状态
  • 示例:监控系统可以统计不同错误码的出现频率

实现方式

  1. 使用@RestControllerAdvice创建全局响应处理器
  2. 自定义ResponseEntity封装类
  3. 统一处理异常转换为标准格式
  4. 可结合AOP实现响应数据的增强处理

典型应用场景

  • 微服务架构中需要规范化的API响应
  • 前后端分离项目中需要明确的数据契约
  • 需要提供开放API的第三方服务
  • 需要严格监控接口质量的系统

具体实现:

java 复制代码
@RequestMapping("/findAll")
@ResponseBody
public ResponseResult findAll() {
    List<Users> list = usersDao.findAll();
    return new ResponseResult<>(200,list);
}

/**
 * 根据用户名查询用户
 * @param username
 * @return
 * @PathVariable 路径变量, 路径参数
 */
@RequestMapping("/findByUsername")
@ResponseBody
public ResponseResult findByUsername(String username) {
    List<Users> user = usersDao.findByName(username);
    return new ResponseResult<>(200,"suc",user);
}

@PostMapping("/insert")
@ResponseBody
public ResponseResult insert(@RequestBody Users users) {
    int count = usersDao.insert(users);
    if(count>0){
        return new ResponseResult(200,"suc");
    }else {
        return new ResponseResult(400,"fail");
    }
}

@PostMapping("/update")
@ResponseBody
public ResponseResult update(@RequestBody Users users) {
    int count = usersDao.update(users);
    if (count>0){
        return new ResponseResult(200,"suc");
    }else {
        return new ResponseResult(400,"fail");
    }
}

@PostMapping("/delete/{id}")
@ResponseBody
public ResponseResult delete(@PathVariable Integer id) {
    int count = usersDao.delete(id);
    if (count>0){
        return new ResponseResult(200,"suc");
    }else {
        return new ResponseResult(400,"fail");
    }
}

ResponseResult类

java 复制代码
package com.qcby.springbootdemotest.model;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class ResponseResult<T> {
private Integer code;//状态码
private String message;//提示信息
private T data;  // 数据
public ResponseResult(Integer code, String message, T data) {
    this.code = code;
    this.message = message;
    this.data = data;
}

public ResponseResult(Integer code, String message) {
    this.code = code;
    this.message = message;
}

public ResponseResult(Integer code, T data) {
    this.code = code;
    this.data = data;
}

}
相关推荐
uzong7 小时前
软件架构指南 Software Architecture Guide
后端
fox_mt8 小时前
AI Coding - ClaudeCode使用指南
java·ai编程
毕设源码-郭学长8 小时前
【开题答辩全过程】以 基于SSM的高校运动会管理系统的设计与实现为例,包含答辩的问题和答案
java·eclipse
qq_5470261798 小时前
Maven 使用指南
java·maven
xiaolyuh1238 小时前
Arthas修改类(如加日志)的实现原理
java
栗子叶8 小时前
Java对象创建的过程
java·开发语言·jvm
勇哥java实战分享8 小时前
短信平台 Pro 版本 ,比开源版本更强大
后端
学历真的很重要9 小时前
LangChain V1.0 Context Engineering(上下文工程)详细指南
人工智能·后端·学习·语言模型·面试·职场和发展·langchain
有一个好名字9 小时前
力扣-从字符串中移除星号
java·算法·leetcode