如何基于 Notadd 构建 API
Notadd 底层实现了 passport 机制,有统一的授权管理,主要支持两种方式进行 API 授权,一个是 client,另一个是 passport,这个在其他文档中有做详细的说明。
这里主要说的是,如何基于 Notadd 进行 API 接口的开发。
业务逻辑
熟悉 Laravel 的同学都应该知道,Laravel 遵循这样的业务逻辑实现:
路由(route) -> 控制器(controller) -> 业务逻辑(model) -> 数据输出(view)
而 Notadd 的 API 业务逻辑实现同样遵循类似的流程:
路由(route) -> 控制器(controller) -> API 处理器(handler) -> 模型(model) -> 数据输出(json)
其中,主要的差异在于,API 处理器提供了对数据输出格式的输出,返回的数据格式统一为:
['code' => 200, // API 接口返回的状态码,默认为 200'data' => [], // API 接口返回的数据,主要为数组形式'message' => 'success!', // API 接口返回的提示信息,可以包含错误信息或成功信息]
路由
Notadd 在实现 API 授权的时候,使用的是有 路由中间件(middleware) 的方式来实现的。
具体实现方式,是在路由的中间配置参数中添加 auth:api 。
例如,在实现 api/setting/all 和 api/setting/set 两个 API 的时候,添加 auth:api 的中间件,代码参考如下:
$this->router->group(['middleware' => ['auth:api', 'web'], 'prefix' => 'api/setting'], function () {$this->router->post('all', 'Notadd\Foundation\Setting\Controllers\SettingController@all');$this->router->post('set', 'Notadd\Foundation\Setting\Controllers\SettingController@set');});
Notadd 针对需要跨域的 API 还提供了 cross 的路由中间件,以实现 API 跨域的功能。
例如,为前两个 API 提供跨域的功能实现,代码参考如下:
$this->router->group(['middleware' => ['auth:api', 'cross', 'web'], 'prefix' => 'api/setting'], function () {$this->router->post('all', 'Notadd\Foundation\Setting\Controllers\SettingController@all');$this->router->post('set', 'Notadd\Foundation\Setting\Controllers\SettingController@set');});
控制器
由于有了独立的 API处理器 ,控制器层可以制作简单处理,仅需向控制器注入 handler,并由 handler 提供的辅助方法返回 API 数据给前台,即可。
例如,在前面路由调用的 SettingController 中,仅需要注入 AllHandler ,使用方法 toResponse 和 generateHttpResponse 来返回结果给前台,代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-11-08 17:01*/namespace Notadd\Foundation\Setting\Controllers;use Notadd\Foundation\Routing\Abstracts\Controller;use Notadd\Foundation\Setting\Contracts\SettingsRepository;use Notadd\Foundation\Setting\Handlers\AllHandler;use Notadd\Foundation\Setting\Handlers\SetHandler;/*** Class SettingController.*/class SettingController extends Controller{/*** @var \Notadd\Foundation\Setting\Contracts\SettingsRepository*/protected $settings;/*** SettingController constructor.** @param \Notadd\Foundation\Setting\Contracts\SettingsRepository $settings** @throws \Illuminate\Contracts\Container\BindingResolutionException*/public function __construct(SettingsRepository $settings){parent::__construct();$this->settings = $settings;}/*** All handler.** @param \Notadd\Foundation\Setting\Handlers\AllHandler $handler** @return \Notadd\Foundation\Routing\Responses\ApiResponse* @throws \Exception*/public function all(AllHandler $handler){return $handler->toResponse()->generateHttpResponse();}/*** Set handler.** @param \Notadd\Foundation\Setting\Handlers\SetHandler $handler** @return \Notadd\Foundation\Routing\Responses\ApiResponse* @throws \Exception*/public function set(SetHandler $handler){return $handler->toResponse()->generateHttpResponse();}}
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 的代码如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-11-23 14:44*/namespace Notadd\Foundation\Setting\Handlers;use Illuminate\Container\Container;use Notadd\Foundation\Passport\Abstracts\DataHandler;use Notadd\Foundation\Setting\Contracts\SettingsRepository;/*** Class AllHandler.*/class AllHandler extends DataHandler{/*** @var \Notadd\Foundation\Setting\Contracts\SettingsRepository*/protected $settings;/*** AllHandler constructor.** @param \Illuminate\Container\Container $container* @param \Notadd\Foundation\Setting\Contracts\SettingsRepository $settings*/public function __construct(Container $container,SettingsRepository $settings) {parent::__construct($container);$this->settings = $settings;}/*** Http code.** @return int*/public function code() // 定义 API 操作结果的状态码{return 200;}/*** Data for handler.** @return array*/public function data() // 定义 API 返回的数据{return $this->settings->all()->toArray();}/*** Errors for handler.** @return array*/public function errors() // 定义 API 操作失败时返回的信息{return ['获取全局设置失败!',];}/*** Messages for handler.** @return array*/public function messages() // 定义 API 操作成功时返回的信息{return ['获取全局设置成功!',];}}
SetHandler 的代码如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-11-23 15:09*/namespace Notadd\Foundation\Setting\Handlers;use Illuminate\Container\Container;use Notadd\Foundation\Passport\Abstracts\SetHandler as AbstractSetHandler;use Notadd\Foundation\Setting\Contracts\SettingsRepository;/*** Class SetHandler.*/class SetHandler extends AbstractSetHandler{/*** @var \Notadd\Foundation\Setting\Contracts\SettingsRepository*/protected $settings;/*** SetHandler constructor.** @param \Illuminate\Container\Container $container* @param \Notadd\Foundation\Setting\Contracts\SettingsRepository $settings*/public function __construct(Container $container,SettingsRepository $settings) {parent::__construct($container);$this->settings = $settings;}/*** Data for handler.** @return array*/public function data() // 定义 API 返回的数据{return $this->settings->all()->toArray();}/*** Errors for handler.** @return array*/public function errors() // 定义 API 操作失败时返回的信息{return ['修改设置失败!',];}/*** Execute Handler.** @return bool*/public function execute() // 定义 API 执行的修改操作{$this->settings->set('site.enabled', $this->request->input('enabled'));$this->settings->set('site.name', $this->request->input('name'));$this->settings->set('site.domain', $this->request->input('domain'));$this->settings->set('site.beian', $this->request->input('beian'));$this->settings->set('site.company', $this->request->input('company'));$this->settings->set('site.copyright', $this->request->input('copyright'));$this->settings->set('site.statistics', $this->request->input('statistics'));return true;}/*** Messages for handler.** @return array*/public function messages() // 定义 API 操作成功时返回的信息{return ['修改设置成功!',];}}
数据输出
API 结果的数据输出,已经在 控制器(controller) 中做了处理。
至此,一个完整的 API 开发完成。
如何开发一个 Notadd 模块
模块代码结构请参考项目 https://github.com/notadd/content
模块将包含如下几个部分:
- 模块注入
- 路由注入
- 门面注入
- CSRF注入
模块安装
对于模块的安装,目前 Notadd 支持的安装方式,仅为在 根项目 中对 模块 进行依赖。
例如,需要安装模块 notadd/content ,可以修改根项目的文件 composer.json,参考代码如下:
{"name": "notadd/notadd","description": "The Notadd Framework.","keywords": ["notadd","cms","foundation","framework"],"homepage": "https://notadd.com","license": "Apache-2.0","type": "notadd-module","authors": [{"name": "twilroad","email": "heshudong@ibenchu.com"}],"autoload": {"classmap": ["storage/databases"]},"require": {"php": ">=7.0","notadd/content": "0.1.*","wikimedia/composer-merge-plugin": "dev-master"},"require-dev": {"fzaninotto/faker": "~1.4","mockery/mockery": "0.9.*","phpunit/phpunit": "~5.0","symfony/css-selector": "3.1.*","symfony/dom-crawler": "3.1.*"},"config": {"preferred-install": "dist"},"scripts": {"post-create-project-cmd": ["php notadd key:generate"],"post-install-cmd": ["Notadd\\Foundation\\Composer\\ComposerScripts::postInstall","php notadd optimize"],"post-update-cmd": ["Notadd\\Foundation\\Composer\\ComposerScripts::postUpdate","php notadd optimize"]},"extra": {"merge-plugin": {"include": ["extensions/*/*/composer.json"],"recurse": true,"replace": false,"merge-dev": false}}}
更新文件 composer.json 后,执行 composer update 即可完成对模块的安装。
完整示例代码,请参考项目 https://github.com/notadd/notadd
目录结构
目录结构如下:
# module 模块目录# resources 资源目录# translations 翻译文件目录# views 视图目录# src 源码目录# ModuleServiceProvider.php 模块服务提供者定义文件# composer.json Composer 配置文件
一个 Notadd 的模块,是一个符合 composer 规范的包,所以,模块对第三方代码有依赖时,可以在 composer.json 中的 require 节点中添加第三方的包。
而作为一个符合 Notadd 模块定义规范的包,composer.json 需拥有如下信息:
- type 必须为 notadd-module
- require 中必须添加包 notadd/installers
代码参考如下(来自插件根目录下的文件 composer.json, 文件中不应该包含 // 的注释信息,此处仅作为说明)
{"name": "notadd/content","description": "Notadd's Content Module.","keywords": ["notadd","cms","framework","content"],"homepage": "https://notadd.com","license": "Apache-2.0","type": "notadd-module", // type 必须设置为 notadd-module"authors": [{"name": "Notadd","email": "notadd@ibenchu.com"}],"require": {"php": ">=7.0","notadd/installers": "0.5.*" // 必须依赖包 notadd/installers},"autoload": {"psr-4": {"Notadd\\Content\\": "src/"}}}
模块注入
所谓 模块注入 ,是 Notadd 在加载模块的时候,会检索模块目录下的类 ModuleServiceProvider,此类必须命名为 ModuleServiceProvider,且需放在源码根目录中,且命名空间必须为 composer.json 的中 autoload 节点定义的符合 psr-4 规范的命名空间,否则 Notadd 将不能正确加载模块!
类 ModuleServiceProvider 的父类必须为 Illuminate\Support\ServiceProvider ,且必须包含 boot 方法。相关服务提供者的概念可以参考 Laravel 文档。
类 ModuleServiceProvider 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-10-08 17:12*/namespace Notadd\Content;use Illuminate\Events\Dispatcher;use Illuminate\Support\ServiceProvider;use Notadd\Content\Listeners\RouteRegister;/*** Class Module.*/class ModuleServiceProvider extends ServiceProvider{/*** Boot service provider.*/public function boot(){$this->app->make(Dispatcher::class)->subscribe(RouteRegister::class); // 订阅事件 RouteRegister}}
路由注入
由于 Notadd 的路由注入,需要实现事件 RouteRegister,并在事件监听中添加 路由 。
所以,所谓的路由注入,实际是在类 ModuleServiceProvider 实现事件 RouteRegister 的订阅,并在事件订阅类中注册模块所需要的路由。
类 ModuleServiceProvider 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-10-08 17:12*/namespace Notadd\Content;use Illuminate\Support\ServiceProvider;/*** Class Module.*/class ModuleServiceProvider extends ServiceProvider{/*** Boot service provider.*/public function boot(){}}
事件订阅类 RouteRegister 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-10-08 18:30*/namespace Notadd\Content\Listeners;use Notadd\Content\Controllers\Api\Article\ArticleController as ArticleApiController;use Notadd\Foundation\Routing\Abstracts\RouteRegister as AbstractRouteRegister;/*** Class RouteRegister.*/class RouteRegister extends AbstractRouteRegister{/*** Handle Route Registrar.*/public function handle(){$this->router->group(['middleware' => ['cross', 'web']], function () { // 路由的注册$this->router->group(['prefix' => 'api/article'], function () {$this->router->post('find', ArticleApiController::class . '@find');$this->router->post('fetch', ArticleApiController::class . '@fetch');});});}}
门面注入
门面,是 Laravel 的一个功能特色,可以通过门面调用对应 IoC 容器的实例,所以 Notadd 必然会保留这一功能。
所谓的路由注入,实际是在类 ModuleServiceProvider 实现事件 FacadeRegister 的订阅,并在事件订阅类中注册模块所需要的路由。
类 ModuleServiceProvider 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-10-08 17:12*/namespace Notadd\Content;use Illuminate\Events\Dispatcher;use Illuminate\Support\ServiceProvider;use Notadd\Content\Listeners\FacadeRegister;/*** Class Module.*/class ModuleServiceProvider extends ServiceProvider{/*** Boot service provider.*/public function boot(){$this->app->make(Dispatcher::class)->subscribe(FacadeRegister::class);}}
事件订阅类 FacadeRegister 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2017, notadd.com* @datetime 2017-01-22 12:20*/namespace Notadd\Content\Listeners;use Notadd\Foundation\Event\Abstracts\EventSubscriber;use Notadd\Foundation\Facades\FacadeRegister as FacadeRegisterEvent;/*** Class FacadeRegister.*/class FacadeRegister extends EventSubscriber{/*** Name of event.** @throws \Exception* @return string|object*/protected function getEvent(){return FacadeRegisterEvent::class;}/*** Event handler.** @param $event*/public function handle(FacadeRegisterEvent $event){$event->register('Log', 'Illuminate\Support\Facades\Log');}}
CSRF注入
所谓的CSRF注入,实际是在类 ModuleServiceProvider 实现事件 CsrfTokenRegister 的订阅,并在事件订阅类中注册模块所需要的路由。
类 ModuleServiceProvider 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-10-08 17:12*/namespace Notadd\Content;use Illuminate\Events\Dispatcher;use Illuminate\Support\ServiceProvider;use Notadd\Content\Listeners\CsrfTokenRegister;/*** Class Module.*/class ModuleServiceProvider extends ServiceProvider{/*** Boot service provider.*/public function boot(){$this->app->make(Dispatcher::class)->subscribe(CsrfTokenRegister::class);}}
事件订阅类 CsrfTokenRegister 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2017, notadd.com* @datetime 2017-02-10 11:04*/namespace Notadd\Content\Listeners;use Notadd\Foundation\Event\Abstracts\EventSubscriber;use Notadd\Foundation\Http\Events\CsrfTokenRegister as CsrfTokenRegisterEvent;/*** Class CsrfTokenRegister.*/class CsrfTokenRegister extends EventSubscriber{/*** Name of event.** @throws \Exception* @return string|object*/protected function getEvent(){return CsrfTokenRegisterEvent::class;}/*** Register excepts.** @param $event*/public function handle(CsrfTokenRegisterEvent $event){$event->registerExcept('api/article*');$event->registerExcept('api/category*');$event->registerExcept('api/content*');$event->registerExcept('api/page*');}}
如何开发一个 Notadd 插件
插件代码结构请参考项目 http://git.oschina.net/notadd/duoshuo
插件将包含如下几个部分:
- 插件注入
- 路由注入
- CSRF注入
插件安装
对于 插件 的安装,仅需将插件文件按照下面的目录结构,放置到插件根目录 extensions (wwwroot/extensions)下,然后执行命令 composer update 即可。
目录结构
目录结构如下:
# wwwroot/extensions 插件根目录# vendor 厂商目录(目录名称仅为示例,开发时自行修改)# extension 插件目录(目录名称仅为示例,开发时自行修改)# configuations 可加载配置文件目录# resources 资源目录# translations 翻译文件目录# views 视图目录# src 源码目录# Extension 插件服务提供者定义文件# composer.json Composer 配置文件
一个 Notadd 的插件,是一个符合 composer 规范的包,所以,插件对第三方代码有依赖时,可以在 composer.json 中的 require 节点中添加第三方的包。
而作为一个符合 Notadd 插件定义规范的包,composer.json 需拥有如下信息:
- type 必须为 notadd-extension
- require 中必须添加包 notadd/installers
代码参考如下(来自插件根目录下的文件 composer.json, 文件中不应该包含 // 的注释信息,此处仅作为说明)
{"name": "notadd/duoshuo","description": "Notadd Extension for Duoshuo.","type": "notadd-extension", // type 必须设置为 notadd-extension"keywords": ["notadd", "duoshuo", "extension"],"homepage": "https://notadd.com","license": "Apache-2.0","authors": [{"name": "Notadd","email": "notadd@ibenchu.com"}],"autoload": {"psr-4": {"Notadd\\Duoshuo\\": "src/"}},"require": {"php": ">=7.0","notadd/installers": "0.5.*" // 必须依赖包 notadd/installers}}
插件注入
所谓 插件注入 ,是 Notadd 在加载插件的时候,会检索插件目录下的类 Extension,此类必须命名为 Extension,且需放在源码根目录中,且命名空间必须为 composer.json 的中 autoload 节点定义的符合 psr-4 规范的命名空间,否则 Notadd 将不能正确加载插件!
类 Extension 的父类必须为 Notadd\Foundation\Addon\Abstracts\Extension ,且必须包含 boot 方法。
类 Extension 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2017, notadd.com* @datetime 2017-02-21 11:28*/namespace Notadd\Duoshuo;use Notadd\Foundation\Addon\Abstracts\Extension as AbstractExtension;/*** Class Extension.*/class Extension extends AbstractExtension{/*** Boot provider.*/public function boot(){}}
路由注入
由于 Notadd 的路由注入,需要实现事件 RouteRegister,并在事件监听中添加 路由 。
所以,所谓的路由注入,实际是在类 Extension 实现事件 RouteRegister 的订阅,并在事件订阅类中注册插件所需要的路由。
类 Extension 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2017, notadd.com* @datetime 2017-02-21 11:28*/namespace Notadd\Duoshuo;use Illuminate\Events\Dispatcher;use Notadd\Duoshuo\Listeners\RouteRegister;use Notadd\Foundation\Addon\Abstracts\Extension as AbstractExtension;/*** Class Extension.*/class Extension extends AbstractExtension{/*** Boot provider.*/public function boot(){$this->app->make(Dispatcher::class)->subscribe(RouteRegister::class);}}
事件订阅类 RouteRegister 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2017, notadd.com* @datetime 2017-02-21 11:50*/namespace Notadd\Duoshuo\Listeners;use Notadd\Duoshuo\Controllers\DuoshuoController;use Notadd\Foundation\Routing\Abstracts\RouteRegister as AbstractRouteRegister;/*** Class RouteRegister.*/class RouteRegister extends AbstractRouteRegister{/*** Handle Route Registrar.*/public function handle(){$this->router->group(['middleware' => ['auth:api', 'cross', 'web'], 'prefix' => 'api/duoshuo'], function () {$this->router->post('backup', DuoshuoController::class . '@backup');$this->router->post('configuration', DuoshuoController::class . '@configuration');$this->router->post('number', DuoshuoController::class . '@number');});}}
CSRF注入
所谓的CSRF注入,实际是在类 Extension 实现事件 CsrfTokenRegister 的订阅,并在事件订阅类中注册插件所需要的路由。
类 Extension 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2017, notadd.com* @datetime 2017-02-21 11:28*/namespace Notadd\Duoshuo;use Illuminate\Events\Dispatcher;use Notadd\Duoshuo\Listeners\CsrfTokenRegister;use Notadd\Foundation\Addon\Abstracts\Extension as AbstractExtension;/*** Class Extension.*/class Extension extends AbstractExtension{/*** Boot provider.*/public function boot(){$this->app->make(Dispatcher::class)->subscribe(CsrfTokenRegister::class);}}
事件订阅类 CsrfTokenRegister 的代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2017, notadd.com* @datetime 2017-02-23 19:38*/namespace Notadd\Duoshuo\Listeners;use Notadd\Foundation\Event\Abstracts\EventSubscriber;use Notadd\Foundation\Http\Events\CsrfTokenRegister as CsrfTokenRegisterEvent;/*** Class CsrfTokenRegister.*/class CsrfTokenRegister extends EventSubscriber{/*** Name of event.** @throws \Exception* @return string|object*/protected function getEvent(){return CsrfTokenRegisterEvent::class;}/*** Register excepts.** @param $event*/public function handle(CsrfTokenRegisterEvent $event){$event->registerExcept('api/duoshuo*');}}
如何开发一个 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 参数进行调整,主要体现:
- 必须定义 output 的 library 别名,此名称,必须与 捆绑 的模块或扩展项目中 composer.json 文件中定义的 name 完全一致,否则无法加载前端扩展
- 必须定义 output 的 libraryTarget 为 umd
配置代码参考如下(来自文件 build/webpack.prod.conf.js):
var path = require('path')var utils = require('./utils')var webpack = require('webpack')var config = require('../config')var merge = require('webpack-merge')var baseWebpackConfig = require('./webpack.base.conf')var HtmlWebpackPlugin = require('html-webpack-plugin')var ExtractTextPlugin = require('extract-text-webpack-plugin')var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')var env = config.build.envvar webpackConfig = merge(baseWebpackConfig, {module: {rules: utils.styleLoaders({sourceMap: config.build.productionSourceMap,extract: true})},output: {path: config.build.assetsRoot,filename: utils.assetsPath('js/extension.js'),library: 'notadd/content', // 必须定义 library 别名libraryTarget: "umd" // 必须定义 libraryTarget 为 umd},plugins: [new webpack.DefinePlugin({'process.env': env}),new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}),new ExtractTextPlugin({filename: utils.assetsPath('css/[name].css')}),new OptimizeCSSPlugin()]})if (config.build.productionGzip) {var CompressionWebpackPlugin = require('compression-webpack-plugin')webpackConfig.plugins.push(new CompressionWebpackPlugin({asset: '[path].gz[query]',algorithm: 'gzip',test: new RegExp('\\.(' +config.build.productionGzipExtensions.join('|') +')$'),threshold: 10240,minRatio: 0.8}))}if (config.build.bundleAnalyzerReport) {var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPluginwebpackConfig.plugins.push(new BundleAnalyzerPlugin())}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
如上代码所示,模块 Core 即为项目构建后的默认导出模块,在该示例中,使用了 mixin 特性,为模块增加 header,install,router的注入逻辑。## 扩展安装注入如上(默认导出模块)述说,installMixin 为模块 Core 注入了 Core.install 的实现,具体代码如下:```ecmascript 6export function installMixin (Core) {Core.install = function (Vue, Notadd) {Core.instance = NotaddvueMixin(Core, Vue)}}export function vueMixin (Core, Vue) {Core.http = Vue.http}
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’ }) } }
## 路由注入如上(默认导出模块)述说,routerMixin 为模块 Core 注入了 Core.router 的实现,具体代码如下:```ecmascript 6import ContentArticle from '../components/Article'import ContentArticleCreate from '../components/ArticleCreate'import ContentArticleDraft from '../components/ArticleDraft'import ContentArticleDraftEdit from '../components/ArticleDraftEdit'import ContentArticleEdit from '../components/ArticleEdit'import ContentArticleRecycle from '../components/ArticleRecycle'import ContentCategory from '../components/ArticleCategory'import ContentComment from '../components/Comment'import ContentComponent from '../components/Component'import ContentDashboard from '../components/Dashboard'import ContentExtension from '../components/Extension'import ContentLayout from '../components/Layout'import ContentPage from '../components/Page'import ContentPageCategory from '../components/PageCategory'import ContentPageCreate from '../components/PageCreate'import ContentPageEdit from '../components/PageEdit'import ContentTemplate from '../components/Template'import ContentTag from '../components/ArticleTag'export function routerMixin (Core) {Core.router = function (router) {router.modules.push({path: '/content',component: ContentLayout,children: [{path: '/',component: ContentDashboard,beforeEnter: router.auth},{path: 'article/all',component: ContentArticle,beforeEnter: router.auth},{path: 'article/create',component: ContentArticleCreate,beforeEnter: router.auth},{path: 'article/:id/draft',component: ContentArticleDraftEdit,beforeEnter: router.auth},{path: 'article/:id/edit',component: ContentArticleEdit,beforeEnter: router.auth},{path: 'article/category',component: ContentCategory,beforeEnter: router.auth},{path: 'article/tag',component: ContentTag,beforeEnter: router.auth},{path: 'article/recycle',component: ContentArticleRecycle,beforeEnter: router.auth},{path: 'article/draft',component: ContentArticleDraft,beforeEnter: router.auth},{path: 'page/all',component: ContentPage,beforeEnter: router.auth},{path: 'page/create',component: ContentPageCreate,beforeEnter: router.auth},{path: 'page/:id/edit',component: ContentPageEdit,beforeEnter: router.auth},{path: 'page/category',component: ContentPageCategory,beforeEnter: router.auth},{path: 'component',component: ContentComponent,beforeEnter: router.auth},{path: 'template',component: ContentTemplate,beforeEnter: router.auth},{path: 'extension',component: ContentExtension,beforeEnter: router.auth},{path: 'comment',component: ContentComment,beforeEnter: router.auth}]})}}
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’ }) } }
## 前端扩展构建和打包在进行代码编写和相关配置之后,使用命令 npm run build 即可完成对扩展模块的打包。## 前端资源注入通过前端工具构建和打包后,可以得到前端静态资源文件(js文件,css文件,图片文件等),可以模块中的类 ModuleServiceProvider 或扩展中的类 Extension 中将静态资源文件发布到 public 目录下。类 ModuleServiceProvider 的代码参考如下:```php<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-10-08 17:12*/namespace Notadd\Content;use Illuminate\Support\ServiceProvider;/*** Class Module.*/class ModuleServiceProvider extends ServiceProvider{/*** Boot service provider.*/public function boot(){$this->publishes([realpath(__DIR__ . '/../resources/mixes/administration/dist/assets/content/administration') => public_path('assets/content/administration'),realpath(__DIR__ . '/../resources/mixes/foreground/dist/assets/content/foreground') => public_path('assets/content/foreground'),], 'public');}}
然而,这样并没有结束,仍然需要告诉 Administration 模块你提供了哪些静态资源文件,给后台的前端页面使用。
在模块中的类 ModuleServiceProvider 或扩展中的类 Extension 中提供了相应注入点,script 方法将告诉后台的前端页面引用前面打包生成的 UMD 模块文件,stylesheet 方法将告诉后台的前端页面引用前面打包生成样式文件。
具体代码参考如下:
<?php/*** This file is part of Notadd.** @author TwilRoad <heshudong@ibenchu.com>* @copyright (c) 2016, notadd.com* @datetime 2016-10-08 17:12*/namespace Notadd\Content;use Illuminate\Support\ServiceProvider;/*** Class Module.*/class ModuleServiceProvider extends ServiceProvider{/*** Boot service provider.*/public function boot(){}/*** Get script of extension.** @return string* @throws \Illuminate\Contracts\Container\BindingResolutionException*/public static function script(){return asset('assets/content/administration/js/module.js');}/*** Get stylesheet of extension.** @return array* @throws \Illuminate\Contracts\Container\BindingResolutionException*/public static function stylesheet(){return [asset('assets/content/administration/css/module.css'),];}}
如何通过插件或模块的方式对后台首页进行模块注入
Notadd 后台管理的首页(Dashboard),是可以通过插件或扩展,进行添加自定义模块的。
支持的自定义模块类型
- html(自定义原生 HTML 代码)
- text(字符串文本)
- button(普通按钮或带链接的按钮)
- chart(自定义图标,基于百度图标)
如何用Vue2写出web单页应用
Notadd 的前端项目均基于 VueJS 2 构建,详细文档请参见官方文档 。
