Discuz! Q 源码浅析
前言
简单分析了一下Discuz! Q 的源码,纯属个人学习,不对的地方望指正哈。
用到的laravel组件
在vendor/discuz/core/composer.json中可以看到
- “illuminate/container”: ioc服务容器
- “illuminate/database”: 数据库
- “illuminate/bus”: 事件总线
- “illuminate/events”: 事件
- “illuminate/config”: 配置服务
- “illuminate/view”: 视图
- “illuminate/session”: session
- 路由没有用laravel,而是”nikic/fast-route”
- 中间件没有用laravel,而是”laminas/laminas-stratigility”,遵循 PSR-7 HTTP 规范
- 处理请求用的”laminas/laminas-httphandlerrunner”,遵循 PSR-15 HTTP 处理器规范
入口文件分析
在public/index.php中
- $app = new Discuz\Foundation\Application(dirname(__DIR__)); //实例化一个容器,并绑定了事件服务和别名绑定
- $app->make(Discuz\Http\Server::class)->listen(); //实例化Server,并处理请求(重心在这里)
Discuz\Http\Server::class 分析
public function listen()
{
// 这里绑定了核心的服务类,包括ApiServiceProvider、WebServiceProvider,这两个Provider会加载routes/下的路由配置和注册中间件
$this->siteBoot();
$pipe = new MiddlewarePipe();
//new RequestHandler里用到了ApiServiceProvider、WebServiceProvider注册的中间件
$pipe->pipe(new RequestHandler([
'/api' => 'discuz.api.middleware',
'/' => 'discuz.web.middleware'
], $this->app));
$psr17Factory = new Psr17Factory();
$request = (new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory))->fromGlobals();
$this->app->instance('request', $request);
$this->app->alias('request', ServerRequestInterface::class);
// 上面的实例化的中间件对象和Request对象都注入到RequestHandlerRunner
$runner = new RequestHandlerRunner(
$pipe,
new SapiEmitter,
function () use ($request) {
return $request;
},
function (Throwable $e) {
$generator = new ErrorResponseGenerator;
return $generator($e, new ServerRequest, new Response);
}
);
// run方法有两个重要步骤:1. 处理请求$this->handler->handle($request);2. 收尾工作$this->emitter->emit($response);
$runner->run();
}
$this->handler->handle($request) 在 Laminas\Stratigility\MiddlewarePipe
- 主要是中间件的处理流程,利用SplQueue的先进先出队列来实现,在ApiServiceProvider、WebServiceProvider中注册了中间件,enqueue进SplQueue队列,然后一个一个的dequeue出队列并处理请求。
- 最后一个中间件是DispatchRoute::class,它则是用来根据请求的url找到对应的控制器开始业务处理。
- PS:中间件的处理不再像laravel那样用到array_reduce的特性,我写了一个简单的demo模拟了下:
class Next
{
private $fallbackHandler;
private $queue;
public function __construct($queue, $fallbackHandler)
{
$this->queue = clone $queue;
$this->fallbackHandler = $fallbackHandler;
}
public function handle($request)
{
if ($this->queue->isEmpty()) {
$this->queue = null;
$fallbackHandler = $this->fallbackHandler;
return $fallbackHandler($request);
}
$middleware = $this->queue->dequeue();
$next = clone $this;
$this->queue = null;
return $middleware($request, $next);
}
}
class SplQueuePipeline
{
private $pipeline;
public function __construct()
{
$this->pipeline = new SplQueue();
}
public function __clone()
{
$this->pipeline = clone $this->pipeline;
}
public function handle($request)
{
return $this->process($request, function ($r) {
return $r;
});
}
public function process($request, $handler)
{
return (new Next($this->pipeline, $handler))->handle($request);
}
public function pipe($middleware)
{
$this->pipeline->enqueue($middleware);
}
}
$pipe = new SplQueuePipeline();
$pipe->pipe(function ($request, $next) {
var_dump("pipe 1");
return $next->handle($request);
});
$pipe->pipe(function ($request, $next) {
var_dump("pipe 2");
return $next->handle($request);
});
$rs = $pipe->handle("request");
var_dump($rs);
疑问:app\Api\Middleware目录下的中间件是如何生效的
- App\Listeners\AddApiMiddleware 将 app\Api\Middleware 目录的中间件进行注册。
- App\Providers\EventServiceProvider 注册了 Discuz\Api\Events\ConfigMiddleware 事件的监听,listener 就是 App\Listeners\AddApiMiddleware。
- App\Providers\EventServiceProvider 定义在配置文件的 providers 数组内。而这些provider 的注册在上面提到的$this->siteBoot();中完成。
- 那 ConfigMiddleware 事件又是怎么产生的呢?在上面提到的 ApiServiceProvider 中,注册完系统中间件后会触发 ConfigMiddleware 事件。这个流程我模拟了一个简单的demo如下:
class EventDispatcher
{
private $bindings = [];
public function listen($eventClass, $listenerClass)
{
if (empty($this->bindings[$eventClass])) {
$this->bindings[$eventClass] = [];
}
$this->bindings[$eventClass][] = $listenerClass;
}
public function dispatch($obj)
{
$eventClass = get_class($obj);
if (!empty($this->bindings[$eventClass])) {
foreach ($this->bindings[$eventClass] as $listenerClass) {
$reflector = new ReflectionClass($listenerClass);
$instance = $reflector->newInstance();
$method = $reflector->getMethod('handle');
$method->invoke($instance, $obj);
}
}
}
}
class AEvent
{
public $pipe;
public function __construct($obj)
{
$this->pipe = $obj;
}
}
class AListener
{
public function handle(AEvent $obj)
{
$obj->pipe->arr[] = "pipe 2";
}
}
$obj = new stdClass();
$obj->arr = ['pipe 1'];
$eventDispatcher = new EventDispatcher();
$eventDispatcher->listen(AEvent::class, AListener::class);
$eventDispatcher->dispatch(new AEvent($obj));
print_r($obj->arr);
本作品采用《CC 协议》,转载必须注明作者和本文链接
Discuz Q
里用到了很多腾讯云的api
,基本都是收费的,可以理解为腾讯云深度定制。没找到前端未打包的源码,不好二开
@showcj 前端有开源的代码的
还是没用伪静态呀
@showcj
@Cooper @showcj
那是旧版了,后台管理还在用,新的前台用的 uni-app
discuz.chat/pages/topic/index?id=4...
dl.discuz.chat/uniapp_latest.zip
请问大佬,后台接口得路由时怎么配置得?
感谢大佬。刚入职就是要二开,看了你的文章帮助很大,再次感谢!!!
现在还有哪些类似discuzQ这样的论坛门户比较好用来快速开发