Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions docs/developer-guide/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
---
title: 系统架构
description: Ikaros 分层架构、模块划分与关键技术决策(ADR)摘要
---

# 系统架构

> 本文是对 Ikaros 主仓库《概要设计文档(HLD)》第 2 章"系统架构"的提炼总结,
> 完整内容见 [ikaros-dev/ikaros](https://github.com/ikaros-dev/ikaros) `docs/High-Level-Design.md`。

## 分层架构

```
┌─────────────────────────────────────────────────────────────────┐
│ 客户端层 (Clients) │
│ Web Console (Vue3 SPA) │ App (Flutter 六端) │ Subsonic 客户端 │
│ App:Windows/Android/iOS/macOS/Linux/Web,含视频/音乐/阅读 │
└───────────────────────────────┬─────────────────────────────────┘
│ HTTPS / HTTP
┌───────────────────────────────┴─────────────────────────────────┐
│ 接入层 (Security) │
│ SecurityWebFilterChain:Basic / Form / JWT / OAuth2 / TOTP │
│ 匿名用户 → RequestAuthorizationManager(RBAC) → 端点路由 │
├───────────────────────────────┬─────────────────────────────────┤
│ 路由层 (Router / Endpoint) │
│ CoreEndpointsBuilder (函数式路由 + SpringDoc) │
│ CustomEndpointsBuilder (自定义Scheme动态端点) │
│ PluginCompositeRouterFunction (插件端点) │
│ SubsonicRouter (/rest/** 协议分发) │
├───────────────────────────────┼─────────────────────────────────┤
│ 领域服务层 (Core Services) │
│ Subject/Episode/Collection/Attachment/Tag/Binding/Music/... │
│ ★ 服务接口(api模块) + 默认实现(server模块, XxxService/Default) │
├───────────────────────────────┼─────────────────────────────────┤
│ 横切能力层 (Infrastructure) │
│ 缓存(CacheAspect: 内存/Redis) │ 任务(Task) │ 事件(Event) │
│ Lucene搜索 │ 邮件通知 │ WebClient │ 全局异常处理 │
├───────────────────────────────┼─────────────────────────────────┤
│ 数据访问层 (Store) │
│ R2DBC Repository (Spring Data R2DBC + 响应式 R2dbcTemplate) │
├───────────────────────────────┼─────────────────────────────────┤
│ 数据存储层 │
│ PostgreSQL 18 │ Redis(可选) │ Lucene索引(工作目录/indices) │
│ 文件系统(附件) │ 工作目录(~/.ikaros: plugins/themes/statics) │
└─────────────────────────────────────────────────────────────────┘
```

## 模块架构(Gradle 多模块)

```
ikaros
├── api/ ★ API契约层(独立可被插件依赖)
│ ├── constant/ 常量(OpenApi/Security/String/File/App)
│ ├── core/ 领域模型与操作接口(subject/episode/attachment/collection/
│ │ tag/authority/role/user/meta/music/subsonic/binding/...)
│ ├── custom/ 自定义Scheme模型(Custom注解/客户端/异常)
│ ├── endpoint/ 端点接口(Endpoint/CustomEndpoint)
│ ├── infra/ 工具(utils)与异常体系(exception)
│ ├── plugin/ 插件API(BasePlugin/ExtensionPoint/事件/常量)
│ ├── search/ 搜索契约(SearchParam/Result/SubjectDoc/Hint/Service)
│ ├── store/ 枚举(enums)与仓库接口约定
│ └── wrap/ 统一包装(CommonResult/PagingWrap)
├── server/ ★ 服务端实现(Spring Boot 应用)
│ ├── core/ 领域服务实现(subject/attachment/binding/music/subsonic/...)
│ │ ├── <domain>/endpoint 函数式路由端点
│ │ ├── <domain>/service 服务接口+impl实现
│ │ ├── <domain>/listener 事件监听
│ │ └── <domain>/handler 责任链步骤/处理器
│ ├── security/ 认证授权(basicauth/formlogin/jwt/oauth2/totp/logout)
│ ├── plugin/ PF4J 插件管理(加载器/扩展点发现/上下文隔离)
│ ├── search/ Lucene 索引实现
│ ├── cache/ 缓存抽象(Aspect + 内存/Redis Manager)
│ ├── store/ 实体(entity)+ 仓库(repository)
│ ├── custom/ 自定义Scheme运行时
│ ├── console/ Console 静态资源托管
│ ├── theme/ 主题服务
│ ├── config/ R2DBC/任务/异步/调度/全局异常等配置
│ └── resources/db/migration/ 版本化 SQL 迁移
├── console/ ★ Web 控制台(Vue 3 SPA,构建产物嵌入 bootJar)
│ └── packages/ shared(共享类型/菜单)/ api-client(生成的API客户端)
├── platform/ 平台相关模块
└── config/ Checkstyle 等构建配置
```

## 关键技术决策(ADR 摘要)

| 决策 | 方案 | 理由 |
|------|------|------|
| ADR-01 响应式栈 | Spring WebFlux + R2DBC | IO 密集场景(流媒体/大目录扫描)下高并发、低资源占用 |
| ADR-02 函数式路由 | RouterFunction + SpringDoc 编程式文档 | 与 WebFlux 契合,端点定义集中、便于插件聚合 |
| ADR-03 插件框架 | PF4J(org.pf4j) | 成熟、类加载器隔离、状态机完整(RESOLVED→STARTED→STOPPED/DISABLED/FAILED) |
| ADR-04 数据库迁移 | 版本化 SQL 迁移(Flyway 风格) | 无缝升级、破坏性变更受控(全面 PostgreSQL) |
| ADR-05 搜索 | Lucene + IKAnalyzer(中文分词) | 毫秒级全文检索,索引落盘工作目录,支持重建 |
| ADR-06 缓存 | 注解切面(Mono/Flux Cacheable/Evict)+ 内存/Redis 可切换 | 对业务代码侵入小,可开关 |
| ADR-07 ID 生成 | UUID v7(时间有序)为主键默认值 | 分布式友好、索引性能优于 UUID v4 |
| ADR-08 附件驱动 | Fetcher 扩展点 + 挂载服务 | 本地/WebDAV/插件自定义统一抽象,安全校验集中 |
| ADR-09 授权模型 | 权限目标字符串(Authority Target)+ 角色绑定 | 细粒度到接口路径,MASTER 超管兜底 |

## 核心机制(要点)

- **端点聚合**:`CoreEndpoint` 统一收集;`CustomEndpoint` 支持自定义 Scheme 动态生成端点;插件端点经 `PluginCompositeRouterFunction` 聚合。
- **领域服务模式**:`XxxService`(接口,位于 api 模块)+ `DefaultXxxService`(实现,位于 server 模块),类/方法/属性要求中文 Javadoc。
- **附件驱动**:`AttachmentDriverFetcher` 扩展点是本地/WebDAV/插件(如 S3)驱动的统一接入点,流式接口支持 Range(206)与外部直链 307 重定向。
- **目录绑定工作流**:通过责任链步骤(`DirectoryBindingStep`)将本地目录扫描结果绑定为条目/剧集。

> 更多设计细节(核心设计模式与机制、部署架构、接口总览)请参考 HLD 原文。
148 changes: 148 additions & 0 deletions docs/developer-guide/contribution-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: 贡献指南
description: Ikaros 开源参与指南:协作流程规范、代码贡献步骤、提交与 PR 规范
---

# 贡献指南

> 本文整理自 Ikaros 主仓库根目录 `CONTRIBUTING.md`,
> 原文见 https://github.com/ikaros-dev/ikaros/blob/main/CONTRIBUTING.md
>
> 仓库开发环境的搭建(JDK 21 + Gradle + PostgreSQL + Vue 3)见
> [快速开始(安装)](/docs/getting-started/prepare) 与项目 README。

## 协作流程规范

新需求、新交互(特别是偏向破坏性的更新):**确定的先提 issue,不确定的提 discussion**,
确定后再创建或转为 issue;issue 定了之后,再 fork 切新分支提 PR。

- 一个 discussion 对应多个 issue
- 一个 issue 对应一个问题,一个 PR 解决一个问题

```mermaid
flowchart TD
S([新需求 / 新交互<br/>特别是破坏性更新]) --> Q{是否确定?}
Q -->|确定| I1[创建 issue]
Q -->|不确定| D1[创建 discussion 讨论]
D1 --> Q2{讨论后确定?}
Q2 -->|是| I2[创建或转为 issue]
Q2 -->|否| D1
I2 --> I1
I1 --> F[fork 仓库切新分支<br/>进行开发]
F --> P[提交 PR]
P --> M[合并,issue 关闭]
D1 -.->|一个 discussion 可衍生多个 issue| I1
I1 -.->|一个 issue 对应一个问题| P
```

## 代码贡献步骤

### 1. Fork 仓库

点击 [Ikaros 仓库](https://github.com/ikaros-dev/ikaros) 主页右上角 `Fork` 按钮。

### 2. Clone 仓库到本地

```bash
git clone https://github.com/{YOUR_USERNAME}/ikaros --recursive
```

### 3. 添加主仓库(upstream)

方便未来同步主仓库最新 commits,并基于最新代码创建分支:

```bash
git remote add upstream https://github.com/ikaros-dev/ikaros.git
git fetch upstream
```

### 4. 初始化 git submodule

主题模板通过 git submodule 关联了另一个仓库:

```bash
git submodule init
git submodule update
```

### 5. 创建新的开发分支

从主仓库的主分支(main)创建:

```bash
git checkout upstream/main
git checkout -b {YOU_BRANCH_NAME}
```

### 6. 开发

在新分支上进行开发。

### 7. 提交代码

```bash
git add .
git commit -s -m "Fix a bug or issue"
```

### 8. 推送到你的 fork 库

提交 PR 前尽量与上游保持同步:

```bash
git fetch upstream main
git merge upstream/main # 有冲突需手动解决
git push origin {YOU_BRANCH_NAME}
```

### 9. 创建 Pull Request

完成代码编写、测试与自测后,在自己的 fork 页面选择 `New pull request`,
向主仓库的 `main` 分支提交 PR,等待 Code Review。

注意事项:

- 提交 PR 前请充分自测
- 每个 PR 尽量只解决一个 issue
- 应尽可能多地添加单元测试,集成测试和 E2E 测试视情况添加

### 10. Review 后更新 commits

直接在当前分支 commit 并 push 即可,无需关闭重开 PR:

```bash
git add .
git commit -s -m "Refactor some code according code review"
git push origin bug/xxx
```

> 进入 Code Review 阶段后**不要强制推送**(force push),否则 Reviewers 需要从头 Review。

### 11. PR 合并后的操作

```bash
# 删本地分支
git checkout main
git branch -D {YOU_BRANCH_NAME}
# 删远端 fork 分支
git push origin --delete {YOU_BRANCH_NAME}
# 清理远端分支引用
git remote prune origin
# 更新本地与 fork 主分支
git pull upstream main
git push origin main
```

## 提交 / PR 规范(补充约定)

Ikaros 主仓库在协作实践中还遵循以下约定(见项目内 `AGENTS.md` / 提交记录):

- Commit 消息格式:`type(scope): 中文描述`,如 `fix(attachment): 附件流接口重定向由 302 改为 307`
- type 取值:feat / fix / refactor / chore / docs / config / style / perf / test
- 服务实现类命名:`XxxService` + `DefaultXxxService`;所有类/方法/属性加中文 Javadoc
- 必须添加单元测试;必须更新 `CHANGELOG.md`
- 不使用 `Signed-off-by` / `Co-Authored-By` 尾部签名

## 参考

- 本指南参考 [开源项目 Halo 贡献指南](https://github.com/halo-dev/halo/blob/master/CONTRIBUTING.md)
120 changes: 120 additions & 0 deletions docs/developer-guide/data-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
---
title: 数据模型
description: Ikaros 内容数据模型:Subject/Episode/Attachment 统一模型、ER 图与音乐/漫画/小说映射
---

# 数据模型

> 本文整合自 Ikaros 主仓库《内容数据结构文档》文档族\n> (`docs/data-structure/README.md` 及 subject/music/comic/novel 分篇)。\n> 主仓库原文:https://github.com/ikaros-dev/ikaros/tree/main/docs/data-structure

## 核心设计思想

Ikaros 的内容数据采用 **"一种条目、二级剧集、通用附件"** 的统一模型,而不是为每种媒体类型分别建表:

```
Subject(条目,type 区分 ANIME/COMIC/GAME/MUSIC/NOVEL/REAL/OTHER)
└── Episode(剧集/歌曲/话/章节,group + sequence 组织)
└── Attachment(附件/文件,经 attachment_reference 绑定)
```

| 内容类型 | 条目 | 剧集 | 附件绑定 | 前端绑定方式 |
|----------|------|------|----------|--------------|
| 动画 ANIME | Subject | 集(MAIN 正片 / OP / ED / SP…) | 每集 1 个视频(可选字幕) | 单资源 |
| 音乐 MUSIC | Subject(专辑) | 歌曲(MAIN,sequence=曲序) | 每首歌 1 音频(可多资源:音频+歌词) | 多资源 |
| 漫画 COMIC | Subject | 话/卷(MAIN,sequence=话序) | 每话多张页面图片(按附件 ID 排序) | 多资源 |
| 小说 NOVEL | Subject | 章节(MAIN,sequence=章序) | 每章 1 个文本文件 | 单资源 |

> 单/多资源判定:前端 `SubjectDetails.vue#initEpisodeHasMultiResource` —— ANIME/GAME/NOVEL 单资源,其余多资源。

## 全局 ER 图

```mermaid
erDiagram
SUBJECT ||--o{ EPISODE : "包含"
SUBJECT ||--o{ SUBJECT_COLLECTION : "被收藏"
SUBJECT ||--o{ SUBJECT_RELATION : "关联(出)"
SUBJECT ||--o{ SUBJECT_SYNC : "同步"
SUBJECT ||--o{ SUBJECT_PERSON : "参与人员"
SUBJECT ||--o{ SUBJECT_CHARACTER : "登场角色"
SUBJECT ||--o{ ATTACHMENT_REFERENCE : "绑定附件"
EPISODE ||--o{ EPISODE_COLLECTION : "观看进度"
EPISODE ||--o{ ATTACHMENT_REFERENCE : "绑定附件"
ATTACHMENT ||--o{ ATTACHMENT_REFERENCE : "被引用"
ATTACHMENT ||--o{ ATTACHMENT_RELATION : "附件间关系"
ATTACHMENT }o--|| ATTACHMENT_DRIVER : "由驱动提供"
PERSON }o--o{ CHARACTER : "person_character"
PERSON }o--o{ SUBJECT : "subject_person"
CHARACTER }o--o{ SUBJECT : "subject_character"
IKUSER ||--o{ SUBJECT_COLLECTION : "收藏"
IKUSER ||--o{ EPISODE_COLLECTION : "进度"

SUBJECT {
uuid id PK
varchar type "SubjectType"
varchar name
varchar name_cn
varchar cover
varchar infobox "key:value 逐行"
varchar summary
boolean nsfw
timestamp air_time
double score
}
EPISODE {
uuid id PK
uuid subject_id FK
varchar name
varchar ep_group "EpisodeGroup"
real sequence
varchar description
timestamp air_time
}
ATTACHMENT {
uuid id PK
uuid parent_id "目录树父节点"
varchar type "File/Directory/Driver_*"
varchar url / path / fs_path
varchar name
bigint size
boolean deleted
uuid driver_id
varchar sha1
}
ATTACHMENT_REFERENCE {
uuid id PK
varchar type "SUBJECT/EPISODE/USER_AVATAR"
uuid attachment_id
uuid reference_id
}
```

## 核心表与约束

| 表 | 作用 | 关键约束 |
|----|------|----------|
| `subject` | 条目主表 | `type`/`name`/`nsfw` 必填 |
| `episode` | 剧集/歌曲/话/章节 | **唯一 `(subject_id, ep_group, sequence, name)`** |
| `attachment` | 附件(文件) | 唯一 `(type, parent_id, name)`;不含审计字段 |
| `attachment_driver` | 附件驱动(LOCAL/WEBDAV/CUSTOM) | — |
| `attachment_reference` | 附件↔条目/剧集绑定 | `type` ∈ SUBJECT/EPISODE/USER_AVATAR |
| `attachment_relation` | 附件间关系(视频↔字幕) | `type=VIDEO_SUBTITLE` |
| `subject_collection` | 用户收藏 | `type` ∈ WISH/DOING/DONE/SHELVE/DISCARD |
| `episode_collection` | 剧集进度 | finish/progress/duration |
| `subject_relation` | 条目关系(前传/续集/OST…) | `relation_type` + 目标条目 |
| `subject_sync` | 外部同步 | **唯一 `(platform, platform_id)`** |
| `tag` | 标签(SUBJECT/EPISODE/ATTACHMENT) | — |

> 通用约定:主键均为 uuidv7(时间有序);业务实体默认含审计字段\n> (create_time/create_uid/update_time/update_uid/ol_version/delete_status),`attachment` 除外。

## 聚合视图(API 层)

- `EpisodeRecord(episode, List<EpisodeResource>)`:剧集 + 附件资源(attachmentId/url/canRead/name/tags)
- `SubjectRecord(subject, List<EpisodeRecord>, List<Tag>, List<SubjectSync>, extra)`:条目完整聚合
- 资源按 **attachment_id(uuidv7 时间序)** 排序 —— 漫画页序、字幕顺序即绑定顺序

## 各内容类型详细文档

- 条目结构:见主仓库 `docs/data-structure/subject.md`(含 infobox 约定、收藏/同步/关系/人员/角色关联)
- 音乐:`docs/data-structure/music.md`(专辑=Subject(MUSIC)、歌曲=Episode 映射、播放数据流)
- 漫画:`docs/data-structure/comic.md`(卷/话/页三级结构、页序约定、阅读流程)
- 小说:`docs/data-structure/novel.md`(章节=Episode、每章单文本附件、阅读流程)
Loading
Loading