目录
一、登录优化-redis
1、SpringBoot集成redis
1.1 pom
1.2 yml
1.3 测试程序(非必须)
1.4 启动redis,执行测试程序
2、令牌主动失效(代码优化)
2.1 UserController设置token到redis
2.2 登录拦截器LoginCheckInterceptor
2.3 UserController更新密码删除redis的token(jwt)
2.4 验证
2.4.1 调用登录接口
2.4.2 调用更新密码接口 编辑
二、SpringBoot项目部署
1、pom检查是否有打包插件
2、打包
3、运行jar
4、测试
三、属性配置方式
1、命令参数方式
2、环境变量方式
3、外部配置文件方式
4、配置优先级
四、多环境开发-Profiles
1、多环境开发-介绍
2、 多环境开发-Profiles(同一个yaml)
3、多环境开发-Profiles(不同yml)
4、多环境开发-Profiles(分组)
前言:针对【SpringBoot3+Vue3】二篇进行一些后端优化和一些实用的后端技术
一、登录优化-redis
1、SpringBoot集成redis
1.1 pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.2 yml
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springbdfdfoot_vue?serverTimezone=UTC
username: root
password: ddfdfdfd
main:
banner-mode: off # 关闭控制台springboot的logo
data:
redis:
host: localhost
port: 6379
1.3 测试程序(非必须)
package com.bocai;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.concurrent.TimeUnit;
@SpringBootTest //如果在测试类上添加这个注解,那么将来单元测试方法执行之前,会先初始化Spring容器
public class RedisTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testSet(){
// 往redis中存储一个键值对 StringRedisTemplate
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set("username","仓颉书");
operations.set("woman","舒淇",15, TimeUnit.SECONDS); //设置有效时间
}
@Test
public void testGet(){
//从redis获取一个键值对
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
System.out.println(operations.get("username"));
}
}
1.4 启动redis,执行测试程序
2、令牌主动失效(代码优化)
2.1 UserController设置token到redis
登录完成生成token时,同时将token存储到redis,按键值对方式,注意设置了时效要与token生成时的时效一致性。
package com.bocai.controller;
import com.bocai.common.Result;
import com.bocai.pojo.User;
import com.bocai.service.UserService;
import com.bocai.utils.JwtUtils;
import com.bocai.utils.Md5Util;
import com.bocai.utils.ThreadLocalUtil;
import jakarta.validation.constraints.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.URL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
@Validated
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 用户注册
* @param username
* @param password
* @return
*/
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$")String password){
log.info("注册用户名:{},密码为:{}",username,password);
// 查询用户
User user = userService.queryUserByUsername(username);
if (user == null){
//没有占用,可以注册
//注册用户
userService.register(username,password);
return Result.success();
}else{
return Result.error("用户名被占用");
}
}
@PostMapping("/login")
public Result login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$")String password) {
log.info("登录用户名:{},密码为:{}", username, password);
// 查询用户
User loginUser = userService.queryUserByUsername(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 jwt = JwtUtils.generateJwt(claims); //让jwt包含了当前登录的员工信息
// 把token存储到redis
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set(jwt,jwt,12, TimeUnit.HOURS); //过期时间与jwt设置的时效要一致性
return Result.success(jwt);
}
return Result.error("密码错误!");
}
/**
* 查询用户信息(从线程获取)
* @return
*/
@GetMapping("/userInfo")
public Result userInfo(){
// 从线程获取存储的jwt信息
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
log.info("查询用户全部信息,从token获取信息为:{}",username);
//根据用户名查询用户
User user = userService.queryUserByUsername(username);
return Result.success(user);
}
//使用上面的的优化版本
// /**
// * 查询用户信息(从token获取)
// * @param token
// * @return
// */
// @GetMapping("/userInfo")
// public Result userInfo(@RequestHeader(name = "Authorization")String token){
// log.info("查询用户全部信息,从token获取信息为:{}",token);
// //根据用户名查询用户
// Map<String, Object> claims = JwtUtils.parseJWT(token);
// String username = (String) claims.get("username");
// User user = userService.queryUserByUsername(username);
// return Result.success(user);
// }
/**
* 更新用户
* @param user
* @return
*/
@PutMapping("/update")
public Result update(@RequestBody @Validated User user){
log.info("修改的用户为:{}",user);
userService.updateUser(user);
return Result.success();
}
/**
* 更新头像
* @param avatarUrl
* @return
*/
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl){
log.info("头像地址是{}",avatarUrl);
userService.updateAvatar(avatarUrl);
return Result.success();
}
/**
* 更新密码
* @param params json数据包含old_pwd,new_pwd,re_pwd
* @return
*/
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String, String> params){
log.info("修改密码传过来数据是:{}",params);
// 1、校验参数
String old_pwd = params.get("old_pwd");
String new_pwd = params.get("new_pwd");
String re_pwd = params.get("re_pwd");
if(!StringUtils.hasLength(old_pwd) || !StringUtils.hasLength(new_pwd) || !StringUtils.hasLength(re_pwd)){
return Result.error("缺少必要参数!");
}
//原密码是否正确
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginUser = userService.queryUserByUsername(username);
if(!loginUser.getPassword().equals(Md5Util.getMD5String(old_pwd))){
return Result.error("原密码不正确!");
}
//新、老密码是否一致
if(old_pwd.equals(new_pwd)){
return Result.error("新、老密码一样!!");
}
//新密码和确认密码不一致!
if(!re_pwd.equals(new_pwd)){
return Result.error("新密码和确认密码不一致!!");
}
// 2 、调用userService
userService.updatePwd(new_pwd);
return Result.success();
}
}
2.2 登录拦截器LoginCheckInterceptor
登录拦截器当中查询redis当中是否存在相同token(jwt),如果不存在所有失效了。抛出异常
package com.bocai.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.bocai.common.Result;
import com.bocai.utils.JwtUtils;
import com.bocai.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import java.util.Map;
/**
* =========================LoginCheckInterceptor 拦截器 interceptor========================
*/
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
//1.获取请求url。
String url = req.getRequestURL().toString();
log.info("请求的url: {}",url);
//2.判断请求url中是否包含login,如果包含,说明是登录操作,放行。
if(url.contains("login")){
log.info("登录操作, 放行...");
return true;
}
//3.获取请求头中的令牌( Authorization)。
String jwt = req.getHeader("Authorization");
//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){
log.info("请求头Authorization为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
// 从redis中获取获取相同的token(jwt)
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
String redisJwt = operations.get(jwt);
if(redisJwt == null){
// token 已经失效
throw new RuntimeException();
}
//解析jwt
Map<String, Object> claims = JwtUtils.parseJWT(jwt);
//6.把业务数据存储到ThreadLocal中
ThreadLocalUtil.set(claims);
//7.放行。
log.info("令牌合法, 放行");
return true;
} catch (Exception e) {//jwt解析失败
e.printStackTrace();
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
}
@Override //目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ...");
}
@Override //视图渲染完毕后运行, 最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清空ThreadLocal中的数据,防止内存泄漏
ThreadLocalUtil.remove();
System.out.println("afterCompletion...");
}
}
2.3 UserController更新密码删除redis的token(jwt)
更新密码的时候同时需要将redis存储的jwt删除掉。这里在更新密码方法上面使用从请求头当中获取Authorization
package com.bocai.controller;
import com.bocai.common.Result;
import com.bocai.pojo.User;
import com.bocai.service.UserService;
import com.bocai.utils.JwtUtils;
import com.bocai.utils.Md5Util;
import com.bocai.utils.ThreadLocalUtil;
import jakarta.validation.constraints.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.URL;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
@Validated
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 用户注册
* @param username
* @param password
* @return
*/
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$")String password){
log.info("注册用户名:{},密码为:{}",username,password);
// 查询用户
User user = userService.queryUserByUsername(username);
if (user == null){
//没有占用,可以注册
//注册用户
userService.register(username,password);
return Result.success();
}else{
return Result.error("用户名被占用");
}
}
@PostMapping("/login")
public Result login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$")String password) {
log.info("登录用户名:{},密码为:{}", username, password);
// 查询用户
User loginUser = userService.queryUserByUsername(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 jwt = JwtUtils.generateJwt(claims); //让jwt包含了当前登录的员工信息
// 把token存储到redis
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set(jwt,jwt,12, TimeUnit.HOURS); //过期时间与jwt设置的时效要一致性
return Result.success(jwt);
}
return Result.error("密码错误!");
}
/**
* 查询用户信息(从线程获取)
* @return
*/
@GetMapping("/userInfo")
public Result userInfo(){
// 从线程获取存储的jwt信息
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
log.info("查询用户全部信息,从token获取信息为:{}",username);
//根据用户名查询用户
User user = userService.queryUserByUsername(username);
return Result.success(user);
}
//使用上面的的优化版本
// /**
// * 查询用户信息(从token获取)
// * @param token
// * @return
// */
// @GetMapping("/userInfo")
// public Result userInfo(@RequestHeader(name = "Authorization")String token){
// log.info("查询用户全部信息,从token获取信息为:{}",token);
// //根据用户名查询用户
// Map<String, Object> claims = JwtUtils.parseJWT(token);
// String username = (String) claims.get("username");
// User user = userService.queryUserByUsername(username);
// return Result.success(user);
// }
/**
* 更新用户
* @param user
* @return
*/
@PutMapping("/update")
public Result update(@RequestBody @Validated User user){
log.info("修改的用户为:{}",user);
userService.updateUser(user);
return Result.success();
}
/**
* 更新头像
* @param avatarUrl
* @return
*/
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl){
log.info("头像地址是{}",avatarUrl);
userService.updateAvatar(avatarUrl);
return Result.success();
}
/**
* 更新密码
* @param params json数据包含old_pwd,new_pwd,re_pwd
* @param jwt 从请求头中获取Authorization赋值给jwt参数
* @return
*/
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String, String> params,@RequestHeader("Authorization") String jwt){
log.info("修改密码传过来数据是:{}",params);
// 1、校验参数
String old_pwd = params.get("old_pwd");
String new_pwd = params.get("new_pwd");
String re_pwd = params.get("re_pwd");
if(!StringUtils.hasLength(old_pwd) || !StringUtils.hasLength(new_pwd) || !StringUtils.hasLength(re_pwd)){
return Result.error("缺少必要参数!");
}
//原密码是否正确
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginUser = userService.queryUserByUsername(username);
if(!loginUser.getPassword().equals(Md5Util.getMD5String(old_pwd))){
return Result.error("原密码不正确!");
}
//新、老密码是否一致
if(old_pwd.equals(new_pwd)){
return Result.error("新、老密码一样!!");
}
//新密码和确认密码不一致!
if(!re_pwd.equals(new_pwd)){
return Result.error("新密码和确认密码不一致!!");
}
// 2 、调用userService
userService.updatePwd(new_pwd);
// 更新密码之后,删除redis中对应的token(jwt)
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.getOperations().delete(jwt);
return Result.success();
}
}
2.4 验证
2.4.1 调用登录接口
2.4.2 调用更新密码接口
二、SpringBoot项目部署
1、pom检查是否有打包插件
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
</image>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
2、打包
插曲:
1、无效的标记: --release
这里要与你得jdk版本一致性
2、注意test当中的case
3、运行jar
java -jar SS.jar
4、测试
三、属性配置方式
1、命令参数方式
2、环境变量方式
3、外部配置文件方式
4、配置优先级
四、多环境开发-Profiles
1、多环境开发-介绍
2、 多环境开发-Profiles(同一个yaml)
3、多环境开发-Profiles(不同yml)
4、多环境开发-Profiles(分组)
分组实现