系统脚手架

一、基础环境

1.开发环境

  • JAVA版本: JDK1.8

  • Maven版本: 3.3.9

  • MySql版本: 8.0.2

  • Spring版本: 2.7.5


2.基础依赖

  • spring-boot-starter

    • 作用: 提供 Spring Boot 核心功能和自动配置(日志、基础组件等)

    • maven 坐标

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

    • 作用: 提供 Web 开发功能,如 Spring MVC、内嵌 Tomcat,用于构建 RESTful 接口和 Web 应用

    • maven 坐标

          <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
      
  • spring-boot-devtools

    • 作用: 提供开发时的热部署、自动重启等功能,提升开发效率

    • maven坐标

              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-devtools</artifactId>
                  <scope>runtime</scope>
              </dependency>
      
  • lombok

    • 作用: 通过注解自动生成 getter/setter、构造器等,减少样板代码

    • maven坐标

              <dependency>
                  <groupId>org.projectlombok</groupId>
                  <artifactId>lombok</artifactId>
                  <scope>annotationProcessor</scope>
              </dependency>
      

二、集成Mybatis Plus插件

1.导入依赖

  • mybatis-plus-boot-starter

    • 作用: 整合 MyBatis-Plus 与 Spring Boot,提供增强的 CRUD、条件构造器、分页等功能

    • 版本: 3.5.2

    • maven坐标

              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-boot-starter</artifactId>
                  <version>3.5.2</version>
              </dependency
      
  • mybatis-plus-generator

    • 作用: MyBatis-Plus 代码生成器,用于根据数据库表快速生成实体、Mapper、Service 等代码

    • 版本: 3.5.2

    • maven坐标

              <dependency>
                  <groupId>com.baomidou</groupId>
                  <artifactId>mybatis-plus-generator</artifactId>
                  <version>3.5.2</version>
              </dependency>
      
  • mysql-connector-java

    • 作用: 提供连接 MySQL 数据库所需的 JDBC 驱动,实现数据源连接和 SQL 通讯

    • 版本: 由 Spring Boot 2.7.5 的依赖管理自动指定(MySQL JDBC 驱动)

    • maven坐标

              <dependency>
                  <groupId>mysql</groupId>
                  <artifactId>mysql-connector-java</artifactId>
                  <scope>runtime</scope>
              </dependency>
      
  • freemarker

    • 作用: 模板引擎,配合代码生成器使用,根据模板生成 Java 源码等文件

    • 版本: 2.3.31

    • maven坐标

              <dependency>
                  <groupId>org.freemarker</groupId>
                  <artifactId>freemarker</artifactId>
                  <version>2.3.31</version>
              </dependency>
      

2.配置文件编辑

  • 数据库文件配置

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/vueshop?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
        username: root
        password: ****
    
    
  • mybatis配置

    mybatis-plus:
      global-config:
        db-config:
          logic-delete-value: 1 # 逻辑已删除值(默认为 1)
          logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    

3.集成分页与全表更新代码

  • 类名c.v.c.MybatisPlusConfig

    • 具体代码

          @Bean
          public MybatisPlusInterceptor mybatisPlusInterceptor() {
              MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
              // 防止全表更新和删除
              interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
              // 分页
              interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
      
              return interceptor;
          }
      

三、集成Sql语句打印分析

1.导入依赖

  • p6spy

    • 作用: 拦截并输出实际执行的 SQL 及参数,便于调试和分析数据库访问性能

    • 版本: 3.9.1

    • maven坐标:

      <dependency>
          <groupId>p6spy</groupId>
          <artifactId>p6spy</artifactId>
          <version>3.9.1</version>
      </dependency>
      

2.配置文件编辑

  • p6spy配置

    # ===================== P6Spy 配置(用于输出 SQL 日志) =====================
    
    # 指定启用的 P6Spy 模块:
    # - com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory:
    #     使用 MyBatis-Plus 提供的日志工厂,输出格式更友好,支持格式化 SQL。
    # - com.p6spy.engine.outage.P6OutageFactory:
    #     开启“长时间执行 SQL 检测”(outage detection)功能,用来发现执行很慢的 SQL。
    modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
    
    # 备用配置示例(P6Spy 原生日志工厂),当前被注释掉:
    # 如果想使用 P6Spy 自带的普通日志格式,可以改用这一行。
    #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
    
    # 自定义 SQL 日志输出格式类:
    # 使用 MyBatis-Plus 内置的 P6Spy 日志格式化器,会输出执行耗时、SQL 语句等信息,并对 SQL 进行格式化。
    logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
    
    # 日志输出目标:
    # - StdoutLogger:直接输出到控制台(System.out),开发环境查看最方便。
    appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
    
    # 如果希望用日志框架(如 Logback / Log4j2)记录 SQL,而不是直接打印到控制台,
    # 可以改用 Slf4JLogger(当前被注释掉):
    #appender=com.p6spy.engine.spy.appender.Slf4JLogger
    
    # 是否在初始化时注销已注册的 JDBC Driver:
    # true 表示先注销已有 Driver,再由 P6Spy 代理接管,避免多个 Driver 冲突。
    deregisterdrivers=true
    
    # 是否使用 URL 前缀区分是否走 P6Spy:
    # true 表示 JDBC URL 需要使用前缀(如 jdbc:p6spy:mysql://...)。
    useprefix=true
    
    # 需要排除、不记录的日志类别(逗号分隔):
    # 可选类别包括:error, info, batch, debug, statement, commit, rollback, result, resultset 等。
    # 这里表示不记录 info、debug、result、commit、resultset,减少日志噪音。
    excludecategories=info,debug,result,commit,resultset
    
    # 日志中时间戳的格式:
    dateformat=yyyy-MM-dd HH:mm:ss
    
    # 示例:指定真正使用的数据库 Driver(当前被注释):
    # 一般在 Spring Boot 中不需要手动配置,由数据源配置决定。
    #driverlist=org.h2.Driver
    
    # 是否开启“慢 SQL”检测(Outage Detection):
    # true 表示开启,当 SQL 执行时间超过阈值时会输出慢 SQL 提示。
    outagedetection=true
    
    # 慢 SQL 判定阈值(单位:秒):
    # 当某条 SQL 执行时间 >= 2 秒时,会被记录为慢 SQL。
    outagedetectioninterval=2
    

四、统一结果集封装

1.业务目标

  • 核心作用: 用于在接口层统一返回状态码、提示信息和数据结构,方便前后端约定与处理。

  • 状态码约定: 200代表正常,非200(如默认失败状态码为400)表示异常。


2.结果集数据结构

  • 包含序列化: 实现 Serializable 接口以便于网络传输。

  • code: 整型,表示状态码。

  • msg: 字符串,表示返回的提示信息。

  • data: Object类型,表示接口实际返回的业务负载数据。


3.具体代码实现

  • 类路径: com.vueshop.lang.Result

    • 具体代码:

      package com.vueshop.lang;
      
      import lombok.Data;
      import java.io.Serializable;
      
      /**
       * 统一的接口返回结果封装
       */
      @Data
      public class Result implements Serializable {
          // 200是正常,非200表示异常
          private int code;
          // 返回信息
          private String msg;
          // 结果数据
          private Object data;
      
          /**
           * 成功 - 无数据返回
           */
          public static Result success() {
              return success(null);
          }
      
          /**
           * 成功 - 带数据返回
           */
          public static Result success(Object data) {
              return success(200, "操作成功", data);
          }
      
          /**
           * 成功 - 自定义状态码、信息和数据
           */
          public static Result success(int code, String msg, Object data) {
              Result r = new Result();
              r.setCode(code);
              r.setMsg(msg);
              r.setData(data);
              return r;
          }
      
          /**
           * 失败 - 默认400状态码,不带数据
           */
          public static Result fail(String msg) {
              return fail(400, msg, null);
          }
      
          /**
           * 失败 - 默认400状态码,带数据
           */
          public static Result fail(String msg, Object data) {
              return fail(400, msg, data);
          }
      
          /**
           * 失败 - 自定义状态码、信息和数据
           */
          public static Result fail(int code, String msg, Object data) {
              Result r = new Result();
              r.setCode(code);
              r.setMsg(msg);
              r.setData(data);
              return r;
          }
      }
      

五、全局异常处理

1.业务目标

  • 统一捕获业务层抛出的异常,返回标准的接口结果结构,避免向前端暴露服务端代码异常详情。

  • 集中记录异常日志,避免在各个 Controller 中重复编写冗余的 try-catch 代码块。

  • 通过自定义异常类区分系统异常和业务异常,并携带业务错误码和错误信息,方便接口统一返回和日志排查。


2.自定义业务异常封装

  • 核心作用: 在业务处理过程中统一抛出特定业务错误,继承自 RuntimeException 异常类。

  • 包含属性: 包含异常提示信息 msg 和默认值为 500 的异常码 code

  • 类路径: com.vueshop.common.exception.HubException

    • 具体代码:

      package com.vueshop.common.exception;
      
      /**
       * 自定义业务异常
       */
      public class HubException extends RuntimeException{
          // 异常提示信息
          private String msg;
          // 异常码
          private int code = 500;
      
          /**
           * 仅自定义异常信息的构造方法
           */
          public HubException(String msg) {
              super(msg);
              this.msg = msg;
          }
      
          /**
           * 自定义异常信息与异常码的构造方法
           */
          public HubException(String msg, int code) {
              super(msg);
              this.msg = msg;
              this.code = code;
          }
      }
      

3.全局异常拦截器实现

  • 核心作用: 使用 @RestControllerAdvice 注解拦截 Controller 层的异常,配合 @ExceptionHandler@ResponseStatus 实现异常分类处理,并将结果封装为之前定义的 Result 结构返回。

  • 拦截类型:

    • HubException: 拦截自定义业务异常,固定返回 HTTP 400 状态码。

    • RuntimeException: 全局兜底的运行时异常处理,统一返回 HTTP 400 状态码。

    • NoHandlerFoundException: 处理 404 资源未找到异常,返回 HTTP 404 状态码及 “404 页面不存在!” 提示。

    • MethodArgumentNotValidException: 处理 @Valid/@Validated 参数校验失败异常,返回 HTTP 400 状态码,并提取首个参数校验错误信息作为提示信息返回给前端。

  • 类路径: com.vueshop.common.exception.GlobalExceptionHandler

    • 具体代码:

      package com.vueshop.common.exception;
      
      import com.vueshop.lang.Result;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.http.HttpStatus;
      import org.springframework.validation.BindingResult;
      import org.springframework.validation.ObjectError;
      import org.springframework.web.bind.MethodArgumentNotValidException;
      import org.springframework.web.bind.annotation.ExceptionHandler;
      import org.springframework.web.bind.annotation.ResponseStatus;
      import org.springframework.web.bind.annotation.RestControllerAdvice;
      import org.springframework.web.servlet.NoHandlerFoundException;
      
      /**
       * 全局异常处理
       */
      @Slf4j
      @RestControllerAdvice
      public class GlobalExceptionHandler {
      
          /**
           * 处理自定义业务异常
           */
          @ResponseStatus(HttpStatus.BAD_REQUEST)
          @ExceptionHandler(value = HubException.class)
          public Result handler(HubException e) {
              log.error("自定义异常 ----{}", e.getMessage());
              return Result.fail(e.getMessage());
          }
      
          /**
           * 全局兜底的运行时异常处理
           */
          @ResponseStatus(HttpStatus.BAD_REQUEST)
          @ExceptionHandler(value = RuntimeException.class)
          public Result handler(RuntimeException e) {
              log.error("运行时异常 ----{}", e.getMessage());
              return Result.fail(e.getMessage());
          }
      
          /**
           * 处理 404 资源未找到异常
           */
          @ResponseStatus(HttpStatus.NOT_FOUND)
          @ExceptionHandler(value = NoHandlerFoundException.class)
          public Result handler(NoHandlerFoundException e) {
              log.error("页面不存在异常 ---- {}",e.getMessage());
              return Result.fail("404 页面不存在!");
          }
      
          /**
           * 处理参数校验失败异常
           */
          @ResponseStatus(HttpStatus.BAD_REQUEST)
          @ExceptionHandler(value = MethodArgumentNotValidException.class)
          public Result handler(MethodArgumentNotValidException  e) {
              // 获取绑定的所有字段异常
              BindingResult bindingResult = e.getBindingResult();
              // 获取出错误中第一个错误信息[N个就取第一个]
              ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
              // 输出并返回实体类校验异常的默认信息
              log.error("运行时异常 ----{}", objectError.getDefaultMessage());
              return Result.fail(objectError.getDefaultMessage());
          }
      }
      

六、腾讯云对象存储

1.业务目标

  • 核心作用: 封装与腾讯云 COS (Cloud Object Storage) 对象存储相关的上传逻辑,对外提供统一的文件上传接口。

  • 功能约定: 支持批量上传图片,并在上传前按指定比例和质量对图片流进行压缩处理。


2.导入依赖

  • cos_api

    • 作用: 接入腾讯云对象存储服务(COS),用于文件/图片上传、下载与管理。

    • 版本: 5.6.89

    • maven坐标:

      <dependency>
          <groupId>com.qcloud</groupId>
          <artifactId>cos_api</artifactId>
          <version>5.6.89</version>
      </dependency>
      
  • hutool-all

    • 作用: 提供丰富的 Java 工具类(如图片处理、IO 工具等),简化日常开发。

    • 版本: 5.8.9

    • maven坐标:

      <dependency>
          <groupId>cn.hutool</groupId>
          <artifactId>hutool-all</artifactId>
          <version>5.8.9</version>
      </dependency>
      

3.配置文件编辑

  • 存储桶参数配置(application.yml

    cos:
      secretId: AKIDmUI2c....
      secretKey: yOiro.....
      region: ap-guangzhou
      bucketName: photo-1320933175
    

4.腾讯云上传图片实现

  • 接口定义:com.vueshop.service.AppUploadService

    • 核心方法:

      List<String> upload(MultipartFile[] files, float scale, double quality) throws IOException;
      
    • 参数说明:

      • files: 待上传的图片文件数组

      • scale: 缩放比例(例如 0.5 表示宽高压缩为原来的一半)

      • quality: 压缩质量(0–1 之间的小数,值越小图片越模糊、体积越小)

    • 返回值:上传成功后每张图片在 COS 上的访问 URL 列表


  • 实现类:com.vueshop.service.impl.AppUploadServiceImpl

    • 类注解:使用 @Service 注解标记为 Spring 服务类

    • 配置属性注入(@Value 注解):

      @Value("${cos.secretId}")
      private String secretId;
      @Value("${cos.secretKey}")
      private String secretKey;
      @Value("${cos.region}")
      private String region;
      @Value("${cos.bucketName}")
      private String bucketName;
      
    • 实现流程:

      1. 校验图片格式

        • 检查参数是否为空或长度为 0,若为空则返回空列表

        • 使用 FileTypeUtil.getType() 校验文件流类型

        • 只支持 pngjpgjpeg 三种图片格式(不区分大小写)

        • 使用 Assert.isTrue() 断言验证文件非空和类型合法性,不符合则抛出异常

      2. 压缩图片

        • 创建 ByteArrayOutputStream 输出流

        • 使用 Img.from(pic.getInputStream()) 将输入流转换为图片对象

        • 调用 .scale(scale) 进行比例缩放

        • 调用 .setQuality(quality) 设置压缩质量

        • 调用 .write(outputStream) 将压缩后的图片写入输出流

      3. 上传到腾讯云 COS

        • 生成唯一的文件名(使用 UUID.randomUUID().toString()

        • 拼接存储路径:"image/" + uuid + "." + type

        • 创建 PutObjectRequest 对象:

          • 调用 getClient().putObject(putObjectRequest) 上传文件

          • 拼接图片访问 URL:"https://" + bucketName + ".cos." + region + ".myqcloud.com/" + path

          • 将 URL 添加到结果列表并返回

      public List<String> upload(MultipartFile[] pics, float scale, double quality) throws IOException {
      
              // 1.检查图片是否符合要求
              if (pics == null || pics.length == 0){
                  return new ArrayList<>();
              }
      
              List<String> paths = new ArrayList<>() ;
              for (MultipartFile pic : pics){
                  // 通过hutool工具校验传入的输入流的类型
                  String type = FileTypeUtil.getType(pic.getInputStream());
                  // 断言验证是否为空
                  Assert.isTrue(!pic.isEmpty(),"图片错误");
                  // 断言验证类型[本质是对集合每一条图片格式进行尾缀格式校验]
                  Assert.isTrue(Arrays.asList("png","jpg","jpeg").contains(type.toLowerCase()),"不支持图片类型");
      
              // 2.压缩图片
              ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                  // 设置压缩图片质量并写入到输出流中
                  Img.from(pic.getInputStream())
                          .scale(scale)
                          .setQuality(quality)
                          .write(outputStream)
                  ;
      
              // 3.上传到腾讯云
              String key = UUID.randomUUID().toString();
                  String path = "image/" + key + "." + type;
                  PutObjectRequest putObjectRequest = new PutObjectRequest(this.bucketName,path,new ByteArrayInputStream(outputStream.toByteArray()),null);
                  getClient().putObject(putObjectRequest);
      
                  String picPath = "https://" + this.bucketName + ".cos." + this.region + ".myqcloud.com/" +path;
                  paths.add(picPath);
              }
      
              return paths;
          }
      
    • COS 客户端构建(getClient() 方法):

      private COSClient getClient() {
          // 1. 设置认证凭证
          COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
          
          // 2. 配置客户端参数
          ClientConfig clientConfig = new ClientConfig();
          clientConfig.setRegion(new Region(this.region));      // 设置地域
          clientConfig.setHttpProtocol(HttpProtocol.https);     // 使用 HTTPS 协议
          
          // 3. 创建并返回 COS 客户端
          return new COSClient(cred, clientConfig);
      }
      

七、解决跨域问题

1.业务目标

  • 核心作用:统一配置后端接口的 CORS 策略,解决前后端分离下的跨域访问问题。

  • 约定:

    • 对所有路径(/**)生效

    • 允许指定来源、请求头和请求方法

    • 暴露 Authorization 响应头,方便前端获取 Token 等信息


2.配置类实现

  • 类路径:com.vueshop.config.CorsConfig

  • 核心注解:

    • @Configuration:标记为 Spring 配置类

    • implements WebMvcConfigurer:实现 Web MVC 配置接口

  • 具体代码:

    package com.vueshop.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
    
        /**
         * 构建通用的跨域配置。
         *
         * 约定:
         * - 允许所有来源发起跨域请求("*")
         * - 允许所有请求头和请求方法
         * - 暴露 "Authorization" 响应头,方便前端获取 Token 等信息
         *
         * @return 配置完成的 CorsConfiguration 实例
         */
        private CorsConfiguration buildConfig() {
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.addAllowedOrigin("*");
            corsConfiguration.addAllowedHeader("*");
            corsConfiguration.addAllowedMethod("*");
            corsConfiguration.addExposedHeader("Authorization");
            return corsConfiguration;
        }
    
        /**
         * 注册全局 CORS 过滤器。
         *
         * 对所有请求路径(/**)应用 buildConfig() 中定义的跨域策略。
         *
         * @return 全局跨域过滤器实例
         */
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", buildConfig());
            return new CorsFilter(source);
        }
    
        /**
         * 使用 Spring MVC 提供的方式配置跨域映射。
         *
         * 约定:
         * - 对所有路径(/**)开放跨域访问
         * - 允许所有来源,允许 GET、POST、DELETE、PUT 请求方法
         * - 预检请求的缓存时间为 3600 秒
         *
         * @param registry CORS 映射注册器
         */
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")
    //          .allowCredentials(true)
                    .allowedMethods("GET", "POST", "DELETE", "PUT")
                    .maxAge(3600);
        }
    }
    

3.配置说明


配置项

说明

addAllowedOrigin("*")

允许所有来源发起跨域请求

addAllowedHeader("*")

允许所有请求头

addAllowedMethod("*")

允许所有请求方法(GET、POST、PUT、DELETE 等)

addExposedHeader("Authorization")

暴露 Authorization 响应头,前端可获取 Token

.maxAge(3600)

预检请求(OPTIONS)缓存 3600 秒(1 小时)

@Bean CorsFilter

注册全局 CORS 过滤器,对所有路径生效

addCorsMappings()

Spring MVC 方式配置跨域映射