springboot项目--后端问题记录

news2024/9/21 1:13:14

springboot项目后端记录

  • 前言
  • 一、包
    • 1. lombok--自动生成勾子方法
      • 作用
      • 依赖
      • 使用
    • 2. Validated--自动校验
      • 作用
      • 依赖
      • 使用
        • 一般参数校验
        • 实体参数校验
      • 结论
    • 3. JWT(json web taken) 令牌生成
      • 什么是taken
      • JWT包
        • 依赖
        • 使用
          • 获取taken
          • 校验
        • 封装的工具类
        • 使用
  • 二、处理技巧
    • 1. 全局异常处理
        • 作用
        • 代码
    • 2. 拦截器(用于请求转发前的验证)
        • 作用
        • 代码
    • 3. sql表中下划线列匹配实体类小驼峰
    • 4. 在mvc框架中,让实体类属性不传到前端(JsonIgnore注解)
    • 5. ThreadLocal处理taken数据重复解析
        • 操作ThreadLocal的工具类
        • 使用
      • ThreadLocal数据移除时机
    • 6. 实体类日期属性转json时格式处理
    • 7. Validated包中分组校验实体类参数(不同方法对校验要求不同)
      • 实现
        • ①定义组接口
        • ②给注解添加分组
        • ③使用时在Vilidated注解中赋值分组
        • 默认分组的情况
    • 8. Validated自定义校验注解
      • 创建注解
      • 创建校验方法类
    • 9. 当前端传过来的参数不是必须的时候
    • 10. 数据库分页查询
      • 依赖
      • 使用方法
    • 11. springBoot中整合mybatis时mapper.xml文件存放位置
    • 12. 文件上传
    • 13. 阿里oss对象存储服务存储文件
      • 使用第三方服务的思路
    • 14. 使用redis实现taken主动失效
      • 实现步骤
        • ① 添加redis的依赖
        • ② 在用户登陆的时候向redis中添加taken
        • ③ 在拦截器中加入taken比对
        • ④ 修改密码后删除redis中的taken
  • 三、关于项目环境方面的东西
    • 1. 项目部署
    • 2. 属性配置方法
      • (1) 项目内配置文件
      • (2) jar包统一目录的application.yml配置文件
      • (3) 环境变量
      • (4) 命令行配置
      • 优先级
    • 3. 多环境管理
      • (1) 单文件中的环境管理
      • (2) 多文件的环境
      • (3) 环境分组
  • 参考


前言

用于记录springboot学习中没见过的注解技巧


一、包

1. lombok–自动生成勾子方法

作用

作用在类之上,自动生成类的 getter setter toString equal等方法

依赖

      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.34</version>
          <scope>provided</scope>
      </dependency>

使用

Data注解自动实现getter setter toString equal等方法
Constructor类型注解用于自动实现构造函数
这些代码会出现在编译以后的class文件中,但是不会出现在源文件中

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常用注解

作用

接收的是当个参数时,用于完成参数的格式校验, 如用户名长度格式要求

依赖

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

使用

一般参数校验

在类之上先加注解@Validated,告诉框架需要对这个类进行校验。
在需要校验的参数前面加@Patten注解,注解的参数regexp里面写匹配的正则表达式

@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

@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注解

validated常用注解

实体类

//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到对应参数前面

	@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和用户名等非敏感信息(这样可以较少对承载的信息的查询)
  • 最后一段数组签名是为了防止数据被篡改,对头部和载荷进行加密计算得来
依赖
      <!-- 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

在这里插入图片描述

	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);
	}
校验

在这里插入图片描述
加密的密钥不能错,校验失败会抛出异常,使用时通过捕捉异常进行判断

	@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"));
	}
封装的工具类
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();
    }

}


使用

二、处理技巧

1. 全局异常处理

@RestControllerAdvice作用及原理

作用

用于捕捉异常,比如在使用validation校验失败时会产生异常,这时就需要进行全局异常的捕捉

代码
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注解,把对象注入容器

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注解,声明是配置类

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转换即可

mybatis:
  configuration:
    map-underscore-to-camel-case: true

4. 在mvc框架中,让实体类属性不传到前端(JsonIgnore注解)

在实体类中可能存在一些私密信息,传到前端就会暴露隐私,如密码等字段,这种情况下springboot提供了对应的注解,只需要在实体类对应属性之上加入注解@JsonIgnore,就可以在对象转json时忽略这个属性

@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的工具类
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中

@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;
	}

数据获取

	@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接口方法

@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注解,并指定格式即可

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接口

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的限制

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注解中赋值分组
//	增加
	@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组。
分组是可以继承的,所有可以通过继承的方式优化代码

@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包下

创建注解

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校验失败)

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)

	@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

依赖

      <dependency>
          <groupId>com.github.pagehelper</groupId>
          <artifactId>pagehelper-spring-boot-starter</artifactId>
          <version>2.1.0</version>
      </dependency>

使用方法

PageHelper.startPage(pageNum, pageSize);

参数pageNum表示要查询第几页
pageSize表示每页存几条数据

这样就算开启了分页插件


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文件存放位置

mapper.xml存放位置
动图SQL参看mybatis框架

12. 文件上传

从前端上传的文件会被springBoot保存在MultipartFile类的对象中

对象方法

String getOriginalFilename(); //获取原始文件名
void transferTo(File dest); //将接收的文件转存到磁盘文件中
long getSize(); //获取文件的大小,单位:字节
byte[] getBytes(); //获取文件内容的字节数组
InputStream getInputStream(); //获取接收到的文件内容的输入流

实现

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去访问你的资源

实现步骤

  1. 在用户登陆的时候向redis中添加taken
  2. 在每次访问资源时都会经过的拦截器中加入taken的校验
    • 如果当前redis中存在taken,则通过,否则说明修改了密码
  3. 修改密码时删除redis中的taken
① 添加redis的依赖
<!-- 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
//	登录
	@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
//	更新密码
	@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的项目打包插件

    <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. 属性配置方法

属性就是一些写在配置文件里面的参数如数据库的连接信息等

spring:
 datasource:
   driver-class-name: com.mysql.cj.jdbc.Driver

(1) 项目内配置文件

这些信息一般都是写在spingboot的配置文件中

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: 环境名称

# 公共部分

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的内容

# 公共部分

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内容

# 开发环境
---
spring:
  config:
    activate:
      on-profile: dev

server:
  port: 8081

(3) 环境分组

在多文件配置的情况下又会出现某个环境的配置很多的情况下,这时又需要对环境的配置进行划分在这里插入图片描述
application.xml配置
在这里插入图片描述在这里插入图片描述

下面展示一下dev环境的内容
application.yml


spring:
  profiles:
    active: test
    group:
      "dev": devDB, devServer, devSelf
      "test" : testServer,testDB

application-devDB.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

server:
  port: 8081

application-devSelf.yml是空文件什么都没写

参考

@RestControllerAdvice作用及原理
validated常用注解
mapper.xml存放位置
mybatis框架

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2115739.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

服务器禁用远程(22)

vim /etc/ssh/sshd_config 修改 ListenAddress 0.0.0.0 为ListenAddress localhost 修改完后 重启一下sshd systemctl restart sshd 修改就生效了

【 html+css 绚丽Loading 】000044 两仪穿行轮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽Loading&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495…

数据库的配置2:客户端navicat的安装与连接数据库的方法

二.客户端的配置&#xff1a; 1.navicat ①安装&#xff1a; 破解根据文档进行破解即可 链接: https://pan.baidu.com/s/1M5KUv_fgRlFA50VfcV1VqA 提取码: j4a2 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 安装步骤很简单&#xff0c;直接下一步就好&…

Pandas处理数据,基本应用

Pandas是一个Python包&#xff0c;提供快速、灵活且表达力强的数据结构&#xff0c;旨在使处理“关系型”或“带标签”数据。专门设计用于进行数据分析和操作&#xff0c;它是建立在numpy之上&#xff0c;提供了易于使用的数据结构和数据分析工具。Pandas最主要的数据结构是Dat…

开启Hyper-V之后用不了VMware了,怎么破?

正文共&#xff1a;800 字 7 图&#xff0c;预估阅读时间&#xff1a;1 分钟 前面我们介绍了如何在Windows 10操作系统中启用Hyper-V虚拟化服务&#xff08;什么&#xff1f;Windows自带的Hyper-V虚拟化你都没用过&#xff1f;&#xff09;&#xff0c;但是在启用Hyper-V服务之…

vulhub spring 远程命令执行漏洞(CVE-2016-4977)

1.执行以下命令启动靶场环境并在浏览器访问 cd /vulhub/spring/CVE-2016-4977#进入漏洞环境所在目录 docker-compose up -d #启动靶场 docker ps #查看容器信息 2.输入以下命令测试环境 192.168.0.107:8080/oauth/authorize?response_type${2*2}&client_idacme&sc…

RMSE 和 RMS 介绍

RMSE&#xff08;Root Mean Square Error&#xff09;和 RMS&#xff08;Root Mean Square&#xff09;都是衡量误差或数据变动的统计量。它们在数据分析、机器学习和统计中应用广泛。以下是它们的详细介绍&#xff1a; 1. RMSE&#xff08;均方根误差&#xff09; 定义&…

【verilog】1. 流水灯例程

文章目录 前言一、定义概念 缩写1. verilog 二、性质三、代码分解释四、完整代码参考文献 前言 数电课设 一、定义概念 缩写 1. verilog Verilog 是一种以代码形式来描述数字系统和电路的硬件描述语言 (HDL)。它由 Gateway Design Automation 在 20 世纪 80年代中期开发&a…

9.06.

#include "mywidget.h"mywidget::mywidget(QWidget *parent): QMainWindow(parent) {/*---------------------窗口设置&#xff08;无边框&#xff09;----------------------*/this->setWindowFlag(Qt::FramelessWindowHint);//窗口大小this->resize(590,950)…

大数据之Flink(二)

4、部署模式 flink部署模式&#xff1a; 会话模式&#xff08;Session Mode&#xff09;单作业模式&#xff08;Per-Job Mode&#xff09;应用模式&#xff08;Application Mode&#xff09; 区别在于集群的生命周期以及资源的分配方式&#xff1b;以及应用的main方法到底在…

WireShark过滤器

文章目录 一、WireShark过滤器概念1. 捕获过滤器&#xff08;Capture Filters&#xff09;2. 显示过滤器&#xff08;Display Filters&#xff09;3. 捕获过滤器与显示过滤器的区别4. 过滤器语法结构实际应用场景 二、WireShark捕获数据包列表1. **No.&#xff08;序号&#xf…

vulhub ThinkPHP5 5.0.23远程代码执行漏洞

步骤一&#xff1a;.执行以下命令启动靶场环境并在浏览器访问 cd thinkphp/5.0.23-rcedocker-compose up -ddocker ps 步骤二&#xff1a;访问靶机环境 步骤三&#xff1a;/index.php?scaptcha 步骤四&#xff1a;利用HackBar _method__construct&filter[]system&me…

心理辅导新篇章:Spring Boot学生评估系统

1 绪论 1.1 研究背景 现在大家正处于互联网加的时代&#xff0c;这个时代它就是一个信息内容无比丰富&#xff0c;信息处理与管理变得越加高效的网络化的时代&#xff0c;这个时代让大家的生活不仅变得更加地便利化&#xff0c;也让时间变得更加地宝贵化&#xff0c;因为每天的…

优化边缘设备上的大型语言模型(LLM)--tinychat

文章目录 一、项目启动1.背景&#xff1a;针对不同操作系统架构的4bit权重重排2.初始环境配置下载LLaMA2-7B-chat模型 3.项目启动项目结构说明评估不同优化技术可能遇到的bug以及措施1.macOS上部署 二、各种优化技术实现1.前置条件2.优化----循环展开3.优化----多线程4.优化---…

OpenCV结构分析与形状描述符(6)带统计的连通组件计算函数connectedComponentsWithStats()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 connectedComponentsWithStats 函数计算布尔图像的连通组件标记图像&#xff0c;并为每个标记产生统计信息。 该函数接受一个具有4或8连通性的二…

盘点4款可以免费帮你将语音转换成文字的工具

我们在寻找语音转文字的工具的时候&#xff0c;不能只考虑他是否免费&#xff0c;还需要关注这个工具的转换准确度&#xff0c;减少第二次修改的麻烦&#xff0c;以及它的转换速度&#xff0c;以便可以有效的提高我们工作效率。基于这些&#xff0c;我要给大家推荐几个既可以免…

2024Java基础总结+【Java数据结构】(2)

面向对象07&#xff1a;简单小结类与对象 面向对象08&#xff1a;封装详解 面向对象09&#xff1a;什么是继承 ctrlh看类的关系&#xff0c;所有的类都默认的或间接继承Object 面向对象10&#xff1a;Super详解 super注意点: super调用父类的构造方法&#xff0c;必须在构造方…

白小白为波司登新品创作歌曲《登峰之路》,穿越风雨守护前行者

随着天气渐凉&#xff0c;波司登品牌推出全新新品——轻薄羽绒叠变系列&#xff0c;作为波司登品牌的新品推荐官&#xff0c;歌手白小白为波司登创作并演唱《轻薄羽绒叠变》系列主题曲《登峰之路》。歌曲中&#xff0c;白小白以激昂澎湃&#xff0c;明快有力的旋律以及深情又充…

【Unity小技巧】物体遮挡轮廓描边效果

前言&#xff1a; 效果展示&#xff1a; 遮挡描边 Demo下载 所用插件 QuickOutline描边插件&#xff08;在Demo里&#xff09; 实现步骤 物体挂载Outline组件&#xff0c;做如下处理 Outline Mode&#xff08;描边模式&#xff09;&#xff1a;Outline Hidden(遮挡模式显示…

让中学生也能一下子认识5000年都无人能识的无穷大自然数

黄小宁 5000多年来数学一直未能证明存在>N一切数的标准无穷大自然数及其倒数&#xff0c;从而一直否定存在这类数&#xff0c;正如西医否定人体存在经络系统那样。 x轴各元点的坐标x变为的有序数对 ( x , y2 x)是平面点p的坐标&#xff0c;点p的全体是直线y2x。 x可变成一…