|
@@ -0,0 +1,301 @@
|
|
|
+package com.tzld.longarticle.recommend.server.service;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.CollectionUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
+import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
|
|
|
+import com.tzld.longarticle.recommend.server.model.NewSortStrategyExport;
|
|
|
+import com.tzld.longarticle.recommend.server.repository.crawler.AccountAvgInfoRepository;
|
|
|
+import com.tzld.longarticle.recommend.server.repository.crawler.ArticleDetailInfoRepository;
|
|
|
+import com.tzld.longarticle.recommend.server.repository.crawler.ArticleRepository;
|
|
|
+import com.tzld.longarticle.recommend.server.repository.crawler.PublishSortLogRepository;
|
|
|
+import com.tzld.longarticle.recommend.server.repository.entity.crawler.AccountAvgInfo;
|
|
|
+import com.tzld.longarticle.recommend.server.repository.entity.crawler.Article;
|
|
|
+import com.tzld.longarticle.recommend.server.repository.entity.crawler.ArticleDetailInfo;
|
|
|
+import com.tzld.longarticle.recommend.server.repository.entity.crawler.PublishSortLog;
|
|
|
+import com.tzld.longarticle.recommend.server.util.DateUtils;
|
|
|
+import com.tzld.longarticle.recommend.server.util.MapBuilder;
|
|
|
+import com.tzld.longarticle.recommend.server.util.feishu.FeiShu;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.util.Pair;
|
|
|
+import org.springframework.http.*;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.util.StringUtils;
|
|
|
+import org.springframework.web.client.RestTemplate;
|
|
|
+
|
|
|
+import java.lang.reflect.Field;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Service
|
|
|
+@Slf4j
|
|
|
+public class DataDashboardService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ArticleRepository articleRepository;
|
|
|
+ @Autowired
|
|
|
+ private ArticleDetailInfoRepository articleDetailInfoRepository;
|
|
|
+ @Autowired
|
|
|
+ private AccountAvgInfoRepository accountAvgInfoRepository;
|
|
|
+ @Autowired
|
|
|
+ private PublishSortLogRepository publishSortLogRepository;
|
|
|
+
|
|
|
+ @ApolloJsonValue("${export.account.ghId:[]}")
|
|
|
+ private static List<String> ghIdList;
|
|
|
+ private static final String sheetToken = "M0pLs3uF6hfL0htn2dMcB9eFn8e";
|
|
|
+
|
|
|
+ public void export(String dateStr) {
|
|
|
+ List<String> dateStrList = getBeforeThreeDays(dateStr);
|
|
|
+ exportFeishuNewSortStrategy(dateStrList, sheetToken, "7d4e12");
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<String> getBeforeThreeDays(String dateStr) {
|
|
|
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
|
|
|
+ // 获取今天的日期
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
+ if (!StringUtils.hasText(dateStr)) {
|
|
|
+ dateStr = today.format(formatter);
|
|
|
+ }
|
|
|
+ // 解析输入的日期字符串为LocalDate对象
|
|
|
+ LocalDate date = LocalDate.parse(dateStr, formatter);
|
|
|
+ // 获取从输入日期前三天的日期
|
|
|
+ LocalDate startDate = date.minusDays(3);
|
|
|
+ // 存储所有日期的列表
|
|
|
+ List<String> datesList = new ArrayList<>();
|
|
|
+ // 从startDate到today遍历日期
|
|
|
+ while (!startDate.isAfter(today)) {
|
|
|
+ // 将当前日期格式化为"yyyyMMdd"并添加到列表中
|
|
|
+ datesList.add(startDate.format(formatter));
|
|
|
+ // 日期加1天
|
|
|
+ startDate = startDate.plusDays(1);
|
|
|
+ }
|
|
|
+ return datesList;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void exportFeishuNewSortStrategy(List<String> dateStrList, String sheetToken, String sheetId) {
|
|
|
+ String minDate = dateStrList.stream().min(String::compareTo).orElse("");
|
|
|
+ List<NewSortStrategyExport> newContentsYesData = newSortStrageData(minDate);
|
|
|
+ if (CollectionUtil.isEmpty(newContentsYesData)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ int rowNum = newContentsYesData.size();
|
|
|
+ List<List<Object>> rows = new ArrayList<>();
|
|
|
+ Field[] fields = NewSortStrategyExport.class.getDeclaredFields();
|
|
|
+ for (NewSortStrategyExport datum : newContentsYesData) {
|
|
|
+ List<Object> rowDatas = new ArrayList<>();
|
|
|
+ rows.add(rowDatas);
|
|
|
+
|
|
|
+ for (Field field : fields) {
|
|
|
+ field.setAccessible(true);
|
|
|
+ try {
|
|
|
+ rowDatas.add(field.get(datum));
|
|
|
+ } catch (IllegalAccessException e) {
|
|
|
+ log.error("获取值出错:{}", field.getName());
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Pair<String, String>> styles = Arrays
|
|
|
+ .asList(
|
|
|
+ Pair.of("K", "0.00%"),
|
|
|
+ Pair.of("L", "0.00%"),
|
|
|
+ Pair.of("M", "0.00%"),
|
|
|
+ Pair.of("N", "0.00%"),
|
|
|
+ Pair.of("O", "0.00%")
|
|
|
+ );
|
|
|
+
|
|
|
+ doSendFeishuSheet(dateStrList, sheetToken, sheetId, rowNum, rows, 2, styles);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<NewSortStrategyExport> newSortStrageData(String dateStr) {
|
|
|
+ long timestamp = DateUtils.dateStrToTimestamp(dateStr, "yyyyMMdd");
|
|
|
+ List<AccountAvgInfo> accountAvgInfoList = accountAvgInfoRepository.getAllByStatusEquals(1);
|
|
|
+ Set<String> ghIds = accountAvgInfoList.stream().map(AccountAvgInfo::getGhId).collect(Collectors.toSet());
|
|
|
+ List<Article> articleList = articleRepository.getByGhIdInAndUpdateTimeGreaterThan(ghIds, timestamp);
|
|
|
+
|
|
|
+ Set<String> snList = articleList.stream().map(Article::getWxSn).collect(Collectors.toSet());
|
|
|
+ List<ArticleDetailInfo> articleDetailInfoList = articleDetailInfoRepository.getAllByWxSnIn(snList);
|
|
|
+ Map<String, List<ArticleDetailInfo>> articleDetailInfoMap = articleDetailInfoList.stream()
|
|
|
+ .collect(Collectors.groupingBy(ArticleDetailInfo::getWxSn));
|
|
|
+ List<PublishSortLog> sortLogList = publishSortLogRepository.findByGhIdInAndDateStrGreaterThanEqual(ghIds, dateStr);
|
|
|
+ Map<String, Map<String, String>> sortStrategyMap = sortLogList.stream()
|
|
|
+ .collect(Collectors.groupingBy(PublishSortLog::getGhId,
|
|
|
+ Collectors.toMap(PublishSortLog::getDateStr, PublishSortLog::getStrategy, (existing, replacement) -> replacement)));
|
|
|
+
|
|
|
+ Map<String, Map<String, AccountAvgInfo>> accountAvgInfoIndexMap = accountAvgInfoList.stream().collect(
|
|
|
+ Collectors.groupingBy(AccountAvgInfo::getGhId, Collectors.toMap(AccountAvgInfo::getPosition, o -> o)));
|
|
|
+ List<NewSortStrategyExport> result = new ArrayList<>();
|
|
|
+ for (Article article : articleList) {
|
|
|
+ List<ArticleDetailInfo> articleDetailInfos = articleDetailInfoMap.get(article.getWxSn());
|
|
|
+ if (CollectionUtils.isEmpty(articleDetailInfos)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Map<String, String> dateStrategy = sortStrategyMap.get(article.getGhId());
|
|
|
+ Date minDate = articleDetailInfos.stream().map(ArticleDetailInfo::getRecallDt).min(Date::compareTo).orElse(new Date());
|
|
|
+ int sumfirstLevel = 0;
|
|
|
+ int sumFission0 = 0;
|
|
|
+ int sumFission1 = 0;
|
|
|
+ int sumFission2 = 0;
|
|
|
+ for (ArticleDetailInfo articleDetailInfo : articleDetailInfos) {
|
|
|
+ if (articleDetailInfo.getRecallDt().equals(minDate)) {
|
|
|
+ sumfirstLevel += Optional.ofNullable(articleDetailInfo.getFirstLevel()).orElse(0);
|
|
|
+ sumFission0 += Optional.ofNullable(articleDetailInfo.getFission0()).orElse(0);
|
|
|
+ sumFission1 += Optional.ofNullable(articleDetailInfo.getFission1()).orElse(0);
|
|
|
+ sumFission2 += Optional.ofNullable(articleDetailInfo.getFission2()).orElse(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Map<String, AccountAvgInfo> accountAvgInfoMap = accountAvgInfoIndexMap.get(article.getGhId());
|
|
|
+ AccountAvgInfo avgInfo = accountAvgInfoMap.get(article.getItemIndex().toString());
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
|
|
+ String date = sdf.format(new Date(article.getUpdateTime() * 1000));
|
|
|
+ NewSortStrategyExport obj = new NewSortStrategyExport();
|
|
|
+ obj.setGhId(article.getGhId());
|
|
|
+ obj.setAccountName(article.getAccountName());
|
|
|
+ obj.setTitle(article.getTitle());
|
|
|
+ obj.setLink(article.getContentUrl());
|
|
|
+ obj.setPosition(article.getItemIndex());
|
|
|
+ obj.setViewCount(article.getShowViewCount());
|
|
|
+ obj.setDateStr(date);
|
|
|
+ obj.setStrategy(dateStrategy.get(date));
|
|
|
+ if (Objects.nonNull(avgInfo)) {
|
|
|
+ obj.setFans(avgInfo.getFans());
|
|
|
+ obj.setAvgViewCount(avgInfo.getReadAvg());
|
|
|
+ if (avgInfo.getReadAvg() > 0) {
|
|
|
+ obj.setReadRate((article.getShowViewCount() * 1.0) / avgInfo.getReadAvg());
|
|
|
+ obj.setFission0ReadAvgRate((sumFission0 * 1.0) / avgInfo.getReadAvg());
|
|
|
+ }
|
|
|
+ if (avgInfo.getFans() > 0) {
|
|
|
+ obj.setReadFansRate((article.getShowViewCount() * 1.0) / avgInfo.getFans());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ obj.setFirstLevel(sumfirstLevel);
|
|
|
+ obj.setFission0(sumFission0);
|
|
|
+ obj.setFission1(sumFission1);
|
|
|
+ obj.setFission2(sumFission2);
|
|
|
+ if (article.getShowViewCount() > 0) {
|
|
|
+ obj.setFirstReadRate((sumfirstLevel * 1.0) / article.getShowViewCount());
|
|
|
+ }
|
|
|
+ if (sumFission0 > 0) {
|
|
|
+ obj.setFission1Fission0Rate((sumFission1 * 1.0) / sumFission0);
|
|
|
+ }
|
|
|
+ result.add(obj);
|
|
|
+ }
|
|
|
+ result.sort(Comparator.comparing(NewSortStrategyExport::getDateStr).reversed()
|
|
|
+ .thenComparing(NewSortStrategyExport::getGhId).thenComparing(NewSortStrategyExport::getPosition));
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private static void doSendFeishuSheet(List<String> dateStrList, String sheetToken, String sheetId,
|
|
|
+ int rowNum, List<List<Object>> rows, Integer startRowIndex,
|
|
|
+ List<Pair<String, String>> styles) {
|
|
|
+ Pair<String, Integer> token = FeiShu.requestAccessToken();
|
|
|
+ RestTemplate restTemplate = new RestTemplate();
|
|
|
+ HttpHeaders httpHeaders = new HttpHeaders();
|
|
|
+ httpHeaders.add("Authorization", "Bearer " + token.getFirst());
|
|
|
+
|
|
|
+ // 先删除掉已存在的dateStr数据
|
|
|
+ HttpEntity<Object> queryEntity = new HttpEntity<>(httpHeaders);
|
|
|
+ ResponseEntity<String> queryResponseEntity = restTemplate.exchange(
|
|
|
+ String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/values/%s!A" + startRowIndex + ":A" + (startRowIndex + (rowNum * 2)),
|
|
|
+ sheetToken, sheetId),
|
|
|
+ HttpMethod.GET, queryEntity, String.class);
|
|
|
+ JSONArray values = JSON.parseObject(queryResponseEntity.getBody())
|
|
|
+ .getJSONObject("data")
|
|
|
+ .getJSONObject("valueRange")
|
|
|
+ .getJSONArray("values");
|
|
|
+ int count = 0;
|
|
|
+ if (!values.isEmpty() && Objects.nonNull(values.get(0))
|
|
|
+ && dateStrList.contains(((JSONArray) values.get(0)).get(0).toString())) {
|
|
|
+ for (Object value : values) {
|
|
|
+ if (((JSONArray) value).get(0) != null) {
|
|
|
+ List<String> dates = ((JSONArray) value).stream().map(Object::toString).collect(Collectors.toList());
|
|
|
+ if (dateStrList.contains(dates.get(0))) {
|
|
|
+ count++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (count > 0) {
|
|
|
+ // 删除当前日期已存在的旧数据
|
|
|
+ httpHeaders.setContentType(MediaType.APPLICATION_JSON);
|
|
|
+ HttpEntity<Object> deleteEntity = new HttpEntity<>(
|
|
|
+ String.format("{\n" +
|
|
|
+ " \"dimension\": {\n" +
|
|
|
+ " \"sheetId\": \"%s\",\n" +
|
|
|
+ " \"majorDimension\": \"ROWS\",\n" +
|
|
|
+ " \"startIndex\": %s,\n" +
|
|
|
+ " \"endIndex\": %s\n" +
|
|
|
+ " }\n" +
|
|
|
+ "}", sheetId, startRowIndex, count + startRowIndex - 1),
|
|
|
+ httpHeaders);
|
|
|
+ restTemplate.exchange(String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/dimension_range", sheetToken),
|
|
|
+ HttpMethod.DELETE, deleteEntity, String.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 插入数据
|
|
|
+ HttpEntity<Object> postEntity = new HttpEntity<>(MapBuilder
|
|
|
+ .builder()
|
|
|
+ .put("valueRange", MapBuilder
|
|
|
+ .builder()
|
|
|
+ .put("range", String.format("%s!A" + startRowIndex + ":S", sheetId) + (rowNum + startRowIndex - 1))
|
|
|
+ .put("values", rows)
|
|
|
+ .build())
|
|
|
+ .build(), httpHeaders);
|
|
|
+ restTemplate.exchange(String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/values_prepend",
|
|
|
+ sheetToken),
|
|
|
+ HttpMethod.POST, postEntity, String.class);
|
|
|
+// // 合并单元格
|
|
|
+// if (isMerge) {
|
|
|
+// HttpEntity<Object> mergeEntity = new HttpEntity<>(MapBuilder
|
|
|
+// .builder()
|
|
|
+// .put("range", String.format("%s!A" + startRowIndex + ":A", sheetId) + (rowNum + startRowIndex - 1))
|
|
|
+// .put("mergeType", "MERGE_ALL")
|
|
|
+// .build(), httpHeaders);
|
|
|
+// restTemplate.exchange(String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/merge_cells",
|
|
|
+// sheetToken),
|
|
|
+// HttpMethod.POST, mergeEntity, String.class);
|
|
|
+// }
|
|
|
+ // 此处先简单处理,调整单元格为”百分比小数点“
|
|
|
+ if (CollectionUtil.isNotEmpty(styles)) {
|
|
|
+ for (Pair<String, String> style : styles) {
|
|
|
+ HttpEntity<Map<Object, Object>> styleEntity = new HttpEntity<>(MapBuilder
|
|
|
+ .builder()
|
|
|
+ .put("appendStyle",
|
|
|
+ MapBuilder
|
|
|
+ .builder()
|
|
|
+ .put("range", String.format("%s!%s" + startRowIndex + ":%s", sheetId,
|
|
|
+ style.getFirst(), style.getFirst())
|
|
|
+ + (rowNum + startRowIndex - 1))
|
|
|
+ .put("style",
|
|
|
+ MapBuilder
|
|
|
+ .builder()
|
|
|
+ .put("formatter", style.getSecond())
|
|
|
+ .build()
|
|
|
+ )
|
|
|
+ .build()
|
|
|
+ )
|
|
|
+ .build(), httpHeaders);
|
|
|
+ restTemplate.exchange(
|
|
|
+ String.format("https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/%s/style",
|
|
|
+ sheetToken),
|
|
|
+ HttpMethod.PUT,
|
|
|
+ styleEntity,
|
|
|
+ String.class
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|