Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 339

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 392

Deprecated: parse_url(): Passing null to parameter #1 ($url) of type string is deprecated in /www/wwwroot/blog_qqvbc_com/usr/plugins/Access/Access_Core.php on line 394
Joyber 的博客

abstract 抽象类(PHP)

作用

  1. 约束子类必须实现指定方法:抽象方法只有声明、没有实现,子类继承后必须重写这个抽象方法,否则子类也得标记为 abstract,不能实例化。用来统一接口规范。
  2. 可以放通用共用代码:和接口 interface 不一样,抽象类里面可以写已经实现好的普通方法、属性,子类直接复用。
  3. 本身不能 new 实例,只能被继承,用来做基类、模板骨架(模板方法模式非常常用)。

对比一句话:

  • interface:全部都是契约,不能写实现,一个类可以多实现
  • abstract class:一半契约(abstract方法)+一半现成代码,只能单继承

写法规则(PHP)

  1. 类前面加 abstract
  2. 抽象方法:前面 abstract,没有函数体,以分号结尾,不能有 {}
  3. 普通方法、属性、常量正常写,随便写实现
  4. 子类 extends 抽象类,必须全部实现父类所有抽象方法
  5. 抽象类不能 new

最简示例

<?php
abstract class CarrierBase
{
    // 抽象方法:子类必须实现
    abstract public function sendOrder(array $orderData): bool;
    abstract public function cancelOrder(int $orderId): bool;

    // 已经实现的公共方法,子类直接继承复用
    protected function log(string $msg): void
    {
        echo date('Y-m-d H:i:s') . ' ' . $msg . PHP_EOL;
    }
}

// 子类实现抽象类
class SelfCourierCarrier extends CarrierBase
{
    public function sendOrder(array $orderData): bool
    {
        $this->log('自有骑手接单:' . $orderData['id']);
        return true;
    }

    public function cancelOrder(int $orderId): bool
    {
        $this->log('自有骑手取消订单:' . $orderId);
        return true;
    }
}

// $base = new CarrierBase(); // 报错!抽象类不能实例化
$carrier = new SelfCourierCarrier();
$carrier->sendOrder(['id' => 1001]);

结合你上面跑腿运力场景的例子(贴合你的 CarrierInterface)

你现在是 CarrierInterface 接口;如果很多运力都共用一套日志、组装请求的逻辑,就适合改成抽象基类:

abstract class AbstractCarrier implements CarrierInterface
{
    // 公共通用能力,所有运力复用
    protected function buildOrderParam(array $raw): array
    {
        return [
            'order_no' => $raw['order_sn'],
            'addr_from' => $raw['from_address'],
            'addr_to' => $raw['to_address'],
        ];
    }
}

class SelfCourierCarrier extends AbstractCarrier
{
    public function dispatch(array $order): bool
    {
        $param = $this->buildOrderParam($order);
        // 自有骑手分发逻辑
        return true;
    }
}

class ThirdPartyCarrier extends AbstractCarrier
{
    public function dispatch(array $order): bool
    {
        $param = $this->buildOrderParam($order);
        // 调用UU/闪送接口
        return true;
    }
}

重点坑点

  1. 抽象方法不能写body:abstract public func(); ✅,带大括号 ❌
  2. 子类实现的方法,访问权限不能比父抽象方法更严格(父public,子类不能改成protected/private)
  3. 抽象类可以没有任何抽象方法(可以,但意义不大,仅用来禁止直接new基类)
  4. 一个类只能继承一个抽象类(PHP单继承)

什么时候选 abstract,什么时候选 interface

  • 多个子类有大量重复共用代码 → abstract 抽象类
  • 只定义调用契约、没有公共逻辑、一个类需要多套契约 → interface

image.png

这是 Mermaid 语法,专门用来在 Markdown 里画流程图,很多平台支持:Typora、语雀、飞书文档、Gitlab、Github、Notion、Trae/VSCode 插件等

你这段是 graph TD,TD = Top Down,从上到下的流程图

基础语法速记

graph TD
    起点 --> 终点       // 箭头连线
    节点ID["显示文字"]  // 方框节点,最常用
    节点ID(圆角文字)    // 圆角框
    节点ID{判断}        // 菱形判断框
graph TD:从上往下
graph LR:从左往右(LR=Left Right,后端架构图最常用)

拆解你这段代码

graph TD
    A["跑腿员端 API (api/errand)"] --> S1["ErrandCourierService"]
    A --> S2["ErrandOrderService"]
    B["管理平台 API (admin/errand)"] --> S1
    B --> S3["ErrandCarrierService"]
    C["用户端下单 (api ErrandController)"] --> S2
    S3 --> M["CarrierManager (运力解析)"]
    M --> I["CarrierInterface"]
    I --> R1["SelfCourierCarrier (自有骑手)"]
    I --> R2["ThirdPartyCarrier (UU/闪送 预留)"]
    S2 --> L["ErrandOrderStatusLog / ErrandTimeNode / ErrandWorkRecord"]
  • A、S1、S2:是节点唯一ID,随便起名,只用来内部连线,不展示
  • ["xxx"]:方括号,矩形框,显示里面的文字
  • -->:单向箭头,表示依赖/调用关系(A 调用 S1)

快速上手规则(记住4条就够用)

  1. 开头固定:graph TD(竖向)或者 graph LR(横向,推荐画架构)
  2. 每个节点格式:ID["展示文本"],ID不能重复
  3. 连线:ID1 --> ID2
  4. 一行一条连线,换行不影响

改成横向架构图版本(后端结构图一般用LR更好看)

graph LR
    A["跑腿员端 API (api/errand)"] --> S1["ErrandCourierService"]
    A --> S2["ErrandOrderService"]
    B["管理平台 API (admin/errand)"] --> S1
    B --> S3["ErrandCarrierService"]
    C["用户端下单 (api ErrandController)"] --> S2
    S3 --> M["CarrierManager (运力解析)"]
    M --> I["CarrierInterface"]
    I --> R1["SelfCourierCarrier (自有骑手)"]
    I --> R2["ThirdPartyCarrier (UU/闪送 预留)"]
    S2 --> L["ErrandOrderStatusLog / ErrandTimeNode / ErrandWorkRecord"]

怎么运行预览

VSCode 装插件:Markdown Preview Mermaid Support,打开md直接看;
飞书/语雀文档,直接粘贴这段代码,选择Mermaid代码块即可渲染。

扩展小补充(你画架构常用)

  • 虚线箭头:-.->
  • 标注文字箭头:A -- 调用 --> B

    graph LR
    A["服务A"] -- 同步调用 --> B["服务B"]
    B -.-> C["异步回调"]

Hyperf 全功能学习实践清单(逐条落地,可直接照着做)

前置准备(先完成,所有练习共用一套环境)

  1. 环境:PHP8.1+、Swoole 5.x(开启协程、http2、openssl);可选 Swow 无扩展模式
  2. 初始化项目:composer create-project hyperf/hyperf-skeleton hyperf-demo
  3. 统一中间件:Docker 安装 Nacos / Consul(服务注册)、MySQL、Redis、RabbitMQ、Jaeger(链路追踪)
  4. 学习原则:单个功能独立跑通 → 多个功能组合成微服务集群 → 整合为小游戏/长连接项目

一、RPC 通信:JSON-RPC + gRPC(微服务核心)

1)JSON-RPC 服务端+客户端

学习目标:实现服务提供方注册、消费方远程调用,基于注解开发

  1. 安装组件

    composer require hyperf/json-rpc hyperf/rpc-server hyperf/rpc-client
  2. 实践步骤
  3. 新建接口 UserServiceInterface,定义方法
  4. 服务提供者:用 @RpcService 注解发布服务,开启注册中心配置
  5. 服务消费者:配置 consumers,依赖注入接口直接调用
  6. 测试:启动两个服务,客户端调用服务端接口,拿到返回值
  7. 进阶:增加RPC参数校验、异常捕获、超时设置

2)gRPC 服务(http2二进制协议)

学习目标:基于proto文件生成代码,搭建gRPC服务端与客户端

  1. 安装protobuf扩展+hyperf gRPC组件
  2. 实践步骤
  3. 编写 .proto 协议文件(定义服务、入参、出参)
  4. 命令行自动生成PHP代码
  5. 配置server.php开启grpc服务端口,实现接口业务逻辑
  6. 客户端调用,完成跨服务二进制通信
  7. 验收:curl/grpc客户端成功调用,查看http2协议日志

二、服务注册与发现(Nacos / Consul)

学习目标:服务自动注册、自动发现,不用硬写IP端口

  1. 组件安装:composer require hyperf/service-register
  2. 二选一练习(优先Nacos,国内主流)

    方案A:Nacos(阿里云,小游戏项目首选)

  3. Docker启动Nacos单机版
  4. RPC服务端增加注解 publishTo="nacos",自动把服务注册到Nacos
  5. 客户端配置注册中心地址,自动从Nacos拉取服务节点
  6. 测试:关闭其中一个服务实例,客户端自动切换到其他节点

方案B:Consul

  1. 启动Consul服务
  2. 修改配置,将RPC服务注册到Consul
  3. 验证:在Consul管理面板看到在线服务实例

验收要点

  • 服务启动自动注册,停止自动下线
  • 客户端无需写死IP,动态获取服务地址

三、负载均衡(内置LB组件)

学习目标:多实例服务自动轮询/随机调用,实现流量分发

  1. 组件:hyperf/load-balancer(框架内置,无需额外安装)
  2. 实践步骤
    1)启动同一个RPC服务,开启3个不同端口实例(多节点集群)
    2)客户端配置负载均衡策略:
  3. 随机(random)
  4. 轮询(round-robin,默认)
  5. 权重轮询(weighted-round-robin)
    3)循环调用接口,打印当前调用到的节点,验证负载分发效果
    4)进阶:节点故障剔除(健康检测)

四、熔断降级(服务治理,防止雪崩)

学习目标:服务调用失败时自动熔断,保护下游服务,避免连环崩溃

  1. 组件:composer require hyperf/circuit-breaker
  2. 实践步骤
    1)给RPC远程调用增加熔断注解 @CircuitBreaker
    2)配置熔断规则:失败率阈值、熔断窗口期、半开重试次数
    3)制造异常:让下游服务持续报错,观察熔断器状态变化(关闭→打开→半开)
    4)配置降级策略:熔断触发后直接返回兜底数据,不继续请求下游
    5)拓展:增加限流(令牌桶/漏桶)+熔断组合使用

五、分布式追踪(链路追踪 Tracing)

学习目标:全链路追踪请求,查看整条调用链耗时、调用节点,排查慢接口

  1. 组件:composer require hyperf/tracer jaeger-client
  2. 实践步骤
    1)Docker启动Jaeger链路追踪服务
    2)配置tracer.php,开启RPC、HTTP、数据库调用自动埋点
    3)发起一次完整调用:HTTP接口 → JSON-RPC服务 → MySQL查询 → Redis缓存
    4)打开Jaeger后台,查看整条链路:每个环节耗时、调用顺序、异常日志
    5)进阶:把TraceId写入日志,实现日志与链路一一对应

六、协程连接池(MySQL / Redis / RabbitMQ,协程必练核心)

协程常驻内存,必须使用连接池,否则会爆连接、内存泄漏

1)MySQL 协程连接池

  1. 组件:hyperf/database(框架自带)
  2. 练习内容
  3. 配置pool参数:最大连接数、最小空闲连接、连接超时、心跳检测
  4. 开启协程并发,同时发起200条SQL查询,观察连接池复用连接,不新建大量连接
  5. 测试:连接耗尽时自动排队等待,避免报错
  6. 使用协程版Eloquent ORM完成增删改查

2)Redis 协程连接池

  1. 组件:hyperf/redis
  2. 练习内容
  3. 配置Redis连接池,设置最大连接、空闲超时
  4. 并发协程批量set/get,验证连接复用
  5. 练习:分布式锁、缓存击穿、缓存降级

3)RabbitMQ 协程连接池(消息队列)

  1. 组件:composer require hyperf/amqp
  2. 练习内容
  3. 配置AMQP连接池,避免频繁创建销毁连接
  4. 生产者:协程批量投递消息
  5. 消费者:多协程消费消息,自动复用连接
  6. 实现消息重试、死信队列

验收标准

并发请求下,连接数量稳定在配置区间,不会无限暴涨。


七、秒级定时任务 Crontab(毫秒级定时,优于Linux crontab)

  1. 组件:hyperf/crontab(内置)
  2. 实践步骤
    1)创建定时任务类,使用注解 @Crontab
    2)练习两种定时规则:
  3. 标准cron表达式(分/时/日/月/周)
  4. 秒级定时:每5秒执行一次、每100毫秒执行一次
    3)开启多进程执行定时任务,避免任务阻塞互相影响
    4)进阶:定时任务分布式锁(Redis锁),防止多实例重复执行任务

八、自定义进程(独立进程处理任务,不阻塞HTTP/RPC服务)

  1. 学习目标:创建独立Worker进程,处理异步任务、消息消费、后台定时业务
  2. 实践步骤
    1)创建自定义进程类,继承 AbstractProcess
    2)配置process.php,设置进程数量、进程名称
    3)两种场景练习:
  3. 场景1:独立进程消费RabbitMQ消息,不占用业务Worker
  4. 场景2:创建心跳进程,定时上报服务健康状态到注册中心
    4)测试:主服务重启不影响常驻后台进程;进程异常自动重启

九、WebSocket 长连接(小游戏、IM聊天、实时推送必备)

  1. 组件:composer require hyperf/websocket-server
  2. 实践步骤
    1)配置server.php,开启WebSocket服务端口
    2)实现握手、消息接收、消息推送、客户端下线事件
    3)练习功能:
  3. 一对一私聊(用户ID绑定连接fd)
  4. 全员广播(小游戏全服推送公告)
  5. 房间分组(游戏房间内消息互通)
  6. 断线重连、心跳保活,防止连接被系统断开
    4)客户端:用JS WebSocket连接服务端,收发消息
    5)进阶:结合Redis发布订阅,实现多服务实例之间消息互通(分布式WebSocket)

十、配套高级能力(DI容器 + AOP + 协程上下文Context,Hyperf核心优势)

1)DI依赖注入 + 注解注入

  1. 练习构造器注入、属性注入、接口绑定实现类
  2. 把所有服务类全部交给容器管理,不手动new对象

2)AOP面向切面(拦截方法,统一做日志、鉴权、埋点)

  1. 创建切面类,拦截控制器、RPC方法
  2. 实现统一日志记录、接口鉴权、参数校验、异常捕获
  3. 练习前置通知、后置通知、异常通知

3)协程上下文 Context(协程最容易踩坑,必练)

  1. 练习用 Context::set() / Context::get() 存储当前请求唯一数据(trace_id、用户ID)
  2. 对比:全局变量在协程并发下会互相污染,Context可以完美隔离不同协程请求数据
  3. 子协程上下文传递,避免子协程拿不到父协程数据

十一、项目整合实战(把上面所有组件拼成3个完整项目)

项目1:微服务RPC集群(用户服务+订单服务+商品服务)

用到:JSON-RPC + Nacos注册发现 + 负载均衡 + 熔断降级 + 链路追踪 + MySQL/Redis连接池

项目2:小游戏后端服务(推荐首选)

用到:WebSocket长连接 + 秒级定时任务 + 自定义进程消费队列 + 分布式锁 + 消息队列RabbitMQ

项目3:API网关项目

用到:HTTP服务 + 路由转发 + RPC调用下游微服务 + 全局AOP鉴权 + 限流熔断


十二、打包部署(Hyperf Box二进制打包)

  1. 练习:使用Hyperf Box把整个项目打包成单文件phar二进制程序
  2. 实现:服务器不需要安装composer、不需要维护vendor,直接一行命令启动服务
  3. 编写Dockerfile,容器化部署整套微服务集群

学习进度规划(按天执行)

第1天:环境搭建 + DI/AOP + Context协程上下文 + 连接池(MySQL/Redis)
第2天:JSON-RPC + gRPC + Nacos服务注册发现 + 负载均衡
第3天:熔断降级 + 分布式链路追踪Jaeger
第4天:RabbitMQ消息队列 + 自定义进程 + 秒级定时任务Crontab
第5天:WebSocket长连接 + 分布式IM消息推送
第6~7天:整合为小游戏后端 + 微服务集群 + 打包容器部署

一、模拟面试类(优先看,完整还原面试场景,适合你转行自测)

  1. 智兰博PLC培训《电气工程师模拟面试(1)》
    亮点:完整真人面试对话,面试官连续提问1200/1500区别、V90伺服、Modbus、出差问题,就是非标公司真实面试流程,能感受面试官提问节奏、怎么组织话术回答。
  2. 辉煌代英丰《PLC面试这些问题全掌握就OK》
    亮点:高频简答合集,扫描周期、输出点无信号排查、编程语言、出差薪资问答,适合碎片背诵。
  3. 玩工控的Jason《自动化十问十答:PLC面试篇》
    亮点:重点讲【通讯故障排查思路】,强调不要死背答案,学会分步定位故障,面试最加分。

二、博途核心考点专项(FB/FC/DB、程序结构,面试必考题)

  1. 李老师讲重点《博图 OB/FC/FB/DB,新手90%卡在这一步》
    亮点:通俗比喻讲清FC无记忆、FB带背景DB,优化DB/非优化DB区别,这个是西门子面试最高频考点。
  2. 李老师讲重点《通过这4道题 彻底搞懂 数据类型和数据结构》
    亮点:DB块地址、高低字节、Real浮点数寻址面试计算题,很多面试官喜欢现场提问。
  3. 阿伟码控《一个视频讲透FB和FC的区别和使用方法》
    亮点:用业务需求演变来讲,适合理解什么时候封装FB,结构化编程面试必问。

三、通讯&现场调试类(区分“只会仿真”和能上岗的工程师,权重最高)

  1. 小灰灰玩自动化《现场PLC通讯不上怎么办?》
    亮点:Modbus RTU排查流程:A/B线、站点冲突、波特率、终端电阻,面试标准排查话术。
  2. 微电赵老师PLC《PLC以前正常突然通信失败的原因》
    亮点:讲容易忽略的坑:24V压降、接头氧化、干扰,对应面试“偶发停机、随机掉线”的回答思路。
  3. 旭科拓-电控李工1《电工不会调试?看完这8步直接出师!》
    亮点:上电前检查、IO单点测试、强制测试、调试完整流程,面试官很爱问:你拿到一台新设备怎么开始调试?
  4. 郑鼎PLC侯俊霞《手把手教你PLC通信项目案例》
    亮点:博途1200 S7通讯、Modbus TCP实操演示,PN通讯、跨PLC数据交互。

四、B站长系列(系统性,适合长线复习,搜索标题即可)

B站搜索标题:
  1. 《西门子S7-1200/1500博途全套教程》(工控老马):运动控制、V90伺服、Profinet、工艺对象,面试运动控制题全覆盖
  2. 《PLC电气工程师面试合集》(PLC老杨):专门整理面试专题,含安全回路、急停、干扰、Eplan相关问题
  3. 《TIA博途结构化编程实战》:FB封装、标准化程序框架,回答“你怎么写规范的项目程序”

五、观看顺序建议(贴合你的面试题库)

  1. 先看【模拟面试视频】,熟悉提问节奏,先知道面试官喜欢怎么问
  2. 再刷 OB/FB/FC/DB、扫描周期、数据类型 基础考点
  3. 重点攻坚:Profinet、Modbus、现场故障排查、上电调试(这是新人拉开差距的地方)
  4. 最后补伺服运动控制、安全回路、HMI、OPC UA拔高题

避坑提醒

很多短视频只背标准答案,面试的时候面试官会顺着答案继续深挖,优先看带现场故障思路、实操演示的视频,不要只背文字。