在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


后端项目初始化


环境准备


  1. JDK 安装
    • 安装的 JDK 版本必须是 8、11 或 17,不能超过 17!推荐使用 11 版本,因为后续可能要用到的缓存库 Caffeine 要求使用 11 版本。
    • 可参考视频安装 JDK:JDK 安装教程
  2. MySQL 数据库安装
    • 最好安装 8.x 版本,或者 5.7 版本。

新建项目


在 IDEA 中新建项目,选择 Spring Initializr 模板。考虑到稳定性,此处选择创建 Java 8 版本的项目。

注意需要替换 Server URL 为 Cloud Native App Initializer ,因为官方的 Server URL 不支持选择 Java 8。

配置如下:

image-20250622191241139

  • Spring Boot 版本:选择 2.7.6。
  • 依赖:根据自己的需要添加一些依赖,比如 Spring Web、MyBatis、MySQL、Lombok。当然,后续通过修改 Maven 配置添加依赖也是可以的。

image-20250622191502364

点击创建,就得到了一个 Spring Boot 项目,需要等待 Maven 为我们安装依赖。


安装完依赖后,先尝试启动一下项目:

image-20250622193358516


结果会报错:

image-20250622191935235

因为我们在 Maven 中引入了 MySQL 依赖,但是项目配置文件中并没有填写 MySQL 的配置。


修改资源目录下的配置文件为 application.yml,指定项目启动的端口号和访问地址前缀、项目名称、数据库配置等。代码如下:

image-20250622193512914

如上图,只保留一个配置文件 yml 即可,删除 properties

server:
  port: 8123
  servlet:
    context-path: /api   # url 后缀, localhost: 8123 和 localhost: 8123/api , 后者会访问成功

spring:
  application:
    name: yu-picture-backend
  # 数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/yu_picture  # 协议 + 本机端口号 + 数据库
    username: root
    password: 123456

image-20250622193130583


重新运行启动类,成功启动:

image-20250622193611938


在浏览器上运行路径: localhost:8123/api/ ,有如下页面,说明模板创建的项目访问成功;

image-20250622193914931


整合依赖


1. MyBatis Plus 数据库操作


MyBatis Plus 是 MyBatis 的增强工具,旨在简化开发流程。它提供了开箱即用的 CRUD 方法、动态查询构造器、分页插件和代码生成器等功能,大幅减少重复代码,同时保持与 MyBatis 原生功能的兼容性。

例如,通过调用 baseMapper.selectById(id),可以直接查询数据库中的记录,而无需手动编写 SQL。参考官方文档引入:MyBatis Plus 快速开始


在 Maven 的 pom.xml 中添加依赖:

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.9</version>
</dependency>

注意,添加该依赖后,记得移除 MyBatis 相关的依赖!否则很容易导致版本冲突。

image-20250623101557188


在项目中新建 mapper 包,后续用于存放操作数据库的 Mapper 类,然后在项目启动类中添加扫描 Mapper 的 @MapperScan 注解:

image-20250623110434880

@SpringBootApplication
@MapperScan("com.yupi.yupicturebackend.mapper")
public class YuPictureBackendApplication {
    public static void main(String[] args) {
        SpringApplication.run(YuPictureBackendApplication.class, args);
    }
}

application.yml 中追加配置,开启日志和逻辑删除功能:

image-20250623112404400

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: false
    # 仅在开发环境开启日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: isDelete # 全局逻辑删除的实体字段名
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

MyBatis-Plus 3.5.9 新特性

image-20250623113038237


MyBatis 和 MyBatis-Plus 都是用于操作数据库的框架,但它们之间存在版本差异,可能会导致一些问题。MyBatis 是一个基础的 ORM 框架,而 MyBatis-Plus 是在 MyBatis 基础上进行增强的框架,提供了更多便捷的功能和特性。

思考:为什么引入 MyBatis-Plus 依赖后,就不再需要单独引入 MyBatis 依赖了呢?

image-20250623115722537

通过依赖分析插件,我们发现 MyBatis-Plus 不仅整合了 MyBatis-Plus 插件,还整合了 MyBatis-Spring。因此,引入 MyBatis-Plus 依赖后,无需再手动引入 MyBatis 依赖,从而避免了因两个依赖中内置的 MyBatis 版本不同而导致的冲突问题


2. Hutool 工具库


Hutool 是主流的 Java 工具类库,集合了丰富的工具类,涵盖字符串处理、日期操作、文件处理、加解密、反射、正则匹配等常见功能。它的轻量化和无侵入性让开发者能够专注于业务逻辑而不必编写重复的工具代码。例如,DateUtil.formatDate(new Date()) 可以快速将当前日期格式化为字符串。

参考官方文档引入:Hutool 简介

在 Maven 的 pom.xml 中添加依赖:

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

添加依赖后,在依赖下面增加必要的说明:

image-20250623120708222


3. Knife4j 接口文档


简介
Knife4j 是基于 Swagger 的增强工具,提供了更加友好的 API 文档界面和功能扩展,例如动态参数调试、分组文档等。它适合用于 Spring Boot 项目中,能够通过简单的配置自动生成接口文档,让开发者和前端快速了解和调试接口,提高开发效率。

参考文档
Knife4j 快速开始

image-20250623121525102

核心要点:注意使用的 SpringBoot 版本,注意 OpenAPI 是一种编写代码规范


配置步骤如下:

(1) 添加依赖
由于使用的是 Spring Boot 2.x,需要选择 OpenAPI 2 的版本。在 Maven 的 pom.xml 中添加以下依赖:

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
    <version>4.4.0</version>
</dependency>

(2) 新建 Controller 包

新建 controller 包,用于存放 API 接口。将模板创建的 demos.web 包下的代码移动到其中,仅用于测试。

image-20250623122551085

demo.web 文件夹中的类复制到 Controller 包后,应删除 demo.web 文件夹,以避免因同名类导致的 Bean 冲突问题


(3) 配置接口文档

application.yml 中追加接口文档配置,扫描 Controller 包:

# 接口文档配置
knife4j:
  enable: true
  openapi:
    title: 接口文档
    version: v1.0
    # 以下是最重要的配置, 识别是哪一个接口需要生成接口文档
    group:
      default:
        group-name: 默认分组
        api-rule: package  # 扫描规则: 扫描一个包下所有的接口
        api-rule-resources:
          - com.yupi.intelligentcollaborativecloudmaplibrary.Controller  # Controller 包的路径

(4) 启动并访问

重启项目后,我们发现多了一条关于生成的接口文档的日志:

image-20250623123422145


访问 http://localhost:8123/api/doc.html ,即可看到接口文档并进行测试调用。

image-20250623123855107


那 knife4j 生成接口文档的依据是什么呢?依据是 OpenAPI 规范。

OpenAPI(原名 Swagger)是一种标准化的接口描述语言,用于定义 RESTful API 的结构和行为。通过 OpenAPI 规范,开发者可以清晰地描述接口的路径、参数、请求体、响应体等信息,从而实现接口文档的自动化生成和维护。

image-20250623124558879


它通过解析代码中的注解(如 @Api@ApiOperation 等),自动生成直观的 API 文档,支持动态调试和分组功能,方便前后端协作:

image-20250623124902126

因此,使用 Knife4j 时,开发者只需按照 OpenAPI 规范在代码中添加相应的注解,Knife4j 就能自动解析这些注解并生成对应的接口文档,让前端和后端开发者能够更高效地进行协作和接口测试。


4. 其他依赖


AOP 切面编程
可以按需引入其他依赖,例如 AOP 切面编程(无需手动指定版本号):

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

启动类注解(可选)

给启动类添加以下注解:

@EnableAspectJAutoProxy(exposeProxy = true)

image-20250623125216987

  • 解释
    exposeProxy = true 的作用是通过 Spring AOP 提供对当前代理对象的访问,使得可以在业务逻辑中访问到当前的代理对象。
  • 你可以在方法执行时通过 AopContext.currentProxy() 获取当前的代理对象。

后续可根据需要随时引入更多依赖。


通用基础代码


简介
通用基础代码是指在任何后端项目中都可以复用的代码。这种代码通常“一辈子只用写一次”,了解其作用后可以直接复制粘贴,无需记忆。


目录结构

image-20250623135845641


1. 自定义异常


自定义错误码

自定义错误码,对错误进行收敛,便于前端统一处理。这里有 2 个小技巧:

  • 自定义错误码时,建议与主流的错误码(例如 HTTP 错误码)的含义保持一致。例如,“未登录”定义为 40100,与 HTTP 401 错误(用户需要进行身份认证)保持一致,更容易理解。
  • 错误码不要完全连续,预留一些间隔,便于后续扩展。

错误码枚举类

exception 包下新建错误码枚举类 ErrorCode.java

image-20250623130136770

import lombok.Getter;

@Getter
public enum ErrorCode {
    SUCCESS(0, "ok"),
    PARAMS_ERROR(40000, "请求参数错误"),
    NOT_LOGIN_ERROR(40100, "未登录"),
    NO_AUTH_ERROR(40101, "无权限"),
    NOT_FOUND_ERROR(40400, "请求数据不存在"),
    FORBIDDEN_ERROR(40300, "禁止访问"),
    SYSTEM_ERROR(50000, "系统内部异常"),
    OPERATION_ERROR(50001, "操作失败");

    private final int code;
    private final String message;

    ErrorCode(int code, String message) {
        this.code = code;
        this.message = message;
    }
}

业务异常类

一般不建议直接抛出 Java 内置的 RuntimeException,而是自定义一个业务异常类 BusinessException.java,与内置的异常类区分开,便于定制化输出错误信息:

image-20250623130628791

import lombok.Getter;

@Getter
public class BusinessException extends RuntimeException {
    private final int code;

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }

    public BusinessException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.code = errorCode.getCode();
    }

    public BusinessException(ErrorCode errorCode, String message) {
        super(message);
        this.code = errorCode.getCode();
    }
}

2. 异常抛出工具类: ThrowUtils


为了更方便地根据条件抛出异常,可以封装一个 ThrowUtils 工具类,类似于断言类,用于简化抛异常的代码:

image-20250623131119952

public class ThrowUtils {
    /**
     * 条件成立则抛出异常
     *
     * @param condition        条件
     * @param runtimeException 异常
     */
    public static void throwIf(boolean condition, RuntimeException runtimeException) {
        if (condition) {
            throw runtimeException;
        }
    }

    /**
     * 条件成立则抛出异常
     *
     * @param condition 条件
     * @param errorCode 错误码
     */
    public static void throwIf(boolean condition, ErrorCode errorCode) {
        throwIf(condition, new BusinessException(errorCode));
    }

    /**
     * 条件成立则抛出异常
     *
     * @param condition 条件
     * @param errorCode 错误码
     * @param message   错误信息
     */
    public static void throwIf(boolean condition, ErrorCode errorCode, String message) {
        throwIf(condition, new BusinessException(errorCode, message));
    }
}

3. 响应包装类:BaseResponse ResultUtils


背景

通常情况下,每个后端接口都需要返回调用码、数据和调用信息等,前端可以根据这些信息进行相应的处理。为了便于前端统一获取这些信息,可以封装一个统一的响应结果类。


通用响应类:BaseResponse

image-20250623131710863

/**
 * 全局响应封装类
 * @param <T>  对每个接口的返回值进行封装, 统一每个接口的返回结果
 */

@Data
public class BaseResponse<T> implements Serializable {
    private int code;    // 状态码
    private T data;      // 数据
    private String message; // 描述信息

    public BaseResponse(int code, T data, String message) {
        this.code = code;
        this.data = data;
        this.message = message;
    }

    public BaseResponse(int code, T data) {
        this(code, data, "");
    }

    public BaseResponse(ErrorCode errorCode) {
        this(errorCode.getCode(), null, errorCode.getMessage());
    }
}

响应工具类:ResultUtils

为了简化接口返回值的创建过程,可以新建一个工具类 ResultUtils,提供成功调用和失败调用的方法,支持灵活地传参:

image-20250623132437789

/**
 * 响应结果工具类
 */
public class ResultUtils {
    /**
     * 成功响应
     *
     * @param data 数据
     * @param <T>  数据类型
     * @return 响应
     */
    public static <T> BaseResponse<T> success(T data) {
        return new BaseResponse<>(0, data, "ok");
    }

    /**
     * 失败响应
     *
     * @param errorCode 错误码
     * @return 响应
     */
    public static BaseResponse<?> error(ErrorCode errorCode) {
        return new BaseResponse<>(errorCode);
    }

    /**
     * 失败响应
     *
     * @param code    错误码
     * @param message 错误信息
     * @return 响应
     */
    public static BaseResponse<?> error(int code, String message) {
        return new BaseResponse<>(code, null, message);
    }

    /**
     * 失败响应
     *
     * @param errorCode 错误码
     * @param message   错误信息
     * @return 响应
     */
    public static BaseResponse<?> error(ErrorCode errorCode, String message) {
        return new BaseResponse<>(errorCode.getCode(), null, message);
    }
}

4. 全局异常处理器:GlobalExceptionHandler


为了防止意料之外的异常,可以利用 AOP 切面全局捕获业务异常和 RuntimeException

image-20250623133143531

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    /**
     * 捕获业务异常
     *
     * @param e 业务异常
     * @return 响应
     */
    @ExceptionHandler(BusinessException.class)
    public BaseResponse<?> businessExceptionHandler(BusinessException e) {
        log.error("BusinessException", e);
        return ResultUtils.error(e.getCode(), e.getMessage());
    }

    /**
     * 捕获运行时异常
     *
     * @param e 运行时异常
     * @return 响应
     */
    @ExceptionHandler(RuntimeException.class)
    public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {
        log.error("RuntimeException", e);
        return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");
    }
}

5. 请求包装类


对于“分页”、“删除某条数据”这类通用的请求,可以封装统一的请求包装类,用于接收前端传来的参数,之后相同参数的请求就不用专门再新建一个类了。


分页请求包装类

接受页号、页面大小、排序字段、排序顺序参数:

@Data
public class PageRequest {
    /**
     * 当前页号,默认值为 1
     */
    private int current = 1;

    /**
     * 页面大小,默认值为 10
     */
    private int pageSize = 10;

    /**
     * 排序字段
     */
    private String sortField;

    /**
     * 排序顺序(默认降序)
     */
    private String sortOrder = "descend";
}

删除请求包装类

接受要删除数据的 id 作为参数:

image-20250623133722810

/**
 * 通用的删除请求类
 */
@Data
public class DeleteRequest implements Serializable {
    /**
     * 数据 ID
     */
    private Long id;

    private static final long serialVersionUID = 1L;
}

6. 全局跨域配置


背景

跨域是指浏览器访问的 URL(前端地址)和后端接口地址的域名(或端口号)不一致导致的。浏览器为了安全,默认禁止跨域请求访问。为了开发调试方便,我们可以通过全局跨域配置,让整个项目所有的接口支持跨域,解决跨域报错。


配置步骤

  1. 新建 config 包,用于存放所有的配置相关代码。
  2. 创建全局跨域配置类 CorsConfig

image-20250623134406647

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 覆盖所有请求
        registry.addMapping("/**")
                // 允许发送 Cookie
                .allowCredentials(true)
                // 放行哪些域名(必须用 patterns,否则 * 会和 allowCredentials 冲突)
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .exposedHeaders("*");
    }
}

示例接口


编写示例接口

移除 controller 包下的其他代码,让项目更干净,然后编写一个纯净的 /health 接口用于健康检查:

image-20250623135022986

@RestController
@RequestMapping("/")
public class MainController {
    /**
     * 健康检查
     */
    @GetMapping("/health")
    public BaseResponse<String> health() {
        return ResultUtils.success("ok");
    }
}

说明:健康检查是指通过访问该接口,快速验证后端服务是否正常运行。因此,该接口的返回值非常简单。


访问http://localhost:8123/api/doc.html,看到输出结果,表示后端初始化完成。

image-20250623135339130


在这里插入图片描述

在这里插入图片描述

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐