mirror of
https://github.com/halo-dev/docs.git
synced 2025-10-19 17:04:09 +00:00
docs: update documentations for 2.21 (#502)
Signed-off-by: Ryan Wang <i@ryanc.cc>
This commit is contained in:
@@ -0,0 +1,445 @@
|
||||
---
|
||||
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.4.0"
|
||||
}
|
||||
```
|
||||
|
||||
Release 版本和版本说明可以在 [GitHub Releases](https://github.com/halo-sigs/halo-gradle-plugin/releases) 上查看。
|
||||
|
||||
## 使用说明
|
||||
|
||||
当在项目中引入了 `devtools` 之后,就可以使用一些额外的构建任务来辅助插件的开发,参考 [构建任务详解](#任务)。
|
||||
|
||||
例如,正在开发 `plugin-starter` 插件时,可以通过 `haloServer` 任务启动 Halo 服务,来测试插件功能:
|
||||
|
||||
```shell
|
||||
./gradlew haloServer
|
||||
```
|
||||
|
||||
看到如下日志时表示 Halo 服务已经启动成功:
|
||||
|
||||
```shell
|
||||
=======================================================================
|
||||
> Halo 启动成功!
|
||||
访问地址:http://localhost:8090/console?language=zh-CN
|
||||
用户名:admin
|
||||
密码:admin
|
||||
API 文档:http://localhost:8090/swagger-ui.html
|
||||
插件开发文档:https://docs.halo.run/developer-guide/plugin/introduction
|
||||
=======================================================================
|
||||
```
|
||||
|
||||
修改代码后,无需停止服务,只需执行:
|
||||
|
||||
```shell
|
||||
./gradlew reload
|
||||
```
|
||||
|
||||
即可应用改动。如果使用 watch 任务启动插件,则不需要执行 `reload`,它会自动监听并重载插件。
|
||||
|
||||
## 配置
|
||||
|
||||
可通过 `build.gradle` 文件中的 `halo {}` 块自定义 Devtools 启动 Halo 服务必要配置,示例如下:
|
||||
|
||||
```groovy
|
||||
halo {
|
||||
version = '2.20'
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
- `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
|
||||
Client:
|
||||
Version: 24.0.7
|
||||
API version: 1.43
|
||||
```
|
||||
|
||||
- `port`:Halo 服务的端口号,如果你的 Halo 服务端口号不想使用默认的 `8090` 或者想使用多个 Halo 服务,可以修改此配置。
|
||||
- `debug`:是否开启调试模式,开启后会在启动 Halo 服务时会自动开启调试模式,此时你可以使用 IDE 连接到 Halo 服务进行调试。
|
||||
- `debugPort`:调试模式下的调试端口号,默认是自动分配端口号,你可以修改此配置来固定调试端口号。
|
||||
- `suspend`:是否在启动时挂起,如果开启则会在启动时挂起直到有调试器连接到 Halo 服务。
|
||||
|
||||
:::warning
|
||||
由于 Halo 2.20.0 版本更改了初始化和登录流程,如果 `halo.version` 指定 `2.20.x` 版本需要将 `run.halo.plugin.devtools` 版本升级到 `0.2.0` 及以上。
|
||||
:::
|
||||
|
||||
## 任务
|
||||
|
||||
本插件提供了 `haloServer` 和 `watch` 两个任务,使用它们的前提条件是需要在本地配置 Docker 环境。
|
||||
|
||||
### 环境要求
|
||||
|
||||
- **Windows 和 Mac 用户**:可以直接安装 [Docker Desktop](https://www.docker.com/products/docker-desktop)。
|
||||
- **Linux 用户**:请参考 [Docker 官方文档](https://docs.docker.com/engine/install/) 安装 Docker。
|
||||
|
||||
确保 Docker 服务已启动后,即可运行 `haloServer` 和 `watch` 任务。
|
||||
|
||||
### 工作目录
|
||||
|
||||
这两个任务会将 Halo 的工作目录挂载到插件项目的 `workplace` 目录下,以确保在重启任务时数据不会丢失。
|
||||
|
||||
### 自定义配置
|
||||
|
||||
如果需要修改 Halo 的配置,您可以在 `workplace` 目录下创建一个 `config` 目录,并添加一个 `application.yaml` 文件。在该文件中,您可以覆盖 Halo 的默认配置。例如:
|
||||
|
||||
```yaml
|
||||
# workplace/config/application.yaml
|
||||
logging:
|
||||
level:
|
||||
run.halo.app: DEBUG
|
||||
```
|
||||
|
||||
更多配置项请参考 [Halo 配置列表](../../../getting-started/install/config.md#配置列表)。
|
||||
|
||||
### haloServer 任务
|
||||
|
||||
使用方式:
|
||||
|
||||
```shell
|
||||
./gradlew haloServer
|
||||
```
|
||||
|
||||
此任务用于启动 Halo 服务并自动将使用此 Gradle 插件的 Halo 插件项目以开发模式加载到 Halo 服务中,当你修改了插件的代码后,可以通过 `reload` 任务使更改生效。
|
||||
|
||||
#### haloServer 任务默认配置
|
||||
|
||||
`haloServer` 任务具有以下默认配置用于连接和操作 Halo 服务:
|
||||
|
||||
```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'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如需修改,可以在 `build.gradle` 文件中进行配置。
|
||||
|
||||
### reload 任务
|
||||
|
||||
使用方式:
|
||||
|
||||
```shell
|
||||
./gradlew reload
|
||||
```
|
||||
|
||||
此任务用于重新加载当前正在开发的插件,修改代码后执行此任务以应用更改。
|
||||
|
||||
该任务基于以下配置调用 Halo API 重新加载插件:
|
||||
|
||||
```groovy
|
||||
halo {
|
||||
// ...
|
||||
superAdminUsername = 'admin'
|
||||
superAdminPassword = 'admin'
|
||||
externalUrl = 'http://localhost:8090'
|
||||
}
|
||||
```
|
||||
|
||||
#### watch 任务
|
||||
|
||||
使用方式:
|
||||
|
||||
```shell
|
||||
./gradlew watch
|
||||
```
|
||||
|
||||
此任务用于监视 Halo 插件项目的变化并自动重新加载到 Halo 服务中。
|
||||
默认只监听 `src/main/java` 和 `src/main/resources` 目录下的文件变化,如果需要监听其他目录,可以在项目的 `build.gradle` 中添加如下配置:
|
||||
|
||||
```groovy
|
||||
haloPlugin {
|
||||
watchDomains {
|
||||
// consoleSource 为自定义的名称,可以随意取
|
||||
consoleSource {
|
||||
// 监听 console/src/ 目录下的文件变化
|
||||
files files('console/src/')
|
||||
// exclude '**/node_modules/**'
|
||||
// exclude '**/.idea/**'
|
||||
// exclude '**/.git/**'
|
||||
// exclude '**/.gradle/**'
|
||||
// exclude 'src/main/resources/console/**'
|
||||
// exclude 'build/**'
|
||||
// exclude 'gradle/**'
|
||||
// exclude 'dist/**'
|
||||
// exclude 'test/java/**'
|
||||
// exclude 'test/resources/**'
|
||||
}
|
||||
// ... 可以添加多个
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`exclude` 接收字符串用于排除不需要监听的文件或目录,可以使用 `**` 通配符。注释掉的是每个 `watchDomains` 都会默认排除的规则。
|
||||
|
||||
### 生成 API client
|
||||
|
||||
#### 什么是 API client
|
||||
|
||||
API client 是一种工具或库,旨在简化前端应用程序与后端服务器之间的通信,尤其是在使用 RESTful API 或 GraphQL API 的情况下。
|
||||
它提供了一种简洁且类型安全的方式来调用服务器端的 API,并处理请求和响应。
|
||||
|
||||
在 TypeScript 环境中,使用 API client 有以下几个优点:
|
||||
|
||||
- 自动化 HTTP 请求:API 客户端封装了 HTTP 请求的细节,如构建 URL、设置请求头、处理查询参数等。开发者只需调用客户端提供的函数即可发送请求。
|
||||
|
||||
- 类型安全:通过结合 OpenAPI 等规范生成的 TypeScript 类型定义,API 客户端可以确保请求和响应的数据类型在编译时就能得到验证。这可以帮助减少运行时的错误,并提高代码的可读性和可维护性。
|
||||
|
||||
- 统一的错误处理:API 客户端可以提供统一的错误处理机制,比如自动重试、错误日志记录等,这样开发者无需在每个 API 调用中重复编写相同的错误处理逻辑。
|
||||
|
||||
- 提高开发效率:通过使用 API 客户端,开发者可以专注于业务逻辑的实现,而不用关心底层的 HTTP 细节。这不仅提高了开发效率,还减少了代码冗余。
|
||||
|
||||
#### 如何生成 API client {#how-to-generate-api-client}
|
||||
|
||||
本插件提供了一个 `generateApiClient` 任务,用于为插件项目生成 API client,生成规则基于 OpenAPI 规范来自动生成客户端代码。
|
||||
|
||||
能生成 API 客户端代码的前提是插件项目中需要对自定义的 API 进行文档声明如:
|
||||
|
||||
```java
|
||||
final var tag = "CommentV1alpha1Console";
|
||||
return SpringdocRouteBuilder.route()
|
||||
.GET("comments", this::listComments, builder -> {
|
||||
builder.operationId("ListComments")
|
||||
.description("List comments.")
|
||||
.tag(tag)
|
||||
.response(responseBuilder()
|
||||
.implementation(ListResult.generateGenericClass(ListedComment.class))
|
||||
);
|
||||
CommentQuery.buildParameters(builder);
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
或者是在插件中定义了自定义模型,自定义模型自动生成的 CRUD APIs 是已经支持的。
|
||||
|
||||
以下是如何配置和使用 `generateApiClient` 的详细步骤:
|
||||
|
||||
##### 配置 `generateApiClient`
|
||||
|
||||
在 build.gradle 文件中,使用 haloPlugin 块来配置 OpenAPI 文档生成和 API 客户端生成的相关设置:
|
||||
|
||||
```groovy
|
||||
haloPlugin {
|
||||
openApi {
|
||||
// outputDir = file("$rootDir/api-docs/openapi/v3_0") // 指定 OpenAPI 文档的输出目录默认输出到 build 目录下,不建议修改,除非需要提交到代码仓库
|
||||
groupingRules {
|
||||
// 定义 API 分组规则,用于为插件项目中的 APIs 分组然后只对此分组生成 API 客户端代码
|
||||
// 定义了一个名为 extensionApis 的分组,task 会通过 /v3/api-docs/extensionApis 访问到 api docs 然后生成 API 客户端代码
|
||||
// extensionApis 名称可以替换为其他名称,但需要与 groupedApiMappings 中的名称一致
|
||||
extensionApis {
|
||||
// 分组显示名称,避免与其他分组重名建议替换 {your-plugin-name} 为插件名称
|
||||
displayName = 'Extension API for {your-plugin-name}'
|
||||
// 分组的 API 规则用于匹配插件项目中的 API 将其划分到此分组,它是一个 Ant 风格的路径匹配规则可以写多个
|
||||
pathsToMatch = ['/apis/staticpage.halo.run/v1alpha1/**']
|
||||
}
|
||||
}
|
||||
groupedApiMappings = [
|
||||
// 这里为固定写法,照搬即可,除非是 groupingRules 中 extensionApis 的名字修改了
|
||||
'/v3/api-docs/extensionApis': 'extensionApis.json'
|
||||
]
|
||||
generator {
|
||||
// 指定 API 客户端代码的输出目录如 console 或 ui
|
||||
outputDir = file("${projectDir}/console/src/api/generated")
|
||||
|
||||
// 定制生成,以下是默认配置可以不需要添加到 build.gradle 中
|
||||
additionalProperties = [
|
||||
useES6: true,
|
||||
useSingleRequestParameter: true,
|
||||
withSeparateModelsAndApi: true,
|
||||
apiPackage: "api",
|
||||
modelPackage: "models"
|
||||
]
|
||||
// 类型映射,用于将 OpenAPI 中的类型映射到 TypeScript 中的类型,以下是默认配置可以不需要添加到 build.gradle 中
|
||||
typeMappings = [
|
||||
set: "Array"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当配置了 `openApi` 规则之后,`haloServer` 或 `watch` 启动可以通过 `http://localhost:8090/swagger-ui.html` 访问 API 文档,
|
||||
并通过选择你配置的 `displayName` 分组来**检查你的规则是否正确**。
|
||||
这一步检查有助于避免生成 API Client 为空或者生成 `roleTemplates.yaml` 为空的情况。
|
||||
|
||||
##### 执行 `generateApiClient`
|
||||
|
||||
在项目目录中执行以下命令即可生成 API 客户端代码到指定目录:
|
||||
|
||||
```shell
|
||||
./gradlew generateApiClient
|
||||
```
|
||||
|
||||
然后在 `openApi.generator.outputDir` 目录创建一个 `index.ts` 文件并创建实例,以瞬间插件为例
|
||||
|
||||
```typescript
|
||||
// console/src/api/index.ts
|
||||
// 先引入 axiosInstance 用于请求
|
||||
import { axiosInstance } from "@halo-dev/api-client";
|
||||
// 这里导入的是声明 API doc 时指定的 tag 名称,如上文中定义的 CommentV1alpha1Console
|
||||
import {
|
||||
ConsoleApiMomentHaloRunV1alpha1MomentApi,
|
||||
MomentV1alpha1Api,
|
||||
UcApiMomentHaloRunV1alpha1MomentApi,
|
||||
} from "./generated";
|
||||
|
||||
// MomentV1alpha1Api 是自定义模型生成的 API tag 这里创建了一个 momentsCoreApiClient 实例
|
||||
const momentsCoreApiClient = {
|
||||
moment: new MomentV1alpha1Api(undefined, "", axiosInstance),
|
||||
};
|
||||
|
||||
// ConsoleApiMomentHaloRunV1alpha1MomentApi 是用于在 console 端调用的 APIs 的 tag,这里创建了一个 momentsConsoleApiClient 实例用于在 console 端调用
|
||||
const momentsConsoleApiClient = {
|
||||
moment: new ConsoleApiMomentHaloRunV1alpha1MomentApi(
|
||||
undefined,
|
||||
"",
|
||||
axiosInstance
|
||||
),
|
||||
};
|
||||
|
||||
// 用于在个人中心调用的 APIs,单独创建一个 momentsUcApiClient 实例
|
||||
const momentsUcApiClient = {
|
||||
moment: new UcApiMomentHaloRunV1alpha1MomentApi(undefined, "", axiosInstance),
|
||||
};
|
||||
// 导出实例
|
||||
export { momentsConsoleApiClient, momentsCoreApiClient, momentsUcApiClient };
|
||||
```
|
||||
|
||||
使用定义的实例:
|
||||
|
||||
```typescript
|
||||
import { momentsConsoleApiClient } from "@/api";
|
||||
|
||||
// 查询瞬间的标签
|
||||
const { data } = await momentsConsoleApiClient.moment.listTags({
|
||||
name: props.keyword?.value,
|
||||
});
|
||||
```
|
||||
|
||||
:::tip
|
||||
它会先执行 `generateOpenApiDocs` 任务根据配置访问 `/v3/api-docs/extensionApis` 获取 OpenAPI 文档,
|
||||
并将 OpenAPI 的 Schema 文件保存到 `openApi.outputDir` 目录下,然后再由 `generateApiClient` 任务根据 Schema 文件生成 API 客户端代码到 `openApi.generator.outputDir` 目录下。
|
||||
:::
|
||||
|
||||
:::warning
|
||||
执行 `generateApiClient` 任务时会先删除 `openApi.generator.outputDir` 下的所有文件,因此建议将 API client 的输出目录设置为一个独立的目录,以避免误删其他文件。
|
||||
|
||||
执行 `generateApiClient` 前建议注释掉你所配置的 `build` 任务对应的 `dependsOn` 任务,以避免因依赖前端构建任务可能无法生成 API Client 的问题。
|
||||
:::
|
||||
|
||||
### generateRoleTemplates 任务
|
||||
|
||||
在 Halo 插件开发中,权限管理是一个关键问题,尤其是配置[角色模板](https://docs.halo.run/developer-guide/plugin/security/rbac#%E8%A7%92%E8%89%B2%E6%A8%A1%E6%9D%BF)时,角色的 `rules` 部分往往让开发者感到困惑。具体来说,如何区分资源、apiGroup、verb 等概念是许多开发者的痛点。
|
||||
|
||||
`generateRoleTemplates` Task 的出现正是为了简化这一过程,该任务能够根据 [配置 Generate Api Client](#配置-generateapiclient) 中的配置获取到 OpenAPI docs 的 JSON 文件,并自动生成 Halo 的 Role YAML 文件,让开发者可以专注于自己的业务逻辑,而不是纠结于复杂的角色 `rules` 配置。
|
||||
|
||||
在生成的 `roleTemplate.yaml` 文件中,rules 部分是基于 OpenAPI docs 中 API 资源和请求方式自动生成的,覆盖了可能的操作。
|
||||
然而,在实际的生产环境中,Role 通常会根据具体的需求被划分为不同的权限级别,例如:
|
||||
|
||||
- 查看权限的角色模板:通常只包含对资源的读取权限,如 get、list、watch 等。
|
||||
- 管理权限的角色模板:则可能包含创建、修改、删除等权限,如 create、update、delete。
|
||||
|
||||
> watch verb 是对于 WebSocket API,不会在 roleTemplates.yaml 中体现为 watch,而是体现为 list,因此需要开发者根据实际情况进行调整。
|
||||
|
||||
因此,生成的 YAML 文件只是一个基础模板,涵盖了所有可用的操作。开发者需要根据自己的实际需求,对这些 rules 进行调整。比如,针对只需要查看资源的场景,开发者可以从生成的 YAML 中删除`修改`和`删除`相关的操作,保留读取权限。
|
||||
而对于需要管理资源的场景,可以保留`创建`、`更新`和`删除`权限,对于角色模板的依赖关系和聚合关系,开发者也可以根据实际情况进行调整。
|
||||
|
||||
通过这种方式,开发者可以使用生成的 YAML 文件作为基础,快速定制出符合不同场景的权限配置,而不必从头开始编写复杂的规则以减少出错的可能性。
|
||||
|
||||
#### 如何使用
|
||||
|
||||
在 build.gradle 文件中,使用 haloPlugin 块来配置 OpenAPI 文档生成和 Role 模板生成的相关设置:
|
||||
|
||||
```groovy
|
||||
haloPlugin {
|
||||
openApi {
|
||||
// 参考配置 generateApiClient 中的配置
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在项目目录中执行以下命令即可生成 `roleTemplates.yaml` 文件到 `worplace` 目录:
|
||||
|
||||
```shell
|
||||
./gradlew generateRoleTemplates
|
||||
```
|
||||
|
||||
## 调试后端代码
|
||||
|
||||
如果你想调试后端代码,可以在 `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.2.x 及以上版本
|
||||
|
||||
如果 `run.halo.plugin.devtools` 从旧版本升级到 `0.2.0` 版本,需要先将 Gradle 版本升级到 `8.3` 以上,你可以通过以下命令升级 Gradle 版本:
|
||||
|
||||
在插件项目根目录下执行:
|
||||
|
||||
```shell
|
||||
# 如将 Gradle 版本升级至 8.9
|
||||
./gradlew wrapper --gradle-version=8.9
|
||||
```
|
@@ -0,0 +1,118 @@
|
||||
---
|
||||
title: 插件注册和配置
|
||||
description: 了解插件定义文件 plugin.yaml 如何配置
|
||||
---
|
||||
|
||||
在 Halo 插件开发中,`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` | 固定写法,定义插件的 API 版本和类型,不可更改。 |
|
||||
| `metadata.name` | 插件的唯一标识名,长度不超过 253 个字符,仅包含小写字母、数字或 `-`,以字母或数字开头和结尾。 |
|
||||
| `spec.enabled` | 表示插件是否在安装时自动启用。为了安全性,生产环境需要用户手动启用。 |
|
||||
| `spec.requires` | 插件支持的 Halo 版本范围,遵循 [SemVer Range Expressions](https://github.com/zafarkhaja/jsemver#range-expressions)。参考:[常用 SemVer Range Expressions](#common-range-expressions) |
|
||||
| `spec.author` | 插件作者的信息,包括名称和支持网址。 |
|
||||
| `spec.logo` | 插件的 logo,支持 URL 链接或相对于项目 `src/main/resources` 目录的文件路径。 |
|
||||
| `spec.settingName`(可选) | 插件配置表单的名称,用于提供用户可视化配置的表单,名称建议为 "插件名-settings"。参考:[表单定义](../../form-schema.md)。 |
|
||||
| `spec.configMapName`(可选) | 插件配置存储的 ConfigMap 名称,通常建议命名为 "插件名-configmap"。没有配置 `settingName` 则不需要配置此项。 |
|
||||
| `spec.homepage` | 插件的主页链接,通常指向插件的官方文档或帮助页面。 |
|
||||
| `spec.repo` | 插件源码的仓库地址。 |
|
||||
| `spec.issues` | 插件的反馈问题地址,可以是 GitHub Issues。 |
|
||||
| `spec.displayName` | 插件的显示名称,它通常是以少数几个字来概括插件的用途。 |
|
||||
| `spec.description` | 插件的简短描述,用于说明插件的用途。 |
|
||||
| `spec.license` | 插件的许可协议,包含协议名称和链接。参考:[Software License](https://en.wikipedia.org/wiki/Software_license)。 |
|
||||
|
||||
:::tip
|
||||
如果你在 plugin.yaml 中配置了 `settingName` 但确没有对应的 `Setting` 自定义模型资源文件,会导致插件无法启动,原因是 `Setting` 模型 `metadata.name` 为你配置的 `settingName` 的资源无法找到。
|
||||
:::
|
||||
|
||||
## 插件运行模式
|
||||
|
||||
Halo 插件可以在两种模式下运行:`deployment`(默认)模式和 `development` 开发模式。
|
||||
|
||||
- `deployment` **模式**:标准的插件发布流程,插件打包成 JAR 文件后部署到 Halo。这是插件的生产运行模式。
|
||||
- `development` **模式**:适用于插件开发阶段,开发者可以在无需打包和部署的情况下直接运行和调试插件,极大地提升了开发效率。
|
||||
|
||||
### 配置运行模式
|
||||
|
||||
要配置插件的运行模式,可以在 Halo 的配置文件中进行以下设置:
|
||||
|
||||
#### 以 `deployment` 模式运行插件
|
||||
|
||||
默认情况下,Halo 以 `deployment` 模式运行插件,无需特别配置。如果需要明确指定,可以参考以下配置:
|
||||
|
||||
```yaml
|
||||
halo:
|
||||
plugin:
|
||||
runtime-mode: deployment
|
||||
```
|
||||
|
||||
参考 [传统方式运行](../hello-world.md#run-with-traditional-way)
|
||||
|
||||
#### 以 `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`。
|
||||
:::
|
||||
|
||||
### 常用的 SemVer Range Expressions {#common-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`
|
||||
|
||||
更多详细信息请参考[SemVer Range Expressions](https://github.com/zafarkhaja/jsemver#range-expressions)
|
@@ -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,260 @@
|
||||
---
|
||||
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,任何插件都可以直接依赖注入这些 Bean。
|
||||
|
||||
### ReactiveExtensionClient
|
||||
|
||||
`ReactiveExtensionClient` 是一个用于管理自定义模型对象的增删改查的 Bean,它是反应式的。
|
||||
|
||||
参考 [与自定义模型交互](../../api-reference/server/extension-client.md) 了解更多。
|
||||
|
||||
### ExtensionClient
|
||||
|
||||
`ExtensionClient` 作用和方法与 ReactiveExtensionClient 一样,但它是阻塞的,只能用在非 NIO 线程中,如后台任务。
|
||||
|
||||
### SchemeManager
|
||||
|
||||
`SchemeManager` 是一个用于管理自定义模型定义的注册和销毁的 Bean。
|
||||
|
||||
API 参考:[SchemeManager](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/extension/SchemeManager.java)
|
||||
|
||||
### UserService
|
||||
|
||||
用于操作 Halo 用户的 Bean,包括获取用户信息、更新密码、创建用户等函数。
|
||||
|
||||
API 参考 [UserService](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/core/user/service/UserService.java)
|
||||
|
||||
### ReactiveUserDetailsService
|
||||
|
||||
用于获取用户信息的 Bean,它只有一个方法 `Mono<UserDetails> findByUsername(String username)`。
|
||||
|
||||
### RoleService
|
||||
|
||||
用于操作 Halo 角色的 Bean,包括查询角色绑定、角色及依赖等函数。
|
||||
|
||||
API 参考 [RoleService](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/core/user/service/RoleService.java)
|
||||
|
||||
### AttachmentService
|
||||
|
||||
用于操作 Halo 附件的 Bean,包括上传、删除附件以及获取附件访问链接等函数。
|
||||
|
||||
API 参考 [AttachmentService](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/core/extension/service/AttachmentService.java)。
|
||||
|
||||
### PostContentService
|
||||
|
||||
在 Halo 中,文章内容具有版本管理的概念。第一个版本是完整的文章内容,称为 `baseSnapshot`。从第二个版本开始,每个版本都是基于 `baseSnapshot` 的增量内容。因此,要查询完整的文章内容,需要获取 `baseSnapshot` 以及对应的版本增量内容并合并。
|
||||
|
||||

|
||||
|
||||
为了方便插件开发者获取文章内容,Halo 提供了 `PostContentService`,以简化这一过程的复杂性。
|
||||
|
||||
- getHeadContent:获取文章的最新版本内容(包括正在编辑的草稿)。
|
||||
- getReleaseContent:获取最新发布的文章内容。
|
||||
- getSpecifiedContent:获取指定 snapshotName 对应的文章内容。
|
||||
- listSnapshots:获取指定文章的所有版本的 Snapshot 对象的名称列表。
|
||||
|
||||
API 参考:[PostContentService](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/content/PostContentService.java)
|
||||
文章自定义模型定义参考 [Post](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/core/extension/content/Post.java)
|
||||
|
||||
### NotificationReasonEmitter
|
||||
|
||||
用于发送通知事件的 Bean。
|
||||
|
||||
使用示例参考 [通知事件触发示例](../../api-reference/server/notification.md#reason-emitter-example)
|
||||
|
||||
API 参考:[NotificationReasonEmitter](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/notification/NotificationReasonEmitter.java)
|
||||
|
||||
### NotificationCenter
|
||||
|
||||
用于管理通知的订阅和取消订阅。
|
||||
|
||||
使用示例参考 [通知订阅示例](../../api-reference/server/notification.md#subscribe-example)
|
||||
|
||||
API 参考:[NotificationCenter](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/notification/NotificationCenter.java)
|
||||
|
||||
### ExternalLinkProcessor
|
||||
|
||||
用于将一个站内相对链接转换为绝对链接。
|
||||
|
||||
如配置了外部访问地址为 `https://example.com`,那么将 `/post/1` 转换为 `https://example.com/post/1`。
|
||||
|
||||
API 参考:[ExternalLinkProcessor](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/infra/ExternalLinkProcessor.java);
|
||||
|
||||
### LoginHandlerEnhancer
|
||||
|
||||
Halo 提供了登录增强机制,插件可以在登录成功或失败时调用登录增强器,使 Halo 可以执行额外的处理逻辑。
|
||||
|
||||
参考 [登录增强器](../../api-reference/server/login-handler-enhancer.md) 了解更多。
|
||||
|
||||
使用示例参考 [用户名密码登陆成功和失败的增强切入示例](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/application/src/main/java/run/halo/app/security/authentication/login/UsernamePasswordHandler.java#L106)
|
||||
|
||||
### BackupRootGetter
|
||||
|
||||
用于获取 Halo 备份文件的根目录。它是 `Supplier<Path>` 的子类。
|
||||
|
||||
### PluginsRootGetter
|
||||
|
||||
用于获取 Halo 插件的根目录。它是 `Supplier<Path>` 的子类。
|
||||
|
||||
### ExtensionGetter
|
||||
|
||||
用于获取扩展点实例(扩展)的 Bean。
|
||||
|
||||
例如,Halo 定义了 `AttachmentHandler` 这个扩展点,你可以通过 `ExtensionGetter` 获取到所有实现了 `AttachmentHandler` 接口的扩展。
|
||||
|
||||
有了它,插件中便可以定义自己的扩展点,然后由其他插件实现以达到插件扩展插件的目的。
|
||||
|
||||
API 参考 [ExtensionGetter](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/plugin/extensionpoint/ExtensionGetter.java)
|
||||
|
||||
### ServerSecurityContextRepository
|
||||
|
||||
用于获取操作用户的认证上下文信息,如登录成功后保存认证信息。
|
||||
|
||||
ServerSecurityContextRepository 被 [ReactorContextWebFilter](https://github.com/spring-projects/spring-security/blob/9d2ca3da6a285f31ebd2da5f019127e1527e5042/web/src/main/java/org/springframework/security/web/server/context/ReactorContextWebFilter.java) 使用来获取操作用户的认证上下文信息并填充到 ReactiveSecurityContextHolder 中。
|
||||
此过滤器的执行顺序在 [SecurityWebFiltersOrder](https://github.com/spring-projects/spring-security/blob/9d2ca3da6a285f31ebd2da5f019127e1527e5042/config/src/main/java/org/springframework/security/config/web/server/SecurityWebFiltersOrder.java#L47) 中定义。因此如果你的过滤器在此过滤器之前执行,那么你将无法从 ReactiveSecurityContextHolder 中获取到操作用户的认证上下文信息只能通过注入 ServerSecurityContextRepository 来获取。
|
||||
|
||||
API 参考:[ServerSecurityContextRepository](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/server/context/ServerSecurityContextRepository.html)
|
||||
|
||||
### CryptoService
|
||||
|
||||
Halo 根据用户名密码登录时,会先使用 CryptoService 的 `readPublicKey` 方法读取公钥,然后使用公钥加密密码,再发送给服务器。
|
||||
|
||||
当插件需要添加一些拦截器处理登录请求时可能需要获取原始密码,此时可以使用 CryptoService 的 `decrypt` 方法解密密码。
|
||||
|
||||
也可以复用它的公钥来作为一些加密算法的密钥。
|
||||
|
||||
API 参考 [CryptoService](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/security/authentication/CryptoService.java)
|
||||
|
||||
### ExternalUrlSupplier
|
||||
|
||||
`ExternalUrlSupplier` 是一个用于获取用户配置的 Halo 外部访问地址的 Bean。
|
||||
|
||||
API 参考:[ExternalUrlSupplier](https://github.com/halo-dev/halo/blob/25086ee3e63f0c8b6ed380140a068c44404ef2b2/api/src/main/java/run/halo/app/infra/ExternalUrlSupplier.java)
|
||||
|
||||
### RateLimiterRegistry
|
||||
|
||||
`RateLimiterRegistry` 是一个用于管理限流器的 Bean。
|
||||
|
||||
如果插件定义的某些 API 需要限流,可以使用 `RateLimiterRegistry` 来创建一个限流器,但是需要注意的是,**必须要在插件停止的生命周期方法里销毁你所创建的限流器**,否则会导致内存泄漏。
|
||||
|
||||
#### 使用示例
|
||||
|
||||
以下示例展示了如何使用 `RateLimiterRegistry` 创建一个限流器:
|
||||
|
||||
```java
|
||||
final Set<String> limiterNames = new HashSet<>();
|
||||
|
||||
Mono<Void> sendEmailVerificationCode(String username, String email) {
|
||||
return buildSendEmailRateLimiter(username, email) // step 1: 构建一个限流器
|
||||
.transformDeferred(rateLimiter -> rateLimiter.map(RateLimiterOperator::of)) // step 2: 返回一个经过变换的新 Mono
|
||||
// step 3: 从这里开始的操作符都会受到限流的影响
|
||||
.flatMap(rateLimiter -> emailVerificationService.sendVerificationCode(username, email))
|
||||
// 转换 RequestNotPermitted 为 RateLimitExceededException 以使全局异常处理器能够处理
|
||||
.onErrorMap(RequestNotPermitted.class, RateLimitExceededException::new);
|
||||
}
|
||||
|
||||
RateLimiter buildSendEmailRateLimiter(String username, String email) {
|
||||
var rateLimiterKey = "send-email-verification-code-" + username + ":" + email;
|
||||
var rateLimiter = rateLimiterRegistry.rateLimiter(rateLimiterKey, new RateLimiterConfig.Builder()
|
||||
// 频次限制为 1 次
|
||||
.limitForPeriod(1)
|
||||
// 限制刷新周期为 60 秒,即 60 秒内只能执行 1 次
|
||||
.limitRefreshPeriod(Duration.ofSeconds(60))
|
||||
.build());
|
||||
// 添加 limiter 到自己实现的 Registry 中保存起来以便在插件停止时清理
|
||||
limiters.add(rateLimiterKey);
|
||||
return limiter;
|
||||
}
|
||||
```
|
||||
|
||||
在插件停止的生命周期方法里销毁你所创建的限流器:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void stop() {
|
||||
limiters.forEach(rateLimiterRegistry::remove);
|
||||
}
|
||||
```
|
||||
|
||||
`transformDeferred` 的作用是将 Mono 传入你提供的变换器中,返回一个经过变换的新 Mono。由于 RateLimiterOperator 是基于 Publisher 的装饰器(decorator),它会监视这个 Mono 的订阅和执行情况,从而对整个 Mono 的操作链进行限流。
|
||||
|
||||
换句话说,因为 `RateLimiterOperator` 装饰了这个 Mono,所以任何接在 `transformDeferred()` 之后的操作符都会受到限流的影响,直到整个流结束。
|
||||
|
||||
### SystemInfoGetter
|
||||
|
||||
用于获取站点基本信息的 Bean,它是 `Supplier<Mono<SystemInfo>>` 的子类,仅有一个 `get` 方法。
|
||||
|
||||
> 此为 `2.20.11` 版本新增的 Bean 因此需要 Halo 2.20.11 及以上版本才可使用。
|
||||
|
||||
可以获取到的数据示例如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"title" : "guqing's blog",
|
||||
"subtitle" : "副标题",
|
||||
"logo" : "/upload/myavatar.png",
|
||||
"favicon" : "/upload/myavatar.png",
|
||||
"url" : "http://localhost:8090",
|
||||
"version" : {
|
||||
"majorVersion" : 2,
|
||||
"minorVersion" : 20,
|
||||
"normalVersion" : "2.20.10",
|
||||
"preRelease" : true,
|
||||
"publicApiStable" : true,
|
||||
"patchVersion" : 10,
|
||||
"preReleaseVersion" : "SNAPSHOT",
|
||||
"buildMetadata" : "",
|
||||
"stable" : false
|
||||
},
|
||||
"seo" : {
|
||||
"blockSpiders" : false,
|
||||
"keywords" : "keyword1,keyword2",
|
||||
"description" : "站点描述"
|
||||
},
|
||||
"locale" : "zh_CN_#Hans",
|
||||
"timeZone" : "Asia/Shanghai",
|
||||
"activatedThemeName" : "theme-earth"
|
||||
}
|
||||
```
|
@@ -0,0 +1,70 @@
|
||||
---
|
||||
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`:插件后端的入口文件,位于 `src/main/java/run/halo/starter` 路径下。你可以根据需要修改包名和类名,但需要确保该类继承 `run.halo.app.plugin.BasePlugin`,以指定其为插件的入口。
|
||||
- `plugin.yaml`:这是插件的描述文件,位于 `src/main/resources` 目录下。该文件是必须的,包含插件的基本信息,如插件名称、版本、作者、描述以及依赖等内容。
|
||||
- `resources/console`:该文件夹通常包含前端部分打包后生成的文件,包括 main.js(JavaScript 文件)和 style.css(样式表)。如果插件不包含前端部分,此目录可以忽略。
|
||||
|
||||
:::warning 注意
|
||||
从 2.11 开始,Halo 支持了 UC 个人中心,且个人中心和 Console 的插件机制共享,所以为了避免歧义,`resources/console` 在后续版本会被重命名为 `resources/ui`,但同时也会兼容 `resources/console`。
|
||||
:::
|
||||
|
||||
### 前端部分
|
||||
|
||||
`ui` 目录下存放插件的前端代码和相关资源,前端部分通常采用 Vue 作为开发框架,并推荐使用 TypeScript 作为主要语言,这有助于在编译阶段捕获潜在错误。
|
||||
|
||||
以下是前端部分各文件夹和文件的说明:
|
||||
|
||||
- `src/index.ts`:作为前端部分的插件的入口文件。
|
||||
- `views`:用于存放 Vue 组件的页面视图文件。
|
||||
- `styles`:用于存放全局样式和自定义 CSS 文件。
|
||||
- `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`:扩展点定义,详细文档可参考 [扩展点](../../extension-points/ui/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