14 KiB
14 KiB
云图报告自动创建 PRD
一、项目基本信息
- 项目路径:
/Users/wxs/projects/browser_script/scriptCat/yuntu/yuntuReportFilling - 项目类型:ScriptCat 浏览器脚本 + Node.js 后端服务 + PostgreSQL
- 业务目标:在云图页面中复用最近一次成功创建报告的请求参数,自动创建一份同比报告,并将关键数据持久化到数据库
- 核心价值:
- 降低人工重复创建报告的成本
- 自动将时间范围回退一年,方便做同比分析
- 为后续追溯和排查保留完整 payload 与 report_id
二、业务背景
当前用户在云图页面手动创建报告后,还需要再次输入或复制相同配置,才能创建同比报告。该过程重复且容易出错。
本项目通过监听用户首次手动创建报告时的请求与响应,缓存最近一次成功的 payload;当用户点击右侧按钮后,脚本自动将顶层 startTime 与 endTime 各回退一年,并再次调用云图创建接口完成报告复制。
三、范围说明
本期范围
- 监听并识别云图“创建报告”接口
- 记录最近一次成功创建报告的完整 payload
- 在页面右侧提供“一键复制同比报告”按钮
- 自动回退顶层
startTime/endTime一年 - 调用后端 API 持久化成功创建的报告数据
- 将手动创建报告和脚本自动创建报告统一写入 PostgreSQL
本期不做
- 不从数据库反向同步最新 payload 到浏览器本地
- 不支持多条历史 payload 管理
- 不支持用户手动编辑复制前的字段
- 不修改除顶层
startTime/endTime外的其他字段
四、目标页面与第三方接口
4.1 目标页面
- 页面域名:
https://yuntu.oceanengine.com - 目标页面路径:
/yuntu_brand/ecom/product/segmentedMarketcreation - 页面示例:
https://yuntu.oceanengine.com/yuntu_brand/ecom/product/segmentedMarketcreation?aadvid=1648829117232140
4.2 需监听与复用的云图接口
- 请求方法:
POST - 接口路径:
/product_node/v2/api/segmentedMarket/createSegmentedMarket - 完整示例:
https://yuntu.oceanengine.com/product_node/v2/api/segmentedMarket/createSegmentedMarket?aadvid=1648829117232140
- 关键识别条件:
- 请求方法必须为
POST - 路径必须匹配
createSegmentedMarket - 查询参数中包含
aadvid
- 请求方法必须为
4.3 第三方接口约束
- 浏览器脚本调用该接口时,必须复用当前用户浏览器会话中的登录态
- 不允许在脚本或后端中硬编码 Cookie、CSRF Token 等敏感信息
- 只允许浏览器脚本直接调用云图接口;后端不直接代调云图接口
- 接口成功后,从响应体
data.reportId中提取业务report_id
五、核心流程
5.1 手动创建报告并记录
触发时机:用户在页面中手动点击“创建报告”并且云图接口返回成功。
执行流程:
- 监听
createSegmentedMarket请求与响应。 - 提取请求 payload 与请求 URL 中的
aadvid。 - 校验响应成功,且响应体存在
data.reportId。 - 将以下内容写入浏览器本地:
lastReportPayload:最近一次成功创建报告的完整 payloadlastReportMeta:最近一次成功创建报告的元信息,至少包含reportId、aadvid、sourceType、capturedAt
- 如果本地写入失败,立即报错,并保持按钮不可用状态;本次不调用后端持久化接口。
- 如果本地写入成功,调用后端 API 将本次手动创建报告写入数据库。
5.2 一键复制同比报告
触发时机:用户点击页面右侧“一键复制同比报告”按钮。
执行流程:
- 读取
lastReportPayload与lastReportMeta。 - 若本地不存在有效 payload,则按钮保持置灰,不允许点击。
- 深拷贝 payload,仅修改顶层
startTime和endTime。 - 使用修改后的 payload 调用云图创建接口。
- 接口成功后,从响应体中读取新的
data.reportId。 - 先将新的 payload 和元信息覆盖写入本地存储。
- 本地写入成功后,调用后端 API 持久化本次自动复制创建的报告数据。
- 持久化成功后,给出成功提示,并显示新的
report_id。
失败处理:
- 云图接口调用失败:提示“同比报告创建失败”,保留旧的本地 payload,不调用后端 API。
- 本地覆盖写入失败:提示“本地缓存更新失败”,不调用后端 API,按钮恢复可点击。
- 后端持久化失败:提示“报告已创建成功,但数据库记录失败”,保留新的本地 payload,按钮恢复可点击。
六、时间处理规则
6.1 处理目标
将顶层 startTime 与 endTime 各回退一个自然年,用于生成同比报告。
6.2 处理原则
- 只处理 payload 顶层字段:
startTimeendTime
- 其他字段保持原值不变
- 日期格式统一为
YYYY-MM-DD - 使用“自然年回退”规则,而不是简单减去 365 天
6.3 具体算法
- 将
startTime、endTime解析为日期对象。 - 分别将年份减 1,尽量保持原始月与日不变。
- 如果目标年份不存在该日期,则回退到目标月份最后一天。
示例:
// 正常日期
2025-03-01 -> 2024-03-01
2026-02-28 -> 2025-02-28
// 闰年特殊情况
2024-02-29 -> 2023-02-28
该规则可以最大限度保持同比区间的自然日语义。
七、页面交互设计
7.1 按钮位置
- 按钮固定显示在页面右侧中部
- 文案建议:
一键复制同比报告
7.2 按钮状态
- 默认状态:
- 当本地存在有效
lastReportPayload时可点击 - 当本地不存在有效
lastReportPayload时置灰且不可点击
- 当本地存在有效
- 加载状态:
- 点击后立即进入 loading
- loading 期间禁止再次点击,防止重复创建
- 完成状态:
- 成功后 toast 提示创建成功,并展示新
report_id - 成功后不自动跳转页面,保持用户留在当前页面,避免打断连续操作
- 成功后 toast 提示创建成功,并展示新
7.3 状态恢复规则
- 用户再次手动成功创建报告后,按钮状态根据本地缓存重新计算
- 任何失败场景都需要正确退出 loading 状态
八、浏览器本地存储设计
8.1 存储方式
- 使用
GM_setValue/GM_getValue
8.2 存储键
lastReportPayloadlastReportMeta
8.3 存储内容
lastReportPayload:
{
"endTime": "2026-02-28",
"name": "测试",
"periodType": "MONTH",
"startTime": "2025-03-01"
}
lastReportMeta:
{
"reportId": "123456789",
"aadvid": "1648829117232140",
"sourceType": "MANUAL_CAPTURE",
"capturedAt": "2026-03-31T15:00:00+08:00"
}
8.4 更新规则
- 每次成功创建报告后覆盖写入
- 始终只保留最近一次成功创建报告的数据
- 脚本启动时不从数据库同步数据
九、后端 API 设计
后端职责仅为持久化成功创建的报告数据,不直接调用云图接口。
9.1 保存报告记录
- 方法:
POST - 路径:
/api/reports - 用途:保存一条已创建成功的报告记录
请求体:
{
"reportId": "123456789",
"sourceType": "AUTO_COPY",
"sourceReportId": "987654321",
"aadvid": "1648829117232140",
"name": "测试",
"price": ["1,100", "101,100000"],
"rules": [
{
"keywords": ["奶粉"],
"op": "INCLUDE"
}
],
"analysisDims": [
"MARKETOVERVIEW",
"PRODUCTINSIGHT",
"MARKETCOMPOSING",
"CONSUMERINSIGHT"
],
"categories": [],
"channels": ["ALL"],
"startTime": "2024-03-01",
"endTime": "2025-02-28",
"periodType": "MONTH",
"userName": "qichumiaosi@163.com",
"payload": {}
}
字段说明:
reportId:云图响应中的data.reportIdsourceType:取值为MANUAL_CAPTURE或AUTO_COPYsourceReportId:MANUAL_CAPTURE时可为空AUTO_COPY时必填,表示复制来源报告 ID
aadvid:从页面 URL 或请求 URL 中提取payload:完整请求体,原样保存
成功响应:
{
"success": true,
"data": {
"id": 1,
"reportId": "123456789"
}
}
失败响应:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "sourceReportId is required when sourceType is AUTO_COPY"
}
}
9.2 后端校验规则
reportId必填sourceType必须为MANUAL_CAPTURE或AUTO_COPYAUTO_COPY场景下sourceReportId必填payload必须为合法 JSONstartTime、endTime必须符合YYYY-MM-DD- 数据库以
report_id做唯一约束,重复写入时返回成功并提示已存在,保证接口幂等
十、数据库设计
10.1 表设计说明
表名:public.yuntu_report_info
字段设计目标:
- 记录云图返回的业务
report_id - 区分手动创建和自动复制
- 能追溯自动复制来源
- 保存结构化字段与完整 payload
10.2 建表 SQL
DROP TABLE IF EXISTS public.yuntu_report_info;
CREATE TABLE public.yuntu_report_info (
id BIGSERIAL PRIMARY KEY,
report_id VARCHAR(64) NOT NULL,
source_type VARCHAR(32) NOT NULL,
source_report_id VARCHAR(64) NULL,
aadvid VARCHAR(32) NOT NULL,
name TEXT NOT NULL,
price JSONB NOT NULL DEFAULT '[]'::jsonb,
rules JSONB NOT NULL DEFAULT '[]'::jsonb,
analysis_dims JSONB NOT NULL DEFAULT '[]'::jsonb,
categories JSONB NOT NULL DEFAULT '[]'::jsonb,
transaction_channels JSONB NOT NULL DEFAULT '[]'::jsonb,
start_time DATE NOT NULL,
end_time DATE NOT NULL,
period_type VARCHAR(32) NULL,
user_name TEXT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT uq_yuntu_report_info_report_id UNIQUE (report_id),
CONSTRAINT chk_yuntu_report_info_source_type
CHECK (source_type IN ('MANUAL_CAPTURE', 'AUTO_COPY')),
CONSTRAINT chk_yuntu_report_info_copy_source
CHECK (
(source_type = 'MANUAL_CAPTURE' AND source_report_id IS NULL)
OR
(source_type = 'AUTO_COPY' AND source_report_id IS NOT NULL)
)
);
CREATE INDEX idx_yuntu_report_info_source_report_id
ON public.yuntu_report_info (source_report_id);
CREATE INDEX idx_yuntu_report_info_created_at
ON public.yuntu_report_info (created_at DESC);
CREATE INDEX idx_yuntu_report_info_aadvid_created_at
ON public.yuntu_report_info (aadvid, created_at DESC);
CREATE OR REPLACE FUNCTION public.set_yuntu_report_info_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_yuntu_report_info_updated_at ON public.yuntu_report_info;
CREATE TRIGGER trg_yuntu_report_info_updated_at
BEFORE UPDATE ON public.yuntu_report_info
FOR EACH ROW
EXECUTE FUNCTION public.set_yuntu_report_info_updated_at();
10.3 字段映射
| 数据库字段 | 来源字段 | 说明 |
|---|---|---|
report_id |
response.data.reportId |
云图报告 ID |
source_type |
脚本内部标记 | 手动捕获或自动复制 |
source_report_id |
lastReportMeta.reportId |
自动复制来源报告 ID |
aadvid |
query 参数 aadvid |
广告主 ID |
name |
payload.name |
报告名称 |
price |
payload.price |
价格区间 |
rules |
payload.rules |
规则配置 |
analysis_dims |
payload.analysisDims |
分析维度 |
categories |
payload.categories |
类目树 |
transaction_channels |
payload.channels |
渠道信息 |
start_time |
payload.startTime |
报告开始日期 |
end_time |
payload.endTime |
报告结束日期 |
period_type |
payload.periodType |
周期类型 |
user_name |
payload.userName |
用户名 |
payload |
完整 payload | 原始请求体 |
十一、错误处理
11.1 浏览器脚本侧
- 拦截不到目标接口:不报错,不显示成功态
- 响应中缺少
data.reportId:提示“未获取到 report_id,无法记录” - 本地存储失败:立即报错,并终止当前脚本链路
- 用户重复点击:忽略后续点击并保持 loading
- 请求超时:提示失败并恢复按钮
11.2 后端侧
- 参数校验失败:返回 4xx 与明确错误信息
- 数据库写入失败:返回 5xx 与错误码
- 重复
report_id写入:按幂等成功处理,不重复插入
十二、验收标准
- 用户在目标页面手动成功创建报告后,脚本能够识别目标接口,并从响应体中正确提取
data.reportId。 - 手动创建成功后,本地必须成功写入
lastReportPayload和lastReportMeta;若写入失败,页面明确报错且按钮不可点击。 - 手动创建成功且本地写入成功后,后端数据库新增一条
source_type = 'MANUAL_CAPTURE'的记录,且report_id与云图响应一致。 - 当本地不存在有效 payload 时,右侧按钮必须置灰且无法点击。
- 点击“一键复制同比报告”后,脚本只能修改顶层
startTime和endTime,其他字段与来源 payload 保持一致。 - 自动复制成功后,新报告的时间范围必须按自然年回退一年的规则生成;闰年日期按目标月最后一天处理。
- 自动复制成功后,本地缓存必须被新 payload 和新
reportId覆盖;数据库新增一条source_type = 'AUTO_COPY'的记录,且source_report_id正确指向来源报告。 - 点击按钮进入 loading 后,用户无法重复触发创建;无论成功还是失败,流程结束后按钮状态必须恢复正确。
- 后端接口重复接收同一个
report_id时,不得插入重复数据。 - 整个方案不得在代码或配置中硬编码用户 Cookie、Token 等敏感登录信息。