Springboot后端实战篇

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加密
  1. 导入Md5Utils工具类

  2. 将输入的密码使用Md5Util类进行MD5加密生成md5String

     @Override
        public void register(String username, String password) {
            //加密
            String md5String = Md5Util.getMD5String(password);
            userMapper.insertUser(username,md5String);
        }
    

参数校验

使用 Spring Validation, 对注册接口的参数进行合法性校验

  1. 引入 Spring Validation 起步依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    
  2. 在参数前面添加 @Pattern 注解

    register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) 
    
  3. 在 Controller 类上添加 @Validated 注解

  4. 在全局异常处理器中处理参数校验失败的异常

    ​ 当用户名和密码的长度不满足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 由三部分组成,通过点(.)分隔:

在这里插入图片描述

  1. Header(头部): 包含了令牌的元数据信息,例如算法(HS256、RS256、HMAC256)、令牌类型(JWT)等。
  2. Payload(负载): 包含了被加密的数据,例如用户的身份信息或其他声明。
  3. Signature(签名): 使用头部指定的算法和密钥对头部和负载进行签名,确保数据的完整性和认证。

这三部分通过Base64 URL编码后连接在一起,形成最终的JWT令牌。

JWT 使用

JWT 的主要使用场景是在客户端和服务器之间安全地传递信息,特别是在身份验证过程中。一般的流程如下:

  1. 认证流程:

    • 用户向服务器提供用户名和密码进行身份验证。
    • 服务器验证用户的身份,并生成包含用户信息的JWT。
    • 服务器将生成的JWT返回给客户端。
  2. 请求认证:

    • 客户端在以后的请求中将JWT放置在请求头部的Authorization字段中,通常格式为 Authorization: Bearer <token>

    • 服务器接收到请求后,解析JWT并验证签名的有效性。

    • 如果验证成功,服务器处理请求;如果验证失败或者JWT过期,服务器拒绝请求。


拦截器

拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行,是java中AOP思想的运用。

  1. 创建拦截器

在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):这个方法在整个请求完成后被调用,即在视图渲染之后。在这个方法中,可以执行一些清理工作,如释放资源、记录日志等。

  1. 配置拦截器

    创建一个配置类,实现WebMvcConfigurer接口,并重写addInterceptors方法。在这个方法中,你可以使用InterceptorRegistry来注册你的拦截器,并指定拦截器应该作用的路径或者排除的路径。

  2. 注册拦截器

    addInterceptors方法中,使用InterceptorRegistryaddInterceptor方法来注册你的拦截器。然后,你可以使用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对象

在这里插入图片描述

实体参数校验

  1. 实体类的成员变量上添加注解
    • @NotNull
    • @NotEmpty
    • @Email
  2. 接口方法的实体参数上添加 @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主键IDnumber
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的错误。为解决此问题,就要用到分组校验。

分组校验

​ 把校验项进行归类分组,在完成不同的功能的时候,校验指定组中的校验项

  1. 定义分组

    在category实体类中定义了两个空接口Add和Update,用于分组校验

    public class Category {
    public interface Add{
    }
    public  interface Update{
    }
    }
    
  2. 定义校验项时指定归属的分组

    @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;//分类别名
    
  3. 校验时指定要校验的分组

    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文章标题string1~10个非空字符
content文章正文string
coverImg封面图像地址string必须是url地址
state发布状态string已发布 | 草稿
categoryId文章分类IDnumber

自定义参数校验
已有的注解不能满足所有的校验需求,特殊的情况需要自定义校验 ( 自定义校验注解 )

  1. 自定义注解 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 {};
    }
    
  2. 自定义校验数据的类 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;
        }
    }
    
  3. 在需要校验的地方使用自定义注解

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

动态 SQL_MyBatis中文网

获取文章详情

三层架构

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);

参数校验

响应参数说明:

名称类型是否必须默认值备注其他信息
codenumber必须响应码, 0-成功,1-失败
messagestring非必须提示信息
dataobject必须返回的数据
|-idnumber非必须主键ID
|-titlestring非必须文章标题
|-contentstring非必须文章正文
|-coverImgstring非必须文章封面图像地址
|-statestring非必须发布状态已发布|草稿
|-categoryIdnumber非必须文章分类ID
|-createTimestring非必须创建时间
|-updateTimestring非必须更新时间

响应数据样例:

{
    "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主键IDnumber
title文章标题string
content文章正文string
coverImg封面图像地址string
state发布状态string已发布 | 草稿
categoryId文章分类IDnumber

分组校验:

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 ,您
可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。

在这里插入图片描述


实现步骤
  1. 引入SDK

    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.15.1</version>
    </dependency>
    
  2. 导入 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进行修改

  1. 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 项目部署

在这里插入图片描述

  1. 引入插件

    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    
  2. Maven中Lifecycle执行package命令

  3. 在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

环境变量方式

在这里插入图片描述

外部配置文件方式

在这里插入图片描述

配置优先级

  1. 命令行参数
  2. 操作系统环境变量
  3. Jar 包所在目录下的 application.yml
  4. 项目中 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)]

配置优先级

  1. 命令行参数
  2. 操作系统环境变量
  3. Jar 包所在目录下的 application.yml
  4. 项目中 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

  • 21
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值