后端笔记之封装返回值以及前端定义Promise 返回类型

一、封装返回值

后端如何封装返回值,适配前端 {code: number, msg: string, obj: boolean} 的格式,核心有两种方式:一是用通用的 ResponseUtil.success() 工具类封装,二是手动构建 Map<String, Object> 自定义返回结构。

1、封装

通过封装通用的响应工具类,统一所有接口的返回格式,避免重复代码。

假设ResponseResult工具类里封装了所有接口的返回格式:

(1)先定义通用响应类(ResponseResult)

首先创建一个实体类,对应前端需要的结构:

java 复制代码
import lombok.Data;

/**
 * 通用响应返回类,适配前端 {code, msg, obj} 格式
 */
@Data
public class ResponseResult<T> {
    // 状态码(200成功,500失败)
    private int code;
    // 提示信息
    private String msg;
    // 业务数据(泛型,支持任意类型,这里前端需要boolean)
    private T obj;

    // 私有构造,通过静态方法创建
    private ResponseResult() {}

    // 成功响应(自定义信息+数据)
    public static <T> ResponseResult<T> success(String msg, T obj) {
        ResponseResult<T> result = new ResponseResult<>();
        result.setCode(200);
        result.setMsg(msg);
        result.setObj(obj);
        return result;
    }

    // 成功响应(默认信息+数据)
    public static <T> ResponseResult<T> success(T obj) {
        return success("操作成功", obj);
    }

    // 失败响应(自定义信息+数据)
    public static <T> ResponseResult<T> fail(String msg, T obj) {
        ResponseResult<T> result = new ResponseResult<>();
        result.setCode(500);
        result.setMsg(msg);
        result.setObj(obj);
        return result;
    }

    // 失败响应(默认信息+数据)
    public static <T> ResponseResult<T> fail(T obj) {
        return fail("操作失败", obj);
    }
}

(2)接口调用

java 复制代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试 ResponseResult 的 Controller 示例
 */
@RestController
@RequestMapping("/test")
public class TestController {

    // 场景1:成功响应 - 默认信息 + boolean 数据(前端需要的类型)
    @GetMapping("/success/boolean")
    public ResponseResult<Boolean> testSuccessBoolean() {
        // 模拟业务逻辑:比如判断用户是否存在,返回 true/false
        boolean userExists = true; // 实际业务中替换为真实逻辑
        // 调用 success 方法,只传数据,使用默认提示语"操作成功"
        return ResponseResult.success(userExists);
    }

    // 场景2:成功响应 - 自定义信息 + boolean 数据
    @GetMapping("/success/custom")
    public ResponseResult<Boolean> testSuccessCustom() {
        boolean checkPass = false;
        // 自定义提示信息
        return ResponseResult.success("权限校验完成", checkPass);
    }

    // 场景3:失败响应 - 默认信息 + boolean 数据
    @GetMapping("/fail/boolean")
    public ResponseResult<Boolean> testFailBoolean() {
        boolean operateResult = false;
        // 调用 fail 方法,只传数据,使用默认提示语"操作失败"
        return ResponseResult.fail(operateResult);
    }

    // 场景4:失败响应 - 自定义信息 + boolean 数据
    @GetMapping("/fail/custom")
    public ResponseResult<Boolean> testFailCustom() {
        boolean saveSuccess = false;
        // 自定义失败提示信息
        return ResponseResult.fail("数据保存失败,请重试", saveSuccess);
    }

    // 扩展场景:返回其他类型数据(泛型的优势)
    @GetMapping("/success/string")
    public ResponseResult<String> testSuccessString() {
        String userName = "张三";
        return ResponseResult.success("查询用户信息成功", userName);
    }
}

(3)返回示例

java 复制代码
// 以 testSuccessBoolean() 为例
{
  "code": 200,
  "msg": "操作成功",
  "obj": true
}

//以 testFailCustom() 为例
{
  "code": 500,
  "msg": "数据保存失败,请重试",
  "obj": false
}

2、手动构建 Map<String, Object>(简单场景)

如果不想创建通用响应类,可直接用 Map 封装返回值,适合临时场景:

java 复制代码
@PostMapping("/test/manage/testData/saveCompleteData")
    public Map<String, Object> saveCompleteData(@RequestBody TestCompleteDataDTO completeData) {
        Map<String, Object> resultMap = new HashMap<>();
        try {
            boolean saveResult = testDataService.saveCompleteData(completeData);
            if (saveResult) {
                resultMap.put("code", 200);
                resultMap.put("msg", "数据保存成功");
                resultMap.put("obj", true);
            } else {
                resultMap.put("code", 500);
                resultMap.put("msg", "数据保存失败:业务校验不通过");
                resultMap.put("obj", false);
            }
        } catch (Exception e) {
            log.error("保存测试数据异常", e); 
            resultMap.put("code", 500);
            resultMap.put("msg", "数据保存失败:" + e.getMessage());
            resultMap.put("obj", false);
        }
        return resultMap;
    }

返回的 JSON 结构和方式一完全一致,但缺点是每个接口都要重复写 put 逻辑,代码冗余。

3、总结

  • 推荐方式一(通用响应类):统一所有接口返回格式,代码复用性高,便于维护(符合企业开发规范);
  • 方式二(手动 Map):适合临时场景,无需创建额外类,但代码冗余,不推荐大规模使用;
  • 核心目标:保证后端返回的 JSON 结构严格匹配前端 {code: number, msg: string, obj: boolean} 的格式。

二、Promise 返回类型

在 TypeScript 中如何灵活定义 Promise 返回类型里的 obj 字段(不再局限于 boolean,适配后端返回的不同类型),核心是通过TypeScript 泛型来实现 obj 类型的动态定义。

原来的 obj: boolean 是固定类型,想要让 obj 支持任意类型(字符串、对象、数组等),需要用泛型接口封装返回结构,这样可以根据不同接口的需求,动态指定 obj 的类型

1、基础泛型定义(推荐,通用)

(1)第一步:定义通用响应接口(复用所有接口)

先创建一个泛型接口,把 code/msg 固定,obj 用泛型 动态指定:

javascript 复制代码
/**
 * 通用响应类型(泛型版)
 * T - obj 字段的类型,可自定义
 */
interface ApiResponse<T = any> {
  code: number;    // 状态码
  msg: string;     // 提示信息
  obj: T;          // 业务数据(泛型,支持任意类型)
}
  • <T = any> 表示泛型默认值为 any,如果不指定 T,obj 就会是 any 类型;
  • 这个接口可以复用在所有前端请求方法中,无需重复定义 code/msg。

(2)第二步:针对不同接口指定 obj 类型

根据后端返回的 obj 类型,在 Promise 中指定泛型参数:

场景 1:obj 还是布尔值(原需求)

javascript 复制代码
// 定义方法时,指定 T 为 boolean
async saveCompleteData(params: TestData): Promise<ApiResponse<boolean>> {
  const res = await axios.post<ApiResponse<boolean>>(
    "/manage/saveCompleteData", 
    params
  );
  return res.data; // 注意:axios 响应的业务数据在 data 中
}

场景 2:obj 是字符串(比如返回订单号)

javascript 复制代码
// 指定 T 为 string
async createOrder(params: OrderData): Promise<ApiResponse<string>> {
  const res = await axios.post<ApiResponse<string>>(
    "/manage/order/create", 
    params
  );
  return res.data;
}

场景 3:obj 是单个业务对象(比如场景详情)

先定义对象类型,再指定泛型:

javascript 复制代码
// 定义场景详情的类型
interface TestDetail {
  id: number;
  name: string;
  status: number;
  createTime: string;
}

// 指定 T 为 TestDetail
async getTestDetail (id: number): Promise<ApiResponse<TestDetail>> {
  const res = await axios.get<ApiResponse<TestDetail>>(
    `/manage/detail/${id}`
  );
  return res.data;
}

场景 4:obj 是数组(比如场景列表)

javascript 复制代码
// 指定 T 为 TestDetail[](数组)
async getList(params: PageParams): Promise<ApiResponse<TestDetail[]>> {
  const res = await axios.post<ApiResponse<TestDetail[]>>(
    "/manage/list", 
    params
  );
  return res.data;
}

场景 5:obj 是嵌套复杂结构(比如订单 + 订单项)

javascript 复制代码
// 定义嵌套类型
interface OrderItem {
  itemId: number;
  goodsName: string;
  price: number;
}

interface OrderDetail {
  orderId: string;
  amount: number;
  items: OrderItem[];
  user: {
    userId: number;
    userName: string;
  };
}

// 指定 T 为 OrderDetail
async getOrderDetail(orderId: string): Promise<ApiResponse<OrderDetail>> {
  const res = await axios.get<ApiResponse<OrderDetail>>(
    `/manage/order/detail/${orderId}`
  );
  return res.data;
}

2、临时自定义(简单场景,不推荐复用)

如果只是单个接口需要修改 obj 类型,不想定义通用接口,可以直接在 Promise 中自定义:

javascript 复制代码
// obj 为字符串类型
async createOrder(params: OrderData): Promise<{
  code: number;
  msg: string;
  obj: string; // 直接改为 string
}> {
  const res = await axios.post("/manage/order/create", params) as any;
  return res.data;
}

// obj 为场景详情对象
async getDetail(id: number): Promise<{
  code: number;
  msg: string;
  obj: {
    id: number;
    name: string;
    status: number;
    createTime: string;
  };
}> {
  const res = await axios.get(`/manage/detail/${id}`) as any;
  return res.data;
}

缺点:每个接口都要重复写 code/msg,代码冗余,仅适合临时场景。

3、obj 可选 / 可空(适配无数据场景)

如果某些接口的 obj 可能为 null(比如删除接口无返回数据),可以给 obj 加可选 / 联合类型:

javascript 复制代码
// 1. obj 为 boolean | null(布尔或 null)
interface ApiResponse<T = any> {
  code: number;
  msg: string;
  obj: T | null; // 支持 null
}

// 2. obj 可选(某些接口可能不返回 obj)
interface ApiResponse<T = any> {
  code: number;
  msg: string;
  obj?: T; // 可选字段
}

// 使用示例:删除接口,obj 为 null
async deleteScenario(id: number): Promise<ApiResponse<null>> {
  const res = await axios.post<ApiResponse<null>>(
    "/manage/delete", 
    { id }
  );
  return res.data;
}

4、注意

  • axios 类型指定:建议给 axios.post/get 也指定泛型(如 axios.post<ApiResponse<boolean>>),这样 res.data 会自动推导类型,避免用 as any 丢失类型提示;
  • 泛型默认值:interface ApiResponse<T = any> 的 = any 是兜底,当不确定 obj 类型时,可直接用ApiResponse(等价于 ApiResponse<any>);
  • 类型推导:定义好接口后,调用方法时 TypeScript 会自动提示 obj 的属性,比如:
javascript 复制代码
const detail = await getDetail(1);
console.log(detail.obj.name); // TS 会提示 name 是 string 类型,避免拼写错误

5、总结

  • 最推荐用泛型接口 ApiResponse<T> 定义返回类型,obj 的类型通过 <T> 动态指定,兼顾复用性和类型安全;
  • obj 可定义为任意类型:boolean/string/number/对象/数组/null 等,只需在泛型中指定对应类型;
  • 避免直接用 any 或重复定义 code/msg,泛型接口是 TypeScript 中统一管理接口返回类型的最佳实践。
相关推荐
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
starlaky4 天前
Django入门笔记
笔记·django
勇气要爆发4 天前
吴恩达《LangChain LLM 应用开发精读笔记》1-Introduction_介绍
笔记·langchain·吴恩达
悠哉悠哉愿意4 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
勇气要爆发4 天前
吴恩达《LangChain LLM 应用开发精读笔记》2-Models, Prompts and Parsers 模型、提示和解析器
android·笔记·langchain
qianshanxue114 天前
计算机操作的一些笔记标题
笔记
土拨鼠烧电路4 天前
笔记11:数据中台:不是数据仓库,是业务能力复用的引擎
数据仓库·笔记
土拨鼠烧电路4 天前
笔记14:集成与架构:连接孤岛,构建敏捷响应能力
笔记·架构
烟花落o4 天前
栈和队列的知识点及代码
开发语言·数据结构·笔记·栈和队列·编程学习