Springboot学习笔记 基础篇 配置文件 采用.yml
格式的配置文件相较于.property
格式的配置文件来说,层级关系更清晰
yml配置信息的书写与获取
使用注解进行值的获取
1 @ConfigurationProperties(prefix = "email")
注意:
值的前面必须有空格作为分隔符
使用空格作为缩进表示层级关系,相同层级左侧对齐
配置信息的获取
1 2 3 4 @Value("${键名}") @ConfigurationProperties(prefix = "email")
Springboot整合mybatis Bean注册
如果要注册的bean对象来自于第三方(不是自定义的),是无法使用@Component
及其衍生注解声明bean的
Bean扫描
Springboot默认扫描启动类所在包及其子包下的内容,如果要扫描其他文件夹,可以手动添加注解@ComponentScan
注册条件
自动配置原理 遵循约定大于配置的原则,在boot程序启动后,起步依赖中的一些bean对象会自动注入到ioc容器中
实战篇 环境搭建
准备工作:
创建数据库表
创建SpringBoot工程,引入对应的依赖(web、mybatis、mysql驱动)
配置文件application.yml中引入mybatis的配置信息
创建包结构,并准备实体类
包结构介绍如下:
config:对应相关的配置信息
controller:控制器存放的包
exception:全局异常
interceptors:拦截器
mapper:和数据库相关的
pojo:实体类相关的
service:接口和对应的实现类,核心功能都由其实现
utils:存放工具类,工具类中通常会提供静态方法供我们在项目中使用
功能:
注册
登录
获取用户详细信息
更新用户基本信息
更新用户头像
更新用户密码
故障修复
对于下面这个报错,只需要将mybatis的版本修改为3.0.3即可解决
Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation]
这个报错是因为没有在标准结果Result对象上面添加@Data
注解,就导致解析的时候无法正确的将其转换为json对象。
上面这个报错解决方案是在userMapper中的文件上添加注解
1 2 @Insert("insert into user(username,password,create_time,update_time) values(#{username},#{password},now(),now())") void add (@Param("username") String username, @Param("password") String password) ;
用户模块 注册 注册相关的api要求
定义统一的消息返回的类Result
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package org.itheima.pojo;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @AllArgsConstructor @NoArgsConstructor public class Result <T > { private Integer code; private String message; private T data; public static <E> Result<E> success (E data) { return new Result<>(0 , "操作成功" , data); } public static Result success () { return new Result(0 , "操作成功" , null ); } public static Result error (String message) { return new Result(1 , message, null ); } }
其中我们定义了三个静态方法:
success(E data)
success():操作成功方法,无需传入相关参数
error(String message)
注意这里使用了泛型,这样我们data类型就可以根据实际情况进行灵活的调整与使用了
注册接口 步骤:
编写UserController类
编写UserService接口
编写UserServiceImpl实现UserService接口
编写注册逻辑register方法
详细代码如下:
UserController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package org.itheima.controller;import jakarta.validation.constraints.Pattern;import org.itheima.pojo.Result;import org.itheima.pojo.User;import org.itheima.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.validation.annotation.Validated;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 private UserService userService; @PostMapping("/register") public Result register (String username, String password) { User u = userService.findByUserName(username); if (u == null ) { userService.register(username, password); return Result.success("注册成功" ); } else { return Result.error("用户名已被占用" ); } } }
UserService接口
1 2 3 4 5 6 7 8 9 10 11 package org.itheima.service;import org.itheima.pojo.User;import org.springframework.stereotype.Service;@Service public interface UserService { User findByUserName (String username) ; void register (String username, String password) ; }
编写UserServiceImpl实现UserService接口
这里的实现中引入了一个密码加密处理的工具类MD5Util,实际上MD5加密的方式是不安全的,可以通过撞库的方式进行密码的破解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package org.itheima.service.impl;import org.itheima.mapper.UserMapper;import org.itheima.pojo.User;import org.itheima.service.UserService;import org.itheima.utils.Md5Util;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class UserServiceImpl implements UserService { @Autowired 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 md5String = Md5Util.getMD5String(password); userMapper.add(username, md5String); } }
UserMapper类中注册方法:注意add方法需要添加注解@Param(“username”)和@Param(“password”),否则会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.gyz.demoregister.mapper;import com.gyz.demoregister.pojo.User;import org.apache.ibatis.annotations.Insert;import org.apache.ibatis.annotations.Mapper;import org.apache.ibatis.annotations.Param;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 (@Param("username") String username, @Param("password") String password) ; }
测试
注册校验 在上面的代码中,存在一定的缺陷,即用户名和密码需要进行一定的校验才能符合我们的要求,如下图所示密码要求是5-16位非空字符,在我们刚刚的代码中没有进行相关的校验,不符合要求,现在我们来进行改进。
这里我们使用Spring Validation ,这是Spring提供的一个参数校验框架,使用预定义的注解完成参数校验
步骤主要有三步:
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-validation</artifactId > </dependency >
1 public Result register (@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
在Controller类上添加@Validated注解
在控制器类上方添加@Validated
注解
1 2 @Validated public class UserController {
这时候我们进行测试:会发现报错500服务器内部错误,这样的响应格式明显是不符合我们要求统一的返回类型Result
这时候我们就可以定义一个全局异常处理器,用来处理参数校验失败的异常
使用注解@RestControllerAdvice
,方法上面需要添加注解@ExceptionHandler(Exception.class),返回值类型为Result
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.gyz.demoregister.exception;import com.gyz.demoregister.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 handlerException (Exception e) { e.printStackTrace(); return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage():"操作失败" ); } }
这里使用spring提供的StringUtils工具类判断错误信息是否为空,如果不为空则返回错误信息,如果为空则返回操作失败。
登录 请求路径 /user/login
请求方式 POST
请求参数username和password
响应数据:Result对象,其中data返回JWT
用户登录成功后,系统会自动下发JWT令牌,然后后续每次请求,都会在请求头header中携带Authorization
,值为JWT令牌
如果检测到用户未登录或者JWT过期或非法,则HTTP响应码为401
登录逻辑
首先判断用户名是否存在于数据库中
如果存在则进行密码校验
如果密码错误则返回账号名或密码错误
如果密码正确则返回JWT令牌
如果不存在则返回账号名或密码错误
注意claims为存储到jwt中的数据,同时这个数据后续也会存在ThreadLocal中方便在其他的DAO接口中进行复用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @PostMapping("/login") public Result login (String username, String password) { User loginUser = userService.findUserByName(username); if (loginUser == null ) { return Result.error("用户名或密码错误!" ); } 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("用户名或密码错误!" ); }
登录认证引入 例如我们登录过后,如果访问文章列表这样的信息的话,就需要携带我们登录过后的令牌才能够进行访问否则直接拒绝访问。例如下面的接口在我们没有登录的状态下是不能直接访问的。
令牌就是一段字符串
承载业务数据,减少后续请求查询数据库的次数【直接从令牌中获取用户的信息】
防篡改,保证信息的合法性和有效性
JWT 被.
分成三个部分
Header(请求头),记录令牌类型、签名算法等。{“alg” : “H256”, “type” : “JWT”}
Payload(有效载荷),携带一些自定义信息、默认信息等。例如{“id”: “1”, “username” : “zhangsan”}
Signature(签名),防止Token被篡改、确保安全性,将header、payload进行加密得到
jwt基于Base64进行编码,这样就算数据中个包含了中文也是可以正确显示的
生成jwt
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3MTI3MzM5OTh9.6cBZ03xMeLItFt4FWSM_Xe2lBcGklI8koRpZZINa-Zk
校验jwt token
如果篡改了头部和载荷部分的数据,那么验证失败
如果秘钥修改了,验证失败
token过期
使用jwt 引入依赖坐标,刷新Maven
1 2 3 4 5 <dependency > <groupId > com.auth0</groupId > <artifactId > java-jwt</artifactId > <version > 4.4.0</version > </dependency >
创建测试类,生成jwt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.gyz.cvmanage;import com.auth0.jwt.JWT;import com.auth0.jwt.JWTVerifier;import com.auth0.jwt.algorithms.Algorithm;import com.auth0.jwt.interfaces.Claim;import com.auth0.jwt.interfaces.DecodedJWT;import org.junit.jupiter.api.Test;import java.util.Date;import java.util.HashMap;import java.util.Map;public class JwtTest { @Test public void testGen () { Map<String, Object> claims = new HashMap<>(); claims.put("id" , 1 ); claims.put("username" , "张三" ); String token = JWT.create() .withClaim("user" , claims) .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12 )) .sign(Algorithm.HMAC256("itheima" )); System.out.println(token); } @Test public void testParse () { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3MTI5NTA3MTR9.2C1Ljtu7VJjjOyw5_vS8j0GFMxVXhPVmzhswSdUjxDo" ; JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("itheima" )).build(); DecodedJWT decodedJWT = jwtVerifier.verify(token); Map<String, Claim> claims = decodedJWT.getClaims(); System.out.println(claims); } }
1 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6IuW8oOS4iSJ9LCJleHAiOjE3MTI5NTA3MTR9.2C1Ljtu7VJjjOyw5_vS8j0GFMxVXhPVmzhswSdUjxDo
解密jwt
1 {exp=1712950714, user={"id":1,"username":"张三"}}
登录成功返回jwt
现在进行校验:即访问**/article/list**接口需要携带token请求头Authorization
才能获取对应的数据。
从请求头中获取数据可以使用注解@RequestHeader
1 Map<String, Object> claims = JwtUtil.parseToken(token);
这段解析的代码如果报错则说明解析失败,如果没有报错则解析成功,可以从其中获取我们所需要的数据
Ctrl+Alt+T idea快捷键
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @RestController @RequestMapping("/article") public class ArticleController { @GetMapping("/list") public Result<String> list (@RequestHeader(name = "Authorization") String token, HttpServletResponse response) { try { Map<String, Object> claims = JwtUtil.parseToken(token); return Result.success("所有文章数据" ); } catch (Exception e) { response.setStatus(401 ); return Result.error("未登录....." ); } } }
测试如下:
上面的代码的确实现了我们所需要的功能,但是存在一个缺陷,如果我们增加业务代码,就需要在代码中编写token校验的内容,这样的工作显然是比较麻烦的,后续为了方便所有的接口都进行jwt的校验,可以编写一个拦截器来进行拦截,如下图所示:
拦截器的实现步骤:
编写LoginInterceptor类实现HandlerInterceptor接口
重写preHandle方法:在请求到达处理器之前,可以用于权限验证、数据校验等操作。如果返回true,表示方放行继续执行后续代码;如果返回false,表示拦截,中断请求处理。
编写web配置类实现WebMvcConfigurer接口,注册我们写好的拦截器
拦截器LoginInterceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization" ); try { Map<String, Object> claims = JwtUtil.parseToken(token); return true ; } catch (Exception e) { response.setStatus(401 ); return false ; } } }
注册拦截器:这里需要注意添加注解@Configuration
,否则配置不会生效
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private LoginInterceptor loginInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor).excludePathPatterns( "/user/login" , "user/register" ); } }
获取用户详细信息 根据用户名获取用户详细信息,用户名从token中获取:
从请求头中获取token
调用jwt工具类对token进行解析
根据用户名查找用户
返回用户实例
1 2 3 4 5 6 7 8 @GetMapping("/userInfo") public Result<User> userInfo (@RequestHeader(name = "Authorization") String token) { Map<String, Object> map = JwtUtil.parseToken(token); String username = (String) map.get("username" ); User user = userService.findUserByName(username); return Result.success(user); }
测试1:不携带Authorization请求头,HTTP响应码为401未授权
测试2:携带Authorization请求头,得到结果:
这里需要注意:接口响应的数据中包含了密码,这显然是不符合要求的【密码属于机密信息需要进行隐藏】。Springboot提供了对应的注解@JsonIgnore
,
让springMVC把当前对象转换为json字符串的时候,忽略password,最终的json字符串就没有password这个属性了
1 2 3 4 5 6 7 8 9 10 11 12 @Data public class User { private Integer id; private String username; @JsonIgnore private String password; private String nickname; private String email; private String userPic; private LocalDateTime createTime; private LocalDateTime updateTime; }
重启项目,再次测试,得到正确的结果:
但是我们可以发现,返回的结果中创建时间和更新时间是null,但数据库中实际上是存在这个数据的,这是因为数据库中的字段是带下划线的,无法和驼峰命名进行自动转化,我们需要在application.yml
中添加相应的配置信息【来进行驼峰和下划线的自动转换】
1 2 3 mybatis: configuration: map-underscore-to-camel-case: true
重新测试,结果正确:
ThreadLocal优化 上面的代码中我们为了获取用户的信息,在userInfo接口中解析了jwt的token,但是实际上我们在拦截器中已经进行解析了;相当于这部分我们进行了重复的工作,因此我们需要对其进行优化,解决的方法就是使用ThreadLocal存储用户的信息
ThreadLocal提供线程的局部变量
用来存取数据:get()/set()
使用ThreadLocal存储的数据,线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ThreadLocalTest { @Test public void testThreadLocalSetAndGet () { ThreadLocal tl = new ThreadLocal(); new Thread(() -> { tl.set("11111111111111" ); System.out.println(Thread.currentThread().getName() + " : " + tl.get()); System.out.println(Thread.currentThread().getName() + " : " + tl.get()); System.out.println(Thread.currentThread().getName() + " : " + tl.get()); }, "蓝色线程" ).start(); new Thread(() -> { tl.set("22222222222222" ); System.out.println(Thread.currentThread().getName() + " : " + tl.get()); System.out.println(Thread.currentThread().getName() + " : " + tl.get()); System.out.println(Thread.currentThread().getName() + " : " + tl.get()); }, "红色线程" ).start(); } }
上面的代码中,创建了两个线程并在线程中分别存放了不同的数据,最终运行测试程序,发现最终两个线程存储的内容是相互隔离的。
ThreadLocal原理如下:两个用户的线程中的数据是相互隔离的,所以是线程安全的。
使用ThreadLocal对代码进行优化步骤十分简单:
引入ThreadLocalUtil工具类
在拦截器中添加数据到ThreadLocal中
在对应的业务代码中获取ThreadLocal中的数据
在afterCompletion
方法中清空ThreadLocal对象防止内存泄漏
拦截器中存储数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization" ); try { Map<String, Object> claims = JwtUtil.parseToken(token); ThreadLocalUtil.set(claims); return true ; } catch (Exception e) { response.setStatus(401 ); return false ; } }
userInfo
接口
直接从全局的ThreadLocal对象中获取数据即可
1 2 3 4 5 6 7 8 9 10 11 @GetMapping("/userInfo") public Result<User> userInfo () { Map<String, Object> map = ThreadLocalUtil.get(); String username = (String) map.get("username" ); User user = userService.findUserByName(username); return Result.success(user); }
更新用户基本信息
更新参数为nickname、email,需要根据id来进行更新,更新的时候需要注意更新时间
restful风格规定了我们进行更新的时候使用的方法为PUT
步骤如下:
完成上述工作后,仍然存在一个问题:参数的校验,这里我们更新的昵称的长度以及email的格式需要符合规范,所以这里需要进行参数的校验;但是由于这些参数都是在User实体类中的,所以需要借助于自带的注解来进行参数的校验
参数校验 更新信息参数校验 【实体参数校验】
需要在实体类的属性上面添加validation提供的注解
在参请求参数前面添加@Validated
注解
实体类属性上添加相应注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Data public class User { @NotNull private Integer 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; }
controller中添加注解 @Validated
注解
1 2 3 4 5 @PutMapping("/update") public Result update (@RequestBody @Validated User user) { userService.update(user); return Result.success(); }
测试更新成功:
nickname不能为空
邮箱地址不合法无法通过
更新用户头像
请求路径:/user/updateAvatar
PATCH:这个更新通常用于部分更新,比如本需求中只需要更新图片
这里也需要进行参数校验,可以使用@url进行校验
1 2 3 4 5 @PatchMapping("/updateAvatar") public Result updateAvatar (@RequestParam @URL String avatarUrl) { userService.updateAvatar(avatarUrl); return Result.success(); }
更新头像
这个报错是因为参数里面并没有这个值,需要自己手动进行获取,这里使用now()方法进行获取即可
更新成功
@URL注解可以自动对url地址进行校验
更新用户密码 请求接口 /user/updatePwd
PATCH:本需求中只需要更新用户密码
请求参数包括三个:
注意这里在进行密码更新的时候,id需要在ThreadLocal中进行获取
测试成功,并对错误接口进行测试
文章分类 总共有5个接口:
新增文章分类
文章分类列表
获取文章分类详情
更新文章分类
删除文章分类
新增文章分类
请求路径:/category
请求方式:POST
接口描述:该接口用于新增文章分类
请求数据样例
1 2 3 4 { "categoryName" :"人文" , "categoryAlias" :"rw" }
数据库表结构如下:
1 2 3 4 5 6 7 8 public class Category { private Integer id; private String categoryName; private String categoryAlias; private Integer createUser; private LocalDateTime createTime; private LocalDateTime updateTime; }
文章分类列表
请求参数:无
请求方式:GET
请求路径:/category
注意这个请求路径和新增文章分类是一样的,不同的是他们的请求方式不一样。
因为这里返回的数据是如下格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 { "code" : 0 , "message" : "操作成功" , "data" : [ { "id" : 3 , "categoryName" : "美食" , "categoryAlias" : "my" , "createTime" : "2023-09-02 12:06:59" , "updateTime" : "2023-09-02 12:06:59" }, { "id" : 4 , "categoryName" : "娱乐" , "categoryAlias" : "yl" , "createTime" : "2023-09-02 12:08:16" , "updateTime" : "2023-09-02 12:08:16" }, { "id" : 5 , "categoryName" : "军事" , "categoryAlias" : "js" , "createTime" : "2023-09-02 12:08:33" , "updateTime" : "2023-09-02 12:08:33" } ] }
经过观察可以发现这也就是Category实体类所对应的数据,所以我们在控制器中写我们方法的时候返回值需要使用Result的泛型:
1 2 3 4 5 @GetMapping public Result<List<Category>> list() { List<Category> cs = categoryService.list(); return Result.success(cs); }
这里通过categoryService中的list方法获取到一个List集合得到全部的数据。【注意:这里所得到的数据需要是当前用户所创建的数据所以在list方法中还需要从ThreadLocal中获取到用户id,然后在userMapper
中查询的时候再根据用户id去查询】
categoryServiceImpl.java
1 2 3 4 5 6 @Override public List<Category> list () { Map<String, Object> map = ThreadLocalUtil.get(); Integer userId = (Integer) map.get("id" ); return categoryMapper.list(userId); }
categoryMapper
1 2 @Select("select * from category where create_user=#{userId}") List<Category> list (Integer userId) ;
测试:
这里的时间格式有点问题,不是例如2024-4-17 11:01:10这样的,所以我们需要在实体类中添加一下注解规范一下时间的格式:
1 2 3 4 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime;
再次测试时间格式就正常了
获取文章分类详情
请求路径:/category/detail
请求方式:GET
接口描述:该接口用于根据ID获取文章分类详情
更新文章分类
请求路径:/category
请求方式:PUT
接口描述:该接口用于更新文章分类
id不能为空
categoryName不能为空
categoryAlias不能为空
这里需要注意请求体为json
格式的,我们需要进行校验,防止其中改的参数为空,所以我们需要在实体类Category中添加相应的校验规则
这里需要添加一个id不为空的校验规则,这样就会导致前面新增文章分类的时候出现id不能为空的报错,因为那个接口中不需要传递id参数,id是由MySQL主键进行自增的,所以这里引出一个规则分组校验
分组校验 把校验项进行归类分组,在完成不同的功能时,校验指定组中的校验项:
定义分组
定义校验项时制定归属的分组
校验时制定要校验的分组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Category { @NotNull(groups = Update.class) private Integer id; @NotEmpty(groups = {Add.class, Update.class}) private String categoryName; @NotEmpty(groups = {Add.class, Update.class}) private String categoryAlias; private Integer createUser; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updateTime; public interface Add { } public interface Update { } }
删除文章分类
请求路径:/category
请求方式:DELETE
接口描述:该接口用于根据ID删除文章分类
【已完成】
文章管理 发布文章(新增)
请求路径:/article
请求方式:POST
接口描述:该接口用于新增文章(发布文章)
请求参数为application/json格式
样例:
1 2 3 4 5 6 7 { "title" : "陕西旅游攻略" , "content" : "兵马俑,华清池,法门寺,华山...爱去哪去哪..." , "coverImg" : "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png" , "state" : "草稿" , "categoryId" : 2 }
这里需要注意参数的校验:
title为1-10个非空字符
coverImg为url地址
state必须是 已发布或者草稿
前两个都很好校验,state需要去编写自定义的校验规则注解:
文章列表查询(条件分页)
文件上传
文件上传如果发生同名的问题需要保证文件的名字是唯一的,防止发生文件覆盖,使用uuid对文件进行重命名
OSS 对象存储服务,Object Storage Service
融合OSS对象存储编写工具类
登录优化-Redis 令牌主动失效机制:
登录成功后,给浏览器响应令牌的同时,把该令牌存储到redis中
LoginInterceptor拦截器中,需要验证浏览器携带的令牌,并同时需要获取到redis中存储的与之相同的令牌
当用户修改密码成功后,删除redis中存储的旧令牌
集成Redis
引入依赖坐标
1 2 3 4 5 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-data-redis</artifactId > </dependency >
修改配置文件
1 2 3 4 data: redis: host: localhost port: 6379
令牌主动失效机制:
登录成功后给浏览器响应令牌的同时,把令牌存储到redis中
LoginInterceptor拦截器中,需要验证浏览器携带的令牌,并同时需要获取到redis中存储的与之相同的令牌
当用户修改密码成功后,删除redis中存储的旧令牌
Springboot项目部署
属性配置方式:
面试篇