feat: 08 数据看板落地(总览 + 转码看板,只读)

为什么:
1) 前面几块把用户、内容、播放、交易、媒资的数据都落库了,运营需要一个地方一次看完,
   否则每个指标都要去查库或翻后台列表。
2) 看板最容易出的问题是"数字看起来对但口径不清",所以每个指标都在代码注释和
   docs/api-phase1.md 里写死口径(时间范围、统计维度),前端 tooltip 直接复用。
3) 取不到数据源的指标(DAU、CDN 流量成本、商店订单金额)一律返回 null 并在 notes 里
   说明缺什么、怎么补 —— 不用 0 冒充,避免运营拿假数字做判断。
4) 站在接入层做只读聚合而不是新建模块:它跨六个域取数,建模块只会把依赖再摊开一层,
   而它只读不写,放在 admin-api 改动面最小。
This commit is contained in:
Vicky committed 2026-10-15 13:03:58 +08:00
1 parent 1fb8427f28
commit d67d9daf8c
5 files changed
+359 -3

No files matched your search

@@ -12,7 +12,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 转码任务编排。
@@ -112,6 +114,37 @@ public class TranscodeService {
.orderByAsc(MediaRendition::getVideoBitrate));
}
/**
* 转码看板数据。
*
* 口径:按 transcode_task.status 全量计数(不含逻辑删除),不限定时间范围 ——
* 队列类指标看的是"当前积压",用累计值才有意义。
* 产物数取自 media_rendition,反映已经生成多少路码率文件。
*/
public Map<String, Object> stats() {
Map<String, Object> data = new LinkedHashMap<>();
data.put("queued", countByStatus(STATUS_QUEUED));
data.put("running", countByStatus(STATUS_RUNNING));
data.put("success", countByStatus(STATUS_SUCCESS));
data.put("failed", countByStatus(STATUS_FAILED));
data.put("canceled", countByStatus("CANCELED"));
data.put("renditions", mediaRenditionMapper.selectCount(null));
return data;
}
/** 失败任务明细:带 error_msg 与 retry_count,便于运营判断是重试还是排查片源。 */
public List<TranscodeTask> failedTasks(int limit) {
return transcodeTaskMapper.selectList(Wrappers.<TranscodeTask>lambdaQuery()
.eq(TranscodeTask::getStatus, STATUS_FAILED)
.orderByDesc(TranscodeTask::getId)
.last("limit " + (limit <= 0 || limit > 100 ? 20 : limit)));
}
private Long countByStatus(String status) {
return transcodeTaskMapper.selectCount(Wrappers.<TranscodeTask>lambdaQuery()
.eq(TranscodeTask::getStatus, status));
}
private TranscodeTask getExisting(Long id) {
TranscodeTask task = id == null ? null : transcodeTaskMapper.selectById(id);
if (task == null) {