开发扩展
<h1>开发扩展</h1>
<p><code>Dcat Admin</code>支持安装扩展工具来帮助丰富你的后台功能。
>需要注意的是,<code>Laravel Admin</code>原有的扩展无法直接在<code>Dcat Admin</code>中使用,但大部分扩展只需要做一些微小的调整就可以正常使用了,有兴趣的同学可以自行移植。</p>
<p>如果大家在使用的过程中有在<code>Dcat Admin</code>的基础上添加一些自己的功能或者组件,不妨做成一个<code>Dcat Admin</code>扩展,这样可以给其它<code>Dcat Admin</code>使用者提供帮助,并且在其它人的使用反馈中的提升扩展的质量。</p>
<p>这篇文档将会以开发一个 <a href="https://github.com/dcat-admin/operation-log">操作日志扩展</a> 为例,一步一步的教大家开发一个扩展,并且发布给他人使用,最终的效果参考<a href="https://github.com/dcat-admin/operation-log">操作日志扩展</a>。</p>
<h2>开始之前</h2>
<p>在开始开发扩展之前,如果是<code>linux</code>环境,请先手动在项目根目录创建一个<code>dcat-admin-extensions</code>目录,并设置可读<strong>可写</strong>权限,扩展文件将会被安装在<code>dcat-admin-extensions</code>目录,请保证拥有以下几个目录的读写权限</p>
<ol>
<li><code>项目目录/dcat-admin-extensions</code> 扩展的安装目录,可以根据配置参数 <code>admin.extensions.dir</code> 进行更改</li>
<li><code>public/vendor</code> 扩展静态资源发布目录</li>
<li><code>resources/lang</code> 语言包目录</li>
</ol>
<h2>1.创建扩展</h2>
<p><code>Dcat Admin</code>的扩展是一个标准的<code>composer</code>扩展包,可以通过<code>composer</code>安装,也可以通过系统内部的<code>本地安装</code>功能直接安装。我们可以通过命令或界面创建一个新的扩展,下面分别简单介绍一下命令和界面创建的方法</p>
<p>1.通过命令创建扩展 </p>
<pre><code class="language-bash">php artisan admin:ext-make 扩展包名 --namespace=命名空间 --theme</code></pre>
<p>命令参数说明</p>
<ul>
<li><code>name</code> 扩展包名称,扩展的名称是一个标准的<code>composer</code>包名,请统一使用<strong>小写字母</strong> + <strong>中划线(-)</strong>的风格命名,标准的格式如 <code>dcat-admin/operation-log</code>,前面一部分可以是个人名称,后面一部分可以是对扩展包功能的概括词语</li>
<li><code>--namespace=</code> 扩展包命名空间,默认会根据包名自动生成,例如你的包名是<code>jiangqh/operation-log</code>,那么默认的命名空间是<code>Jiangqh/OperationLog</code></li>
<li><code>--theme</code> 是否为主题扩展</li>
</ul>
<p>那么在当前这个例子中我们运行一下命令生成扩展包</p>
<pre><code class="language-php"># `--namespace`
php artisan admin:ext-make dcat-admin/operation-log --namespace=&quot;Dcat\Admin\OperationLog&quot;</code></pre>
<p>2.通过管理页面创建扩展</p>
<p>打开扩展管理页面<code>http://localhost/admin/auth/extensions</code>,然后点击表格下面第一行的<strong>快速创建</strong>,然后输入扩展包名和命名空间即可,在实际开发中也更推荐大家使用界面创建扩展,这样更方便</p>
<p>扩展创建完成之后可以看到扩展文件夹下多了个<code>dcat-admin/extensions/dcat-admin/operation-log</code>目录,目录结构如下</p>
<pre><code class="language-php">├── README.md
├── composer.json # composer配置文件
├── version.php # 扩展包版本管理文件
├── logo.png # logo
├── updates # 扩展包每个版本对应的表迁移文件
├── resources
│ ├── lang # 语言包
│ ├── assets # 静态资源
│ │ ├── css
│ │ │ └── index.css # css示例文件
│ │ └── js
│ │ └── index.js # js示例文件
│ └── views
│ └── index.blade.php # 视图示例文件
└── src
├── OperationLogServiceProvider.php # 扩展包服务提供者
├── Setting.php # 扩展设置表单
├── Models # 模型目录
└── Http
├── routes.php # 扩展包路由文件
├── Middleware # 扩展包中间件文件夹
└── Controllers # 扩展包控制器
└── OperationLogController.php</code></pre>
<p>然后你还可以设置扩展的<code>logo</code>以及扩展名称,设置之后会在扩展管理页面展示,让你的扩展更具容易被人记住!</p>
<p><a name="logo"></a></p>
<h3>扩展logo</h3>
<p>扩展<code>logo</code>必须放在扩展的根目录,并且文件名必须是<code>logo.png</code>,尺寸建议是<code>100x100</code>。</p>
<h3>扩展名称</h3>
<p>扩展名称需要修改<code>composer.json</code>里面的<code>alias</code>参数,如果不设置则默认展示包名</p>
<h2>2.启用扩展</h2>
<p>扩展创建成功之后就可以在管理页面<code>http://localhost/admin/auth/extensions</code> 看到新创建的扩展了,效果如下</p>
<p><a href="{{public}}/assets/img/2x/ext-1.png" target="_blank">
<img src="{{public}}/assets/img/2x/ext-1.png" alt="" />
</a></p>
<p>然后我们分别点击扩展对应的 <code>更新至1.0.0版本</code> 以及 <code>启用</code> 按钮,就可以使这个扩展生效了。
新创建的扩展会生成一个默认的控制器,在这个例子中,我们可以尝试访问<code>http://localhost:8000/admin/operation-log</code>,如果可以正常访问则说明扩展启用成功。</p>
<h2>3.功能开发</h2>
<p>这个扩展的功能主要是用来记录用户的操作记录,然后提供一个查看记录的页面,然后我们可以把默认创建的扩展文件中用不到的给清理掉,清理后的目录结构如下</p>
<pre><code class="language-php">├── README.md
├── composer.json # composer配置文件
├── version.php # 扩展包版本管理文件
├── logo.png # logo
├── updates # 扩展包每个版本对应的表迁移文件
├── resources
│ └── lang # 语言包
└── src
├── OperationLogServiceProvider.php # 扩展包服务提供者
├── Setting.php # 扩展设置表单
├── Models # 模型目录
└── Http
├── routes.php # 扩展包路由文件
├── Middleware # 扩展包中间件文件夹
└── Controllers # 扩展包控制器
└── OperationLogController.php</code></pre>
<p>接下来就正式进入功能开发部分</p>
<h3>创建迁移文件 (migration)</h3>
<p>首先我们需要创建一个表迁移文件,运行命令 <code>php artisan make:migration CreateOperationLogTable</code>,然后写入文件内容如下</p>
<pre><code class="language-php">&lt;?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateOperationLogTable extends Migration
{
// 这里可以指定你的数据库连接
public function getConnection()
{
return config('database.connection') ?: config('database.default');
}
public function up()
{
if (! Schema::hasTable('admin_operation_log')) {
Schema::create('admin_operation_log', function (Blueprint $table) {
$table-&gt;bigIncrements('id')-&gt;unsigned();
$table-&gt;bigInteger('user_id');
$table-&gt;string('path');
$table-&gt;string('method', 10);
$table-&gt;string('ip');
$table-&gt;text('input');
$table-&gt;index('user_id');
$table-&gt;timestamps();
});
}
}
public function down()
{
Schema::dropIfExists('admin_operation_log');
}
}</code></pre>
<p>然后把文件移动到 <code>扩展目录/updates</code> 目录,并重命名为 <code>create_opration_log_table.php</code>。最后我们需要修改扩展的版本管理文件 <code>扩展目录/version.php</code>,写入迁移文件的文件名,最终内容如下</p>
<pre><code class="language-php">&lt;?php
return [
'1.0.0' =&gt; [
'版本变化描述1',
'版本变化描述2',
'create_opration_log_table.php', // 迁移文件名称,安装或更新版本时会自动执行
],
];</code></pre>
<p>关于扩展的版本管理更详细的介绍,请前往<a href="extension-f.md#version">扩展 - 版本管理</a>章节查看。</p>
<h3>模型、控制器和路由</h3>
<p>创建模型文件 <code>扩展目录/src/Models/OperationLog</code>,模型内容点击<a href="https://github.com/dcat-admin/operation-log/blob/master/src/Models/OperationLog.php">OperationLog.php</a>查看;</p>
<p>然后修改我们的控制器<code>扩展目录/src/Http/Controllers/OperationLogController.php</code>,控制器内容点击<a href="https://github.com/dcat-admin/operation-log/blob/master/src/Http/Controllers/LogController.php">LogController.php</a>查看;</p>
<p>最后需要修改我们的路由文件,尽量让你的路由不与其他路由产生冲突</p>
<pre><code class="language-php">use Dcat\Admin\OperationLog\Http\Controllers;
use Illuminate\Support\Facades\Route;
Route::get('auth/operation-logs', Controllers\OperationLogController::class.'@index')-&gt;name('dcat-admin.operation-log.index');
Route::delete('auth/operation-logs/{id}', Controllers\OperationLogController::class.'@destroy')-&gt;name('dcat-admin.operation-log.destroy');</code></pre>
<h3>语言包</h3>
<p>在控制器中,我们可以让一些文本描述支持语言包翻译功能,在这个例子中我们以<code>en</code>以及<code>zh_CN</code>两种语言为例,在<code>扩展包/resources/lang</code>目录下分别创建<code>en/log.php</code>和<code>zh_CN/log.php</code>文件,并写入以下内容</p>
<pre><code class="language-php">// en
return [
'title' =&gt; 'Operation Log',
'setting_title' =&gt; 'Operation Log',
];
// zh_CN
return [
'title' =&gt; '操作日志',
'setting_title' =&gt; '操作日志',
];</code></pre>
<p>最后在控制器中可以通过下面的方法访问语言包内容,关于多语言的更多用法可以参考laravel官方文档</p>
<pre><code class="language-php">use Dcat\Admin\OperationLog\OperationLogServiceProvider;
OperationLogServiceProvider:trans('log.title');
OperationLogServiceProvider:trans('log.setting_title');</code></pre>
<h3>定义菜单</h3>
<p>接下来我们还需要给我们的扩展生成菜单,打开<code>扩展目录/src/OperationLogServiceProvider.php</code>,然后修改内容如下</p>
<pre><code class="language-php">class OperationLogServiceProvider extends ServiceProvider
{
// 定义菜单
protected $menu = [
[
'title' =&gt; 'Operation Log',
'uri' =&gt; 'auth/operation-logs',
'icon' =&gt; '', // 图标可以留空
],
];
public function settingForm()
{
return new Setting($this);
}
}</code></pre>
<p>如果你想注册带有层级关系的菜单,可以通过下面这种方式注册</p>
<pre><code class="language-php">// 注册菜单
protected $menu = [
[
'title' =&gt; 'Operation Log',
'uri' =&gt; '',
'icon' =&gt; 'feather icon-x',
],
[
'parent' =&gt; 'Operation Log', // 指定父级菜单
'title' =&gt; 'List',
'uri' =&gt; 'auth/operation-logs',
],
];</code></pre>
<h3>测试扩展</h3>
<p>上面的步骤都完成之后,我们可以先开始测试一下上面的功能,验证下是否有错误,然后再进行后续的开发。</p>
<p>由于刚开始创建扩展的时候,我们已经安装并启用了这个扩展,所以此处我们要先<strong>卸载</strong>当前扩展,再重新更新到<code>1.0.0</code>版本,这样数据表和菜单才会被创建。</p>
<p>> {tip} <strong>卸载</strong>功能会删除扩展的数据或数据表,请谨慎操作,以免造成数据丢失!!!</p>
<p>打开扩展管理页面<code>http://域名/admin/auth/extensions</code>,找到当前扩展,鼠标移动到扩展行,点击 <code>卸载</code> 按钮并确认,然后重新点击<code>更新至1.0.0版本</code> 以及 <code>启用</code> 按钮,
最后 <code>F5</code> 刷新浏览器即可看到新创建的菜单,点击菜单可访问操作日志管理页面<code>admin/auth/operation-logs</code>。</p>
<h3>注册中间件</h3>
<p>现在我们的扩展还需要一个中间件来记录用户的操作,创建文件 <code>扩展目录/src/Http/Middleware/LogOperation.php</code>,文件内容点击<a href="https://github.com/dcat-admin/operation-log/blob/master/src/Http/Middleware/LogOperation.php">LogOperation.php</a>查看。</p>
<p>然后我们需要注册这个中间件,让这个中间件生效,打开<code>扩展目录/src/OperationLogServiceProvider.php</code>,然后修改内容如下</p>
<pre><code class="language-php">class OperationLogServiceProvider extends ServiceProvider
{
protected $middleware = [
'middle' =&gt; [ // 注册中间件
LogOperation::class,
],
];
protected $menu = [
[
'title' =&gt; 'Operation Log',
'uri' =&gt; 'auth/operation-logs',
],
];
public function settingForm()
{
return new Setting($this);
}
}</code></pre>
<p><code>$middleware</code>属性中注册的中间件最后会合并到配置参数<code>admin.route.middleware</code>中,中间件注册支持以下三种类型注册</p>
<ol>
<li><code>before</code> 中间件会在最前面,也就是最先执行</li>
<li><code>middle</code> 中间件会在<code>admin.auth</code>(登陆鉴权中)和<code>admin.permission</code>(权限判断)两个中间件<strong>之间</strong>执行</li>
<li><code>after</code> 中间件会在最后执行</li>
</ol>
<p>在这个例子中,操作日志记录用户操作时显然需要记录登陆用户的信息,所以中间件必须在 <code>admin.auth</code> 中间件之后执行,这样才能拿到登陆用户数据;
并且无权限的操作也需要记录,所以必须在 <code>admin.permission</code> 中间件之前执行,所以只有注册为 <code>middle</code> 类型的中间件才能满足上述要求!</p>
<p>注册完中间件之后,我们随意访问一下系统中的其他页面(除了操作日志管理页面),然后再访问操作日志管理页面,就可以看到用户的操作日志了,到这里插件基本开发完毕。</p>
<h3>配置参数(设置)</h3>
<p>在当前这个例子中,我们需要让用户能配置一些自定义参数(比如配置不需要记录操作日志的路由),所以我们还需要一个 <code>配置表单</code> 让用户能通过页面直接配置相关参数,
我们需要在 <code>OperationLogServiceProvider</code> 类里面的 <code>settingForm</code> 返回这个配置表单的对象</p>
<pre><code class="language-php">class OperationLogServiceProvider extends ServiceProvider
{
...
// 返回配置表单对象,如果不需要保存配置参数,则请删除这个方法
public function settingForm()
{
return new Setting($this);
}
}</code></pre>
<p>然后我们需要修改配置表单类 <code>扩展目录/src/Setting.php</code> 如下</p>
<pre><code class="language-php">namespace Dcat\Admin\OperationLog;
use Dcat\Admin\Extend\Setting as Form;
use Dcat\Admin\OperationLog\Models\OperationLog;
use Dcat\Admin\Support\Helper;
class Setting extends Form
{
// 返回表单弹窗标题
public function title()
{
return $this-&gt;trans('log.title');
}
// 格式化待保存的配置参数值
protected function formatInput(array $input)
{
// 转化为数组,注意如果这里保存的时候是数组,那么读取出来的时候也是数组
$input['except'] = Helper::array($input['except']);
$input['allowed_methods'] = Helper::array($input['allowed_methods']);
return $input;
}
public function form()
{
// 定义表单字段
$this-&gt;tags('except');
$this-&gt;multipleSelect('allowed_methods')
-&gt;options(array_combine(OperationLog::$methods, OperationLog::$methods));
$this-&gt;tags('secret_fields');
}
}</code></pre>
<p>以上设置完成之后我们就可以在扩展管理页面保存自定义参数了</p>
<p><a href="{{public}}/assets/img/2x/ext-2.png" target="_blank">
<img src="{{public}}/assets/img/2x/ext-2.png" alt="" />
</a></p>
<p>配置参数读取用法如下,我们可以在中间件 <code>LogOperation</code> 中使用这些参数</p>
<pre><code class="language-php">use Dcat\Admin\OperationLog\OperationLogServiceProvider;
// 读取配置参数
$except = OperationLogServiceProvider::setting('except');
$allowedMethods = OperationLogServiceProvider::setting('allowed_methods');
$secretFields = OperationLogServiceProvider::setting('secret_fields');</code></pre>
<h3>服务注册与初始化</h3>
<p>由于当前这个例子中没有使用到服务注册与初始化相关功能,所以这部分内容先略过,有相关需要的同学可以参考<a href="extension-f.md#service">扩展 - 服务注册与初始化</a>章节。</p>
<h3>视图 (view)</h3>
<p>由于当前这个例子中没有使用到自定义静态资源的功能,所以这部分内容先略过,有相关需要的同学可以参考<a href="extension-f.md#view">扩展 - 视图</a>章节。</p>
<h3>静态资源</h3>
<p>由于当前这个例子中没有使用到自定义静态资源的功能,所以这部分内容先略过,有相关需要的同学可以参考<a href="extension-f.md#assets">扩展 - 静态资源</a>章节。</p>
<h3>修改 composer.json & README.md</h3>
<p>代码部分完成之后,需要修改<code>composer.json</code>里面的内容,将<code>description</code>、<code>keywords</code>、<code>license</code>、<code>authors</code>等内容替换为你的信息,然后不要忘记完善<code>README.md</code>,补充使用文档等相关信息。</p>
<h3>发布扩展</h3>
<h4>上传应用市场</h4>
<p>正式版发布后会上线应用市场功能,开发者可以把扩展发布到应用市场,然后用户就可以通过页面直接安装使用,敬请期待...</p>
<p><a name="github"></a></p>
<h4>上传到Github</h4>
<p>先登录你的Github,创建一个仓库,然后按照页面上的提示把你的代码push上去</p>
<pre><code>git init
git remote add origin https://github.com/&lt;your-name&gt;/&lt;your-repository&gt;.git
git add .
git commit -am &quot;Initial commit.&quot;
git push origin master</code></pre>
<p><a name="packagist"></a></p>
<h4>发布到Packagist.org</h4>
<p>接下来就是发布你的项目到<code>Packagist.org</code>,如果没有账号的话,先注册一个,然后打开顶部导航的<code>Submit</code>, 填入仓库地址提交</p>
<p>默认情况下,当您推送新代码时,<code>Packagist.org</code>不会自动更新,所以,您需要创建一个GitHub服务钩子, 你也可以使用点击页面上的<code>Update</code>按钮手动更新它,但我建议自动执行这个过程</p>
<p>提交之后,由于各地的镜像同步时间的延迟,可能在用 <code>composer</code> 安装的时候,会暂时找不到你的项目,这个时候可能需要等待同步完成</p>
<p>发布完成之后就可以通过 <code>composer</code> 安装你的扩展了</p>