|
@@ -1,6 +1,7 @@
|
|
|
package com.tzld.longarticle.recommend.server.service.recommend;
|
|
|
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
|
|
|
import com.tzld.longarticle.recommend.server.common.enums.aigc.PublishContentStatusEnum;
|
|
|
import com.tzld.longarticle.recommend.server.common.enums.longArticle.ArticleVideoAuditStatusEnum;
|
|
@@ -16,12 +17,12 @@ import com.tzld.longarticle.recommend.server.model.vo.ArticleVideoAuditListVO;
|
|
|
import com.tzld.longarticle.recommend.server.repository.aigc.ProducePlanExeRecordRepository;
|
|
|
import com.tzld.longarticle.recommend.server.repository.longArticle.*;
|
|
|
import com.tzld.longarticle.recommend.server.util.DateUtils;
|
|
|
+import com.tzld.longarticle.recommend.server.util.RedisUtil;
|
|
|
import com.tzld.longarticle.recommend.server.util.page.Page;
|
|
|
import com.xxl.job.core.biz.model.ReturnT;
|
|
|
import com.xxl.job.core.handler.annotation.XxlJob;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.collections4.CollectionUtils;
|
|
|
-import org.apache.commons.collections4.MapUtils;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
import org.springframework.data.redis.core.RedisTemplate;
|
|
@@ -56,6 +57,8 @@ public class ArticleVideoAuditService {
|
|
|
private ProducePlanExeRecordRepository producePlanExeRecordRepository;
|
|
|
@Autowired
|
|
|
private ArticlePoolPromotionSourceRepository articlePoolPromotionSourceRepository;
|
|
|
+ @Autowired
|
|
|
+ private RedisUtil redisUtil;
|
|
|
|
|
|
@Autowired
|
|
|
private RedisTemplate<String, String> redisTemplate;
|
|
@@ -72,10 +75,10 @@ public class ArticleVideoAuditService {
|
|
|
public Page<ArticleVideoAuditListVO> list(ArticleVideoAuditListParam param) {
|
|
|
int offset = (param.getPageNum() - 1) * param.getPageSize();
|
|
|
int count = articleAuditMapper.articleVideoAuditListCount(param.getContentId(), param.getStatus(),
|
|
|
- param.getTitle(), param.getAuditAccount(), param.getSourceProducePlan(), param.getAuditTimestamp());
|
|
|
+ param.getTitle(), param.getAuditAccount(), param.getSourceProducePlan(), param.getFlowPoolLevel(), param.getAuditTimestamp());
|
|
|
List<ArticleVideoAuditListVO> list = articleAuditMapper.articleVideoAuditList(param.getContentId(),
|
|
|
param.getStatus(), param.getTitle(), param.getAuditAccount(), param.getSourceProducePlan()
|
|
|
- , param.getAuditTimestamp(), offset, param.getPageSize(), poolLevelDesc);
|
|
|
+ , param.getFlowPoolLevel(), param.getAuditTimestamp(), offset, param.getPageSize(), poolLevelDesc);
|
|
|
buildArticleVideoAuditListVO(list);
|
|
|
Page<ArticleVideoAuditListVO> page = new Page<>(param.getPageNum(), param.getPageSize());
|
|
|
page.setTotalSize(count);
|
|
@@ -119,74 +122,89 @@ public class ArticleVideoAuditService {
|
|
|
}
|
|
|
|
|
|
public Page<ArticleVideoAuditListVO> next(ArticleVideoAuditListParam param) {
|
|
|
- if (Objects.nonNull(param.getSourceProducePlan())) {
|
|
|
- param.setPageSize(1);
|
|
|
- return list(param);
|
|
|
- }
|
|
|
Page<ArticleVideoAuditListVO> result = new Page<>();
|
|
|
- Long now = System.currentTimeMillis();
|
|
|
- String redisKey = "article-pool-audit-next-list";
|
|
|
- Map<Object, Object> entries = redisTemplate.opsForHash().entries(redisKey);
|
|
|
- List<String> excludeContentIds = new ArrayList<>();
|
|
|
- entries.forEach((k, v) -> {
|
|
|
- long timestamp = Long.parseLong((String) v);
|
|
|
- if (now > timestamp) {
|
|
|
- redisTemplate.opsForHash().delete(redisKey, k);
|
|
|
- } else {
|
|
|
- excludeContentIds.add((String) k);
|
|
|
+
|
|
|
+ String lockKey = "article-pool-audit-lock";
|
|
|
+ String requestId = UUID.randomUUID().toString();
|
|
|
+ int retryCount = 0;
|
|
|
+ boolean lockAcquired = false;
|
|
|
+
|
|
|
+ // 尝试获取分布式锁,最多重试10次,每次间隔200ms
|
|
|
+ while (retryCount < 10) {
|
|
|
+ lockAcquired = redisUtil.tryAcquireLock(lockKey, requestId);
|
|
|
+ if (lockAcquired) {
|
|
|
+ break;
|
|
|
}
|
|
|
- });
|
|
|
- // 根据配置判断当日是否审核完成 并 选择内容池返回
|
|
|
+ retryCount++;
|
|
|
+ try {
|
|
|
+ Thread.sleep(200); // 等待200ms后重试
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ return result; // 返回空结果
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!lockAcquired) {
|
|
|
+ return result; // 返回空结果
|
|
|
+ }
|
|
|
+
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ String id;
|
|
|
+ String inAuditListRedisKey = "article-pool-in-audit-list";
|
|
|
ArticleVideoAuditListVO item = null;
|
|
|
- List<String> excludePoolLevel = new ArrayList<>();
|
|
|
- String poolLevel = getAuditPoolLevel(excludePoolLevel);
|
|
|
- if (Objects.isNull(poolLevel)) {
|
|
|
- item = articleAuditMapper.articleVideoAuditNext(param.getContentId(),
|
|
|
- param.getStatus(), param.getTitle(), param.getAuditAccount(), param.getSourceProducePlan(),
|
|
|
- poolLevel, excludeContentIds);
|
|
|
- } else {
|
|
|
- do {
|
|
|
+ try {
|
|
|
+ String dateStr = DateUtils.getCurrentDateStr("yyyyMMdd");
|
|
|
+ String auditQueueRedisKey = "article-pool-audit-queue-" + dateStr;
|
|
|
+ // 从待审核队列中获取数据,如未获取到则从数据库查询一条
|
|
|
+ Long size = redisTemplate.opsForZSet().size(auditQueueRedisKey);
|
|
|
+ if (Objects.isNull(size) || size == 0) {
|
|
|
+ List<String> entries = redisTemplate.opsForList().range(inAuditListRedisKey, 0, -1);
|
|
|
+ List<String> excludeContentIds = new ArrayList<>();
|
|
|
+ if (CollectionUtils.isNotEmpty(entries)) {
|
|
|
+ excludeContentIds = entries.stream().map(o -> {
|
|
|
+ JSONObject json = JSONObject.parseObject(o);
|
|
|
+ return json.getString("id");
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+ }
|
|
|
item = articleAuditMapper.articleVideoAuditNext(param.getContentId(),
|
|
|
param.getStatus(), param.getTitle(), param.getAuditAccount(), param.getSourceProducePlan(),
|
|
|
- poolLevel, excludeContentIds);
|
|
|
- if (Objects.nonNull(item)) {
|
|
|
- break;
|
|
|
+ null, excludeContentIds);
|
|
|
+ } else {
|
|
|
+ // 从待审核队列中获取数据, 如获取到的内容已审核,则重新获取
|
|
|
+ while (true) {
|
|
|
+ Set<String> ids = redisTemplate.opsForZSet().reverseRangeByScore(auditQueueRedisKey, 0, 100, 0, 1);
|
|
|
+ if (CollectionUtils.isNotEmpty(ids)) {
|
|
|
+ id = ids.iterator().next();
|
|
|
+ redisTemplate.opsForZSet().remove(auditQueueRedisKey, id);
|
|
|
+ item = articleAuditMapper.articleVideoAuditNext(Arrays.asList(id), null, null,
|
|
|
+ null, null, null, null);
|
|
|
+ if (item.getStatus() == ArticleVideoAuditStatusEnum.WAITING.getCode()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
- excludePoolLevel.add(poolLevel);
|
|
|
- poolLevel = getAuditPoolLevel(excludePoolLevel);
|
|
|
- } while (Objects.nonNull(poolLevel));
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ // 释放锁
|
|
|
+ redisUtil.releaseLock(lockKey, requestId);
|
|
|
}
|
|
|
if (Objects.isNull(item)) {
|
|
|
return result;
|
|
|
}
|
|
|
- redisTemplate.opsForHash().put(redisKey, item.getContentId(), String.valueOf(now + 600000));
|
|
|
+
|
|
|
+ // 添加到超时队列
|
|
|
+ JSONObject json = new JSONObject();
|
|
|
+ json.put("id", item.getContentId());
|
|
|
+ json.put("timestamp", now + (15 * 60 * 1000));
|
|
|
+ redisTemplate.opsForList().rightPush(inAuditListRedisKey, json.toJSONString());
|
|
|
List<ArticleVideoAuditListVO> list = Collections.singletonList(item);
|
|
|
buildArticleVideoAuditListVO(list);
|
|
|
result.setObjs(list);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- private String getAuditPoolLevel(List<String> excludePoolLevel) {
|
|
|
- if (MapUtils.isEmpty(dailyAuditPoolCount)) {
|
|
|
- return null;
|
|
|
- }
|
|
|
- String dateStr = DateUtils.getCurrentDateStr("yyyyMMdd");
|
|
|
- Set<String> keySet = dailyAuditPoolCount.keySet();
|
|
|
- keySet = keySet.stream().sorted().collect(Collectors.toCollection(LinkedHashSet::new));
|
|
|
- for (String poolLevel : keySet) {
|
|
|
- if (excludePoolLevel.contains(poolLevel)) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- int target = dailyAuditPoolCount.get(poolLevel);
|
|
|
- String key = "article_audit_count_" + dateStr + "_" + poolLevel;
|
|
|
- int totalCount = Integer.parseInt(Optional.ofNullable(redisTemplate.opsForValue().get(key)).orElse("0"));
|
|
|
- if (target > totalCount) {
|
|
|
- return poolLevel;
|
|
|
- }
|
|
|
- }
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
public void auditArticle(ArticleAuditParam param) {
|
|
|
LongArticleTitleAudit titleAudit = titleAuditRepository.getByContentId(param.getContentId());
|
|
|
Long now = System.currentTimeMillis();
|
|
@@ -359,7 +377,7 @@ public class ArticleVideoAuditService {
|
|
|
|
|
|
@XxlJob("shuffleAuditGroup")
|
|
|
public ReturnT<String> shuffleAuditGroup(String param) {
|
|
|
- List<String> auditUser = Arrays.asList("a","b","c","d","e","f","g","h","i","j");
|
|
|
+ List<String> auditUser = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
|
|
|
List<LongArticleTitleAudit> contentIds = titleAuditRepository.getByStatus(ArticleVideoAuditStatusEnum.WAITING.getCode());
|
|
|
for (int i = 0; i < contentIds.size(); i++) {
|
|
|
int per = i % auditUser.size();
|
|
@@ -372,7 +390,7 @@ public class ArticleVideoAuditService {
|
|
|
|
|
|
public void addAudit(ArticleVideoAuditAddAuditParam param) {
|
|
|
ProducePlanExeRecord exeRecord = producePlanExeRecordRepository.getByPlanExeId(param.getContentId());
|
|
|
- List<String> auditUser = Arrays.asList("a","b","c","d","e","f","g","h","i","j");
|
|
|
+ List<String> auditUser = Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j");
|
|
|
Random random = new Random();
|
|
|
int index = random.nextInt(auditUser.size());
|
|
|
String auditAccount = auditUser.get(index);
|
|
@@ -398,4 +416,96 @@ public class ArticleVideoAuditService {
|
|
|
item.setAuditAccount(auditAccount);
|
|
|
titleAuditRepository.save(item);
|
|
|
}
|
|
|
+
|
|
|
+ @XxlJob("articlePoolAuditQueueJob")
|
|
|
+ public ReturnT<String> articlePoolAuditQueueJob(String param) {
|
|
|
+ String lockKey = "article-pool-audit-queue-job-lock";
|
|
|
+ String requestId = UUID.randomUUID().toString();
|
|
|
+ Boolean lockAcquired = redisUtil.tryAcquireLock(lockKey, requestId);
|
|
|
+ if (!lockAcquired) {
|
|
|
+ return ReturnT.SUCCESS;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ Long now = System.currentTimeMillis();
|
|
|
+ String dateStr = DateUtils.getCurrentDateStr("yyyyMMdd");
|
|
|
+ String inAuditListRedisKey = "article-pool-in-audit-list";
|
|
|
+ String auditQueueRedisKey = "article-pool-audit-queue-" + dateStr;
|
|
|
+ // 判断是否审核超时,重新加入待审核队列
|
|
|
+ while (true) {
|
|
|
+ List<String> inAuditList = redisTemplate.opsForList().range(inAuditListRedisKey, 0, 0);
|
|
|
+ if (CollectionUtils.isEmpty(inAuditList)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ JSONObject firstObj = JSONObject.parseObject(inAuditList.get(0));
|
|
|
+ if (now > firstObj.getLong("timestamp")) {
|
|
|
+ redisTemplate.opsForList().leftPop(inAuditListRedisKey);
|
|
|
+ String id = firstObj.getString("id");
|
|
|
+ LongArticleTitleAudit videoAudit = titleAuditRepository.getByContentId(id);
|
|
|
+ if (videoAudit.getStatus() == ArticleVideoAuditStatusEnum.WAITING.getCode()) {
|
|
|
+ ContentPoolEnum poolEnum = ContentPoolEnum.from(videoAudit.getFlowPoolLevel());
|
|
|
+ redisTemplate.opsForZSet().add(auditQueueRedisKey, id, poolEnum.getWeight());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ List<String> excludeContentIds = new ArrayList<>();
|
|
|
+ // 审核中列表获取,按内容池分组
|
|
|
+ List<String> inAuditList = redisTemplate.opsForList().range(inAuditListRedisKey, 0, -1);
|
|
|
+ Map<String, List<LongArticleTitleAudit>> inAuditListPoolCountMap = new HashMap<>();
|
|
|
+ if (CollectionUtils.isNotEmpty(inAuditList)) {
|
|
|
+ List<String> inAuditListIds = inAuditList.stream().map(item -> JSONObject.parseObject(item).getString("id")).collect(Collectors.toList());
|
|
|
+ List<LongArticleTitleAudit> auditQueueList = titleAuditRepository.getByContentIdIn(new ArrayList<>(inAuditListIds));
|
|
|
+ inAuditListPoolCountMap = auditQueueList.stream().collect(Collectors.groupingBy(item -> ContentPoolEnum.from(item.getFlowPoolLevel()).getContentPool()));
|
|
|
+ excludeContentIds.addAll(inAuditListIds);
|
|
|
+ }
|
|
|
+ // 待审核列表获取,按内容池分组
|
|
|
+ Set<String> auditQueueIds = redisTemplate.opsForZSet().rangeByScore(auditQueueRedisKey, 0, 100);
|
|
|
+ Map<String, List<LongArticleTitleAudit>> auditQueuePoolCountMap = new HashMap<>();
|
|
|
+ if (CollectionUtils.isNotEmpty(auditQueueIds)) {
|
|
|
+ List<LongArticleTitleAudit> auditQueueList = titleAuditRepository.getByContentIdIn(new ArrayList<>(auditQueueIds));
|
|
|
+ auditQueuePoolCountMap = auditQueueList.stream().collect(Collectors.groupingBy(item -> ContentPoolEnum.from(item.getFlowPoolLevel()).getContentPool()));
|
|
|
+ excludeContentIds.addAll(auditQueueIds);
|
|
|
+ }
|
|
|
+ // 每日配置发送量不足添加
|
|
|
+ for (Map.Entry<String, Integer> entry : dailyAuditPoolCount.entrySet()) {
|
|
|
+ ContentPoolEnum poolEnum = ContentPoolEnum.from(entry.getKey());
|
|
|
+ String key = "article_audit_count_" + dateStr + "_" + poolEnum.getContentPool();
|
|
|
+ int totalCount = Integer.parseInt(Optional.ofNullable(redisTemplate.opsForValue().get(key)).orElse("0"));
|
|
|
+ if (totalCount < entry.getValue()) {
|
|
|
+ List<LongArticleTitleAudit> auditQueueList = auditQueuePoolCountMap.getOrDefault(poolEnum.getContentPool(), new ArrayList<>());
|
|
|
+ List<LongArticleTitleAudit> inAuditListPool = inAuditListPoolCountMap.getOrDefault(poolEnum.getContentPool(), new ArrayList<>());
|
|
|
+ int needCount = entry.getValue() - (totalCount + auditQueueList.size() + inAuditListPool.size());
|
|
|
+ if (needCount > 0) {
|
|
|
+ List<ArticleVideoAuditListVO> addList = articleAuditMapper.articleVideoWatingAuditList(
|
|
|
+ Arrays.asList(ArticleVideoAuditStatusEnum.WAITING.getCode()),
|
|
|
+ Arrays.asList(poolEnum.getContentPool()), excludeContentIds, needCount);
|
|
|
+ if (CollectionUtils.isNotEmpty(addList)) {
|
|
|
+ for (ArticleVideoAuditListVO item : addList) {
|
|
|
+ redisTemplate.opsForZSet().add(auditQueueRedisKey, item.getContentId(), poolEnum.getWeight());
|
|
|
+ excludeContentIds.add(item.getContentId());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 待发布内容不足添加
|
|
|
+ Long auditQueueSize = redisTemplate.opsForZSet().size(auditQueueRedisKey);
|
|
|
+ if (Objects.isNull(auditQueueSize) || auditQueueSize < 20) {
|
|
|
+ List<ArticleVideoAuditListVO> list = articleAuditMapper.articleVideoWatingAuditList(
|
|
|
+ Arrays.asList(ArticleVideoAuditStatusEnum.WAITING.getCode()), null, excludeContentIds, 40);
|
|
|
+ if (CollectionUtils.isNotEmpty(list)) {
|
|
|
+ for (ArticleVideoAuditListVO item : list) {
|
|
|
+ ContentPoolEnum poolEnum = ContentPoolEnum.from(item.getFlowPoolLevel());
|
|
|
+ redisTemplate.opsForZSet().add(auditQueueRedisKey, item.getContentId(), poolEnum.getWeight());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ redisTemplate.expire(auditQueueRedisKey, 12, TimeUnit.HOURS);
|
|
|
+
|
|
|
+ } finally {
|
|
|
+ redisUtil.releaseLock(lockKey, requestId);
|
|
|
+ }
|
|
|
+ return ReturnT.SUCCESS;
|
|
|
+ }
|
|
|
}
|