Skip to content

Commit cff7c7e

Browse files
authored
feat: restructure RSS generation to support extension by other plugins (#39)
### What this PR does? 重写 RSS 生成并支持被其他插件扩展 ```release-note 重写 RSS 生成并支持被其他插件扩展 ```
1 parent 857696c commit cff7c7e

40 files changed

+1621
-834
lines changed

.github/workflows/cd.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ jobs:
1616
with:
1717
app-id: app-KhIVw
1818
skip-node-setup: true
19+
artifacts-path: 'app/build/libs'

.github/workflows/ci.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ jobs:
1313
uses: halo-sigs/reusable-workflows/.github/workflows/plugin-ci.yaml@v1
1414
with:
1515
skip-node-setup: true
16+
artifacts-path: 'app/build/libs'

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,4 @@ application-local.properties
7272

7373
/admin-frontend/node_modules/
7474
/workplace/
75+
*/workplace/

README.md

+95-9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,92 @@
22

33
Halo 2.0 的 RSS 订阅链接生成插件
44

5+
## 如何扩展 RSS 源
6+
7+
> 从 feed 插件 v1.4.0 版本开始,支持扩展 RSS 功能。
8+
9+
`feed` 插件提供了扩展点,允许其他插件扩展 RSS 源。
10+
11+
### 步骤 1:在插件中引入 feed 依赖
12+
13+
在你的插件项目中添加 `feed` 插件的依赖:
14+
15+
```groovy
16+
dependencies {
17+
// ...
18+
compileOnly "run.halo.feed:api:{version}"
19+
}
20+
```
21+
22+
`{version}` 替换为实际的 `feed` 插件版本号。
23+
24+
### 步骤 2:实现 `RssRouteItem` 扩展点接口
25+
26+
创建一个类实现 `run.halo.feed.RssRouteItem` 接口,提供自定义的 RSS 数据源。例如:
27+
28+
```java
29+
public class MomentRssProvider implements RssRouteItem {
30+
// 实现具体的 RSS 提供逻辑
31+
}
32+
```
33+
34+
你可以参考 [PostRssProvider](./app/src/main/java/run/halo/feed/provider/PostRssProvider.java) 示例。
35+
36+
### 步骤 3:声明扩展点
37+
38+
`src/main/resources/extensions`
39+
目录下,声明你的扩展。你可以参考 [ext-definition.yaml](app/src/main/resources/extensions/ext-definition.yaml) 文件来完成此步骤。
40+
41+
### 步骤 4:定义配置类并清理 RSS 缓存
42+
43+
在插件中定义一个配置类,使用 `@ConditionalOnClass` 注解确保只有在 `run.halo.feed.RssRouteItem` 类存在时才会创建对应的
44+
Bean。同时,定义事件监听器来清理缓存。
45+
46+
`@ConditionalOnClass` 注解只能使用 name 属性来指定类全限定名,不支持使用 value 属性。
47+
48+
示例代码:
49+
50+
```java
51+
52+
@Configuration
53+
@ConditionalOnClass(name = "run.halo.feed.RssRouteItem")
54+
@RequiredArgsConstructor
55+
public class RssAutoConfiguration {
56+
private final ApplicationEventPublisher eventPublisher;
57+
58+
@Bean
59+
public MomentRssProvider momentRssProvider() {
60+
return new MomentRssProvider();
61+
}
62+
63+
@Async
64+
@EventListener({MomentUpdatedEvent.class, MomentDeletedEvent.class, ContextClosedEvent.class})
65+
public void onMomentUpdatedOrDeleted() {
66+
var rule = CacheClearRule.forExact("/feed/moments/rss.xml");
67+
var event = RssCacheClearRequested.forRule(this, rule);
68+
eventPublisher.publishEvent(event);
69+
}
70+
}
71+
```
72+
73+
此配置确保了当 `RssRouteItem` 接口存在时,插件才会自动创建 `MomentRssProvider` 并监听相关事件来清理缓存。
74+
75+
### 步骤 5:声明插件依赖
76+
77+
`plugin.yaml` 文件中声明 `feed` 插件为可选依赖,确保当 `feed` 插件存在并启用时,插件能够自动注册 RSS 源。
78+
79+
```yaml
80+
apiVersion: plugin.halo.run/v1alpha1
81+
kind: Plugin
82+
metadata:
83+
name: moment
84+
spec:
85+
pluginDependencies:
86+
"PluginFeed?": ">=1.4.0"
87+
```
88+
89+
这样,当 `feed` 插件可用时,插件会自动注册自定义的 RSS 源。
90+
591
## 开发环境
692

793
```bash
@@ -28,15 +114,15 @@ cd path/to/plugin-feed
28114

29115
```yaml
30116
halo:
31-
plugin:
32-
runtime-mode: development
33-
classes-directories:
34-
- "build/classes"
35-
- "build/resources"
36-
lib-directories:
37-
- "libs"
38-
fixedPluginPath:
39-
- "/path/to/plugin-feed"
117+
plugin:
118+
runtime-mode: development
119+
classes-directories:
120+
- "build/classes"
121+
- "build/resources"
122+
lib-directories:
123+
- "libs"
124+
fixedPluginPath:
125+
- "/path/to/plugin-feed"
40126
```
41127

42128
## 使用方式

api/build.gradle

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
plugins {
2+
id 'java-library'
3+
id 'maven-publish'
4+
id "io.freefair.lombok" version "8.0.0-rc2"
5+
}
6+
7+
group = 'run.halo.feed'
8+
version = rootProject.version
9+
10+
java {
11+
sourceCompatibility = JavaVersion.VERSION_17
12+
targetCompatibility = JavaVersion.VERSION_17
13+
withSourcesJar()
14+
}
15+
16+
compileJava.options.encoding = "UTF-8"
17+
compileTestJava.options.encoding = "UTF-8"
18+
javadoc.options.encoding = "UTF-8"
19+
20+
dependencies {
21+
api platform('run.halo.tools.platform:plugin:2.20.11')
22+
compileOnly 'run.halo.app:api'
23+
}
24+
25+
test {
26+
useJUnitPlatform()
27+
}
28+
29+
publishing {
30+
publications {
31+
mavenJava(MavenPublication) {
32+
from components.java
33+
artifact tasks.sourcesJar
34+
35+
artifactId = 'api'
36+
version = project.hasProperty('version') ? project.property('version') : 'unspecified'
37+
38+
pom {
39+
name = 'RSS'
40+
description = '为站点生成 RSS 订阅链接'
41+
url = 'https://www.halo.run/store/apps/app-KhIVw'
42+
43+
licenses {
44+
license {
45+
name = 'GPL-3.0'
46+
url = 'https://github.com/halo-dev/plugin-feed/blob/main/LICENSE'
47+
}
48+
}
49+
50+
developers {
51+
developer {
52+
id = 'guqing'
53+
name = 'guqing'
54+
55+
}
56+
}
57+
58+
scm {
59+
connection = 'scm:git:[email protected]:halo-dev/plugin-feed.git'
60+
developerConnection = 'scm:git:[email protected]:halo-dev/plugin-feed.git'
61+
url = 'https://github.com/halo-dev/plugin-feed'
62+
}
63+
}
64+
}
65+
}
66+
repositories {
67+
maven {
68+
url = version.endsWith('-SNAPSHOT') ? 'https://s01.oss.sonatype.org/content/repositories/snapshots/' :
69+
'https://s01.oss.sonatype.org/content/repositories/releases/'
70+
credentials {
71+
username = project.findProperty("ossr.user") ?: System.getenv("OSSR_USERNAME")
72+
password = project.findProperty("ossr.password") ?: System.getenv("OSSR_PASSWORD")
73+
}
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package run.halo.feed;
2+
3+
import org.springframework.util.Assert;
4+
5+
public record CacheClearRule(Type type, String value) {
6+
public CacheClearRule {
7+
Assert.notNull(type, "Type cannot be null");
8+
Assert.notNull(value, "Value cannot be null");
9+
if (type == Type.EXACT && !value.startsWith("/")) {
10+
throw new IllegalArgumentException("Exact value must start with /");
11+
}
12+
}
13+
14+
public static CacheClearRule forPrefix(String prefix) {
15+
return new CacheClearRule(Type.PREFIX, prefix);
16+
}
17+
18+
public static CacheClearRule forExact(String exact) {
19+
return new CacheClearRule(Type.EXACT, exact);
20+
}
21+
22+
public static CacheClearRule forContains(String contains) {
23+
return new CacheClearRule(Type.CONTAINS, contains);
24+
}
25+
26+
@Override
27+
public String toString() {
28+
return "CacheClearRule{" +
29+
"type=" + type +
30+
", value='" + value + '\'' +
31+
'}';
32+
}
33+
34+
public enum Type {
35+
PREFIX,
36+
EXACT,
37+
CONTAINS
38+
}
39+
}
40+

0 commit comments

Comments
 (0)