mirror of
https://github.com/halo-dev/docs.git
synced 2025-10-20 01:17:19 +00:00
docs: update documentation for Halo 2.18 (#389)
为 [Halo 2.18](https://github.com/halo-dev/halo/releases/tag/v2.18.0) 更新文档。 /kind documentation ```release-note None ```
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: Devtools
|
||||
description: 了解 Halo 的 Devtools 插件开发工具的使用
|
||||
---
|
||||
|
||||
Devtools 插件开发工具提供了一些 Task 用于辅助 Halo 插件的运行与调试,使用此工具的前提是需要具有 [Docker](https://docs.docker.com/get-docker/) 环境。
|
||||
|
||||
Devtools 还提供了一些其他的构建任务,如插件打包、插件检查等。
|
||||
|
||||
## 安装
|
||||
|
||||
Devtools 是使用 Java 开发的一个 [Gradle](https://gradle.org/) 插件,如果你使用的 [plugin-starter](https://github.com/halo-sigs/plugin-starter) 创建的插件项目,那么你无需任何操作,它已经默认集成了 Devtools 插件。
|
||||
|
||||
你可以在项目的 `build.gradle` 中找到它:
|
||||
|
||||
```groovy
|
||||
plugins {
|
||||
// ...
|
||||
id "run.halo.plugin.devtools" version "0.0.7"
|
||||
}
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
当在项目中引入了 `devtools` 之后,就可以使用一些额外的构建任务:
|
||||
|
||||
- `haloServer`:此构建任务用于启动 Halo 服务并自动将依赖此 `devtools` 的 Halo 插件项目以开发模式加载到 Halo 服务中。
|
||||
- `watch`:此构建任务用于监视 Halo 插件项目的变化并自动重新加载到 Halo 服务中
|
||||
- `reloadPlugin`:此构建任务用于重载插件,如果你此时使用的是 `haloServer` 运行的插件,改动代码后可以运行此任务来重载插件代码使用新的改动被应用。
|
||||
|
||||
一个可能的使用场景:
|
||||
|
||||
正在开发 `plugin-starter` 插件,此时想测试插件的功能如看到默认提供的菜单项,你可以通过 `haloServer` 将插件运行起来:
|
||||
|
||||
```shell
|
||||
./gradlew haloServer
|
||||
```
|
||||
|
||||
看到如下日志时表示 Halo 服务已经启动成功:
|
||||
|
||||
```shell
|
||||
Halo 初始化成功,访问: http://localhost:8090/console
|
||||
用户名:admin
|
||||
密码:admin
|
||||
```
|
||||
|
||||
然后改动了某行代码需要使其生效,可以继续保持 `haloServer` 的运行,然后执行:
|
||||
|
||||
```shell
|
||||
./gradlew reloadPlugin
|
||||
```
|
||||
|
||||
来时新的改动应用到现有服务上。
|
||||
|
||||
但如果你使用的 `watch` 任务启动插件则不需要执行 `reloadPlugin` 任务,它会监听文件的改动自动重载插件。
|
||||
|
||||
## 配置
|
||||
|
||||
在 `build.gradle` 文件中作出配置可以更改 `devtools` 的行为:
|
||||
|
||||
```groovy
|
||||
halo {
|
||||
version = '2.9.1'
|
||||
superAdminUsername = 'admin'
|
||||
superAdminPassword = 'admin'
|
||||
externalUrl = 'http://localhost:8090'
|
||||
docker {
|
||||
// windows 默认为 npipe:////./pipe/docker_engine
|
||||
url = 'unix:///var/run/docker.sock'
|
||||
apiVersion = '1.42'
|
||||
}
|
||||
port = 8090
|
||||
debug = true
|
||||
debugPort = 5005
|
||||
}
|
||||
```
|
||||
|
||||
`halo {}` 这个配置对象下面用于配置 Halo 服务器的一些信息,所有配置的默认值如上所示,你可以直接使用默认值而不进行任何配置。
|
||||
|
||||
- `version`:表示要使用的 Halo 版本,随着插件 API 的更新你可能需要更高的 Halo 版本来运行插件,可自行更改。
|
||||
- `superAdminUsername`: Halo 的超级管理员用户名,当你启动插件时会自动根据此配置和 `superAdminPassword` 为你初始化 Halo 的超级管理员账户。
|
||||
- `superAdminPassword`:Halo 的超级管理员用户密码。
|
||||
- `externalUrl`:Halo 的外部访问地址,一般默认即可,但如果修改了端口号映射可能需要修改。
|
||||
- `docker.url`:用于配置连接 Docker 的 url 信息,在 Mac 或 Linux 系统上默认是 `unix:///var/run/docker.sock`,在 windows 上默认是 `npipe:////./pipe/docker_engine`。
|
||||
- `docker.apiVersion`:Docker 的 API 版本,使用 `docker version` 命令可以查看到,如果你的 Docker 版本过低可能需要更改此配置,示例:
|
||||
|
||||
```shell
|
||||
➤ docker version 11:38:06
|
||||
Client:
|
||||
Version: 24.0.7
|
||||
API version: 1.43
|
||||
```
|
||||
|
||||
- `port`:Halo 服务的端口号,如果你的 Halo 服务端口号不想使用默认的 `8090` 或者想使用多个 Halo 服务,可以修改此配置。
|
||||
- `debug`:是否开启调试模式,开启后会在启动 Halo 服务时会自动开启调试模式,此时你可以使用 IDE 连接到 Halo 服务进行调试。
|
||||
- `debugPort`:调试模式下的调试端口号,默认是自动分配端口号,你可以修改此配置来固定调试端口号。
|
||||
- `suspend`:是否在启动时挂起,如果开启则会在启动时挂起直到有调试器连接到 Halo 服务。
|
||||
|
||||
## 调试后端代码
|
||||
|
||||
如果你想调试后端代码,可以在 `build.gradle` 中配置
|
||||
|
||||
```groovy
|
||||
halo {
|
||||
debug = true
|
||||
}
|
||||
```
|
||||
|
||||
然后通过 IDEA 运行 `haloServer` 以便于配合 `IDEA` 进行调试。
|
||||
|
||||

|
||||
|
||||
首先点击上图中 `1` 处的 `haloServer` 运行插件,然后点击 `2` 处的 `Attach debugger` 让 IDEA 连接到 Halo 服务,此时会打开一个调试窗口就可以开始打断点调试了。
|
||||
|
||||
可能会因为日志太快而点击不到 `Attach debugger`,那么你可以配置
|
||||
|
||||
```groovy
|
||||
halo {
|
||||
debug = true
|
||||
suspend = true
|
||||
}
|
||||
```
|
||||
|
||||
这样,在点击 `haloServer` 启动插件时会挂起等待在 `Attach debugger` 处,直到你点击 `Attach debugger` 连接调试器后才会继续执行。
|
@@ -0,0 +1,96 @@
|
||||
---
|
||||
title: Halo 架构概览
|
||||
description: Halo 架构概览
|
||||
---
|
||||
|
||||
Halo 是一个基于 Spring Boot 的 Java Web 应用,Web 层不再使用 Servlet 技术,而是充分向异步和非阻塞的反应式编程靠拢,使用 Netty 作为 Web 服务器,使用 [Reactor](https://projectreactor.io/) 作为异步编程框架,使用 R2DBC 作为数据库访问框架,使用 WebFlux 作为 Web 层框架。
|
||||
|
||||
Halo 由以下几个核心模块组成:
|
||||
|
||||
- 安全模块:提供用户认证、授权、用户管理等功能。
|
||||
- 插件模块:提供插件管理、插件加载、插件通信、扩展点等功能。
|
||||
- 主题模块:提供主题管理、模板渲染、主题配置等功能。
|
||||
- 内置内容管理模块:提供文章、分类、标签、评论、附件、页面、菜单、设置等功能。
|
||||
|
||||
## Halo 核心概念和 Extension
|
||||
|
||||
### 自定义模型 {#extension}
|
||||
|
||||
Extension 自定义模型提供了一种声明和管理数据模型的方法,它是 Halo 的核心概念之一。Halo 中的所有数据模型都是通过 Extension 来定义的,包括文章、分类、标签、评论、附件、页面、菜单、设置等,这便于插件系统可以灵活的进行数据模型的扩展,设计文档参考:[自定义模型设计](https://github.com/halo-dev/rfcs/tree/main/extension)。
|
||||
|
||||
每个自定义模型都有三大类属性:metadata、spec、和 status。
|
||||
|
||||
1. metadata 用于标识自定义模型,每个自定义模型都至少有三个 metadata 属性:name、creationTimestamp、version,除此之外还有 labels 用于标识自定义模型的标签,annotations 用于存放扩展信息,deletionTimestamp 用于标识自定义模型是否被删除,finalizers 用于标识自定义模型的是否可回收。
|
||||
2. spec 描述用户期望达到的理想状态(Desired State),比如用户可以配置插件的 `spec.enable` 属性为 `true` 来启用插件或者为 `false` 来停用插件,这就是用户期望达到的理想状态,然后插件控制器会根据用户的期望状态来实现插件的启用或停用,它是声明式的,用户只需要声明期望状态,实际状态由具体的控制器来维护,最终达到用户期望的状态。
|
||||
3. status 描述当前实际状态(Actual State),比如用户可以通过 `status.phase` 属性来查看插件启用进行到了哪一步,中间过程可能包含多个步骤,比如插件解析、加载、资源准备等,这些步骤都是由插件控制器来实现的,它是实际状态,只要插件控制器还在运行,它就会一直更新状态,最终达到用户期望的状态。
|
||||
|
||||
每个自定义模型注册后都会默认生成 CRUD APIs,通过这些 APIs 就可以对自定义模型对象进行增删改查的操作,然后只需要编写控制器来实现自定义模型的业务逻辑即可,这就是 Halo 的异步编程模型。
|
||||
|
||||
### 控制器 {#controller}
|
||||
|
||||
在 Halo 中,用户通过自定义模型定义资源的期望状态,Controller 负责监视资源的实际状态,当资源的实际状态和“期望状态”不一致时,Controller 则对系统进行必要的更改,以确保两者一致,这个过程被称之为调谐(Reconcile),而实现调谐的逻辑被称之为 Reconciler。Reconciler 获取对象的名称并返回是否需要重试(例如发生一些错误),如果需要重试,则 Controller 会在稍后再次调用 Reconciler,而这个过程会一直重复,直到 Reconciler 返回成功为止,这个过程被称之为调谐循环(Reconciliation Loop)。
|
||||
|
||||
### 自定义模型生命周期 {#extension-lifecycle}
|
||||
|
||||
所有 Halo 的自定义模型对象都遵循一个共同的生命周期,可以将其视为状态机,尽管某些特定的自定义模型扩展了这一点并提供了更多状态。要编写正确的控制器,了解公共对象生命周期非常重要。
|
||||
|
||||
所有自定义模型对象都存在以下状态之一:
|
||||
|
||||
- `DOES_NOT_EXIST`:Halo 不知道该对象。该状态不区分“尚未创建”和“已删除”。
|
||||
- `ACTIVE`:Halo 知道该对象并且该对象尚未被删除(未设置 `metadata.deletionTimestamp`)。在此状态下,任何更新操作(PUT、PATCH、服务器端处理等)都将导致相同的状态。
|
||||
- `DELETING`:Halo 知道该对象,该对象已被删除,但尚未完全删除。这可能是因为对象有一个或多个终结器(在 `metadata.finilizers` 中),客户端仍然可以访问该对象,并且可以看到它正在删除,因为设置了 `metadata.deleteTimestamp` 字段。当最后一个终结器被删除时,该对象将从存储中删除,并真正不存在。
|
||||
|
||||
下图描述了上述状态:
|
||||
|
||||
```text
|
||||
+---- object
|
||||
| updated
|
||||
v |
|
||||
+----------+ |
|
||||
| +----+
|
||||
object --------->| ACTIVE |
|
||||
created | +-----------+
|
||||
| +---+------+ |
|
||||
| | |
|
||||
| | |
|
||||
+------------+---+ | |
|
||||
| | object deleted |
|
||||
| |<--- without finalizers |
|
||||
| | object deleted
|
||||
| DOES_NOT_EXIST | with finalizers
|
||||
| | |
|
||||
| |<--- finalizers removed |
|
||||
| | | |
|
||||
+----------------+ | |
|
||||
| |
|
||||
| |
|
||||
+---+------+ |
|
||||
| | |
|
||||
| DELETING |<----------+
|
||||
| |
|
||||
+----------+
|
||||
```
|
||||
|
||||
总结:自定义模型对象的删除并不是立即生效的,而是需要经过两个步骤,第一步是将对象的 `metadata.deletionTimestamp` 字段设置为当前时间,第二步是将对象的 `metadata.finalizers` 字段设置为空,这样对象才会真正被删除,第一步是由用户发起的,第二步是由 Halo 控制器发起的。
|
||||
|
||||
### Secret {#secret}
|
||||
|
||||
Secret 用于解决密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到自定义模型的 Spec 中,或 API 响应中。
|
||||
|
||||
### ConfigMap {#configmap}
|
||||
|
||||
ConfigMap 自定义模型用来保存 key-value pair 配置数据,这个数据可以在 Reconciler 里使用,或者被用来为插件或者主题存储配置数据。
|
||||
|
||||
虽然 ConfigMap 跟 Secret 类似,但是 ConfigMap 更方便的处理不含敏感信息的字符串。
|
||||
|
||||
### Setting {#setting}
|
||||
|
||||
Setting 自定义模型用于提供用户配置声明,用户可以通过 Setting 来声明一些模板需要的配置,比如主题设置、插件设置、系统设置等都可以通过 Setting 来声明,就能在 UI 层面提供配置入口,用户可以通过 UI 来配置这些设置,而不需要修改配置文件。
|
||||
|
||||
### 基于角色的访问控制(RBAC){#rbac}
|
||||
|
||||
Halo 使用基于角色的访问控制(Role-based Access Control,RBAC)来控制用户对资源的访问权限,RBAC 通过将角色分配给用户来实现访问控制,用户可以通过角色来访问资源,角色可以通过权限来访问资源。
|
||||
|
||||
RBAC 主要引入了角色(Role)和角色绑定(RoleBinding)的抽象概念,插件可以通过定义角色来提供用户对资源的分配入口,用户可以通过角色绑定来获取角色,从而获取资源的访问权限。
|
||||
|
||||
而对于底层角色,用户分配起来比较麻烦,因此 Halo 提供了**角色模板**的概念,通过将角色标记为模板来使用一组功能相关的角色,如文章查看的角色可能必须包含标签和分类的查看才算是一组完整的功能,因此可以将文章查看的角色标记为模板,并依赖标签和分类的查看角色,这样用户就可以通过角色模板来获取一组功能相关的角色,而不需要一个一个的分配角色。
|
@@ -0,0 +1,102 @@
|
||||
---
|
||||
title: 插件注册和配置
|
||||
description: 了解插件定义文件 plugin.yaml 如何配置
|
||||
---
|
||||
|
||||
一个典型的插件描述文件 plugin.yaml 如下所示:
|
||||
|
||||
```yaml
|
||||
apiVersion: plugin.halo.run/v1alpha1
|
||||
kind: Plugin
|
||||
metadata:
|
||||
name: hello-world
|
||||
spec:
|
||||
enabled: true
|
||||
requires: ">=2.0.0"
|
||||
author:
|
||||
name: Halo
|
||||
website: https://www.halo.run
|
||||
logo: https://www.halo.run/logo
|
||||
# settingName: hello-world-settings
|
||||
# configMapName: hello-world-configmap
|
||||
homepage: https://github.com/halo-dev/plugin-starter#readme
|
||||
repo: https://github.com/halo-dev/plugin-starter
|
||||
issues: https://github.com/halo-dev/plugin-starter/issues
|
||||
displayName: "插件 Hello world"
|
||||
description: "插件开发的 hello world,用于学习如何开发一个简单的 Halo 插件"
|
||||
license:
|
||||
- name: "GPL-3.0"
|
||||
url: "https://github.com/halo-dev/plugin-starter/blob/main/LICENSE"
|
||||
```
|
||||
|
||||
- `apiVersion` 和 `kind`:为固定写法,每个插件写法都是一样的不可变更。
|
||||
- `metadata.name`:它是插件的唯一标识名,包含不超过 253 个字符,仅包含小写字母、数字或`-`,以字母或数字开头,以字母或数字结尾。
|
||||
- `spec.enabled`:表示是否要在安装时自动启用插件,出于安全性考虑,仅在插件开发模式下有效,生产模式需要由用户手动启用。
|
||||
- `spec.requires`:插件受支持的 Halo 版本,遵循 [Semantic Range Expressions](https://github.com/zafarkhaja/jsemver#range-expressions),以下是支持的符号及其解释的列表:
|
||||
- 常规符号:`>`、`>=`、`<`、`<=`、`=`、`!=`
|
||||
- 通配符范围 ( `*` | `X`| `x`):`1.*` 解释为 `>=1.0.0 && <2.0.0`
|
||||
- 波形符范围 ( `~` ):`~2.5`解释为 `>=1.5.0 && <1.6.0`
|
||||
- 连字符范围 ( `-` ):`0.0-2.0`解释为 `>=1.0.0 && <=2.0.0`
|
||||
- 插入符范围 ( `^` ):`^0.2.3`解释为 `>=0.2.3 && <0.3.0`
|
||||
- 部分版本范围:`1` 解释为 `1.x` 或 `>=1.0.0 && <2.0.0`
|
||||
- 否定运算符:`!(1.x)` 解释为 `<1.0.0 && >=2.0.0`
|
||||
- 带括号的表达式:`~1.3 || (1.4.* && !=1.4.5) || ~2`
|
||||
|
||||
- `spec.author`:插件作者的名称和可获得支持的网站地址。
|
||||
- `spec.logo`:插件 logo,可以是域名或相对于项目 `src/main/resources` 目录的文件路径,如将 logo 放在 `src/main/resources/logo.png` 则配置为 `logo.png` 即可。
|
||||
- `spec.settingName`:插件配置表单名称,对应一个 `Setting` 自定义模型资源文件,可为用户提供可视化的配置表单,参考:[表单定义](../../form-schema.md)。如果插件没有配置提供给用户则不需要配置此项,名称推荐为 "插件名-settings",命名规同 `metadata.name`。
|
||||
- `spec.configMapName`:表单定义对应的值标识名,它声明了插件的配置值将存储在哪个 ConfigMap 中,通常我们推荐命名为 "插件名-configmap",没有配置 `settingName` 则不需要配置此项,命名规同 `metadata.name`。
|
||||
|
||||
:::tip
|
||||
如果你在 plugin.yaml 中配置了 `settingName` 但确没有对应的 `Setting` 自定义模型资源文件,会导致插件无法启动,原因是 `Setting` 模型 `metadata.name` 为你配置的 `settingName` 的资源无法找到。
|
||||
:::
|
||||
|
||||
- `spec.homepage`:通常设置为插件官网或帮助中心链接等。
|
||||
- `spec.repo`:插件源码地址。
|
||||
- `spec.issues`:插件问题反馈地址,如果你的插件是开源在 GitHub 上,可以直接配置为 GitHub Issues 地址。
|
||||
- `spec.displayName`:插件的显示名称,它通常是以少数几个字来概括插件的用途。
|
||||
- `spec.description`:插件描述,用一段简短的说明来介绍插件的用途。
|
||||
- `spec.license`:插件使用的软件协议,参考:<https://en.wikipedia.org/wiki/Software_license>。
|
||||
|
||||
Halo 的插件可以在两种模式下运行:`development` 和 `deployment`。
|
||||
`deployment`(默认)模式是插件创建的标准工作流程:为每个插件创建一个新的 Gradle 项目,编码插件(声明新的扩展点和/或添加新的扩展),将插件打包成一个 JAR 文件,部署 JAR 文件到 Halo。
|
||||
这些操作非常耗时,因此引入了 `development` 开发模式。
|
||||
|
||||
对于插件开发人员来说,`development` 运行模式的主要优点是不必打包和部署插件。在开发模式下,您可以以简单快速的流程快速开发插件。
|
||||
|
||||
### 配置
|
||||
|
||||
如果你想以 `deployment` 运行插件则参考 [传统方式运行](../hello-world.md#run-with-traditional-way) 做如下配置:
|
||||
|
||||
```yaml
|
||||
halo:
|
||||
plugin:
|
||||
runtime-mode: deployment
|
||||
```
|
||||
|
||||
`deployment` 是默认的运行模式,因此你可以不配置 `runtime-mode`。
|
||||
|
||||
如果你想以 `development` 运行并开发插件则将 `runtime-mode` 修改为 `development`,同时配置 `fixed-plugin-path` 为插件项目绝对路径,可以配置多个。
|
||||
|
||||
```yaml
|
||||
# macOS / Linux
|
||||
plugin:
|
||||
runtime-mode: development
|
||||
fixed-plugin-path:
|
||||
# 配置为插件绝对路径
|
||||
- /path/to/halo-plugin-hello-world
|
||||
|
||||
# Windows
|
||||
halo:
|
||||
plugin:
|
||||
runtime-mode: development
|
||||
fixed-plugin-path:
|
||||
# 配置为插件绝对路径
|
||||
- C:\path\to\halo-plugin-hello-world
|
||||
```
|
||||
|
||||
:::tip Note
|
||||
|
||||
1. `development` 开发模式下,既可以运行 `fixed-plugin-path` 下的插件,也可以运行通过 `Console` 管理端安装的 JAR 格式的插件。
|
||||
2. 如果使用 [DevTools 运行方式](../hello-world.md#run-with-devtools) 来开发插件,则不需要配置 `runtime-mode` 和 `fixed-plugin-path`。
|
||||
:::
|
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: 生命周期
|
||||
description: 了解插件从启动到卸载的过程
|
||||
---
|
||||
|
||||
根据[插件项目文件结构](../../basics/structure.md)所展示的 `StarterPlugin.java` 中,具有如下方法:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void start() {
|
||||
System.out.println("插件启动成功!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
System.out.println("插件停止!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
System.out.println("插件被删除!");
|
||||
}
|
||||
```
|
||||
|
||||
它们就是插件的生命周期方法,分别对应插件的启动、停止和删除。
|
||||
|
||||
1. 继承 `run.halo.app.plugin.BasePlugin` 类后,你可以重写这些方法来干预插件的生命周期,例如在插件启动时初始化一些资源,在插件停止时清理掉这些资源。
|
||||
2. 一个插件项目只允许有一个类继承 `BasePlugin` 类且标记为 Bean,此时这个类将被作为插件的后端入口,如果有多个类继承了 `BasePlugin` 会导致插件无法启动或生命周期方法无法被调用。
|
||||
|
||||
:::tip Note
|
||||
如果一个类继承了 `BasePlugin` 类但没有标记为 Bean,那么它将不会被 Halo 识别到,其中的生命周期方法也不会被调用。
|
||||
:::
|
||||
|
||||
### 插件启动
|
||||
|
||||
插件被安装后,只加载了插件的 `plugin.yaml`,类及其他资源文件的加载均在启动时进行。
|
||||
当插件加载完类文件并准备好启动插件后就会调用插件的 `start()` 方法,这有助于插件在启动时做一些事情,例如初始化。
|
||||
|
||||
### 插件停止
|
||||
|
||||
插件停止时,会删除在启动时创建的自定义资源,例如插件设置等通过 `yaml` 创建的自定义模型资源。
|
||||
插件定义的自定义模型也需要在此时清理掉。
|
||||
|
||||
### 插件删除
|
||||
|
||||
插件被卸载时被调用。
|
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: 插件中的对象管理
|
||||
description: 了解如何在创建中创建对象和管理对象依赖
|
||||
---
|
||||
|
||||
在插件中你可以使用 [Spring Framework](https://spring.io/projects/spring-framework/) 提供的常用 Bean 注解来标注一个类,然后就能使用依赖注入功能注入其他类的对象。这省去了使用工厂创建类和维护的过程,你可以像开发一个常规的 Spring 项目一样来开发插件,目前支持以下 Spring Framework 的特性:
|
||||
|
||||
1. [Core Technologies](https://docs.spring.io/spring-framework/reference/core.html)
|
||||
2. [Web on Reactive](https://docs.spring.io/spring-framework/reference/web-reactive.html)
|
||||
3. [Testing](https://docs.spring.io/spring-framework/reference/testing.html)
|
||||
|
||||
通过模板插件创建的项目中你会看到 `StarterPlugin` 标注了 `@Component` 注解:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class StarterPlugin extends BasePlugin {
|
||||
}
|
||||
```
|
||||
|
||||
假设项目中有一个 `FruitService`,并将其声明了为了 Bean:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class FruitService {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
你可以在任何同样声明为 Bean 的类中使用[依赖注入](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-dependencies)来使用它:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class Demo {
|
||||
private final FruitService fruitService;
|
||||
|
||||
public Demo(FruitService fruitService) {
|
||||
this.fruitService = fruitService;
|
||||
}
|
||||
// use it...
|
||||
}
|
||||
```
|
||||
|
||||
### 依赖注入 Halo 提供的 Bean
|
||||
|
||||
目前 Halo 只提供了少数几个 Bean 可以供插件依赖注入:
|
||||
|
||||
- run.halo.app.extension.ReactiveExtensionClient:用于管理自定义模型对象的增删改查,它是反应式的。
|
||||
- run.halo.app.extension.ExtensionClient:用于管理自定义模型对象的增删改查,它是阻塞的,只能用在非 NIO 线程中,如后台任务。
|
||||
- run.halo.app.extension.SchemeManager:用于管理自定义模型定义的注册和销毁。
|
||||
- run.halo.app.infra.ExternalUrlSupplier:用于获取用户配置的 Halo 外部访问地址。
|
||||
- run.halo.app.core.extension.service.AttachmentService:用于操作附件。
|
||||
- run.halo.app.notification.NotificationReasonEmitter:用于发送通知。
|
||||
- run.halo.app.notification.NotificationCenter:用于管理通知的订阅和取消订阅。
|
||||
- run.halo.app.infra.ExternalLinkProcessor:用于处理将一个相对地址转换为外部访问地址。
|
||||
- org.springframework.security.web.server.context.ServerSecurityContextRepository:用于获取操作用户的认证上下文信息,如自动登录场景。
|
||||
|
||||
即其他不在上述列表中的类的对象都是不可依赖注入的。
|
@@ -0,0 +1,69 @@
|
||||
---
|
||||
title: 插件项目结构
|
||||
description: 了解插件项目的文件结构
|
||||
---
|
||||
|
||||
新创建的插件项目典型的目录结构如下所示:
|
||||
|
||||
```text
|
||||
├── ui
|
||||
│ ├── src
|
||||
│ │ ├── assets
|
||||
│ │ │ └── logo.svg
|
||||
│ │ ├── views
|
||||
│ │ │ └── HomeView.vue
|
||||
│ │ └── index.ts
|
||||
│ ├── env.d.ts
|
||||
│ ├── package.json
|
||||
│ ├── pnpm-lock.yaml
|
||||
│ ├── tsconfig.app.json
|
||||
│ ├── tsconfig.config.json
|
||||
│ ├── tsconfig.json
|
||||
│ ├── tsconfig.vitest.json
|
||||
│ └── vite.config.ts
|
||||
├── gradle
|
||||
├── src
|
||||
│ └── main
|
||||
│ ├── java
|
||||
│ │ └── run
|
||||
│ │ └── halo
|
||||
│ │ └── starter
|
||||
│ │ └── StarterPlugin.java
|
||||
│ └── resources
|
||||
│ ├── console
|
||||
│ │ ├── main.js
|
||||
│ │ └── style.css
|
||||
│ └── plugin.yaml
|
||||
├── LICENSE
|
||||
├── README.md
|
||||
├── build.gradle
|
||||
├── gradle.properties
|
||||
├── gradlew
|
||||
├── gradlew.bat
|
||||
└── settings.gradle
|
||||
```
|
||||
|
||||
该目录包含了前端和后端两个部分,让我们依次看一下它们中的每一个。
|
||||
|
||||
### 后端部分
|
||||
|
||||
所有的后端代码都放在 `src` 目录下,它是一个常规的 `Java` 项目目录结构。
|
||||
|
||||
- `StarterPlugin.java` 为插件的后端入口示例文件,类名可以任意但它必须继承 `run.halo.app.plugin.BasePlugin` 类来标记它作为插件入口。
|
||||
- `resources` 下的 `plugin.yaml` 为插件的资源描述文件,它是必须的,它描述了插件的基本信息,包括插件的名称、版本、作者、描述、依赖等。
|
||||
- `resources/console` 下的两个文件 `main.js` 和 `style.css` 是前端插件部分打包时输出的产物。一个插件可以没有前端部分,因此 `resources/console` 同样可以不存在。
|
||||
|
||||
:::caution 注意
|
||||
从 2.11 开始,Halo 支持了 UC 个人中心,且个人中心和 Console 的插件机制共享,所以为了避免歧义,`resources/console` 在后续版本会被重命名为 `resources/ui`,但同时也会兼容 `resources/console`。
|
||||
:::
|
||||
|
||||
### 前端部分
|
||||
|
||||
`ui` 目录下为插件的前端部分的工程目录,包括了源码、配置文件和静态资源文件。
|
||||
同样的,将所有前端项目源码放到 `src` 中。我们建议使用 `TypeScript` 作为编程语言,它可以帮助你在编译时而非运行时捕获错误。
|
||||
|
||||
- `src/index.ts` 作为前端部分的插件的入口文件。
|
||||
- `views` 中存放视图文件。
|
||||
- `styles` 中存放样式。
|
||||
- `components` 中放一些公共组件。
|
||||
- `assets` 用于放静态资源文件。
|
@@ -0,0 +1,126 @@
|
||||
---
|
||||
title: 入口文件
|
||||
description: UI 扩展部分的入口文件
|
||||
---
|
||||
|
||||
入口文件即 Halo 核心会加载的文件,所有插件有且只有一个入口文件,构建之后会放置在插件项目的 `src/resources/console` 下,名为 `main.js`。
|
||||
|
||||
为了方便开发者,我们已经在 [halo-dev/plugin-starter](https://github.com/halo-dev/plugin-starter) 配置好了基础项目结构,包括构建配置,后续文档也会以此为准。
|
||||
|
||||
## 定义入口文件
|
||||
|
||||
```ts title="ui/src/index.ts"
|
||||
import { definePlugin } from "@halo-dev/console-shared";
|
||||
|
||||
export default definePlugin({
|
||||
components: {},
|
||||
routes: [],
|
||||
ucRoutes: [],
|
||||
extensionPoints: {}
|
||||
});
|
||||
```
|
||||
|
||||
## 类型定义
|
||||
|
||||
```ts
|
||||
export function definePlugin(plugin: PluginModule): PluginModule {
|
||||
return plugin;
|
||||
}
|
||||
```
|
||||
|
||||
```ts title="PluginModule"
|
||||
import type { Component, Ref } from "vue";
|
||||
import type { RouteRecordRaw, RouteRecordName } from "vue-router";
|
||||
import type { FunctionalPage } from "../states/pages";
|
||||
import type { AttachmentSelectProvider } from "../states/attachment-selector";
|
||||
import type { EditorProvider, PluginTab } from "..";
|
||||
import type { AnyExtension } from "@halo-dev/richtext-editor";
|
||||
import type { CommentSubjectRefProvider } from "@/states/comment-subject-ref";
|
||||
import type { BackupTab } from "@/states/backup";
|
||||
import type { PluginInstallationTab } from "@/states/plugin-installation-tabs";
|
||||
import type { EntityFieldItem } from "@/states/entity";
|
||||
import type { OperationItem } from "@/states/operation";
|
||||
import type { ThemeListTab } from "@/states/theme-list-tabs";
|
||||
import type {
|
||||
Attachment,
|
||||
Backup,
|
||||
ListedPost,
|
||||
Plugin,
|
||||
Theme,
|
||||
} from "@halo-dev/api-client";
|
||||
|
||||
export interface RouteRecordAppend {
|
||||
parentName: RouteRecordName;
|
||||
route: RouteRecordRaw;
|
||||
}
|
||||
|
||||
export interface ExtensionPoint {
|
||||
// @deprecated
|
||||
"page:functional:create"?: () => FunctionalPage[] | Promise<FunctionalPage[]>;
|
||||
|
||||
"attachment:selector:create"?: () =>
|
||||
| AttachmentSelectProvider[]
|
||||
| Promise<AttachmentSelectProvider[]>;
|
||||
|
||||
"editor:create"?: () => EditorProvider[] | Promise<EditorProvider[]>;
|
||||
|
||||
"plugin:self:tabs:create"?: () => PluginTab[] | Promise<PluginTab[]>;
|
||||
|
||||
"default:editor:extension:create"?: () =>
|
||||
| AnyExtension[]
|
||||
| Promise<AnyExtension[]>;
|
||||
|
||||
"comment:subject-ref:create"?: () => CommentSubjectRefProvider[];
|
||||
|
||||
"backup:tabs:create"?: () => BackupTab[] | Promise<BackupTab[]>;
|
||||
|
||||
"plugin:installation:tabs:create"?: () =>
|
||||
| PluginInstallationTab[]
|
||||
| Promise<PluginInstallationTab[]>;
|
||||
|
||||
"post:list-item:operation:create"?: (
|
||||
post: Ref<ListedPost>
|
||||
) => OperationItem<ListedPost>[] | Promise<OperationItem<ListedPost>[]>;
|
||||
|
||||
"plugin:list-item:operation:create"?: (
|
||||
plugin: Ref<Plugin>
|
||||
) => OperationItem<Plugin>[] | Promise<OperationItem<Plugin>[]>;
|
||||
|
||||
"backup:list-item:operation:create"?: (
|
||||
backup: Ref<Backup>
|
||||
) => OperationItem<Backup>[] | Promise<OperationItem<Backup>[]>;
|
||||
|
||||
"attachment:list-item:operation:create"?: (
|
||||
attachment: Ref<Attachment>
|
||||
) => OperationItem<Attachment>[] | Promise<OperationItem<Attachment>[]>;
|
||||
|
||||
"plugin:list-item:field:create"?: (
|
||||
plugin: Ref<Plugin>
|
||||
) => EntityFieldItem[] | Promise<EntityFieldItem[]>;
|
||||
|
||||
"post:list-item:field:create"?: (
|
||||
post: Ref<ListedPost>
|
||||
) => EntityFieldItem[] | Promise<EntityFieldItem[]>;
|
||||
|
||||
"theme:list:tabs:create"?: () => ThemeListTab[] | Promise<ThemeListTab[]>;
|
||||
|
||||
"theme:list-item:operation:create"?: (
|
||||
theme: Ref<Theme>
|
||||
) => OperationItem<Theme>[] | Promise<OperationItem<Theme>[]>;
|
||||
}
|
||||
|
||||
export interface PluginModule {
|
||||
components?: Record<string, Component>;
|
||||
|
||||
routes?: RouteRecordRaw[] | RouteRecordAppend[];
|
||||
|
||||
ucRoutes?: RouteRecordRaw[] | RouteRecordAppend[];
|
||||
|
||||
extensionPoints?: ExtensionPoint;
|
||||
}
|
||||
```
|
||||
|
||||
- `components`:组件列表,key 为组件名称,value 为组件对象,在此定义之后,加载插件时会自动注册到 Vue App 全局。
|
||||
- `routes`:Console 控制台路由定义,详细文档可参考 [路由定义](../../api-reference/ui/route.md)
|
||||
- `ucRoutes`:UC 个人中心路由定义,详细文档可参考 [路由定义](../../api-reference/ui/route.md)
|
||||
- `extensionPoints`:扩展点定义,详细文档可参考 [扩展点](../../api-reference/ui/extension-points/index.md)
|
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: 介绍
|
||||
description: 介绍插件 UI 部分的基础知识。
|
||||
---
|
||||
|
||||
Halo 插件体系的 UI 部分可以让开发者在 Console 控制台和 UC 个人中心添加新的页面或者扩展已有的功能。
|
||||
|
||||
在开始之前,建议先熟悉或安装以下库和工具:
|
||||
|
||||
1. [Node.js 18+](https://nodejs.org)
|
||||
2. [pnpm 8+](https://pnpm.io)
|
||||
3. [Vue.js 3](https://vuejs.org)
|
Reference in New Issue
Block a user