三 【实战】Hyperf 第一个接口(v1/apply 机构入驻)

506 阅读4分钟

目标。 1 用migration 建表。

2 实现 /v1/apply 接口,机构入驻。 打开自动审核配置后,直接通过。(建机构,建用户数据)

备注: 文档内容为真实开发日志,很多个第一次。(migrate,查表,读写redis,读配置,事务 hyperf watch ,使用枚举,验频)等, 比较折腾,耐心看。

1 表格设计。

表名 edu_user \n

字段 id, tname,pwd,status,created_at,updated_at,deleted_at,last_login_at

这里简化处理,真实生产项目 ,建议加salt 字段,再加login_log 表

2 官网文档阅读 。

数据库迁移 (hyperf.wiki)

3 migration

3.1 创建迁移文件 。

docker exec -it hyperf bash
cd api 
php bin/hyperf.php gen:migration create_users_table --create=users

3.2 编写migration 文件 。

<?php

use Hyperf\Database\Schema\Schema;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string("tname", 32);
            $table->string("pwd", 32);
            $table->tinyInteger("status")->default(1);
            $table->integer("last_login_at");
            $table->integer("created_at");
            $table->integer("updated_at");
            $table->integer("deleted_at")->nullable(true);
            $table->datetimes();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('users');
    }
}

3.3 跑migration 代码 。

 php bin/hyperf.php migrate

SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'created_at' (SQL: create table `edu_users` (`id` bigint unsigned not null auto_increm
ent primary key, `tname` varchar(32) not null, `pwd` varchar(32) not null, `status` tinyint not null default '1', `last_login_at` int not null, `created_
at` int not null, `updated_at` int not null, `deleted_at` int null, `created_at` datetime null, `updated_at` datetime null) default character set utf8mb4
 collate 'utf8mb4_unicode_ci')
[ERROR] PDOException: SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'created_at' in /data/project/api.xuxing.tech/vendor/hyperf/data
base/src/Connection.php:347

Column already exists: 1060 Duplicate column name 'created_at' 去掉代码的 $table->datetimes(); 再跑一次。

Migrating: 2023_06_23_113756_create_users_table
App\Listener\DbQueryExecutedListener listener.
Migrated:  2023_06_23_113756_create_users_table

用工具看,表已创建 。

image.png

3.4 再练手一下修改表.

加个mobile 字段,用于手机号登录,修改tname 长度,限20

<?php

use Hyperf\Database\Schema\Schema;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Migrations\Migration;

class AlterUsersTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string("mobile", "32");
            $table->string("tname", 20)->change();
            $table->index("mobile");
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('users', function (Blueprint $table) {
            //
        });
    }
}

跑migration 时报错。

 php bin/hyperf.php migrate
 
Changing columns for table "users" requires Doctrine DBAL; install "doctrine/dbal".
[ERROR] RuntimeException: Changing columns for table "users" requires Doctrine DBAL; install "doctrine/dbal". in /data/project/api.xuxing.tech/vendor/hyp
erf/database/src/Schema/Grammars/ChangeColumn.php:34

排下坑,装一下

composer require "doctrine/dbal:^3.0"


composer require "doctrine/dbal:^3.0"
Info from https://repo.packagist.org: #StandWithUkraine
./composer.json has been updated
Running composer update doctrine/dbal
Loading composer repositories with package information
Updating dependencies
Lock file operations: 3 installs, 0 updates, 0 removals
  - Locking doctrine/cache (2.2.0)
  - Locking doctrine/dbal (3.6.3)
  - Locking doctrine/event-manager (1.2.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 3 installs, 0 updates, 0 removals
  - Downloading doctrine/event-manager (1.2.0)
  - Downloading doctrine/cache (2.2.0)
  - Downloading doctrine/dbal (3.6.3)
  - Installing doctrine/event-manager (1.2.0): Extracting archive
  - Installing doctrine/cache (2.2.0): Extracting archive
  - Installing doctrine/dbal (3.6.3): Extracting archive
Generating optimized autoload files


再跑一次迁移。

php bin/hyperf.php migrate

image.png

四 重新设计。

前面练手试了下 hyperf 的migrate . 现在重新设计一下接口。

4.1 . 表格

edu_org 机构表 edu_apply 申请记录 edu_users 添加 cur_org_id 当前机构字段。

4.2. 接口清单 。

  1. v1/apply 申请机构. 当前阶段,apply 即生效,后期再做管理后台
curl -H 'Content-Type: application/json' -X POST https://api.xxx.com/v1/apply -d '{
  "mobile": "138666688878",
  "org_name": "测试机构1",
  "contact": "王先生",
  "remark" : ""
}'
  1. v1/login 使用jwt + redis 实现单点登录.
curl -H 'Content-Type: application/json' -X POST https://api.xxx.com/v1/login -d '{
  "mobile": "138666688878",
  "pwd": "123456",
}'
  1. v1/logout 使用jwt + redis 实现单点登录.
curl -X GET https://api.xxx.com/v1/logout?token=xxx

五 开始实施。

5.1 建表

1 apply 表

docker-compose>docker exec -it hyperf bash
cd api
php bin/hyperf.php gen:migration create_apply_table --create=apply

编辑migrate 文件

<?php

use Hyperf\Database\Schema\Schema;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Migrations\Migration;

class CreateOrgTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('org', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->datetimes();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('org');
    }
}

保存文件后

php bin/hyperf.php migrate 

2 org 表。 (越简单越好,别一开始过度设计,符合场景和真实业务有必要再补字段)

 php bin/hyperf.php gen:migration create_org_table --create=org

当前,如果是生产项目 ,不要如此草率,值多进一步考虑,在一阶段要实现哪些,后期明确有和大概率有的提交准备上。

<?php

use Hyperf\Database\Schema\Schema;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Migrations\Migration;

class CreateOrgTable extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('org', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string("tname", 32);
            $table->string("remark", 255);
            $table->tinyInteger("status")->default(0);
            $table->integer("created_at");
            $table->integer("updated_at");
            $table->integer("deleted_at")->nullable(true);

        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('org');
    }
}

保存文件后

php bin/hyperf.php migrate 

3 用户表,增加与机构的关系 (员工可在多个机构为不同的员工) 当前机构 ,当前员工id . 这里简要设计 cur_staff_id 为 0 时为超管。 cur_org_id + cur_staff_id 唯一。 cur_org_id ,cur_staff_id。 这里先点到为止,后续rbac ,以及添加机构员工时,再补关系表。

创migrate 文件
<?php

use Hyperf\Database\Schema\Schema;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Migrations\Migration;

class AlertUserTable2 extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::table('user', function (Blueprint $table) {
            $table->integer("cur_org_id" );
            $table->integer("cur_staff_id");
            $table->unique(["cur_org_id", "cur_staff_id"]);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::table('user', function (Blueprint $table) {
            //
        });
    }
}

php bin/hyperf.php migrate 

这里发现,表名叫 edu_users 不好,更换为 edu_user

php bin/hyperf.php gen:migration rename_user

migration 文件

<?php

use Hyperf\Database\Schema\Schema;
use Hyperf\Database\Schema\Blueprint;
use Hyperf\Database\Migrations\Migration;

class RenameUser extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::rename("users", "user");
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
 
    }
}

保存文件后

php bin/hyperf.php migrate 

建表完成。如图。

image.png

5.2 实现apply 接口。

翻下文档 ,解决几个问题。

  1. 如何创建控制器。
  2. 接 postJson 数据。
  3. 入mysql 数据,期望实现 insert(table,table, data) , update(table,table, data, $where) 等方法。 (说直白点,将数组转sql 。
  4. mysql 事务。
  5. 如何定义接口返回。
  6. 设置与读取 .env

5.2.1 如何创建控制器

翻了下文档 。 个人选择 通过配置文件定义路由 hyperf.wiki/3.0/#/zh-cn…

这样出来的代码,入口归总后,找起来方便。避免后续乱添加文件,不知道哪里是哪里。(很久以前用Yii ,ci 时就极不爽这个)

// 1 在 config/route.php 文件中,加上
Router::post('/post', 'App\Controller\IndexController::post');
// 2 copy paste IndexController 至 ApplyController ,然后照着改即可。

5.2.2 获取json 数据。

翻文档得

// 存在则返回,不存在则返回 null
$name = $request->input('user.name');
// 存在则返回,不存在则返回默认值 Hyperf
$name = $request->input('user.name', 'Hyperf');
// 以数组形式返回所有 Json 数据
$name = $request->all();

5.2.3 数据库相关用法示例。

Db::table('users')->insert( ['email' => 'john@example.com', 'votes' => 0] );
$id = Db::table('users')->insertGetId( ['email' => 'john@example.com', 'votes' => 0] );
Db::table('users')->where('id', 1)->update(['votes' => 1]);

说明一下,个人较为反感orm ,sql 本来是件简单事,又不是存在啥智力缺陷写不出来sql , 或者你要今天mysql 明天oracle 等装x 需求,就没必要去研究什么 where,or_where,union 等破事了。 而且,真到性能优化时, 恶心的orm 套一层后,你会想哭。 所以,原生sql 加几个助手方法就ok 了。

5.2.4 mysql 事务。


use Hyperf\DbConnection\Db;

Db::beginTransaction();
try{

    // Do something...

    Db::commit();
} catch(\Throwable $ex){
    Db::rollBack();
}

5.2.5 定义返回 。

抄 IndexController 的例子,直接return 数组即可。

    return [
        'method' => $method,
        'message' => "Hello {$user}.",
    ];
}

通常响应数据 类似

{
  "code": 0,
  "msg": "SUCCESS",
  "data1": "1111",
  "data2": "1111",
}

然后code 使用枚举类来实现 。 hyperf.wiki/3.0/#/zh-cn…

php bin/hyperf.php gen:constant ErrorCode
declare(strict_types=1);

namespace App\Constants;

use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;

#[Constants]
class ErrorCode extends AbstractConstants
{
    /**
     * @Message("Server Error!")
     */
    const SERVER_ERROR = 500;

    /**
     * @Message("系统参数错误")
     */
    const SYSTEM_INVALID = 700;
}

用户可以使用 ErrorCode::getMessage(ErrorCode::SERVER_ERROR) 来获取对应错误信息。

5.2.6 折腾下配置。

hyperf.wiki/3.0/#/zh-cn… .env 中配置。 config 中设置,默认值。 原则, 先取 .env 然后取 config 的默认值。 使用注解来获取配置。

use Hyperf\Config\Annotation\Value;

class IndexController
{
    #[Value("config.key")]
    private $configValue;

    public function index()
    {
        return $this->configValue;
    }
}

这玩意,越来越像 java,dotnet 了,怪怪的。还好我都会点皮毛,见怪不怪了。

再次说明,就一般问题而言,查下官网文档是效率最高的,没有之一。 不要碰到事就百度,google ,严重浪费时间

5.3 准备就绪,开整代码。

5.3.1 定义apply 错误Constant

php bin/hyperf.php gen:constant ErrorCode

app\Constants\ErrorCode.php

<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace App\Constants;

use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;

#[Constants]
class ErrorCode extends AbstractConstants
{
    /**
     * @Message("Server Error!")
     */
    public const SERVER_ERROR = 500;


    /**
     * @Message("参数不足")
     */
    public const APPLY_LOCK_PARAMS= 701;


    /**
     * @Message("参数不足")
     */
    public const APPLY_REPEAT= 702;



}

一开始,就不往复杂的整了,我就做个小项目,就不折腾国际化, 响应不同的http 状态码这些破事了, 直接响应json ,简单直接,前端(也是自己整)也喜欢

再定义一个 Enum 用于存状态,类别等业务枚举数据

5.3.2 配置 .env

# org 申请业务
APPLY_AUTO_AUDIT=1

后期有转为后台审核时改掉即可。

5.3.3 route 添加配置

\config\routes.php

//添加
Router::post('/v1/apply', 'App\Controller\ApplyController::apply');

5.3.4 处理代码热更新。

突然发现一件事情,改了代码后,hyperf 未生效。需要ctrl +c 再重启服务 . 这个在开发场景下体验极差. 翻了下文档,有个watcher hyperf.wiki/3.0/#/zh-cn…

//安装
composer require hyperf/watcher --dev

//发布
php bin/hyperf.php vendor:publish hyperf/watcher

//启动。
php bin/hyperf.php server:watch

提示端口被占, 于是改Dockerfile

FROM  swr.cn-south-1.myhuaweicloud.com/docker-study/hyperf:1.0

WORKDIR  /data/project
ENTRYPOINT ["php", "/data/project/api.xuxing.tech/bin/hyperf.php", "server:watch"]

EXPOSE 9501

docker-compose down docker-compose up -d

调试一下,改下indexController 的返回,看是否生效。 很遗憾,没生效。 开启开启排坑流程

docker inspect hyperf |grep -C2 hyperf

        "Path": "php",
        "Args": [
            "/data/project/api/bin/hyperf.php",
            "start"
        ],

发现还是start

将build 出来的镜像剁了

docker-compose down
docker images

REPOSITORY                                             TAG                       IMAGE ID       CREATED         SIZE
docker-compose_hyperf                                  latest                    e680c0352182   12 hours ago    122MB
hyperf/hyperf                                          8.0-alpine-v3.15-swoole   2139f47d3995   6 days ago      122MB
swr.cn-south-1.myhuaweicloud.com/docker-study/hyperf   1.0                       2139f47d3995   6 days ago      122MB
redis                                                  latest                    8e69fcb59ff4   8 days ago      130MB

docker rmi docker-compose_hyperf:latest

docker-compose up -d

再看 docker inspect hyperf |grep -C2 hyperf

        "Path": "php",
        "Args": [
            "/data/project/api/bin/hyperf.php",
            "server:watch"
        ],

再测,修改代码后重启curl . 搞定。 curl 127.0.0.1 {"method":"GET","message":"Hello23332sss2 Hyperf.","debug":2}

最后,文档还提到有些不足。

  • 删除文件和修改.env需要手动重启才能生效。

5.3.5

终于到写控制器了。 千万别急 走一些前续流程。

  1. 前序处理。 1 写注释代码用来干啥 , 2 列出代码步骤 , 3 打开postman 搭好测试环境 两个原因。
1 代码是给人来看的。
2 理清思路,做事不慌,记忆力是件不靠谱的事,列清楚了,更有效率。
3  tdd. 快速可测是效率的根基.

代码示例。

<?php

declare(strict_types=1);
/**
 *
 * @Author xuxing
 * @description 机构申请接口. (apply_auto_audit 打开后,自动生效)
 */
namespace App\Controller;



class ApplyController extends AbstractController
{


    public function apply()
    {

        return [
            'test' => '收到请求'
        ];

        //请求示例。
        /*
        curl -H 'Content-Type: application/json' -X POST https://api.xxx.com/v1/apply -d '{
          "mobile": "138666688878",
          "org_name": "测试机构1",
          "contact": "王先生",
          "remark" : ""
        }'
        */

        //一 校验
        //1 cc 用redis ,限一下请求频率。
        //2 接收请求数据,校验参数。
        //3 是否重复apply  (状态为pending)

        //二 数据入库。
        //2 开启事务。
        //3 组装apply 数据,入库
        //4 判断 apply_auto_audit 是否打开
        //5 打开后,org 数据入库,user 数据入库,默认密码  123456

        //三 发通知后台审。
        // later 处理。


    }
}
  1. postman 测试看route 是否生效。

image.png

  1. 先实现功能。(这个时间,简单实现,别想太多封装技巧啥的)

3.1 走通读取配置。 又tmd 遇坑

//参照文档 ,结果失效。
#[Value("config.app_name")]
private $configValue;

//太晚,我对框架实现细节无兴趣,不想排坑,换个方法实现 

return [
    'test' => '收到请求',
    "apply_auto_audit" => config("apply_auto_audit")
];

image.png

返回处理包装一下 ,请求数据验频这些通用处理 app/AbstractController.php

<?php

...
abstract class AbstractController
{
      ...  
     * @param $code
     * @param array $data
     * @return array|string[]
     */
    protected function code($code, $data = [])
    {
        return !empty($data) ? array_merge([
            'code' => $code,
            'msg'  => ErrorCode::getMessage($code)
        ], $data) : [
            'code' => $code,
            'msg'  => ErrorCode::getMessage($code)];
    }



    /**
     * 请求数据验率
     * @param $data
     * @return array|string[]
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    protected function validCc($data)
    {
        $redis    = $this->container->get(\Redis::class);
        $redisKey = md5(json_encode($data));
        if ($redis->get($redisKey) != null) {
            return $this->code(ErrorCode::TOO_FREQUENTLY);
        }
        $redis->setex($redisKey,3,1);
    }


}

最后,实现代码如下。

<?php

declare(strict_types=1);
/**
 *
 * @Author xuxing
 * @description 机构申请接口. (apply_auto_audit 打开后,自动生效)
 */

namespace App\Controller;

use App\Constants\Enum;
use App\Constants\ErrorCode;
use Hyperf\Config\Annotation\Value;
use Hyperf\DbConnection\Db;

use Hyperf\Redis\Redis;


class ApplyController extends AbstractController
{

    /**
     * 机构申请.
     * @return array|string[]
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    public function apply()
    {

        $postData = $this->request->all();

        $this->validCc($postData);

        //获取数据,并校验参数。
        $mobile = $this->request->input('mobile');
        if($mobile == null) {
            return $this->code(ErrorCode::APPLY_LOCK_PARAMS);
        }

        $org_name = $this->request->input('org_name');
        if($org_name == null) {
            return $this->code(ErrorCode::APPLY_LOCK_PARAMS);
        }

        $contact = $this->request->input('contact');
        if($contact == null) {
            return $this->code(ErrorCode::APPLY_LOCK_PARAMS);
        }

        $remark = $this->request->input('remark');

        //判断是否申请过
        $has_apply = Db::table('apply')->where(
           [
                ['status', '=', Enum::APPLY_STATUS_PENDING],
                ['mobile', '=', $mobile],
            ]
        )->exists();

        if($has_apply) {
            return $this->code(ErrorCode::APPLY_REPEAT);
        }

        $has_user =  Db::table('user')->where(
            [
                ['mobile', '=', $mobile],
            ]
        )->exists();

        if($has_user) {
            return $this->code(ErrorCode::APPLY_USER_EXIST);
        }

        Db::beginTransaction();
        try{
            //组装 apply 数据。
            $applyData = [
                'mobile' => $mobile,
                'org_name' => $org_name,
                "contact" => $contact,
                "remark" => $remark,
                "status" => Enum::APPLY_STATUS_PENDING,
                "created_at" => time(),
                "updated_at" => time(),
            ];
            $apply_id = Db::table('apply')->insertGetId( $applyData );

            //开启自动审核后
            if (config("apply_auto_audit")) {
                $org_data = [
                   "tname" => $org_name,
                   "remark" => $remark,
                   "status" => Enum::ORG_STATUS_OK,
                   "created_at" => time(),
                   "updated_at" => time(),
                ];
                $org_id = Db::table('org')->insertGetId( $org_data );

                $userData =[
                    'tname' => $contact,
                    "pwd" => md5('123456'),
                    "status" => Enum::USER_STATUS_OK,
                    "mobile" => $mobile,
                    "cur_org_id" => $org_id,
                    "cur_staff_id" => 0,
                    "last_login_at" => time(),
                    "created_at" => time(),
                    "updated_at" => time(),
                ];
                Db::table('user')->insertGetId( $userData );
                Db::table('apply')->where('id', $apply_id)->update(['status' => Enum::APPLY_STATUS_OK]);

            }

            Db::commit();
        } catch(\Throwable $ex){
            return $this->code(ErrorCode::ERROR, ["ex" => $ex->getMessage()]);
            Db::rollBack();
        }
        return ['code' => ErrorCode::SUCCESS, "msg" => 'apply success'];
    }

}

image.png

测试ok,查看数据库,符合预期 。

总结。

第一次用hyperf 写接口。大坑没有,小坑有一些。(config 读取,watch )等 。
边开发,连写文档的经验不足 ,效率被拉址得很低。 最后写controller 时,实在不愿意每步写文档了, 还有一些待处理的事宜。 例如,写日志。

最后,git 提交,睡觉 .明天再来。

遇坑,重启容器后报错。

watcher 失效。

带着报错调试,Listener 失效,取数据还是对象。 修改 DbQueryExecutedListener.php

<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace App\Listener;

use Hyperf\Collection\Arr;
use Hyperf\Database\Events\QueryExecuted;
use Hyperf\Database\Events\StatementPrepared;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use PDO;

#[Listener]
class DbQueryExecutedListener implements ListenerInterface
{
    /**
     * @var LoggerInterface
     */
    private $logger;

    public function __construct(ContainerInterface $container)
    {
        $this->logger = $container->get(LoggerFactory::class)->get('sql');
    }

    public function listen(): array
    {
        return [
            QueryExecuted::class,
            StatementPrepared::class,
        ];
    }

    /**
     * @param QueryExecuted $event
     */
    public function process(object $event): void
    {

        if ($event instanceof StatementPrepared) {
            $event->statement->setFetchMode(PDO::FETCH_ASSOC);
        }

        if ($event instanceof QueryExecuted) {
            $sql = $event->sql;
            if (! Arr::isAssoc($event->bindings)) {
                $position = 0;
                foreach ($event->bindings as $value) {
                    $position = strpos($sql, '?', $position);
                    if ($position === false) {
                        break;
                    }
                    $value = "'{$value}'";
                    $sql = substr_replace($sql, $value, $position, 1);
                    $position += strlen($value);
                }
            }

            $this->logger->info(sprintf('[%s] %s', $event->time, $sql));
        }
    }
}

还有一些不好的代码习惯,引发的bug

例如,未使用use ,就直接Inject 
use Hyperf\Di\Annotation\Inject;

然后还碰到了 ErrorCode::getMessage($code) 内容突然为空。 重启hyperf 后解决。

总结。 当新增文件后,watcher 可能会失效, 后续有大改动时, docker restart hyperf