mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-26 16:10:49 +08:00
Compare commits
8 Commits
feature/da
...
feature/rs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92e630df22 | ||
|
|
c6b0f32b09 | ||
|
|
5f5b6f84a8 | ||
|
|
cd57d478f2 | ||
|
|
da07313df8 | ||
|
|
c08ecb5e33 | ||
|
|
0a722c81c5 | ||
|
|
15071471b2 |
@@ -1,7 +1,10 @@
|
|||||||
package com.openisle.controller;
|
package com.openisle.controller;
|
||||||
|
|
||||||
import com.openisle.model.Post;
|
import com.openisle.model.Post;
|
||||||
|
import com.openisle.model.Comment;
|
||||||
|
import com.openisle.model.CommentSort;
|
||||||
import com.openisle.service.PostService;
|
import com.openisle.service.PostService;
|
||||||
|
import com.openisle.service.CommentService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
@@ -31,6 +34,7 @@ import java.util.regex.Pattern;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class RssController {
|
public class RssController {
|
||||||
private final PostService postService;
|
private final PostService postService;
|
||||||
|
private final CommentService commentService;
|
||||||
|
|
||||||
@Value("${app.website-url:https://www.open-isle.com}")
|
@Value("${app.website-url:https://www.open-isle.com}")
|
||||||
private String websiteUrl;
|
private String websiteUrl;
|
||||||
@@ -103,6 +107,12 @@ public class RssController {
|
|||||||
enclosure = absolutifyUrl(enclosure, base);
|
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>");
|
sb.append("<item>");
|
||||||
elem(sb, "title", cdata(nullSafe(p.getTitle())));
|
elem(sb, "title", cdata(nullSafe(p.getTitle())));
|
||||||
elem(sb, "link", link);
|
elem(sb, "link", link);
|
||||||
@@ -110,8 +120,11 @@ public class RssController {
|
|||||||
elem(sb, "pubDate", toRfc1123Gmt(p.getCreatedAt().atZone(ZoneId.systemDefault())));
|
elem(sb, "pubDate", toRfc1123Gmt(p.getCreatedAt().atZone(ZoneId.systemDefault())));
|
||||||
// 摘要
|
// 摘要
|
||||||
elem(sb, "description", cdata(plain));
|
elem(sb, "description", cdata(plain));
|
||||||
// 全文(HTML)
|
// 全文(HTML):正文 + 优雅的 Markdown 区块(已转 HTML)
|
||||||
sb.append("<content:encoded><![CDATA[").append(absHtml).append("]]></content:encoded>");
|
sb.append("<content:encoded><![CDATA[")
|
||||||
|
.append(absHtml)
|
||||||
|
.append(footerHtml)
|
||||||
|
.append("]]></content:encoded>");
|
||||||
// 首图 enclosure(图片类型)
|
// 首图 enclosure(图片类型)
|
||||||
if (enclosure != null) {
|
if (enclosure != null) {
|
||||||
sb.append("<enclosure url=\"").append(escapeXml(enclosure)).append("\" type=\"")
|
sb.append("<enclosure url=\"").append(escapeXml(enclosure)).append("\" type=\"")
|
||||||
@@ -136,8 +149,12 @@ public class RssController {
|
|||||||
private static String sanitizeHtml(String html) {
|
private static String sanitizeHtml(String html) {
|
||||||
if (html == null) return "";
|
if (html == null) return "";
|
||||||
Safelist wl = Safelist.relaxed()
|
Safelist wl = Safelist.relaxed()
|
||||||
.addTags("pre", "code", "figure", "figcaption", "picture", "source",
|
.addTags(
|
||||||
"table","thead","tbody","tr","th","td","h1","h2","h3","h4","h5","h6")
|
"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("a", "href", "title", "target", "rel")
|
||||||
.addAttributes("img", "src", "alt", "title", "width", "height")
|
.addAttributes("img", "src", "alt", "title", "width", "height")
|
||||||
.addAttributes("source", "srcset", "type", "media")
|
.addAttributes("source", "srcset", "type", "media")
|
||||||
@@ -246,6 +263,59 @@ public class RssController {
|
|||||||
return "image/jpeg";
|
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 ===================== */
|
/* ===================== 时间/字符串/XML ===================== */
|
||||||
|
|
||||||
private static String toRfc1123Gmt(ZonedDateTime zdt) {
|
private static String toRfc1123Gmt(ZonedDateTime zdt) {
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ public enum PointHistoryType {
|
|||||||
POST_LIKED,
|
POST_LIKED,
|
||||||
COMMENT_LIKED,
|
COMMENT_LIKED,
|
||||||
INVITE,
|
INVITE,
|
||||||
SYSTEM_ONLINE
|
SYSTEM_ONLINE,
|
||||||
|
REDEEM
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package com.openisle.service;
|
|||||||
import com.openisle.exception.FieldException;
|
import com.openisle.exception.FieldException;
|
||||||
import com.openisle.exception.NotFoundException;
|
import com.openisle.exception.NotFoundException;
|
||||||
import com.openisle.model.PointGood;
|
import com.openisle.model.PointGood;
|
||||||
|
import com.openisle.model.PointHistory;
|
||||||
|
import com.openisle.model.PointHistoryType;
|
||||||
import com.openisle.model.User;
|
import com.openisle.model.User;
|
||||||
import com.openisle.repository.PointGoodRepository;
|
import com.openisle.repository.PointGoodRepository;
|
||||||
|
import com.openisle.repository.PointHistoryRepository;
|
||||||
import com.openisle.repository.UserRepository;
|
import com.openisle.repository.UserRepository;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -18,6 +21,7 @@ public class PointMallService {
|
|||||||
private final PointGoodRepository pointGoodRepository;
|
private final PointGoodRepository pointGoodRepository;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final NotificationService notificationService;
|
private final NotificationService notificationService;
|
||||||
|
private final PointHistoryRepository pointHistoryRepository;
|
||||||
|
|
||||||
public List<PointGood> listGoods() {
|
public List<PointGood> listGoods() {
|
||||||
return pointGoodRepository.findAll();
|
return pointGoodRepository.findAll();
|
||||||
@@ -32,6 +36,13 @@ public class PointMallService {
|
|||||||
user.setPoint(user.getPoint() - good.getCost());
|
user.setPoint(user.getPoint() - good.getCost());
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
notificationService.createPointRedeemNotifications(user, good.getName() + ": " + contact);
|
notificationService.createPointRedeemNotifications(user, good.getName() + ": " + contact);
|
||||||
|
PointHistory history = new PointHistory();
|
||||||
|
history.setUser(user);
|
||||||
|
history.setType(PointHistoryType.REDEEM);
|
||||||
|
history.setAmount(-good.getCost());
|
||||||
|
history.setBalance(user.getPoint());
|
||||||
|
history.setCreatedAt(java.time.LocalDateTime.now());
|
||||||
|
pointHistoryRepository.save(history);
|
||||||
return user.getPoint();
|
return user.getPoint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,9 @@
|
|||||||
}}</NuxtLink>
|
}}</NuxtLink>
|
||||||
加入社区 🎉,获得 {{ item.amount }} 积分
|
加入社区 🎉,获得 {{ item.amount }} 积分
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="item.type === 'REDEEM'">
|
||||||
|
兑换商品,消耗 {{ -item.amount }} 积分
|
||||||
|
</template>
|
||||||
<template v-else-if="item.type === 'SYSTEM_ONLINE'"> 积分历史系统上线 </template>
|
<template v-else-if="item.type === 'SYSTEM_ONLINE'"> 积分历史系统上线 </template>
|
||||||
<i class="fas fa-coins"></i> 你目前的积分是 {{ item.balance }}
|
<i class="fas fa-coins"></i> 你目前的积分是 {{ item.balance }}
|
||||||
</div>
|
</div>
|
||||||
@@ -188,6 +191,7 @@ const iconMap = {
|
|||||||
COMMENT_LIKED: 'fas fa-thumbs-up',
|
COMMENT_LIKED: 'fas fa-thumbs-up',
|
||||||
INVITE: 'fas fa-user-plus',
|
INVITE: 'fas fa-user-plus',
|
||||||
SYSTEM_ONLINE: 'fas fa-clock',
|
SYSTEM_ONLINE: 'fas fa-clock',
|
||||||
|
REDEEM: 'fas fa-gift',
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
1
frontend_nuxt/public/tencent2707107139169774686.txt
Normal file
1
frontend_nuxt/public/tencent2707107139169774686.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
1839503219847005265
|
||||||
Reference in New Issue
Block a user