🎨 文章发布/更新/删除后续事件执行过程解耦
This commit is contained in:
parent
97375a3dbd
commit
a019078802
@ -97,4 +97,9 @@ forest([ˈfôrəst],n.森林)是一款现代化的知识社区项目,使
|
||||
## 鸣谢
|
||||
- 感谢 `JetBrains` 对本项目的帮助,为作者提供了开源许可版 `JetBrains` 全家桶
|
||||
|
||||
![JetBrains](src/main/resources/static/jb_beam.svg)
|
||||
![JetBrains](src/main/resources/static/jb_beam.svg)
|
||||
|
||||
|
||||
## ⭐ Star 历史
|
||||
|
||||
[![Stargazers over time](https://starchart.cc/rymcu/forest.svg)](https://starchart.cc/rymcu/forest)
|
||||
|
66
src/main/java/com/rymcu/forest/handler/ArticleHandler.java
Normal file
66
src/main/java/com/rymcu/forest/handler/ArticleHandler.java
Normal file
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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<Article> 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<Article> 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<Article> 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<Article> 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("已有评论的文章不允许删除!");
|
||||
|
@ -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
|
||||
</select>
|
||||
|
||||
<select id="getArticlesByIds" resultMap="DTOResultMap">
|
||||
@ -61,6 +61,6 @@
|
||||
from forest_article art
|
||||
join forest_article_content content on art.id = content.id_article
|
||||
where article_status = 0
|
||||
and id = #{id};
|
||||
and id = #{id}
|
||||
</select>
|
||||
</mapper>
|
||||
|
@ -39,6 +39,6 @@
|
||||
<select id="getById" resultMap="BaseResultMap">
|
||||
SELECT id, portfolio_title, portfolio_description
|
||||
FROM forest_portfolio
|
||||
where id = #{id};
|
||||
where id = #{id}
|
||||
</select>
|
||||
</mapper>
|
||||
|
@ -37,6 +37,6 @@
|
||||
<select id="getById" resultMap="BaseResultMap">
|
||||
SELECT id, nickname, signature
|
||||
FROM forest_user
|
||||
where id = #{id};
|
||||
where id = #{id}
|
||||
</select>
|
||||
</mapper>
|
||||
|
Loading…
Reference in New Issue
Block a user