如何基于 Notadd 构建 API

Notadd 底层实现了 passport 机制,有统一的授权管理,主要支持两种方式进行 API 授权,一个是 client,另一个是 passport,这个在其他文档中有做详细的说明。

这里主要说的是,如何基于 Notadd 进行 API 接口的开发。

业务逻辑

熟悉 Laravel 的同学都应该知道,Laravel 遵循这样的业务逻辑实现:

  1. 路由(route) -> 控制器(controller) -> 业务逻辑(model) -> 数据输出(view)

而 Notadd 的 API 业务逻辑实现同样遵循类似的流程:

  1. 路由(route) -> 控制器(controller) -> API 处理器(handler) -> 模型(model) -> 数据输出(json)

其中,主要的差异在于,API 处理器提供了对数据输出格式的输出,返回的数据格式统一为:

  1. [
  2. 'code' => 200, // API 接口返回的状态码,默认为 200
  3. 'data' => [], // API 接口返回的数据,主要为数组形式
  4. 'message' => 'success!', // API 接口返回的提示信息,可以包含错误信息或成功信息
  5. ]

路由

Notadd 在实现 API 授权的时候,使用的是有 路由中间件(middleware) 的方式来实现的。

具体实现方式,是在路由的中间配置参数中添加 auth:api

例如,在实现 api/setting/all 和 api/setting/set 两个 API 的时候,添加 auth:api 的中间件,代码参考如下:

  1. $this->router->group(['middleware' => ['auth:api', 'web'], 'prefix' => 'api/setting'], function () {
  2. $this->router->post('all', 'Notadd\Foundation\Setting\Controllers\SettingController@all');
  3. $this->router->post('set', 'Notadd\Foundation\Setting\Controllers\SettingController@set');
  4. });

Notadd 针对需要跨域的 API 还提供了 cross 的路由中间件,以实现 API 跨域的功能。

例如,为前两个 API 提供跨域的功能实现,代码参考如下:

  1. $this->router->group(['middleware' => ['auth:api', 'cross', 'web'], 'prefix' => 'api/setting'], function () {
  2. $this->router->post('all', 'Notadd\Foundation\Setting\Controllers\SettingController@all');
  3. $this->router->post('set', 'Notadd\Foundation\Setting\Controllers\SettingController@set');
  4. });

控制器

由于有了独立的 API处理器 ,控制器层可以制作简单处理,仅需向控制器注入 handler,并由 handler 提供的辅助方法返回 API 数据给前台,即可。

例如,在前面路由调用的 SettingController 中,仅需要注入 AllHandler ,使用方法 toResponse 和 generateHttpResponse 来返回结果给前台,代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-11-08 17:01
  8. */
  9. namespace Notadd\Foundation\Setting\Controllers;
  10. use Notadd\Foundation\Routing\Abstracts\Controller;
  11. use Notadd\Foundation\Setting\Contracts\SettingsRepository;
  12. use Notadd\Foundation\Setting\Handlers\AllHandler;
  13. use Notadd\Foundation\Setting\Handlers\SetHandler;
  14. /**
  15. * Class SettingController.
  16. */
  17. class SettingController extends Controller
  18. {
  19. /**
  20. * @var \Notadd\Foundation\Setting\Contracts\SettingsRepository
  21. */
  22. protected $settings;
  23. /**
  24. * SettingController constructor.
  25. *
  26. * @param \Notadd\Foundation\Setting\Contracts\SettingsRepository $settings
  27. *
  28. * @throws \Illuminate\Contracts\Container\BindingResolutionException
  29. */
  30. public function __construct(SettingsRepository $settings)
  31. {
  32. parent::__construct();
  33. $this->settings = $settings;
  34. }
  35. /**
  36. * All handler.
  37. *
  38. * @param \Notadd\Foundation\Setting\Handlers\AllHandler $handler
  39. *
  40. * @return \Notadd\Foundation\Routing\Responses\ApiResponse
  41. * @throws \Exception
  42. */
  43. public function all(AllHandler $handler)
  44. {
  45. return $handler->toResponse()->generateHttpResponse();
  46. }
  47. /**
  48. * Set handler.
  49. *
  50. * @param \Notadd\Foundation\Setting\Handlers\SetHandler $handler
  51. *
  52. * @return \Notadd\Foundation\Routing\Responses\ApiResponse
  53. * @throws \Exception
  54. */
  55. public function set(SetHandler $handler)
  56. {
  57. return $handler->toResponse()->generateHttpResponse();
  58. }
  59. }

API Handler 和模型

在 API Handler中提供了模型的操作接口。

在 Notadd 中,提供了两类 API Handler,一类是 DataHandler,另一类是 SetHandler,顾名思义,DataHandler 仅提供数据返回接口,而 SetHandler 不仅提供数据返回接口,还提供其他操作处理的接口。

具体差异体现在,DataHandler 在返回数据接口时仅调用方法 data,而 SetHandler 在调用 data 方法前还有调用 execute 方法。

例如,在前面的 SettingController 中使用的 AllHandler 为 DataHandler 类 Handler,提供返回所有 配置项 的 API 功能,SetHandler 为 SetHandler 类 Handler,提供 修改配置项 并返回所有 配置项 的 API 功能。

AllHandler 的代码如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-11-23 14:44
  8. */
  9. namespace Notadd\Foundation\Setting\Handlers;
  10. use Illuminate\Container\Container;
  11. use Notadd\Foundation\Passport\Abstracts\DataHandler;
  12. use Notadd\Foundation\Setting\Contracts\SettingsRepository;
  13. /**
  14. * Class AllHandler.
  15. */
  16. class AllHandler extends DataHandler
  17. {
  18. /**
  19. * @var \Notadd\Foundation\Setting\Contracts\SettingsRepository
  20. */
  21. protected $settings;
  22. /**
  23. * AllHandler constructor.
  24. *
  25. * @param \Illuminate\Container\Container $container
  26. * @param \Notadd\Foundation\Setting\Contracts\SettingsRepository $settings
  27. */
  28. public function __construct(
  29. Container $container,
  30. SettingsRepository $settings
  31. ) {
  32. parent::__construct($container);
  33. $this->settings = $settings;
  34. }
  35. /**
  36. * Http code.
  37. *
  38. * @return int
  39. */
  40. public function code() // 定义 API 操作结果的状态码
  41. {
  42. return 200;
  43. }
  44. /**
  45. * Data for handler.
  46. *
  47. * @return array
  48. */
  49. public function data() // 定义 API 返回的数据
  50. {
  51. return $this->settings->all()->toArray();
  52. }
  53. /**
  54. * Errors for handler.
  55. *
  56. * @return array
  57. */
  58. public function errors() // 定义 API 操作失败时返回的信息
  59. {
  60. return [
  61. '获取全局设置失败!',
  62. ];
  63. }
  64. /**
  65. * Messages for handler.
  66. *
  67. * @return array
  68. */
  69. public function messages() // 定义 API 操作成功时返回的信息
  70. {
  71. return [
  72. '获取全局设置成功!',
  73. ];
  74. }
  75. }

SetHandler 的代码如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-11-23 15:09
  8. */
  9. namespace Notadd\Foundation\Setting\Handlers;
  10. use Illuminate\Container\Container;
  11. use Notadd\Foundation\Passport\Abstracts\SetHandler as AbstractSetHandler;
  12. use Notadd\Foundation\Setting\Contracts\SettingsRepository;
  13. /**
  14. * Class SetHandler.
  15. */
  16. class SetHandler extends AbstractSetHandler
  17. {
  18. /**
  19. * @var \Notadd\Foundation\Setting\Contracts\SettingsRepository
  20. */
  21. protected $settings;
  22. /**
  23. * SetHandler constructor.
  24. *
  25. * @param \Illuminate\Container\Container $container
  26. * @param \Notadd\Foundation\Setting\Contracts\SettingsRepository $settings
  27. */
  28. public function __construct(
  29. Container $container,
  30. SettingsRepository $settings
  31. ) {
  32. parent::__construct($container);
  33. $this->settings = $settings;
  34. }
  35. /**
  36. * Data for handler.
  37. *
  38. * @return array
  39. */
  40. public function data() // 定义 API 返回的数据
  41. {
  42. return $this->settings->all()->toArray();
  43. }
  44. /**
  45. * Errors for handler.
  46. *
  47. * @return array
  48. */
  49. public function errors() // 定义 API 操作失败时返回的信息
  50. {
  51. return [
  52. '修改设置失败!',
  53. ];
  54. }
  55. /**
  56. * Execute Handler.
  57. *
  58. * @return bool
  59. */
  60. public function execute() // 定义 API 执行的修改操作
  61. {
  62. $this->settings->set('site.enabled', $this->request->input('enabled'));
  63. $this->settings->set('site.name', $this->request->input('name'));
  64. $this->settings->set('site.domain', $this->request->input('domain'));
  65. $this->settings->set('site.beian', $this->request->input('beian'));
  66. $this->settings->set('site.company', $this->request->input('company'));
  67. $this->settings->set('site.copyright', $this->request->input('copyright'));
  68. $this->settings->set('site.statistics', $this->request->input('statistics'));
  69. return true;
  70. }
  71. /**
  72. * Messages for handler.
  73. *
  74. * @return array
  75. */
  76. public function messages() // 定义 API 操作成功时返回的信息
  77. {
  78. return [
  79. '修改设置成功!',
  80. ];
  81. }
  82. }

数据输出

API 结果的数据输出,已经在 控制器(controller) 中做了处理。

至此,一个完整的 API 开发完成。

如何开发一个 Notadd 模块

模块代码结构请参考项目 https://github.com/notadd/content

模块将包含如下几个部分:

  • 模块注入
  • 路由注入
  • 门面注入
  • CSRF注入

模块安装

对于模块的安装,目前 Notadd 支持的安装方式,仅为在 根项目 中对 模块 进行依赖。

例如,需要安装模块 notadd/content ,可以修改根项目的文件 composer.json,参考代码如下:

  1. {
  2. "name": "notadd/notadd",
  3. "description": "The Notadd Framework.",
  4. "keywords": [
  5. "notadd",
  6. "cms",
  7. "foundation",
  8. "framework"
  9. ],
  10. "homepage": "https://notadd.com",
  11. "license": "Apache-2.0",
  12. "type": "notadd-module",
  13. "authors": [
  14. {
  15. "name": "twilroad",
  16. "email": "heshudong@ibenchu.com"
  17. }
  18. ],
  19. "autoload": {
  20. "classmap": [
  21. "storage/databases"
  22. ]
  23. },
  24. "require": {
  25. "php": ">=7.0",
  26. "notadd/content": "0.1.*",
  27. "wikimedia/composer-merge-plugin": "dev-master"
  28. },
  29. "require-dev": {
  30. "fzaninotto/faker": "~1.4",
  31. "mockery/mockery": "0.9.*",
  32. "phpunit/phpunit": "~5.0",
  33. "symfony/css-selector": "3.1.*",
  34. "symfony/dom-crawler": "3.1.*"
  35. },
  36. "config": {
  37. "preferred-install": "dist"
  38. },
  39. "scripts": {
  40. "post-create-project-cmd": [
  41. "php notadd key:generate"
  42. ],
  43. "post-install-cmd": [
  44. "Notadd\\Foundation\\Composer\\ComposerScripts::postInstall",
  45. "php notadd optimize"
  46. ],
  47. "post-update-cmd": [
  48. "Notadd\\Foundation\\Composer\\ComposerScripts::postUpdate",
  49. "php notadd optimize"
  50. ]
  51. },
  52. "extra": {
  53. "merge-plugin": {
  54. "include": [
  55. "extensions/*/*/composer.json"
  56. ],
  57. "recurse": true,
  58. "replace": false,
  59. "merge-dev": false
  60. }
  61. }
  62. }

更新文件 composer.json 后,执行 composer update 即可完成对模块的安装。

完整示例代码,请参考项目 https://github.com/notadd/notadd

目录结构

目录结构如下:

  1. # module 模块目录
  2. # resources 资源目录
  3. # translations 翻译文件目录
  4. # views 视图目录
  5. # src 源码目录
  6. # ModuleServiceProvider.php 模块服务提供者定义文件
  7. # composer.json Composer 配置文件

一个 Notadd 的模块,是一个符合 composer 规范的包,所以,模块对第三方代码有依赖时,可以在 composer.json 中的 require 节点中添加第三方的包。

而作为一个符合 Notadd 模块定义规范的包,composer.json 需拥有如下信息:

  • type 必须为 notadd-module
  • require 中必须添加包 notadd/installers

代码参考如下(来自插件根目录下的文件 composer.json, 文件中不应该包含 // 的注释信息,此处仅作为说明)

  1. {
  2. "name": "notadd/content",
  3. "description": "Notadd's Content Module.",
  4. "keywords": [
  5. "notadd",
  6. "cms",
  7. "framework",
  8. "content"
  9. ],
  10. "homepage": "https://notadd.com",
  11. "license": "Apache-2.0",
  12. "type": "notadd-module", // type 必须设置为 notadd-module
  13. "authors": [
  14. {
  15. "name": "Notadd",
  16. "email": "notadd@ibenchu.com"
  17. }
  18. ],
  19. "require": {
  20. "php": ">=7.0",
  21. "notadd/installers": "0.5.*" // 必须依赖包 notadd/installers
  22. },
  23. "autoload": {
  24. "psr-4": {
  25. "Notadd\\Content\\": "src/"
  26. }
  27. }
  28. }

模块注入

所谓 模块注入 ,是 Notadd 在加载模块的时候,会检索模块目录下的类 ModuleServiceProvider,此类必须命名为 ModuleServiceProvider,且需放在源码根目录中,且命名空间必须为 composer.json 的中 autoload 节点定义的符合 psr-4 规范的命名空间,否则 Notadd 将不能正确加载模块!

类 ModuleServiceProvider 的父类必须为 Illuminate\Support\ServiceProvider ,且必须包含 boot 方法。相关服务提供者的概念可以参考 Laravel 文档。

类 ModuleServiceProvider 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-10-08 17:12
  8. */
  9. namespace Notadd\Content;
  10. use Illuminate\Events\Dispatcher;
  11. use Illuminate\Support\ServiceProvider;
  12. use Notadd\Content\Listeners\RouteRegister;
  13. /**
  14. * Class Module.
  15. */
  16. class ModuleServiceProvider extends ServiceProvider
  17. {
  18. /**
  19. * Boot service provider.
  20. */
  21. public function boot()
  22. {
  23. $this->app->make(Dispatcher::class)->subscribe(RouteRegister::class); // 订阅事件 RouteRegister
  24. }
  25. }

路由注入

由于 Notadd 的路由注入,需要实现事件 RouteRegister,并在事件监听中添加 路由 。

所以,所谓的路由注入,实际是在类 ModuleServiceProvider 实现事件 RouteRegister 的订阅,并在事件订阅类中注册模块所需要的路由。

类 ModuleServiceProvider 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-10-08 17:12
  8. */
  9. namespace Notadd\Content;
  10. use Illuminate\Support\ServiceProvider;
  11. /**
  12. * Class Module.
  13. */
  14. class ModuleServiceProvider extends ServiceProvider
  15. {
  16. /**
  17. * Boot service provider.
  18. */
  19. public function boot()
  20. {
  21. }
  22. }

事件订阅类 RouteRegister 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-10-08 18:30
  8. */
  9. namespace Notadd\Content\Listeners;
  10. use Notadd\Content\Controllers\Api\Article\ArticleController as ArticleApiController;
  11. use Notadd\Foundation\Routing\Abstracts\RouteRegister as AbstractRouteRegister;
  12. /**
  13. * Class RouteRegister.
  14. */
  15. class RouteRegister extends AbstractRouteRegister
  16. {
  17. /**
  18. * Handle Route Registrar.
  19. */
  20. public function handle()
  21. {
  22. $this->router->group(['middleware' => ['cross', 'web']], function () { // 路由的注册
  23. $this->router->group(['prefix' => 'api/article'], function () {
  24. $this->router->post('find', ArticleApiController::class . '@find');
  25. $this->router->post('fetch', ArticleApiController::class . '@fetch');
  26. });
  27. });
  28. }
  29. }

门面注入

门面,是 Laravel 的一个功能特色,可以通过门面调用对应 IoC 容器的实例,所以 Notadd 必然会保留这一功能。

所谓的路由注入,实际是在类 ModuleServiceProvider 实现事件 FacadeRegister 的订阅,并在事件订阅类中注册模块所需要的路由。

类 ModuleServiceProvider 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-10-08 17:12
  8. */
  9. namespace Notadd\Content;
  10. use Illuminate\Events\Dispatcher;
  11. use Illuminate\Support\ServiceProvider;
  12. use Notadd\Content\Listeners\FacadeRegister;
  13. /**
  14. * Class Module.
  15. */
  16. class ModuleServiceProvider extends ServiceProvider
  17. {
  18. /**
  19. * Boot service provider.
  20. */
  21. public function boot()
  22. {
  23. $this->app->make(Dispatcher::class)->subscribe(FacadeRegister::class);
  24. }
  25. }

事件订阅类 FacadeRegister 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2017, notadd.com
  7. * @datetime 2017-01-22 12:20
  8. */
  9. namespace Notadd\Content\Listeners;
  10. use Notadd\Foundation\Event\Abstracts\EventSubscriber;
  11. use Notadd\Foundation\Facades\FacadeRegister as FacadeRegisterEvent;
  12. /**
  13. * Class FacadeRegister.
  14. */
  15. class FacadeRegister extends EventSubscriber
  16. {
  17. /**
  18. * Name of event.
  19. *
  20. * @throws \Exception
  21. * @return string|object
  22. */
  23. protected function getEvent()
  24. {
  25. return FacadeRegisterEvent::class;
  26. }
  27. /**
  28. * Event handler.
  29. *
  30. * @param $event
  31. */
  32. public function handle(FacadeRegisterEvent $event)
  33. {
  34. $event->register('Log', 'Illuminate\Support\Facades\Log');
  35. }
  36. }

CSRF注入

所谓的CSRF注入,实际是在类 ModuleServiceProvider 实现事件 CsrfTokenRegister 的订阅,并在事件订阅类中注册模块所需要的路由。

类 ModuleServiceProvider 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-10-08 17:12
  8. */
  9. namespace Notadd\Content;
  10. use Illuminate\Events\Dispatcher;
  11. use Illuminate\Support\ServiceProvider;
  12. use Notadd\Content\Listeners\CsrfTokenRegister;
  13. /**
  14. * Class Module.
  15. */
  16. class ModuleServiceProvider extends ServiceProvider
  17. {
  18. /**
  19. * Boot service provider.
  20. */
  21. public function boot()
  22. {
  23. $this->app->make(Dispatcher::class)->subscribe(CsrfTokenRegister::class);
  24. }
  25. }

事件订阅类 CsrfTokenRegister 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2017, notadd.com
  7. * @datetime 2017-02-10 11:04
  8. */
  9. namespace Notadd\Content\Listeners;
  10. use Notadd\Foundation\Event\Abstracts\EventSubscriber;
  11. use Notadd\Foundation\Http\Events\CsrfTokenRegister as CsrfTokenRegisterEvent;
  12. /**
  13. * Class CsrfTokenRegister.
  14. */
  15. class CsrfTokenRegister extends EventSubscriber
  16. {
  17. /**
  18. * Name of event.
  19. *
  20. * @throws \Exception
  21. * @return string|object
  22. */
  23. protected function getEvent()
  24. {
  25. return CsrfTokenRegisterEvent::class;
  26. }
  27. /**
  28. * Register excepts.
  29. *
  30. * @param $event
  31. */
  32. public function handle(CsrfTokenRegisterEvent $event)
  33. {
  34. $event->registerExcept('api/article*');
  35. $event->registerExcept('api/category*');
  36. $event->registerExcept('api/content*');
  37. $event->registerExcept('api/page*');
  38. }
  39. }

如何开发一个 Notadd 插件

插件代码结构请参考项目 http://git.oschina.net/notadd/duoshuo

插件将包含如下几个部分:

  • 插件注入
  • 路由注入
  • CSRF注入

插件安装

对于 插件 的安装,仅需将插件文件按照下面的目录结构,放置到插件根目录 extensions (wwwroot/extensions)下,然后执行命令 composer update 即可。

目录结构

目录结构如下:

  1. # wwwroot/extensions 插件根目录
  2. # vendor 厂商目录(目录名称仅为示例,开发时自行修改)
  3. # extension 插件目录(目录名称仅为示例,开发时自行修改)
  4. # configuations 可加载配置文件目录
  5. # resources 资源目录
  6. # translations 翻译文件目录
  7. # views 视图目录
  8. # src 源码目录
  9. # Extension 插件服务提供者定义文件
  10. # composer.json Composer 配置文件

一个 Notadd 的插件,是一个符合 composer 规范的包,所以,插件对第三方代码有依赖时,可以在 composer.json 中的 require 节点中添加第三方的包。

而作为一个符合 Notadd 插件定义规范的包,composer.json 需拥有如下信息:

  • type 必须为 notadd-extension
  • require 中必须添加包 notadd/installers

代码参考如下(来自插件根目录下的文件 composer.json, 文件中不应该包含 // 的注释信息,此处仅作为说明)

  1. {
  2. "name": "notadd/duoshuo",
  3. "description": "Notadd Extension for Duoshuo.",
  4. "type": "notadd-extension", // type 必须设置为 notadd-extension
  5. "keywords": ["notadd", "duoshuo", "extension"],
  6. "homepage": "https://notadd.com",
  7. "license": "Apache-2.0",
  8. "authors": [
  9. {
  10. "name": "Notadd",
  11. "email": "notadd@ibenchu.com"
  12. }
  13. ],
  14. "autoload": {
  15. "psr-4": {
  16. "Notadd\\Duoshuo\\": "src/"
  17. }
  18. },
  19. "require": {
  20. "php": ">=7.0",
  21. "notadd/installers": "0.5.*" // 必须依赖包 notadd/installers
  22. }
  23. }

插件注入

所谓 插件注入 ,是 Notadd 在加载插件的时候,会检索插件目录下的类 Extension,此类必须命名为 Extension,且需放在源码根目录中,且命名空间必须为 composer.json 的中 autoload 节点定义的符合 psr-4 规范的命名空间,否则 Notadd 将不能正确加载插件!

类 Extension 的父类必须为 Notadd\Foundation\Addon\Abstracts\Extension ,且必须包含 boot 方法。

类 Extension 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2017, notadd.com
  7. * @datetime 2017-02-21 11:28
  8. */
  9. namespace Notadd\Duoshuo;
  10. use Notadd\Foundation\Addon\Abstracts\Extension as AbstractExtension;
  11. /**
  12. * Class Extension.
  13. */
  14. class Extension extends AbstractExtension
  15. {
  16. /**
  17. * Boot provider.
  18. */
  19. public function boot()
  20. {
  21. }
  22. }

路由注入

由于 Notadd 的路由注入,需要实现事件 RouteRegister,并在事件监听中添加 路由 。

所以,所谓的路由注入,实际是在类 Extension 实现事件 RouteRegister 的订阅,并在事件订阅类中注册插件所需要的路由。

类 Extension 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2017, notadd.com
  7. * @datetime 2017-02-21 11:28
  8. */
  9. namespace Notadd\Duoshuo;
  10. use Illuminate\Events\Dispatcher;
  11. use Notadd\Duoshuo\Listeners\RouteRegister;
  12. use Notadd\Foundation\Addon\Abstracts\Extension as AbstractExtension;
  13. /**
  14. * Class Extension.
  15. */
  16. class Extension extends AbstractExtension
  17. {
  18. /**
  19. * Boot provider.
  20. */
  21. public function boot()
  22. {
  23. $this->app->make(Dispatcher::class)->subscribe(RouteRegister::class);
  24. }
  25. }

事件订阅类 RouteRegister 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2017, notadd.com
  7. * @datetime 2017-02-21 11:50
  8. */
  9. namespace Notadd\Duoshuo\Listeners;
  10. use Notadd\Duoshuo\Controllers\DuoshuoController;
  11. use Notadd\Foundation\Routing\Abstracts\RouteRegister as AbstractRouteRegister;
  12. /**
  13. * Class RouteRegister.
  14. */
  15. class RouteRegister extends AbstractRouteRegister
  16. {
  17. /**
  18. * Handle Route Registrar.
  19. */
  20. public function handle()
  21. {
  22. $this->router->group(['middleware' => ['auth:api', 'cross', 'web'], 'prefix' => 'api/duoshuo'], function () {
  23. $this->router->post('backup', DuoshuoController::class . '@backup');
  24. $this->router->post('configuration', DuoshuoController::class . '@configuration');
  25. $this->router->post('number', DuoshuoController::class . '@number');
  26. });
  27. }
  28. }

CSRF注入

所谓的CSRF注入,实际是在类 Extension 实现事件 CsrfTokenRegister 的订阅,并在事件订阅类中注册插件所需要的路由。

类 Extension 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2017, notadd.com
  7. * @datetime 2017-02-21 11:28
  8. */
  9. namespace Notadd\Duoshuo;
  10. use Illuminate\Events\Dispatcher;
  11. use Notadd\Duoshuo\Listeners\CsrfTokenRegister;
  12. use Notadd\Foundation\Addon\Abstracts\Extension as AbstractExtension;
  13. /**
  14. * Class Extension.
  15. */
  16. class Extension extends AbstractExtension
  17. {
  18. /**
  19. * Boot provider.
  20. */
  21. public function boot()
  22. {
  23. $this->app->make(Dispatcher::class)->subscribe(CsrfTokenRegister::class);
  24. }
  25. }

事件订阅类 CsrfTokenRegister 的代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2017, notadd.com
  7. * @datetime 2017-02-23 19:38
  8. */
  9. namespace Notadd\Duoshuo\Listeners;
  10. use Notadd\Foundation\Event\Abstracts\EventSubscriber;
  11. use Notadd\Foundation\Http\Events\CsrfTokenRegister as CsrfTokenRegisterEvent;
  12. /**
  13. * Class CsrfTokenRegister.
  14. */
  15. class CsrfTokenRegister extends EventSubscriber
  16. {
  17. /**
  18. * Name of event.
  19. *
  20. * @throws \Exception
  21. * @return string|object
  22. */
  23. protected function getEvent()
  24. {
  25. return CsrfTokenRegisterEvent::class;
  26. }
  27. /**
  28. * Register excepts.
  29. *
  30. * @param $event
  31. */
  32. public function handle(CsrfTokenRegisterEvent $event)
  33. {
  34. $event->registerExcept('api/duoshuo*');
  35. }
  36. }

如何开发一个 Notadd Administration 模块的前端扩展

阅读此文档,需对 Laravel,VueJS 2,Webpack 有了解。

前端扩展,指的是,针对项目 notadd/administration 的前端部分进行扩展功能的开发。

完整示例,请参考模块项目 notadd/content

前端扩展包含的功能注入点如下:

  • 扩展安装注入
  • 头部菜单注入
  • 路由注入
  • 侧边栏菜单注入

说明

项目 notadd/administration 的前端部分,是基于 VueJS 2 实现的单页应用(SPA)。

所以,对前端进行扩展,实际是对 VueJS 项目的扩展。

由于 VueJS 项目基于 Webpack 进行构建和打包,所以前端扩展项目也必须基于 Webpack 进行构建和打包。

如何创建和开发 VueJS 2 的项目,请参见 VueJS 官方文档

但是,Notadd 的前端扩展项目,并不是一个完整的 VueJS 2 的项目,因为 Notadd 只接受 UMD 模块风格的前端模块注入,所以在使用 Webpack 进行模块构建时,webpackConfig 中需要针对 output 参数进行调整,主要体现:

  • 必须定义 outputlibrary 别名,此名称,必须与 捆绑 的模块或扩展项目中 composer.json 文件中定义的 name 完全一致,否则无法加载前端扩展
  • 必须定义 outputlibraryTargetumd

配置代码参考如下(来自文件 build/webpack.prod.conf.js):

  1. var path = require('path')
  2. var utils = require('./utils')
  3. var webpack = require('webpack')
  4. var config = require('../config')
  5. var merge = require('webpack-merge')
  6. var baseWebpackConfig = require('./webpack.base.conf')
  7. var HtmlWebpackPlugin = require('html-webpack-plugin')
  8. var ExtractTextPlugin = require('extract-text-webpack-plugin')
  9. var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
  10. var env = config.build.env
  11. var webpackConfig = merge(baseWebpackConfig, {
  12. module: {
  13. rules: utils.styleLoaders({
  14. sourceMap: config.build.productionSourceMap,
  15. extract: true
  16. })
  17. },
  18. output: {
  19. path: config.build.assetsRoot,
  20. filename: utils.assetsPath('js/extension.js'),
  21. library: 'notadd/content', // 必须定义 library 别名
  22. libraryTarget: "umd" // 必须定义 libraryTarget 为 umd
  23. },
  24. plugins: [
  25. new webpack.DefinePlugin({
  26. 'process.env': env
  27. }),
  28. new webpack.optimize.UglifyJsPlugin({
  29. compress: {
  30. warnings: false
  31. }
  32. }),
  33. new ExtractTextPlugin({
  34. filename: utils.assetsPath('css/[name].css')
  35. }),
  36. new OptimizeCSSPlugin()
  37. ]
  38. })
  39. if (config.build.productionGzip) {
  40. var CompressionWebpackPlugin = require('compression-webpack-plugin')
  41. webpackConfig.plugins.push(
  42. new CompressionWebpackPlugin({
  43. asset: '[path].gz[query]',
  44. algorithm: 'gzip',
  45. test: new RegExp(
  46. '\\.(' +
  47. config.build.productionGzipExtensions.join('|') +
  48. ')$'
  49. ),
  50. threshold: 10240,
  51. minRatio: 0.8
  52. })
  53. )
  54. }
  55. if (config.build.bundleAnalyzerReport) {
  56. var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  57. webpackConfig.plugins.push(new BundleAnalyzerPlugin())
  58. }
  59. module.exports = webpackConfig

默认导出模块

使用 Webpack 导出 UMD 模块风格的模块是,在 Webpack 配置中定义的 entry 入口文件中,必须使用默认导出模块,作为 Notadd 前端功能注入的注入点。

代码参考如下:

```ecmascript 6 import {headerMixin, installMixin, routerMixin} from ‘./helpers/mixes’

let Core = {}

headerMixin(Core) installMixin(Core) routerMixin(Core)

export default Core

  1. 如上代码所示,模块 Core 即为项目构建后的默认导出模块,在该示例中,使用了 mixin 特性,为模块增加 headerinstallrouter的注入逻辑。
  2. ## 扩展安装注入
  3. 如上(默认导出模块)述说,installMixin 为模块 Core 注入了 Core.install 的实现,具体代码如下:
  4. ```ecmascript 6
  5. export function installMixin (Core) {
  6. Core.install = function (Vue, Notadd) {
  7. Core.instance = Notadd
  8. vueMixin(Core, Vue)
  9. }
  10. }
  11. export function vueMixin (Core, Vue) {
  12. Core.http = Vue.http
  13. }

Core.install 的调用者,为该方法提供了两个对象,一个是 Vue 全局对象,一个是 Notadd 全局对象。

Vue 全局对象提供的特性,可以参考 VueJS 2 的官方文档。

Notadd 全局对象主要包含如下特性:

  • Notadd.Vue:Vue 全局对象的副本
  • Notadd.http:axios 全局对象的副本
  • Notadd.store:Vuex 对象的副本
  • Notadd.components:常用的功能型组件(符合 Vue 组件规范)
  • Notadd.layouts:常用的布局型组件(符合 Vue 组件规范)

所以,如果模块 Core 中需要使用 Vue 或 Notadd 的任意对象,均可通过 mixin 特性来附加。

头部菜单注入

如上(默认导出模块)述说,headerMixin 为模块 Core 注入了 Core.header 的实现,具体代码如下:

```ecmascript 6 export function headerMixin (Core) { Core.header = function (menu) { menu.push({ ‘text’: ‘文章’, ‘icon’: ‘icon icon-article’, ‘uri’: ‘/content’ }) } }

  1. ## 路由注入
  2. 如上(默认导出模块)述说,routerMixin 为模块 Core 注入了 Core.router 的实现,具体代码如下:
  3. ```ecmascript 6
  4. import ContentArticle from '../components/Article'
  5. import ContentArticleCreate from '../components/ArticleCreate'
  6. import ContentArticleDraft from '../components/ArticleDraft'
  7. import ContentArticleDraftEdit from '../components/ArticleDraftEdit'
  8. import ContentArticleEdit from '../components/ArticleEdit'
  9. import ContentArticleRecycle from '../components/ArticleRecycle'
  10. import ContentCategory from '../components/ArticleCategory'
  11. import ContentComment from '../components/Comment'
  12. import ContentComponent from '../components/Component'
  13. import ContentDashboard from '../components/Dashboard'
  14. import ContentExtension from '../components/Extension'
  15. import ContentLayout from '../components/Layout'
  16. import ContentPage from '../components/Page'
  17. import ContentPageCategory from '../components/PageCategory'
  18. import ContentPageCreate from '../components/PageCreate'
  19. import ContentPageEdit from '../components/PageEdit'
  20. import ContentTemplate from '../components/Template'
  21. import ContentTag from '../components/ArticleTag'
  22. export function routerMixin (Core) {
  23. Core.router = function (router) {
  24. router.modules.push({
  25. path: '/content',
  26. component: ContentLayout,
  27. children: [
  28. {
  29. path: '/',
  30. component: ContentDashboard,
  31. beforeEnter: router.auth
  32. },
  33. {
  34. path: 'article/all',
  35. component: ContentArticle,
  36. beforeEnter: router.auth
  37. },
  38. {
  39. path: 'article/create',
  40. component: ContentArticleCreate,
  41. beforeEnter: router.auth
  42. },
  43. {
  44. path: 'article/:id/draft',
  45. component: ContentArticleDraftEdit,
  46. beforeEnter: router.auth
  47. },
  48. {
  49. path: 'article/:id/edit',
  50. component: ContentArticleEdit,
  51. beforeEnter: router.auth
  52. },
  53. {
  54. path: 'article/category',
  55. component: ContentCategory,
  56. beforeEnter: router.auth
  57. },
  58. {
  59. path: 'article/tag',
  60. component: ContentTag,
  61. beforeEnter: router.auth
  62. },
  63. {
  64. path: 'article/recycle',
  65. component: ContentArticleRecycle,
  66. beforeEnter: router.auth
  67. },
  68. {
  69. path: 'article/draft',
  70. component: ContentArticleDraft,
  71. beforeEnter: router.auth
  72. },
  73. {
  74. path: 'page/all',
  75. component: ContentPage,
  76. beforeEnter: router.auth
  77. },
  78. {
  79. path: 'page/create',
  80. component: ContentPageCreate,
  81. beforeEnter: router.auth
  82. },
  83. {
  84. path: 'page/:id/edit',
  85. component: ContentPageEdit,
  86. beforeEnter: router.auth
  87. },
  88. {
  89. path: 'page/category',
  90. component: ContentPageCategory,
  91. beforeEnter: router.auth
  92. },
  93. {
  94. path: 'component',
  95. component: ContentComponent,
  96. beforeEnter: router.auth
  97. },
  98. {
  99. path: 'template',
  100. component: ContentTemplate,
  101. beforeEnter: router.auth
  102. },
  103. {
  104. path: 'extension',
  105. component: ContentExtension,
  106. beforeEnter: router.auth
  107. },
  108. {
  109. path: 'comment',
  110. component: ContentComment,
  111. beforeEnter: router.auth
  112. }
  113. ]
  114. })
  115. }
  116. }

Core.router 的调用者,为该方法提供了一个 router 对象,该 router 对象中包含如下特性:

  • auth: 后台登录验证中间件
  • bases: 基础路由定义
  • modules: 模块路由定义

侧边栏菜单注入

侧边栏菜单注入,提供了扩展管理子级菜单的注入,由 Core.sidebar 提供注入,代码参考如下:

```ecmascript 6 export default { sidebar: function (sidebar) { sidebar.push({ text: ‘多说评论’, icon: ‘fa fa-comment’, uri: ‘/duoshuo’ }) } }

  1. ## 前端扩展构建和打包
  2. 在进行代码编写和相关配置之后,使用命令 npm run build 即可完成对扩展模块的打包。
  3. ## 前端资源注入
  4. 通过前端工具构建和打包后,可以得到前端静态资源文件(js文件,css文件,图片文件等),可以模块中的类 ModuleServiceProvider 或扩展中的类 Extension 中将静态资源文件发布到 public 目录下。
  5. ModuleServiceProvider 的代码参考如下:
  6. ```php
  7. <?php
  8. /**
  9. * This file is part of Notadd.
  10. *
  11. * @author TwilRoad <heshudong@ibenchu.com>
  12. * @copyright (c) 2016, notadd.com
  13. * @datetime 2016-10-08 17:12
  14. */
  15. namespace Notadd\Content;
  16. use Illuminate\Support\ServiceProvider;
  17. /**
  18. * Class Module.
  19. */
  20. class ModuleServiceProvider extends ServiceProvider
  21. {
  22. /**
  23. * Boot service provider.
  24. */
  25. public function boot()
  26. {
  27. $this->publishes([
  28. realpath(__DIR__ . '/../resources/mixes/administration/dist/assets/content/administration') => public_path('assets/content/administration'),
  29. realpath(__DIR__ . '/../resources/mixes/foreground/dist/assets/content/foreground') => public_path('assets/content/foreground'),
  30. ], 'public');
  31. }
  32. }

然而,这样并没有结束,仍然需要告诉 Administration 模块你提供了哪些静态资源文件,给后台的前端页面使用。

在模块中的类 ModuleServiceProvider 或扩展中的类 Extension 中提供了相应注入点,script 方法将告诉后台的前端页面引用前面打包生成的 UMD 模块文件,stylesheet 方法将告诉后台的前端页面引用前面打包生成样式文件。

具体代码参考如下:

  1. <?php
  2. /**
  3. * This file is part of Notadd.
  4. *
  5. * @author TwilRoad <heshudong@ibenchu.com>
  6. * @copyright (c) 2016, notadd.com
  7. * @datetime 2016-10-08 17:12
  8. */
  9. namespace Notadd\Content;
  10. use Illuminate\Support\ServiceProvider;
  11. /**
  12. * Class Module.
  13. */
  14. class ModuleServiceProvider extends ServiceProvider
  15. {
  16. /**
  17. * Boot service provider.
  18. */
  19. public function boot()
  20. {
  21. }
  22. /**
  23. * Get script of extension.
  24. *
  25. * @return string
  26. * @throws \Illuminate\Contracts\Container\BindingResolutionException
  27. */
  28. public static function script()
  29. {
  30. return asset('assets/content/administration/js/module.js');
  31. }
  32. /**
  33. * Get stylesheet of extension.
  34. *
  35. * @return array
  36. * @throws \Illuminate\Contracts\Container\BindingResolutionException
  37. */
  38. public static function stylesheet()
  39. {
  40. return [
  41. asset('assets/content/administration/css/module.css'),
  42. ];
  43. }
  44. }

如何通过插件或模块的方式对后台首页进行模块注入

Notadd 后台管理的首页(Dashboard),是可以通过插件或扩展,进行添加自定义模块的。

支持的自定义模块类型

  • html(自定义原生 HTML 代码)
  • text(字符串文本)
  • button(普通按钮或带链接的按钮)
  • chart(自定义图标,基于百度图标)

如何用Vue2写出web单页应用

Notadd 的前端项目均基于 VueJS 2 构建,详细文档请参见官方文档