generated from halo-dev/plugin-starter
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: restructure RSS generation to support extension by other plugins
- Loading branch information
Showing
37 changed files
with
1,127 additions
and
792 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,3 +16,4 @@ jobs: | |
with: | ||
app-id: app-KhIVw | ||
skip-node-setup: true | ||
artifacts-path: 'app/build/libs' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -72,3 +72,4 @@ application-local.properties | |
|
||
/admin-frontend/node_modules/ | ||
/workplace/ | ||
*/workplace/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
plugins { | ||
id 'java-library' | ||
id 'maven-publish' | ||
id "io.freefair.lombok" version "8.0.0-rc2" | ||
} | ||
|
||
group = 'run.halo.feed' | ||
version = rootProject.version | ||
|
||
java { | ||
sourceCompatibility = JavaVersion.VERSION_17 | ||
targetCompatibility = JavaVersion.VERSION_17 | ||
} | ||
|
||
compileJava.options.encoding = "UTF-8" | ||
compileTestJava.options.encoding = "UTF-8" | ||
javadoc.options.encoding = "UTF-8" | ||
|
||
dependencies { | ||
api platform('run.halo.tools.platform:plugin:2.20.0-SNAPSHOT') | ||
compileOnly 'run.halo.app:api' | ||
} | ||
|
||
test { | ||
useJUnitPlatform() | ||
} | ||
|
||
publishing { | ||
publications { | ||
mavenJava(MavenPublication) { | ||
from components.java | ||
artifactId = 'api' | ||
version = project.hasProperty('version') ? project.property('version') : 'unspecified' | ||
|
||
pom { | ||
name = 'RSS' | ||
description = '为站点生成 RSS 订阅链接' | ||
url = 'https://www.halo.run/store/apps/app-KhIVw' | ||
|
||
licenses { | ||
license { | ||
name = 'GPL-3.0' | ||
url = 'https://github.com/halo-dev/plugin-feed/blob/main/LICENSE' | ||
} | ||
} | ||
|
||
developers { | ||
developer { | ||
id = 'guqing' | ||
name = 'guqing' | ||
email = '[email protected]' | ||
} | ||
} | ||
|
||
scm { | ||
connection = 'scm:git:[email protected]:halo-dev/plugin-feed.git' | ||
developerConnection = 'scm:git:[email protected]:halo-dev/plugin-feed.git' | ||
url = 'https://github.com/halo-dev/plugin-feed' | ||
} | ||
} | ||
} | ||
} | ||
repositories { | ||
maven { | ||
url = version.endsWith('-SNAPSHOT') ? 'https://s01.oss.sonatype.org/content/repositories/snapshots/' : | ||
'https://s01.oss.sonatype.org/content/repositories/releases/' | ||
credentials { | ||
username = project.findProperty("ossr.user") ?: System.getenv("OSSR_USERNAME") | ||
password = project.findProperty("ossr.password") ?: System.getenv("OSSR_PASSWORD") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package run.halo.feed; | ||
|
||
import jakarta.validation.constraints.NotBlank; | ||
import java.time.Instant; | ||
import java.util.List; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
|
||
@Data | ||
@Builder | ||
public class RSS2 { | ||
/** | ||
* (Recommended) The name of the feed, which should be plain text only | ||
*/ | ||
@NotBlank | ||
private String title; | ||
|
||
/** | ||
* (Recommended) The URL of the website associated with the feed, which should link to a | ||
* human-readable website | ||
*/ | ||
@NotBlank | ||
private String link; | ||
|
||
/** | ||
* (Optional) The summary of the feed, which should be plain text only | ||
*/ | ||
private String description; | ||
|
||
/** | ||
* The primary language of the feed, which should be a value from | ||
* <a href="https://www.rssboard.org/rss-language-codes">RSS Language Codes</a> or ISO | ||
* 639 language codes | ||
*/ | ||
private String language; | ||
|
||
/** | ||
* (Recommended) The URL of the image that represents the channel, which should be relatively | ||
* large and square | ||
*/ | ||
private String image; | ||
|
||
private List<Item> items; | ||
|
||
@Data | ||
@Builder | ||
public static class Item { | ||
/** | ||
* (Required) The title of the item, which should be plain text only | ||
*/ | ||
@NotBlank | ||
private String title; | ||
|
||
/** | ||
* (Recommended) The URL of the item, which should link to a human-readable website | ||
*/ | ||
@NotBlank | ||
private String link; | ||
|
||
/** | ||
* <p>(Recommended) The content of the item. For an Atom feed, it's the atom:content | ||
* element.</p> | ||
* <p>For a JSON feed, it's the content_html field.</p> | ||
*/ | ||
@NotBlank | ||
private String description; | ||
|
||
/** | ||
* (Optional) The author of the item | ||
*/ | ||
private String author; | ||
|
||
/** | ||
* (Optional) The category of the item. You can use a plain string or an array of strings | ||
*/ | ||
private List<String> categories; | ||
|
||
/** | ||
* (Recommended) The publication | ||
* <a href="https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date">date </a> of the item, which should be a Date object | ||
* following <a href="https://docs.rsshub.app/joinus/advanced/pub-date">the standard</a> | ||
*/ | ||
private Instant pubDate; | ||
|
||
/** | ||
* (Optional) The unique identifier of the item | ||
*/ | ||
private String guid; | ||
|
||
/** | ||
* (Optional) The URL of an enclosure associated with the item | ||
*/ | ||
private String enclosureUrl; | ||
|
||
/** | ||
* (Optional) The size of the enclosure file in byte, which should be a number | ||
*/ | ||
private String enclosureLength; | ||
|
||
/** | ||
* (Optional) The MIME type of the enclosure file, which should be a string | ||
*/ | ||
private String enclosureType; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package run.halo.feed; | ||
|
||
import org.pf4j.ExtensionPoint; | ||
import org.springframework.lang.NonNull; | ||
import org.springframework.web.reactive.function.server.ServerRequest; | ||
import reactor.core.publisher.Mono; | ||
|
||
public interface RssRouteItem extends ExtensionPoint { | ||
|
||
@NonNull | ||
String pathPattern(); | ||
|
||
@NonNull | ||
String displayName(); | ||
|
||
default String description() { | ||
return ""; | ||
} | ||
|
||
/** | ||
* An example URI for this route. | ||
*/ | ||
default String example() { | ||
return ""; | ||
} | ||
|
||
/** | ||
* The namespace of this route to avoid conflicts. | ||
*/ | ||
default String namespace() { | ||
return ""; | ||
} | ||
|
||
@NonNull | ||
Mono<RSS2> handler(ServerRequest request); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
plugins { | ||
id 'java' | ||
id "io.freefair.lombok" version "8.0.0-rc2" | ||
id "run.halo.plugin.devtools" version "0.4.1" | ||
} | ||
|
||
group = 'run.halo.feed' | ||
version = rootProject.version | ||
|
||
dependencies { | ||
implementation project(':api') | ||
compileOnly 'run.halo.app:api' | ||
implementation 'org.dom4j:dom4j:2.1.3' | ||
implementation('org.apache.commons:commons-text:1.10.0') { | ||
// Because the transitive dependency already exists in halo. | ||
exclude group: 'org.apache.commons', module: 'commons-lang3' | ||
} | ||
|
||
testImplementation 'run.halo.app:api' | ||
testImplementation 'org.springframework.boot:spring-boot-starter-test' | ||
} | ||
|
||
test { | ||
useJUnitPlatform() | ||
} | ||
|
||
halo { | ||
version = '2.20.10' | ||
debug = true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,6 @@ public class BasicSetting { | |
|
||
private Integer outputNum = 20; | ||
|
||
|
||
enum DescriptionType { | ||
/** | ||
* 全文 | ||
|
File renamed without changes.
108 changes: 108 additions & 0 deletions
108
app/src/main/java/run/halo/feed/FeedPluginEndpoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package run.halo.feed; | ||
|
||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept; | ||
import static org.springframework.web.reactive.function.server.RequestPredicates.path; | ||
|
||
import lombok.AllArgsConstructor; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.lang.NonNull; | ||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; | ||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.reactive.function.server.HandlerFunction; | ||
import org.springframework.web.reactive.function.server.RequestPredicate; | ||
import org.springframework.web.reactive.function.server.RouterFunction; | ||
import org.springframework.web.reactive.function.server.RouterFunctions; | ||
import org.springframework.web.reactive.function.server.ServerRequest; | ||
import org.springframework.web.reactive.function.server.ServerResponse; | ||
import reactor.core.publisher.Mono; | ||
import run.halo.app.plugin.extensionpoint.ExtensionGetter; | ||
import run.halo.feed.provider.PostRssProvider; | ||
|
||
@Component | ||
@AllArgsConstructor | ||
public class FeedPluginEndpoint { | ||
static final RequestPredicate requestPredicate = accept( | ||
MediaType.TEXT_XML, | ||
MediaType.APPLICATION_RSS_XML | ||
); | ||
|
||
private final PostRssProvider postRssProvider; | ||
private final ExtensionGetter extensionGetter; | ||
private final RssCacheManager rssCacheManager; | ||
|
||
@Bean | ||
RouterFunction<ServerResponse> rssRouterFunction() { | ||
return RouterFunctions.route() | ||
.GET(path("/feed.xml").or(path("/rss.xml")).and(requestPredicate), | ||
request -> rssCacheManager.get("rss.xml", postRssProvider.handler(request)) | ||
.flatMap(this::buildResponse) | ||
) | ||
.build(); | ||
} | ||
|
||
@Bean | ||
RouterFunction<ServerResponse> additionalRssRouter() { | ||
var pathMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/feed/**"); | ||
return new RouterFunction<>() { | ||
@Override | ||
@NonNull | ||
public Mono<HandlerFunction<ServerResponse>> route(@NonNull ServerRequest request) { | ||
return pathMatcher.matches(request.exchange()) | ||
.filter(ServerWebExchangeMatcher.MatchResult::isMatch) | ||
.flatMap(matched -> handleRequest(request)); | ||
} | ||
|
||
private Mono<HandlerFunction<ServerResponse>> handleRequest(ServerRequest request) { | ||
return extensionGetter.getEnabledExtensions(RssRouteItem.class) | ||
.filter(item -> StringUtils.isNotBlank(item.pathPattern())) | ||
.map(routeItem -> new RouteItem(buildRequestPredicate(routeItem), | ||
buildHandleFunction(routeItem)) | ||
) | ||
.filter(route -> route.requestPredicate.test(request)) | ||
.next() | ||
.map(RouteItem::handler); | ||
} | ||
|
||
record RouteItem(RequestPredicate requestPredicate, | ||
HandlerFunction<ServerResponse> handler) { | ||
} | ||
|
||
private HandlerFunction<ServerResponse> buildHandleFunction(RssRouteItem routeItem) { | ||
return request -> rssCacheManager.get(request.path(), routeItem.handler(request)) | ||
.flatMap(item -> buildResponse(item)); | ||
} | ||
|
||
private RequestPredicate buildRequestPredicate(RssRouteItem routeItem) { | ||
return path(buildPathPattern(routeItem)).and(requestPredicate); | ||
} | ||
|
||
private String buildPathPattern(RssRouteItem routeItem) { | ||
var sb = new StringBuilder("/feed/"); | ||
|
||
if (StringUtils.isNotBlank(routeItem.namespace())) { | ||
sb.append(routeItem.namespace()); | ||
if (!routeItem.namespace().endsWith("/")) { | ||
sb.append("/"); | ||
} | ||
} | ||
|
||
String pathPattern = routeItem.pathPattern(); | ||
if (pathPattern.startsWith("/")) { | ||
pathPattern = pathPattern.substring(1); | ||
} | ||
sb.append(pathPattern); | ||
|
||
return sb.toString(); | ||
} | ||
}; | ||
} | ||
|
||
private Mono<ServerResponse> buildResponse(String xml) { | ||
return ServerResponse.ok().contentType(MediaType.TEXT_XML) | ||
.bodyValue(xml); | ||
} | ||
} |
Oops, something went wrong.