mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-03-26 22:27:32 +08:00
Compare commits
6 Commits
codex/add-
...
codex/fix-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3dcc98122 | ||
|
|
c648d4cf39 | ||
|
|
c6e0dc6a1d | ||
|
|
92e630df22 | ||
|
|
c6b0f32b09 | ||
|
|
5f5b6f84a8 |
@@ -1,7 +1,10 @@
|
||||
package com.openisle.controller;
|
||||
|
||||
import com.openisle.model.Post;
|
||||
import com.openisle.model.Comment;
|
||||
import com.openisle.model.CommentSort;
|
||||
import com.openisle.service.PostService;
|
||||
import com.openisle.service.CommentService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
@@ -31,6 +34,7 @@ import java.util.regex.Pattern;
|
||||
@RequiredArgsConstructor
|
||||
public class RssController {
|
||||
private final PostService postService;
|
||||
private final CommentService commentService;
|
||||
|
||||
@Value("${app.website-url:https://www.open-isle.com}")
|
||||
private String websiteUrl;
|
||||
@@ -103,6 +107,12 @@ public class RssController {
|
||||
enclosure = absolutifyUrl(enclosure, base);
|
||||
}
|
||||
|
||||
// 6) 构造优雅的附加区块(原文链接 + 精选评论),编入 <content:encoded>
|
||||
List<Comment> topComments = commentService
|
||||
.getCommentsForPost(p.getId(), CommentSort.MOST_INTERACTIONS);
|
||||
topComments = topComments.subList(0, Math.min(10, topComments.size()));
|
||||
String footerHtml = buildFooterHtml(base, link, topComments);
|
||||
|
||||
sb.append("<item>");
|
||||
elem(sb, "title", cdata(nullSafe(p.getTitle())));
|
||||
elem(sb, "link", link);
|
||||
@@ -110,8 +120,11 @@ public class RssController {
|
||||
elem(sb, "pubDate", toRfc1123Gmt(p.getCreatedAt().atZone(ZoneId.systemDefault())));
|
||||
// 摘要
|
||||
elem(sb, "description", cdata(plain));
|
||||
// 全文(HTML)
|
||||
sb.append("<content:encoded><![CDATA[").append(absHtml).append("]]></content:encoded>");
|
||||
// 全文(HTML):正文 + 优雅的 Markdown 区块(已转 HTML)
|
||||
sb.append("<content:encoded><![CDATA[")
|
||||
.append(absHtml)
|
||||
.append(footerHtml)
|
||||
.append("]]></content:encoded>");
|
||||
// 首图 enclosure(图片类型)
|
||||
if (enclosure != null) {
|
||||
sb.append("<enclosure url=\"").append(escapeXml(enclosure)).append("\" type=\"")
|
||||
@@ -136,8 +149,12 @@ public class RssController {
|
||||
private static String sanitizeHtml(String html) {
|
||||
if (html == null) return "";
|
||||
Safelist wl = Safelist.relaxed()
|
||||
.addTags("pre", "code", "figure", "figcaption", "picture", "source",
|
||||
"table","thead","tbody","tr","th","td","h1","h2","h3","h4","h5","h6")
|
||||
.addTags(
|
||||
"pre","code","figure","figcaption","picture","source",
|
||||
"table","thead","tbody","tr","th","td",
|
||||
"h1","h2","h3","h4","h5","h6",
|
||||
"hr","blockquote"
|
||||
)
|
||||
.addAttributes("a", "href", "title", "target", "rel")
|
||||
.addAttributes("img", "src", "alt", "title", "width", "height")
|
||||
.addAttributes("source", "srcset", "type", "media")
|
||||
@@ -246,6 +263,59 @@ public class RssController {
|
||||
return "image/jpeg";
|
||||
}
|
||||
|
||||
/* ===================== 附加区块(原文链接 + 精选评论) ===================== */
|
||||
|
||||
/**
|
||||
* 将“原文链接 + 精选评论(最多 10 条)”以优雅的 Markdown 形式渲染为 HTML,
|
||||
* 并做 sanitize + 绝对化,然后拼入 content:encoded 尾部。
|
||||
*/
|
||||
private static String buildFooterHtml(String baseUrl, String originalLink, List<Comment> topComments) {
|
||||
StringBuilder md = new StringBuilder(256);
|
||||
|
||||
// 分割线
|
||||
md.append("\n\n---\n\n");
|
||||
|
||||
// 原文链接(强调 + 可点击)
|
||||
md.append("**原文链接:** ")
|
||||
.append("[").append(originalLink).append("](").append(originalLink).append(")")
|
||||
.append("\n\n");
|
||||
|
||||
// 精选评论(仅当有评论时展示)
|
||||
if (topComments != null && !topComments.isEmpty()) {
|
||||
md.append("### 精选评论(Top ").append(Math.min(10, topComments.size())).append(")\n\n");
|
||||
for (Comment c : topComments) {
|
||||
String author = usernameOf(c);
|
||||
String content = nullSafe(c.getContent()).replace("\r", "");
|
||||
// 使用引用样式展示,提升可读性
|
||||
md.append("> @").append(author).append(": ").append(content).append("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染为 HTML,并保持和正文一致的处理流程
|
||||
String html = renderMarkdown(md.toString());
|
||||
String safe = sanitizeHtml(html);
|
||||
return absolutifyHtml(safe, baseUrl);
|
||||
}
|
||||
|
||||
private static String usernameOf(Comment c) {
|
||||
if (c == null) return "匿名";
|
||||
try {
|
||||
Object authorObj = c.getAuthor();
|
||||
if (authorObj == null) return "匿名";
|
||||
// 反射避免直接依赖实体字段名变化(也可直接强转到具体类型)
|
||||
String username;
|
||||
try {
|
||||
username = (String) authorObj.getClass().getMethod("getUsername").invoke(authorObj);
|
||||
} catch (Exception e) {
|
||||
username = null;
|
||||
}
|
||||
if (username == null || username.isEmpty()) return "匿名";
|
||||
return username;
|
||||
} catch (Exception ignored) {
|
||||
return "匿名";
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================== 时间/字符串/XML ===================== */
|
||||
|
||||
private static String toRfc1123Gmt(ZonedDateTime zdt) {
|
||||
|
||||
@@ -34,11 +34,12 @@ class PostServiceTest {
|
||||
TaskScheduler taskScheduler = mock(TaskScheduler.class);
|
||||
EmailSender emailSender = mock(EmailSender.class);
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
PointService pointService = mock(PointService.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, PublishMode.DIRECT);
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
Post post = new Post();
|
||||
@@ -80,11 +81,12 @@ class PostServiceTest {
|
||||
TaskScheduler taskScheduler = mock(TaskScheduler.class);
|
||||
EmailSender emailSender = mock(EmailSender.class);
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
PointService pointService = mock(PointService.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, PublishMode.DIRECT);
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
Post post = new Post();
|
||||
@@ -132,11 +134,12 @@ class PostServiceTest {
|
||||
TaskScheduler taskScheduler = mock(TaskScheduler.class);
|
||||
EmailSender emailSender = mock(EmailSender.class);
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
PointService pointService = mock(PointService.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, PublishMode.DIRECT);
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
when(postRepo.countByAuthorAfter(eq("alice"), any())).thenReturn(1L);
|
||||
@@ -165,11 +168,12 @@ class PostServiceTest {
|
||||
TaskScheduler taskScheduler = mock(TaskScheduler.class);
|
||||
EmailSender emailSender = mock(EmailSender.class);
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
PointService pointService = mock(PointService.class);
|
||||
|
||||
PostService service = new PostService(postRepo, userRepo, catRepo, tagRepo, lotteryRepo,
|
||||
notifService, subService, commentService, commentRepo,
|
||||
reactionRepo, subRepo, notificationRepo, postReadService,
|
||||
imageUploader, taskScheduler, emailSender, context, PublishMode.DIRECT);
|
||||
imageUploader, taskScheduler, emailSender, context, pointService, PublishMode.DIRECT);
|
||||
when(context.getBean(PostService.class)).thenReturn(service);
|
||||
|
||||
User author = new User();
|
||||
|
||||
Reference in New Issue
Block a user