|
@@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
import org.springframework.jdbc.core.JdbcTemplate;
|
|
|
|
|
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
@@ -76,6 +77,9 @@ public class ArticleService {
|
|
|
@Resource
|
|
@Resource
|
|
|
private JdbcTemplate jdbcTemplate;
|
|
private JdbcTemplate jdbcTemplate;
|
|
|
|
|
|
|
|
|
|
+ @Resource(name = "taskExecutor")
|
|
|
|
|
+ private ThreadPoolTaskExecutor taskExecutor;
|
|
|
|
|
+
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
|
|
|
public Page<Article> getPublicList(Long categoryId, String keyword, String dimensionCode, int page, int size,
|
|
public Page<Article> getPublicList(Long categoryId, String keyword, String dimensionCode, int page, int size,
|
|
@@ -386,6 +390,16 @@ public class ArticleService {
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public void create(Article article, Long adminId) {
|
|
public void create(Article article, Long adminId) {
|
|
|
|
|
+ // 幂等检查:同一标题不允许重复创建
|
|
|
|
|
+ if (article.getTitle() != null && !article.getTitle().trim().isEmpty()) {
|
|
|
|
|
+ LambdaQueryWrapper<Article> dupCheck = new LambdaQueryWrapper<Article>()
|
|
|
|
|
+ .eq(Article::getTitle, article.getTitle().trim());
|
|
|
|
|
+ Long existingCount = articleMapper.selectCount(dupCheck);
|
|
|
|
|
+ if (existingCount != null && existingCount > 0) {
|
|
|
|
|
+ throw new RuntimeException("标题「" + article.getTitle().trim() + "」已存在,请勿重复创建");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// Auto-calculate wordCount from content
|
|
// Auto-calculate wordCount from content
|
|
|
if (article.getContent() != null) {
|
|
if (article.getContent() != null) {
|
|
|
article.setWordCount(calculateWordCount(article.getContent()));
|
|
article.setWordCount(calculateWordCount(article.getContent()));
|
|
@@ -403,16 +417,20 @@ public class ArticleService {
|
|
|
}
|
|
}
|
|
|
articleMapper.insert(article);
|
|
articleMapper.insert(article);
|
|
|
|
|
|
|
|
- // AI auto-tagging
|
|
|
|
|
- try {
|
|
|
|
|
- List<String> tagNames = autoGenerateTags(article.getContent());
|
|
|
|
|
- if (!tagNames.isEmpty()) {
|
|
|
|
|
- saveAutoTags(article.getId(), tagNames);
|
|
|
|
|
|
|
+ // AI auto-tagging (异步,不阻塞 HTTP 响应)
|
|
|
|
|
+ final Long articleId = article.getId();
|
|
|
|
|
+ final String content = article.getContent();
|
|
|
|
|
+ taskExecutor.execute(() -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<String> tagNames = autoGenerateTags(content);
|
|
|
|
|
+ if (!tagNames.isEmpty()) {
|
|
|
|
|
+ saveAutoTags(articleId, tagNames);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("AI 自动标签生成失败(不影响文章创建): articleId={}, error={}",
|
|
|
|
|
+ articleId, e.getMessage());
|
|
|
}
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.warn("AI 自动标签生成失败(不影响文章创建): articleId={}, error={}",
|
|
|
|
|
- article.getId(), e.getMessage());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Transactional
|
|
@Transactional
|
|
@@ -428,18 +446,22 @@ public class ArticleService {
|
|
|
article.setUpdatedAt(new Date());
|
|
article.setUpdatedAt(new Date());
|
|
|
articleMapper.updateById(article);
|
|
articleMapper.updateById(article);
|
|
|
|
|
|
|
|
- // AI auto-tagging only when content changed
|
|
|
|
|
|
|
+ // AI auto-tagging only when content changed (异步,不阻塞 HTTP 响应)
|
|
|
if (contentChanged && article.getContent() != null) {
|
|
if (contentChanged && article.getContent() != null) {
|
|
|
- try {
|
|
|
|
|
- jdbcTemplate.update("DELETE FROM article_tags WHERE article_id = ?", article.getId());
|
|
|
|
|
- List<String> tagNames = autoGenerateTags(article.getContent());
|
|
|
|
|
- if (!tagNames.isEmpty()) {
|
|
|
|
|
- saveAutoTags(article.getId(), tagNames);
|
|
|
|
|
|
|
+ final Long articleId = article.getId();
|
|
|
|
|
+ final String content = article.getContent();
|
|
|
|
|
+ taskExecutor.execute(() -> {
|
|
|
|
|
+ try {
|
|
|
|
|
+ jdbcTemplate.update("DELETE FROM article_tags WHERE article_id = ?", articleId);
|
|
|
|
|
+ List<String> tagNames = autoGenerateTags(content);
|
|
|
|
|
+ if (!tagNames.isEmpty()) {
|
|
|
|
|
+ saveAutoTags(articleId, tagNames);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("AI 自动标签更新失败(不影响文章更新): articleId={}, error={}",
|
|
|
|
|
+ articleId, e.getMessage());
|
|
|
}
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.warn("AI 自动标签更新失败(不影响文章更新): articleId={}, error={}",
|
|
|
|
|
- article.getId(), e.getMessage());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|