hyperf| 编码实践一: 基础篇

本文涉及的产品
语种识别,语种识别 100万字符
图片翻译,图片翻译 100张
日志服务 SLS,月写入数据量 50GB 1个月
简介: 整个团队使用 hyperf 开发已经超过半年, 积累了一些最佳实践和规约, 方便团队后续开发, 提供给大家参考~

整个团队使用 hyperf 开发已经超过半年, 积累了一些最佳实践和规约, 方便团队后续开发, 提供给大家参考~

<?php

declare(strict_types=1);

namespace App\Command;

use App\Amqp\Producer\TestProducer;
use App\Model\Logistic;
use Carbon\Carbon;
use Hyperf\Amqp\Producer;
use Hyperf\Command\Command as HyperfCommand;
use Hyperf\Command\Annotation\Command;
use Hyperf\DbConnection\Db;
use Hyperf\Logger\Logger;
use Hyperf\Logger\LoggerFactory;
use Mt\Util\Log;
use PDO;

/**
 * @Command
 */
class HyperfDemoCommand extends HyperfCommand
{
    protected $name = 'hd';

    public function configure()
    {
        parent::configure();
        $this->setDescription('Hyperf Demo Command');
    }

    public function handle()
    {
        $this->line('Hello Hyperf!', 'info');
    }

    /**
     * 生命周期
     * @link https://doc.hyperf.io/#/zh-cn/lifecycle
     */
    public function lifecycle()
    {
        // 生命周期分析法是非常常用的一种分析手法, 可以帮忙快速理解一个新系统
        // 分析方式一: 流程图, 入口->出口, 一次 http 请求经过了哪些步骤?
        // 分析方式二: 分层, 分解/拆分问题

        // hyperf 生命周期: container application swoole http coroutine
    }

    /**
     * 协程
     * @link https://www.jianshu.com/p/12d645ac02b2 swoole-wiki 笔记, 全面梳理 swoole 基础知识
     * @link https://doc.hyperf.io/#/zh-cn/coroutine
     */
    public function coroutine()
    {
        // demo: basic
        go(function () {
            sleep(1);
            echo 'go' . PHP_EOL;
        });
        echo 'main' . PHP_EOL;

        // demo: 性能测试
        // 使用命令行 time 命令查看耗时, 可以看到 sys/user 分别耗时
        // 推荐使用 for 循环, 明确使用的协程的数量, 避免把下游服务(db/第三方接口)打爆
        for ($i = 0; $i < 10; $i++) {
            go(function () use ($i) {
                // do some **io task**
                sleep(1);
                echo "go $i \n";
            });
        }

        // swoole runtime
        // 暂时不要使用 SWOOLE_HOOK_CURL, curl api 目前只兼容了大部分常用的
        !defined('SWOOLE_HOOK_FLAGS') && define('SWOOLE_HOOK_FLAGS', SWOOLE_HOOK_ALL | SWOOLE_HOOK_CURL);

        // todo: 协程更多特性 demo
        // 特性一定要使用其 **最佳实践**, 不能为了使用而使用
    }

    /**
     * 配置
     * 配置哲学: 约定大于配置, 不必要的配置导致不必要的灵活性
     * @link http://deploy-dev.mengtuiapp.com/doc#/ms?id=config-%e6%a8%a1%e5%9d%97%e8%af%a6%e8%a7%a3
     * @link https://doc.hyperf.io/#/zh-cn/config
     */
    public function config()
    {
        // 推荐统一使用使用 config()
        var_dump(config('app_name'));

        // 不推荐使用 Config 对象重新设置配置

        // 只允许在配置文件中使用 env()
    }

    /**
     * 容器 container
     * 依赖注入 DI
     * @link https://doc.hyperf.io/#/zh-cn/di
     */
    public function container()
    {
        // Config 加载后, container 会根据 config 实例化, 并处理类之间的依赖关
        // container 在手, 天下我有
        // 封装了非常好用 container() 的方法, 推荐统一使用此方法

        // 想要 logger
        /** @var Logger $logger */
        $logger = container(LoggerFactory::class)->get('test');
        $logger->info('test');

        // 当然, 这样常用的类已经封装好了
        // 效果和上面等同
        Log::get('test')->info('test2');

        // 一个常犯的错误: new + @Inject
        // new 出来的类里面使用了注解, 那么这个类必须交给 container 管理, 注解才能生效
    }

    /**
     * 事件机制
     * @link https://doc.hyperf.io/#/zh-cn/event
     * @link https://doc.hyperf.io/#/zh-cn/event?id=%e6%b3%a8%e6%84%8f%e4%ba%8b%e9%a1%b9 事件中的循环依赖问题
     */
    public function listener()
    {
        // 典型场景: swoole event
        // \Hyperf\Server\SwooleEvent
        // config/autoload/server.php server中配置 swoole event 的回调

        // 典型场景: application event
        // \Mt\Listener\BootAppConfListener::process kms 处理等

        // 典型场景: db event
        // \Mt\Listener\DbQueryExecutedListener::process db 日志/监控都是在这里实现

        // 事件机制使用场景非常多, 并且可以有效扩展系统能力, 务必熟悉并掌握
    }


    /**
     * 路由
     * @link https://doc.hyperf.io/#/zh-cn/router
     */
    public function routes()
    {
        // 统一在 routes.php 中定义路由
        // apidog 组件已经设置 @AutoController 注解失效

        // 可以使用 route 文件, 定义更多路由
        // 实现: \Mt\Util\RoutesDispatcher

        // 还有一种简单的方式: 在 routes.php 中 require
    }

    /**
     * 中间件
     * @link https://doc.hyperf.io/#/zh-cn/middleware/middleware
     */
    public function middleware()
    {
        // 这里是狭义的中间件, 只处理 request->response
        // 执行顺序: 洋葱模型 全局->类级别->方法级别

        // demo1: http log, 记录 http 请求日志
        // \Mt\Middleware\HttpLogMiddleware

        // demo2: 鉴权
        // \Mt\Middleware\AuthMiddleware

        // demo3: sso, 单点登录
        // \Mt\Middleware\SsoMiddleware

        // demo4: 跨域中间件, 跨域配置也可以直接挂在 nginx 上
        // CorsMiddleware
    }

    /**
     * 控制器
     * @link https://doc.hyperf.io/#/zh-cn/controller
     */
    public function controller()
    {
        // 最佳实践: 封装好 BaseController, 约定好 response 数据格式等常用功能
        // \Mt\Util\AbstractController

        // 知识点一: swoole 会自动为每个请求分配好协程
        // 知识点二: 贡献数据要使用 **协程上下文(Context)**, 不可使用 类属性/类常量
    }

    /**
     * 请求
     * @link https://doc.hyperf.io/#/zh-cn/request
     */
    public function request()
    {
        // 统一使用 \Hyperf\HttpServer\Request 来处理请求
        // 或者基于 \Hyperf\HttpServer\Request 对象进行封装

        // psr-7 标准 api
        // \Hyperf\HttpServer\Request 提供: 请求路径 输入预处理 json cookie file

        // 注意: header 相关方法要注意返回值, 可能嵌套一层 array
    }

    /**
     * 响应
     * @link https://doc.hyperf.io/#/zh-cn/response
     */
    public function response()
    {
        // 统一使用 Hyperf\HttpServer\Response 对象
        // 响应格式以及自动设置 `content-type`: json xml raw view
        // 其他: redirect file 等
    }

    /**
     * 异常处理器
     * @link https://doc.hyperf.io/#/zh-cn/exception-handler
     */
    public function exceptionHandler()
    {
        // 如果 swoole worker 进程遇到未捕获的异常, 会导致进程退出, 所以必须要有异常处理器

        // demo1: 统一处理 user exception
        // \Mt\Handler\HttpExceptionHandler 输出日志并根据环境返回 response

        // demo2: error_reporting() 错误
        // Hyperf\ExceptionHandler\Listener\ErrorExceptionHandler
        // 还有一种方式: php bin/hyperf.php > runtime/run.log 中
    }

    /**
     * 日志
     * @link https://doc.hyperf.io/#/zh-cn/logger
     */
    public function log()
    {
        // 已经封装好了, 直接使用
        // 注意日志的结构: channel / level / message / context
        // 不同环境: dev 会直接打到 stdout, 其他环境打到日志文件->日志服务
        Log::get('test')->info('test');
    }

    /**
     * 命令行
     * @link https://doc.hyperf.io/#/zh-cn/command
     */
    public function command()
    {
        // 脚本有制作好的脚手架, 使用脚手架可以减少大量重复性的开发工作
        // mslib/core/Util/ScriptJob/AbstractScriptScaffold.php
        // doc/script_scaffold.md

        // 常见错误一: 脚本中有语法错误, 导致 `Command "hd" is not defined.`
//        echo 'error 1'

        // 常见错误二: 使用 amqp producer 报错
        // 这是因为 amqp 连接池在 command 执行完后没有比较好的方式进行关闭, 实际脚本逻辑是正常执行的
        /** @var Producer $producer */
        $producer = container(Producer::class);
        $producer->produce(new TestProducer(Carbon::now()));
    }

    /**
     * 单元测试
     * @link https://doc.hyperf.io/#/zh-cn/testing
     */
    public function test()
    {
        // 调试代码
        // 传统方式: 修改 -> 重启 server -> 浏览器/其他工具 测试接口
        // 单元测试: 通过配合 testing,来快速调试代码,顺便完成单元测试

        // 测试替身 mock
        // 有时候对 被测系统(SUT) 进行测试是很困难的,因为它依赖于其他无法在测试环境中使用的组件
    }

    /**
     * 视图
     * @link https://doc.hyperf.io/#/zh-cn/view
     */
    public function view()
    {
        // 不建议使用, 需要使用单独的进程完成视图的渲染
        // 前后端分离 + 组件化平台构建
    }

    /**
     * 验证器
     * @link https://doc.hyperf.io/#/zh-cn/validation
     */
    public function validation()
    {
        // 推荐使用, 可以将请求校验从 Controller 逻辑中解耦出来
    }

    /**
     * session
     * @link https://doc.hyperf.io/#/zh-cn/session
     */
    public function session()
    {
        // 兼容必须使用 session 的场景
        // 微服务中不建议使用
    }

    /**
     * db
     * @link https://doc.hyperf.io/#/zh-cn/db/quick-start
     * @link https://doc.hyperf.io/#/zh-cn/db/querybuilder
     * @link https://doc.hyperf.io/#/zh-cn/db/model
     */
    public function db()
    {
        // 配置最佳实践: 已 db 为粒度, 哪怕在同一个 db 实例上, 也分开进行配置
        // 读写分离: 优先使用 db 层的读写分离, 业务中显式使用 可写连接/只读连接
        // 不允许使用 default, 显式命名并使用

        // 连接池配置
        $pool = [
            'min_connections' => 1,
            'max_connections' => 10,
            'connect_timeout' => 10.0,
            'wait_timeout' => 3.0,
            // 心跳检查
            'heartbeat' => -1,
            'max_idle_time' => (float) env('DB_MAX_IDLE_TIME', 60),
        ];

        // pdo 配置
        $option = [
            // 框架默认配置
            PDO::ATTR_CASE => PDO::CASE_NATURAL,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
            PDO::ATTR_STRINGIFY_FETCHES => false,
            // 不支持 MySQL prepare 协议的 db, 需要配置为 true
            PDO::ATTR_EMULATE_PREPARES => false,
        ];

        // 事务: 必须显式指定连接
        $db = Db::connection('pt_logistic');

        // 自动管理事务
        $db->transaction(function () {
            // do something
        });

        // 手动管理事务
        $db->beginTransaction();
        try {
            // do something
            $db->commit();
        } catch (\Throwable $ex) {
            $db->rollBack();
        }

        // query builder
        // 所有连接从 Model 中获取, 不允许直接使用 Db::connection()
        // 查询结果, 统一转化为数组
        // 原则上不允许使用 join

        // 返回一行
        Logistic::query()->first();
        // 返回单个值
        Logistic::query()->value('id');
        // 返回一列值
        Logistic::query()->pluck('name', 'id');
        // 返回多行
        Logistic::query()->limit(10)->get();

        // 悲观锁
        Logistic::query()->sharedLock()->limit(10)->get();
        Logistic::query()->lockForUpdate()->limit(10)->get();

        // 批量插入/更新: 传入数组即可
        Logistic::query()->insert([['id' => time()]]);
        Logistic::query()->update([['id' => 1]]);

        // 软删除: use SoftDeletes;

        // 极简 db 组件: https://doc.hyperf.io/#/zh-cn/db/db
        // 性能更好, 不推荐使用在较重业务中使用
    }
}
目录
相关文章
|
2月前
|
开发框架 缓存 前端开发
electron-builder 解析:你了解其背后的构建原理吗?
本文首发于微信公众号“前端徐徐”,详细解析了 electron-builder 的工作原理。electron-builder 是一个专为整合前端项目与 Electron 应用的打包工具,负责管理依赖、生成配置文件及多平台构建。文章介绍了前端项目的构建流程、配置信息收集、依赖处理、asar 打包、附加资源准备、Electron 打包、代码签名、资源压缩、卸载程序生成、安装程序生成及最终安装包输出等环节。通过剖析 electron-builder 的原理,帮助开发者更好地理解和掌握跨端桌面应用的构建流程。
194 2
|
4月前
|
JSON Java API
解码Spring Boot与JSON的完美融合:提升你的Web开发效率,实战技巧大公开!
【8月更文挑战第29天】Spring Boot作为Java开发的轻量级框架,通过`jackson`库提供了强大的JSON处理功能,简化了Web服务和数据交互的实现。本文通过代码示例介绍如何在Spring Boot中进行JSON序列化和反序列化操作,并展示了处理复杂JSON数据及创建RESTful API的方法,帮助开发者提高效率和应用性能。
195 0
|
6月前
|
SQL NoSQL Linux
gRPC 基础编码使用手册
gRPC 基础编码使用手册
73 6
|
5月前
|
XML Java 测试技术
《手把手教你》系列基础篇(八十八)-java+ selenium自动化测试-框架设计基础-Log4j 2实现日志输出-下篇(详解教程)
【7月更文挑战第6天】本文介绍了如何使用Log4j2将日志输出到文件中,重点在于配置文件的结构和作用。配置文件包含两个主要部分:`appenders`和`loggers`。`appenders`定义了日志输出的目标,如控制台(Console)或其他文件,如RollingFile,设置输出格式和策略。`loggers`定义了日志记录器,通过`name`属性关联到特定的类或包,并通过`appender-ref`引用`appenders`来指定输出位置。`additivity`属性控制是否继承父logger的配置。
47 0
|
5月前
|
XML Java 测试技术
《手把手教你》系列基础篇(八十七)-java+ selenium自动化测试-框架设计基础-Log4j 2实现日志输出-上篇(详解教程)
【7月更文挑战第5天】Apache Log4j 2是一个日志框架,它是Log4j的升级版,提供了显著的性能提升,借鉴并改进了Logback的功能,同时修复了Logback架构中的问题。Log4j2的特点包括API与实现的分离,支持SLF4J,自动重新加载配置,以及高级过滤选项。它还引入了基于lambda表达式的延迟评估,低延迟的异步记录器和无垃圾模式。配置文件通常使用XML,但也可以是JSON或YAML,其中定义了日志级别、输出目的地(Appender)和布局(Layout)。
52 0
|
7月前
|
JSON JavaScript 前端开发
Golang深入浅出之-Go语言JSON处理:编码与解码实战
【4月更文挑战第26天】本文探讨了Go语言中处理JSON的常见问题及解决策略。通过`json.Marshal`和`json.Unmarshal`进行编码和解码,同时指出结构体标签、时间处理、omitempty使用及数组/切片区别等易错点。建议正确使用结构体标签,自定义处理`time.Time`,明智选择omitempty,并理解数组与切片差异。文中提供基础示例及时间类型处理的实战代码,帮助读者掌握JSON操作。
187 1
Golang深入浅出之-Go语言JSON处理:编码与解码实战
|
6月前
|
Python
技术经验解读:【Python】torrentParser1.04增加获得磁力链URI功能
技术经验解读:【Python】torrentParser1.04增加获得磁力链URI功能
28 0
|
7月前
|
存储 JSON JavaScript
【YAML语法规范指南】从入门到精通,揭秘神秘语法,引领配置文件解析指南(基础结构篇)
"YAML Ain't Markup Language"(简称YAML)是一种专为人类设计的数据序列化语言,适用于多种现代编程语言,可广泛应用于各类日常任务。它是一种以人类可读形式呈现的、适用于多种语言的Unicode数据序列化标准。它基于敏捷编程中常见的本地数据结构,广泛应用于配置文件、互联网消息传递、对象持久化以及数据审计等多个领域。遵循Unicode标准、
705 8
【YAML语法规范指南】从入门到精通,揭秘神秘语法,引领配置文件解析指南(基础结构篇)
|
7月前
|
存储 缓存 Cloud Native
云原生系列Go语言篇-模块、包和导入 Part 2
我们已经学习了如何在单个模块中使用包,接下来该学习如何集成第三方模块及其中的包。然后,我们会学习如何发布自己模块并添加版本,以及Go的中央服务:pkg.go.dev、模块代理和校验和(checksum)数据库。
110 5
|
7月前
|
存储 Cloud Native Go
云原生系列Go语言篇-模块、包和导入 Part 1
大部分编程语言都有将代码组织到命名空间和库的系统,Go也不例外。在学习其它特性时我们看到了,Go对这些老思想引入了新方法。本章中,读者会学习到如何通过包和模块组织代码、如何导入、如何使用第三方库以及如何创建自有库。
92 0
下一篇
DataWorks