diff --git a/README.md b/README.md index fa3ff2f..6796675 100644 --- a/README.md +++ b/README.md @@ -97,4 +97,9 @@ forest([ˈfôrəst],n.森林)是一款现代化的知识社区项目,使 ## 鸣谢 - 感谢 `JetBrains` 对本项目的帮助,为作者提供了开源许可版 `JetBrains` 全家桶 -![JetBrains](src/main/resources/static/jb_beam.svg) \ No newline at end of file +![JetBrains](src/main/resources/static/jb_beam.svg) + + +## ⭐ Star 历史 + +[![Stargazers over time](https://starchart.cc/rymcu/forest.svg)](https://starchart.cc/rymcu/forest) diff --git a/src/main/java/com/rymcu/forest/handler/ArticleHandler.java b/src/main/java/com/rymcu/forest/handler/ArticleHandler.java new file mode 100644 index 0000000..8627f62 --- /dev/null +++ b/src/main/java/com/rymcu/forest/handler/ArticleHandler.java @@ -0,0 +1,66 @@ +package com.rymcu.forest.handler; + +import com.rymcu.forest.core.constant.NotificationConstant; +import com.rymcu.forest.handler.event.ArticleDeleteEvent; +import com.rymcu.forest.handler.event.ArticleEvent; +import com.rymcu.forest.lucene.service.LuceneService; +import com.rymcu.forest.util.NotificationUtils; +import com.rymcu.forest.wx.mp.utils.JsonUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +/** + * Created on 2022/8/16 20:42. + * + * @author ronger + * @email ronger-x@outlook.com + */ +@Slf4j +@Component +public class ArticleHandler { + @Resource + private LuceneService luceneService; + + @EventListener + @Async + public void processArticlePostEvent(ArticleEvent articleEvent) throws InterruptedException { + Thread.sleep(1000); + log.info(String.format("执行文章发布相关事件:[%s]", JsonUtils.toJson(articleEvent))); + // 发送系统通知 + if (articleEvent.getNotification()) { + NotificationUtils.sendAnnouncement(articleEvent.getIdArticle(), NotificationConstant.Article, articleEvent.getArticleTitle()); + } else { + // 发送关注通知 + StringBuilder dataSummary = new StringBuilder(); + if (articleEvent.getIsUpdate()) { + dataSummary.append(articleEvent.getNickname()).append("更新了文章: ").append(articleEvent.getArticleTitle()); + NotificationUtils.sendArticlePush(articleEvent.getIdArticle(), NotificationConstant.UpdateArticle, dataSummary.toString(), articleEvent.getArticleAuthorId()); + } else { + dataSummary.append(articleEvent.getNickname()).append("发布了文章: ").append(articleEvent.getArticleTitle()); + NotificationUtils.sendArticlePush(articleEvent.getIdArticle(), NotificationConstant.PostArticle, dataSummary.toString(), articleEvent.getArticleAuthorId()); + } + } + // 草稿不更新索引 + if (articleEvent.getIsUpdate()) { + log.info("更新文章索引,id={}", articleEvent.getIdArticle()); + luceneService.updateArticle(articleEvent.getIdArticle()); + } else { + log.info("写入文章索引,id={}", articleEvent.getIdArticle()); + luceneService.writeArticle(articleEvent.getIdArticle()); + } + log.info("执行完成文章发布相关事件...id={}", articleEvent.getIdArticle()); + } + + @EventListener + @Async + public void processArticleDeleteEvent(ArticleDeleteEvent articleDeleteEvent) throws InterruptedException { + Thread.sleep(1000); + log.info(String.format("执行文章删除相关事件:[%s]", JsonUtils.toJson(articleDeleteEvent))); + luceneService.deleteArticle(articleDeleteEvent.getIdArticle()); + log.info("执行完成文章删除相关事件...id={}", articleDeleteEvent.getIdArticle()); + } +} diff --git a/src/main/java/com/rymcu/forest/handler/event/ArticleDeleteEvent.java b/src/main/java/com/rymcu/forest/handler/event/ArticleDeleteEvent.java new file mode 100644 index 0000000..c44b34c --- /dev/null +++ b/src/main/java/com/rymcu/forest/handler/event/ArticleDeleteEvent.java @@ -0,0 +1,18 @@ +package com.rymcu.forest.handler.event; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * Created on 2022/8/20 18:51. + * + * @author ronger + * @email ronger-x@outlook.com + */ +@Data +@AllArgsConstructor +public class ArticleDeleteEvent { + + private Long idArticle; + +} diff --git a/src/main/java/com/rymcu/forest/handler/event/ArticleEvent.java b/src/main/java/com/rymcu/forest/handler/event/ArticleEvent.java new file mode 100644 index 0000000..57940d2 --- /dev/null +++ b/src/main/java/com/rymcu/forest/handler/event/ArticleEvent.java @@ -0,0 +1,27 @@ +package com.rymcu.forest.handler.event; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * Created on 2022/8/16 20:56. + * + * @author ronger + * @email ronger-x@outlook.com + */ +@Data +@AllArgsConstructor +public class ArticleEvent { + + private Long idArticle; + + private String articleTitle; + + private Boolean isUpdate; + + private Boolean notification; + + private String nickname; + + private Long articleAuthorId; +} diff --git a/src/main/java/com/rymcu/forest/lucene/service/impl/LuceneServiceImpl.java b/src/main/java/com/rymcu/forest/lucene/service/impl/LuceneServiceImpl.java index 8d8b8d3..72f4a79 100644 --- a/src/main/java/com/rymcu/forest/lucene/service/impl/LuceneServiceImpl.java +++ b/src/main/java/com/rymcu/forest/lucene/service/impl/LuceneServiceImpl.java @@ -66,7 +66,7 @@ public class LuceneServiceImpl implements LuceneService { int totalCount = list.size(); int perThreadCount = 3000; // 加1避免线程池的参数为0 - int threadCount = totalCount / perThreadCount + (totalCount % perThreadCount == 0 ? 0 : 1) + 1; + int threadCount = totalCount / perThreadCount + (totalCount % perThreadCount == 0 ? 0 : 1); ExecutorService pool = Executors.newFixedThreadPool(threadCount); CountDownLatch countDownLatch1 = new CountDownLatch(1); CountDownLatch countDownLatch2 = new CountDownLatch(threadCount); diff --git a/src/main/java/com/rymcu/forest/lucene/service/impl/PortfolioLuceneServiceImpl.java b/src/main/java/com/rymcu/forest/lucene/service/impl/PortfolioLuceneServiceImpl.java index 4d35c34..07bc0ee 100644 --- a/src/main/java/com/rymcu/forest/lucene/service/impl/PortfolioLuceneServiceImpl.java +++ b/src/main/java/com/rymcu/forest/lucene/service/impl/PortfolioLuceneServiceImpl.java @@ -56,7 +56,7 @@ public class PortfolioLuceneServiceImpl implements PortfolioLuceneService { int totalCount = list.size(); int perThreadCount = 3000; // 加1避免线程池的参数为0 - int threadCount = totalCount / perThreadCount + (totalCount % perThreadCount == 0 ? 0 : 1) + 1; + int threadCount = totalCount / perThreadCount + (totalCount % perThreadCount == 0 ? 0 : 1); ExecutorService pool = Executors.newFixedThreadPool(threadCount); CountDownLatch countDownLatch1 = new CountDownLatch(1); CountDownLatch countDownLatch2 = new CountDownLatch(threadCount); diff --git a/src/main/java/com/rymcu/forest/lucene/util/ArticleIndexUtil.java b/src/main/java/com/rymcu/forest/lucene/util/ArticleIndexUtil.java index 9b3880e..eaeb772 100644 --- a/src/main/java/com/rymcu/forest/lucene/util/ArticleIndexUtil.java +++ b/src/main/java/com/rymcu/forest/lucene/util/ArticleIndexUtil.java @@ -1,7 +1,6 @@ package com.rymcu.forest.lucene.util; import cn.hutool.core.io.FileUtil; -import cn.hutool.core.util.StrUtil; import com.rymcu.forest.lucene.model.ArticleLucene; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; @@ -12,6 +11,7 @@ import org.apache.lucene.index.Term; import java.io.IOException; import java.util.Arrays; +import java.util.concurrent.locks.ReentrantLock; /** * 文章索引更新工具类 @@ -20,68 +20,85 @@ import java.util.Arrays; */ public class ArticleIndexUtil { - /** lucene索引保存目录 */ - private static final String PATH = - System.getProperty("user.dir") + StrUtil.SLASH + LucenePath.ARTICLE_INDEX_PATH; + /** + * lucene索引保存目录 + */ + private static final String PATH = + System.getProperty("user.dir") + LucenePath.ARTICLE_INDEX_PATH; - private static final String WINDOW_PATH = - System.getProperty("user.dir") + StrUtil.BACKSLASH + "lucene\\index\\article"; - - /** 删除所有运行中保存的索引 */ - public static void deleteAllIndex() { - if (FileUtil.exist(LucenePath.ARTICLE_INCREMENT_INDEX_PATH)) { - FileUtil.del(LucenePath.ARTICLE_INCREMENT_INDEX_PATH); + /** + * 删除所有运行中保存的索引 + */ + public static void deleteAllIndex() { + if (FileUtil.exist(LucenePath.ARTICLE_INCREMENT_INDEX_PATH)) { + FileUtil.del(LucenePath.ARTICLE_INCREMENT_INDEX_PATH); + } } - } - public static void addIndex(ArticleLucene t) { - creatIndex(t); - } - - public static void updateIndex(ArticleLucene t) { - deleteIndex(t.getIdArticle()); - creatIndex(t); - } - - /** - * 增加或创建单个索引 - * - * @param t - * @throws Exception - */ - private static synchronized void creatIndex(ArticleLucene t) { - System.out.println("创建单个索引"); - IndexWriter writer; - try { - writer = IndexUtil.getIndexWriter(LucenePath.ARTICLE_INCREMENT_INDEX_PATH, false); - Document doc = new Document(); - doc.add(new StringField("id", t.getIdArticle() + "", Field.Store.YES)); - doc.add(new TextField("title", t.getArticleTitle(), Field.Store.YES)); - doc.add(new TextField("summary", t.getArticleContent(), Field.Store.YES)); - writer.addDocument(doc); - writer.close(); - } catch (IOException e) { - e.printStackTrace(); + public static void addIndex(ArticleLucene t) { + creatIndex(t); } - } - /** 删除单个索引 */ - public static synchronized void deleteIndex(Long id) { - Arrays.stream(FileUtil.ls(PATH)) - .forEach( - each -> { - if (each.isDirectory()) { - IndexWriter writer; - try { - writer = IndexUtil.getIndexWriter(each.getAbsolutePath(), false); - writer.deleteDocuments(new Term("id", String.valueOf(id))); - writer.forceMergeDeletes(); // 强制删除 - writer.commit(); - writer.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - }); - } + public static void updateIndex(ArticleLucene t) { + deleteIndex(t.getIdArticle()); + creatIndex(t); + } + + /** + * 增加或创建单个索引 + * + * @param t + * @throws Exception + */ + private static void creatIndex(ArticleLucene t) { + System.out.printf("创建单个索引"); + IndexWriter writer; + ReentrantLock reentrantLock = new ReentrantLock(); + reentrantLock.lock(); + try { + boolean create = true; + if (FileUtil.exist(LucenePath.ARTICLE_INCREMENT_INDEX_PATH)) { + create = false; + } + writer = IndexUtil.getIndexWriter(LucenePath.ARTICLE_INCREMENT_INDEX_PATH, create); + Document doc = new Document(); + doc.add(new StringField("id", t.getIdArticle() + "", Field.Store.YES)); + doc.add(new TextField("title", t.getArticleTitle(), Field.Store.YES)); + doc.add(new TextField("summary", t.getArticleContent(), Field.Store.YES)); + writer.addDocument(doc); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + reentrantLock.unlock(); + } + } + + /** + * 删除单个索引 + */ + public static void deleteIndex(Long id) { + Arrays.stream(FileUtil.ls(PATH)) + .forEach( + each -> { + if (each.isDirectory()) { + IndexWriter writer; + ReentrantLock reentrantLock = new ReentrantLock(); + reentrantLock.lock(); + try { + writer = IndexUtil.getIndexWriter(each.getAbsolutePath(), false); + writer.deleteDocuments(new Term("id", String.valueOf(id))); + writer.forceMerge(1); + // 强制删除 + writer.forceMergeDeletes(); + writer.commit(); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + reentrantLock.unlock(); + } + } + }); + } } diff --git a/src/main/java/com/rymcu/forest/lucene/util/LucenePath.java b/src/main/java/com/rymcu/forest/lucene/util/LucenePath.java index 234aeb3..0595bd0 100644 --- a/src/main/java/com/rymcu/forest/lucene/util/LucenePath.java +++ b/src/main/java/com/rymcu/forest/lucene/util/LucenePath.java @@ -8,7 +8,7 @@ package com.rymcu.forest.lucene.util; public final class LucenePath { /** lucene 目录 */ - public static final String INDEX_PATH = "lucene/index"; + public static final String INDEX_PATH = "/lucene/index"; /** 文章 lucene 目录 */ public static final String ARTICLE_INDEX_PATH = INDEX_PATH + "/article"; diff --git a/src/main/java/com/rymcu/forest/service/impl/ArticleServiceImpl.java b/src/main/java/com/rymcu/forest/service/impl/ArticleServiceImpl.java index 58585f3..7456c7c 100644 --- a/src/main/java/com/rymcu/forest/service/impl/ArticleServiceImpl.java +++ b/src/main/java/com/rymcu/forest/service/impl/ArticleServiceImpl.java @@ -1,8 +1,8 @@ package com.rymcu.forest.service.impl; import com.rymcu.forest.core.constant.NotificationConstant; -import com.rymcu.forest.core.exception.ContentNotExistException; import com.rymcu.forest.core.exception.BusinessException; +import com.rymcu.forest.core.exception.ContentNotExistException; import com.rymcu.forest.core.exception.UltraViresException; import com.rymcu.forest.core.service.AbstractService; import com.rymcu.forest.dto.*; @@ -10,26 +10,33 @@ import com.rymcu.forest.entity.Article; import com.rymcu.forest.entity.ArticleContent; import com.rymcu.forest.entity.Tag; import com.rymcu.forest.entity.User; -import com.rymcu.forest.lucene.service.LuceneService; +import com.rymcu.forest.handler.event.ArticleDeleteEvent; +import com.rymcu.forest.handler.event.ArticleEvent; import com.rymcu.forest.mapper.ArticleMapper; import com.rymcu.forest.service.ArticleService; import com.rymcu.forest.service.NotificationService; import com.rymcu.forest.service.TagService; import com.rymcu.forest.service.UserService; -import com.rymcu.forest.util.*; +import com.rymcu.forest.util.Html2TextUtil; +import com.rymcu.forest.util.UserUtils; +import com.rymcu.forest.util.Utils; +import com.rymcu.forest.util.XssUtils; import com.rymcu.forest.web.api.exception.BaseApiException; import com.rymcu.forest.web.api.exception.ErrorCode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.apache.commons.text.StringEscapeUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tk.mybatis.mapper.entity.Condition; import javax.annotation.Resource; import java.io.UnsupportedEncodingException; -import java.util.*; +import java.util.Date; +import java.util.List; +import java.util.Objects; /** * @author ronger @@ -45,9 +52,9 @@ public class ArticleServiceImpl extends AbstractService
implements Arti @Resource private UserService userService; @Resource - private LuceneService luceneService; - @Resource private NotificationService notificationService; + @Resource + private ApplicationEventPublisher publisher; @Value("${resource.domain}") private String domain; @@ -141,39 +148,16 @@ public class ArticleServiceImpl extends AbstractService
implements Arti articleMapper.updateArticleContent(newArticle.getIdArticle(), articleContent, articleContentHtml); } Long newArticleId = newArticle.getIdArticle(); - // 发送相关通知 + // 更新文章链接 if (DEFAULT_STATUS.equals(newArticle.getArticleStatus())) { - // 发送系统通知 - if (notification) { - NotificationUtils.sendAnnouncement(newArticleId, NotificationConstant.Article, newArticle.getArticleTitle()); - } else { - // 发送关注通知 - StringBuilder dataSummary = new StringBuilder(); - if (isUpdate) { - dataSummary.append(user.getNickname()).append("更新了文章: ").append(newArticle.getArticleTitle()); - NotificationUtils.sendArticlePush(newArticleId, NotificationConstant.UpdateArticle, dataSummary.toString(), newArticle.getArticleAuthorId()); - } else { - dataSummary.append(user.getNickname()).append("发布了文章: ").append(newArticle.getArticleTitle()); - NotificationUtils.sendArticlePush(newArticleId, NotificationConstant.PostArticle, dataSummary.toString(), newArticle.getArticleAuthorId()); - } - } - // 草稿不更新索引 - if (isUpdate) { - log.info("更新文章索引,id={}", newArticleId); - luceneService.updateArticle(newArticleId); - } else { - log.info("写入文章索引,id={}", newArticleId); - luceneService.writeArticle(newArticleId); - } - // 更新文章链接 + // 文章 newArticle.setArticlePermalink(domain + "/article/" + newArticleId); newArticle.setArticleLink("/article/" + newArticleId); } else { - // 更新文章链接 + // 草稿 newArticle.setArticlePermalink(domain + "/draft/" + newArticleId); newArticle.setArticleLink("/draft/" + newArticleId); } - tagService.saveTagArticle(newArticle, articleContentHtml, user.getIdUser()); if (StringUtils.isNotBlank(articleContentHtml)) { String previewContent = Html2TextUtil.getContent(articleContentHtml); @@ -183,6 +167,12 @@ public class ArticleServiceImpl extends AbstractService
implements Arti newArticle.setArticlePreviewContent(previewContent); } articleMapper.updateByPrimaryKeySelective(newArticle); + // 更新标签 + tagService.saveTagArticle(newArticle, articleContentHtml, user.getIdUser()); + if (DEFAULT_STATUS.equals(newArticle.getArticleStatus())) { + // 文章发布事件 + publisher.publishEvent(new ArticleEvent(newArticleId, newArticle.getArticleTitle(), isUpdate, notification, user.getNickname(), newArticle.getArticleAuthorId())); + } return newArticleId; } @@ -195,7 +185,9 @@ public class ArticleServiceImpl extends AbstractService
implements Arti deleteLinkedData(id); // 删除文章 int result = articleMapper.deleteByPrimaryKey(id); - luceneService.deleteArticle(id); + if (result > 0) { + publisher.publishEvent(new ArticleDeleteEvent(id)); + } return result; } else { throw new BusinessException("已有评论的文章不允许删除!"); diff --git a/src/main/java/mapper/lucene/ArticleLuceneMapper.xml b/src/main/java/mapper/lucene/ArticleLuceneMapper.xml index 95a8c61..9efadf7 100755 --- a/src/main/java/mapper/lucene/ArticleLuceneMapper.xml +++ b/src/main/java/mapper/lucene/ArticleLuceneMapper.xml @@ -35,7 +35,7 @@ select art.id, art.article_title, content.article_content_html as article_content from forest_article art join forest_article_content content on art.id = content.id_article - where article_status = 0; + where article_status = 0 diff --git a/src/main/java/mapper/lucene/PortfolioLuceneMapper.xml b/src/main/java/mapper/lucene/PortfolioLuceneMapper.xml index b1d09d4..2079fdf 100644 --- a/src/main/java/mapper/lucene/PortfolioLuceneMapper.xml +++ b/src/main/java/mapper/lucene/PortfolioLuceneMapper.xml @@ -39,6 +39,6 @@ diff --git a/src/main/java/mapper/lucene/UserLuceneMapper.xml b/src/main/java/mapper/lucene/UserLuceneMapper.xml index 31cb8f9..db196c2 100644 --- a/src/main/java/mapper/lucene/UserLuceneMapper.xml +++ b/src/main/java/mapper/lucene/UserLuceneMapper.xml @@ -37,6 +37,6 @@