3.3 响应对象与输出控制


3.3 响应对象与输出控制

输入读完了,本节管回程:控制器把结果交给框架后,响应以什么形态、什么状态码、什么响应头送出。控制器里随手 return $data 的背后是响应对象的自动包装,本节把这层「自动」打开,让你在 JSON、跳转、视图、下载四种形态间做出正确选择,并能完全自控输出的每个细节。

一、响应的自动包装:return 的背后

控制器返回什么,框架就尝试包装成什么:数组与对象包成 JSON,字符串包成 HTML,视图结果包成页面,Response 实例原样放行。大多数接口项目里,json() 助手就够了:

// 显式 JSON:状态码、响应头一步到位 public function detail(int $id) { $book = BookService::detail($id); if (! $book) { return json(['code' => 404, 'msg' => '不存在'], 404); } return json(['code' => 0, 'data' => $book]) ->header(['Cache-Control' => 'max-age=60']); }

自动包装省心,但有两个必须自己把关的点:状态码默认永远是 200——业务失败靠 JSON 里的 code 字段表达没问题,但 HTTP 层语义(404、422、429)客户端网关与监控都在看,该给就给;响应头是输出契约的一部分,缓存策略、跨域标记都在这里(跨域中间件是更系统的做法,见 2.4)。

图 3-2:响应形态选择图 · 四种出口

图 3-2:响应形态选择图 · 四种出口

二、重定向:跳转与闪存数据

表单提交后的「成功并回到列表」、登录后的「回到来时的页面」,都靠重定向。关键是闪存数据(flash)——把一次性提示挂到会话上,跳转后的页面取一次即失效:

public function save() { BookService::save($this->request->only(['title', 'price'])); // 跳转 + 闪存提示:下一个请求能看到,再下一个就没了 return redirect((string) Route::buildUrl('Book/list')) ->with('notice', '保存成功'); } // 列表页控制器里取出闪存 $notice = session('notice'); session()->forget('notice'); // 用完即清,防止残留

重定向的 URL 用 3.1 的 buildUrl 生成,两个纪律在这里交汇。

三、视图与下载:两个专用出口

视图出口只留一个引子(第 5 章整章展开):

public function index() { return view('book/index', [ 'list' => BookService::pageList(1), 'title' => '校园二手书', ]); }

下载出口值得完整写一遍,因为它是「响应对象完全自控」的最佳示例:

use think\Response; public function export() { // 服务层产出 CSV 内容字符串(业务在先,输出在后) $csv = OrderService::exportCsv($this->request->only(['date_start', 'date_end'])); return Response::create($csv, 'html', 200)->header([ 'Content-Type' => 'text/csv; charset=utf-8', 'Content-Disposition' => 'attachment; filename="orders.csv"', ]); }

状态码、内容类型、处置头全部显式——这就是响应对象的控制力。导出场景另一个常被忽略的点是内存:大表导出应分批取数、流式输出,这根线到第 4 章分页与第 8 章队列(异步导出)还会接续。

四、动手练习:统一接口信封

背景:练习项目 JSON 返回格式随写随变,前端对字段叫苦。操作:在 BaseController 里加两个受保护方法 ok($data)fail(int $code, string $msg),内部统一 json(['code'=>..,'msg'=>..,'data'=>..]) 并对失败路径给对应 HTTP 状态码;把已有接口全部改走信封。结果示例:前端只需处理一种结构,401、404、422 在 HTTP 层可观测,监控告警可以按状态码统计。解读:信封方法看似只是省几行,实际是把「对外契约」收敛到一处——将来要加 trace_id、要换字段名,改一个类完成。变式:给信封附上响应耗时(结合 2.4 的 RequestLog 中间件已经在加的响应头,体会中间件与信封各自该管什么)。

⚠️ 常见坑:不要在响应里直接吐异常消息与 SQL——调试信息属于日志,不属于客户端。生产环境的异常输出策略在第 8 章调试章节统一配置。

💡 关键直觉:响应是承诺——状态码、结构、字段名一旦发布就是契约。宁可一开始多花十分钟定信封,也别让前端陪你改三轮字段名。

五、信封之外:状态码与异常的映射工程

统一信封解决了「结构一致」,工程上还要解决两个配套问题。一是 HTTP 状态码与业务码的两层表达:信封里的业务码表达「业务层怎么看」,HTTP 状态码表达「传输层怎么看」——参数不合规配 422、未登录配 401、越权配 403、业务规则失败(库存不足)配 200 加非零业务码。前端据此分派:4 跳登录页、42 提示表单、业务码弹业务提示。两层混用(一切皆 200,或业务错误全配 500)都会把网关监控与前端处理同时搞瞎。

二是 异常到响应的统一映射:业务代码里随手返回错误信封,会让「错误怎么表达」散落几十处。工程做法是把可预期的业务异常定义成专门的异常类型,在全局异常处理里统一翻译成信封——控制器只管抛,出口只管译,改信封结构时只动一处。这个模式与 3.2 的输入、6.1 的验证天然衔接:验证失败抛验证异常,出口翻译成 422 信封,全链路无人手工拼错误结构。

本节要点回顾

  • 自动包装:返回值统一封装为响应对象,数组变 JSON、字符串变 HTML、视图变页面。
  • 状态码自觉:HTTP 状态码给客户端与监控看,业务 code 给前端逻辑看,两层信号都要给。
  • 重定向配闪存:跳转提示用 flash 存取一次即清,URL 用 buildUrl 生成。
  • 下载即全自控:内容类型、处置头、状态码显式声明,大文件分批流式处理。
  • 统一信封:输出结构收敛在基类的两个方法里,契约变更一处生效。

本章的分拣台到此完工。下一站是全书最重的一站——第 4 章数据站台:模型与 ORM。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U