首页
直播
壁纸
友链
搜索
1
微信小程序支付全链路实战:JSAPI 下单、调起支付、回调验签与退款
266 阅读
2
微信小程序云开发实战:云函数、云数据库与云存储的正确使用姿势
256 阅读
3
微信小程序自定义 tabBar 实战:custom-tab-bar 从适配到深色模式
255 阅读
4
微信小程序 Skyline 渲染引擎实战:worklet 动画从原理到落地
253 阅读
5
微信小程序分包进阶:独立分包、预下载与分包异步化实战
246 阅读
服务器运维
后端技术
前端技术
梯子
数据库
小程序
登录
搜索
标签搜索
fastadmin
Redis
微信小程序
前端开发
RabbitMQ
Go
服务器
codex
buildadmin
小程序
mysql
Nginx
Docker
Vue3
Node.js
MySQL优化
Linux
TypeScript
JWT
PHP
沿途的风景
累计撰写
74
篇文章
累计收到
0
条评论
首页
栏目
服务器运维
后端技术
前端技术
梯子
数据库
小程序
页面
直播
壁纸
友链
搜索到
3
篇与
» PHP
的结果
2026-08-06
webman 中间件与依赖注入实战:洋葱模型、PHP-DI 容器与 AOP
webman 中间件与依赖注入实战:洋葱模型、PHP-DI 容器与 AOPwebman 的请求处理链路看似简单(路由 → 中间件 → 控制器),但中间的洋葱模型和 PHP-DI 容器藏着不少中级开发者没吃透的细节。这篇文章把中间件的执行顺序、全局/路由级/方法级三种挂载方式、依赖注入的边界一次性讲清楚,最后再聊一个常驻内存框架特有的话题——AOP。一、洋葱模型:代码顺序与执行顺序是两回事class AuthMiddleware implements MiddlewareInterface { public function process(Request $request, callable $handler): Response { // ── 前置逻辑(进入时执行)── $userId = $this->jwt->parseToken($request); $request->userId = $userId; $response = $handler($request); // ← 下一层洋葱 // ── 后置逻辑(返回时执行)── $response->withHeader('X-Trace-Id', $request->header('x-trace-id')); return $response; } }三层中间件的执行顺序:请求 → [A前置] → [B前置] → [C前置] → Controller 响应 ← [A后置] ← [B后置] ← [C后置] ←最容易踩的坑:异常发生时的执行路径。 如果控制器抛异常,中间件的后置代码默认不会执行——除非你在中间件里 try/catch 包住 $handler($request)。跨域中间件经常因此"时灵时不灵":异常响应没带 CORS 头,浏览器报的不是后端错误而是跨域错误。所以 CORS 中间件要这样写:try { $response = $handler($request); } catch (\Throwable $e) { $response = response($e->getMessage(), 500); } return $response->withHeaders([ 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Headers' => 'Content-Type, Authorization', ]);二、三种挂载粒度1. 全局中间件(config/middleware.php)return [ // 每个请求都会经过 app\middleware\AccessLog::class, app\middleware\Cors::class, ];适合:日志、CORS、限流。注意全局中间件每个请求都会跑,别放重逻辑。2. 路由/应用级中间件// config/route.php Route::group('/admin', function () { Route::resource('/users', app\admin\controller\UserController::class); })->middleware([ app\middleware\AdminAuth::class, ]); // 或整个应用目录生效:app/admin/config/middleware.php return [ app\middleware\AdminAuth::class, ];多应用(多入口目录)模式下,app/{应用名}/config/middleware.php 只对该应用生效——这是做多租户后台、API 版本隔离的天然工具。3. 控制器方法级(中间件别名)// config/middleware.php 里注册别名 return [ 'auth' => [app\middleware\Auth::class], ]; // 控制器里按方法指定 class OrderController { #[Middleware(['auth'])] // 整个控制器生效 public function index() {} #[Middleware(['auth', 'throttle:60,1'])] public function create() {} }执行优先级:全局 → 应用级 → 路由级 → 方法级。同层内按注册顺序。三、实战:三个高频中间件的正确写法限流中间件(基于 Redis 滑动窗口)class Throttle implements MiddlewareInterface { public function __construct(private Redis $redis) {} public function process(Request $request, callable $handler): Response { $key = 'throttle:' . $request->getRealIp() . ':' . date('YmdHi'); $current = $this->redis->incr($key); if ($current === 1) { $this->redis->expire($key, 60); } if ($current > 120) { return json(['code' => 429, 'msg' => 'too many requests'], 429); } return $handler($request); } }注意 incr 后再 expire 的写法:只在第一次设置过期,避免每请求都重置 TTL。请求日志(含慢请求标记)class AccessLog implements MiddlewareInterface { public function process(Request $request, callable $handler): Response { $start = microtime(true); $response = $handler($request); $cost = round((micro(true) - $start) * 1000, 2); Log::info('access', [ 'ip' => $request->getRealIp(), 'path' => $request->path(), 'cost' => $cost, 'slow' => $cost > 500 ? '⚡' : '', ]); return $response; } }JWT 认证 + 用户上下文注入class Auth implements MiddlewareInterface { public function process(Request $request, callable $handler): Response { $token = $request->header('authorization', ''); try { $payload = JWT::decode(trim(str_replace('Bearer ', '', $token)), new Key(config('jwt.secret'), 'HS256')); } catch (\Throwable $e) { return json(['code' => 401, 'msg' => 'unauthorized'], 401); } // 挂到 request 上传递(请求级隔离,不用静态属性) $request->uid = $payload->uid; return $handler($request); } }控制器里 $request->uid 直接取——请求结束自动销毁,没有跨请求污染。四、依赖注入:webman 的 PHP-DI 容器webman 内置 PHP-DI。最常见的三个用法:1. 构造器注入(推荐)class OrderController { public function __construct( private OrderService $orderService, // 自动解析 private LoggerInterface $logger, ) {} }常驻内存的特性让注入几乎零成本:控制器和依赖都在进程启动阶段实例化一次,之后每个请求直接复用。这也意味着——注入的依赖必须是"无状态"或"请求安全"的。Service 里存了请求相关的属性就会串数据。2. 接口绑定// config/container.php return [ PayInterface::class => \DI\create(WechatPay::class), ]; // 之后注入 PayInterface 拿到的就是 WechatPay切支付渠道只改这一行。多环境(沙箱/生产)也可以在这里做条件绑定。3. 自动装配的边界PHP-DI 按类型提示递归解析依赖,但碰到标量参数就无能为力,需要显式定义:// config/container.php return [ \DI\create(SmsClient::class) ->constructor(\DI\value(config('sms.appkey'))), ];五、AOP:常驻内存框架的专属福利AOP(面向切面编程)在 FPM 下性能代价大,但 webman 的进程常驻让织入成本只付一次。webman 通过 php-di/AOP 代理实现,最常见的场景是缓存注解:#[Cacheable(prefix: 'user:', ttl: 300)] public function getUserProfile(int $uid): array { return $this->userDao->findWithRelations($uid); }切面拦截方法调用:先查 Redis,命中直接返回;未命中执行原方法并写缓存。类似的还有 #[Transactional](自动事务包裹)、#[Retry](失败重试)。两个注意点:AOP 只对容器管理的对象生效——new OrderService() 出来的裸对象没有代理拦截逻辑发生在方法级,粒度比中间件(请求级)细,适合业务横切(缓存/事务/重试),不适合请求横切(认证/日志)六、一张图理清请求全链路请求 → 全局中间件(日志/CORS/限流) → 应用/路由中间件(认证) → 方法级中间件(权限) → 中间件层容器解析控制器(构造器注入,进程级单例) → 方法内 AOP 切面(缓存/事务) → Service(业务逻辑,保持无状态) → Model/Dao每一层解决不同粒度的横切问题:请求级用中间件,业务级用 AOP,对象组装用容器——三者各司其职,别把认证写进 Service,也别把缓存注解挂在中间件上。写在最后中间件和依赖注入是 webman 工程化的骨架。写中间件时时刻记住两件事:异常会跳过后置逻辑、常驻内存下禁止请求级状态进静态属性;用容器时记住注入对象是进程级复用的,Service 必须无状态。下一篇讲数据库层——连接池怎么配、事务怎么包、大数据怎么跑,那是常驻内存框架最容易翻车的第二现场。
2026年08月06日
6 阅读
0 评论
0 点赞
2026-08-04
webman 架构解析:常驻内存进程模型与 PHP-FPM 的本质差异
webman 架构解析:常驻内存进程模型与 PHP-FPM 的本质差异webman 是基于 Workerman 构建的高性能 PHP 框架,官方基准 QPS 常态化压到十几万。但很多人把 webman 当"换个写法的 Laravel"来用,结果内存泄漏、全局状态污染这些经典问题轮番上演。要真正用好 webman,必须先理解它与 PHP-FPM 在架构上的本质差异——这是所有后续实践的地基。一、两种执行模型:请求级生命周期 vs 常驻内存PHP-FPM:每次请求都是新生浏览器 → Nginx → PHP-FPM(worker) → 加载框架 → 初始化 → 处理请求 → 返回 → 销毁一切FPM 模式下,每个请求结束都会释放全部内存、销毁所有变量。代价是每次请求都要重新加载框架(几十毫秒的 bootstrap),但好处是状态绝对隔离——你再怎么写全局变量,下一个请求都是白纸。webman:一次加载,永久服务浏览器 → webman(worker 进程) → 直接路由分发 → 处理请求 → 返回(框架和对象留在内存)webman 启动时把框架、路由表、配置全部加载进内存,之后每个请求只是"触发一次回调"。QPS 高的根源就在这:省掉了每请求一次的框架 bootstrap 开销。这个差异带来两个直接推论:性能提升的本质是"摊销":初始化成本只付一次,请求越多收益越大内存不自动释放:请求之间共享进程内存,全局状态会"存活"——这是福也是祸二、进程模型:master / worker 的分工master 进程(不处理业务) ├── 监听端口、管理 worker ├── fork 出 N 个 worker 进程 └── 收到 reload 信号时逐个平滑重启 worker │ ├── worker-1 ← 每个进程独立事件循环,可同时 hold 住数千连接 ├── worker-2 ├── ... └── worker-N(默认 = CPU 核数)三个关键认知:1. worker 数不是越多越好。 webman 是多进程 + 每进程事件循环(非阻塞 IO)模型,worker 数建议等于 CPU 核数。如果你的业务有阻塞调用(同步 HTTP 客户端、同步 RPC),连接会"卡住"事件循环,这时才需要加进程数来对冲——但更好的解法是消灭阻塞调用(后面协程篇细讲)。2. 进程间不共享内存。 你在 worker-1 里写的静态变量,worker-2 看不见。跨进程共享状态要用 Redis 或进程间通信,别指望 static 属性全站生效。3. request 对象请求间隔离,但对象池里的组件不是。 框架层面的组件(DB 连接、日志实例)跨请求复用,这是性能来源;你自己的类如果注册成了单例,它同样跨请求存活。三、启动流程:从 public/index.php 说起// public/index.php(简化) require_once __DIR__ . '/../vendor/autoload.php'; support\App::run();App::run() 内部做了什么:1. 加载 .env 与 config/*.php(此后修改配置需重启) 2. 扫描 app/controller 生成路由表(注解路由另算) 3. 初始化容器(webman 基于 PHP-DI,支持构造器注入) 4. 加载 bootstrap/*.php(进程启动时执行一次的钩子) 5. 初始化数据库/Redis 连接(懒加载,首次使用时建立) 6. master fork workers → 每个 worker 进入事件循环等待请求bootstrap 目录是理解 webman 的钥匙:放在这里的代码每个进程只执行一次。数据库连接的建立、连接池的初始化、Swoole 协程运行时的开启,都在这个阶段。官方的 webman/database、webman/redis 等插件都是通过 bootstrap 文件挂载的。四、常驻内存的四宗罪与解法罪一:静态属性 / 全局变量跨请求污染// ❌ 经典事故:上一单用户的数据泄漏给下一单 class OrderService { public static ?array $currentUser = null; } // 请求A设置了 currentUser,请求B进来时它还活着!解法:请求级数据一律走 Request 对象传递,或用 $request->xxx 属性挂载。静态属性只允许存"进程级不变量"(配置、编译好的正则、容器实例)。罪二:内存泄漏每请求增长的数组、未释放的大结果集、监听器越挂越多……FPM 下这些泄漏随请求结束被清零,webman 下会累积到 memory_limit 进程崩溃。排查手段:// 在中间件里记录内存水位 public function process(Request $request, callable $handler): Response { $before = memory_get_usage(); $response = $handler($request); $this->logger->info('mem delta', [ 'delta' => memory_get_usage() - $before, 'peak' => memory_get_peak_usage(), ]); return $response; }内存水位持续爬升就是泄漏信号。常见元凶:往静态数组里 push 不清理、查询返回百万行、日志 handler 无限累积。罪三:数据库连接断开(MySQL gone away)worker 空闲超过 wait_timeout(默认 8 小时)后,MySQL 单方面断开,而 webman 还握着死连接。解法:配置连接心跳 + 断线重连。用 ORM 插件时开启:// think-orm 配置 'break_reconnect' => true,更彻底的方案是连接池(数据库篇细讲),池化组件自带健康检查。罪四:代码改了不生效FPM 的肌肉记忆是"保存即生效",webman 下路由、配置、类定义都在内存里,必须 restart 或 reload。开发环境用 php start.php restart 太重,webman 有监视文件变自动 reload 的方案(workerman/webman-framework 配合 monitor 进程,dev 模式自动开启)——但注意生产环境要关掉。五、平滑重启的原理与边界php start.php reload # 平滑重启:逐个重启 worker php start.php restart # 硬重启:master 也重启 php start.php stop # 停止 php start.php status # 查看进程状态reload 的机制:master 收到信号后,先通知一个 worker 停止接收新请求,等它处理完当前请求再杀掉重建,依次轮转。这意味着:reload 只更新"启动时加载"的代码:controller 文件是每次请求 include 的吗?——webman 中 controller 默认支持 reload 更新(优化级别 support/App 的 reloadable 路径),但 config/、bootstrap/、路由定义的改动必须 restartreload 期间正在执行的长任务会被等待,但超过 $maxWaitTime(可配)会被强杀,注意保护长任务六、与 FPM 共存:渐进式迁移策略存量 Laravel/TP 项目不必推倒重来。实战迁移路径:新接口用 webman 写,旧接口留在 FPM,Nginx 按路径分流复用原有 Model 层:webman 支持 think-orm 和 Laravel Eloquent(laravel/database 插件),业务模型代码几乎平移逐步搬迁高频接口:把 QPS 最高、逻辑最薄的接口先迁过去,验证稳定性后再迁复杂业务分流配置示例:location /api/v2/ { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://127.0.0.1:8787; # webman } location /api/ { try_files $uri /index.php?$query_string; # 旧 FPM 应用 }写在最后webman 不是"更快的 Laravel",而是一种不同的运行时模型。把进程模型、启动流程、常驻内存的边界刻进脑子,后面的中间件、连接池、协程实践才有意义。下一篇我们进入请求处理的核心链路:中间件洋葱模型与依赖注入容器的实战。
2026年08月04日
4 阅读
0 评论
0 点赞
2026-08-01
AB-Admin:给 Typecho 后台换上 Material Design 3 新装
前言Typecho 的默认后台界面,怎么说呢——功能齐全,但长相确实停留在上个时代的审美。白底蓝链接、紧凑的表格、没有圆角没有过渡动画,每次写文章都像在填 Excel 表格。如果你也有同感,那 AB-Admin(Admin Beautify) 可能正是你在找的东西。这是一款基于 Material Design 3 设计体系的 Typecho 后台美化增强插件,号称"可能是史上颜值最高的 Typecho 后台美化插件"。本文结合我博客上实际安装使用的 v2.1.43 版本,从功能介绍、安装配置到实际体验,完整聊聊这个插件。插件概览项目信息插件名AB-Admin(原名 Admin Beautify)作者LHL当前版本v2.1.43设计体系Material Design 3(MD3)Typecho 版本基于 1.3.0 开发,兼容 1.2.1GitHubgithub.com/lhl77/Typecho-Plugin-AdminBeautify配套插件AB Store(AdminBeautifyStore)插件仓库核心功能1. MD3 主题色系统AB-Admin 不是简单换个 CSS 皮,而是注入了一套完整的 Material Design 3 设计 Token 变量。打开插件代码可以看到,它定义了几十组 CSS 自定义属性::root { --md-primary: #6750A4; --md-on-primary: #FFFFFF; --md-surface: #FFFBFF; --md-on-surface: #1C1B1F; --md-outline: #79747E; /* ... 几十个变量 */ }暗色模式也有一套对应变量(--md-dark-primary、--md-dark-surface 等),切换时自动生效。这意味着插件不仅仅改了配色,而是建立了一套可被其他插件引用的设计系统。插件内置了多种预设配色方案(紫色、红色、蓝色等),也可以自定义主题色。颜色值会根据亮/暗模式自动生成对应的明暗变体。2. 亮暗模式切换通过 <html> 标签的 data-theme="light" 或 data-theme="dark" 属性手动切换,不依赖系统偏好。这比 @media (prefers-color-scheme: dark) 更灵活——你可以白天用亮色、晚上切暗色,不管系统设置是什么。对于插件开发者来说,暗色样式建议用 [data-theme="dark"] 选择器覆盖,而不是依赖媒体查询:/* 正确写法 */ .my-card { background: var(--md-surface-container-low, #f7f2fa); } [data-theme="dark"] .my-card { background: var(--md-surface-container-low, #1d1b20); }3. 响应式设计内置移动端适配,在手机上自动切换为顶部折叠菜单模式。后台管理不再只能坐在电脑前操作——手机上也能舒舒服服地审阅评论、管理文章。4. 自定义登录页背景支持设置登录页背景图片 URL,并可调整虚化方式和虚化大小。告别千篇一律的默认登录页,给博客一个有辨识度的后台入口。5. 双布局模式支持侧边栏布局和顶部导航布局两种后台布局,根据个人习惯自由切换。侧边栏模式更传统,顶部导航模式则更接近现代 Web 应用的风格。6. AJAX 页面导航后台页面切换采用 AJAX 方式,不完整重载页面。每次导航后会派发 ab:pageload 事件,让兼容脚本知道页面变了、该重新执行修复了。// 监听 AB-Admin 的 AJAX 导航事件 document.addEventListener('ab:pageload', function(e) { var url = e.detail.url; console.log('页面已切换到:', url); // 在这里重新绑定 DOM 事件、注入样式等 });这个设计直接影响了兼容脚本的工作方式——稍后详细说。开发者 APIAB-Admin 不只是"好看",它还提供了一套面向插件开发者的 API。横幅通知:showNotice()if (typeof AdminBeautify !== 'undefined') { AdminBeautify.showNotice('操作成功!', { type: 'success' }); }调用前记得判断 AdminBeautify 是否存在,避免插件未启用时报错。对话框 API:alert / confirm / promptAB-Admin 覆写了原生的 alert、confirm、prompt,将它们替换为 MD3 风格的异步弹窗。同时也提供了 Promise 化的 API:// 异步确认框 const result = await AdminBeautify.confirm('确定删除这篇文章?'); if (result) { // 用户点了确认 } // 异步输入框 const name = await AdminBeautify.prompt('请输入分类名称:');踩坑提醒:覆写后,原生 confirm() 和 prompt() 不再阻塞——它们会立即返回 false / null。如果你的老代码依赖同步返回值,逻辑会被打断,需要改用 Promise API。兼容性脚本系统这是 AB-Admin 最有特色的设计之一。插件目录下有个 assets/compat/ 文件夹,专门存放用于修复其他插件排版问题的 JS 脚本:assets/compat/ ├── FuckAdComment.js # 反广告评论增强 ├── Links.js # Links Plus 友链插件兼容 ├── Mirages.js # Mirages 主题兼容 ├── Notice.js # Notice 通知插件兼容 ├── TelegramNotice.js # Telegram 推送插件兼容 ├── TeStore.js # TeStore 插件仓库兼容 ├── Typecho121.js # Typecho 1.2.1 版本兼容 └── README.md # 开发文档工作原理AB-Admin 自动扫描 compat/ 目录下所有 .js 文件每个脚本的元数据(名称、简介、适用插件)展示在设置页面用户可单独启用/禁用每个脚本支持一键从 GitHub 同步最新兼容脚本支持添加外部 JS 链接加载额外兼容脚本脚本开发规范每个兼容脚本需要在头部注释中声明元数据:/** * @name MyPlugin 兼容 * @description 修复 MyPlugin 在 AB-Admin 下的排版问题 * @plugins MyPlugin * @version 1.0.0 * @author YourName */关键注意事项:必须判断页面:通过 URL 或 DOM 确认是否为目标页面,不要影响其他页面监听 ab:pageload:由于 AJAX 导航,脚本只在首次加载执行一次,不监听此事件则跳转后修复不生效保持幂等:修复函数可能被多次调用,避免重复注入;离开目标页面时需清理已注入的样式兼容暗色模式:同时处理 [data-theme="dark"] 下的显示效果AB Store:配套插件仓库AB-Admin 还有一个配套插件 AdminBeautifyStore(AB Store),它是一个 Typecho 插件仓库,可以在后台直接搜索、安装、管理其他插件,不需要再手动下载上传。AB Store 的主要功能:在后台浏览插件市场一键安装/更新插件支持开发者投稿与 AB-Admin 深度集成,安装的插件自动适配兼容脚本两个插件配合使用,Typecho 的插件管理体验直接拉满,从"手动 FTP 时代"进入了"应用商店时代"。安装方法方法一:手动安装前往 GitHub Releases 下载最新版本解压后将 AdminBeautify 文件夹上传至 /usr/plugins/进入后台 → 控制台 → 插件 → 启用 AdminBeautify清除浏览器缓存(重要!)方法二:通过 AB Store 安装如果你已经装了 AB Store,可以直接在插件市场搜索安装。安装后配置启用插件后进入设置页面,可以配置:主题色:选择预设方案或自定义颜色布局模式:侧边栏 / 顶部导航登录页背景:填入图片 URL,调整虚化参数兼容脚本:逐个启用/禁用字体:可选加载 Noto Sans SC外部 JS:添加额外兼容脚本链接实际体验我博客上实际安装的就是 v2.1.43 版本。几个直观感受:界面:MD3 风格确实好看很多,圆角卡片、层次分明的阴影、流畅的过渡动画,写文章时心情都好了。暗色模式在夜间使用非常舒适,不是简单的黑白反转,而是有一套完整的暗色色板。性能:主 JS 文件约 200KB,CSS 约 197KB,体积不算小但可以接受。AJAX 导航让后台切换页面几乎无感,比原生整页刷新快不少。兼容性:内置的兼容脚本覆盖了常见的 Typecho 插件(Notice、Links Plus、TelegramNotice 等),基本开箱即用。如果你用的是不太常见的插件,可能需要自己写兼容脚本,但文档写得很清楚,门槛不高。稳定性:从 2.1.4x 版本起完全不再加密 JS 代码,透明度很高。作者更新也比较勤快,GitHub 上的 issue 响应及时。注意事项清除缓存:启用后如果样式异常,先清浏览器缓存异步弹窗陷阱:confirm() / prompt() 不再阻塞,老代码需要适配1.2.1 兼容性:Typecho 1.2.1 下导航栏可能有 CSS 问题,需在设置中开启 1.2.1 兼容脚本CSS 冲突:如果之前装过其他后台美化插件(如 SimpleAdmin),可能存在样式冲突弹窗 z-index:第三方插件的全局弹窗建议 z-index 设为 9999 以上(AB 侧边栏约 1000)字体继承:自定义组件建议用 font-family: inherit 跟随页面字体总结AB-Admin 是目前 Typecho 生态里完成度最高的后台美化插件,没有之一。它不只是换了一套皮肤,而是建立了一套完整的设计系统——从 MD3 色彩变量到开发者 API,从兼容脚本机制到 AJAX 导航,每个环节都考虑到了。如果你还在用 Typecho 默认后台,强烈建议试试。装完之后你会觉得:原来 Typecho 后台也可以这么好看。GitHub:github.com/lhl77/Typecho-Plugin-AdminBeautifyAB Store:github.com/lhl77/Typecho-Plugin-AdminBeautifyStore作者博客:blog.lhl.one
2026年08月01日
5 阅读
0 评论
0 点赞
0:00