From 1f465bc5f835eb080499b987e8599b6724c5b008 Mon Sep 17 00:00:00 2001 From: Nekoli Date: Fri, 24 Jul 2026 01:45:04 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=85=E4=BA=8C?= =?UTF-8?q?=E6=AD=A5=E9=AA=8C=E8=AF=81=E3=80=81=E6=A0=87=E7=AD=BE=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E3=80=81=E8=A7=92=E8=89=B2=E4=B8=8E=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/user-guide/roles.md | 163 +++++++++++++++++++++++++++++++++++++++ docs/user-guide/tags.md | 84 ++++++++++++++++++++ docs/user-guide/totp.md | 95 +++++++++++++++++++++++ sidebars.js | 3 + 4 files changed, 345 insertions(+) create mode 100644 docs/user-guide/roles.md create mode 100644 docs/user-guide/tags.md create mode 100644 docs/user-guide/totp.md diff --git a/docs/user-guide/roles.md b/docs/user-guide/roles.md new file mode 100644 index 0000000..cce6c6e --- /dev/null +++ b/docs/user-guide/roles.md @@ -0,0 +1,163 @@ +--- +title: 角色与权限 +description: 管理用户角色和权限,实现细粒度的访问控制。 +--- + +Ikaros 提供了角色(Role)和权限(Authority)管理功能,方便对多用户进行细粒度的访问控制。 + +## 角色管理 + +角色是一组权限的集合,系统默认包含以下角色: + +- **admin** — 管理员,拥有所有权限 +- **user** — 普通用户,可查看和管理自己的内容 + +### 获取角色列表 + +```bash +GET /api/v1/roles +``` + +### 创建角色 + +```bash +POST /api/v1/role +Content-Type: application/json + +{ + "name": "editor", + "displayName": "编辑者", + "description": "内容编辑角色" +} +``` + +### 更新角色 + +```bash +PUT /api/v1/role +Content-Type: application/json + +{ + "id": "角色UUID", + "name": "editor", + "displayName": "编辑者", + "description": "可编辑内容" +} +``` + +### 删除角色 + +```bash +DELETE /api/v1/role/id/{roleId} +``` + +## 权限管理 + +权限(Authority)定义了用户能执行的具体操作。 + +### 创建权限 + +```bash +POST /api/v1/authority +Content-Type: application/json + +{ + "allow": true, + "type": "SUBJECT", + "target": "write", + "authority": "ikaros:subject:write" +} +``` + +### 查询权限列表 + +```bash +GET /api/v1/authorities?page=0&size=20 +``` + +可选参数: + +| 参数 | 说明 | +|------|------| +| page | 页码(从0开始) | +| size | 每页数量 | +| type | 权限类型筛选 | +| target | 操作目标筛选 | + +## 角色-权限关联 + +### 为角色添加权限 + +```bash +POST /api/v1/role/authorities +Content-Type: application/json + +{ + "roleId": "角色UUID", + "authorityIds": ["权限1ID", "权限2ID"] +} +``` + +### 查询角色的权限 + +```bash +GET /api/v1/role/authorities/roleId/{roleId} +``` + +### 移除角色的权限 + +```bash +DELETE /api/v1/role/authorities +Content-Type: application/json + +{ + "roleId": "角色UUID", + "authorityIds": ["权限1ID", "权限2ID"] +} +``` + +## 用户-角色关联 + +### 为用户分配角色 + +```bash +POST /api/v1/user/roles +Content-Type: application/json + +{ + "userId": "用户UUID", + "roleIds": ["角色ID1", "角色ID2"] +} +``` + +### 查询用户的角色 + +```bash +GET /api/v1/user/roles/{userId} +``` + +### 移除用户的角色 + +```bash +DELETE /api/v1/user/roles +Content-Type: application/json + +{ + "userId": "用户UUID", + "roleIds": ["角色ID1", "角色ID2"] +} +``` + +## 典型配置场景 + +### 只读访客 + +新建只读角色,仅赋予查看条目的权限,适合给朋友分享媒体库但不允许修改。 + +### 内容编辑者 + +新建编辑角色,赋予条目和附件的读写权限,允许协助整理媒体库但无法管理用户和系统设置。 + +### 管理员 + +管理员拥有全部权限,适合负责整个服务器运维的人员。 diff --git a/docs/user-guide/tags.md b/docs/user-guide/tags.md new file mode 100644 index 0000000..601c2a4 --- /dev/null +++ b/docs/user-guide/tags.md @@ -0,0 +1,84 @@ +--- +title: 标签管理 +description: 为条目和附件添加标签,方便分类管理。 +--- + +Ikaros 支持为条目(Subject)和附件(Attachment)添加标签,方便您对内容进行分类管理和快速检索。 + +## 标签类型 + +标签分为两种类型: + +- **SUBJECT(条目标签)** — 用于标记条目,如"番剧推荐"、"经典动画"、"新番"等 +- **ATTACHMENT(附件标签)** — 用于标记附件,如"字幕"、"封面图"、"主题曲"等 + +## 创建标签 + +```bash +POST /api/v1/tag +Content-Type: application/json + +{ + "name": "我的标签", + "type": "SUBJECT", + "masterId": "条目UUID" +} +``` + +参数说明: + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| name | string | 是 | 标签名称 | +| type | string | 是 | 标签类型:`SUBJECT` 或 `ATTACHMENT` | +| masterId | string | 否 | 关联的条目或附件 ID,不填则创建全局标签 | + +## 查询标签 + +### 按条件查询 + +```bash +GET /api/v1/tags/condition?type=SUBJECT&name=动画 +``` + +可选参数: + +| 参数 | 说明 | +|------|------| +| type | 标签类型筛选 | +| masterId | 关联对象 ID 筛选 | +| name | 标签名称模糊搜索 | + +### 查询条目的标签 + +```bash +GET /api/v1/tags/subject/subjectId/{subjectId} +``` + +### 查询附件的标签 + +```bash +GET /api/v1/tags/attachment/attachmentId/{attachmentId} +``` + +## 删除标签 + +### 按 ID 删除 + +```bash +DELETE /api/v1/tag/id/{id} +``` + +### 按条件删除 + +```bash +DELETE /api/v1/tag/condition?type=SUBJECT&name=我的标签&masterId=条目UUID +``` + +## 使用场景 + +**整理番剧:** 给不同类型的番剧打上标签("热血"、"治愈"、"搞笑"),方便后续按标签筛选查看。 + +**管理附件:** 给同一部番剧的封面图、字幕、主题曲分别打上标签,便于附件管理。 + +**协作标记:** 多人协作时可以通过标签系统共享标记,方便团队成员快速定位内容。 diff --git a/docs/user-guide/totp.md b/docs/user-guide/totp.md new file mode 100644 index 0000000..7f1e407 --- /dev/null +++ b/docs/user-guide/totp.md @@ -0,0 +1,95 @@ +--- +title: 二步验证(TOTP) +description: 为您的 Ikaros 账号开启 TOTP 二步验证,提升账号安全性。 +--- + +Ikaros 支持基于 TOTP(Time-based One-Time Password)的二步验证,可以在登录时额外要求输入一次性验证码,有效保护账号安全。 + +## 工作原理 + +1. 用户在手机上安装认证器 App(如 Google Authenticator、Microsoft Authenticator、Authy 等) +2. 在 Ikaros 控制台中生成密钥,用认证器 App 扫描二维码或手动输入密钥 +3. 登录时先输入用户名密码,验证通过后再输入认证器 App 上显示的 6 位验证码 +4. 验证码每 30 秒刷新一次 + +## 开启二步验证 + +### 1. 生成密钥 + +登录 Ikaros 控制台后,调用以下接口获取 TOTP 密钥和 URI: + +```bash +POST /api/v1/security/auth/totp/setup +``` + +返回示例: + +```json +{ + "secret": "JBSWY3DPEHPK3PXP", + "otpAuthUri": "otpauth://totp/Ikaros:username?secret=JBSWY3DPEHPK3PXP&issuer=Ikaros" +} +``` + +### 2. 添加至认证器 App + +- **Google Authenticator**: 打开 App → 添加 → 输入设置密钥 → 输入密钥 `JBSWY3DPEHPK3PXP` 或使用 otpauth URI 生成二维码扫描 +- **Microsoft Authenticator**: 打开 App → 添加 → 其他账号 → 手动输入密钥 +- **Authy**: 打开 App → 设置 → 添加账号 → 手动输入密钥 + +### 3. 验证并启用 + +在认证器 App 上获取 6 位验证码,调用以下接口验证并启用二步验证: + +```bash +POST /api/v1/security/auth/totp/enable?code=123456 +``` + +如果验证码正确,二步验证即启用成功。 + +## 使用二步验证登录 + +1. 先调用登录接口获取 JWT Token +2. 如果响应中 `totpRequired` 为 `true`,说明该账号开启了二步验证 +3. 调用验证接口,传入临时 Token 和认证器 App 上显示的 6 位验证码: + +```bash +POST /api/v1/security/auth/totp/validate +Content-Type: application/json + +{ + "tempToken": "上一步获取的临时Token", + "code": "123456" +} +``` + +4. 验证通过后返回正式 JWT Token,后续请求带上此 Token 即可 + +## 查询二步验证状态 + +```bash +GET /api/v1/security/auth/totp/status +``` + +返回: + +```json +{ + "enabled": true +} +``` + +## 关闭二步验证 + +需要提供当前登录密码验证身份: + +```bash +POST /api/v1/security/auth/totp/disable?password=your_password +``` + +## 注意事项 + +- **请务必在关闭二步验证前确保能正常登录**,否则可能被锁定在账号外 +- 如果丢失了认证器 App,可以通过控制台其他管理员账号协助关闭二步验证 +- 二步验证的密钥仅保存在服务端数据库中,建议在生成密钥后立即备份 +- 同一账号可以在多个认证器 App 上同时配置(用同一密钥) diff --git a/sidebars.js b/sidebars.js index be3312d..020551a 100644 --- a/sidebars.js +++ b/sidebars.js @@ -60,6 +60,9 @@ const sidebars = { "user-guide/settings", "user-guide/users", "user-guide/collections", + "user-guide/tags", + "user-guide/totp", + "user-guide/roles", "user-guide/faq" ] }, From bb0f493b7dd771fa8f0b2cc0ec3a3731c9edaeaa Mon Sep 17 00:00:00 2001 From: Nekoli Date: Fri, 24 Jul 2026 01:46:36 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=85=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E8=B7=AF=E7=94=B1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/user-guide/custom-routes.md | 107 +++++++++++++++++++++++++++++++ sidebars.js | 1 + 2 files changed, 108 insertions(+) create mode 100644 docs/user-guide/custom-routes.md diff --git a/docs/user-guide/custom-routes.md b/docs/user-guide/custom-routes.md new file mode 100644 index 0000000..0d29194 --- /dev/null +++ b/docs/user-guide/custom-routes.md @@ -0,0 +1,107 @@ +--- +title: 自定义路由 +description: Ikaros 的自定义资源路由系统,支持像 Kubernetes CRD 一样注册自定义资源并自动生成 RESTful API。 +--- + +Ikaros 提供了一套类似 Kubernetes Custom Resource 的自定义资源系统。开发者可以通过注解定义自定义资源,系统会自动为其生成完整的 RESTful CRUD API 路由。 + +## 核心概念 + +### 自定义资源(Custom Resource) + +通过 `@Custom` 注解定义一个自定义资源,包含以下属性: + +| 属性 | 说明 | 示例 | +|------|------|------| +| `group` | API 分组 | `"myapp.ikaros.run"` | +| `version` | API 版本 | `"v1"` | +| `kind` | 资源类型名称 | `"Bookmark"` | +| `singular` | 单数形式 | `"bookmark"` | +| `plural` | 复数形式 | `"bookmarks"` | + +### API 路由规则 + +注册自定义资源后,系统会自动生成以下 RESTful API: + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/apis/{group}/{version}/{plural}` | 查询所有资源 | +| GET | `/apis/{group}/{version}/{plural}?page=0&size=20` | 分页查询 | +| GET | `/apis/{group}/{version}/{singular}/{name}` | 按名称查询单个资源 | +| GET | `/apis/{group}/{version}/{singular}/{name}/metadata/{metaName}` | 查询资源的元数据 | +| POST | `/apis/{group}/{version}/{plural}` | 创建资源 | +| PUT | `/apis/{group}/{version}/{singular}/{name}` | 更新资源 | +| PUT | `/apis/{group}/{version}/{singular}/{name}/metadata/{metaName}` | 更新资源的元数据 | +| DELETE | `/apis/{group}/{version}/{singular}/{name}` | 删除资源 | + +## 自定义资源示例 + +下面是一个自定义资源的 Java 示例: + +```java +package run.ikaros.api.core.bookmark; + +import run.ikaros.api.custom.Custom; +import run.ikaros.api.custom.ReactiveCustomClient; +import run.ikaros.api.store.enums.BookmarkType; +import java.net.URL; + +@Custom(group = "myapp.ikaros.run", version = "v1", + kind = "Bookmark", singular = "bookmark", plural = "bookmarks") +public class Bookmark { + private String name; + private URL url; + private BookmarkType type; + private String description; +} +``` + +注册后自动生成的 API: + +```bash +# 创建书签 +POST /apis/myapp.ikaros.run/v1/bookmarks + +# 获取所有书签 +GET /apis/myapp.ikaros.run/v1/bookmarks + +# 按名称获取书签 +GET /apis/myapp.ikaros.run/v1/bookmark/my-favorite-site + +# 更新书签 +PUT /apis/myapp.ikaros.run/v1/bookmark/my-favorite-site + +# 删除书签 +DELETE /apis/myapp.ikaros.run/v1/bookmark/my-favorite-site +``` + +## 元数据系统 + +每个自定义资源都支持附加元数据(Key-Value 类型),可以用于扩展资源属性: + +```bash +# 获取元数据 +GET /apis/myapp.ikaros.run/v1/bookmark/my-favorite-site/metadata/tags + +# 更新元数据 +PUT /apis/myapp.ikaros.run/v1/bookmark/my-favorite-site/metadata/tags +请求体为: "[\"动画\",\"漫画\"]" +``` + +> ⚠️ 注意:PUT 更新元数据时,如果数据类型是字符串,必须加上英文双引号。正确:`"new value"`,错误:`new value` + +## 开发自定义资源 + +1. 定义一个 POJO 类,标注 `@Custom` 注解 +2. 实现响应的属性字段 +3. 系统启动时会自动扫描并注册该资源 +4. API 路由和 OpenAPI Schema 会自动生成 + +自定义资源系统通过 `CustomSchemeManager` 管理注册的 Scheme,通过 `ReactiveCustomClient` 进行数据的 CRUD 操作。 + +## 适用场景 + +- 扩展现有条目的自定义属性(如评分、标签组等) +- 添加插件专属的数据模型 +- 存储用户自定义的配置和偏好 +- 构建独立于核心功能的数据集合 diff --git a/sidebars.js b/sidebars.js index 020551a..14484f0 100644 --- a/sidebars.js +++ b/sidebars.js @@ -63,6 +63,7 @@ const sidebars = { "user-guide/tags", "user-guide/totp", "user-guide/roles", + "user-guide/custom-routes", "user-guide/faq" ] }, From 2ed8141ac1bbe5da096c5c2bae010d617dc4587a Mon Sep 17 00:00:00 2001 From: Nekoli Date: Fri, 24 Jul 2026 01:50:15 +0800 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E8=A1=A5=E5=85=85=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E6=8C=87=E5=8D=97=E3=80=81=E6=B5=8B=E8=AF=95=E6=8C=87?= =?UTF-8?q?=E5=8D=97=E3=80=81=E6=8F=92=E4=BB=B6=E5=BC=80=E5=8F=91=E6=8C=87?= =?UTF-8?q?=E5=8D=97=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development-guide.md | 189 ++++++++++++++++++++++++ docs/plugin-development-guide.md | 242 +++++++++++++++++++++++++++++++ docs/testing-guide.md | 197 +++++++++++++++++++++++++ sidebars.js | 3 + 4 files changed, 631 insertions(+) create mode 100644 docs/development-guide.md create mode 100644 docs/plugin-development-guide.md create mode 100644 docs/testing-guide.md diff --git a/docs/development-guide.md b/docs/development-guide.md new file mode 100644 index 0000000..4bcfad9 --- /dev/null +++ b/docs/development-guide.md @@ -0,0 +1,189 @@ +--- +title: 开发指南 +description: Ikaros 本地开发环境搭建、编译打包和代码规范。 +--- + +## 环境要求 + +| 工具 | 版本 | +|------|------| +| JDK | 21 | +| Gradle | 8.14.5(使用项目自带 wrapper) | +| SpringBoot | 4.0.1 | +| CheckStyle | 9.3 | +| Node.js | 18+(编译前端需要) | +| Vue | 3 | +| Database | PostgreSQL 18+ | +| IDE | IntelliJ IDEA(推荐) | +| Docker | 本地开发建议安装(用于测试容器) | + +## 获取代码 + +```shell +git clone https://github.com/ikaros-dev/ikaros.git --recursive +cd ikaros +``` + +> ⚠️ `--recursive` 是必须的,主题使用 git submodule 管理。如果已经 clone 但没加该参数,执行以下命令初始化: +> +> ```shell +> git submodule init +> git submodule update +> ``` + +`--recursive` 是必须的,主题使用 git submodule 管理。如果已经 clone 但没加该参数,执行以下命令: + +```shell +git submodule init +git submodule update +``` + +## 本地开发数据库 + +推荐使用 Docker 启动 PostgreSQL 18: + +```shell +docker run -d \ + --name ikarosdb_dev \ + -p 5432:5432 \ + -e POSTGRES_DB=ikaros \ + -e POSTGRES_USER=ikaros \ + -e POSTGRES_PASSWORD=openpostgresql \ + postgres:18.3-alpine +``` + +也可以直接在本地安装 PostgreSQL 18,下载地址:[EnterpriseDB PostgreSQL Downloads](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads) + +安装后通过 psql 创建用户和数据库: + +```shell +psql -U postgres -c "CREATE USER ikaros WITH PASSWORD 'openpostgresql';" +psql -U postgres -c "CREATE DATABASE ikaros OWNER ikaros;" +psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE ikaros TO ikaros;" +``` + +## 配置本地配置文件 + +复制配置文件模板: + +```shell +cp config/server/resource/application-local.yaml.example \ + src/main/resources/application-local.yaml +``` + +根据本地环境修改 `application-local.yaml` 中的数据库连接等配置。 + +## 编译 + +> 执行 gradle 任务前,默认会走一遍单元测试,如需跳过,加上 `-x test` 即可。 + +### 全量编译(含测试) + +```shell +# Linux / Mac +./gradlew clean build + +# Windows +./gradlew.bat clean build +``` + +### 跳过测试编译 + +```shell +# Linux / Mac +./gradlew clean build -x test + +# Windows +./gradlew.bat clean build -x test +``` + +### 编译打包(生产用 Fast Jar) + +```shell +# Linux / Mac +./gradlew clean bootJar -x test + +# Windows +./gradlew.bat clean bootJar -x test +``` + +打包后的文件在 `server/build/libs/ikaros-server.jar`。 + +### 编译 Console 前端 + +```shell +./gradlew buildFrontend -x test +``` + +## 本地运行 + +### IDEA 运行配置 + +1. 打开 `run.ikaros.server.IkarosApplication` 运行配置 +2. **Active profiles** 设置为:`dev,local` +3. 社区版 IDEA 在 VM options 中添加:`-Dspring.profiles.active=dev,local` + +运行后访问: +- 控制台: `http://localhost:9999/console` +- 默认账号: `tomoki` / 密码: `tomoki` + +### 命令行运行 + +```shell +java -jar server/build/libs/ikaros-server.jar \ + --spring.profiles.active=dev,local +``` + +## 代码规范 + +Ikaros 使用 Google Java Style(CheckStyle 9.3)作为代码规范标准。 + +### IDEA 配置 CheckStyle + +1. 安装插件:`CheckStyle-IDEA` +2. 打开 `Setting` → `Tools` → `Checkstyle` +3. **Checkstyle version** 选择 `9.3` +4. **扫描范围** 勾选包括测试代码 +5. **Configuration file** 选择项目下的 `config/checkstyle/checkstyle.xml` +6. 添加变量值: + + ```text + checkstyle-suppressions.xml + checkstyle-xpath-suppressions.xml + ``` + +### IDEA 代码格式化 + +1. 打开 `Setting` → `Editor` → `Code Style` → `Java` +2. `Scheme` 选择 `Project` +3. 点击右边齿轮 → `Import Scheme` → `Checkstyle configuration` +4. 选择 `config/checkstyle/checkstyle.xml` +5. 保存 + +> 提交代码前务必用 CheckStyle 检查,**CI 会拦截未通过 CheckStyle 检查的 PR**。 + +## 提交代码 + +```shell +git add . +git commit -s -m "feat: 我的修改内容" +``` + +> Commit 需加 `-s`(Signed-off-by),参考 [DCO](https://developercertificate.org/)。 + +## 项目模块结构 + +| 模块 | 说明 | +|------|------| +| `api/` | 公共 API 和模型定义 | +| `server/` | 服务端核心代码 | +| `console/` | 前端控制台(Vue 3) | +| `config/` | CheckStyle 配置等 | + +## 生成 API 文档 + +项目启动后可通过 Swagger UI 查看 API 文档: + +``` +http://localhost:9999/swagger-ui.html +``` diff --git a/docs/plugin-development-guide.md b/docs/plugin-development-guide.md new file mode 100644 index 0000000..bf7d6b9 --- /dev/null +++ b/docs/plugin-development-guide.md @@ -0,0 +1,242 @@ +--- +title: 插件开发指南 +description: Ikaros 插件开发教程,包含项目结构、配置文件和 API 扩展点。 +--- + +Ikaros 基于 PF4J 插件框架构建,支持通过插件扩展功能。插件以 jar 包形式分发,可以在控制台中安装、启动、停止和卸载。 + +## 快速开始 + +### 项目结构 + +一个标准的 Ikaros 插件项目结构如下: + +``` +plugin-starter/ +├── src/ +│ └── main/ +│ ├── java/ +│ │ └── run/ikaros/plugin/starter/ +│ │ ├── StarterPlugin.java # 插件入口类 +│ │ └── ... +│ └── resources/ +│ └── plugin.yml # 插件配置文件 +├── build.gradle 或 pom.xml +└── README.md +``` + +### plugin.yml 配置 + +插件根目录下需要 `plugin.yml` 文件来描述插件信息: + +```yaml +# 插件唯一名称 +name: plugin-starter +# 插件入口类(需继承 BasePlugin) +clazz: run.ikaros.plugin.starter.StarterPlugin +# 插件版本(遵循 semver 规范,如 1.0.0) +version: 1.0.0 +# 兼容的核心版本(* 表示全部兼容) +requires: "*" +# 作者信息 +author: + name: Ikaros OSS Team + website: https://github.com/ikaros-dev +# 插件 Logo URL +logo: https://github.com/ikaros-dev/ikaros/blob/master/assets/logo.png +# 项目主页 +homepage: https://github.com/ikaros-dev/plugin-starter +# 显示名称 +displayName: "PluginStarterDemo" +# 描述信息 +description: "PluginStarterDemo" +# 开源协议 +license: "AGPL-2.0" +``` + +### 插件入口类 + +插件入口类需继承 `BasePlugin`: + +```java +package run.ikaros.plugin.starter; + +import run.ikaros.api.plugin.BasePlugin; +import org.pf4j.PluginWrapper; + +public class StarterPlugin extends BasePlugin { + + public StarterPlugin(PluginWrapper wrapper) { + super(wrapper); + } + + @Override + public void start() { + log.info("插件启动"); + } + + @Override + public void stop() { + log.info("插件停止"); + } +} +``` + +### 构建配置 + +使用 Gradle 构建插件: + +```gradle +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + // Ikaros API,provided 级别 + compileOnly 'run.ikaros:api:1.0.0' +} + +jar { + manifest { + attributes( + // 插件类路径前缀 + "Plugin-Class": "run.ikaros.plugin.starter.StarterPlugin", + "Plugin-Id": "plugin-starter", + "Plugin-Version": "1.0.0" + ) + } +} +``` + +## 核心 API + +### 扩展点(Extension Point) + +Ikaros 提供了 `IkarosExtensionPoint` 接口,插件可以注册自定义扩展: + +```java +import run.ikaros.api.plugin.IkarosExtensionPoint; + +public class MyExtension implements IkarosExtensionPoint { + // 实现扩展逻辑 +} +``` + +### 插件事件 + +插件可以监听和发布事件: + +```java +// 监听插件配置变更 +@EventListener +public void onConfigChange(PluginConfigMapChangeEvent event) { + // 处理配置变更 +} + +// 监听配置创建 +@EventListener +public void onConfigCreate(PluginConfigMapCreateEvent event) { + // 处理配置创建 +} +``` + +相关事件: + +| 事件类 | 说明 | +|--------|------| +| `PluginConfigMapChangeEvent` | 插件配置变更 | +| `PluginConfigMapCreateEvent` | 插件配置创建 | +| `PluginConfigMapUpdateEvent` | 插件配置更新 | +| `PluginAwareEvent` | 插件生命周期事件 | + +### 自定义资源 + +通过 `@Custom` 注解可以在插件中定义自定义资源,系统会自动生成 RESTful API: + +```java +import run.ikaros.api.custom.Custom; + +@Custom(group = "myplugin.ikaros.run", version = "v1", + kind = "Bookmark", singular = "bookmark", plural = "bookmarks") +public class Bookmark { + @Name + private String name; + private String url; + private String type; +} +``` + +具体参考:[自定义路由](./custom-routes)。 + +### 配置表单 + +插件可以定义配置表单,使用 [FormKit Schema](https://formkit.com/essentials/schema) 格式: + +```java +// 在 Plugin 模型中设置 configMapSchemas 字段 +// 核心会读取插件目录下的 configMapSchemas 文本文件 +``` + +配置文件内容示例(JSON Schema for FormKit): + +```json +[ + { + "$formkit": "text", + "name": "apiKey", + "label": "API Key", + "placeholder": "输入 API Key" + }, + { + "$formkit": "select", + "name": "theme", + "label": "主题", + "options": [ + { "value": "light", "label": "浅色" }, + { "value": "dark", "label": "深色" } + ] + } +] +``` + +### 通知服务 + +插件可以使用通知服务发送邮件等通知: + +```java +// 测试邮件发送 +POST /api/v1/notify/mail/test +``` + +## 插件版本适配 + +插件版本需与 Ikaros 核心版本匹配: + +- **插件 1.x.x** → 需要核心版本 >= 1.0.7 +- **插件 0.3.x** → 只能在核心 0.3.x 上运行 +- 插件的大版本和小版本需与核心保持一致 + +## 插件加载机制 + +Ikaros 使用 PF4J 作为插件框架,加载流程如下: + +1. **扫描**:启动时扫描 `~/.ikaros/plugins/` 目录下的 jar 包 +2. **发现**:读取 `plugin.yml` 解析插件描述 +3. **加载**:使用 `IkarosJarPluginLoader` 加载插件类 +4. **初始化**:创建插件专属的 Spring ApplicationContext +5. **启动**:调用插件的 `start()` 方法 + +开发模式下,可以通过配置指定开发插件目录,无需每次打包。 + +## 最佳实践 + +1. **最小依赖**:插件只依赖 `api` 模块,不依赖 `server` 内部实现 +2. **独立配置**:使用 ConfigMap 存储插件配置,不要直接操作数据库 +3. **事件驱动**:优先使用事件机制与核心通信,减少直接耦合 +4. **错误处理**:插件启动失败不应影响核心运行 +5. **资源清理**:在 `stop()` 方法中释放插件占用的资源 +6. **版本兼容**:标明插件需要的核心版本范围,避免不兼容升级 diff --git a/docs/testing-guide.md b/docs/testing-guide.md new file mode 100644 index 0000000..1f9c4b6 --- /dev/null +++ b/docs/testing-guide.md @@ -0,0 +1,197 @@ +--- +title: 测试指南 +description: Ikaros 测试规范、测试类型和编写测试的最佳实践。 +--- + +Ikaros 项目要求所有贡献者编写充分的测试。测试分为单元测试、集成测试和 E2E 测试。GitHub CI 会自动运行测试,未通过的 PR 不会被合并。 + +## 测试环境准备 + +### Docker 环境 + +部分集成测试使用 **Testcontainers**,需要本地有 Docker 运行环境: + +- **Windows**: 安装 [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/) +- **Mac**: 安装 [Docker Desktop for Mac](https://docs.docker.com/desktop/install/mac-install/) +- **Linux**: 安装 Docker Engine + +### 数据库 + +测试用数据库也推荐通过 Docker 启动: + +```shell +docker run -d \ + --name ikaros_test_db \ + -p 5432:5432 \ + -e POSTGRES_DB=ikaros_test \ + -e POSTGRES_USER=ikaros \ + -e POSTGRES_PASSWORD=openpostgresql \ + postgres:18.3-alpine +``` + +### 运行全部测试 + +```shell +# 运行全部测试(含集成测试) +./gradlew test + +# 跳过测试(仅编译) +./gradlew build -x test +``` + +### 运行指定测试 + +```shell +# 运行指定类 +./gradlew test --tests "run.ikaros.server.plugin.PluginPropertiesTest" + +# 运行指定包下所有测试 +./gradlew test --tests "run.ikaros.server.plugin.*" + +# 运行匹配模式的测试 +./gradlew test --tests "*Plugin*" +``` + +## 测试类型 + +### 单元测试 + +单元测试使用 **JUnit 5** + **Mockito**,配合 Reactor 的 `StepVerifier` 测试响应式代码。 + +**示例:Mockito 模拟依赖** + +```java +package run.ikaros.server.theme; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; +import run.ikaros.api.core.setting.ConfigMap; +import run.ikaros.api.custom.ReactiveCustomClient; + +class DefaultThemeServiceTest { + + private ReactiveCustomClient reactiveCustomClient; + private DefaultThemeService defaultThemeService; + + @BeforeEach + void setUp() { + reactiveCustomClient = Mockito.mock(ReactiveCustomClient.class); + defaultThemeService = new DefaultThemeService(reactiveCustomClient); + } + + @Test + void getCurrentTheme_whenThemeSet() { + ConfigMap configMap = new ConfigMap(); + configMap.putDataItem("THEME_SELECT", "dark"); + when(reactiveCustomClient.findOne(any(), any())) + .thenReturn(Mono.just(configMap)); + + StepVerifier.create(defaultThemeService.getCurrentTheme()) + .expectNext("dark") + .verifyComplete(); + } +} +``` + +**测试要点:** + +- 使用 `@Test` 注解(JUnit 5) +- 使用 `Mockito.mock()` 模拟外部依赖 +- 响应式方法使用 `StepVerifier` 验证 +- 断言使用 AssertJ(`assertThat`) + +### 集成测试 + +集成测试会启动 Spring Boot 应用上下文,验证组件之间的协作。 + +**示例:** + +```java +package run.ikaros.server.plugin; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import reactor.test.StepVerifier; + +@SpringBootTest +class PluginPropertiesTest { + + @Autowired + private PluginProperties pluginProperties; + + @Test + void testPluginPropertiesLoaded() { + assertThat(pluginProperties).isNotNull(); + } +} +``` + +部分集成测试使用了 **Testcontainers**,需要 Docker 环境: +- `@Testcontainers` 注解声明 +- `@Container` 定义容器实例 +- 测试运行时会自动启动和销毁容器 + +### 工厂测试 + +对于 Plugin 相关的工厂类,有专门的基础测试: + +```java +package run.ikaros.server.plugin; + +class IkarosExtensionFactoryTest { + // 验证扩展工厂能正确创建插件扩展实例 +} +``` + +## 测试覆盖率 + +项目使用 **JaCoCo** 生成测试覆盖率报告: + +```shell +# 生成覆盖率报告 +./gradlew test jacocoTestReport +``` + +报告位置:`server/build/reports/jacoco/test/html/index.html` + +## 测试规范 + +1. **测试类命名**:`{被测类名}Test`,放在与被测类相同的包路径下 +2. **测试方法命名**:`{方法名}_{场景}`,如 `getCurrentTheme_whenThemeSet` +3. **AAA 模式**:Arrange(准备)→ Act(执行)→ Assert(断言) +4. **每个测试只测一个行为**,一个方法中只验证一个逻辑 +5. **不依赖测试顺序**,每个测试应独立运行 +6. **不依赖外部服务**(除非是集成测试),使用 Mock 隔离依赖 +7. **时间相关测试**:避免依赖系统时间,使用固定时间或 Mock + +## 工具类测试 + +纯工具类(如 `DateUtil`、`TimeTest`)测试示例: + +```java +package run.ikaros.server.test; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +class TimeTest { + + @Test + void testTimeConversion() { + // Arrange + long input = 3600L; + // Act + String result = TimeUtil.format(input); + // Assert + assertThat(result).isEqualTo("1h 0m 0s"); + } +} +``` diff --git a/sidebars.js b/sidebars.js index 14484f0..a7cec44 100644 --- a/sidebars.js +++ b/sidebars.js @@ -82,6 +82,9 @@ const sidebars = { "plugins/plugin-openlist" ] }, + "development-guide", + "testing-guide", + "plugin-development-guide", "about" ],