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协议的核心特性之一。
这种无状态特性带来以下具体表现:
- 每个请求必须包含服务器处理该请求所需的全部信息
- 服务器不会基于之前的请求来推断当前请求的上下文
- 默认情况下,服务器不会记录客户端的历史访问记录
无状态设计的优缺点: 优点:
- 简化服务器设计,降低服务器资源消耗
- 提高可扩展性,服务器可以更容易地处理大量并发请求
- 减少服务器故障时的数据丢失风险
缺点:
- 无法直接支持需要保持状态的Web应用(如购物车、用户登录等)
- 需要额外机制(如Cookie、Session)来实现状态管理
为了解决无状态带来的限制,Web开发中常采用以下技术:
- Cookie:在客户端存储少量数据
- Session:在服务器端保存用户状态
- Token:通过令牌验证用户身份
典型应用场景对比:
- 静态网页:无状态完全适用
- 动态网页:需要额外状态管理机制
- Web应用:必须实现某种形式的状态保持
2.1 session

Session(会话)是Web开发中用于跟踪用户状态的重要机制。以下是关于session的详细说明:
- 基本概念
- 服务器端存储的用户状态信息
- 每个用户拥有独立的session数据
- 通常通过cookie中的session ID来识别用户
- 工作原理
- 客户端首次访问时,服务器创建session
- 服务器生成唯一session ID并返回给客户端
- 客户端后续请求携带session ID
- 服务器根据ID查找对应的session数据
- 典型应用场景
- 用户登录状态维护
- 购物车功能实现
- 个性化设置存储
- 表单数据跨页面暂存
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响应
增强可维护性
- 统一修改响应格式时只需修改一处代码
- 方便添加全局的响应拦截和日志记录
- 便于实现响应数据的统一加密或签名
监控和统计
- 统一的格式便于收集和分析接口调用情况
- 可以基于响应码统计系统健康状态
- 示例:监控系统可以统计不同错误码的出现频率
实现方式
- 使用
@RestControllerAdvice创建全局响应处理器 - 自定义
ResponseEntity封装类 - 统一处理异常转换为标准格式
- 可结合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;
}
}