springboot项目后端记录
- 前言
- 一、包
- 二、处理技巧
-
- [1. 全局异常处理](#1. 全局异常处理)
- [2. 拦截器(用于请求转发前的验证)](#2. 拦截器(用于请求转发前的验证))
- [3. sql表中下划线列匹配实体类小驼峰](#3. sql表中下划线列匹配实体类小驼峰)
- [4. 在mvc框架中,让实体类属性不传到前端(JsonIgnore注解)](#4. 在mvc框架中,让实体类属性不传到前端(JsonIgnore注解))
- [5. ThreadLocal处理taken数据重复解析](#5. ThreadLocal处理taken数据重复解析)
- [6. 实体类日期属性转json时格式处理](#6. 实体类日期属性转json时格式处理)
- [7. Validated包中分组校验实体类参数(不同方法对校验要求不同)](#7. Validated包中分组校验实体类参数(不同方法对校验要求不同))
- [8. Validated自定义校验注解](#8. Validated自定义校验注解)
- [9. 当前端传过来的参数不是必须的时候](#9. 当前端传过来的参数不是必须的时候)
- [10. 数据库分页查询](#10. 数据库分页查询)
- [11. springBoot中整合mybatis时mapper.xml文件存放位置](#11. springBoot中整合mybatis时mapper.xml文件存放位置)
- [12. 文件上传](#12. 文件上传)
- [13. 阿里oss对象存储服务存储文件](#13. 阿里oss对象存储服务存储文件)
- [14. 使用redis实现taken主动失效](#14. 使用redis实现taken主动失效)
-
- 实现步骤
-
- [① 添加redis的依赖](#① 添加redis的依赖)
- [② 在用户登陆的时候向redis中添加taken](#② 在用户登陆的时候向redis中添加taken)
- [③ 在拦截器中加入taken比对](#③ 在拦截器中加入taken比对)
- [④ 修改密码后删除redis中的taken](#④ 修改密码后删除redis中的taken)
- 三、关于项目环境方面的东西
-
- [1. 项目部署](#1. 项目部署)
- [2. 属性配置方法](#2. 属性配置方法)
-
- [(1) 项目内配置文件](#(1) 项目内配置文件)
- [(2) jar包统一目录的application.yml配置文件](#(2) jar包统一目录的application.yml配置文件)
- [(3) 环境变量](#(3) 环境变量)
- [(4) 命令行配置](#(4) 命令行配置)
- 优先级
- [3. 多环境管理](#3. 多环境管理)
-
- [(1) 单文件中的环境管理](#(1) 单文件中的环境管理)
- [(2) 多文件的环境](#(2) 多文件的环境)
- [(3) 环境分组](#(3) 环境分组)
- 参考
前言
用于记录springboot学习中没见过的注解技巧
一、包
1. lombok--自动生成勾子方法
作用
作用在类之上,自动生成类的 getter setter toString equal等方法
依赖
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
使用
Data注解自动实现getter setter toString equal等方法
Constructor类型注解用于自动实现构造函数
这些代码会出现在编译以后的class文件中,但是不会出现在源文件中
java
package com.xduzmh.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//统一响应结果
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {
private Integer code;//业务状态码 0-成功 1-失败
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);
}
}
2. Validated--自动校验
自动校验中的分组校验和自定义校验注解放在了第二章技巧里面
validated常用注解
作用
接收的是当个参数时,用于完成参数的格式校验, 如用户名长度格式要求
依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
使用
一般参数校验
在类之上先加注解@Validated
,告诉框架需要对这个类进行校验。
在需要校验的参数前面加@Patten
注解,注解的参数regexp里面写匹配的正则表达式
java
@Validated
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// 注册
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\s{5,16}$") String username, @Pattern(regexp = "^\\s{5,16}$") String password){
//判断是否存在
User user = userService.findByUserName(username);
if (null != user){
return Result.error("用户存在");
}else {
// 注册(需要对密码进行加密)
String md5String = Md5Util.getMD5String(username);
userService.register(username, md5String);
return Result.success();
}
}
也可以在需要被校验的参数前面加注解@Validated
java
@PostMapping("/register")
public Result register(@Validated @Pattern(regexp = "^\\S{5,16}$") String username,@Validated @Pattern(regexp = "^\\S{5,16}$") String password){
//判断是否存在
User user = userService.findByUserName(username);
if (null != user){
return Result.error("用户被占用");
}else {
// 注册(需要对密码进行加密)
String md5String = Md5Util.getMD5String(password);
userService.register(username, md5String);
return Result.success();
}
}
实体参数校验
需要校验的参数由一个实体类封装,这时需要对实体类的属性添加限制注解。在需要校验的实体参数前加@Validated注解
实体类
java
//Data注解来自lombok,可以自动生成类的 getter setter toString equal等方法
@Data
public class User {
@NotNull
private Integer id;//主键ID
private String username;//用户名
@JsonIgnore
private String password;//密码
@NotNull
@NotEmpty
@Pattern(regexp = "^\\S{1,10}$")
private String nickname;//昵称
@Email
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
参数校验使用的方法,加注解@Validated
到对应参数前面
java
@PutMapping("/update")
public Result update(@RequestBody @Validated User user){
userService.update(user);
return Result.success("更新成功");
}
结论
- 当参数是普通参数时Validated注解可以放在参数之前或者类之上都可以
- 参数是封装实体类的时候,Validated注解只能放在参数前
- 用在实体属性上的那些注解(@NotNull,@Email等)也可以直接用在参数前面
3. JWT(json web taken) 令牌生成
什么是taken
taken本质上是一个字符串
- 作用
- 给客户的身份验证,客户在登陆后获得taken,如何以后的每一次访问都需要带上taken,防止未登陆的用户可以访问系统内资源
- 要求
- 承载业务数据, 减少后续请求查询数据库的次数
- 防篡改, 保证信息的合法性和有效性
JWT包
JWT包内实现了JWT标准的taken封装和拆解。jwt令牌主要有三个部分组成。这几个部分原本的数据是json格式的,通过算个转换为了Base64(即常用的64个字符,包括大小写字母,数字等)。
- 头包含加密算法类型。
- 中间这段是承载的信息,一般是客户端的id和用户名等非敏感信息(这样可以较少对承载的信息的查询)
- 最后一段数组签名是为了防止数据被篡改,对头部和载荷进行加密计算得来
依赖
xml
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<!-- 用于Taken令牌生成-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
使用
获取taken
java
public void jwtTest(){
Map<String, Object> user = new HashMap<>();
user.put("username", "zhangsan");
user.put("ID", 1);
String jwt = JWT.create().withClaim("user", user)
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
.sign(Algorithm.HMAC256("zhangsan"));
System.out.println(jwt);
}
校验
加密的密钥不能错,校验失败会抛出异常,使用时通过捕捉异常进行判断
java
@Test
public void jwtDecodeJwt(){
String taken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" +
".eyJ1c2VyIjp7IklEIjoxLCJ1c2VybmFtZSI6InpoYW5nc2FuIn0sImV4cCI6MTcyNTU5MTA5MX0" +
".ihpqakbm6OTg5dVcVPmqGea7izW2Fa4TQyUlqcFH9GI";
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("zhangsan")).build();
//校验失败会抛出异常,所以在使用时需要对异常进行处理
DecodedJWT decodedJWT = jwtVerifier.verify(taken);
Map<String, Claim> claims = decodedJWT.getClaims();
System.out.println(claims.get("user"));
}
封装的工具类
java
package com.xduzmh.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
private static final String KEY = "itheima";
//接收业务数据,生成token并返回
public static String genToken(Map<String, Object> claims) {
return JWT.create()
.withClaim("claims", claims)
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
.sign(Algorithm.HMAC256(KEY));
}
//接收token,验证token,并返回业务数据
public static Map<String, Object> parseToken(String token) {
return JWT.require(Algorithm.HMAC256(KEY))
.build()
.verify(token)
.getClaim("claims")
.asMap();
}
}
xml
使用
二、处理技巧
1. 全局异常处理
作用
用于捕捉异常,比如在使用validation校验失败时会产生异常,这时就需要进行全局异常的捕捉
代码
java
package com.xduzmh.exception;
import com.xduzmh.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 exceptionHandler(Exception e){
e.printStackTrace();
return Result.error(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "操作失败");
}
}
StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "操作失败"
主要是处理没有错误信息的情况。
2. 拦截器(用于请求转发前的验证)
作用
在指定接收到请求时会先传给拦截器,如果符合要求则通过请求,不符合则拦截。
可以用来对taken进行校验,拦截taken不符的请求
代码
实现拦截器对象
需要实现HandlerInterceptor 接口的preHandle方法,返回true放行,返回false拦截,请求参数在request对象中,返回值可以在response设置
一般情况下会创建interceptors包,所有拦截器都放在这个包下面
记得在类上加Component注解,把对象注入容器
java
package com.xduzmh.interceptors;
import com.xduzmh.utils.JwtUtil;
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 {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String taken = request.getHeader("Authorization");
try {
Map<String, Object> claims = JwtUtil.parseToken(taken);
} catch (Exception e) {
response.setStatus(401);
return false;
}
return true;
}
}
注册拦截器
拦截器需要在web配置类中添加, web配置了需要实现接口WebMvcConfigurer下的addInterceptors方法来添加拦截器。
可以用excludePathPatterns("/user/register","/user/login")来放行方法配置类放在config包下
记得在类上加Configuration注解,声明是配置类
java
package com.xduzmh.config;
import com.xduzmh.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 {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(loginInterceptor)
.excludePathPatterns("/user/register","/user/login");
}
}
3. sql表中下划线列匹配实体类小驼峰
在实体类中属性是小驼峰命名,表中是下划线命名,这时需要进行转换
只需要在spring配置文件中开启mybatis转换即可
yml
mybatis:
configuration:
map-underscore-to-camel-case: true
4. 在mvc框架中,让实体类属性不传到前端(JsonIgnore注解)
在实体类中可能存在一些私密信息,传到前端就会暴露隐私,如密码等字段,这种情况下springboot提供了对应的注解,只需要在实体类对应属性之上加入注解@JsonIgnore,就可以在对象转json时忽略这个属性
java
@Data
public class User {
private Integer id;//主键ID
private String username;//用户名
@JsonIgnore
private String password;//密码
private String nickname;//昵称
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
5. ThreadLocal处理taken数据重复解析
Tomcat服务器会为每个用户开辟单独的线程,所有可以用ThreadLocal来存储需要频繁使用的数据,如用户名等属性
如前所说的,用户名username可以从taken中解析出来,但是每次用都要进行解析就十分的麻烦且费事,我们就可以把username放在一个全局的ThreadLocal中,需要的时候直接get()。
ThreadLocal存数据的时候可以在拦截器里面存,因为需要taken的请求都会过经过拦截器。直接在拦截器里面添加即可。
操作ThreadLocal的工具类
java
package com.xduzmh.utils;
import java.util.HashMap;
import java.util.Map;
/**
* ThreadLocal 工具类
*/
@SuppressWarnings("all")
public class ThreadLocalUtil {
//提供ThreadLocal对象,
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
//根据键获取值
public static <T> T get(){
return (T) THREAD_LOCAL.get();
}
//存储键值对
public static void set(Object value){
THREAD_LOCAL.set(value);
}
//清除ThreadLocal 防止内存泄漏
public static void remove(){
THREAD_LOCAL.remove();
}
}
使用
在拦截器中向ThreadLocal增加数据,把taken解析出来的数就存到ThreadLocal中
java
@Component
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String taken = request.getHeader("Authorization");
try {
Map<String, Object> claims = JwtUtil.parseToken(taken);
ThreadLocalUtil.set(claims);
} catch (Exception e) {
response.setStatus(401);
return false;
}
return true;
}
数据获取
java
@GetMapping("/userInfo")
public Result<User> userInfo(){
Map<String, Object> claims = ThreadLocalUtil.get();
String username = (String) claims.get("username");
User user = userService.findByUserName(username);
return Result.success(user);
}
ThreadLocal数据移除时机
ThreadLocal是全局的,如果只添加而不移除就会造成内存泄漏
移除的时机是这次请求结束的时候,请求结束的处理需要在拦截器类里面实现对应的afterCompletion接口方法
java
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String taken = request.getHeader("Authorization");
try {
Map<String, Object> claims = JwtUtil.parseToken(taken);
ThreadLocalUtil.set(claims);
} catch (Exception e) {
response.setStatus(401);
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ThreadLocalUtil.remove();
}
}
6. 实体类日期属性转json时格式处理
只需要在实体类对应的日期属性上加JsonFormat注解,并指定格式即可
java
public class Category {
private Integer id;//主键ID
@NotEmpty
private String categoryName;//分类名称
@NotEmpty
private String categoryAlias;//分类别名
private Integer createUser;//创建人ID
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
加和不加的区别
7. Validated包中分组校验实体类参数(不同方法对校验要求不同)
这里的主要场景是用于不同方法对同一个属性参数的要求可能不同,如ID字段,有的方法没有要求,有的方法要求非空,这时就不能同时满足两个方法,这时就需要对校验进行分组
实现
①定义组接口
组接口是实现在实体类里面的public接口
java
package com.xduzmh.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import java.time.LocalDateTime;
//Data注解来自lombok,可以自动生成类的 getter setter toString equal等方法
@Data
public class Category {
public interface Add {
}
public interface Update {
}
@NotNull
private Integer id;//主键ID
@NotEmpty
private String categoryName;//分类名称
@NotEmpty
private String categoryAlias;//分类别名
private Integer createUser;//创建人ID
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")
private LocalDateTime updateTime;//更新时间
}
②给注解添加分组
如果 id 属性的@NotNull属于Update 组的就只需要指定一下@NotNull的group属性=Update .class。
这样,只有在指定Validated注解使用Update 这个组时,id才有NotNull的限制
java
package com.xduzmh.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import java.time.LocalDateTime;
//Data注解来自lombok,可以自动生成类的 getter setter toString equal等方法
@Data
public class Category {
public interface Add {
}
public interface Update {
}
@NotNull(groups = Update.class)
private Integer id;//主键ID
@NotEmpty(groups = {Update.class, Add.class})
private String categoryName;//分类名称
@NotEmpty(groups = {Update.class, Add.class})
private String categoryAlias;//分类别名
private Integer createUser;//创建人ID
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")
private LocalDateTime updateTime;//更新时间
}
③使用时在Vilidated注解中赋值分组
java
// 增加
@PostMapping
public Result add(@RequestBody @Validated(Category.Add.class) Category category){
categoryService.add(category);
return Result.success();
}
// 更新分类名和别名
@PutMapping
public Result update(@RequestBody @Validated(Category.Update.class) Category category){
categoryService.update(category);
return Result.success();
}
默认分组的情况
上面的写法中,如果不指定组,那sping会默认分配给Default组。
分组是可以继承的,所有可以通过继承的方式优化代码
java
@Data
public class Category {
public interface Add extends Default {
}
public interface Update extends Default{
}
@NotNull(groups = Update.class)
private Integer id;//主键ID
@NotEmpty
private String categoryName;//分类名称
@NotEmpty
private String categoryAlias;//分类别名
private Integer createUser;//创建人ID
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH-mm-ss")
private LocalDateTime updateTime;//更新时间
}
对于上面的代码,相当于
@NotEmpty private String categoryName;//分类名称 @NotEmpty private String categoryAlias;//分类别名
属于默认分组,也就相当于这两个以及包含在了Add组和Update组里面了。
当Validated注解没有指定参数时,使用的是默认分组Default
8. Validated自定义校验注解
注解一般防止anno包下,校验方法实现类放在validation包下
创建注解
java
package com.xduzmh.anno;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.FIELD}) //元注解,声明注解作用位置
@Retention(RetentionPolicy.RUNTIME) //元注解,声明注解保留时间
@Constraint(validatedBy = StateValidation.class) //声明校验处理方法的类
public @interface State {
String message() default "{状态只能是\"草稿\"和\"已发布\"}"; //校验失败的提示信息
Class<?>[] groups() default {}; //注解的分组信息
Class<? extends Payload>[] payload() default {}; //注解的附加数据,一般不用
}
创建校验方法类
校验类需要实现接口ConstraintValidator<注解, 需要校验数据类型>的isValid方法(true校验通过,false校验失败)
java
package com.xduzmh.validation;
import com.xduzmh.anno.State;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
public class StateValidation implements ConstraintValidator<State, String> {
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if(s == null){
return false;
}
if (s.equals("已发布") || s.equals("草稿")){
return true;
}
return false;
}
}
9. 当前端传过来的参数不是必须的时候
当前端传过来的参数不是必须的时候, 可以在参数前指定注解`@RequestParam(required = false)
java
@GetMapping
public Result<PageBean<Article>> list(
Integer pageNum,
Integer pageSize,
@RequestParam(required = false) Integer categoryId,
@RequestParam(required = false) String state
){
PageBean<Article> res = articleService.list(pageNum, pageSize, categoryId, state);
return Result.success(res);
}
10. 数据库分页查询
mybatis的分页查询需要使用到插件pagehelper
依赖
xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
使用方法
java
PageHelper.startPage(pageNum, pageSize);
参数pageNum
表示要查询第几页
pageSize
表示每页存几条数据
这样就算开启了分页插件
java
List<Article> as = articleMapper.list(categoryId, state,userId);
Page<Article> page = (Page<Article>) as;
pageBean.setTotal(page.getTotal());
pageBean.setItems(page.getResult());
调用数据集查询后得到的结果要强转成Page类,这样才能获取到查询结果的一些信息,如总共查询到几条数据等
具体结果表示
11. springBoot中整合mybatis时mapper.xml文件存放位置
动图SQL参看mybatis框架
12. 文件上传
从前端上传的文件会被springBoot保存在MultipartFile类的对象中
对象方法
java
String getOriginalFilename(); //获取原始文件名
void transferTo(File dest); //将接收的文件转存到磁盘文件中
long getSize(); //获取文件的大小,单位:字节
byte[] getBytes(); //获取文件内容的字节数组
InputStream getInputStream(); //获取接收到的文件内容的输入流
实现
java
package com.xduzmh.controller;
import com.github.pagehelper.util.MSUtils;
import com.xduzmh.pojo.Result;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
@RestController
public class FileUploadContoller {
@PostMapping("/upload")
public Result<String> upload(MultipartFile file) throws IOException {
//获取文件后缀名
String filenamesuffix = file.getOriginalFilename().
substring(file.getOriginalFilename().lastIndexOf("."));
//通过uuid生成随机字符串当做文件名,防止同名图片覆盖
String filename = UUID.randomUUID().toString();
System.out.println(filenamesuffix + filename);
//把文件存在本地磁盘. 如果这里有oss服务,可以调用服务把图片存在oss服务器上
file.transferTo(new File("C:\\Users\\Administrator\\Desktop\\pic\\"
+ filename + filenamesuffix));
return Result.success("图片的url");
}
}
13. 阿里oss对象存储服务存储文件
使用第三方服务的思路
- 准备工作
- 开通服务,获取相关数据(密钥等数据)
- 参照官方SDK编写入门程序
- 看官方案例
- 集成使用
- 参考官方案例写自己的代码
14. 使用redis实现taken主动失效
为什么要让令牌主动失效
- 当你泄露密码时,别人用你的密码登陆了服务获得了一个taken
- 你知道情况后立即修改了密码
- 你虽然修改了密码,但是别人已经登陆了,有了taken,就可以用这个taken去访问你的资源
实现步骤
- 在用户登陆的时候向redis中添加taken
- 在每次访问资源时都会经过的拦截器中加入taken的校验
- 如果当前redis中存在taken,则通过,否则说明修改了密码
- 修改密码时删除redis中的taken
① 添加redis的依赖
xml
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.2.4</version>
</dependency>
在使用时调用stringRedisTemplate
对象中的方法获得对redis的操作
Springboot会自动帮我们注入一个StringRedisTemplate对象
② 在用户登陆的时候向redis中添加taken
java
// 登录
@PostMapping("/login")
public Result login(@Pattern(regexp = "^\\S{5,16}$") String username,
@Pattern(regexp = "^\\S{5,16}$") String password){
User user = userService.findByUserName(username);
//用户不存在
if (null == user){
return Result.error("用户名错误");
}
// 存在的情况下验证密码是否正确,正确的话赋予令牌
if(user.getPassword().equals(Md5Util.getMD5String(password))){
//密码一致登录成功, 授予登录成功的客户端taken
Map<String, Object> claims = new HashMap<>();
claims.put("id", user.getId());
claims.put("username", user.getUsername());
String taken = JwtUtil.genToken(claims);
//添加到redis
stringRedisTemplate.opsForValue().set(taken,taken,12, TimeUnit.HOURS);
return Result.success(taken);
}
//密码错误
return Result.error("密码错误");
}
③ 在拦截器中加入taken比对
④ 修改密码后删除redis中的taken
java
// 更新密码
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String, String> params, @RequestHeader("Authorization") String taken){
String oldPwd = params.get("old_pwd");
String newPwd = params.get("new_pwd");
String rePwd = params.get("re_pwd");
//非空判断
if(!StringUtils.hasLength(oldPwd) || !StringUtils.hasLength(newPwd) || !StringUtils.hasLength(rePwd)){
return Result.error("密码不能为空");
}
// 原密码比对
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginuser = userService.findByUserName(username);
if (!loginuser.getPassword().equals(Md5Util.getMD5String(oldPwd))){
return Result.error("密码不正确");
}
// 对比两次密码是否一致
if(!newPwd.equals(rePwd)){
return Result.error("两次输入密码不一致");
}
//修改密码
userService.updatePwd(newPwd);
//删除redis中旧的taken
stringRedisTemplate.opsForValue().getOperations().delete(taken);
return Result.success("密码修改成功");
}
三、关于项目环境方面的东西
1. 项目部署
项目部署需要把项目打包成jar包,放在服务器上,通过
java -jar 包名
运行jar包
在项目打包时需要配置springboot的项目打包插件
xml
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
然后执行maven的package指令,打包好的jar包在target目录下,把jar包放在服务器下通过java -jar 包名
运行
当然运行的环境必须要有JRE且版本必须是java17及其以上
2. 属性配置方法
属性就是一些写在配置文件里面的参数如数据库的连接信息等
yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
(1) 项目内配置文件
这些信息一般都是写在spingboot的配置文件中
yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/big_event
username: root
password: 123456
mybatis:
configuration:
map-underscore-to-camel-case: true
mapper-locations: /mapper/*.xml
(2) jar包统一目录的application.yml配置文件
在jar包同一目录下添加application.yml配置文件,写法和项目内的配置文件是一样的
(3) 环境变量
直接把要配置的参数加到环境变量中
变量名就是属性名,中间用
.
隔开
(4) 命令行配置
这种配置方法是在执行java命令时把属性当作参数传进去
java -jar 包名 --属性名=属性值
优先级
上面四种方法优先级逐渐增加
命令行 > 环境变量 > 外部配置文件 > 内部配置文件
使了一下这些配置能不能兼容
测试方法在内部配置文件配置了web访问路径是 /aaa。在执行命令行是没有管web访问路径,发现访问路径没有发生改变。
这说明各个配置方式不兼容
初学者,出现错误希望告知笔者,谢谢
3. 多环境管理
在项目开发过程中往往会有不同的开发阶段(开发,测试,发布),不同的开发阶段往往使用了不同的配置环境。这就需要进行环境的频繁切换。频繁的修改配置文件中的很多属性,这就很麻烦。
springboot提供了多环境管理机制
(1) 单文件中的环境管理
在单个文件中的环境管理,各个环境是通过
---
分隔开的。公共的配置往往写在最开始,便于查找修改,如何才是各个环境的配置
环境名称的指定通过属性
spring.config.activate.on-profile:环境名称
指定在选择生效环境时只需要配置
spring.profiles active: 环境名称
yml
# 公共部分
mybatis:
configuration:
map-underscore-to-camel-case: true
mapper-locations: /mapper/*.xml
server:
servlet:
context-path: /a
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/big_event
username: root
password: 123456
profiles:
active: test
# 开发环境
---
spring:
config:
activate:
on-profile: dev
server:
port: 8081
# 测试环境
---
spring:
config:
activate:
on-profile: test
server:
port: 8082
# 发布环境
---
spring:
config:
activate:
on-profile: pro
server:
port: 8083
(2) 多文件的环境
在单文件管理中,如果很多个环境放在一个文件中就会很难管理,所以springboot也提供了多文件的环境管理。
只需要在命名文件为
application-环境名.yml
注意,文件名一定要是application-环境名.yml
,环境名不区分大小写,但是一定要对,一定要和配置文件里面配置的环境名字一样
application的内容
yml
# 公共部分
mybatis:
configuration:
map-underscore-to-camel-case: true
mapper-locations: /mapper/*.xml
server:
servlet:
context-path: /a
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/big_event
username: root
password: 123456
profiles:
active: test
application-dev.yml内容
yml
# 开发环境
---
spring:
config:
activate:
on-profile: dev
server:
port: 8081
(3) 环境分组
在多文件配置的情况下又会出现某个环境的配置很多的情况下,这时又需要对环境的配置进行划分
application.xml配置
下面展示一下dev环境的内容
application.yml
yml
spring:
profiles:
active: test
group:
"dev": devDB, devServer, devSelf
"test" : testServer,testDB
application-devDB.yml
yml
mybatis:
configuration:
map-underscore-to-camel-case: true
mapper-locations: /mapper/*.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://locahost:3306/big_event
username: root
password: 123456
application-devServer.yml
yml
server:
port: 8081
application-devSelf.yml是空文件什么都没写
参考
@RestControllerAdvice作用及原理validated常用注解
mapper.xml存放位置
mybatis框架