Springboot后端实战篇
视频链接:
【黑马程序员SpringBoot3+Vue3全套视频教程,springboot+vue企业级全栈开发从基础、实战到面试一套通关】https://www.bilibili.com/video/BV14z4y1N7pg?p=20&vd_source=12bdb5b78bd5d1c45cab173f3aad839b
注册
引入Spring Validation依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
导入Md5Util工具类
package com.zjf.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Md5Util {
/**
* 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
*/
protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
protected static MessageDigest messagedigest = null;
static {
try {
messagedigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException nsaex) {
System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
nsaex.printStackTrace();
}
}
/**
* 生成字符串的md5校验值
*
* @param s
* @return
*/
public static String getMD5String(String s) {
return getMD5String(s.getBytes());
}
/**
* 判断字符串的md5校验码是否与一个已知的md5码相匹配
*
* @param password 要校验的字符串
* @param md5PwdStr 已知的md5校验码
* @return
*/
public static boolean checkPassword(String password, String md5PwdStr) {
String s = getMD5String(password);
return s.equals(md5PwdStr);
}
public static String getMD5String(byte[] bytes) {
messagedigest.update(bytes);
return bufferToHex(messagedigest.digest());
}
private static String bufferToHex(byte bytes[]) {
return bufferToHex(bytes, 0, bytes.length);
}
private static String bufferToHex(byte bytes[], int m, int n) {
StringBuffer stringbuffer = new StringBuffer(2 * n);
int k = m + n;
for (int l = m; l < k; l++) {
appendHexPair(bytes[l], stringbuffer);
}
return stringbuffer.toString();
}
private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
// 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
stringbuffer.append(c0);
stringbuffer.append(c1);
}
}
三层架构
UserController
package com.zjf.controller;
// 导入必要的包
@Validated// @Validated: 这个注解表示这个控制器中的方法参数将会进行校验。
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userservice;
// @Pattern: 这个注解用于校验方法参数是否符合正则表达式的规则。正则表达式"^\S{5,16}$",用户名和密码的长度为5~16位非空字符
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
if (userService.findByUserName(username) != null) {
// 如果用户名已存在,返回错误信息
return Result.error("用户名已存在");
} else {
// 如果用户名不存在,调用userService的register方法注册新用户
userService.register(username, password);
// 注册成功,返回成功信息
return Result.success();
}
}
}
UserServiceImpl
package com.zjf.service.impl;
import com.zjf.utils.Md5Util;
// 导入必要的包
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public void register(String username, String password) {
使用Md5Util工具类对密码进行MD5加密
String md5String = Md5Util.getMD5String(password);
userMapper.insertUser(username,md5String);
}
@Override
public User findByUserName(String username) {
return userMapper.findByUserName(username);
}
}
UserMappper
package com.zjf.mapper;
@Mapper
public interface UserMapper {
@Insert("insert into user(username,password,create_time,update_time)" +
" values(#{username},#{password},now(),now())")
void insertUser(String username, String password);
@Select("select * from user where username=#{username}")
User findByUserName(String username);
}
知识要点
Md5加密
-
导入Md5Utils工具类
-
将输入的密码使用Md5Util类进行MD5加密生成md5String
@Override public void register(String username, String password) { //加密 String md5String = Md5Util.getMD5String(password); userMapper.insertUser(username,md5String); }
参数校验
使用 Spring Validation, 对注册接口的参数进行合法性校验
-
引入 Spring Validation 起步依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
-
在参数前面添加 @Pattern 注解
register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password)
-
在 Controller 类上添加 @Validated 注解
-
在全局异常处理器中处理参数校验失败的异常
当用户名和密码的长度不满足5~16位非空字符时,参数校验失败,服务器抛出错误,“status”: 500, “error”: “Internal Server Error”,表示服务器遇到了一个内部错误。此时提供一个全局的异常处理机制。当应用程序中的任何控制器方法抛出异常时,
handleException
方法会被调用。它会打印异常的堆栈跟踪信息,并返回一个包含错误信息的Result
对象给客户端。这种方式有助于统一异常处理逻辑,提高代码的可维护性。package com.zjf.exception; //导入必要的包 // @RestControllerAdvice: 这是一个Spring框架的注解,用于标记一个类作为全局的异常处理类。 // 这个类会处理所有控制器(@Controller或@RestController)中抛出的异常,并将异常处理结果以JSON或其他序列化形式返回给客户端。 @RestControllerAdvice public class GlobalExceptionHandler { // @ExceptionHandler(Exception.class): 这个方法级别的注解用于指定这个方法将会处理Exception.class类型的异常。 // 在这里,它指定了这个方法将处理所有的异常,因为Exception是所有异常类的基类。 @ExceptionHandler(Exception.class) public Result handleException(Exception e){ e.printStackTrace(); // 打印异常的堆栈跟踪信息到控制台,这通常用于开发过程中的调试。 return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : "操作失败"); // 使用StringUtils.hasLength(e.getMessage())来检查异常是否有消息内容,如果有,则将异常消息作为错误信息返回。 // 如果没有,则返回一个默认的字符串"操作失败"。 // 这里的Result是一个自定义的响应实体类,它包含了处理异常后的结果。 } }
登录
引入jwt依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
导入工具类jwtUtil
package com.zjf.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 ))
.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();
}
}
编写UserController
@PostMapping("/login")
public Result login(
@Pattern(regexp = "^\\S{5,16}$") String username,
@Pattern(regexp = "^\\S{5,6}$") String password) {
// 根据用户名查询用户信息
User loginUser = userService.findByUserName(username);
// 判断该用户是否存在
if (loginUser == null) {
// 如果用户不存在,返回错误信息
return Result.error("用户名错误");
}
// 判断密码是否正确,loginUser对象中的password是密文
if (Md5Util.getMD5String(password).equals(loginUser.getPassword())) {
// 如果密码正确,登录成功
// 创建一个包含用户ID和用户名的声明映射
Map<String, Object> claims = new HashMap<>();
claims.put("id", loginUser.getId());
claims.put("username", loginUser.getUsername());
// 使用JwtUtil工具类生成一个JWT令牌,将声明映射作为载荷
String token = JwtUtil.genToken(claims);
// 返回成功结果,包含生成的JWT令牌
return Result.success(token);
}
// 如果密码错误,返回错误信息
return Result.error("密码错误");
}
登录拦截器
LoginInterceptor
LoginInterceptor
是一个用于验证用户是否登录的拦截器。它通过检查HTTP请求中的JWT来验证用户的身份,如果JWT有效,则允许请求继续;如果JWT无效,则拒绝请求。
package com.zjf.interceptors;
@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; // 如果令牌验证成功,返回true,继续执行后续的请求处理
} catch (Exception e) {
// 如果解析令牌时出现异常,说明令牌可能无效或过期
// 设置响应状态码为401,
response.setStatus(401);
return false;
}
}
}
WebConfig
WebConfig是一个配置类,用于注册一个登录拦截器(LoginInterceptor)。通过实现Spring MVC的WebMvcConfigurer接口,并重写addInterceptors方法,将登录拦截器添加到拦截器注册表中。在拦截器的配置中,指定了不拦截登录接口和注册接口的路径,即对路径为/user/login和/user/register的请求不进行拦截
package com.zjf.config;
import com.zjf.interceptors.LoginInterceptor;
import com.zjf.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/login","/user/register");
}
}
知识要点
JWT
JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。JWT 可以通过数字签名进行验证,因此是一种非常安全的方式,通常用于在身份验证(Authentication)和信息交换(Information Exchange)方面。
JWT 结构
JWT 由三部分组成,通过点(.)分隔:
- Header(头部): 包含了令牌的元数据信息,例如算法(HS256、RS256、HMAC256)、令牌类型(JWT)等。
- Payload(负载): 包含了被加密的数据,例如用户的身份信息或其他声明。
- Signature(签名): 使用头部指定的算法和密钥对头部和负载进行签名,确保数据的完整性和认证。
这三部分通过Base64 URL编码后连接在一起,形成最终的JWT令牌。
JWT 使用
JWT 的主要使用场景是在客户端和服务器之间安全地传递信息,特别是在身份验证过程中。一般的流程如下:
-
认证流程:
- 用户向服务器提供用户名和密码进行身份验证。
- 服务器验证用户的身份,并生成包含用户信息的JWT。
- 服务器将生成的JWT返回给客户端。
-
请求认证:
-
客户端在以后的请求中将JWT放置在请求头部的Authorization字段中,通常格式为
Authorization: Bearer <token>
。 -
服务器接收到请求后,解析JWT并验证签名的有效性。
-
如果验证成功,服务器处理请求;如果验证失败或者JWT过期,服务器拒绝请求。
-
拦截器
拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行,是java中AOP思想的运用。
- 创建拦截器
在Spring框架中,拦截器是通过实现HandlerInterceptor
接口来创建的。这个接口定义了三个方法,分别对应请求处理的不同阶段:
-
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
:这个方法在请求实际处理之前被调用。在这个方法中,可以执行一些前置处理,如身份验证、权限检查等。如果这个方法返回true
,则请求会继续执行;如果返回false
,则请求将被终止。 -
postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
:这个方法在请求实际处理之后,但在视图渲染之前被调用。在这个方法中,可以对模型的属性进行修改,或者添加额外的模型数据。 -
afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
:这个方法在整个请求完成后被调用,即在视图渲染之后。在这个方法中,可以执行一些清理工作,如释放资源、记录日志等。
-
配置拦截器
创建一个配置类,实现
WebMvcConfigurer
接口,并重写addInterceptors
方法。在这个方法中,你可以使用InterceptorRegistry
来注册你的拦截器,并指定拦截器应该作用的路径或者排除的路径。 -
注册拦截器
在
addInterceptors
方法中,使用InterceptorRegistry
的addInterceptor
方法来注册你的拦截器。然后,你可以使用addPathPatterns
来指定拦截器应该拦截哪些路径,或者使用excludePathPatterns
来指定拦截器不应该拦截哪些路径。
获取用户详细信息
编写UserController
注意:/userInfo接口不携带参数username,token令牌中存放了用户的id和usern,通过携带token令牌,获取username。@RequestHeader
注解用于将请求头中的信息绑定到控制器方法参数上。当你在方法参数前面加上@RequestHeader
注解,并指定name
属性时,Spring会自动将请求中名为Authorization
的头部的值赋给该参数。当HTTP请求到达服务器并调用这个方法时,Spring会查找请求头中名为Authorization
的字段,并将其值作为字符串参数authorizationHeader
传递给方法。
@GetMapping("/userInfo")
public Result<User> userInfo(@RequestHeader(name = "Authorization") String token){
// 使用JwtUtil工具类来解析传递的JWT字符串,获取其中的负载(Payload)
Map<String, Object> map = JwtUtil.parseToken(token);
// 从解析出来的负载中获取用户的用户名
String username =(String) map.get("username");
// 使用userService的findByUserName方法根据用户名查询用户的信息
User user = userService.findByUserName(username);
// 如果查询到用户,则将用户信息封装在Result对象中返回,表示请求成功
return Result.success(user);
}
测试结果
在测试结果中,用户的密码password同样也响应给客户端。密码通常是敏感信息,不应该在网络请求的响应中返回给客户端。这可能导致用户隐私泄露的问题。在设计和实现时,应实现将密码等敏感信息从序列化的过程中排除出去。
@JsonIgnore
是Jackson库中的一个注解,它用于在将Java对象序列化为JSON格式时忽略特定的属性。当你想要隐藏某些敏感信息或不希望这些信息被客户端看到时,比如密码、加密密钥等。在Java类的某个字段或方法上添加@JsonIgnore
注解,Jackson在序列化过程中就会跳过这个字段或方法,不会将其包含在生成的JSON中。
在本例中User类的password字段上加上**@JsonIgnore**,实现在被序列化成 JSON 时,password
字段将会被忽略,不会出现在生成的 JSON 中。
@JsonIgnore
private String password;//密码
测试结果
ThreadLocal
ThreadLocal
,即线程本地变量,提供线程局部变量。如果你创建了一个ThreadLocal
变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。
- 用来存取数据 : set()/get()
- 使用 ThreadLocal 存储的数据 , 线程安全
- 用完记得调用 remove 方法释放
在之前的代码中,我们使用**@RequestHeader**注解获取请求头中名为Authorization
的头部的值,并使用JwtUtil工具类来解析token,获取其中的username。如果存在多个这样的接口,相应的会重复编写这串代码。通过拦截器中通一的解析token实现代码的复用
导入ThreadLocal 工具类
package com.zjf.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();
}
}
在登录拦截器中实现线程局部变量
@Component
public class LoginInterceptor implements HandlerInterceptor{
// preHandle方法在请求处理之前调用(即在Controller方法之前)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
try {
// 使用JwtUtil工具类解析令牌,获取其中的负载
Map<String, Object> claims = JwtUtil.parseToken(token);
// 将解析出的负载存储到线程局部变量中,以便在后续的请求处理中可以使用
ThreadLocalUtil.set(claims);
// 如果令牌验证成功,返回true,继续执行后续的请求处理
return true;
} catch (Exception e) {
// 如果解析令牌时出现异常,说明令牌可能无效或过期
// 设置响应状态码为401
response.setStatus(401);
// 返回false,不再继续执行后续的请求处理
return false;
}
}
// afterCompletion方法在请求处理完成后调用(即在Controller方法之后)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理线程局部变量,避免内存泄漏
ThreadLocalUtil.remove();
}
}
修改/userInfo接口
@GetMapping("/userInfo")
public Result<User> userInfo(){
Map<String, Object> map = ThreadLocalUtil.get(); // 使用ThreadLocalUtil工具类从线程局部变量中获取存储的数据
String username = (String) map.get("username"); // 从获取的数据中提取用户名
User user = userService.findByUserName(username); // 使用findByUserName方法根据用户名查询用户的信息
return Result.success(user);
}
更新用户基本信息
三层架构
UserController
@PutMapping("/update")
public Result update(@RequestBody User user) {
userService.update(user);
return Result.success();
}
UserServiceImpl
@Override
public void update(User user) {
user.setUpdateTime(LocalDateTime.now());
userMapper.update(user);
}
UserMapper
@Update("update user set nickname = #{nickname},email = #{email},update_time = #{updateTime} where id = #{id}")
void update(User user);
参数校验
对上述的更新用户基本信息update代码进行参数校验,不同与之前注册时对属性的参数,update方法传入的参数是一个User对象
实体参数校验
- 实体类的成员变量上添加注解
- @NotNull
- @NotEmpty
- 接口方法的实体参数上添加 @Validated 注解
更新用户头像
三层架构
UserController
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl){
userService.updateAvatar(avatarUrl);
return Result.success();
}
UserServiceImpl
@Override
public void updateAvatar(String avatarUrl) {
Map<String, Object> map = ThreadLocalUtil.get();//ThreadLocalUtil中获取一个Map对象
Integer id = (Integer) map.get("id");//从Map对象中获取用户id
userMapper.updateAvatar(avatarUrl,id);
}
UserMapper
@Update("update user set user_pic = #{userPic},update_time = NOW() where id = #{id}")//update_time字段更新为当前时间,使NOW()函数获取
void updateAvatar(String userPic,Integer id);
知识要点
参数校验
@URL注解,通常用于参数验证,表示该参数必须是一个合法的URL格式。
public Result updateAvatar(@RequestParam @URL String avatarUrl)
@PatchMapping
@PatchMapping
注解用于将 HTTP PATCH 请求映射到指定的处理方法。HTTP PATCH 方法通常用于更新资源的部分内容,而不是替换整个资源。@PatchMapping
用于部分更新资源,而 @PutMapping
用于替换整个资源。@PatchMapping
请求仅包含要更新的部分内容,而 @PutMapping
请求包含完整的资源表述。
更新用户密码
三层架构
Usercontroller
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String,String> params){
// 从请求体中获取旧密码、新密码和确认密码。
String oldPwd = params.get("old_pwd");
String newPwd = params.get("new_pwd");
String re_pwd = params.get("re_pwd");
// 判断这三个密码参数是否为空,如果为空,返回错误信息。
if(!StringUtils.hasLength(oldPwd)||!StringUtils.hasLength(newPwd)||!StringUtils.hasLength(re_pwd)){
return Result.error("参数错误");
}
// 从ThreadLocal中获取用户名,这通常在用户登录时设置。
Map<String,Object> map= ThreadLocalUtil.get();
String username = (String) map.get("username");
// 使用用户名查询当前登录的用户信息。
User loginUser = userService.findByUserName(username);
// 比较传入的旧密码的MD5值是否与数据库中的一致,不一致则返回错误信息。
if(!Md5Util.getMD5String(oldPwd).equals(loginUser.getPassword())){
return Result.error("旧密码错误");
}
// 检查新密码是否与旧密码相同,如果相同则返回错误信息。
if(Md5Util.getMD5String(newPwd).equals(loginUser.getPassword())){
return Result.error("新密码不能与旧密码一致");
}
// 检查新密码和确认密码是否相同,不同则返回错误信息。
if(!newPwd.equals(re_pwd)){
return Result.error("两次密码不一致");
}
// 如果所有检查都通过,调用userService的updatePwd方法更新密码。
userService.updatePwd(newPwd);
return Result.success();
}
UserService
@Override
public void updatePwd(String newPwd) {
Map<String, Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
String md5String = Md5Util.getMD5String(newPwd);//对新密码进行MD5加密
userMapper.updatePwd(md5String,id);
}
UserMapper
@Update("update user set password = #{newPwd},update_time = now() where id = #{id}")
void updatePwd(String newPwd, Integer id);
新增文章分类
三层架构
CategoryController
package com.zjf.controller;
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping
public Result add(@RequestBody @Validated Category category){
categoryService.add(category);
return Result.success();
}
}
CategoryServiceImpl
package com.zjf.service.impl;
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
@Override
public void add(Category category) {
Map<String,Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");//获取当前用户id值
category.setCreateTime(LocalDateTime.now());
category.setUpdateTime(LocalDateTime.now());
category.setCreateUser(id);//将当前用户id值赋给创建分类用户Create_User字段
categoryMapper.add(category);
}
}
CategoryMapper
@Mapper
public interface CategoryMapper {
@Insert("insert into category(category_name, category_alias, create_user, create_time, update_time)" +
" VALUE(#{categoryName},#{categoryAlias},#{createUser},#{createTime},#{updateTime})")
void add(Category category);
}
知识要点
参数校验
未进行控制层的参数校验之前,测试时如果传入categoryName或categoryName为空,系统会在SQL层抛出错误,通过参数校验,实现controller层抛出错误
1.在Category实体类 categoryName, categoryName字段上添加@NotEmpty注解
@NotEmpty
private String categoryName;//分类名称
@NotEmpty
private String categoryName;//分类别名
2.在控制层对应方法参数前加上@Validated注解
public Result add(@RequestBody @Validated Category category)
文章分类列表
三层架构
CategoryController
@GetMapping
public Result<List<Category>> list(){
List<Category> cs = categoryService.list();
return Result.success(cs);
}
CategoryServiceImpl
public List<Category> list() {
Map<String,Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");//获取当前用户id
return categoryMapper.list(id);
}
CategoryMapper
@Select("select * from category where create_user = #{id}")
List<Category> list(Integer id);//id是user表id,也就是category表的create_user
知识要点
@JsonFormat注解
用于格式化Java对象中的日期和时间属性,当这个对象被转换为JSON字符串时,会按照指定的格式来格式化日期和时间
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;//更新时间
测试结果:
{
"id": 1,
"categoryName": "美食",
"categoryAlias": "ms",
"createUser": 10,
"createTime": "2024-06-18T17:29:26",//未使用注解
"updateTime": "2024-06-18 17:29:26"//使用了@JsonFrmat注解
}
获取文章分类详情
三层架构
CategoryController
@GetMapping("/detail")
public Result<Category> detail(Integer id){//参数id使category表的id
Category category = categoryService.detail(id);
return Result.success(category);//返会category对象
}
CategoryServiceImpl
@Override
public Category detail(Integer id) {
return categoryMapper.findById(id);
}
CategoryMapper
@Select("select * from category where id = #{id}")
Category findById(Integer id);
更新文章分类
三层架构
CategoryController
@PutMapping
public Result update(@RequestBody @Validated(Category.Update.class) Category category){//@Validated参数校验
categoryService.update(category);
return Result.success();
CategoryServiceImpl
@Override
public void update(Category category) {
category.setUpdateTime(LocalDateTime.now());
categoryMapper.update(category);
}
CategoryMapper
@Update("update category set category_name=#{categoryName},category_alias=#{categoryAlias},update_time=#{updateTime} where id=#{id}")
void update(Category category);
知识要点
参数校验
更新文章分类参数说明:
参数名称 | 说明 | 类型 | 是否必须 | 备注 |
---|---|---|---|---|
id | 主键ID | number | 是 | |
categoryName | 分类名称 | string | 是 | |
categoryAlias | 分类别名 | string | 是 |
id、categoryName、categoryAlias均为必须,故在实体类Category类的相关字段上添加相应注解
@NotNull
private Integer id;//主键ID
@NotEmpty
private String categoryName;//分类名称
@NotEmpty
private String categoryAlias;//分类别名
注意:在新增文章分类中,已经为categoryName、categoryAlias添加了@NotEmpty注解。通过给id、categoryName、categoryAlias添加注解,以及为控制层方法参数添加@Validated注解,实现了更新文章分类的参数校验,但我们注意到在新增文章分类方法中,也为相同的参数category添加了@Validated参数校验。此时,在执行新增文章分类时,会抛出id 不能为null的错误。为解决此问题,就要用到分组校验。
分组校验
把校验项进行归类分组,在完成不同的功能的时候,校验指定组中的校验项
-
定义分组
在category实体类中定义了两个空接口Add和Update,用于分组校验
public class Category { public interface Add{ } public interface Update{ } }
-
定义校验项时指定归属的分组
@NotNull(groups = Update.class) private Integer id;//主键ID @NotEmpty(groups = {Add.class, Update.class}) private String categoryName;//分类名称 @NotEmpty(groups = {Add.class, Update.class}) private String categoryAlias;//分类别名
-
校验时指定要校验的分组
public Result update(@RequestBody @Validated(Category.Update.class) Category category){ }
public Result add(@RequestBody @Validated(Category.Add.class) Category category){
}
注:
-
定义校验时,如果没有指定分组,默认属于default组
-
分组之间可以继承,A extends B,那么A用有B中所有的校验项
上述步骤2.定义校验项时指定归属的分组,可简化如下
@NotNull(groups = Update.class) private Integer id;//主键ID @NotEmpty private String categoryName;//分类名称 @NotEmpty private String categoryAlias;//分类别名
删除文章分类
三层架构
CategoryController
@DeleteMapping
public Result delete(Integer id){
categoryService.delete(id);
return Result.success();
}
CategoryServiceImpl
@Override
public void delete(Integer id) {
categoryMapper.delete(id);
}
CategoryMapper
@Delete("delete from category where id=#{id}")
void delete(Integer id);
新增文章
三层架构
ArticleController
@RestController
@RequestMapping("/article")
public class ArticleController {
@Autowired
private ArticleService articleService;;
@PostMapping
public Result add(@RequestBody Article article){
articleService.add(article);
return Result.success();
}
}
ArticleServiceImpl
@Service
public class ArticleServiceImpl implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Override
public void add(Article article) {
Map<String,Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
article.setCreateUser(id);
article.setCreateTime(LocalDateTime.now());
article.setUpdateTime(LocalDateTime.now());
articleMapper.add(article);
}
}
ArticleMapper
@Mapper
public interface ArticleMapper {
@Insert("insert into article(title,content,cover_img,state,category_id,create_user,create_time,update_time)" +"" +
"values (#{title},#{content},#{coverImg},#{state},#{categoryId},#{createUser},#{createTime},#{updateTime})")
void add(Article article);
}
知识要点
参数校验
新增文章请求参数说明:
参数名称 | 说明 | 类型 | 是否必须 | 备注 | |
---|---|---|---|---|---|
title | 文章标题 | string | 是 | 1~10个非空字符 | |
content | 文章正文 | string | 是 | ||
coverImg | 封面图像地址 | string | 是 | 必须是url地址 | |
state | 发布状态 | string | 是 | 已发布 | 草稿 | |
categoryId | 文章分类ID | number | 是 |
自定义参数校验
已有的注解不能满足所有的校验需求,特殊的情况需要自定义校验 ( 自定义校验注解 )
-
自定义注解 State
package com.zjf.anno; @Documented//元注解 @Target({FIELD})//元注解 @Retention(RUNTIME)//元注解 @Constraint(validatedBy = {StateVlidation.class}) public @interface State { //提供校验后的提示信息 String message() default "{state参数的值只能是已发布或草稿}"; //指定分组 Class<?>[] groups() default {}; //负载 获取到State注解的附加信息 Class<? extends Payload>[] payload() default {}; }
-
自定义校验数据的类 StateValidation实现 ConstraintValidator 接口
package com.zjf.validation; public class StateVlidation implements ConstraintValidator<State,String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { if(value==null){ return false; } if(value.equals("已发布")||value.equals("草稿")){ return true; } return false; } }
-
在需要校验的地方使用自定义注解
public class Article {
private Integer id;//主键ID
@NotEmpty
@Pattern(regexp = "^\\S{1,10}$")
private String title;//文章标题
@NotEmpty
private String content;//文章内容
@NotEmpty
@URL
private String coverImg;//封面图像
@State
private String state;//发布状态 已发布|草稿
@NotNull
private Integer categoryId;//文章分类id
private Integer createUser;//创建人ID
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
文章列表 ( 条件分页 )
引入pagehelper依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
导入PageBean实体
package com.zjf.pojo;
//分页返回结果对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean <T>{
private Long total;//总条数
private List<T> items;//当前页数据集合
}
三层架构
ArticleController
@GetMapping
// pageNum参数用于接收请求的页码,pageSize参数用于接收每页显示的记录数;
//@RequestParam(required = false)表示该参数是可选的,可输入也可不输入
public Result<PageBean<Article> > list(Integer pageNum, Integer pageSize, @RequestParam(required = false) Integer categoryId, @RequestParam(required = false) String state){
PageBean<Article> pageBean = articleService.list(pageNum,pageSize,categoryId,state); // 获取分页查询的结果,并存储在pageBean对象中
return Result.success(pageBean);
}
ArticleServiceImpl
/**
* 重写list方法,以实现分页查询文章列表的功能。
* 根据传入的页码、每页记录数、分类ID和文章状态来筛选和分页文章。
*
* @param pageNum 请求的页码
* @param pageSize 每页显示的记录数
* @param categoryId 文章分类ID
* @param state 文章的状态
* @return 包含分页信息和文章列表的PageBean对象
*/
@Override
public PageBean<Article> list(Integer pageNum, Integer pageSize, Integer categoryId, String state) {
PageBean<Article> pb = new PageBean<>(); // 创建PageBean对象,用于存储分页信息和文章列表
PageHelper.startPage(pageNum, pageSize); //开启分页查询
Map<String, Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");//获取用户当前id
List<Article> as = articleMapper.list(categoryId, state, id);
// 将查询结果转换为Page对象
Page<Article> p = (Page<Article>) as;
// 设置PageBean的总记录数,从Page对象中获取
pb.setTotal(p.getTotal());
// 设置PageBean的文章列表,从Page对象中获取查询结果
pb.setItems(p.getResult());
// 返回包含分页信息和文章列表的PageBean对象
return pb;
}
ArticleMapper
List<Article> list(Integer categoryId, String state, Integer id);
ArticleMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zjf.mapper.ArticleMapper">
<select id="list" resultType="com.zjf.pojo.Article">
select * from article
<where>
<if test="categoryId!=null">
category_id=#{categoryId}
</if>
<if test="state!=null and state!=''">
and state=#{state}
</if>
and create_user=#{id}
</where>
</select>
</mapper>
知识要点
@RequestParam
@RequestParam注解是将请求参数绑定到控制器的方法参数上
1、语法:@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)
2、属性:
- value:表示参数名,即前端页面传过来的参数名
- defaultValue:参数默认值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值
- required:表示是否要强制包含该参数,默认值为false,表示允许请求中不包含该参数,并且该参数值会为设为null。true表示该请求中必须包含该参数否则报错
动态SQL
获取文章详情
三层架构
ArticleController
@GetMapping("/detail")
public Result detail(Integer id){
Article article = articleService.findById(id);
return Result.success(article);
}
ArticleServiceImpl
@Override
public Article findById(Integer id) {
Article article = articleMapper.findById(id);
return article;
}
ArticleMapper
@Select("select * from article where id=#{id}")
Article findById(Integer id);
参数校验
响应参数说明:
名称 | 类型 | 是否必须 | 默认值 | 备注 | 其他信息 |
---|---|---|---|---|---|
code | number | 必须 | 响应码, 0-成功,1-失败 | ||
message | string | 非必须 | 提示信息 | ||
data | object | 必须 | 返回的数据 | ||
|-id | number | 非必须 | 主键ID | ||
|-title | string | 非必须 | 文章标题 | ||
|-content | string | 非必须 | 文章正文 | ||
|-coverImg | string | 非必须 | 文章封面图像地址 | ||
|-state | string | 非必须 | 发布状态 | 已发布|草稿 | |
|-categoryId | number | 非必须 | 文章分类ID | ||
|-createTime | string | 非必须 | 创建时间 | ||
|-updateTime | string | 非必须 | 更新时间 |
响应数据样例:
{
"code": 0,
"message": "操作成功",
"data": {
"id": 4,
"title": "北京旅游攻略",
"content": "天安门,颐和园,鸟巢,长城...爱去哪去哪...",
"coverImg": "https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ad-e0f4631cbed4.png",
"state": "已发布",
"categoryId": 2,
"createTime": "2023-09-03 11:35:04",
"updateTime": "2023-09-03 11:40:31"
}
}
使用@JsonIgnore 注解在序列化和反序列化时忽略createUser字段;使用@JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”) 注解在序列化和反序列化时,按照指定的日期时间格式进行处理。
@JsonIgnore
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;//更新时间
更新文章
三层架构
ArticleController
@PutMapping
public Result update(@RequestBody @Validated(Category.Update.class) Category category){
categoryService.update(category);
return Result.success();
}
ArticleServiceImpl
@Override
public void update(Category category) {
category.setUpdateTime(LocalDateTime.now());
categoryMapper.update(category);
}
ArticleMapper
@Update("update article set title=#{title},content=#{content},cover_img=#{coverImg},state=#{state},category_id=#{categoryId},update_time=now() where id=#{id}")
void update(Article article);
参数校验
请求参数说明:
参数名称 | 说明 | 类型 | 是否必须 | 备注 | |
---|---|---|---|---|---|
id | 主键ID | number | 是 | ||
title | 文章标题 | string | 是 | ||
content | 文章正文 | string | 是 | ||
coverImg | 封面图像地址 | string | 是 | ||
state | 发布状态 | string | 是 | 已发布 | 草稿 | |
categoryId | 文章分类ID | number | 是 |
分组校验:
public class Article {
@NotNull(groups = Article.update.class)
private Integer id;//主键ID
//新增文章已添加,保持不变
public interface add{
}
public interface update{
}
}
@PostMapping
public Result add(@RequestBody @Validated(Article.add.class) Article article){
}
@PutMapping
public Result update(@RequestBody @Validated(Article.update.class) Article article){
}
删除文章
三层架构
ArticleController
@DeleteMapping
public Result delete(Integer id){
articleService.delete(id);
return Result.success();
}
ArticleServiceImpl
@Override
public void delete(Integer id) {
articleMapper.delete(id);
}
ArticleMapper
@Delete("delete from article where id=#{id}")
void delete(Integer id);
文件上传
本地存储
FileUploadController
package com.zjf.controller;
@RestController
public class FileUploadController {
@PostMapping("/upload")
public Result<String> upload(MultipartFile file) throws Exception {
// 获取上传文件的原始文件名
String originalFilename = file.getOriginalFilename();
// 生成一个新的文件名,防止文件名冲突
// 使用UUID生成唯一标识,并保留原始文件的扩展名
String filename = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
// 将文件保存到本地文件系统的代码
file.transferTo(new File("D:\\桌面\\大事件\\files\\" + filename));
// 返回一个包含文件URL的Result对象,表示文件上传成功
return Result.success("url地址......");
}
}
阿里云 OSS
阿里云对象存储 OSS ( Object Storage Service ),是一款海量、安全、低成本、高可靠的云存储服务。使用 OSS ,您
可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
- 使用操作说明详见 大事件\04_综合案例资料\05_阿里云oss\AliOSS.pdf
- OSS快速入门官方参考文档
实现步骤
-
引入SDK
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.15.1</version> </dependency>
-
导入 AliOssUtil工具类
package com.zjf.bigeventadmin.utils; import com.aliyun.oss.ClientException; import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import java.io.InputStream; public class AliOssUtil { private static final String ENDPOINT = "https://oss-cn-beijing.aliyuncs.com"; private static final String ACCESS_KEY_ID = "LTAI5tQ8e13igWZUMTjMEEQV"; private static final String SECRET_ACCESS_KEY = "MffMJoM24sc59SEBEJQDb0cfBVOAC9"; private static final String BUCKET_NAME = "big-event-gwd"; //上传文件,返回文件的公网访问地址 public static String uploadFile(String objectName, InputStream inputStream){ // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(ENDPOINT,ACCESS_KEY_ID,SECRET_ACCESS_KEY); //公文访问地址 String url = ""; try { // 创建存储空间。 ossClient.createBucket(BUCKET_NAME); ossClient.putObject(BUCKET_NAME, objectName, inputStream); url = "https://"+BUCKET_NAME+"."+ENDPOINT.substring(ENDPOINT.lastIndexOf("/")+1)+"/"+objectName; } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } } return url; } }
注意: ENDPOINT ,ACCESS_KEY_ID,ACCESS_KEY_SECRET,BUCKET_NAME根据个人对象存储OSS进行修改
-
FileUploadController
@PostMapping("/upload") public Result<String> upload(MultipartFile file) throws Exception { String originalFilename = file.getOriginalFilename(); String filename = UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf(".")); String url = AliOssUtil.uploadFile(filename, file.getInputStream()); return Result.success(url); }
登陆优化-redis
在原来的登陆实现中,用户登陆系统,当修改密码成功后,那将来用户需要使用新的密码重新登陆系统。重新登录成功后,后台会下发新的令牌,但旧的令牌在之前的程序中没有作废,仍然可以使用旧的令牌访问用户资源。
SpringBoot 集成 redis
- 导入 spring-boot-starter-data-redis 起步依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 在 yml 配置文件中 , 配置 redis 连接信息
spring:
data:
redis:
host: localhost
port: 6379
- 调用 API(StringRedisTemplate) 完成字符串的存取操作
编写redis测试代码
package com.zjf;
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
public class RedisTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void test1()
{
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set("username", "zjf");
operations.set("id","1",15, TimeUnit.SECONDS);
}
@Test
public void test2()
{
String username = stringRedisTemplate.opsForValue().get("username");
System.out.println(username);
}
}
令牌主动失效机制
- 登录成功后,给浏览器响应令牌的同时,把该令牌存储到 redis 中
/省略无关代码,保持不变/
@Autowired
private StringRedisTemplate stringRedisTemplate;
@PostMapping("/login")
public Result login(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
User loginUser = userService.findByUserName(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 token = JwtUtil.genToken(claims);
// 使用stringRedisTemplate操作Redis,将token作为key,token本身作为value存入Redis
// 设置过期时间为1小时
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set(token, token, 1, TimeUnit.HOURS);
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.set(token, token, 1, TimeUnit.HOURS);
return Result.success(token);
}
return Result.error("密码错误");
}
- LoginInterceptor 拦截器中,需要验证浏览器携带的令牌,并同时需要获取到 redis 中存储的与之相同的令牌
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
try {
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
String redisToken = operations.get(token);
if (redisToken == null) {
throw new RuntimeException();
}
Map<String, Object> claims = JwtUtil.parseToken(token);
ThreadLocalUtil.set(claims);
return true;
} catch (Exception e) {
response.setStatus(401);
return false;
}
}
- 当用户修改密码成功后,删除 redis 中存储的旧令牌
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String,String> params,@RequestHeader("Authorization") String token){
String oldPwd = params.get("old_pwd");
String newPwd = params.get("new_pwd");
String re_pwd = params.get("re_pwd");
if(!StringUtils.hasLength(oldPwd)||!StringUtils.hasLength(newPwd)||!StringUtils.hasLength(re_pwd)){
return Result.error("参数错误");
}
Map<String,Object> map= ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginUser = userService.findByUserName(username);
if(!Md5Util.getMD5String(oldPwd).equals(loginUser.getPassword())){
return Result.error("旧密码错误");
}
if(Md5Util.getMD5String(newPwd).equals(loginUser.getPassword())){
return Result.error("新密码不能与旧密码一致");
}
if(!newPwd.equals(re_pwd)){
return Result.error("两次密码不一致");
}
userService.updatePwd(newPwd);
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
operations.getOperations().delete(token);
return Result.success();
}
SpringBoot 项目部署
-
引入插件
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin>
-
Maven中Lifecycle执行package命令
-
在target目录下,CMD执行命令java -jar big_event-0.0.1-SNAPSHOT.jar
属性配置方式
项目配置文件方式
在项目resources文件夹下application.yml文件下配置
server:
port: 8080
命令行参数方式
java -jar big_event-0.0.1-SNAPSHOT.jar --server.port=8081
环境变量方式
外部配置文件方式
配置优先级
- 命令行参数
- 操作系统环境变量
- Jar 包所在目录下的 application.yml
- 项目中 resources 目录下的 application.yml
多环境开发-Profiles
1. 单文件配置
在实际项目的开发过程中,我们程序往往需要在不同环境中运行。例如:开发环境、测试环境和生产环境。
每个环境中的配置参数可能都会有所不同,例如数据库连接信息、文件服务器等等。
SpringBoot 提供的 Profiles 可以用来隔离应用程序配置的各个部分,并在特定环境下指定部分配置生效
-
如何分隔不同环境的配置?
---
-
如何指定哪些配置属于哪个环境?
spring:
config:
activate:
on-profile: 环境名称
-
如何指定哪个环境的配置生效?
spring: profiles: active: 环境名称
示例:appliction.yml
# 通用信息,指定生生效的环境 #多环境下的共性的属性 spring: profiles: active: test --- # 生产环境 spring: config: activate: on-profile: pro server: port: 8081 --- # 开发环境 spring: config: activate: on-profile: dev server: port: 8082 --- # 测试环境 spring: config: activate: on-profile: test server:8083
**注:**如果特定环境中的配置和通用信息冲突了,特定环境配置生效
2. 多文件配置
开发(dev
)、测试(test
)、生产(prod
)分别建立配置文件
-
application.yml
或者application.properties
用于存放所有环境通用的配置,指定激活的环境 -
application-dev.yml
或者application-dev.properties
存放开发环境的特殊配置 -
application-test.yml
或者application-test.properties
存放测试环境的特殊配置 -
application-prod.yml
或者application-prod.properties
存放生产环境的特殊配置多环境开发 -Pofiles- 分组
示例:
spring:
profiles:
active: dev
group:
"dev": devDB,devServer,devSelf
"pro": proDB,proServer,proSelf
"test": testDB,testServer,testself
server:
port: 8080
命令行参数方式
java -jar big_event-0.0.1-SNAPSHOT.jar --server.port=8081
环境变量方式
[外链图片转存中…(img-zXRz0HaV-1719121990679)]
外部配置文件方式
[外链图片转存中…(img-7lWx4EH8-1719121990679)]
配置优先级
- 命令行参数
- 操作系统环境变量
- Jar 包所在目录下的 application.yml
- 项目中 resources 目录下的 application.yml
多环境开发-Profiles
1. 单文件配置
在实际项目的开发过程中,我们程序往往需要在不同环境中运行。例如:开发环境、测试环境和生产环境。
每个环境中的配置参数可能都会有所不同,例如数据库连接信息、文件服务器等等。
SpringBoot 提供的 Profiles 可以用来隔离应用程序配置的各个部分,并在特定环境下指定部分配置生效
-
如何分隔不同环境的配置?
---
-
如何指定哪些配置属于哪个环境?
spring:
config:
activate:
on-profile: 环境名称
-
如何指定哪个环境的配置生效?
spring: profiles: active: 环境名称
示例:appliction.yml
# 通用信息,指定生生效的环境 #多环境下的共性的属性 spring: profiles: active: test --- # 生产环境 spring: config: activate: on-profile: pro server: port: 8081 --- # 开发环境 spring: config: activate: on-profile: dev server: port: 8082 --- # 测试环境 spring: config: activate: on-profile: test server:8083
**注:**如果特定环境中的配置和通用信息冲突了,特定环境配置生效
2. 多文件配置
开发(dev
)、测试(test
)、生产(prod
)分别建立配置文件
-
application.yml
或者application.properties
用于存放所有环境通用的配置,指定激活的环境 -
application-dev.yml
或者application-dev.properties
存放开发环境的特殊配置 -
application-test.yml
或者application-test.properties
存放测试环境的特殊配置 -
application-prod.yml
或者application-prod.properties
存放生产环境的特殊配置[外链图片转存中…(img-YJ45HlLh-1719121990679)]
多环境开发 -Pofiles- 分组
[外链图片转存中…(img-QBLV2ETX-1719121990680)]
示例:
spring:
profiles:
active: dev
group:
"dev": devDB,devServer,devSelf
"pro": proDB,proServer,proSelf
"test": testDB,testServer,testself