WpGet:私有 WordPress 插件存储库





5.00/5 (7投票s)
使用 Angular 和 PHP slim 框架作为后端实现一个私有的 WordPress 存储库。
目录
- 1. 引言
- 2. 为什么我们需要 WordPress 的私有仓库
- 3. 功能需求:一个公共 WordPress 仓库应该是什么样的
- 4. 技术需求:如何实现一个私有 WordPress 仓库
- 5. 后端
- 6. 前端
- 7. WordPress 插件
- 8. 关注点
- 9. 结论
- 10. 参考
- 11. 历史记录
1. 引言
本文介绍了 WpGet 的设计和构建过程,这是一个本地部署的 WordPress 插件仓库。这是一个有趣的实现,可以作为面向 API 的 Web 应用程序的参考。它基于 PHP 技术栈,后端使用 Slim 框架,前端使用基于 Token 的身份验证的 Angular2。我将讨论后端和前端的设计,并深入探讨持续集成和交付到本地部署实例。
2. 为什么我们需要 WordPress 的私有仓库
WordPress 是世界上使用最广泛的 CMS,拥有最重要的插件系统。它可以定制超过 5000 个插件,覆盖了 60% 的 CMS 驱动网站(占所有网站的 29%)。开发者很容易在公共仓库创建和发布新插件。但是,如果我必须创建和维护一个私有插件呢?我说的是
- 具有高智力含量的插件,
- 尚未准备好公开的插件,可能专注于某些特定的业务场景
- 用于注入自定义插件依赖的自定义超级插件
目前,公共仓库提供了以下机会
- 制作免费插件
- 制作付费插件(但放在主仓库中,对所有愿意购买的人都可用)
- 手动安装你的插件
前两种选择是公共插件销售商最广泛采用的方式,而第三种是在代理商或公司中非常普遍的做法,他们有一些常用的插件在许多网站上重用。
WpGet
的出现提供了第四种机会。它被设计为一个私有仓库,您可以将插件推送到其中,并允许 WordPress 安装从该源更新,除了标准的平台。对于来自其他技术的人来说,WpGet
类似于 NuGet、Maven 或 NPM 包管理器。
下面是一个简单的图示,解释 WPGet
的工作原理。
我将所有代码托管在 Github 上,您也可以在那里下载最新版本。
3. 功能需求:一个公共 WordPress 仓库应该是什么样的
根据我的经验,我玩过许多包仓库(Java 的 Maven、PHP 的 Composer、.NET 的 Nuget 等……),我相信在 2018 年,每个人每天在开发过程中都会使用它们。然而,WordPress 插件呢?事实上,插件只是一个 zip 文件。一个 zip 文件包含实现基于 WordPress 标准功能的 PHP 文件,但从更高的角度来看,它仅仅是一个 zip 文件。因此,插件仓库可以被视为一个原始 zip 文件仓库。通过使用 Nexus 这样的现成产品,可以实现这一目的,它可以在原始仓库中存储版本化的 zip 文件。虽然这对于归档插件来说是可以的,但它在与 WordPress 本身的集成方面可能会遇到一些限制,因为没有插件可以集成这样的系统。在 Github 上搜索,有一些管理插件的 WordPress 项目使用了一些 http 仓库,但没有一个看起来足够成熟可以被视为标准,也没有一个 UI 可以让你了解幕后发生的事情。
为了更清楚地说明,我们这个项目需要
- 提供插件更新,保留所有软件包并提供给 WordPress 安装。使用基于 Token 的身份验证,所有过程都保持安全。
- 易于设置:设置必须简单。只需将捆绑包复制到服务器并完成几个步骤。插件集成非常容易,只需添加一个类。
- 面向 API:所有核心功能都通过 API 公开。这将允许您将交付过程与公司中的其他工具集成。
- UI 以获得控制:一个简单的 UI 显示已加载的软件包,并赋予您控制权。
- 服务器要求低:只需带有 PHP 和数据库的 Web 服务器。对于少量数据,使用 SQLite,甚至不需要数据库服务器。
- 版本控制:拥有一个存储和版本化 WordPress 插件的系统
- 可集成到开发流程中:能够使用推送脚本发布 WordPress 插件
- 为人而设计:如果您不喜欢 shell 命令,可以手动上传软件包
- 多租户:允许通过同一安装实例管理多个仓库的系统(例如,每个客户一个仓库)
- 安全:一个允许只有受信任的网站下载软件包的安全系统
4. 技术需求:如何实现一个私有 WordPress 仓库
驱动我设计的原则是
- 采用标准技术
- 面向 API 的应用程序
- 后端/前端完全分离,前端为 SPA
- 基于 Token 的身份验证
- 使用与 WordPress 兼容的技术以简化安装
- 以一键安装包的形式交付
为了保证技术的兼容性,我的后端选择了一个基于 Slim 框架的 PHP 应用程序。这是因为我希望使用一个简单轻量级的框架,因为应用程序设计为只管理少量数据库实体和一个非常简单的业务逻辑。顺便说一句,Slim 有很多可以集成的模块,所以应用程序的进一步扩展将不受任何限制。前端使用 Angular 应用程序实现,这是一个强大的解决方案。
5. 后端
5.1. 多个后端应用
WpGet
主要为小型应用程序而设计,例如开发一些插件并希望提供给客户的 Web 代理公司。基于这样的场景,可以很容易地想象应用程序的负载不会太重。用户实际上不需要一直操作 UI,发布软件包的频率不高(每天 1-2 次),并且客户只有在插件发生变化时才会更新 WordPress 安装。我们没有水晶球可以肯定地说,但在大多数 WpGet
应用中,性能和负载可能都不是问题。无论如何,我想设计一个可以未来扩展的架构,例如,如果我们想在 SaaS 环境中发布这个应用程序。虽然这个应用程序不使用除数据库和存储文件夹之外的会话或共享内存,但随着用户增长,它将很容易进行水平扩展。顺便说一句,我还想创建一个允许应用程序随着应用程序模块使用情况增长的结构。因此,我将这个整体拆分为三个组件
Auth
:提供身份验证的应用程序Api
:托管所有 UI 和 CRUD 操作 API 的应用程序catalog
:协调与 WordPress 和发布者集成的应用程序
这将允许将适当的资源分配给单个组件。此外,我们将有三个较小的应用程序,它们各自只有必需的依赖项,使它们更快、更易于部署。在常规的本地部署包中,所有应用程序都捆绑在一起以简化安装,但通过配置和服务器操作,我们可以轻松地迁移到更复杂的解决方案。
5.2. 自动化路由
Slim 框架是一个很好的解决方案,可以快速、简单地创建 REST 端点。
您可以在主应用程序文件中使用 lambda 函数,或者创建可调用类。
示例
$app->any('/mycontrollerpath', function ($request, $response, $args) {
// do stuff here
});
$app->any('/user', 'MyRestfulController');
class DynamicController
{
public function __invoke($request, $response, $args)
{
// do stuff here
}
}
与此同时,我不得不接受处理来自没有严格契约的服务(例如 WCF 或 Web API,为了更好地解释……)的非类型化数据,我想避免在主应用程序文件中列举所有可能的路由。我想到的最佳解决方案是声明一些类或注解,然后装饰控制器,然后在运行时扫描并动态使用这些信息。这将导致非常类似于 ASP.NET WebApi
引擎的东西,但我更喜欢找到一个更简单的解决方案,因为
- 这是一个非常非标准的解决方案
- PHP 没有“原生”的应用程序内存状态,所以我必须添加一些内存状态(例如,使用
memcache
)或者每次加载都浪费资源。
我找到的解决方案既简单又非常高效
- 我创建了一个名为
DynamicController
的基类控制器。这个控制器实现了Invoke
方法,并将接收所有操作调用,然后分派到同名且具有相同 HTTP 方法的方法。这是基于命名约定,所以您将有 /mycontroller/myaction get 调用由Mycontroller
类中的getMyAction
方法处理。 - 操作方法的实际实现位于实现
DynamicController
的类中。 - 在应用程序文件中,您只需要为每个控制器添加一个条目
$app->any('/repository/{action}[/{id}]', RepositoryController::class);
5.3. 自动化数据库创建
我将改进的另一个部分是关于 CRUD 操作。我使用了 Eloquent ORM,它是一个非常简单的框架来设置。在其简单的实现中,要创建一个公开 CRUD 操作的 REST 服务,您需要
- 创建一个模型类。唯一必需的信息是表名。
- 所有字段的访问都可以按名称进行,因为 PHP 成员是动态的,所以如果您需要
set
\get
一个字段,只需使用它即可。 - 在查询表达式中,有一个流畅的语法,以
string
格式接受字段名称。 - 您可以使用一些 API 来管理模式操作。
在一个简单的场景中,我可以使用一些数据库脚本来管理模式定义,但在这种情况下,由于应用程序将使用许多不同的数据库进行安装,我希望设置一个不依赖于数据库方言的系统。因此,我放弃了 SQL 选项,并开始使用 Eloquent API。问题是我也不想创建一个庞大的脚本来复制相同的无聊代码,这些代码会检查表是否存在,然后创建字段等等。我的解决方案是
- 创建一个表示表的接口,并要求实现该接口以定义基本信息(例如,表名和字段列表)
- 我为每个表实现了一个实例,说明我想要每个表中包含哪些字段
- 我创建了一个
Manager
类,它加载所有表类,然后基于类定义应用模式更改
接口示例
abstract class TableBase
{
public abstract function getFieldDefinition();
public abstract function getTableName();
//field list
public function getBaseFields()
{
return array(
'id' =>'bigIncrements',
'created_at'=>'timestamp',
'updated_at'=>'timestamp',
);
}
function getAllColumns()
{
return array_merge($this->getBaseFields(),$this->getFieldDefinition());
}
}
实现示例
class UsersTable extends TableBase
{
public function getFieldDefinition()
{
return array(
'username' =>'string',
'password' =>'string',
'token'=>'string',
);
}
public function getTableName()
{
return "user";
}
}
示例:Manager 用法
$um= new UpdateManager($dbSettings);
$um->addTable(new RepositoryTable());
$um->addTable(new PublishTokenTable());
$um->addTable(new UsersTable());
$um->addTable(new PackageTable());
$um->run();
5.4. 自动化 CRUD 操作
随着路由和 ORM 层就绪,我创建了一个 EntityController
,这是一个抽象的通用类,用于管理通用模型上的基本 CRUD 操作。要添加实体,您只需创建一个基于它的具体类并注册路由即可。
示例:实现 Entity
class User extends \Illuminate\Database\Eloquent\Model {
protected $table = 'user';
}
示例:实现 Controller
class UserController extends EntityController
{
public function getTableDefinition()
{
return new UsersTable();
}
public function getModel()
{
return '\WpGet\Models\User';
}
}
示例:注册 Startup
$app->any('/user/{action}[/{id}]', UserController::class);
所有基本操作都已得到管理。在某些情况下,您可以覆盖方法并执行额外的业务逻辑,例如特殊验证、计算字段或保存相关实体。
5.5. 自动化依赖管理
如果说 PHP 有什么让我觉得棘手,那就是依赖管理。 concerns 不在于 Composer 和包管理,后者非常棒,解决了大多数问题。我的抱怨在于需要链接文件。您将在主应用程序中使用 include
语句或其变体,如 include once、requires 等。这非常令人讨厌,并且可能导致问题,例如在我们的情况下,我们将有许多入口点(您还记得我们决定有三个不同的应用程序吗?)。在许多基于 PHP 的解决方案中,它们实现了某些引导程序来简化脚本加载(例如 Drupal)。在这个应用程序中,我想实现一个简单的引导框架。我在产品中发现的最佳方法是为每个模块或插件定义依赖列表,然后引导程序将根据活动插件收集和加载所有文件。在这种情况下,由于我们没有插件系统,只有三个应用程序,我决定避免这种过度设计的解决方案,并找到了一个更简单的。我实现了一个名为“DependencyManager
”的类,该类根据文件夹或文件列表解析依赖项。每个应用程序都提供依赖项列表,以便依赖管理器扫描文件系统加载所有 PHP 文件。
示例:Dependency Management 用法
$dm= new DependencyManager($appPath);
$dm->requireOnceFolders( array(
'src/models',
'src/controllers/base',
'src/controllers',
'src/db/base',
'src/db',
'src/handlers',
'src/utils',
));
如果应用程序将来增长并需要一个真正的插件系统,我们将需要让依赖管理器从数据库或某些配置文件中读取列表。
6. 前端
UI 应用程序是用 Angular 编写的(我指的是 2+ 版本,而不是 AngularJS,如果您对此感到困惑的话 ;))。尽管我想尽可能地保持所有内容接近标准,但有一些点需要注意,当您设计下一个 Angular 应用程序时会很有用。
6.1. UI 框架:Prime NG
第一个问题是关于要使用的组件框架。我的个人选择列表
CoreUI
PrimeNG
Material
我认为这些是市场上最好的免费解决方案。根据我的经验,我发现 Material 管理起来非常困难,我宁愿不使用它,除非上下文需要,尽管它是一个非常好的框架。CoreUI 非常完整,我喜欢它,因为您可以在没有任何平面设计能力的情况下创建精美的应用程序。此外,它基于 Bootstrap,这有助于设计师更好地看待和定制它。PrimeNG 不基于 Bootstrap,这在我要求 Bootstrap 人员介入时带来了一些问题。与 PrimeNG 框架无关,只是现在大家已经知道 Bootstrap,但并非所有人都知道 PrimeNg。顺便说一句,PrimeNG 拥有 Angular 2+ 最佳的 Grid 系统,所以在这许多项目中,我将其与 CoreUI 混合使用。但是,在这种情况下,我想保持简单,尽可能减少依赖项,所以我只为所有应用程序使用了 PrimeNG。
6.2. 实现 Http Interceptor 管理认证
这是一个常见问题:身份验证客户端通过 HTTP 调用服务器。使用基于 Token 的身份验证很容易将 Token 添加到请求中,问题在于不要每次都这样做。在 Angular 中,我找到了两种方法
-
扩展 HttpClient:在这种情况下,您扩展类,重写方法,您可以管理一个额外的 HTTP 头,并将未登录用户重定向到登录页。
-
使用拦截器:拦截器就像一个钩子,允许您在一个点管理应用程序的所有请求。这在 Angular 2 的早期并不存在,但从 Angular 4 开始可用。
在大多数应用程序中,有几个好理由偏向于 HttpClient
扩展模式。最大的问题在于多少 HttpInterceptor
定义可能相互作用以及如何管理不同的外部目标。在我们的应用程序中,这些问题并不那么重要,因为我们向登录发送未经验证的请求,而所有其他请求都已验证。不过,在一个简单的应用程序中,有一个单一的地点来管理所有事情,而无需要求服务使用特殊的 http 客户端而不是基础客户端,这真是太棒了。所以,我遵循了拦截器的方式,以下是管理基于 Token 的身份验证的基本知识
示例:来自拦截器的代码
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
forceLogout(): any {
localStorage.clear();
document.location.href= this.config.baseHref;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if( checkIfRequestNeedToken()) // checkIfRequestNeedToken code is omitted.
// In this case is the request to app backend
{
//user null means no login done, user not null but 401 means token expired
if (this.auth.currentUser() ) {
let url=req.url;
return next.handle(req.clone({
setHeaders: {
'Authorization': 'Bearer '+this.auth.currentUser().token
}
}))
.catch((err, source) => {
if (err.status == 401 || err.status == 0) {
this.forceLogout();
return Observable.empty();
} else {
return Observable.throw(err);
}
});
}
else
{
//completely force logout
this.forceLogout();
return;
}
}
else
{
return next.handle(req);
}
}
}
6.3. 动态计算 baseHref
BaseHref
是一个参数,它告诉 Angular 从主机名开始的相对路径,并且是路由所必需的。在标准应用程序中,很容易将一个参数添加到构建命令中,该命令基于您要发布的发布环境。这很容易管理,并且不会给您带来任何实际问题,因为您基本上有许多安装(生产、QA、测试、集成),它们是相同的副本。在这种情况下,由于应用程序是本地部署的,我不知道应用程序将在何处安装。它可能位于网站的根目录或子文件夹中。因此,我决定根据客户环境设置来挂钩 baseHref
。此路径是从动态配置变量中获取的,该变量在安装过程中设置。因此,当用户安装时,后端会计算并存储 baseHref
,UI 在常规使用过程中会使用它。这是设置参数的代码片段。请注意,您可以在此处放置任何逻辑来定义您所需的值
示例:Base href 覆盖 (app,module.ts)
export function baseHrefFactory (config: ConfigurationService) {
return window.location.href.substring
(window.location.href.lastIndexOf(config.baseHref));
}
//...
providers: [
//...
{
provide: APP_INITIALIZER,
useFactory:configFactory,
deps: [ConfigurationService,HttpClientModule],
multi: true
},
{ provide: APP_BASE_HREF,
useFactory: baseHrefFactory,
deps: [ConfigurationService,HttpClientModule],
},
//...
6.4. 分离编译和配置
环境的使用非常有益,并且这个开发框架考虑到了应用程序可能部署在多个地方的可能性,这真是太棒了。问题在于它如何做到:在编译时。虽然在简单的项目中,您可以为每次提交进行多次构建,为每个部署环境生成不同的构件或在发布期间重新构建代码,但我无法为用户将要进行的所有安装构建应用程序。因此,我决定改变这种方法并使用动态设置。
设置文件是一个放在 assets 文件夹中的 json 文件。对于那些会说不安全的人,我还会提醒您,环境文件也存储在纯文本的 js 文件中,因此安全性级别相同。js 中的信息是在安装时创建的,并且值是根据环境计算的。这将使我们能够只进行一次构建,生成一个通用的包,并在启动时调整参数。
7. WordPress 插件
关于 WordPress 插件,需要对引擎的标准、核心功能进行深入解释,才能说明它。这是一项巨大的审查,并且与本文的重点(主要关注 WpGet
应用程序)有点偏离。这就是为什么我决定在这里只解释一个用户角度的高级概述。本章的目的是解释创建符合 WpGet
规范的新插件的基础知识。我将专门写另一篇文章来介绍插件架构和 WordPress 特定问题。
如前所述,WordPress 插件基本上是一个 zip 文件。主要想法是在其中放置一个 yaml 格式的文件来解释插件是什么。我选择了 Yaml 格式,因为它是一种易于阅读和编写的格式,并且比 XML 或 JSON 格式更适合包含文本数据(只需考虑转义特殊字符……)。此清单必须命名为 .wpget.yml,在上传包时,应用程序会读取它,无论它在哪里。顺便说一句,建议将其放置在 plugin 目录中。请注意,yml 文件放置在插件内部,也可以由插件本身用于确定当前版本和显示设置。到目前为止,我描述的是 WordPress 开发者如何描述其插件并将信息加载到 wpget
系统中。另一个问题是如何让插件与 wpget
服务器交互以获取更新。一如既往,有许多方法可以做到这一点。在分析期间,我涵盖了其中许多方法
-
手动添加和注册一个类。 我们可以为用户提供一个现成的 PHP 类,可以将其放置在插件本身中,然后进行注册和配置以使插件可更新。这是供应商端最简单的解决方案(只需准备类和示例模块),但是可扩展性最差,因为每个用户的插件都必须复制该类,复制代码,并且每个插件都需要一些手动交互。
-
创建一个伞形插件:此解决方案将通过在公共 WordPress 仓库中创建和发布插件来克服前一种实现的限制。此插件将在 WordPress 中引入集成所需的基本类,因此无需再复制粘贴类。此外,这将允许管理模块的基本设置(
wpget
的 URL,身份验证的 Token),避免硬编码配置。 -
创建一个替代插件管理器:这将允许您连接到许多
wpget
仓库,下载可用软件包的完整列表,然后安装\更新它们。
我决定从供应商的简单解决方案开始,因为用户付出的努力最少。在此验证阶段之后,如果许多用户报告安装问题,我将考虑实施解决方案(2),以便他们在 WordPress 端进行更轻松的管理。关于解决方案(3),我认为它没有比(2)真正的优势,但实际上它是一个更具侵入性的实现。
回到实际解决方案,我们需要两个步骤。首先,您需要在根目录中添加 .wpget.yml 文件,内容如下
示例:Yaml 示例
version: 3.7.0
name: my-plugin
homepage: https://github.com/zeppaman/WpGet
upgrade_notice: >
[Improvement] New changes made in version 4.0 were causing problem
at websites running on PHP version less than 5.0
author: Francesco Minà
author_profile: https://github.com/zeppaman/WpGet
requires: 4.9.4
其他参数已省略,您必须查看本文档附带的完整 yaml 示例
然后,您需要插入 PHP 类(您可以在此处下载)并将其集成到插件文件中,如下所示
示例:集成示例
// remember to set variables (es in wp-config.php file) before use this file
// // repository config data
// define( 'WPGET_REPO_SLUG','test000' );
// define( 'WPGET_PACKAGE_NAME','my-plugin' );
// define( 'WPGET_API_URL','https://:3000/web/' );
// define( 'WPGET_TOKEN_READ','FnrvNuzwKodEgIqxsBctbFc2SxMncM');
// // plugin info
// // plugin filename
// define( 'WPGET_PLUGIN_FILE', 'plugin-test.php' );
// // plugin directory
// define( 'WPGET_PLUGIN_DIR', 'plugin-test' );
require_once( 'WpGetUpdater.php' );
8. 关注点
在前一章中,我探讨了应用程序主要模块中最有趣的点。此外,还有一些重要的话题需要讨论,它们不直接与前端或后端相关,我将在其中讨论。我指的是 DevOps 部分以及打包\安装过程。
8.1. 安装过程
由于此应用程序将由用户安装,因此我想创建一个安装程序,该程序将为您执行所有必需的操作。此脚本将
- 检查应用程序是否已安装。如果是,则什么也不做
- 确保所有文件夹都存在且具有正确的权限
- 在数据库中创建数据模式
- 为前端生成设置文件
- 如果以上任何步骤失败,则失败
尽管这会很棒,但这是一个安装脚本,而不是一个安装向导。这意味着用户必须遵循以下步骤
- 获取服务器
- 将 zip 文件解压到服务器磁盘上的某个位置
- 使 /web 文件夹可访问到 Web
- 将数据库设置插入设置文件中
- 运行安装脚本
为了避免运行未配置的应用程序,我在 .htacces (对于 Apache 安装) 中添加了一个重写规则,如果应用程序未安装,则将所有流量重定向到安装页面。这可以防止用户阅读前面的点列表并在点 (2) 处停止,不知道该做什么。安装脚本检查写入权限,并极大地帮助用户达到正确的服务器配置。实际上没有太多设置要做,除了插入数据库连接设置,拥有一些可写入的目录(storage, logs, assets),但在我的经验中,我发现一个应用程序直到所有都正常才启动比一个不工作的应用程序要好。
8.2. 构建和打包
这是一个开源项目,因此代码托管在 Github 上(对于开源代码,如今还有其他选择吗?)。这为我自动化流程提供了一些有趣的机会,最终,我选择了 CircleCI,它对公共项目免费且易于使用。我的构建过程只是下载代码,删除未使用的文件夹,构建 Angular 应用程序。这非常简单,但 CircleCI 在 Docker 镜像中运行所有过程。它提供了一个 Node 镜像和一个 PHP 镜像,但没有一个同时包含两者。这个限制可以通过创建一个同时安装了 Node 和 PHP 的新 Docker 镜像来解决。我选择了懒惰的解决方案:我使用了 CircleCI 提供的 PHP Docker 镜像,但在编译的第一步安装了 Node。这将在打包阶段极大地帮助我,因为所有文件都在同一个文件夹中,所以我只需要生成一个 zip 文件并将其作为 zip 文件上传到 CircleCI。这是 CircleCI 与安装脚本的配置。
示例:circle ci config
# PHP CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-php/ for more details
#
version: 2
jobs:
build:
docker:
- image: circleci/php:7.1.5-browsers
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
keys:
- v1-dependencies-{{ checksum "composer.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: chmod -R 777 *
- run: sh .circleci/build.sh
- store_artifacts:
path: /tmp/artifacts
- save_cache:
paths:
- ./vendor
key: v1-dependencies-{{ checksum "composer.json" }}
# run tests!
# - run: phpunitconfig.yml
示例:circle ci startup script
#install node
php -r "copy('https://getcomposer.org.cn/installer', 'composer-setup.php');"
php composer-setup.php
php -r "unlink('composer-setup.php');"
php composer.phar self-update
sudo mv composer.phar /usr/local/bin/composer
composer install -n --prefer-dist
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
#angular build
cd src-ui;
npm install;
npm run ng build --env prod;
cd ..;
#delete unused folders
rm -rf src-ui;
rm -rf .circleci;
rm -rf .git;
rm -rf .vscode;
rm -rf web/ui/assets/settings.json
#build artifact
mkdir /tmp/artifacts;
zip -r /tmp/artifacts/web.zip .
9. 结论
开发这个应用程序给了我机会,使用基于以下技术的现代 Web 应用程序的所有组件进行测试
- PHP
- Slim
- Angular
将此类应用程序应用于本地部署世界会带来一些我们通常在为常规业务应用程序开发过程中不会遇到的问题。这很有趣,因为这个项目积累的经验可以作为新项目的基础,或者如果应用程序的需求需要,可以直接使用。
10 参考文献
- WordPress 使用统计
- GitHub 项目
- GitHub 插件项目
11 历史
- 2018 年 2 月 10 日:
wpget
的首次发布 - 2018 年 2 月 14 日:本文的首次发布
- 2018 年 2 月 15 日:更新二进制文件,更新链接