目录
一、登录优化-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.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/springbdfdfoot_vue?serverTimezone=UTCusername: rootpassword: ddfdfdfdmain:banner-mode: off # 关闭控制台springboot的logodata:redis:host: localhostport: 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 {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Testpublic void testSet(){// 往redis中存储一个键值对 StringRedisTemplateValueOperations<String, String> operations = stringRedisTemplate.opsForValue();operations.set("username","仓颉书");operations.set("woman","舒淇",15, TimeUnit.SECONDS); //设置有效时间}@Testpublic 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 {@Autowiredprivate UserService userService;@Autowiredprivate 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存储到redisValueOperations<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 、调用userServiceuserService.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 {@Autowiredprivate 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 --------> 阿里巴巴fastJSONString 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();}//解析jwtMap<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 --------> 阿里巴巴fastJSONString 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 {@Autowiredprivate UserService userService;@Autowiredprivate 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存储到redisValueOperations<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 、调用userServiceuserService.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(分组)
分组实现