快速上手:Java后端开发-通用基础代码

一、通用基础代码

一套规范的异常处理方案 + 通用基础代码,能解决这些问题:​

  • 统一错误码、响应 / 请求格式,前后端交互更高效​
  • 集中捕获异常、配置跨域,减少重复代码​
  • 通用工具类 “一次编写终身复用”,节省开发时间​
  • 便于问题排查,日志记录、服务监控更规范

二、核心组件实现详解

1. 错误码枚举(ErrorCode)—— 统一错误定义​

作用:管理所有业务错误类型,避免错误码分散在代码里。​

代码实现:

@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, "操作失败");

    // 状态码(建议遵循HTTP状态码语义,扩展业务码)
    private final int code;

    // 错误描述
    private final String message;

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

关键说明:​

  • 按 “成功 / 客户端错误 / 服务端错误” 分类,逻辑清晰​
  • 用@Getter简化 getter(需引入 Lombok),不用手动写​
  • 集中管理错误信息,改错误描述时只需改枚举,不用改业务代码​

2. 自定义业务异常(BusinessException)—— 业务异常专属​

作用:和 Java 内置异常(如 RuntimeException)区分开,方便携带错误码。​

代码实现:

@Getter

public class BusinessException extends RuntimeException {

    // 错误码(与ErrorCode对应)

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

}

关键说明:

  • 继承RuntimeException:不用强制在方法上写throws,更灵活​
  • 保留code字段:全局异常处理器靠这个码返回正确响应​
  • 多构造方法:想直接传枚举、想加自定义信息都支持

3. 异常抛出工具类(ThrowUtils)—— 简化条件判断​

作用:把 “条件判断 + 抛异常” 的重复代码缩成一行,不用每次写if-else。​

代码实现:

public class ThrowUtils {

    /**
    * 条件成立则抛异常(支持任意RuntimeException)
    */
    public static void throwIf(boolean condition, RuntimeException runtimeException) {
        if (condition) {
            throw runtimeException;
        }
    }

  
    public static void throwIf(boolean condition, ErrorCode errorCode) {
        throwIf(condition, new BusinessException(errorCode));
    }

   

    public static void throwIf(boolean condition, ErrorCode errorCode, String message) {
        throwIf(condition, new BusinessException(errorCode, message));
    }

}

使用示例:

// 原来要写两行:判断+抛异常
if (user == null) {
    throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "用户不存在");
}
// 现在一行搞定
ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR, "用户不存在");

4. 全局异常处理器(GlobalExceptionHandler)—— 集中抓异常​

作用:所有接口的异常都在这处理,不用每个接口单独写try-catch。​

代码实现:

@RestControllerAdvice

@Slf4j

public class GlobalExceptionHandler {
    /**
    * 捕获业务异常(BusinessException)
    */
    @ExceptionHandler(BusinessException.class)
    public BaseResponse<?> businessExceptionHandler(BusinessException e) {
        log.error("BusinessException: ", e); // 记录异常堆栈,便于排查
        return ResultUtils.error(e.getCode(), e.getMessage());
    }

    /**
    * 捕获通用运行时异常(RuntimeException)
    */
    @ExceptionHandler(RuntimeException.class)
    public BaseResponse<?> runtimeExceptionHandler(RuntimeException e) {
        log.error("RuntimeException: ", e); // 记录异常堆栈 
        return ResultUtils.error(ErrorCode.SYSTEM_ERROR, "系统错误");
    }

}

核心注解说明

  • @RestControllerAdvice:全局异常处理注解,作用于所有 @RestController
  • @ExceptionHandler:指定捕获的异常类型(如 BusinessException、RuntimeException)
  • @Slf4j:Lombok 注解,简化日志记录(需引入 Lombok)

处理逻辑:

  • 优先抓业务异常:返回对应的错误码和信息(如 “用户不存在”)​
  • 再抓系统异常:返回通用 “系统错误”,避免暴露技术细节给用户​
  • 所有异常都记日志:出问题时能通过日志定位哪里错了​

5. 响应包装类(BaseResponse + ResultUtils)—— 统一接口返回格式​

作用:所有接口都返回相同结构(code+data+message),前端不用适配多种格式。​

5.1 统一响应体(BaseResponse)
@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());
    }
}

5.2 响应工具类(ResultUtils)​

作用:不用每次手动new BaseResponse,用静态方法直接返回结果。

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 错误码
     * @return 响应
     */
    public static BaseResponse<?> error(ErrorCode errorCode, String message) {
        return new BaseResponse<>(errorCode.getCode(), null, message);
    }
}

使用示例:

// 成功返回用户列表
@GetMapping("/list")
public BaseResponse<List<User>> getUserList() {
    List<User> list = userService.list();
    return ResultUtils.success(list); // 一行搞定
}

// 失败返回错误
@GetMapping("/detail")
public BaseResponse<User> getUserDetail(Long id) {
    User user = userService.getById(id);
    ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR, "用户不存在");
    return ResultUtils.success(user);
}

6. 请求包装类 —— 减少重复参数定义​

作用:把常用请求参数(如分页、删除)封装成类,不用每个接口都写相同字段。​

6.1 分页请求包装类(PageRequest)​

场景:用户列表、商品列表等需要分页的接口,统一接收 “页号、页大小、排序” 参数。

@Data
public class PageRequest {

    /**
     * 当前页号
     */
    private int current = 1;

    /**
     * 页面大小
     */
    private int pageSize = 10;

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

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

使用示例:

// 分页查用户,直接用PageRequest接收参数
@GetMapping("/list")
public BaseResponse<Page<User>> getUserList(PageRequest request) {
    // 转成MyBatis-Plus的Page对象(根据框架调整)
    Page<User> page = new Page<>(request.getCurrent(), request.getPageSize());
    Page<User> userPage = userService.page(page, new LambdaQueryWrapper<>());
    return ResultUtils.success(userPage);
}
6.2 删除请求包装类(DeleteRequest)​

场景:删除用户、删除订单等接口,都需要 “id” 参数,统一封装避免重复写。

@Data
public class DeleteRequest implements Serializable {

    /**
     * id
     */
    private Long id;

    private static final long serialVersionUID = 1L;
}

使用示例:

// 删除用户,用DeleteRequest接收id
@PostMapping("/delete")
public BaseResponse<Boolean> deleteUser(@RequestBody DeleteRequest request) {
    ThrowUtils.throwIf(request.getId() == null, ErrorCode.PARAMS_ERROR, "id不能为空");
    boolean success = userService.removeById(request.getId());
    ThrowUtils.throwIf(!success, ErrorCode.OPERATION_ERROR, "删除失败");
    return ResultUtils.success(true);
}

7. 全局跨域配置(CorsConfig)—— 解决前后端访问问题​

先通俗讲跨域:​

前端跑在localhost:8080,后端跑在localhost:8081,浏览器觉得 “不同端口不安全”,会拦截请求 —— 这就是跨域。配置后所有接口都支持跨域,不用每个接口单独处理。​

代码实现:

@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("*");
    }
}

核心价值:一次配置终身用,开发时不用再跟前端纠结 “为什么调接口报跨域错”。​

8. 健康检查接口(MainController)—— 监控服务是否活着​

作用:项目部署后,运维或监控工具(如 Prometheus)需要知道服务是否正常,访问/health返回 “ok” 就说明服务活着。​

代码实现:

@RestController
@RequestMapping("/")
public class MainController {

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

使用场景:​

  • 运维定时访问http://你的服务地址/health,如果返回{"code":0,"data":"ok","message":""},说明服务正常;​
  • 云服务器(如阿里云)的健康检查功能,靠这个接口判断是否需要重启服务。

三、实战使用示例(整合通用代码)​

完整接口示例:分页查用户 + 删除用户

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 分页查用户(用PageRequest接收参数)
     */
    @GetMapping("/list")
    public BaseResponse<Page<User>> getUserList(PageRequest request) {
        // 1. 参数校验(用ThrowUtils简化)
        ThrowUtils.throwIf(request.getCurrent() < 1, ErrorCode.PARAMS_ERROR, "页号不能小于1");
        ThrowUtils.throwIf(request.getPageSize() > 100, ErrorCode.PARAMS_ERROR, "每页最多100条");
        
        // 2. 业务逻辑(分页查询)
        Page<User> page = new Page<>(request.getCurrent(), request.getPageSize());
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        // 排序(如果传了sortField)
        if (StringUtils.hasText(request.getSortField())) {
            queryWrapper.orderBy(true, "ascend".equals(request.getSortOrder()), request.getSortField());
        }
        Page<User> userPage = userService.page(page, queryWrapper);
        
        // 3. 返回结果(用ResultUtils)
        return ResultUtils.success(userPage);
    }

    /**
     * 删除用户(用DeleteRequest接收参数)
     */
    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteUser(@RequestBody DeleteRequest request) {
        // 1. 参数校验
        ThrowUtils.throwIf(request.getId() == null, ErrorCode.PARAMS_ERROR, "id不能为空");
        
        // 2. 业务逻辑
        boolean success = userService.removeById(request.getId());
        ThrowUtils.throwIf(!success, ErrorCode.OPERATION_ERROR, "删除失败(用户可能已不存在)");
        
        // 3. 返回结果
        return ResultUtils.success(true);
    }
}

四、注意事项(避坑指南)​

  • 错误码不要乱加:新增错误时先看 ErrorCode 有没有现成的,避免重复(如 “参数错误” 不用再定义 40001);​
  • 跨域配置生产环境优化:allowedOriginPatterns("*")在生产环境要改成具体域名(如"https://yourdomain.com"),更安全;​
  • 序列化不要忘:涉及网络传输(如响应体、请求体)的类,要实现Serializable,避免反序列化报错;​
  • 日志要记堆栈:log.error("异常:", e)要带e,否则看不到异常堆栈,没法定位问题;​
  • 默认值要合理:分页请求的current=1、pageSize=10,符合大多数场景,减少前端传参压力。​

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值