14 KiB
Raw Blame History

云图报告自动创建 PRD

一、项目基本信息

  • 项目路径:/Users/wxs/projects/browser_script/scriptCat/yuntu/yuntuReportFilling
  • 项目类型ScriptCat 浏览器脚本 + Node.js 后端服务 + PostgreSQL
  • 业务目标:在云图页面中复用最近一次成功创建报告的请求参数,自动创建一份同比报告,并将关键数据持久化到数据库
  • 核心价值:
    • 降低人工重复创建报告的成本
    • 自动将时间范围回退一年,方便做同比分析
    • 为后续追溯和排查保留完整 payload 与 report_id

二、业务背景

当前用户在云图页面手动创建报告后,还需要再次输入或复制相同配置,才能创建同比报告。该过程重复且容易出错。

本项目通过监听用户首次手动创建报告时的请求与响应,缓存最近一次成功的 payload当用户点击右侧按钮后脚本自动将顶层 startTimeendTime 各回退一年,并再次调用云图创建接口完成报告复制。

三、范围说明

本期范围

  • 监听并识别云图“创建报告”接口
  • 记录最近一次成功创建报告的完整 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 手动创建报告并记录

触发时机:用户在页面中手动点击“创建报告”并且云图接口返回成功。

执行流程:

  1. 监听 createSegmentedMarket 请求与响应。
  2. 提取请求 payload 与请求 URL 中的 aadvid
  3. 校验响应成功,且响应体存在 data.reportId
  4. 将以下内容写入浏览器本地:
    • lastReportPayload:最近一次成功创建报告的完整 payload
    • lastReportMeta:最近一次成功创建报告的元信息,至少包含 reportIdaadvidsourceTypecapturedAt
  5. 如果本地写入失败,立即报错,并保持按钮不可用状态;本次不调用后端持久化接口。
  6. 如果本地写入成功,调用后端 API 将本次手动创建报告写入数据库。

5.2 一键复制同比报告

触发时机:用户点击页面右侧“一键复制同比报告”按钮。

执行流程:

  1. 读取 lastReportPayloadlastReportMeta
  2. 若本地不存在有效 payload则按钮保持置灰不允许点击。
  3. 深拷贝 payload仅修改顶层 startTimeendTime
  4. 使用修改后的 payload 调用云图创建接口。
  5. 接口成功后,从响应体中读取新的 data.reportId
  6. 先将新的 payload 和元信息覆盖写入本地存储。
  7. 本地写入成功后,调用后端 API 持久化本次自动复制创建的报告数据。
  8. 持久化成功后,给出成功提示,并显示新的 report_id

失败处理:

  • 云图接口调用失败:提示“同比报告创建失败”,保留旧的本地 payload不调用后端 API。
  • 本地覆盖写入失败:提示“本地缓存更新失败”,不调用后端 API按钮恢复可点击。
  • 后端持久化失败:提示“报告已创建成功,但数据库记录失败”,保留新的本地 payload按钮恢复可点击。

六、时间处理规则

6.1 处理目标

将顶层 startTimeendTime 各回退一个自然年,用于生成同比报告。

6.2 处理原则

  • 只处理 payload 顶层字段:
    • startTime
    • endTime
  • 其他字段保持原值不变
  • 日期格式统一为 YYYY-MM-DD
  • 使用“自然年回退”规则,而不是简单减去 365 天

6.3 具体算法

  1. startTimeendTime 解析为日期对象。
  2. 分别将年份减 1尽量保持原始月与日不变。
  3. 如果目标年份不存在该日期,则回退到目标月份最后一天。

示例:

// 正常日期
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
    • 成功后不自动跳转页面,保持用户留在当前页面,避免打断连续操作

7.3 状态恢复规则

  • 用户再次手动成功创建报告后,按钮状态根据本地缓存重新计算
  • 任何失败场景都需要正确退出 loading 状态

八、浏览器本地存储设计

8.1 存储方式

  • 使用 GM_setValue / GM_getValue

8.2 存储键

  • lastReportPayload
  • lastReportMeta

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.reportId
  • sourceType:取值为 MANUAL_CAPTUREAUTO_COPY
  • sourceReportId
    • 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_CAPTUREAUTO_COPY
  • AUTO_COPY 场景下 sourceReportId 必填
  • payload 必须为合法 JSON
  • startTimeendTime 必须符合 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 写入:按幂等成功处理,不重复插入

十二、验收标准

  1. 用户在目标页面手动成功创建报告后,脚本能够识别目标接口,并从响应体中正确提取 data.reportId
  2. 手动创建成功后,本地必须成功写入 lastReportPayloadlastReportMeta;若写入失败,页面明确报错且按钮不可点击。
  3. 手动创建成功且本地写入成功后,后端数据库新增一条 source_type = 'MANUAL_CAPTURE' 的记录,且 report_id 与云图响应一致。
  4. 当本地不存在有效 payload 时,右侧按钮必须置灰且无法点击。
  5. 点击“一键复制同比报告”后,脚本只能修改顶层 startTimeendTime,其他字段与来源 payload 保持一致。
  6. 自动复制成功后,新报告的时间范围必须按自然年回退一年的规则生成;闰年日期按目标月最后一天处理。
  7. 自动复制成功后,本地缓存必须被新 payload 和新 reportId 覆盖;数据库新增一条 source_type = 'AUTO_COPY' 的记录,且 source_report_id 正确指向来源报告。
  8. 点击按钮进入 loading 后,用户无法重复触发创建;无论成功还是失败,流程结束后按钮状态必须恢复正确。
  9. 后端接口重复接收同一个 report_id 时,不得插入重复数据。
  10. 整个方案不得在代码或配置中硬编码用户 Cookie、Token 等敏感登录信息。