feat: 采集失败KOC补交截图闭环 + 任务中心分发详情弹窗
公开数据补交闭环(F1): - distributions 新增 public_metrics_screenshot_key(mysql/0022 迁移) - KOC 门户:链接已回填但采集失败时展示补交数据截图入口(kind=public-metrics) - 运营端:回收队列"核对补录"支持公开互动数据(赞/藏/评),按补交截图补录后采集状态置成功 - 企微催办:采集失败纳入待办(公开数据待补交) - task-result-image 支持 kind=publish 输出发布截图 门户文案(F2): - 自动采集数据明确"由系统每天自动采集,无需你填写",仅识别失败时才手动填写曝光/阅读 任务中心分发详情: - 任务中心/数据回收的分发表行可点击,弹窗查看详情 - 详情按任务类型适配:发布阶段/数据回收(自动采集)/创作者数据(第7天)/截图回收四区块三段式布局
This commit is contained in:
@@ -656,19 +656,62 @@ export async function POST(request: Request) {
|
||||
const forbidden = await distributionOwnerGate(body.distributionId);
|
||||
if (forbidden) return forbidden;
|
||||
const distributionId = String(body.distributionId ?? "");
|
||||
const exposure = Math.max(0, numberValue(body.exposure));
|
||||
const views = Math.max(0, numberValue(body.views));
|
||||
const hasCreatorMetrics =
|
||||
body.exposure !== undefined &&
|
||||
body.exposure !== null &&
|
||||
body.views !== undefined &&
|
||||
body.views !== null;
|
||||
const exposure = hasCreatorMetrics
|
||||
? Math.max(0, numberValue(body.exposure))
|
||||
: null;
|
||||
const views = hasCreatorMetrics ? Math.max(0, numberValue(body.views)) : null;
|
||||
// 采集失败兜底:运营按 KOC 补交的数据截图核对补录公开互动数据
|
||||
const hasPublicMetrics =
|
||||
body.publicLikes !== undefined ||
|
||||
body.publicCollects !== undefined ||
|
||||
body.publicComments !== undefined;
|
||||
const publicLikes = Math.max(0, numberValue(body.publicLikes));
|
||||
const publicCollects = Math.max(0, numberValue(body.publicCollects));
|
||||
const publicComments = Math.max(0, numberValue(body.publicComments));
|
||||
await db
|
||||
.prepare(
|
||||
`UPDATE distributions SET
|
||||
exposure = ?,
|
||||
views = ?,
|
||||
ocr_status = 'manual',
|
||||
exposure = CASE WHEN ? THEN ? ELSE exposure END,
|
||||
views = CASE WHEN ? THEN ? ELSE views END,
|
||||
ocr_status = CASE WHEN ? THEN 'manual' ELSE ocr_status END,
|
||||
status = CASE WHEN exposure IS NOT NULL AND views IS NOT NULL THEN 'complete' ELSE status END,
|
||||
latest_likes = CASE WHEN ? THEN ? ELSE latest_likes END,
|
||||
latest_collects = CASE WHEN ? THEN ? ELSE latest_collects END,
|
||||
latest_comments = CASE WHEN ? THEN ? ELSE latest_comments END,
|
||||
collection_status = CASE WHEN ? THEN 'success' ELSE collection_status END,
|
||||
collection_status_description = CASE
|
||||
WHEN ? THEN '运营已按补交截图核对补录'
|
||||
ELSE collection_status_description
|
||||
END,
|
||||
collection_updated_at = CASE
|
||||
WHEN ? THEN CURRENT_TIMESTAMP
|
||||
ELSE collection_updated_at
|
||||
END,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?`,
|
||||
)
|
||||
.bind(exposure, views, distributionId)
|
||||
.bind(
|
||||
hasCreatorMetrics ? 1 : 0,
|
||||
exposure,
|
||||
hasCreatorMetrics ? 1 : 0,
|
||||
views,
|
||||
hasCreatorMetrics ? 1 : 0,
|
||||
hasPublicMetrics ? 1 : 0,
|
||||
publicLikes,
|
||||
hasPublicMetrics ? 1 : 0,
|
||||
publicCollects,
|
||||
hasPublicMetrics ? 1 : 0,
|
||||
publicComments,
|
||||
hasPublicMetrics ? 1 : 0,
|
||||
hasPublicMetrics ? 1 : 0,
|
||||
hasPublicMetrics ? 1 : 0,
|
||||
distributionId,
|
||||
)
|
||||
.run();
|
||||
const ownerTask = await db
|
||||
.prepare("SELECT task_id FROM distributions WHERE id = ?")
|
||||
|
||||
@@ -10,20 +10,33 @@ export async function GET(request: Request) {
|
||||
const isAdmin = await isAdminRequest(request);
|
||||
try {
|
||||
await ensureSchema();
|
||||
const distributionId = new URL(request.url).searchParams
|
||||
.get("distribution")
|
||||
?.trim();
|
||||
const url = new URL(request.url);
|
||||
const distributionId = url.searchParams.get("distribution")?.trim();
|
||||
// kind=public-metrics 时返回 KOC 补交的公开数据截图(仅后台核对用)
|
||||
const imageKind = url.searchParams.get("kind") === "public-metrics"
|
||||
? "public-metrics"
|
||||
: "creator";
|
||||
if (!distributionId) {
|
||||
return Response.json({ error: "缺少分发记录" }, { status: 400 });
|
||||
}
|
||||
const row = await getRawDb()
|
||||
.prepare(
|
||||
`SELECT screenshot_key FROM distributions
|
||||
`SELECT screenshot_key, public_metrics_screenshot_key FROM distributions
|
||||
WHERE id = ?`,
|
||||
)
|
||||
.bind(distributionId)
|
||||
.first<{ screenshot_key: string | null }>();
|
||||
const mcpToken = new URL(request.url).searchParams.get("mcp_token") || "";
|
||||
.first<{
|
||||
screenshot_key: string | null;
|
||||
public_metrics_screenshot_key: string | null;
|
||||
}>();
|
||||
const mcpToken = url.searchParams.get("mcp_token") || "";
|
||||
const key =
|
||||
imageKind === "public-metrics"
|
||||
? row?.public_metrics_screenshot_key ?? null
|
||||
: row?.screenshot_key ?? null;
|
||||
if (imageKind === "public-metrics" && !isAdmin) {
|
||||
return adminForbidden();
|
||||
}
|
||||
if (
|
||||
!isAdmin &&
|
||||
(!row?.screenshot_key ||
|
||||
@@ -36,18 +49,20 @@ export async function GET(request: Request) {
|
||||
return adminForbidden();
|
||||
}
|
||||
if (
|
||||
!row?.screenshot_key ||
|
||||
!row.screenshot_key.startsWith("creator-center/")
|
||||
!key ||
|
||||
!key.startsWith(
|
||||
imageKind === "public-metrics" ? "public-metrics/" : "creator-center/",
|
||||
)
|
||||
) {
|
||||
return Response.json({ error: "创作者截图不存在" }, { status: 404 });
|
||||
return Response.json({ error: "截图不存在" }, { status: 404 });
|
||||
}
|
||||
const object = await getUploadBucket().get(row.screenshot_key);
|
||||
const object = await getUploadBucket().get(key);
|
||||
if (!object) {
|
||||
return Response.json({ error: "创作者截图不存在" }, { status: 404 });
|
||||
return Response.json({ error: "截图不存在" }, { status: 404 });
|
||||
}
|
||||
const headers = new Headers({
|
||||
"Cache-Control": "private, no-store",
|
||||
"Content-Disposition": 'inline; filename="creator-center-screenshot"',
|
||||
"Content-Disposition": `inline; filename="${imageKind}-screenshot"`,
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
});
|
||||
object.writeHttpMetadata(headers);
|
||||
|
||||
@@ -76,6 +76,7 @@ async function handleGet(request: Request) {
|
||||
d.result_screenshot_key,
|
||||
d.publish_screenshot_key,
|
||||
d.screenshot_key,
|
||||
d.public_metrics_screenshot_key,
|
||||
t.recovery_example_images
|
||||
FROM distributions d
|
||||
JOIN contents c ON c.id = d.content_id
|
||||
@@ -92,6 +93,7 @@ async function handleGet(request: Request) {
|
||||
result_screenshot_key: string | null;
|
||||
publish_screenshot_key: string | null;
|
||||
screenshot_key: string | null;
|
||||
public_metrics_screenshot_key: string | null;
|
||||
recovery_example_images: string;
|
||||
}>()
|
||||
: await getRawDb()
|
||||
@@ -101,6 +103,7 @@ async function handleGet(request: Request) {
|
||||
d.result_screenshot_key,
|
||||
d.publish_screenshot_key,
|
||||
d.screenshot_key,
|
||||
d.public_metrics_screenshot_key,
|
||||
t.recovery_example_images
|
||||
FROM distributions d
|
||||
JOIN contents c ON c.id = d.content_id
|
||||
@@ -118,6 +121,7 @@ async function handleGet(request: Request) {
|
||||
result_screenshot_key: string | null;
|
||||
publish_screenshot_key: string | null;
|
||||
screenshot_key: string | null;
|
||||
public_metrics_screenshot_key: string | null;
|
||||
recovery_example_images: string;
|
||||
}>();
|
||||
const asset =
|
||||
@@ -142,6 +146,10 @@ async function handleGet(request: Request) {
|
||||
? row?.screenshot_key?.startsWith("creator-center/")
|
||||
? { index: 1, key: row.screenshot_key }
|
||||
: undefined
|
||||
: imageKind === "public-metrics"
|
||||
? row?.public_metrics_screenshot_key?.startsWith("public-metrics/")
|
||||
? { index: 1, key: row.public_metrics_screenshot_key }
|
||||
: undefined
|
||||
: imageKind === "video"
|
||||
? row
|
||||
? findAsset(row.video_assets, imageIndex, ["content-videos/"])
|
||||
@@ -196,7 +204,9 @@ async function handleGet(request: Request) {
|
||||
const headers = new Headers();
|
||||
object.writeHttpMetadata(headers);
|
||||
const isMutableEvidence =
|
||||
imageKind === "publish" || imageKind === "creator";
|
||||
imageKind === "publish" ||
|
||||
imageKind === "creator" ||
|
||||
imageKind === "public-metrics";
|
||||
headers.set(
|
||||
"Cache-Control",
|
||||
isMutableEvidence ? "private, no-store" : "private, max-age=3600",
|
||||
|
||||
@@ -87,7 +87,12 @@ async function handlePost(request: Request) {
|
||||
}
|
||||
const isCreatorCenter = upload.uploadKind === "creator-center";
|
||||
const isTaskResult = upload.uploadKind === "task-result";
|
||||
if (!["publish", "creator-center", "task-result"].includes(upload.uploadKind)) {
|
||||
const isPublicMetrics = upload.uploadKind === "public-metrics";
|
||||
if (
|
||||
!["publish", "creator-center", "task-result", "public-metrics"].includes(
|
||||
upload.uploadKind,
|
||||
)
|
||||
) {
|
||||
return Response.json({ error: "不支持的截图类型" }, { status: 400 });
|
||||
}
|
||||
const assignment = upload.delegationToken
|
||||
@@ -119,7 +124,7 @@ async function handlePost(request: Request) {
|
||||
if (!assignment) {
|
||||
return Response.json({ error: "笔记与领取凭证不匹配" }, { status: 403 });
|
||||
}
|
||||
if (isCreatorCenter && !assignment.publish_url) {
|
||||
if ((isCreatorCenter || isPublicMetrics) && !assignment.publish_url) {
|
||||
return Response.json(
|
||||
{ error: "请先回填这篇笔记的发布信息" },
|
||||
{ status: 409 },
|
||||
@@ -143,6 +148,8 @@ async function handlePost(request: Request) {
|
||||
? `task-results/${upload.distributionId}/${uid("shot")}.${extension}`
|
||||
: isCreatorCenter
|
||||
? `creator-center/${upload.distributionId}/${uid("shot")}.${extension}`
|
||||
: isPublicMetrics
|
||||
? `public-metrics/${upload.distributionId}/${uid("shot")}.${extension}`
|
||||
: `publish-evidence/${upload.distributionId}/${uid("shot")}.${extension}`;
|
||||
await getUploadBucket().put(key, upload.fileBytes, {
|
||||
httpMetadata: { contentType: upload.fileType },
|
||||
@@ -160,6 +167,18 @@ async function handlePost(request: Request) {
|
||||
upload.distributionId,
|
||||
)
|
||||
.run();
|
||||
} else if (isPublicMetrics) {
|
||||
// 公开数据自动采集失败时的兜底凭证:运营据此核对补录互动数据
|
||||
await getRawDb()
|
||||
.prepare(
|
||||
`UPDATE distributions SET
|
||||
public_metrics_screenshot_key = ?,
|
||||
collection_status_description = 'KOC已补交数据截图,待运营核对',
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?`,
|
||||
)
|
||||
.bind(key, upload.distributionId)
|
||||
.run();
|
||||
} else if (isCreatorCenter) {
|
||||
await getRawDb()
|
||||
.prepare(
|
||||
@@ -226,7 +245,9 @@ async function handlePost(request: Request) {
|
||||
? "task-result"
|
||||
: isCreatorCenter
|
||||
? "creator-center"
|
||||
: "publish",
|
||||
: isPublicMetrics
|
||||
? "public-metrics"
|
||||
: "publish",
|
||||
ocrStatus: isCreatorCenter ? "failed" : undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -326,6 +326,12 @@ async function handleGet(request: Request) {
|
||||
d.ocr_status,
|
||||
d.exposure,
|
||||
d.views,
|
||||
d.collection_status,
|
||||
d.latest_likes,
|
||||
d.latest_comments,
|
||||
d.latest_collects,
|
||||
d.collection_updated_at,
|
||||
d.public_metrics_screenshot_key,
|
||||
c.title,
|
||||
c.body,
|
||||
c.source_row,
|
||||
@@ -400,6 +406,12 @@ async function handleGet(request: Request) {
|
||||
d.ocr_status,
|
||||
d.exposure,
|
||||
d.views,
|
||||
d.collection_status,
|
||||
d.latest_likes,
|
||||
d.latest_comments,
|
||||
d.latest_collects,
|
||||
d.collection_updated_at,
|
||||
d.public_metrics_screenshot_key,
|
||||
c.title,
|
||||
c.body,
|
||||
c.source_row,
|
||||
|
||||
@@ -7,9 +7,8 @@ export async function GET(request: Request) {
|
||||
if (!(await isAdminRequest(request))) return adminForbidden();
|
||||
try {
|
||||
await ensureSchema();
|
||||
const distributionId = new URL(request.url).searchParams
|
||||
.get("distribution")
|
||||
?.trim();
|
||||
const url = new URL(request.url);
|
||||
const distributionId = url.searchParams.get("distribution")?.trim();
|
||||
const scope = await getOwnerScope(request);
|
||||
if (
|
||||
scope.kind === "owner" &&
|
||||
@@ -17,23 +16,31 @@ export async function GET(request: Request) {
|
||||
) {
|
||||
return ownerForbidden();
|
||||
}
|
||||
// kind=publish 时返回 KOC 上传的发布截图(分发详情页核对用)
|
||||
const imageKind = url.searchParams.get("kind") === "publish" ? "publish" : "result";
|
||||
const imageIndex = Math.max(
|
||||
1,
|
||||
Number(new URL(request.url).searchParams.get("index") || 1),
|
||||
Number(url.searchParams.get("index") || 1),
|
||||
);
|
||||
if (!distributionId) {
|
||||
return Response.json({ error: "缺少任务记录" }, { status: 400 });
|
||||
}
|
||||
const row = await getRawDb()
|
||||
.prepare(
|
||||
`SELECT result_screenshot_key FROM distributions
|
||||
`SELECT result_screenshot_key, publish_screenshot_key FROM distributions
|
||||
WHERE id = ?`,
|
||||
)
|
||||
.bind(distributionId)
|
||||
.first<{ result_screenshot_key: string | null }>();
|
||||
const screenshotKey = row
|
||||
? parseResultScreenshotKeys(row.result_screenshot_key)[imageIndex - 1]
|
||||
: undefined;
|
||||
.first<{
|
||||
result_screenshot_key: string | null;
|
||||
publish_screenshot_key: string | null;
|
||||
}>();
|
||||
const screenshotKey =
|
||||
imageKind === "publish"
|
||||
? row?.publish_screenshot_key ?? null
|
||||
: row
|
||||
? parseResultScreenshotKeys(row.result_screenshot_key)[imageIndex - 1]
|
||||
: undefined;
|
||||
if (!screenshotKey) {
|
||||
return Response.json({ error: "任务截图不存在" }, { status: 404 });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user