是时候使用 Lumen 8 + API Resource 开发项目了!

使用 Lumen 8 + Api Resource  进行 API 开发

写在前面

工作中使用 Laravel 开发 API 项目已经有些年头了,发现每次启动新的 Api 项目的时都会在 Laravel 基础上进行一些预处理,包括针对 API 项目的结构设计,统一响应结构的封装,异常的捕获处理以及授权模块的配置等。总是在做一些重复的工作,那索性将这些常用的基础封装做成一个「启动模板」好了。

项目地址戳这儿

更新内容

  • 感谢 @渔郎 纠正改进文章内容(2020-06-28)
  • 实现了根据实际业务场景自定义响应码和多语言的响应描述,目录结构有参考 @mingzaily 同学的实现(2020-06-05)
  • 更好的支持 Api Resource:使用with()$additonal 附加额外数据,响应返回前使用withResponse()对响应进行处理(2020-06-04)
  • 补充响应成功时的使用示例(2020-06-03)
  • 规范 Reponse 中的 message 提示(2020-06-02)
  • 升级支持 Laravel 8 (2020-10-10)
  • 已封装成独立的 package,同时支持 Laravel 8 和 Lumen 8(2020-12-18)

使用示例:

为什么是 Lumen ?

现如今,中大型项目中通常使用前后端分离方式开发,前后端分别通过不同的代码仓库各自维护着项目代码,Laravel 只负责项目中的 API 部分,提供 API 给前端调用。这种场景下,使用 Laravel 进行开发 API 稍微显得有点“臃肿”了。

相比之下,Lumen 针对项目中的 API 开发场景,精简了Laravel 中的很多部分,更适合 API 开发。有了 Laravel 使用经验,切换到 Lumen 也较为容易。

概览

  • 适配 Laravel 7 中新增的 HttpClient 客户端(已升级到 Laravel 8)
  • 使用 Laravel 原生的 Api Resource
  • 规范统一的响应结构
  • 使用 Jwt-auth 方式授权
  • 支持日志记录到 MongoDB
  • 合理有效地『Repository & Service』架构设计(😏)

规范的响应结构

摘选自:RESTful 服务最佳实践

code——包含一个整数类型的HTTP响应状态码。
status——包含文本:”success”,”fail”或”error”。HTTP状态响应码在500-599之间为”fail”,在400-499之间为”error”,其它均为”success”(例如:响应状态码为1XX、2XX和3XX)。
message——当状态值为”fail”和”error”时有效,用于显示错误信息。参照国际化(il8n)标准,它可以包含信息号或者编码,可以只包含其中一个,或者同时包含并用分隔符隔开。
data——包含响应的body。当状态值为”fail”或”error”时,data仅包含错误原因或异常名称。

说明

整体响应结构设计参考如上,相对严格地遵守了 RESTful 设计准则,返回合理的 HTTP 状态码。

考虑到业务通常需要返回不同的“业务描述处理结果”,在所有响应结构中都支持传入符合业务场景的message

  • data:
    • 查询单条数据时直接返回对象结构,减少数据层级;
    • 查询列表数据时返回数组结构;
    • 创建或更新成功,返回修改后的数据;(也可以不返回数据直接返回空对象)
    • 删除成功时返回空对象
  • status:
    • error, 客户端(前端)出错,HTTP 状态响应码在400-599之间。如,传入错误参数,访问不存在的数据资源等
    • fail,服务端(后端)出错,HTTP 状态响应码在500-599之间。如,代码语法错误,空对象调用函数,连接数据库失败,undefined index等
    • success, HTTP 响应状态码为1XX、2XX和3XX,用来表示业务处理成功。
  • message: 描述执行的请求操作处理的结果;也可以支持国际化,根据实际业务需求来切换。
  • code: HTTP 响应状态码;可以根据实际业务需求,调整成业务操作码

代码实现

<?php


namespace App\Http;

use Illuminate\Http\Resources\Json\JsonResource;
use  \Illuminate\Http\Response as HttpResponse;

class Response
{
    public function errorNotFound($message = 'Not Found')
    {
        $this->fail($message, HttpResponse::HTTP_NOT_FOUND);
    }

    /**
     * @param  string  $message
     * @param  int  $code
     * @param  null  $data
     * @param  array  $header
     * @param  int  $options
     * @throws \Illuminate\Http\Exceptions\HttpResponseException
     */
    public function fail(string $message = '', int $code = HttpResponse::HTTP_INTERNAL_SERVER_ERROR, $data = null, array $header = [], int $options = 0)
    {
        $status = ($code >= 400 && $code <= 499) ? 'error' : 'fail';
        $message = (!$message && isset(HttpResponse::$statusTexts[$code])) ? HttpResponse::$statusTexts[$code] : 'Service error';

        response()->json([
            'status' => $status,
            'code' => $code,
            'message' => $message,// 错误描述
            'data' => (object) $data,// 错误详情
        ], $code, $header, $options)->throwResponse();
    }

    public function errorBadRequest($message = 'Bad Request')
    {
        $this->fail($message, HttpResponse::HTTP_BAD_REQUEST);
    }

    public function errorForbidden($message = 'Forbidden')
    {
        $this->fail($message, HttpResponse::HTTP_FORBIDDEN);
    }

    public function errorInternal($message = 'Internal Error')
    {
        $this->fail($message, HttpResponse::HTTP_INTERNAL_SERVER_ERROR);
    }

    public function errorUnauthorized($message = 'Unauthorized')
    {
        $this->fail($message, HttpResponse::HTTP_UNAUTHORIZED);
    }

    public function errorMethodNotAllowed($message = 'Method Not Allowed')
    {
        $this->fail($message, HttpResponse::HTTP_METHOD_NOT_ALLOWED);
    }

    public function accepted($message = 'Accepted')
    {
        return $this->success(null, $message, HttpResponse::HTTP_ACCEPTED);
    }

    /**
     * @param  JsonResource|array|null  $data
     * @param  string  $message
     * @param  int  $code
     * @param  array  $headers
     * @param  int  $option
     * @return \Illuminate\Http\JsonResponse|JsonResource
     */
    public function success($data, string $message = '', $code = HttpResponse::HTTP_OK, array $headers = [], $option = 0)
    {
        $message = (!$message && isset(HttpResponse::$statusTexts[$code])) ? HttpResponse::$statusTexts[$code] : 'OK';
        $additionalData = [
            'status' => 'success',
            'code' => $code,
            'message' => $message
        ];

        if ($data instanceof JsonResource) {
            return $data->additional($additionalData);
        }

        return response()->json(array_merge($additionalData, ['data' => $data ?: (object) $data]), $code, $headers, $option);
    }

    /**
     * @param  JsonResource|array|null  $data
     * @param  string  $message
     * @param  string  $location
     * @return \Illuminate\Http\JsonResponse|JsonResource
     */
    public function created($data = null, $message = 'Created', string $location = '')
    {
        $response = $this->success($data, $message, HttpResponse::HTTP_CREATED);
        if ($location) {
            $response->header('Location', $location);
        }

        return $response;
    }

    public function noContent($message = 'No content')
    {
        return $this->success(null, $message, HttpResponse::HTTP_NO_CONTENT);
    }
}

使用

在需要进行 HTTP 响应的地方使用 \\App\\Traits\\Helpers\\App\\Http\\Response中封装的响应方法进行调用。

通常使用是在 Controller 层中根据业务处理的结果进行响应,所以在 \\App\\Http\\Controllers基类中已经引入了 Helperstrait,可以直接在 Controller 中进行如下调用:

// 操作成功情况
$this->response->success($data,$message);
$this->response->success(new  UserCollection($resource),  '成功');// 返回 API Resouce Collection
$this->response->success(new  UserResource($user),  '成功');// 返回 API Resouce
$user  =  ["name"=>"nickname","email"=>"longjian.huang@foxmail.com"];
$this->response->success($user,  '成功');// 返回普通数组

$this->response->created($data,$message);
$this->response->accepted($message);
$this->response->noContent($message);

// 操作失败或异常情况
$this->response->fail($message);
$this->response->errorNotFound();
$this->response->errorBadRequest();
$this->response->errorForbidden();
$this->response->errorInternal();
$this->response->errorUnauthorized();
$this->response->errorMethodNotAllowed();

操作成功时的响应结构

  • 返回单条数据
{
    "data": {
        "nickname": "Jiannei",
        "email": "longjian.huang@foxmail.com"
    },
    "status": "success",
    "code": 200,
    "message": "成功"
}
  • 返回列表数据
{
    "data": [
        {
            "nickname": "Jiannei",
            "email": "longjian.huang@foxmail.com"
        },
        {
            "nickname": "Qian",
            "email": "1234567891@foxmail.com"
        },
        {
            "nickname": "Turbo",
            "email": "123456789@foxmail.com"
        }
        // ...
    ],
    "links": {
        "first": "http://lumen-api.test/users?page=1",
        "last": null,
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "path": "http://lumen-api.test/users",
        "per_page": 15,
        "to": 13
    },
    "status": "success",
    "code": 200,
    "message": "成功"
}

操作失败时的响应结构

{
    "status": "fail",
    "code": 500,
    "message": "Service error",
    "data": {}
}

异常捕获时的响应结构

整体格式与业务操作成功和业务操作失败时的一致,相比失败时,data 部分会增加额外的异常信息展示,方便项目开发阶段进行快速地问题定位。

  • 自定义实现了 ValidationException 的响应结构
{
    "status": "error",
    "code": 422,
    "message": "Validation error",
    "data": {
        "email": [
            "The email has already been taken."
        ],
        "password": [
            "The password field is required."
        ]
    }
}
  • NotFoundException异常捕获的响应结构

关闭 debug 时:

{
    "status": "error",
    "code": 404,
    "message": "Service error",
    "data": {
        "message": "No query results for model [App\\Models\\User] 19"
    }
}

开启 debug 时:

{
    "status": "error",
    "code": 404,
    "message": "Service error",
    "data": {
        "message": "No query results for model [App\\Models\\User] 19",
        "exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException",
        "file": "/var/www/lumen-api-starter/vendor/laravel/lumen-framework/src/Exceptions/Handler.php",
        "line": 107,
        "trace": [
            {
                "file": "/var/www/lumen-api-starter/app/Exceptions/Handler.php",
                "line": 55,
                "function": "render",
                "class": "Laravel\\Lumen\\Exceptions\\Handler",
                "type": "->"
            },
            {
                "file": "/var/www/lumen-api-starter/vendor/laravel/lumen-framework/src/Routing/Pipeline.php",
                "line": 72,
                "function": "render",
                "class": "App\\Exceptions\\Handler",
                "type": "->"
            },
            {
                "file": "/var/www/lumen-api-starter/vendor/laravel/lumen-framework/src/Routing/Pipeline.php",
                "line": 50,
                "function": "handleException",
                "class": "Laravel\\Lumen\\Routing\\Pipeline",
                "type": "->"
            }
            // ...
        ]
    }
}
  • 其他类型异常捕获时的响应结构
{
    "status": "fail",
    "code": 500,
    "message": "syntax error, unexpected '$user' (T_VARIABLE)",
    "data": {
        "message": "syntax error, unexpected '$user' (T_VARIABLE)",
        "exception": "ParseError",
        "file": "/var/www/lumen-api-starter/app/Http/Controllers/UsersController.php",
        "line": 34,
        "trace": [
            {
                "file": "/var/www/lumen-api-starter/vendor/composer/ClassLoader.php",
                "line": 322,
                "function": "Composer\\Autoload\\includeFile"
            },
            {
                "function": "loadClass",
                "class": "Composer\\Autoload\\ClassLoader",
                "type": "->"
            },
            {
                "function": "spl_autoload_call"
            }
           // ...
        ]
    }
}

特别说明:使用 Postman 等 Api 测试工具的时候,需要添加 X-Requested-With:XMLHttpRequest或者Accept:application/jsonheader 信息来表明是 Api 请求,否则在异常捕获到后返回的可能不是预期的 JSON 格式响应。

丰富的日志模式支持

  • 支持记录日志(包括业务错误记录的日志和捕获的异常信息等)到 MongoDB,方便线上问题的排查
  • 记录到 MongoDB 的日志,支持以每日、每月以及每年按表进行拆分
  • 支持记录 sql 语句

Repository & Service 模式架构

使用了andersao/l5-repository 进行项目结构设计,补充添加了 Service 层。

职责说明

待补充。

规范

命名规范:待补充

使用规范:待补充

Packages

其他

依照惯例,如果对您的日常工作有所帮助或启发,欢迎三连 star + fork + follow

如果有任何批评建议,通过邮箱(longjian.huang@foxmail.com)的方式可以联系到我。

总之,欢迎各路英雄好汉。

QQ 群:1105120693

参考


最近比较忙,后续文档还在整理中,包含「处理 Api 异常」、「处理好项目中的日志记录」、「规范项目中的常量使用」和「有效地使用 Repository & Service」等,各位好汉和进群讨论交流。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 3年前 自动加精
Jianne
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 65

很好的文章,先mark,期待持续更新!

3年前 评论

赞一下。最好在有一个laravel的就好了。。 repository + service 怎么用? repository 写 orm 查询, 然后到serivce 处理逻辑?

3年前 评论
Jianne

@laravel_denghy 后续的还在整理当中 :smirk:

3年前 评论

先三连了再说 :smile:

3年前 评论
Jianne

@白小白 文中的 Response 在 Laravel 中也是可以使用的哦,针对 Laravel 的 Api 启动模板什么的,后续也会有,毕竟现在使用 Laravel 的还是要更多一点。

3年前 评论
Jianne

@白小白 『repository + service』模式之前记得有个大佬写过文章,后来链接失效了。如你所说,service 主要是负责非业务类的 PHP 逻辑, repository 中处理系统业务和维护数据,而Laravel 的 Eloquent Model 只保留数据实体的定义和数据实体之间的联系等。后续会将实际的应用以及规范整理出来,到时候一起讨论~

3年前 评论
WhiteDragon 3年前
Jianne (作者) (楼主) 3年前
Jianne

@666666 :kissing_heart:

3年前 评论

我觉得完全可以出一个系统教程期待继续更新 :smile:

3年前 评论
Jianne

@firstsight 社区中的教程就很不错啊:Laravel 教程 - 实战构架 API 服务器 ( Laravel 6.x ) 6.x

3年前 评论

我阅读了一下你的代码,有两个问题能帮忙解答吗

  1. $this->validate返回的json是怎么处理的
  2. 为什么Repository还要分Repositories\Eloquent实体和Contracts\Repositories契约呀,service使用的时候去依赖注入契约

谢谢

3年前 评论
Jianne

@mingzaily 感谢反馈,对于问题回复如下:

  1. 翻阅 $this->validate()源码如下
// vendor/laravel/lumen-framework/src/Routing/ProvidesConvenienceMethods.php
public function validate(Request $request, array $rules, array $messages = [], array $customAttributes = [])
{
    $validator = $this->getValidationFactory()->make($request->all(), $rules, $messages, $customAttributes);

    if ($validator->fails()) {
        $this->throwValidationException($request, $validator);
    }

    return $this->extractInputFromRules($request, $rules);
}

可以看到如果对于 Request 中的参数校验失败会调用 throwValidationException,也就是抛出一个\Illuminate\Validation\ValidationException异常

 protected function throwValidationException(Request $request, $validator)
{
    throw new ValidationException($validator, $this->buildFailedValidationResponse(
        $request, $this->formatValidationErrors($validator)
    ));
}

继续看下其中调用的 buildFailedValidationResponse,结果是不是就明朗了

protected function buildFailedValidationResponse(Request $request, array $errors)
{
    if (isset(static::$responseBuilder)) {
        return call_user_func(static::$responseBuilder, $request, $errors);
    }

    return new JsonResponse($errors, 422);
}

看下原生的 Lumen 对异常是如何捕获处理的:可以看到如果是 ValidationException 对象实例的话会返回调用其中的 getResponse方法处理的响应

public function render($request, Throwable $e)
{
    if (method_exists($e, 'render')) {
        return $e->render($request);
    } elseif ($e instanceof Responsable) {
        return $e->toResponse($request);
    }

    if ($e instanceof HttpResponseException) {
        return $e->getResponse();
    } elseif ($e instanceof ModelNotFoundException) {
        $e = new NotFoundHttpException($e->getMessage(), $e);
    } elseif ($e instanceof AuthorizationException) {
        $e = new HttpException(403, $e->getMessage());
    } elseif ($e instanceof ValidationException && $e->getResponse()) {
        return $e->getResponse();
    }

    return $request->expectsJson()
                    ? $this->prepareJsonResponse($request, $e)
                    : $this->prepareResponse($request, $e);
}

再继续跟进下看看,ValidationException中的 getResponse在做什么:也就是throwValidationException中第二个参数抛出的 Response 对象

public function getResponse()
{
    return $this->response;
}

所以,慢慢思路就清晰了,原先ValidationException异常抛出JsonResponse对象实例,返回的数据结构我没法控制,那我就在抛出异常时返回一个我封装好的 Response 实例就好了:

// app/Http/Controllers/Controller.php

<?php

namespace App\Http\Controllers;

use App\Traits\Helpers;
use Illuminate\Http\Request;
use Laravel\Lumen\Routing\Controller as BaseController;

class Controller extends BaseController
{
    use Helpers;

    /**
     * Custom Failed Validation Response
     *
     * @param  Request  $request
     * @param  array  $errors
     * @return mixed
     */
    protected function buildFailedValidationResponse(Request $request, array $errors)
    {
        if (isset(static::$responseBuilder)) {
            return call_user_func(static::$responseBuilder, $request, $errors);
        }

        $this->response->fail('Validation error', 422, $errors);
    }
}

简单说就是在 Controller 基类中覆盖原先ValidationException异常构建的方法buildFailedValidationResponse,让 ta 抛出我们自定义的 $this->response->fail('Validation error', 422, $errors)异常响应。(你继续跟进 fail 方法,会发现实际上也是抛出了 JsonReponse 实例,只不过增加了额外的status、message和code,并且把 errors 丢到了 data 中)

  1. 「Repository 还要分 Repositories\Eloquent 实体和 Contracts\Repositories 契约」,这个是安装 L5-repository后提供的 make:repository命令执行后生成的,可以手动创建 Repository 文件不要 Contracts;Respository 和 Service 的结合,后续专门整理文章来讨论,三言两语说不清。
3年前 评论
mingzaily 3年前
麻雀在森林 3年前
Jianne (作者) (楼主) 3年前
Jianne (作者) (楼主) 3年前

我也仿造大佬你撸了一个Lumen6的,不过我跟你的异常处理那一块不太一样 地址

3年前 评论
Jianne

@mingzaily 看了一下,是一次不错的尝试哈~(项目结构更完善了)

  1. success 中去掉了 JsonResource 的判断,这个原先的考虑是在 Controller 中可以直接使用如下方式通过 Laravel 的 API Resource 方式来对数据返回前进行格式和结构的转换
// 返回 API Resouce Collection
return $this->response->success(new UserCollection($resource), '成功');
// 返回 API Resouce
return $this->response->success(new UserResource($user), '成功');
// 返回普通数组
$user = ["name"=>"nickname","email"=>"longjian.huang@foxmail.com"];
return $this->response->success($user, '成功');
  1. 自定义实际业务中的异常响应与框架异常格式统一,同时支持自定义异常的状态码控制,这个思路没毛病 :+1:

(应该是我比较懒,不然应该是我先实现)

3年前 评论
mingzaily 3年前

还是用laravel吧!

3年前 评论

mark 膜拜一下大佬

3年前 评论
游离不2

lumen 不支持 horizon,美中不足

3年前 评论

lumen7.x毕竟不是LTS版本,能不能搞一个lumen6.x版本呢?

3年前 评论
Jianne

@laozhou_666 可以安排一下

3年前 评论
Jianne

@游离不2 horizon 日常用得很多吗

3年前 评论

@Jiannei 哈哈 关注你帖子的人感觉比较多啊。 感觉你可以同步更新一个多版本的、也可以弄成一个composer 包 或者 提取出来公共目录,至少这样自己在基础上再次修改~

3年前 评论
Jianne

@白小白 感谢建议 :relaxed:

你可以看下 lumen-api-starter 的最新实现,已经是将完整的响应逻辑全部提取到单独的 Support 目录了,想最大程度地保留框架的「整洁」。

多版本的有在考虑,想着弄出几个 LTS 分支,以及最简版的 Simple 分支(去掉一些扩展,比如 prettus/l5-repository有些同学可能用不习惯),Composer 包也是个思路,emmm...

3年前 评论

不方便,基于路由的很多函数都没有,比如 路由名称什么的。

最近在重新构建一套本打算也是想用 lumen 做接口的,但是在 路由那里 实在是忍不了。

可能是用 laravel 用惯了,我构建系统的权限是基于 路由名称来的,所以 开发上面给我的体验很不友好。

对了,还有表单校验这个部分,个人习惯校验和控制前分离,类似laravel 我会单独分离校验与控制器部分,在业务之前就校验完成,但是在该项目里,如果要做到这种情况,我需要重新编写部分代码(这部分代码 需要在Lumen 重新在写一遍,我尝试过使用composer 在lumen里加载 laravel 相关套件 以便支持 FormRequest,加入后框架运行起来各种雪崩,一个问题接着一个,随后放弃。)

vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php
3年前 评论
Jianne

@seebyyu 你讲得对 :ok_hand:

刚从 Laravel 过来的时候会有多种的「水土不服」,使用 Lumen 的过程中,给我的感受是「牺牲了一部分的开发效率」,但「带来了更多的思考空间」。潜意识下会去对比 Laravel 中的用法和实现,就比平常更多的机会接触到了「底层源码」。

另外,文章的主体内容是讨论了 使用 Lumen 开发 API 项目前,如何去考虑更好地统一数据响应结构,这其中封装实现的过程,放在 Laravel 中开发 API 项目也是适用的。

为了不误导后面学习或工作中想使用 Lumen 的同学,自作主张提几点建议:

  1. 如果是新手小白,不建议直接上手 Lumen,文档没有 Laravel 的齐全,很多 package 没有作 Lumen 适配;
  2. 全栈型项目(前后端一起开发迭代,项目需要快速成型),不建议使用 Lumen,Laravel 更加合适;
  3. 纯 API 服务类项目,可以考虑使用 Lumen;
  4. ....
3年前 评论

大佬 是不是大厂都是后台用laravel,前端api用Lumen这种分开写的

3年前 评论
Jianne

@嘿喵财运旺旺 都会有,看架构师心情

3年前 评论
Jianne

@PHP-Coder :smile:

3年前 评论

那应该哪一种比较合理,之前在模型写访问器,和后端部分冲突,一度想复制个api用的模型,想想比较另类放弃了,活来看了Lumen,感觉比较适合我这种情况,但又想想是不是太另类了

3年前 评论
Jianne

@嘿喵财运旺旺 不折腾怎么变强

3年前 评论
臭鼬 3年前

我想咨询,service 之间的调用要如何处理,比方我登陆需要 UserService,又需要向 WX 第三方发送请求,封装了 WxService,我现在有点纠结

  • 在控制器中用两个 Service,如果采用这个方式,控制器怎么注入两个 Service,登录控制器已经注入了一个 Service
      public function __construct(UserService $service)
      {
          $this->service = $service;
          $this->middleware('auth:api', ['except' => ['store']]);
      }
  • 将 WxService 注入到 Uservice,该如何注入,封装的 WxService 是否该写静态化 static 方法,还是之间 new
3年前 评论
plsxysam 3年前
Jianne (楼主) 3年前
Jianne

最近比较忙(买基金去了:neutral_face:),后续文档还在整理中,包含「处理 Api 异常」、「处理好项目中的日志记录」、「规范项目中的常量使用」和「有效地使用 Repository & Service」等,各位好汉可以进群讨论交流。

QQ群:1105120693

3年前 评论
LW_aravel 3年前

大佬,问下,我想在laravel里面用这个应该怎么弄

3年前 评论
Jianne

@xiaoL8888 一样的思路哈

3年前 评论

大佬,现在有laravel版的了吗 :joy:

3年前 评论
Jianne

@嫩东哥 已经同时支持 Laravel 8 和 Lumen 8:

使用示例:

3年前 评论
dongzhiyu 3年前

统一格式,和枚举都封装成插件了!安装插件就得了!群主做的插件吧!

3年前 评论

列表数据不能吧links去除,完全没用

3年前 评论
leirhy

能不能把 $data = array_merge_recursive(['data' => $this->parseDataFrom($resource)], $paginationInformation) 改成 $data = array_merge_recursive(['rows' => $this->parseDataFrom($resource)], $paginationInformation); 两个data有时候真的容易脑乱。

3年前 评论
Jianne (楼主) 3年前
Jianne

@leirhy 这个之前在 Issue 中有过讨论,唯独这分页数据返回的格式口味不好统一,可以考虑增加配置参数来处理下。

3年前 评论
Jianne

发现还是有朋友继续在关注这篇文章,给后来的朋友说明下,本篇文章中的所有功能都封装成了独立的 package,可以通过 composer 安装到项目中使用,目前只支持 Laravel 8 或 Lumen 8 以上版本。(写文章时 Laravel 还是 7.X 版本,对于低版本的朋友,可以参考本篇的内容简单封装下)

使用示例:

另外,为了能更及时地处理反馈,组建了一个 QQ 群(1105120693),大家可以一起来交流学习ヾ(◍°∇°◍)ノ゙

3年前 评论

太棒了,支持 laravel 8 的话来试试

3年前 评论
Jianne (楼主) 3年前

HTTP状态码最好和业务Code码分开

2年前 评论

这里好像走不到自定义的message字符串,message都是返回OK,看了你的测试示例,是可以的,所以有点疑惑

file

2年前 评论
Jianne (楼主) 2年前

很好的文章,收益匪浅。期待尽快能完善:合理有效地『Repository & Service』架构设计

2年前 评论

$this->response->success(new UserCollection($resource), '成功'); 如何要根据请求动态显示字段,需要如何处理?

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!