You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dsl3/service/impl/ArticleServiceImpl.java

377 lines
16 KiB

package com.aurora.service.impl;
import com.alibaba.fastjson.JSON;
import com.aurora.model.dto.*;
import com.aurora.entity.Article;
import com.aurora.entity.ArticleTag;
import com.aurora.entity.Category;
import com.aurora.entity.Tag;
import com.aurora.enums.FileExtEnum;
import com.aurora.enums.FilePathEnum;
import com.aurora.exception.BizException;
import com.aurora.mapper.ArticleMapper;
import com.aurora.mapper.ArticleTagMapper;
import com.aurora.mapper.CategoryMapper;
import com.aurora.mapper.TagMapper;
import com.aurora.service.ArticleService;
import com.aurora.service.ArticleTagService;
import com.aurora.service.RedisService;
import com.aurora.service.TagService;
import com.aurora.strategy.context.SearchStrategyContext;
import com.aurora.strategy.context.UploadStrategyContext;
import com.aurora.util.BeanCopyUtil;
import com.aurora.util.PageUtil;
import com.aurora.util.UserUtil;
import com.aurora.model.vo.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.SneakyThrows;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayInputStream;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static com.aurora.constant.RabbitMQConstant.SUBSCRIBE_EXCHANGE;
import static com.aurora.constant.RedisConstant.*;
import static com.aurora.enums.ArticleStatusEnum.*;
import static com.aurora.enums.StatusCodeEnum.ARTICLE_ACCESS_FAIL;
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
@Autowired
private ArticleMapper articleMapper;
@Autowired
private ArticleTagMapper articleTagMapper;
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private TagMapper tagMapper;
@Autowired
private TagService tagService;
@Autowired
private ArticleTagService articleTagService;
@Autowired
private RedisService redisService;
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private UploadStrategyContext uploadStrategyContext;
@Autowired
private SearchStrategyContext searchStrategyContext;
@SneakyThrows
@Override
public TopAndFeaturedArticlesDTO listTopAndFeaturedArticles() {
List<ArticleCardDTO> articleCardDTOs = articleMapper.listTopAndFeaturedArticles();
if (articleCardDTOs.size() == 0) {
return new TopAndFeaturedArticlesDTO();
} else if (articleCardDTOs.size() > 3) {
articleCardDTOs = articleCardDTOs.subList(0, 3);
}
TopAndFeaturedArticlesDTO topAndFeaturedArticlesDTO = new TopAndFeaturedArticlesDTO();
topAndFeaturedArticlesDTO.setTopArticle(articleCardDTOs.get(0));
articleCardDTOs.remove(0);
topAndFeaturedArticlesDTO.setFeaturedArticles(articleCardDTOs);
return topAndFeaturedArticlesDTO;
}
@SneakyThrows
@Override
public PageResultDTO<ArticleCardDTO> listArticles() {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<Article>()
.eq(Article::getIsDelete, 0)
.in(Article::getStatus, 1, 2);
CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> articleMapper.selectCount(queryWrapper));
List<ArticleCardDTO> articles = articleMapper.listArticles(PageUtil.getLimitCurrent(), PageUtil.getSize());
return new PageResultDTO<>(articles, asyncCount.get());
}
@SneakyThrows
@Override
public PageResultDTO<ArticleCardDTO> listArticlesByCategoryId(Integer categoryId) {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<Article>().eq(Article::getCategoryId, categoryId);
CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> articleMapper.selectCount(queryWrapper));
List<ArticleCardDTO> articles = articleMapper.getArticlesByCategoryId(PageUtil.getLimitCurrent(), PageUtil.getSize(), categoryId);
return new PageResultDTO<>(articles, asyncCount.get());
}
@SneakyThrows
@Override
public ArticleDTO getArticleById(Integer articleId) {
Article articleForCheck = articleMapper.selectOne(new LambdaQueryWrapper<Article>().eq(Article::getId, articleId));
if (Objects.isNull(articleForCheck)) {
return null;
}
if (articleForCheck.getStatus().equals(2)) {
Boolean isAccess;
try {
isAccess = redisService.sIsMember(ARTICLE_ACCESS + UserUtil.getUserDetailsDTO().getId(), articleId);
} catch (Exception exception) {
throw new BizException(ARTICLE_ACCESS_FAIL);
}
if (isAccess.equals(false)) {
throw new BizException(ARTICLE_ACCESS_FAIL);
}
}
updateArticleViewsCount(articleId);
CompletableFuture<ArticleDTO> asyncArticle = CompletableFuture.supplyAsync(() -> articleMapper.getArticleById(articleId));
CompletableFuture<ArticleCardDTO> asyncPreArticle = CompletableFuture.supplyAsync(() -> {
ArticleCardDTO preArticle = articleMapper.getPreArticleById(articleId);
if (Objects.isNull(preArticle)) {
preArticle = articleMapper.getLastArticle();
}
return preArticle;
});
CompletableFuture<ArticleCardDTO> asyncNextArticle = CompletableFuture.supplyAsync(() -> {
ArticleCardDTO nextArticle = articleMapper.getNextArticleById(articleId);
if (Objects.isNull(nextArticle)) {
nextArticle = articleMapper.getFirstArticle();
}
return nextArticle;
});
ArticleDTO article = asyncArticle.get();
if (Objects.isNull(article)) {
return null;
}
Double score = redisService.zScore(ARTICLE_VIEWS_COUNT, articleId);
if (Objects.nonNull(score)) {
article.setViewCount(score.intValue());
}
article.setPreArticleCard(asyncPreArticle.get());
article.setNextArticleCard(asyncNextArticle.get());
return article;
}
@Override
public void accessArticle(ArticlePasswordVO articlePasswordVO) {
Article article = articleMapper.selectOne(new LambdaQueryWrapper<Article>().eq(Article::getId, articlePasswordVO.getArticleId()));
if (Objects.isNull(article)) {
throw new BizException("文章不存在");
}
if (article.getPassword().equals(articlePasswordVO.getArticlePassword())) {
redisService.sAdd(ARTICLE_ACCESS + UserUtil.getUserDetailsDTO().getId(), articlePasswordVO.getArticleId());
} else {
throw new BizException("密码错误");
}
}
@SneakyThrows
@Override
public PageResultDTO<ArticleCardDTO> listArticlesByTagId(Integer tagId) {
LambdaQueryWrapper<ArticleTag> queryWrapper = new LambdaQueryWrapper<ArticleTag>().eq(ArticleTag::getTagId, tagId);
CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> articleTagMapper.selectCount(queryWrapper));
List<ArticleCardDTO> articles = articleMapper.listArticlesByTagId(PageUtil.getLimitCurrent(), PageUtil.getSize(), tagId);
return new PageResultDTO<>(articles, asyncCount.get());
}
@SneakyThrows
@Override
public PageResultDTO<ArchiveDTO> listArchives() {
LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<Article>().eq(Article::getIsDelete, 0).eq(Article::getStatus, 1);
CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> articleMapper.selectCount(queryWrapper));
List<ArticleCardDTO> articles = articleMapper.listArchives(PageUtil.getLimitCurrent(), PageUtil.getSize());
HashMap<String, List<ArticleCardDTO>> map = new HashMap<>();
for (ArticleCardDTO article : articles) {
LocalDateTime createTime = article.getCreateTime();
int month = createTime.getMonth().getValue();
int year = createTime.getYear();
String key = year + "-" + month;
if (Objects.isNull(map.get(key))) {
List<ArticleCardDTO> articleCardDTOS = new ArrayList<>();
articleCardDTOS.add(article);
map.put(key, articleCardDTOS);
} else {
map.get(key).add(article);
}
}
List<ArchiveDTO> archiveDTOs = new ArrayList<>();
map.forEach((key, value) -> archiveDTOs.add(ArchiveDTO.builder().Time(key).articles(value).build()));
archiveDTOs.sort((o1, o2) -> {
String[] o1s = o1.getTime().split("-");
String[] o2s = o2.getTime().split("-");
int o1Year = Integer.parseInt(o1s[0]);
int o1Month = Integer.parseInt(o1s[1]);
int o2Year = Integer.parseInt(o2s[0]);
int o2Month = Integer.parseInt(o2s[1]);
if (o1Year > o2Year) {
return -1;
} else if (o1Year < o2Year) {
return 1;
} else return Integer.compare(o2Month, o1Month);
});
return new PageResultDTO<>(archiveDTOs, asyncCount.get());
}
@SneakyThrows
@Override
public PageResultDTO<ArticleAdminDTO> listArticlesAdmin(ConditionVO conditionVO) {
CompletableFuture<Integer> asyncCount = CompletableFuture.supplyAsync(() -> articleMapper.countArticleAdmins(conditionVO));
List<ArticleAdminDTO> articleAdminDTOs = articleMapper.listArticlesAdmin(PageUtil.getLimitCurrent(), PageUtil.getSize(), conditionVO);
Map<Object, Double> viewsCountMap = redisService.zAllScore(ARTICLE_VIEWS_COUNT);
articleAdminDTOs.forEach(item -> {
Double viewsCount = viewsCountMap.get(item.getId());
if (Objects.nonNull(viewsCount)) {
item.setViewsCount(viewsCount.intValue());
}
});
return new PageResultDTO<>(articleAdminDTOs, asyncCount.get());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveOrUpdateArticle(ArticleVO articleVO) {
Category category = saveArticleCategory(articleVO);
Article article = BeanCopyUtil.copyObject(articleVO, Article.class);
if (Objects.nonNull(category)) {
article.setCategoryId(category.getId());
}
article.setUserId(UserUtil.getUserDetailsDTO().getUserInfoId());
this.saveOrUpdate(article);
saveArticleTag(articleVO, article.getId());
if (article.getStatus().equals(1)) {
rabbitTemplate.convertAndSend(SUBSCRIBE_EXCHANGE, "*", new Message(JSON.toJSONBytes(article.getId()), new MessageProperties()));
}
}
@Override
public void updateArticleTopAndFeatured(ArticleTopFeaturedVO articleTopFeaturedVO) {
Article article = Article.builder()
.id(articleTopFeaturedVO.getId())
.isTop(articleTopFeaturedVO.getIsTop())
.isFeatured(articleTopFeaturedVO.getIsFeatured())
.build();
articleMapper.updateById(article);
}
@Override
public void updateArticleDelete(DeleteVO deleteVO) {
List<Article> articles = deleteVO.getIds().stream()
.map(id -> Article.builder()
.id(id)
.isDelete(deleteVO.getIsDelete())
.build())
.collect(Collectors.toList());
this.updateBatchById(articles);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteArticles(List<Integer> articleIds) {
articleTagMapper.delete(new LambdaQueryWrapper<ArticleTag>()
.in(ArticleTag::getArticleId, articleIds));
articleMapper.deleteBatchIds(articleIds);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ArticleAdminViewDTO getArticleByIdAdmin(Integer articleId) {
Article article = articleMapper.selectById(articleId);
Category category = categoryMapper.selectById(article.getCategoryId());
String categoryName = null;
if (Objects.nonNull(category)) {
categoryName = category.getCategoryName();
}
List<String> tagNames = tagMapper.listTagNamesByArticleId(articleId);
ArticleAdminViewDTO articleAdminViewDTO = BeanCopyUtil.copyObject(article, ArticleAdminViewDTO.class);
articleAdminViewDTO.setCategoryName(categoryName);
articleAdminViewDTO.setTagNames(tagNames);
return articleAdminViewDTO;
}
@Override
public List<String> exportArticles(List<Integer> articleIds) {
List<Article> articles = articleMapper.selectList(new LambdaQueryWrapper<Article>()
.select(Article::getArticleTitle, Article::getArticleContent)
.in(Article::getId, articleIds));
List<String> urls = new ArrayList<>();
for (Article article : articles) {
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(article.getArticleContent().getBytes())) {
String url = uploadStrategyContext.executeUploadStrategy(article.getArticleTitle() + FileExtEnum.MD.getExtName(), inputStream, FilePathEnum.MD.getPath());
urls.add(url);
} catch (Exception e) {
e.printStackTrace();
throw new BizException("导出文章失败");
}
}
return urls;
}
@Override
public List<ArticleSearchDTO> listArticlesBySearch(ConditionVO condition) {
return searchStrategyContext.executeSearchStrategy(condition.getKeywords());
}
public void updateArticleViewsCount(Integer articleId) {
redisService.zIncr(ARTICLE_VIEWS_COUNT, articleId, 1D);
}
private Category saveArticleCategory(ArticleVO articleVO) {
Category category = categoryMapper.selectOne(new LambdaQueryWrapper<Category>()
.eq(Category::getCategoryName, articleVO.getCategoryName()));
if (Objects.isNull(category) && !articleVO.getStatus().equals(DRAFT.getStatus())) {
category = Category.builder()
.categoryName(articleVO.getCategoryName())
.build();
categoryMapper.insert(category);
}
return category;
}
@Transactional(rollbackFor = Exception.class)
public void saveArticleTag(ArticleVO articleVO, Integer articleId) {
if (Objects.nonNull(articleVO.getId())) {
articleTagMapper.delete(new LambdaQueryWrapper<ArticleTag>()
.eq(ArticleTag::getArticleId, articleVO.getId()));
}
List<String> tagNames = articleVO.getTagNames();
if (CollectionUtils.isNotEmpty(tagNames)) {
List<Tag> existTags = tagService.list(new LambdaQueryWrapper<Tag>()
.in(Tag::getTagName, tagNames));
List<String> existTagNames = existTags.stream()
.map(Tag::getTagName)
.collect(Collectors.toList());
List<Integer> existTagIds = existTags.stream()
.map(Tag::getId)
.collect(Collectors.toList());
tagNames.removeAll(existTagNames);
if (CollectionUtils.isNotEmpty(tagNames)) {
List<Tag> tags = tagNames.stream().map(item -> Tag.builder()
.tagName(item)
.build())
.collect(Collectors.toList());
tagService.saveBatch(tags);
List<Integer> tagIds = tags.stream()
.map(Tag::getId)
.collect(Collectors.toList());
existTagIds.addAll(tagIds);
}
List<ArticleTag> articleTags = existTagIds.stream().map(item -> ArticleTag.builder()
.articleId(articleId)
.tagId(item)
.build())
.collect(Collectors.toList());
articleTagService.saveBatch(articleTags);
}
}
}