🎨 文章发布/更新/删除后续事件执行过程解耦

This commit is contained in:
ronger 2022-08-20 19:11:30 +08:00
parent 97375a3dbd
commit a019078802
12 changed files with 226 additions and 101 deletions

View File

@ -98,3 +98,8 @@ forest[ˈfôrəst]n.森林)是一款现代化的知识社区项目,使
- 感谢 `JetBrains` 对本项目的帮助,为作者提供了开源许可版 `JetBrains` 全家桶
![JetBrains](src/main/resources/static/jb_beam.svg)
## ⭐ Star 历史
[![Stargazers over time](https://starchart.cc/rymcu/forest.svg)](https://starchart.cc/rymcu/forest)

View 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());
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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,14 +20,15 @@ import java.util.Arrays;
*/
public class ArticleIndexUtil {
/** lucene索引保存目录 */
/**
* lucene索引保存目录
*/
private static final String PATH =
System.getProperty("user.dir") + StrUtil.SLASH + LucenePath.ARTICLE_INDEX_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);
@ -49,11 +50,17 @@ public class ArticleIndexUtil {
* @param t
* @throws Exception
*/
private static synchronized void creatIndex(ArticleLucene t) {
System.out.println("创建单个索引");
private static void creatIndex(ArticleLucene t) {
System.out.printf("创建单个索引");
IndexWriter writer;
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
try {
writer = IndexUtil.getIndexWriter(LucenePath.ARTICLE_INCREMENT_INDEX_PATH, false);
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));
@ -62,24 +69,34 @@ public class ArticleIndexUtil {
writer.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
/** 删除单个索引 */
public static synchronized void deleteIndex(Long id) {
/**
* 删除单个索引
*/
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.forceMergeDeletes(); // 强制删除
writer.forceMerge(1);
// 强制删除
writer.forceMergeDeletes();
writer.commit();
writer.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
});

View File

@ -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";

View File

@ -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);
}
// 更新文章链接
if (DEFAULT_STATUS.equals(newArticle.getArticleStatus())) {
// 文章
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("已有评论的文章不允许删除!");

View File

@ -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>

View File

@ -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>

View File

@ -37,6 +37,6 @@
<select id="getById" resultMap="BaseResultMap">
SELECT id, nickname, signature
FROM forest_user
where id = #{id};
where id = #{id}
</select>
</mapper>