3、后端框架搭建:Spring Boot项目创建、MyBatis-Plus集成、统一返回结果封装、全局异常处理

好,咱们正式开始动手了。这一章,我会带你把后端项目的骨架搭起来。说白了,就是让Spring Boot跑起来,连上数据库,再搞定接口的返回格式和异常处理。

我个人习惯,先把基础设施弄扎实了,后面写业务代码才不慌。你想想看,如果连统一返回格式都没有,每个接口返回的数据结构都不一样,前端同学不得疯掉?

3.1 Spring Boot项目创建

我用的是Spring Initializr,IDEA里直接新建项目就行。选Java 17,Spring Boot 2.7.x版本,这个版本比较稳,我在好几个生产项目里都用过。

依赖方面,先勾这几个核心的:

  • Spring Web — 提供RESTful API能力
  • Spring Boot DevTools — 热部署,改代码不用重启
  • Lombok — 少写getter/setter,代码清爽
  • MySQL Driver — 连数据库用

项目建好后,pom.xml里会自动生成这些依赖。不过别急,我们还要手动加MyBatis-Plus。

小提示: 如果你用的是阿里云Maven镜像,记得检查一下settings.xml配置。我曾经因为镜像没配好,卡在下载依赖上浪费了半小时。

3.2 MyBatis-Plus集成

MyBatis-Plus,说白了就是MyBatis的增强版。它帮你把单表的CRUD都封装好了,你连SQL都不用写。

pom.xml里加上这个:

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

然后配置application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/oa_system?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

嗯,这里要注意。逻辑删除字段我习惯叫deleted,0表示未删除,1表示已删除。这样删除数据时只是标记一下,不会真丢数据。

接下来,写一个BaseMapper

public interface BaseMapper<T> extends com.baomidou.mybatisplus.core.mapper.BaseMapper<T> {
    // 这里可以加一些公共方法
}

再写个配置类,扫描Mapper:

@Configuration
@MapperScan("com.oa.system.mapper")
public class MyBatisPlusConfig {
    // 分页插件,这个很常用
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}
避坑指南: 我曾经遇到过一个问题,分页查询时总条数不对。后来发现是没加PaginationInnerInterceptor。记住,分页插件一定要注册,不然分页功能会失效。

3.3 统一返回结果封装

为什么需要统一返回结果?你想想看,如果每个接口返回的格式都不一样,前端处理起来得多痛苦。我见过一个项目,有的接口返回{code: 200, data: {...}},有的返回{success: true, result: {...}},前端同学直接崩溃。

我的做法是定义一个Result类:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    // 成功
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "操作成功", data);
    }

    // 失败
    public static <T> Result<T> error(Integer code, String message) {
        return new Result<>(code, message, null);
    }

    // 带自定义消息的成功
    public static <T> Result<T> success(String message, T data) {
        return new Result<>(200, message, data);
    }
}

这样,所有接口返回的都是Result对象。前端拿到数据后,先看code,如果是200就正常处理,否则弹个错误提示。

核心要点: 统一返回格式不只是为了好看,更是为了前后端协作的效率。我建议把code定义成枚举,比如200成功、400参数错误、500服务器异常,这样更规范。

3.4 全局异常处理

程序跑起来,难免会出异常。如果每个接口都写try-catch,代码会变得很臃肿。我的做法是用@ControllerAdvice做全局异常处理。

先定义一个业务异常类:

@Data
@EqualsAndHashCode(callSuper = true)
public class BusinessException extends RuntimeException {
    private Integer code;

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

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

然后写全局异常处理器:

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusinessException(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    // 处理参数校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValidationException(MethodArgumentNotValidException e) {
        String message = e.getBindingResult().getAllErrors().stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.joining(", "));
        return Result.error(400, message);
    }

    // 处理其他未知异常
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) {
        log.error("系统异常", e);
        return Result.error(500, "服务器内部错误,请稍后重试");
    }
}

这样,业务代码里只需要抛出BusinessException,全局处理器会自动捕获并返回统一格式的错误信息。

个人经验: 我建议把异常信息记录到日志里,方便排查问题。另外,生产环境不要暴露详细的异常堆栈给前端,只返回友好的提示信息就行。

3.5 验证一下

写个简单的测试接口:

@RestController
@RequestMapping("/api/test")
public class TestController {

    @GetMapping("/hello")
    public Result<String> hello() {
        return Result.success("Hello, OA System!");
    }

    @GetMapping("/error")
    public Result<?> error() {
        throw new BusinessException("这是一个测试异常");
    }
}

启动项目,访问/api/test/hello,应该返回:

{
    "code": 200,
    "message": "操作成功",
    "data": "Hello, OA System!"
}

访问/api/test/error,返回:

{
    "code": 400,
    "message": "这是一个测试异常",
    "data": null
}

嗯,到这里,后端框架的基础就搭好了。下一章,我们会开始设计数据库表结构,然后写第一个业务模块——用户管理。

本章总结:
  • Spring Boot项目创建,选对依赖
  • MyBatis-Plus集成,配置数据源和分页插件
  • 统一返回结果封装,用Result类规范接口格式
  • 全局异常处理,用@ControllerAdvice统一捕获异常

记住,框架搭建是地基,地基稳了,后面盖楼才踏实。我曾经见过一个项目,框架没搭好就急着写业务,结果后面改接口格式改到崩溃。所以,别急,慢慢来。