springboot实战篇1
1. 环境搭建
执行资料中的big_event.sql脚本,准备数据库表
创建springboot工程,引入对应的依赖(web、mybatis、mysql驱动)
配置文件application.yml中引入mybatis的配置信息
创建包结构,并准备实体类
1.1准备数据库
-- 使用数据库
use big_event;
alter database big_event character set utf8;
-- 用户表
create table user (id int unsigned primary key auto_increment comment 'ID',username varchar(20) not null unique comment '用户名',password varchar(32) comment '密码',nickname varchar(10) default '' comment '昵称',email varchar(128) default '' comment '邮箱',user_pic varchar(128) default '' comment '头像',create_time datetime not null comment '创建时间',update_time datetime not null comment '修改时间'
) comment '用户表';-- 分类表
create table category(id int unsigned primary key auto_increment comment 'ID',category_name varchar(32) not null comment '分类名称',category_alias varchar(32) not null comment '分类别名',create_user int unsigned not null comment '创建人ID',create_time datetime not null comment '创建时间',update_time datetime not null comment '修改时间',constraint fk_category_user foreign key (create_user) references user(id) -- 外键约束
);
-- 文章表
create table article(id int unsigned primary key auto_increment comment 'ID',title varchar(30) not null comment '文章标题',content varchar(10000) not null comment '文章内容',cover_img varchar(128) not null comment '文章封面',state varchar(10) default '草稿' comment '文章状态: 只能是[已发布] 或者 [草稿]',category_id int unsigned comment '文章分类ID',create_user int unsigned not null comment '创建人ID',create_time datetime not null comment '创建时间',update_time datetime not null comment '修改时间',constraint fk_article_category foreign key (category_id) references category(id),-- 外键约束constraint fk_article_user foreign key (create_user) references user(id) -- 外键约束
)
1.2创建springboot项目 导入对应的依赖:
<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 http://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>3.2.3</version></parent><groupId>com.itheima</groupId><artifactId>big-event</artifactId><version>1.0-SNAPSHOT</version><packaging>jar</packaging><name>big-event</name><url>http://maven.apache.org</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies>
<!-- web依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- mybatis依赖--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.4</version></dependency><!-- mysql驱动--><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.1</version><scope>test</scope></dependency><!-- lombok依赖--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!-- validator依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!-- java-jwt坐标--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency>
<!-- 单元测试的坐标--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies></project>
1.3appliction.yml中引入mybatis的配置信息:
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/big_eventusername: rootpassword: 1234mybatis:configuration:map-underscore-to-camel-case: true # 开启驼峰命名和下划线命名的转换
1.4创建包结构,准备实体类:
1.5编写启动类
package com.itheima;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;/*** Hello world!**/
@SpringBootApplication
@MapperScan("com.itheima.mapper")
public class BigEventAppliction
{public static void main( String[] args ){SpringApplication.run(BigEventAppliction.class,args);}
}
2.注册接口的开发:
Result
2.1controller.UserController:
@Validated
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("/register")public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\ S{5,16}$") String password) {User u = userService.findByUsername(username);if (u == null){//没有占用//注册userService.register(username,password);return Result.success();}else {//占用return Result.error("用户名已被占用");}}}
2.2service.impl.UserServiceImpl
package com.itheima.service.impl;import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import com.itheima.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic User findByUsername(String username) {User u = userMapper.findByUserName(username);return u;}@Overridepublic void register(String username, String password) {//加密String md5String = Md5Util.getMD5String(password);//添加userMapper.add(username,md5String);}
}
2.3mapper.UserMapper
package com.itheima.mapper;import com.itheima.pojo.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@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},#{password},now(),now())")void add(String username, String password);
}
2.4接口测试工具Apifox
- 具体操作可以查看Apifox帮助文档:
Apifox界面:
数据库界面:注册成功
注意在Result类中需要添加@Data注解
2.5参数校验:
参数过于繁琐的解决方法:
Spring Validation
Spring 提供的一个参数校验框架,使用预定义的注解完成参数校验
2.5.1使用步骤:
-
- 引入Spring Validation 起步依赖
-
- 在参数前面添加@Pattern注解
-
- 在Controller类上添加@Validated注解
<!-- validator依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password
2.6 全局异常处理器:
exception. GlobalExceptionHandler
package com.itheima.exception;import com.itheima.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)public Result handleExcpetion(Exception e){e.printStackTrace();return Result.error(StringUtils.hasLength(e.getMessage())?e.getMessage():"操作失败");}
}
3.登录
3.1UserController
@PostMapping("/login")public Result login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password){//根据y用户名查询用户信息User loginUser = userService.findByUsername(username);//判断用户是否存在if(loginUser == null){//不存在return Result.error("用户名错误");}//判断密码是否正确,LoginUser对象中的password是密文if(Md5Util.getMD5String(password).equals(loginUser.getPassword())){Map<String,Object> claims = new HashMap<>();claims.put("id",loginUser.getId());claims.put("username",loginUser.getUsername());String token = JwtUtil.genToken(claims);return Result.success(token);}return Result.error("密码错误");}
3.2登录认证:
一般情况下要先完成登录接口完成之后才能进入其他接口,所以其他接口就需要在提供服务之前,对登录状态进行一个检查–登录认证;
3.2.1先创建article类;
package com.itheima.controller;import com.itheima.pojo.Result;
import com.itheima.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;import java.util.Map;@RestController
@RequestMapping("/article")
public class ArticleController {@GetMapping("/list")public Result<String> list(/*@RequestHeader(name = "Authorization")String token, HttpServletResponse response*/){
// //验证token
// try {
// Map<String,Object> claims = JwtUtil.parseToken(token);
// return Result.success("文章列表查询成功");
// } catch (Exception e) {
// response.setStatus(401);
// return Result.error("未登录");
// }return Result.success("文章列表查询成功");}
}
解决方法:令牌
令牌就是一段字符串
- 承载业务数据, 减少后续请求查询数据库的次数
- 防篡改, 保证信息的合法性和有效性
JWT令牌
1.令牌的生成;
- 导入依赖:
<!-- java-jwt坐标--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency>
- 生成JWT
@Testpublic void testGen(){Map<String,Object> claims = new HashMap<>();claims.put("id",1);claims.put("username","张三");//生成jwt代码String token = JWT.create().withClaim("user",claims)//添加载荷.withExpiresAt(new Date(System.currentTimeMillis()+1000*60*60*12))//设置过期时间.sign(Algorithm.HMAC256("itheima"));//设置签名算法和密钥System.out.println(token);}
- 验证:
@Testpublic void testParse(){//定义字符串,模拟用户传递过来的tokenString token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +".eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3NTMwNjk2OTh9" +".kG5LboxQ_56hrtRxc0uETTVgjrpN_ngWA5ush1scB8M";JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("itheima")).build();DecodedJWT decodedJWT = jwtVerifier.verify(token);//验证token,生成一个解析后的JWT对象Map<String, Claim> claims = decodedJWT.getClaims();System.out.println(claims.get("user "));//如果纂改了头部和载荷部分的数据,那么验证失败//如果密钥改了 验证失败//如果token过期了 验证失败}
注意:JWT验证时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。
如果JWT令牌解析时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法。
@PostMapping("/login")public Result login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password){//根据y用户名查询用户信息User loginUser = userService.findByUsername(username);//判断用户是否存在if(loginUser == null){//不存在return Result.error("用户名错误");}//判断密码是否正确,LoginUser对象中的password是密文if(Md5Util.getMD5String(password).equals(loginUser.getPassword())){Map<String,Object> claims = new HashMap<>();claims.put("id",loginUser.getId());claims.put("username",loginUser.getUsername());String token = JwtUtil.genToken(claims);return Result.success(token);}return Result.error("密码错误");}@GetMapping("/userInfo")public Result<User> userInfo(/*@RequestHeader(name = "Authorization")String token*/){//根据用户名查询用户
// Map<String,Object> map = JwtUtil.parseToken(token);
// String username = (String) map.get("username");Map<String,Object> map = ThreadLocalUtil.get();String username = (String) map.get("username");User user = userService.findByUsername(username);return Result.success(user);}
拦截器的完成
因为后续会有多个接口完成相同的操作那么就可以使用拦截器来统一进行操作;
具体实现代码:
1.拦截器的代码:interceptors.LoginInterceptor:
package com.itheima.interceptors;import com.itheima.pojo.Result;
import com.itheima.utils.JwtUtil;
import com.itheima.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import java.util.Map;@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception{//令牌的验证String token = request.getHeader("Authorization");//验证tokentry {Map<String,Object> claims = JwtUtil.parseToken(token);//把业务数据存储到ThreadLocal中ThreadLocalUtil.set(claims);//放行return true;} catch (Exception e) {response.setStatus(401);//不放行return false;}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清空Threadlocal的数据ThreadLocalUtil.remove();}}
2.注册拦截器:config.WebConfig:
package com.itheima.config;import com.itheima.interceptors.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 WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry){//登录接口和注册接口不拦截registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login","/user/register");}
}
4、获取用户详细信息
4.1UserController
@GetMapping("/userInfo")public Result<User> userInfo(/*@RequestHeader(name = "Authorization")String token*/){//根据用户名查询用户
// Map<String,Object> map = JwtUtil.parseToken(token);
// String username = (String) map.get("username");Map<String,Object> map = ThreadLocalUtil.get();String username = (String) map.get("username");User user = userService.findByUsername(username);return Result.success(user);}
4.2在Apifox整体添加请求头:
Autheriation是请求头的名字,冒号后面是值
4.3响应的时候不让某些元素显示:
4.4注意开启驼峰命名和下划线命名的转换
mybatis:configuration:map-underscore-to-camel-case: true # 开启驼峰命名和下划线命名的转换
5.优化代码
ThreadLocal
提供线程局部变量
用来存取数据: set()/get()
使用ThreadLocal存储的数据, 线程安全
1.在LoginInterceptor中存入ThreadLocal
Map<String,Object> claims = JwtUtil.parseToken(token);//把业务数据存储到ThreadLocal中ThreadLocalUtil.set(claims);
2.在UserController使用
@GetMapping("/userInfo")public Result<User> userInfo(/*@RequestHeader(name = "Authorization")String token*/){//根据用户名查询用户
// Map<String,Object> map = JwtUtil.parseToken(token);
// String username = (String) map.get("username");Map<String,Object> map = ThreadLocalUtil.get();String username = (String) map.get("username");User user = userService.findByUsername(username);return Result.success(user);}
3.清空ThreadLocal中的数据 防止数据泄露
@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清空Threadlocal的数据ThreadLocalUtil.remove();}