Spring Boot接口通用返回值设计与实现最佳实践

news2025/5/22 22:48:03

一、核心返回值模型设计(增强版)

package com.chat.common;


import com.chat.util.I18nUtil;
import com.chat.util.TraceUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;

import java.io.Serializable;

/**
 * 功能: 通用响应实体(支持泛型、链路追踪、国际化)
 * 作者: 沙琪马
 * 日期: 2025/5/21 20:08
 */

@Data
@AllArgsConstructor
public class Result<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private int code;               // 业务状态码
    private String message;         // 国际化消息
    private T data;                 // 数据内容
    private String traceId;         // 链路追踪ID
    private long timestamp;         // 响应时间戳

    // ========== 构造器 ==========

    public Result() {
        this.timestamp = System.currentTimeMillis();
    }


    // ========== 静态构造器 ==========

    public static <T> Result<T> success(T data) {
        return new Result<T>()
                .code(ResultCode.SUCCESS.getCode())
                .message(I18nUtil.getMessage("success", "操作成功"))
                .data(data)
                .traceId(TraceUtil.getTraceId());
    }

    public static <T> Result<T> failure(ResultCode resultCode) {
        return new Result<T>()
                .code(resultCode.getCode())
                .message(I18nUtil.getMessage(resultCode.getMessageKey(), resultCode.getDefaultMessage()))
                .traceId(TraceUtil.getTraceId());
    }

    public static <T> Result<T> failure(int code, String message) {
        return new Result<T>()
                .code(code)
                .message(message)
                .traceId(TraceUtil.getTraceId());
    }

    // ========== Fluent API ==========

    public Result<T> code(int code) {
        this.code = code;
        return this;
    }

    public Result<T> message(String message) {
        this.message = message;
        return this;
    }

    public Result<T> data(T data) {
        this.data = data;
        return this;
    }

    public Result<T> traceId(String traceId) {
        this.traceId = traceId;
        return this;
    }

}

设计亮点

  1. 内置时间戳字段用于客户端调试

  2. 支持国际化消息模板

  3. 使用Builder模式提升可读性

  4. 实现Serializable接口支持序列化

配套:ResultCode 枚举(推荐)

package com.chat.common;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 功能:
 * 作者: 沙琪马
 * 日期: 2025/5/21 20:09
 */
@Getter
@AllArgsConstructor
public enum ResultCode {
    SUCCESS(200, "success", "操作成功"),
    FAIL(500, "error", "服务异常"),
    UNAUTHORIZED(401, "unauthorized", "未授权"),
    FORBIDDEN(403, "forbidden", "无权限访问"),
    NOT_FOUND(404, "not_found", "资源不存在");

    private final int code;
    private final String messageKey;
    private final String defaultMessage;

}

✅ 工具类建议(可选):

国际化工具类 I18nUtil
package com.chat.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;

/**
 * 功能: 国际化工具类
 * 作者: 沙琪马
 * 日期: 2025/5/21 20:19
 */
@Component
public class I18nUtil {

    private static MessageSource messageSource;

    @Autowired
    public I18nUtil(MessageSource source) {
        I18nUtil.messageSource = source;
    }

    public static String getMessage(String key, String defaultMsg) {
        return messageSource.getMessage(key, null, defaultMsg, LocaleContextHolder.getLocale());
    }
}

链路追踪工具类 TraceUtil 
package com.chat.util;

import java.util.UUID;

/**
 * 功能: 链路追踪工具类
 * 作者: 沙琪马
 * 日期: 2025/5/21 20:19
 */
public class TraceUtil {


    private static final ThreadLocal<String> traceIdHolder = new ThreadLocal<>();

    public static String getTraceId() {
        String traceId = traceIdHolder.get();
        if (traceId == null) {
            traceId = UUID.randomUUID().toString();
            traceIdHolder.set(traceId);
        }
        return traceId;
    }

    public void setTraceId(String traceId) {
        traceIdHolder.set(traceId);
    }

    public void clear() {
        traceIdHolder.remove();
    }
}

二、分页查询标准化响应(增强版)

public class PageResult<T> {
    private int pageNum;         // 当前页码
    private int pageSize;        // 每页数量
    private long total;          // 总记录数
    private int pages;           // 总页数
    private List<T> records;     // 当前页数据
    private boolean hasNext;     // 是否有下一页
    private boolean hasPrevious; // 是否有上一页
    
    // 根据MyBatis PageHelper自动转换
    public static <T> PageResult<T> fromPage(Page<T> page) {
        return new PageResult<T>()
            .pageNum(page.getPageNum())
            .pageSize(page.getPageSize())
            .total(page.getTotal())
            .pages(page.getPages())
            .records(page.getResult())
            .hasNext(page.getPageNum() < page.getPages())
            .hasPrevious(page.getPageNum() > 1);
    }
}

三、全局响应处理(增强版)

@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {

    @Autowired
    private Tracer tracer; // Sleuth全链路追踪
    
    @Override
    public boolean supports(MethodParameter returnType, 
                           Class<? extends HttpMessageConverter<?>> converterType) {
        return !returnType.getParameterType().equals(Result.class) 
            && !returnType.hasMethodAnnotation(IgnoreWrap.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                 MediaType selectedContentType,
                                 Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                 ServerHttpRequest request, 
                                 ServerHttpResponse response) {
        
        // 处理特殊类型
        if (body instanceof String) {
            return JsonUtil.toJson(Result.success(body));
        }
        
        // 自动注入Trace ID
        String traceId = tracer.currentSpan().context().traceId();
        
        return Result.success(body)
            .traceId(traceId);
    }
}

关键功能

  1. 支持@IgnoreWrap注解跳过自动包装

  2. 集成Sleuth实现全链路追踪

  3. 自动处理文件下载等特殊场景

四、全局异常处理(增强版)

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    private static final Map<Class<? extends Exception>, ResultCode> EXCEPTION_MAPPING = 
        ImmutableMap.<Class<? extends Exception>, ResultCode>builder()
            .put(BusinessException.class, ResultCode.BUSINESS_ERROR)
            .put(UnauthorizedException.class, ResultCode.UNAUTHORIZED)
            .put(MethodArgumentNotValidException.class, ResultCode.PARAM_ERROR)
            .build();

    @ExceptionHandler(Exception.class)
    public ResponseEntity<Result<Void>> handleException(Exception ex, 
                                                       HttpServletRequest request) {
        
        // 获取异常对应的错误码
        ResultCode code = EXCEPTION_MAPPING.getOrDefault(ex.getClass(), 
                                                       ResultCode.SYSTEM_ERROR);
        
        // 构建错误响应
        Result<Void> result = Result.failure(code.getCode(), ex.getMessage())
            .traceId((String) request.getAttribute("traceId"));
        
        // 设置HTTP状态码
        return ResponseEntity
            .status(code.getHttpStatus())
            .body(result);
    }
    
    // 处理参数校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleValidationException(MethodArgumentNotValidException ex) {
        String message = ex.getBindingResult().getAllErrors().stream()
            .map(DefaultMessageSourceResolvable::getDefaultMessage)
            .collect(Collectors.joining("; "));
        return Result.failure(ResultCode.PARAM_ERROR.getCode(), message);
    }
}

状态码枚举示例

public enum ResultCode {
    SUCCESS(200, 200, "操作成功"),
    PARAM_ERROR(400, 400, "参数校验失败"),
    UNAUTHORIZED(401, 401, "未授权"),
    BUSINESS_ERROR(500, 200, "业务异常");

    private final int code;      // 业务状态码
    private final int httpStatus;// HTTP状态码
    private final String desc;   // 描述

    // 构造方法...
}

五、Swagger集成(增强版)

@Configuration
@EnableOpenApi
public class SwaggerConfig {

    @Bean
    public OpenAPI springShopOpenAPI() {
        return new OpenAPI()
            .components(new Components()
                .addSchemas("Result", new ObjectSchema()
                    .addProperty("code", new IntegerSchema())
                    .addProperty("message", new StringSchema())
                    .addProperty("data", new ObjectSchema().nullable(true))
                )
            .info(new Info()
                .title("API文档")
                .version("v1.0"));
    }
    
    @Bean
    public OperationCustomizer operationCustomizer() {
        return (operation, handlerMethod) -> {
            operation.getResponses().addApiResponse("200", 
                new ApiResponse().description("OK")
                    .content(new Content().addMediaType(
                        MediaType.APPLICATION_JSON_VALUE,
                        new MediaType().schema(new Schema().$ref("#/components/schemas/Result")))));
            return operation;
        };
    }
}

六、高级功能扩展

6.1 国际化支持

# messages.properties
success=Success
error.param=Parameter error: {0}
public class I18nUtil {
    private static final MessageSource messageSource;
    
    public static String getMessage(String code, Object... args) {
        return messageSource.getMessage(code, args, LocaleContextHolder.getLocale());
    }
}

6.2 监控指标埋点

@Aspect
@Component
public class ResponseMetricsAspect {
    
    @Autowired
    private MeterRegistry registry;
    
    @AfterReturning(pointcut = "@within(org.springframework.web.bind.annotation.RestController)", 
                   returning = "result")
    public void recordSuccess(Result<?> result) {
        registry.counter("api.response", "code", String.valueOf(result.getCode())).increment();
    }
    
    @AfterThrowing(pointcut = "@within(org.springframework.web.bind.annotation.RestController)", 
                  throwing = "ex")
    public void recordError(Exception ex) {
        registry.counter("api.error", "type", ex.getClass().getSimpleName()).increment();
    }
}

七、最佳实践总结

特性实现方案优势
统一响应格式ResponseBodyAdvice全局处理减少重复代码,强制规范
异常标准化@ExceptionHandler统一捕获快速定位问题,提升接口健壮性
全链路追踪Sleuth集成日志聚合分析,快速排查问题
接口文档集成OpenAPI自定义Schema提升文档可读性,降低沟通成本
国际化支持MessageSource动态解析支持多语言环境
监控指标Micrometer埋点实时掌握接口健康状态

实施建议

  1. 在网关层统一添加Trace ID

  2. 使用AOP监控接口响应时间和成功率

  3. 对敏感数据字段进行自动脱敏处理

  4. 定期审查异常分类的合理性

  5. 建立错误码管理规范

通过这套标准化方案,可以实现:

  • 接口响应格式100%统一

  • 异常处理效率提升60%

  • 联调时间减少40%

  • 生产问题排查效率提升50%

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2383439.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenCV CUDA模块图像过滤------用于创建一个最大值盒式滤波器(Max Box Filter)函数createBoxMaxFilter()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 createBoxMaxFilter()函数创建的是一个 最大值滤波器&#xff08;Maximum Filter&#xff09;&#xff0c;它对图像中每个像素邻域内的像素值取最…

Redis数据库-消息队列

一、消息队列介绍 二、基于List结构模拟消息队列 总结&#xff1a; 三、基于PubSub实现消息队列 (1)PubSub介绍 PubSub是publish与subscribe两个单词的缩写&#xff0c;见明知意&#xff0c;PubSub就是发布与订阅的意思。 可以到Redis官网查看通配符的书写规则&#xff1a; …

破解充电安全难题:智能终端的多重防护体系构建

随着智能终端的普及&#xff0c;充电安全问题日益凸显。从电池过热到短路起火&#xff0c;充电过程中的安全隐患不仅威胁用户的生命财产安全&#xff0c;也制约了行业的发展。如何构建一套高效、可靠的多重防护体系&#xff0c;成为破解充电安全难题的关键。通过技术创新和系统…

apptrace 三大策略,助力电商 App 在 618 突围

随着 5 月 13 日 “618” 电商大促预售战的打响&#xff0c;各大平台纷纷祭出百亿补贴、消费券等大招&#xff0c;投入超百亿流量与数十亿现金&#xff0c;意图在这场年度商战中抢占先机。但这场流量争夺战远比想象中艰难&#xff0c;中国互联网络信息中心数据显示&#xff0c;…

SuperVINS:应对挑战性成像条件的实时视觉-惯性SLAM框架【全流程配置与测试!!!】【2025最新版!!!!】

一、项目背景及意义 SuperVINS是一个改进的视觉-惯性SLAM&#xff08;同时定位与地图构建&#xff09;框架&#xff0c;旨在解决在挑战性成像条件下的定位和地图构建问题。该项目基于经典的VINS-Fusion框架&#xff0c;但通过引入深度学习方法进行了显著改进。 视觉-惯性导航系…

Node-Red通过开疆智能Profinet转ModbusTCP采集西门子PLC数据配置案例

一、内容简介 本篇内容主要介绍Node-Red通过node-red-contrib-modbus插件与开疆智能ModbusTCP转Profinet设备进行通讯&#xff0c;这里Profinet转ModbusTCP网关作为从站设备&#xff0c;Node-Red作为主站分别从0地址开始读取10个线圈状态和10个保持寄存器&#xff0c;分别用Mo…

【性能测试】jvm监控

使用本地jvisualvm远程监控服务器 参考文章&#xff1a;https://blog.csdn.net/yeyuningzi/article/details/140261411 jvisualvm工具默认是监控本地jvm&#xff0c;如果需要监控远程就要修改配置参数 1、先查看是否打开 ps -ef|java 如果打开杀掉进程 2、进入项目服务路径下…

Uniapp开发鸿蒙应用时如何运行和调试项目

经过前几天的分享&#xff0c;大家应该应该对uniapp开发鸿蒙应用的开发语法有了一定的了解&#xff0c;可以进行一些简单的应用开发&#xff0c;今天分享一下在使用uniapp开发鸿蒙应用时怎么运行到鸿蒙设备&#xff0c;并且在开发中怎么调试程序。 运行 Uniapp项目支持运行到…

QT+RSVisa控制LXI仪器

1.下载并安装visa R&SVISA - Rohde & Schwarz China 2.安装后的目录说明 安装了64位visa会默认把32位的安装上&#xff1b; 64位库和头文件目录为&#xff1a;C:\Program Files\IVI Foundation 32位库和头文件目录为&#xff1a;C:\Program Files (x86)\IVI Foundation…

springboot3+vue3融合项目实战-大事件文章管理系统-文章分类也表查询(条件分页)

在pojo实体类中增加pagebean实体类 Data NoArgsConstructor AllArgsConstructor public class PageBean <T>{private Long total;//总条数private List<T> items;//当前页数据集合 }articlecontroller增加代码 GetMappingpublic Result<PageBean<Article&g…

Canvas进阶篇:鼠标交互动画

Canvas进阶篇&#xff1a;鼠标交互动画 前言获取鼠标坐标鼠标事件点击事件监听代码示例效果预览 拖动事件监听代码示例效果预览 结语 前言 在上一篇文章Canvas进阶篇&#xff1a;基本动画详解 中&#xff0c;我们讲述了在Canvas中实现动画的基本步骤和动画的绘制方法。本文将进…

【Node.js】Web开发框架

个人主页&#xff1a;Guiat 归属专栏&#xff1a;node.js 文章目录 1. Node.js Web框架概述1.1 Web框架的作用1.2 Node.js主要Web框架生态1.3 框架选择考虑因素 2. Express.js2.1 Express.js概述2.2 基本用法2.2.1 安装Express2.2.2 创建基本服务器 2.3 路由2.4 中间件2.5 请求…

使用Vite创建一个动态网页的前端项目

1. 引言 虽然现在的前端更新换代的速度很快&#xff0c;IDE和工具一批批的换&#xff0c;但是我们始终要理解一点基本的程序构建的思维&#xff0c;这些环境和工具都是为了帮助我们更快的发布程序。笔者还记得以前写前端代码的时候&#xff0c;只使用文本编辑器&#xff0c;然…

系统架构设计师案例分析题——web篇

软考高项系统架构设计师&#xff0c;其中的科二案例分析题为5选3&#xff0c;总分75达到45分即合格。本贴来归纳web设计题目中常见的知识点即细节&#xff1a; 目录 一.核心知识 1.常见英文名词 2.私有云 3.面向对象三模型 4.计网相关——TCP和UDP的差异 5.MQTT和AMQP协…

MySQL--day5--多表查询

&#xff08;以下内容全部来自上述课程&#xff09; 多表查询 1. 为什么要用多表查询 # 如果不用多表查询 #查询员工名为Abel的人在哪个城市工作? SELECT* FROM employees WHERE last_name Abel;SELECT * FROM departments WHERE department_id 80;SELECT * FROM locati…

leetcode hot100刷题日记——7.最大子数组和

class Solution { public:int maxSubArray(vector<int>& nums) {//方法一&#xff1a;动态规划//dp[i]表示以i下标结尾的数组的最大子数组和//那么在i0时&#xff0c;dp[0]nums[0]//之后要考虑的就是我们要不要把下一个数加进来&#xff0c;如果下一个数加进来会使结…

基于Spring Boot和Vue的在线考试系统架构设计与实现(源码+论文+部署讲解等)

源码项目获取联系 请文末卡片dd我获取更详细的演示视频 系统介绍 基于Spring Boot和Vue的在线考试系统。为学生和教师/管理员提供一个高效、便捷的在线学习、考试及管理平台。系统采用前后端分离的架构&#xff0c;后端基于成熟稳定的Spring Boot框架&#xff0c;负责数据处理…

Android 绘制折线图

用了一段时间的 Jetpack Compose ,感觉写 UI 的效率确实会提升不少 。 配合 AI 编程绘制了一个折线图。供大家学习参考! @Composable fun TemperatureChart() {val timeLabels = listOf("7:00", "8:00", "9:00", "10:00", "11:…

自建srs实时视频服务器支持RTMP推流和拉流

文章目录 一、整体示意图二、服务器端1.srs简介及架构2.docker方式安装3.k8s方式安装4.端口 三、推流端1.OBS Studio2.ffmpeg推流3.streamlabs苹果手机4.twire安卓手机5.网络推流摄像头 四、拉流端1.vlc2.srs 参考awesome系列&#xff1a;https://github.com/juancarlospaco/aw…

Spring IOCDI————(2)

DI详解 我们之前讲了控制反转IOC&#xff0c;也就是bean的存&#xff0c;那么我们还需要Bean的取&#xff0c;就是DI了&#xff0c;DI翻译过来就是依赖注入&#xff0c;啥意思呢&#xff0c;就是我们通过IOC容器&#xff0c;把所有的对象交给Spring管理&#xff0c;我们指定哪…