一、封装返回值
后端如何封装返回值,适配前端 {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 中统一管理接口返回类型的最佳实践。