Compare commits

55 Commits
Author SHA1 Message Date
19904408334 e736ebc9f7 chore(cid): 更新数据库、Redis、Consul 及 Jaeger 地址与凭据 2026-09-04 14:05:14 +08:00
19904408334 06c64d1069 chore(deploy): 更新 PostgreSQL 密码 2026-09-02 17:12:33 +08:00
admin 51b266cb39 gitea,gitea-runner写入docker-compose 2026-08-06 09:40:32 +08:00
lmk b45c4a2316 数据引擎重构 2026-07-16 14:34:06 +08:00
lmk 802139c349 提交代码 2026-07-02 10:23:07 +08:00
lmk 7f6ec3a757 Merge remote-tracking branch 'origin/dev' into dev 2026-06-26 14:14:41 +08:00
admin 3d965ba11e common版本更新 2026-06-25 17:05:13 +08:00
admin b25cea818e common版本更新 2026-06-25 14:47:50 +08:00
lmk e3fc2fdadb Merge remote-tracking branch 'origin/dev' into dev 2026-06-24 09:15:49 +08:00
admin 918ed552e4 common版本升级 2026-06-23 18:32:00 +08:00
lmkandClaude 22f96a2dce fix(qa): ISSUE-006 — 新增字段编码唯一性校验,避免创建重复字段
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 17:34:32 +08:00
lmkandClaude b78ad2b561 fix(qa): ISSUE-009 — 修复指标名称中单位重复显示(如'订单金额(分)(分)')
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-17 17:34:24 +08:00
lmk 9eeb7584b2 fix(qa): ISSUE-005 — 修复 GetAllBusinesses 等 SELECT 查询ID为0的Bug
根因: common库的 selectHook 自动给 Model() 查询追加 tenant_id = 1 条件,
而数据库中已有数据的 tenant_id = 0,导致查询结果为空且 ID 字段为 0。
暴露表现: 前端编辑按钮不可用(openBizModal(0) 走新建分支而非编辑分支)。

修复: 将所有 SELECT 查询改为 gfdb.DB(ctx).GetAll() 原生 SQL,
绕过 selectHook 的自动租户过滤,保证 ID 字段正确填充。
INSERT/UPDATE/DELETE 不受影响(updateHook/deleteHook 不追加租户条件)。
2026-06-17 10:04:25 +08:00
lmk 9e283f67a0 test(qa): regression test for ISSUE-002 — parseExpression 非 indicator 字段占位符
验证当衍生指标表达式引用未在 indicators 勾选的字段时,
{fieldCode} 占位符被替换为裸字段名,不残留大括号到 SQL 中。
2026-06-16 19:33:57 +08:00
lmk 4c2f7f7326 fix(qa): ISSUE-004 — 修复 vet 警告:append with no values
extract.go:164 的 append(whereArgs) 在 Go vet 中报错,
改为直接赋值 whereArgs(函数内不修改该切片,无需拷贝)。
2026-06-16 19:17:57 +08:00
lmk 7336f2d438 fix(qa): ISSUE-003 — 增加字段类型校验,拒绝 STRING/INT/FLOAT/DATE/DATETIME/JSONB 之外的字段类型
之前 BOOLEAN/BOOLEAN 等无效字段类型不会被拒绝,
导致建表时 fallback 到 VARCHAR(256) 产生类型不匹配。
现在显式校验 FieldType 和 DataType 都在允许范围内。
2026-06-16 19:17:05 +08:00
lmk 6e6f9087b6 fix(qa): ISSUE-002 — 修复 parseExpression {field} 占位符残留 SQL 语法错误
当衍生指标表达式引用未在 indicators 中勾选的字段时,
原代码返回 {fieldCode} 占位符,导致生成的 SQL 包含大括号语法错误。
改为始终返回裸 fieldCode,避免 SQL 错误。
2026-06-16 18:56:45 +08:00
lmk ae9ada7f9d fix(qa): ISSUE-001 — 修复 executeCount 不一致的 result 访问模式
executeCount 函数混用了 result.List()[0] 和 result[0] 两种访问模式。
改为统一使用 result.List()[0] 遍历列值,并在 int64 类型断言失败时
通过 gfdb 的 Int64() 方法安全获取数值。
2026-06-16 18:39:42 +08:00
lmk 276ae55de9 chore: add gstack skill routing rules to CLAUDE.md 2026-06-16 17:47:02 +08:00
admin 32bd6c8895 1 2026-06-16 16:13:10 +08:00
lmk b4fc6f54af 数据引擎-快手平台数据抽取bug修复 2026-06-16 10:44:10 +08:00
lmk e5133eea34 修复需要从前端传新增人修改人的问题 2026-06-12 09:51:09 +08:00
lmk 9dc12634f5 重构数据引擎和报表引擎 2026-06-11 13:12:51 +08:00
lmk 419473f266 重构数据引擎和报表引擎 2026-06-11 13:06:54 +08:00
lmk 285a0fc632 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	controller/copydata/campaign_report_controller.go
#	controller/copydata/population_report_controller.go
#	controller/copydata/unit_report_controller.go
#	controller/dict/api_field_mapping_config_controller.go
#	controller/tencent/oauth_controller.go
#	dao/copydata/api_account_report_detail_dao.go
#	dao/copydata/api_account_report_sum_dao.go
#	dao/copydata/campaign_report_detail_dao.go
#	dao/copydata/campaign_report_sum_dao.go
#	dao/copydata/creative_report_detail_dao.go
#	dao/copydata/creative_report_sum_dao.go
#	dao/copydata/material_report_dao.go
#	dao/copydata/population_report_dao.go
#	dao/copydata/storewide_report_detail_dao.go
#	dao/copydata/storewide_report_sum_dao.go
#	dao/copydata/task_report_dao.go
#	dao/copydata/unit_report_detail_dao.go
#	dao/copydata/unit_report_sum_dao.go
#	dao/dict/api_field_mapping_config_dao.go
#	dao/tencent/account_relation_dao.go
#	dao/tencent/audio_dao.go
#	dao/tencent/image_dao.go
#	dao/tencent/video_dao.go
#	deploy-k3s.ps1
#	model/dto/copydata/api_account_report_detail_dto.go
#	model/dto/copydata/api_account_report_sum_dto.go
#	model/dto/copydata/campaign_report_detail_dto.go
#	model/dto/copydata/campaign_report_sum_dto.go
#	model/dto/copydata/creative_report_detail_dto.go
#	model/dto/copydata/creative_report_sum_dto.go
#	model/dto/copydata/material_report_dto.go
#	model/dto/copydata/population_report_dto.go
#	model/dto/copydata/storewide_report_detail_dto.go
#	model/dto/copydata/storewide_report_sum_dto.go
#	model/dto/copydata/task_report_dto.go
#	model/dto/copydata/unit_report_detail_dto.go
#	model/dto/copydata/unit_report_sum_dto.go
#	model/dto/dict/api_field_mapping_config_dto.go
#	model/entity/copydata/api_account_report_detail.go
#	model/entity/copydata/api_account_report_sum.go
#	model/entity/copydata/campaign_report_detail.go
#	model/entity/copydata/campaign_report_sum.go
#	model/entity/copydata/creative_report_detail.go
#	model/entity/copydata/creative_report_sum.go
#	model/entity/copydata/material_report.go
#	model/entity/copydata/population_report.go
#	model/entity/copydata/storewide_report_detail.go
#	model/entity/copydata/storewide_report_sum.go
#	model/entity/copydata/task_report.go
#	model/entity/copydata/unit_report_detail.go
#	model/entity/copydata/unit_report_sum.go
#	model/entity/tencent/image.go
#	model/entity/tencent/video.go
#	scheduler/run_account_report_task.go
#	service/copydata/api_account_report_service.go
#	service/copydata/campaign_report_service.go
#	service/copydata/creative_report_sum_service.go
#	service/copydata/material_report_service.go
#	service/copydata/storewide_report_sum_service.go
#	service/copydata/task_report_service.go
#	service/copydata/unit_report_service.go
#	service/dict/api_field_mapping_config_service.go
#	service/tencent/image_service.go
#	service/tencent/video_service.go
2026-06-11 10:15:44 +08:00
admin dbd9aa54f0 ci/cd调整 2026-06-10 16:10:09 +08:00
admin 062a09fc59 ci/cd调整 2026-06-10 15:56:02 +08:00
lmk 812693caae 新增快手平台和对应的接口 2026-06-01 14:08:17 +08:00
lmk 15db71b7ba 重构数据引擎 2026-05-29 18:39:32 +08:00
lmk 3ced686cb5 添加状态 2026-05-07 09:07:20 +08:00
lmk a1a259733d 同步视频 2026-05-06 17:29:09 +08:00
lmk 162bab15e6 同步音频和图片 2026-05-06 16:19:22 +08:00
lmk 3814c95047 添加sql脚本,修复模块 2026-04-30 13:45:41 +08:00
lmk 222815a12a 修改服务名 2026-04-22 09:39:05 +08:00
lmk 88e46d7877 k3s动态扩容 2026-04-17 17:46:13 +08:00
lmk 2d844645a0 k3s部署项目 2026-04-16 09:26:00 +08:00
lmk ac9ec66890 配置文件修改以及重新引包 2026-04-11 10:54:47 +08:00
lmk e1c0981bf9 Merge remote-tracking branch 'origin/dev' into dev 2026-04-10 10:28:43 +08:00
lmk 9e2167d843 bug修复 2026-04-10 10:28:30 +08:00
admin b6f9a832c0 增加dockerfile配置 2026-04-09 16:14:31 +08:00
lmk ee713c5c0f 补偿机制任务协程处理 2026-04-09 15:16:43 +08:00
lmk eb5e0af308 修复补偿机制的日志表缺失的问题 2026-04-09 13:43:15 +08:00
lmk 5797fbb43e 修改包名 2026-04-09 10:48:30 +08:00
lmk 556ec5075e 补偿数据逻辑调整 2026-04-09 09:48:22 +08:00
lmk 5d72dd3aff 抽取数据添加协程逻辑调整 2026-04-08 17:26:00 +08:00
lmk 000ea03420 抽取数据添加协程 2026-04-08 16:00:54 +08:00
lmk 073a098317 补偿逻辑修改 2026-04-08 14:30:09 +08:00
lmk a25bba717f 抽取数据逻辑修复+ 2026-04-08 14:05:15 +08:00
lmk 5f2c9c3855 抽取数据添加补偿机制 2026-04-08 09:03:20 +08:00
lmk 14db0dd2fe Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	go.sum
2026-04-07 09:57:31 +08:00
lmk 5b8a4c103d 定时任务抽取数据 2026-04-07 09:57:11 +08:00
lmk 4f3ad39eeb 定时任务抽取数据 2026-04-07 09:51:32 +08:00
admin c8162b1bfe golang版本升级1.26.0 2026-04-03 11:30:12 +08:00
admin 2c6a8490b8 golang版本升级1.26.0 2026-04-03 11:15:09 +08:00
lmk 41089cca2d 批量新增 获取广告计划数据 2026-04-02 17:02:47 +08:00
142 changed files with 22920 additions and 11466 deletions
+30
View File
@@ -0,0 +1,30 @@
{
"permissions": {
"allow": [
"Bash(go build *)",
"Bash(go test *)",
"Bash(go run *)",
"Bash(go mod *)",
"Bash(curl *)",
"Bash(npx *)",
"Bash(brew *)",
"Bash(npm install *)",
"Bash(npm run *)",
"Bash(python3 *)",
"Bash(psql *)",
"Bash(PGPASSWORD=*)",
"Bash(lsof *)",
"Bash(pkill *)",
"Bash(sleep *)",
"Bash(mkdir *)",
"Bash(which *)",
"Bash(pwd *)",
"Bash(echo *)",
"Bash(source *)",
"Bash(export *)",
"Bash(unset *)",
"Bash(package *)",
"Bash(kill *)"
]
}
}
+53
View File
@@ -0,0 +1,53 @@
# 2026-06-01 工作记录
## 快手代发订单详情新增
- 新增 `open.dropshipping.order.merchant.detail``seed_data_kuaishou.sql`
- 第19个接口,POST、prefetch 自 dropshipping_order_list、single_record
## 快手接口全面审查
发现 prefetch 相关7个接口存在3个严重Bug:
1. Prefetch 阶段业务参数未包入 param JSONbody_wrapper_field 未生效)
2. Prefetch 阶段 `parseResp(nil config)` 无法解析 data.items/data.cpsOrderList 等路径
3. Prefetch 循环未处理游标分页(固定页码递增)
## Bug 修复(dynamic_sync.go
1. `syncWithPrefetch` 改用 `buildReqBody(prefetchIface)` 构建请求,body_wrapper_field 正确包装
2. 改用 `parseRespExt(resp.Body, prefetchIface.ResponseConfig)` 解析响应
3. 支持游标分页的 prefetch 循环(cursor/pcursor
4. `buildPrefetchParams` 增加过滤 body_wrapper_field/exclude_from_wrapper/cursor_pagination/time_field_mode
5. 新增 `collectPrefetchEntities` 辅助函数
6. 修复并发阶段 `inQuery` 变量缺失的问题
## 同步调度并发锁修复
1. `SyncByConfig` 新增 `syncRunningMap` 内存锁(sync.Map.LoadOrStore),防止同一接口并发执行
2. 调度器从 `time.NewTicker` 改为 `for { run(); time.Sleep(interval) }`,前一次完成后才开始计时
## MEMORY.md 更新
- response_config 增加 `single_record` 字段说明
- prefetch 流程补充游标分页、参数构建、响应解析的详细说明
- 新增「并发保护」章节,记录内存锁和调度器时序
- 调度器章节补充 for+sleep 模式说明
## 钉钉平台新增
- 创建 `seed_data_dingtalk.sql`:钉钉开放平台 + 部门列表接口
- auth_type: OAUTH2access_token 在 URL 查询参数中
- 部门列表:POST + parameters_location=query + recursive 递归遍历,响应 `result` 为数组
- 表名:`dingtalk_department`,冲突键:dept_id
## 递归遍历支持
- 新增 `RecursiveConfig` + `parseRecursiveConfig` + `syncRecursive` 函数
- `syncRecursive`:BFS 队列遍历,先查根级 → 对每个子部门递归查下级,防重复 + 最大深度限制
- `buildReqBody` / `buildPrefetchParams` 增加过滤 `recursive` / `max_recursive_depth`
## 钉钉智能人事平台
- 新建 `seed_data_dingtalk_hrm.sql`:钉钉智能人事专用平台
- auth_type: OAUTH2 + Header 认证(`x-acs-dingtalk-access-token`),独立于 oapi 平台
- 企业职位列表 `position_list`POST + cursor 分页 + hasMoreAPI base `api.dingtalk.com`
## 钉钉角色列表 + 引擎增强
- `parseRespExt` 增加 `has_more_field` 支持 + 数字游标(float64→string)
- `syncWithPrefetch` 支持递归预取来源(BFS
- `buildReqBody` 增加 `pagination_mode: "offset"` 支持(计算 offset = (page-1)*pageSize
- `syncSingleAPI` 增加 hasMore 驱动的分页循环(钉钉 offset/size + hasMore
- 新增 `hasMoreCheck` 辅助函数
- 新增 `seed_data_dingtalk.sql` 角色列表接口:偏移分页 + hasMore
+36
View File
@@ -0,0 +1,36 @@
# 2026-06-03 工作记录
## 钉钉智能薪酬平台对接
- 新增 `dingtalk_salary` 平台 + 2个接口到 `sql/seed_data_dingtalk_salary.sql`
- 新认证类型 `APP_SIGNATURE`app-id + signature 头部认证
- 签名算法:MD5(body_string + app_secret).toUpperCase()
### 接口明细
1. **salary_dept_list**`POST /oapi/salary/dept/list`,无入参,单次拉取全量部门
2. **salary_statistics**`POST /oapi/salary/statistics/dept`prefetch 自 salary_dept_listrow_inject statisticsMonth
### 代码改动
- `api_client.go`: `applyAuthHeader` 扩展支持 `APP_SIGNATURE` 认证,新增 `applyAppSignatureAuth` + `computeBodySignature`
- `platform_manager.go`: 新增 `APP_SIGNATURE` case,从 auth_config 提取 app_id/app_secret
- `dynamic_sync.go`:
- `toFloat64` 扩展支持 string→float64(适配 salary API 返回 code: "200" 字符串)
- `buildReqBody`/`buildPrefetchParams` skip list 新增 `row_inject`
- 新增 `injectRowFields` 函数:将请求参数注入响应行
- `injectRowFields` 调用于 syncWithPrefetch 中3处 row 注入点
## 新增钉钉智能薪酬人力成本报表接口 (2026-06-03 第二阶段)
### 新增接口
3. **salary_statistic_report**`POST /oapi/salary/statistic/report/data`,无分页,single_record,获取指定月份的人力成本报表
- 入参:`calBizId`(格式 yyyyMM"M",如 "202606M",为空默认当月)
- 出参:`data.calBizId` + `data.sumStatisticsData`(成本项数组 JSONB
- 表:`dingtalk_salary_statistic_report`conflict_key: `calBizId`
4. **salary_statistic_report_group**`POST /oapi/salary/statistic/report/groupData`,无分页,single_record
- 入参:`calBizId` + `salaryGroupName`(必填),使用 `row_inject` 注入 salaryGroupName
- 表:`dingtalk_salary_statistic_report_group`conflict_keys: `calBizId` + `salaryGroupName`
### 代码改动
- `dynamic_sync.go`:修复主同步路径(非 prefetch)中 `injectRowFields` 未调用的问题
- 在 5 处 savePage 调用前添加 injectRowFields:初始页、游标分页循环、hasMore 分页、普通分页(有/无 response_config
- 使 `row_inject` 在单记录(single_record)非 prefetch 接口中也能生效
+15
View File
@@ -0,0 +1,15 @@
# 2026-06-08 工作日志
## goview-report 项目初始化
- 基于 GoView (gitee.com/bufanyun/go-view-server) 初始化报表引擎项目
- 位置: `/Users/xujiaqian/GolandProjects/HDWL/goview-report/`
- 改造内容:
- 模块名 hotgo → goview-report
- MySQL 驱动替换为 PostgreSQL 驱动 (github.com/gogf/gf/contrib/drivers/pgsql/v2)
- Go 版本升级到 1.26.0GoFrame 升级到 v2.10.0
- config.yaml 中数据库连接改为 pgsql://postgres:root@127.0.0.1:5432/engine
- 端口保持 8090,与 data-engine(:3002) 不冲突
- 元数据表前缀 hg_,data-engine 同步表无前缀,同库不同前缀
- 创建 Dockerfile + k8s/deployment+service+configmap
- 编译验证通过
- 与 data-engine 关系: 读写分离,goview-report 只读 PG 中的同步表做报表展示
+18
View File
@@ -0,0 +1,18 @@
# 2026-06-09 工作日志
## 公共查询接口开发
- 新增 `/public/query` POST 接口,支持:
- 字段白名单验证(只允许表定义中声明的字段)
- 表名白名单验证(只允许 api_interface 中有 table_definition 的表)
- WHERE 条件(支持 `_eq/_ne/_gt/_lt/_ge/_le/_like/_in/_between` 操作符)
- GROUP BY / ORDER BY / 分页
- 强制 tenant_id = 1 租户隔离
- 新增辅助接口:
- `GET /public/tables` - 获取可查询表列表
- `GET /public/tables/{table}/columns` - 获取表字段列表
- `DELETE /public/cache/clear` - 清除表缓存
- 文件位置:
- `model/dto/public/public_query_dto.go`
- `service/public/public_query_service.go`
- `controller/public/public_query_controller.go`
- 同步修复 `dynamic_sync.go` 中残留的无效代码 "了呢"
+179
View File
@@ -0,0 +1,179 @@
# 2026-06-10
## 通用报表引擎 - 补充前端 CRUD 能力
`common/report` 包补充了配置的 CRUD 接口,使前端可以直接维护报表配置,无需操作数据库。
### 新增文件变更
- `model/model.go`: 新增 SaveBusinessReq/SaveReportReq/SaveFieldReq/SaveExtractConfigReq 等请求结构体,SaveResult/DeleteResult 响应结构体
- `config/loader.go`: 新增 Create/Update/Delete + GetByID 方法(Business/Report/Field/ExtractConfig 四种配置)
- `api.go`: 新增 SaveBusiness/SaveReport/SaveField/SaveExtractConfig + Delete/Get 接口(新增/修改合一,id 为空则新增)
- `example_usage.go`: 新增第五、六节,CRUD 调用示例 + 任意新平台零代码接入流程
### 关键设计决策
- **Save 合一**:通过 `ID *int64` 区分新增(nil)和修改(有值),简化前端调用
- **软删除**status → INACTIVE + deleted_at=NOW(),数据不丢失
- **缓存失效**:写操作后自动失效对应业务/报表缓存,保证下次读取一致性
- 所有 CRUD 方法先调用 initTables() 确保表存在
### example_usage.go 完整重写
将示例文件从分节的注释风格重写为6个完整场景的 Go 函数示例:
1. **快手电商完整接入**(最详细,从业务注册到前端查询6步全流程)
2. **已有配置的 CRUD 二次开发参考**(业务/报表/字段/抽取 增删改查)
3. **任意平台接入通用模式**(淘宝示例,零硬编码)
4. **外部业务服务调用**goview-report 等直接 import
5. **Direct 模式示例**(不聚合的逐行抽取)
6. **前端 HTTP API 交互流程**(伪代码,展示 API 调用顺序)
每个场景都有实际可运行的代码片段、生成的 SQL 预览、返回值输出示例。
## 报表引擎管理前端页面
为报表引擎创建了可直接使用的前端管理页面,访问 `/admin/report`
### 新增文件
- `controller/report/report_controller.go`: HTTP API Controller16个端点暴露公共报表服务的全部能力(业务/报表/字段/抽取配置的 CRUD、数据抽取触发、自动建表、用户选择查询)
- `controller/report/report_admin_controller.go`: 嵌入式HTML SPA5个Tab页:
- **业务管理**:列表 CRUD
- **报表配置**:按业务筛选列表 CRUD
- **字段配置**:按业务+报表筛选,维度/指标/筛选角色管理
- **抽取配置**:按业务+报表筛选,支持 DIRECT/AGGREGATE 模式配置 + 一键执行抽取
- **数据查询**:可视化维度选择器、指标聚合选择器、筛选条件构建器、查询结果展示
- `main.go`: 注册 reportCtrl.ReportController 到 RouteRegister + `/admin/report` 页面路由
### 技术要点
- 纯原生 HTML/CSS/JS,零框架依赖,与现有管理后台 `/admin` 风格统一
- 所有 CRUD 通过 `ID *int64` 区分新增(nil/修改(有值)
- 查询Tab支持动态字段选择:维度多选、指标聚合方式选择、筛选条件动态构建(支持 BETWEEN/IN 等操作符)
- 管理后台页面增加了返回链接 `← 返回管理后台`
## 修复 report_common_ddl.sql 语法错误
PostgreSQL 不支持 MySQL 风格的内联 `COMMENT '...'` 语法。修复了全部5张表的列定义:
- 移除所有列定义中的内联 `COMMENT 'xxx'`MySQL 语法)
- 已有的 `COMMENT ON COLUMN` 独立语句保留不变
- `report_extract_log` 表补充了缺失的 `COMMENT ON COLUMN` 语句(business_code/report_code/extract_code/stat_date/extract_type/status/total_count/success_count/fail_count
关键区别:MySQL 支持 `column_name VARCHAR(64) COMMENT 'xxx'`PostgreSQL 必须用 `COMMENT ON COLUMN table.column IS 'xxx'`
## 补充 tenant_id 到报表系统表
5张报表系统表原先缺失 `tenant_id`,与项目核心表(`init_core_tables.sql`)不一致。修改内容:
- **DDL SQL** (`sql/report_common_ddl.sql`)5张表全部新增 `tenant_id BIGINT NOT NULL DEFAULT 0` 列,所有 UNIQUE 约束和索引都加入 tenant_id
- **Go initTables** (`common/report/report.go`):同步更新 5 张表的 DDL 定义,加上 tenant_id
- 约束变更:`uk_business_report_code UNIQUE (business_code, report_code)``(tenant_id, business_code, report_code)`,其余类推
当前 report 模块租户支持为占位符级别(硬编码 tenant_id=1),后续需从 context 动态获取。
- **单独迁移脚本**: `sql/report_add_tenant_id.sql`,对已存在的表执行 ALTER TABLE ADD COLUMN + 重建约束/索引
## 快手电商报表初始化种子数据
**初版(已废弃)**:字段名随意捏造,与真实 DDL 不对齐。
**重写版** `sql/seed_data_report_kuaishou.sql`(基于 seed_data_kuaishou.sql 实际 DDL):
- 1 条业务配置 (kuaishou_ecommerce)
- 5 条报表配置:order_analysis / item_analysis / refund_analysis / cps_order_analysis / dropshipping_analysis
- 76 条字段配置(订单23+商品15+售后12+分销13+代发13),含维度/指标/衍生字段
- 5 条抽取配置(AGGREGATE 聚合模式,每个报表一条)
**严格设计原则**
- sourceField 对齐源表实际列名(camelCaseoid, status, createTime, totalFee, buyerNick 等)
- targetField / field_code 使用 snake_casestat_date, total_fee, buyer_nick 等)
- 金额字段源表存分(BIGINT),报表提供衍生元字段(CALCULATED expression / 100.0)
- 增量抽取依据统一用 updateTime(毫秒时间戳 BIGINT
- transform_rulests_to_date FORMAT createTime → stat_date(yyyy-MM-dd)
- 所有 INSERT 使用 ON CONFLICT DO NOTHING 保证幂等
## 修复报表管理前端编辑不显示数据问题
`report_admin_controller.go` 中修复了编辑弹窗不显示数据的问题:
**根因**
- GoFrame `MiddlewareHandlerResponse` 包裹响应为 `{code:0, data: <struct>}`,而单条查询返回结构体为 `getBusinessRes{Data: ...}` 序列化为 `{"data": {...}}`,最终响应为 `{code:0, data: {"data": {...}}}` 双层嵌套
- 某些 GoFrame 版本可能序列化 Go 字段名(首字母大写 `Data`)而非 json tag`data`),导致 `res.data` 为 undefined
**修复内容**
1. `api()` 函数增加兼容逻辑:当 `json.data.Data` 存在而 `json.data.data` 不存在时,自动取 `json.data.Data`
2. `api()` 函数增加 `console.log` 调试日志,打印原始响应和解包后的数据
3. 四个编辑函数(`openBizModal/openReportModal/openFieldModal/openExtractModal`):
- 增加 fallback`res.data !== undefined ? res.data : res.Data`
- 增加数据有效性校验:`if (!d || !d.id)` 时弹出错误提示并阻止打开空表单
- 增加 `console.log` 调试日志
## 修复 report_report_config 查询 SQL 永假条件问题
**现象**:查询 `report_report_config` 时生成的 SQL 包含 `WHERE (("id" = 1) AND ("id" = 0 AND "business_code" = '' AND ...))`,id 不可能同时为 1 和 0,导致查询始终返回 0 行。
**根因**`common/db/gfdb/gfdb.go``Model()` 方法使用了 `OmitNil()`(只过滤 nil 指针,不过滤零值)。当 GoFrame v2 内部的软删除机制(`deleted_at IS NULL`)自动构建 struct WHERE 条件时,由于 `OmitNil()` 不忽略零值字段,结构体中所有字段的零值(`id=0`, `business_code=""`, `report_code=""` 等)都被加入 WHERE 条件,与显式的 `Where("id", 1)` 叠加形成 `id=1 AND id=0` 的永假条件。
**修复**`OmitNil()` **保留**,额外追加 `.OmitEmptyWhere()`,两者同时生效:
- `OmitNil()`:保持 INSERT/UPDATE 的 Data() 行为不变(nil 指针/map 字段不写入 SQL,走 DB 默认值),避免破坏数据引擎写入逻辑
- `OmitEmptyWhere()`:过滤 struct WHERE 条件中的零值字段,解决软删除 hook 注入零值条件的问题
最终代码:`OmitNil().OmitEmptyWhere()`
**影响范围**`common/db/gfdb/gfdb.go` - 全局 DB Model 初始化。写入行为完全兼容,查询条件不再包含零值 struct 字段。
**安全性审计(2026-06-10**:逐文件审查了数据引擎核心同步路径全部 40+ 处 `gfdb.Model()` 调用:
- 100% 传字符串表名(非 struct),不会触发 struct WHERE 推断
- 100% 使用 `Where("col", val)` 字符串键值对(非 struct/map),`OmitEmptyWhere()` 对此无效
- `OmitEmptyWhere()` 不影响 `Data()`/`Insert()`/`Update()`/`Save()`
- 结论:`OmitEmptyWhere()` 对数据引擎核心同步逻辑完全无影响,唯一生效场景是 `common/report` 软删除 hook
## 修复 GetXxxByID 方法零值结构体返回问题
**现象**:前端编辑报表/业务/字段/抽取配置时一直提示"未获取到XX数据(ID=N"。与上述 OmitNil 问题同根:SQL 返回 0 行时,GoFrame 的 `One()` 可能不返回 `sql.ErrNoRows`(驱动差异),此时 `GetXxxByID` 判断 `err == nil` 就返回了零值结构体 `{ID: 0, ...}`。Controller 包装为 `{code:0, data:{"data":{id:0}}}`,前端 `!d.id` 判定失败弹出错误提示。
**修复**`common/report/config/loader.go` 中全部 7 个 `One()` 调用方法,在 `err` 检查之外增加 `result.IsEmpty()` 检查:
- `GetBusinessByID` / `GetReportByID` / `GetFieldByID` / `GetExtractConfigByID`:四者模式一致,`_` 改为 `result` 变量并追加 `if result.IsEmpty()`
- `GetBusiness` / `GetReport`by code)、`GetExtractLog`:同上,追加 `IsEmpty()` 守卫
**双重防御**gfdb.go 的 `OmitEmptyWhere()` 是根本修复(消除错误 SQL);loader.go 的 `IsEmpty()` 是兜底守卫(即使 SQL 返回 0 行也不返回零值结构体)。
## 修复编辑弹窗数据提取的三层兼容
**根因**`api()` 返回 `json.data` 给调用者。当 GoFrame `MiddlewareHandlerResponse` 双层包裹时(`{code:0, data: {"data": {...}}}`),`api()` 返回 `{"data": {...}}`,调用者通过 `res.data` 正确提取。但当 GoFrame 单层包裹时(`{code:0, data: {...}}`),`api()` 直接返回 `{...config...}`,调用者 `res.data` 为 undefined → `d` 为 undefined → 触发 "未获取到XX数据" 错误。
**修复**4 个编辑函数(`openBizModal`/`openReportModal`/`openFieldModal`/`openExtractModal`)的数据提取改为三层 fallback:
```javascript
const d = res && res.data !== undefined ? res.data : (res && res.Data !== undefined ? res.Data : res);
```
- 第一层:`res.data`(双层包裹,json tag 小写)
- 第二层:`res.Data`Go 字段名大写 D
- 第三层:`res` 本身(单层包裹,数据直接在 res 里)
同时 console.log 增加打印 `raw:``data:` 便于排查。
## 修复报表引擎实体缺失 TenantId 字段 → 查询 tenant_id 不匹配返回零值
**现象**`GET /report/business?id=1` 返回 `code: 0` 但 data 为全零值 `{id: 0, businessCode: "", ...}`,尽管 DB 中实际有数据。
**根因**:四个报表引擎实体结构体均缺失 `TenantId` 字段,但 DDL 中表定义有 `tenant_id BIGINT NOT NULL DEFAULT 0`
1. `CreateBusiness` 序列化 `BusinessConfig` 时,JSON 中无 `tenant_id` → insert map 中无 `tenant_id` key
2. `insertHook` 检查 `in.Data[i]["tenant_id"]` 是否存在 → 不存在 → 不设置 → DB 用 DEFAULT 0
3. `selectHook``ctxWithUser` 获取 `TenantId=1`,注入 `WHERE tenant_id = 1`
4. DB 行 `tenant_id=0` 与查询 `tenant_id=1` 不匹配 → 查不到行 → 返回零值结构体
**修复**`common/report/model/model.go`):给 `BusinessConfig``ReportConfig``FieldConfig``ExtractConfig` 四个结构体新增 `TenantId uint64 \`orm:"tenant_id" json:"tenant_id"\``。
**原理**:添加后序列化 JSON 包含 `"tenant_id": 0``insertHook` 检测到 key 存在且为零值,自动赋值为 `userInfo.TenantId`。
**调试日志**`common/report/config/loader.go`):`GetBusinessByID` 增加 `g.Log().Infof` 打印 `id`、`err`、`IsEmpty`、`biz.ID`、`biz.BusinessCode`,便于排查后续类似问题。同时补充 import `"github.com/gogf/gf/v2/frame/g"`。
## 修复 GoFrame One(&struct) 直接映射失效 → 改用两步模式
**现象**:修复 TenantId 后,SQL 能正确执行并返回1行(`[rows:1]`, `IsEmpty=false`),但 struct 字段全为零值(`biz.ID=0, biz.BusinessCode=""`)。
**根因**`loader.go` 中 7 处 `One(&struct)` 直接传结构体指针,在 GoFrame v2.10.0 + `selectHook` 修改 SQL 的组合下,struct 自动映射失效 —— 查询返回 `gdb.Record` 但未 populate 结构体。
**修复**:全部 7 处改为项目已验证的两步模式(参照 `dao/dict/api_interface_dao.go`):
```go
// 旧写法(struct 映射失效)
result, err := gfdb.DB(ctx).Model(ctx, "table").Where(...).One(&biz)
// 新写法(先取 Record,再手动 Struct
r, err := gfdb.DB(ctx).Model(ctx, "table").Where(...).One()
if err = r.Struct(&biz); err != nil { return nil, err }
```
**影响的方法**`GetBusiness`、`GetBusinessByID`、`GetReport`、`GetReportByID`、`GetFieldByID`、`GetExtractConfigByID`、`GetExtractLog`。
+616
View File
@@ -0,0 +1,616 @@
# 数据引擎 - 项目记忆 (MEMORY.md)
## 项目概述
Golang + GoFrame v2 + PostgreSQL 的配置化数据同步引擎。通过 `api_datasource_platform` + `api_interface` 两表驱动,支持多平台多接口的数据同步,无需重新编译代码。
## 核心表结构
`sql/init_core_tables.sql`
- `api_datasource_platform` — 数据源平台配置
- `api_interface` — API 接口配置(request_config / response_config / table_definition 三大 JSONB
- `sync_tracker` — 同步跟踪(last_sync_time, sync_status
- `sync_task_log` — 同步任务日志(用于补偿重试)
## 认证类型(auth_type)模板
### 1. OAuth2(腾讯广告)
```
auth_type: OAUTH2
token: access_token 值
client_id / client_secret: OAuth2 凭证
auth_config: {
"token_in_query": true, // token 放 URL 查询参数
"query_key": "access_token", // 参数名
"refresh_token": "xxx", // 刷新 token
"extra_query_params": {
"timestamp": "{timestamp}",
"nonce": "{nonce}"
}
}
```
安全校验:`applyAuthHeader` 中 OAUTH2/TOKEN 模式会设 `Authorization: Bearer {token}`,但 `token_in_query=true` 时跳过 Header,转加 URL 参数。
### 2. API_KEY + 签名(快手电商)
```
auth_type: API_KEY
token/access_token: 调用凭证
auth_config: {
"sign_algorithm": "md5_upper", // 签名算法: md5 / md5_upper
"app_key": "xxx",
"app_secret": "xxx",
"token_in_query": true,
"query_key": "access_token",
"extra_query_params": {
"timestamp": "{timestamp_ms}",
"signMethod": "MD5"
}
}
```
签名过程:所有查询参数按 key 排序 → `k1=v1&k2=v2&...&key=app_secret` → MD5 摘要 → 追加 `sign` 参数。
`{timestamp_ms}` 替换为毫秒时间戳,`{timestamp}` 替换为秒时间戳,`{nonce}` 替换为随机数。
### 3. TOKEN(简单 Bearer Token
```
auth_type: TOKEN
token: "xxx"
```
`applyAuthHeader``Authorization: Bearer {token}`
### 4. SIGN(仅签名,无额外 token
```
auth_type: SIGN
auth_config: { "app_key": "...", "app_secret": "..." }
```
`applyAuthHeader` 不设特殊 Header,仅通过 `applySignature` 追加签名参数。`PlatformManager``auth_config` 读取 app_key/app_secret。
## request_config 字段完整参考
| 字段 | 类型 | 含义 | 适用平台 |
|------|------|------|---------|
| `parameters_location` | string | `"query"`=URL查询参数,不设则POST走JSON body | 通用 |
| `page_param` | string | 分页参数名,默认`"page"` | 通用 |
| `page_size_param` | string | 每页条数参数名,默认`"page_size"` | 通用 |
| `page_size` | int | 每页条数,**覆盖 config.yml 全局默认值**,需按各平台最大限制设置(如快手最大50,腾讯100) | 通用 |
| `cursor_pagination` | bool | 是否游标分页(true=游标,false/无=普通分页) | 通用 |
| `time_field` | string | 增量时间字段名 | 通用 |
| `time_field_mode` | string | `"range"`=beginTime/endTime模式(快手),`"filtering"`=filtering数组模式(腾讯,默认) | 通用 |
| `full_sync_start_time` | int | 全量同步时的时间起点,毫秒时间戳。`lastSyncTime=0` 时优先读取此值,不存在则回退90天前(range)或不传时间过滤(filtering | 通用 |
| `prefetch` | object | 预取配置(见下方) | 需遍历实体的接口 |
| `recursive` | object | 递归遍历配置(见下方) | 树形结构接口(如钉钉部门) |
| `max_recursive_depth` | int | 递归最大深度,默认20 | 递归遍历使用 |
| `body_wrapper_field` | string | 业务参数包装字段名,如`"param"` | 快手 |
| `exclude_from_wrapper` | string[] | body_wrapper 时不包装的字段 | 快手 |
| `top_level_params` | string[] | 保留在顶层的字段(备用) | 通用 |
| `fields` | string[] | API 的 fields 参数 | 腾讯 |
| `row_inject` | string[] | 将请求参数值注入到每行数据中(如 salaryGroupName),解决响应不含某请求参数但需存储的场景 | 通用(prefetch 和非 prefetch 均支持) |
### prefetch 预取配置
```json
"prefetch": {
"url": "/advertiser/get", // 预取接口的相对路径
"method": "GET", // HTTP 方法
"response_path": "data.list", // 从响应中取列表的 JSON 路径
"target_param": "account_id", // 预取值注入到数据接口的参数名
"value_field": "account_id" // 从预取列表里取哪个字段的值
}
```
流程:先分页拉取预取接口 → 提取所有 ID → 逐个 ID 并发拉取数据接口 → 将 ID 注入每行数据。
**预取阶段分页**:自动适配预取来源接口的分页方式:
- **游标分页**`cursor_pagination: true`):首次 `cursor=""`,后续从响应 `cursor_field` 取值,直到 `""``"nomore"` 停止
- **普通分页**:读取 `page_info.total_page` 遍历全部页码
**预取阶段参数构建**:使用 `buildReqBody(prefetchIface)` 构建,自动处理 `body_wrapper_field` 包装(param JSON)、时间过滤(增量时传 beginTime/endTime)、分页参数名。响应解析使用 `prefetchIface.ResponseConfig`,正确识别各种 `list_path``data.orderList``data.items``data.cpsOrderList` 等)。
**预取阶段时间过滤**:增量同步时自动复用预取来源接口的 `time_field` + `time_field_mode`(如果来源支持增量,prefetch 会自动带上时间范围只拉增量数据)。
## response_config 字段完整参考
| 字段 | 类型 | 含义 |
|------|------|------|
| `success_field` | string | 成功标识字段名,默认`"code"` |
| `success_value` | number | 成功标识值,默认`0` |
| `message_field` | string | 错误信息字段名,默认`"message"` |
| `list_path` | string | 数据列表在 JSON 中的路径,如`"data.list"``"data.orderList"` |
| `cursor_field` | string | 游标字段路径,如`"data.cursor"` |
| `cursor_end_marker` | string | 游标结束标记,如`"nomore"`(代码中硬判断此字符串) |
| `single_record` | bool | 单记录模式:`list_path` 指向的路径是单个对象而非数组,会自动包装为单元素数组 |
**响应解析逻辑**`parseRespExt` 函数
1. 检查 `success_field` 的值是否等于 `success_value` | **注意**`success_value` 仅支持数字类型(内部通过 `toFloat64` 转换),不支持字符串如 `"SUCCESS"` 或布尔值 `true`
2.`list_path` 遍历到数据列表数组 | 支持路径如 `data.orderList`,最后一段是数组 OK(代码有兼容处理)
3. 每条数据调用 `flattenRow` 展平嵌套 field`orderBaseInfo.oid``oid`
4. 提取 `cursor_field` 用于下一页,为空或等于 `cursor_end_marker` 时停止循环
5.`page_info.total_page` 获取总页数(普通分页用)
**游标分页首次请求**:代码自动传 `cursor=""`(首页不传游标),由服务端返回第一页数据 + 下一页游标值。
**单记录模式**`"single_record": true` 时,`list_path` 指向单个对象(如 `"data"`),自动包装成单元素数组用于统一处理。
## table_definition 字段完整参考
| 字段 | 类型 | 含义 |
|------|------|------|
| `table_name` | string | 目标表名,如`"kuaishou_order_list"` |
| `columns` | array | 列定义数组,每个元素 `{"name":"字段名","type":"PG类型","comment":"说明"}` |
| `conflict_keys` | string[] | upsert 冲突键,如`["oid"]``["image_id","account_id"]` |
注意:
- 列名必须和 API 响应中的字段名**完全一致**camelCase/snake_case 取决于 API
- 嵌套字段会被 `flattenRow` 自动展平到顶层
- `raw_data` 列自动包含,存储完整原始 JSON
- 自动建表时会额外添加 id/tenant_id/creator/created_at/updater/updated_at/deleted_at 审计字段
- **必须写全**`columns` 中需列出 API 响应中所有平铺的标量字段(字符串/数字/布尔),数组和嵌套对象字段保留在 `raw_data` 中即可
## 同步策略原则
- **API 支持时间过滤**(有 beginTime/endTime 或 filtering 参数)→ 配 `time_field` + `time_field_mode`,实现增量同步
- **API 不支持时间过滤** → 不配 `time_field`,走全量同步
- **详情/子接口**(通过 prefetch 遍历):prefetch 阶段自动复用预取来源接口的 `time_field_mode`(如果来源接口支持增量,prefetch 也会带上时间过滤只拉增量数据)
## 全量同步流程
```
SyncByConfig(platformCode, interfaceCode, isFullSync=false)
├─ getLastSyncTime() → 无记录返回 0
│ lastSyncTime=0 → 全量
├─ markSyncRunning() 标记 running
├─ buildReqBody(page=1, cursor="", lastSyncTime=0)
│ ├─ 游标分页: cursor="" 首次空游标
│ ├─ range 模式: beginTime=90天前, endTime=now
│ └─ filtering 模式: 不添加时间过滤
├─ API → parseRespExt → flattenRow → savePage(upsert)
└─ 游标循环 → 直到 cursor="" 或 "nomore"
updateSyncTime() 记录最大时间戳
```
## 增量同步流程
```
SyncByConfig(platformCode, interfaceCode, isFullSync=false)
├─ getLastSyncTime() → 返回上次 maxTime
│ lastSyncTime>0 → 增量
├─ buildReqBody(cursor="", lastSyncTime=xxx)
│ ├─ range 模式: beginTime=lastSyncTime, endTime=now
│ └─ filtering 模式: filtering=[{field=time_field, operator=GREATER_EQUALS, values=[lastSyncTime]}]
└─ 后续同全量(只拉取时间范围内的数据)
```
## 并发保护
### 内存锁(同一进程内)
`SyncByConfig` 入口使用 `sync.Map``syncRunningMap`)记录正在执行的接口。若同一接口的同步请求再次到达(如调度器重叠),直接跳过并打印警告:
```
WARN 接口 [kuaishou/order_list] 正在同步中,跳过重复请求
```
无论接口类型(prefetch/非prefetch、腾讯/快手、现有/新增)均自动生效。
### 异常中断检测(DB 状态)
如果 `sync_tracker.sync_status = "running"`,说明上次同步异常中断(进程崩溃),自动回退全量。
## 补偿机制
`compensation.go` 定时扫描 `sync_task_log` 中 status="failed" 的任务,自动调用 `SyncByConfig` 重试(增量),支持指数退避(5, 15, 30, 60, 120 分钟)。
## 自动同步调度
`sync_scheduler.go``runAutoSync` 遍历所有 ACTIVE 平台下的所有配置了 table_definition 的活跃接口,自动调用同步。
**调度器时序**:使用 `for { runAutoSync(); time.Sleep(interval) }` 模式,而非 `time.NewTicker`。每次同步**完全结束后**才开始计时 interval,避免前一次未跑完就启动下一次导致重叠。无论全量跑 30 分钟还是 70 分钟,下一次都在「本次完成时间 + interval」后执行。
## 三种遍历模式对比
| 模式 | 配置 | 适用场景 | 流程 |
|------|------|---------|------|
| **普通分页** | `page_param` + `page_size_param` | 列表接口分页 | 请求第1页 → 读 `page_info.total_page` → 遍历后续页 |
| **游标分页** | `cursor_pagination: true` | 实时滚动列表(快手订单) | 首次 `cursor=""` → 响应返回 `cursor` → 直到 `""``"nomore"` |
| **prefetch 预取** | `prefetch: {url, ...}` | 先取实体列表,再查详情 | 分页拉取实体来源列表 → 提取全部 ID → 并发查详情 |
| **recursive 递归** | `recursive: {key_field, target_param}` | 树形结构(钉钉部门) | BFS 队列:根级调用 → 提取子ID → 逐级递归下级 → 防重复 |
## 新增平台 + 接口的操作步骤
### 操作步骤
1.`sql/` 下创建 `seed_data_{平台编码}.sql`
2. 先执行 `sql/init_core_tables.sql`(仅首次需要)
3. 执行新建的 seed SQL 文件(INSERT 平台 → INSERT 接口)
4. 配置认证凭据(token / app_key / app_secret 等)
5. 重启服务,管理后台验证:`http://localhost:3002/admin`
### 种子 SQL 标准模板
#### 模板 A:OAuth2 + 普通分页(腾讯广告风格)
```sql
-- =============================================
-- 1. 创建 {平台编码} 平台
-- =============================================
INSERT INTO api_datasource_platform (
tenant_id, creator, created_at, updater, updated_at,
platform_code, platform_name, description, status,
api_base_url, auth_type,
token, client_id, client_secret,
auth_config,
rate_limit_per_minute, rate_limit_per_hour,
concurrency_limit, request_timeout_ms, max_retries, retry_delay_ms
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
'{平台编码}', '{平台名称}', '{描述}', 'ACTIVE',
'{API_BASE_URL}', 'OAUTH2',
'{access_token}', '{client_id}', '{client_secret}',
'{
"token_in_query": true,
"query_key": "access_token",
"refresh_token": "{refresh_token}",
"extra_query_params": {
"timestamp": "{timestamp}",
"nonce": "{nonce}"
}
}'::jsonb,
60, 3600, 10, 30000, 3, 1000
);
-- 2. 创建 {接口名} 接口(普通分页 + 增量 filtering
INSERT INTO api_interface (
tenant_id, creator, created_at, updater, updated_at,
platform_id, name, code, url, method, status, auth_type,
request_config, response_config, table_definition
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
(SELECT id FROM api_datasource_platform WHERE platform_code = '{平台编码}'),
'{接口名称}', '{接口编码}',
'{相对路径}', 'GET', 'active', 'inherit',
'{
"parameters_location": "query",
"page": 1,
"page_size": 100,
"page_param": "page",
"page_size_param": "page_size",
"pagination_mode": "PAGINATION_MODE_NORMAL",
"time_field": "last_modified_time",
"fields": ["field1", "field2"]
}'::jsonb,
'{
"success_field": "code",
"success_value": 0,
"message_field": "message",
"list_path": "data.list"
}'::jsonb,
'{
"table_name": "{表名}",
"columns": [
{"name": "id", "type": "BIGINT", "comment": "主键"},
{"name": "name", "type": "VARCHAR(200)", "comment": "名称"},
{"name": "created_time", "type": "BIGINT", "comment": "创建时间"}
],
"conflict_keys": ["id"]
}'::jsonb
);
```
#### 模板 BOAuth2 + prefetch 预取(遍历账户拉取子数据,腾讯广告风格)
```sql
-- 预取接口(先拉取实体列表)
INSERT INTO api_interface (
tenant_id, creator, created_at, updater, updated_at,
platform_id, name, code, url, method, status, auth_type,
request_config, response_config, table_definition
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
(SELECT id FROM api_datasource_platform WHERE platform_code = '{平台编码}'),
'{实体列表名称}', '{实体编码}',
'{实体列表相对路径}', 'GET', 'active', 'inherit',
'{
"parameters_location": "query",
"page": 1,
"page_size": 100,
"page_param": "page",
"page_size_param": "page_size",
"fields": ["id", "name"]
}'::jsonb,
'{
"success_field": "code",
"success_value": 0,
"list_path": "data.list"
}'::jsonb,
'{
"table_name": "{实体表名}",
"columns": [
{"name": "id", "type": "BIGINT", "comment": "实体ID"},
{"name": "name", "type": "VARCHAR(200)", "comment": "实体名称"}
],
"conflict_keys": ["id"]
}'::jsonb
);
-- 数据接口(遍历每个实体拉取数据)
INSERT INTO api_interface (
tenant_id, creator, created_at, updater, updated_at,
platform_id, name, code, url, method, status, auth_type,
request_config, response_config, table_definition
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
(SELECT id FROM api_datasource_platform WHERE platform_code = '{平台编码}'),
'{数据接口名称}', '{数据接口编码}',
'{数据接口相对路径}', 'GET', 'active', 'inherit',
'{
"parameters_location": "query",
"page": 1,
"page_size": 100,
"page_param": "page",
"page_size_param": "page_size",
"time_field": "last_modified_time",
"prefetch": {
"url": "{实体列表相对路径}",
"method": "GET",
"response_path": "data.list",
"target_param": "{参数名}",
"value_field": "{取值字段}"
}
}'::jsonb,
'{
"success_field": "code",
"success_value": 0,
"list_path": "data.list"
}'::jsonb,
'{
"table_name": "{数据表名}",
"columns": [
{"name": "id", "type": "BIGINT", "comment": "ID"},
{"name": "{参数名}", "type": "BIGINT", "comment": "实体ID(预取注入)"},
{"name": "created_time", "type": "BIGINT", "comment": "创建时间"}
],
"conflict_keys": ["id", "{参数名}"]
}'::jsonb
);
```
#### 模板 CAPI_KEY + MD5 签名 + 游标分页 + param 包装(快手风格)
```sql
INSERT INTO api_datasource_platform (
tenant_id, creator, created_at, updater, updated_at,
platform_code, platform_name, description, status,
api_base_url, auth_type,
auth_config,
rate_limit_per_minute, rate_limit_per_hour,
concurrency_limit, request_timeout_ms, max_retries, retry_delay_ms
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
'{平台编码}', '{平台名称}', '{描述}', 'ACTIVE',
'{API_BASE_URL}', 'API_KEY',
'{
"sign_algorithm": "md5_upper",
"app_key": "{YOUR_APP_KEY}",
"app_secret": "{YOUR_APP_SECRET}",
"token_in_query": true,
"query_key": "access_token",
"extra_query_params": {
"timestamp": "{timestamp_ms}",
"signMethod": "MD5"
}
}'::jsonb,
100, 3600, 5, 30000, 3, 1000
);
INSERT INTO api_interface (
tenant_id, creator, created_at, updater, updated_at,
platform_id, name, code, url, method, status, auth_type,
request_config, response_config, table_definition
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
(SELECT id FROM api_datasource_platform WHERE platform_code = '{平台编码}'),
'{接口名称}', '{接口编码}',
'{相对路径}', 'GET', 'active', 'inherit',
'{
"parameters_location": "query",
"page_param": "cursor",
"page_size_param": "pageSize",
"cursor_pagination": true,
"method": "{API方法名}",
"version": 1,
"signMethod": "MD5",
"pageSize": 50,
"time_field": "updateTime",
"time_field_mode": "range",
"body_wrapper_field": "param",
"exclude_from_wrapper": ["method", "version", "signMethod"],
"{其他业务参数}": {值}
}'::jsonb,
'{
"success_field": "result",
"success_value": 1,
"list_path": "data.{数组字段名}",
"cursor_field": "data.cursor",
"cursor_end_marker": "nomore"
}'::jsonb,
'{
"table_name": "{表名}",
"columns": [
{"name": "oid", "type": "BIGINT", "comment": "订单ID"},
{"name": "createTime", "type": "BIGINT", "comment": "创建时间"},
{"name": "updateTime", "type": "BIGINT", "comment": "更新时间"},
{"name": "{字段名}", "type": "{类型}", "comment": "{说明}"}
],
"conflict_keys": ["oid"]
}'::jsonb
);
```
#### 模板 DPOST + JSON Body(腾讯音频风格,无 prefetch,无增量)
```sql
INSERT INTO api_interface (
tenant_id, creator, created_at, updater, updated_at,
platform_id, name, code, url, method, status, auth_type,
request_config, response_config, table_definition
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
(SELECT id FROM api_datasource_platform WHERE platform_code = '{平台编码}'),
'{接口名称}', '{接口编码}',
'{相对路径}', 'POST', 'active', 'inherit',
'{
"page": 1,
"page_size": 100,
"page_param": "page",
"page_size_param": "page_size",
"fields": ["field1", "field2"]
}'::jsonb,
'{
"success_field": "code",
"success_value": 0,
"message_field": "message",
"list_path": "data.list"
}'::jsonb,
'{
"table_name": "{表名}",
"columns": [
{"name": "id", "type": "BIGINT", "comment": "ID"},
{"name": "name", "type": "VARCHAR(200)", "comment": "名称"}
],
"conflict_keys": ["id"]
}'::jsonb
);
```
注意:POST 方法且无 `parameters_location: "query"` 时,参数以 JSON body 形式发送。
#### 模板 EOAuth2 + POST + 无分页(钉钉风格)
```sql
INSERT INTO api_datasource_platform (
tenant_id, creator, created_at, updater, updated_at,
platform_code, platform_name, description, status,
api_base_url, auth_type,
auth_config,
rate_limit_per_minute, rate_limit_per_hour,
concurrency_limit, request_timeout_ms, max_retries, retry_delay_ms
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
'dingtalk', '钉钉', '钉钉开放平台数据同步', 'ACTIVE',
'https://oapi.dingtalk.com', 'OAUTH2',
'{
"token_in_query": true,
"query_key": "access_token"
}'::jsonb,
60, 3600, 5, 30000, 3, 1000
);
INSERT INTO api_interface (
tenant_id, creator, created_at, updater, updated_at,
platform_id, name, code, url, method, status, auth_type,
request_config, response_config, table_definition
) VALUES (
1, 'admin', NOW(), 'admin', NOW(),
(SELECT id FROM api_datasource_platform WHERE platform_code = 'dingtalk'),
'{接口名称}', '{接口编码}',
'{相对路径}', 'POST', 'active', 'inherit',
'{
"parameters_location": "query",
"page_param": "cursor",
"page_size_param": "pageSize",
"{业务参数}": {值}
}'::jsonb,
'{
"success_field": "errcode",
"success_value": 0,
"message_field": "errmsg",
"list_path": "result"
}'::jsonb,
'{
"table_name": "{表名}",
"columns": [
{"name": "id", "type": "BIGINT", "comment": "ID"},
{"name": "name", "type": "VARCHAR(300)", "comment": "名称"}
],
"conflict_keys": ["id"]
}'::jsonb
);
```
注意:钉钉使用 `access_token` 作为 URL 查询参数,通过 `token_in_query: true` + `query_key: "access_token"` 配置。POST 请求通过 `parameters_location: "query"` 将所有参数放在 URL 查询字符串中(DingTalk 同时支持 query 和 body 传参)。
如需递归遍历树形结构(如部门全量),在 `request_config` 中添加:
```json
"recursive": {
"key_field": "dept_id", // 从响应行中提取哪个字段的值用于递归
"target_param": "dept_id" // 递归参数注入到请求中的参数名
},
"max_recursive_depth": 20 // 可选,最大递归深度,默认20
```
递归流程:BFS 遍历,先查根级空参 → 提取每行的 `key_field` → 逐级注入 `target_param` 查询下级 → 防重复循环保护 → 所有结果合并入库。
## 新增接口步骤总结
1. **确定平台**:已有平台直接跳到第3步,否则先新增平台(参考对应认证类型的模板)
2. **确定平台 auth_type**OAUTH2 / TOKEN / API_KEY / SIGN → 复制对应模板的 auth_config
3. **分析 API**
- 请求方式(GET/POST
- 分页方式(普通分页 → page + total_page / 游标分页 → cursor + cursor_end
- 成功标识(code=0 / result=1 / errcode=0 等)
- 数据列表路径(data.list / data.orderList / data.records 等)
- 数据字段名(注意大小写,嵌套结构会被展开)
- 是否需要遍历实体(prefetch)
- 增量时间字段(字段名 + 值类型)
4. **选择模板**并填入对应值
> ⚠️ **全量同步起始时间**:如果接口数据量很大,首次全量不想从 90 天前拉取,可以在 `request_config` 中加 `"full_sync_start_time": <毫秒时间戳>` 指定起点。例如只同步最近 30 天:
> ```json
> "full_sync_start_time": 1745942400000
> ```
> - **range 模式**(快手 `time_field_mode: "range"`):全量时取此时间作为 beginTime
> - **filtering 模式**(腾讯 `time_field_mode: "filtering"`):全量时生成 `GREATER_EQUALS` 过滤条件
> - **不配此字段**:行为不变(range 回退 90 天,filtering 不传时间过滤)
5. **执行 seed SQL**
6. **在管理后台验证**`http://{host}:3002/admin`
## 代码审计发现的项目规范
### 包命名规范(2026-06-03 审计)
- `model/dto/dict/` 下的 `.go` 文件必须用 `package dict`(曾错误写为 `package api_feature`,已修复)
- `model/entity/dict/` 下的 `.go` 文件用 `package dict`
- `consts/api-feature/` 目录下的文件用 `package api_feature`
- `consts/public/``package public`
- **不要**在 `consts/dict/` 中重复定义 `PlatformStatus`/`ApiMethod` 等类型,应使用 `consts/api-feature` 中的定义
### 数据库状态值规范
- `api_datasource_platform.status`:使用大写 `"ACTIVE"` / `"INACTIVE"`
- `api_interface.status`:使用小写 `"active"` / `"inactive"`
- 代码中查平台状态时不要使用 `api_feature.PlatformStatusActive`(其值为小写),应直接用 `"ACTIVE"` 字符串
### Known Bad Practices(已知待改进项)
- 敏感信息(access_token / app_secret)仍分散在 SQL seed文件和 config.yml 中,建议迁移到环境变量或 Vault
- `consts/dict/consts.go``scheduler/run_sync_task_log_task.go` 可能随业务扩展需要更新
## 通用报表引擎 (common/report)
2026-06-10: 通用报表公共包,配置驱动的报表子系统,支持前端自由选择维度/指标/筛选/时间查询。
### 架构分层 (9个Go文件)
```
common/report/
├── api.go # ReportService 统一门面(单例)
├── report.go # 5张系统表 DDL
├── example_usage.go # 完整使用示例文档
├── model/model.go # 实体/请求/响应/常量
├── config/loader.go # 配置加载器(读缓存+CRUD)
├── builder/sql_builder.go # 动态 SQL 构建
├── executor/executor.go # 查询执行器
├── extract/extract.go # 天级数据抽取(DIRECT/AGGREGATE
└── ddlsync/creator.go # 统计宽表自动创建
```
### 核心能力
1. **自动建表**:根据 FieldConfig 自动 CREATE TABLE stat_xxx
2. **数据抽取**DIRECT(逐行)/ AGGREGATE(聚合 SUM/COUNT)模式,幂等保证
3. **动态查询**:前端选维度+指标+筛选+时间 → 实时构建 SQL → 分页返回
4. **配置 CRUD**2026-06-10 新增):BusinessConfig/ReportConfig/FieldConfig/ExtractConfig 全部可前端维护
### 对外接口 (ReportService)
- 查询:`QueryReportByUserSelect` / `GetReportFields` / `GetAllBusinesses` / `GetAllReports`
- 抽取:`ExtractDailyData` / `AutoCreateStatTable`
- CRUD`SaveBusiness/DeleteBusiness/GetBusiness``SaveReport/DeleteReport/GetReport``SaveField/DeleteField/GetField``SaveExtractConfig/DeleteExtractConfig/GetExtractConfig/GetExtractConfigs`
### 设计原则
- 零硬编码:任意平台/接口通过配置接入,不改代码
- 配置驱动:5张 PG 表存储全部配置,CRUD API 支持前端维护
- 单例模式:`report.GetService()` 可在任意业务服务中直接调用
+1
View File
@@ -0,0 +1 @@
.git
+4
View File
@@ -1 +1,5 @@
/.idea/*
/data-engine
/resource/*
.gstack/
/docs/*
+36
View File
@@ -0,0 +1,36 @@
# data-engine
> ⚠️ **启动时必读:** 每次会话开始时,先读取 `docs/INDEX.md` 了解项目全景和最近变更。
## Skill routing
When the user's request matches an available skill, invoke it via the Skill tool. When in doubt, invoke the skill.
Key routing rules:
- Product ideas/brainstorming → invoke /office-hours
- Strategy/scope → invoke /plan-ceo-review
- Architecture → invoke /plan-eng-review
- Design system/plan review → invoke /design-consultation or /plan-design-review
- Full review pipeline → invoke /autoplan
- Bugs/errors → invoke /investigate
- QA/testing site behavior → invoke /qa or /qa-only
- Code review/diff check → invoke /review
- Visual polish → invoke /design-review
- Ship/deploy/PR → invoke /ship or /land-and-deploy
- Save progress → invoke /context-save
- Resume context → invoke /context-restore
- Author a backlog-ready spec/issue → invoke /spec
## Agent skills
### Issue tracker
Issues tracked as local markdown files under `.scratch/`. See `docs/agents/issue-tracker.md`.
### Triage labels
Default five-role vocabulary (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`.
### Domain docs
Single-context repo. See `docs/agents/domain.md`.
+21 -53
View File
@@ -1,57 +1,25 @@
FROM golang:1.25.3
RUN go env -w GO111MODULE=on
RUN go env -w GOPROXY=https://goproxy.cn,direct
ENV WORKDIR /usr/local/bin/app
WORKDIR $WORKDIR
# 阶段1: 构建
FROM golang:alpine AS builder
ENV TIME_ZONE=Asia/Seoul
RUN useradd -ms /bin/bash golang
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk add --no-cache git ca-certificates tzdata
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn,direct
ENV CGO_ENABLED=0
ENV GOTOOLCHAIN=auto
WORKDIR /build
COPY . .
RUN go mod download && go mod tidy
RUN go build -ldflags="-s -w" -o main ./main.go
RUN ln -sf /usr/share/zoneinfo/$TIME_ZONE /etc/localtime \
&& chmod 0755 /usr/bin/wall \
&& chmod 0755 /usr/bin/passwd \
&& chmod 0755 /usr/bin/newgrp \
&& chmod 0755 /usr/bin/chfn \
&& chmod 0755 /usr/bin/chage \
&& chmod 0755 /usr/bin/gpasswd \
&& chmod 0755 /usr/bin/chsh \
&& chmod 0755 /usr/bin/expiry \
&& chmod 0755 /bin/umount \
&& chmod 0755 /bin/mount \
&& chmod 0755 /bin/su \
&& chmod 0755 /sbin/unix_chkpwd \
&& chmod 110 /usr/bin/chfn \
&& chmod 110 /usr/bin/passwd \
&& chmod 110 /usr/bin/newgrp \
&& chmod 110 /usr/bin/chsh \
&& chmod 110 /usr/bin/wall \
&& chmod 110 /usr/bin/gpasswd \
&& chmod 110 /usr/bin/expiry \
&& chmod 110 /usr/bin/chage \
&& chmod 110 /bin/mount \
&& chmod 110 /bin/umount \
&& chmod 110 /bin/su \
&& chmod 110 /sbin/unix_chkpwd \
&& chmod 110 /usr/lib/openssh/ssh-keysign \
&& chmod 110 /usr/bin/ssh-agent
EXPOSE 3013
COPY go.mod go.sum ./
# RUN chown -R golang:golang $WORKDIR
RUN go mod download && go mod verify
COPY ../../cid $WORKDIR
RUN chown -R golang:golang $WORKDIR
# Remove SetUID, SetGID
RUN chmod 0755 /usr/local/bin/app/api \
&& chmod 0755 /usr/local/bin/app/common \
&& chmod 0755 /usr/local/bin/app/middleware \
&& chmod 0755 /usr/local/bin/app/model \
&& rm -rf .git \
&& rm -rf .gitignore \
&& rm -rf .gitlab-ci.yml \
&& rm -rf Dockerfile.bak \
&& rm -rf config-local.yml
USER golang
RUN go build -v -o /usr/local/bin/app ./...
EXPOSE 3002
CMD ./cid
CMD ["./main"]
+900
View File
@@ -0,0 +1,900 @@
package report
import (
"context"
"fmt"
"strings"
"time"
"dataengine/common/report/config"
"dataengine/common/report/ddlsync"
"dataengine/common/report/executor"
"dataengine/common/report/extract"
"dataengine/common/report/model"
"gitea.redpowerfuture.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
)
// getUserNameFromCtx 从上下文中获取当前用户名
func getUserNameFromCtx(ctx context.Context) string {
if user, ok := ctx.Value("user").(*beans.User); ok && user != nil && user.UserName != "" {
return user.UserName
}
return "system"
}
// ReportService 报表公共服务
// 对外暴露的统一接口
type ReportService struct {
configLoader *config.ConfigLoader
tableCreator *ddlsync.StatTableCreator
queryExecutor *executor.QueryExecutor
dailyExtractor *extract.DailyExtractor
}
var defaultService *ReportService
// GetService 获取报表服务单例
func GetService() *ReportService {
if defaultService == nil {
defaultService = &ReportService{
configLoader: config.GetLoader(),
tableCreator: ddlsync.NewStatTableCreator(),
queryExecutor: executor.NewQueryExecutor(),
dailyExtractor: extract.NewDailyExtractor(),
}
}
return defaultService
}
// ============================================================
// 核心接口 1: 自动创建统计宽表
// 首次抽取前调用
// ============================================================
// AutoCreateStatTable 根据配置自动创建统计宽表
// businessCode: 业务编码
// reportCode: 报表编码
func (s *ReportService) AutoCreateStatTable(ctx context.Context, businessCode, reportCode string) (*model.AutoCreateStatTableResp, error) {
// 初始化系统表
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
resp, err := s.tableCreator.AutoCreateStatTable(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("AutoCreateStatTable 失败: %w", err)
}
return resp, nil
}
// ============================================================
// 核心接口 2: 按天抽取数据
// 业务层定时任务调用
// ============================================================
// ExtractDailyData 按天抽取数据
// businessCode: 业务编码
// reportCode: 报表编码
// statDate: 统计日期 yyyy-MM-dd
// executor: 执行人
func (s *ReportService) ExtractDailyData(ctx context.Context, businessCode, reportCode, statDate, executor string) (*model.ExtractDailyDataResp, error) {
// 1. 先确保统计宽表存在
report, err := s.configLoader.GetReport(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("获取报表配置失败: %w", err)
}
// 检查表是否存在
result, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT EXISTS (SELECT 1 FROM pg_tables WHERE tablename = $1) AS exists",
strings.ToLower(report.StatTableName))
if err != nil {
return nil, fmt.Errorf("检查统计宽表失败: %w", err)
}
tableExists := false
if len(result) > 0 {
tableExists = result[0]["exists"].Bool()
}
if !tableExists {
// 表不存在,先创建
if _, createErr := s.AutoCreateStatTable(ctx, businessCode, reportCode); createErr != nil {
return nil, fmt.Errorf("创建统计宽表失败: %w", createErr)
}
}
resp, err := s.dailyExtractor.ExtractDailyData(ctx, businessCode, reportCode, statDate, executor)
if err != nil {
return nil, fmt.Errorf("ExtractDailyData 失败: %w", err)
}
// 清除缓存
s.configLoader.InvalidateCache(businessCode, reportCode)
return resp, nil
}
// ============================================================
// 核心接口 3: 批量回填数据
// 首次接入时对日期范围逐天抽取
// ============================================================
// Backfill 批量回填数据
// 遍历 startDate → endDate 逐天调用 ExtractDailyData
// 返回汇总统计
func (s *ReportService) Backfill(ctx context.Context, req *model.BackfillReq) (*model.BackfillResp, error) {
start := time.Now()
startDate, err := time.Parse("2006-01-02", req.StartDate)
if err != nil {
return nil, fmt.Errorf("开始日期格式错误: %w", err)
}
endDate, err := time.Parse("2006-01-02", req.EndDate)
if err != nil {
return nil, fmt.Errorf("结束日期格式错误: %w", err)
}
if endDate.Before(startDate) {
return nil, fmt.Errorf("结束日期不能早于开始日期")
}
totalDays := 0
successDays := 0
failDays := 0
var lastErr error
for d := startDate; !d.After(endDate); d = d.AddDate(0, 0, 1) {
totalDays++
dateStr := d.Format("2006-01-02")
resp, err := s.ExtractDailyData(ctx, req.BusinessCode, req.ReportCode, dateStr, req.Executor)
if err != nil {
failDays++
lastErr = fmt.Errorf("回填 %s 失败: %w", dateStr, err)
continue
}
if !resp.Success {
failDays++
lastErr = fmt.Errorf("回填 %s 异常: %s", dateStr, resp.ErrorMsg)
continue
}
successDays++
}
execTime := time.Since(start).Milliseconds()
resp := &model.BackfillResp{
Success: failDays == 0,
TotalDays: totalDays,
SuccessDays: successDays,
FailDays: failDays,
ExecTimeMs: execTime,
}
if lastErr != nil {
resp.ErrorMsg = lastErr.Error()
}
return resp, nil
}
// ============================================================
// 核心接口 4: 用户选择查询(最核心)
// 前端用户选择条件 → 实时构建SQL → 返回报表数据
// ============================================================
// QueryReportByUserSelect 根据用户选择实时查询报表数据
// 不是自动生成报表,是用户在前端选择维度/指标/筛选/时间后实时查询展示
func (s *ReportService) QueryReportByUserSelect(ctx context.Context, req *model.UserSelectQueryReq) (*model.UserSelectQueryResp, error) {
// 参数校验
if req.BusinessCode == "" {
return nil, fmt.Errorf("businessCode 不能为空")
}
if req.ReportCode == "" {
return nil, fmt.Errorf("reportCode 不能为空")
}
resp, err := s.queryExecutor.QueryReportByUserSelect(ctx, req)
if err != nil {
return nil, fmt.Errorf("QueryReportByUserSelect 失败: %w", err)
}
return resp, nil
}
// ============================================================
// 辅助接口
// ============================================================
// GetReportFields 获取报表可用字段(按维度/指标/筛选分类)
func (s *ReportService) GetReportFields(ctx context.Context, businessCode, reportCode string) (*model.GetReportFieldsResp, error) {
resp, err := s.configLoader.GetReportFields(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("GetReportFields 失败: %w", err)
}
return resp, nil
}
// GetAllBusinesses 获取所有启用业务列表
// ListBusinesses 分页获取业务列表
func (s *ReportService) ListBusinesses(ctx context.Context, pageNum, pageSize int) ([]model.BusinessConfig, int, error) {
return s.configLoader.ListBusinesses(ctx, pageNum, pageSize)
}
func (s *ReportService) GetAllBusinesses(ctx context.Context) ([]model.BusinessConfig, error) {
return s.configLoader.GetAllBusinesses(ctx)
}
// GetAllReports 获取业务下所有报表列表
// ListReports 分页获取报表列表
func (s *ReportService) ListReports(ctx context.Context, businessCode, reportName string, pageNum, pageSize int) ([]model.ReportConfig, int, error) {
return s.configLoader.ListReports(ctx, businessCode, reportName, pageNum, pageSize)
}
func (s *ReportService) GetAllReports(ctx context.Context, businessCode string) ([]model.ReportConfig, error) {
return s.configLoader.GetAllReports(ctx, businessCode)
}
// InvalidateCache 失效指定业务报表缓存
func (s *ReportService) InvalidateCache(businessCode, reportCode string) {
s.configLoader.InvalidateCache(businessCode, reportCode)
}
// InitSystemTables 初始化系统表
func (s *ReportService) InitSystemTables(ctx context.Context) error {
return initTables(ctx)
}
// ============================================================
// 配置 CRUD: 业务
// ============================================================
// SaveBusiness 保存业务配置(新增/修改合一)
func (s *ReportService) SaveBusiness(ctx context.Context, req *model.SaveBusinessReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
// 新增时校验 businessCode 唯一性
if req.ID == nil || *req.ID == 0 {
r, err := gfdb.DB(ctx).GetAll(ctx, "SELECT id FROM report_business_config WHERE business_code = $1 LIMIT 1", req.BusinessCode)
if err != nil {
return nil, fmt.Errorf("校验业务编码失败: %w", err)
}
if !r.IsEmpty() {
return nil, fmt.Errorf("业务编码 %s 已存在", req.BusinessCode)
}
}
userName := getUserNameFromCtx(ctx)
biz := &model.BusinessConfig{
SQLBaseDO: beans.SQLBaseDO{Creator: userName, Updater: userName, TenantId: 1},
BusinessCode: req.BusinessCode,
BusinessName: req.BusinessName,
Description: req.Description,
Status: req.Status,
Config: req.Config,
}
if req.Status == "" {
biz.Status = model.StatusActive
}
if biz.Config == nil {
biz.Config = make(map[string]interface{})
}
if req.ID != nil && *req.ID > 0 {
// 更新
biz.Id = *req.ID
if err := s.configLoader.UpdateBusiness(ctx, biz); err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: *req.ID, Message: "更新成功"}, nil
}
// 新增
id, err := s.configLoader.CreateBusiness(ctx, biz)
if err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: id, Message: "创建成功"}, nil
}
// DeleteBusiness 删除业务配置
func (s *ReportService) DeleteBusiness(ctx context.Context, id int64) (*model.DeleteResult, error) {
biz, err := s.configLoader.GetBusinessByID(ctx, id)
if err != nil {
return nil, err
}
// 级联检查:该业务下是否存在报表
r, err := gfdb.DB(ctx).GetAll(ctx, "SELECT COUNT(1) AS cnt FROM report_report_config WHERE business_code = $1 AND deleted_at IS NULL LIMIT 1", biz.BusinessCode)
if err == nil && !r.IsEmpty() {
cnt := r[0]["cnt"].Int()
if cnt > 0 {
return &model.DeleteResult{Success: false, Message: fmt.Sprintf("业务 %s 下存在 %d 个报表配置,请先删除关联报表后再删除业务", biz.BusinessCode, cnt)}, nil
}
}
if err := s.configLoader.DeleteBusiness(ctx, id, biz.BusinessCode); err != nil {
return nil, err
}
return &model.DeleteResult{Success: true, Message: "删除成功"}, nil
}
// GetBusiness 获取单个业务配置
func (s *ReportService) GetBusiness(ctx context.Context, id int64) (*model.BusinessConfig, error) {
return s.configLoader.GetBusinessByID(ctx, id)
}
// ============================================================
// 配置 CRUD: 报表
// ============================================================
// SaveReport 保存报表配置(新增/修改合一)
func (s *ReportService) SaveReport(ctx context.Context, req *model.SaveReportReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
// 新增时校验 reportCode 唯一性(同业务下)
if req.ID == nil || *req.ID == 0 {
r, err := gfdb.DB(ctx).GetAll(ctx, "SELECT id FROM report_report_config WHERE business_code = $1 AND report_code = $2 LIMIT 1",
req.BusinessCode, req.ReportCode)
if err != nil {
return nil, fmt.Errorf("校验报表编码失败: %w", err)
}
if !r.IsEmpty() {
return nil, fmt.Errorf("业务 %s 下报表编码 %s 已存在", req.BusinessCode, req.ReportCode)
}
}
userName := getUserNameFromCtx(ctx)
rpt := &model.ReportConfig{
SQLBaseDO: beans.SQLBaseDO{Creator: userName, Updater: userName, TenantId: 1},
BusinessCode: req.BusinessCode,
ReportCode: req.ReportCode,
ReportName: req.ReportName,
Description: req.Description,
Status: req.Status,
StatTableName: req.StatTableName,
StatTableComment: req.StatTableComment,
DateField: req.DateField,
PrimaryKeys: req.PrimaryKeys,
ConflictKeys: req.ConflictKeys,
Config: req.Config,
}
if req.Status == "" {
rpt.Status = model.StatusActive
}
if rpt.DateField == "" {
rpt.DateField = "stat_date"
}
if rpt.PrimaryKeys == nil {
rpt.PrimaryKeys = []string{"id"}
}
if rpt.ConflictKeys == nil {
rpt.ConflictKeys = []string{rpt.DateField}
}
if rpt.Config == nil {
rpt.Config = make(map[string]interface{})
}
if req.ID != nil && *req.ID > 0 {
rpt.Id = *req.ID
if err := s.configLoader.UpdateReport(ctx, rpt); err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: *req.ID, Message: "更新成功"}, nil
}
id, err := s.configLoader.CreateReport(ctx, rpt)
if err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: id, Message: "创建成功"}, nil
}
// DeleteReport 删除报表配置
func (s *ReportService) DeleteReport(ctx context.Context, id int64) (*model.DeleteResult, error) {
rpt, err := s.configLoader.GetReportByID(ctx, id)
if err != nil {
return nil, err
}
// 级联检查:该报表下是否存在未删除的字段
r, err := gfdb.DB(ctx).GetAll(ctx, "SELECT COUNT(1) AS cnt FROM report_field_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL LIMIT 1",
rpt.BusinessCode, rpt.ReportCode)
if err == nil && !r.IsEmpty() && r[0]["cnt"].Int() > 0 {
return &model.DeleteResult{Success: false, Message: fmt.Sprintf("报表 %s 下存在字段配置,请先删除关联字段后再删除报表", rpt.ReportCode)}, nil
}
// 级联检查:该报表下是否存在抽取配置
r, err = gfdb.DB(ctx).GetAll(ctx, "SELECT COUNT(1) AS cnt FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL LIMIT 1",
rpt.BusinessCode, rpt.ReportCode)
if err == nil && !r.IsEmpty() && r[0]["cnt"].Int() > 0 {
return &model.DeleteResult{Success: false, Message: fmt.Sprintf("报表 %s 下存在抽取配置,请先删除关联抽取配置后再删除报表", rpt.ReportCode)}, nil
}
if err := s.configLoader.DeleteReport(ctx, id, rpt.BusinessCode, rpt.ReportCode); err != nil {
return nil, err
}
return &model.DeleteResult{Success: true, Message: "删除成功"}, nil
}
// GetReport 获取单个报表配置
func (s *ReportService) GetReport(ctx context.Context, id int64) (*model.ReportConfig, error) {
rpt, err := s.configLoader.GetReportByID(ctx, id)
if err != nil {
return nil, err
}
// 同时加载全部字段(含已失效),编辑时前端可直接回显
allFields, err := s.configLoader.GetAllFields(ctx, rpt.BusinessCode, rpt.ReportCode)
if err == nil {
rpt.Fields = allFields
}
return rpt, nil
}
// ============================================================
// 合并操作:报表+字段一起保存
// ============================================================
// SaveReportWithFields 保存报表及字段配置(报表+字段全量替换)
func (s *ReportService) SaveReportWithFields(ctx context.Context, req *model.SaveReportWithFieldsReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
// 自动填充每个字段的 businessCode/reportCode(从报表级继承)
for i := range req.Fields {
if req.Fields[i].BusinessCode == "" {
req.Fields[i].BusinessCode = req.BusinessCode
}
if req.Fields[i].ReportCode == "" {
req.Fields[i].ReportCode = req.ReportCode
}
}
var reportID int64
userName := getUserNameFromCtx(ctx)
// 事务:要么全成功,要么不动
err := gfdb.DB(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 1. 保存报表(新增或更新)
now := time.Now()
if req.ID != nil && *req.ID > 0 {
// 更新报表
data := map[string]interface{}{
"business_code": req.BusinessCode,
"report_code": req.ReportCode,
"report_name": req.ReportName,
"description": req.Description,
"status": req.Status,
"stat_table_name": req.StatTableName,
"stat_table_comment": req.StatTableComment,
"date_field": req.DateField,
"primary_keys": req.PrimaryKeys,
"conflict_keys": req.ConflictKeys,
"config": req.Config,
"updated_at": now,
"updater": userName,
}
if _, err := tx.Model("report_report_config").Where("id", *req.ID).Data(data).Update(); err != nil {
return fmt.Errorf("更新报表失败: %w", err)
}
reportID = *req.ID
} else {
// 新增时校验 reportCode 唯一性
cnt, err := tx.Model("report_report_config").Where("business_code", req.BusinessCode).Where("report_code", req.ReportCode).Count()
if err != nil {
return fmt.Errorf("校验报表编码失败: %w", err)
}
if cnt > 0 {
return fmt.Errorf("业务 %s 下报表编码 %s 已存在", req.BusinessCode, req.ReportCode)
}
data := map[string]interface{}{
"business_code": req.BusinessCode,
"report_code": req.ReportCode,
"report_name": req.ReportName,
"description": req.Description,
"status": ifVal(req.Status, model.StatusActive),
"stat_table_name": req.StatTableName,
"stat_table_comment": req.StatTableComment,
"date_field": ifVal(req.DateField, "stat_date"),
"primary_keys": req.PrimaryKeys,
"conflict_keys": req.ConflictKeys,
"config": req.Config,
"created_at": now,
"updated_at": now,
"creator": userName,
"updater": userName,
"tenant_id": 1,
}
if data["primary_keys"] == nil || len(data["primary_keys"].([]string)) == 0 {
data["primary_keys"] = []string{"id"}
}
if data["conflict_keys"] == nil || len(data["conflict_keys"].([]string)) == 0 {
data["conflict_keys"] = []string{ifVal(req.DateField, "stat_date")}
}
result, err := tx.Model("report_report_config").Data(data).Insert()
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
return fmt.Errorf("业务 %s 下报表编码 %s 已存在", req.BusinessCode, req.ReportCode)
}
return fmt.Errorf("创建报表失败: %w", err)
}
rid, err := result.LastInsertId()
if err != nil {
return fmt.Errorf("获取自增ID失败: %w", err)
}
reportID = rid
}
// 2. 获取报表下现有字段(含已删除,编辑时重新添加的字段需走 UPDATE 而非 INSERT
rows, err := tx.Model("report_field_config").
Where("business_code", req.BusinessCode).
Where("report_code", req.ReportCode).
All()
if err != nil {
return fmt.Errorf("获取现有字段失败: %w", err)
}
existingMap := make(map[string]int64) // fieldCode -> id
for _, row := range rows {
existingMap[row["field_code"].String()] = row["id"].Int64()
}
// 3. 处理请求中的字段(新增或更新)
for i := range req.Fields {
f := &req.Fields[i]
status := ifVal(f.Status, model.StatusActive)
dataType := ifVal(f.DataType, model.FieldTypeString)
if existingID, ok := existingMap[f.FieldCode]; ok {
// 已存在 → 更新
updateData := map[string]interface{}{
"field_name": f.FieldName,
"field_type": f.FieldType,
"data_type": dataType,
"field_role": f.FieldRole,
"is_aggregatable": f.IsAggregatable,
"is_filterable": f.IsFilterable,
"is_queryable": f.IsQueryable,
"is_sortable": f.IsSortable,
"default_aggregate": f.DefaultAggregate,
"valid_aggregates": f.ValidAggregates,
"filter_operators": f.FilterOperators,
"expression": f.Expression,
"expression_type": f.ExpressionType,
"format_pattern": f.FormatPattern,
"unit": f.Unit,
"dict_code": f.DictCode,
"sort_order": f.SortOrder,
"group_name": f.GroupName,
"status": status,
"updated_at": now,
"updater": userName,
}
if _, err := tx.Model("report_field_config").Where("id", existingID).Data(updateData).Update(); err != nil {
return fmt.Errorf("更新字段 %s 失败: %w", f.FieldCode, err)
}
delete(existingMap, f.FieldCode)
} else {
// 不存在 → 新增
insertData := map[string]interface{}{
"business_code": req.BusinessCode,
"report_code": req.ReportCode,
"field_code": f.FieldCode,
"field_name": f.FieldName,
"field_type": f.FieldType,
"data_type": dataType,
"field_role": f.FieldRole,
"is_aggregatable": f.IsAggregatable,
"is_filterable": f.IsFilterable,
"is_queryable": f.IsQueryable,
"is_sortable": f.IsSortable,
"default_aggregate": f.DefaultAggregate,
"valid_aggregates": f.ValidAggregates,
"filter_operators": f.FilterOperators,
"expression": f.Expression,
"expression_type": f.ExpressionType,
"format_pattern": f.FormatPattern,
"unit": f.Unit,
"dict_code": f.DictCode,
"sort_order": f.SortOrder,
"group_name": f.GroupName,
"status": status,
"created_at": now,
"updated_at": now,
"creator": userName,
"updater": userName,
"tenant_id": 1,
}
if _, err := tx.Model("report_field_config").Data(insertData).Insert(); err != nil {
if strings.Contains(err.Error(), "duplicate key") {
return fmt.Errorf("字段编码 %s 在当前报表下已存在,请更换字段编码", f.FieldCode)
}
return fmt.Errorf("创建字段 %s 失败: %w", f.FieldCode, err)
}
}
}
// 4. 删除剩余字段(不在请求中的旧字段)
for fieldCode, existingID := range existingMap {
if _, err := tx.Model("report_field_config").Where("id", existingID).Data(map[string]interface{}{
"status": model.StatusInactive,
"deleted_at": now,
"updated_at": now,
"updater": userName,
}).Update(); err != nil {
return fmt.Errorf("删除字段 %s 失败: %w", fieldCode, err)
}
}
return nil
})
if err != nil {
return nil, err
}
// 清理缓存
s.configLoader.InvalidateCache(req.BusinessCode, req.ReportCode)
return &model.SaveResult{Success: true, ID: reportID, Message: "报表及字段保存成功"}, nil
}
// ============================================================
// 配置 CRUD: 字段
// ============================================================
// SaveField 保存字段配置(新增/修改合一)
func (s *ReportService) SaveField(ctx context.Context, req *model.SaveFieldReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
if req.BusinessCode == "" {
return nil, fmt.Errorf("businessCode 不能为空")
}
if req.ReportCode == "" {
return nil, fmt.Errorf("reportCode 不能为空")
}
userName := getUserNameFromCtx(ctx)
field := &model.FieldConfig{
SQLBaseDO: beans.SQLBaseDO{Creator: userName, Updater: userName, TenantId: 1},
BusinessCode: req.BusinessCode,
ReportCode: req.ReportCode,
FieldCode: req.FieldCode,
FieldName: req.FieldName,
FieldType: req.FieldType,
DataType: req.DataType,
FieldRole: req.FieldRole,
IsAggregatable: req.IsAggregatable,
IsFilterable: req.IsFilterable,
IsQueryable: req.IsQueryable,
IsSortable: req.IsSortable,
DefaultAggregate: req.DefaultAggregate,
ValidAggregates: req.ValidAggregates,
FilterOperators: req.FilterOperators,
Expression: req.Expression,
ExpressionType: req.ExpressionType,
FormatPattern: req.FormatPattern,
Unit: req.Unit,
DictCode: req.DictCode,
SortOrder: req.SortOrder,
GroupName: req.GroupName,
Status: req.Status,
}
// 校验字段类型
validFieldTypes := map[string]bool{
model.FieldTypeString: true,
model.FieldTypeInt: true,
model.FieldTypeFloat: true,
model.FieldTypeDate: true,
model.FieldTypeDatetime: true,
model.FieldTypeJsonb: true,
}
if req.FieldType != "" && !validFieldTypes[req.FieldType] {
return nil, fmt.Errorf("不支持的字段类型: %s,仅支持 STRING/INT/FLOAT/DATE/DATETIME/JSONB", req.FieldType)
}
if req.DataType != "" && !validFieldTypes[req.DataType] {
return nil, fmt.Errorf("不支持的存储类型: %s,仅支持 STRING/INT/FLOAT/DATE/DATETIME/JSONB", req.DataType)
}
if req.Status == "" {
field.Status = model.StatusActive
}
if field.DataType == "" {
field.DataType = model.FieldTypeString
}
if field.ValidAggregates == nil {
field.ValidAggregates = []string{}
}
if field.FilterOperators == nil {
field.FilterOperators = []string{"=", "!=", ">", "<", ">=", "<=", "IN", "LIKE", "BETWEEN"}
}
// 新增时校验字段编码唯一性
if req.ID == nil || *req.ID == 0 {
existing, err := s.configLoader.GetReportFields(ctx, req.BusinessCode, req.ReportCode)
if err != nil {
return nil, fmt.Errorf("校验字段编码失败: %w", err)
}
allFields := append(append(existing.Dimensions, existing.Indicators...), existing.Filters...)
for _, f := range allFields {
if f.FieldCode == req.FieldCode && f.Status == model.StatusActive {
return nil, fmt.Errorf("字段编码 %s 已存在(业务: %s, 报表: %s", req.FieldCode, req.BusinessCode, req.ReportCode)
}
}
}
if req.ID != nil && *req.ID > 0 {
field.Id = *req.ID
if err := s.configLoader.UpdateField(ctx, field); err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: *req.ID, Message: "更新成功"}, nil
}
id, err := s.configLoader.CreateField(ctx, field)
if err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: id, Message: "创建成功"}, nil
}
// DeleteField 删除字段配置
func (s *ReportService) DeleteField(ctx context.Context, id int64) (*model.DeleteResult, error) {
field, err := s.configLoader.GetFieldByID(ctx, id)
if err != nil {
return nil, err
}
if err := s.configLoader.DeleteField(ctx, id, field.BusinessCode, field.ReportCode); err != nil {
return nil, err
}
return &model.DeleteResult{Success: true, Message: "删除成功"}, nil
}
// GetField 获取单个字段配置
func (s *ReportService) GetField(ctx context.Context, id int64) (*model.FieldConfig, error) {
return s.configLoader.GetFieldByID(ctx, id)
}
// ============================================================
// 配置 CRUD: 抽取配置
// ============================================================
// SaveExtractConfig 保存抽取配置(新增/修改合一)
func (s *ReportService) SaveExtractConfig(ctx context.Context, req *model.SaveExtractConfigReq) (*model.SaveResult, error) {
if err := initTables(ctx); err != nil {
return nil, fmt.Errorf("初始化系统表失败: %w", err)
}
// 新增时校验 extractCode 唯一性(同业务+报表下)
if req.ID == nil || *req.ID == 0 {
r, err := gfdb.DB(ctx).GetAll(ctx, "SELECT id FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND extract_code = $3 LIMIT 1",
req.BusinessCode, req.ReportCode, req.ExtractCode)
if err != nil {
return nil, fmt.Errorf("校验抽取编码失败: %w", err)
}
if !r.IsEmpty() {
return nil, fmt.Errorf("业务 %s/报表 %s 下抽取编码 %s 已存在", req.BusinessCode, req.ReportCode, req.ExtractCode)
}
}
userName := getUserNameFromCtx(ctx)
ec := &model.ExtractConfig{
SQLBaseDO: beans.SQLBaseDO{Creator: userName, Updater: userName, TenantId: 1},
BusinessCode: req.BusinessCode,
ReportCode: req.ReportCode,
ExtractCode: req.ExtractCode,
ExtractName: req.ExtractName,
SourceTableName: req.SourceTableName,
SourceTableAlias: req.SourceTableAlias,
TargetTableName: req.TargetTableName,
IsEnabled: req.IsEnabled,
ExtractType: req.ExtractType,
ExtractMode: req.ExtractMode,
ExtractKeyField: req.ExtractKeyField,
ExtractKeyFormat: req.ExtractKeyFormat,
GroupByFields: req.GroupByFields,
FilterExpression: req.FilterExpression,
JoinConfigs: req.JoinConfigs,
FieldMappings: req.FieldMappings,
TransformRules: req.TransformRules,
BatchSize: req.BatchSize,
Status: req.Status,
}
if req.Status == "" {
ec.Status = model.StatusActive
}
if ec.ExtractType == "" {
ec.ExtractType = model.ExtractTypeIncremental
}
if ec.ExtractMode == "" {
ec.ExtractMode = model.ExtractModeDirect
}
if ec.BatchSize == 0 {
ec.BatchSize = 1000
}
if ec.JoinConfigs == nil {
ec.JoinConfigs = []model.JoinConfig{}
}
if ec.FieldMappings == nil {
ec.FieldMappings = []model.FieldMapping{}
}
if ec.TransformRules == nil {
ec.TransformRules = []model.TransformRule{}
}
if ec.GroupByFields == nil {
ec.GroupByFields = []string{}
}
if req.ID != nil && *req.ID > 0 {
ec.Id = *req.ID
if err := s.configLoader.UpdateExtractConfig(ctx, ec); err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: *req.ID, Message: "更新成功"}, nil
}
id, err := s.configLoader.CreateExtractConfig(ctx, ec)
if err != nil {
return nil, err
}
return &model.SaveResult{Success: true, ID: id, Message: "创建成功"}, nil
}
// DeleteExtractConfig 删除抽取配置
func (s *ReportService) DeleteExtractConfig(ctx context.Context, id int64) (*model.DeleteResult, error) {
ec, err := s.configLoader.GetExtractConfigByID(ctx, id)
if err != nil {
return nil, err
}
if err := s.configLoader.DeleteExtractConfig(ctx, id, ec.BusinessCode, ec.ReportCode); err != nil {
return nil, err
}
return &model.DeleteResult{Success: true, Message: "删除成功"}, nil
}
// GetExtractConfig 获取单个抽取配置
func (s *ReportService) GetExtractConfig(ctx context.Context, id int64) (*model.ExtractConfig, error) {
return s.configLoader.GetExtractConfigByID(ctx, id)
}
// GetExtractConfigs 获取业务报表下所有抽取配置
// ListExtractConfigs 分页获取抽取配置列表
func (s *ReportService) ListExtractConfigs(ctx context.Context, businessCode, reportCode string, pageNum, pageSize int) ([]model.ExtractConfig, int, error) {
return s.configLoader.ListExtractConfigs(ctx, businessCode, reportCode, pageNum, pageSize)
}
func (s *ReportService) GetExtractConfigs(ctx context.Context, businessCode, reportCode string) ([]model.ExtractConfig, error) {
return s.configLoader.GetExtractConfigs(ctx, businessCode, reportCode)
}
// GetExtractConfigsAll 获取业务报表下所有抽取配置(不限 status,用于填充字段展示)
func (s *ReportService) GetExtractConfigsAll(ctx context.Context, businessCode, reportCode string) ([]model.ExtractConfig, error) {
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT id, extract_code, extract_name, status FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND status != $3 ORDER BY id ASC",
businessCode, reportCode, model.StatusInactive)
if err != nil {
return nil, err
}
var configs []model.ExtractConfig
for _, record := range r {
var ec model.ExtractConfig
if err := record.Struct(&ec); err != nil {
return nil, err
}
configs = append(configs, ec)
}
return configs, nil
}
// ifVal 三目运算:非空返回 s,否则返回 fallback
func ifVal(s, fallback string) string {
if s == "" {
return fallback
}
return s
}
+528
View File
@@ -0,0 +1,528 @@
package builder
import (
"context"
"fmt"
"regexp"
"strings"
"dataengine/common/report/config"
"dataengine/common/report/model"
)
// SQLBuilder 动态SQL构建器
type SQLBuilder struct {
loader *config.ConfigLoader
}
// NewSQLBuilder 创建SQL构建器
func NewSQLBuilder() *SQLBuilder {
return &SQLBuilder{
loader: config.GetLoader(),
}
}
// BuildQuerySQL 根据用户选择构建查询SQL
func (b *SQLBuilder) BuildQuerySQL(ctx context.Context, req *model.UserSelectQueryReq) (string, []interface{}, map[string]interface{}, error) {
// 1. 校验配置
report, err := b.loader.GetReport(ctx, req.BusinessCode, req.ReportCode)
if err != nil {
return "", nil, nil, fmt.Errorf("获取报表配置失败: %w", err)
}
fieldMap, err := b.loader.GetFieldMap(ctx, req.BusinessCode, req.ReportCode)
if err != nil {
return "", nil, nil, fmt.Errorf("获取字段配置失败: %w", err)
}
tableName := report.StatTableName
// 2. 构建 SELECT 部分
selectClause, err := b.buildSelectClause(req, fieldMap, report)
if err != nil {
return "", nil, nil, err
}
// 3. 构建 FROM 部分
fromClause := tableName
// 4. 构建 WHERE 部分
whereClause, whereArgs, err := b.buildWhereClause(ctx, req, fieldMap, report)
if err != nil {
return "", nil, nil, err
}
// 5. 构建 GROUP BY 部分
groupByClause, err := b.buildGroupByClause(req, fieldMap)
if err != nil {
return "", nil, nil, err
}
// 6. 构建 ORDER BY 部分
orderByClause, err := b.buildOrderByClause(req, fieldMap)
if err != nil {
return "", nil, nil, err
}
// 7. 组合完整SQL
var sql strings.Builder
sql.WriteString("SELECT ")
sql.WriteString(selectClause)
sql.WriteString(" FROM ")
sql.WriteString(fromClause)
if whereClause != "" {
sql.WriteString(" WHERE ")
sql.WriteString(whereClause)
}
if groupByClause != "" {
sql.WriteString(" GROUP BY ")
sql.WriteString(groupByClause)
}
if orderByClause != "" {
sql.WriteString(" ORDER BY ")
sql.WriteString(orderByClause)
}
// 8. 统计总数SQL
countSql := "SELECT COUNT(*) FROM " + fromClause
if whereClause != "" {
countSql += " WHERE " + whereClause
}
if groupByClause != "" {
if whereClause != "" {
countSql = fmt.Sprintf("SELECT COUNT(*) FROM (SELECT 1 FROM %s WHERE %s GROUP BY %s) AS t",
fromClause, whereClause, groupByClause)
} else {
countSql = fmt.Sprintf("SELECT COUNT(*) FROM (SELECT 1 FROM %s GROUP BY %s) AS t",
fromClause, groupByClause)
}
}
metadata := map[string]interface{}{
"countSql": countSql,
"tableName": tableName,
"reportConfig": report,
}
return sql.String(), whereArgs, metadata, nil
}
// buildSelectClause 构建SELECT子句
func (b *SQLBuilder) buildSelectClause(req *model.UserSelectQueryReq, fieldMap map[string]*model.FieldConfig, report *model.ReportConfig) (string, error) {
var selectParts []string
// 1. 添加维度字段
for _, dim := range req.Dimensions {
dim = strings.TrimSpace(dim)
if dim == "" {
continue
}
fc, ok := fieldMap[dim]
if !ok {
return "", fmt.Errorf("维度字段不存在: %s", dim)
}
if fc.FieldRole != model.RoleDimension && fc.FieldRole != model.RoleFilter {
return "", fmt.Errorf("字段 %s 不可作为维度", dim)
}
selectParts = append(selectParts, dim)
}
// 2. 添加指标字段(含聚合)
if len(req.Indicators) == 0 {
return "", fmt.Errorf("必须选择至少一个指标")
}
for _, ind := range req.Indicators {
fc, ok := fieldMap[ind.FieldCode]
if !ok {
return "", fmt.Errorf("指标字段不存在: %s", ind.FieldCode)
}
alias := ind.Alias
if alias == "" {
alias = ind.FieldCode
}
agg := strings.ToUpper(ind.Aggregate)
if agg == "" {
agg = fc.DefaultAggregate
if agg == "" {
agg = model.AggregateSum
}
}
// 校验聚合方式
if len(fc.ValidAggregates) > 0 {
valid := false
for _, v := range fc.ValidAggregates {
if strings.ToUpper(v) == agg {
valid = true
break
}
}
if !valid {
return "", fmt.Errorf("字段 %s 不支持聚合方式 %s", ind.FieldCode, agg)
}
}
// 处理衍生指标(表达式)
if fc.ExpressionType == "CALCULATED" && fc.Expression != "" {
expr := b.parseExpression(fc.Expression, req.Indicators)
selectParts = append(selectParts, fmt.Sprintf("%s AS %s", expr, alias))
} else {
selectParts = append(selectParts, fmt.Sprintf("%s(%s) AS %s", agg, ind.FieldCode, alias))
}
}
// 3. 添加时间分组字段
if req.TimeGroup != "" && req.TimeGroup != "day" {
dateField := report.DateField
if dateField == "" {
dateField = "stat_date"
}
timeGroupExpr := b.buildTimeGroupExpr(dateField, req.TimeGroup)
selectParts = append(selectParts, timeGroupExpr)
}
return strings.Join(selectParts, ", "), nil
}
// buildWhereClause 构建WHERE子句
func (b *SQLBuilder) buildWhereClause(ctx context.Context, req *model.UserSelectQueryReq, fieldMap map[string]*model.FieldConfig, report *model.ReportConfig) (string, []interface{}, error) {
var conditions []string
var args []interface{}
// 1. 租户过滤
conditions = append(conditions, "tenant_id = 1")
// 2. 时间范围过滤
if req.TimeRange != nil {
dateField := report.DateField
if dateField == "" {
dateField = "stat_date"
}
if req.TimeRange.StartDate != "" {
conditions = append(conditions, fmt.Sprintf("%s >= ?", dateField))
args = append(args, req.TimeRange.StartDate)
}
if req.TimeRange.EndDate != "" {
conditions = append(conditions, fmt.Sprintf("%s <= ?", dateField))
args = append(args, req.TimeRange.EndDate)
}
}
// 3. 业务过滤
conditions = append(conditions, "business_code = ?")
args = append(args, req.BusinessCode)
// 4. 用户筛选条件
for _, filter := range req.Filters {
fc, ok := fieldMap[filter.FieldCode]
if !ok {
return "", nil, fmt.Errorf("筛选字段不存在: %s", filter.FieldCode)
}
if !fc.IsFilterable {
return "", nil, fmt.Errorf("字段 %s 不可用于筛选", filter.FieldCode)
}
op := strings.ToUpper(filter.Operator)
if op == "" {
op = "="
}
// 校验操作符
if len(fc.FilterOperators) > 0 {
valid := false
for _, v := range fc.FilterOperators {
if strings.ToUpper(v) == op {
valid = true
break
}
}
if !valid {
return "", nil, fmt.Errorf("字段 %s 不支持操作符 %s", filter.FieldCode, op)
}
}
cond, vals, err := b.buildFilterCondition(filter, op, fc.FieldType)
if err != nil {
return "", nil, err
}
conditions = append(conditions, cond)
args = append(args, vals...)
}
return strings.Join(conditions, " AND "), args, nil
}
// buildFilterCondition 构建单个筛选条件
func (b *SQLBuilder) buildFilterCondition(filter model.FilterCondition, op string, fieldType string) (string, []interface{}, error) {
field := filter.FieldCode
var args []interface{}
switch op {
case "=":
return fmt.Sprintf("%s = ?", field), []interface{}{filter.Value}, nil
case "!=":
return fmt.Sprintf("%s != ?", field), []interface{}{filter.Value}, nil
case ">":
return fmt.Sprintf("%s > ?", field), []interface{}{filter.Value}, nil
case "<":
return fmt.Sprintf("%s < ?", field), []interface{}{filter.Value}, nil
case ">=":
return fmt.Sprintf("%s >= ?", field), []interface{}{filter.Value}, nil
case "<=":
return fmt.Sprintf("%s <= ?", field), []interface{}{filter.Value}, nil
case "IN":
values, err := b.convertToSlice(filter.Value)
if err != nil {
return "", nil, err
}
placeholders := make([]string, len(values))
for i := range values {
placeholders[i] = "?"
args = append(args, values[i])
}
return fmt.Sprintf("%s IN (%s)", field, strings.Join(placeholders, ",")), args, nil
case "LIKE":
return fmt.Sprintf("%s LIKE ?", field), []interface{}{"%" + fmt.Sprintf("%v", filter.Value) + "%"}, nil
case "BETWEEN":
return fmt.Sprintf("%s BETWEEN ? AND ?", field), []interface{}{filter.Value, filter.Value2}, nil
default:
return fmt.Sprintf("%s = ?", field), []interface{}{filter.Value}, nil
}
}
// buildGroupByClause 构建GROUP BY子句
func (b *SQLBuilder) buildGroupByClause(req *model.UserSelectQueryReq, fieldMap map[string]*model.FieldConfig) (string, error) {
var groupFields []string
for _, dim := range req.Dimensions {
fc, ok := fieldMap[dim]
if !ok {
continue
}
if fc.FieldRole == model.RoleDimension || fc.FieldRole == model.RoleFilter {
groupFields = append(groupFields, dim)
}
}
if len(groupFields) == 0 {
return "", nil
}
return strings.Join(groupFields, ", "), nil
}
// buildOrderByClause 构建ORDER BY子句
func (b *SQLBuilder) buildOrderByClause(req *model.UserSelectQueryReq, fieldMap map[string]*model.FieldConfig) (string, error) {
if len(req.OrderBy) == 0 {
return "", nil
}
var orderParts []string
for _, order := range req.OrderBy {
field := order.FieldCode
dir := strings.ToUpper(order.Direction)
if dir == "" {
dir = "ASC"
}
if dir != "ASC" && dir != "DESC" {
return "", fmt.Errorf("排序方向必须是 ASC 或 DESC")
}
fc, ok := fieldMap[field]
if !ok {
return "", fmt.Errorf("排序字段不存在: %s", field)
}
if !fc.IsSortable {
return "", fmt.Errorf("字段 %s 不可排序", field)
}
orderParts = append(orderParts, fmt.Sprintf("%s %s", field, dir))
}
return strings.Join(orderParts, ", "), nil
}
// buildTimeGroupExpr 构建时间分组表达式
func (b *SQLBuilder) buildTimeGroupExpr(dateField, timeGroup string) string {
switch timeGroup {
case "week":
return fmt.Sprintf("DATE_TRUNC('week', %s::date)::text AS time_group", dateField)
case "month":
return fmt.Sprintf("TO_CHAR(%s::date, 'YYYY-MM') AS time_group", dateField)
case "year":
return fmt.Sprintf("TO_CHAR(%s::date, 'YYYY') AS time_group", dateField)
case "quarter":
return "TO_CHAR(" + dateField + "::date, 'YYYY-\"Q\"Q') AS time_group"
default:
return dateField + " AS time_group"
}
}
// parseExpression 解析衍生指标表达式
// 将 {fieldCode} 占位符替换为原始字段名,当字段不在 indicators 中时仍返回 fieldCode
// 避免未替换的 {fieldCode} 占位符泄漏到 SQL 中导致语法错误
func (b *SQLBuilder) parseExpression(expr string, indicators []model.IndicatorSelect) string {
re := regexp.MustCompile(`\{([^}]+)\}`)
return re.ReplaceAllStringFunc(expr, func(match string) string {
fieldCode := match[1 : len(match)-1]
for _, ind := range indicators {
if ind.FieldCode == fieldCode {
return fieldCode
}
}
// 字段不在 indicators 中也返回裸字段名(避免 {fieldCode} 残留 SQL 导致语法错误)
return fieldCode
})
}
// convertToSlice 转换为切片
func (b *SQLBuilder) convertToSlice(v interface{}) ([]interface{}, error) {
switch val := v.(type) {
case []interface{}:
return val, nil
case []string:
result := make([]interface{}, len(val))
for i, s := range val {
result[i] = s
}
return result, nil
case string:
parts := strings.Split(val, ",")
result := make([]interface{}, len(parts))
for i, p := range parts {
result[i] = strings.TrimSpace(p)
}
return result, nil
default:
return []interface{}{v}, nil
}
}
// BuildCountSQL 构建统计总数SQL
func (b *SQLBuilder) BuildCountSQL(sql string) string {
sql = regexp.MustCompile(`(?i)SELECT\s+.*?\s+FROM`).ReplaceAllString(sql, "SELECT COUNT(*) FROM")
return sql
}
// AddLimit 添加分页
func (b *SQLBuilder) AddLimit(sql string, page, pageSize int) string {
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 1000 {
pageSize = 1000
}
offset := (page - 1) * pageSize
return fmt.Sprintf("%s LIMIT %d OFFSET %d", sql, pageSize, offset)
}
// GenerateInsertSQL 生成upsert SQL
func (b *SQLBuilder) GenerateInsertSQL(tableName string, columns []string, conflictKeys []string) string {
cols := strings.Join(columns, ", ")
placeholders := make([]string, len(columns))
for i := range columns {
placeholders[i] = fmt.Sprintf("$%d", i+1)
}
placeholdersStr := strings.Join(placeholders, ", ")
var updateParts []string
for _, col := range columns {
if col == "id" || col == "created_at" {
continue
}
updateParts = append(updateParts, fmt.Sprintf("%s = EXCLUDED.%s", col, col))
}
sql := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", tableName, cols, placeholdersStr)
if len(conflictKeys) > 0 {
sql += " ON CONFLICT (" + strings.Join(conflictKeys, ", ") + ")"
sql += " DO UPDATE SET " + strings.Join(updateParts, ", ")
}
return sql
}
// BuildExtractSQL 构建数据抽取SQL
func (b *SQLBuilder) BuildExtractSQL(ctx context.Context, extractConfig *model.ExtractConfig, statDate string) (string, []interface{}, error) {
var selectParts []string
var args []interface{}
// 基础字段
selectParts = append(selectParts, "tenant_id")
selectParts = append(selectParts, fmt.Sprintf("'%s' AS business_code", extractConfig.BusinessCode))
selectParts = append(selectParts, fmt.Sprintf("'%s' AS stat_date", statDate))
// 字段映射
sourceTable := extractConfig.SourceTableName
if extractConfig.SourceTableAlias != "" {
sourceTable = extractConfig.SourceTableAlias
}
for _, mapping := range extractConfig.FieldMappings {
targetField := mapping.TargetField
sourceField := mapping.SourceField
if mapping.TransformRule != nil {
expr := b.applyTransformRule(mapping.TransformRule, sourceField)
selectParts = append(selectParts, fmt.Sprintf("%s AS %s", expr, targetField))
} else {
selectParts = append(selectParts, fmt.Sprintf("%s.%s AS %s", sourceTable, sourceField, targetField))
}
}
// 构建 FROM 和 JOIN
fromClause := extractConfig.SourceTableName
if extractConfig.SourceTableAlias != "" {
fromClause += " " + extractConfig.SourceTableAlias
}
for _, join := range extractConfig.JoinConfigs {
joinType := "LEFT JOIN"
if strings.ToUpper(join.JoinType) == "INNER" {
joinType = "INNER JOIN"
} else if strings.ToUpper(join.JoinType) == "RIGHT" {
joinType = "RIGHT JOIN"
}
fromClause += fmt.Sprintf(" %s %s %s ON %s", joinType, join.JoinTable, join.JoinAlias, join.JoinCondition)
}
// WHERE 条件
whereClause := ""
if extractConfig.FilterExpression != "" {
whereClause = " WHERE " + extractConfig.FilterExpression
}
sql := fmt.Sprintf("SELECT %s FROM %s%s", strings.Join(selectParts, ", "), fromClause, whereClause)
return sql, args, nil
}
// applyTransformRule 应用转换规则
func (b *SQLBuilder) applyTransformRule(rule *model.TransformRule, sourceField string) string {
switch rule.RuleType {
case "CALCULATE":
if rule.Expression != "" {
return strings.ReplaceAll(rule.Expression, "{source}", sourceField)
}
case "FORMAT":
if rule.Format != "" {
return fmt.Sprintf("TO_CHAR(%s, '%s')", sourceField, rule.Format)
}
case "MAPPING":
// 运行时映射,需要在代码中处理
return sourceField
}
return sourceField
}
+484
View File
@@ -0,0 +1,484 @@
package builder
import (
"context"
"testing"
"dataengine/common/report/model"
)
// Test data - reusable field configs
var testFields = map[string]*model.FieldConfig{
"shop_id": {FieldCode: "shop_id", FieldName: "店铺ID", FieldType: "STRING", FieldRole: "DIMENSION", IsFilterable: true, IsSortable: true, FilterOperators: []string{"=", "IN"}},
"shop_name": {FieldCode: "shop_name", FieldName: "店铺名称", FieldType: "STRING", FieldRole: "DIMENSION", IsFilterable: true, IsSortable: true, FilterOperators: []string{"=", "LIKE"}},
"stat_date": {FieldCode: "stat_date", FieldName: "统计日期", FieldType: "DATE", FieldRole: "DIMENSION", IsFilterable: true, IsSortable: true, FilterOperators: []string{"=", ">=", "<=", "BETWEEN"}},
"order_count": {FieldCode: "order_count", FieldName: "订单数", FieldType: "INT", FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM", ValidAggregates: []string{"SUM", "COUNT"}},
"order_amount": {FieldCode: "order_amount", FieldName: "订单金额", FieldType: "FLOAT", FieldRole: "INDICATOR", IsAggregatable: true, IsSortable: true, DefaultAggregate: "SUM", ValidAggregates: []string{"SUM", "AVG", "MAX", "MIN"}},
"refund_rate": {FieldCode: "refund_rate", FieldName: "退款率", FieldType: "FLOAT", FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "AVG", ValidAggregates: []string{"AVG"}, Expression: "{refund_amount} / NULLIF({order_amount}, 0) * 100", ExpressionType: "CALCULATED"},
"order_status": {FieldCode: "order_status", FieldName: "订单状态", FieldType: "STRING", FieldRole: "FILTER", IsFilterable: true, FilterOperators: []string{"=", "IN"}},
}
// Test data - reusable report config
var testReport = &model.ReportConfig{
StatTableName: "test_stat_table",
DateField: "stat_date",
}
func TestBuildSelectClause(t *testing.T) {
b := &SQLBuilder{}
t.Run("basic dimensions + indicators", func(t *testing.T) {
req := &model.UserSelectQueryReq{
Dimensions: []string{"shop_id", "shop_name"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_count", Aggregate: "SUM", Alias: "total_orders"},
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total_amount"},
},
}
sql, err := b.buildSelectClause(req, testFields, testReport)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if sql == "" {
t.Fatal("empty SELECT clause")
}
assertContains(t, sql, "shop_id")
assertContains(t, sql, "shop_name")
assertContains(t, sql, "SUM(order_count) AS total_orders")
assertContains(t, sql, "SUM(order_amount) AS total_amount")
})
t.Run("calculated field", func(t *testing.T) {
req := &model.UserSelectQueryReq{
Dimensions: []string{"shop_id"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_count", Aggregate: "SUM", Alias: "orders"},
{FieldCode: "refund_rate", Aggregate: "AVG", Alias: "rate"},
},
}
sql, err := b.buildSelectClause(req, testFields, testReport)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, sql, "shop_id")
assertContains(t, sql, "SUM(order_count) AS orders")
assertContains(t, sql, "refund_amount")
assertContains(t, sql, "order_amount")
})
t.Run("time_group weekly", func(t *testing.T) {
req := &model.UserSelectQueryReq{
Dimensions: []string{"shop_id"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_count", Aggregate: "SUM", Alias: "orders"},
},
TimeGroup: "week",
}
sql, err := b.buildSelectClause(req, testFields, testReport)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, sql, "DATE_TRUNC")
assertContains(t, sql, "time_group")
})
t.Run("time_group monthly", func(t *testing.T) {
req := &model.UserSelectQueryReq{
Dimensions: []string{"shop_id"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_amount", Aggregate: "SUM"},
},
TimeGroup: "month",
}
sql, err := b.buildSelectClause(req, testFields, testReport)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, sql, "YYYY-MM")
assertContains(t, sql, "time_group")
})
t.Run("invalid dimension field", func(t *testing.T) {
req := &model.UserSelectQueryReq{
Dimensions: []string{"nonexistent_field"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_count", Aggregate: "SUM"},
},
}
_, err := b.buildSelectClause(req, testFields, testReport)
if err == nil {
t.Fatal("expected error for nonexistent dimension")
}
})
t.Run("no indicators", func(t *testing.T) {
req := &model.UserSelectQueryReq{}
_, err := b.buildSelectClause(req, testFields, testReport)
if err == nil {
t.Fatal("expected error for no indicators")
}
})
t.Run("invalid aggregate", func(t *testing.T) {
req := &model.UserSelectQueryReq{
Dimensions: []string{"shop_id"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_count", Aggregate: "AVG"},
},
}
_, err := b.buildSelectClause(req, testFields, testReport)
if err == nil {
t.Fatal("expected error for invalid aggregate")
}
})
}
func TestBuildFilterCondition(t *testing.T) {
b := &SQLBuilder{}
tests := []struct {
name string
operator string
value interface{}
value2 interface{}
wantLike string
}{
{"operator =", "=", "active", nil, "order_status = ?"},
{"operator !=", "!=", "cancelled", nil, "order_status != ?"},
{"operator >", ">", 100, nil, "order_amount > ?"},
{"operator <", "<", 50, nil, "order_amount < ?"},
{"operator >=", ">=", 100, nil, "order_amount >= ?"},
{"operator <=", "<=", 50, nil, "order_amount <= ?"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
filter := model.FilterCondition{
FieldCode: "order_amount",
Operator: tt.operator,
Value: tt.value,
Value2: tt.value2,
}
cond, args, err := b.buildFilterCondition(filter, tt.operator, "FLOAT")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if cond == "" {
t.Fatal("empty condition")
}
if len(args) == 0 {
t.Fatal("no args")
}
})
}
}
func TestBuildFilterConditionIN(t *testing.T) {
b := &SQLBuilder{}
filter := model.FilterCondition{
FieldCode: "order_status",
Operator: "IN",
Value: []interface{}{1, 2, 3},
}
cond, args, err := b.buildFilterCondition(filter, "IN", "STRING")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, cond, "order_status IN (?,?,?)")
if len(args) != 3 {
t.Fatalf("expected 3 args, got %d", len(args))
}
}
func TestBuildFilterConditionLIKE(t *testing.T) {
b := &SQLBuilder{}
filter := model.FilterCondition{
FieldCode: "shop_name",
Operator: "LIKE",
Value: "旗舰",
}
cond, args, err := b.buildFilterCondition(filter, "LIKE", "STRING")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, cond, "shop_name LIKE ?")
if len(args) != 1 {
t.Fatalf("expected 1 arg, got %d", len(args))
}
val := args[0].(string)
assertContains(t, val, "%旗舰%")
}
func TestBuildFilterConditionBETWEEN(t *testing.T) {
b := &SQLBuilder{}
filter := model.FilterCondition{
FieldCode: "stat_date",
Operator: "BETWEEN",
Value: "2026-06-01",
Value2: "2026-06-16",
}
cond, args, err := b.buildFilterCondition(filter, "BETWEEN", "DATE")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, cond, "stat_date BETWEEN ? AND ?")
if len(args) != 2 {
t.Fatalf("expected 2 args, got %d", len(args))
}
}
func TestBuildGroupByClause(t *testing.T) {
b := &SQLBuilder{}
t.Run("with dimensions", func(t *testing.T) {
req := &model.UserSelectQueryReq{
Dimensions: []string{"shop_id", "shop_name"},
}
gb, err := b.buildGroupByClause(req, testFields)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, gb, "shop_id")
assertContains(t, gb, "shop_name")
})
t.Run("no dimensions", func(t *testing.T) {
req := &model.UserSelectQueryReq{}
gb, err := b.buildGroupByClause(req, testFields)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if gb != "" {
t.Fatalf("expected empty, got %s", gb)
}
})
}
func TestBuildOrderByClause(t *testing.T) {
b := &SQLBuilder{}
t.Run("with orders", func(t *testing.T) {
req := &model.UserSelectQueryReq{
OrderBy: []model.OrderCondition{
{FieldCode: "order_amount", Direction: "DESC"},
{FieldCode: "shop_id", Direction: "ASC"},
},
}
ob, err := b.buildOrderByClause(req, testFields)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, ob, "order_amount DESC")
assertContains(t, ob, "shop_id ASC")
})
t.Run("no orders", func(t *testing.T) {
req := &model.UserSelectQueryReq{}
ob, err := b.buildOrderByClause(req, testFields)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ob != "" {
t.Fatalf("expected empty, got %s", ob)
}
})
t.Run("invalid field", func(t *testing.T) {
req := &model.UserSelectQueryReq{
OrderBy: []model.OrderCondition{
{FieldCode: "nonexistent", Direction: "ASC"},
},
}
_, err := b.buildOrderByClause(req, testFields)
if err == nil {
t.Fatal("expected error for nonexistent field")
}
})
}
func TestBuildTimeGroupExpr(t *testing.T) {
b := &SQLBuilder{}
tests := []struct {
group string
wantSub string
}{
{"year", "YYYY"},
{"week", "DATE_TRUNC"},
{"month", "YYYY-MM"},
{"quarter", "Q"},
{"day", "stat_date"},
}
for _, tt := range tests {
t.Run(tt.group, func(t *testing.T) {
got := b.buildTimeGroupExpr("stat_date", tt.group)
assertContains(t, got, tt.wantSub)
assertContains(t, got, "time_group")
})
}
}
func TestAddLimit(t *testing.T) {
b := &SQLBuilder{}
t.Run("normal pagination", func(t *testing.T) {
sql := b.AddLimit("SELECT * FROM t", 2, 10)
assertContains(t, sql, "LIMIT 10")
assertContains(t, sql, "OFFSET 10")
})
t.Run("page 1", func(t *testing.T) {
sql := b.AddLimit("SELECT * FROM t", 1, 20)
assertContains(t, sql, "OFFSET 0")
})
t.Run("capped page size", func(t *testing.T) {
sql := b.AddLimit("SELECT * FROM t", 1, 5000)
assertContains(t, sql, "LIMIT 1000")
})
}
func TestBuildWhereClause(t *testing.T) {
b := &SQLBuilder{}
t.Run("time range only", func(t *testing.T) {
req := &model.UserSelectQueryReq{
BusinessCode: "TEST_BIZ",
TimeRange: &model.TimeRange{
StartDate: "2026-06-01",
EndDate: "2026-06-16",
},
}
where, args, err := b.buildWhereClause(context.Background(), req, testFields, testReport)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, where, "tenant_id")
assertContains(t, where, "stat_date")
assertContains(t, where, "business_code = ?")
if len(args) < 2 {
t.Fatalf("expected at least 2 args, got %d", len(args))
}
})
t.Run("with filters", func(t *testing.T) {
req := &model.UserSelectQueryReq{
BusinessCode: "TEST_BIZ",
Filters: []model.FilterCondition{
{FieldCode: "order_status", Operator: "=", Value: 1},
{FieldCode: "shop_name", Operator: "LIKE", Value: "旗舰"},
},
}
where, args, err := b.buildWhereClause(context.Background(), req, testFields, testReport)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assertContains(t, where, "order_status = ?")
assertContains(t, where, "shop_name LIKE ?")
if len(args) < 3 {
t.Fatalf("expected at least 3 args, got %d", len(args))
}
})
t.Run("invalid filter field", func(t *testing.T) {
req := &model.UserSelectQueryReq{
BusinessCode: "TEST_BIZ",
Filters: []model.FilterCondition{
{FieldCode: "nonexistent", Operator: "=", Value: "x"},
},
}
_, _, err := b.buildWhereClause(context.Background(), req, testFields, testReport)
if err == nil {
t.Fatal("expected error for nonexistent filter field")
}
})
}
func TestParseExpression(t *testing.T) {
b := &SQLBuilder{}
expr := "{refund_amount} / NULLIF({order_amount}, 0) * 100"
indicators := []model.IndicatorSelect{
{FieldCode: "refund_amount"},
{FieldCode: "order_amount"},
}
result := b.parseExpression(expr, indicators)
assertContains(t, result, "refund_amount")
assertContains(t, result, "order_amount")
assertContains(t, result, "NULLIF")
}
// Regression: ISSUE-002 — calculated expression referencing non-indicator field
// {fieldCode} placeholder should be replaced with bare field name, not left in SQL
// Found by /qa on 2026-06-16
func TestParseExpressionRegressionNonIndicatorRef(t *testing.T) {
b := &SQLBuilder{}
expr := "{refund_amount} / NULLIF({external_field}, 0) * 100"
indicators := []model.IndicatorSelect{
{FieldCode: "refund_amount"},
}
result := b.parseExpression(expr, indicators)
// external_field not in indicators, but should still be replaced with bare field name
if !contains(result, "external_field") {
t.Fatalf("expected external_field in expression, got: %s", result)
}
if contains(result, "{") || contains(result, "}") {
t.Fatalf("expression should not contain unreplaced brace placeholders: %s", result)
}
if !contains(result, "refund_amount") {
t.Fatalf("expected refund_amount in expression, got: %s", result)
}
if !contains(result, "NULLIF") {
t.Fatalf("expected NULLIF in expression, got: %s", result)
}
}
func TestConvertToSlice(t *testing.T) {
b := &SQLBuilder{}
t.Run("from []interface{}", func(t *testing.T) {
result, err := b.convertToSlice([]interface{}{1, 2, 3})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(result) != 3 {
t.Fatalf("expected 3 elements, got %d", len(result))
}
})
t.Run("from comma string", func(t *testing.T) {
result, err := b.convertToSlice("a,b,c")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(result) != 3 {
t.Fatalf("expected 3 elements, got %d", len(result))
}
})
}
// assertContains helper
func assertContains(t *testing.T, s, substr string) {
t.Helper()
if s == "" {
t.Fatalf("want substr %q in empty string", substr)
}
if !contains(s, substr) {
t.Fatalf("want %q to contain %q", s, substr)
}
}
func contains(s, substr string) bool {
return len(s) >= len(substr) && searchString(s, substr)
}
func searchString(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
+770
View File
@@ -0,0 +1,770 @@
package config
import (
"context"
"encoding/json"
"fmt"
"sync"
"dataengine/common/report/model"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/frame/g"
)
// ConfigLoader 配置加载器
type ConfigLoader struct {
mu sync.RWMutex
// 缓存
businessCache map[string]*model.BusinessConfig
reportCache map[string]*model.ReportConfig
fieldCache map[string][]model.FieldConfig
extractCache map[string][]model.ExtractConfig
}
var (
defaultLoader *ConfigLoader
once sync.Once
)
// GetLoader 获取配置加载器单例
func GetLoader() *ConfigLoader {
once.Do(func() {
defaultLoader = &ConfigLoader{
businessCache: make(map[string]*model.BusinessConfig),
reportCache: make(map[string]*model.ReportConfig),
fieldCache: make(map[string][]model.FieldConfig),
extractCache: make(map[string][]model.ExtractConfig),
}
})
return defaultLoader
}
// GetBusiness 获取业务配置
func (l *ConfigLoader) GetBusiness(ctx context.Context, businessCode string) (*model.BusinessConfig, error) {
l.mu.RLock()
if biz, ok := l.businessCache[businessCode]; ok {
l.mu.RUnlock()
return biz, nil
}
l.mu.RUnlock()
var biz model.BusinessConfig
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_business_config WHERE business_code = $1 AND deleted_at IS NULL LIMIT 1",
businessCode)
if err != nil {
return nil, fmt.Errorf("查询业务配置失败: %w", err)
}
if r.IsEmpty() {
return nil, fmt.Errorf("业务配置不存在: %s", businessCode)
}
if err = r[0].Struct(&biz); err != nil {
return nil, err
}
if biz.Config == nil {
biz.Config = make(map[string]interface{})
}
l.mu.Lock()
l.businessCache[businessCode] = &biz
l.mu.Unlock()
return &biz, nil
}
// GetReport 获取报表配置
func (l *ConfigLoader) GetReport(ctx context.Context, businessCode, reportCode string) (*model.ReportConfig, error) {
key := businessCode + ":" + reportCode
l.mu.RLock()
if rpt, ok := l.reportCache[key]; ok {
l.mu.RUnlock()
return rpt, nil
}
l.mu.RUnlock()
var rpt model.ReportConfig
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_report_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL LIMIT 1",
businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("查询报表配置失败: %w", err)
}
if r.IsEmpty() {
return nil, fmt.Errorf("报表配置不存在: %s/%s", businessCode, reportCode)
}
if err = r[0].Struct(&rpt); err != nil {
return nil, err
}
if rpt.PrimaryKeys == nil {
rpt.PrimaryKeys = []string{"id"}
}
if rpt.ConflictKeys == nil {
rpt.ConflictKeys = []string{"stat_date"}
}
if rpt.Config == nil {
rpt.Config = make(map[string]interface{})
}
l.mu.Lock()
l.reportCache[key] = &rpt
l.mu.Unlock()
return &rpt, nil
}
// GetFields 获取报表字段配置
func (l *ConfigLoader) GetFields(ctx context.Context, businessCode, reportCode string) ([]model.FieldConfig, error) {
key := businessCode + ":" + reportCode
l.mu.RLock()
if fields, ok := l.fieldCache[key]; ok {
l.mu.RUnlock()
return fields, nil
}
l.mu.RUnlock()
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_field_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY sort_order ASC",
businessCode, reportCode)
if err != nil {
return nil, err
}
var fields []model.FieldConfig
for _, record := range r {
var f model.FieldConfig
if err := record.Struct(&f); err != nil {
return nil, err
}
if f.ValidAggregates == nil {
f.ValidAggregates = []string{}
}
if f.FilterOperators == nil {
f.FilterOperators = []string{"=", "!=", ">", "<", ">=", "<=", "IN", "LIKE", "BETWEEN"}
}
fields = append(fields, f)
}
l.mu.Lock()
l.fieldCache[key] = fields
l.mu.Unlock()
return fields, nil
}
// GetAllFields 获取报表全部字段(含 INACTIVE,不含已删除,用于编辑回显)
func (l *ConfigLoader) GetAllFields(ctx context.Context, businessCode, reportCode string) ([]model.FieldConfig, error) {
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_field_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY sort_order ASC",
businessCode, reportCode)
if err != nil {
return nil, err
}
var fields []model.FieldConfig
for _, record := range r {
var f model.FieldConfig
if err := record.Struct(&f); err != nil {
return nil, err
}
if f.ValidAggregates == nil {
f.ValidAggregates = []string{}
}
if f.FilterOperators == nil {
f.FilterOperators = []string{"=", "!=", ">", "<", ">=", "<=", "IN", "LIKE", "BETWEEN"}
}
fields = append(fields, f)
}
return fields, nil
}
// GetFieldMap 获取字段配置Map
func (l *ConfigLoader) GetFieldMap(ctx context.Context, businessCode, reportCode string) (map[string]*model.FieldConfig, error) {
fields, err := l.GetFields(ctx, businessCode, reportCode)
if err != nil {
return nil, err
}
fieldMap := make(map[string]*model.FieldConfig)
for i := range fields {
fieldMap[fields[i].FieldCode] = &fields[i]
}
return fieldMap, nil
}
// GetExtractConfigs 获取抽取配置
func (l *ConfigLoader) GetExtractConfigs(ctx context.Context, businessCode, reportCode string) ([]model.ExtractConfig, error) {
key := businessCode + ":" + reportCode
l.mu.RLock()
if configs, ok := l.extractCache[key]; ok {
l.mu.RUnlock()
return configs, nil
}
l.mu.RUnlock()
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY id ASC",
businessCode, reportCode)
if err != nil {
return nil, err
}
var configs []model.ExtractConfig
for _, record := range r {
var ec model.ExtractConfig
if err := record.Struct(&ec); err != nil {
return nil, err
}
if ec.JoinConfigs == nil {
ec.JoinConfigs = []model.JoinConfig{}
}
if ec.FieldMappings == nil {
ec.FieldMappings = []model.FieldMapping{}
}
if ec.TransformRules == nil {
ec.TransformRules = []model.TransformRule{}
}
if ec.GroupByFields == nil {
ec.GroupByFields = []string{}
}
if ec.ExtractMode == "" {
ec.ExtractMode = model.ExtractModeDirect
}
configs = append(configs, ec)
}
l.mu.Lock()
l.extractCache[key] = configs
l.mu.Unlock()
return configs, nil
}
// GetExtractLog 获取抽取记录
func (l *ConfigLoader) GetExtractLog(ctx context.Context, businessCode, reportCode, extractCode, statDate string) (*model.ExtractLog, error) {
var log model.ExtractLog
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_extract_log WHERE business_code = $1 AND report_code = $2 AND extract_code = $3 AND stat_date = $4 LIMIT 1",
businessCode, reportCode, extractCode, statDate)
if err != nil {
return nil, err
}
if r.IsEmpty() {
return nil, nil
}
if err = r[0].Struct(&log); err != nil {
return nil, err
}
return &log, nil
}
// CreateExtractLog 创建抽取记录
func (l *ConfigLoader) CreateExtractLog(ctx context.Context, log *model.ExtractLog) error {
data, _ := json.Marshal(log)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
_, err := gfdb.DB(ctx).Model(ctx, "report_extract_log").Data(m).Save()
return err
}
// UpdateExtractLog 更新抽取记录
func (l *ConfigLoader) UpdateExtractLog(ctx context.Context, log *model.ExtractLog) error {
data, _ := json.Marshal(log)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
_, err := gfdb.DB(ctx).Model(ctx, "report_extract_log").
Where("business_code", log.BusinessCode).
Where("report_code", log.ReportCode).
Where("extract_code", log.ExtractCode).
Where("stat_date", log.StatDate).
Data(m).
Update()
return err
}
// InvalidateCache 失效缓存
func (l *ConfigLoader) InvalidateCache(businessCode, reportCode string) {
l.mu.Lock()
delete(l.businessCache, businessCode)
delete(l.reportCache, businessCode+":"+reportCode)
delete(l.fieldCache, businessCode+":"+reportCode)
delete(l.extractCache, businessCode+":"+reportCode)
l.mu.Unlock()
}
// InvalidateBusinessCache 只失效业务缓存(不影响报表/字段)
func (l *ConfigLoader) InvalidateBusinessCache(businessCode string) {
l.mu.Lock()
delete(l.businessCache, businessCode)
l.mu.Unlock()
}
// ============================================================
// CRUD: BusinessConfig
// ============================================================
// CreateBusiness 创建业务配置
func (l *ConfigLoader) CreateBusiness(ctx context.Context, biz *model.BusinessConfig) (int64, error) {
data, _ := json.Marshal(biz)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
delete(m, "updated_at")
delete(m, "deleted_at")
result, err := gfdb.DB(ctx).Model(ctx, "report_business_config").Data(m).Insert()
if err != nil {
return 0, fmt.Errorf("创建业务配置失败: %w", err)
}
id, _ := result.LastInsertId()
l.InvalidateBusinessCache(biz.BusinessCode)
return id, nil
}
// UpdateBusiness 更新业务配置
func (l *ConfigLoader) UpdateBusiness(ctx context.Context, biz *model.BusinessConfig) error {
data, _ := json.Marshal(biz)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
delete(m, "deleted_at")
_, err := gfdb.DB(ctx).Model(ctx, "report_business_config").
Where("id", biz.Id).
Data(m).
Update()
if err != nil {
return fmt.Errorf("更新业务配置失败: %w", err)
}
l.InvalidateBusinessCache(biz.BusinessCode)
return nil
}
// DeleteBusiness 删除业务配置(软删除)
func (l *ConfigLoader) DeleteBusiness(ctx context.Context, id int64, businessCode string) error {
_, err := gfdb.DB(ctx).Model(ctx, "report_business_config").
Where("id", id).
Data(map[string]interface{}{
"status": model.StatusInactive,
"deleted_at": "NOW()",
}).
Update()
if err != nil {
return fmt.Errorf("删除业务配置失败: %w", err)
}
l.InvalidateBusinessCache(businessCode)
return nil
}
// GetBusinessByID 根据ID获取业务配置
func (l *ConfigLoader) GetBusinessByID(ctx context.Context, id int64) (*model.BusinessConfig, error) {
var biz model.BusinessConfig
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_business_config WHERE id = $1 LIMIT 1", id)
if err != nil {
g.Log().Infof(ctx, "[GetBusinessByID] id=%d, err=%v", id, err)
return nil, fmt.Errorf("查询业务配置失败: %w", err)
}
if r.IsEmpty() {
return nil, fmt.Errorf("业务配置不存在: id=%d", id)
}
if err = r[0].Struct(&biz); err != nil {
return nil, err
}
g.Log().Infof(ctx, "[GetBusinessByID] id=%d, biz.Id=%d, biz.BusinessCode=%s",
id, biz.Id, biz.BusinessCode)
return &biz, nil
}
// ============================================================
// CRUD: ReportConfig
// ============================================================
// CreateReport 创建报表配置
func (l *ConfigLoader) CreateReport(ctx context.Context, rpt *model.ReportConfig) (int64, error) {
data, _ := json.Marshal(rpt)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
delete(m, "updated_at")
delete(m, "deleted_at")
result, err := gfdb.DB(ctx).Model(ctx, "report_report_config").Data(m).Insert()
if err != nil {
return 0, fmt.Errorf("创建报表配置失败: %w", err)
}
id, _ := result.LastInsertId()
l.InvalidateCache(rpt.BusinessCode, rpt.ReportCode)
return id, nil
}
// UpdateReport 更新报表配置
func (l *ConfigLoader) UpdateReport(ctx context.Context, rpt *model.ReportConfig) error {
data, _ := json.Marshal(rpt)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
delete(m, "deleted_at")
_, err := gfdb.DB(ctx).Model(ctx, "report_report_config").
Where("id", rpt.Id).
Data(m).
Update()
if err != nil {
return fmt.Errorf("更新报表配置失败: %w", err)
}
l.InvalidateCache(rpt.BusinessCode, rpt.ReportCode)
return nil
}
// DeleteReport 删除报表配置(软删除)
func (l *ConfigLoader) DeleteReport(ctx context.Context, id int64, businessCode, reportCode string) error {
_, err := gfdb.DB(ctx).Model(ctx, "report_report_config").
Where("id", id).
Data(map[string]interface{}{
"status": model.StatusInactive,
"deleted_at": "NOW()",
}).
Update()
if err != nil {
return fmt.Errorf("删除报表配置失败: %w", err)
}
l.InvalidateCache(businessCode, reportCode)
return nil
}
// GetReportByID 根据ID获取报表配置
func (l *ConfigLoader) GetReportByID(ctx context.Context, id int64) (*model.ReportConfig, error) {
var rpt model.ReportConfig
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_report_config WHERE id = $1 LIMIT 1", id)
if err != nil {
return nil, fmt.Errorf("查询报表配置失败: %w", err)
}
if r.IsEmpty() {
return nil, fmt.Errorf("报表配置不存在: id=%d", id)
}
if err = r[0].Struct(&rpt); err != nil {
return nil, err
}
return &rpt, nil
}
// ============================================================
// CRUD: FieldConfig
// ============================================================
// CreateField 创建字段配置
func (l *ConfigLoader) CreateField(ctx context.Context, field *model.FieldConfig) (int64, error) {
data, _ := json.Marshal(field)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
delete(m, "updated_at")
delete(m, "deleted_at")
result, err := gfdb.DB(ctx).Model(ctx, "report_field_config").Data(m).Insert()
if err != nil {
return 0, fmt.Errorf("创建字段配置失败: %w", err)
}
id, _ := result.LastInsertId()
l.InvalidateCache(field.BusinessCode, field.ReportCode)
return id, nil
}
// UpdateField 更新字段配置
func (l *ConfigLoader) UpdateField(ctx context.Context, field *model.FieldConfig) error {
data, _ := json.Marshal(field)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
delete(m, "deleted_at")
_, err := gfdb.DB(ctx).Model(ctx, "report_field_config").
Where("id", field.Id).
Data(m).
Update()
if err != nil {
return fmt.Errorf("更新字段配置失败: %w", err)
}
l.InvalidateCache(field.BusinessCode, field.ReportCode)
return nil
}
// DeleteField 删除字段配置(软删除)
func (l *ConfigLoader) DeleteField(ctx context.Context, id int64, businessCode, reportCode string) error {
_, err := gfdb.DB(ctx).Model(ctx, "report_field_config").
Where("id", id).
Data(map[string]interface{}{
"status": model.StatusInactive,
"deleted_at": "NOW()",
}).
Update()
if err != nil {
return fmt.Errorf("删除字段配置失败: %w", err)
}
l.InvalidateCache(businessCode, reportCode)
return nil
}
// GetFieldByID 根据ID获取字段配置
func (l *ConfigLoader) GetFieldByID(ctx context.Context, id int64) (*model.FieldConfig, error) {
var field model.FieldConfig
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_field_config WHERE id = $1 LIMIT 1", id)
if err != nil {
return nil, fmt.Errorf("查询字段配置失败: %w", err)
}
if r.IsEmpty() {
return nil, fmt.Errorf("字段配置不存在: id=%d", id)
}
if err = r[0].Struct(&field); err != nil {
return nil, err
}
return &field, nil
}
// ============================================================
// CRUD: ExtractConfig
// ============================================================
// CreateExtractConfig 创建抽取配置
func (l *ConfigLoader) CreateExtractConfig(ctx context.Context, ec *model.ExtractConfig) (int64, error) {
data, _ := json.Marshal(ec)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
delete(m, "updated_at")
delete(m, "deleted_at")
result, err := gfdb.DB(ctx).Model(ctx, "report_extract_config").Data(m).Insert()
if err != nil {
return 0, fmt.Errorf("创建抽取配置失败: %w", err)
}
id, _ := result.LastInsertId()
l.InvalidateCache(ec.BusinessCode, ec.ReportCode)
return id, nil
}
// UpdateExtractConfig 更新抽取配置
func (l *ConfigLoader) UpdateExtractConfig(ctx context.Context, ec *model.ExtractConfig) error {
data, _ := json.Marshal(ec)
var m map[string]interface{}
json.Unmarshal(data, &m)
delete(m, "id")
delete(m, "created_at")
delete(m, "deleted_at")
_, err := gfdb.DB(ctx).Model(ctx, "report_extract_config").
Where("id", ec.Id).
Data(m).
Update()
if err != nil {
return fmt.Errorf("更新抽取配置失败: %w", err)
}
l.InvalidateCache(ec.BusinessCode, ec.ReportCode)
return nil
}
// DeleteExtractConfig 删除抽取配置(软删除)
func (l *ConfigLoader) DeleteExtractConfig(ctx context.Context, id int64, businessCode, reportCode string) error {
_, err := gfdb.DB(ctx).Model(ctx, "report_extract_config").
Where("id", id).
Data(map[string]interface{}{
"status": model.StatusInactive,
"deleted_at": "NOW()",
}).
Update()
if err != nil {
return fmt.Errorf("删除抽取配置失败: %w", err)
}
l.InvalidateCache(businessCode, reportCode)
return nil
}
// GetExtractConfigByID 根据ID获取抽取配置
func (l *ConfigLoader) GetExtractConfigByID(ctx context.Context, id int64) (*model.ExtractConfig, error) {
var ec model.ExtractConfig
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_extract_config WHERE id = $1 LIMIT 1", id)
if err != nil {
return nil, fmt.Errorf("查询抽取配置失败: %w", err)
}
if r.IsEmpty() {
return nil, fmt.Errorf("抽取配置不存在: id=%d", id)
}
if err = r[0].Struct(&ec); err != nil {
return nil, err
}
return &ec, nil
}
// GetAllBusinesses 获取所有业务配置
func (l *ConfigLoader) GetAllBusinesses(ctx context.Context) ([]model.BusinessConfig, error) {
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_business_config WHERE deleted_at IS NULL ORDER BY id ASC",
)
// (removed status filter)
if err != nil {
return nil, err
}
var businesses []model.BusinessConfig
for _, record := range r {
var biz model.BusinessConfig
if err := record.Struct(&biz); err != nil {
return nil, err
}
businesses = append(businesses, biz)
}
return businesses, nil
}
// GetAllReports 获取所有报表配置
func (l *ConfigLoader) GetAllReports(ctx context.Context, businessCode string) ([]model.ReportConfig, error) {
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_report_config WHERE business_code = $1 AND deleted_at IS NULL ORDER BY id ASC",
businessCode)
if err != nil {
return nil, err
}
var reports []model.ReportConfig
for _, record := range r {
var rpt model.ReportConfig
if err := record.Struct(&rpt); err != nil {
return nil, err
}
reports = append(reports, rpt)
}
return reports, nil
}
// ListBusinesses 分页获取业务列表
func (l *ConfigLoader) ListBusinesses(ctx context.Context, pageNum, pageSize int) ([]model.BusinessConfig, int, error) {
total, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT COUNT(*) AS cnt FROM report_business_config WHERE deleted_at IS NULL")
if err != nil {
return nil, 0, err
}
count := 0
if !total.IsEmpty() {
count = total[0]["cnt"].Int()
}
offset := (pageNum - 1) * pageSize
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_business_config WHERE deleted_at IS NULL ORDER BY id ASC LIMIT $1 OFFSET $2",
pageSize, offset)
if err != nil {
return nil, 0, err
}
var businesses []model.BusinessConfig
for _, record := range r {
var biz model.BusinessConfig
if err := record.Struct(&biz); err != nil {
return nil, 0, err
}
businesses = append(businesses, biz)
}
return businesses, count, nil
}
// ListReports 分页获取报表列表
func (l *ConfigLoader) ListReports(ctx context.Context, businessCode, reportName string, pageNum, pageSize int) ([]model.ReportConfig, int, error) {
namePattern := "%" + reportName + "%"
total, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT COUNT(*) AS cnt FROM report_report_config WHERE business_code = $1 AND report_name ILIKE $2 AND deleted_at IS NULL",
businessCode, namePattern)
if err != nil {
return nil, 0, err
}
count := 0
if !total.IsEmpty() {
count = total[0]["cnt"].Int()
}
offset := (pageNum - 1) * pageSize
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_report_config WHERE business_code = $1 AND report_name ILIKE $2 AND deleted_at IS NULL ORDER BY id ASC LIMIT $3 OFFSET $4",
businessCode, namePattern, pageSize, offset)
if err != nil {
return nil, 0, err
}
var reports []model.ReportConfig
for _, record := range r {
var rpt model.ReportConfig
if err := record.Struct(&rpt); err != nil {
return nil, 0, err
}
reports = append(reports, rpt)
}
return reports, count, nil
}
// ListExtractConfigs 分页获取抽取配置列表
func (l *ConfigLoader) ListExtractConfigs(ctx context.Context, businessCode, reportCode string, pageNum, pageSize int) ([]model.ExtractConfig, int, error) {
total, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT COUNT(*) AS cnt FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL",
businessCode, reportCode)
if err != nil {
return nil, 0, err
}
count := 0
if !total.IsEmpty() {
count = total[0]["cnt"].Int()
}
offset := (pageNum - 1) * pageSize
r, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT * FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY id ASC LIMIT $3 OFFSET $4",
businessCode, reportCode, pageSize, offset)
if err != nil {
return nil, 0, err
}
var configs []model.ExtractConfig
for _, record := range r {
var ec model.ExtractConfig
if err := record.Struct(&ec); err != nil {
return nil, 0, err
}
configs = append(configs, ec)
}
l.mu.Lock()
l.extractCache[businessCode+":"+reportCode] = configs
l.mu.Unlock()
return configs, count, nil
}
// GetReportFields 获取报表可用字段(按角色分类)
func (l *ConfigLoader) GetReportFields(ctx context.Context, businessCode, reportCode string) (*model.GetReportFieldsResp, error) {
fields, err := l.GetFields(ctx, businessCode, reportCode)
if err != nil {
return nil, err
}
resp := &model.GetReportFieldsResp{
BusinessCode: businessCode,
ReportCode: reportCode,
Dimensions: []model.FieldConfig{},
Indicators: []model.FieldConfig{},
Filters: []model.FieldConfig{},
}
for _, f := range fields {
switch f.FieldRole {
case model.RoleDimension:
resp.Dimensions = append(resp.Dimensions, f)
case model.RoleIndicator:
resp.Indicators = append(resp.Indicators, f)
case model.RoleFilter, model.RoleFilterOnly:
resp.Filters = append(resp.Filters, f)
}
}
return resp, nil
}
+69
View File
@@ -0,0 +1,69 @@
package report
import (
"context"
"time"
"github.com/gogf/gf/v2/os/gcron"
"github.com/gogf/gf/v2/os/gctx"
"github.com/sirupsen/logrus"
)
// ExecDailyExtract 执行一次全量每日抽取(遍历所有启用业务+报表)
// 提取出独立函数,供定时任务和 HTTP 端点共同使用
func ExecDailyExtract(ctx context.Context) {
svc := GetService()
logrus.Info("[报表引擎] 开始全量每日抽取任务")
businesses, err := svc.GetAllBusinesses(ctx)
if err != nil {
logrus.Errorf("[报表引擎] 获取业务列表失败: %v", err)
return
}
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
for _, biz := range businesses {
reports, err := svc.GetAllReports(ctx, biz.BusinessCode)
if err != nil {
logrus.Errorf("[报表引擎] 获取报表列表失败 [%s]: %v", biz.BusinessCode, err)
continue
}
for _, rpt := range reports {
logrus.Infof("[报表引擎] 开始抽取 %s/%s 日期 %s", biz.BusinessCode, rpt.ReportCode, yesterday)
resp, err := svc.ExtractDailyData(ctx, biz.BusinessCode, rpt.ReportCode, yesterday, "system")
if err != nil {
logrus.Errorf("[报表引擎] 抽取失败 %s/%s %s: %v", biz.BusinessCode, rpt.ReportCode, yesterday, err)
continue
}
if resp.Success {
logrus.Infof("[报表引擎] 抽取成功 %s/%s %s 总数:%d 耗时:%dms",
biz.BusinessCode, rpt.ReportCode, yesterday, resp.TotalCount, resp.ExecTimeMs)
} else {
logrus.Errorf("[报表引擎] 抽取异常 %s/%s %s: %s", biz.BusinessCode, rpt.ReportCode, yesterday, resp.ErrorMsg)
}
}
}
logrus.Info("[报表引擎] 全量每日抽取任务完成")
}
// StartDailyExtractJob 启动每日自动抽取定时任务
// 每天凌晨 2:00 遍历所有已启用的业务+报表,抽取前一天数据
// 在 main.go 中调用:report.StartDailyExtractJob()
func StartDailyExtractJob() {
// 每天凌晨 2:00 执行
_, err := gcron.Add(gctx.New(), "0 0 2 * * *", func(ctx context.Context) {
ExecDailyExtract(ctx)
}, "daily-report-extract")
if err != nil {
logrus.Errorf("[报表引擎] 启动定时任务失败: %v", err)
return
}
logrus.Info("[报表引擎] 每日自动抽取定时任务已启动 (02:00)")
}
+195
View File
@@ -0,0 +1,195 @@
package ddlsync
import (
"context"
"fmt"
"strings"
"time"
"dataengine/common/report/config"
"dataengine/common/report/model"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/sirupsen/logrus"
)
// StatTableCreator 统计宽表创建器
type StatTableCreator struct {
loader *config.ConfigLoader
}
// NewStatTableCreator 创建统计宽表创建器
func NewStatTableCreator() *StatTableCreator {
return &StatTableCreator{
loader: config.GetLoader(),
}
}
// AutoCreateStatTable 根据配置自动创建统计宽表
func (c *StatTableCreator) AutoCreateStatTable(ctx context.Context, businessCode, reportCode string) (*model.AutoCreateStatTableResp, error) {
start := time.Now()
logger := logrus.WithFields(logrus.Fields{
"businessCode": businessCode,
"reportCode": reportCode,
})
// 1. 获取报表配置
report, err := c.loader.GetReport(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("获取报表配置失败: %w", err)
}
// 2. 获取字段配置
fields, err := c.loader.GetFields(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("获取字段配置失败: %w", err)
}
if len(fields) == 0 {
return nil, fmt.Errorf("报表字段配置为空,请先配置字段")
}
// 3. 构建建表SQL
tableName := report.StatTableName
sql := c.buildCreateTableSQL(tableName, report, fields)
logger.Infof("创建统计宽表: %s", tableName)
// 4. 执行建表
_, err = gfdb.DB(ctx).Exec(ctx, sql)
if err != nil {
return nil, fmt.Errorf("建表失败: %w", err)
}
// 5. 创建冲突唯一索引
if len(report.ConflictKeys) > 0 {
indexName := fmt.Sprintf("uq_%s_conflict", tableName)
indexCols := strings.Join(report.ConflictKeys, ", ")
indexSQL := fmt.Sprintf("CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s (%s)", indexName, tableName, indexCols)
if _, err := gfdb.DB(ctx).Exec(ctx, indexSQL); err != nil {
logger.Warnf("创建冲突索引失败: %v", err)
}
}
// 6. 添加字段注释
for _, f := range fields {
if f.FieldName != "" {
commentSQL := fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", tableName, f.FieldCode, strings.ReplaceAll(f.FieldName, "'", "''"))
if _, err := gfdb.DB(ctx).Exec(ctx, commentSQL); err != nil {
logger.Warnf("添加字段注释失败 [%s]: %v", f.FieldCode, err)
}
}
}
execTime := time.Since(start).Milliseconds()
logger.Infof("统计宽表 %s 创建完成,字段数: %d,耗时: %dms", tableName, len(fields), execTime)
return &model.AutoCreateStatTableResp{
Success: true,
TableName: tableName,
ColumnCount: len(fields),
ExecTimeMs: execTime,
}, nil
}
// buildCreateTableSQL 构建建表SQL
func (c *StatTableCreator) buildCreateTableSQL(tableName string, report *model.ReportConfig, fields []model.FieldConfig) string {
var cols []string
// 基础字段
cols = append(cols, "id BIGSERIAL PRIMARY KEY")
cols = append(cols, "tenant_id BIGINT NOT NULL DEFAULT 0")
cols = append(cols, "business_code VARCHAR(64) NOT NULL DEFAULT ''")
cols = append(cols, "creator VARCHAR(64) DEFAULT ''")
cols = append(cols, "created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()")
cols = append(cols, "updater VARCHAR(64) DEFAULT ''")
cols = append(cols, "updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()")
cols = append(cols, "deleted_at TIMESTAMP WITH TIME ZONE")
// 日期维度字段
dateField := report.DateField
if dateField == "" {
dateField = "stat_date"
}
cols = append(cols, fmt.Sprintf("%s VARCHAR(16) NOT NULL DEFAULT ''", dateField))
// 业务字段
for _, f := range fields {
if f.FieldCode == dateField {
continue
}
cols = append(cols, fmt.Sprintf("%s %s", f.FieldCode, model.FieldTypeToPG(f.FieldType)))
}
// 原始数据
cols = append(cols, "raw_data JSONB DEFAULT '{}'")
sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (\n %s\n)", tableName, strings.Join(cols, ",\n "))
return sql
}
// DropStatTable 删除统计宽表
func (c *StatTableCreator) DropStatTable(ctx context.Context, businessCode, reportCode string) error {
report, err := c.loader.GetReport(ctx, businessCode, reportCode)
if err != nil {
return err
}
sql := fmt.Sprintf("DROP TABLE IF EXISTS %s CASCADE", report.StatTableName)
_, err = gfdb.DB(ctx).Exec(ctx, sql)
return err
}
// TableExists 检查表是否存在
func (c *StatTableCreator) TableExists(ctx context.Context, tableName string) (bool, error) {
result, err := gfdb.DB(ctx).GetAll(ctx, "SELECT COUNT(*) FROM pg_tables WHERE tablename = $1", strings.ToLower(tableName))
if err != nil {
return false, err
}
if len(result) == 0 {
return false, nil
}
count := result[0]["count"].Int()
return count > 0, nil
}
// GetTableColumns 获取表字段列表
func (c *StatTableCreator) GetTableColumns(ctx context.Context, tableName string) ([]string, error) {
var columns []string
sql := `SELECT column_name FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public' ORDER BY ordinal_position`
rows, err := gfdb.DB(ctx).GetAll(ctx, sql, strings.ToLower(tableName))
if err != nil {
return nil, err
}
for _, row := range rows.List() {
if col, ok := row["column_name"].(string); ok {
columns = append(columns, col)
}
}
return columns, nil
}
// AlterTableAddColumns 为已存在的表添加新字段
func (c *StatTableCreator) AlterTableAddColumns(ctx context.Context, tableName string, newFields []model.FieldConfig) error {
existingCols, err := c.GetTableColumns(ctx, tableName)
if err != nil {
return err
}
existingMap := make(map[string]bool)
for _, col := range existingCols {
existingMap[col] = true
}
for _, f := range newFields {
if existingMap[f.FieldCode] {
continue
}
sql := fmt.Sprintf("ALTER TABLE %s ADD COLUMN IF NOT EXISTS %s %s", tableName, f.FieldCode, model.FieldTypeToPG(f.FieldType))
if _, err := gfdb.DB(ctx).Exec(ctx, sql); err != nil {
return fmt.Errorf("添加字段 %s 失败: %w", f.FieldCode, err)
}
}
return nil
}
+666
View File
@@ -0,0 +1,666 @@
package report
// ============================================================
// 通用报表引擎 - 完整调用示例
// ============================================================
//
// 包名: dataengine/common/report
// 入口: report.GetService() → *ReportService
//
// 接口一览:
// ┌──────────────┬─────────────────────────────────────────────────┐
// │ 分类 │ 接口 │
// ├──────────────┼─────────────────────────────────────────────────┤
// │ 配置 CRUD │ SaveBusiness / DelBusiness / GetBusiness │
// │ │ SaveReport / DelReport / GetReport │
// │ │ SaveField / DelField / GetField │
// │ │ SaveExtractConfig / DelExtractConfig / Get.. │
// ├──────────────┼─────────────────────────────────────────────────┤
// │ 数据抽取 │ ExtractDailyData / AutoCreateStatTable │
// ├──────────────┼─────────────────────────────────────────────────┤
// │ 报表查询 │ QueryReportByUserSelect │
// ├──────────────┼─────────────────────────────────────────────────┤
// │ 辅助查询 │ GetAllBusinesses / GetAllReports │
// │ │ GetReportFields / GetExtractConfigs │
// └──────────────┴─────────────────────────────────────────────────┘
//
// ============================================================
// ============================================================================
// 场景一:新平台零代码接入(快手电商为例)
// ============================================================================
//
// 背景:快手订单数据已通过数据引擎同步到 kuaishou_order_list 表(每条订单一行)。
// 需求:按店铺+天聚合订单数据 → 自动建统计宽表 → 抽取 → 前端自由查询报表。
//
// 全程只调 API 不写 SQL,前端管理后台可直接操作。
/*
package example
import (
"context"
"encoding/json"
"fmt"
"time"
"dataengine/common/report"
"dataengine/common/report/model"
)
func KuaishouExample() {
ctx := context.Background()
svc := report.GetService()
// ─── Step 1: 注册业务 ───────────────────────────────────────────
// 一个业务就是一个数据源平台(快手/抖音/淘宝/...)
_, _ = svc.SaveBusiness(ctx, &model.SaveBusinessReq{
BusinessCode: "KUAISHOU", // 唯一标识,后续所有接口都用它
BusinessName: "快手电商",
Description: "快手平台电商业务线",
Status: model.StatusActive,
Operator: "admin",
})
// ─── Step 2: 注册报表 ───────────────────────────────────────────
// 一个业务可以有多个报表(店铺日报、商品日报、主播日报...)
_, _ = svc.SaveReport(ctx, &model.SaveReportReq{
BusinessCode: "KUAISHOU",
ReportCode: "shop_daily_report", // 报表唯一编码
ReportName: "快手店铺日报",
StatTableName: "stat_kuaishou_shop_daily", // 统计宽表名(自动创建)
DateField: "stat_date", // 日期字段
ConflictKeys: []string{"shop_id", "stat_date"}, // 唯一约束(upsert 依据)
Operator: "admin",
})
// ─── Step 3: 配置字段(前端可选择的所有维度/指标/筛选) ─────────
// 这是前端"自定义报表"的数据源 —— 前端建好字段后,
// 用户选择哪些维度、哪些指标、怎么筛选,实时查。
// 3a. 维度字段:分组依据,不可聚合
dimensions := []model.SaveFieldReq{
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "shop_id", FieldName: "店铺ID", FieldType: "STRING",
FieldRole: "DIMENSION", IsSortable: true, SortOrder: 1, GroupName: "店铺", Operator: "admin"},
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "shop_name", FieldName: "店铺名称", FieldType: "STRING",
FieldRole: "DIMENSION", IsSortable: true, SortOrder: 2, GroupName: "店铺", Operator: "admin"},
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "stat_date", FieldName: "统计日期", FieldType: "DATE",
FieldRole: "DIMENSION", IsSortable: true, SortOrder: 3, GroupName: "时间", Operator: "admin"},
}
// 3b. 指标字段:聚合度量,前端可选 SUM/COUNT/AVG/MAX/MIN
indicators := []model.SaveFieldReq{
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "order_count", FieldName: "订单数", FieldType: "INT",
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM",
ValidAggregates: []string{"SUM", "COUNT", "AVG", "MAX", "MIN"},
SortOrder: 10, GroupName: "订单", Operator: "admin"},
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "order_amount", FieldName: "订单金额(元)", FieldType: "FLOAT",
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM",
ValidAggregates: []string{"SUM", "AVG", "MAX", "MIN"},
SortOrder: 11, GroupName: "金额", Unit: "元", Operator: "admin"},
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "paid_amount", FieldName: "实付金额(元)", FieldType: "FLOAT",
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM",
ValidAggregates: []string{"SUM", "AVG"},
SortOrder: 12, GroupName: "金额", Unit: "元", Operator: "admin"},
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "refund_amount", FieldName: "退款金额(元)", FieldType: "FLOAT",
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "SUM",
ValidAggregates: []string{"SUM", "AVG"},
SortOrder: 13, GroupName: "退款", Unit: "元", Operator: "admin"},
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "buyer_count", FieldName: "下单买家数", FieldType: "INT",
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "COUNT",
ValidAggregates: []string{"COUNT"},
SortOrder: 14, GroupName: "用户", Operator: "admin"},
// 衍生指标:退款率 = 退款金额 / 订单金额 * 100
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "refund_rate", FieldName: "退款率", FieldType: "FLOAT",
FieldRole: "INDICATOR", IsAggregatable: true, DefaultAggregate: "AVG",
Expression: "{refund_amount} / NULLIF({order_amount}, 0) * 100",
ExpressionType: "CALCULATED", FormatPattern: "#,##0.00",
Unit: "%", SortOrder: 20, GroupName: "退款", Operator: "admin"},
}
// 3c. 筛选字段:纯筛选,不出现在 SELECT 中
filters := []model.SaveFieldReq{
{BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
FieldCode: "order_status", FieldName: "订单状态", FieldType: "STRING",
FieldRole: "FILTER", IsFilterable: true,
FilterOperators: []string{"=", "IN"},
SortOrder: 30, GroupName: "筛选", Operator: "admin"},
}
for _, f := range dimensions {
_, _ = svc.SaveField(ctx, &f)
}
for _, f := range indicators {
_, _ = svc.SaveField(ctx, &f)
}
for _, f := range filters {
_, _ = svc.SaveField(ctx, &f)
}
// ─── Step 4: 配置数据抽取规则 ────────────────────────────────────
// 关键:AGGREGATE 模式,从源表聚合到统计宽表
_, _ = svc.SaveExtractConfig(ctx, &model.SaveExtractConfigReq{
BusinessCode: "KUAISHOU",
ReportCode: "shop_daily_report",
ExtractCode: "extract_shop_daily",
ExtractName: "快手店铺订单按天聚合",
SourceTableName: "kuaishou_order_list", // ← 源表(订单明细)
SourceTableAlias: "o",
TargetTableName: "stat_kuaishou_shop_daily", // ← 目标(统计宽表)
ExtractType: model.ExtractTypeIncremental, // 增量抽取
ExtractMode: model.ExtractModeAggregate, // ← 聚合模式
ExtractKeyField: "created_at", // 增量依据字段
GroupByFields: []string{"shop_id"}, // ← GROUP BY
FilterExpression: "o.order_status != 'CANCELLED'", // 过滤取消订单
// 字段映射:源表字段 → 目标表字段 + 聚合函数
FieldMappings: []model.FieldMapping{
{SourceField: "shop_id", TargetField: "shop_id", FieldType: "STRING"},
{SourceField: "shop_name", TargetField: "shop_name", FieldType: "STRING"},
{SourceField: "id", TargetField: "order_count", FieldType: "INT", AggregateFunction: "COUNT"},
{SourceField: "order_amount", TargetField: "order_amount", FieldType: "FLOAT", AggregateFunction: "SUM"},
{SourceField: "paid_amount", TargetField: "paid_amount", FieldType: "FLOAT", AggregateFunction: "SUM"},
{SourceField: "refund_amount",TargetField: "refund_amount",FieldType: "FLOAT", AggregateFunction: "SUM"},
{SourceField: "buyer_id", TargetField: "buyer_count", FieldType: "INT", AggregateFunction: "COUNT"},
},
BatchSize: 1000,
Operator: "admin",
})
// ─── Step 5: 每天定时抽取(cron/k8s CronJob 中调用) ─────────
// ExtractDailyData 内部:
// 1. 检测 stat_kuaishou_shop_daily 表是否存在
// 2. 不存在 → 根据 FieldConfig 自动 CREATE TABLE
// 3. 从 kuaishou_order_list 按 AGGREGATE 模式抽取当天数据
// 4. UPSERT 到统计宽表
today := time.Now().Format("2006-01-02")
resp, _ := svc.ExtractDailyData(ctx, "KUAISHOU", "shop_daily_report", today, "cron")
fmt.Printf("[%s] 抽取完成: 总%d 成功%d 失败%d 耗时%dms\n",
today, resp.TotalCount, resp.SuccessCount, resp.FailCount, resp.ExecTimeMs)
// 实际抽取生成的 SQLAGGREGATE 模式):
//
// INSERT INTO stat_kuaishou_shop_daily (...)
// SELECT ROW_NUMBER() OVER () AS id,
// '2026-06-10' AS stat_date,
// o.shop_id, o.shop_name,
// COUNT(o.id) AS order_count,
// SUM(o.order_amount) AS order_amount,
// SUM(o.paid_amount) AS paid_amount,
// SUM(o.refund_amount) AS refund_amount,
// COUNT(o.buyer_id) AS buyer_count
// FROM kuaishou_order_list o
// WHERE o.created_at::date = '2026-06-10'
// GROUP BY o.shop_id
// ─── Step 6: 前端查询 ──────────────────────────────────────────
// 6a. 前端先拉取可用字段列表
fields, _ := svc.GetReportFields(ctx, "KUAISHOU", "shop_daily_report")
// 返回:
// dimensions: [shop_id, shop_name, stat_date]
// indicators: [order_count, order_amount, paid_amount, refund_amount, buyer_count, refund_rate]
// filters: [order_status]
// 前端据此渲染选择器面板
// 6b. 用户选择: 维度=店铺, 指标=金额+订单数, 时间=近7天, 排名=TOP10
top10Req := &model.UserSelectQueryReq{
BusinessCode: "KUAISHOU",
ReportCode: "shop_daily_report",
Dimensions: []string{"shop_id", "shop_name"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total_amount"},
{FieldCode: "order_count", Aggregate: "SUM", Alias: "total_orders"},
{FieldCode: "paid_amount", Aggregate: "SUM", Alias: "total_paid"},
{FieldCode: "refund_rate", Aggregate: "AVG", Alias: "avg_refund_rate"},
},
TimeRange: &model.TimeRange{
StartDate: time.Now().AddDate(0, 0, -7).Format("2006-01-02"),
EndDate: today,
},
OrderBy: []model.OrderCondition{{FieldCode: "total_amount", Direction: "DESC"}},
Page: 1,
PageSize: 10,
}
top10Resp, _ := svc.QueryReportByUserSelect(ctx, top10Req)
fmt.Printf("TOP10 销售额排行 (总数=%d):\n", top10Resp.Total)
for i, row := range top10Resp.List {
b, _ := json.Marshal(row)
fmt.Printf(" %d. %s\n", i+1, string(b))
}
// 实际生成 SQL:
// SELECT o.shop_id, o.shop_name,
// SUM(o.order_amount) AS total_amount,
// SUM(o.order_count) AS total_orders,
// SUM(o.paid_amount) AS total_paid,
// AVG(refund_amount / NULLIF(order_amount,0) * 100) AS avg_refund_rate
// FROM stat_kuaishou_shop_daily o
// WHERE stat_date BETWEEN '2026-06-03' AND '2026-06-10'
// GROUP BY o.shop_id, o.shop_name
// ORDER BY total_amount DESC
// LIMIT 10 OFFSET 0
// 6c. 用户切换维度: 每日趋势(聚合所有店铺)
trendReq := &model.UserSelectQueryReq{
BusinessCode: "KUAISHOU",
ReportCode: "shop_daily_report",
Dimensions: []string{"stat_date"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "daily_amount"},
{FieldCode: "order_count", Aggregate: "SUM", Alias: "daily_orders"},
{FieldCode: "refund_amount",Aggregate: "SUM", Alias: "daily_refund"},
},
TimeRange: &model.TimeRange{
StartDate: time.Now().AddDate(0, 0, -30).Format("2006-01-02"),
EndDate: today,
},
TimeGroup: "day",
OrderBy: []model.OrderCondition{{FieldCode: "stat_date", Direction: "ASC"}},
PageSize: 100,
}
trendResp, _ := svc.QueryReportByUserSelect(ctx, trendReq)
fmt.Printf("30天趋势 (共%d行):\n", trendResp.Total)
// 6d. 加筛选条件: 只看活跃订单,金额>10000
filteredReq := &model.UserSelectQueryReq{
BusinessCode: "KUAISHOU",
ReportCode: "shop_daily_report",
Dimensions: []string{"shop_id", "shop_name"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total_amount"},
},
Filters: []model.FilterCondition{
{FieldCode: "order_status", Operator: "=", Value: "ACTIVE"},
{FieldCode: "total_amount", Operator: ">=", Value: 10000}, // 指标别名也可筛选
},
OrderBy: []model.OrderCondition{{FieldCode: "total_amount", Direction: "DESC"}},
PageSize: 20,
}
_ = filteredReq
// 6e. 按周汇总趋势
weeklyReq := &model.UserSelectQueryReq{
BusinessCode: "KUAISHOU",
ReportCode: "shop_daily_report",
Dimensions: []string{"shop_id"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "weekly_amount"},
},
TimeGroup: "week",
OrderBy: []model.OrderCondition{{FieldCode: "weekly_amount", Direction: "DESC"}},
PageSize: 50,
}
_ = weeklyReq
}
// ============================================================================
// 场景二:已有配置管理(CRUD 二次开发参考)
// ============================================================================
func CRUDExample() {
ctx := context.Background()
svc := report.GetService()
// ── 业务 CRUD ────────────────────────────────────────────
// 新增
result, _ := svc.SaveBusiness(ctx, &model.SaveBusinessReq{
BusinessCode: "DOUYIN", BusinessName: "抖音电商",
Operator: "admin",
})
businessId := result.Id
// 修改(传 ID 即修改)
result, _ = svc.SaveBusiness(ctx, &model.SaveBusinessReq{
ID: &businessId, BusinessCode: "DOUYIN",
BusinessName: "抖音电商(新版)", Operator: "admin",
})
// 查询
biz, _ := svc.GetBusiness(ctx, businessId)
fmt.Printf("%s: %s\n", biz.BusinessCode, biz.BusinessName)
// 删除
svc.DeleteBusiness(ctx, businessId)
// 全部列表
allBiz, _ := svc.GetAllBusinesses(ctx)
for _, b := range allBiz {
fmt.Printf("- %s (%s)\n", b.BusinessCode, b.BusinessName)
}
// ── 报表 CRUD ────────────────────────────────────────────
reportResult, _ := svc.SaveReport(ctx, &model.SaveReportReq{
BusinessCode: "DOUYIN",
ReportCode: "shop_daily_report",
ReportName: "抖音店铺日报",
StatTableName: "stat_douyin_shop_daily",
ConflictKeys: []string{"shop_id", "stat_date"},
Operator: "admin",
})
reportId := reportResult.Id
rpt, _ := svc.GetReport(ctx, reportId)
svc.DeleteReport(ctx, reportId)
_ = rpt
// ── 字段 CRUD ────────────────────────────────────────────
// 新增字段(id 不传 = 新增)
fieldResult, _ := svc.SaveField(ctx, &model.SaveFieldReq{
BusinessCode: "DOUYIN", ReportCode: "shop_daily_report",
FieldCode: "order_amount", FieldName: "订单金额",
FieldType: "FLOAT", FieldRole: "INDICATOR",
IsAggregatable: true, DefaultAggregate: "SUM",
ValidAggregates: []string{"SUM", "AVG", "MAX", "MIN"},
SortOrder: 10, GroupName: "金额", Operator: "admin",
})
fieldId := fieldResult.Id
// 修改字段(传 id = 更新)
svc.SaveField(ctx, &model.SaveFieldReq{
ID: &fieldId,
FieldName: "订单金额(元)",
Operator: "admin",
// ... 只传要修改的字段,未传的保持原值
})
f, _ := svc.GetField(ctx, fieldId)
svc.DeleteField(ctx, fieldId)
_ = f
// ── 抽取配置 CRUD ────────────────────────────────────────
ecResult, _ := svc.SaveExtractConfig(ctx, &model.SaveExtractConfigReq{
BusinessCode: "DOUYIN", ReportCode: "shop_daily_report",
ExtractCode: "extract_daily", ExtractName: "按天聚合抽取",
SourceTableName: "douyin_order_list", SourceTableAlias: "o",
TargetTableName: "stat_douyin_shop_daily",
ExtractMode: "AGGREGATE",
ExtractKeyField: "created_at",
GroupByFields: []string{"shop_id"},
FieldMappings: []model.FieldMapping{
{SourceField: "id", TargetField: "order_count", FieldType: "INT", AggregateFunction: "COUNT"},
{SourceField: "order_amount", TargetField: "order_amount", FieldType: "FLOAT", AggregateFunction: "SUM"},
},
Operator: "admin",
})
ecId := ecResult.Id
ec, _ := svc.GetExtractConfig(ctx, ecId)
allEc, _ := svc.GetExtractConfigs(ctx, "DOUYIN", "shop_daily_report")
svc.DeleteExtractConfig(ctx, ecId)
_, _ = ec, allEc
}
// ============================================================================
// 场景三:任意平台接入的通用模式(零硬编码)
// ============================================================================
//
// 假设要接入淘宝平台,taobao_order_list 表已有数据。
// 全程不写任何代码,只需调 CRUD API。
func GenericPlatformExample() {
ctx := context.Background()
svc := report.GetService()
// 1. 前端注册业务
svc.SaveBusiness(ctx, &model.SaveBusinessReq{
BusinessCode: "TAOBAO", BusinessName: "淘宝电商",
Operator: "admin",
})
// 2. 注册报表 + 指定统计宽表名(以后表名不再手工管理)
svc.SaveReport(ctx, &model.SaveReportReq{
BusinessCode: "TAOBAO",
ReportCode: "shop_daily_report",
ReportName: "淘宝店铺日报",
StatTableName: "stat_taobao_shop_daily",
ConflictKeys: []string{"shop_id", "stat_date"},
Operator: "admin",
})
// 3. 前端选择统计维度(自由组合,随时增删改)
svc.SaveField(ctx, &model.SaveFieldReq{
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
FieldCode: "shop_id", FieldName: "店铺ID",
FieldType: "STRING", FieldRole: "DIMENSION",
SortOrder: 1, GroupName: "店铺", Operator: "admin",
})
svc.SaveField(ctx, &model.SaveFieldReq{
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
FieldCode: "shop_name", FieldName: "店铺名称",
FieldType: "STRING", FieldRole: "DIMENSION",
SortOrder: 2, GroupName: "店铺", Operator: "admin",
})
svc.SaveField(ctx, &model.SaveFieldReq{
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
FieldCode: "stat_date", FieldName: "统计日期",
FieldType: "DATE", FieldRole: "DIMENSION",
SortOrder: 3, GroupName: "时间", Operator: "admin",
})
// 4. 前端选择统计指标(自由组合,随时增删改)
svc.SaveField(ctx, &model.SaveFieldReq{
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
FieldCode: "order_count", FieldName: "订单数",
FieldType: "INT", FieldRole: "INDICATOR",
IsAggregatable: true, DefaultAggregate: "SUM",
ValidAggregates: []string{"SUM", "COUNT", "AVG"},
SortOrder: 10, GroupName: "订单", Operator: "admin",
})
svc.SaveField(ctx, &model.SaveFieldReq{
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
FieldCode: "order_amount", FieldName: "订单金额",
FieldType: "FLOAT", FieldRole: "INDICATOR",
IsAggregatable: true, DefaultAggregate: "SUM",
ValidAggregates: []string{"SUM", "AVG", "MAX", "MIN"},
SortOrder: 11, GroupName: "金额", Unit: "元", Operator: "admin",
})
svc.SaveField(ctx, &model.SaveFieldReq{
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
FieldCode: "buyer_count", FieldName: "下单买家数",
FieldType: "INT", FieldRole: "INDICATOR",
IsAggregatable: true, DefaultAggregate: "COUNT",
ValidAggregates: []string{"COUNT"},
SortOrder: 12, GroupName: "用户", Operator: "admin",
})
// 5. 配置抽取规则
svc.SaveExtractConfig(ctx, &model.SaveExtractConfigReq{
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
ExtractCode: "extract_daily", ExtractName: "淘宝按天聚合",
SourceTableName: "taobao_order_list", SourceTableAlias: "o",
TargetTableName: "stat_taobao_shop_daily",
ExtractMode: "AGGREGATE",
ExtractKeyField: "created_at",
GroupByFields: []string{"shop_id"},
FieldMappings: []model.FieldMapping{
{SourceField: "shop_id", TargetField: "shop_id", FieldType: "STRING"},
{SourceField: "shop_name", TargetField: "shop_name", FieldType: "STRING"},
{SourceField: "id", TargetField: "order_count", FieldType: "INT", AggregateFunction: "COUNT"},
{SourceField: "order_amount", TargetField: "order_amount", FieldType: "FLOAT", AggregateFunction: "SUM"},
{SourceField: "buyer_id", TargetField: "buyer_count", FieldType: "INT", AggregateFunction: "COUNT"},
},
BatchSize: 1000,
Operator: "admin",
})
// 6. 定时任务每天执行
svc.ExtractDailyData(ctx, "TAOBAO", "shop_daily_report", "2026-06-10", "cron")
// 7. 前端实时查询
req := &model.UserSelectQueryReq{
BusinessCode: "TAOBAO", ReportCode: "shop_daily_report",
Dimensions: []string{"shop_id", "shop_name"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total"},
{FieldCode: "order_count", Aggregate: "SUM", Alias: "orders"},
},
TimeRange: &model.TimeRange{StartDate: "2026-06-01", EndDate: "2026-06-10"},
Page: 1, PageSize: 20,
}
resp, _ := svc.QueryReportByUserSelect(ctx, req)
fmt.Printf("查询结果: 共%d条, 耗时%dms\n", resp.Total, resp.ExecTimeMs)
// 平台接入完毕。全程零代码改动。
}
// ============================================================================
// 场景四:在外部业务服务(如 goview-report)中调用
// ============================================================================
func ExternalServiceExample() {
ctx := context.Background()
svc := report.GetService()
// 直接用,不需要初始化,内部自动懒加载和建表。
today := time.Now().Format("2006-01-02")
// 按天抽取
svc.ExtractDailyData(ctx, "KUAISHOU", "shop_daily_report", today, "cron")
// 实时查询
svc.QueryReportByUserSelect(ctx, &model.UserSelectQueryReq{
BusinessCode: "KUAISHOU", ReportCode: "shop_daily_report",
Dimensions: []string{"shop_id", "shop_name"},
Indicators: []model.IndicatorSelect{
{FieldCode: "order_amount", Aggregate: "SUM", Alias: "total_amount"},
},
TimeRange: &model.TimeRange{StartDate: "2026-06-01", EndDate: today},
Page: 1, PageSize: 20,
})
}
// ============================================================================
// 场景五:Direct 模式(逐行抽取,不做聚合)
// ============================================================================
//
// 适用于源表已经是一行一条统计记录的场景(如已预聚合的报表源表)。
func DirectModeExample() {
ctx := context.Background()
svc := report.GetService()
svc.SaveBusiness(ctx, &model.SaveBusinessReq{
BusinessCode: "HDWL", BusinessName: "HDWL业务", Operator: "admin",
})
svc.SaveReport(ctx, &model.SaveReportReq{
BusinessCode: "HDWL", ReportCode: "shop_daily_stat",
ReportName: "HDWL店铺日报", StatTableName: "stat_hdwl_shop_daily",
ConflictKeys: []string{"report_date"},
Operator: "admin",
})
// Direct 模式:不聚合,逐行映射
svc.SaveExtractConfig(ctx, &model.SaveExtractConfigReq{
BusinessCode: "HDWL", ReportCode: "shop_daily_stat",
ExtractCode: "extract_direct", ExtractName: "直接逐行抽取",
SourceTableName: "hdwl_daily_summary", SourceTableAlias: "s",
TargetTableName: "stat_hdwl_shop_daily",
ExtractMode: "DIRECT", // ← DIRECT 模式
ExtractKeyField: "report_date",
FieldMappings: []model.FieldMapping{
{SourceField: "shop_id", TargetField: "shop_id", FieldType: "STRING"},
{SourceField: "shop_name", TargetField: "shop_name", FieldType: "STRING"},
{SourceField: "total_sale", TargetField: "sale_amount", FieldType: "FLOAT"},
{SourceField: "total_orders", TargetField: "order_count", FieldType: "INT"},
},
Operator: "admin",
})
svc.ExtractDailyData(ctx, "HDWL", "shop_daily_stat", "2026-06-10", "cron")
}
// ============================================================================
// 场景六:前端交互流程参考
// ============================================================================
//
// 前端代码调用示例(伪代码,展示 API 调用顺序):
//
// // 1. 页面加载 → 获取业务列表 → 渲染下拉框
// GET /api/report/businesses
// 返回: [{ businessCode: "KUAISHOU", businessName: "快手电商" }, ...]
//
// // 2. 用户选择业务 → 获取报表列表
// GET /api/report/reports?businessCode=KUAISHOU
// 返回: [{ reportCode: "shop_daily_report", reportName: "快手店铺日报" }, ...]
//
// // 3. 用户选择报表 → 获取可用字段 → 渲染维度/指标/筛选选择器
// GET /api/report/fields?businessCode=KUAISHOU&reportCode=shop_daily_report
// 返回: { dimensions: [...], indicators: [...], filters: [...] }
//
// // 4. 用户选好条件 → 查询
// POST /api/report/query
// Body: {
// "businessCode": "KUAISHOU", "reportCode": "shop_daily_report",
// "dimensions": ["shop_id", "shop_name"],
// "indicators": [
// {"fieldCode": "order_amount", "aggregate": "SUM", "alias": "total"},
// {"fieldCode": "order_count", "aggregate": "SUM", "alias": "count"}
// ],
// "timeRange": {"startDate": "2026-06-01", "endDate": "2026-06-10"},
// "orderBy": [{"fieldCode": "total", "direction": "DESC"}],
// "page": 1, "pageSize": 20
// }
// 返回: { list: [...], total: 152, page: 1, totalPages: 8, execTimeMs: 45 }
//
// // 5. 翻页/换维度/换指标 → 重新调 Step 4
//
// 管理后台(用户可自行维护配置):
//
// // 新增业务
// POST /api/report/business/save
// Body: { "businessCode": "TAOBAO", "businessName": "淘宝电商" }
//
// // 新增/修改字段(用户自定义统计维度)
// POST /api/report/field/save
// Body: { "businessCode": "TAOBAO", "reportCode": "shop_daily_report",
// "fieldCode": "category", "fieldName": "商品类目",
// "fieldType": "STRING", "fieldRole": "DIMENSION" }
//
// // 配置抽取规则
// POST /api/report/extractConfig/save
// Body: { "businessCode": "TAOBAO", "reportCode": "shop_daily_report",
// "extractCode": "extract_daily", "sourceTableName": "taobao_order_list",
// "extractMode": "AGGREGATE", "groupByFields": ["shop_id"],
// "fieldMappings": [...] }
// ============================================================================
// Direct vs AGGREGATE 模式对比
// ============================================================================
//
// ┌──────────┬──────────────────────────────────────────────┐
// │ 模式 │ 行为 │
// ├──────────┼──────────────────────────────────────────────┤
// │ DIRECT │ 逐行映射,1:1 从源表复制到目标表 │
// │ │ 适用:源表已是一行一统计 │
// │ │ SQL: SELECT ... FROM source │
// ├──────────┼──────────────────────────────────────────────┤
// │ AGGREGATE│ GROUP BY + 聚合函数,N:1 聚合 │
// │ │ 适用:源表是明细表(如订单行) │
// │ │ SQL: SELECT ... SUM() COUNT() ... │
// │ │ FROM source GROUP BY groupByFields │
// └──────────┴──────────────────────────────────────────────┘
//
// 可聚合函数: SUM / COUNT / AVG / MAX / MIN
// 字段角色: DIMENSION(维度) / INDICATOR(指标) / FILTER(筛选) / FILTER_ONLY
// 字段类型: STRING / INT / FLOAT / DATE / DATETIME / JSONB
// 操作符: = / != / > / < / >= / <= / IN / LIKE / BETWEEN
// 时间分组: day / week / month / quarter
*/
+199
View File
@@ -0,0 +1,199 @@
package executor
import (
"context"
"fmt"
"math"
"strings"
"time"
"dataengine/common/report/builder"
"dataengine/common/report/model"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/sirupsen/logrus"
)
// QueryExecutor SQL执行器
type QueryExecutor struct {
sqlBuilder *builder.SQLBuilder
}
// NewQueryExecutor 创建查询执行器
func NewQueryExecutor() *QueryExecutor {
return &QueryExecutor{
sqlBuilder: builder.NewSQLBuilder(),
}
}
// QueryReportByUserSelect 根据用户选择实时查询报表数据(核心接口)
func (e *QueryExecutor) QueryReportByUserSelect(ctx context.Context, req *model.UserSelectQueryReq) (*model.UserSelectQueryResp, error) {
start := time.Now()
logger := logrus.WithFields(logrus.Fields{
"businessCode": req.BusinessCode,
"reportCode": req.ReportCode,
})
// 1. 参数校验
if err := e.validateReq(req); err != nil {
return nil, fmt.Errorf("参数校验失败: %w", err)
}
// 2. 构建查询SQL
querySQL, queryArgs, metadata, err := e.sqlBuilder.BuildQuerySQL(ctx, req)
if err != nil {
return nil, fmt.Errorf("构建查询SQL失败: %w", err)
}
logger.Debugf("查询SQL: %s args: %v", querySQL, queryArgs)
// 3. 获取记录总数
total, err := e.executeCount(ctx, metadata, queryArgs)
if err != nil {
return nil, fmt.Errorf("查询总数失败: %w", err)
}
// 4. 分页查询
if req.Page < 1 {
req.Page = 1
}
if req.PageSize < 1 {
req.PageSize = 20
}
if req.PageSize > 1000 {
req.PageSize = 1000
}
pagedSQL := e.sqlBuilder.AddLimit(querySQL, req.Page, req.PageSize)
logger.Debugf("分页SQL: %s", pagedSQL)
dataResult, err := gfdb.DB(ctx).GetAll(ctx, pagedSQL, queryArgs...)
if err != nil {
return nil, fmt.Errorf("查询数据失败: %w", err)
}
var list []map[string]interface{}
if dataResult.Len() > 0 {
list = dataResult.List()
}
// 计算总页数
totalPages := int(math.Ceil(float64(total) / float64(req.PageSize)))
execTime := time.Since(start).Milliseconds()
resp := &model.UserSelectQueryResp{
List: list,
Total: total,
Page: req.Page,
PageSize: req.PageSize,
TotalPages: totalPages,
Sql: querySQL,
ExecTimeMs: execTime,
}
logger.Infof("报表查询完成, 总数:%d 耗时:%dms", total, execTime)
return resp, nil
}
// executeCount 执行总数统计
func (e *QueryExecutor) executeCount(ctx context.Context, metadata map[string]interface{}, args []interface{}) (int64, error) {
countSql, ok := metadata["countSql"].(string)
if !ok || countSql == "" {
return 0, nil
}
result, err := gfdb.DB(ctx).GetAll(ctx, countSql, args...)
if err != nil {
return 0, fmt.Errorf("统计总数失败: %w", err)
}
var total int64
if result.Len() > 0 {
firstRow := result.List()[0]
// 遍历第一行所有列,找到第一个可解析为 int64 的值作为总数
for _, v := range firstRow {
if n, ok := v.(int64); ok {
total = n
break
}
}
// 如果遍历未找到 int64 类型值,尝试通过 gfdb 的 Int64 方法获取
if total == 0 && len(firstRow) > 0 {
for k := range firstRow {
total = result[0][k].Int64()
break
}
}
}
return total, nil
}
// validateReq 校验请求参数
func (e *QueryExecutor) validateReq(req *model.UserSelectQueryReq) error {
if req.BusinessCode == "" {
return fmt.Errorf("业务编码不能为空")
}
if req.ReportCode == "" {
return fmt.Errorf("报表编码不能为空")
}
if len(req.Indicators) == 0 {
return fmt.Errorf("必须选择至少一个指标")
}
// 校验指标
for i, ind := range req.Indicators {
if ind.FieldCode == "" {
return fmt.Errorf("第%d个指标字段编码不能为空", i+1)
}
if ind.Aggregate != "" {
agg := strings.ToUpper(ind.Aggregate)
validAggs := map[string]bool{
"SUM": true,
"COUNT": true,
"AVG": true,
"MAX": true,
"MIN": true,
}
if !validAggs[agg] {
return fmt.Errorf("不支持的聚合方式: %s", ind.Aggregate)
}
}
}
// 校验时间分组
if req.TimeGroup != "" {
validGroups := map[string]bool{
"day": true,
"week": true,
"month": true,
"quarter": true,
"year": true,
}
if !validGroups[req.TimeGroup] {
return fmt.Errorf("不支持的时间分组: %s", req.TimeGroup)
}
}
return nil
}
// RawQuery 执行原始SQL查询(用于特殊场景)
func (e *QueryExecutor) RawQuery(ctx context.Context, sql string, args ...interface{}) ([]map[string]interface{}, error) {
result, err := gfdb.DB(ctx).GetAll(ctx, sql, args...)
if err != nil {
return nil, fmt.Errorf("原始查询失败: %w", err)
}
if result.Len() > 0 {
return result.List(), nil
}
return nil, nil
}
// ExecRaw 执行原始SQLINSERT/UPDATE/DELETE
func (e *QueryExecutor) ExecRaw(ctx context.Context, sql string, args ...interface{}) error {
_, err := gfdb.DB(ctx).Exec(ctx, sql, args...)
return err
}
+670
View File
@@ -0,0 +1,670 @@
package extract
import (
"context"
"fmt"
"sort"
"strings"
"time"
"dataengine/common/report/config"
"dataengine/common/report/model"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
"github.com/sirupsen/logrus"
)
// DailyExtractor 天级数据抽取器
type DailyExtractor struct {
loader *config.ConfigLoader
}
// NewDailyExtractor 创建抽取器
func NewDailyExtractor() *DailyExtractor {
return &DailyExtractor{
loader: config.GetLoader(),
}
}
// ExtractDailyData 按天抽取数据(业务层定时任务调用)
func (e *DailyExtractor) ExtractDailyData(ctx context.Context, businessCode, reportCode, statDate, executor string) (*model.ExtractDailyDataResp, error) {
start := time.Now()
logger := logrus.WithFields(logrus.Fields{
"businessCode": businessCode,
"reportCode": reportCode,
"statDate": statDate,
})
// 1. 获取报表配置
report, err := e.loader.GetReport(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("获取报表配置失败: %w", err)
}
// 2. 获取抽取配置
extractConfigs, err := e.loader.GetExtractConfigs(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("获取抽取配置失败: %w", err)
}
if len(extractConfigs) == 0 {
return nil, fmt.Errorf("没有可用的抽取配置")
}
// 3. 获取字段配置
fieldMap, err := e.loader.GetFieldMap(ctx, businessCode, reportCode)
if err != nil {
return nil, fmt.Errorf("获取字段配置失败: %w", err)
}
// 4. 确保统计宽表存在
if err := e.ensureStatTableExists(ctx, report, fieldMap); err != nil {
return nil, fmt.Errorf("确保统计宽表存在失败: %w", err)
}
totalCount := 0
successCount := 0
failCount := 0
var lastErr error
// 5. 遍历每个抽取配置
for _, ec := range extractConfigs {
// 检查幂等性
exLog, err := e.loader.GetExtractLog(ctx, businessCode, reportCode, ec.ExtractCode, statDate)
if err != nil {
logger.Errorf("获取抽取记录失败: %v", err)
}
if exLog != nil && exLog.Status == model.ExtractStatusSuccess {
logger.Infof("抽取配置 %s 日期 %s 已完成,跳过", ec.ExtractCode, statDate)
continue
}
// 创建抽取记录
extractLog := &model.ExtractLog{
BusinessCode: businessCode,
ReportCode: reportCode,
ExtractCode: ec.ExtractCode,
StatDate: statDate,
ExtractType: ec.ExtractType,
Status: model.ExtractStatusRunning,
Executor: executor,
StartTime: &start,
}
if err := e.loader.CreateExtractLog(ctx, extractLog); err != nil {
logger.Errorf("创建抽取日志失败: %v", err)
}
// 执行抽取
c, s, f, err := e.executeExtract(ctx, &ec, report, fieldMap, statDate)
totalCount += c
successCount += s
failCount += f
// 更新抽取记录
now := time.Now()
extractLog.EndTime = &now
extractLog.TotalCount = c
extractLog.SuccessCount = s
extractLog.FailCount = f
if err != nil {
extractLog.Status = model.ExtractStatusFailed
extractLog.ErrorMessage = err.Error()
lastErr = err
logger.Errorf("抽取配置 %s 执行失败: %v", ec.ExtractCode, err)
} else {
extractLog.Status = model.ExtractStatusSuccess
logger.Infof("抽取配置 %s 完成, 总数:%d 成功:%d 失败:%d", ec.ExtractCode, c, s, f)
}
if updateErr := e.loader.UpdateExtractLog(ctx, extractLog); updateErr != nil {
logger.Errorf("更新抽取记录失败: %v", updateErr)
}
}
execTime := time.Since(start).Milliseconds()
logger.Infof("按天抽取完成, 总数:%d 成功:%d 失败:%d 耗时:%dms", totalCount, successCount, failCount, execTime)
resp := &model.ExtractDailyDataResp{
Success: lastErr == nil,
TotalCount: totalCount,
SuccessCount: successCount,
FailCount: failCount,
ExecTimeMs: execTime,
}
if lastErr != nil {
resp.ErrorMsg = lastErr.Error()
}
return resp, nil
}
// executeExtract 执行单个抽取配置
func (e *DailyExtractor) executeExtract(ctx context.Context, ec *model.ExtractConfig, report *model.ReportConfig, fieldMap map[string]*model.FieldConfig, statDate string) (total, success, fail int, err error) {
logger := logrus.WithField("extractCode", ec.ExtractCode)
// 1. 构建抽取SQL
extractSQL, whereArgs, err := e.buildExtractSQL(ctx, ec, report, statDate)
if err != nil {
return 0, 0, 0, fmt.Errorf("构建抽取SQL失败: %w", err)
}
logger.Debugf("抽取SQL: %s", extractSQL)
// 2. 分批抽取
batchSize := ec.BatchSize
if batchSize <= 0 {
batchSize = 1000
}
offset := 0
for {
// 添加分页
pagedSQL := fmt.Sprintf("%s LIMIT %d OFFSET %d", extractSQL, batchSize, offset)
args := whereArgs
rows, queryErr := gfdb.DB(ctx).GetAll(ctx, pagedSQL, args...)
if queryErr != nil {
return total, success, fail, fmt.Errorf("抽取查询失败: %w", queryErr)
}
batchCount := rows.Len()
if batchCount == 0 {
break
}
// 3. 应用转换规则(仅 DIRECT 模式需注入审计字段,AGGREGATE 模式已由SQL处理)
dataList := rows.List()
if ec.ExtractMode != model.ExtractModeAggregate {
for i := range dataList {
e.applyTransformRules(ec, dataList[i])
dataList[i]["tenant_id"] = 1
dataList[i]["business_code"] = ec.BusinessCode
}
}
// 4. 写入统计宽表
c, _, writeErr := e.batchUpsert(ctx, report.StatTableName, report.ConflictKeys, dataList)
if writeErr != nil {
logger.Errorf("批量写入失败 (offset=%d): %v", offset, writeErr)
fail += batchCount
} else {
success += c
}
total += batchCount
offset += batchSize
if batchCount < batchSize {
break
}
}
return total, success, fail, nil
}
// buildExtractSQL 构建抽取SQL
func (e *DailyExtractor) buildExtractSQL(ctx context.Context, ec *model.ExtractConfig, report *model.ReportConfig, statDate string) (string, []interface{}, error) {
var args []interface{}
sourceTable := ec.SourceTableName
if ec.SourceTableAlias != "" {
sourceTable = ec.SourceTableAlias
} else {
sourceTable = "s"
}
// 日期字段
dateField := report.DateField
if dateField == "" {
dateField = "stat_date"
}
// 判断抽取模式
mode := ec.ExtractMode
if mode == "" {
mode = model.ExtractModeDirect
}
if mode == model.ExtractModeAggregate {
return e.buildAggregateExtractSQL(ec, report, sourceTable, dateField, statDate)
}
// === 默认 DIRECT 模式:逐行抽取 ===
return e.buildDirectExtractSQL(ec, report, sourceTable, dateField, statDate), args, nil
}
// buildDirectExtractSQL 逐行抽取模式SQL(直接映射,不做聚合)
func (e *DailyExtractor) buildDirectExtractSQL(ec *model.ExtractConfig, report *model.ReportConfig, sourceTable, dateField, statDate string) string {
var selectParts []string
// 基础审计字段(常量注入)
selectParts = append(selectParts, "0 AS id")
selectParts = append(selectParts, "1 AS tenant_id")
selectParts = append(selectParts, fmt.Sprintf("'%s' AS business_code", ec.BusinessCode))
selectParts = append(selectParts, "'system' AS creator")
selectParts = append(selectParts, "NOW() AS created_at")
selectParts = append(selectParts, "'system' AS updater")
selectParts = append(selectParts, "NOW() AS updated_at")
selectParts = append(selectParts, "NULL::TIMESTAMP AS deleted_at")
// 日期字段
selectParts = append(selectParts, fmt.Sprintf("'%s' AS %s", statDate, dateField))
// 原始数据
selectParts = append(selectParts, "'{}'::JSONB AS raw_data")
// 字段映射
for _, mapping := range ec.FieldMappings {
targetField := mapping.TargetField
sourceField := mapping.SourceField
var expr string
if mapping.TransformRule != nil {
expr = e.applyTransformExpr(mapping.TransformRule, fmt.Sprintf("%s.%s", sourceTable, sourceField))
} else {
expr = fmt.Sprintf("%s.%s", sourceTable, sourceField)
}
if mapping.DefaultValue != nil {
expr = fmt.Sprintf("COALESCE(%s, '%v')", expr, mapping.DefaultValue)
}
selectParts = append(selectParts, fmt.Sprintf("%s AS %s", expr, targetField))
}
// FROM + JOIN
fromClause := e.buildFromClause(ec, sourceTable)
// JOIN 字段映射
selectParts = append(selectParts, e.buildJoinFieldSelects(ec)...)
// WHERE
whereClause := e.buildWhereClause(ec, sourceTable, statDate)
return fmt.Sprintf("SELECT %s FROM %s %s", strings.Join(selectParts, ", "), fromClause, whereClause)
}
// buildAggregateExtractSQL 聚合抽取模式SQLGROUP BY + SUM/COUNT/AVG
func (e *DailyExtractor) buildAggregateExtractSQL(ec *model.ExtractConfig, report *model.ReportConfig, sourceTable, dateField, statDate string) (string, []interface{}, error) {
var selectParts []string
var groupByParts []string
var args []interface{}
// 基础审计字段(聚合模式下用常量)
selectParts = append(selectParts, "ROW_NUMBER() OVER () AS id") // 伪自增ID
selectParts = append(selectParts, "1 AS tenant_id")
selectParts = append(selectParts, fmt.Sprintf("'%s' AS business_code", ec.BusinessCode))
selectParts = append(selectParts, "'system' AS creator")
selectParts = append(selectParts, "NOW() AS created_at")
selectParts = append(selectParts, "'system' AS updater")
selectParts = append(selectParts, "NOW() AS updated_at")
selectParts = append(selectParts, "NULL::TIMESTAMP AS deleted_at")
// 日期字段(常量)
selectParts = append(selectParts, fmt.Sprintf("'%s' AS %s", statDate, dateField))
// 原始数据
selectParts = append(selectParts, "'{}'::JSONB AS raw_data")
// GroupByFields 集合(快速查找)
gbySet := make(map[string]bool)
for _, gbf := range ec.GroupByFields {
gbySet[gbf] = true
}
// 添加 GroupBy 字段到 SELECT 和 GROUP BY
for _, gbf := range ec.GroupByFields {
selectParts = append(selectParts, fmt.Sprintf("%s.%s", sourceTable, gbf))
groupByParts = append(groupByParts, fmt.Sprintf("%s.%s", sourceTable, gbf))
}
// 字段映射:根据 AggregateFunction 决定聚合方式
for _, mapping := range ec.FieldMappings {
targetField := mapping.TargetField
sourceField := mapping.SourceField
// 构建源表达式
var sourceExpr string
if mapping.TransformRule != nil {
sourceExpr = e.applyTransformExpr(mapping.TransformRule, fmt.Sprintf("%s.%s", sourceTable, sourceField))
} else {
sourceExpr = fmt.Sprintf("%s.%s", sourceTable, sourceField)
}
// 判断是否需要聚合
aggFunc := strings.ToUpper(mapping.AggregateFunction)
if aggFunc != "" && !gbySet[sourceField] {
// 聚合字段:SUM(s.xxx) / COUNT(s.xxx) / AVG(s.xxx)
expr := fmt.Sprintf("%s(%s)", aggFunc, sourceExpr)
if mapping.DefaultValue != nil {
expr = fmt.Sprintf("COALESCE(%s, %v)", expr, mapping.DefaultValue)
}
selectParts = append(selectParts, fmt.Sprintf("%s AS %s", expr, targetField))
} else if gbySet[sourceField] {
// GroupBy 字段不需要重复加入 SELECT(已通过 groupByFields 处理)
continue
} else {
// 非聚合字段,也未在 GroupBy 中 → 用 MAX/MIN 取值(兼容 PG only_full_group_by
expr := fmt.Sprintf("MAX(%s)", sourceExpr)
if mapping.DefaultValue != nil {
expr = fmt.Sprintf("COALESCE(%s, %v)", expr, mapping.DefaultValue)
}
selectParts = append(selectParts, fmt.Sprintf("%s AS %s", expr, targetField))
}
}
// FROM + JOIN
fromClause := e.buildFromClause(ec, sourceTable)
// WHERE
whereClause := e.buildWhereClause(ec, sourceTable, statDate)
// 组合 SQL
sql := fmt.Sprintf("SELECT %s FROM %s %s",
strings.Join(selectParts, ", "),
fromClause,
whereClause)
// GROUP BY
if len(groupByParts) > 0 {
sql += " GROUP BY " + strings.Join(groupByParts, ", ")
}
return sql, args, nil
}
// buildFromClause 构建FROM + JOIN子句
func (e *DailyExtractor) buildFromClause(ec *model.ExtractConfig, sourceTable string) string {
fromClause := fmt.Sprintf("%s %s", ec.SourceTableName, sourceTable)
for _, join := range ec.JoinConfigs {
joinType := "LEFT JOIN"
jType := strings.ToUpper(join.JoinType)
if jType == "INNER" {
joinType = "INNER JOIN"
} else if jType == "RIGHT" {
joinType = "RIGHT JOIN"
}
joinAlias := join.JoinAlias
if joinAlias == "" {
joinAlias = join.JoinTable
}
fromClause += fmt.Sprintf(" %s %s %s ON %s", joinType, join.JoinTable, joinAlias, join.JoinCondition)
}
return fromClause
}
// buildJoinFieldSelects 构建JOIN表的字段映射SELECT部分
func (e *DailyExtractor) buildJoinFieldSelects(ec *model.ExtractConfig) []string {
var parts []string
for _, join := range ec.JoinConfigs {
joinAlias := join.JoinAlias
if joinAlias == "" {
joinAlias = join.JoinTable
}
for _, jm := range join.FieldMappings {
targetField := jm.TargetField
sourceExpr := fmt.Sprintf("%s.%s", joinAlias, jm.SourceField)
if jm.TransformRule != nil {
sourceExpr = e.applyTransformExpr(jm.TransformRule, sourceExpr)
}
parts = append(parts, fmt.Sprintf("%s AS %s", sourceExpr, targetField))
}
}
return parts
}
// buildWhereClause 构建WHERE子句
func (e *DailyExtractor) buildWhereClause(ec *model.ExtractConfig, sourceTable, statDate string) string {
var whereConditions []string
// 日期范围(增量抽取)
if ec.ExtractType == model.ExtractTypeIncremental && ec.ExtractKeyField != "" {
dateCondition := fmt.Sprintf("%s.%s::date = '%s'", sourceTable, ec.ExtractKeyField, statDate)
whereConditions = append(whereConditions, dateCondition)
}
// 自定义过滤条件
if ec.FilterExpression != "" {
whereConditions = append(whereConditions, ec.FilterExpression)
}
if len(whereConditions) == 0 {
return ""
}
return "WHERE " + strings.Join(whereConditions, " AND ")
}
// applyTransformExpr 应用转换表达式
func (e *DailyExtractor) applyTransformExpr(rule *model.TransformRule, sourceExpr string) string {
switch rule.RuleType {
case "CALCULATE":
if rule.Expression != "" {
return strings.ReplaceAll(rule.Expression, "{source}", sourceExpr)
}
case "FORMAT":
if rule.Format != "" {
return fmt.Sprintf("TO_CHAR(%s, '%s')", sourceExpr, rule.Format)
}
case "MAPPING":
// 在代码中运行时做映射
return sourceExpr
}
return sourceExpr
}
// applyTransformRules 应用运行时转换规则(映射等代码转换)
func (e *DailyExtractor) applyTransformRules(ec *model.ExtractConfig, row map[string]interface{}) {
for _, rule := range ec.TransformRules {
if rule.RuleType != "MAPPING" {
continue
}
sourceField := rule.Expression // 存储源字段名
targetField := rule.RuleCode // 存储目标字段名
if sourceVal, ok := row[sourceField]; ok {
strVal := gconv.String(sourceVal)
if mapped, exists := rule.Mapping[strVal]; exists {
row[targetField] = mapped
}
}
}
}
// ensureStatTableExists 确保统计宽表存在,如有新增字段自动加列
func (e *DailyExtractor) ensureStatTableExists(ctx context.Context, report *model.ReportConfig, fieldMap map[string]*model.FieldConfig) error {
tableName := report.StatTableName
// 检查表是否存在
result, err := gfdb.DB(ctx).GetAll(ctx, "SELECT COUNT(*) FROM pg_tables WHERE tablename = $1", strings.ToLower(tableName))
if err != nil {
return err
}
count := 0
if len(result) > 0 {
count = result[0]["count"].Int()
}
if count == 0 {
// 需要建表
return e.createStatTable(ctx, report, fieldMap)
}
// 表已存在,检查并添加新增字段
if err := e.alterTableAddColumns(ctx, tableName, fieldMap); err != nil {
logrus.Warnf("自动加列失败 %s: %v", tableName, err)
// 不返回 error,加列失败不应阻塞抽取
}
logrus.Infof("统计宽表 %s 已存在", tableName)
return nil
}
// alterTableAddColumns 为已存在的统计宽表添加新增字段
func (e *DailyExtractor) alterTableAddColumns(ctx context.Context, tableName string, fieldMap map[string]*model.FieldConfig) error {
// 获取已有列
rows, err := gfdb.DB(ctx).GetAll(ctx,
"SELECT column_name FROM information_schema.columns WHERE table_name = $1 AND table_schema = 'public'",
strings.ToLower(tableName))
if err != nil {
return fmt.Errorf("查询表结构失败: %w", err)
}
existingCols := make(map[string]bool)
for _, row := range rows.List() {
if col, ok := row["column_name"].(string); ok {
existingCols[col] = true
}
}
for _, fc := range fieldMap {
if existingCols[fc.FieldCode] {
continue
}
colType := model.FieldTypeToPG(fc.FieldType)
sql := fmt.Sprintf("ALTER TABLE %s ADD COLUMN IF NOT EXISTS %s %s", tableName, fc.FieldCode, colType)
if _, err := gfdb.DB(ctx).Exec(ctx, sql); err != nil {
return fmt.Errorf("添加字段 %s 失败: %w", fc.FieldCode, err)
}
logrus.Infof("统计宽表 %s 自动添加字段 %s %s", tableName, fc.FieldCode, colType)
}
return nil
}
// createStatTable 创建统计宽表
func (e *DailyExtractor) createStatTable(ctx context.Context, report *model.ReportConfig, fieldMap map[string]*model.FieldConfig) error {
var cols []string
// 标准审计字段
cols = append(cols, "id BIGSERIAL PRIMARY KEY")
cols = append(cols, "tenant_id BIGINT NOT NULL DEFAULT 0")
cols = append(cols, "business_code VARCHAR(64) NOT NULL DEFAULT ''")
cols = append(cols, "creator VARCHAR(64) DEFAULT ''")
cols = append(cols, "created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()")
cols = append(cols, "updater VARCHAR(64) DEFAULT ''")
cols = append(cols, "updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()")
cols = append(cols, "deleted_at TIMESTAMP WITH TIME ZONE")
// 日期字段
dateField := report.DateField
if dateField == "" {
dateField = "stat_date"
}
cols = append(cols, fmt.Sprintf("%s VARCHAR(16) NOT NULL DEFAULT ''", dateField))
// 业务字段
for _, fc := range fieldMap {
fc := fc
if fc.FieldCode == dateField {
continue
}
colType := model.FieldTypeToPG(fc.FieldType)
cols = append(cols, fmt.Sprintf("%s %s", fc.FieldCode, colType))
}
// 原始数据
cols = append(cols, "raw_data JSONB DEFAULT '{}'")
tableName := report.StatTableName
sql := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (\n %s\n)", tableName, strings.Join(cols, ",\n "))
logrus.Infof("创建统计宽表: %s", tableName)
if _, err := gfdb.DB(ctx).Exec(ctx, sql); err != nil {
return fmt.Errorf("建表失败: %w", err)
}
// 冲突唯一索引
if len(report.ConflictKeys) > 0 {
indexName := fmt.Sprintf("uq_%s_conflict", tableName)
indexCols := strings.Join(report.ConflictKeys, ", ")
indexSQL := fmt.Sprintf("CREATE UNIQUE INDEX IF NOT EXISTS %s ON %s (%s)", indexName, tableName, indexCols)
if _, err := gfdb.DB(ctx).Exec(ctx, indexSQL); err != nil {
logrus.Warnf("创建冲突索引失败: %v", err)
}
}
// 字段注释
for _, fc := range fieldMap {
fc := fc
if fc.FieldName != "" {
escaped := strings.ReplaceAll(fc.FieldName, "'", "''")
commentSQL := fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", tableName, fc.FieldCode, escaped)
if _, err := gfdb.DB(ctx).Exec(ctx, commentSQL); err != nil {
logrus.Warnf("添加字段注释失败 [%s.%s]: %v", tableName, fc.FieldCode, err)
}
}
}
return nil
}
// batchUpsert 批量写入统计宽表(使用原始SQL INSERT,避免ORM兼容问题)
func (e *DailyExtractor) batchUpsert(ctx context.Context, tableName string, conflictKeys []string, rows []map[string]interface{}) (int, []string, error) {
if len(rows) == 0 {
return 0, nil, nil
}
// 去除 id 和 created_at 列(数据库自增/默认值)
var keys []string
for k := range rows[0] {
if k == "id" || k == "created_at" || k == "updated_at" || k == "deleted_at" {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
cols := strings.Join(keys, ", ")
placeholders := make([]string, len(keys))
for i := range keys {
placeholders[i] = fmt.Sprintf("$%d", i+1)
}
phStr := strings.Join(placeholders, ", ")
sqlStr := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", tableName, cols, phStr)
if len(conflictKeys) > 0 {
var updateParts []string
for _, k := range keys {
if k == "id" || k == "created_at" {
continue
}
updateParts = append(updateParts, fmt.Sprintf("%s = EXCLUDED.%s", k, k))
}
sqlStr += " ON CONFLICT (" + strings.Join(conflictKeys, ", ") + ") DO UPDATE SET " + strings.Join(updateParts, ", ")
}
batchSize := 100
total := 0
for i := 0; i < len(rows); i += batchSize {
end := i + batchSize
if end > len(rows) {
end = len(rows)
}
batch := rows[i:end]
for _, row := range batch {
var args []interface{}
for _, k := range keys {
args = append(args, row[k])
}
if _, err := gfdb.DB(ctx).Exec(ctx, sqlStr, args...); err != nil {
logrus.Errorf("逐行写入 %s 失败: %v", tableName, err)
} else {
total++
}
}
}
return total, nil, nil
}
+473
View File
@@ -0,0 +1,473 @@
package model
import (
"time"
"gitea.redpowerfuture.com/red-future/common/beans"
)
// ============================================================
// 实体定义
// ============================================================
// BusinessConfig 业务配置
type BusinessConfig struct {
beans.SQLBaseDO `orm:",inherit"`
BusinessCode string `orm:"business_code" json:"businessCode" dc:"业务编码" eg:"KUAISHOU"`
BusinessName string `orm:"business_name" json:"businessName" dc:"业务名称" eg:"快手电商"`
Description string `orm:"description" json:"description" dc:"描述" eg:"快手电商店铺每日统计报表"`
Status string `orm:"status" json:"status" dc:"status" eg:"ACTIVE"`
Config map[string]interface{} `orm:"config" json:"config" dc:"扩展配置"`
}
// ReportConfig 报表配置
type ReportConfig struct {
beans.SQLBaseDO `orm:",inherit"`
BusinessCode string `orm:"business_code" json:"businessCode" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `orm:"report_code" json:"reportCode" dc:"报表编码" eg:"shop_daily"`
ReportName string `orm:"report_name" json:"reportName" dc:"报表名称" eg:"店铺日报"`
Description string `orm:"description" json:"description" dc:"描述" eg:"快手电商店铺每日统计报表"`
Status string `orm:"status" json:"status" dc:"状态 ACTIVE/INACTIVE" eg:"ACTIVE"`
StatTableName string `orm:"stat_table_name" json:"statTableName" dc:"统计宽表名" eg:"stat_kuaishou_shop_daily"`
StatTableComment string `orm:"stat_table_comment" json:"statTableComment" dc:"统计宽表注释" eg:"快手电商-店铺日报"`
DateField string `orm:"date_field" json:"dateField" dc:"日期字段名" eg:"stat_date"`
PrimaryKeys []string `orm:"primary_keys" json:"primaryKeys" dc:"主键字段列表"`
ConflictKeys []string `orm:"conflict_keys" json:"conflictKeys" dc:"冲突键(唯一索引)"`
Config map[string]interface{} `orm:"config" json:"config" dc:"扩展配置"`
ExtractConfigIDs string `orm:"-" json:"extractConfigIds" dc:"抽取配置ID列表,逗号分隔" eg:"1,2,3"`
Fields []FieldConfig `orm:"-" json:"fields,omitempty" dc:"字段配置列表(查询时返回)"`
}
// FieldConfig 字段配置
type FieldConfig struct {
beans.SQLBaseDO `orm:",inherit"`
BusinessCode string `orm:"business_code" json:"businessCode" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `orm:"report_code" json:"reportCode" dc:"reportCode" eg:"shop_daily"`
FieldCode string `orm:"field_code" json:"fieldCode" dc:"字段编码" eg:"order_amount"`
FieldName string `orm:"field_name" json:"fieldName" dc:"字段名称" eg:"订单金额"`
FieldType string `orm:"field_type" json:"fieldType" dc:"字段类型 STRING/INT/FLOAT/DATE/DATETIME/JSONB" eg:"FLOAT"`
DataType string `orm:"data_type" json:"dataType" dc:"数据存储类型" eg:"STRING"`
FieldRole string `orm:"field_role" json:"fieldRole" dc:"字段角色 DIMENSION(维度)/INDICATOR(指标)/FILTER(筛选)/FILTER_ONLY(仅筛选)" eg:"DIMENSION"`
IsAggregatable bool `orm:"is_aggregatable" json:"isAggregatable" dc:"是否可聚合" eg:"true"`
IsFilterable bool `orm:"is_filterable" json:"isFilterable" dc:"是否可筛选" eg:"true"`
IsQueryable bool `orm:"is_queryable" json:"isQueryable" dc:"是否可查询" eg:"true"`
IsSortable bool `orm:"is_sortable" json:"isSortable" dc:"是否可排序" eg:"true"`
DefaultAggregate string `orm:"default_aggregate" json:"defaultAggregate" dc:"默认聚合方式 SUM/COUNT/AVG/MAX/MIN" eg:"SUM"`
ValidAggregates []string `orm:"valid_aggregates" json:"validAggregates" dc:"可选聚合列表"`
FilterOperators []string `orm:"filter_operators" json:"filterOperators" dc:"可选操作符列表 =/!=/>/</>=/<=/IN/LIKE/BETWEEN"`
Expression string `orm:"expression" json:"expression" dc:"表达式(衍生字段)" eg:"order_amount - refund_amount"`
ExpressionType string `orm:"expression_type" json:"expressionType" dc:"表达式类型 DIRECT/CALCULATED" eg:"DIRECT"`
FormatPattern string `orm:"format_pattern" json:"formatPattern" dc:"格式化模板" eg:"#,##0.00"`
Unit string `orm:"unit" json:"unit" dc:"单位"`
DictCode string `orm:"dict_code" json:"dictCode" dc:"字典编码" eg:"dict_order_type"`
SortOrder int `orm:"sort_order" json:"sortOrder" dc:"排序号" eg:"1"`
GroupName string `orm:"group_name" json:"groupName" dc:"分组名称" eg:"交易指标"`
Status string `orm:"status" json:"status" dc:"status" eg:"ACTIVE"`
}
// ExtractConfig 抽取配置
type ExtractConfig struct {
beans.SQLBaseDO `orm:",inherit"`
BusinessCode string `orm:"business_code" json:"businessCode" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `orm:"report_code" json:"reportCode" dc:"reportCode" eg:"shop_daily"`
ExtractCode string `orm:"extract_code" json:"extractCode" dc:"抽取编码" eg:"kuaishou_order_extract"`
ExtractName string `orm:"extract_name" json:"extractName" dc:"抽取名称" eg:"订单抽取"`
SourceTableName string `orm:"source_table_name" json:"sourceTableName" dc:"源表名" eg:"ods_kuaishou_order"`
SourceTableAlias string `orm:"source_table_alias" json:"sourceTableAlias" dc:"源表别名" eg:"o"`
TargetTableName string `orm:"target_table_name" json:"targetTableName" dc:"目标表名" eg:"stat_kuaishou_shop_daily"`
IsEnabled bool `orm:"is_enabled" json:"isEnabled" dc:"是否启用" eg:"true"`
ExtractType string `orm:"extract_type" json:"extractType" dc:"抽取类型 FULL/INCREMENTAL" eg:"INCREMENTAL"`
ExtractMode string `orm:"extract_mode" json:"extractMode" dc:"抽取模式 DIRECT(逐行)/AGGREGATE(聚合)" eg:"DIRECT"`
ExtractKeyField string `orm:"extract_key_field" json:"extractKeyField" dc:"抽取关键字段(增量依据)" eg:"updated_at"`
ExtractKeyFormat string `orm:"extract_key_format" json:"extractKeyFormat" dc:"关键字段格式" eg:"yyyy-MM-dd HH:mm:ss"`
GroupByFields []string `orm:"group_by_fields" json:"groupByFields" dc:"GROUP BY 字段列表"`
FilterExpression string `orm:"filter_expression" json:"filterExpression" dc:"过滤表达式" eg:"status = 'PAID'"`
JoinConfigs []JoinConfig `orm:"join_configs" json:"joinConfigs" dc:"JOIN配置"`
FieldMappings []FieldMapping `orm:"field_mappings" json:"fieldMappings" dc:"字段映射列表"`
TransformRules []TransformRule `orm:"transform_rules" json:"transformRules" dc:"转换规则列表"`
BatchSize int `orm:"batch_size" json:"batchSize" dc:"批处理大小" eg:"1000"`
Status string `orm:"status" json:"status" dc:"status" eg:"ACTIVE"`
}
// ExtractLog 抽取记录(不含 Creator/Updater/DeletedAt,表结构不同)
type ExtractLog struct {
ID int64 `orm:"id" json:"id" dc:"主键ID" eg:"1"`
TenantId uint64 `orm:"tenant_id" json:"tenantId" dc:"租户ID" eg:"1"`
BusinessCode string `orm:"business_code" json:"businessCode" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `orm:"report_code" json:"reportCode" dc:"reportCode" eg:"shop_daily"`
ExtractCode string `orm:"extract_code" json:"extractCode" dc:"抽取编码" eg:"kuaishou_order_extract"`
StatDate string `orm:"stat_date" json:"statDate" dc:"统计日期" eg:"2026-06-01"`
ExtractType string `orm:"extract_type" json:"extractType" dc:"抽取类型 FULL/INCREMENTAL" eg:"INCREMENTAL"`
Status string `orm:"status" json:"status" dc:"status" eg:"ACTIVE"`
TotalCount int `orm:"total_count" json:"totalCount" dc:"总记录数" eg:"100"`
SuccessCount int `orm:"success_count" json:"successCount" dc:"成功记录数" eg:"98"`
FailCount int `orm:"fail_count" json:"failCount" dc:"失败记录数" eg:"2"`
StartTime *time.Time `orm:"start_time" json:"startTime" dc:"开始时间"`
EndTime *time.Time `orm:"end_time" json:"endTime" dc:"结束时间"`
ErrorMessage string `orm:"error_message" json:"errorMessage" dc:"错误信息"`
Executor string `orm:"executor" json:"executor" dc:"执行人" eg:"system"`
CreatedAt *time.Time `orm:"created_at" json:"createdAt" dc:"创建时间"`
UpdatedAt *time.Time `orm:"updated_at" json:"updatedAt" dc:"更新时间"`
}
// ============================================================
// 辅助结构
// ============================================================
// JoinConfig 关联配置
type JoinConfig struct {
JoinTable string `json:"joinTable" dc:"关联表名" eg:"ods_kuaishou_shop"`
JoinAlias string `json:"joinAlias" dc:"关联表别名" eg:"s"`
JoinType string `json:"joinType" dc:"LEFT/RIGHT/INNER" eg:"LEFT"`
JoinCondition string `json:"joinCondition" dc:"关联条件" eg:"o.shop_id = s.shop_id"`
FieldMappings []FieldMapping `json:"fieldMappings" dc:"字段映射列表"`
}
// FieldMapping 字段映射
type FieldMapping struct {
SourceField string `json:"sourceField" dc:"源字段"`
TargetField string `json:"targetField" dc:"目标字段"`
FieldType string `json:"fieldType" dc:"字段类型 STRING/INT/FLOAT/DATE/DATETIME/JSONB" eg:"FLOAT"`
AggregateFunction string `json:"aggregateFunction" dc:"聚合函数 SUM/COUNT/AVG/MAX/MIN"`
DefaultValue interface{} `json:"defaultValue" dc:"默认值"`
TransformRule *TransformRule `json:"transformRule,omitempty" dc:"transformRule"`
}
// TransformRule 转换规则
type TransformRule struct {
RuleCode string `json:"ruleCode" dc:"规则编码" eg:"AMOUNT_CONVERT"`
RuleType string `json:"ruleType" dc:"DIRECT/MAPPING/FORMAT/CALCULATE" eg:"FORMAT"`
Expression string `json:"expression" dc:"表达式(衍生字段)" eg:"order_amount - refund_amount"`
Format string `json:"format" dc:"format" eg:"%Y-%m-%d"`
Mapping map[string]interface{} `json:"mapping" dc:"映射表(MAPPING类型时使用)"`
}
// ============================================================
// 前端请求/响应结构体
// ============================================================
// UserSelectQueryReq 用户选择查询请求
type UserSelectQueryReq struct {
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" v:"required" dc:"报表编码" eg:"shop_daily"`
Dimensions []string `json:"dimensions" dc:"统计维度列表,如 shop_id/anchor_id/date"`
Indicators []IndicatorSelect `json:"indicators" dc:"统计指标列表(含聚合方式)"`
Filters []FilterCondition `json:"filters" dc:"筛选条件列表"`
TimeRange *TimeRange `json:"timeRange" dc:"时间范围"`
TimeGroup string `json:"timeGroup" dc:"时间分组: day/week/month/quarter" eg:"day"`
OrderBy []OrderCondition `json:"orderBy" dc:"排序条件"`
Page int `json:"page" dc:"页码" d:"1" eg:"1"`
PageSize int `json:"pageSize" dc:"每页条数" d:"20" eg:"20"`
}
// IndicatorSelect 指标选择
type IndicatorSelect struct {
FieldCode string `json:"fieldCode" dc:"字段编码" eg:"order_amount"`
Aggregate string `json:"aggregate" dc:"聚合方式: SUM/COUNT/AVG/MAX/MIN" eg:"SUM"`
Alias string `json:"alias" dc:"别名" eg:"总订单金额"`
}
// FilterCondition 筛选条件
type FilterCondition struct {
FieldCode string `json:"fieldCode" dc:"字段编码" eg:"order_amount"`
Operator string `json:"operator" dc:"操作符: =/!=/>/</>=/<=/IN/LIKE/BETWEEN" eg:"="`
Value interface{} `json:"value" dc:"值"`
Value2 interface{} `json:"value2" dc:"第二个值(BETWEEN时使用)"`
}
// TimeRange 时间范围
type TimeRange struct {
StartDate string `json:"startDate" dc:"开始日期 yyyy-MM-dd" eg:"2026-06-01"`
EndDate string `json:"endDate" dc:"结束日期 yyyy-MM-dd" eg:"2026-06-30"`
}
// OrderCondition 排序条件
type OrderCondition struct {
FieldCode string `json:"fieldCode" dc:"字段编码" eg:"order_amount"`
Direction string `json:"direction" dc:"排序方向: ASC/DESC" eg:"DESC"`
}
// UserSelectQueryResp 用户选择查询响应
type UserSelectQueryResp struct {
List []map[string]interface{} `json:"list" dc:"数据列表"`
Total int64 `json:"total" dc:"总数" eg:"100"`
Page int `json:"page" dc:"当前页" eg:"1"`
PageSize int `json:"pageSize" dc:"每页条数" eg:"20"`
TotalPages int `json:"totalPages" dc:"总页数" eg:"5"`
Sql string `json:"sql,omitempty" dc:"执行的SQL(调试用)" eg:"SELECT 1"`
ExecTimeMs int64 `json:"execTimeMs" dc:"执行耗时(毫秒)" eg:"1234"`
}
// ExtractDailyDataReq 按天抽取数据请求
type ExtractDailyDataReq struct {
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" v:"required" dc:"报表编码" eg:"shop_daily"`
StatDate string `json:"statDate" v:"required" dc:"统计日期 yyyy-MM-dd" eg:"2026-06-01"`
Executor string `json:"executor" dc:"执行人" eg:"system"`
}
// ExtractDailyDataResp 按天抽取数据响应
type ExtractDailyDataResp struct {
Success bool `json:"success" dc:"是否成功" eg:"true"`
TotalCount int `json:"totalCount" dc:"总记录数" eg:"100"`
SuccessCount int `json:"successCount" dc:"成功记录数" eg:"98"`
FailCount int `json:"failCount" dc:"失败记录数" eg:"2"`
ExecTimeMs int64 `json:"execTimeMs" dc:"执行耗时(毫秒)" eg:"1234"`
ErrorMsg string `json:"errorMsg" dc:"错误信息"`
}
// AutoCreateStatTableReq 自动创建统计宽表请求
type AutoCreateStatTableReq struct {
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" v:"required" dc:"报表编码" eg:"shop_daily"`
Creator string `json:"creator" dc:"创建人" eg:"admin"`
}
// AutoCreateStatTableResp 自动创建统计宽表响应
type AutoCreateStatTableResp struct {
Success bool `json:"success" dc:"是否成功" eg:"true"`
TableName string `json:"tableName" dc:"创建的表名" eg:"stat_kuaishou_shop_daily"`
ColumnCount int `json:"columnCount" dc:"字段数量" eg:"15"`
ExecTimeMs int64 `json:"execTimeMs" dc:"执行耗时(毫秒)" eg:"1234"`
}
// GetReportFieldsResp 获取报表可用字段响应
type GetReportFieldsResp struct {
BusinessCode string `json:"businessCode" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" dc:"报表编码" eg:"shop_daily"`
Dimensions []FieldConfig `json:"dimensions" dc:"维度字段列表"`
Indicators []FieldConfig `json:"indicators" dc:"指标字段列表"`
Filters []FieldConfig `json:"filters" dc:"筛选字段列表"`
}
// BackfillReq 批量回填请求
type BackfillReq struct {
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" v:"required" dc:"报表编码" eg:"shop_daily"`
StartDate string `json:"startDate" v:"required" dc:"开始日期 yyyy-MM-dd" eg:"2026-06-01"`
EndDate string `json:"endDate" v:"required" dc:"结束日期 yyyy-MM-dd" eg:"2026-06-30"`
Executor string `json:"executor" dc:"执行人" eg:"admin"`
}
// BackfillResp 批量回填响应
type BackfillResp struct {
Success bool `json:"success" dc:"是否全部成功" eg:"true"`
TotalDays int `json:"totalDays" dc:"总天数" eg:"30"`
SuccessDays int `json:"successDays" dc:"成功天数" eg:"28"`
FailDays int `json:"failDays" dc:"失败天数" eg:"2"`
ExecTimeMs int64 `json:"execTimeMs" dc:"总耗时(毫秒)" eg:"5678"`
ErrorMsg string `json:"errorMsg" dc:"错误信息"`
}
// ============================================================
// 配置 CRUD 请求/响应
// ============================================================
// SaveBusinessReq 保存业务配置请求(新增/修改合一)
type SaveBusinessReq struct {
ID *int64 `json:"id" dc:"有值为更新,无值为新增" eg:"1"`
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
BusinessName string `json:"businessName" v:"required" dc:"业务名称" eg:"快手电商"`
Description string `json:"description" dc:"描述" eg:"快手电商店铺每日统计报表"`
Status string `json:"status" dc:"状态 ACTIVE/INACTIVE" d:"ACTIVE" eg:"ACTIVE"`
Config map[string]interface{} `json:"config" dc:"扩展配置"`
Operator string `json:"operator" dc:"操作人" eg:"="`
}
// SaveReportReq 保存报表配置请求
type SaveReportReq struct {
ID *int64 `json:"id" dc:"主键ID" eg:"1"`
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" v:"required" dc:"报表编码" eg:"shop_daily"`
ReportName string `json:"reportName" v:"required" dc:"报表名称" eg:"店铺日报"`
Description string `json:"description" dc:"描述" eg:"快手电商店铺每日统计报表"`
Status string `json:"status" dc:"状态" d:"ACTIVE" eg:"ACTIVE"`
StatTableName string `json:"statTableName" v:"required" dc:"统计宽表名"`
StatTableComment string `json:"statTableComment" dc:"统计宽表注释"`
DateField string `json:"dateField" dc:"日期字段" d:"stat_date" eg:"stat_date"`
PrimaryKeys []string `json:"primaryKeys" dc:"主键字段"`
ConflictKeys []string `json:"conflictKeys" dc:"冲突键(唯一索引)"`
Config map[string]interface{} `json:"config" dc:"扩展配置"`
Operator string `json:"operator" dc:"操作人" eg:"="`
}
// SaveFieldReq 保存字段配置请求
type SaveFieldReq struct {
ID *int64 `json:"id" dc:"主键ID" eg:"1"`
BusinessCode string `json:"businessCode" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" dc:"报表编码" eg:"shop_daily"`
FieldCode string `json:"fieldCode" v:"required" dc:"字段编码" eg:"order_amount"`
FieldName string `json:"fieldName" v:"required" dc:"字段名称" eg:"订单金额"`
FieldType string `json:"fieldType" v:"required" dc:"字段类型 STRING/INT/FLOAT/DATE/DATETIME/JSONB" eg:"FLOAT"`
DataType string `json:"dataType" dc:"数据存储类型" d:"STRING" eg:"STRING"`
FieldRole string `json:"fieldRole" v:"required" dc:"字段角色 DIMENSION/INDICATOR/FILTER/FILTER_ONLY" eg:"DIMENSION"`
IsAggregatable bool `json:"isAggregatable" dc:"是否可聚合" eg:"true"`
IsFilterable bool `json:"isFilterable" dc:"是否可筛选" d:"true" eg:"true"`
IsQueryable bool `json:"isQueryable" dc:"是否可查询" d:"true" eg:"true"`
IsSortable bool `json:"isSortable" dc:"是否可排序" d:"true" eg:"true"`
DefaultAggregate string `json:"defaultAggregate" dc:"默认聚合方式" eg:"SUM"`
ValidAggregates []string `json:"validAggregates" dc:"可选聚合列表"`
FilterOperators []string `json:"filterOperators" dc:"可选操作符列表"`
Expression string `json:"expression" dc:"表达式(衍生字段)" eg:"order_amount - refund_amount"`
ExpressionType string `json:"expressionType" dc:"表达式类型 DIRECT/CALCULATED" eg:"DIRECT"`
FormatPattern string `json:"formatPattern" dc:"格式化模板" eg:"#,##0.00"`
Unit string `json:"unit" dc:"单位" eg:"元"`
DictCode string `json:"dictCode" dc:"字典编码" eg:"dict_order_type"`
SortOrder int `json:"sortOrder" dc:"排序" eg:"1"`
GroupName string `json:"groupName" dc:"分组名称" eg:"交易指标"`
Status string `json:"status" dc:"状态" d:"ACTIVE" eg:"ACTIVE"`
Operator string `json:"operator" dc:"操作人" eg:"admin"`
}
// SaveReportWithFieldsReq 保存报表及字段配置请求(字段全量替换)
type SaveReportWithFieldsReq struct {
ID *int64 `json:"id" dc:"主键ID(更新时必填,新建为空)"`
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" v:"required" dc:"报表编码" eg:"shop_daily"`
ReportName string `json:"reportName" v:"required" dc:"报表名称" eg:"店铺日报"`
Description string `json:"description" dc:"描述" eg:"快手电商店铺每日统计报表"`
Status string `json:"status" dc:"状态" d:"ACTIVE" eg:"ACTIVE"`
StatTableName string `json:"statTableName" v:"required" dc:"统计宽表名" eg:"stat_kuaishou_shop_daily"`
StatTableComment string `json:"statTableComment" dc:"统计宽表注释" eg:"快手电商-店铺日报"`
DateField string `json:"dateField" dc:"日期字段" d:"stat_date" eg:"stat_date"`
PrimaryKeys []string `json:"primaryKeys" dc:"主键字段"`
ConflictKeys []string `json:"conflictKeys" dc:"冲突键(唯一索引)"`
Config map[string]interface{} `json:"config" dc:"扩展配置"`
Fields []SaveFieldReq `json:"fields" dc:"字段配置列表(全量替换,不在列表中的字段将被删除)"`
}
// SaveExtractConfigReq 保存抽取配置请求
type SaveExtractConfigReq struct {
ID *int64 `json:"id" dc:"主键ID" eg:"1"`
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" v:"required" dc:"报表编码" eg:"shop_daily"`
ExtractCode string `json:"extractCode" v:"required" dc:"抽取编码" eg:"kuaishou_order_extract"`
ExtractName string `json:"extractName" v:"required" dc:"抽取名称" eg:"订单抽取"`
SourceTableName string `json:"sourceTableName" v:"required" dc:"源表名" eg:"ods_kuaishou_order"`
SourceTableAlias string `json:"sourceTableAlias" dc:"源表别名" eg:"o"`
TargetTableName string `json:"targetTableName" v:"required" dc:"目标表名" eg:"stat_kuaishou_shop_daily"`
IsEnabled bool `json:"isEnabled" dc:"是否启用" d:"true" eg:"true"`
ExtractType string `json:"extractType" dc:"抽取类型 FULL/INCREMENTAL" d:"INCREMENTAL" eg:"INCREMENTAL"`
ExtractMode string `json:"extractMode" dc:"抽取模式 DIRECT/AGGREGATE" d:"DIRECT" eg:"DIRECT"`
ExtractKeyField string `json:"extractKeyField" dc:"抽取关键字段(增量依据)" eg:"updated_at"`
ExtractKeyFormat string `json:"extractKeyFormat" dc:"关键字段格式" eg:"yyyy-MM-dd HH:mm:ss"`
GroupByFields []string `json:"groupByFields" dc:"GROUP BY 字段列表"`
FilterExpression string `json:"filterExpression" dc:"过滤表达式" eg:"status = 'PAID'"`
JoinConfigs []JoinConfig `json:"joinConfigs" dc:"JOIN配置"`
FieldMappings []FieldMapping `json:"fieldMappings" dc:"字段映射列表"`
TransformRules []TransformRule `json:"transformRules" dc:"转换规则列表"`
BatchSize int `json:"batchSize" dc:"批处理大小" d:"1000" eg:"1000"`
Status string `json:"status" dc:"状态" d:"ACTIVE" eg:"ACTIVE"`
Operator string `json:"operator" dc:"操作人" eg:"="`
}
// PageReq 分页请求参数
type PageReq struct {
PageNum int `json:"pageNum" d:"1" dc:"页码"`
PageSize int `json:"pageSize" d:"20" dc:"每页条数"`
}
// PageRes 分页响应
type PageRes struct {
Total int `json:"total" dc:"总数"`
Page int `json:"page" dc:"当前页"`
PageSize int `json:"pageSize" dc:"每页条数"`
TotalPages int `json:"totalPages" dc:"总页数"`
}
// IdReq 通用 ID 请求
type IdReq struct {
ID int64 `json:"id" v:"required" dc:"主键ID" eg:"1"`
}
// SaveResult 写操作通用返回
type SaveResult struct {
Success bool `json:"success" dc:"是否成功" eg:"true"`
ID int64 `json:"id" dc:"主键ID" eg:"1"`
Message string `json:"message" dc:"结果消息" eg:"操作成功"`
}
// DeleteResult 删除结果
type DeleteResult struct {
Success bool `json:"success" dc:"是否成功" eg:"true"`
Message string `json:"message" dc:"结果消息" eg:"操作成功"`
}
// GetExtractConfigsReq 获取抽取配置列表请求
type GetExtractConfigsReq struct {
BusinessCode string `json:"businessCode" v:"required" dc:"业务编码" eg:"KUAISHOU"`
ReportCode string `json:"reportCode" v:"required" dc:"报表编码" eg:"shop_daily"`
}
// ============================================================
// 常量定义
// ============================================================
const (
// 状态
StatusActive = "ACTIVE"
StatusInactive = "INACTIVE"
// 字段角色
RoleDimension = "DIMENSION"
RoleIndicator = "INDICATOR"
RoleFilter = "FILTER"
RoleFilterOnly = "FILTER_ONLY"
// 字段类型
FieldTypeString = "STRING"
FieldTypeInt = "INT"
FieldTypeFloat = "FLOAT"
FieldTypeDate = "DATE"
FieldTypeDatetime = "DATETIME"
FieldTypeJsonb = "JSONB"
// 聚合方式
AggregateSum = "SUM"
AggregateCount = "COUNT"
AggregateAvg = "AVG"
AggregateMax = "MAX"
AggregateMin = "MIN"
// 操作符
OperatorEq = "="
OperatorNe = "!="
OperatorGt = ">"
OperatorLt = "<"
OperatorGe = ">="
OperatorLe = "<="
OperatorIn = "IN"
OperatorLike = "LIKE"
OperatorBetween = "BETWEEN"
// 抽取类型
ExtractTypeFull = "FULL"
ExtractTypeIncremental = "INCREMENTAL"
// 抽取模式
ExtractModeDirect = "DIRECT" // 逐行抽取(默认,源表每行 → 宽表一行)
ExtractModeAggregate = "AGGREGATE" // 聚合抽取(按 GROUP BY 聚合,SUM/COUNT/AVG
// 抽取状态
ExtractStatusRunning = "RUNNING"
ExtractStatusSuccess = "SUCCESS"
ExtractStatusFailed = "FAILED"
)
// FieldTypeToPG 字段类型转PG列类型定义
func FieldTypeToPG(fieldType string) string {
switch fieldType {
case FieldTypeInt:
return "NUMERIC(20,0) DEFAULT 0"
case FieldTypeFloat:
return "NUMERIC(20,4) DEFAULT 0"
case FieldTypeDate:
return "VARCHAR(16) DEFAULT ''"
case FieldTypeDatetime:
return "TIMESTAMP WITH TIME ZONE"
case FieldTypeJsonb:
return "JSONB DEFAULT '{}'"
default:
return "VARCHAR(256) DEFAULT ''"
}
}
+146
View File
@@ -0,0 +1,146 @@
package report
import (
"context"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
)
// initTables 初始化系统表
func initTables(ctx context.Context) error {
ddls := []string{
// 业务配置表
`CREATE TABLE IF NOT EXISTS report_business_config (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
business_name VARCHAR(128) NOT NULL,
description TEXT DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
config JSONB DEFAULT '{}',
creator VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updater VARCHAR(64) DEFAULT '',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
)`,
// 报表配置表
`CREATE TABLE IF NOT EXISTS report_report_config (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
report_code VARCHAR(64) NOT NULL,
report_name VARCHAR(128) NOT NULL,
description TEXT DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
stat_table_name VARCHAR(128) NOT NULL,
stat_table_comment VARCHAR(256) DEFAULT '',
date_field VARCHAR(64) DEFAULT 'stat_date',
primary_keys JSONB DEFAULT '["id"]'::jsonb,
conflict_keys JSONB DEFAULT '["stat_date"]'::jsonb,
config JSONB DEFAULT '{}',
creator VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updater VARCHAR(64) DEFAULT '',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT uk_business_report_code UNIQUE (tenant_id, business_code, report_code)
)`,
// 字段配置表
`CREATE TABLE IF NOT EXISTS report_field_config (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
report_code VARCHAR(64) NOT NULL,
field_code VARCHAR(64) NOT NULL,
field_name VARCHAR(128) NOT NULL,
field_type VARCHAR(32) NOT NULL,
data_type VARCHAR(32) NOT NULL DEFAULT 'STRING',
field_role VARCHAR(32) NOT NULL,
is_aggregatable BOOLEAN DEFAULT FALSE,
is_filterable BOOLEAN DEFAULT TRUE,
is_queryable BOOLEAN DEFAULT TRUE,
is_sortable BOOLEAN DEFAULT TRUE,
default_aggregate VARCHAR(32) DEFAULT '',
valid_aggregates JSONB DEFAULT '[]'::jsonb,
filter_operators JSONB DEFAULT '["=","!=",">","<",">=","<=","IN","LIKE","BETWEEN"]'::jsonb,
expression VARCHAR(512) DEFAULT '',
expression_type VARCHAR(32) DEFAULT '',
format_pattern VARCHAR(64) DEFAULT '',
unit VARCHAR(32) DEFAULT '',
dict_code VARCHAR(64) DEFAULT '',
sort_order INT DEFAULT 0,
group_name VARCHAR(64) DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
creator VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updater VARCHAR(64) DEFAULT '',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT uk_business_report_field_code UNIQUE (tenant_id, business_code, report_code, field_code)
)`,
// 抽取配置表
`CREATE TABLE IF NOT EXISTS report_extract_config (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
report_code VARCHAR(64) NOT NULL,
extract_code VARCHAR(64) NOT NULL,
extract_name VARCHAR(128) NOT NULL,
source_table_name VARCHAR(128) NOT NULL,
source_table_alias VARCHAR(64) DEFAULT '',
target_table_name VARCHAR(128) NOT NULL,
is_enabled BOOLEAN DEFAULT TRUE,
extract_type VARCHAR(32) NOT NULL DEFAULT 'FULL',
extract_mode VARCHAR(32) NOT NULL DEFAULT 'DIRECT',
extract_key_field VARCHAR(64) DEFAULT '',
extract_key_format VARCHAR(64) DEFAULT '',
group_by_fields JSONB DEFAULT '[]'::jsonb,
filter_expression TEXT DEFAULT '',
join_configs JSONB DEFAULT '[]'::jsonb,
field_mappings JSONB DEFAULT '[]'::jsonb,
transform_rules JSONB DEFAULT '[]'::jsonb,
batch_size INT DEFAULT 1000,
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
creator VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updater VARCHAR(64) DEFAULT '',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT uk_business_report_extract_code UNIQUE (tenant_id, business_code, report_code, extract_code)
)`,
// 抽取记录表
`CREATE TABLE IF NOT EXISTS report_extract_log (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
report_code VARCHAR(64) NOT NULL,
extract_code VARCHAR(64) NOT NULL,
stat_date VARCHAR(16) NOT NULL,
extract_type VARCHAR(32) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'RUNNING',
total_count INT DEFAULT 0,
success_count INT DEFAULT 0,
fail_count INT DEFAULT 0,
start_time TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
end_time TIMESTAMP WITH TIME ZONE,
error_message TEXT DEFAULT '',
executor VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT uk_extract_keys UNIQUE (tenant_id, business_code, report_code, extract_code, stat_date)
)`,
}
for _, ddl := range ddls {
if _, err := gfdb.DB(ctx).Exec(ctx, ddl); err != nil {
return err
}
}
return nil
}
+48 -11
View File
@@ -1,5 +1,5 @@
server:
address : ":3001"
address : ":3013"
name: "data-engine"
workerId: 1
logPath: "resource/log/server"
@@ -9,15 +9,37 @@ rate:
limit: 200
burst: 300
# 报表引擎配置
report:
# 是否由本进程内部自动调度每日抽取(gcron "0 0 2 * * *"
# true — 进程启动后自动在每天 02:00 执行全量抽取,无需外部依赖
# false — 不自启调度,由 PPGo_Job 通过 POST /report/extractAll 触发(推荐)
extract_enabled: false
# 数据同步配置
sync:
page_size: 100 # 每次分页请求条数
concurrency: 5 # 同步并发数
retry_count: 3 # 最大重试次数
sync_interval_minutes: 60 # 自动同步间隔(分钟)
compensation_interval_seconds: 300 # 补偿调度器扫描间隔(秒)
# 是否由本进程内部自动循环执行全平台同步(每次完成后等待 sync_interval_minutes 再继续)
# true — 进程启动后自动在后台循环同步所有 ACTIVE 平台的接口数据
# false — 不自启调度,由 PPGo_Job 通过 POST /sync/ctrl/triggerAll 触发(推荐)
auto_sync_enabled: false
sync_timeout_minutes: 120 # 单次同步超时(分钟),全量超大表可适当调大
default_lookback_days: 10 # 全量同步默认回溯天数(快手限制90天内,留1天余量)(单接口可通过 request_config.full_sync_start_time 覆盖)
default_tenant_id: 1 # 自动同步使用的租户 ID(多租户部署时配置)
# Database.
database:
default:
- type: "pgsql"
host: "localhost"
port: "5432"
user: "postgres"
pass: "root"
name: "cid"
default:
type: "pgsql"
host: "192.168.0.83"
port: "15432"
user: "sql9f15b63fd203b36e"
pass: "1ec94b1acdaf57b66030242d418fad5a"
name: "engine"
role: "master"
maxIdle: "5"
maxOpen: "20"
@@ -33,7 +55,7 @@ database:
redis:
# 集群模式配置方法
default:
address: 192.168.3.30:6379
address: 192.168.0.83:6379
db: 0
idleTimeout: "60s" #连接最大空闲时间,使用时间字符串例如30s/1m/1d
maxConnLifetime: "90s" #连接最长存活时间,使用时间字符串例如30s/1m/1d
@@ -43,7 +65,22 @@ redis:
writeTimeout: "30s" #TCP的Write操作超时时间,使用时间字符串例如30s/1m/1d
maxActive: 100
consul:
address: 116.204.74.41:8500
address: 192.168.0.83:8500
#k8s:
# apiServer: "https://192.168.3.37:6443"
# token: "eyJhbGciOiJSUzI1NiIsImtpZCI6IlR6X0QtelVDYkZPYnpjTDlfamZzOEFjbERJb2dUMTRqMjNfYUNncGcwRW8ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxODA3ODY3MTA1LCJpYXQiOjE3NzYzMzExMDUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiNjAwMGY0ODctZTQ4Ni00NTUxLWIyNjgtMzE1MWE1MDE5YjU4Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJkYXNoYm9hcmQtYWRtaW4iLCJ1aWQiOiJlZjAxY2IxNS0xNTc0LTRlYTYtODg4Ny03YjZhODY3OWFkYmIifX0sIm5iZiI6MTc3NjMzMTEwNSwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50Omt1YmVybmV0ZXMtZGFzaGJvYXJkOmRhc2hib2FyZC1hZG1pbiJ9.Z5MEy-dhWq6lruuR6QSI--cYRZzDoeEes78a4lLMtXa8lSLt1FZy16kNVn2pJli74RSk0kKS2F5GAatyuyGjF_yB7Tm1Tb7iCjzeUM7xRpdvXEmk4MUImlzwGeW31NXpwFrfR0oPS-9TLlbahEmsgwL0DUOC0CekSiToVbIduwEmmrB0FMPFayr5_wqGDmxdqtaAH3K-LJNCqPiAMqevsxTebrhyJSrThU-Pi7iJYm_sUd8VHKrRxrwDiGA77j4lfita_hZSdyrZe8qsrbGqomHxFWk9ZzcJJ9q7OLS13OXo-Xbcu-_TZcfDOreZhune9ctM2lmuOtmYnad47gYRcA"
# kubeconfig: ""
# namespace: "default"
# pass: jiahui8888
jaeger: #链路追踪
addr: 116.204.74.41:4318
addr: 192.168.0.83:4318
# 平台认证信息(已迁移到 api_datasource_platform 表中管理)
# 保留此处仅作参考,系统启动后从数据库读取
tencent:
oauth:
client_id: "1112038234"
client_secret: "GxyjXFbZAs5dnsNQ"
refresh_token: "d15b37363a42449026d337708516e95e"
access_token: "4bacfc7c9b0a31f70ec0eb4771f8b542"
+5 -8
View File
@@ -1,13 +1,10 @@
package api_feature
// AppStatus 应用状态
type AppStatus string
// ApplicationStatus 应用状态(暂未使用,保留供未来扩展)
type ApplicationStatus string
const (
AppStatusActive AppStatus = "active" // 启用
AppStatusInactive AppStatus = "inactive" // 停用
ApplicationStatusPending ApplicationStatus = "pending"
ApplicationStatusApproved ApplicationStatus = "approved"
ApplicationStatusRejected ApplicationStatus = "rejected"
)
func (s AppStatus) String() string {
return string(s)
}
+5 -12
View File
@@ -1,17 +1,10 @@
package api_feature
// AppType 应用类型
type AppType string
// ApplicationType 应用类型(暂未使用,保留供未来扩展)
type ApplicationType string
const (
AppTypeWeb AppType = "web" // Web应用
AppTypeMobile AppType = "mobile" // 移动应用
AppTypeMiniApp AppType = "mini_app" // 小程序
AppTypeH5 AppType = "h5" // H5应用
AppTypeDesktop AppType = "desktop" // 桌面应用
AppTypeThirdParty AppType = "third_party" // 第三方应用
ApplicationTypeAPI ApplicationType = "api"
ApplicationTypeWebhook ApplicationType = "webhook"
ApplicationTypeSDK ApplicationType = "sdk"
)
func (s AppType) String() string {
return string(s)
}
+5 -10
View File
@@ -1,16 +1,11 @@
package api_feature
// FetchStatus 数据取状态
// FetchStatus 数据取状态(暂未使用,保留供未来扩展)
type FetchStatus string
const (
FetchStatusPending FetchStatus = "pending" // 待执行
FetchStatusRunning FetchStatus = "running" // 执行中
FetchStatusSuccess FetchStatus = "success" // 成功
FetchStatusFailed FetchStatus = "failed" // 失败
FetchStatusRateLimit FetchStatus = "rate_limit" // 触发限流
FetchStatusPending FetchStatus = "pending"
FetchStatusRunning FetchStatus = "running"
FetchStatusCompleted FetchStatus = "completed"
FetchStatusFailed FetchStatus = "failed"
)
func (f FetchStatus) String() string {
return string(f)
}
+3 -8
View File
@@ -1,14 +1,9 @@
package api_feature
// LimitType 限流类型
// LimitType 限流类型(暂未使用,保留供未来扩展)
type LimitType string
const (
LimitTypeApp LimitType = "app" // 应用维度限流
LimitTypeTenant LimitType = "tenant" // 租户维度限流
LimitTypeApi LimitType = "api" // 接口维度限流
LimitTypeRate LimitType = "rate"
LimitTypeBurst LimitType = "burst"
)
func (l LimitType) String() string {
return string(l)
}
+3 -7
View File
@@ -1,13 +1,9 @@
package api_feature
// MappingStatus 映射状态
// MappingStatus 字段映射状态(暂未使用,保留供未来扩展)
type MappingStatus string
const (
MappingStatusActive MappingStatus = "active" // 启用
MappingStatusInactive MappingStatus = "inactive" // 停用
MappingStatusMapped MappingStatus = "mapped"
MappingStatusUnmapped MappingStatus = "unmapped"
)
func (s MappingStatus) String() string {
return string(s)
}
+6 -16
View File
@@ -1,21 +1,11 @@
package api_feature
// SyncPlatform 同步平台类型
type SyncPlatform string
// PlatformType 平台类型(暂未使用,保留供未来扩展)
type PlatformType string
const (
PlatformTaobao SyncPlatform = "taobao" // 淘宝
PlatformJD SyncPlatform = "jd" // 京东
PlatformKuaishou SyncPlatform = "kuaishou" // 快手
PlatformDouyin SyncPlatform = "douyin" // 抖音
PlatformXhs SyncPlatform = "xhs" // 小红书
PlatformPdd SyncPlatform = "pdd" // 拼多多
PlatformXianyu SyncPlatform = "xianyu" // 闲鱼
PlatformTmall SyncPlatform = "tmall" // 天猫
PlatformWechat SyncPlatform = "wechat" // 微信
PlatformCustom SyncPlatform = "custom" // 自定义平台
PlatformTypeAds PlatformType = "ads"
PlatformTypeEcom PlatformType = "ecom"
PlatformTypeSocial PlatformType = "social"
PlatformTypeDingTalk PlatformType = "dingtalk"
)
func (s SyncPlatform) String() string {
return string(s)
}
+7 -10
View File
@@ -1,16 +1,13 @@
package api_feature
// TransformType 转换类型
// TransformType 字段转换类型(暂未使用,保留供未来扩展)
type TransformType string
const (
TransformTypeFixed TransformType = "fixed" // 固定值
TransformTypeMapping TransformType = "mapping" // 值映射
TransformTypeRegex TransformType = "regex" // 正则转换
TransformTypeFunction TransformType = "function" // 函数转换
TransformTypeScript TransformType = "script" // 脚本转换
TransformTypeDirect TransformType = "direct"
TransformTypeFormatDate TransformType = "formatDate"
TransformTypeMapValue TransformType = "mapValue"
TransformTypeConcat TransformType = "concat"
TransformTypeCalc TransformType = "calc"
TransformTypeRegex TransformType = "regex"
)
func (t TransformType) String() string {
return string(t)
}
+9 -22
View File
@@ -1,6 +1,12 @@
package dict
// 转换类型
// 注意:请不要在此文件添加与 consts/api-feature 包重复的类型定义。
// PlatformStatus 和 ApiMethod 等类型已在 dataengine/consts/api-feature 中定义,
// 请使用 api_feature.PlatformStatus / api_feature.ApiMethod 替代。
// Package dict 仅提供该包特有时使用。
// 转换类型(暂未使用,保留供未来扩展)
const (
TransformTypeDirect = "direct" // 直接映射
TransformTypeFormatDate = "formatDate" // 日期格式化
@@ -10,7 +16,7 @@ const (
TransformTypeRegex = "regex" // 正则提取
)
// 业务域
// 业务域(暂未使用,保留供未来扩展)
const (
BusinessDomainUser = "user" // 用户
BusinessDomainOrder = "order" // 订单
@@ -18,7 +24,7 @@ const (
BusinessDomainPayment = "payment" // 支付
)
// 字段类型
// 字段类型(暂未使用,保留供未来扩展)
const (
FieldTypeString = "string"
FieldTypeNumber = "number"
@@ -30,22 +36,3 @@ const (
FieldTypeTime = "time"
FieldTypeDateTime = "datetime"
)
// 平台状态
type PlatformStatus string
const (
PlatformStatusActive PlatformStatus = "active"
PlatformStatusInactive PlatformStatus = "inactive"
)
// 接口方法
type ApiMethod string
const (
ApiMethodGET ApiMethod = "GET"
ApiMethodPOST ApiMethod = "POST"
ApiMethodPUT ApiMethod = "PUT"
ApiMethodDELETE ApiMethod = "DELETE"
ApiMethodPATCH ApiMethod = "PATCH"
)
+4 -14
View File
@@ -2,18 +2,8 @@ package public
// PostgreSQL表名常量
const (
ApiInterfaceTable = "cid_api_interface" // 接口管理表
CidAccountReportDetailTable = "cid_account_report_detail" // 广告账户数据明细
CidAccountReportSumTable = "cid_account_report_sum" // 广告账户数据汇总
FieldMappingConfigTable = "field_mapping_config" // 字段映射表
DatasourcePlatformTable = "datasource_platform" // 数据源平台表
TaskReportTable = "task_report" // 调控任务数据表
PopulationReportTable = "population_report" // 人群报表表
MaterialReportTable = "material_report" // 素材报表表
StorewideReportSumTable = "storewide_report_sum" // 全站数据sum表
StorewideReportDetailTable = "storewide_report_detail" // 全站数据detail表
CreativeReportSumTable = "creative_report_sum" // 广告创意数据sum表
CreativeReportDetailTable = "creative_report_detail" // 广告创意数据detail表
UnitReportSumTable = "unit_report_sum" // 广告创意数据detail表
UnitReportDetailTable = "unit_report_detail" // 广告创意数据detail表
ApiInterfaceTable = "api_interface" // 接口管理表
DatasourcePlatformTable = "api_datasource_platform" // 数据源平台
SyncTaskLogTable = "sync_task_log" // 同步任务日志
SyncTrackerTable = "sync_tracker" // 同步跟踪表(增量同步时间记录)
)
@@ -1,32 +0,0 @@
package copydata
import (
dto "cid/model/dto/copydata"
service "cid/service/copydata"
"context"
)
type AccountReport struct{}
// AccountReportDetail CidAccountReportDetail 广告数据报表详情控制器
var AccountReportDetail = new(AccountReport)
// CreateCidAccountReportDetail 创建广告数据报表详情
func (c *AccountReport) CreateCidAccountReportDetail(ctx context.Context, req *dto.CreateCidAccountReportDetailReq) (res *dto.CreateCidAccountReportDetailRes, err error) {
return service.CidAccountReportDetail.Create(ctx, req.CidAccountReportDetailItem)
}
// BatchCreateCidAccountReportDetail 批量创建广告数据报表详情
func (c *AccountReport) BatchCreateCidAccountReportDetail(ctx context.Context, req *dto.BatchCreateCidAccountReportDetailReq) (res *dto.BatchCreateCidAccountReportDetailRes, err error) {
return service.CidAccountReportDetail.BatchCreate(ctx, req)
}
// CreateCidAccountReportSum 创建广告数据报表汇总
func (c *AccountReport) CreateCidAccountReportSum(ctx context.Context, req *dto.CreateCidAccountReportSumReq) (res *dto.CreateCidAccountReportSumRes, err error) {
return service.CidAccountReportDetail.CreateSum(ctx, req.CidAccountReportSumItem)
}
// BatchCreateCidAccountReportSum 批量创建广告数据报表汇总
func (c *AccountReport) BatchCreateCidAccountReportSum(ctx context.Context, req *dto.BatchCreateCidAccountReportSumReq) (res *dto.BatchCreateCidAccountReportSumRes, err error) {
return service.CidAccountReportDetail.BatchCreateSum(ctx, req)
}
@@ -1,30 +0,0 @@
package copydata
import (
dto "cid/model/dto/copydata"
service "cid/service/copydata"
"context"
)
type creativeReport struct{}
// CreativeReport 广告效果指标表控制器
var CreativeReport = new(creativeReport)
// CreateCreativeReportSum 创建广告效果指标表
func (c *creativeReport) CreateCreativeReportSum(ctx context.Context, req *dto.CreateCreativeReportSumReq) (res *dto.CreateCreativeReportSumRes, err error) {
return service.CreativeReportSum.Create(ctx, req.CreativeReportSumItem)
}
// BatchCreateCreativeReportSum 批量创建广告效果指标表
func (c *creativeReport) BatchCreateCreativeReportSum(ctx context.Context, req *dto.BatchCreateCreativeReportSumReq) (res *dto.BatchCreateCreativeReportSumRes, err error) {
return service.CreativeReportSum.BatchCreate(ctx, req)
}
func (c *creativeReport) CreateCreativeReportDetail(ctx context.Context, req *dto.CreateCreativeReportDetailReq) (res *dto.CreateCreativeReportDetailRes, err error) {
return service.CreativeReportSum.CreateDetail(ctx, req.CreativeReportDetailItem)
}
func (c *creativeReport) BatchCreateCreativeReportDetail(ctx context.Context, req *dto.BatchCreateCreativeReportDetailReq) (res *dto.BatchCreateCreativeReportDetailRes, err error) {
return service.CreativeReportSum.BatchCreateDetail(ctx, req)
}
@@ -1,27 +0,0 @@
package copydata
import (
dto "cid/model/dto/copydata"
service "cid/service/copydata"
"context"
)
type materialReport struct{}
// MaterialReport 素材报表数据控制器
var MaterialReport = new(materialReport)
// CreateMaterialReport 创建素材报表数据
func (c *materialReport) CreateMaterialReport(ctx context.Context, req *dto.CreateMaterialReportReq) (res *dto.CreateMaterialReportRes, err error) {
return service.MaterialReport.Create(ctx, req.MaterialReportItem)
}
// BatchCreateMaterialReport 批量创建素材报表数据
func (c *materialReport) BatchCreateMaterialReport(ctx context.Context, req *dto.BatchCreateMaterialReportReq) (res *dto.BatchCreateMaterialReportRes, err error) {
return service.MaterialReport.BatchCreate(ctx, req)
}
// ListMaterialReport 获取素材报表数据列表
func (c *materialReport) ListMaterialReport(ctx context.Context, req *dto.ListMaterialReportReq) (res *dto.ListMaterialReportRes, err error) {
return service.MaterialReport.List(ctx, req)
}
@@ -1,29 +0,0 @@
package copydata
import (
dto "cid/model/dto/copydata"
service "cid/service/copydata"
"context"
"gitea.com/red-future/common/beans"
)
var PopulationReport = new(populationReport)
type populationReport struct{}
// CreatePopulationReport 创建人群报表数据
func (c *populationReport) CreatePopulationReport(ctx context.Context, req *dto.CreatePopulationReportReq) (*dto.CreatePopulationReportRes, error) {
return service.PopulationReportService.Create(ctx, req.PopulationReportItem)
}
// BatchCreatePopulationReport 批量创建人群报表数据
func (c *populationReport) BatchCreatePopulationReport(ctx context.Context, req *dto.BatchCreatePopulationReportReq) (*dto.BatchCreatePopulationReportRes, error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
return service.PopulationReportService.BatchCreate(ctx, req.Items)
}
// ListPopulationReport 查询人群报表数据列表
func (c *populationReport) ListPopulationReport(ctx context.Context, req *dto.ListPopulationReportReq) (*dto.ListPopulationReportRes, error) {
return service.PopulationReportService.List(ctx, req)
}
@@ -1,32 +0,0 @@
package copydata
import (
dto "cid/model/dto/copydata"
service "cid/service/copydata"
"context"
)
type storewideReport struct{}
// StorewideReport 广告效果指标控制器
var StorewideReport = new(storewideReport)
// CreateStorewideReport 创建广告效果指标表
func (c *storewideReport) CreateStorewideReport(ctx context.Context, req *dto.CreateStorewideReportSumReq) (res *dto.CreateStorewideReportSumRes, err error) {
return service.StorewideReportSum.Create(ctx, req.StorewideReportSumItem)
}
// BatchCreateStorewideReport 批量创建广告效果指标表
func (c *storewideReport) BatchCreateStorewideReport(ctx context.Context, req *dto.BatchCreateStorewideReportSumReq) (res *dto.BatchCreateStorewideReportSumRes, err error) {
return service.StorewideReportSum.BatchCreate(ctx, req)
}
// CreateStorewideReportDetail 创建广告效果指标表
func (c *storewideReport) CreateStorewideReportDetail(ctx context.Context, req *dto.CreateStorewideReportDetailReq) (res *dto.CreateStorewideReportDetailRes, err error) {
return service.StorewideReportSum.CreateDetail(ctx, req.StorewideReportDetailItem)
}
// BatchCreateStorewideReportDetail 批量创建广告效果指标表
func (c *storewideReport) BatchCreateStorewideReportDetail(ctx context.Context, req *dto.BatchCreateStorewideReportDetailReq) (res *dto.BatchCreateStorewideReportDetailRes, err error) {
return service.StorewideReportSum.BatchCreateDetail(ctx, req)
}
@@ -1,27 +0,0 @@
package copydata
import (
dto "cid/model/dto/copydata"
service "cid/service/copydata"
"context"
)
type taskReport struct{}
// TaskReport 调控任务数据控制器
var TaskReport = new(taskReport)
// CreateTaskReport 创建调控任务数据
func (c *taskReport) CreateTaskReport(ctx context.Context, req *dto.CreateTaskReportReq) (res *dto.CreateTaskReportRes, err error) {
return service.TaskReport.Create(ctx, req.TaskReportItem)
}
// BatchCreateTaskReport 批量创建调控任务数据
func (c *taskReport) BatchCreateTaskReport(ctx context.Context, req *dto.BatchCreateTaskReportReq) (res *dto.BatchCreateTaskReportRes, err error) {
return service.TaskReport.BatchCreate(ctx, req)
}
// ListTaskReport 获取调控任务数据列表
func (c *taskReport) ListTaskReport(ctx context.Context, req *dto.ListTaskReportReq) (res *dto.ListTaskReportRes, err error) {
return service.TaskReport.List(ctx, req)
}
@@ -1,35 +0,0 @@
package copydata
import (
dto "cid/model/dto/copydata"
"cid/service/copydata"
"context"
"gitea.com/red-future/common/beans"
)
type unitReport struct{}
// UnitReport 获取广告单元数据控制器
var UnitReport = new(unitReport)
// Create 创建广告效果指标
func (c *unitReport) Create(ctx context.Context, req *dto.CreateUnitReportSumReq) (res *dto.CreateUnitReportSumRes, err error) {
return copydata.UnitReportSumService.Create(ctx, req.UnitReportSumItem)
}
// BatchCreate 批量创建广告效果指标
func (c *unitReport) BatchCreate(ctx context.Context, req *dto.BatchCreateUnitReportSumReq) (res *dto.BatchCreateUnitReportSumRes, err error) {
return copydata.UnitReportSumService.BatchCreate(ctx, req)
}
// CreateDetail 创建广告效果指标详情
func (c *unitReport) CreateDetail(ctx context.Context, req *dto.CreateUnitReportDetailReq) (res *dto.CreateUnitReportDetailRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
return copydata.UnitReportSumService.CreateDetail(ctx, req.UnitReportDetailItem)
}
// BatchCreateDetail 批量创建广告效果指标详情
func (c *unitReport) BatchCreateDetail(ctx context.Context, req *dto.BatchCreateUnitReportDetailReq) (res *dto.BatchCreateUnitReportDetailRes, err error) {
return copydata.UnitReportSumService.BatchCreateDetail(ctx, req)
}
+360
View File
@@ -0,0 +1,360 @@
package debug
import (
"github.com/gogf/gf/v2/net/ghttp"
)
var DebugController = new(debugCtrl)
type debugCtrl struct{}
// DebugPage 调试页面
func (c *debugCtrl) DebugPage(r *ghttp.Request) {
r.Response.Header().Set("Content-Type", "text/html; charset=utf-8")
r.Response.Write(adminHTML)
r.Exit()
}
var adminHTML = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据引擎管理后台</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f0f2f5; color: #333; }
.header { background: #001529; color: #fff; padding: 14px 24px; display: flex; align-items: center; gap: 12px; }
.header h1 { font-size: 18px; font-weight: 600; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.tabs { display: flex; gap: 0; margin-bottom: 20px; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.tab { padding: 12px 28px; cursor: pointer; font-size: 14px; color: #666; border-bottom: 2px solid transparent; transition: all .2s; }
.tab:hover { color: #1890ff; }
.tab.active { color: #1890ff; border-bottom-color: #1890ff; font-weight: 600; }
.panel { display: none; }
.panel.active { display: block; }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.toolbar h2 { font-size: 16px; color: #1a1a2e; }
.btn { display: inline-flex; align-items: center; gap: 4px; padding: 8px 16px; border: none; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all .2s; }
.btn-primary { background: #1890ff; color: #fff; }
.btn-primary:hover { background: #40a9ff; }
.btn-danger { background: #ff4d4f; color: #fff; }
.btn-danger:hover { background: #ff7875; }
.btn-sm { padding: 4px 10px; font-size: 12px; }
.btn-outline { background: #fff; border: 1px solid #d9d9d9; color: #333; }
.btn-outline:hover { border-color: #1890ff; color: #1890ff; }
.btn-success { background: #52c41a; color: #fff; }
.btn-success:hover { background: #73d13d; }
table { width: 100%; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.06); border-collapse: collapse; }
th { background: #fafafa; padding: 12px 16px; text-align: left; font-size: 13px; color: #666; font-weight: 600; border-bottom: 1px solid #f0f0f0; }
td { padding: 12px 16px; font-size: 13px; border-bottom: 1px solid #f0f0f0; }
tr:hover td { background: #fafafa; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 500; }
.badge.active { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
.badge.inactive { background: #fff2f0; color: #ff4d4f; border: 1px solid #ffccc7; }
.actions { display: flex; gap: 6px; }
.modal-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.45); z-index: 1000; }
.modal-overlay.open { display: block; }
.modal { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); background: #fff; border-radius: 8px; width: 600px; max-width: 90%; max-height: 85vh; overflow-y: auto; z-index: 1001; box-shadow: 0 4px 24px rgba(0,0,0,0.15); }
.modal.open { display: block; }
.modal-header { padding: 16px 20px; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; }
.modal-header h3 { font-size: 16px; }
.modal-close { background: none; border: none; font-size: 20px; cursor: pointer; color: #999; }
.modal-body { padding: 20px; }
.modal-footer { padding: 12px 20px; border-top: 1px solid #f0f0f0; display: flex; justify-content: flex-end; gap: 8px; }
.form-group { margin-bottom: 14px; }
.form-group label { display: block; font-size: 13px; color: #666; margin-bottom: 4px; font-weight: 500; }
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 8px 12px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 13px; outline: none; transition: border .2s; }
.form-group input:focus, .form-group select:focus { border-color: #1890ff; box-shadow: 0 0 0 2px rgba(24,144,255,0.1); }
.form-group textarea { font-family: "Fira Code", "Consolas", monospace; font-size: 12px; min-height: 80px; resize: vertical; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.loading { text-align: center; padding: 40px; color: #999; }
.empty { text-align: center; padding: 32px; color: #999; font-size: 14px; }
.interfaces-section { margin-top: 8px; }
.sub-table { background: #fafafa; border-radius: 6px; overflow: hidden; margin-bottom: 8px; box-shadow: none; }
.sub-table th { background: #f0f0f0; font-size: 12px; padding: 8px 12px; }
.sub-table td { padding: 8px 12px; font-size: 12px; }
.json-preview { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; color: #1890ff; }
.json-preview:hover { text-decoration: underline; }
.platform-row { cursor: pointer; }
.platform-row td:first-child { position: relative; }
.expand-icon { margin-right: 8px; font-size: 10px; color: #999; transition: transform .2s; display: inline-block; }
.expand-icon.open { transform: rotate(90deg); }
.interface-detail { display: none; }
.interface-detail.open { display: table-row; }
.interface-detail td { padding: 0; }
.interface-detail-inner { padding: 12px 16px 12px 44px; background: #fafafa; }
.toast { position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 6px; color: #fff; font-size: 13px; z-index: 2000; animation: slideIn .3s; }
.toast.success { background: #52c41a; }
.toast.error { background: #ff4d4f; }
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
</style>
</head>
<body>
<div class="header"><h1>📊 数据引擎管理后台</h1></div>
<div class="container">
<div class="tabs">
<div class="tab active" onclick="switchTab('platforms')">平台管理</div>
<div class="tab" onclick="switchTab('interfaces')">接口管理</div>
</div>
<div id="platforms-panel" class="panel active">
<div class="toolbar"><h2>平台列表</h2><button class="btn btn-primary" onclick="openPlatformModal()">+ 新建平台</button></div>
<div id="platform-loading" class="loading">加载中...</div>
<div id="platform-list"></div>
</div>
<div id="interfaces-panel" class="panel">
<div class="toolbar"><h2>接口列表</h2><button class="btn btn-primary" onclick="openInterfaceModal()">+ 新建接口</button></div>
<div id="interface-loading" class="loading">加载中...</div>
<div id="interface-list"></div>
</div>
</div>
<div class="modal-overlay" id="modalOverlay" onclick="closeModal()"></div>
<div class="modal" id="modal">
<div class="modal-header"><h3 id="modalTitle"></h3><button class="modal-close" onclick="closeModal()">&times;</button></div>
<div class="modal-body" id="modalBody"></div>
<div class="modal-footer" id="modalFooter"></div>
</div>
<script>
const BASE = '';
function toast(msg, type) {
const t = document.createElement('div');
t.className = 'toast ' + type;
t.textContent = msg;
document.body.appendChild(t);
setTimeout(() => t.remove(), 2500);
}
async function api(method, url, body) {
const opt = { method, headers: {} };
if (body) { opt.headers['Content-Type'] = 'application/json'; opt.body = JSON.stringify(body); }
const resp = await fetch(BASE + url, opt);
if (!resp.ok) { const t = await resp.text(); throw new Error(t || resp.statusText); }
const json = await resp.json();
// 解包 GoFrame 标准响应: {"code":0,"message":"success","data":{...}}
if (json.code !== undefined && json.data !== undefined) {
if (json.code !== 0) { throw new Error(json.message || '请求失败'); }
return json.data;
}
return json;
}
const P = { platform: '/datasource/platform/controller', iface: '/api/interface/controller' };
function switchTab(name) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
document.querySelector('.tab[onclick*="' + name + '"]').classList.add('active');
document.getElementById(name + '-panel').classList.add('active');
if (name === 'platforms') loadPlatforms();
else loadInterfaces();
}
// ========== 平台管理 ==========
async function loadPlatforms() {
document.getElementById('platform-loading').style.display = 'block';
try {
const data = await api('GET', P.platform + '/listDatasourcePlatforms?page=1&pageSize=100');
document.getElementById('platform-loading').style.display = 'none';
if (!data.list || data.list.length === 0) {
document.getElementById('platform-list').innerHTML = '<div class="empty">暂无平台,点击"新建平台"添加</div>';
return;
}
let html = '<table><thead><tr><th style="width:30px"></th><th>编码</th><th>名称</th><th>API地址</th><th>认证</th><th>状态</th><th>操作</th></tr></thead><tbody>';
for (const p of data.list) {
const sc = p.status === 'ACTIVE' ? 'active' : 'inactive';
html += '<tr class="platform-row" onclick="togglePlatform(' + p.id + ')">' +
'<td><span class="expand-icon" id="expand-' + p.id + '">▶</span></td>' +
'<td><strong>' + esc(p.platformCode) + '</strong></td>' +
'<td>' + esc(p.platformName) + '</td>' +
'<td style="font-size:12px;color:#666">' + esc(p.apiBaseUrl || '-') + '</td>' +
'<td>' + esc(p.authType || '-') + '</td>' +
'<td><span class="badge ' + sc + '">' + p.status + '</span></td>' +
'<td class="actions" onclick="event.stopPropagation()">' +
'<button class="btn btn-sm btn-outline" onclick="openPlatformModal(' + p.id + ')">编辑</button>' +
'<button class="btn btn-sm btn-danger" onclick="deletePlatform(' + p.id + ')">删除</button></td></tr>' +
'<tr class="interface-detail" id="ifaces-' + p.id + '"><td colspan="7"><div class="interface-detail-inner" id="ifaces-content-' + p.id + '">点击展开...</div></td></tr>';
}
html += '</tbody></table>';
document.getElementById('platform-list').innerHTML = html;
} catch (e) { document.getElementById('platform-loading').style.display = 'none'; toast(e.message, 'error'); }
}
async function togglePlatform(id) {
const row = document.getElementById('ifaces-' + id);
const icon = document.getElementById('expand-' + id);
row.classList.toggle('open');
icon.classList.toggle('open');
if (row.classList.contains('open') && document.getElementById('ifaces-content-' + id).textContent === '点击展开...') {
try {
const data = await api('GET', P.iface + '/listApiInterfaces?platformId=' + id + '&page=1&pageSize=100');
const content = document.getElementById('ifaces-content-' + id);
if (!data.list || data.list.length === 0) { content.innerHTML = '<div class="empty" style="padding:8px">暂无接口</div>'; return; }
let h = '<table class="sub-table"><thead><tr><th>编码</th><th>名称</th><th>URL</th><th>方法</th><th>状态</th><th>请求配置</th><th>响应配置</th><th>操作</th></tr></thead><tbody>';
for (const fi of data.list) {
h += '<tr><td>' + esc(fi.code) + '</td><td>' + esc(fi.name) + '</td><td style="font-size:11px">' + esc(fi.url) + '</td>' +
'<td><span class="badge ' + (fi.method === 'GET' ? 'active' : '') + '">' + fi.method + '</span></td>' +
'<td><span class="badge ' + (fi.status === 'active' ? 'active' : 'inactive') + '">' + fi.status + '</span></td>' +
'<td class="json-preview" title="' + esc(JSON.stringify(fi.requestConfig||{})) + '">' + esc(JSON.stringify(fi.requestConfig||{}).substring(0,25)) + '</td>' +
'<td class="json-preview" title="' + esc(JSON.stringify(fi.responseConfig||{})) + '">' + esc(JSON.stringify(fi.responseConfig||{}).substring(0,25)) + '</td>' +
'<td class="actions"><button class="btn btn-sm btn-outline" onclick="event.stopPropagation();openInterfaceModal(' + fi.id + ')">编辑</button>' +
'<button class="btn btn-sm btn-danger" onclick="event.stopPropagation();deleteInterface(' + fi.id + ')">删</button></td></tr>';
}
h += '</tbody></table>';
content.innerHTML = h;
} catch (e) { document.getElementById('ifaces-content-' + id).textContent = '加载失败'; }
}
}
async function deletePlatform(id) {
if (!confirm('确定删除此平台?')) return;
try { await api('DELETE', P.platform + '/deleteDatasourcePlatform?id=' + id); toast('删除成功', 'success'); loadPlatforms(); }
catch (e) { toast(e.message, 'error'); }
}
// ========== 接口管理 ==========
async function loadInterfaces() {
document.getElementById('interface-loading').style.display = 'block';
try {
const data = await api('GET', P.iface + '/listApiInterfaces?page=1&pageSize=100');
document.getElementById('interface-loading').style.display = 'none';
if (!data.list || data.list.length === 0) {
document.getElementById('interface-list').innerHTML = '<div class="empty">暂无接口,点击"新建接口"添加</div>';
return;
}
let html = '<table><thead><tr><th>编码</th><th>名称</th><th>URL</th><th>方法</th><th>状态</th><th>平台</th><th>请求配置</th><th>响应配置</th><th>操作</th></tr></thead><tbody>';
for (const fi of data.list) {
html += '<tr><td><strong>' + esc(fi.code) + '</strong></td><td>' + esc(fi.name) + '</td>' +
'<td style="font-size:12px;color:#666">' + esc(fi.url) + '</td>' +
'<td><span class="badge ' + (fi.method === 'GET' ? 'active' : '') + '">' + fi.method + '</span></td>' +
'<td><span class="badge ' + (fi.status === 'active' ? 'active' : 'inactive') + '">' + fi.status + '</span></td>' +
'<td>' + esc(fi.platformName || '-') + '</td>' +
'<td class="json-preview" title="' + esc(JSON.stringify(fi.requestConfig||{})) + '">' + esc(JSON.stringify(fi.requestConfig||{}).substring(0,30)) + '</td>' +
'<td class="json-preview" title="' + esc(JSON.stringify(fi.responseConfig||{})) + '">' + esc(JSON.stringify(fi.responseConfig||{}).substring(0,30)) + '</td>' +
'<td class="actions"><button class="btn btn-sm btn-outline" onclick="openInterfaceModal(' + fi.id + ')">编辑</button>' +
'<button class="btn btn-sm btn-danger" onclick="deleteInterface(' + fi.id + ')">删除</button></td></tr>';
}
html += '</tbody></table>';
document.getElementById('interface-list').innerHTML = html;
} catch (e) { document.getElementById('interface-loading').style.display = 'none'; toast(e.message, 'error'); }
}
async function deleteInterface(id) {
if (!confirm('确定删除此接口?')) return;
try { await api('DELETE', P.iface + '/deleteApiInterface?id=' + id); toast('删除成功', 'success'); loadInterfaces(); loadPlatforms(); }
catch (e) { toast(e.message, 'error'); }
}
// ========== 平台表单 ==========
async function openPlatformModal(id) {
const title = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
try {
if (id) {
const data = await api('GET', P.platform + '/getDatasourcePlatform?id=' + id);
title.textContent = '编辑平台';
body.innerHTML = pfHTML(data);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveP(' + id + ')">保存</button>';
} else {
title.textContent = '新建平台';
body.innerHTML = pfHTML(null);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveP(0)">创建</button>';
}
document.getElementById('modalOverlay').classList.add('open');
document.getElementById('modal').classList.add('open');
} catch (e) { toast('加载平台详情失败: ' + e.message, 'error'); }
}
function pfHTML(d) {
d = d || {};
const authCfg = d.authConfig ? JSON.stringify(d.authConfig, null, 2) : '{}';
return '<div class="form-row"><div class="form-group"><label>编码 *</label><input id="f-pcode" value="' + esc(d.platformCode||'') + '"></div>' +
'<div class="form-group"><label>名称 *</label><input id="f-pname" value="' + esc(d.platformName||'') + '"></div></div>' +
'<div class="form-group"><label>描述</label><input id="f-pdesc" value="' + esc(d.description||'') + '"></div>' +
'<div class="form-group"><label>API地址</label><input id="f-apiurl" value="' + esc(d.apiBaseUrl||'') + '"></div>' +
'<div class="form-row"><div class="form-group"><label>认证类型 *</label><select id="f-auth"><option value="OAUTH2"' + (d.authType==='OAUTH2'?' selected':'') + '>OAuth2</option><option value="TOKEN"' + (d.authType==='TOKEN'?' selected':'') + '>Token</option><option value="API_KEY"' + (d.authType==='API_KEY'?' selected':'') + '>API Key</option><option value="SIGN"' + (d.authType==='SIGN'?' selected':'') + '>签名</option></select></div>' +
'<div class="form-group"><label>状态</label><select id="f-ps"><option value="ACTIVE"' + ((d.status||'ACTIVE')==='ACTIVE'?' selected':'') + '>启用</option><option value="INACTIVE"' + (d.status==='INACTIVE'?' selected':'') + '>停用</option></select></div></div>' +
'<div class="form-group"><label>Token / access_token</label><input id="f-tk" value="' + esc(d.token||'') + '"></div>' +
'<div class="form-group"><label>API Key</label><input id="f-apik" value="' + esc(d.apiKey||'') + '"></div>' +
'<div class="form-row"><div class="form-group"><label>Client ID</label><input id="f-cid" value="' + esc(d.clientId||'') + '"></div>' +
'<div class="form-group"><label>Client Secret</label><input id="f-cs" value="' + esc(d.clientSecret||'') + '"></div></div>' +
'<div class="form-group"><label>认证配置 JSON</label><textarea id="f-ac" rows="4">' + esc(authCfg) + '</textarea></div>' +
'<div class="form-row"><div class="form-group"><label>限流/分钟</label><input id="f-rpm" type="number" value="' + (d.rateLimitPerMinute||60) + '"></div>' +
'<div class="form-group"><label>超时(ms)</label><input id="f-to" type="number" value="' + (d.requestTimeoutMs||30000) + '"></div></div>';
}
async function saveP(id) {
const body = {
platformCode: v('f-pcode'), platformName: v('f-pname'), description: v('f-pdesc'),
apiBaseUrl: v('f-apiurl'), authType: v('f-auth'), status: v('f-ps'),
token: v('f-tk'), apiKey: v('f-apik'), clientId: v('f-cid'), clientSecret: v('f-cs'),
rateLimitPerMinute: parseInt(v('f-rpm'))||60, requestTimeoutMs: parseInt(v('f-to'))||30000,
};
try { body.authConfig = JSON.parse(v('f-ac')); } catch(e) { toast('auth_config JSON 格式错误', 'error'); return; }
try {
if (id) { await api('PUT', P.platform + '/updateDatasourcePlatform', { ...body, id }); toast('更新成功', 'success'); }
else { await api('POST', P.platform + '/createDatasourcePlatform', body); toast('创建成功', 'success'); }
closeModal(); loadPlatforms();
} catch(e) { toast(e.message, 'error'); }
}
// ========== 接口表单 ==========
async function openInterfaceModal(id) {
try {
const pl = await api('GET', P.platform + '/listDatasourcePlatforms?page=1&pageSize=100');
const opts = (pl.list||[]).map(p => '<option value="' + p.id + '">' + esc(p.platformName) + '</option>').join('');
const title = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
if (id) {
const data = await api('GET', P.iface + '/getApiInterface?id=' + id);
title.textContent = '编辑接口';
body.innerHTML = iFormHTML(data, opts);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveI(' + id + ')">保存</button>';
} else {
title.textContent = '新建接口';
body.innerHTML = iFormHTML(null, opts);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveI(0)">创建</button>';
}
document.getElementById('modalOverlay').classList.add('open');
document.getElementById('modal').classList.add('open');
} catch (e) { toast('加载接口详情失败: ' + e.message, 'error'); }
}
function iFormHTML(d, opts) {
d = d || {};
const pid = (d.platformId != null) ? d.platformId : '';
const sel = opts.replace('value="' + pid + '"', 'value="' + pid + '" selected');
return '<div class="form-group"><label>所属平台</label><select id="f-pid">' + sel + '</select></div>' +
'<div class="form-row"><div class="form-group"><label>编码 *</label><input id="f-ic" value="' + esc(d.code||'') + '"></div>' +
'<div class="form-group"><label>名称 *</label><input id="f-in" value="' + esc(d.name||'') + '"></div></div>' +
'<div class="form-row"><div class="form-group"><label>URL *</label><input id="f-iu" value="' + esc(d.url||'') + '"></div>' +
'<div class="form-group"><label>方法 *</label><select id="f-im"><option value="GET"' + ((d.method||'GET')==='GET'?' selected':'') + '>GET</option><option value="POST"' + (d.method==='POST'?' selected':'') + '>POST</option></select></div></div>' +
'<div class="form-group"><label>请求配置 JSON</label><textarea id="f-rqc" rows="6">' + esc(d.requestConfig ? JSON.stringify(d.requestConfig, null, 2) : '{}') + '</textarea></div>' +
'<div class="form-group"><label>响应配置 JSON</label><textarea id="f-rsc" rows="4">' + esc(d.responseConfig ? JSON.stringify(d.responseConfig, null, 2) : '{}') + '</textarea></div>' +
'<div class="form-group"><label>表结构定义 JSON</label><textarea id="f-td" rows="6">' + esc(d.tableDefinition ? JSON.stringify(d.tableDefinition, null, 2) : '{}') + '</textarea></div>';
}
async function saveI(id) {
let rqc, rsc, td;
try { rqc = JSON.parse(v('f-rqc')); } catch(e) { toast('请求配置 JSON 错误', 'error'); return; }
try { rsc = JSON.parse(v('f-rsc')); } catch(e) { toast('响应配置 JSON 错误', 'error'); return; }
try { td = JSON.parse(v('f-td')); } catch(e) { toast('表结构定义 JSON 错误', 'error'); return; }
const body = { platformId: parseInt(v('f-pid')), code: v('f-ic'), name: v('f-in'), url: v('f-iu'),
method: v('f-im'), requestConfig: rqc, responseConfig: rsc, tableDefinition: td };
try {
if (id) { await api('PUT', P.iface + '/updateApiInterface', { ...body, id }); toast('更新成功', 'success'); }
else { await api('POST', P.iface + '/createApiInterface', body); toast('创建成功', 'success'); }
closeModal(); loadInterfaces(); loadPlatforms();
} catch(e) { toast(e.message, 'error'); }
}
function closeModal() { document.getElementById('modalOverlay').classList.remove('open'); document.getElementById('modal').classList.remove('open'); }
function v(id) { return document.getElementById(id).value; }
function esc(s) { if (!s && s!==0) return ''; const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
loadPlatforms();
</script>
</body>
</html>`
@@ -1,11 +1,11 @@
package dict
import (
dto "cid/model/dto/dict"
service "cid/service/dict"
"context"
dto "dataengine/model/dto/dict"
service "dataengine/service/dict"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type datasourcePlatformController struct{}
@@ -15,25 +15,25 @@ var DatasourcePlatform = new(datasourcePlatformController)
// CreateDatasourcePlatform 创建数据源平台
func (c *datasourcePlatformController) CreateDatasourcePlatform(ctx context.Context, req *dto.CreateDatasourcePlatformReq) (res *dto.CreateDatasourcePlatformRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
return service.DatasourcePlatform.Create(ctx, req)
}
// ListDatasourcePlatforms 获取数据源平台列表
func (c *datasourcePlatformController) ListDatasourcePlatforms(ctx context.Context, req *dto.ListDatasourcePlatformReq) (res *dto.ListDatasourcePlatformRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
return service.DatasourcePlatform.List(ctx, req)
}
// GetDatasourcePlatform 获取数据源平台详情
func (c *datasourcePlatformController) GetDatasourcePlatform(ctx context.Context, req *dto.GetDatasourcePlatformReq) (res *dto.GetDatasourcePlatformRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
return service.DatasourcePlatform.GetOne(ctx, req)
}
// GetPlatformByCode 根据平台编码获取平台信息
func (c *datasourcePlatformController) GetPlatformByCode(ctx context.Context, req *dto.GetPlatformByCodeReq) (res *dto.GetPlatformByCodeRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
platform, err := service.DatasourcePlatform.GetByPlatformCode(ctx, req.PlatformCode)
if err != nil {
return nil, err
@@ -45,27 +45,27 @@ func (c *datasourcePlatformController) GetPlatformByCode(ctx context.Context, re
// UpdateDatasourcePlatform 更新数据源平台
func (c *datasourcePlatformController) UpdateDatasourcePlatform(ctx context.Context, req *dto.UpdateDatasourcePlatformReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
err = service.DatasourcePlatform.Update(ctx, req)
return
}
// UpdateDatasourcePlatformStatus 更新数据源平台状态
func (c *datasourcePlatformController) UpdateDatasourcePlatformStatus(ctx context.Context, req *dto.UpdateDatasourcePlatformStatusReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
err = service.DatasourcePlatform.UpdateStatus(ctx, req)
return
}
// DeleteDatasourcePlatform 删除数据源平台
func (c *datasourcePlatformController) DeleteDatasourcePlatform(ctx context.Context, req *dto.DeleteDatasourcePlatformReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
err = service.DatasourcePlatform.Delete(ctx, req)
return
}
// GetPlatformStatistics 获取平台统计信息
func (c *datasourcePlatformController) GetPlatformStatistics(ctx context.Context, req *dto.GetPlatformStatisticsReq) (res *dto.GetPlatformStatisticsRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
return service.DatasourcePlatform.GetStatistics(ctx)
}
@@ -1,65 +0,0 @@
package dict
import (
dto "cid/model/dto/dict"
service "cid/service/dict"
"context"
"gitea.com/red-future/common/beans"
)
type fieldMappingConfigController struct{}
// FieldMappingConfig 字段映射配置控制器
var FieldMappingConfig = new(fieldMappingConfigController)
// CreateFieldMappingConfig 创建字段映射配置
func (c *fieldMappingConfigController) CreateFieldMappingConfig(ctx context.Context, req *dto.CreateFieldMappingConfigReq) (res *dto.CreateFieldMappingConfigRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
return service.FieldMappingConfig.Create(ctx, req)
}
// ListFieldMappingConfig 获取字段映射配置列表
func (c *fieldMappingConfigController) ListFieldMappingConfig(ctx context.Context, req *dto.ListFieldMappingConfigReq) (res *dto.ListFieldMappingConfigRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
return service.FieldMappingConfig.List(ctx, req)
}
// GetFieldMappingConfig 获取字段映射配置详情
func (c *fieldMappingConfigController) GetFieldMappingConfig(ctx context.Context, req *dto.GetFieldMappingConfigReq) (res *dto.GetFieldMappingConfigRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
return service.FieldMappingConfig.GetOne(ctx, req)
}
// UpdateFieldMappingConfig 更新字段映射配置
func (c *fieldMappingConfigController) UpdateFieldMappingConfig(ctx context.Context, req *dto.UpdateFieldMappingConfigReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
err = service.FieldMappingConfig.Update(ctx, req)
return
}
// UpdateFieldMappingConfigStatus 更新字段映射配置状态
func (c *fieldMappingConfigController) UpdateFieldMappingConfigStatus(ctx context.Context, req *dto.UpdateFieldMappingConfigStatusReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
err = service.FieldMappingConfig.UpdateStatus(ctx, req)
return
}
// DeleteFieldMappingConfig 删除字段映射配置
func (c *fieldMappingConfigController) DeleteFieldMappingConfig(ctx context.Context, req *dto.DeleteFieldMappingConfigReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
err = service.FieldMappingConfig.Delete(ctx, req)
return
}
// QueryFieldMappingByVendorApi 根据厂商和接口查询字段映射
func (c *fieldMappingConfigController) QueryFieldMappingByVendorApi(ctx context.Context, req *dto.QueryFieldMappingByVendorApiReq) (res *dto.QueryFieldMappingByVendorApiRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
return service.FieldMappingConfig.QueryByVendorApi(ctx, req)
}
// ValidateFieldMapping 验证字段映射配置
func (c *fieldMappingConfigController) ValidateFieldMapping(ctx context.Context, req *dto.ValidateFieldMappingReq) (res *dto.ValidateFieldMappingRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
return service.FieldMappingConfig.Validate(ctx, req)
}
+9 -9
View File
@@ -1,11 +1,11 @@
package dict
import (
dto "cid/model/dto/dict"
service "cid/service/dict"
"context"
dto "dataengine/model/dto/dict"
service "dataengine/service/dict"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
)
type apiInterfaceController struct{}
@@ -15,39 +15,39 @@ var ApiInterface = new(apiInterfaceController)
// CreateApiInterface 创建接口
func (c *apiInterfaceController) CreateApiInterface(ctx context.Context, req *dto.CreateApiInterfaceReq) (res *dto.CreateApiInterfaceRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
return service.ApiInterface.Create(ctx, req)
}
// ListApiInterface 获取接口列表
func (c *apiInterfaceController) ListApiInterface(ctx context.Context, req *dto.ListApiInterfaceReq) (res *dto.ListApiInterfaceRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
return service.ApiInterface.List(ctx, req)
}
// GetApiInterface 获取接口详情
func (c *apiInterfaceController) GetApiInterface(ctx context.Context, req *dto.GetApiInterfaceReq) (res *dto.GetApiInterfaceRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
return service.ApiInterface.GetOne(ctx, req)
}
// UpdateApiInterface 更新接口
func (c *apiInterfaceController) UpdateApiInterface(ctx context.Context, req *dto.UpdateApiInterfaceReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
err = service.ApiInterface.Update(ctx, req)
return
}
// UpdateApiInterfaceStatus 更新接口状态
func (c *apiInterfaceController) UpdateApiInterfaceStatus(ctx context.Context, req *dto.UpdateApiInterfaceStatusReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
err = service.ApiInterface.UpdateStatus(ctx, req)
return
}
// DeleteApiInterface 删除接口
func (c *apiInterfaceController) DeleteApiInterface(ctx context.Context, req *dto.DeleteApiInterfaceReq) (res *beans.ResponseEmpty, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
err = service.ApiInterface.Delete(ctx, req)
return
}
@@ -0,0 +1,41 @@
package public
import (
"context"
dto "dataengine/model/dto/public"
svc "dataengine/service/public"
"github.com/gogf/gf/v2/frame/g"
)
type publicQueryController struct{}
// PublicQuery 公共查询控制器
var PublicQuery = new(publicQueryController)
// QueryReq 查询请求(包含空结构用于路径)
type ClearCacheReq struct {
g.Meta `path:"/public/cache/clear" method:"delete" tags:"公共查询" summary:"清除查询缓存"`
}
// Query 执行公共查询
func (c *publicQueryController) Query(ctx context.Context, req *dto.QueryReq) (res *dto.QueryRes, err error) {
return svc.PublicQuery.Query(ctx, req)
}
// GetTableList 获取可查询表列表
func (c *publicQueryController) GetTableList(ctx context.Context, req *dto.TableListReq) (res *dto.TableListRes, err error) {
return svc.PublicQuery.GetTableList(ctx)
}
// GetColumnList 获取表字段列表
func (c *publicQueryController) GetColumnList(ctx context.Context, req *dto.ColumnListReq) (res *dto.ColumnListRes, err error) {
return svc.PublicQuery.GetColumnList(ctx, req.Table)
}
// ClearCache 清除查询缓存(管理接口)
func (c *publicQueryController) ClearCache(ctx context.Context, req *ClearCacheReq) (res *dto.TableListRes, err error) {
svc.PublicQuery.ClearTableCache()
return &dto.TableListRes{List: []dto.TableInfo{}}, nil
}
@@ -0,0 +1,968 @@
package report
import (
"github.com/gogf/gf/v2/net/ghttp"
)
// ReportAdminPage 报表引擎管理页面
func ReportAdminPage(r *ghttp.Request) {
r.Response.Header().Set("Content-Type", "text/html; charset=utf-8")
r.Response.Write(reportAdminHTML)
r.Exit()
}
var reportAdminHTML = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>报表引擎管理</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f0f2f5; color: #333; }
.header { background: #001529; color: #fff; padding: 12px 24px; display: flex; align-items: center; gap: 16px; }
.header h1 { font-size: 18px; font-weight: 600; }
.header a { color: #91a7ff; text-decoration: none; font-size: 13px; }
.container { max-width: 1500px; margin: 0 auto; padding: 16px; }
.tabs { display: flex; gap: 0; margin-bottom: 16px; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
.tab { padding: 10px 24px; cursor: pointer; font-size: 13px; color: #666; border-bottom: 2px solid transparent; transition: all .2s; }
.tab:hover { color: #1890ff; }
.tab.active { color: #1890ff; border-bottom-color: #1890ff; font-weight: 600; }
.panel { display: none; }
.panel.active { display: block; }
.toolbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; flex-wrap: wrap; gap: 8px; }
.toolbar h2 { font-size: 15px; color: #1a1a2e; }
.toolbar-left { display: flex; align-items: center; gap: 8px; }
.toolbar-right { display: flex; gap: 6px; }
.btn { display: inline-flex; align-items: center; gap: 4px; padding: 6px 14px; border: none; border-radius: 6px; font-size: 12px; cursor: pointer; transition: all .2s; white-space: nowrap; }
.btn-primary { background: #1890ff; color: #fff; }
.btn-primary:hover { background: #40a9ff; }
.btn-danger { background: #ff4d4f; color: #fff; }
.btn-danger:hover { background: #ff7875; }
.btn-success { background: #52c41a; color: #fff; }
.btn-success:hover { background: #73d13d; }
.btn-warning { background: #faad14; color: #fff; }
.btn-warning:hover { background: #ffc53d; }
.btn-sm { padding: 4px 10px; font-size: 11px; }
.btn-outline { background: #fff; border: 1px solid #d9d9d9; color: #333; }
.btn-outline:hover { border-color: #1890ff; color: #1890ff; }
table { width: 100%; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,0.06); border-collapse: collapse; }
th { background: #fafafa; padding: 10px 14px; text-align: left; font-size: 12px; color: #666; font-weight: 600; border-bottom: 1px solid #f0f0f0; }
td { padding: 10px 14px; font-size: 12px; border-bottom: 1px solid #f0f0f0; }
tr:hover td { background: #fafafa; }
.badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 500; }
.badge.active { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
.badge.inactive { background: #fff2f0; color: #ff4d4f; border: 1px solid #ffccc7; }
.badge.info { background: #e6f7ff; color: #1890ff; border: 1px solid #91d5ff; }
.actions { display: flex; gap: 4px; }
.modal-overlay { display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.45); z-index: 1000; }
.modal-overlay.open { display: block; }
.modal { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); background: #fff; border-radius: 8px; width: 720px; max-width: 95%; max-height: 88vh; overflow-y: auto; z-index: 1001; box-shadow: 0 4px 24px rgba(0,0,0,0.15); }
.modal.open { display: block; }
.modal-header { padding: 14px 20px; border-bottom: 1px solid #f0f0f0; display: flex; justify-content: space-between; align-items: center; position: sticky; top: 0; background: #fff; z-index: 1; }
.modal-header h3 { font-size: 15px; }
.modal-close { background: none; border: none; font-size: 20px; cursor: pointer; color: #999; }
.modal-body { padding: 20px; }
.modal-footer { padding: 12px 20px; border-top: 1px solid #f0f0f0; display: flex; justify-content: flex-end; gap: 8px; position: sticky; bottom: 0; background: #fff; }
.form-group { margin-bottom: 12px; }
.form-group label { display: block; font-size: 12px; color: #666; margin-bottom: 4px; font-weight: 500; }
.form-group input, .form-group select, .form-group textarea { width: 100%; padding: 7px 10px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 12px; outline: none; transition: border .2s; }
.form-group input:focus, .form-group select:focus, .form-group textarea:focus { border-color: #1890ff; box-shadow: 0 0 0 2px rgba(24,144,255,0.1); }
.form-group textarea { font-family: "Fira Code", "Consolas", monospace; font-size: 11px; min-height: 60px; resize: vertical; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.form-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; }
.form-inline { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; }
.form-inline .form-group { margin-bottom: 0; flex: 1; min-width: 150px; }
.checkbox-group { display: flex; flex-wrap: wrap; gap: 8px; }
.checkbox-group label { display: flex; align-items: center; gap: 4px; font-size: 12px; cursor: pointer; font-weight: normal; }
.select-biz { padding: 7px 10px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 12px; }
.loading { text-align: center; padding: 40px; color: #999; }
.empty { text-align: center; padding: 32px; color: #999; font-size: 13px; }
.toast { position: fixed; top: 16px; right: 16px; padding: 10px 20px; border-radius: 6px; color: #fff; font-size: 13px; z-index: 2000; animation: slideIn .3s; }
.toast.success { background: #52c41a; }
.toast.error { background: #ff4d4f; }
.toast.info { background: #1890ff; }
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
.result-box { margin-top: 12px; background: #fafafa; border-radius: 6px; padding: 12px; max-height: 400px; overflow: auto; font-size: 12px; font-family: monospace; white-space: pre-wrap; }
.query-section { background: #fff; border-radius: 8px; padding: 16px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); margin-bottom: 12px; }
.query-section h3 { font-size: 14px; margin-bottom: 10px; color: #333; }
.chip { display: inline-flex; align-items: center; gap: 4px; padding: 2px 10px; background: #f0f5ff; border: 1px solid #adc6ff; border-radius: 12px; font-size: 11px; cursor: pointer; }
.chip.selected { background: #1890ff; color: #fff; border-color: #1890ff; }
.chip-group { display: flex; flex-wrap: wrap; gap: 6px; }
.field-tag { display: inline-flex; align-items: center; background: #f6ffed; border: 1px solid #b7eb8f; border-radius: 4px; padding: 2px 8px; font-size: 11px; margin: 2px; }
.field-tag .remove { margin-left: 4px; cursor: pointer; color: #ff4d4f; font-weight: bold; }
.add-filter-row { display: grid; grid-template-columns: 1fr 1fr 1.5fr 40px; gap: 6px; align-items: end; margin-bottom: 6px; }
.section-label { font-size: 12px; color: #999; margin-bottom: 6px; }
</style>
</head>
<body>
<div class="header">
<h1>&#x1F4CA; 报表引擎管理</h1>
<a href="/admin">&larr; 返回管理后台</a>
</div>
<div class="container">
<div class="tabs">
<div class="tab active" onclick="switchTab('business',this)">业务管理</div>
<div class="tab" onclick="switchTab('report',this)">报表配置</div>
<div class="tab" onclick="switchTab('field',this)">字段配置</div>
<div class="tab" onclick="switchTab('extract',this)">抽取配置</div>
<div class="tab" onclick="switchTab('query',this)">数据查询</div>
</div>
<!-- ========== 业务管理 ========== -->
<div id="tab-business" class="panel active">
<div class="toolbar">
<div class="toolbar-left"><h2>业务列表</h2></div>
<div class="toolbar-right">
<button class="btn btn-outline btn-sm" onclick="initTables()">初始化系统表</button>
<button class="btn btn-primary btn-sm" onclick="openBizModal()">+ 新建业务</button>
</div>
</div>
<div id="biz-loading" class="loading">加载中...</div>
<div id="biz-list"></div>
</div>
<!-- ========== 报表配置 ========== -->
<div id="tab-report" class="panel">
<div class="toolbar">
<div class="toolbar-left">
<h2>报表列表</h2>
<select class="select-biz" id="report-biz-select" onchange="loadReportList()">
<option value="">-- 选择业务 --</option>
</select>
</div>
<div class="toolbar-right">
<button class="btn btn-primary btn-sm" onclick="openReportModal()">+ 新建报表</button>
</div>
</div>
<div id="report-loading" class="loading" style="display:none">加载中...</div>
<div id="report-list"></div>
</div>
<!-- ========== 字段配置 ========== -->
<div id="tab-field" class="panel">
<div class="toolbar">
<div class="toolbar-left">
<h2>字段列表</h2>
<select class="select-biz" id="field-biz-select" onchange="onFieldBizChange()">
<option value="">-- 选择业务 --</option>
</select>
<select class="select-biz" id="field-report-select" onchange="loadFieldList()">
<option value="">-- 选择报表 --</option>
</select>
</div>
<div class="toolbar-right">
<button class="btn btn-primary btn-sm" onclick="openFieldModal()">+ 新建字段</button>
</div>
</div>
<div id="field-loading" class="loading" style="display:none">加载中...</div>
<div id="field-list"></div>
</div>
<!-- ========== 抽取配置 ========== -->
<div id="tab-extract" class="panel">
<div class="toolbar">
<div class="toolbar-left">
<h2>抽取配置列表</h2>
<select class="select-biz" id="extract-biz-select" onchange="onExtractBizChange()">
<option value="">-- 选择业务 --</option>
</select>
<select class="select-biz" id="extract-report-select" onchange="loadExtractList()">
<option value="">-- 选择报表 --</option>
</select>
</div>
<div class="toolbar-right">
<button class="btn btn-success btn-sm" onclick="execExtract()">执行抽取</button>
<button class="btn btn-warning btn-sm" onclick="execBackfill()">批量回填</button>
<button class="btn btn-primary btn-sm" onclick="openExtractModal()">+ 新建抽取配置</button>
</div>
</div>
<div id="extract-loading" class="loading" style="display:none">加载中...</div>
<div id="extract-list"></div>
</div>
<!-- ========== 数据查询 ========== -->
<div id="tab-query" class="panel">
<div class="toolbar">
<div class="toolbar-left">
<h2>数据查询</h2>
<select class="select-biz" id="query-biz-select" onchange="onQueryBizChange()">
<option value="">-- 选择业务 --</option>
</select>
<select class="select-biz" id="query-report-select" onchange="loadQueryFields()">
<option value="">-- 选择报表 --</option>
</select>
</div>
</div>
<div id="query-content" style="display:none">
<div class="query-section">
<h3>&#x1F4CA; 统计维度</h3>
<div id="dimension-chips" class="chip-group"></div>
</div>
<div class="query-section">
<h3>&#x1F4C8; 统计指标(点击后选择聚合方式)</h3>
<div id="indicator-area"></div>
<div id="selected-indicators" style="margin-top:8px"></div>
</div>
<div class="query-section">
<h3>&#x1F50D; 筛选条件</h3>
<div id="filter-area"></div>
</div>
<div class="query-section">
<h3>&#x1F4C5; 时间范围</h3>
<div class="form-inline">
<div class="form-group"><label>开始日期</label><input type="date" id="query-start-date"></div>
<div class="form-group"><label>结束日期</label><input type="date" id="query-end-date"></div>
<div class="form-group"><label>排序字段</label><input id="query-order-field" placeholder="如 total_amount"></div>
<div class="form-group"><label>排序方向</label><select id="query-order-dir"><option value="DESC">降序</option><option value="ASC">升序</option></select></div>
<div class="form-group"><label>每页</label><input type="number" id="query-page-size" value="20" style="width:70px"></div>
<div><button class="btn btn-primary" onclick="executeQuery()" style="margin-top:22px">查询</button></div>
</div>
</div>
<div id="query-result" class="result-box" style="display:none"></div>
</div>
<div id="query-empty" class="empty" style="display:none">请先选择业务和报表</div>
</div>
</div><!-- container -->
<!-- Modal -->
<div class="modal-overlay" id="modalOverlay" onclick="closeModal()"></div>
<div class="modal" id="modal">
<div class="modal-header"><h3 id="modalTitle"></h3><button class="modal-close" onclick="closeModal()">&times;</button></div>
<div class="modal-body" id="modalBody"></div>
<div class="modal-footer" id="modalFooter"></div>
</div>
<script>
const BASE = '';
let state = { businesses: [], currentBiz: null };
function toast(msg, type) {
const t = document.createElement('div');
t.className = 'toast ' + (type||'info');
t.textContent = msg;
document.body.appendChild(t);
setTimeout(() => t.remove(), 2500);
}
async function api(method, url, body) {
const opt = { method, headers: {} };
if (body) { opt.headers['Content-Type'] = 'application/json'; opt.body = JSON.stringify(body); }
const resp = await fetch(BASE + url, opt);
if (!resp.ok) { const t = await resp.text(); throw new Error(t || resp.statusText); }
const json = await resp.json();
console.log('[API] ' + method + ' ' + url, 'raw response:', json);
// GoFrame MiddlewareHandlerResponse 包裹: {code:0, data: <returnValue>}
// returnValue 可能是 {data: {...}} 或 {list: [...]} 等
if (json.code !== undefined && json.data !== undefined) {
if (json.code !== 0) throw new Error(json.message || '请求失败');
console.log('[API] extracted data:', json.data);
// 兼容 GoFrame 的 data 可能包含首字母大写的 Go 字段名
// 如 getBusinessRes{Data: ...} 在某些版本可能序列化为 {"Data": ...}
// 此时尝试统一:若有 Data 字段且无 data 字段,取 Data
if (json.data && json.data.Data !== undefined && json.data.data === undefined) {
return json.data.Data;
}
return json.data;
}
return json;
}
function esc(s) {
if (s == null || s === undefined) return '';
if (typeof s === 'object') s = JSON.stringify(s);
const d = document.createElement('div');
d.textContent = String(s);
return d.innerHTML;
}
function jsAttr(s) {
// 用于 onclick 等 JS 属性中的字符串值转义(替换反斜杠和单引号)
if (s == null || s === undefined) return '';
return String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}
function v(id) { return document.getElementById(id).value; }
async function initTables() {
try {
await api('POST', '/report/initTables');
toast('系统表初始化完成', 'success');
} catch(e) { toast(e.message, 'error'); }
}
// ==================== 导航 ====================
function switchTab(name, tabEl) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
if (tabEl) tabEl.classList.add('active');
document.getElementById('tab-' + name).classList.add('active');
if (name === 'business') loadBizList();
else if (name === 'report') loadBizSelects().then(() => loadReportList());
else if (name === 'field') loadBizSelects().then(() => onFieldBizChange());
else if (name === 'extract') loadBizSelects().then(() => onExtractBizChange());
else if (name === 'query') loadBizSelects().then(() => { document.getElementById('query-content').style.display='none'; document.getElementById('query-empty').style.display='block'; });
}
// ==================== 业务选择器 ====================
async function loadBizSelects() {
try {
const data = await api('GET', '/report/businesses');
state.businesses = data.list || [];
const optHtml = '<option value="">-- 选择业务 --</option>' + state.businesses.map(b => '<option value="'+b.businessCode+'">'+esc(b.businessName)+' ('+b.businessCode+')</option>').join('');
['report-biz-select','field-biz-select','extract-biz-select','query-biz-select'].forEach(id => {
const sel = document.getElementById(id);
const oldVal = sel.value;
sel.innerHTML = optHtml;
if (oldVal) sel.value = oldVal;
});
} catch(e) { toast(e.message, 'error'); }
}
async function onFieldBizChange() {
const bc = v('field-biz-select');
const rs = document.getElementById('field-report-select');
rs.innerHTML = '<option value="">-- 选择报表 --</option>';
if (!bc) return;
try {
const data = await api('GET', '/report/reports?businessCode=' + bc);
rs.innerHTML = '<option value="">-- 选择报表 --</option>' + (data.list||[]).map(r => '<option value="'+r.reportCode+'">'+esc(r.reportName)+' ('+r.reportCode+')</option>').join('');
} catch(e) { toast(e.message, 'error'); }
}
async function onExtractBizChange() {
const bc = v('extract-biz-select');
const rs = document.getElementById('extract-report-select');
rs.innerHTML = '<option value="">-- 选择报表 --</option>';
if (!bc) return;
try {
const data = await api('GET', '/report/reports?businessCode=' + bc);
rs.innerHTML = '<option value="">-- 选择报表 --</option>' + (data.list||[]).map(r => '<option value="'+r.reportCode+'">'+esc(r.reportName)+' ('+r.reportCode+')</option>').join('');
} catch(e) { toast(e.message, 'error'); }
}
// ==================== 业务 CRUD ====================
async function loadBizList() {
document.getElementById('biz-loading').style.display = 'block';
try {
const data = await api('GET', '/report/businesses');
document.getElementById('biz-loading').style.display = 'none';
const list = data.list || [];
if (list.length === 0) {
document.getElementById('biz-list').innerHTML = '<div class="empty">暂无业务,点击"新建业务"添加</div>';
return;
}
let h = '<table><thead><tr><th>ID</th><th>编码</th><th>名称</th><th>描述</th><th>创建人</th><th>状态</th><th>操作</th></tr></thead><tbody>';
for (const b of list) {
const sc = b.status === 'ACTIVE' ? 'active' : 'inactive';
h += '<tr><td>'+b.id+'</td><td><strong>'+esc(b.businessCode)+'</strong></td><td>'+esc(b.businessName)+'</td>' +
'<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+esc(b.description||'-')+'</td>' +
'<td>'+esc(b.creator||'-')+'</td>' +
'<td><span class="badge '+sc+'">'+(b.status||'ACTIVE')+'</span></td>' +
'<td class="actions">' +
'<button class="btn btn-sm btn-outline" onclick="openBizModal('+b.id+')">编辑</button>' +
'<button class="btn btn-sm btn-danger" onclick="deleteBiz('+b.id+')">删除</button></td></tr>';
}
h += '</tbody></table>';
document.getElementById('biz-list').innerHTML = h;
} catch(e) {
document.getElementById('biz-loading').style.display = 'none';
toast(e.message, 'error');
}
}
function bizFormHTML(d) {
d = d || {};
return '<div class="form-row"><div class="form-group"><label>业务编码 *</label><input id="f-bcode" value="'+esc(d.businessCode||'')+'" placeholder="如 KUAISHOU"></div>' +
'<div class="form-group"><label>业务名称 *</label><input id="f-bname" value="'+esc(d.businessName||'')+'" placeholder="如 快手电商"></div></div>' +
'<div class="form-group"><label>描述</label><input id="f-bdesc" value="'+esc(d.description||'')+'" placeholder="业务描述"></div>' +
'<div class="form-row"><div class="form-group"><label>操作人</label><input id="f-bop" value="'+esc(d.creator||'admin')+'"></div>' +
'<div class="form-group"><label>状态</label><select id="f-bs"><option value="ACTIVE"'+((d.status||'ACTIVE')==='ACTIVE'?' selected':'')+'>启用</option><option value="INACTIVE"'+(d.status==='INACTIVE'?' selected':'')+'>停用</option></select></div></div>';
}
async function openBizModal(id) {
const title = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
if (id) {
try {
const res = await api('GET', '/report/business?id=' + id);
const d = res && res.data !== undefined ? res.data : (res && res.Data !== undefined ? res.Data : res);
console.log('[BizEdit] id=' + id + ' raw:', res, 'data:', d);
if (!d || !d.id) { toast('未获取到业务数据(ID='+id+'', 'error'); return; }
title.textContent = '编辑业务';
body.innerHTML = bizFormHTML(d);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveBiz('+id+')">保存</button>';
} catch(e) { toast(e.message, 'error'); return; }
} else {
title.textContent = '新建业务';
body.innerHTML = bizFormHTML(null);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveBiz(0)">创建</button>';
}
document.getElementById('modalOverlay').classList.add('open');
document.getElementById('modal').classList.add('open');
}
async function saveBiz(id) {
const body = {
businessCode: v('f-bcode'), businessName: v('f-bname'),
description: v('f-bdesc'), status: v('f-bs'), operator: v('f-bop'),
};
if (id) body.id = id;
try {
await api('POST', '/report/business/save', body);
toast(id ? '更新成功' : '创建成功', 'success');
closeModal(); loadBizList(); loadBizSelects();
} catch(e) { toast(e.message, 'error'); }
}
async function deleteBiz(id) {
if (!confirm('确定删除此业务?')) return;
try { await api('DELETE', '/report/business?id='+id); toast('删除成功', 'success'); loadBizList(); loadBizSelects(); }
catch(e) { toast(e.message, 'error'); }
}
// ==================== 报表 CRUD ====================
async function loadReportList() {
const bc = v('report-biz-select');
if (!bc) { document.getElementById('report-list').innerHTML = '<div class="empty">请先选择业务</div>'; return; }
document.getElementById('report-loading').style.display = 'block';
try {
const data = await api('GET', '/report/reports?businessCode=' + bc);
document.getElementById('report-loading').style.display = 'none';
const list = data.list || [];
if (list.length === 0) {
document.getElementById('report-list').innerHTML = '<div class="empty">暂无报表</div>';
return;
}
let h = '<table><thead><tr><th>ID</th><th>编码</th><th>名称</th><th>统计宽表</th><th>日期字段</th><th>冲突键</th><th>状态</th><th>操作</th></tr></thead><tbody>';
for (const r of list) {
const sc = r.status === 'ACTIVE' ? 'active' : 'inactive';
h += '<tr><td>'+r.id+'</td><td><strong>'+esc(r.reportCode)+'</strong></td><td>'+esc(r.reportName)+'</td>' +
'<td style="font-family:monospace;font-size:11px">'+esc(r.statTableName)+'</td>' +
'<td>'+esc(r.dateField||'stat_date')+'</td>' +
'<td style="font-size:11px">'+esc((r.conflictKeys||[]).join(', '))+'</td>' +
'<td><span class="badge '+sc+'">'+(r.status||'ACTIVE')+'</span></td>' +
'<td class="actions">' +
'<button class="btn btn-sm btn-outline" onclick="openReportModal('+r.id+')">编辑</button>' +
'<button class="btn btn-sm btn-danger" onclick="deleteReport('+r.id+')">删除</button></td></tr>';
}
h += '</tbody></table>';
document.getElementById('report-list').innerHTML = h;
} catch(e) { document.getElementById('report-loading').style.display = 'none'; toast(e.message, 'error'); }
}
function reportFormHTML(d) {
d = d || {};
return '<div class="form-group"><label>所属业务</label><input id="f-rbiz" value="'+esc(d.businessCode||v('report-biz-select')||'')+'" readonly style="background:#f5f5f5"></div>' +
'<div class="form-row"><div class="form-group"><label>报表编码 *</label><input id="f-rcode" value="'+esc(d.reportCode||'')+'" placeholder="如 shop_daily_report"></div>' +
'<div class="form-group"><label>报表名称 *</label><input id="f-rname" value="'+esc(d.reportName||'')+'" placeholder="如 店铺日报"></div></div>' +
'<div class="form-group"><label>描述</label><input id="f-rdesc" value="'+esc(d.description||'')+'"></div>' +
'<div class="form-row"><div class="form-group"><label>统计宽表名 *</label><input id="f-rtable" value="'+esc(d.statTableName||'')+'" placeholder="如 stat_kuaishou_shop_daily"></div>' +
'<div class="form-group"><label>统计宽表注释</label><input id="f-rtcomment" value="'+esc(d.statTableComment||'')+'"></div></div>' +
'<div class="form-row"><div class="form-group"><label>日期字段</label><input id="f-rdate" value="'+esc(d.dateField||'stat_date')+'"></div>' +
'<div class="form-group"><label>操作人</label><input id="f-rop" value="'+esc(d.creator||'admin')+'"></div></div>' +
'<div class="form-group"><label>冲突键(逗号分隔)</label><input id="f-rck" value="'+esc((d.conflictKeys||[]).join(','))+'"></div>' +
'<div class="form-group"><label>主键(逗号分隔)</label><input id="f-rpk" value="'+esc((d.primaryKeys||['id']).join(','))+'"></div>' +
'<div class="form-row"><div class="form-group"><label>状态</label><select id="f-rs"><option value="ACTIVE"'+((d.status||'ACTIVE')==='ACTIVE'?' selected':'')+'>启用</option><option value="INACTIVE"'+(d.status==='INACTIVE'?' selected':'')+'>停用</option></select></div></div>';
}
async function openReportModal(id) {
const title = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
if (id) {
try {
const res = await api('GET', '/report/report?id=' + id);
const d = res && res.data !== undefined ? res.data : (res && res.Data !== undefined ? res.Data : res);
console.log('[ReportEdit] id=' + id + ' raw:', res, 'data:', d);
if (!d || !d.id) { toast('未获取到报表数据(ID='+id+'', 'error'); return; }
title.textContent = '编辑报表';
body.innerHTML = reportFormHTML(d);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveReport('+id+')">保存</button>';
} catch(e) { toast(e.message, 'error'); return; }
} else {
title.textContent = '新建报表';
body.innerHTML = reportFormHTML(null);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveReport(0)">创建</button>';
}
document.getElementById('modalOverlay').classList.add('open');
document.getElementById('modal').classList.add('open');
}
async function saveReport(id) {
const body = {
businessCode: v('f-rbiz'), reportCode: v('f-rcode'), reportName: v('f-rname'),
description: v('f-rdesc'), statTableName: v('f-rtable'), statTableComment: v('f-rtcomment'),
dateField: v('f-rdate'), operator: v('f-rop'), status: v('f-rs'),
conflictKeys: v('f-rck').split(',').map(s => s.trim()).filter(Boolean),
primaryKeys: v('f-rpk').split(',').map(s => s.trim()).filter(Boolean),
};
if (id) body.id = id;
try {
await api('POST', '/report/report/save', body);
toast(id ? '更新成功' : '创建成功', 'success');
closeModal(); loadReportList();
} catch(e) { toast(e.message, 'error'); }
}
async function deleteReport(id) {
if (!confirm('确定删除此报表?')) return;
try { await api('DELETE', '/report/report?id='+id); toast('删除成功', 'success'); loadReportList(); }
catch(e) { toast(e.message, 'error'); }
}
// ==================== 字段 CRUD ====================
async function loadFieldList() {
const bc = v('field-biz-select');
const rc = v('field-report-select');
if (!bc || !rc) { document.getElementById('field-list').innerHTML = '<div class="empty">请先选择业务和报表</div>'; return; }
document.getElementById('field-loading').style.display = 'block';
try {
const data = await api('GET', '/report/fields?businessCode=' + bc + '&reportCode=' + rc);
document.getElementById('field-loading').style.display = 'none';
const dims = data.dimensions || [], inds = data.indicators || [], fils = data.filters || [];
if (dims.length + inds.length + fils.length === 0) {
document.getElementById('field-list').innerHTML = '<div class="empty">暂无字段</div>';
return;
}
const allFields = [...dims.map(f => ({...f, _role: '维度'})), ...inds.map(f => ({...f, _role: '指标'})), ...fils.map(f => ({...f, _role: '筛选'}))];
let h = '<table><thead><tr><th>ID</th><th>角色</th><th>编码</th><th>名称</th><th>类型</th><th>可聚合</th><th>默认聚合</th><th>分组</th><th>排序</th><th>状态</th><th>操作</th></tr></thead><tbody>';
for (const f of allFields) {
const sc = f.status === 'ACTIVE' ? 'active' : 'inactive';
h += '<tr><td>'+f.id+'</td><td><span class="badge '+(f.fieldRole==='DIMENSION'?'info':f.fieldRole==='INDICATOR'?'success':'')+'">'+f._role+'</span></td>' +
'<td><strong>'+esc(f.fieldCode)+'</strong></td><td>'+esc(f.fieldName)+'</td>' +
'<td>'+esc(f.fieldType)+'</td>' +
'<td>'+(f.isAggregatable ? '✓' : '-')+'</td>' +
'<td>'+esc(f.defaultAggregate||'-')+'</td>' +
'<td>'+esc(f.groupName||'-')+'</td><td>'+f.sortOrder+'</td>' +
'<td><span class="badge '+sc+'">'+(f.status||'ACTIVE')+'</span></td>' +
'<td class="actions">' +
'<button class="btn btn-sm btn-outline" onclick="openFieldModal('+f.id+')">编辑</button>' +
'<button class="btn btn-sm btn-danger" onclick="deleteField('+f.id+')">删除</button></td></tr>';
}
h += '</tbody></table>';
document.getElementById('field-list').innerHTML = h;
} catch(e) { document.getElementById('field-loading').style.display = 'none'; toast(e.message, 'error'); }
}
function fieldFormHTML(d) {
d = d || {};
return '<div class="form-row"><div class="form-group"><label>所属业务</label><input id="f-fbiz" value="'+esc(d.businessCode||v('field-biz-select')||'')+'" readonly style="background:#f5f5f5"></div>' +
'<div class="form-group"><label>所属报表</label><input id="f-frep" value="'+esc(d.reportCode||v('field-report-select')||'')+'" readonly style="background:#f5f5f5"></div></div>' +
'<div class="form-row-3"><div class="form-group"><label>字段编码 *</label><input id="f-fcode" value="'+esc(d.fieldCode||'')+'" placeholder="如 order_amount"></div>' +
'<div class="form-group"><label>字段名称 *</label><input id="f-fname" value="'+esc(d.fieldName||'')+'"></div>' +
'<div class="form-group"><label>字段类型 *</label><select id="f-ftype"><option value="STRING"'+(d.fieldType==='STRING'||!d.fieldType?' selected':'')+'>STRING</option><option value="INT"'+(d.fieldType==='INT'?' selected':'')+'>INT</option><option value="FLOAT"'+(d.fieldType==='FLOAT'?' selected':'')+'>FLOAT</option><option value="DATE"'+(d.fieldType==='DATE'?' selected':'')+'>DATE</option><option value="DATETIME"'+(d.fieldType==='DATETIME'?' selected':'')+'>DATETIME</option><option value="JSONB"'+(d.fieldType==='JSONB'?' selected':'')+'>JSONB</option></select></div></div>' +
'<div class="form-row-3"><div class="form-group"><label>字段角色 *</label><select id="f-frole"><option value="DIMENSION"'+(d.fieldRole==='DIMENSION'||!d.fieldRole?' selected':'')+'>维度(DIMENSION)</option><option value="INDICATOR"'+(d.fieldRole==='INDICATOR'?' selected':'')+'>指标(INDICATOR)</option><option value="FILTER"'+(d.fieldRole==='FILTER'?' selected':'')+'>筛选(FILTER)</option><option value="FILTER_ONLY"'+(d.fieldRole==='FILTER_ONLY'?' selected':'')+'>仅筛选(FILTER_ONLY)</option></select></div>' +
'<div class="form-group"><label>数据存储类型</label><select id="f-fdtype"><option value="STRING"'+(d.dataType==='STRING'||!d.dataType?' selected':'')+'>STRING</option><option value="INT"'+(d.dataType==='INT'?' selected':'')+'>INT</option><option value="FLOAT"'+(d.dataType==='FLOAT'?' selected':'')+'>FLOAT</option><option value="DATE"'+(d.dataType==='DATE'?' selected':'')+'>DATE</option><option value="DATETIME"'+(d.dataType==='DATETIME'?' selected':'')+'>DATETIME</option><option value="JSONB"'+(d.dataType==='JSONB'?' selected':'')+'>JSONB</option></select></div>' +
'<div class="form-group"><label>默认聚合</label><select id="f-faggr"><option value=""'+(!d.defaultAggregate?' selected':'')+'>无</option><option value="SUM"'+(d.defaultAggregate==='SUM'?' selected':'')+'>SUM</option><option value="COUNT"'+(d.defaultAggregate==='COUNT'?' selected':'')+'>COUNT</option><option value="AVG"'+(d.defaultAggregate==='AVG'?' selected':'')+'>AVG</option><option value="MAX"'+(d.defaultAggregate==='MAX'?' selected':'')+'>MAX</option><option value="MIN"'+(d.defaultAggregate==='MIN'?' selected':'')+'>MIN</option></select></div></div>' +
'<div class="form-row-3"><div class="form-group"><label>分组名称</label><input id="f-fgroup" value="'+esc(d.groupName||'')+'"></div>' +
'<div class="form-group"><label>排序号</label><input id="f-fsort" type="number" value="'+(d.sortOrder||0)+'"></div>' +
'<div class="form-group"><label>单位</label><input id="f-funit" value="'+esc(d.unit||'')+'"></div></div>' +
'<div class="form-group"><label>表达式(衍生字段如 {refund}/{order}*100</label><input id="f-fexpr" value="'+esc(d.expression||'')+'"></div>' +
'<div class="form-row"><div class="form-group"><label>操作人</label><input id="f-fop" value="'+esc(d.creator||'admin')+'"></div>' +
'<div class="form-group"><label>状态</label><select id="f-fs"><option value="ACTIVE"'+((d.status||'ACTIVE')==='ACTIVE'?' selected':'')+'>启用</option><option value="INACTIVE"'+(d.status==='INACTIVE'?' selected':'')+'>停用</option></select></div></div>' +
'<div class="checkbox-group" style="margin-top:8px"><label><input type="checkbox" id="f-fagg" '+(d.isAggregatable===true?'checked':'')+'> 可聚合</label>' +
'<label><input type="checkbox" id="f-ffilter" '+((d.isFilterable===undefined||d.isFilterable===true)?'checked':'')+'> 可筛选</label>' +
'<label><input type="checkbox" id="f-fquery" '+((d.isQueryable===undefined||d.isQueryable===true)?'checked':'')+'> 可查询</label>' +
'<label><input type="checkbox" id="f-fsortable" '+((d.isSortable===undefined||d.isSortable===true)?'checked':'')+'> 可排序</label></div>';
}
async function openFieldModal(id) {
const title = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
if (id) {
try {
const res = await api('GET', '/report/field?id=' + id);
const d = res && res.data !== undefined ? res.data : (res && res.Data !== undefined ? res.Data : res);
console.log('[FieldEdit] id=' + id + ' raw:', res, 'data:', d);
if (!d || !d.id) { toast('未获取到字段数据(ID='+id+'', 'error'); return; }
title.textContent = '编辑字段';
body.innerHTML = fieldFormHTML(d);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveField('+id+')">保存</button>';
} catch(e) { toast(e.message, 'error'); return; }
} else {
title.textContent = '新建字段';
body.innerHTML = fieldFormHTML(null);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveField(0)">创建</button>';
}
document.getElementById('modalOverlay').classList.add('open');
document.getElementById('modal').classList.add('open');
}
function fieldVal(id) { const el = document.getElementById(id); return el ? (el.type==='checkbox'?el.checked:el.value) : ''; }
async function saveField(id) {
const body = {
businessCode: v('f-fbiz'), reportCode: v('f-frep'),
fieldCode: v('f-fcode'), fieldName: v('f-fname'),
fieldType: v('f-ftype'), dataType: v('f-fdtype'), fieldRole: v('f-frole'),
defaultAggregate: v('f-faggr'), groupName: v('f-fgroup'),
sortOrder: parseInt(v('f-fsort'))||0, unit: v('f-funit'),
expression: v('f-fexpr'), operator: v('f-fop'), status: v('f-fs'),
isAggregatable: fieldVal('f-fagg'),
isFilterable: fieldVal('f-ffilter'),
isQueryable: fieldVal('f-fquery'),
isSortable: fieldVal('f-fsortable'),
};
if (id) body.id = id;
try {
await api('POST', '/report/field/save', body);
toast(id ? '更新成功' : '创建成功', 'success');
closeModal(); loadFieldList();
} catch(e) { toast(e.message, 'error'); }
}
async function deleteField(id) {
if (!confirm('确定删除此字段?')) return;
try { await api('DELETE', '/report/field?id='+id); toast('删除成功', 'success'); loadFieldList(); }
catch(e) { toast(e.message, 'error'); }
}
// ==================== 抽取配置 CRUD ====================
async function loadExtractList() {
const bc = v('extract-biz-select');
const rc = v('extract-report-select');
if (!bc || !rc) { document.getElementById('extract-list').innerHTML = '<div class="empty">请先选择业务和报表</div>'; return; }
document.getElementById('extract-loading').style.display = 'block';
try {
const data = await api('GET', '/report/extractConfigs?businessCode=' + bc + '&reportCode=' + rc);
document.getElementById('extract-loading').style.display = 'none';
const list = data.list || [];
if (list.length === 0) {
document.getElementById('extract-list').innerHTML = '<div class="empty">暂无抽取配置</div>';
return;
}
let h = '<table><thead><tr><th>ID</th><th>编码</th><th>名称</th><th>源表</th><th>目标表</th><th>抽取模式</th><th>抽取类型</th><th>启用</th><th>操作</th></tr></thead><tbody>';
for (const ec of list) {
h += '<tr><td>'+ec.id+'</td><td><strong>'+esc(ec.extractCode)+'</strong></td><td>'+esc(ec.extractName)+'</td>' +
'<td style="font-family:monospace;font-size:11px">'+esc(ec.sourceTableName||'-')+'</td>' +
'<td style="font-family:monospace;font-size:11px">'+esc(ec.targetTableName||'-')+'</td>' +
'<td><span class="badge '+(ec.extractMode==='AGGREGATE'?'info':'active')+'">'+(ec.extractMode||'DIRECT')+'</span></td>' +
'<td><span class="badge active">'+(ec.extractType||'INCREMENTAL')+'</span></td>' +
'<td>'+(ec.isEnabled ? '✓' : '✗')+'</td>' +
'<td class="actions">' +
'<button class="btn btn-sm btn-outline" onclick="openExtractModal('+ec.id+')">编辑</button>' +
'<button class="btn btn-sm btn-danger" onclick="deleteExtract('+ec.id+')">删除</button></td></tr>';
}
h += '</tbody></table>';
document.getElementById('extract-list').innerHTML = h;
} catch(e) { document.getElementById('extract-loading').style.display = 'none'; toast(e.message, 'error'); }
}
function extractFormHTML(d) {
d = d || {};
return '<div class="form-row"><div class="form-group"><label>所属业务</label><input id="f-ebiz" value="'+esc(d.businessCode||v('extract-biz-select')||'')+'" readonly style="background:#f5f5f5"></div>' +
'<div class="form-group"><label>所属报表</label><input id="f-erep" value="'+esc(d.reportCode||v('extract-report-select')||'')+'" readonly style="background:#f5f5f5"></div></div>' +
'<div class="form-row"><div class="form-group"><label>抽取编码 *</label><input id="f-ecode" value="'+esc(d.extractCode||'')+'"></div>' +
'<div class="form-group"><label>抽取名称 *</label><input id="f-ename" value="'+esc(d.extractName||'')+'"></div></div>' +
'<div class="form-row"><div class="form-group"><label>源表名 *</label><input id="f-esrc" value="'+esc(d.sourceTableName||'')+'"></div>' +
'<div class="form-group"><label>源表别名</label><input id="f-esrca" value="'+esc(d.sourceTableAlias||'')+'"></div></div>' +
'<div class="form-row"><div class="form-group"><label>目标表名 *</label><input id="f-etgt" value="'+esc(d.targetTableName||'')+'"></div>' +
'<div class="form-group"><label>增量依据字段</label><input id="f-ekey" value="'+esc(d.extractKeyField||'')+'"></div></div>' +
'<div class="form-row-3"><div class="form-group"><label>抽取模式</label><select id="f-emode"><option value="DIRECT"'+(d.extractMode==='DIRECT'||!d.extractMode?' selected':'')+'>DIRECT</option><option value="AGGREGATE"'+(d.extractMode==='AGGREGATE'?' selected':'')+'>AGGREGATE</option></select></div>' +
'<div class="form-group"><label>抽取类型</label><select id="f-etype"><option value="INCREMENTAL"'+(d.extractType==='INCREMENTAL'||!d.extractType?' selected':'')+'>增量(INCREMENTAL)</option><option value="FULL"'+(d.extractType==='FULL'?' selected':'')+'>全量(FULL)</option></select></div>' +
'<div class="form-group"><label>批大小</label><input id="f-ebatch" type="number" value="'+(d.batchSize||1000)+'"></div></div>' +
'<div class="form-group"><label>GROUP BY 字段(逗号分隔)</label><input id="f-egroup" value="'+esc((d.groupByFields||[]).join(','))+'"></div>' +
'<div class="form-group"><label>过滤表达式</label><input id="f-efilter" value="'+esc(d.filterExpression||'')+'" placeholder="如 o.status != \'CANCELLED\'"></div>' +
'<div class="form-group"><label>操作人</label><input id="f-eop" value="'+esc(d.creator||'admin')+'"></div>' +
'<div class="form-row"><div class="form-group"><label>状态</label><select id="f-es"><option value="ACTIVE"'+((d.status||'ACTIVE')==='ACTIVE'?' selected':'')+'>启用</option><option value="INACTIVE"'+(d.status==='INACTIVE'?' selected':'')+'>停用</option></select></div>' +
'<div class="form-group"><label><input type="checkbox" id="f-eenabled" '+((d.isEnabled===undefined||d.isEnabled===true)?'checked':'')+'> 启用抽取</label></div></div>';
}
async function openExtractModal(id) {
const title = document.getElementById('modalTitle');
const body = document.getElementById('modalBody');
const footer = document.getElementById('modalFooter');
if (id) {
try {
const res = await api('GET', '/report/extractConfig?id=' + id);
const d = res && res.data !== undefined ? res.data : (res && res.Data !== undefined ? res.Data : res);
console.log('[ExtractEdit] id=' + id + ' raw:', res, 'data:', d);
if (!d || !d.id) { toast('未获取到抽取配置数据(ID='+id+'', 'error'); return; }
title.textContent = '编辑抽取配置';
body.innerHTML = extractFormHTML(d);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveExtract('+id+')">保存</button>';
} catch(e) { toast(e.message, 'error'); return; }
} else {
title.textContent = '新建抽取配置';
body.innerHTML = extractFormHTML(null);
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveExtract(0)">创建</button>';
}
document.getElementById('modalOverlay').classList.add('open');
document.getElementById('modal').classList.add('open');
}
async function saveExtract(id) {
const body = {
businessCode: v('f-ebiz'), reportCode: v('f-erep'),
extractCode: v('f-ecode'), extractName: v('f-ename'),
sourceTableName: v('f-esrc'), sourceTableAlias: v('f-esrca'),
targetTableName: v('f-etgt'), extractKeyField: v('f-ekey'),
extractMode: v('f-emode'), extractType: v('f-etype'),
batchSize: parseInt(v('f-ebatch'))||1000,
groupByFields: v('f-egroup').split(',').map(s => s.trim()).filter(Boolean),
filterExpression: v('f-efilter'), operator: v('f-eop'),
status: v('f-es'), isEnabled: document.getElementById('f-eenabled').checked,
};
if (id) body.id = id;
try {
await api('POST', '/report/extractConfig/save', body);
toast(id ? '更新成功' : '创建成功', 'success');
closeModal(); loadExtractList();
} catch(e) { toast(e.message, 'error'); }
}
async function deleteExtract(id) {
if (!confirm('确定删除此抽取配置?')) return;
try { await api('DELETE', '/report/extractConfig?id='+id); toast('删除成功', 'success'); loadExtractList(); }
catch(e) { toast(e.message, 'error'); }
}
async function execExtract() {
const bc = v('extract-biz-select');
const rc = v('extract-report-select');
if (!bc || !rc) { toast('请先选择业务和报表', 'error'); return; }
const date = prompt('请输入统计日期 (yyyy-MM-dd)', new Date().toISOString().slice(0,10));
if (!date) return;
try {
const res = await api('POST', '/report/extract', { businessCode: bc, reportCode: rc, statDate: date, executor: 'admin' });
toast('抽取完成!成功: ' + res.successCount + ' 失败: ' + res.failCount + ' 耗时: ' + res.execTimeMs + 'ms', res.success ? 'success' : 'error');
} catch(e) { toast(e.message, 'error'); }
}
// ==================== 数据查询 ====================
let queryCtx = { dimensions: [], indicators: [], filters: [], availableFields: null };
async function execBackfill() {
const bc = v('extract-biz-select');
const rc = v('extract-report-select');
if (!bc || !rc) { toast('请先选择业务和报表', 'error'); return; }
const start = prompt('请输入开始日期 (yyyy-MM-dd)');
if (!start) return;
const end = prompt('请输入结束日期 (yyyy-MM-dd)');
if (!end) return;
if (!confirm('确定回填 ' + start + ' ~ ' + end + ' 的数据?')) return;
try {
const res = await api('POST', '/report/backfill', { businessCode: bc, reportCode: rc, startDate: start, endDate: end, executor: 'admin' });
toast('回填完成!成功: ' + res.successDays + '/' + res.totalDays + ' 天, 耗时: ' + res.execTimeMs + 'ms' + (res.failDays > 0 ? ' 失败: ' + res.failDays + ' 天' : ''), res.success ? 'success' : 'error');
} catch(e) { toast(e.message, 'error'); }
}
async function onQueryBizChange() {
const bc = v('query-biz-select');
const rs = document.getElementById('query-report-select');
rs.innerHTML = '<option value="">-- 选择报表 --</option>';
queryCtx = { dimensions: [], indicators: [], filters: [], availableFields: null };
if (!bc) return;
try {
const data = await api('GET', '/report/reports?businessCode=' + bc);
rs.innerHTML = '<option value="">-- 选择报表 --</option>' + (data.list||[]).map(r => '<option value="'+r.reportCode+'">'+esc(r.reportName)+' ('+r.reportCode+')</option>').join('');
} catch(e) { toast(e.message, 'error'); }
}
async function loadQueryFields() {
const bc = v('query-biz-select');
const rc = v('query-report-select');
if (!bc || !rc) {
document.getElementById('query-content').style.display = 'none';
document.getElementById('query-empty').style.display = 'block';
return;
}
try {
const data = await api('GET', '/report/fields?businessCode=' + bc + '&reportCode=' + rc);
queryCtx.availableFields = {
dimensions: data.dimensions || [],
indicators: data.indicators || [],
filters: data.filters || []
};
queryCtx.dimensions = [];
queryCtx.indicators = [];
queryCtx.filters = [];
renderQueryUI();
document.getElementById('query-content').style.display = 'block';
document.getElementById('query-empty').style.display = 'none';
document.getElementById('query-result').style.display = 'none';
} catch(e) { toast(e.message, 'error'); }
}
function renderQueryUI() {
const f = queryCtx.availableFields;
if (!f) return;
// 维度
let dimHtml = '';
for (const d of f.dimensions) {
const sel = queryCtx.dimensions.includes(d.fieldCode);
dimHtml += '<span class="chip'+(sel?' selected':'')+'" onclick="toggleDim(\''+jsAttr(d.fieldCode)+'\')">'+esc(d.fieldName||d.fieldCode)+'</span>';
}
document.getElementById('dimension-chips').innerHTML = dimHtml;
// 指标
let indHtml = '';
for (const d of f.indicators) {
const sel = queryCtx.indicators.find(i => i.fieldCode === d.fieldCode);
const fn = esc(d.fieldName||d.fieldCode);
const unitSuffix = d.unit && fn.indexOf('('+d.unit+')') === -1 ? '('+d.unit+')' : '';
indHtml += '<span class="chip'+(sel?' selected':'')+'" onclick="toggleInd(\''+jsAttr(d.fieldCode)+'\', \''+jsAttr(d.defaultAggregate||'SUM')+'\', \''+jsAttr(d.fieldName||d.fieldCode)+'\')">'+fn+unitSuffix+'</span>';
}
document.getElementById('indicator-area').innerHTML = indHtml;
renderSelectedIndicators();
// 筛选
let filHtml = '';
for (const d of [...f.filters, ...f.indicators, ...f.dimensions]) {
filHtml += '<span class="chip" onclick="addFilter(\''+jsAttr(d.fieldCode)+'\', \''+jsAttr(d.fieldName||d.fieldCode)+'\')">'+esc(d.fieldName||d.fieldCode)+'</span>';
}
document.getElementById('filter-area').innerHTML = filHtml;
// 默认时间范围
if (!document.getElementById('query-start-date').value) {
const today = new Date().toISOString().slice(0,10);
const weekAgo = new Date(Date.now() - 7*86400000).toISOString().slice(0,10);
document.getElementById('query-start-date').value = weekAgo;
document.getElementById('query-end-date').value = today;
}
}
function toggleDim(code) {
const idx = queryCtx.dimensions.indexOf(code);
if (idx >= 0) queryCtx.dimensions.splice(idx, 1);
else queryCtx.dimensions.push(code);
renderQueryUI();
}
function toggleInd(code, defAggr, name) {
const idx = queryCtx.indicators.findIndex(i => i.fieldCode === code);
if (idx >= 0) { queryCtx.indicators.splice(idx, 1); renderSelectedIndicators(); renderQueryUI(); return; }
const aggr = prompt('选择聚合方式 (SUM/COUNT/AVG/MAX/MIN)', defAggr||'SUM');
if (!aggr) return;
queryCtx.indicators.push({ fieldCode: code, aggregate: aggr.toUpperCase(), alias: code + '_' + aggr.toLowerCase() });
renderSelectedIndicators();
renderQueryUI();
}
function renderSelectedIndicators() {
if (queryCtx.indicators.length === 0) {
document.getElementById('selected-indicators').innerHTML = '';
return;
}
let h = '<div style="font-size:12px;color:#666;margin-bottom:4px">已选指标:</div>';
for (const ind of queryCtx.indicators) {
h += '<span class="field-tag">'+esc(ind.fieldCode)+' ('+ind.aggregate+') <span class="remove" onclick="removeInd(\''+jsAttr(ind.fieldCode)+'\')">×</span></span>';
}
document.getElementById('selected-indicators').innerHTML = h;
}
function removeInd(code) {
queryCtx.indicators = queryCtx.indicators.filter(i => i.fieldCode !== code);
renderSelectedIndicators();
renderQueryUI();
}
let filterCounter = 0;
function addFilter(code, name) {
filterCounter++;
const area = document.getElementById('filter-area');
const ops = ['=','!=','>','<','>=','<=','IN','LIKE','BETWEEN'];
const isBetween = ops[ops.length-1];
const row = document.createElement('div');
row.className = 'add-filter-row';
row.id = 'filter-row-'+filterCounter;
row.innerHTML = '<select id="fcol-'+filterCounter+'" style="padding:6px 8px;border:1px solid #d9d9d9;border-radius:6px;font-size:11px"><option value="'+esc(code)+'">'+esc(name)+' ('+esc(code)+')</option></select>' +
'<select id="fop-'+filterCounter+'" style="padding:6px 8px;border:1px solid #d9d9d9;border-radius:6px;font-size:11px" onchange="onFilterOpChange('+filterCounter+')">'+ops.map(o=>'<option value="'+o+'">'+o+'</option>').join('')+'</select>' +
'<input id="fval-'+filterCounter+'" style="padding:6px 8px;border:1px solid #d9d9d9;border-radius:6px;font-size:11px" placeholder="值">' +
'<button class="btn btn-sm btn-danger" style="padding:4px 8px" onclick="removeFilter('+filterCounter+')">×</button>';
area.appendChild(row);
}
function onFilterOpChange(idx) {
const op = document.getElementById('fop-'+idx).value;
const valEl = document.getElementById('fval-'+idx);
if (op === 'BETWEEN') valEl.placeholder = '值1,值2';
else if (op === 'IN') valEl.placeholder = '逗号分隔';
else valEl.placeholder = '值';
}
function removeFilter(idx) {
const row = document.getElementById('filter-row-'+idx);
if (row) row.remove();
}
async function executeQuery() {
const bc = v('query-biz-select');
const rc = v('query-report-select');
if (!bc || !rc) { toast('请先选择业务和报表', 'error'); return; }
const filters = [];
document.querySelectorAll('[id^="filter-row-"]').forEach(row => {
const m = row.id.match(/filter-row-(\d+)/);
if (!m) return;
const cid = m[1];
const colEl = document.getElementById('fcol-'+cid);
const opEl = document.getElementById('fop-'+cid);
const valEl = document.getElementById('fval-'+cid);
if (!colEl || !opEl || !valEl || !valEl.value.trim()) return;
const op = opEl.value;
let val = valEl.value.trim();
let val2 = null;
if (op === 'BETWEEN') {
const parts = val.split(',').map(s => s.trim());
if (parts.length >= 2) { val = parts[0]; val2 = parts[1]; }
else return;
} else if (op === 'IN') {
val = val.split(',').map(s => s.trim());
}
const f = { fieldCode: colEl.value, operator: op, value: val };
if (val2 !== null) f.value2 = val2;
filters.push(f);
});
const body = {
businessCode: bc, reportCode: rc,
dimensions: queryCtx.dimensions,
indicators: queryCtx.indicators,
filters: filters,
timeRange: { startDate: v('query-start-date'), endDate: v('query-end-date') },
orderBy: v('query-order-field') ? [{ fieldCode: v('query-order-field'), direction: v('query-order-dir') }] : [],
page: 1, pageSize: parseInt(v('query-page-size'))||20,
};
const resultBox = document.getElementById('query-result');
resultBox.style.display = 'block';
resultBox.textContent = '查询中...';
try {
const res = await api('POST', '/report/query', body);
let output = '总记录数: ' + res.total + ' | 执行耗时: ' + res.execTimeMs + 'ms\n';
if (res.sql) output += '生成SQL: ' + res.sql + '\n';
output += '\n';
if (res.list && res.list.length > 0) {
// 表头
const keys = Object.keys(res.list[0]);
output += keys.join('\t') + '\n';
output += keys.map(() => '---').join('\t') + '\n';
for (const row of res.list) {
output += keys.map(k => row[k] != null ? String(row[k]) : '').join('\t') + '\n';
}
} else {
output += '(无数据)';
}
resultBox.textContent = output;
} catch(e) { resultBox.textContent = '查询失败: ' + e.message; }
}
// ==================== 通用 ====================
function closeModal() {
document.getElementById('modalOverlay').classList.remove('open');
document.getElementById('modal').classList.remove('open');
}
// 初始化
loadBizList();
loadBizSelects();
</script>
</body>
</html>`
+499
View File
@@ -0,0 +1,499 @@
package report
import (
"context"
"strconv"
"strings"
reportSvc "dataengine/common/report"
"dataengine/common/report/model"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/sirupsen/logrus"
)
// ============================================================
// 批量操作(供 PPGo_Job 调度)
// ============================================================
type extractAllReq struct {
g.Meta `path:"/extractAll" method:"post" tags:"报表引擎" summary:"全量抽取" dc:"遍历所有启用业务+报表,抽取前一天数据"`
}
type extractAllRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
func (c *report) ExtractAll(ctx context.Context, req *extractAllReq) (*extractAllRes, error) {
ctx = ctxWithUser(ctx)
logrus.Info("[HTTP] 触发全量抽取")
reportSvc.ExecDailyExtract(ctx)
return &extractAllRes{Success: true, Message: "全量抽取已触发"}, nil
}
type report struct{}
var ReportController = new(report)
func svc() *reportSvc.ReportService {
return reportSvc.GetService()
}
func ctxWithUser(ctx context.Context) context.Context {
return context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
}
// ============================================================
// 业务 CRUD
// ============================================================
type listBusinessesReq struct {
g.Meta `path:"/businesses" method:"get" tags:"报表引擎" summary:"业务列表"`
model.PageReq
}
type listBusinessesRes struct {
List []model.BusinessConfig `json:"list"`
model.PageRes
}
func (c *report) ListBusinesses(ctx context.Context, req *listBusinessesReq) (*listBusinessesRes, error) {
ctx = ctxWithUser(ctx)
list, total, err := svc().ListBusinesses(ctx, req.PageNum, req.PageSize)
if err != nil {
logrus.Errorf("[报表引擎] 获取业务列表失败: %v", err)
return nil, err
}
totalPages := (total + req.PageSize - 1) / req.PageSize
return &listBusinessesRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil
}
type getBusinessReq struct {
g.Meta `path:"/business" method:"get" tags:"报表引擎" summary:"获取业务"`
ID int64 `json:"id" v:"required"`
}
type getBusinessRes struct {
Data *model.BusinessConfig `json:"data"`
}
func (c *report) GetBusiness(ctx context.Context, req *getBusinessReq) (*getBusinessRes, error) {
ctx = ctxWithUser(ctx)
data, err := svc().GetBusiness(ctx, req.ID)
if err != nil {
logrus.Errorf("[报表引擎] 获取业务失败 id=%d: %v", req.ID, err)
return nil, err
}
return &getBusinessRes{Data: data}, nil
}
type saveBusinessReq struct {
g.Meta `path:"/business/save" method:"post" tags:"报表引擎" summary:"保存业务"`
model.SaveBusinessReq
}
type saveBusinessRes struct {
*model.SaveResult
}
func (c *report) SaveBusiness(ctx context.Context, req *saveBusinessReq) (*saveBusinessRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().SaveBusiness(ctx, &req.SaveBusinessReq)
if err != nil {
logrus.Errorf("[报表引擎] 保存业务失败: %v", err)
return nil, err
}
return &saveBusinessRes{SaveResult: result}, nil
}
type deleteBusinessReq struct {
g.Meta `path:"/business" method:"delete" tags:"报表引擎" summary:"删除业务"`
ID int64 `json:"id" v:"required"`
}
type deleteBusinessRes struct {
*model.DeleteResult
}
func (c *report) DeleteBusiness(ctx context.Context, req *deleteBusinessReq) (*deleteBusinessRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().DeleteBusiness(ctx, req.ID)
if err != nil {
logrus.Errorf("[报表引擎] 删除业务失败 id=%d: %v", req.ID, err)
return nil, err
}
return &deleteBusinessRes{DeleteResult: result}, nil
}
// ============================================================
// 报表 CRUD
// ============================================================
type listReportsReq struct {
g.Meta `path:"/reports" method:"get" tags:"报表引擎" summary:"报表列表"`
BusinessCode string `json:"businessCode" v:"required"`
ReportName string `json:"reportName" dc:"报表名称(模糊搜索)"`
model.PageReq
}
type listReportsRes struct {
List []model.ReportConfig `json:"list"`
model.PageRes
}
func (c *report) ListReports(ctx context.Context, req *listReportsReq) (*listReportsRes, error) {
ctx = ctxWithUser(ctx)
list, total, err := svc().ListReports(ctx, req.BusinessCode, req.ReportName, req.PageNum, req.PageSize)
if err != nil {
logrus.Errorf("[报表引擎] 获取报表列表失败 %s: %v", req.BusinessCode, err)
return nil, err
}
// 填充抽取配置ID(逗号分隔,不限 status)
for i := range list {
configs, err := svc().GetExtractConfigsAll(ctx, list[i].BusinessCode, list[i].ReportCode)
if err == nil {
ids := make([]string, 0, len(configs))
for _, ec := range configs {
ids = append(ids, strconv.FormatInt(ec.Id, 10))
}
list[i].ExtractConfigIDs = strings.Join(ids, ",")
}
}
totalPages := (total + req.PageSize - 1) / req.PageSize
return &listReportsRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil
}
type getReportReq struct {
g.Meta `path:"/report" method:"get" tags:"报表引擎" summary:"获取报表"`
ID int64 `json:"id" v:"required"`
}
type getReportRes struct {
Data *model.ReportConfig `json:"data"`
}
func (c *report) GetReport(ctx context.Context, req *getReportReq) (*getReportRes, error) {
ctx = ctxWithUser(ctx)
data, err := svc().GetReport(ctx, req.ID)
if err != nil {
logrus.Errorf("[报表引擎] 获取报表失败 id=%d: %v", req.ID, err)
return nil, err
}
// 填充抽取配置ID(逗号分隔,不限 status)
configs, err := svc().GetExtractConfigsAll(ctx, data.BusinessCode, data.ReportCode)
if err == nil {
ids := make([]string, 0, len(configs))
for _, ec := range configs {
ids = append(ids, strconv.FormatInt(ec.Id, 10))
}
data.ExtractConfigIDs = strings.Join(ids, ",")
}
return &getReportRes{Data: data}, nil
}
type saveReportReq struct {
g.Meta `path:"/report/save" method:"post" tags:"报表引擎" summary:"保存报表"`
model.SaveReportReq
}
type saveReportRes struct {
*model.SaveResult
}
func (c *report) SaveReport(ctx context.Context, req *saveReportReq) (*saveReportRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().SaveReport(ctx, &req.SaveReportReq)
if err != nil {
logrus.Errorf("[报表引擎] 保存报表失败: %v", err)
return nil, err
}
return &saveReportRes{SaveResult: result}, nil
}
type saveReportWithFieldsReq struct {
g.Meta `path:"/report/saveWithFields" method:"post" tags:"报表引擎" summary:"保存报表及字段配置(全量替换)"`
model.SaveReportWithFieldsReq
}
type saveReportWithFieldsRes struct {
*model.SaveResult
}
func (c *report) SaveReportWithFields(ctx context.Context, req *saveReportWithFieldsReq) (*saveReportWithFieldsRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().SaveReportWithFields(ctx, &req.SaveReportWithFieldsReq)
if err != nil {
logrus.Errorf("[报表引擎] 保存报表及字段失败 %s/%s: %v", req.BusinessCode, req.ReportCode, err)
return nil, err
}
logrus.Infof("[报表引擎] 保存报表及字段成功 %s/%s id=%d", req.BusinessCode, req.ReportCode, result.ID)
return &saveReportWithFieldsRes{SaveResult: result}, nil
}
type deleteReportReq struct {
g.Meta `path:"/report" method:"delete" tags:"报表引擎" summary:"删除报表"`
ID int64 `json:"id" v:"required"`
}
type deleteReportRes struct {
*model.DeleteResult
}
func (c *report) DeleteReport(ctx context.Context, req *deleteReportReq) (*deleteReportRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().DeleteReport(ctx, req.ID)
if err != nil {
logrus.Errorf("[报表引擎] 删除报表失败 id=%d: %v", req.ID, err)
return nil, err
}
return &deleteReportRes{DeleteResult: result}, nil
}
// ============================================================
// 字段 CRUD
// ============================================================
type getReportFieldsReq struct {
g.Meta `path:"/fields" method:"get" tags:"报表引擎" summary:"报表字段列表(按角色分组)"`
BusinessCode string `json:"businessCode" v:"required"`
ReportCode string `json:"reportCode" v:"required"`
model.PageReq
}
func (c *report) GetReportFields(ctx context.Context, req *getReportFieldsReq) (*model.GetReportFieldsResp, error) {
ctx = ctxWithUser(ctx)
resp, err := svc().GetReportFields(ctx, req.BusinessCode, req.ReportCode)
if err != nil {
logrus.Errorf("[报表引擎] 获取报表字段失败 %s/%s: %v", req.BusinessCode, req.ReportCode, err)
return nil, err
}
return resp, nil
}
type getFieldReq struct {
g.Meta `path:"/field" method:"get" tags:"报表引擎" summary:"获取字段"`
ID int64 `json:"id" v:"required"`
}
type getFieldRes struct {
Data *model.FieldConfig `json:"data"`
}
func (c *report) GetField(ctx context.Context, req *getFieldReq) (*getFieldRes, error) {
ctx = ctxWithUser(ctx)
data, err := svc().GetField(ctx, req.ID)
if err != nil {
logrus.Errorf("[报表引擎] 获取字段失败 id=%d: %v", req.ID, err)
return nil, err
}
return &getFieldRes{Data: data}, nil
}
type saveFieldReq struct {
g.Meta `path:"/field/save" method:"post" tags:"报表引擎" summary:"保存字段"`
model.SaveFieldReq
}
type saveFieldRes struct {
*model.SaveResult
}
func (c *report) SaveField(ctx context.Context, req *saveFieldReq) (*saveFieldRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().SaveField(ctx, &req.SaveFieldReq)
if err != nil {
logrus.Errorf("[报表引擎] 保存字段失败: %v", err)
return nil, err
}
return &saveFieldRes{SaveResult: result}, nil
}
type deleteFieldReq struct {
g.Meta `path:"/field" method:"delete" tags:"报表引擎" summary:"删除字段"`
ID int64 `json:"id" v:"required"`
}
type deleteFieldRes struct {
*model.DeleteResult
}
func (c *report) DeleteField(ctx context.Context, req *deleteFieldReq) (*deleteFieldRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().DeleteField(ctx, req.ID)
if err != nil {
logrus.Errorf("[报表引擎] 删除字段失败 id=%d: %v", req.ID, err)
return nil, err
}
return &deleteFieldRes{DeleteResult: result}, nil
}
// ============================================================
// 抽取配置 CRUD
// ============================================================
type getExtractConfigsReq struct {
g.Meta `path:"/extractConfigs" method:"get" tags:"报表引擎" summary:"抽取配置列表"`
BusinessCode string `json:"businessCode" v:"required"`
ReportCode string `json:"reportCode" v:"required"`
model.PageReq
}
type getExtractConfigsRes struct {
List []model.ExtractConfig `json:"list"`
model.PageRes
}
func (c *report) GetExtractConfigs(ctx context.Context, req *getExtractConfigsReq) (*getExtractConfigsRes, error) {
ctx = ctxWithUser(ctx)
list, total, err := svc().ListExtractConfigs(ctx, req.BusinessCode, req.ReportCode, req.PageNum, req.PageSize)
if err != nil {
logrus.Errorf("[报表引擎] 获取抽取配置列表失败 %s/%s: %v", req.BusinessCode, req.ReportCode, err)
return nil, err
}
totalPages := (total + req.PageSize - 1) / req.PageSize
return &getExtractConfigsRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil
}
type getExtractConfigReq struct {
g.Meta `path:"/extractConfig" method:"get" tags:"报表引擎" summary:"获取抽取配置"`
ID int64 `json:"id" v:"required"`
}
type getExtractConfigRes struct {
Data *model.ExtractConfig `json:"data"`
}
func (c *report) GetExtractConfig(ctx context.Context, req *getExtractConfigReq) (*getExtractConfigRes, error) {
ctx = ctxWithUser(ctx)
data, err := svc().GetExtractConfig(ctx, req.ID)
if err != nil {
logrus.Errorf("[报表引擎] 获取抽取配置失败 id=%d: %v", req.ID, err)
return nil, err
}
return &getExtractConfigRes{Data: data}, nil
}
type saveExtractConfigReq struct {
g.Meta `path:"/extractConfig/save" method:"post" tags:"报表引擎" summary:"保存抽取配置"`
model.SaveExtractConfigReq
}
type saveExtractConfigRes struct {
*model.SaveResult
}
func (c *report) SaveExtractConfig(ctx context.Context, req *saveExtractConfigReq) (*saveExtractConfigRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().SaveExtractConfig(ctx, &req.SaveExtractConfigReq)
if err != nil {
logrus.Errorf("[报表引擎] 保存抽取配置失败: %v", err)
return nil, err
}
return &saveExtractConfigRes{SaveResult: result}, nil
}
type deleteExtractConfigReq struct {
g.Meta `path:"/extractConfig" method:"delete" tags:"报表引擎" summary:"删除抽取配置"`
ID int64 `json:"id" v:"required"`
}
type deleteExtractConfigRes struct {
*model.DeleteResult
}
func (c *report) DeleteExtractConfig(ctx context.Context, req *deleteExtractConfigReq) (*deleteExtractConfigRes, error) {
ctx = ctxWithUser(ctx)
result, err := svc().DeleteExtractConfig(ctx, req.ID)
if err != nil {
logrus.Errorf("[报表引擎] 删除抽取配置失败 id=%d: %v", req.ID, err)
return nil, err
}
return &deleteExtractConfigRes{DeleteResult: result}, nil
}
// ============================================================
// 数据操作
// ============================================================
type extractDataReq struct {
g.Meta `path:"/extract" method:"post" tags:"报表引擎" summary:"执行按天数据抽取"`
model.ExtractDailyDataReq
}
func (c *report) ExtractData(ctx context.Context, req *extractDataReq) (*model.ExtractDailyDataResp, error) {
ctx = ctxWithUser(ctx)
resp, err := svc().ExtractDailyData(ctx, req.BusinessCode, req.ReportCode, req.StatDate, req.Executor)
if err != nil {
logrus.Errorf("[报表引擎] 按天抽取失败 %s/%s %s: %v", req.BusinessCode, req.ReportCode, req.StatDate, err)
return nil, err
}
return resp, nil
}
type backfillReq struct {
g.Meta `path:"/backfill" method:"post" tags:"报表引擎" summary:"批量回填数据"`
model.BackfillReq
}
func (c *report) Backfill(ctx context.Context, req *backfillReq) (*model.BackfillResp, error) {
ctx = ctxWithUser(ctx)
resp, err := svc().Backfill(ctx, &req.BackfillReq)
if err != nil {
logrus.Errorf("[报表引擎] 批量回填失败 %s/%s %s~%s: %v", req.BusinessCode, req.ReportCode, req.StartDate, req.EndDate, err)
return nil, err
}
return resp, nil
}
type autoCreateTableReq struct {
g.Meta `path:"/autoCreateTable" method:"post" tags:"报表引擎" summary:"自动创建统计宽表"`
model.AutoCreateStatTableReq
}
func (c *report) AutoCreateTable(ctx context.Context, req *autoCreateTableReq) (*model.AutoCreateStatTableResp, error) {
ctx = ctxWithUser(ctx)
resp, err := svc().AutoCreateStatTable(ctx, req.BusinessCode, req.ReportCode)
if err != nil {
logrus.Errorf("[报表引擎] 自动建表失败 %s/%s: %v", req.BusinessCode, req.ReportCode, err)
return nil, err
}
return resp, nil
}
type queryReportReq struct {
g.Meta `path:"/query" method:"post" tags:"报表引擎" summary:"用户选择查询"`
model.UserSelectQueryReq
}
func (c *report) QueryReport(ctx context.Context, req *queryReportReq) (*model.UserSelectQueryResp, error) {
ctx = ctxWithUser(ctx)
resp, err := svc().QueryReportByUserSelect(ctx, &req.UserSelectQueryReq)
if err != nil {
logrus.Errorf("[报表引擎] 查询报表失败 %s/%s: %v", req.BusinessCode, req.ReportCode, err)
return nil, err
}
return resp, nil
}
type initTablesReq struct {
g.Meta `path:"/initTables" method:"post" tags:"报表引擎" summary:"初始化系统表"`
}
type initTablesRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
func (c *report) InitTables(ctx context.Context, req *initTablesReq) (*initTablesRes, error) {
ctx = ctxWithUser(ctx)
if err := svc().InitSystemTables(ctx); err != nil {
return nil, err
}
return &initTablesRes{Success: true, Message: "系统表初始化完成"}, nil
}
+203
View File
@@ -0,0 +1,203 @@
package sync
import (
"context"
svc "dataengine/service/sync"
"fmt"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
"github.com/sirupsen/logrus"
)
var PlatformSyncController = new(syncCtrl)
type syncCtrl struct{}
// RefreshTokenReq 刷新 Token 请求
type RefreshTokenReq struct {
g.Meta `path:"/refreshToken" method:"post" tags:"平台同步" summary:"刷新Token" dc:"刷新指定平台的认证Token(如腾讯OAuth2"`
PlatformCode string `json:"platformCode" v:"required" dc:"平台编码"`
}
// RefreshTokenRes 刷新 Token 响应
type RefreshTokenRes struct {
Success bool `json:"success"`
Message string `json:"message"`
}
// RefreshToken 手动触发 Token 刷新
// 由 PPGo_Job 定时调度,定期刷新平台 Token(如腾讯 OAuth2 access_token
func (c *syncCtrl) RefreshToken(ctx context.Context, req *RefreshTokenReq) (*RefreshTokenRes, error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
logrus.Infof("[HTTP] 触发 Token 刷新: platform=%s", req.PlatformCode)
pm := &svc.PlatformManager{}
platform, _, err := pm.GetPlatformWithInterfaces(ctx, req.PlatformCode)
if err != nil {
return nil, fmt.Errorf("读取平台配置失败: %w", err)
}
if err := svc.RefreshTencentToken(ctx, platform); err != nil {
return &RefreshTokenRes{Success: false, Message: err.Error()}, nil
}
return &RefreshTokenRes{Success: true, Message: "Token 刷新成功"}, nil
}
// TriggerSyncReq 触发同步请求
type TriggerSyncReq struct {
g.Meta `path:"/trigger" method:"post" tags:"平台同步" summary:"触发同步" dc:"根据平台编码和接口编码触发数据同步"`
PlatformCode string `json:"platformCode" v:"required" dc:"平台编码"`
InterfaceCode string `json:"interfaceCode" v:"required" dc:"接口编码"`
FullSync bool `json:"fullSync" dc:"是否全量同步,true=全量 false=增量"`
}
// TriggerSyncRes 触发同步响应
type TriggerSyncRes struct {
Success bool `json:"success"`
TableName string `json:"tableName"`
TotalRows int `json:"totalRows"`
InsertedRows int `json:"insertedRows"`
Duration string `json:"duration"`
}
// TriggerSync 触发同步
func (c *syncCtrl) TriggerSync(ctx context.Context, req *TriggerSyncReq) (*TriggerSyncRes, error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
logrus.Infof("触发同步: platform=%s, interface=%s, fullSync=%v", req.PlatformCode, req.InterfaceCode, req.FullSync)
result, err := svc.SyncByConfig(ctx, req.PlatformCode, req.InterfaceCode, req.FullSync)
if err != nil {
return nil, err
}
return &TriggerSyncRes{
Success: true, TableName: result.TableName,
TotalRows: result.TotalRows, InsertedRows: result.InsertedRows,
Duration: result.Duration,
}, nil
}
// QueryPlatformConfigReq 查询平台配置请求
type QueryPlatformConfigReq struct {
g.Meta `path:"/config" method:"get" tags:"平台同步" summary:"查询平台配置" dc:"查看平台和接口配置"`
PlatformCode string `json:"platformCode" v:"required" dc:"平台编码"`
}
// QueryPlatformConfigRes 查询平台配置响应
type QueryPlatformConfigRes struct {
PlatformName string `json:"platformName"`
PlatformCode string `json:"platformCode"`
ApiBaseUrl string `json:"apiBaseUrl"`
AuthType string `json:"authType"`
HasToken bool `json:"hasToken"`
InterfaceCount int `json:"interfaceCount"`
Interfaces []struct {
Code string `json:"code"`
Name string `json:"name"`
Url string `json:"url"`
HasTableDef bool `json:"hasTableDef"`
} `json:"interfaces"`
}
// ============================================================
// 批量操作(供 PPGo_Job 调度)
// ============================================================
// TriggerAllSyncReq 触发全平台同步请求
type TriggerAllSyncReq struct {
g.Meta `path:"/triggerAll" method:"post" tags:"平台同步" summary:"触发全平台同步" dc:"遍历所有ACTIVE平台/接口执行同步"`
FullSync bool `json:"fullSync" dc:"true=强制全量同步,false=自动判断增量/全量"`
}
// TriggerAllSyncRes 触发全平台同步响应
type TriggerAllSyncRes struct {
Success bool `json:"success"`
Message string `json:"message"`
Results []svc.SyncRunItemResult `json:"results,omitempty"`
}
// TriggerAllSync 触发全平台同步(等价于内部自动同步的一次完整执行)
func (c *syncCtrl) TriggerAllSync(ctx context.Context, req *TriggerAllSyncReq) (*TriggerAllSyncRes, error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
logrus.Infof("[HTTP] 触发全平台同步, fullSync=%v", req.FullSync)
results := svc.TriggerAllSync(ctx, req.FullSync)
hasError := false
for _, r := range results {
if !r.Success {
hasError = true
break
}
}
res := &TriggerAllSyncRes{Results: results}
if hasError {
res.Success = false
res.Message = "全平台同步完成,部分失败"
} else {
res.Success = true
res.Message = "全平台同步完成"
}
return res, nil
}
// CompensateReq 触发补偿扫描请求
type CompensateReq struct {
g.Meta `path:"/compensate" method:"post" tags:"平台同步" summary:"触发补偿扫描" dc:"扫描失败任务并重试"`
}
// CompensateRes 触发补偿扫描响应
type CompensateRes struct {
Success bool `json:"success"`
Message string `json:"message"`
Result *svc.CompensateResult `json:"result,omitempty"`
}
// Compensate 触发补偿扫描(等价于内部补偿调度的一次完整执行)
func (c *syncCtrl) Compensate(ctx context.Context, req *CompensateReq) (*CompensateRes, error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
logrus.Info("[HTTP] 触发补偿扫描")
result := svc.TriggerCompensation(ctx)
res := &CompensateRes{Result: result}
if result.TotalFailed == 0 {
res.Success = true
res.Message = "补偿扫描完成,没有待补偿任务"
} else if result.Failed > 0 {
res.Success = false
res.Message = fmt.Sprintf("补偿扫描完成,共 %d 个失败任务,成功补偿 %d 个,失败 %d 个,已达最大重试 %d 个",
result.TotalFailed, result.Succeeded, result.Failed, result.MaxRetryReached)
} else {
res.Success = true
res.Message = fmt.Sprintf("补偿扫描完成,共 %d 个失败任务,成功补偿 %d 个",
result.TotalFailed, result.Succeeded)
}
return res, nil
}
// QueryConfig 查询配置
func (c *syncCtrl) QueryConfig(ctx context.Context, req *QueryPlatformConfigReq) (*QueryPlatformConfigRes, error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
pm := &svc.PlatformManager{}
platform, interfaces, err := pm.GetPlatformWithInterfaces(ctx, req.PlatformCode)
if err != nil {
return nil, err
}
res := &QueryPlatformConfigRes{
PlatformName: platform.PlatformName, PlatformCode: platform.PlatformCode,
ApiBaseUrl: platform.ApiBaseUrl, AuthType: platform.AuthType,
HasToken: platform.AccessToken != "", InterfaceCount: len(interfaces),
}
for _, iface := range interfaces {
res.Interfaces = append(res.Interfaces, struct {
Code string `json:"code"`
Name string `json:"name"`
Url string `json:"url"`
HasTableDef bool `json:"hasTableDef"`
}{
Code: iface.Code, Name: iface.Name, Url: iface.Url,
HasTableDef: iface.TableDefinition != nil && len(iface.TableDefinition) > 0,
})
}
return res, nil
}
@@ -1,86 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var CidAccountReportDetail = new(cidAccountReportDetailDao)
type cidAccountReportDetailDao struct{}
// Insert 插入广告数据报表详情
func (d *cidAccountReportDetailDao) Insert(ctx context.Context, req *dto.CidAccountReportDetailItem) (id int64, err error) {
var entityData *entity.CidAccountReportDetail
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.CidAccountReportDetailTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入广告数据报表详情
func (d *cidAccountReportDetailDao) BatchInsert(ctx context.Context, reqs []*dto.CidAccountReportDetailItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
// 分批处理,每批100条
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.CidAccountReportDetail, len(batch))
for j, req := range batch {
var entityData entity.CidAccountReportDetail
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
// 执行批量插入
_, err = gfdb.DB(ctx).Model(ctx, consts.CidAccountReportDetailTable).Data(entityList).Insert()
if err != nil {
// 批量插入失败,尝试逐条插入
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
@@ -1,86 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var CidAccountReportSum = new(CidAccountReportSumDao)
type CidAccountReportSumDao struct{}
// Insert 插入广告数据报表详情
func (d *CidAccountReportSumDao) Insert(ctx context.Context, req *dto.CidAccountReportSumItem) (id int64, err error) {
var entityData *entity.CidAccountReportSum
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.CidAccountReportSumTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入广告数据报表详情
func (d *CidAccountReportSumDao) BatchInsert(ctx context.Context, reqs []*dto.CidAccountReportSumItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
// 分批处理,每批100条
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.CidAccountReportSum, len(batch))
for j, req := range batch {
var entityData entity.CidAccountReportSum
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
// 执行批量插入
_, err = gfdb.DB(ctx).Model(ctx, consts.CidAccountReportSumTable).Data(entityList).Insert()
if err != nil {
// 批量插入失败,尝试逐条插入
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
@@ -1,81 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var CreativeReportDetail = new(CreativeReportDetailDao)
type CreativeReportDetailDao struct{}
func (d *CreativeReportDetailDao) Insert(ctx context.Context, req *dto.CreativeReportDetailItem) (id int64, err error) {
var entityData *entity.CreativeReportDetail
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.CreativeReportDetailTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
func (d *CreativeReportDetailDao) BatchInsert(ctx context.Context, reqs []*dto.CreativeReportDetailItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.CreativeReportDetail, len(batch))
for j, req := range batch {
var entityData entity.CreativeReportDetail
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
_, err = gfdb.DB(ctx).Model(ctx, consts.CreativeReportDetailTable).Data(entityList).Insert()
if err != nil {
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
-86
View File
@@ -1,86 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var CreativeReportSum = new(CreativeReportSumDao)
type CreativeReportSumDao struct{}
// Insert 插入广告效果指标表
func (d *CreativeReportSumDao) Insert(ctx context.Context, req *dto.CreativeReportSumItem) (id int64, err error) {
var entityData *entity.CreativeReportSum
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.CreativeReportSumTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入广告效果指标表
func (d *CreativeReportSumDao) BatchInsert(ctx context.Context, reqs []*dto.CreativeReportSumItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
// 分批处理,每批 100 条
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.CreativeReportSum, len(batch))
for j, req := range batch {
var entityData entity.CreativeReportSum
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
// 执行批量插入
_, err = gfdb.DB(ctx).Model(ctx, consts.CreativeReportSumTable).Data(entityList).Insert()
if err != nil {
// 批量插入失败,尝试逐条插入
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
-128
View File
@@ -1,128 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var MaterialReport = new(materialReportDao)
type materialReportDao struct{}
// Insert 插入素材报表数据
func (d *materialReportDao) Insert(ctx context.Context, req *dto.MaterialReportItem) (id int64, err error) {
var entityData *entity.MaterialReport
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.MaterialReportTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入素材报表数据
func (d *materialReportDao) BatchInsert(ctx context.Context, reqs []*dto.MaterialReportItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.MaterialReport, len(batch))
for j, req := range batch {
var entityData entity.MaterialReport
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
_, err = gfdb.DB(ctx).Model(ctx, consts.MaterialReportTable).Data(entityList).Insert()
if err != nil {
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
// List 查询素材报表数据列表
func (d *materialReportDao) List(ctx context.Context, req *dto.ListMaterialReportReq) ([]*entity.MaterialReport, int, error) {
model := gfdb.DB(ctx).Model(ctx, consts.MaterialReportTable).Model
if req.ReportDateStr != "" {
model = model.Where("report_date_str", req.ReportDateStr)
}
if req.PhotoId != "" {
model = model.Where("photo_id", req.PhotoId)
}
if req.CampaignId != nil {
model = model.Where("campaign_id", req.CampaignId)
}
if req.UnitId != nil {
model = model.Where("unit_id", req.UnitId)
}
if req.CreativeId != nil {
model = model.Where("creative_id", req.CreativeId)
}
if req.Keyword != "" {
model = model.Where("(photo_name LIKE ? OR campaign_name LIKE ? OR unit_name LIKE ? OR creative_name LIKE ?)",
"%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%", "%"+req.Keyword+"%")
}
model = model.OrderDesc("created_at")
total, err := model.Count()
if err != nil {
return nil, 0, err
}
var list []*entity.MaterialReport
if req.Page != nil {
err = model.Page(int(req.Page.PageNum), int(req.Page.PageSize)).Scan(&list)
} else {
err = model.Scan(&list)
}
if err != nil {
return nil, 0, err
}
return list, total, nil
}
-124
View File
@@ -1,124 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var PopulationReport = new(populationReportDao)
type populationReportDao struct{}
// Insert 插入人群报表数据
func (d *populationReportDao) Insert(ctx context.Context, req *dto.PopulationReportItem) (id int64, err error) {
var entityData *entity.PopulationReport
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.PopulationReportTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入人群报表数据
func (d *populationReportDao) BatchInsert(ctx context.Context, reqs []*dto.PopulationReportItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
// 分批处理,每批 100 条
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.PopulationReport, len(batch))
for j, req := range batch {
var entityData entity.PopulationReport
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
// 执行批量插入
_, err = gfdb.DB(ctx).Model(ctx, consts.PopulationReportTable).Data(entityList).Insert()
if err != nil {
// 批量插入失败,尝试逐条插入
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
// List 查询人群报表数据列表
func (d *populationReportDao) List(ctx context.Context, req *dto.ListPopulationReportReq) ([]*entity.PopulationReport, int, error) {
model := gfdb.DB(ctx).Model(ctx, consts.PopulationReportTable).Model
// 构建查询条件
if req.ReportDateStr != "" {
model = model.Where("report_date_str", req.ReportDateStr)
}
if req.PhotoId != "" {
model = model.Where("photo_id", req.PhotoId)
}
if req.Keyword != "" {
model = model.Where("photo_name LIKE ?", "%"+req.Keyword+"%")
}
// 设置排序
model = model.OrderDesc("created_at")
// 分页查询并获取总数
total, err := model.Count()
if err != nil {
return nil, 0, err
}
var list []*entity.PopulationReport
if req.Page != nil {
err = model.Page(int(req.Page.PageNum), int(req.Page.PageSize)).Scan(&list)
} else {
err = model.Scan(&list)
}
if err != nil {
return nil, 0, err
}
return list, total, nil
}
@@ -1,86 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var StorewideReportDetail = new(StorewideReportDetailDao)
type StorewideReportDetailDao struct{}
// Insert 插入广告效果指标表
func (d *StorewideReportDetailDao) Insert(ctx context.Context, req *dto.StorewideReportDetailItem) (id int64, err error) {
var entityData *entity.StorewideReportDetail
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.StorewideReportDetailTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入广告效果指标表
func (d *StorewideReportDetailDao) BatchInsert(ctx context.Context, reqs []*dto.StorewideReportDetailItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
// 分批处理,每批 100 条
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.StorewideReportDetail, len(batch))
for j, req := range batch {
var entityData entity.StorewideReportDetail
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
// 执行批量插入
_, err = gfdb.DB(ctx).Model(ctx, consts.StorewideReportDetailTable).Data(entityList).Insert()
if err != nil {
// 批量插入失败,尝试逐条插入
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
-86
View File
@@ -1,86 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var StorewideReportSum = new(StorewideReportSumDao)
type StorewideReportSumDao struct{}
// Insert 插入广告效果指标表
func (d *StorewideReportSumDao) Insert(ctx context.Context, req *dto.StorewideReportSumItem) (id int64, err error) {
var entityData *entity.StorewideReportSum
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.StorewideReportSumTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入广告效果指标表
func (d *StorewideReportSumDao) BatchInsert(ctx context.Context, reqs []*dto.StorewideReportSumItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
// 分批处理,每批 100 条
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.StorewideReportSum, len(batch))
for j, req := range batch {
var entityData entity.StorewideReportSum
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
// 执行批量插入
_, err = gfdb.DB(ctx).Model(ctx, consts.StorewideReportSumTable).Data(entityList).Insert()
if err != nil {
// 批量插入失败,尝试逐条插入
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
+172
View File
@@ -0,0 +1,172 @@
package copydata
import (
"context"
consts "dataengine/consts/public"
dto "dataengine/model/dto/copydata"
entity "dataengine/model/entity/copydata"
"dataengine/utils"
"time"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/util/gconv"
"github.com/sirupsen/logrus"
)
var SyncTaskLog = new(SyncTaskLogDao)
type SyncTaskLogDao struct{}
// Create 创建任务日志(如果task_id已存在则返回现有ID)
func (d *SyncTaskLogDao) Create(ctx context.Context, req *dto.CreateSyncTaskLogReq) (int64, error) {
existingTask, err := d.GetByTaskID(ctx, req.TaskID, req.TaskType)
if err == nil && existingTask != nil {
logrus.Debugf("任务日志已存在,task_id=%s, task_type=%s, id=%d", req.TaskID, req.TaskType, existingTask.Id)
return existingTask.Id, nil
}
data := map[string]interface{}{
"task_id": req.TaskID,
"task_type": req.TaskType,
"platform_code": req.PlatformCode,
"interface_code": req.InterfaceCode,
"advertiser_id": req.AdvertiserID,
"start_time": req.StartTime,
"end_time": req.EndTime,
"status": req.Status,
"max_retry": req.MaxRetry,
"page_info": req.PageInfo,
"request_params": req.RequestParams,
"retry_count": 0,
"duration_ms": 0,
"tenant_id": utils.GetCurrentTenantId(ctx),
}
r, err := gfdb.DB(ctx).Model(ctx, consts.SyncTaskLogTable).Data(data).Insert()
if err != nil {
return 0, err
}
return r.LastInsertId()
}
// Update 更新任务日志
func (d *SyncTaskLogDao) Update(ctx context.Context, req *dto.UpdateSyncTaskLogReq) error {
data := make(gdb.Map)
if req.Status != "" {
data["status"] = req.Status
}
if req.RetryCount != nil {
data["retry_count"] = *req.RetryCount
}
if req.ErrorMessage != "" {
data["error_message"] = req.ErrorMessage
}
if req.ErrorCode != "" {
data["error_code"] = req.ErrorCode
}
if req.ResultSummary != nil {
data["result_summary"] = req.ResultSummary
}
if req.NextRetryTime != nil {
data["next_retry_time"] = req.NextRetryTime
}
if req.CompletedAt != nil {
data["completed_at"] = req.CompletedAt
}
if req.DurationMs != nil {
data["duration_ms"] = *req.DurationMs
}
data["updated_at"] = time.Now()
_, err := gfdb.DB(ctx).Model(ctx, consts.SyncTaskLogTable).
Data(data).
Where("id", req.ID).
Update()
return err
}
// QueryFailedTasks 查询需要补偿的失败任务
func (d *SyncTaskLogDao) QueryFailedTasks(ctx context.Context, req *dto.QueryFailedTasksReq) ([]*dto.SyncTaskLogItem, error) {
model := gfdb.DB(ctx).Model(ctx, consts.SyncTaskLogTable).Model
// 状态过滤
if len(req.Status) > 0 {
model = model.WhereIn("status", req.Status)
}
// 任务类型过滤
if req.TaskType != "" {
model = model.Where("task_type", req.TaskType)
}
// 只查询到达重试时间的任务(或从未设置过重试时间)
model = model.Where(
"(next_retry_time <= ? OR next_retry_time IS NULL)",
time.Now(),
)
// 限制数量
limit := req.Limit
if limit <= 0 {
limit = 100
}
model = model.Limit(limit)
var results []*entity.SyncTaskLog
if err := model.Scan(&results); err != nil {
return nil, err
}
items := make([]*dto.SyncTaskLogItem, len(results))
for i, r := range results {
item := &dto.SyncTaskLogItem{}
gconv.Struct(r, item)
items[i] = item
}
return items, nil
}
// GetByTaskID 根据任务ID获取日志
func (d *SyncTaskLogDao) GetByTaskID(ctx context.Context, taskID, taskType string) (*entity.SyncTaskLog, error) {
var result *entity.SyncTaskLog
err := gfdb.DB(ctx).Model(ctx, consts.SyncTaskLogTable).
Where("task_id", taskID).
Where("task_type", taskType).
Scan(&result)
if err != nil {
return nil, err
}
return result, nil
}
// QueryAllPageTasksByParentID 根据主任务ID查询所有分页任务
func (d *SyncTaskLogDao) QueryAllPageTasksByParentID(ctx context.Context, parentTaskID string, limit int) ([]*dto.SyncTaskLogItem, error) {
if limit <= 0 {
limit = 1000
}
model := gfdb.DB(ctx).Model(ctx, consts.SyncTaskLogTable).Model
model = model.Where("task_type", "account_report_page")
model = model.WhereLike("task_id", parentTaskID+"_page_%")
model = model.Limit(limit)
var results []*entity.SyncTaskLog
if err := model.Scan(&results); err != nil {
return nil, err
}
items := make([]*dto.SyncTaskLogItem, len(results))
for i, r := range results {
item := &dto.SyncTaskLogItem{}
gconv.Struct(r, item)
items[i] = item
}
return items, nil
}
-124
View File
@@ -1,124 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var TaskReport = new(taskReportDao)
type taskReportDao struct{}
// Insert 插入调控任务数据
func (d *taskReportDao) Insert(ctx context.Context, req *dto.TaskReportItem) (id int64, err error) {
var entityData *entity.TaskReport
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.TaskReportTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入调控任务数据
func (d *taskReportDao) BatchInsert(ctx context.Context, reqs []*dto.TaskReportItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
// 分批处理,每批 100 条
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.TaskReport, len(batch))
for j, req := range batch {
var entityData entity.TaskReport
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
// 执行批量插入
_, err = gfdb.DB(ctx).Model(ctx, consts.TaskReportTable).Data(entityList).Insert()
if err != nil {
// 批量插入失败,尝试逐条插入
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
// List 查询调控任务数据列表
func (d *taskReportDao) List(ctx context.Context, req *dto.ListTaskReportReq) ([]*entity.TaskReport, int, error) {
model := gfdb.DB(ctx).Model(ctx, consts.TaskReportTable).Model
// 构建查询条件
if req.ReportDateStr != "" {
model = model.Where("report_date_str", req.ReportDateStr)
}
if req.PhotoId != "" {
model = model.Where("photo_id", req.PhotoId)
}
if req.Keyword != "" {
model = model.Where("photo_name LIKE ?", "%"+req.Keyword+"%")
}
// 设置排序
model = model.OrderDesc("created_at")
// 分页查询并获取总数
total, err := model.Count()
if err != nil {
return nil, 0, err
}
var list []*entity.TaskReport
if req.Page != nil {
err = model.Page(int(req.Page.PageNum), int(req.Page.PageSize)).Scan(&list)
} else {
err = model.Scan(&list)
}
if err != nil {
return nil, 0, err
}
return list, total, nil
}
-83
View File
@@ -1,83 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var UnitReportDetail = new(UnitReportDetailDao)
type UnitReportDetailDao struct{}
// Insert 插入广告效果指标详情
func (d *UnitReportDetailDao) Insert(ctx context.Context, req *dto.UnitReportDetailItem) (id int64, err error) {
var entityData *entity.UnitReportDetail
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.UnitReportDetailTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入广告效果指标详情
func (d *UnitReportDetailDao) BatchInsert(ctx context.Context, reqs []*dto.UnitReportDetailItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.UnitReportDetail, len(batch))
for j, req := range batch {
var entityData entity.UnitReportDetail
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
_, err = gfdb.DB(ctx).Model(ctx, consts.UnitReportDetailTable).Data(entityList).Insert()
if err != nil {
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
-83
View File
@@ -1,83 +0,0 @@
package copydata
import (
consts "cid/consts/public"
dto "cid/model/dto/copydata"
entity "cid/model/entity/copydata"
"context"
"errors"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/util/gconv"
)
var UnitReportSum = new(UnitReportSumDao)
type UnitReportSumDao struct{}
// Insert 插入广告效果指标
func (d *UnitReportSumDao) Insert(ctx context.Context, req *dto.UnitReportSumItem) (id int64, err error) {
var entityData *entity.UnitReportSum
if err = gconv.Struct(req, &entityData); err != nil {
return
}
r, err := gfdb.DB(ctx).Model(ctx, consts.UnitReportSumTable).Data(&entityData).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// BatchInsert 批量插入广告效果指标
func (d *UnitReportSumDao) BatchInsert(ctx context.Context, reqs []*dto.UnitReportSumItem) (successCount int64, failCount int64, failedIndexes []int64, err error) {
if len(reqs) == 0 {
return 0, 0, nil, errors.New("批量插入数据不能为空")
}
batchSize := 100
successCount = 0
failCount = 0
failedIndexes = make([]int64, 0)
for i := 0; i < len(reqs); i += batchSize {
end := i + batchSize
if end > len(reqs) {
end = len(reqs)
}
batch := reqs[i:end]
entityList := make([]*entity.UnitReportSum, len(batch))
for j, req := range batch {
var entityData entity.UnitReportSum
if err = gconv.Struct(req, &entityData); err != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+j))
continue
}
entityList[j] = &entityData
}
if len(entityList) == 0 {
continue
}
_, err = gfdb.DB(ctx).Model(ctx, consts.UnitReportSumTable).Data(entityList).Insert()
if err != nil {
for k := range batch {
_, singleErr := d.Insert(ctx, batch[k])
if singleErr != nil {
failCount++
failedIndexes = append(failedIndexes, int64(i+k))
} else {
successCount++
}
}
} else {
successCount += int64(len(entityList))
}
}
return successCount, failCount, failedIndexes, nil
}
+59 -43
View File
@@ -1,17 +1,15 @@
package dict
import (
consts1 "cid/consts/api-feature"
consts "cid/consts/public"
dto "cid/model/dto/dict"
entity "cid/model/entity/dict"
"context"
"strconv"
"time"
consts "dataengine/consts/public"
dto "dataengine/model/dto/dict"
entity "dataengine/model/entity/dict"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -20,16 +18,18 @@ var DatasourcePlatform = new(datasourcePlatformDao)
type datasourcePlatformDao struct{}
// Insert 插入数据源平台
func (d *datasourcePlatformDao) Insert(ctx context.Context, req *dto.CreateDatasourcePlatformReq) (ID int64, err error) {
func (d *datasourcePlatformDao) Insert(ctx context.Context, req *dto.CreateDatasourcePlatformReq, createdBy string, tenantId uint64) (ID int64, err error) {
var res *entity.DatasourcePlatform
if err = gconv.Struct(req, &res); err != nil {
return
}
// 设置创建时间
// 设置创建时间和更新时间
now := time.Now()
res.CreatedAt = &now
res.UpdatedAt = &now
// 设置审计字段(服务端生成,不依赖前端入参)
now := gtime.Now()
res.TenantId = tenantId
res.Creator = createdBy
res.CreatedAt = now
res.Updater = createdBy
res.UpdatedAt = now
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).Data(&res).Insert()
if err != nil {
@@ -39,15 +39,17 @@ func (d *datasourcePlatformDao) Insert(ctx context.Context, req *dto.CreateDatas
}
// Update 更新数据源平台
func (d *datasourcePlatformDao) Update(ctx context.Context, req *dto.UpdateDatasourcePlatformReq) (rows int64, err error) {
// 设置更新时间
func (d *datasourcePlatformDao) Update(ctx context.Context, req *dto.UpdateDatasourcePlatformReq, updatedBy string, tenantId uint64) (rows int64, err error) {
// 设置更新人和更新时间(服务端生成,不依赖前端入参)
data := gconv.Map(req)
data[entity.DatasourcePlatformCols.UpdatedAt] = time.Now()
data[entity.DatasourcePlatformCols.Updater] = updatedBy
data[entity.DatasourcePlatformCols.UpdatedAt] = gtime.Now()
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Data(data).
OmitEmpty().
Where(entity.DatasourcePlatformCols.ID, req.Id).
Where(entity.DatasourcePlatformCols.Id, req.Id).
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
Update()
if err != nil {
return
@@ -56,9 +58,10 @@ func (d *datasourcePlatformDao) Update(ctx context.Context, req *dto.UpdateDatas
}
// Delete 删除数据源平台
func (d *datasourcePlatformDao) Delete(ctx context.Context, req *dto.DeleteDatasourcePlatformReq) (rows int64, err error) {
func (d *datasourcePlatformDao) Delete(ctx context.Context, req *dto.DeleteDatasourcePlatformReq, tenantId uint64) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Where(entity.DatasourcePlatformCols.ID, req.Id).
Where(entity.DatasourcePlatformCols.Id, req.Id).
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
Delete()
if err != nil {
return
@@ -67,9 +70,10 @@ func (d *datasourcePlatformDao) Delete(ctx context.Context, req *dto.DeleteDatas
}
// GetOne 获取单个数据源平台
func (d *datasourcePlatformDao) GetOne(ctx context.Context, req *dto.GetDatasourcePlatformReq) (res *entity.DatasourcePlatform, err error) {
func (d *datasourcePlatformDao) GetOne(ctx context.Context, req *dto.GetDatasourcePlatformReq, tenantId uint64) (res *entity.DatasourcePlatform, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Where(entity.DatasourcePlatformCols.ID, req.Id).
Where(entity.DatasourcePlatformCols.Id, req.Id).
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
One()
if err != nil {
return
@@ -79,9 +83,10 @@ func (d *datasourcePlatformDao) GetOne(ctx context.Context, req *dto.GetDatasour
}
// GetByPlatformCode 根据平台编码获取数据源平台
func (d *datasourcePlatformDao) GetByPlatformCode(ctx context.Context, platformCode string) (res *entity.DatasourcePlatform, err error) {
func (d *datasourcePlatformDao) GetByPlatformCode(ctx context.Context, platformCode string, tenantId uint64) (res *entity.DatasourcePlatform, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Where(entity.DatasourcePlatformCols.PlatformCode, platformCode).
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
One()
if err != nil {
return
@@ -91,13 +96,13 @@ func (d *datasourcePlatformDao) GetByPlatformCode(ctx context.Context, platformC
}
// Count 获取数据源平台数量
func (d *datasourcePlatformDao) Count(ctx context.Context, req *dto.ListDatasourcePlatformReq) (count int, err error) {
return d.buildListFilter(ctx, req).Count()
func (d *datasourcePlatformDao) Count(ctx context.Context, req *dto.ListDatasourcePlatformReq, tenantId uint64) (count int, err error) {
return d.buildListFilter(ctx, req, tenantId).Count()
}
// List 获取数据源平台列表
func (d *datasourcePlatformDao) List(ctx context.Context, req *dto.ListDatasourcePlatformReq) (res []entity.DatasourcePlatform, total int, err error) {
model := d.buildListFilter(ctx, req)
func (d *datasourcePlatformDao) List(ctx context.Context, req *dto.ListDatasourcePlatformReq, tenantId uint64) (res []entity.DatasourcePlatform, total int, err error) {
model := d.buildListFilter(ctx, req, tenantId)
model.OrderDesc(entity.DatasourcePlatformCols.CreatedAt)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
@@ -111,9 +116,12 @@ func (d *datasourcePlatformDao) List(ctx context.Context, req *dto.ListDatasourc
}
// buildListFilter 构建列表查询的过滤条件
func (d *datasourcePlatformDao) buildListFilter(ctx context.Context, req *dto.ListDatasourcePlatformReq) *gdb.Model {
func (d *datasourcePlatformDao) buildListFilter(ctx context.Context, req *dto.ListDatasourcePlatformReq, tenantId uint64) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).Model
// 租户隔离
model.Where(entity.DatasourcePlatformCols.TenantId, tenantId)
// 关键字搜索(平台名称或编码)
if !g.IsEmpty(req.Keyword) {
model.WhereLike(entity.DatasourcePlatformCols.PlatformName, "%"+req.Keyword+"%")
@@ -140,14 +148,15 @@ func (d *datasourcePlatformDao) buildListFilter(ctx context.Context, req *dto.Li
}
// UpdateStatus 更新数据源平台状态
func (d *datasourcePlatformDao) UpdateStatus(ctx context.Context, ID int64, status string, updatedBy string) (rows int64, err error) {
func (d *datasourcePlatformDao) UpdateStatus(ctx context.Context, ID int64, status string, updatedBy string, tenantId uint64) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Data(map[string]interface{}{
entity.DatasourcePlatformCols.Status: status,
entity.DatasourcePlatformCols.UpdatedBy: updatedBy,
entity.DatasourcePlatformCols.UpdatedAt: time.Now(),
entity.DatasourcePlatformCols.Updater: updatedBy,
entity.DatasourcePlatformCols.UpdatedAt: gtime.Now(),
}).
Where(entity.DatasourcePlatformCols.ID, ID).
Where(entity.DatasourcePlatformCols.Id, ID).
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
Update()
if err != nil {
return
@@ -156,12 +165,13 @@ func (d *datasourcePlatformDao) UpdateStatus(ctx context.Context, ID int64, stat
}
// ExistsByPlatformCode 检查平台编码是否存在
func (d *datasourcePlatformDao) ExistsByPlatformCode(ctx context.Context, platformCode string, excludeId ...int64) (exists bool, err error) {
func (d *datasourcePlatformDao) ExistsByPlatformCode(ctx context.Context, platformCode string, tenantId uint64, excludeId ...int64) (exists bool, err error) {
model := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Where(entity.DatasourcePlatformCols.PlatformCode, platformCode)
Where(entity.DatasourcePlatformCols.PlatformCode, platformCode).
Where(entity.DatasourcePlatformCols.TenantId, tenantId)
if len(excludeId) > 0 && excludeId[0] > 0 {
model.WhereNot(entity.DatasourcePlatformCols.ID, excludeId[0])
model.WhereNot(entity.DatasourcePlatformCols.Id, excludeId[0])
}
count, err := model.Count()
@@ -172,9 +182,10 @@ func (d *datasourcePlatformDao) ExistsByPlatformCode(ctx context.Context, platfo
}
// ListActivePlatforms 获取所有启用的平台
func (d *datasourcePlatformDao) ListActivePlatforms(ctx context.Context) (res []entity.DatasourcePlatform, err error) {
func (d *datasourcePlatformDao) ListActivePlatforms(ctx context.Context, tenantId uint64) (res []entity.DatasourcePlatform, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Where(entity.DatasourcePlatformCols.Status, consts1.PlatformStatusActive).
Where(entity.DatasourcePlatformCols.Status, "ACTIVE").
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
OrderAsc(entity.DatasourcePlatformCols.PlatformName).
All()
if err != nil {
@@ -185,11 +196,13 @@ func (d *datasourcePlatformDao) ListActivePlatforms(ctx context.Context) (res []
}
// GetPlatformStatistics 获取平台统计信息
func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context) (stats map[string]int64, err error) {
func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context, tenantId uint64) (stats map[string]int64, err error) {
stats = make(map[string]int64)
// 总平台数
total, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).Count()
total, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
Count()
if err != nil {
return nil, err
}
@@ -197,7 +210,8 @@ func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context) (stat
// 启用平台数
active, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Where(entity.DatasourcePlatformCols.Status, consts1.MappingStatusActive).
Where(entity.DatasourcePlatformCols.Status, "ACTIVE").
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
Count()
if err != nil {
return nil, err
@@ -212,6 +226,7 @@ func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context) (stat
for _, authType := range authTypes {
count, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Where(entity.DatasourcePlatformCols.AuthType, authType).
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
Count()
if err != nil {
return nil, err
@@ -223,14 +238,15 @@ func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context) (stat
}
// BatchUpdateStatus 批量更新平台状态
func (d *datasourcePlatformDao) BatchUpdateStatus(ctx context.Context, ids []int64, status string, updatedBy string) (rows int64, err error) {
func (d *datasourcePlatformDao) BatchUpdateStatus(ctx context.Context, ids []int64, status string, updatedBy string, tenantId uint64) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
Data(map[string]interface{}{
entity.DatasourcePlatformCols.Status: status,
entity.DatasourcePlatformCols.UpdatedBy: updatedBy,
entity.DatasourcePlatformCols.UpdatedAt: strconv.FormatInt(time.Now().Unix(), 10),
entity.DatasourcePlatformCols.Updater: updatedBy,
entity.DatasourcePlatformCols.UpdatedAt: gtime.Now(),
}).
WhereIn(entity.DatasourcePlatformCols.ID, ids).
WhereIn(entity.DatasourcePlatformCols.Id, ids).
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
Update()
if err != nil {
return
-274
View File
@@ -1,274 +0,0 @@
package dict
import (
consts "cid/consts/public"
dto "cid/model/dto/dict"
entity "cid/model/entity/dict"
"context"
"time"
"gitea.com/red-future/common/beans"
"gitea.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
var FieldMappingConfig = new(fieldMappingConfigDao)
type fieldMappingConfigDao struct{}
// Insert 插入字段映射配置
func (d *fieldMappingConfigDao) Insert(ctx context.Context, req *dto.CreateFieldMappingConfigReq) (id int64, err error) {
var config entity.FieldMappingConfig
if err = gconv.Struct(req, &config); err != nil {
return
}
now := time.Now()
config.CreatedTime = now
config.UpdatedTime = now
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).Data(&config).Insert()
if err != nil {
return
}
return r.LastInsertId()
}
// Update 更新字段映射配置
func (d *fieldMappingConfigDao) Update(ctx context.Context, req *dto.UpdateFieldMappingConfigReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).Data(&req).OmitEmpty().Where(entity.FieldMappingConfigCol.Id, req.Id).Update()
if err != nil {
return
}
return r.RowsAffected()
}
// Delete 删除字段映射配置
func (d *fieldMappingConfigDao) Delete(ctx context.Context, req *dto.DeleteFieldMappingConfigReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Where(entity.FieldMappingConfigCol.Id, req.Id).
Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetOne 获取单个字段映射配置
func (d *fieldMappingConfigDao) GetOne(ctx context.Context, req *dto.GetFieldMappingConfigReq) (res *entity.FieldMappingConfig, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Where(entity.FieldMappingConfigCol.Id, req.Id).
One()
if err != nil {
return
}
err = r.Struct(&res)
return
}
// Count 获取字段映射配置数量
func (d *fieldMappingConfigDao) Count(ctx context.Context, req *dto.ListFieldMappingConfigReq) (count int, err error) {
return d.buildListFilter(ctx, req).Count()
}
// List 获取字段映射配置列表
func (d *fieldMappingConfigDao) List(ctx context.Context, req *dto.ListFieldMappingConfigReq) (res []entity.FieldMappingConfig, total int, err error) {
model := d.buildListFilter(ctx, req)
model.OrderDesc(entity.FieldMappingConfigCol.CreatedTime)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
}
r, total, err := model.AllAndCount(false)
if err != nil {
return
}
err = r.Structs(&res)
return
}
// buildListFilter 构建列表查询的过滤条件
func (d *fieldMappingConfigDao) buildListFilter(ctx context.Context, req *dto.ListFieldMappingConfigReq) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).Model
if !g.IsEmpty(req.Keyword) {
model.WhereLike(entity.FieldMappingConfigCol.ConfigName, "%"+req.Keyword+"%").
WhereOrLike(entity.FieldMappingConfigCol.SourceFieldDesc, "%"+req.Keyword+"%").
WhereOrLike(entity.FieldMappingConfigCol.TargetFieldDesc, "%"+req.Keyword+"%")
}
if !g.IsEmpty(req.ConfigName) {
model.Where(entity.FieldMappingConfigCol.ConfigName, req.ConfigName)
}
if !g.IsEmpty(req.VendorName) {
model.Where(entity.FieldMappingConfigCol.VendorName, req.VendorName)
}
if !g.IsEmpty(req.ApiName) {
model.Where(entity.FieldMappingConfigCol.ApiName, req.ApiName)
}
if !g.IsEmpty(req.ApiVersion) {
model.Where(entity.FieldMappingConfigCol.ApiVersion, req.ApiVersion)
}
if !g.IsEmpty(req.SourceField) {
model.Where(entity.FieldMappingConfigCol.SourceField, req.SourceField)
}
if !g.IsEmpty(req.TargetField) {
model.Where(entity.FieldMappingConfigCol.TargetField, req.TargetField)
}
if !g.IsEmpty(req.TransformType) {
model.Where(entity.FieldMappingConfigCol.TransformType, req.TransformType)
}
if !g.IsEmpty(req.BusinessDomain) {
model.Where(entity.FieldMappingConfigCol.BusinessDomain, req.BusinessDomain)
}
if !g.IsEmpty(req.FieldGroup) {
model.Where(entity.FieldMappingConfigCol.FieldGroup, req.FieldGroup)
}
if req.IsActive != nil {
model.Where(entity.FieldMappingConfigCol.IsActive, *req.IsActive)
}
model.OmitEmptyWhere()
return model
}
// UpdateStatus 更新配置状态
func (d *fieldMappingConfigDao) UpdateStatus(ctx context.Context, id int64, isActive bool) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Data(g.Map{
"is_active": isActive,
"updated_time": time.Now(),
}).
Where(entity.FieldMappingConfigCol.Id, id).
Update()
if err != nil {
return
}
return r.RowsAffected()
}
// GetByIds 根据ID列表获取配置列表
func (d *fieldMappingConfigDao) GetByIds(ctx context.Context, ids []int64) (res []entity.FieldMappingConfig, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
WhereIn(entity.FieldMappingConfigCol.Id, ids).
All()
if err != nil {
return
}
err = r.Structs(&res)
return
}
// GetByVendorAndApi 根据厂商和接口获取字段映射配置
func (d *fieldMappingConfigDao) GetByVendorAndApi(ctx context.Context, vendorName, apiName, apiVersion string, isActive *bool) (res []*entity.FieldMappingConfig, err error) {
model := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Where(entity.FieldMappingConfigCol.VendorName, vendorName).
Where(entity.FieldMappingConfigCol.ApiName, apiName)
if !g.IsEmpty(apiVersion) {
model.Where(entity.FieldMappingConfigCol.ApiVersion, apiVersion)
}
if isActive != nil {
model.Where(entity.FieldMappingConfigCol.IsActive, *isActive)
}
model.OrderDesc(entity.FieldMappingConfigCol.Priority).
OrderAsc(entity.FieldMappingConfigCol.Id)
r, err := model.All()
if err != nil {
return
}
err = r.Structs(&res)
return
}
// CheckDuplicate 检查重复配置
func (d *fieldMappingConfigDao) CheckDuplicate(ctx context.Context, vendorName, apiName, sourceField, targetField string, excludeId int64) (exists bool, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
model := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Where(entity.FieldMappingConfigCol.VendorName, vendorName).
Where(entity.FieldMappingConfigCol.ApiName, apiName).
Where(entity.FieldMappingConfigCol.SourceField, sourceField).
Where(entity.FieldMappingConfigCol.TargetField, targetField)
if excludeId > 0 {
model.WhereNot(entity.FieldMappingConfigCol.Id, excludeId)
}
count, err := model.Count()
if err != nil {
return
}
exists = count > 0
return
}
// GetActiveConfigsByBusinessDomain 根据业务域获取启用的配置
func (d *fieldMappingConfigDao) GetActiveConfigsByBusinessDomain(ctx context.Context, businessDomain string) (res []entity.FieldMappingConfig, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Where(entity.FieldMappingConfigCol.BusinessDomain, businessDomain).
Where(entity.FieldMappingConfigCol.IsActive, true).
OrderDesc(entity.FieldMappingConfigCol.Priority).
OrderAsc(entity.FieldMappingConfigCol.Id).
All()
if err != nil {
return
}
err = r.Structs(&res)
return
}
// GetFieldGroupsByVendorApi 获取指定厂商接口的字段分组
func (d *fieldMappingConfigDao) GetFieldGroupsByVendorApi(ctx context.Context, vendorName, apiName string) (groups []string, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Fields(entity.FieldMappingConfigCol.FieldGroup).
Where(entity.FieldMappingConfigCol.VendorName, vendorName).
Where(entity.FieldMappingConfigCol.ApiName, apiName).
Where(entity.FieldMappingConfigCol.IsActive, true).
Group(entity.FieldMappingConfigCol.FieldGroup).
All()
if err != nil {
return
}
for _, record := range r {
group := record.Map()[entity.FieldMappingConfigCol.FieldGroup]
if groupStr, ok := group.(string); ok && groupStr != "" {
groups = append(groups, groupStr)
}
}
return
}
// DeleteExpiredConfigs 删除已过期的配置
func (d *fieldMappingConfigDao) DeleteExpiredConfigs(ctx context.Context) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Where("expiry_date IS NOT NULL AND expiry_date < ?", time.Now()).
Delete()
if err != nil {
return
}
return r.RowsAffected()
}
// GetConfigsEffectiveNow 获取当前生效的配置
func (d *fieldMappingConfigDao) GetConfigsEffectiveNow(ctx context.Context) (res []entity.FieldMappingConfig, err error) {
now := time.Now()
r, err := gfdb.DB(ctx).Model(ctx, consts.FieldMappingConfigTable).
Where(entity.FieldMappingConfigCol.IsActive, true).
Where("(effective_date IS NULL OR effective_date <= ?)", now).
Where("(expiry_date IS NULL OR expiry_date > ?)", now).
OrderDesc(entity.FieldMappingConfigCol.Priority).
OrderAsc(entity.FieldMappingConfigCol.Id).
All()
if err != nil {
return
}
err = r.Structs(&res)
return
}
+69 -24
View File
@@ -1,14 +1,15 @@
package dict
import (
consts "cid/consts/public"
dto "cid/model/dto/dict"
entity "cid/model/entity/dict"
"context"
consts "dataengine/consts/public"
dto "dataengine/model/dto/dict"
entity "dataengine/model/entity/dict"
"gitea.com/red-future/common/db/gfdb"
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
@@ -17,11 +18,19 @@ var ApiInterface = new(apiInterfaceDao)
type apiInterfaceDao struct{}
// Insert 插入接口
func (d *apiInterfaceDao) Insert(ctx context.Context, req *dto.CreateApiInterfaceReq) (id int64, err error) {
func (d *apiInterfaceDao) Insert(ctx context.Context, req *dto.CreateApiInterfaceReq, creator string, tenantId uint64) (id int64, err error) {
var res *entity.ApiInterface
if err = gconv.Struct(req, &res); err != nil {
return
}
// 设置审计字段(服务端生成,不依赖前端入参)
now := gtime.Now()
res.TenantId = tenantId
res.Creator = creator
res.CreatedAt = now
res.Updater = creator
res.UpdatedAt = now
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).Data(&res).Insert()
if err != nil {
return
@@ -30,8 +39,18 @@ func (d *apiInterfaceDao) Insert(ctx context.Context, req *dto.CreateApiInterfac
}
// Update 更新接口
func (d *apiInterfaceDao) Update(ctx context.Context, req *dto.UpdateApiInterfaceReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).Data(&req).OmitEmpty().Where(entity.ApiInterfaceCols.Id, req.Id).Update()
func (d *apiInterfaceDao) Update(ctx context.Context, req *dto.UpdateApiInterfaceReq, updater string, tenantId uint64) (rows int64, err error) {
// 设置更新人和更新时间(服务端生成,不依赖前端入参)
data := gconv.Map(req)
data[entity.ApiInterfaceCols.Updater] = updater
data[entity.ApiInterfaceCols.UpdatedAt] = gtime.Now()
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
Data(data).
OmitEmpty().
Where(entity.ApiInterfaceCols.Id, req.Id).
Where(entity.ApiInterfaceCols.TenantId, tenantId).
Update()
if err != nil {
return
}
@@ -39,8 +58,11 @@ func (d *apiInterfaceDao) Update(ctx context.Context, req *dto.UpdateApiInterfac
}
// Delete 删除接口
func (d *apiInterfaceDao) Delete(ctx context.Context, req *dto.DeleteApiInterfaceReq) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).Where(entity.ApiInterfaceCols.Id, req.Id).Delete()
func (d *apiInterfaceDao) Delete(ctx context.Context, req *dto.DeleteApiInterfaceReq, tenantId uint64) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
Where(entity.ApiInterfaceCols.Id, req.Id).
Where(entity.ApiInterfaceCols.TenantId, tenantId).
Delete()
if err != nil {
return
}
@@ -48,8 +70,11 @@ func (d *apiInterfaceDao) Delete(ctx context.Context, req *dto.DeleteApiInterfac
}
// GetOne 获取单个接口
func (d *apiInterfaceDao) GetOne(ctx context.Context, req *dto.GetApiInterfaceReq) (res *entity.ApiInterface, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).Where(entity.ApiInterfaceCols.Id, req.Id).One()
func (d *apiInterfaceDao) GetOne(ctx context.Context, req *dto.GetApiInterfaceReq, tenantId uint64) (res *entity.ApiInterface, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
Where(entity.ApiInterfaceCols.Id, req.Id).
Where(entity.ApiInterfaceCols.TenantId, tenantId).
One()
if err != nil {
return
}
@@ -58,13 +83,13 @@ func (d *apiInterfaceDao) GetOne(ctx context.Context, req *dto.GetApiInterfaceRe
}
// Count 获取接口数量
func (d *apiInterfaceDao) Count(ctx context.Context, req *dto.ListApiInterfaceReq) (count int, err error) {
return d.buildListFilter(ctx, req).Count()
func (d *apiInterfaceDao) Count(ctx context.Context, req *dto.ListApiInterfaceReq, tenantId uint64) (count int, err error) {
return d.buildListFilter(ctx, req, tenantId).Count()
}
// List 获取接口列表
func (d *apiInterfaceDao) List(ctx context.Context, req *dto.ListApiInterfaceReq) (res []entity.ApiInterface, total int, err error) {
model := d.buildListFilter(ctx, req)
func (d *apiInterfaceDao) List(ctx context.Context, req *dto.ListApiInterfaceReq, tenantId uint64) (res []entity.ApiInterface, total int, err error) {
model := d.buildListFilter(ctx, req, tenantId)
model.OrderDesc(entity.ApiInterfaceCols.CreatedAt)
if req.Page != nil {
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
@@ -78,26 +103,45 @@ func (d *apiInterfaceDao) List(ctx context.Context, req *dto.ListApiInterfaceReq
}
// buildListFilter 构建列表查询的过滤条件
func (d *apiInterfaceDao) buildListFilter(ctx context.Context, req *dto.ListApiInterfaceReq) *gdb.Model {
func (d *apiInterfaceDao) buildListFilter(ctx context.Context, req *dto.ListApiInterfaceReq, tenantId uint64) *gdb.Model {
model := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).Model
// 租户隔离
model.Where(entity.ApiInterfaceCols.TenantId, tenantId)
if !g.IsEmpty(req.Keyword) {
model.WhereLike(entity.ApiInterfaceCols.Name, "%"+req.Keyword+"%")
model.WhereOrLike(entity.ApiInterfaceCols.Code, "%"+req.Keyword+"%")
}
model.Where(entity.ApiInterfaceCols.PlatformId, req.PlatformId)
model.Where(entity.ApiInterfaceCols.Name, req.Name)
model.Where(entity.ApiInterfaceCols.Code, req.Code)
model.Where(entity.ApiInterfaceCols.Method, req.Method)
model.Where(entity.ApiInterfaceCols.Status, req.Status)
if req.PlatformId > 0 {
model.Where(entity.ApiInterfaceCols.PlatformId, req.PlatformId)
}
if !g.IsEmpty(req.Name) {
model.Where(entity.ApiInterfaceCols.Name, req.Name)
}
if !g.IsEmpty(req.Code) {
model.Where(entity.ApiInterfaceCols.Code, req.Code)
}
if !g.IsEmpty(req.Method) {
model.Where(entity.ApiInterfaceCols.Method, req.Method)
}
if !g.IsEmpty(req.Status) {
model.Where(entity.ApiInterfaceCols.Status, req.Status)
}
model.OmitEmptyWhere()
return model
}
// UpdateStatus 更新接口状态
func (d *apiInterfaceDao) UpdateStatus(ctx context.Context, id int64, status string) (rows int64, err error) {
func (d *apiInterfaceDao) UpdateStatus(ctx context.Context, id int64, status string, updater string, tenantId uint64) (rows int64, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
Data(map[string]interface{}{"status": status}).
Data(map[string]interface{}{
entity.ApiInterfaceCols.Status: status,
entity.ApiInterfaceCols.Updater: updater,
entity.ApiInterfaceCols.UpdatedAt: gtime.Now(),
}).
Where(entity.ApiInterfaceCols.Id, id).
Where(entity.ApiInterfaceCols.TenantId, tenantId).
Update()
if err != nil {
return
@@ -106,9 +150,10 @@ func (d *apiInterfaceDao) UpdateStatus(ctx context.Context, id int64, status str
}
// GetByIds 根据ID列表获取接口列表
func (d *apiInterfaceDao) GetByIds(ctx context.Context, ids []int64) (res []entity.ApiInterface, err error) {
func (d *apiInterfaceDao) GetByIds(ctx context.Context, ids []int64, tenantId uint64) (res []entity.ApiInterface, err error) {
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
WhereIn(entity.ApiInterfaceCols.Id, ids).
Where(entity.ApiInterfaceCols.TenantId, tenantId).
All()
if err != nil {
return
+359
View File
@@ -0,0 +1,359 @@
-- ============================================================
-- 报表引擎 — 快手电商演示数据初始化脚本
-- ============================================================
-- 使用方式:
-- PGPASSWORD='Bjang09@686^*^' psql -h 116.204.74.41 \
-- -p 15432 -U postgres -d engine -f demo_kuaishou.sql
-- ============================================================
BEGIN;
-- ============================================================
-- 第〇步:确保系统表存在(CREATE TABLE IF NOT EXISTS
-- ============================================================
CREATE TABLE IF NOT EXISTS report_business_config (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
business_name VARCHAR(128) NOT NULL,
description TEXT DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
config JSONB DEFAULT '{}',
creator VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updater VARCHAR(64) DEFAULT '',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE TABLE IF NOT EXISTS report_report_config (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
report_code VARCHAR(64) NOT NULL,
report_name VARCHAR(128) NOT NULL,
description TEXT DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
stat_table_name VARCHAR(128) NOT NULL,
stat_table_comment VARCHAR(256) DEFAULT '',
date_field VARCHAR(64) DEFAULT 'stat_date',
primary_keys JSONB DEFAULT '["id"]'::jsonb,
conflict_keys JSONB DEFAULT '["stat_date"]'::jsonb,
config JSONB DEFAULT '{}',
creator VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updater VARCHAR(64) DEFAULT '',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT uk_business_report_code UNIQUE (tenant_id, business_code, report_code)
);
CREATE TABLE IF NOT EXISTS report_field_config (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
report_code VARCHAR(64) NOT NULL,
field_code VARCHAR(64) NOT NULL,
field_name VARCHAR(128) NOT NULL,
field_type VARCHAR(32) NOT NULL,
data_type VARCHAR(32) NOT NULL DEFAULT 'STRING',
field_role VARCHAR(32) NOT NULL,
is_aggregatable BOOLEAN DEFAULT FALSE,
is_filterable BOOLEAN DEFAULT TRUE,
is_queryable BOOLEAN DEFAULT TRUE,
is_sortable BOOLEAN DEFAULT TRUE,
default_aggregate VARCHAR(32) DEFAULT '',
valid_aggregates JSONB DEFAULT '[]'::jsonb,
filter_operators JSONB DEFAULT '["=","!=",">","<",">=","<=","IN","LIKE","BETWEEN"]'::jsonb,
expression VARCHAR(512) DEFAULT '',
expression_type VARCHAR(32) DEFAULT '',
format_pattern VARCHAR(64) DEFAULT '',
unit VARCHAR(32) DEFAULT '',
dict_code VARCHAR(64) DEFAULT '',
sort_order INT DEFAULT 0,
group_name VARCHAR(64) DEFAULT '',
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
creator VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updater VARCHAR(64) DEFAULT '',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT uk_business_report_field_code UNIQUE (tenant_id, business_code, report_code, field_code)
);
CREATE TABLE IF NOT EXISTS report_extract_config (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
report_code VARCHAR(64) NOT NULL,
extract_code VARCHAR(64) NOT NULL,
extract_name VARCHAR(128) NOT NULL,
source_table_name VARCHAR(128) NOT NULL,
source_table_alias VARCHAR(64) DEFAULT '',
target_table_name VARCHAR(128) NOT NULL,
is_enabled BOOLEAN DEFAULT TRUE,
extract_type VARCHAR(32) NOT NULL DEFAULT 'FULL',
extract_mode VARCHAR(32) NOT NULL DEFAULT 'DIRECT',
extract_key_field VARCHAR(64) DEFAULT '',
extract_key_format VARCHAR(64) DEFAULT '',
group_by_fields JSONB DEFAULT '[]'::jsonb,
filter_expression TEXT DEFAULT '',
join_configs JSONB DEFAULT '[]'::jsonb,
field_mappings JSONB DEFAULT '[]'::jsonb,
transform_rules JSONB DEFAULT '[]'::jsonb,
batch_size INT DEFAULT 1000,
status VARCHAR(16) NOT NULL DEFAULT 'ACTIVE',
creator VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updater VARCHAR(64) DEFAULT '',
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
deleted_at TIMESTAMP WITH TIME ZONE,
CONSTRAINT uk_business_report_extract_code UNIQUE (tenant_id, business_code, report_code, extract_code)
);
CREATE TABLE IF NOT EXISTS report_extract_log (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL DEFAULT 0,
business_code VARCHAR(64) NOT NULL,
report_code VARCHAR(64) NOT NULL,
extract_code VARCHAR(64) NOT NULL,
stat_date VARCHAR(16) NOT NULL,
extract_type VARCHAR(32) NOT NULL,
status VARCHAR(16) NOT NULL DEFAULT 'RUNNING',
total_count INT DEFAULT 0,
success_count INT DEFAULT 0,
fail_count INT DEFAULT 0,
start_time TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
end_time TIMESTAMP WITH TIME ZONE,
error_message TEXT DEFAULT '',
executor VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT uk_extract_keys UNIQUE (tenant_id, business_code, report_code, extract_code, stat_date)
);
-- ============================================================
-- 第一步:创建源数据表 + 模拟数据
-- ============================================================
-- 店铺表
DROP TABLE IF EXISTS demo_kuaishou_shop CASCADE;
CREATE TABLE demo_kuaishou_shop (
shop_id VARCHAR(64) PRIMARY KEY,
shop_name VARCHAR(128) NOT NULL,
shop_level VARCHAR(16) DEFAULT '普通',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
INSERT INTO demo_kuaishou_shop VALUES
('KS001', '快手官方旗舰店', '旗舰'),
('KS002', '数码3C专营店', '高级'),
('KS003', '美妆个护优选', '普通'),
('KS004', '食品生鲜超市', '高级'),
('KS005', '服装潮流馆', '普通');
-- 订单明细表
DROP TABLE IF EXISTS demo_kuaishou_orders CASCADE;
CREATE TABLE demo_kuaishou_orders (
id BIGSERIAL PRIMARY KEY,
order_no VARCHAR(64) NOT NULL,
shop_id VARCHAR(64) NOT NULL,
order_status VARCHAR(16) NOT NULL DEFAULT '已完成',
product_name VARCHAR(256),
product_category VARCHAR(64),
quantity INT DEFAULT 1,
unit_price NUMERIC(12,2) DEFAULT 0,
order_amount NUMERIC(12,2) DEFAULT 0,
refund_amount NUMERIC(12,2) DEFAULT 0,
shipping_cost NUMERIC(10,2) DEFAULT 0,
payment_method VARCHAR(32) DEFAULT '微信',
buyer_id VARCHAR(64) DEFAULT '',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- 生成30天模拟订单
DO $$
DECLARE
v_date DATE;
v_order_date DATE := CURRENT_DATE - INTERVAL '30 days';
v_order_count INT;
v_shop_id VARCHAR(64);
v_shop_ids VARCHAR[] := ARRAY['KS001','KS002','KS003','KS004','KS005'];
v_categories VARCHAR[] := ARRAY['手机','电脑','美妆','食品','服装'];
v_payments VARCHAR[] := ARRAY['微信','支付宝','快手支付'];
v_statuses VARCHAR[] := ARRAY['已完成','已完成','已完成','已完成','已完成','已完成','已完成','已取消','已完成'];
v_price NUMERIC(12,2);
v_qty INT;
BEGIN
FOR day_offset IN 0..29 LOOP
v_date := v_order_date + day_offset;
v_order_count := 20 + floor(random() * 31)::int;
FOR i IN 1..v_order_count LOOP
v_shop_id := v_shop_ids[1 + floor(random() * 5)::int];
v_qty := 1 + floor(random() * 5)::int;
v_price := round((50 + random() * 2000)::numeric, 2);
INSERT INTO demo_kuaishou_orders (order_no, shop_id, order_status, product_name,
product_category, quantity, unit_price, order_amount, refund_amount,
shipping_cost, payment_method, buyer_id, created_at, updated_at)
VALUES (
'KS' || to_char(v_date, 'YYYYMMDD') || '-' || lpad(i::text, 4, '0'),
v_shop_id, v_statuses[1 + floor(random() * 9)::int],
'商品' || (1000 + floor(random() * 9000)::int),
v_categories[1 + floor(random() * 5)::int],
v_qty, v_price, round((v_price * v_qty)::numeric, 2),
CASE WHEN random() < 0.1 THEN round((v_price * v_qty * random() * 0.5)::numeric, 2) ELSE 0 END,
round((5 + random() * 15)::numeric, 2),
v_payments[1 + floor(random() * 3)::int],
'buyer_' || (10000 + floor(random() * 90000)::int),
v_date + time '08:00:00' + (random() * interval '12 hours'),
v_date + time '08:00:00' + (random() * interval '12 hours')
);
END LOOP;
END LOOP;
END $$;
ANALYZE demo_kuaishou_orders;
-- ============================================================
-- 第二步:插入系统配置(先清空避免重复)
-- ============================================================
DELETE FROM report_business_config WHERE business_code = 'KUAISHOU';
DELETE FROM report_report_config WHERE business_code = 'KUAISHOU';
DELETE FROM report_field_config WHERE business_code = 'KUAISHOU';
DELETE FROM report_extract_config WHERE business_code = 'KUAISHOU';
-- 1. 业务配置
INSERT INTO report_business_config (tenant_id, business_code, business_name, description, status, config, creator)
VALUES (1, 'KUAISHOU', '快手电商', '快手电商平台订单数据', 'ACTIVE', '{}', 'system');
-- 2. 报表配置
INSERT INTO report_report_config (tenant_id, business_code, report_code, report_name, description,
status, stat_table_name, stat_table_comment, date_field, primary_keys, conflict_keys, config, creator)
VALUES (1, 'KUAISHOU', 'shop_daily', '店铺日报',
'按店铺+日期统计订单数据', 'ACTIVE',
'stat_kuaishou_shop_daily', '快手电商店铺日报统计宽表',
'stat_date', '["id"]'::jsonb, '["stat_date", "shop_id"]'::jsonb, '{}', 'system');
-- 3. 字段配置
INSERT INTO report_field_config (tenant_id, business_code, report_code, field_code, field_name,
field_type, data_type, field_role, is_aggregatable, is_filterable, is_queryable, is_sortable,
default_aggregate, valid_aggregates, filter_operators, sort_order, group_name, status, creator)
VALUES
-- 维度
(1, 'KUAISHOU', 'shop_daily', 'stat_date', '统计日期',
'DATE', 'STRING', 'DIMENSION', false, true, true, true,
'', '[]'::jsonb, '["=",">=","<=","BETWEEN"]'::jsonb, 1, '时间维度', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'order_no', '订单号',
'STRING', 'STRING', 'DIMENSION', false, true, true, true,
'', '[]'::jsonb, '["=","IN"]'::jsonb, 0, '订单维度', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'shop_id', '店铺ID',
'STRING', 'STRING', 'DIMENSION', false, true, true, true,
'', '[]'::jsonb, '["=","IN"]'::jsonb, 2, '店铺维度', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'shop_name', '店铺名称',
'STRING', 'STRING', 'DIMENSION', false, true, true, true,
'', '[]'::jsonb, '["=","LIKE"]'::jsonb, 3, '店铺维度', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'product_category', '商品类目',
'STRING', 'STRING', 'DIMENSION', false, true, true, true,
'', '[]'::jsonb, '["=","IN"]'::jsonb, 4, '商品维度', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'order_status', '订单状态',
'STRING', 'STRING', 'DIMENSION', false, true, true, true,
'', '[]'::jsonb, '["=","IN"]'::jsonb, 5, '订单维度', 'ACTIVE', 'system'),
-- 指标
(1, 'KUAISHOU', 'shop_daily', 'order_count', '订单数',
'INT', 'INT', 'INDICATOR', true, false, true, true,
'SUM', '["SUM","COUNT"]'::jsonb, '[]'::jsonb, 10, '交易指标', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'order_amount', '订单金额',
'FLOAT', 'FLOAT', 'INDICATOR', true, false, true, true,
'SUM', '["SUM","AVG","MAX","MIN"]'::jsonb, '[]'::jsonb, 11, '交易指标', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'refund_amount', '退款金额',
'FLOAT', 'FLOAT', 'INDICATOR', true, false, true, true,
'SUM', '["SUM","AVG"]'::jsonb, '[]'::jsonb, 12, '交易指标', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'shipping_cost', '运费',
'FLOAT', 'FLOAT', 'INDICATOR', true, false, true, true,
'SUM', '["SUM","AVG"]'::jsonb, '[]'::jsonb, 13, '交易指标', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'total_quantity', '商品数量',
'INT', 'INT', 'INDICATOR', true, false, true, true,
'SUM', '["SUM","AVG"]'::jsonb, '[]'::jsonb, 14, '交易指标', 'ACTIVE', 'system'),
(1, 'KUAISHOU', 'shop_daily', 'avg_price', '平均单价',
'FLOAT', 'FLOAT', 'INDICATOR', true, false, true, true,
'AVG', '["AVG","MAX","MIN"]'::jsonb, '[]'::jsonb, 15, '交易指标', 'ACTIVE', 'system');
-- 4. 抽取配置(DIRECT 模式:逐行抽取,查询时动态聚合)
INSERT INTO report_extract_config (tenant_id, business_code, report_code, extract_code, extract_name,
source_table_name, source_table_alias, target_table_name, is_enabled,
extract_type, extract_mode, extract_key_field, group_by_fields, filter_expression,
field_mappings, join_configs, batch_size, status, creator)
VALUES (1, 'KUAISHOU', 'shop_daily', 'shop_daily_direct', '店铺日订单(逐行,可自由组合查询)',
'demo_kuaishou_orders', 'o', 'stat_kuaishou_shop_daily',
true, 'INCREMENTAL', 'DIRECT', 'created_at',
'[]'::jsonb,
'o.order_status = ''已完成''',
'[
{"sourceField":"order_no", "targetField":"order_no", "fieldType":"STRING", "aggregateFunction":"", "defaultValue":""},
{"sourceField":"shop_id", "targetField":"shop_id", "fieldType":"STRING", "aggregateFunction":"", "defaultValue":""},
{"sourceField":"product_category", "targetField":"product_category", "fieldType":"STRING", "aggregateFunction":"", "defaultValue":""},
{"sourceField":"order_status", "targetField":"order_status", "fieldType":"STRING", "aggregateFunction":"", "defaultValue":""},
{"sourceField":"order_amount", "targetField":"order_amount", "fieldType":"FLOAT", "aggregateFunction":"", "defaultValue":0},
{"sourceField":"refund_amount", "targetField":"refund_amount", "fieldType":"FLOAT", "aggregateFunction":"", "defaultValue":0},
{"sourceField":"shipping_cost", "targetField":"shipping_cost", "fieldType":"FLOAT", "aggregateFunction":"", "defaultValue":0},
{"sourceField":"quantity", "targetField":"total_quantity", "fieldType":"INT", "aggregateFunction":"", "defaultValue":0},
{"sourceField":"unit_price", "targetField":"unit_price", "fieldType":"FLOAT", "aggregateFunction":"", "defaultValue":0}
]'::jsonb,
'[
{
"joinTable":"demo_kuaishou_shop",
"joinAlias":"s",
"joinType":"LEFT",
"joinCondition":"o.shop_id = s.shop_id",
"fieldMappings":[
{"sourceField":"shop_name", "targetField":"shop_name", "fieldType":"STRING", "aggregateFunction":""},
{"sourceField":"shop_level", "targetField":"shop_level", "fieldType":"STRING", "aggregateFunction":""}
]
}
]'::jsonb,
1000, 'ACTIVE', 'system'
);
-- ============================================================
-- 第三步:验证
-- ============================================================
SELECT '✅ 源表数据' AS info, COUNT(*) AS FROM demo_kuaishou_orders
UNION ALL
SELECT '✅ 店铺数', COUNT(*) FROM demo_kuaishou_shop
UNION ALL
SELECT '✅ 业务配置', COUNT(*) FROM report_business_config WHERE business_code = 'KUAISHOU'
UNION ALL
SELECT '✅ 报表配置', COUNT(*) FROM report_report_config WHERE business_code = 'KUAISHOU'
UNION ALL
SELECT '✅ 字段配置', COUNT(*) FROM report_field_config WHERE business_code = 'KUAISHOU'
UNION ALL
SELECT '✅ 抽取配置', COUNT(*) FROM report_extract_config WHERE business_code = 'KUAISHOU';
SELECT '📅 数据日期范围:' AS info, MIN(created_at::date)::text || ' ~ ' || MAX(created_at::date)::text AS FROM demo_kuaishou_orders;
COMMIT;
-- ============================================================
-- 使用说明
-- ============================================================
-- 1. 启动项目(会自动创建系统表)
-- 2. 执行本脚本
-- 3. 打开 http://localhost:3013/admin/report
-- 4. 抽取配置 → 选"快手电商/shop_daily" → 点"执行抽取"
-- 5. 数据查询 → 选"快手电商/shop_daily" → 自由选维度/指标查询
-- ============================================================
--
-- 上线前清理 Demo 数据:
-- DELETE FROM report_business_config WHERE business_code = 'KUAISHOU';
-- DELETE FROM report_report_config WHERE business_code = 'KUAISHOU';
-- DELETE FROM report_field_config WHERE business_code = 'KUAISHOU';
-- DELETE FROM report_extract_config WHERE business_code = 'KUAISHOU';
-- DELETE FROM report_extract_log WHERE business_code = 'KUAISHOU';
-- DROP TABLE IF EXISTS stat_kuaishou_shop_daily CASCADE;
-- DROP TABLE IF EXISTS demo_kuaishou_orders CASCADE;
-- DROP TABLE IF EXISTS demo_kuaishou_shop CASCADE;
-- ============================================================
+568
View File
@@ -0,0 +1,568 @@
# 数据引擎 — 平台与接口配置指南
> 适用版本:HDWL data-engine
> 阅读对象:实施工程师、运维人员
> 前置知识:了解 JSON 基本格式即可
---
## 目录
1. [概念说明](#1-概念说明)
2. [平台管理 (api_datasource_platform)](#2-平台管理)
3. [接口管理 (api_interface)](#3-接口管理)
4. [实战:新增一个平台](#4-实战新增一个平台)
5. [常见问题](#5-常见问题)
---
## 1. 概念说明
```
平台 (Platform) → 对接的第三方系统(如:快手电商、腾讯广告、钉钉)
└── 接口 (Interface) → 平台提供的具体 API(如:订单列表、商品详情)
└── 表 (Table) → 数据最终存储的数据库表
```
**举例**
- **平台** = 快手电商
- **接口1** = 订单列表 → 数据存入 `kuaishou_order_list`
- **接口2** = 商品列表 → 数据存入 `kuaishou_item_list`
- **接口3** = 售后单列表 → 数据存入 `kuaishou_refund_list`
系统会按照配置的**调度周期**,自动拉取每个接口的数据并存入对应的数据库表。
---
## 2. 平台管理
平台配置存储在 `api_datasource_platform` 表中,定义了一个第三方平台的基本信息和认证方式。
### 2.1 基础字段
| 字段 | 类型 | 必填 | 说明 | 示例 |
|------|------|------|------|------|
| `platform_code` | VARCHAR | ✅ | 平台唯一编码,不能重复 | `kuaishou``tencent` |
| `platform_name` | VARCHAR | ✅ | 平台显示名称 | `快手电商``腾讯广告` |
| `description` | VARCHAR | ❌ | 平台描述 | `快手电商开放平台数据同步` |
| `status` | VARCHAR | ✅ | 状态,`ACTIVE`=启用,`INACTIVE`=停用 | `ACTIVE` |
| `api_base_url` | VARCHAR | ✅ | API 域名,接口地址会拼接在此 URL 后面 | `https://openapi.kwaixiaodian.com` |
### 2.2 认证类型 (auth_type)
| 类型 | 说明 | 适用场景 |
|------|------|----------|
| `TOKEN` | 简单 Token 认证,放在 Header 中 | 内部系统 |
| `API_KEY` | API Key 认证,Token 放在请求参数中 | **快手电商**、钉钉 |
| `OAUTH2` | OAuth 2.0 认证 | 腾讯广告 |
| `SIGN` | 签名认证,使用 appKey + appSecret 签名 | 部分开放平台 |
| `APP_SIGNATURE` | 应用签名认证,基于请求体 MD5 | 钉钉智能薪酬 |
### 2.3 认证配置 (auth_config)
`auth_config` 是一个 **JSON 对象**,根据不同的 `auth_type` 配置不同内容。
#### API_KEY 类型配置(快手电商示例)
```json
{
"sign_algorithm": "md5",
"app_key": "ks651333099611149957",
"app_secret": "JPUXG2CS3I7tqRWbKaLrYQ",
"sign_secret": "7bc51baab818cf86e121a48d99ff3fe4",
"token_in_query": true,
"query_key": "access_token",
"extra_query_params": {
"timestamp": "{timestamp_ms}"
}
}
```
| 字段 | 说明 |
|------|------|
| `sign_algorithm` | 签名算法,可选 `md5`(小写输出)或 `md5_upper`(大写输出)或 `HMAC_SHA256` |
| `app_key` | 平台分配的 AppKey(也叫 AppId、client_id),**快手签名必填** |
| `app_secret` | 平台分配的 AppSecret,用于 OAuth 获取 Token |
| `sign_secret` | 签名专用密钥(**快手特有**),平台分配,在"应用详情"中查看 |
| `token_in_query` | `true`=Token 放在 URL 参数中;`false`=Token 放在 Header 中 |
| `query_key` | Token 的参数名,默认 `access_token` |
| `extra_query_params` | 额外需要拼接的参数,支持 `{timestamp}`(秒)、`{timestamp_ms}`(毫秒)、`{nonce}` 占位符 |
> **快手签名算法**(仅供参考,代码已实现):
> 1. 所有参数按字母排序,拼接成 `key1=value1&key2=value2&...`
> 2. 末尾追加 `&signSecret=你的signSecret`
> 3. 对整个字符串取 MD5
#### SIGN 类型配置
```json
{
"sign_algorithm": "md5",
"app_key": "your_app_key",
"app_secret": "your_app_secret"
}
```
### 2.4 限流与重试字段
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `rate_limit_per_minute` | INT | 100 | 每分钟最大请求次数,防止触发平台限流 |
| `rate_limit_per_hour` | INT | 3600 | 每小时最大请求次数 |
| `concurrency_limit` | INT | 5 | 并发请求数 |
| `request_timeout_ms` | INT | 30000 | 单次请求超时时间(毫秒) |
| `max_retries` | INT | 3 | 请求失败后的最大重试次数 |
| `retry_delay_ms` | INT | 1000 | 重试间隔(毫秒),每次翻倍 |
### 2.5 Token 与 API Key 字段
| 字段 | 说明 | 快手示例 |
|------|------|----------|
| `token` | 认证 Token / Refresh Token | 快手 OAuth 返回的 `refresh_token` |
| `api_key` | API Key / Access Token | 快手 OAuth 返回的 `access_token` |
| `client_id` | OAuth2 Client ID | 腾讯广告的 client_id |
| `client_secret` | OAuth2 Client Secret | 腾讯广告的 client_secret |
---
## 3. 接口管理
接口配置存储在 `api_interface` 表中,定义了一个平台下每个 API 的请求方式、参数、响应解析规则和存储表结构。
### 3.1 基础字段
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `platform_id` | INT | ✅ | 所属平台的 ID(关联 `api_datasource_platform.id` |
| `name` | VARCHAR | ✅ | 接口名称,如"订单列表" |
| `code` | VARCHAR | ✅ | 接口编码,唯一标识,如 `order_list` |
| `url` | VARCHAR | ✅ | API 路径,会拼接在平台 `api_base_url` 后面 |
| `method` | VARCHAR | ✅ | HTTP 方法,`GET``POST` |
| `status` | VARCHAR | ✅ | 状态,`active`=启用,`inactive`=停用 |
| `auth_type` | VARCHAR | ✅ | 认证方式,`inherit`=继承平台配置 |
### 3.2 请求配置 (request_config)
`request_config` 是一个 **JSON 对象**,定义如何构造 API 请求参数。
#### 3.2.1 完整字段说明
```json
{
"page_param": "cursor",
"page_size_param": "pageSize",
"cursor_pagination": true,
"pagination_mode": "offset",
"page_size": 50,
"initial_cursor": "",
"method": "open.order.cursor.list",
"version": 1,
"signMethod": "MD5",
"time_field": "updateTime",
"time_field_mode": "range",
"full_sync_start_time": 1700000000000,
"queryType": 2,
"parameters_location": "query",
"body_wrapper_field": "param",
"exclude_from_wrapper": ["method", "version", "signMethod"],
"prefetch": { ... },
"recursive": { ... },
"row_inject": ["statisticsMonth"],
"max_recursive_depth": 20
}
```
#### 3.2.2 分页配置
| 字段 | 说明 | 示例 |
|------|------|------|
| `page_param` | 页码/游标的参数名 | `cursor``page``offset` |
| `page_size_param` | 每页条数的参数名 | `pageSize``page_size` |
| `page_size` | 每页请求条数,覆盖全局配置 | `50` |
| `cursor_pagination` | `true`=游标分页;不配置=普通页码分页 | `true` |
| `pagination_mode` | `offset`=偏移量分页(`offset=(page-1)*size`);不配置=页码分页 | `offset` |
| `initial_cursor` | 游标分页时的初始游标值,不配则为空字符串 | `""` |
**三种分页模式对比**
| 模式 | 配置方式 | 适用平台 | 翻页方式 |
|------|----------|----------|----------|
| 游标分页 | `cursor_pagination: true` | 快手、钉钉 | 响应返回 cursor,下次请求带上 |
| 普通分页 | 默认 | 腾讯广告 | 响应返回 totalPages,按页码翻 |
| hasMore 分页 | `has_more_field` 在 response_config | 钉钉 | 响应有 hasMore 字段判断是否有下一页 |
#### 3.2.3 时间过滤配置
用于增量同步——只拉取上次同步之后新增/更新的数据。
| 字段 | 说明 |
|------|------|
| `time_field` | 时间字段名,如 `updateTime``createTime` |
| `time_field_mode` | 时间模式:`range`=快手模式(beginTime/endTime)`filtering`=腾讯模式(filtering 数组) |
| `full_sync_start_time` | 首次全量同步的起始时间戳(**Unix 毫秒**),不配则用全局 `default_lookback_days` |
| `queryType` | 快手专用:`1`=按创建时间查(90天内)`2`=按更新时间查(240天内) |
> **全局配置** `config.yml` 中 `sync.default_lookback_days` 控制默认回溯天数(默认 89 天)。
#### 3.2.4 业务参数
除了上述系统级字段外,`request_config` 中其他字段都会作为**业务参数**发送给 API。
**快手示例**`orderViewStatus``cpsType``sort` 等是业务参数):
```json
{
"orderViewStatus": 1,
"cpsType": 1,
"sort": 1,
"pageSize": 50
}
```
#### 3.2.5 body_wrapper_field(快手专用)
快手 API 要求所有业务参数打包成一个 JSON 字符串,放在 `param` 字段中发送。
```json
{
"body_wrapper_field": "param",
"exclude_from_wrapper": ["method", "version", "signMethod"]
}
```
**效果**
- 业务参数 `orderViewStatus=1, pageSize=50, sort=1` → 打包为 `param={"orderViewStatus":1,"pageSize":50,"sort":1}`
- `method``version``signMethod` 保持在顶层,不被打包
#### 3.2.6 预取配置 (prefetch)
用于需要**先获取实体列表,再逐个查详情**的场景。
```json
{
"prefetch": {
"url": "/open/order/cursor/list",
"method": "GET",
"response_path": "data.orderList",
"target_param": "oid",
"value_field": "oid"
}
}
```
| 字段 | 说明 |
|------|------|
| `url` | 预取数据的 API 路径 |
| `method` | HTTP 方法 |
| `response_path` | 从响应中提取实体列表的 JSON 路径,如 `data.orderList` |
| `target_param` | 将提取的值传给目标接口时使用的参数名 |
| `value_field` | 从实体中取哪个字段的值,不配则传整个实体对象 |
**工作流程示例**(订单详情):
1. 先调用 `/open/order/cursor/list` 获取所有订单 ID 列表
2. 对每个订单 ID,调用 `/open/order/detail?oid=xxx` 获取详情
3. 所有详情数据合并存入 `kuaishou_order_detail`
#### 3.2.7 递归遍历配置 (recursive)
用于树形结构数据(如钉钉部门树)。
```json
{
"recursive": {
"key_field": "dept_id",
"target_param": "parent_id"
}
}
```
| 字段 | 说明 |
|------|------|
| `key_field` | 当前节点的 ID 字段名 |
| `target_param` | 传给下级查询的参数名 |
| `max_recursive_depth` | 最大递归深度,默认 20 |
#### 3.2.8 参数位置控制
| 字段 | 说明 |
|------|------|
| `parameters_location` | `query`=参数放 URL 查询字符串;不配=GET 放 URLPOST 放 Body |
#### 3.2.9 字段注入 (row_inject)
如果响应中缺少请求参数中的某些字段,可以用 `row_inject` 注入到每行数据中。
```json
{
"row_inject": ["statisticsMonth"]
}
```
### 3.3 响应配置 (response_config)
`response_config` 是一个 **JSON 对象**,定义如何解析 API 响应,判断成功/失败,提取数据。
```json
{
"success_field": "result",
"success_value": 1,
"message_field": "error_msg",
"list_path": "data.orderList",
"cursor_field": "data.cursor",
"cursor_end_marker": "nomore",
"single_record": false,
"has_more_field": "data.hasMore"
}
```
| 字段 | 说明 | 默认值 |
|------|------|--------|
| `success_field` | 判断成功的字段名 | `code` |
| `success_value` | 成功的值(数字) | `0` |
| `message_field` | 错误消息的字段名 | `message` |
| `list_path` | 数据列表的 JSON 路径,如 `data.orderList` | `data` |
| `cursor_field` | 游标值的 JSON 路径,如 `data.cursor` | 无 |
| `cursor_end_marker` | 游标结束标记,收到此值表示到底 | `nomore` |
| `single_record` | `true`=响应是单条记录(详情接口),会自动包装成数组 | `false` |
| `has_more_field` | hasMore 分页模式的判断字段路径 | 无 |
#### 成功判断逻辑
```
读取响应的 success_field 字段 → 转成数字 → 和 success_value 比较
相等 = 成功
不相等 = 失败,取 message_field 作为错误消息
```
**快手示例**`result` 字段值 = `1` 表示成功
**腾讯示例**`code` 字段值 = `0` 表示成功
#### 数据提取逻辑
```
1. 按 list_path 逐层进入 JSON
2. 最后一段如果是数组 → 直接作为列表
3. 最后一段是对象 → 找对象里的 list/orderList 字段
4. 如果 single_record=true → 将对象包装为单元素数组
5. 每行数据自动展平(子对象字段合并到顶层)
6. 每行附加 raw_data 字段(原始 JSON
```
### 3.4 表结构定义 (table_definition)
`table_definition`**JSON 对象**,定义目标表的表名、列结构和冲突处理。
```json
{
"table_name": "kuaishou_order_list",
"columns": [
{ "name": "oid", "type": "BIGINT", "comment": "订单ID" },
{ "name": "status", "type": "INT", "comment": "订单状态码" },
{ "name": "createTime", "type": "BIGINT", "comment": "创建时间" },
{ "name": "updateTime", "type": "BIGINT", "comment": "更新时间" },
{ "name": "totalFee", "type": "BIGINT", "comment": "总金额(分)" },
{ "name": "buyerNick", "type": "VARCHAR(100)", "comment": "买家昵称" },
{ "name": "itemTitle", "type": "VARCHAR(300)", "comment": "商品标题" }
],
"conflict_keys": ["oid"]
}
```
| 字段 | 说明 |
|------|------|
| `table_name` | 数据库表名,系统会自动创建 |
| `columns` | 列定义数组 |
| `columns[].name` | 列名,**必须与 API 响应中的字段名一致** |
| `columns[].type` | PostgreSQL 数据类型:`BIGINT``INT``VARCHAR(n)``TEXT``BOOLEAN``JSONB` |
| `columns[].comment` | 列的注释说明 |
| `conflict_keys` | 唯一键,插入时冲突则更新(UPSERT),如 `["oid"]` |
> **自动建表**:系统首次同步时会自动创建表。
> **自动过滤**:只写入 `columns` 中定义的字段,多余的忽略。
> **raw_data**:每行会自动附带一个 `raw_data` 字段,保存完整的原始 JSON 响应。
---
## 4. 实战:新增一个平台
以"快手电商"为例,完整步骤:
### 第 1 步:创建平台配置
`api_datasource_platform` 表插入一条记录:
```sql
INSERT INTO api_datasource_platform (
tenant_id, platform_code, platform_name, description, status,
api_base_url, auth_type, token, api_key, auth_config,
rate_limit_per_minute, request_timeout_ms, max_retries, retry_delay_ms
) VALUES (
1, -- 租户 ID
'kuaishou', -- 平台编码(唯一)
'快手电商', -- 名
'快手电商开放平台数据同步',
'ACTIVE', -- 状态
'https://openapi.kwaixiaodian.com', -- API 域名
'API_KEY', -- 认证类型
'你的refresh_token', -- TokenOAuth 返回)
'你的access_token', -- API KeyOAuth 返回)
'{...}', -- auth_config(见 2.3 节)
100, 30000, 3, 1000 -- 限流/超时/重试
);
```
### 第 2 步:创建接口配置
对每个需要同步的 API,在 `api_interface` 表插入一条记录:
```sql
INSERT INTO api_interface (
tenant_id, platform_id, name, code, url, method, status, auth_type,
request_config, response_config, table_definition
) VALUES (
1,
(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),
'订单列表', -- 名称
'order_list', -- 编码(唯一)
'/open/order/cursor/list', -- API 路径
'GET', -- HTTP 方法
'active', -- 状态
'inherit', -- 认证(继承平台)
'{...}', -- request_config(见 3.2 节)
'{...}', -- response_config(见 3.3 节)
'{...}' -- table_definition(见 3.4 节)
);
```
### 第 3 步:验证
系统启动后会自动:
1. 根据 `table_definition` 创建数据库表
2. 按调度周期拉取数据
3. 写入表中
---
## 5. 常见问题
### Q1:怎么判断数据有没有同步成功?
查看 `sync_tracker` 表,每个接口有一条记录:
- `sync_status` = `success` 表示成功
- `last_sync_time` = 上次同步的时间戳
- 查看 `sync_task_log` 表可以看到每次同步的详细日志
### Q2:怎么修改同步频率?
编辑 `config.yml`
```yaml
sync:
sync_interval_minutes: 60 # 改这个,单位分钟
```
### Q3:接口报"签名校验失败"怎么排查?
1. 确认 `app_key``app_secret``sign_secret` 和快手后台一致
2. 确认 `access_token` 没有过期
3. 确认 `sign_algorithm` 配置正确(快手用 `md5`
4. 查看日志中的 "签名原文" 和 "签名值",对比快手官方签名工具的结果
### Q4:想抽取历史数据怎么办?
在接口的 `request_config` 中设置 `full_sync_start_time`Unix 毫秒时间戳),或修改 `config.yml``sync.default_lookback_days`
### Q5:数据重复怎么办?
配置 `table_definition` 中的 `conflict_keys`,系统会用 **UPSERT** 模式(冲突时自动更新,不产生重复数据)。
### Q6:如何停用某个接口?
```sql
UPDATE api_interface SET status = 'inactive' WHERE code = '接口编码';
```
---
> 📅 最后更新:2026-06-16
> 📝 如有疑问,请联系开发团队
---
## 6. 同步机制说明
### 6.1 同步生命周期
```
第 1 次同步(全量)
lastSyncTime = 0 → 从 default_lookback_days 天前开始拉取
时间分片:每 7 天一个分片(如 90 天 ≈ 13 个分片循环)
完成后:记录 lastSyncTime = 数据中最大的更新时间
第 N 次同步(增量)
读取 lastSyncTime → beginTime = lastSyncTime
只拉取上次同步之后新增/更新的数据
完成后:更新 lastSyncTime
(每隔 sync_interval_minutes 分钟循环一次)
```
### 6.2 配置项
编辑 `config.yml`
```yaml
sync:
page_size: 100 # 每次分页请求条数
concurrency: 5 # 同步并发数
retry_count: 3 # 最大重试次数
sync_interval_minutes: 60 # 自动同步间隔(分钟),增量频率
compensation_interval_seconds: 300 # 失败重试扫描间隔(秒)
auto_sync_enabled: true # 是否启用自动同步
sync_timeout_minutes: 120 # 单次同步超时(分钟)
default_lookback_days: 89 # 首次全量回溯天数(可按接口覆盖)
default_tenant_id: 1 # 租户 ID
```
| 配置项 | 说明 | 建议值 |
|--------|------|--------|
| `sync_interval_minutes` | 增量同步频率 | 60(1小时) |
| `default_lookback_days` | 首次全量拉多少天 | 快手限制 90 天内,建议 89 |
| `compensation_interval_seconds` | 失败后多久重试 | 300(5分钟) |
| `auto_sync_enabled` | 是否自动跑 | 生产环境 `true` |
### 6.3 覆盖单个接口的回溯天数
在接口 `request_config` 中加入 `full_sync_start_time`Unix **毫秒**
```json
{ "full_sync_start_time": 1769200000000 }
```
### 6.4 查看同步状态
```sql
-- 所有接口的同步状态
SELECT platform_code, interface_code, sync_status,
to_timestamp(last_sync_time) AS last_sync_at
FROM sync_tracker ORDER BY platform_code, interface_code;
-- 最近失败记录
SELECT * FROM sync_task_log WHERE status = 'failed'
ORDER BY start_time DESC LIMIT 20;
```
- `sync_status = 'success'`:上次成功
- `sync_status = 'running'`:正在跑
- `last_sync_time = 0`:还没完成过同步
### 6.5 手动重新全量
`last_sync_time` 置 0,下次自动变全量:
```sql
UPDATE sync_tracker SET last_sync_time = 0
WHERE platform_code = 'kuaishou' AND interface_code = 'order_list';
```
---
> 📅 最后更新:2026-06-16
+657
View File
@@ -0,0 +1,657 @@
# Data Engine 通用数据同步引擎 - 使用文档
## 一、概述
本系统是一个**配置驱动的通用数据同步引擎**,核心思想是:**平台管理 + 接口管理**。
您只需要通过 API 维护好平台和接口的配置信息(认证方式、请求参数、响应解析、目标表结构),系统就会自动:
1. **创建目标表**(根据 `table_definition` 自动建表)
2. **拉取数据**(分页请求、多步骤请求、并发处理)
3. **写入数据库**(批量 upsert
4. **增量同步**(通过 `filtering` 按最后修改时间过滤)
5. **自动调度****推荐由 PPGo_Job 外部调度**(支持内部定时和外部 HTTP 触发两种模式)
6. **补偿重试**(失败的同步任务自动重试,退避递增)
7. **Token 自动刷新**OAuth2 token 过期自动续期,不占用重试次数)
**不需要为每个平台写一行业务代码。**
---
## 二、数据库初始化
### 2.1 核心表
首次使用需执行 `sql/init_core_tables.sql`,创建以下 4 张表:
| 表名 | 说明 |
|------|------|
| `api_datasource_platform` | 数据源平台配置(认证信息、限流等) |
| `api_interface` | 接口配置(URL、请求参数、表结构等) |
| `sync_task_log` | 同步任务日志(记录状态,用于补偿) |
| `sync_tracker` | 同步跟踪(记录每个接口的最后同步时间) |
### 2.2 初始化数据
执行 `sql/seed_data.sql` 创建腾讯广告平台 + 4 个接口配置。
如需清空重来:
```sql
ALTER SEQUENCE api_datasource_platform_id_seq RESTART WITH 1;
ALTER SEQUENCE api_interface_id_seq RESTART WITH 1;
DELETE FROM api_interface;
DELETE FROM api_datasource_platform;
\i sql/seed_data.sql
```
其他平台的 seed 数据:
- `sql/seed_data_dingtalk.sql` — 钉钉平台(部门、用户、考勤)
- `sql/seed_data_dingtalk_hrm.sql` — 钉钉 HRM
- `sql/seed_data_dingtalk_salary.sql` — 钉钉智能薪酬
- `sql/seed_data_kuaishou.sql` — 快手平台
- `sql/seed_data_report_kuaishou.sql` — 快手报表引擎演示数据
---
## 三、配置说明
### 3.1 平台管理 (`api_datasource_platform`)
| 字段 | 说明 | 示例 |
|------|------|------|
| `platform_code` | 平台编码(唯一) | `tencent` |
| `platform_name` | 平台名称 | `腾讯广告` |
| `api_base_url` | API 基础地址 | `https://api.e.qq.com/v3.0` |
| `auth_type` | 认证类型 | `OAUTH2` / `TOKEN` / `API_KEY` / `SIGN` / `APP_SIGNATURE` |
| `token` | access_token 明文 | `xxxxx` |
| `client_id` / `client_secret` | OAuth2 凭证 | `xxxxx` |
| `auth_config` | 自定义认证配置 (JSONB) | 详见 3.1.1 |
| `rate_limit_per_minute` | 每分钟请求限制 | `60` |
| `request_timeout_ms` | 请求超时(毫秒) | `30000` |
> **注意:** 认证凭据现在统一存储在数据库中(`api_datasource_platform` 表),不再放在 `config.yml` 中。`config.yml` 中 `tencent.oauth.*` 仅作参考。
#### 3.1.1 `auth_config` 字段详解
```json
{
"token_in_query": true, // token 放在 URL 查询参数(如腾讯广告)
"query_key": "access_token", // 查询参数名,默认 "access_token"
"header_name": "Authorization", // token 放请求头时的头名
"header_format": "Bearer {token}",
"refresh_token": "xxxxx", // OAuth2 refresh_token
"extra_query_params": { // 额外查询参数
"timestamp": "{timestamp}", // {timestamp} → 当前 Unix 时间戳
"nonce": "{nonce}" // {nonce} → 随机字符串
},
"app_key": "xxx", // SIGN 认证的应用 Key
"app_secret": "xxx", // SIGN 认证的应用 Secret
"sign_algorithm": "md5", // 签名算法: md5 / md5_upper
"sign_secret": "xxx" // 签名专用密钥(可选,默认用 app_secret
}
```
#### 3.1.2 认证类型说明
| 类型 | 说明 | token 传递方式 |
|------|------|------|
| `OAUTH2` | OAuth2 授权 | URL query 或 Header,支持自动刷新 |
| `TOKEN` | 静态 Token | `Authorization: Bearer <token>` |
| `API_KEY` | API Key | `X-API-Key <api_key>` |
| `SIGN` | 参数签名 | URL query + `sign` 参数 |
| `APP_SIGNATURE` | App-ID + 签名 | Header: `app-id` + `signature` |
### 3.2 接口管理 (`api_interface`)
| 字段 | 说明 | 示例 |
|------|------|------|
| `platform_id` | 所属平台 ID | `1` |
| `name` / `code` | 接口名称 / 唯一编码 | `图片素材` / `image` |
| `url` | 接口地址(相对路径) | `/images/get` |
| `method` | 请求方法 | `GET` / `POST` |
| `request_config` | 请求配置 (JSONB) | 详见 3.2.1 |
| `response_config` | 响应配置 (JSONB) | 详见 3.2.2 |
| `table_definition` | 表结构定义 (JSONB) | 详见 3.3 |
#### 3.2.1 `request_config` 字段详解
```json
{
"parameters_location": "query", // 参数位置: "query"(URL) / "body"(默认POST)
"page": 1,
"page_size": 100,
"page_param": "page", // 分页参数名(可自定义)
"page_size_param": "page_size",
"pagination_mode": "offset", // 分页模式: 默认页码 / "offset"(偏移量)
"time_field": "last_modified_time", // 增量时间字段名
"time_field_mode": "filtering", // 时间模式: "filtering"(腾讯) / "range"(快手)
"cursor_pagination": true, // 游标分页
"initial_cursor": "", // 初始游标值
"fields": ["field1", "field2"], // 请求字段列表
"body_wrapper_field": "param", // Body 包装字段(快手 API 的 param JSON
"exclude_from_wrapper": ["method"], // 不包装的顶层字段
"row_inject": ["statisticsMonth"], // 将请求参数注入到响应行中
"prefetch": { ... }, // 预取配置(见下文)
"recursive": { ... }, // 递归配置(见下文)
"max_recursive_depth": 20, // 递归最大深度
"full_sync_start_time": 1700000000000 // 全量同步起始时间(毫秒时间戳)
}
```
**`parameters_location` 说明**
- 未设置或 `"body"` → 参数放在请求体中(POST 请求)
- `"query"` → 参数放在 URL 查询字符串中(GET 请求)
-`method=GET` 时,即使不设置也默认走 query
**支持 4 种分页模式:**
| 模式 | 配置 | 说明 |
|------|------|------|
| 普通分页 | 默认 | `page` 递增,支持 `total_page` 判断结束 |
| 偏移量分页 | `pagination_mode: "offset"` | `offset = (page-1) * pageSize` |
| 游标分页 | `cursor_pagination: true` | 从响应 `cursor_field` 取游标继续下一页 |
| hasMore 分页 | `response_config.has_more_field` | 从响应 `has_more` 字段判断是否还有下一页 |
**预取(prefetch**:某些接口需要"先拿列表 → 遍历每个元素查数据"(如先拉账户列表,再遍历拉图片)。并发处理,并发数由 `sync.concurrency` 控制。
```json
"prefetch": {
"url": "/advertiser/get", // 预取接口地址
"method": "GET",
"response_path": "data.list", // 从响应中取值路径
"target_param": "account_id", // 注入主请求的参数名
"value_field": "account_id" // 从预取结果取哪个字段值
}
```
**递归(recursive**:用于树形结构接口(如钉钉部门树),先查根级 → 对每个子节点递归查下级。
```json
"recursive": {
"key_field": "dept_id", // 递归键字段
"target_param": "dept_id" // 注入到请求的参数名
}
```
**时间分片(range mode**:快手等平台使用 `beginTime/endTime` 做时间范围查询,全量同步时自动按 3 天分片循环拉取。
#### 3.2.2 `response_config` 字段详解
系统默认解析的响应格式:
```json
{ "code": 0, "message": "success", "data": { "list": [...], "page_info": { "total_page": N } } }
```
可通过 `response_config` 自定义:
```json
{
"list_path": "data.list", // 数据列表路径
"success_field": "code", // 成功标识字段,默认 "code"
"success_value": 0, // 成功值,默认 0
"message_field": "message", // 错误消息字段
"cursor_field": "data.cursor", // 游标字段路径(游标分页用)
"has_more_field": "data.has_more", // hasMore 字段路径
"single_record": false // 是否为单条记录(自动包装为数组)
}
```
### 3.3 `table_definition` 字段详解
```json
{
"table_name": "tencent_image",
"columns": [
{ "name": "image_id", "type": "VARCHAR(100)", "comment": "图片ID" },
{ "name": "account_id", "type": "BIGINT", "comment": "账户ID" }
],
"conflict_keys": ["image_id", "account_id"]
}
```
**自动添加的列**(无需声明):
| 列名 | 类型 | 说明 |
|------|------|------|
| `id` | BIGSERIAL PRIMARY KEY | 自增主键 |
| `tenant_id` | BIGINT | 租户 ID |
| `creator` | VARCHAR(64) | 创建人 |
| `created_at` | TIMESTAMPTZ | 创建时间 |
| `updater` | VARCHAR(64) | 更新人 |
| `updated_at` | TIMESTAMPTZ | 更新时间 |
| `deleted_at` | TIMESTAMPTZ | 软删除 |
| `raw_data` | JSONB | 原始响应数据(含展平的嵌套字段) |
---
## 四、API 接口
服务端口:`3013`(由 `config.yml``server.address` 配置)
路由规则:系统使用 `RouteRegister` 自动注册,URL 路径由 **struct type name** 转换为 kebab-case 生成。
### 4.1 平台管理 CRUD
基础路径:`/datasource/platform/controller`
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/datasource/platform/controller/createDatasourcePlatform` | 创建平台 |
| GET | `/datasource/platform/controller/listDatasourcePlatforms` | 平台列表 |
| GET | `/datasource/platform/controller/getDatasourcePlatform` | 平台详情 |
| GET | `/datasource/platform/controller/getPlatformByCode` | 按平台编码查询 |
| PUT | `/datasource/platform/controller/updateDatasourcePlatform` | 更新平台 |
| PUT | `/datasource/platform/controller/updateDatasourcePlatformStatus` | 更新平台状态 |
| DELETE | `/datasource/platform/controller/deleteDatasourcePlatform` | 删除平台 |
| GET | `/datasource/platform/controller/getPlatformStatistics` | 平台统计 |
| POST | `/datasource/platform/controller/testPlatformConnection` | 测试平台连接 |
### 4.2 接口管理 CRUD
基础路径:`/api/interface/controller`
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/interface/controller/createApiInterface` | 创建接口 |
| GET | `/api/interface/controller/listApiInterfaces` | 接口列表 |
| GET | `/api/interface/controller/getApiInterface` | 接口详情 |
| PUT | `/api/interface/controller/updateApiInterface` | 更新接口 |
| PUT | `/api/interface/controller/updateApiInterfaceStatus` | 更新接口状态 |
| DELETE | `/api/interface/controller/deleteApiInterface` | 删除接口 |
### 4.3 同步控制
基础路径:`/sync/ctrl`
| 方法 | 路径 | 说明 |
|------|------|------|
| **POST** | **`/sync/ctrl/triggerAll`** | **全平台同步(PPGo_Job 主任务)** |
| **POST** | **`/sync/ctrl/compensate`** | **补偿扫描(PPGo_Job 辅助任务)** |
| POST | `/sync/ctrl/trigger` | 触发单个接口同步 |
| GET | `/sync/ctrl/config` | 查询平台配置 |
**触发单个接口同步示例:**
```bash
curl -X POST http://localhost:3013/sync/ctrl/trigger \
-H 'Content-Type: application/json' \
-d '{"platformCode":"tencent","interfaceCode":"image","fullSync":true}'
```
参数说明:
- `platformCode`:平台编码(必填)
- `interfaceCode`:接口编码(必填)
- `fullSync``true`=全量拉取,`false`=增量(默认自动判断)
响应:
```json
{
"success": true,
"tableName": "tencent_image",
"totalRows": 1500,
"insertedRows": 1450,
"duration": "12.3s"
}
```
**全平台同步示例:**
```bash
curl -X POST http://localhost:3013/sync/ctrl/triggerAll -v
```
响应:
```json
{
"success": true,
"message": "全平台同步完成",
"results": [
{ "platformCode": "tencent", "interfaceCode": "account_relation", "success": true },
{ "platformCode": "tencent", "interfaceCode": "image", "success": true }
]
}
```
### 4.4 报表引擎 API
基础路径:`/report`
| 方法 | 路径 | 说明 |
|------|------|------|
| **POST** | **`/report/extractAll`** | **全量抽取(PPGo_Job 报表任务)** |
| POST | `/report/extract` | 按天数据抽取 |
| POST | `/report/backfill` | 批量回填数据 |
| POST | `/report/query` | 用户选择查询 |
| POST | `/report/autoCreateTable` | 自动创建统计宽表 |
| POST | `/report/initTables` | 初始化系统表 |
| GET | `/report/businesses` | 业务列表 |
| GET/POST/DELETE | `/report/business[/save]` | 业务 CRUD |
| GET | `/report/reports` | 报表列表 |
| GET/POST/DELETE | `/report/report[/save][/saveWithFields]` | 报表 CRUD |
| GET | `/report/fields` | 报表字段列表 |
| GET/POST/DELETE | `/report/field[/save]` | 字段 CRUD |
| GET | `/report/extractConfigs` | 抽取配置列表 |
| GET/POST/DELETE | `/report/extractConfig[/save]` | 抽取配置 CRUD |
### 4.5 管理页面
| 路径 | 说明 |
|------|------|
| `GET /admin` | 数据引擎管理后台(平台/接口管理) |
| `GET /admin/report` | 报表引擎管理页面 |
---
## 五、配置文件 (`config.yml`)
```yaml
server:
address: ":3013" # 服务端口
# 报表引擎配置
report:
extract_enabled: false # 是否启用内部定时抽取(推荐 false,由 PPGo_Job 触发)
# 数据同步配置
sync:
page_size: 100 # 每次分页请求条数
concurrency: 5 # 并发处理数(prefetch 遍历实体时)
retry_count: 3 # 最大重试次数
sync_interval_minutes: 60 # 自动同步间隔(内部调度模式用)
compensation_interval_seconds: 300 # 补偿扫描间隔(内部调度模式用)
auto_sync_enabled: false # 是否启用内部自动同步(推荐 false,由 PPGo_Job 触发)
sync_timeout_minutes: 120 # 单次同步超时(分钟)
default_lookback_days: 89 # 全量同步默认回溯天数
default_tenant_id: 1 # 自动同步使用的租户 ID
```
---
## 六、调度策略(核心变更)
系统支持 **两种调度模式**,通过配置开关切换:
### 6.1 推荐模式:PPGo_Job 外部调度
```
config.yml:
report.extract_enabled: false
sync.auto_sync_enabled: false ← 默认值
```
内部调度器不启动,由 PPGo_Job 定时调用 HTTP 端点触发。详情见第八章。
### 6.2 备选模式:内部自动调度
```
config.yml:
report.extract_enabled: true
sync.auto_sync_enabled: true
```
- 同步引擎:进程启动后自动循环执行全平台同步,每次完成后等待 `sync_interval_minutes` 再继续
- 报表引擎:每天凌晨 2:00 执行全量抽取(gcron `0 0 2 * * *`
- 补偿调度器:独立于 `auto_sync_enabled`,始终随服务启动(内部模式)
### 6.3 异常中断恢复
- 同步开始前写 `sync_tracker.sync_status='running'`
- 同步完成后写 `'success'`
- 重启检测到 `'running'` → 日志告警 → 重新全量同步
---
## 七、Token 自动刷新机制
### 7.1 工作原理
系统支持 OAuth2 token 过期自动刷新,**不需要人工介入**:
1. `ApiClient` 发送请求 → 收到 401 或 token 过期错误
2. 自动调用平台的 `RefreshToken` 回调函数(如 `RefreshTencentToken`
3. 刷新成功后**立即重试**原请求(不消耗退避重试次数)
4. 每个 `doRequest` 最多刷新一次,避免死循环
5. 刷新后的 token 同时更新内存和数据库
```
doRequest
├── execute() → HTTP 401 / token_expired
├── isTokenExpiredError() → true
├── RefreshTencentToken()
│ ├── POST https://api.e.qq.com/oauth/refresh_token
│ ├── 更新 platform.Token / platform.AccessToken
│ └── 更新数据库 api_datasource_platform.token + auth_config
└── continue(用新 token 重试,不消耗重试次数)
```
### 7.2 token 过期错误检测
系统通过关键字匹配识别 token 过期错误:
```go
tokenExpiredKeywords := []string{
"TOKEN过期", "token过期", "token_expired", "Token过期",
"result=28", // 快手
"access_token", "token已失效", "token无效",
}
```
### 7.3 token 过期时的处理策略
| 场景 | 行为 |
|------|------|
| 自动同步中 | 跳过该平台剩余接口,继续处理下一个平台 |
| 补偿扫描中 | 标记为 `manual_review`,等待人工处理,不再重试 |
| 手动触发 | 自动刷新后重试 |
### 7.4 腾讯广告 OAuth2 刷新(当前已实现)
已支持腾讯广告 OAuth2 token 自动刷新,使用 `refresh_token` 换取新的 `access_token`
- 刷新端点:`POST https://api.e.qq.com/oauth/refresh_token`
- 超时:30 秒
- 互斥锁:防止并发刷新(`refreshTokenMu sync.Mutex`
---
## 八、PPGo_Job 定时任务配置
系统推荐由 **PPGo_Job** 外部调度,需在 PPGo_Job 中配置以下任务。
### 任务 1:全平台数据同步
| 字段 | 值 |
|------|-----|
| 任务名称 | `数据引擎 - 全平台同步` |
| 请求 URL | `http://<data-engine-host>:3013/sync/ctrl/triggerAll` |
| 请求方法 | `POST` |
| Header | `Content-Type: application/json` |
| Body | 空(不需要请求体) |
| Cron | `0 0 * * * ?`(每小时整点) |
| 超时 | `7200` 秒(2小时) |
| 说明 | 遍历所有 ACTIVE 平台下有 table_definition 的接口,自动判断全量/增量。所有平台的同步都在同一任务中完成 |
执行流程:
```
triggerAll
└── runAutoSync()
├── 查所有 ACTIVE 平台
├── 对每个平台 → 查所有有 table_definition 的接口
│ ├── 查 sync_tracker → 有记录=增量,无记录=全量
│ ├── SyncByConfig(platformCode, interfaceCode)
│ └── token 过期 → 跳过该平台剩余接口
└── 返回每个接口的执行结果
```
### 任务 2:失败任务补偿
| 字段 | 值 |
|------|-----|
| 任务名称 | `数据引擎 - 补偿扫描` |
| 请求 URL | `http://<data-engine-host>:3013/sync/ctrl/compensate` |
| 请求方法 | `POST` |
| Header | `Content-Type: application/json` |
| Body | 空 |
| Cron | `0 0/5 * * * ?`(每5分钟) |
| 超时 | `600` 秒(10分钟) |
| 说明 | 扫描 sync_task_log 中 status=failed 的任务,按退避策略重试 |
补偿退避策略:
```
第1次重试 → 等待 5分钟
第2次重试 → 等待 15分钟
第3次重试 → 等待 30分钟
第4次重试 → 等待 60分钟
第5次重试 → 等待 120分钟(封顶)
达最大次数 → 标记 manual_review,等待人工处理
```
### 任务 3:报表每日抽取(可选)
| 字段 | 值 |
|------|-----|
| 任务名称 | `数据引擎 - 每日抽取` |
| 请求 URL | `http://<data-engine-host>:3013/report/extractAll` |
| 请求方法 | `POST` |
| Header | `Content-Type: application/json` |
| Body | 空 |
| Cron | `0 0 2 * * ?`(每天凌晨2点) |
| 超时 | `3600` 秒(1小时) |
| 说明 | 遍历所有启用的业务+报表,抽取前一天数据到统计宽表 |
---
## 九、已配置平台
### 9.1 腾讯广告
| 项目 | 值 |
|------|-----|
| platform_code | `tencent` |
| API Base | `https://api.e.qq.com/v3.0` |
| 认证 | OAUTH2access_token 在 URL query 中,支持自动刷新) |
**4 个接口:**
| 接口编码 | 名称 | 方法+路径 | 同步模式 | 增量 |
|---------|------|----------|---------|------|
| `account_relation` | 账户列表 | `GET /advertiser/get` | 普通分页 | 不支持(全量+去重) |
| `image` | 图片素材 | `GET /images/get` | **预取**(遍历账户) | ✅ `last_modified_time` |
| `video` | 视频素材 | `GET /videos/get` | **预取**(遍历账户) | ✅ `last_modified_time` |
| `audio` | 音频素材 | `POST /muse_audios/get` | 普通分页 POST | 不支持(全量+去重) |
**图片/视频同步流程:**
```
SyncByConfig("tencent", "image")
├── ① 预取: 分页拉取 /advertiser/get → 所有 account_id
│ 数据同时存入 tencent_account_relation 表
├── ② 并发遍历账户(config.yml concurrency=5
│ 每个 account_id → GET /images/get?account_id=xxx&page=1&page_size=100
│ 自动补充 filtering(增量时)
│ 结果 upsert 到 tencent_image
└── ③ 更新 sync_tracker 记录同步时间
```
### 9.2 其他平台
| 平台 | seed 文件 | 接口数 |
|------|----------|--------|
| 钉钉 | `seed_data_dingtalk.sql` | ~10个 |
| 钉钉 HRM | `seed_data_dingtalk_hrm.sql` | ~5个 |
| 钉钉智能薪酬 | `seed_data_dingtalk_salary.sql` | ~3个 |
| 快手 | `seed_data_kuaishou.sql` | ~5个 |
---
## 十、补偿机制
### 10.1 工作原理
1. 同步失败 → 自动写入 `sync_task_log`status=failed
2. 补偿扫描(由 PPGo_Job 调用 `/sync/ctrl/compensate`)扫描 failed 记录
3. 对未达最大重试次数的任务,调用 `SyncByConfig` 重试
4. 重试间隔按退避策略递增:5min → 15min → 30min → 60min → 120min
5. 达最大次数 → 标记 `manual_review`,等待人工介入
6. Token 过期错误 → 直接标记 `manual_review`,不再重试
### 10.2 配置
```yaml
sync:
compensation_interval_seconds: 300 # 扫描间隔(内部调度用)
retry_count: 3 # 最大重试次数
```
---
## 十一、快速开始
```bash
# 1. 建核心表
PGPASSWORD='xxx' psql -h <host> -p 15432 -U postgres -d engine -f sql/init_core_tables.sql
# 2. 初始化腾讯广告数据
PGPASSWORD='xxx' psql -h <host> -p 15432 -U postgres -d engine -f sql/seed_data.sql
# 3. 配置 PPGo_Job(推荐)或启用内部调度
# 见第六章调度策略 和 第八章 PPGo_Job 配置
# 4. 启动服务
cd D:/WorkSpace/HDWL/data-engine
go run main.go
# 5. 手动测试同步
curl -X POST http://localhost:3013/sync/ctrl/trigger \
-H 'Content-Type: application/json' \
-d '{"platformCode":"tencent","interfaceCode":"image","fullSync":true}'
```
---
## 十二、常见问题
**Q: 响应格式不符合 `{code: 0, data: {list: [...]}}` 怎么办?**
通过接口管理的 `response_config` 自定义 `list_path``success_field``success_value`。如需新的解析逻辑,修改 `dynamic_sync.go``parseRespExt` 函数。
**Q: 如何添加新平台?**
1. 调用平台管理 API 创建平台配置
2. 调用接口管理 API 创建接口(含 `table_definition`
3. 平台状态设为 `ACTIVE`,接口状态设为 `active`
4. 系统下次同步时自动建表并拉取数据
**Q: prefetch 的响应格式要求?**
必须是 JSON`response_path` 指向一个数组。如 `response_path: "data.list"``{"data":{"list":[...]}}` 取值。
**Q: 如何排查同步失败?**
1. 查询 `sync_task_log` 表看失败记录和错误信息
2. `GET /sync/ctrl/config?platformCode=tencent` 查看平台配置
3. 检查服务日志中 `equivalent curl` 输出,手动复现请求
4. PPGo_Job 补偿任务会自动重试,日志会打印重试过程
**Q: Token 过期了怎么办?**
如果配置了 OAuth2 且 `refresh_token` 有效,系统会自动刷新。手动强制刷新可用 `_update_token.go`
```bash
# 先修改 _update_token.go 中 token 和 refresh_token 的值
cd D:/WorkSpace/HDWL/data-engine
go run _update_token.go
```
**Q: 内部调度和 PPGo_Job 调度有什么区别?**
| 对比项 | 内部调度 | PPGo_Job 调度(推荐) |
|-------|---------|-------------------|
| 依赖 | 无外部依赖 | 需要 PPGo_Job 服务 |
| 配置 | `auto_sync_enabled: true` | `auto_sync_enabled: false` |
| 控制 | 进程内循环,重启失效 | 统一调度中心,支持暂停/手动执行 |
| 多服务协调 | 不支持 | 支持 |
| 运维 | 需要登录服务器 | 通过 Web UI 管理所有任务 |
+271
View File
@@ -0,0 +1,271 @@
#!/usr/bin/env python3
"""Add pagination to all list endpoints + change status filter to deleted_at."""
# ========== 1. Controller: request/response structs + handlers ==========
ctrl_path = '/Users/xujiaqian/GolandProjects/HDWL/data-engine/controller/report/report_controller.go'
with open(ctrl_path) as f:
ctrl = f.read()
# 1a. listBusinessesReq - add PageReq embed
ctrl = ctrl.replace(
'type listBusinessesReq struct {\n\t\tg.Meta `path:"/businesses" method:"get" tags:"报表引擎" summary:"业务列表"`\n\t}',
'type listBusinessesReq struct {\n\t\tg.Meta `path:"/businesses" method:"get" tags:"报表引擎" summary:"业务列表"`\n\t\tmodel.PageReq\n\t}'
)
# 1b. listBusinessesRes - add PageRes embed + rename List
ctrl = ctrl.replace(
'type listBusinessesRes struct {\n\t\tList []model.BusinessConfig `json:"list"`\n\t}',
'type listBusinessesRes struct {\n\t\tList []model.BusinessConfig `json:"list"`\n\t\tmodel.PageRes\n\t}'
)
# 1c. ListBusinesses handler - pass page params + calc TotalPages
ctrl = ctrl.replace(
'\t\tlist, err := svc().GetAllBusinesses(ctx)',
'\t\tlist, total, err := svc().GetAllBusinesses(ctx, req.PageNum, req.PageSize)'
)
ctrl = ctrl.replace(
'\t\treturn &listBusinessesRes{List: list}, nil',
'\t\ttotalPages := (total + req.PageSize - 1) / req.PageSize\n\t\treturn &listBusinessesRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil'
)
# 1d. listReportsReq - add PageReq embed
ctrl = ctrl.replace(
'type listReportsReq struct {\n\t\tg.Meta `path:"/reports" method:"get" tags:"报表引擎" summary:"报表列表"`\n\t\tBusinessCode string `json:"businessCode" v:"required"`\n\t}',
'type listReportsReq struct {\n\t\tg.Meta `path:"/reports" method:"get" tags:"报表引擎" summary:"报表列表"`\n\t\tBusinessCode string `json:"businessCode" v:"required"`\n\t\tmodel.PageReq\n\t}'
)
# 1e. listReportsRes - add PageRes embed
ctrl = ctrl.replace(
'type listReportsRes struct {\n\t\tList []model.ReportConfig `json:"list"`\n\t}',
'type listReportsRes struct {\n\t\tList []model.ReportConfig `json:"list"`\n\t\tmodel.PageRes\n\t}'
)
# 1f. ListReports handler
ctrl = ctrl.replace(
'\t\tlist, err := svc().GetAllReports(ctx, req.BusinessCode)',
'\t\tlist, total, err := svc().GetAllReports(ctx, req.BusinessCode, req.PageNum, req.PageSize)'
)
ctrl = ctrl.replace(
'\t\treturn &listReportsRes{List: list}, nil',
'\t\ttotalPages := (total + req.PageSize - 1) / req.PageSize\n\t\treturn &listReportsRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil'
)
# 1g. getExtractConfigsReq - add PageReq embed
ctrl = ctrl.replace(
'type getExtractConfigsReq struct {\n\t\tg.Meta `path:"/extractConfigs" method:"get" tags:"报表引擎" summary:"抽取配置列表"`\n\t\tBusinessCode string `json:"businessCode" v:"required"`\n\t\tReportCode string `json:"reportCode" v:"required"`\n\t}',
'type getExtractConfigsReq struct {\n\t\tg.Meta `path:"/extractConfigs" method:"get" tags:"报表引擎" summary:"抽取配置列表"`\n\t\tBusinessCode string `json:"businessCode" v:"required"`\n\t\tReportCode string `json:"reportCode" v:"required"`\n\t\tmodel.PageReq\n\t}'
)
# 1h. getExtractConfigsRes - add PageRes embed
ctrl = ctrl.replace(
'type getExtractConfigsRes struct {\n\t\tList []model.ExtractConfig `json:"list"`\n\t}',
'type getExtractConfigsRes struct {\n\t\tList []model.ExtractConfig `json:"list"`\n\t\tmodel.PageRes\n\t}'
)
# 1i. GetExtractConfigs handler
ctrl = ctrl.replace(
'\t\tlist, err := svc().GetExtractConfigs(ctx, req.BusinessCode, req.ReportCode)',
'\t\tlist, total, err := svc().GetExtractConfigs(ctx, req.BusinessCode, req.ReportCode, req.PageNum, req.PageSize)'
)
ctrl = ctrl.replace(
'\t\treturn &getExtractConfigsRes{List: list}, nil',
'\t\ttotalPages := (total + req.PageSize - 1) / req.PageSize\n\t\treturn &getExtractConfigsRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil'
)
with open(ctrl_path, 'w') as f:
f.write(ctrl)
print('✅ Controller done')
# ========== 2. API service layer ==========
api_path = '/Users/xujiaqian/GolandProjects/HDWL/data-engine/common/report/api.go'
with open(api_path) as f:
api = f.read()
# 2a. GetAllBusinesses
api = api.replace(
'func (s *ReportService) GetAllBusinesses(ctx context.Context) ([]model.BusinessConfig, error) {\n\t\treturn s.configLoader.GetAllBusinesses(ctx)',
'func (s *ReportService) GetAllBusinesses(ctx context.Context, pageNum, pageSize int) ([]model.BusinessConfig, int, error) {\n\t\treturn s.configLoader.GetAllBusinesses(ctx, pageNum, pageSize)'
)
# 2b. GetAllReports
api = api.replace(
'func (s *ReportService) GetAllReports(ctx context.Context, businessCode string) ([]model.ReportConfig, error) {\n\t\treturn s.configLoader.GetAllReports(ctx, businessCode)',
'func (s *ReportService) GetAllReports(ctx context.Context, businessCode string, pageNum, pageSize int) ([]model.ReportConfig, int, error) {\n\t\treturn s.configLoader.GetAllReports(ctx, businessCode, pageNum, pageSize)'
)
# 2c. GetExtractConfigs
api = api.replace(
'func (s *ReportService) GetExtractConfigs(ctx context.Context, businessCode, reportCode string) ([]model.ExtractConfig, error) {\n\t\treturn s.configLoader.GetExtractConfigs(ctx, businessCode, reportCode)',
'func (s *ReportService) GetExtractConfigs(ctx context.Context, businessCode, reportCode string, pageNum, pageSize int) ([]model.ExtractConfig, int, error) {\n\t\treturn s.configLoader.GetExtractConfigs(ctx, businessCode, reportCode, pageNum, pageSize)'
)
with open(api_path, 'w') as f:
f.write(api)
print('✅ Service done')
# ========== 3. Loader layer ==========
loader_path = '/Users/xujiaqian/GolandProjects/HDWL/data-engine/common/report/config/loader.go'
with open(loader_path) as f:
loader = f.read()
# 3a. GetAllBusinesses - add deleted_at + pagination
old_biz = '''func (l *ConfigLoader) GetAllBusinesses(ctx context.Context) ([]model.BusinessConfig, error) {
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_business_config WHERE status = $1 ORDER BY id ASC",
\t\tmodel.StatusActive)
\tif err != nil {
\t\treturn nil, err
\t}
\tvar businesses []model.BusinessConfig
\tfor _, record := range r {
\t\tvar biz model.BusinessConfig
\t\tif err := record.Struct(&biz); err != nil {
\t\t\treturn nil, err
\t\t}
\t\tbusinesses = append(businesses, biz)
\t}
\treturn businesses, nil
}'''
new_biz = '''func (l *ConfigLoader) GetAllBusinesses(ctx context.Context, pageNum, pageSize int) ([]model.BusinessConfig, int, error) {
\t// 查询总数
\ttotal, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT COUNT(*) AS cnt FROM report_business_config WHERE deleted_at IS NULL")
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tcount := 0
\tif !total.IsEmpty() {
\t\tcount = total[0]["cnt"].Int()
\t}
\toffset := (pageNum - 1) * pageSize
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_business_config WHERE deleted_at IS NULL ORDER BY id ASC LIMIT $1 OFFSET $2",
\t\tpageSize, offset)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tvar businesses []model.BusinessConfig
\tfor _, record := range r {
\t\tvar biz model.BusinessConfig
\t\tif err := record.Struct(&biz); err != nil {
\t\t\treturn nil, 0, err
\t\t}
\t\tbusinesses = append(businesses, biz)
\t}
\treturn businesses, count, nil
}'''
loader = loader.replace(old_biz, new_biz, 1)
# 3b. GetAllReports - add deleted_at + pagination
old_rpt = '''func (l *ConfigLoader) GetAllReports(ctx context.Context, businessCode string) ([]model.ReportConfig, error) {
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_report_config WHERE business_code = $1 ORDER BY id ASC",
\t\tbusinessCode)
\tif err != nil {
\t\treturn nil, err
\t}
\tvar reports []model.ReportConfig
\tfor _, record := range r {
\t\tvar rpt model.ReportConfig
\t\tif err := record.Struct(&rpt); err != nil {
\t\t\treturn nil, err
\t\t}
\t\treports = append(reports, rpt)
\t}
\treturn reports, nil
}'''
new_rpt = '''func (l *ConfigLoader) GetAllReports(ctx context.Context, businessCode string, pageNum, pageSize int) ([]model.ReportConfig, int, error) {
\t// 查询总数
\ttotal, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT COUNT(*) AS cnt FROM report_report_config WHERE business_code = $1 AND deleted_at IS NULL",
\t\tbusinessCode)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tcount := 0
\tif !total.IsEmpty() {
\t\tcount = total[0]["cnt"].Int()
\t}
\toffset := (pageNum - 1) * pageSize
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_report_config WHERE business_code = $1 AND deleted_at IS NULL ORDER BY id ASC LIMIT $2 OFFSET $3",
\t\tbusinessCode, pageSize, offset)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tvar reports []model.ReportConfig
\tfor _, record := range r {
\t\tvar rpt model.ReportConfig
\t\tif err := record.Struct(&rpt); err != nil {
\t\t\treturn nil, 0, err
\t\t}
\t\treports = append(reports, rpt)
\t}
\treturn reports, count, nil
}'''
loader = loader.replace(old_rpt, new_rpt, 1)
# 3c. GetExtractConfigs - add deleted_at + pagination
old_ec = '''func (l *ConfigLoader) GetExtractConfigs(ctx context.Context, businessCode, reportCode string) ([]model.ExtractConfig, error) {
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND status = $3 AND is_enabled = $4",
\t\tbusinessCode, reportCode, model.StatusActive, true)
\tif err != nil {
\t\treturn nil, err
\t}
\tvar configs []model.ExtractConfig
\tfor _, record := range r {
\t\tvar ec model.ExtractConfig
\t\tif err := record.Struct(&ec); err != nil {
\t\t\treturn nil, err
\t\t}
\t\tconfigs = append(configs, ec)
\t}
\tl.mu.Lock()
\tl.extractCache[businessCode+":"+reportCode] = configs
\tl.mu.Unlock()
\treturn configs, nil
}'''
new_ec = '''func (l *ConfigLoader) GetExtractConfigs(ctx context.Context, businessCode, reportCode string, pageNum, pageSize int) ([]model.ExtractConfig, int, error) {
\t// 查询总数
\ttotal, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT COUNT(*) AS cnt FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL",
\t\tbusinessCode, reportCode)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tcount := 0
\tif !total.IsEmpty() {
\t\tcount = total[0]["cnt"].Int()
\t}
\toffset := (pageNum - 1) * pageSize
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY id ASC LIMIT $3 OFFSET $4",
\t\tbusinessCode, reportCode, pageSize, offset)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tvar configs []model.ExtractConfig
\tfor _, record := range r {
\t\tvar ec model.ExtractConfig
\t\tif err := record.Struct(&ec); err != nil {
\t\t\treturn nil, 0, err
\t\t}
\t\tconfigs = append(configs, ec)
\t}
\treturn configs, count, nil
}'''
loader = loader.replace(old_ec, new_ec, 1)
with open(loader_path, 'w') as f:
f.write(loader)
print('✅ Loader done')
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
import re
path = '/Users/xujiaqian/GolandProjects/HDWL/data-engine/common/report/config/loader.go'
with open(path) as f:
content = f.read()
# Fix `\$1` → `$1` (the \ came from Python escaping in previous script)
content = content.replace('\\$1', '$1')
content = content.replace('\\$2', '$2')
with open(path, 'w') as f:
f.write(content)
print('Fixed')
+255
View File
@@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""Final pass: apply all list-pagination + deleted_at filter changes."""
import re
# ============= LOADER =============
lp = '/Users/xujiaqian/GolandProjects/HDWL/data-engine/common/report/config/loader.go'
with open(lp) as f:
l = f.read()
# 1. GetAllBusinesses: status=ACTIVE -> deleted_at IS NULL
l = l.replace(
'"SELECT * FROM report_business_config WHERE status = $1 ORDER BY id ASC"',
'"SELECT * FROM report_business_config WHERE deleted_at IS NULL ORDER BY id ASC"'
)
l = l.replace('\t\tmodel.StatusActive)', ')\n\t\t// (removed status filter)')
# 2. GetAllReports: add deleted_at IS NULL (keep existing)
# Already has no status filter, just verify
# (The user already removed status filter from GetAllReports earlier)
# 3. GetFields: status=ACTIVE -> deleted_at IS NULL
l = l.replace(
'"SELECT * FROM report_field_config WHERE business_code = $1 AND report_code = $2 AND status = $3 ORDER BY sort_order ASC"',
'"SELECT * FROM report_field_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY sort_order ASC"'
)
# Remove StatusActive arg from GetFields call
l = l.replace(
'businessCode, reportCode, model.StatusActive)',
'businessCode, reportCode)'
)
# 4. GetExtractConfigs: status=ACTIVE AND is_enabled=true -> deleted_at IS NULL
l = l.replace(
'"SELECT * FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND status = $3 AND is_enabled = $4"',
'"SELECT * FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY id ASC"'
)
l = l.replace(
'businessCode, reportCode, model.StatusActive, true)',
'businessCode, reportCode)'
)
# 5. Add GetAllFields (for edit query, returns all non-deleted regardless of status)
if 'GetAllFields' not in l:
# Add after GetFields function ends
marker = 'return fields, nil\n}\n\n// GetFieldMap 获取字段配置Map'
add = '''return fields, nil
}
// GetAllFields 获取报表全部字段(含 INACTIVE,不含已删除,用于编辑回显)
func (l *ConfigLoader) GetAllFields(ctx context.Context, businessCode, reportCode string) ([]model.FieldConfig, error) {
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_field_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY sort_order ASC",
\t\tbusinessCode, reportCode)
\tif err != nil {
\t\treturn nil, err
\t}
\tvar fields []model.FieldConfig
\tfor _, record := range r {
\t\tvar f model.FieldConfig
\t\tif err := record.Struct(&f); err != nil {
\t\t\treturn nil, err
\t\t}
\t\tif f.ValidAggregates == nil {
\t\t\tf.ValidAggregates = []string{}
\t\t}
\t\tif f.FilterOperators == nil {
\t\t\tf.FilterOperators = []string{"=", "!=", ">", "<", ">=", "<=", "IN", "LIKE", "BETWEEN"}
\t\t}
\t\tfields = append(fields, f)
\t}
\treturn fields, nil
}
// GetFieldMap 获取字段配置Map'''
l = l.replace(marker, add, 1)
# 6. Add paginated list methods
# ListBusinesses
if 'ListBusinesses' not in l:
add = '''// ListBusinesses 分页获取业务列表
func (l *ConfigLoader) ListBusinesses(ctx context.Context, pageNum, pageSize int) ([]model.BusinessConfig, int, error) {
\ttotal, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT COUNT(*) AS cnt FROM report_business_config WHERE deleted_at IS NULL")
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tcount := 0
\tif !total.IsEmpty() {
\t\tcount = total[0]["cnt"].Int()
\t}
\toffset := (pageNum - 1) * pageSize
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_business_config WHERE deleted_at IS NULL ORDER BY id ASC LIMIT $1 OFFSET $2",
\t\tpageSize, offset)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tvar businesses []model.BusinessConfig
\tfor _, record := range r {
\t\tvar biz model.BusinessConfig
\t\tif err := record.Struct(&biz); err != nil {
\t\t\treturn nil, 0, err
\t\t}
\t\tbusinesses = append(businesses, biz)
\t}
\treturn businesses, count, nil
}
// ListReports 分页获取报表列表
func (l *ConfigLoader) ListReports(ctx context.Context, businessCode string, pageNum, pageSize int) ([]model.ReportConfig, int, error) {
\ttotal, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT COUNT(*) AS cnt FROM report_report_config WHERE business_code = $1 AND deleted_at IS NULL",
\t\tbusinessCode)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tcount := 0
\tif !total.IsEmpty() {
\t\tcount = total[0]["cnt"].Int()
\t}
\toffset := (pageNum - 1) * pageSize
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_report_config WHERE business_code = $1 AND deleted_at IS NULL ORDER BY id ASC LIMIT $2 OFFSET $3",
\t\tbusinessCode, pageSize, offset)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tvar reports []model.ReportConfig
\tfor _, record := range r {
\t\tvar rpt model.ReportConfig
\t\tif err := record.Struct(&rpt); err != nil {
\t\t\treturn nil, 0, err
\t\t}
\t\treports = append(reports, rpt)
\t}
\treturn reports, count, nil
}
// ListExtractConfigs 分页获取抽取配置列表
func (l *ConfigLoader) ListExtractConfigs(ctx context.Context, businessCode, reportCode string, pageNum, pageSize int) ([]model.ExtractConfig, int, error) {
\ttotal, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT COUNT(*) AS cnt FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL",
\t\tbusinessCode, reportCode)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tcount := 0
\tif !total.IsEmpty() {
\t\tcount = total[0]["cnt"].Int()
\t}
\toffset := (pageNum - 1) * pageSize
\tr, err := gfdb.DB(ctx).GetAll(ctx,
\t\t"SELECT * FROM report_extract_config WHERE business_code = $1 AND report_code = $2 AND deleted_at IS NULL ORDER BY id ASC LIMIT $3 OFFSET $4",
\t\tbusinessCode, reportCode, pageSize, offset)
\tif err != nil {
\t\treturn nil, 0, err
\t}
\tvar configs []model.ExtractConfig
\tfor _, record := range r {
\t\tvar ec model.ExtractConfig
\t\tif err := record.Struct(&ec); err != nil {
\t\t\treturn nil, 0, err
\t\t}
\t\tconfigs = append(configs, ec)
\t}
\tl.mu.Lock()
\tl.extractCache[businessCode+":"+reportCode] = configs
\tl.mu.Unlock()
\treturn configs, count, nil
}
'''
# Insert before the "//" comment separator near the end
marker2 = '\n// GetReportFields 获取报表可用字段(按角色分类)'
if marker2 in l:
l = l.replace(marker2, add + marker2, 1)
else:
l += add
with open(lp, 'w') as f:
f.write(l)
print('✅ Loader')
# ============= SERVICE (api.go) =============
ap = '/Users/xujiaqian/GolandProjects/HDWL/data-engine/common/report/api.go'
with open(ap) as f:
a = f.read()
# Add ListBusinesses/ListReports/ListExtractConfigs service methods
if 'func (s *ReportService) ListBusinesses' not in a:
add_svc = '''
// ListBusinesses 分页获取业务列表
func (s *ReportService) ListBusinesses(ctx context.Context, pageNum, pageSize int) ([]model.BusinessConfig, int, error) {
\treturn s.configLoader.ListBusinesses(ctx, pageNum, pageSize)
}
// ListReports 分页获取报表列表
func (s *ReportService) ListReports(ctx context.Context, businessCode string, pageNum, pageSize int) ([]model.ReportConfig, int, error) {
\treturn s.configLoader.ListReports(ctx, businessCode, pageNum, pageSize)
}
// ListExtractConfigs 分页获取抽取配置列表
func (s *ReportService) ListExtractConfigs(ctx context.Context, businessCode, reportCode string, pageNum, pageSize int) ([]model.ExtractConfig, int, error) {
\treturn s.configLoader.ListExtractConfigs(ctx, businessCode, reportCode, pageNum, pageSize)
}
'''
# Insert after the service type definition
a = a.replace(
's.configLoader.GetAllBusinesses(ctx)\n}',
's.configLoader.GetAllBusinesses(ctx)\n}' + add_svc
)
with open(ap, 'w') as f:
f.write(a)
print('✅ Service')
# ============= CONTROLLER =============
cp = '/Users/xujiaqian/GolandProjects/HDWL/data-engine/controller/report/report_controller.go'
with open(cp) as f:
c = f.read()
# Fix ListBusinesses handler
c = c.replace(
'list, err := svc().GetAllBusinesses(ctx)',
'list, total, err := svc().ListBusinesses(ctx, req.PageNum, req.PageSize)'
)
c = c.replace(
'return &listBusinessesRes{List: list}, nil',
'totalPages := (total + req.PageSize - 1) / req.PageSize\n\t\treturn &listBusinessesRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil'
)
# Fix ListReports handler
c = c.replace(
'list, err := svc().GetAllReports(ctx, req.BusinessCode)',
'list, total, err := svc().ListReports(ctx, req.BusinessCode, req.PageNum, req.PageSize)'
)
c = c.replace(
'return &listReportsRes{List: list}, nil',
'totalPages := (total + req.PageSize - 1) / req.PageSize\n\t\treturn &listReportsRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil'
)
# Fix GetExtractConfigs handler
c = c.replace(
'list, err := svc().GetExtractConfigs(ctx, req.BusinessCode, req.ReportCode)',
'list, total, err := svc().ListExtractConfigs(ctx, req.BusinessCode, req.ReportCode, req.PageNum, req.PageSize)'
)
c = c.replace(
'return &getExtractConfigsRes{List: list}, nil',
'totalPages := (total + req.PageSize - 1) / req.PageSize\n\t\treturn &getExtractConfigsRes{List: list, PageRes: model.PageRes{Total: total, Page: req.PageNum, PageSize: req.PageSize, TotalPages: totalPages}}, nil'
)
with open(cp, 'w') as f:
f.write(c)
print('✅ Controller')
print('\nAll done!')
+82 -47
View File
@@ -1,34 +1,44 @@
module cid
module dataengine
go 1.25.5
go 1.26.0
require (
gitea.com/red-future/common v0.0.4
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.0
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5
github.com/gogf/gf/v2 v2.10.0
github.com/olekukonko/errors v1.1.0
golang.org/x/net v0.47.0
gitea.redpowerfuture.com/red-future/common v0.0.29
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2
github.com/gogf/gf/v2 v2.10.2
github.com/olekukonko/errors v1.3.0
github.com/sirupsen/logrus v1.9.4
)
//replace gitea.com/red-future/common => ../common
//replace gitea.redpowerfuture.com/red-future/common => ../common
require (
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emirpasic/gods/v2 v2.0.0-alpha // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fatih/color v1.19.0 // indirect
github.com/fsnotify/fsnotify v1.10.1 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-ego/gse v1.0.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 // indirect
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
@@ -37,12 +47,13 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/flatbuffers v25.12.19+incompatible // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/grokify/html-strip-tags-go v0.1.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/hashicorp/consul/api v1.26.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/hashicorp/consul/api v1.33.5 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
@@ -51,42 +62,66 @@ require (
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/lib/pq v1.12.3 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mailru/easyjson v0.9.2 // indirect
github.com/mattn/go-colorable v0.1.15 // indirect
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mattn/go-runewidth v0.0.24 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/olekukonko/ll v0.0.9 // indirect
github.com/olekukonko/tablewriter v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
github.com/olekukonko/ll v0.1.8 // indirect
github.com/olekukonko/tablewriter v1.1.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/redis/go-redis/v9 v9.12.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/r3labs/diff/v2 v2.15.1 // indirect
github.com/redis/go-redis/v9 v9.21.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/tiger1103/gfast-token v1.0.10 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.mongodb.org/mongo-driver/v2 v2.4.0 // indirect
go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
github.com/vcaesar/cedar v0.30.0 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver/v2 v2.6.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
golang.org/x/crypto v0.44.0 // indirect
golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae // indirect
golang.org/x/sync v0.18.0 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect
golang.org/x/net v0.56.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sys v0.46.0 // indirect
golang.org/x/term v0.44.0 // indirect
golang.org/x/text v0.38.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
google.golang.org/grpc v1.79.3 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.35.3 // indirect
k8s.io/apimachinery v0.35.3 // indirect
k8s.io/client-go v0.35.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
+552
View File
@@ -0,0 +1,552 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
gitea.redpowerfuture.com/red-future/common v0.0.29 h1:5McaN5pSewvrLUHQzWMX6EaUvD+B5I5bMYoU+clHJk4=
gitea.redpowerfuture.com/red-future/common v0.0.29/go.mod h1:50U1Xi+Ie56z09S5LQbZvaken0Mxv3OeS9LgR7U/ZRY=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0=
github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs=
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emirpasic/gods/v2 v2.0.0-alpha h1:dwFlh8pBg1VMOXWGipNMRt8v96dKAIvBehtCt6OtunU=
github.com/emirpasic/gods/v2 v2.0.0-alpha/go.mod h1:W0y4M2dtBB9U5z3YlghmpuUhiaZT2h6yoeE+C1sCp6A=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-ego/gse v1.0.2 h1:+27lYFPhQEhA9igtdOsJPRKYL/k3TwYsxBF5jr6KFv4=
github.com/go-ego/gse v1.0.2/go.mod h1:Fy35G+q7VV7Et1zIKO8o/sW1kkugV3znXap/lF/11zc=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2 h1:u8EpP24GkprogROnJ7htMov9Fc66pTP1eVYrWxiCYOs=
github.com/gogf/gf/contrib/drivers/pgsql/v2 v2.10.2/go.mod h1:GmvM3r8GVByVMi4RD2+MCs5+CfxVXPMeT8mVDkAaAXE=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2 h1:iTQegT+lEg/wDKvj2mi3W1wrdrwFarjokf88EXVVgu4=
github.com/gogf/gf/contrib/nosql/redis/v2 v2.10.2/go.mod h1:ZRw3GNz5cq4uYrW4TPSVyrYWaoqzujKdWro/AOcGBaE=
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ=
github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k=
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec=
github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5/go.mod h1:cfzTn2HS9RDX8f5pUVkbGxUWcSosouqfNQ1G6cY0V88=
github.com/gogf/gf/v2 v2.10.2 h1:46IO0Uc8e85/FqdftJFskfDejJLBL0JBnGS5qOftUu8=
github.com/gogf/gf/v2 v2.10.2/go.mod h1:Svl1N+E8G/QshU2DUbh/3J/AJauqCgUnxHurXWR4Qx0=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/flatbuffers v25.12.19+incompatible h1:haMV2JRRJCe1998HeW/p0X9UaMTK6SDo0ffLn2+DbLs=
github.com/google/flatbuffers v25.12.19+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4=
github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/hashicorp/consul/api v1.33.5 h1:Nn6q87zudRU1rLBTJEgaWxz9STCNadilLCD7B8OA5aI=
github.com/hashicorp/consul/api v1.33.5/go.mod h1:pa6fJOSHKLOzNHpUVeqLDtxA5+J1D7NNzLasuk8eRXA=
github.com/hashicorp/consul/sdk v0.17.3 h1:oZMMxzQGSsiT+ToOH50y3Qcs0nc9Ud+7L5lRx+EmMU0=
github.com/hashicorp/consul/sdk v0.17.3/go.mod h1:jnOmYjiNfVRpBaujQ1DFFVs0N6g3S1y6wygSjLTzYfc=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M=
github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U=
github.com/olekukonko/errors v1.3.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8=
github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw=
github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I=
github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=
github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/r3labs/diff/v2 v2.15.1 h1:EOrVqPUzi+njlumoqJwiS/TgGgmZo83619FNDB9xQUg=
github.com/r3labs/diff/v2 v2.15.1/go.mod h1:I8noH9Fc2fjSaMxqF3G2lhDdC0b+JXCfyx85tWFM9kc=
github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E=
github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s=
github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/vcaesar/cedar v0.30.0 h1:9fSDpM7FTjjUdPiBUUa0MWYMRGSEcqgFXvppZcZ4d7Y=
github.com/vcaesar/cedar v0.30.0/go.mod h1:lyuGvALuZZDPNXwpzv/9LyxW+8Y6faN7zauFezNsnik=
github.com/vcaesar/tt v0.20.1 h1:D/jUeeVCNbq3ad8M7hhtB3J9x5RZ6I1n1eZ0BJp7M+4=
github.com/vcaesar/tt v0.20.1/go.mod h1:cH2+AwGAJm19Wa6xvEa+0r+sXDJBT0QgNQey6mwqLeU=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8=
go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M=
golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ=
k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4=
k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8=
k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg=
k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+51
View File
@@ -0,0 +1,51 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: data-engine-config
namespace: default
data:
config.yml: |
server:
address : ":3001"
name: "data-engine"
workerId: 1
logPath: "resource/log/server"
logStdout: true
errorStack: true
rate:
limit: 200
burst: 300
database:
default:
- type: "pgsql"
host: "localhost"
port: "5432"
user: "postgres"
pass: "root"
name: "cid"
role: "master"
maxIdle: "5"
maxOpen: "20"
maxLifetime: "60s"
charset: "utf8mb4"
debug: true
dryRun: false
createdAt: "created_at"
updatedAt: "updated_at"
deletedAt: "deleted_at"
timeMaintainDisabled: false
redis:
default:
address: 192.168.3.30:6379
db: 0
idleTimeout: "60s"
maxConnLifetime: "90s"
waitTimeout: "60s"
dialTimeout: "30s"
readTimeout: "30s"
writeTimeout: "30s"
maxActive: 100
consul:
address: ""
jaeger:
addr: ""
+60
View File
@@ -0,0 +1,60 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: data-engine
namespace: default
labels:
app: data-engine
spec:
replicas: 1
selector:
matchLabels:
app: data-engine
template:
metadata:
labels:
app: data-engine
spec:
containers:
- name: data-engine
image: data-engine:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3002
protocol: TCP
name: http
env:
- name: TZ
value: "Asia/Seoul"
volumeMounts:
- name: config-volume
mountPath: /usr/local/bin/app/config.yml
subPath: config.yml
- name: log-volume
mountPath: /usr/local/bin/app/resource/log
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
tcpSocket:
port: 3002
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
tcpSocket:
port: 3002
initialDelaySeconds: 15
periodSeconds: 5
timeoutSeconds: 3
volumes:
- name: config-volume
configMap:
name: data-engine-config
- name: log-volume
emptyDir: {}
restartPolicy: Always
+69
View File
@@ -0,0 +1,69 @@
# Horizontal Pod Autoscaler - 水平Pod自动扩缩容配置
# 根据 CPU 和内存使用率自动调整 Pod 副本数量
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: data-engine-hpa # HPA 资源名称
namespace: default # 命名空间
spec:
# 指定要自动扩缩容的目标 Deployment
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: data-engine
# 副本数量范围
minReplicas: 1 # 最小副本数:始终保持至少 1 个 Pod 运行
maxReplicas: 10 # 最大副本数:防止无限扩容,最多扩展到 10 个 Pod
# 扩缩容触发指标(满足任一条件即触发)
metrics:
# CPU 使用率指标
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # 当所有 Pod 的平均 CPU 使用率超过 70% 时触发扩容
# 内存使用率指标
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80 # 当所有 Pod 的平均内存使用率超过 80% 时触发扩容
# 扩缩容行为策略
behavior:
# 向上扩容策略(快速响应负载增加)
scaleUp:
stabilizationWindowSeconds: 60 # 稳定窗口期:60 秒内只基于最高需求决策,防止抖动
policies:
# 策略1:按固定数量扩容
- type: Pods
value: 2 # 每次最多增加 2 个 Pod
periodSeconds: 60 # 每 60 秒评估一次
# 策略2:按比例扩容
- type: Percent
value: 50 # 每次最多增加当前副本数的 50%
periodSeconds: 60 # 每 60 秒评估一次
selectPolicy: Max # 选择两个策略中扩容幅度更大的方式(更激进)
# 向下缩容策略(保守缩容,避免频繁波动)
scaleDown:
stabilizationWindowSeconds: 300 # 稳定窗口期:300 秒(5分钟),比扩容更保守
policies:
# 策略1:按固定数量缩容
- type: Pods
value: 1 # 每次最多减少 1 个 Pod
periodSeconds: 60 # 每 60 秒评估一次
# 策略2:按比例缩容
- type: Percent
value: 25 # 每次最多减少当前副本数的 25%
periodSeconds: 60 # 每 60 秒评估一次
selectPolicy: Min # 选择两个策略中缩容幅度更小的方式(更保守)
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-engine-log-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: local-path
+17
View File
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: data-engine-service
namespace: default
labels:
app: data-engine
spec:
type: NodePort
selector:
app: data-engine
ports:
- name: http
port: 3002
targetPort: 3002
nodePort: 30302
protocol: TCP
+49 -16
View File
@@ -1,33 +1,66 @@
package main
import (
"cid/controller/copydata"
"cid/controller/dict"
"dataengine/common/report"
"dataengine/controller/debug"
"dataengine/controller/dict"
"dataengine/controller/public"
reportCtrl "dataengine/controller/report"
syncCtrl "dataengine/controller/sync"
syncSvc "dataengine/service/sync"
"os"
"os/signal"
"syscall"
"gitea.com/red-future/common/http"
"gitea.com/red-future/common/jaeger"
_ "gitea.com/red-future/common/ragflow" // RAGFlow 客户端自动初始化
"gitea.redpowerfuture.com/red-future/common/http"
"gitea.redpowerfuture.com/red-future/common/jaeger"
_ "gitea.redpowerfuture.com/red-future/common/k3sconfig"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
"golang.org/x/net/context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/sirupsen/logrus"
)
func main() {
ctx := context.Background()
ctx := gctx.New()
defer jaeger.ShutDown(ctx)
// 内部调度开关(默认关闭,由 PPGo_Job 通过 HTTP 端点统一调度)
extractEnabled := g.Cfg().MustGet(ctx, "report.extract_enabled", false).Bool()
syncEnabled := g.Cfg().MustGet(ctx, "sync.auto_sync_enabled", false).Bool()
if extractEnabled || syncEnabled {
if syncEnabled {
syncSvc.InitAndStartAutoSync(ctx)
}
if extractEnabled {
report.StartDailyExtractJob()
}
logrus.Infof("[main] 内部调度已启用 extract=%v sync=%v", extractEnabled, syncEnabled)
} else {
logrus.Info("[main] 内部调度已禁用,由 PPGo_Job 外部调度")
}
http.RouteRegister([]interface{}{
// 接口管理
dict.ApiInterface,
dict.FieldMappingConfig,
dict.DatasourcePlatform,
copydata.AccountReportDetail,
copydata.TaskReport,
copydata.PopulationReport,
copydata.MaterialReport,
copydata.StorewideReport,
copydata.CreativeReport,
copydata.UnitReport,
// 平台同步引擎
syncCtrl.PlatformSyncController,
// 公共查询接口
public.PublicQuery,
// 报表引擎 CRUD API
reportCtrl.ReportController,
})
select {}
// 管理后台页面
g.Server().BindHandler("/admin", debug.DebugController.DebugPage)
// 报表引擎管理页面
g.Server().BindHandler("/admin/report", reportCtrl.ReportAdminPage)
// 捕获退出信号,实现优雅关闭
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
sig := <-quit
logrus.Infof("收到信号 %v,正在退出...", sig)
}
@@ -1,318 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateCidAccountReportDetailReq 创建广告数据报表详情请求
type CreateCidAccountReportDetailReq struct {
g.Meta `path:"/createCidAccountReportDetail" method:"post" tags:"广告数据报表" summary:"创建广告数据报表详情" dc:"创建新的广告数据报表详情"`
*CidAccountReportDetailItem
}
// CreateCidAccountReportDetailRes 创建广告数据报表详情响应
type CreateCidAccountReportDetailRes struct {
Id int64 `json:"id" dc:"广告数据ID"`
}
// BatchCreateCidAccountReportDetailReq 批量创建广告数据报表详情请求
type BatchCreateCidAccountReportDetailReq struct {
g.Meta `path:"/batchCreateCidAccountReportDetail" method:"post" tags:"广告数据报表" summary:"批量创建广告数据报表详情" dc:"批量创建广告数据报表详情"`
Items []*CidAccountReportDetailItem `json:"items" v:"required" dc:"广告数据列表"`
}
// BatchCreateCidAccountReportDetailRes 批量创建广告数据报表详情响应
type BatchCreateCidAccountReportDetailRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// CidAccountReportDetailItem 广告数据报表详情项
type CidAccountReportDetailItem struct {
// 基础信息字段
DataType string `json:"dataType" v:"required" dc:"类型type"`
// 核心指标字段
T0OrderPaymentAmt string `json:"t0OrderPaymentAmt" dc:"当日总成交订单金额"`
CreativeMaterialType string `json:"creativeMaterialType" dc:"视频素材类型"`
LiveName string `json:"liveName" dc:"直播间名称"`
AuthorId string `json:"authorId" dc:"直播用户快手Id"`
PicUrl string `json:"picUrl" dc:"图片URL"`
PicName string `json:"picName" dc:"图片名称"`
PicId string `json:"picId" dc:"图片Id"`
CoverUrl string `json:"coverUrl" dc:"封面URL"`
CoverId int64 `json:"coverId" dc:"封面Id"`
// 转化率相关字段
ItemOrderConversionRatio *float64 `json:"itemOrderConversionRatio" dc:"转化率"`
ItemCardClickRatio *float64 `json:"itemCardClickRatio" dc:"点击率"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
LivePlayCntCost *float64 `json:"livePlayCntCost" dc:"直播间观看成本"`
AdMerchantFollowCost *float64 `json:"adMerchantFollowCost" dc:"涨粉成本"`
AdMerchantFollow *int64 `json:"adMerchantFollow" dc:"涨粉数"`
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交GMV"`
// 视频信息字段
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoIdStr string `json:"photoIdStr" dc:"视频id"`
PhotoId string `json:"photoId" dc:"视频id"`
// 受众属性字段
ModPriceSegment string `json:"modPriceSegment" dc:"设备价格区间"`
AgeSegment string `json:"ageSegment" dc:"年龄段"`
Province string `json:"province" dc:"省份名称"`
Gender string `json:"gender" dc:"性别"`
// 播放率相关字段
AdPhotoPlayedFiveRatio *float64 `json:"adPhotoPlayedFiveRatio" dc:"作品5秒播放率"`
AdPhotoPlayedThreeRatio *float64 `json:"adPhotoPlayedThreeRatio" dc:"作品3秒播放率"`
// 订单相关字段
OrderSubmitRoi *float64 `json:"orderSubmitRoi" dc:"订单提交ROI"`
OrderSubmitAmt *int64 `json:"orderSubmitAmt" dc:"外部订单金额"`
EventOrderSubmitCost *float64 `json:"eventOrderSubmitCost" dc:"订单提交成本"`
EventOrderSubmit *int64 `json:"eventOrderSubmit" dc:"订单提交数"`
EventOrderPaidRoi *float64 `json:"eventOrderPaidRoi" dc:"订单支付率"`
EventAppInvoked *int64 `json:"eventAppInvoked" dc:"唤起应用数"`
EventAddShoppingCart *int64 `json:"eventAddShoppingCart" dc:"添加购物车次数"`
ConversionNumCost *float64 `json:"conversionNumCost" dc:"转化成本"`
AdEffectivePlayNum *int64 `json:"adEffectivePlayNum" dc:"有效播放数"`
AdItemClick *int64 `json:"adItemClick" dc:"行为数"`
// 商品信息字段
MerchantProductId string `json:"merchantProductId" dc:"商品ID"`
// 花费相关字段
CostTotal *float64 `json:"costTotal" dc:"花费"`
AdShow *int64 `json:"adShow" dc:"曝光数"`
AdShow1kCost *float64 `json:"adShow1kCost" dc:"平均千次广告曝光花费"`
// 封面相关字段
Impression *int64 `json:"impression" dc:"封面曝光数"`
PhotoClick *int64 `json:"photoClick" dc:"封面点击数"`
PhotoClickRatio *float64 `json:"photoClickRatio" dc:"封面点击率"`
// 点击相关字段
Click *int64 `json:"click" dc:"素材曝光数"`
ActionbarClick *int64 `json:"actionbarClick" dc:"行为数"`
ActionbarClickCost *float64 `json:"actionbarClickCost" dc:"行为成本"`
EspClickRatio *float64 `json:"espClickRatio" dc:"行为率"`
ActionRatio *float64 `json:"actionRatio" dc:"素材点击率"`
AdItemClickCount *int64 `json:"adItemClickCount" dc:"预约组件点击数"`
// 直播时长字段
EspLivePlayedSeconds *int64 `json:"espLivePlayedSeconds" dc:"直播平均观看时长"`
// 播放数据字段
PlayedThreeSeconds *int64 `json:"playedThreeSeconds" dc:"作品3秒播放数"`
Play3sRatio *float64 `json:"play3sRatio" dc:"作品3秒播放率"`
PlayedFiveSeconds *int64 `json:"playedFiveSeconds" dc:"作品5秒播放数"`
Play5sRatio *float64 `json:"play5sRatio" dc:"作品5秒播放率"`
PlayedEnd *int64 `json:"playedEnd" dc:"作品完播数"`
PlayEndRatio *float64 `json:"playEndRatio" dc:"作品完播率"`
// 互动数据字段
Share *int64 `json:"share" dc:"作品分享数"`
Comment *int64 `json:"comment" dc:"作品评论数"`
Likes *int64 `json:"likes" dc:"作品点赞数"`
Report *int64 `json:"report" dc:"作品举报数"`
Block *int64 `json:"block" dc:"作品拉黑数"`
ItemNegative *int64 `json:"itemNegative" dc:"详情页减少此类作品数"`
// 直播互动字段
LiveShare *int64 `json:"liveShare" dc:"直播送礼数"`
LiveComment *int64 `json:"liveComment" dc:"直播评论数"`
LiveReward *int64 `json:"liveReward" dc:"直播送礼数"`
// 播放效果字段
EffectivePlayCount *int64 `json:"effectivePlayCount" dc:"有效播放数"`
EffectivePlayRatio *float64 `json:"effectivePlayRatio" dc:"有效播放率"`
// 转化字段
ConversionNum *int64 `json:"conversionNum" dc:"转化数"`
ConversionCostEsp *float64 `json:"conversionCostEsp" dc:"转化成本"`
Roi *float64 `json:"roi" dc:"直接ROI"`
Gmv *float64 `json:"gmv" dc:"直接GMV"`
// GMV时间序列字段
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计GMV"`
T1Gmv *float64 `json:"t1Gmv" dc:"次日累计GMV"`
T3Gmv *float64 `json:"t3Gmv" dc:"3日累计GMV"`
T7Gmv *float64 `json:"t7Gmv" dc:"7日累计GMV"`
T15Gmv *float64 `json:"t15Gmv" dc:"15日累计GMV"`
T30Gmv *float64 `json:"t30Gmv" dc:"30日累计GMV"`
// ROI时间序列字段
T0Roi *float64 `json:"t0Roi" dc:"当日累计ROI"`
T1Roi *float64 `json:"t1Roi" dc:"次日累计ROI"`
T3Roi *float64 `json:"t3Roi" dc:"3日累计ROI"`
T7Roi *float64 `json:"t7Roi" dc:"7日累计ROI"`
T15Roi *float64 `json:"t15Roi" dc:"15日累计ROI"`
T30Roi *float64 `json:"t30Roi" dc:"30日累计ROI"`
// 订单相关字段
PaiedOrder *int64 `json:"paiedOrder" dc:"直接订单数"`
OrderRatio *float64 `json:"orderRatio" dc:"直接下单率"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
T0OrderCntRatio *float64 `json:"t0OrderCntRatio" dc:"累计订单下单率"`
T1OrderCnt *int64 `json:"t1OrderCnt" dc:"次日累计订单数"`
T3OrderCnt *int64 `json:"t3OrderCnt" dc:"3日累计订单数"`
T7OrderCnt *int64 `json:"t7OrderCnt" dc:"7日累计订单数"`
T15OrderCnt *int64 `json:"t15OrderCnt" dc:"15日累计订单数"`
T30OrderCnt *int64 `json:"t30OrderCnt" dc:"30日累计订单数"`
// 粉丝相关字段
MerchantRecoFans *int64 `json:"merchantRecoFans" dc:"涨粉数"`
T1Retention *float64 `json:"t1Retention" dc:"次日涨粉留存数"`
T7Retention *float64 `json:"t7Retention" dc:"7日涨粉留存数"`
T15Retention *float64 `json:"t15Retention" dc:"15日涨粉留存数"`
T30Retention *float64 `json:"t30Retention" dc:"30日涨粉留存数"`
T1RetentionRatio *float64 `json:"t1RetentionRatio" dc:"次日涨粉留存率"`
T7RetentionRatio *float64 `json:"t7RetentionRatio" dc:"7日涨粉留存率"`
T15RetentionRatio *float64 `json:"t15RetentionRatio" dc:"15日涨粉留存率"`
T30RetentionRatio *float64 `json:"t30RetentionRatio" dc:"30日涨粉留存率"`
// 直播预约字段
ReservationSuccess *int64 `json:"reservationSuccess" dc:"直播预约成功数"`
ReservationCost *float64 `json:"reservationCost" dc:"直播预约成功成本"`
// 直播观看字段
StandardLivePlayedStarted *int64 `json:"standardLivePlayedStarted" dc:"直播观看数"`
AdLivePlayCnt *int64 `json:"adLivePlayCnt" dc:"直播间人气数"`
AdLivePlayCntCost *float64 `json:"adLivePlayCntCost" dc:"直播间人气成本"`
LiveAudienceCost *float64 `json:"liveAudienceCost" dc:"直播观看成本"`
// 商品点击字段
LiveEventGoodsView *int64 `json:"liveEventGoodsView" dc:"直播间商品点击数"`
GoodsClickRatio *float64 `json:"goodsClickRatio" dc:"直播间商品点击率"`
// 新客相关字段
DirectAttrPlatNewBuyerCnt *int64 `json:"directAttrPlatNewBuyerCnt" dc:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30AttrPlatTotalBuyerCnt" dc:"30日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `json:"directAttrSellerNewBuyerCnt" dc:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30AttrSellerTotalBuyerCnt" dc:"30日累计店铺新客"`
// 间接订单字段
T7IndirectOrderAmt *float64 `json:"t7IndirectOrderAmt" dc:"7日间接订单金额"`
T7IndirectOrderCnt *int64 `json:"t7IndirectOrderCnt" dc:"7日间接订单数"`
// 粉丝人均销售额字段
FansT0GmvPerFans *float64 `json:"fansT0GmvPerFans" dc:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `json:"fansT3GmvPerFans" dc:"3日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `json:"fansT7GmvPerFans" dc:"7日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `json:"fansT15GmvPerFans" dc:"15日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `json:"fansT30GmvPerFans" dc:"30日新增粉丝人均销售额"`
// 涨粉成本字段
RecoFansCost *float64 `json:"recoFansCost" dc:"涨粉成本"`
// 智能优惠券字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"智能优惠券订单GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"智能优惠券订单数"`
// 粉丝GMV字段
FansT0Gmv *float64 `json:"fansT0Gmv" dc:"涨粉当日GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" dc:"涨粉次日GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" dc:"涨粉7日GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" dc:"涨粉15日GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" dc:"涨粉30日GMV"`
// 粉丝ROI字段
FansT0Roi *float64 `json:"fansT0Roi" dc:"涨粉当日ROI"`
FansT1Roi *float64 `json:"fansT1Roi" dc:"涨粉次日ROI"`
FansT7Roi *float64 `json:"fansT7Roi" dc:"涨粉7日ROI"`
FansT15Roi *float64 `json:"fansT15Roi" dc:"涨粉15日ROI"`
FansT30Roi *float64 `json:"fansT30Roi" dc:"涨粉30日ROI"`
// 新客GMV字段
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0ShopNewBuyerOrderPaymentAmt" dc:"当日新客GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1ShopNewBuyerOrderPaymentAmt" dc:"投后1日新客GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3ShopNewBuyerOrderPaymentAmt" dc:"投后3日新客GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7ShopNewBuyerOrderPaymentAmt" dc:"投后7日新客GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15ShopNewBuyerOrderPaymentAmt" dc:"投后15日新客GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30ShopNewBuyerOrderPaymentAmt" dc:"投后30日新客GMV"`
// 新客订单量字段
T0ShopNewBuyerOrderCnt *int64 `json:"t0ShopNewBuyerOrderCnt" dc:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1ShopNewBuyerOrderCnt" dc:"投后1日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3ShopNewBuyerOrderCnt" dc:"投后3日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7ShopNewBuyerOrderCnt" dc:"投后7日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15ShopNewBuyerOrderCnt" dc:"投后15日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30ShopNewBuyerOrderCnt" dc:"投后30日新客成交订单量"`
// 新客复购率字段
T1NewBuyerRepurchaseRatio *float64 `json:"t1NewBuyerRepurchaseRatio" dc:"投后1日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3NewBuyerRepurchaseRatio" dc:"投后3日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7NewBuyerRepurchaseRatio" dc:"投后7日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15NewBuyerRepurchaseRatio" dc:"投后15日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30NewBuyerRepurchaseRatio" dc:"投后30日新客复购率"`
// 新客ROI字段
T0ShopNewBuyerRoi *float64 `json:"t0ShopNewBuyerRoi" dc:"投后当日新客ROI"`
T1ShopNewBuyerRoi *float64 `json:"t1ShopNewBuyerRoi" dc:"投后1日新客ROI"`
T3ShopNewBuyerRoi *float64 `json:"t3ShopNewBuyerRoi" dc:"投后3日新客ROI"`
T7ShopNewBuyerRoi *float64 `json:"t7ShopNewBuyerRoi" dc:"投后7日新客ROI"`
T15ShopNewBuyerRoi *float64 `json:"t15ShopNewBuyerRoi" dc:"投后15日新客ROI"`
T30ShopNewBuyerRoi *float64 `json:"t30ShopNewBuyerRoi" dc:"投后30日新客ROI"`
// 电话卡相关字段
CreateCardOrderCnt *int64 `json:"createCardOrderCnt" dc:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `json:"forwardTsCreateCardOrderCnt" dc:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `json:"createCardOrderCost" dc:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `json:"forwardTsCreateCardOrderCost" dc:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `json:"activateCardOrderCnt" dc:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `json:"forwardTsActivateCardOrderCnt" dc:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `json:"activateCardOrderCost" dc:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `json:"forwardTsActivateCardOrderCost" dc:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `json:"createCardOrderRatio" dc:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `json:"forwardTsCreateCardOrderRatio" dc:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `json:"activateCardOrderCntRatio" dc:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `json:"forwardTsActivateCardOrderRatio" dc:"电话卡激活率(计费时间)"`
// 全站数据字段
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
// 时间字段
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间"`
// 广告结构字段
CampaignId *int64 `json:"campaignId" dc:"计划ID"`
CampaignName string `json:"campaignName" dc:"计划名称"`
UnitId *int64 `json:"unitId" dc:"单元ID"`
UnitName string `json:"unitName" dc:"单元名称"`
CreativeId *int64 `json:"creativeId" dc:"创意ID"`
CreativeName string `json:"creativeName" dc:"创意名称"`
// 补贴相关字段
CidActualRoiAfterSubsidy *float64 `json:"cidActualRoiAfterSubsidy" dc:"补贴后实际ROI"`
CidCouponAmount *int64 `json:"cidCouponAmount" dc:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `json:"cidCouponCallbackPaidRefundAmount" dc:"退单有回传_核销券金额"`
CidVoucherCost *float64 `json:"cidVoucherCost" dc:"券成本"`
}
// ListCidAccountReportDetailReq 获取广告数据报表详情列表请求
type ListCidAccountReportDetailReq struct {
g.Meta `path:"/listCidAccountReportDetail" method:"get" tags:"广告数据报表" summary:"获取广告数据报表详情列表" dc:"分页查询广告数据报表详情列表"`
*beans.Page
DataType string `json:"dataType" dc:"类型type"`
ReportDateStr string `json:"reportDateStr" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划ID"`
UnitId *int64 `json:"unitId" dc:"单元ID"`
CreativeId *int64 `json:"creativeId" dc:"创意ID"`
AuthorId string `json:"authorId" dc:"直播用户快手Id"`
MerchantProductId string `json:"merchantProductId" dc:"商品ID"`
Keyword string `json:"keyword" dc:"关键字(搜索直播间名称、视频名称等)"`
}
// ListCidAccountReportDetailRes 获取广告数据报表详情列表响应
type ListCidAccountReportDetailRes struct {
List []*CidAccountReportDetailItem `json:"list" dc:"广告数据列表"`
Total int `json:"total" dc:"总数"`
}
@@ -1,318 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateCidAccountReportSumReq 创建广告数据报表详情请求
type CreateCidAccountReportSumReq struct {
g.Meta `path:"/createCidAccountReportSum" method:"post" tags:"广告数据报表" summary:"创建广告数据报表详情" dc:"创建新的广告数据报表详情"`
*CidAccountReportSumItem
}
// CreateCidAccountReportSumRes 创建广告数据报表详情响应
type CreateCidAccountReportSumRes struct {
Id int64 `json:"id" dc:"广告数据ID"`
}
// BatchCreateCidAccountReportSumReq 批量创建广告数据报表详情请求
type BatchCreateCidAccountReportSumReq struct {
g.Meta `path:"/batchCreateCidAccountReportSum" method:"post" tags:"广告数据报表" summary:"批量创建广告数据报表详情" dc:"批量创建广告数据报表详情"`
Items []*CidAccountReportSumItem `json:"items" v:"required" dc:"广告数据列表"`
}
// BatchCreateCidAccountReportSumRes 批量创建广告数据报表详情响应
type BatchCreateCidAccountReportSumRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// CidAccountReportSumItem 广告数据报表详情项
type CidAccountReportSumItem struct {
// 基础信息字段
DataType string `json:"dataType" v:"required" dc:"类型type"`
// 核心指标字段
T0OrderPaymentAmt string `json:"t0OrderPaymentAmt" dc:"当日总成交订单金额"`
CreativeMaterialType string `json:"creativeMaterialType" dc:"视频素材类型"`
LiveName string `json:"liveName" dc:"直播间名称"`
AuthorId string `json:"authorId" dc:"直播用户快手Id"`
PicUrl string `json:"picUrl" dc:"图片URL"`
PicName string `json:"picName" dc:"图片名称"`
PicId string `json:"picId" dc:"图片Id"`
CoverUrl string `json:"coverUrl" dc:"封面URL"`
CoverId int64 `json:"coverId" dc:"封面Id"`
// 转化率相关字段
ItemOrderConversionRatio *float64 `json:"itemOrderConversionRatio" dc:"转化率"`
ItemCardClickRatio *float64 `json:"itemCardClickRatio" dc:"点击率"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
LivePlayCntCost *float64 `json:"livePlayCntCost" dc:"直播间观看成本"`
AdMerchantFollowCost *float64 `json:"adMerchantFollowCost" dc:"涨粉成本"`
AdMerchantFollow *int64 `json:"adMerchantFollow" dc:"涨粉数"`
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交GMV"`
// 视频信息字段
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoIdStr string `json:"photoIdStr" dc:"视频id"`
PhotoId string `json:"photoId" dc:"视频id"`
// 受众属性字段
ModPriceSegment string `json:"modPriceSegment" dc:"设备价格区间"`
AgeSegment string `json:"ageSegment" dc:"年龄段"`
Province string `json:"province" dc:"省份名称"`
Gender string `json:"gender" dc:"性别"`
// 播放率相关字段
AdPhotoPlayedFiveRatio *float64 `json:"adPhotoPlayedFiveRatio" dc:"作品5秒播放率"`
AdPhotoPlayedThreeRatio *float64 `json:"adPhotoPlayedThreeRatio" dc:"作品3秒播放率"`
// 订单相关字段
OrderSubmitRoi *float64 `json:"orderSubmitRoi" dc:"订单提交ROI"`
OrderSubmitAmt *int64 `json:"orderSubmitAmt" dc:"外部订单金额"`
EventOrderSubmitCost *float64 `json:"eventOrderSubmitCost" dc:"订单提交成本"`
EventOrderSubmit *int64 `json:"eventOrderSubmit" dc:"订单提交数"`
EventOrderPaidRoi *float64 `json:"eventOrderPaidRoi" dc:"订单支付率"`
EventAppInvoked *int64 `json:"eventAppInvoked" dc:"唤起应用数"`
EventAddShoppingCart *int64 `json:"eventAddShoppingCart" dc:"添加购物车次数"`
ConversionNumCost *float64 `json:"conversionNumCost" dc:"转化成本"`
AdEffectivePlayNum *int64 `json:"adEffectivePlayNum" dc:"有效播放数"`
AdItemClick *int64 `json:"adItemClick" dc:"行为数"`
// 商品信息字段
MerchantProductId string `json:"merchantProductId" dc:"商品ID"`
// 花费相关字段
CostTotal *float64 `json:"costTotal" dc:"花费"`
AdShow *int64 `json:"adShow" dc:"曝光数"`
AdShow1kCost *float64 `json:"adShow1kCost" dc:"平均千次广告曝光花费"`
// 封面相关字段
Impression *int64 `json:"impression" dc:"封面曝光数"`
PhotoClick *int64 `json:"photoClick" dc:"封面点击数"`
PhotoClickRatio *float64 `json:"photoClickRatio" dc:"封面点击率"`
// 点击相关字段
Click *int64 `json:"click" dc:"素材曝光数"`
ActionbarClick *int64 `json:"actionbarClick" dc:"行为数"`
ActionbarClickCost *float64 `json:"actionbarClickCost" dc:"行为成本"`
EspClickRatio *float64 `json:"espClickRatio" dc:"行为率"`
ActionRatio *float64 `json:"actionRatio" dc:"素材点击率"`
AdItemClickCount *int64 `json:"adItemClickCount" dc:"预约组件点击数"`
// 直播时长字段
EspLivePlayedSeconds *int64 `json:"espLivePlayedSeconds" dc:"直播平均观看时长"`
// 播放数据字段
PlayedThreeSeconds *int64 `json:"playedThreeSeconds" dc:"作品3秒播放数"`
Play3sRatio *float64 `json:"play3sRatio" dc:"作品3秒播放率"`
PlayedFiveSeconds *int64 `json:"playedFiveSeconds" dc:"作品5秒播放数"`
Play5sRatio *float64 `json:"play5sRatio" dc:"作品5秒播放率"`
PlayedEnd *int64 `json:"playedEnd" dc:"作品完播数"`
PlayEndRatio *float64 `json:"playEndRatio" dc:"作品完播率"`
// 互动数据字段
Share *int64 `json:"share" dc:"作品分享数"`
Comment *int64 `json:"comment" dc:"作品评论数"`
Likes *int64 `json:"likes" dc:"作品点赞数"`
Report *int64 `json:"report" dc:"作品举报数"`
Block *int64 `json:"block" dc:"作品拉黑数"`
ItemNegative *int64 `json:"itemNegative" dc:"详情页减少此类作品数"`
// 直播互动字段
LiveShare *int64 `json:"liveShare" dc:"直播送礼数"`
LiveComment *int64 `json:"liveComment" dc:"直播评论数"`
LiveReward *int64 `json:"liveReward" dc:"直播送礼数"`
// 播放效果字段
EffectivePlayCount *int64 `json:"effectivePlayCount" dc:"有效播放数"`
EffectivePlayRatio *float64 `json:"effectivePlayRatio" dc:"有效播放率"`
// 转化字段
ConversionNum *int64 `json:"conversionNum" dc:"转化数"`
ConversionCostEsp *float64 `json:"conversionCostEsp" dc:"转化成本"`
Roi *float64 `json:"roi" dc:"直接ROI"`
Gmv *float64 `json:"gmv" dc:"直接GMV"`
// GMV时间序列字段
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计GMV"`
T1Gmv *float64 `json:"t1Gmv" dc:"次日累计GMV"`
T3Gmv *float64 `json:"t3Gmv" dc:"3日累计GMV"`
T7Gmv *float64 `json:"t7Gmv" dc:"7日累计GMV"`
T15Gmv *float64 `json:"t15Gmv" dc:"15日累计GMV"`
T30Gmv *float64 `json:"t30Gmv" dc:"30日累计GMV"`
// ROI时间序列字段
T0Roi *float64 `json:"t0Roi" dc:"当日累计ROI"`
T1Roi *float64 `json:"t1Roi" dc:"次日累计ROI"`
T3Roi *float64 `json:"t3Roi" dc:"3日累计ROI"`
T7Roi *float64 `json:"t7Roi" dc:"7日累计ROI"`
T15Roi *float64 `json:"t15Roi" dc:"15日累计ROI"`
T30Roi *float64 `json:"t30Roi" dc:"30日累计ROI"`
// 订单相关字段
PaiedOrder *int64 `json:"paiedOrder" dc:"直接订单数"`
OrderRatio *float64 `json:"orderRatio" dc:"直接下单率"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
T0OrderCntRatio *float64 `json:"t0OrderCntRatio" dc:"累计订单下单率"`
T1OrderCnt *int64 `json:"t1OrderCnt" dc:"次日累计订单数"`
T3OrderCnt *int64 `json:"t3OrderCnt" dc:"3日累计订单数"`
T7OrderCnt *int64 `json:"t7OrderCnt" dc:"7日累计订单数"`
T15OrderCnt *int64 `json:"t15OrderCnt" dc:"15日累计订单数"`
T30OrderCnt *int64 `json:"t30OrderCnt" dc:"30日累计订单数"`
// 粉丝相关字段
MerchantRecoFans *int64 `json:"merchantRecoFans" dc:"涨粉数"`
T1Retention *float64 `json:"t1Retention" dc:"次日涨粉留存数"`
T7Retention *float64 `json:"t7Retention" dc:"7日涨粉留存数"`
T15Retention *float64 `json:"t15Retention" dc:"15日涨粉留存数"`
T30Retention *float64 `json:"t30Retention" dc:"30日涨粉留存数"`
T1RetentionRatio *float64 `json:"t1RetentionRatio" dc:"次日涨粉留存率"`
T7RetentionRatio *float64 `json:"t7RetentionRatio" dc:"7日涨粉留存率"`
T15RetentionRatio *float64 `json:"t15RetentionRatio" dc:"15日涨粉留存率"`
T30RetentionRatio *float64 `json:"t30RetentionRatio" dc:"30日涨粉留存率"`
// 直播预约字段
ReservationSuccess *int64 `json:"reservationSuccess" dc:"直播预约成功数"`
ReservationCost *float64 `json:"reservationCost" dc:"直播预约成功成本"`
// 直播观看字段
StandardLivePlayedStarted *int64 `json:"standardLivePlayedStarted" dc:"直播观看数"`
AdLivePlayCnt *int64 `json:"adLivePlayCnt" dc:"直播间人气数"`
AdLivePlayCntCost *float64 `json:"adLivePlayCntCost" dc:"直播间人气成本"`
LiveAudienceCost *float64 `json:"liveAudienceCost" dc:"直播观看成本"`
// 商品点击字段
LiveEventGoodsView *int64 `json:"liveEventGoodsView" dc:"直播间商品点击数"`
GoodsClickRatio *float64 `json:"goodsClickRatio" dc:"直播间商品点击率"`
// 新客相关字段
DirectAttrPlatNewBuyerCnt *int64 `json:"directAttrPlatNewBuyerCnt" dc:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30AttrPlatTotalBuyerCnt" dc:"30日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `json:"directAttrSellerNewBuyerCnt" dc:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30AttrSellerTotalBuyerCnt" dc:"30日累计店铺新客"`
// 间接订单字段
T7IndirectOrderAmt *float64 `json:"t7IndirectOrderAmt" dc:"7日间接订单金额"`
T7IndirectOrderCnt *int64 `json:"t7IndirectOrderCnt" dc:"7日间接订单数"`
// 粉丝人均销售额字段
FansT0GmvPerFans *float64 `json:"fansT0GmvPerFans" dc:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `json:"fansT3GmvPerFans" dc:"3日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `json:"fansT7GmvPerFans" dc:"7日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `json:"fansT15GmvPerFans" dc:"15日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `json:"fansT30GmvPerFans" dc:"30日新增粉丝人均销售额"`
// 涨粉成本字段
RecoFansCost *float64 `json:"recoFansCost" dc:"涨粉成本"`
// 智能优惠券字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"智能优惠券订单GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"智能优惠券订单数"`
// 粉丝GMV字段
FansT0Gmv *float64 `json:"fansT0Gmv" dc:"涨粉当日GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" dc:"涨粉次日GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" dc:"涨粉7日GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" dc:"涨粉15日GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" dc:"涨粉30日GMV"`
// 粉丝ROI字段
FansT0Roi *float64 `json:"fansT0Roi" dc:"涨粉当日ROI"`
FansT1Roi *float64 `json:"fansT1Roi" dc:"涨粉次日ROI"`
FansT7Roi *float64 `json:"fansT7Roi" dc:"涨粉7日ROI"`
FansT15Roi *float64 `json:"fansT15Roi" dc:"涨粉15日ROI"`
FansT30Roi *float64 `json:"fansT30Roi" dc:"涨粉30日ROI"`
// 新客GMV字段
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0ShopNewBuyerOrderPaymentAmt" dc:"当日新客GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1ShopNewBuyerOrderPaymentAmt" dc:"投后1日新客GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3ShopNewBuyerOrderPaymentAmt" dc:"投后3日新客GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7ShopNewBuyerOrderPaymentAmt" dc:"投后7日新客GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15ShopNewBuyerOrderPaymentAmt" dc:"投后15日新客GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30ShopNewBuyerOrderPaymentAmt" dc:"投后30日新客GMV"`
// 新客订单量字段
T0ShopNewBuyerOrderCnt *int64 `json:"t0ShopNewBuyerOrderCnt" dc:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1ShopNewBuyerOrderCnt" dc:"投后1日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3ShopNewBuyerOrderCnt" dc:"投后3日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7ShopNewBuyerOrderCnt" dc:"投后7日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15ShopNewBuyerOrderCnt" dc:"投后15日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30ShopNewBuyerOrderCnt" dc:"投后30日新客成交订单量"`
// 新客复购率字段
T1NewBuyerRepurchaseRatio *float64 `json:"t1NewBuyerRepurchaseRatio" dc:"投后1日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3NewBuyerRepurchaseRatio" dc:"投后3日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7NewBuyerRepurchaseRatio" dc:"投后7日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15NewBuyerRepurchaseRatio" dc:"投后15日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30NewBuyerRepurchaseRatio" dc:"投后30日新客复购率"`
// 新客ROI字段
T0ShopNewBuyerRoi *float64 `json:"t0ShopNewBuyerRoi" dc:"投后当日新客ROI"`
T1ShopNewBuyerRoi *float64 `json:"t1ShopNewBuyerRoi" dc:"投后1日新客ROI"`
T3ShopNewBuyerRoi *float64 `json:"t3ShopNewBuyerRoi" dc:"投后3日新客ROI"`
T7ShopNewBuyerRoi *float64 `json:"t7ShopNewBuyerRoi" dc:"投后7日新客ROI"`
T15ShopNewBuyerRoi *float64 `json:"t15ShopNewBuyerRoi" dc:"投后15日新客ROI"`
T30ShopNewBuyerRoi *float64 `json:"t30ShopNewBuyerRoi" dc:"投后30日新客ROI"`
// 电话卡相关字段
CreateCardOrderCnt *int64 `json:"createCardOrderCnt" dc:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `json:"forwardTsCreateCardOrderCnt" dc:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `json:"createCardOrderCost" dc:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `json:"forwardTsCreateCardOrderCost" dc:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `json:"activateCardOrderCnt" dc:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `json:"forwardTsActivateCardOrderCnt" dc:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `json:"activateCardOrderCost" dc:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `json:"forwardTsActivateCardOrderCost" dc:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `json:"createCardOrderRatio" dc:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `json:"forwardTsCreateCardOrderRatio" dc:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `json:"activateCardOrderCntRatio" dc:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `json:"forwardTsActivateCardOrderRatio" dc:"电话卡激活率(计费时间)"`
// 全站数据字段
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
// 时间字段
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间"`
// 广告结构字段
CampaignId *int64 `json:"campaignId" dc:"计划ID"`
CampaignName string `json:"campaignName" dc:"计划名称"`
UnitId *int64 `json:"unitId" dc:"单元ID"`
UnitName string `json:"unitName" dc:"单元名称"`
CreativeId *int64 `json:"creativeId" dc:"创意ID"`
CreativeName string `json:"creativeName" dc:"创意名称"`
// 补贴相关字段
CidActualRoiAfterSubsidy *float64 `json:"cidActualRoiAfterSubsidy" dc:"补贴后实际ROI"`
CidCouponAmount *int64 `json:"cidCouponAmount" dc:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `json:"cidCouponCallbackPaidRefundAmount" dc:"退单有回传_核销券金额"`
CidVoucherCost *float64 `json:"cidVoucherCost" dc:"券成本"`
}
// ListCidAccountReportSumReq 获取广告数据报表详情列表请求
type ListCidAccountReportSumReq struct {
g.Meta `path:"/listCidAccountReportSum" method:"get" tags:"广告数据报表" summary:"获取广告数据报表详情列表" dc:"分页查询广告数据报表详情列表"`
*beans.Page
DataType string `json:"dataType" dc:"类型type"`
ReportDateStr string `json:"reportDateStr" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划ID"`
UnitId *int64 `json:"unitId" dc:"单元ID"`
CreativeId *int64 `json:"creativeId" dc:"创意ID"`
AuthorId string `json:"authorId" dc:"直播用户快手Id"`
MerchantProductId string `json:"merchantProductId" dc:"商品ID"`
Keyword string `json:"keyword" dc:"关键字(搜索直播间名称、视频名称等)"`
}
// ListCidAccountReportSumRes 获取广告数据报表详情列表响应
type ListCidAccountReportSumRes struct {
List []*CidAccountReportSumItem `json:"list" dc:"广告数据列表"`
Total int `json:"total" dc:"总数"`
}
@@ -1,230 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
type CreateCreativeReportDetailReq struct {
g.Meta `path:"/createCreativeReportDetail" method:"post" tags:"广告效果明细报表" summary:"创建广告效果指标明细表" dc:"创建新的广告效果指标明细表"`
*CreativeReportDetailItem
}
type CreateCreativeReportDetailRes struct {
Id int64 `json:"id" dc:"广告效果明细 ID"`
}
type BatchCreateCreativeReportDetailReq struct {
g.Meta `path:"/batchCreateCreativeReportDetail" method:"post" tags:"广告效果明细报表" summary:"批量创建广告效果指标明细表" dc:"批量创建广告效果指标明细表"`
Items []*CreativeReportDetailItem `json:"items" v:"required" dc:"广告效果明细列表"`
}
type BatchCreateCreativeReportDetailRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
type CreativeReportDetailItem struct {
T0OrderPaymentAmt *float64 `json:"t0OrderPaymentAmt" dc:"当日总成交订单金额"`
CreativeMaterialType string `json:"creativeMaterialType" dc:"视频素材类型"`
LiveName string `json:"liveName" dc:"直播间名称"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
PicUrl string `json:"picUrl" dc:"图片 URL"`
PicName string `json:"picName" dc:"图片名称"`
PicId string `json:"picId" dc:"图片 Id"`
CoverUrl string `json:"coverUrl" dc:"封面 URL"`
CoverId *int64 `json:"coverId" dc:"封面 Id"`
ItemOrderConversionRatio *float64 `json:"itemOrderConversionRatio" dc:"转化率"`
ItemCardClickRatio *float64 `json:"itemCardClickRatio" dc:"点击率"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
LivePlayCntCost *float64 `json:"livePlayCntCost" dc:"直播间观看成本"`
AdMerchantFollowCost *float64 `json:"adMerchantFollowCost" dc:"涨粉成本"`
AdMerchantFollow *int64 `json:"adMerchantFollow" dc:"涨粉数"`
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交 ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交 GMV"`
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoIdStr string `json:"photoIdStr" dc:"视频 id"`
PhotoId string `json:"photoId" dc:"视频 id"`
ModPriceSegment string `json:"modPriceSegment" dc:"设备价格区间"`
AgeSegment string `json:"ageSegment" dc:"年龄段"`
Province string `json:"province" dc:"省份名称"`
Gender string `json:"gender" dc:"性别"`
AdPhotoPlayedFiveRatio *float64 `json:"adPhotoPlayedFiveRatio" dc:"作品 5 秒播放率"`
AdPhotoPlayedThreeRatio *float64 `json:"adPhotoPlayedThreeRatio" dc:"作品 3 秒播放率"`
OrderSubmitRoi *float64 `json:"orderSubmitRoi" dc:"订单提交 ROI"`
OrderSubmitAmt *int64 `json:"orderSubmitAmt" dc:"外部订单金额"`
EventOrderSubmitCost *float64 `json:"eventOrderSubmitCost" dc:"订单提交成本"`
EventOrderSubmit *int64 `json:"eventOrderSubmit" dc:"订单提交数"`
EventOrderPaidRoi *float64 `json:"eventOrderPaidRoi" dc:"订单支付率"`
EventAppInvoked *int64 `json:"eventAppInvoked" dc:"唤起应用数"`
EventAddShoppingCart *int64 `json:"eventAddShoppingCart" dc:"添加购物车次数"`
ConversionNumCost *float64 `json:"conversionNumCost" dc:"转化成本(回传时间)"`
AdEffectivePlayNum *int64 `json:"adEffectivePlayNum" dc:"有效播放数"`
AdItemClick *int64 `json:"adItemClick" dc:"行为数"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
CostTotal *float64 `json:"costTotal" dc:"花费"`
AdShow *int64 `json:"adShow" dc:"曝光数"`
AdShow1kCost *float64 `json:"adShow1kCost" dc:"平均千次广告曝光花费"`
Impression *int64 `json:"impression" dc:"封面曝光数"`
PhotoClick *int64 `json:"photoClick" dc:"封面点击数"`
PhotoClickRatio *float64 `json:"photoClickRatio" dc:"封面点击率"`
Click *int64 `json:"click" dc:"素材曝光数"`
ActionbarClick *int64 `json:"actionbarClick" dc:"行为数"`
ActionbarClickCost *float64 `json:"actionbarClickCost" dc:"行为成本"`
EspClickRatio *float64 `json:"espClickRatio" dc:"行为率"`
ActionRatio *float64 `json:"actionRatio" dc:"素材点击率"`
AdItemClickCount *int64 `json:"adItemClickCount" dc:"预约组件点击数"`
EspLivePlayedSeconds *int64 `json:"espLivePlayedSeconds" dc:"直播平均观看时长"`
PlayedThreeSeconds *int64 `json:"playedThreeSeconds" dc:"作品 3 秒播放数"`
Play3sRatio *float64 `json:"play3sRatio" dc:"作品 3 秒播放率"`
PlayedFiveSeconds *int64 `json:"playedFiveSeconds" dc:"作品 5 秒播放数"`
Play5sRatio *float64 `json:"play5sRatio" dc:"作品 5 秒播放率"`
PlayedEnd *int64 `json:"playedEnd" dc:"作品完播数"`
PlayEndRatio *float64 `json:"playEndRatio" dc:"作品完播率"`
Share *int64 `json:"share" dc:"作品分享数"`
Comment *int64 `json:"comment" dc:"作品评论数"`
Likes *int64 `json:"likes" dc:"作品点赞数"`
Report *int64 `json:"report" dc:"作品举报数"`
Block *int64 `json:"block" dc:"作品拉黑数"`
ItemNegative *int64 `json:"itemNegative" dc:"详情页减少此类作品数"`
LiveShare *int64 `json:"liveShare" dc:"直播送礼数"`
LiveComment *int64 `json:"liveComment" dc:"直播评论数"`
LiveReward *int64 `json:"liveReward" dc:"直播送礼数"`
EffectivePlayCount *int64 `json:"effectivePlayCount" dc:"有效播放数"`
EffectivePlayRatio *float64 `json:"effectivePlayRatio" dc:"有效播放率"`
ConversionNum *int64 `json:"conversionNum" dc:"转化数"`
ConversionCostEsp *float64 `json:"conversionCostEsp" dc:"转化成本"`
Roi *float64 `json:"roi" dc:"直接 ROI"`
Gmv *float64 `json:"gmv" dc:"直接 GMV"`
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计 GMV"`
T1Gmv *float64 `json:"t1Gmv" dc:"次日累计 GMV"`
T7Gmv *float64 `json:"t7Gmv" dc:"7 日累计 GMV"`
T15Gmv *float64 `json:"t15Gmv" dc:"15 日累计 GMV"`
T30Gmv *float64 `json:"t30Gmv" dc:"30 日累计 GMV"`
T0Roi *float64 `json:"t0Roi" dc:"当日累计 ROI"`
T1Roi *float64 `json:"t1Roi" dc:"次日累计 ROI"`
T7Roi *float64 `json:"t7Roi" dc:"7 日累计 ROI"`
T15Roi *float64 `json:"t15Roi" dc:"15 日累计 ROI"`
T30Roi *float64 `json:"t30Roi" dc:"30 日累计 ROI"`
PaiedOrder *int64 `json:"paiedOrder" dc:"直接订单数"`
OrderRatio *float64 `json:"orderRatio" dc:"直接下单率"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
T0OrderCntRatio *float64 `json:"t0OrderCntRatio" dc:"累计订单下单率"`
T1OrderCnt *int64 `json:"t1OrderCnt" dc:"次日累计订单数"`
T7OrderCnt *int64 `json:"t7OrderCnt" dc:"7 日累计订单数"`
T15OrderCnt *int64 `json:"t15OrderCnt" dc:"15 日累计订单数"`
T30OrderCnt *int64 `json:"t30OrderCnt" dc:"30 日累计订单数"`
MerchantRecoFans *int64 `json:"merchantRecoFans" dc:"涨粉数"`
T1Retention *float64 `json:"t1Retention" dc:"次日涨粉留存数"`
T7Retention *float64 `json:"t7Retention" dc:"7 日涨粉留存数"`
T15Retention *float64 `json:"t15Retention" dc:"15 日涨粉留存数"`
T30Retention *float64 `json:"t30Retention" dc:"30 日涨粉留存数"`
T1RetentionRatio *float64 `json:"t1RetentionRatio" dc:"次日涨粉留存率"`
T7RetentionRatio *float64 `json:"t7RetentionRatio" dc:"7 日涨粉留存率"`
T15RetentionRatio *float64 `json:"t15RetentionRatio" dc:"15 日涨粉留存率"`
T30RetentionRatio *float64 `json:"t30RetentionRatio" dc:"30 日涨粉留存率"`
ReservationSuccess *int64 `json:"reservationSuccess" dc:"直播预约成功数"`
ReservationCost *float64 `json:"reservationCost" dc:"直播预约成功成本"`
StandardLivePlayedStarted *int64 `json:"standardLivePlayedStarted" dc:"直播观看数"`
AdLivePlayCnt *int64 `json:"adLivePlayCnt" dc:"直播间人气数"`
AdLivePlayCntCost *float64 `json:"adLivePlayCntCost" dc:"直播间人气成本"`
LiveAudienceCost *float64 `json:"liveAudienceCost" dc:"直播观看成本"`
LiveEventGoodsView *int64 `json:"liveEventGoodsView" dc:"直播间商品点击数"`
GoodsClickRatio *float64 `json:"goodsClickRatio" dc:"直播间商品点击率"`
DirectAttrPlatNewBuyerCnt *int64 `json:"directAttrPlatNewBuyerCnt" dc:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30AttrPlatTotalBuyerCnt" dc:"30 日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `json:"directAttrSellerNewBuyerCnt" dc:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30AttrSellerTotalBuyerCnt" dc:"30 日累计店铺新客"`
T3Gmv *float64 `json:"t3Gmv" dc:"3 日累计 GMV"`
T3OrderCnt *int64 `json:"t3OrderCnt" dc:"3 日累计订单数"`
T3Roi *float64 `json:"t3Roi" dc:"3 日累计 ROI"`
T7IndirectOrderAmt *float64 `json:"t7IndirectOrderAmt" dc:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `json:"t7IndirectOrderCnt" dc:"7 日间接订单数"`
FansT0GmvPerFans *float64 `json:"fansT0GmvPerFans" dc:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `json:"fansT3GmvPerFans" dc:"3 日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `json:"fansT7GmvPerFans" dc:"7 日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `json:"fansT15GmvPerFans" dc:"15 日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `json:"fansT30GmvPerFans" dc:"30 日新增粉丝人均销售额"`
RecoFansCost *float64 `json:"recoFansCost" dc:"涨粉成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"智能优惠券订单数"`
FansT0Gmv *float64 `json:"fansT0Gmv" dc:"涨粉当日 GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" dc:"涨粉次日 GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" dc:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" dc:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" dc:"涨粉 30 日 GMV"`
FansT0Roi *float64 `json:"fansT0Roi" dc:"涨粉当日 ROI"`
FansT1Roi *float64 `json:"fansT1Roi" dc:"涨粉次日 ROI"`
FansT7Roi *float64 `json:"fansT7Roi" dc:"涨粉 7 日 ROI"`
FansT15Roi *float64 `json:"fansT15Roi" dc:"涨粉 15 日 ROI"`
FansT30Roi *float64 `json:"fansT30Roi" dc:"涨粉 30 日 ROI"`
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0ShopNewBuyerOrderPaymentAmt" dc:"当日新客 GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1ShopNewBuyerOrderPaymentAmt" dc:"投后 1 日新客 GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3ShopNewBuyerOrderPaymentAmt" dc:"投后 3 日新客 GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7ShopNewBuyerOrderPaymentAmt" dc:"投后 7 日新客 GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15ShopNewBuyerOrderPaymentAmt" dc:"投后 15 日新客 GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30ShopNewBuyerOrderPaymentAmt" dc:"投后 30 日新客 GMV"`
T0ShopNewBuyerOrderCnt *int64 `json:"t0ShopNewBuyerOrderCnt" dc:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1ShopNewBuyerOrderCnt" dc:"投后 1 日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3ShopNewBuyerOrderCnt" dc:"投后 3 日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7ShopNewBuyerOrderCnt" dc:"投后 7 日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15ShopNewBuyerOrderCnt" dc:"投后 15 日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30ShopNewBuyerOrderCnt" dc:"投后 30 日新客成交订单量"`
T1NewBuyerRepurchaseRatio *float64 `json:"t1NewBuyerRepurchaseRatio" dc:"投后 1 日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3NewBuyerRepurchaseRatio" dc:"投后 3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7NewBuyerRepurchaseRatio" dc:"投后 7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15NewBuyerRepurchaseRatio" dc:"投后 15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30NewBuyerRepurchaseRatio" dc:"投后 30 日新客复购率"`
T0ShopNewBuyerRoi *float64 `json:"t0ShopNewBuyerRoi" dc:"投后当日新客 ROI"`
T1ShopNewBuyerRoi *float64 `json:"t1ShopNewBuyerRoi" dc:"投后 1 日新客 ROI"`
T3ShopNewBuyerRoi *float64 `json:"t3ShopNewBuyerRoi" dc:"投后 3 日新客 ROI"`
T7ShopNewBuyerRoi *float64 `json:"t7ShopNewBuyerRoi" dc:"投后 7 日新客 ROI"`
T15ShopNewBuyerRoi *float64 `json:"t15ShopNewBuyerRoi" dc:"投后 15 日新客 ROI"`
T30ShopNewBuyerRoi *float64 `json:"t30ShopNewBuyerRoi" dc:"投后 30 日新客 ROI"`
CreateCardOrderCnt *int64 `json:"createCardOrderCnt" dc:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `json:"forwardTsCreateCardOrderCnt" dc:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `json:"createCardOrderCost" dc:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `json:"forwardTsCreateCardOrderCost" dc:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `json:"activateCardOrderCnt" dc:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `json:"forwardTsActivateCardOrderCnt" dc:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `json:"activateCardOrderCost" dc:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `json:"forwardTsActivateCardOrderCost" dc:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `json:"createCardOrderRatio" dc:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `json:"forwardTsCreateCardOrderRatio" dc:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `json:"activateCardOrderCntRatio" dc:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `json:"forwardTsActivateCardOrderRatio" dc:"电话卡激活率(计费时间)"`
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
CampaignName string `json:"campaignName" dc:"计划名称"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
UnitName string `json:"unitName" dc:"单元名称"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
CreativeName string `json:"creativeName" dc:"创意名称"`
CidActualRoiAfterSubsidy *float64 `json:"cidActualRoiAfterSubsidy" dc:"补贴后实际 ROI"`
CidCouponAmount *int64 `json:"cidCouponAmount" dc:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `json:"cidCouponCallbackPaidRefundAmount" dc:"退单有回传_核销券金额"`
CidVoucherCost *float64 `json:"cidVoucherCost" dc:"券成本"`
}
type ListCreativeReportDetailReq struct {
g.Meta `path:"/listCreativeReportDetail" method:"get" tags:"广告效果明细报表" summary:"获取广告效果指标明细表列表" dc:"分页查询广告效果指标明细表列表"`
*beans.Page
ReportDateStr string `json:"reportDateStr" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
Keyword string `json:"keyword" dc:"关键字"`
}
type ListCreativeReportDetailRes struct {
List []*CreativeReportDetailItem `json:"list" dc:"广告效果明细列表"`
Total int `json:"total" dc:"总数"`
}
@@ -1,342 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateCreativeReportSumReq 创建广告效果指标表请求
type CreateCreativeReportSumReq struct {
g.Meta `path:"/createCreativeReportSum" method:"post" tags:"广告效果报表" summary:"创建广告效果指标表" dc:"创建新的广告效果指标表"`
*CreativeReportSumItem
}
// CreateCreativeReportSumRes 创建广告效果指标表响应
type CreateCreativeReportSumRes struct {
Id int64 `json:"id" dc:"广告效果 ID"`
}
// BatchCreateCreativeReportSumReq 批量创建广告效果指标表请求
type BatchCreateCreativeReportSumReq struct {
g.Meta `path:"/batchCreateCreativeReportSum" method:"post" tags:"广告效果报表" summary:"批量创建广告效果指标表" dc:"批量创建广告效果指标表"`
Items []*CreativeReportSumItem `json:"items" v:"required" dc:"广告效果列表"`
}
// BatchCreateCreativeReportSumRes 批量创建广告效果指标表响应
type BatchCreateCreativeReportSumRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// CreativeReportSumItem 广告效果指标表项
type CreativeReportSumItem struct {
// 订单金额相关字段
T0OrderPaymentAmt *float64 `json:"t0OrderPaymentAmt" dc:"当日总成交订单金额"`
// 素材类型字段
CreativeMaterialType string `json:"creativeMaterialType" dc:"视频素材类型(视频:HORIZONTAL_SCREEN, VERTICAL_SCREEN, UNKNOWN_TYPE;图集:ATLAS;长图:ATLAS_VERTICAL"`
// 直播相关字段
LiveName string `json:"liveName" dc:"直播间名称"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
// 图片相关字段
PicUrl string `json:"picUrl" dc:"图片 URL"`
PicName string `json:"picName" dc:"图片名称"`
PicId string `json:"picId" dc:"图片 Id"`
// 封面相关字段
CoverUrl string `json:"coverUrl" dc:"封面 URL"`
CoverId *int64 `json:"coverId" dc:"封面 Id"`
// 转化率相关字段
ItemOrderConversionRatio *float64 `json:"itemOrderConversionRatio" dc:"转化率"`
ItemCardClickRatio *float64 `json:"itemCardClickRatio" dc:"点击率"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
// 成本相关字段
LivePlayCntCost *float64 `json:"livePlayCntCost" dc:"直播间观看成本"`
AdMerchantFollowCost *float64 `json:"adMerchantFollowCost" dc:"涨粉成本"`
AdMerchantFollow *int64 `json:"adMerchantFollow" dc:"涨粉数"`
// 净成交相关字段
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交 ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交 GMV"`
// 视频相关字段
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoIdStr string `json:"photoIdStr" dc:"视频 id"`
PhotoId string `json:"photoId" dc:"视频 id"`
// 用户属性相关字段
ModPriceSegment string `json:"modPriceSegment" dc:"设备价格区间"`
AgeSegment string `json:"ageSegment" dc:"年龄段"`
Province string `json:"province" dc:"省份名称"`
Gender string `json:"gender" dc:"性别"`
// 播放率相关字段
AdPhotoPlayedFiveRatio *float64 `json:"adPhotoPlayedFiveRatio" dc:"作品 5 秒播放率"`
AdPhotoPlayedThreeRatio *float64 `json:"adPhotoPlayedThreeRatio" dc:"作品 3 秒播放率"`
// 订单提交相关字段
OrderSubmitRoi *float64 `json:"orderSubmitRoi" dc:"订单提交 ROI"`
OrderSubmitAmt *int64 `json:"orderSubmitAmt" dc:"外部订单金额"`
EventOrderSubmitCost *float64 `json:"eventOrderSubmitCost" dc:"订单提交成本"`
EventOrderSubmit *int64 `json:"eventOrderSubmit" dc:"订单提交数"`
EventOrderPaidRoi *float64 `json:"eventOrderPaidRoi" dc:"订单支付率"`
// 用户行为相关字段
EventAppInvoked *int64 `json:"eventAppInvoked" dc:"唤起应用数"`
EventAddShoppingCart *int64 `json:"eventAddShoppingCart" dc:"添加购物车次数"`
// 转化成本相关字段
ConversionNumCost *float64 `json:"conversionNumCost" dc:"转化成本(回传时间)"`
AdEffectivePlayNum *int64 `json:"adEffectivePlayNum" dc:"有效播放数"`
AdItemClick *int64 `json:"adItemClick" dc:"行为数"`
// 商品相关字段
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
// 花费相关字段
CostTotal *float64 `json:"costTotal" dc:"花费"`
// 曝光相关字段
AdShow *int64 `json:"adShow" dc:"曝光数"`
AdShow1kCost *float64 `json:"adShow1kCost" dc:"平均千次广告曝光花费"`
Impression *int64 `json:"impression" dc:"封面曝光数"`
PhotoClick *int64 `json:"photoClick" dc:"封面点击数"`
PhotoClickRatio *float64 `json:"photoClickRatio" dc:"封面点击率"`
Click *int64 `json:"click" dc:"素材曝光数"`
// 行为相关字段
ActionbarClick *int64 `json:"actionbarClick" dc:"行为数"`
ActionbarClickCost *float64 `json:"actionbarClickCost" dc:"行为成本"`
EspClickRatio *float64 `json:"espClickRatio" dc:"行为率"`
ActionRatio *float64 `json:"actionRatio" dc:"素材点击率"`
AdItemClickCount *int64 `json:"adItemClickCount" dc:"预约组件点击数"`
// 直播时长相关字段
EspLivePlayedSeconds *int64 `json:"espLivePlayedSeconds" dc:"直播平均观看时长"`
// 作品播放相关字段
PlayedThreeSeconds *int64 `json:"playedThreeSeconds" dc:"作品 3 秒播放数"`
Play3sRatio *float64 `json:"play3sRatio" dc:"作品 3 秒播放率"`
PlayedFiveSeconds *int64 `json:"playedFiveSeconds" dc:"作品 5 秒播放数"`
Play5sRatio *float64 `json:"play5sRatio" dc:"作品 5 秒播放率"`
PlayedEnd *int64 `json:"playedEnd" dc:"作品完播数"`
PlayEndRatio *float64 `json:"playEndRatio" dc:"作品完播率"`
// 作品互动相关字段
Share *int64 `json:"share" dc:"作品分享数"`
Comment *int64 `json:"comment" dc:"作品评论数"`
Likes *int64 `json:"likes" dc:"作品点赞数"`
Report *int64 `json:"report" dc:"作品举报数"`
Block *int64 `json:"block" dc:"作品拉黑数"`
ItemNegative *int64 `json:"itemNegative" dc:"详情页减少此类作品数"`
// 直播互动相关字段
LiveShare *int64 `json:"liveShare" dc:"直播送礼数"`
LiveComment *int64 `json:"liveComment" dc:"直播评论数"`
LiveReward *int64 `json:"liveReward" dc:"直播送礼数"`
// 有效播放相关字段
EffectivePlayCount *int64 `json:"effectivePlayCount" dc:"有效播放数"`
EffectivePlayRatio *float64 `json:"effectivePlayRatio" dc:"有效播放率"`
// 转化相关字段
ConversionNum *int64 `json:"conversionNum" dc:"转化数"`
ConversionCostEsp *float64 `json:"conversionCostEsp" dc:"转化成本"`
Roi *float64 `json:"roi" dc:"直接 ROI"`
Gmv *float64 `json:"gmv" dc:"直接 GMV"`
// 累计 GMV 相关字段
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计 GMV"`
T1Gmv *float64 `json:"t1Gmv" dc:"次日累计 GMV"`
T7Gmv *float64 `json:"t7Gmv" dc:"7 日累计 GMV"`
T15Gmv *float64 `json:"t15Gmv" dc:"15 日累计 GMV"`
T30Gmv *float64 `json:"t30Gmv" dc:"30 日累计 GMV"`
// 累计 ROI 相关字段
T0Roi *float64 `json:"t0Roi" dc:"当日累计 ROI"`
T1Roi *float64 `json:"t1Roi" dc:"次日累计 ROI"`
T7Roi *float64 `json:"t7Roi" dc:"7 日累计 ROI"`
T15Roi *float64 `json:"t15Roi" dc:"15 日累计 ROI"`
T30Roi *float64 `json:"t30Roi" dc:"30 日累计 ROI"`
// 订单相关字段
PaiedOrder *int64 `json:"paiedOrder" dc:"直接订单数"`
OrderRatio *float64 `json:"orderRatio" dc:"直接下单率"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
T0OrderCntRatio *float64 `json:"t0OrderCntRatio" dc:"累计订单下单率"`
T1OrderCnt *int64 `json:"t1OrderCnt" dc:"次日累计订单数"`
T7OrderCnt *int64 `json:"t7OrderCnt" dc:"7 日累计订单数"`
T15OrderCnt *int64 `json:"t15OrderCnt" dc:"15 日累计订单数"`
T30OrderCnt *int64 `json:"t30OrderCnt" dc:"30 日累计订单数"`
// 涨粉相关字段
MerchantRecoFans *int64 `json:"merchantRecoFans" dc:"涨粉数"`
T1Retention *float64 `json:"t1Retention" dc:"次日涨粉留存数"`
T7Retention *float64 `json:"t7Retention" dc:"7 日涨粉留存数"`
T15Retention *float64 `json:"t15Retention" dc:"15 日涨粉留存数"`
T30Retention *float64 `json:"t30Retention" dc:"30 日涨粉留存数"`
T1RetentionRatio *float64 `json:"t1RetentionRatio" dc:"次日涨粉留存率"`
T7RetentionRatio *float64 `json:"t7RetentionRatio" dc:"7 日涨粉留存率"`
T15RetentionRatio *float64 `json:"t15RetentionRatio" dc:"15 日涨粉留存率"`
T30RetentionRatio *float64 `json:"t30RetentionRatio" dc:"30 日涨粉留存率"`
// 直播预约相关字段
ReservationSuccess *int64 `json:"reservationSuccess" dc:"直播预约成功数"`
ReservationCost *float64 `json:"reservationCost" dc:"直播预约成功成本"`
// 直播观看相关字段
StandardLivePlayedStarted *int64 `json:"standardLivePlayedStarted" dc:"直播观看数"`
AdLivePlayCnt *int64 `json:"adLivePlayCnt" dc:"直播间人气数"`
AdLivePlayCntCost *float64 `json:"adLivePlayCntCost" dc:"直播间人气成本"`
LiveAudienceCost *float64 `json:"liveAudienceCost" dc:"直播观看成本"`
// 直播间商品相关字段
LiveEventGoodsView *int64 `json:"liveEventGoodsView" dc:"直播间商品点击数"`
GoodsClickRatio *float64 `json:"goodsClickRatio" dc:"直播间商品点击率"`
// 新客相关字段
DirectAttrPlatNewBuyerCnt *int64 `json:"directAttrPlatNewBuyerCnt" dc:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30AttrPlatTotalBuyerCnt" dc:"30 日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `json:"directAttrSellerNewBuyerCnt" dc:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30AttrSellerTotalBuyerCnt" dc:"30 日累计店铺新客"`
// 3 日累计相关字段
T3Gmv *float64 `json:"t3Gmv" dc:"3 日累计 GMV"`
T3OrderCnt *int64 `json:"t3OrderCnt" dc:"3 日累计订单数"`
T3Roi *float64 `json:"t3Roi" dc:"3 日累计 ROI"`
// 7 日间接订单相关字段
T7IndirectOrderAmt *float64 `json:"t7IndirectOrderAmt" dc:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `json:"t7IndirectOrderCnt" dc:"7 日间接订单数"`
// 粉丝人均销售额相关字段
FansT0GmvPerFans *float64 `json:"fansT0GmvPerFans" dc:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `json:"fansT3GmvPerFans" dc:"3 日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `json:"fansT7GmvPerFans" dc:"7 日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `json:"fansT15GmvPerFans" dc:"15 日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `json:"fansT30GmvPerFans" dc:"30 日新增粉丝人均销售额"`
// 涨粉成本相关字段
RecoFansCost *float64 `json:"recoFansCost" dc:"涨粉成本"`
// 智能优惠券相关字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"智能优惠券订单数"`
// 粉丝 GMV 相关字段
FansT0Gmv *float64 `json:"fansT0Gmv" dc:"涨粉当日 GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" dc:"涨粉次日 GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" dc:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" dc:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" dc:"涨粉 30 日 GMV"`
// 粉丝 ROI 相关字段
FansT0Roi *float64 `json:"fansT0Roi" dc:"涨粉当日 ROI"`
FansT1Roi *float64 `json:"fansT1Roi" dc:"涨粉次日 ROI"`
FansT7Roi *float64 `json:"fansT7Roi" dc:"涨粉 7 日 ROI"`
FansT15Roi *float64 `json:"fansT15Roi" dc:"涨粉 15 日 ROI"`
FansT30Roi *float64 `json:"fansT30Roi" dc:"涨粉 30 日 ROI"`
// 新客 GMV 相关字段
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0ShopNewBuyerOrderPaymentAmt" dc:"当日新客 GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1ShopNewBuyerOrderPaymentAmt" dc:"投后 1 日新客 GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3ShopNewBuyerOrderPaymentAmt" dc:"投后 3 日新客 GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7ShopNewBuyerOrderPaymentAmt" dc:"投后 7 日新客 GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15ShopNewBuyerOrderPaymentAmt" dc:"投后 15 日新客 GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30ShopNewBuyerOrderPaymentAmt" dc:"投后 30 日新客 GMV"`
// 新客订单数相关字段
T0ShopNewBuyerOrderCnt *int64 `json:"t0ShopNewBuyerOrderCnt" dc:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1ShopNewBuyerOrderCnt" dc:"投后 1 日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3ShopNewBuyerOrderCnt" dc:"投后 3 日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7ShopNewBuyerOrderCnt" dc:"投后 7 日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15ShopNewBuyerOrderCnt" dc:"投后 15 日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30ShopNewBuyerOrderCnt" dc:"投后 30 日新客成交订单量"`
// 新客复购率相关字段
T1NewBuyerRepurchaseRatio *float64 `json:"t1NewBuyerRepurchaseRatio" dc:"投后 1 日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3NewBuyerRepurchaseRatio" dc:"投后 3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7NewBuyerRepurchaseRatio" dc:"投后 7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15NewBuyerRepurchaseRatio" dc:"投后 15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30NewBuyerRepurchaseRatio" dc:"投后 30 日新客复购率"`
// 新客 ROI 相关字段
T0ShopNewBuyerRoi *float64 `json:"t0ShopNewBuyerRoi" dc:"投后当日新客 ROI"`
T1ShopNewBuyerRoi *float64 `json:"t1ShopNewBuyerRoi" dc:"投后 1 日新客 ROI"`
T3ShopNewBuyerRoi *float64 `json:"t3ShopNewBuyerRoi" dc:"投后 3 日新客 ROI"`
T7ShopNewBuyerRoi *float64 `json:"t7ShopNewBuyerRoi" dc:"投后 7 日新客 ROI"`
T15ShopNewBuyerRoi *float64 `json:"t15ShopNewBuyerRoi" dc:"投后 15 日新客 ROI"`
T30ShopNewBuyerRoi *float64 `json:"t30ShopNewBuyerRoi" dc:"投后 30 日新客 ROI"`
// 制卡订单相关字段
CreateCardOrderCnt *int64 `json:"createCardOrderCnt" dc:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `json:"forwardTsCreateCardOrderCnt" dc:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `json:"createCardOrderCost" dc:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `json:"forwardTsCreateCardOrderCost" dc:"有效制卡订单成本(计费时间)"`
// 电话卡激活相关字段
ActivateCardOrderCnt *int64 `json:"activateCardOrderCnt" dc:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `json:"forwardTsActivateCardOrderCnt" dc:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `json:"activateCardOrderCost" dc:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `json:"forwardTsActivateCardOrderCost" dc:"电话卡激活订单成本(计费时间)"`
// 制卡订单率相关字段
CreateCardOrderRatio *float64 `json:"createCardOrderRatio" dc:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `json:"forwardTsCreateCardOrderRatio" dc:"有效制卡订单率(计费时间)"`
// 电话卡激活率相关字段
ActivateCardOrderCntRatio *float64 `json:"activateCardOrderCntRatio" dc:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `json:"forwardTsActivateCardOrderRatio" dc:"电话卡激活率(计费时间)"`
// 直播观看相关字段
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
// 报告日期字段
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间"`
// 广告计划相关字段
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
CampaignName string `json:"campaignName" dc:"计划名称"`
// 广告单元相关字段
UnitId *int64 `json:"unitId" dc:"单元 ID"`
UnitName string `json:"unitName" dc:"单元名称"`
// 广告创意相关字段
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
CreativeName string `json:"creativeName" dc:"创意名称"`
// 新增字段
CidActualRoiAfterSubsidy *float64 `json:"cidActualRoiAfterSubsidy" dc:"补贴后实际 ROI"`
CidCouponAmount *int64 `json:"cidCouponAmount" dc:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `json:"cidCouponCallbackPaidRefundAmount" dc:"退单有回传_核销券金额"`
CidVoucherCost *float64 `json:"cidVoucherCost" dc:"券成本"`
}
// ListCreativeReportSumReq 获取广告效果指标表列表请求
type ListCreativeReportSumReq struct {
g.Meta `path:"/listCreativeReportSum" method:"get" tags:"广告效果报表" summary:"获取广告效果指标表列表" dc:"分页查询广告效果指标表列表"`
*beans.Page
ReportDateStr string `json:"reportDateStr" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
Keyword string `json:"keyword" dc:"关键字(搜索直播间名称、视频名称等)"`
}
// ListCreativeReportSumRes 获取广告效果指标表列表响应
type ListCreativeReportSumRes struct {
List []*CreativeReportSumItem `json:"list" dc:"广告效果列表"`
Total int `json:"total" dc:"总数"`
}
-234
View File
@@ -1,234 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateMaterialReportReq 创建素材报表数据请求
type CreateMaterialReportReq struct {
g.Meta `path:"/createMaterialReport" method:"post" tags:"素材报表" summary:"创建素材报表数据" dc:"创建新的素材报表数据"`
*MaterialReportItem
}
// CreateMaterialReportRes 创建素材报表数据响应
type CreateMaterialReportRes struct {
Id int64 `json:"id" dc:"素材报表数据 ID"`
}
// BatchCreateMaterialReportReq 批量创建素材报表数据请求
type BatchCreateMaterialReportReq struct {
g.Meta `path:"/batchCreateMaterialReport" method:"post" tags:"素材报表" summary:"批量创建素材报表数据" dc:"批量创建素材报表数据"`
Items []*MaterialReportItem `json:"items" v:"required" dc:"素材报表数据列表"`
}
// BatchCreateMaterialReportRes 批量创建素材报表数据响应
type BatchCreateMaterialReportRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// MaterialReportItem 素材报表数据项
type MaterialReportItem struct {
CreatedBy *string `json:"createdBy" dc:"创建人"`
UpdatedBy *string `json:"updatedBy" dc:"更新人"`
T0OrderPaymentAmt *float64 `json:"t0OrderPaymentAmt" dc:"当日订单支付金额"`
CreativeMaterialType *string `json:"creativeMaterialType" dc:"创意素材类型"`
LiveName *string `json:"liveName" dc:"直播间名称"`
AuthorId *string `json:"authorId" dc:"作者 ID"`
PicUrl *string `json:"picUrl" dc:"图片 URL"`
PicName *string `json:"picName" dc:"图片名称"`
PicId *string `json:"picId" dc:"图片 ID"`
CoverUrl *string `json:"coverUrl" dc:"封面 URL"`
CoverId *int64 `json:"coverId" dc:"封面 ID"`
ItemOrderConversionRatio *float64 `json:"itemOrderConversionRatio" dc:"商品订单转化率"`
ItemCardClickRatio *float64 `json:"itemCardClickRatio" dc:"商品卡点击率"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
LivePlayCntCost *float64 `json:"livePlayCntCost" dc:"直播间观看成本"`
AdMerchantFollowCost *float64 `json:"adMerchantFollowCost" dc:"商家涨粉成本"`
AdMerchantFollow *int64 `json:"adMerchantFollow" dc:"商家涨粉数"`
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"当日净成交 ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"当日净成交 GMV"`
PhotoName *string `json:"photoName" dc:"照片名称"`
PhotoIdStr *string `json:"photoIdStr" dc:"照片 ID 字符串"`
PhotoId *string `json:"photoId" dc:"照片 ID"`
ModPriceSegment *string `json:"modPriceSegment" dc:"价格区间"`
AgeSegment *string `json:"ageSegment" dc:"年龄段"`
Province *string `json:"province" dc:"省份"`
Gender *string `json:"gender" dc:"性别"`
AdPhotoPlayedFiveRatio *float64 `json:"adPhotoPlayedFiveRatio" dc:"广告照片播放 5 秒比率"`
AdPhotoPlayedThreeRatio *float64 `json:"adPhotoPlayedThreeRatio" dc:"广告照片播放 3 秒比率"`
OrderSubmitRoi *float64 `json:"orderSubmitRoi" dc:"提交订单 ROI"`
OrderSubmitAmt *int64 `json:"orderSubmitAmt" dc:"提交订单金额"`
EventOrderSubmitCost *float64 `json:"eventOrderSubmitCost" dc:"事件订单提交成本"`
EventOrderSubmit *int64 `json:"eventOrderSubmit" dc:"事件订单提交数"`
EventOrderPaidRoi *float64 `json:"eventOrderPaidRoi" dc:"事件订单支付 ROI"`
EventAppInvoked *int64 `json:"eventAppInvoked" dc:"事件 APP 调用数"`
EventAddShoppingCart *int64 `json:"eventAddShoppingCart" dc:"事件加购数"`
ConversionNumCost *float64 `json:"conversionNumCost" dc:"转化数量成本"`
AdEffectivePlayNum *int64 `json:"adEffectivePlayNum" dc:"广告有效播放数"`
AdItemClick *int64 `json:"adItemClick" dc:"广告商品点击数"`
MerchantProductId *string `json:"merchantProductId" dc:"商家商品 ID"`
CostTotal *float64 `json:"costTotal" dc:"总花费"`
AdShow *int64 `json:"adShow" dc:"广告展示数"`
AdShow1kCost *float64 `json:"adShow1kCost" dc:"千次展示成本"`
Impression *int64 `json:"impression" dc:"曝光数"`
PhotoClick *int64 `json:"photoClick" dc:"照片点击数"`
PhotoClickRatio *float64 `json:"photoClickRatio" dc:"照片点击率"`
Click *int64 `json:"click" dc:"点击数"`
ActionbarClick *int64 `json:"actionbarClick" dc:"操作栏点击数"`
ActionbarClickCost *float64 `json:"actionbarClickCost" dc:"操作栏点击成本"`
EspClickRatio *float64 `json:"espClickRatio" dc:"ESP 点击率"`
ActionRatio *float64 `json:"actionRatio" dc:"操作比率"`
AdItemCount *int64 `json:"adItemCount" dc:"广告商品点击次数"`
EspLivePlayedSeconds *int64 `json:"espLivePlayedSeconds" dc:"ESP 直播播放秒数"`
PlayedThreeSeconds *int64 `json:"playedThreeSeconds" dc:"播放 3 秒数"`
Play3sRatio *float64 `json:"play3sRatio" dc:"3 秒播放率"`
PlayedFiveSeconds *int64 `json:"playedFiveSeconds" dc:"播放 5 秒数"`
Play5sRatio *float64 `json:"play5sRatio" dc:"5 秒播放率"`
PlayedEnd *int64 `json:"playedEnd" dc:"播放结束数"`
PlayEndRatio *float64 `json:"playEndRatio" dc:"完播率"`
Share *int64 `json:"share" dc:"分享数"`
Comment *int64 `json:"comment" dc:"评论数"`
Likes *int64 `json:"likes" dc:"点赞数"`
Report *int64 `json:"report" dc:"举报数"`
Block *int64 `json:"block" dc:"拉黑数"`
ItemNegative *int64 `json:"itemNegative" dc:"商品负反馈数"`
LiveShare *int64 `json:"liveShare" dc:"直播分享数"`
LiveComment *int64 `json:"liveComment" dc:"直播评论数"`
LiveReward *int64 `json:"liveReward" dc:"直播打赏数"`
EffectivePlayCount *int64 `json:"effectivePlayCount" dc:"有效播放数"`
EffectivePlayRatio *float64 `json:"effectivePlayRatio" dc:"有效播放率"`
ConversionNum *int64 `json:"conversionNum" dc:"转化数"`
ConversionCostEsp *float64 `json:"conversionCostEsp" dc:"转化成本 ESP"`
Roi *float64 `json:"roi" dc:"ROI"`
Gmv *float64 `json:"gmv" dc:"GMV"`
T0Gmv *float64 `json:"t0Gmv" dc:"当日 GMV"`
T1Gmv *float64 `json:"t1Gmv" dc:"次日 GMV"`
T7Gmv *float64 `json:"t7Gmv" dc:"7 日 GMV"`
T15Gmv *float64 `json:"t15Gmv" dc:"15 日 GMV"`
T30Gmv *float64 `json:"t30Gmv" dc:"30 日 GMV"`
T0Roi *float64 `json:"t0Roi" dc:"当日 ROI"`
T1Roi *float64 `json:"t1Roi" dc:"次日 ROI"`
T7Roi *float64 `json:"t7Roi" dc:"7 日 ROI"`
T15Roi *float64 `json:"t15Roi" dc:"15 日 ROI"`
T30Roi *float64 `json:"t30Roi" dc:"30 日 ROI"`
PaiedOrder *int64 `json:"paiedOrder" dc:"支付订单数"`
OrderRatio *float64 `json:"orderRatio" dc:"订单比率"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日订单成本"`
T0OrderCntRatio *float64 `json:"t0OrderCntRatio" dc:"当日订单比率"`
T1OrderCnt *int64 `json:"t1OrderCnt" dc:"次日订单数"`
T7OrderCnt *int64 `json:"t7OrderCnt" dc:"7 日订单数"`
T15OrderCnt *int64 `json:"t15OrderCnt" dc:"15 日订单数"`
T30OrderCnt *int64 `json:"t30OrderCnt" dc:"30 日订单数"`
MerchantRecoFans *int64 `json:"merchantRecoFans" dc:"商家推荐粉丝数"`
T1Retention *float64 `json:"t1Retention" dc:"次日留存率"`
T7Retention *float64 `json:"t7Retention" dc:"7 日留存率"`
T15Retention *float64 `json:"t15Retention" dc:"15 日留存率"`
T30Retention *float64 `json:"t30Retention" dc:"30 日留存率"`
T1RetentionRatio *float64 `json:"t1RetentionRatio" dc:"次日留存比率"`
T7RetentionRatio *float64 `json:"t7RetentionRatio" dc:"7 日留存比率"`
T15RetentionRatio *float64 `json:"t15RetentionRatio" dc:"15 日留存比率"`
T30RetentionRatio *float64 `json:"t30RetentionRatio" dc:"30 日留存比率"`
ReservationSuccess *int64 `json:"reservationSuccess" dc:"预约成功数"`
ReservationCost *float64 `json:"reservationCost" dc:"预约成本"`
StandardLivePlayedStarted *int64 `json:"standardLivePlayedStarted" dc:"标准直播开始数"`
AdLivePlayCnt *int64 `json:"adLivePlayCnt" dc:"广告直播播放数"`
AdLivePlayCntCost *float64 `json:"adLivePlayCntCost" dc:"广告直播播放成本"`
LiveAudienceCost *float64 `json:"liveAudienceCost" dc:"直播观众成本"`
LiveEventGoodsView *int64 `json:"liveEventGoodsView" dc:"直播事件商品浏览数"`
GoodsClickRatio *float64 `json:"goodsClickRatio" dc:"商品点击率"`
DirectAttrPlatNewBuyerCnt *int64 `json:"directAttrPlatNewBuyerCnt" dc:"直接吸引平台新买家数"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30AttrPlatTotalBuyerCnt" dc:"30 日吸引平台总买家数"`
DirectAttrSellerNewBuyerCnt *int64 `json:"directAttrSellerNewBuyerCnt" dc:"直接吸引商家新买家数"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30AttrSellerTotalBuyerCnt" dc:"30 日吸引商家总买家数"`
T3Gmv *float64 `json:"t3Gmv" dc:"3 日 GMV"`
T3OrderCnt *int64 `json:"t3OrderCnt" dc:"3 日订单数"`
T3Roi *float64 `json:"t3Roi" dc:"3 日 ROI"`
T7IndirectOrderAmt *float64 `json:"t7IndirectOrderAmt" dc:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `json:"t7IndirectOrderCnt" dc:"7 日间接订单数"`
FansT0GmvPerFans *float64 `json:"fansT0GmvPerFans" dc:"粉丝当日人均 GMV"`
FansT3GmvPerFans *float64 `json:"fansT3GmvPerFans" dc:"粉丝 3 日人均 GMV"`
FansT7GmvPerFans *float64 `json:"fansT7GmvPerFans" dc:"粉丝 7 日人均 GMV"`
FansT15GmvPerFans *float64 `json:"fansT15GmvPerFans" dc:"粉丝 15 日人均 GMV"`
FansT30GmvPerFans *float64 `json:"fansT30GmvPerFans" dc:"粉丝 30 日人均 GMV"`
RecoFansCost *float64 `json:"recoFansCost" dc:"推荐粉丝成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"白盒直接订单支付金额"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"白盒直接订单数"`
FansT0Gmv *float64 `json:"fansT0Gmv" dc:"粉丝当日 GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" dc:"粉丝次日 GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" dc:"粉丝 7 日 GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" dc:"粉丝 15 日 GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" dc:"粉丝 30 日 GMV"`
FansT0Roi *float64 `json:"fansT0Roi" dc:"粉丝当日 ROI"`
FansT1Roi *float64 `json:"fansT1Roi" dc:"粉丝次日 ROI"`
FansT7Roi *float64 `json:"fansT7Roi" dc:"粉丝 7 日 ROI"`
FansT15Roi *float64 `json:"fansT15Roi" dc:"粉丝 15 日 ROI"`
FansT30Roi *float64 `json:"fansT30Roi" dc:"粉丝 30 日 ROI"`
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0ShopNewBuyerOrderPaymentAmt" dc:"当日店铺新客订单支付金额"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1ShopNewBuyerOrderPaymentAmt" dc:"次日店铺新客订单支付金额"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3ShopNewBuyerOrderPaymentAmt" dc:"3 日店铺新客订单支付金额"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7ShopNewBuyerOrderPaymentAmt" dc:"7 日店铺新客订单支付金额"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15ShopNewBuyerOrderPaymentAmt" dc:"15 日店铺新客订单支付金额"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30ShopNewBuyerOrderPaymentAmt" dc:"30 日店铺新客订单支付金额"`
T0ShopNewBuyerOrderCnt *int64 `json:"t0ShopNewBuyerOrderCnt" dc:"当日店铺新客订单数"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1ShopNewBuyerOrderCnt" dc:"次日店铺新客订单数"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3ShopNewBuyerOrderCnt" dc:"3 日店铺新客订单数"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7ShopNewBuyerOrderCnt" dc:"7 日店铺新客订单数"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15ShopNewBuyerOrderCnt" dc:"15 日店铺新客订单数"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30ShopNewBuyerOrderCnt" dc:"30 日店铺新客订单数"`
T1NewBuyerRepurchaseRatio *float64 `json:"t1NewBuyerRepurchaseRatio" dc:"次日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3NewBuyerRepurchaseRatio" dc:"3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7NewBuyerRepurchaseRatio" dc:"7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15NewBuyerRepurchaseRatio" dc:"15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30NewBuyerRepurchaseRatio" dc:"30 日新客复购率"`
T0ShopNewBuyerRoi *float64 `json:"t0ShopNewBuyerRoi" dc:"当日店铺新客 ROI"`
T1ShopNewBuyerRoi *float64 `json:"t1ShopNewBuyerRoi" dc:"次日店铺新客 ROI"`
T3ShopNewBuyerRoi *float64 `json:"t3ShopNewBuyerRoi" dc:"3 日店铺新客 ROI"`
T7ShopNewBuyerRoi *float64 `json:"t7ShopNewBuyerRoi" dc:"7 日店铺新客 ROI"`
T15ShopNewBuyerRoi *float64 `json:"t15ShopNewBuyerRoi" dc:"15 日店铺新客 ROI"`
T30ShopNewBuyerRoi *float64 `json:"t30ShopNewBuyerRoi" dc:"30 日店铺新客 ROI"`
CreateCardOrderCnt *int64 `json:"createCardOrderCnt" dc:"创建卡片订单数"`
ForwardTsCreateCardOrderCnt *int64 `json:"forwardTsCreateCardOrderCnt" dc:"转发 TS 创建卡片订单数"`
CreateCardOrderCost *float64 `json:"createCardOrderCost" dc:"创建卡片订单成本"`
ForwardTsCreateCardOrderCost *float64 `json:"forwardTsCreateCardOrderCost" dc:"转发 TS 创建卡片订单成本"`
ActivateCardOrderCnt *int64 `json:"activateCardOrderCnt" dc:"激活卡片订单数"`
ForwardTsActivateCardOrderCnt *int64 `json:"forwardTsActivateCardOrderCnt" dc:"转发 TS 激活卡片订单数"`
ActivateCardOrderCost *float64 `json:"activateCardOrderCost" dc:"激活卡片订单成本"`
ForwardTsActivateCardOrderCost *float64 `json:"forwardTsActivateCardOrderCost" dc:"转发 TS 激活卡片订单成本"`
CreateCardOrderRatio *float64 `json:"createCardOrderRatio" dc:"创建卡片订单比率"`
ForwardTsCreateCardOrderRatio *float64 `json:"forwardTsCreateCardOrderRatio" dc:"转发 TS 创建卡片订单比率"`
ActivateCardOrderCntRatio *float64 `json:"activateCardOrderCntRatio" dc:"激活卡片订单比率"`
ForwardTsActivateCardOrderRatio *float64 `json:"forwardTsActivateCardOrderRatio" dc:"转发 TS 激活卡片订单比率"`
LivePlayCnt *int64 `json:"livePlayCnt" dc:"直播播放数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"商品入口点击数"`
ShowCnt *int64 `json:"showCnt" dc:"展示数"`
ReportDateStr string `json:"reportDateStr" v:"required" dc:"报告日期(格式:YYYY-MM-DD"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
CampaignName *string `json:"campaignName" dc:"计划名称"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
UnitName *string `json:"unitName" dc:"单元名称"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
CreativeName *string `json:"creativeName" dc:"创意名称"`
}
// ListMaterialReportReq 获取素材报表数据列表请求
type ListMaterialReportReq struct {
g.Meta `path:"/listMaterialReport" method:"get" tags:"素材报表" summary:"获取素材报表数据列表" dc:"分页查询素材报表数据列表"`
*beans.Page
ReportDateStr string `json:"reportDateStr" dc:"报告日期"`
PhotoId string `json:"photoId" dc:"照片 ID"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
Keyword string `json:"keyword" dc:"关键字(搜索照片名称、计划名称等)"`
}
// ListMaterialReportRes 获取素材报表数据列表响应
type ListMaterialReportRes struct {
List []*MaterialReportItem `json:"list" dc:"素材报表数据列表"`
Total int `json:"total" dc:"总数"`
}
-210
View File
@@ -1,210 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// PopulationReportItem 调控任务数据项
type PopulationReportItem struct {
PhotoName string `json:"photoName" description:"视频名称"`
PhotoIdStr string `json:"photoIdStr" description:"视频 id"`
PhotoId string `json:"photoId" description:"视频 id"`
ModPriceSegment string `json:"modPriceSegment" description:"设备价格区间"`
AgeSegment string `json:"ageSegment" description:"年龄段"`
Province string `json:"province" description:"省份名称"`
Gender string `json:"gender" description:"性别"`
MerchantProductId string `json:"merchantProductId" description:"商品 ID"`
ReportDateStr string `json:"reportDateStr" v:"required" description:"时间"`
CampaignId *int64 `json:"campaignId" description:"计划 ID"`
CampaignName string `json:"campaignName" description:"计划名称"`
UnitId *int64 `json:"unitId" description:"单元 ID"`
UnitName string `json:"unitName" description:"单元名称"`
CreativeId *int64 `json:"creativeId" description:"创意 ID"`
CreativeName string `json:"creativeName" description:"创意名称"`
AdPhotoPlayedFiveRatio *float64 `json:"adPhotoPlayedFiveRatio" description:"作品 5 秒播放率"`
AdPhotoPlayedThreeRatio *float64 `json:"adPhotoPlayedThreeRatio" description:"作品 3 秒播放率"`
OrderSubmitRoi *float64 `json:"orderSubmitRoi" description:"订单提交 ROI"`
OrderSubmitAmt *int64 `json:"orderSubmitAmt" description:"外部订单金额"`
EventOrderSubmitCost *float64 `json:"eventOrderSubmitCost" description:"订单提交成本"`
EventOrderSubmit *int64 `json:"eventOrderSubmit" description:"订单提交数"`
EventOrderPaidRoi *float64 `json:"eventOrderPaidRoi" description:"订单支付率"`
EventAppInvoked *int64 `json:"eventAppInvoked" description:"唤起应用数"`
EventAddShoppingCart *int64 `json:"eventAddShoppingCart" description:"添加购物车次数"`
ConversionNumCost *float64 `json:"conversionNumCost" description:"转化成本"`
AdEffectivePlayNum *int64 `json:"adEffectivePlayNum" description:"有效播放数"`
AdItemClick *int64 `json:"adItemClick" description:"行为数"`
CostTotal *float64 `json:"costTotal" description:"花费"`
AdShow *int64 `json:"adShow" description:"曝光数"`
AdShow1kCost *float64 `json:"adShow1kCost" description:"平均千次广告曝光花费"`
Impression *int64 `json:"impression" description:"封面曝光数"`
PhotoClick *int64 `json:"photoClick" description:"封面点击数"`
PhotoClickRatio *float64 `json:"photoClickRatio" description:"封面点击率"`
Click *int64 `json:"click" description:"素材曝光数"`
ActionbarClick *int64 `json:"actionbarClick" description:"行为数"`
ActionbarClickCost *float64 `json:"actionbarClickCost" description:"行为成本"`
EspClickRatio *float64 `json:"espClickRatio" description:"行为率"`
ActionRatio *float64 `json:"actionRatio" description:"素材点击率"`
AdItemClickCount *int64 `json:"adItemClickCount" description:"预约组件点击数"`
EspLivePlayedSeconds *int64 `json:"espLivePlayedSeconds" description:"直播平均观看时长"`
PlayedThreeSeconds *int64 `json:"playedThreeSeconds" description:"作品 3 秒播放数"`
Play3sRatio *float64 `json:"play3sRatio" description:"作品 3 秒播放率"`
PlayedFiveSeconds *int64 `json:"playedFiveSeconds" description:"作品 5 秒播放数"`
Play5sRatio *float64 `json:"play5sRatio" description:"作品 5 秒播放率"`
PlayedEnd *int64 `json:"playedEnd" description:"作品完播数"`
PlayEndRatio *float64 `json:"playEndRatio" description:"作品完播率"`
Share *int64 `json:"share" description:"作品分享数"`
Comment *int64 `json:"comment" description:"作品评论数"`
Likes *int64 `json:"likes" description:"作品点赞数"`
Report *int64 `json:"report" description:"作品举报数"`
Block *int64 `json:"block" description:"作品拉黑数"`
ItemNegative *int64 `json:"itemNegative" description:"详情页减少此类作品数"`
LiveShare *int64 `json:"liveShare" description:"直播送礼数"`
LiveComment *int64 `json:"liveComment" description:"直播评论数"`
LiveReward *int64 `json:"liveReward" description:"直播送礼数"`
EffectivePlayCount *int64 `json:"effectivePlayCount" description:"有效播放数"`
EffectivePlayRatio *float64 `json:"effectivePlayRatio" description:"有效播放率"`
ConversionNum *int64 `json:"conversionNum" description:"转化数"`
ConversionCostEsp *float64 `json:"conversionCostEsp" description:"转化成本"`
Roi *float64 `json:"roi" description:"直接 ROI"`
Gmv *float64 `json:"gmv" description:"直接 GMV"`
T0Gmv *float64 `json:"t0Gmv" description:"当日累计 GMV"`
T1Gmv *float64 `json:"t1Gmv" description:"次日累计 GMV"`
T3Gmv *float64 `json:"t3Gmv" description:"3 日累计 GMV"`
T7Gmv *float64 `json:"t7Gmv" description:"7 日累计 GMV"`
T15Gmv *float64 `json:"t15Gmv" description:"15 日累计 GMV"`
T30Gmv *float64 `json:"t30Gmv" description:"30 日累计 GMV"`
T0Roi *float64 `json:"t0Roi" description:"当日累计 ROI"`
T1Roi *float64 `json:"t1Roi" description:"次日累计 ROI"`
T3Roi *float64 `json:"t3Roi" description:"3 日累计 ROI"`
T7Roi *float64 `json:"t7Roi" description:"7 日累计 ROI"`
T15Roi *float64 `json:"t15Roi" description:"15 日累计 ROI"`
T30Roi *float64 `json:"t30Roi" description:"30 日累计 ROI"`
PaiedOrder *int64 `json:"paiedOrder" description:"直接订单数"`
OrderRatio *float64 `json:"orderRatio" description:"直接下单率"`
T0OrderCnt *int64 `json:"t0OrderCnt" description:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" description:"当日累计订单成本"`
T0OrderCntRatio *float64 `json:"t0OrderCntRatio" description:"累计订单下单率"`
T1OrderCnt *int64 `json:"t1OrderCnt" description:"次日累计订单数"`
T7OrderCnt *int64 `json:"t7OrderCnt" description:"7 日累计订单数"`
T15OrderCnt *int64 `json:"t15OrderCnt" description:"15 日累计订单数"`
T30OrderCnt *int64 `json:"t30OrderCnt" description:"30 日累计订单数"`
MerchantRecoFans *int64 `json:"merchantRecoFans" description:"涨粉数"`
T1Retention *float64 `json:"t1Retention" description:"次日涨粉留存数"`
T7Retention *float64 `json:"t7Retention" description:"7 日涨粉留存数"`
T15Retention *float64 `json:"t15Retention" description:"15 日涨粉留存数"`
T30Retention *float64 `json:"t30Retention" description:"30 日涨粉留存数"`
T1RetentionRatio *float64 `json:"t1RetentionRatio" description:"次日涨粉留存率"`
T7RetentionRatio *float64 `json:"t7RetentionRatio" description:"7 日涨粉留存率"`
T15RetentionRatio *float64 `json:"t15RetentionRatio" description:"15 日涨粉留存率"`
T30RetentionRatio *float64 `json:"t30RetentionRatio" description:"30 日涨粉留存率"`
ReservationSuccess *int64 `json:"reservationSuccess" description:"直播预约成功数"`
ReservationCost *float64 `json:"reservationCost" description:"直播预约成功成本"`
StandardLivePlayedStarted *int64 `json:"standardLivePlayedStarted" description:"直播观看数"`
AdLivePlayCnt *int64 `json:"adLivePlayCnt" description:"直播间人气数"`
AdLivePlayCntCost *float64 `json:"adLivePlayCntCost" description:"直播间人气成本"`
LiveAudienceCost *float64 `json:"liveAudienceCost" description:"直播观看成本"`
LiveEventGoodsView *int64 `json:"liveEventGoodsView" description:"直播间商品点击数"`
GoodsClickRatio *float64 `json:"goodsClickRatio" description:"直播间商品点击率"`
DirectAttrPlatNewBuyerCnt *int64 `json:"directAttrPlatNewBuyerCnt" description:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30AttrPlatTotalBuyerCnt" description:"30 日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `json:"directAttrSellerNewBuyerCnt" description:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30AttrSellerTotalBuyerCnt" description:"30 日累计店铺新客"`
T7IndirectOrderAmt *float64 `json:"t7IndirectOrderAmt" description:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `json:"t7IndirectOrderCnt" description:"7 日间接订单数"`
FansT0GmvPerFans *float64 `json:"fansT0GmvPerFans" description:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `json:"fansT3GmvPerFans" description:"3 日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `json:"fansT7GmvPerFans" description:"7 日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `json:"fansT15GmvPerFans" description:"15 日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `json:"fansT30GmvPerFans" description:"30 日新增粉丝人均销售额"`
RecoFansCost *float64 `json:"recoFansCost" description:"涨粉成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" description:"智能优惠券订单数"`
FansT0Gmv *float64 `json:"fansT0Gmv" description:"涨粉当日 GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" description:"涨粉次日 GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" description:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" description:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" description:"涨粉 30 日 GMV"`
FansT0Roi *float64 `json:"fansT0Roi" description:"涨粉当日 ROI"`
FansT1Roi *float64 `json:"fansT1Roi" description:"涨粉次日 ROI"`
FansT7Roi *float64 `json:"fansT7Roi" description:"涨粉 7 日 ROI"`
FansT15Roi *float64 `json:"fansT15Roi" description:"涨粉 15 日 ROI"`
FansT30Roi *float64 `json:"fansT30Roi" description:"涨粉 30 日 ROI"`
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0ShopNewBuyerOrderPaymentAmt" description:"当日新客 GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1ShopNewBuyerOrderPaymentAmt" description:"投后 1 日新客 GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3ShopNewBuyerOrderPaymentAmt" description:"投后 3 日新客 GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7ShopNewBuyerOrderPaymentAmt" description:"投后 7 日新客 GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15ShopNewBuyerOrderPaymentAmt" description:"投后 15 日新客 GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30ShopNewBuyerOrderPaymentAmt" description:"投后 30 日新客 GMV"`
T0ShopNewBuyerOrderCnt *int64 `json:"t0ShopNewBuyerOrderCnt" description:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1ShopNewBuyerOrderCnt" description:"投后 1 日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3ShopNewBuyerOrderCnt" description:"投后 3 日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7ShopNewBuyerOrderCnt" description:"投后 7 日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15ShopNewBuyerOrderCnt" description:"投后 15 日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30ShopNewBuyerOrderCnt" description:"投后 30 日新客成交订单量"`
T1NewBuyerRepurchaseRatio *float64 `json:"t1NewBuyerRepurchaseRatio" description:"投后 1 日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3NewBuyerRepurchaseRatio" description:"投后 3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7NewBuyerRepurchaseRatio" description:"投后 7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15NewBuyerRepurchaseRatio" description:"投后 15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30NewBuyerRepurchaseRatio" description:"投后 30 日新客复购率"`
T0ShopNewBuyerRoi *float64 `json:"t0ShopNewBuyerRoi" description:"投后当日新客 ROI"`
T1ShopNewBuyerRoi *float64 `json:"t1ShopNewBuyerRoi" description:"投后 1 日新客 ROI"`
T3ShopNewBuyerRoi *float64 `json:"t3ShopNewBuyerRoi" description:"投后 3 日新客 ROI"`
T7ShopNewBuyerRoi *float64 `json:"t7ShopNewBuyerRoi" description:"投后 7 日新客 ROI"`
T15ShopNewBuyerRoi *float64 `json:"t15ShopNewBuyerRoi" description:"投后 15 日新客 ROI"`
T30ShopNewBuyerRoi *float64 `json:"t30ShopNewBuyerRoi" description:"投后 30 日新客 ROI"`
CreateCardOrderCnt *int64 `json:"createCardOrderCnt" description:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `json:"forwardTsCreateCardOrderCnt" description:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `json:"createCardOrderCost" description:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `json:"forwardTsCreateCardOrderCost" description:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `json:"activateCardOrderCnt" description:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `json:"forwardTsActivateCardOrderCnt" description:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `json:"activateCardOrderCost" description:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `json:"forwardTsActivateCardOrderCost" description:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `json:"createCardOrderRatio" description:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `json:"forwardTsCreateCardOrderRatio" description:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `json:"activateCardOrderCntRatio" description:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `json:"forwardTsActivateCardOrderRatio" description:"电话卡激活率(计费时间)"`
LivePlayCnt *int64 `json:"livePlayCnt" description:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" description:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" description:"全站曝光"`
}
// CreatePopulationReportReq 创建调控任务数据请求参数
type CreatePopulationReportReq struct {
g.Meta `path:"/createPopulationReport" method:"post"`
*PopulationReportItem
}
// CreatePopulationReportRes 创建调控任务数据响应参数
type CreatePopulationReportRes struct {
Id int64 `json:"id"`
}
// BatchCreatePopulationReportReq 批量创建调控任务数据请求参数
type BatchCreatePopulationReportReq struct {
g.Meta `path:"/batchCreatePopulationReport" method:"post"`
Items []*PopulationReportItem `json:"items" v:"required"`
}
// BatchCreatePopulationReportRes 批量创建调控任务数据响应参数
type BatchCreatePopulationReportRes struct {
SuccessCount int64 `json:"successCount"`
FailCount int64 `json:"failCount"`
FailedItems []int64 `json:"failedItems"`
}
// ListPopulationReportReq 查询调控任务数据列表请求参数
type ListPopulationReportReq struct {
g.Meta `path:"/listPopulationReport" method:"post"`
*beans.Page
ReportDateStr string `json:"reportDateStr" description:"时间"`
PhotoId string `json:"photoId" description:"视频 id"`
Keyword string `json:"keyword" description:"关键词"`
}
// ListPopulationReportRes 查询调控任务数据列表响应参数
type ListPopulationReportRes struct {
List []*PopulationReportItem `json:"list"`
Total int `json:"total"`
}
@@ -1,132 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateStorewideReportDetailReq 创建广告效果指标表请求
type CreateStorewideReportDetailReq struct {
g.Meta `path:"/createStorewideReportDetail" method:"post" tags:"广告效果指标" summary:"创建广告效果指标表" dc:"创建新的广告效果指标表"`
*StorewideReportDetailItem
}
// CreateStorewideReportDetailRes 创建广告效果指标表响应
type CreateStorewideReportDetailRes struct {
Id int64 `json:"id" dc:"广告效果指标 ID"`
}
// BatchCreateStorewideReportDetailReq 批量创建广告效果指标表请求
type BatchCreateStorewideReportDetailReq struct {
g.Meta `path:"/batchCreateStorewideReportDetail" method:"post" tags:"广告效果指标" summary:"批量创建广告效果指标表" dc:"批量创建广告效果指标表"`
Items []*StorewideReportDetailItem `json:"items" v:"required" dc:"广告效果指标列表"`
}
// BatchCreateStorewideReportDetailRes 批量创建广告效果指标表响应
type BatchCreateStorewideReportDetailRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// StorewideReportDetailItem 广告效果指标表项
type StorewideReportDetailItem struct {
// 订单金额相关字段
T0OrderPaymentAmt *float64 `json:"t0OrderPaymentAmt" dc:"当日总成交订单金额"`
// 素材类型字段
CreativeMaterialType string `json:"creativeMaterialType" dc:"视频素材类型(视频:HORIZONTAL_SCREEN, VERTICAL_SCREEN, UNKNOWN_TYPE;图集:ATLAS;长图:ATLAS_VERTICAL"`
// 直播相关字段
LiveName string `json:"liveName" dc:"直播间名称"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
// 图片相关字段
PicUrl string `json:"picUrl" dc:"图片 URL"`
PicName string `json:"picName" dc:"图片名称"`
PicId string `json:"picId" dc:"图片 Id"`
// 封面相关字段
CoverUrl string `json:"coverUrl" dc:"封面 URL"`
CoverId *int64 `json:"coverId" dc:"封面 Id"`
// 商品卡点击相关字段
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
// 净成交相关字段
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交 ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交 GMV"`
// 视频相关字段
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoIdStr string `json:"photoIdStr" dc:"视频 id"`
PhotoId string `json:"photoId" dc:"视频 id"`
// 行为相关字段
AdItemClick *int64 `json:"adItemClick" dc:"行为数"`
// 商品相关字段
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
// 花费相关字段
CostTotal *float64 `json:"costTotal" dc:"花费"`
// 行为率字段
EspClickRatio *float64 `json:"espClickRatio" dc:"行为率"`
// 当日 GMV 相关字段
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计 GMV"`
T0Roi *float64 `json:"t0Roi" dc:"当日累计 ROI"`
// 当日订单相关字段
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
// 智能优惠券相关字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"智能优惠券订单数"`
// 直播观看相关字段
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
// 商品入口点击相关字段
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
// 曝光相关字段
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
// 报告日期字段
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间"`
// 广告计划相关字段
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
CampaignName string `json:"campaignName" dc:"计划名称"`
// 广告单元相关字段
UnitId *int64 `json:"unitId" dc:"单元 ID"`
UnitName string `json:"unitName" dc:"单元名称"`
// 广告创意相关字段
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
CreativeName string `json:"creativeName" dc:"创意名称"`
}
// ListStorewideReportDetailReq 获取广告效果指标表列表请求
type ListStorewideReportDetailReq struct {
g.Meta `path:"/listStorewideReportDetail" method:"get" tags:"广告效果指标" summary:"获取广告效果指标表列表" dc:"分页查询广告效果指标表列表"`
*beans.Page
ReportDateStr string `json:"reportDateStr" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
Keyword string `json:"keyword" dc:"关键字(搜索直播间名称、视频名称等)"`
}
// ListStorewideReportDetailRes 获取广告效果指标表列表响应
type ListStorewideReportDetailRes struct {
List []*StorewideReportDetailItem `json:"list" dc:"广告效果指标列表"`
Total int `json:"total" dc:"总数"`
}
@@ -1,92 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateStorewideReportSumReq 创建广告效果指标表请求
type CreateStorewideReportSumReq struct {
g.Meta `path:"/createStorewideReportSum" method:"post" tags:"广告效果指标" summary:"创建广告效果指标表" dc:"创建新的广告效果指标表"`
*StorewideReportSumItem
}
// CreateStorewideReportSumRes 创建广告效果指标表响应
type CreateStorewideReportSumRes struct {
Id int64 `json:"id" dc:"广告效果指标 ID"`
}
// BatchCreateStorewideReportSumReq 批量创建广告效果指标表请求
type BatchCreateStorewideReportSumReq struct {
g.Meta `path:"/batchCreateStorewideReportSum" method:"post" tags:"广告效果指标" summary:"批量创建广告效果指标表" dc:"批量创建广告效果指标表"`
Items []*StorewideReportSumItem `json:"items" v:"required" dc:"广告效果指标列表"`
}
// BatchCreateStorewideReportSumRes 批量创建广告效果指标表响应
type BatchCreateStorewideReportSumRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// StorewideReportSumItem 广告效果指标表项
type StorewideReportSumItem struct {
// 新增字段
T0OrderPaymentAmt *float64 `json:"t0OrderPaymentAmt" dc:"当日总成交订单金额"`
CreativeMaterialType string `json:"creativeMaterialType" dc:"视频素材类型"`
LiveName string `json:"liveName" dc:"直播间名称"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
PicUrl string `json:"picUrl" dc:"图片 URL"`
PicName string `json:"picName" dc:"图片名称"`
PicId string `json:"picId" dc:"图片 Id"`
CoverUrl string `json:"coverUrl" dc:"封面 URL"`
CoverId *int64 `json:"coverId" dc:"封面 Id"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交 ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交 GMV"`
// 原有字段
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoIdStr string `json:"photoIdStr" dc:"视频 id"`
PhotoId string `json:"photoId" dc:"视频 id"`
AdItemClick *int64 `json:"adItemClick" dc:"行为数"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
CostTotal *float64 `json:"costTotal" dc:"花费"`
EspClickRatio *float64 `json:"espClickRatio" dc:"行为率"`
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计 GMV"`
T0Roi *float64 `json:"t0Roi" dc:"当日累计 ROI"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"智能优惠券订单数"`
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
CampaignName string `json:"campaignName" dc:"计划名称"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
UnitName string `json:"unitName" dc:"单元名称"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
CreativeName string `json:"creativeName" dc:"创意名称"`
}
// ListStorewideReportSumReq 获取广告效果指标表列表请求
type ListStorewideReportSumReq struct {
g.Meta `path:"/listStorewideReportSum" method:"get" tags:"广告效果指标" summary:"获取广告效果指标表列表" dc:"分页查询广告效果指标表列表"`
*beans.Page
ReportDateStr string `json:"reportDateStr" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
Keyword string `json:"keyword" dc:"关键字(搜索直播间名称、视频名称等)"`
}
// ListStorewideReportSumRes 获取广告效果指标表列表响应
type ListStorewideReportSumRes struct {
List []*StorewideReportSumItem `json:"list" dc:"广告效果指标列表"`
Total int `json:"total" dc:"总数"`
}
+62
View File
@@ -0,0 +1,62 @@
package copydata
// CreateSyncTaskLogReq 创建同步任务日志请求
type CreateSyncTaskLogReq struct {
TaskID string `json:"taskId"`
TaskType string `json:"taskType"`
PlatformCode string `json:"platformCode"`
InterfaceCode string `json:"interfaceCode"`
AdvertiserID int64 `json:"advertiserId"`
StartTime interface{} `json:"startTime"`
EndTime interface{} `json:"endTime"`
Status string `json:"status"`
MaxRetry int `json:"maxRetry"`
PageInfo interface{} `json:"pageInfo,omitempty"`
RequestParams interface{} `json:"requestParams,omitempty"`
}
// UpdateSyncTaskLogReq 更新同步任务日志请求
type UpdateSyncTaskLogReq struct {
ID int64 `json:"id"`
Status string `json:"status,omitempty"`
RetryCount *int `json:"retryCount,omitempty"`
ErrorMessage string `json:"errorMessage,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
ResultSummary interface{} `json:"resultSummary,omitempty"`
NextRetryTime interface{} `json:"nextRetryTime,omitempty"`
CompletedAt interface{} `json:"completedAt,omitempty"`
DurationMs *int64 `json:"durationMs,omitempty"`
}
// QueryFailedTasksReq 查询失败任务请求
type QueryFailedTasksReq struct {
Status []string `json:"status,omitempty"` // 查询的状态列表
TaskType string `json:"taskType,omitempty"` // 任务类型
MaxRetries *int `json:"maxRetries,omitempty"` // 最大重试次数过滤
Limit int `json:"limit,omitempty"` // 限制数量
}
// SyncTaskLogItem 同步任务日志项
type SyncTaskLogItem struct {
Id int64 `json:"id"`
TaskID string `json:"taskId"`
TaskType string `json:"taskType"`
PlatformCode string `json:"platformCode"`
InterfaceCode string `json:"interfaceCode"`
AdvertiserID int64 `json:"advertiserId"`
StartTime interface{} `json:"startTime"`
EndTime interface{} `json:"endTime"`
Status string `json:"status"`
RetryCount int `json:"retryCount"`
MaxRetry int `json:"maxRetry"`
ErrorMessage string `json:"errorMessage,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
ResultSummary interface{} `json:"resultSummary,omitempty"`
PageInfo interface{} `json:"pageInfo,omitempty"`
RequestParams interface{} `json:"requestParams,omitempty"`
NextRetryTime interface{} `json:"nextRetryTime,omitempty"`
CompletedAt interface{} `json:"completedAt,omitempty"`
DurationMs int64 `json:"durationMs"`
CreatedAt interface{} `json:"createdAt"`
UpdatedAt interface{} `json:"updatedAt"`
}
-92
View File
@@ -1,92 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateTaskReportReq 创建调控任务数据请求
type CreateTaskReportReq struct {
g.Meta `path:"/createTaskReport" method:"post" tags:"调控任务" summary:"创建调控任务数据" dc:"创建新的调控任务数据"`
*TaskReportItem
}
// CreateTaskReportRes 创建调控任务数据响应
type CreateTaskReportRes struct {
Id int64 `json:"id" dc:"调控任务数据 ID"`
}
// BatchCreateTaskReportReq 批量创建调控任务数据请求
type BatchCreateTaskReportReq struct {
g.Meta `path:"/batchCreateTaskReport" method:"post" tags:"调控任务" summary:"批量创建调控任务数据" dc:"批量创建调控任务数据"`
Items []*TaskReportItem `json:"items" v:"required" dc:"调控任务数据列表"`
}
// BatchCreateTaskReportRes 批量创建调控任务数据响应
type BatchCreateTaskReportRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// TaskReportItem 调控任务数据项
type TaskReportItem struct {
// 转化率相关字段
ItemOrderConversionRatio *float64 `json:"itemOrderConversionRatio" dc:"转化率"`
ItemCardClickRatio *float64 `json:"itemCardClickRatio" dc:"点击率"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
LivePlayCntCost *float64 `json:"livePlayCntCost" dc:"直播间观看成本"`
AdMerchantFollowCost *float64 `json:"adMerchantFollowCost" dc:"涨粉成本"`
AdMerchantFollow *int64 `json:"adMerchantFollow" dc:"涨粉数"`
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交 ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交 GMV"`
// 视频信息字段
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoId string `json:"photoId" dc:"视频 id"`
// 核心指标字段
CostTotal *float64 `json:"costTotal" dc:"花费"`
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计 GMV"`
T0Roi *float64 `json:"t0Roi" dc:"当日累计 ROI"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
// 粉丝 GMV 字段
FansT0Gmv *float64 `json:"fansT0Gmv" dc:"涨粉当日 GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" dc:"涨粉次日 GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" dc:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" dc:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" dc:"涨粉 30 日 GMV"`
// 粉丝 ROI 字段
FansT0Roi *float64 `json:"fansT0Roi" dc:"涨粉当日 ROI"`
FansT1Roi *float64 `json:"fansT1Roi" dc:"涨粉次日 ROI"`
FansT7Roi *float64 `json:"fansT7Roi" dc:"涨粉 7 日 ROI"`
FansT15Roi *float64 `json:"fansT15Roi" dc:"涨粉 15 日 ROI"`
FansT30Roi *float64 `json:"fansT30Roi" dc:"涨粉 30 日 ROI"`
// 全站数据字段
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
// 时间字段
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间(格式:YYYY-MM-DD"`
}
// ListTaskReportReq 获取调控任务数据列表请求
type ListTaskReportReq struct {
g.Meta `path:"/listTaskReport" method:"get" tags:"调控任务" summary:"获取调控任务数据列表" dc:"分页查询调控任务数据列表"`
*beans.Page
ReportDateStr string `json:"reportDateStr" dc:"时间"`
PhotoId string `json:"photoId" dc:"视频 id"`
Keyword string `json:"keyword" dc:"关键字(搜索视频名称等)"`
}
// ListTaskReportRes 获取调控任务数据列表响应
type ListTaskReportRes struct {
List []*TaskReportItem `json:"list" dc:"调控任务数据列表"`
Total int `json:"total" dc:"总数"`
}
@@ -1,237 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateUnitReportDetailReq 创建广告效果指标详情请求
type CreateUnitReportDetailReq struct {
g.Meta `path:"/createUnitReportDetail" method:"post" tags:"广告效果指标详情" summary:"创建广告效果指标详情" dc:"创建新的广告效果指标详情"`
*UnitReportDetailItem
}
// CreateUnitReportDetailRes 创建广告效果指标详情响应
type CreateUnitReportDetailRes struct {
Id int64 `json:"id" dc:"广告数据 ID"`
}
// BatchCreateUnitReportDetailReq 批量创建广告效果指标详情请求
type BatchCreateUnitReportDetailReq struct {
g.Meta `path:"/batchCreateUnitReportDetail" method:"post" tags:"广告效果指标详情" summary:"批量创建广告效果指标详情" dc:"批量创建广告效果指标详情"`
Items []*UnitReportDetailItem `json:"items" v:"required" dc:"广告数据列表"`
}
// BatchCreateUnitReportDetailRes 批量创建广告效果指标详情响应
type BatchCreateUnitReportDetailRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// UnitReportDetailItem 广告效果指标详情项
type UnitReportDetailItem struct {
T0OrderPaymentAmt *float64 `json:"t0OrderPaymentAmt" dc:"当日总成交订单金额"`
CreativeMaterialType string `json:"creativeMaterialType" dc:"视频素材类型(视频:HORIZONTAL_SCREEN, VERTICAL_SCREEN, UNKNOWN_TYPE;图集:ATLAS;长图:ATLAS_VERTICAL"`
LiveName string `json:"liveName" dc:"直播间名称"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
PicUrl string `json:"picUrl" dc:"图片 URL"`
PicName string `json:"picName" dc:"图片名称"`
PicId string `json:"picId" dc:"图片 Id"`
CoverUrl string `json:"coverUrl" dc:"封面 URL"`
CoverId *int64 `json:"coverId" dc:"封面 Id"`
ItemOrderConversionRatio *float64 `json:"itemOrderConversionRatio" dc:"转化率"`
ItemCardClickRatio *float64 `json:"itemCardClickRatio" dc:"点击率"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
LivePlayCntCost *float64 `json:"livePlayCntCost" dc:"直播间观看成本"`
AdMerchantFollowCost *float64 `json:"adMerchantFollowCost" dc:"涨粉成本"`
AdMerchantFollow *int64 `json:"adMerchantFollow" dc:"涨粉数"`
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交 ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交 GMV"`
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoIdStr string `json:"photoIdStr" dc:"视频 id"`
PhotoId string `json:"photoId" dc:"视频 id"`
ModPriceSegment string `json:"modPriceSegment" dc:"设备价格区间,"0-500", "501-1000", "1001-1500", "1501-2000", "2001-2500", "2501-3000", "3001-3500", "3501-4000", "4001-4500", "4501-5000", "5001-5500", "5501+", "未知""`
AgeSegment string `json:"ageSegment" dc:"年龄段"0-17", "18-23", "24-30", "31-40", "41-49", "50+","未知""`
Province string `json:"province" dc:"省份名称"`
Gender string `json:"gender" dc:"男,女,未知三种类型"`
AdPhotoPlayedFiveRatio *float64 `json:"adPhotoPlayedFiveRatio" dc:"作品 5 秒播放率"`
AdPhotoPlayedThreeRatio *float64 `json:"adPhotoPlayedThreeRatio" dc:"作品 3 秒播放率"`
OrderSubmitRoi *float64 `json:"orderSubmitRoi" dc:"订单提交 ROI"`
OrderSubmitAmt *int64 `json:"orderSubmitAmt" dc:"外部订单金额"`
EventOrderSubmitCost *float64 `json:"eventOrderSubmitCost" dc:"订单提交成本"`
EventOrderSubmit *int64 `json:"eventOrderSubmit" dc:"订单提交数"`
EventOrderPaidRoi *float64 `json:"eventOrderPaidRoi" dc:"订单支付率"`
EventAppInvoked *int64 `json:"eventAppInvoked" dc:"唤起应用数"`
EventAddShoppingCart *int64 `json:"eventAddShoppingCart" dc:"添加购物车次数"`
ConversionNumCost *float64 `json:"conversionNumCost" dc:"转化成本(回传时间)"`
AdEffectivePlayNum *int64 `json:"adEffectivePlayNum" dc:"有效播放数"`
AdItemClick *int64 `json:"adItemClick" dc:"行为数"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
CostTotal *float64 `json:"costTotal" dc:"花费"`
AdShow *int64 `json:"adShow" dc:"曝光数"`
AdShow1kCost *float64 `json:"adShow1kCost" dc:"平均千次广告曝光花费"`
Impression *int64 `json:"impression" dc:"封面曝光数"`
PhotoClick *int64 `json:"photoClick" dc:"封面点击数"`
PhotoClickRatio *float64 `json:"photoClickRatio" dc:"封面点击率"`
Click *int64 `json:"click" dc:"素材曝光数"`
ActionbarClick *int64 `json:"actionbarClick" dc:"行为数"`
ActionbarClickCost *float64 `json:"actionbarClickCost" dc:"行为成本"`
EspClickRatio *float64 `json:"espClickRatio" dc:"行为率"`
ActionRatio *float64 `json:"actionRatio" dc:"素材点击率"`
AdItemClickCount *int64 `json:"adItemClickCount" dc:"预约组件点击数"`
EspLivePlayedSeconds *int64 `json:"espLivePlayedSeconds" dc:"直播平均观看时长"`
PlayedThreeSeconds *int64 `json:"playedThreeSeconds" dc:"作品 3 秒播放数"`
Play3sRatio *float64 `json:"play3sRatio" dc:"作品 3 秒播放率"`
PlayedFiveSeconds *int64 `json:"playedFiveSeconds" dc:"作品 5 秒播放数"`
Play5sRatio *float64 `json:"play5sRatio" dc:"作品 5 秒播放率"`
PlayedEnd *int64 `json:"playedEnd" dc:"作品完播数"`
PlayEndRatio *float64 `json:"playEndRatio" dc:"作品完播率"`
Share *int64 `json:"share" dc:"作品分享数"`
Comment *int64 `json:"comment" dc:"作品评论数"`
Likes *int64 `json:"likes" dc:"作品点赞数"`
Report *int64 `json:"report" dc:"作品举报数"`
Block *int64 `json:"block" dc:"作品拉黑数"`
ItemNegative *int64 `json:"itemNegative" dc:"详情页减少此类作品数"`
LiveShare *int64 `json:"liveShare" dc:"直播送礼数"`
LiveComment *int64 `json:"liveComment" dc:"直播评论数"`
LiveReward *int64 `json:"liveReward" dc:"直播送礼数"`
EffectivePlayCount *int64 `json:"effectivePlayCount" dc:"有效播放数"`
EffectivePlayRatio *float64 `json:"effectivePlayRatio" dc:"有效播放率"`
ConversionNum *int64 `json:"conversionNum" dc:"转化数"`
ConversionCostEsp *float64 `json:"conversionCostEsp" dc:"转化成本"`
Roi *float64 `json:"roi" dc:"直接 ROI"`
Gmv *float64 `json:"gmv" dc:"直接 GMV"`
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计 GMV"`
T1Gmv *float64 `json:"t1Gmv" dc:"次日累计 GMV"`
T7Gmv *float64 `json:"t7Gmv" dc:"7 日累计 GMV"`
T15Gmv *float64 `json:"t15Gmv" dc:"15 日累计 GMV"`
T30Gmv *float64 `json:"t30Gmv" dc:"30 日累计 GMV"`
T0Roi *float64 `json:"t0Roi" dc:"当日累计 ROI"`
T1Roi *float64 `json:"t1Roi" dc:"次日累计 ROI"`
T7Roi *float64 `json:"t7Roi" dc:"7 日累计 ROI"`
T15Roi *float64 `json:"t15Roi" dc:"15 日累计 ROI"`
T30Roi *float64 `json:"t30Roi" dc:"30 日累计 ROI"`
PaiedOrder *int64 `json:"paiedOrder" dc:"直接订单数"`
OrderRatio *float64 `json:"orderRatio" dc:"直接下单率"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
T0OrderCntRatio *float64 `json:"t0OrderCntRatio" dc:"累计订单下单率"`
T1OrderCnt *int64 `json:"t1OrderCnt" dc:"次日累计订单数"`
T7OrderCnt *int64 `json:"t7OrderCnt" dc:"7 日累计订单数"`
T15OrderCnt *int64 `json:"t15OrderCnt" dc:"15 日累计订单数"`
T30OrderCnt *int64 `json:"t30OrderCnt" dc:"30 日累计订单数"`
MerchantRecoFans *int64 `json:"merchantRecoFans" dc:"涨粉数"`
T1Retention *float64 `json:"t1Retention" dc:"次日涨粉留存数"`
T7Retention *float64 `json:"t7Retention" dc:"7 日涨粉留存数"`
T15Retention *float64 `json:"t15Retention" dc:"15 日涨粉留存数"`
T30Retention *float64 `json:"t30Retention" dc:"30 日涨粉留存数"`
T1RetentionRatio *float64 `json:"t1RetentionRatio" dc:"次日涨粉留存率"`
T7RetentionRatio *float64 `json:"t7RetentionRatio" dc:"7 日涨粉留存率"`
T15RetentionRatio *float64 `json:"t15RetentionRatio" dc:"15 日涨粉留存率"`
T30RetentionRatio *float64 `json:"t30RetentionRatio" dc:"30 日涨粉留存率"`
ReservationSuccess *int64 `json:"reservationSuccess" dc:"直播预约成功数"`
ReservationCost *float64 `json:"reservationCost" dc:"直播预约成功成本"`
StandardLivePlayedStarted *int64 `json:"standardLivePlayedStarted" dc:"直播观看数"`
AdLivePlayCnt *int64 `json:"adLivePlayCnt" dc:"直播间人气数"`
AdLivePlayCntCost *float64 `json:"adLivePlayCntCost" dc:"直播间人气成本"`
LiveAudienceCost *float64 `json:"liveAudienceCost" dc:"直播观看成本"`
LiveEventGoodsView *int64 `json:"liveEventGoodsView" dc:"直播间商品点击数"`
GoodsClickRatio *float64 `json:"goodsClickRatio" dc:"直播间商品点击率"`
DirectAttrPlatNewBuyerCnt *int64 `json:"directAttrPlatNewBuyerCnt" dc:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30AttrPlatTotalBuyerCnt" dc:"30 日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `json:"directAttrSellerNewBuyerCnt" dc:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30AttrSellerTotalBuyerCnt" dc:"30 日累计店铺新客"`
T3Gmv *float64 `json:"t3Gmv" dc:"3 日累计 GMV"`
T3OrderCnt *int64 `json:"t3OrderCnt" dc:"3 日累计订单数"`
T3Roi *float64 `json:"t3Roi" dc:"3 日累计 ROI"`
T7IndirectOrderAmt *float64 `json:"t7IndirectOrderAmt" dc:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `json:"t7IndirectOrderCnt" dc:"7 日间接订单数"`
FansT0GmvPerFans *float64 `json:"fansT0GmvPerFans" dc:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `json:"fansT3GmvPerFans" dc:"3 日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `json:"fansT7GmvPerFans" dc:"7 日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `json:"fansT15GmvPerFans" dc:"15 日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `json:"fansT30GmvPerFans" dc:"30 日新增粉丝人均销售额"`
RecoFansCost *float64 `json:"recoFansCost" dc:"涨粉成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"智能优惠券订单数"`
FansT0Gmv *float64 `json:"fansT0Gmv" dc:"涨粉当日 GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" dc:"涨粉次日 GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" dc:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" dc:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" dc:"涨粉 30 日 GMV"`
FansT0Roi *float64 `json:"fansT0Roi" dc:"涨粉当日 ROI"`
FansT1Roi *float64 `json:"fansT1Roi" dc:"涨粉次日 ROI"`
FansT7Roi *float64 `json:"fansT7Roi" dc:"涨粉 7 日 ROI"`
FansT15Roi *float64 `json:"fansT15Roi" dc:"涨粉 15 日 ROI"`
FansT30Roi *float64 `json:"fansT30Roi" dc:"涨粉 30 日 ROI"`
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0ShopNewBuyerOrderPaymentAmt" dc:"当日新客 GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1ShopNewBuyerOrderPaymentAmt" dc:"投后 1 日新客 GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3ShopNewBuyerOrderPaymentAmt" dc:"投后 3 日新客 GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7ShopNewBuyerOrderPaymentAmt" dc:"投后 7 日新客 GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15ShopNewBuyerOrderPaymentAmt" dc:"投后 15 日新客 GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30ShopNewBuyerOrderPaymentAmt" dc:"投后 30 日新客 GMV"`
T0ShopNewBuyerOrderCnt *int64 `json:"t0ShopNewBuyerOrderCnt" dc:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1ShopNewBuyerOrderCnt" dc:"投后 1 日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3ShopNewBuyerOrderCnt" dc:"投后 3 日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7ShopNewBuyerOrderCnt" dc:"投后 7 日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15ShopNewBuyerOrderCnt" dc:"投后 15 日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30ShopNewBuyerOrderCnt" dc:"投后 30 日新客成交订单量"`
T1NewBuyerRepurchaseRatio *float64 `json:"t1NewBuyerRepurchaseRatio" dc:"投后 1 日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3NewBuyerRepurchaseRatio" dc:"投后 3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7NewBuyerRepurchaseRatio" dc:"投后 7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15NewBuyerRepurchaseRatio" dc:"投后 15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30NewBuyerRepurchaseRatio" dc:"投后 30 日新客复购率"`
T0ShopNewBuyerRoi *float64 `json:"t0ShopNewBuyerRoi" dc:"投后当日新客 ROI"`
T1ShopNewBuyerRoi *float64 `json:"t1ShopNewBuyerRoi" dc:"投后 1 日新客 ROI"`
T3ShopNewBuyerRoi *float64 `json:"t3ShopNewBuyerRoi" dc:"投后 3 日新客 ROI"`
T7ShopNewBuyerRoi *float64 `json:"t7ShopNewBuyerRoi" dc:"投后 7 日新客 ROI"`
T15ShopNewBuyerRoi *float64 `json:"t15ShopNewBuyerRoi" dc:"投后 15 日新客 ROI"`
T30ShopNewBuyerRoi *float64 `json:"t30ShopNewBuyerRoi" dc:"投后 30 日新客 ROI"`
CreateCardOrderCnt *int64 `json:"createCardOrderCnt" dc:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `json:"forwardTsCreateCardOrderCnt" dc:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `json:"createCardOrderCost" dc:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `json:"forwardTsCreateCardOrderCost" dc:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `json:"activateCardOrderCnt" dc:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `json:"forwardTsActivateCardOrderCnt" dc:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `json:"activateCardOrderCost" dc:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `json:"forwardTsActivateCardOrderCost" dc:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `json:"createCardOrderRatio" dc:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `json:"forwardTsCreateCardOrderRatio" dc:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `json:"activateCardOrderCntRatio" dc:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `json:"forwardTsActivateCardOrderRatio" dc:"电话卡激活率(计费时间)"`
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
CampaignName string `json:"campaignName" dc:"计划名称"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
UnitName string `json:"unitName" dc:"单元名称"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
CreativeName string `json:"creativeName" dc:"创意名称"`
CidActualRoiAfterSubsidy *float64 `json:"cidActualRoiAfterSubsidy" dc:"补贴后实际 ROI"`
CidCouponAmount *int64 `json:"cidCouponAmount" dc:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `json:"cidCouponCallbackPaidRefundAmount" dc:"退单有回传_核销券金额"`
CidVoucherCost *float64 `json:"cidVoucherCost" dc:"券成本"`
}
// ListUnitReportDetailReq 获取广告效果指标详情列表请求
type ListUnitReportDetailReq struct {
g.Meta `path:"/listUnitReportDetail" method:"get" tags:"广告效果指标详情" summary:"获取广告效果指标详情列表" dc:"分页查询广告效果指标详情列表"`
*beans.Page
ReportDateStr string `json:"reportDateStr" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
Keyword string `json:"keyword" dc:"关键字(搜索直播间名称、视频名称等)"`
}
// ListUnitReportDetailRes 获取广告效果指标详情列表响应
type ListUnitReportDetailRes struct {
List []*UnitReportDetailItem `json:"list" dc:"广告数据列表"`
Total int `json:"total" dc:"总数"`
}
-237
View File
@@ -1,237 +0,0 @@
package copydata
import (
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateUnitReportSumReq 创建广告效果指标请求
type CreateUnitReportSumReq struct {
g.Meta `path:"/createUnitReportSum" method:"post" tags:"广告效果指标" summary:"创建广告效果指标" dc:"创建新的广告效果指标"`
*UnitReportSumItem
}
// CreateUnitReportSumRes 创建广告效果指标响应
type CreateUnitReportSumRes struct {
Id int64 `json:"id" dc:"广告数据 ID"`
}
// BatchCreateUnitReportSumReq 批量创建广告效果指标请求
type BatchCreateUnitReportSumReq struct {
g.Meta `path:"/batchCreateUnitReportSum" method:"post" tags:"广告效果指标" summary:"批量创建广告效果指标" dc:"批量创建广告效果指标"`
Items []*UnitReportSumItem `json:"items" v:"required" dc:"广告数据列表"`
}
// BatchCreateUnitReportSumRes 批量创建广告效果指标响应
type BatchCreateUnitReportSumRes struct {
SuccessCount int64 `json:"successCount" dc:"成功数量"`
FailCount int64 `json:"failCount" dc:"失败数量"`
FailedItems []int64 `json:"failedItems" dc:"失败项索引"`
}
// UnitReportSumItem 广告效果指标项
type UnitReportSumItem struct {
T0OrderPaymentAmt *float64 `json:"t0OrderPaymentAmt" dc:"当日总成交订单金额"`
CreativeMaterialType string `json:"creativeMaterialType" dc:"视频素材类型(视频:HORIZONTAL_SCREEN, VERTICAL_SCREEN, UNKNOWN_TYPE;图集:ATLAS;长图:ATLAS_VERTICAL"`
LiveName string `json:"liveName" dc:"直播间名称"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
PicUrl string `json:"picUrl" dc:"图片 URL"`
PicName string `json:"picName" dc:"图片名称"`
PicId string `json:"picId" dc:"图片 Id"`
CoverUrl string `json:"coverUrl" dc:"封面 URL"`
CoverId *int64 `json:"coverId" dc:"封面 Id"`
ItemOrderConversionRatio *float64 `json:"itemOrderConversionRatio" dc:"转化率"`
ItemCardClickRatio *float64 `json:"itemCardClickRatio" dc:"点击率"`
ItemCardClkCnt *int64 `json:"itemCardClkCnt" dc:"商品卡点击数"`
LivePlayCntCost *float64 `json:"livePlayCntCost" dc:"直播间观看成本"`
AdMerchantFollowCost *float64 `json:"adMerchantFollowCost" dc:"涨粉成本"`
AdMerchantFollow *int64 `json:"adMerchantFollow" dc:"涨粉数"`
NetT0OrderCnt *int64 `json:"netT0OrderCnt" dc:"当日累计净成交订单数"`
NetT0Roi *float64 `json:"netT0Roi" dc:"净成交 ROI"`
NetT0Gmv *float64 `json:"netT0Gmv" dc:"净成交 GMV"`
PhotoName string `json:"photoName" dc:"视频名称"`
PhotoIdStr string `json:"photoIdStr" dc:"视频 id"`
PhotoId string `json:"photoId" dc:"视频 id"`
ModPriceSegment string `json:"modPriceSegment" dc:"设备价格区间,"0-500", "501-1000", "1001-1500", "1501-2000", "2001-2500", "2501-3000", "3001-3500", "3501-4000", "4001-4500", "4501-5000", "5001-5500", "5501+", "未知""`
AgeSegment string `json:"ageSegment" dc:"年龄段"0-17", "18-23", "24-30", "31-40", "41-49", "50+","未知""`
Province string `json:"province" dc:"省份名称"`
Gender string `json:"gender" dc:"男,女,未知三种类型"`
AdPhotoPlayedFiveRatio *float64 `json:"adPhotoPlayedFiveRatio" dc:"作品 5 秒播放率"`
AdPhotoPlayedThreeRatio *float64 `json:"adPhotoPlayedThreeRatio" dc:"作品 3 秒播放率"`
OrderSubmitRoi *float64 `json:"orderSubmitRoi" dc:"订单提交 ROI"`
OrderSubmitAmt *int64 `json:"orderSubmitAmt" dc:"外部订单金额"`
EventOrderSubmitCost *float64 `json:"eventOrderSubmitCost" dc:"订单提交成本"`
EventOrderSubmit *int64 `json:"eventOrderSubmit" dc:"订单提交数"`
EventOrderPaidRoi *float64 `json:"eventOrderPaidRoi" dc:"订单支付率"`
EventAppInvoked *int64 `json:"eventAppInvoked" dc:"唤起应用数"`
EventAddShoppingCart *int64 `json:"eventAddShoppingCart" dc:"添加购物车次数"`
ConversionNumCost *float64 `json:"conversionNumCost" dc:"转化成本(回传时间)"`
AdEffectivePlayNum *int64 `json:"adEffectivePlayNum" dc:"有效播放数"`
AdItemClick *int64 `json:"adItemClick" dc:"行为数"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
CostTotal *float64 `json:"costTotal" dc:"花费"`
AdShow *int64 `json:"adShow" dc:"曝光数"`
AdShow1kCost *float64 `json:"adShow1kCost" dc:"平均千次广告曝光花费"`
Impression *int64 `json:"impression" dc:"封面曝光数"`
PhotoClick *int64 `json:"photoClick" dc:"封面点击数"`
PhotoClickRatio *float64 `json:"photoClickRatio" dc:"封面点击率"`
Click *int64 `json:"click" dc:"素材曝光数"`
ActionbarClick *int64 `json:"actionbarClick" dc:"行为数"`
ActionbarClickCost *float64 `json:"actionbarClickCost" dc:"行为成本"`
EspClickRatio *float64 `json:"espClickRatio" dc:"行为率"`
ActionRatio *float64 `json:"actionRatio" dc:"素材点击率"`
AdItemClickCount *int64 `json:"adItemClickCount" dc:"预约组件点击数"`
EspLivePlayedSeconds *int64 `json:"espLivePlayedSeconds" dc:"直播平均观看时长"`
PlayedThreeSeconds *int64 `json:"playedThreeSeconds" dc:"作品 3 秒播放数"`
Play3sRatio *float64 `json:"play3sRatio" dc:"作品 3 秒播放率"`
PlayedFiveSeconds *int64 `json:"playedFiveSeconds" dc:"作品 5 秒播放数"`
Play5sRatio *float64 `json:"play5sRatio" dc:"作品 5 秒播放率"`
PlayedEnd *int64 `json:"playedEnd" dc:"作品完播数"`
PlayEndRatio *float64 `json:"playEndRatio" dc:"作品完播率"`
Share *int64 `json:"share" dc:"作品分享数"`
Comment *int64 `json:"comment" dc:"作品评论数"`
Likes *int64 `json:"likes" dc:"作品点赞数"`
Report *int64 `json:"report" dc:"作品举报数"`
Block *int64 `json:"block" dc:"作品拉黑数"`
ItemNegative *int64 `json:"itemNegative" dc:"详情页减少此类作品数"`
LiveShare *int64 `json:"liveShare" dc:"直播送礼数"`
LiveComment *int64 `json:"liveComment" dc:"直播评论数"`
LiveReward *int64 `json:"liveReward" dc:"直播送礼数"`
EffectivePlayCount *int64 `json:"effectivePlayCount" dc:"有效播放数"`
EffectivePlayRatio *float64 `json:"effectivePlayRatio" dc:"有效播放率"`
ConversionNum *int64 `json:"conversionNum" dc:"转化数"`
ConversionCostEsp *float64 `json:"conversionCostEsp" dc:"转化成本"`
Roi *float64 `json:"roi" dc:"直接 ROI"`
Gmv *float64 `json:"gmv" dc:"直接 GMV"`
T0Gmv *float64 `json:"t0Gmv" dc:"当日累计 GMV"`
T1Gmv *float64 `json:"t1Gmv" dc:"次日累计 GMV"`
T7Gmv *float64 `json:"t7Gmv" dc:"7 日累计 GMV"`
T15Gmv *float64 `json:"t15Gmv" dc:"15 日累计 GMV"`
T30Gmv *float64 `json:"t30Gmv" dc:"30 日累计 GMV"`
T0Roi *float64 `json:"t0Roi" dc:"当日累计 ROI"`
T1Roi *float64 `json:"t1Roi" dc:"次日累计 ROI"`
T7Roi *float64 `json:"t7Roi" dc:"7 日累计 ROI"`
T15Roi *float64 `json:"t15Roi" dc:"15 日累计 ROI"`
T30Roi *float64 `json:"t30Roi" dc:"30 日累计 ROI"`
PaiedOrder *int64 `json:"paiedOrder" dc:"直接订单数"`
OrderRatio *float64 `json:"orderRatio" dc:"直接下单率"`
T0OrderCnt *int64 `json:"t0OrderCnt" dc:"当日累计订单数"`
T0OrderCntCost *float64 `json:"t0OrderCntCost" dc:"当日累计订单成本"`
T0OrderCntRatio *float64 `json:"t0OrderCntRatio" dc:"累计订单下单率"`
T1OrderCnt *int64 `json:"t1OrderCnt" dc:"次日累计订单数"`
T7OrderCnt *int64 `json:"t7OrderCnt" dc:"7 日累计订单数"`
T15OrderCnt *int64 `json:"t15OrderCnt" dc:"15 日累计订单数"`
T30OrderCnt *int64 `json:"t30OrderCnt" dc:"30 日累计订单数"`
MerchantRecoFans *int64 `json:"merchantRecoFans" dc:"涨粉数"`
T1Retention *float64 `json:"t1Retention" dc:"次日涨粉留存数"`
T7Retention *float64 `json:"t7Retention" dc:"7 日涨粉留存数"`
T15Retention *float64 `json:"t15Retention" dc:"15 日涨粉留存数"`
T30Retention *float64 `json:"t30Retention" dc:"30 日涨粉留存数"`
T1RetentionRatio *float64 `json:"t1RetentionRatio" dc:"次日涨粉留存率"`
T7RetentionRatio *float64 `json:"t7RetentionRatio" dc:"7 日涨粉留存率"`
T15RetentionRatio *float64 `json:"t15RetentionRatio" dc:"15 日涨粉留存率"`
T30RetentionRatio *float64 `json:"t30RetentionRatio" dc:"30 日涨粉留存率"`
ReservationSuccess *int64 `json:"reservationSuccess" dc:"直播预约成功数"`
ReservationCost *float64 `json:"reservationCost" dc:"直播预约成功成本"`
StandardLivePlayedStarted *int64 `json:"standardLivePlayedStarted" dc:"直播观看数"`
AdLivePlayCnt *int64 `json:"adLivePlayCnt" dc:"直播间人气数"`
AdLivePlayCntCost *float64 `json:"adLivePlayCntCost" dc:"直播间人气成本"`
LiveAudienceCost *float64 `json:"liveAudienceCost" dc:"直播观看成本"`
LiveEventGoodsView *int64 `json:"liveEventGoodsView" dc:"直播间商品点击数"`
GoodsClickRatio *float64 `json:"goodsClickRatio" dc:"直播间商品点击率"`
DirectAttrPlatNewBuyerCnt *int64 `json:"directAttrPlatNewBuyerCnt" dc:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30AttrPlatTotalBuyerCnt" dc:"30 日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `json:"directAttrSellerNewBuyerCnt" dc:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30AttrSellerTotalBuyerCnt" dc:"30 日累计店铺新客"`
T3Gmv *float64 `json:"t3Gmv" dc:"3 日累计 GMV"`
T3OrderCnt *int64 `json:"t3OrderCnt" dc:"3 日累计订单数"`
T3Roi *float64 `json:"t3Roi" dc:"3 日累计 ROI"`
T7IndirectOrderAmt *float64 `json:"t7IndirectOrderAmt" dc:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `json:"t7IndirectOrderCnt" dc:"7 日间接订单数"`
FansT0GmvPerFans *float64 `json:"fansT0GmvPerFans" dc:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `json:"fansT3GmvPerFans" dc:"3 日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `json:"fansT7GmvPerFans" dc:"7 日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `json:"fansT15GmvPerFans" dc:"15 日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `json:"fansT30GmvPerFans" dc:"30 日新增粉丝人均销售额"`
RecoFansCost *float64 `json:"recoFansCost" dc:"涨粉成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpxWhiteboxDirectOrderPaymentAmt" dc:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpxWhiteboxDirectOrderCnt" dc:"智能优惠券订单数"`
FansT0Gmv *float64 `json:"fansT0Gmv" dc:"涨粉当日 GMV"`
FansT1Gmv *float64 `json:"fansT1Gmv" dc:"涨粉次日 GMV"`
FansT7Gmv *float64 `json:"fansT7Gmv" dc:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `json:"fansT15Gmv" dc:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `json:"fansT30Gmv" dc:"涨粉 30 日 GMV"`
FansT0Roi *float64 `json:"fansT0Roi" dc:"涨粉当日 ROI"`
FansT1Roi *float64 `json:"fansT1Roi" dc:"涨粉次日 ROI"`
FansT7Roi *float64 `json:"fansT7Roi" dc:"涨粉 7 日 ROI"`
FansT15Roi *float64 `json:"fansT15Roi" dc:"涨粉 15 日 ROI"`
FansT30Roi *float64 `json:"fansT30Roi" dc:"涨粉 30 日 ROI"`
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0ShopNewBuyerOrderPaymentAmt" dc:"当日新客 GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1ShopNewBuyerOrderPaymentAmt" dc:"投后 1 日新客 GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3ShopNewBuyerOrderPaymentAmt" dc:"投后 3 日新客 GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7ShopNewBuyerOrderPaymentAmt" dc:"投后 7 日新客 GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15ShopNewBuyerOrderPaymentAmt" dc:"投后 15 日新客 GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30ShopNewBuyerOrderPaymentAmt" dc:"投后 30 日新客 GMV"`
T0ShopNewBuyerOrderCnt *int64 `json:"t0ShopNewBuyerOrderCnt" dc:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1ShopNewBuyerOrderCnt" dc:"投后 1 日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3ShopNewBuyerOrderCnt" dc:"投后 3 日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7ShopNewBuyerOrderCnt" dc:"投后 7 日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15ShopNewBuyerOrderCnt" dc:"投后 15 日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30ShopNewBuyerOrderCnt" dc:"投后 30 日新客成交订单量"`
T1NewBuyerRepurchaseRatio *float64 `json:"t1NewBuyerRepurchaseRatio" dc:"投后 1 日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3NewBuyerRepurchaseRatio" dc:"投后 3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7NewBuyerRepurchaseRatio" dc:"投后 7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15NewBuyerRepurchaseRatio" dc:"投后 15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30NewBuyerRepurchaseRatio" dc:"投后 30 日新客复购率"`
T0ShopNewBuyerRoi *float64 `json:"t0ShopNewBuyerRoi" dc:"投后当日新客 ROI"`
T1ShopNewBuyerRoi *float64 `json:"t1ShopNewBuyerRoi" dc:"投后 1 日新客 ROI"`
T3ShopNewBuyerRoi *float64 `json:"t3ShopNewBuyerRoi" dc:"投后 3 日新客 ROI"`
T7ShopNewBuyerRoi *float64 `json:"t7ShopNewBuyerRoi" dc:"投后 7 日新客 ROI"`
T15ShopNewBuyerRoi *float64 `json:"t15ShopNewBuyerRoi" dc:"投后 15 日新客 ROI"`
T30ShopNewBuyerRoi *float64 `json:"t30ShopNewBuyerRoi" dc:"投后 30 日新客 ROI"`
CreateCardOrderCnt *int64 `json:"createCardOrderCnt" dc:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `json:"forwardTsCreateCardOrderCnt" dc:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `json:"createCardOrderCost" dc:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `json:"forwardTsCreateCardOrderCost" dc:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `json:"activateCardOrderCnt" dc:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `json:"forwardTsActivateCardOrderCnt" dc:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `json:"activateCardOrderCost" dc:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `json:"forwardTsActivateCardOrderCost" dc:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `json:"createCardOrderRatio" dc:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `json:"forwardTsCreateCardOrderRatio" dc:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `json:"activateCardOrderCntRatio" dc:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `json:"forwardTsActivateCardOrderRatio" dc:"电话卡激活率(计费时间)"`
LivePlayCnt *int64 `json:"livePlayCnt" dc:"全站直播观看数"`
ItemEntranceClkCnt *int64 `json:"itemEntranceClkCnt" dc:"小黄车点击数"`
ShowCnt *int64 `json:"showCnt" dc:"全站曝光"`
ReportDateStr string `json:"reportDateStr" v:"required" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
CampaignName string `json:"campaignName" dc:"计划名称"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
UnitName string `json:"unitName" dc:"单元名称"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
CreativeName string `json:"creativeName" dc:"创意名称"`
CidActualRoiAfterSubsidy *float64 `json:"cidActualRoiAfterSubsidy" dc:"补贴后实际 ROI"`
CidCouponAmount *int64 `json:"cidCouponAmount" dc:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `json:"cidCouponCallbackPaidRefundAmount" dc:"退单有回传_核销券金额"`
CidVoucherCost *float64 `json:"cidVoucherCost" dc:"券成本"`
}
// ListUnitReportSumReq 获取广告效果指标列表请求
type ListUnitReportSumReq struct {
g.Meta `path:"/listUnitReportSum" method:"get" tags:"广告效果指标" summary:"获取广告效果指标列表" dc:"分页查询广告效果指标列表"`
*beans.Page
ReportDateStr string `json:"reportDateStr" dc:"时间"`
CampaignId *int64 `json:"campaignId" dc:"计划 ID"`
UnitId *int64 `json:"unitId" dc:"单元 ID"`
CreativeId *int64 `json:"creativeId" dc:"创意 ID"`
AuthorId string `json:"authorId" dc:"直播用户快手 Id"`
MerchantProductId string `json:"merchantProductId" dc:"商品 ID"`
Keyword string `json:"keyword" dc:"关键字(搜索直播间名称、视频名称等)"`
}
// ListUnitReportSumRes 获取广告效果指标列表响应
type ListUnitReportSumRes struct {
List []*UnitReportSumItem `json:"list" dc:"广告数据列表"`
Total int `json:"total" dc:"总数"`
}
+10 -15
View File
@@ -1,10 +1,10 @@
package api_feature
package dict
import (
"cid/consts/api-feature"
entity "cid/model/entity/dict"
"dataengine/consts/api-feature"
entity "dataengine/model/entity/dict"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
@@ -27,10 +27,7 @@ type CreateDatasourcePlatformReq struct {
RequestTimeoutMs int `json:"requestTimeoutMs" dc:"请求超时时间(毫秒)" d:"30000"`
MaxRetries int `json:"maxRetries" dc:"最大重试次数" d:"3"`
RetryDelayMs int `json:"retryDelayMs" dc:"重试延迟(毫秒)" d:"1000"`
CreatedBy string `json:"createdBy" v:"required" dc:"创建人"`
CreatedAt string `json:"createdAt" v:"required" dc:"创建时间"`
UpdatedBy string `json:"updatedBy" v:"required" dc:"修改人"`
UpdatedAt string `json:"updatedAt" v:"required" dc:"修改人"`
AuthConfig map[string]interface{} `json:"authConfig" dc:"自定义认证配置(JSON),支持各平台特有认证方式"`
}
// CreateDatasourcePlatformRes 创建数据源平台响应
@@ -57,6 +54,7 @@ type ListDatasourcePlatformRes struct {
type DatasourcePlatformItem struct {
Id int64 `json:"id,string"`
TenantId uint64 `json:"tenantId"`
PlatformCode string `json:"platformCode"`
PlatformName string `json:"platformName"`
Description string `json:"description"`
@@ -108,9 +106,7 @@ type UpdateDatasourcePlatformReq struct {
RequestTimeoutMs int `json:"requestTimeoutMs" dc:"请求超时时间(毫秒)"`
MaxRetries int `json:"maxRetries" dc:"最大重试次数"`
RetryDelayMs int `json:"retryDelayMs" dc:"重试延迟(毫秒)"`
UpdatedBy string `json:"updatedBy" v:"required" dc:"更新人"`
UpdatedAt string `json:"updatedAt" v:"required" dc:"更新时间"`
Version string `json:"version" v:"required" dc:"版本"`
AuthConfig map[string]interface{} `json:"authConfig" dc:"自定义认证配置(JSON)"`
}
// DeleteDatasourcePlatformReq 删除数据源平台请求
@@ -121,10 +117,9 @@ type DeleteDatasourcePlatformReq struct {
// UpdateDatasourcePlatformStatusReq 更新数据源平台状态请求
type UpdateDatasourcePlatformStatusReq struct {
g.Meta `path:"/updateDatasourcePlatformStatus" method:"put" tags:"数据源平台管理" summary:"更新数据源平台状态" dc:"更新数据源平台状态"`
Id int64 `json:"id" v:"required" dc:"平台ID"`
Status api_feature.PlatformStatus `json:"status" v:"required|in:ACTIVE,INACTIVE" dc:"状态:ACTIVE启用/INACTIVE停用"`
UpdatedBy string `json:"updatedBy" v:"required" dc:"更新人"`
g.Meta `path:"/updateDatasourcePlatformStatus" method:"put" tags:"数据源平台管理" summary:"更新数据源平台状态" dc:"更新数据源平台状态"`
Id int64 `json:"id" v:"required" dc:"平台ID"`
Status api_feature.PlatformStatus `json:"status" v:"required|in:ACTIVE,INACTIVE" dc:"状态:ACTIVE启用/INACTIVE停用"`
}
// 以下是几个额外的实用API接口
@@ -1,221 +0,0 @@
package api_feature
import (
entity "cid/model/entity/dict"
"time"
"gitea.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateFieldMappingConfigReq 创建字段映射配置请求
type CreateFieldMappingConfigReq struct {
g.Meta `path:"/field-mapping-configs" method:"post" tags:"字段映射配置" summary:"创建字段映射配置" dc:"创建新的字段映射配置"`
ConfigName string `json:"configName" v:"required#配置名称不能为空" dc:"配置名称"`
VendorName string `json:"vendorName" v:"required#厂商名称不能为空" dc:"厂商名称"`
ApiName string `json:"apiName" v:"required#接口名称不能为空" dc:"接口名称"`
ApiVersion string `json:"apiVersion" dc:"接口版本"`
SourceField string `json:"sourceField" v:"required#源字段不能为空" dc:"源字段名"`
SourceFieldType string `json:"sourceFieldType" dc:"源字段数据类型"`
SourceFieldDesc string `json:"sourceFieldDesc" dc:"源字段描述"`
TargetField string `json:"targetField" v:"required#目标字段不能为空" dc:"目标字段名"`
TargetFieldType string `json:"targetFieldType" v:"required#目标字段类型不能为空" dc:"目标数据类型"`
TargetFieldDesc string `json:"targetFieldDesc" dc:"字段描述"`
TransformType string `json:"transformType" dc:"转换类型" d:"direct"`
TransformParams map[string]interface{} `json:"transformParams" dc:"转换参数"`
ValidationRules map[string]interface{} `json:"validationRules" dc:"验证规则"`
DefaultValue string `json:"defaultValue" dc:"默认值"`
IsRequired bool `json:"isRequired" dc:"是否必填" d:"false"`
IsActive bool `json:"isActive" dc:"是否启用" d:"true"`
Priority int `json:"priority" dc:"优先级" d:"0"`
BusinessDomain string `json:"businessDomain" dc:"业务域"`
FieldGroup string `json:"fieldGroup" dc:"字段分组"`
ConfigVersion int `json:"configVersion" dc:"配置版本号" d:"1"`
EffectiveDate *time.Time `json:"effectiveDate" dc:"生效时间"`
ExpiryDate *time.Time `json:"expiryDate" dc:"失效时间"`
CreatedBy string `json:"createdBy" dc:"创建人"`
}
// CreateFieldMappingConfigRes 创建字段映射配置响应
type CreateFieldMappingConfigRes struct {
Id int64 `json:"id" dc:"配置ID"`
}
// ListFieldMappingConfigReq 获取字段映射配置列表请求
type ListFieldMappingConfigReq struct {
g.Meta `path:"/field-mapping-configs" method:"get" tags:"字段映射配置" summary:"获取字段映射配置列表" dc:"分页查询字段映射配置列表"`
*beans.Page
ConfigName string `json:"configName" dc:"配置名称"`
VendorName string `json:"vendorName" dc:"厂商名称"`
ApiName string `json:"apiName" dc:"接口名称"`
ApiVersion string `json:"apiVersion" dc:"接口版本"`
SourceField string `json:"sourceField" dc:"源字段名"`
TargetField string `json:"targetField" dc:"目标字段名"`
TransformType string `json:"transformType" dc:"转换类型"`
IsActive *bool `json:"isActive" dc:"是否启用"`
BusinessDomain string `json:"businessDomain" dc:"业务域"`
FieldGroup string `json:"fieldGroup" dc:"字段分组"`
Keyword string `json:"keyword" dc:"关键字搜索"`
}
// ListFieldMappingConfigRes 获取字段映射配置列表响应
type ListFieldMappingConfigRes struct {
List []FieldMappingConfigItem `json:"list" dc:"配置列表"`
Total int `json:"total" dc:"总数"`
}
// FieldMappingConfigItem 字段映射配置列表项
type FieldMappingConfigItem struct {
Id int64 `json:"id,string" dc:"配置ID"`
ConfigName string `json:"configName" dc:"配置名称"`
VendorName string `json:"vendorName" dc:"厂商名称"`
ApiName string `json:"apiName" dc:"接口名称"`
ApiVersion string `json:"apiVersion" dc:"接口版本"`
SourceField string `json:"sourceField" dc:"源字段名"`
TargetField string `json:"targetField" dc:"目标字段名"`
TargetFieldType string `json:"targetFieldType" dc:"目标数据类型"`
TransformType string `json:"transformType" dc:"转换类型"`
TransformTypeName string `json:"transformTypeName" dc:"转换类型名称"`
IsActive bool `json:"isActive" dc:"是否启用"`
Priority int `json:"priority" dc:"优先级"`
BusinessDomain string `json:"businessDomain" dc:"业务域"`
BusinessDomainName string `json:"businessDomainName" dc:"业务域名称"`
FieldGroup string `json:"fieldGroup" dc:"字段分组"`
ConfigVersion int `json:"configVersion" dc:"配置版本号"`
CreatedBy string `json:"createdBy" dc:"创建人"`
CreatedTime time.Time `json:"createdTime" dc:"创建时间"`
UpdatedBy string `json:"updatedBy" dc:"更新人"`
UpdatedTime time.Time `json:"updatedTime" dc:"更新时间"`
}
// GetFieldMappingConfigReq 获取字段映射配置详情请求
type GetFieldMappingConfigReq struct {
g.Meta `path:"/field-mapping-configs/{id}" method:"get" tags:"字段映射配置" summary:"获取字段映射配置详情" dc:"获取字段映射配置详情"`
Id int64 `json:"id" v:"required#配置ID不能为空" dc:"配置ID"`
}
// GetFieldMappingConfigRes 获取字段映射配置详情响应
type GetFieldMappingConfigRes struct {
*entity.FieldMappingConfig
TransformTypeName string `json:"transformTypeName,omitempty" dc:"转换类型名称"`
BusinessDomainName string `json:"businessDomainName,omitempty" dc:"业务域名称"`
}
// UpdateFieldMappingConfigReq 更新字段映射配置请求
type UpdateFieldMappingConfigReq struct {
g.Meta `path:"/field-mapping-configs/{id}" method:"put" tags:"字段映射配置" summary:"更新字段映射配置" dc:"更新字段映射配置信息"`
Id int64 `json:"id" v:"required#配置ID不能为空" dc:"配置ID"`
ConfigName string `json:"configName" dc:"配置名称"`
VendorName string `json:"vendorName" dc:"厂商名称"`
ApiName string `json:"apiName" dc:"接口名称"`
ApiVersion string `json:"apiVersion" dc:"接口版本"`
SourceField string `json:"sourceField" dc:"源字段名"`
SourceFieldType string `json:"sourceFieldType" dc:"源字段数据类型"`
SourceFieldDesc string `json:"sourceFieldDesc" dc:"源字段描述"`
TargetField string `json:"targetField" dc:"目标字段名"`
TargetFieldType string `json:"targetFieldType" dc:"目标数据类型"`
TargetFieldDesc string `json:"targetFieldDesc" dc:"字段描述"`
TransformType string `json:"transformType" dc:"转换类型"`
TransformParams map[string]interface{} `json:"transformParams" dc:"转换参数"`
ValidationRules map[string]interface{} `json:"validationRules" dc:"验证规则"`
DefaultValue string `json:"defaultValue" dc:"默认值"`
IsRequired bool `json:"isRequired" dc:"是否必填"`
IsActive bool `json:"isActive" dc:"是否启用"`
Priority int `json:"priority" dc:"优先级"`
BusinessDomain string `json:"businessDomain" dc:"业务域"`
FieldGroup string `json:"fieldGroup" dc:"字段分组"`
ConfigVersion int `json:"configVersion" dc:"配置版本号"`
EffectiveDate *time.Time `json:"effectiveDate" dc:"生效时间"`
ExpiryDate *time.Time `json:"expiryDate" dc:"失效时间"`
UpdatedBy string `json:"updatedBy" dc:"更新人"`
}
// DeleteFieldMappingConfigReq 删除字段映射配置请求
type DeleteFieldMappingConfigReq struct {
g.Meta `path:"/field-mapping-configs/{id}" method:"delete" tags:"字段映射配置" summary:"删除字段映射配置" dc:"删除字段映射配置"`
Id int64 `json:"id" v:"required#配置ID不能为空" dc:"配置ID"`
}
// UpdateFieldMappingConfigStatusReq 更新字段映射配置状态请求
type UpdateFieldMappingConfigStatusReq struct {
g.Meta `path:"/field-mapping-configs/{id}/status" method:"put" tags:"字段映射配置" summary:"更新字段映射配置状态" dc:"更新字段映射配置状态"`
Id int64 `json:"id" v:"required#配置ID不能为空" dc:"配置ID"`
IsActive bool `json:"isActive" v:"required#状态不能为空" dc:"是否启用:true启用/false停用"`
}
// BatchCreateFieldMappingConfigReq 批量创建字段映射配置请求
type BatchCreateFieldMappingConfigReq struct {
g.Meta `path:"/field-mapping-configs/batch" method:"post" tags:"字段映射配置" summary:"批量创建字段映射配置" dc:"批量创建字段映射配置"`
Configs []BatchFieldMappingConfigItem `json:"configs" v:"required#配置列表不能为空" dc:"配置列表"`
}
// BatchFieldMappingConfigItem 批量创建配置项
type BatchFieldMappingConfigItem struct {
ConfigName string `json:"configName" v:"required#配置名称不能为空" dc:"配置名称"`
VendorName string `json:"vendorName" v:"required#厂商名称不能为空" dc:"厂商名称"`
ApiName string `json:"apiName" v:"required#接口名称不能为空" dc:"接口名称"`
ApiVersion string `json:"apiVersion" dc:"接口版本"`
SourceField string `json:"sourceField" v:"required#源字段不能为空" dc:"源字段名"`
SourceFieldType string `json:"sourceFieldType" dc:"源字段数据类型"`
SourceFieldDesc string `json:"sourceFieldDesc" dc:"源字段描述"`
TargetField string `json:"targetField" v:"required#目标字段不能为空" dc:"目标字段名"`
TargetFieldType string `json:"targetFieldType" v:"required#目标字段类型不能为空" dc:"目标数据类型"`
TargetFieldDesc string `json:"targetFieldDesc" dc:"字段描述"`
TransformType string `json:"transformType" dc:"转换类型" d:"direct"`
TransformParams map[string]interface{} `json:"transformParams" dc:"转换参数"`
ValidationRules map[string]interface{} `json:"validationRules" dc:"验证规则"`
DefaultValue string `json:"defaultValue" dc:"默认值"`
IsRequired bool `json:"isRequired" dc:"是否必填" d:"false"`
IsActive bool `json:"isActive" dc:"是否启用" d:"true"`
Priority int `json:"priority" dc:"优先级" d:"0"`
BusinessDomain string `json:"businessDomain" dc:"业务域"`
FieldGroup string `json:"fieldGroup" dc:"字段分组"`
ConfigVersion int `json:"configVersion" dc:"配置版本号" d:"1"`
}
// BatchCreateFieldMappingConfigRes 批量创建字段映射配置响应
type BatchCreateFieldMappingConfigRes struct {
SuccessCount int `json:"successCount" dc:"成功数量"`
FailedCount int `json:"failedCount" dc:"失败数量"`
FailedItems []BatchCreateFailedItem `json:"failedItems" dc:"失败项列表"`
}
// BatchCreateFailedItem 批量创建失败项
type BatchCreateFailedItem struct {
Index int `json:"index" dc:"项索引"`
ConfigName string `json:"configName" dc:"配置名称"`
Error string `json:"error" dc:"错误信息"`
}
// QueryFieldMappingByVendorApiReq 根据厂商和接口查询字段映射请求
type QueryFieldMappingByVendorApiReq struct {
g.Meta `path:"/field-mapping-configs/query" method:"get" tags:"字段映射配置" summary:"根据厂商和接口查询字段映射" dc:"根据厂商和接口查询字段映射配置"`
VendorName string `json:"vendorName" v:"required#厂商名称不能为空" dc:"厂商名称"`
ApiName string `json:"apiName" v:"required#接口名称不能为空" dc:"接口名称"`
ApiVersion string `json:"apiVersion" dc:"接口版本"`
IsActive *bool `json:"isActive" dc:"是否启用" d:"true"`
}
// QueryFieldMappingByVendorApiRes 根据厂商和接口查询字段映射响应
type QueryFieldMappingByVendorApiRes struct {
List []*entity.FieldMappingConfig `json:"list" dc:"字段映射配置列表"`
}
// ValidateFieldMappingReq 验证字段映射配置请求
type ValidateFieldMappingReq struct {
g.Meta `path:"/field-mapping-configs/validate" method:"post" tags:"字段映射配置" summary:"验证字段映射配置" dc:"验证字段映射配置的有效性"`
ConfigName string `json:"configName" v:"required#配置名称不能为空" dc:"配置名称"`
VendorName string `json:"vendorName" v:"required#厂商名称不能为空" dc:"厂商名称"`
ApiName string `json:"apiName" v:"required#接口名称不能为空" dc:"接口名称"`
SourceField string `json:"sourceField" v:"required#源字段不能为空" dc:"源字段名"`
TargetField string `json:"targetField" v:"required#目标字段不能为空" dc:"目标字段名"`
TestValue interface{} `json:"testValue" dc:"测试值"`
}
// ValidateFieldMappingRes 验证字段映射配置响应
type ValidateFieldMappingRes struct {
IsValid bool `json:"isValid" dc:"是否有效"`
TransformedValue interface{} `json:"transformedValue" dc:"转换后的值"`
Error string `json:"error" dc:"错误信息"`
Warnings []string `json:"warnings" dc:"警告信息"`
}
+31 -27
View File
@@ -1,26 +1,27 @@
package api_feature
package dict
import (
"cid/consts/api-feature"
entity "cid/model/entity/dict"
"dataengine/consts/api-feature"
entity "dataengine/model/entity/dict"
"gitea.com/red-future/common/beans"
"gitea.redpowerfuture.com/red-future/common/beans"
"github.com/gogf/gf/v2/frame/g"
)
// CreateApiInterfaceReq 创建接口请求
type CreateApiInterfaceReq struct {
g.Meta `path:"/createApiInterface" method:"post" tags:"接口管理" summary:"创建接口" dc:"创建新的数据接口"`
PlatformId int64 `json:"platformId" v:"required" dc:"所属平台ID"`
Name string `json:"name" v:"required" dc:"接口名称"`
Code string `json:"code" v:"required" dc:"接口编码"`
Url string `json:"url" v:"required" dc:"接口地址"`
Method api_feature.ApiMethod `json:"method" v:"required" dc:"请求方法"`
Status api_feature.PlatformStatus `json:"status" dc:"接口状态" d:"active"`
AuthType string `json:"authType" dc:"认证类型"`
RequestConfig map[string]interface{} `json:"requestConfig" dc:"请求配置"`
ResponseConfig map[string]interface{} `json:"responseConfig" dc:"响应配置"`
LimitConfig map[string]interface{} `json:"limitConfig" dc:"接口独立限流配置"`
g.Meta `path:"/createApiInterface" method:"post" tags:"接口管理" summary:"创建接口" dc:"创建新的数据接口"`
PlatformId int64 `json:"platformId" v:"required" dc:"所属平台ID"`
Name string `json:"name" v:"required" dc:"接口名称"`
Code string `json:"code" v:"required" dc:"接口编码"`
Url string `json:"url" v:"required" dc:"接口地址"`
Method api_feature.ApiMethod `json:"method" v:"required" dc:"请求方法"`
Status api_feature.PlatformStatus `json:"status" dc:"接口状态" d:"active"`
AuthType string `json:"authType" dc:"认证类型"`
RequestConfig map[string]interface{} `json:"requestConfig" dc:"请求配置"`
ResponseConfig map[string]interface{} `json:"responseConfig" dc:"响应配置"`
LimitConfig map[string]interface{} `json:"limitConfig" dc:"接口独立限流配置"`
TableDefinition map[string]interface{} `json:"tableDefinition" dc:"表结构定义(JSON),用于自动建表"`
}
// CreateApiInterfaceRes 创建接口响应
@@ -56,7 +57,9 @@ type ApiInterfaceItem struct {
Method api_feature.ApiMethod `json:"method"`
Status api_feature.PlatformStatus `json:"status"`
StatusName string `json:"statusName"`
CreatedBy string `json:"createdBy"`
CreatedAt int64 `json:"createdAt"`
UpdatedBy string `json:"updatedBy"`
UpdatedAt int64 `json:"updatedAt"`
}
@@ -74,18 +77,19 @@ type GetApiInterfaceRes struct {
// UpdateApiInterfaceReq 更新接口请求
type UpdateApiInterfaceReq struct {
g.Meta `path:"/updateApiInterface" method:"put" tags:"接口管理" summary:"更新接口" dc:"更新接口信息"`
Id int64 `json:"id" v:"required" dc:"接口ID"`
PlatformId int64 `json:"platformId" dc:"所属平台ID"`
Name string `json:"name" dc:"接口名称"`
Code string `json:"code" dc:"接口编码"`
Url string `json:"url" dc:"接口地址"`
Method api_feature.ApiMethod `json:"method" dc:"请求方法"`
Status api_feature.PlatformStatus `json:"status,omitempty" dc:"接口状态"`
AuthType string `json:"authType" dc:"认证类型"`
RequestConfig map[string]interface{} `json:"requestConfig" dc:"请求配置"`
ResponseConfig map[string]interface{} `json:"responseConfig" dc:"响应配置"`
LimitConfig map[string]interface{} `json:"limitConfig" dc:"接口独立限流配置"`
g.Meta `path:"/updateApiInterface" method:"put" tags:"接口管理" summary:"更新接口" dc:"更新接口信息"`
Id int64 `json:"id" v:"required" dc:"接口ID"`
PlatformId int64 `json:"platformId" dc:"所属平台ID"`
Name string `json:"name" dc:"接口名称"`
Code string `json:"code" dc:"接口编码"`
Url string `json:"url" dc:"接口地址"`
Method api_feature.ApiMethod `json:"method" dc:"请求方法"`
Status api_feature.PlatformStatus `json:"status,omitempty" dc:"接口状态"`
AuthType string `json:"authType" dc:"认证类型"`
RequestConfig map[string]interface{} `json:"requestConfig" dc:"请求配置"`
ResponseConfig map[string]interface{} `json:"responseConfig" dc:"响应配置"`
LimitConfig map[string]interface{} `json:"limitConfig" dc:"接口独立限流配置"`
TableDefinition map[string]interface{} `json:"tableDefinition" dc:"表结构定义(JSON),用于自动建表"`
}
// DeleteApiInterfaceReq 删除接口请求
+62
View File
@@ -0,0 +1,62 @@
package public
import (
"github.com/gogf/gf/v2/frame/g"
)
// QueryReq 公共查询请求
type QueryReq struct {
g.Meta `path:"/public/query" method:"post" tags:"公共查询" summary:"公共数据查询" dc:"支持字段/表名/过滤/分组/分页的通用查询接口"`
Table string `json:"table" v:"required" dc:"表名(白名单限制)"`
Fields string `json:"fields" dc:"查询字段,逗号分隔,默认 *"`
Where map[string]interface{} `json:"where" dc:"过滤条件,支持操作符: =, !=, >, <, >=, <=, like, in, between"`
GroupBy string `json:"groupBy" dc:"分组字段,逗号分隔"`
OrderBy string `json:"orderBy" dc:"排序字段,如: create_time desc"`
Page int `json:"page" dc:"页码,默认1" d:"1"`
PageSize int `json:"pageSize" dc:"每页条数,默认20,最大100" d:"20"`
}
// QueryRes 公共查询响应
type QueryRes struct {
List []map[string]interface{} `json:"list" dc:"数据列表"`
Total int64 `json:"total" dc:"总数"`
Page int `json:"page" dc:"当前页码"`
Size int `json:"size" dc:"每页条数"`
}
// TableListReq 获取可查询表列表请求
type TableListReq struct {
g.Meta `path:"/public/tables" method:"get" tags:"公共查询" summary:"获取可查询表列表" dc:"获取配置了表结构的同步表列表"`
}
// TableListRes 获取可查询表列表响应
type TableListRes struct {
List []TableInfo `json:"list" dc:"表列表"`
}
// TableInfo 表信息
type TableInfo struct {
TableName string `json:"tableName" dc:"表名"`
PlatformName string `json:"platformName" dc:"平台名称"`
InterfaceName string `json:"interfaceName" dc:"接口名称"`
Columns []string `json:"columns" dc:"可用字段列表"`
}
// ColumnListReq 获取表字段列表请求
type ColumnListReq struct {
g.Meta `path:"/public/tables/{table}/columns" method:"get" tags:"公共查询" summary:"获取表字段列表" dc:"获取指定表的字段列表"`
Table string `json:"table" dc:"表名"`
}
// ColumnListRes 获取表字段列表响应
type ColumnListRes struct {
TableName string `json:"tableName" dc:"表名"`
Columns []Column `json:"columns" dc:"字段列表"`
}
// Column 字段信息
type Column struct {
Name string `json:"name" dc:"字段名"`
Type string `json:"type" dc:"字段类型"`
Comment string `json:"comment" dc:"字段说明"`
}
@@ -1,664 +0,0 @@
package copydata
import "gitea.com/red-future/common/beans"
// CidAccountReportDetail 广告数据报表详情实体
type CidAccountReportDetail struct {
beans.SQLBaseDO `orm:",inherit"`
// 基础信息字段
DataType string `orm:"data_type" json:"dataType" description:"类型type"`
// 核心指标字段
T0OrderPaymentAmt string `orm:"t0_order_payment_amt" json:"t0OrderPaymentAmt" description:"当日总成交订单金额"`
CreativeMaterialType string `orm:"creative_material_type" json:"creativeMaterialType" description:"视频素材类型"`
LiveName string `orm:"live_name" json:"liveName" description:"直播间名称"`
AuthorId string `orm:"author_id" json:"authorId" description:"直播用户快手Id"`
PicUrl string `orm:"pic_url" json:"picUrl" description:"图片URL"`
PicName string `orm:"pic_name" json:"picName" description:"图片名称"`
PicId string `orm:"pic_id" json:"picId" description:"图片Id"`
CoverUrl string `orm:"cover_url" json:"coverUrl" description:"封面URL"`
CoverId int64 `orm:"cover_id" json:"coverId" description:"封面Id"`
// 转化率相关字段
ItemOrderConversionRatio *float64 `orm:"item_order_conversion_ratio" json:"itemOrderConversionRatio" description:"转化率"`
ItemCardClickRatio *float64 `orm:"item_card_click_ratio" json:"itemCardClickRatio" description:"点击率"`
ItemCardClkCnt *int64 `orm:"item_card_clk_cnt" json:"itemCardClkCnt" description:"商品卡点击数"`
LivePlayCntCost *float64 `orm:"live_play_cnt_cost" json:"livePlayCntCost" description:"直播间观看成本"`
AdMerchantFollowCost *float64 `orm:"ad_merchant_follow_cost" json:"adMerchantFollowCost" description:"涨粉成本"`
AdMerchantFollow *int64 `orm:"ad_merchant_follow" json:"adMerchantFollow" description:"涨粉数"`
NetT0OrderCnt *int64 `orm:"net_t0_order_cnt" json:"netT0OrderCnt" description:"当日累计净成交订单数"`
NetT0Roi *float64 `orm:"net_t0_roi" json:"netT0Roi" description:"净成交ROI"`
NetT0Gmv *float64 `orm:"net_t0_gmv" json:"netT0Gmv" description:"净成交GMV"`
// 视频信息字段
PhotoName string `orm:"photo_name" json:"photoName" description:"视频名称"`
PhotoIdStr string `orm:"photo_id_str" json:"photoIdStr" description:"视频id"`
PhotoId string `orm:"photo_id" json:"photoId" description:"视频id"`
// 受众属性字段
ModPriceSegment string `orm:"mod_price_segment" json:"modPriceSegment" description:"设备价格区间"`
AgeSegment string `orm:"age_segment" json:"ageSegment" description:"年龄段"`
Province string `orm:"province" json:"province" description:"省份名称"`
Gender string `orm:"gender" json:"gender" description:"性别"`
// 播放率相关字段
AdPhotoPlayedFiveRatio *float64 `orm:"ad_photo_played_five_ratio" json:"adPhotoPlayedFiveRatio" description:"作品5秒播放率"`
AdPhotoPlayedThreeRatio *float64 `orm:"ad_photo_played_three_ratio" json:"adPhotoPlayedThreeRatio" description:"作品3秒播放率"`
// 订单相关字段
OrderSubmitRoi *float64 `orm:"order_submit_roi" json:"orderSubmitRoi" description:"订单提交ROI"`
OrderSubmitAmt *int64 `orm:"order_submit_amt" json:"orderSubmitAmt" description:"外部订单金额"`
EventOrderSubmitCost *float64 `orm:"event_order_submit_cost" json:"eventOrderSubmitCost" description:"订单提交成本"`
EventOrderSubmit *int64 `orm:"event_order_submit" json:"eventOrderSubmit" description:"订单提交数"`
EventOrderPaidRoi *float64 `orm:"event_order_paid_roi" json:"eventOrderPaidRoi" description:"订单支付率"`
EventAppInvoked *int64 `orm:"event_app_invoked" json:"eventAppInvoked" description:"唤起应用数"`
EventAddShoppingCart *int64 `orm:"event_add_shopping_cart" json:"eventAddShoppingCart" description:"添加购物车次数"`
ConversionNumCost *float64 `orm:"conversion_num_cost" json:"conversionNumCost" description:"转化成本"`
AdEffectivePlayNum *int64 `orm:"ad_effective_play_num" json:"adEffectivePlayNum" description:"有效播放数"`
AdItemClick *int64 `orm:"ad_item_click" json:"adItemClick" description:"行为数"`
// 商品信息字段
MerchantProductId string `orm:"merchant_product_id" json:"merchantProductId" description:"商品ID"`
// 花费相关字段
CostTotal *float64 `orm:"cost_total" json:"costTotal" description:"花费"`
AdShow *int64 `orm:"ad_show" json:"adShow" description:"曝光数"`
AdShow1kCost *float64 `orm:"ad_show1k_cost" json:"adShow1kCost" description:"平均千次广告曝光花费"`
// 封面相关字段
Impression *int64 `orm:"impression" json:"impression" description:"封面曝光数"`
PhotoClick *int64 `orm:"photo_click" json:"photoClick" description:"封面点击数"`
PhotoClickRatio *float64 `orm:"photo_click_ratio" json:"photoClickRatio" description:"封面点击率"`
// 点击相关字段
Click *int64 `orm:"click" json:"click" description:"素材曝光数"`
ActionbarClick *int64 `orm:"actionbar_click" json:"actionbarClick" description:"行为数"`
ActionbarClickCost *float64 `orm:"actionbar_click_cost" json:"actionbarClickCost" description:"行为成本"`
EspClickRatio *float64 `orm:"esp_click_ratio" json:"espClickRatio" description:"行为率"`
ActionRatio *float64 `orm:"action_ratio" json:"actionRatio" description:"素材点击率"`
AdItemClickCount *int64 `orm:"ad_item_click_count" json:"adItemClickCount" description:"预约组件点击数"`
// 直播时长字段
EspLivePlayedSeconds *int64 `orm:"esp_live_played_seconds" json:"espLivePlayedSeconds" description:"直播平均观看时长"`
// 播放数据字段
PlayedThreeSeconds *int64 `orm:"played_three_seconds" json:"playedThreeSeconds" description:"作品3秒播放数"`
Play3sRatio *float64 `orm:"play3s_ratio" json:"play3sRatio" description:"作品3秒播放率"`
PlayedFiveSeconds *int64 `orm:"played_five_seconds" json:"playedFiveSeconds" description:"作品5秒播放数"`
Play5sRatio *float64 `orm:"play5s_ratio" json:"play5sRatio" description:"作品5秒播放率"`
PlayedEnd *int64 `orm:"played_end" json:"playedEnd" description:"作品完播数"`
PlayEndRatio *float64 `orm:"play_end_ratio" json:"playEndRatio" description:"作品完播率"`
// 互动数据字段
Share *int64 `orm:"share" json:"share" description:"作品分享数"`
Comment *int64 `orm:"comment" json:"comment" description:"作品评论数"`
Likes *int64 `orm:"likes" json:"likes" description:"作品点赞数"`
Report *int64 `orm:"report" json:"report" description:"作品举报数"`
Block *int64 `orm:"block" json:"block" description:"作品拉黑数"`
ItemNegative *int64 `orm:"item_negative" json:"itemNegative" description:"详情页减少此类作品数"`
// 直播互动字段
LiveShare *int64 `orm:"live_share" json:"liveShare" description:"直播送礼数"`
LiveComment *int64 `orm:"live_comment" json:"liveComment" description:"直播评论数"`
LiveReward *int64 `orm:"live_reward" json:"liveReward" description:"直播送礼数"`
// 播放效果字段
EffectivePlayCount *int64 `orm:"effective_play_count" json:"effectivePlayCount" description:"有效播放数"`
EffectivePlayRatio *float64 `orm:"effective_play_ratio" json:"effectivePlayRatio" description:"有效播放率"`
// 转化字段
ConversionNum *int64 `orm:"conversion_num" json:"conversionNum" description:"转化数"`
ConversionCostEsp *float64 `orm:"conversion_cost_esp" json:"conversionCostEsp" description:"转化成本"`
Roi *float64 `orm:"roi" json:"roi" description:"直接ROI"`
Gmv *float64 `orm:"gmv" json:"gmv" description:"直接GMV"`
// GMV时间序列字段
T0Gmv *float64 `orm:"t0_gmv" json:"t0Gmv" description:"当日累计GMV"`
T1Gmv *float64 `orm:"t1_gmv" json:"t1Gmv" description:"次日累计GMV"`
T3Gmv *float64 `orm:"t3_gmv" json:"t3Gmv" description:"3日累计GMV"`
T7Gmv *float64 `orm:"t7_gmv" json:"t7Gmv" description:"7日累计GMV"`
T15Gmv *float64 `orm:"t15_gmv" json:"t15Gmv" description:"15日累计GMV"`
T30Gmv *float64 `orm:"t30_gmv" json:"t30Gmv" description:"30日累计GMV"`
// ROI时间序列字段
T0Roi *float64 `orm:"t0_roi" json:"t0Roi" description:"当日累计ROI"`
T1Roi *float64 `orm:"t1_roi" json:"t1Roi" description:"次日累计ROI"`
T3Roi *float64 `orm:"t3_roi" json:"t3Roi" description:"3日累计ROI"`
T7Roi *float64 `orm:"t7_roi" json:"t7Roi" description:"7日累计ROI"`
T15Roi *float64 `orm:"t15_roi" json:"t15Roi" description:"15日累计ROI"`
T30Roi *float64 `orm:"t30_roi" json:"t30Roi" description:"30日累计ROI"`
// 订单相关字段
PaiedOrder *int64 `orm:"paied_order" json:"paiedOrder" description:"直接订单数"`
OrderRatio *float64 `orm:"order_ratio" json:"orderRatio" description:"直接下单率"`
T0OrderCnt *int64 `orm:"t0_order_cnt" json:"t0OrderCnt" description:"当日累计订单数"`
T0OrderCntCost *float64 `orm:"t0_order_cnt_cost" json:"t0OrderCntCost" description:"当日累计订单成本"`
T0OrderCntRatio *float64 `orm:"t0_order_cnt_ratio" json:"t0OrderCntRatio" description:"累计订单下单率"`
T1OrderCnt *int64 `orm:"t1_order_cnt" json:"t1OrderCnt" description:"次日累计订单数"`
T3OrderCnt *int64 `orm:"t3_order_cnt" json:"t3OrderCnt" description:"3日累计订单数"`
T7OrderCnt *int64 `orm:"t7_order_cnt" json:"t7OrderCnt" description:"7日累计订单数"`
T15OrderCnt *int64 `orm:"t15_order_cnt" json:"t15OrderCnt" description:"15日累计订单数"`
T30OrderCnt *int64 `orm:"t30_order_cnt" json:"t30OrderCnt" description:"30日累计订单数"`
// 粉丝相关字段
MerchantRecoFans *int64 `orm:"merchant_reco_fans" json:"merchantRecoFans" description:"涨粉数"`
T1Retention *float64 `orm:"t1_retention" json:"t1Retention" description:"次日涨粉留存数"`
T7Retention *float64 `orm:"t7_retention" json:"t7Retention" description:"7日涨粉留存数"`
T15Retention *float64 `orm:"t15_retention" json:"t15Retention" description:"15日涨粉留存数"`
T30Retention *float64 `orm:"t30_retention" json:"t30Retention" description:"30日涨粉留存数"`
T1RetentionRatio *float64 `orm:"t1_retention_ratio" json:"t1RetentionRatio" description:"次日涨粉留存率"`
T7RetentionRatio *float64 `orm:"t7_retention_ratio" json:"t7RetentionRatio" description:"7日涨粉留存率"`
T15RetentionRatio *float64 `orm:"t15_retention_ratio" json:"t15RetentionRatio" description:"15日涨粉留存率"`
T30RetentionRatio *float64 `orm:"t30_retention_ratio" json:"t30RetentionRatio" description:"30日涨粉留存率"`
// 直播预约字段
ReservationSuccess *int64 `orm:"reservation_success" json:"reservationSuccess" description:"直播预约成功数"`
ReservationCost *float64 `orm:"reservation_cost" json:"reservationCost" description:"直播预约成功成本"`
// 直播观看字段
StandardLivePlayedStarted *int64 `orm:"standard_live_played_started" json:"standardLivePlayedStarted" description:"直播观看数"`
AdLivePlayCnt *int64 `orm:"ad_live_play_cnt" json:"adLivePlayCnt" description:"直播间人气数"`
AdLivePlayCntCost *float64 `orm:"ad_live_play_cnt_cost" json:"adLivePlayCntCost" description:"直播间人气成本"`
LiveAudienceCost *float64 `orm:"live_audience_cost" json:"liveAudienceCost" description:"直播观看成本"`
// 商品点击字段
LiveEventGoodsView *int64 `orm:"live_event_goods_view" json:"liveEventGoodsView" description:"直播间商品点击数"`
GoodsClickRatio *float64 `orm:"goods_click_ratio" json:"goodsClickRatio" description:"直播间商品点击率"`
// 新客相关字段
DirectAttrPlatNewBuyerCnt *int64 `orm:"direct_attr_plat_new_buyer_cnt" json:"directAttrPlatNewBuyerCnt" description:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `orm:"t30_attr_plat_total_buyer_cnt" json:"t30AttrPlatTotalBuyerCnt" description:"30日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `orm:"direct_attr_seller_new_buyer_cnt" json:"directAttrSellerNewBuyerCnt" description:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `orm:"t30_attr_seller_total_buyer_cnt" json:"t30AttrSellerTotalBuyerCnt" description:"30日累计店铺新客"`
// 间接订单字段
T7IndirectOrderAmt *float64 `orm:"t7_indirect_order_amt" json:"t7IndirectOrderAmt" description:"7日间接订单金额"`
T7IndirectOrderCnt *int64 `orm:"t7_indirect_order_cnt" json:"t7IndirectOrderCnt" description:"7日间接订单数"`
// 粉丝人均销售额字段
FansT0GmvPerFans *float64 `orm:"fans_t0_gmv_per_fans" json:"fansT0GmvPerFans" description:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `orm:"fans_t3_gmv_per_fans" json:"fansT3GmvPerFans" description:"3日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `orm:"fans_t7_gmv_per_fans" json:"fansT7GmvPerFans" description:"7日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `orm:"fans_t15_gmv_per_fans" json:"fansT15GmvPerFans" description:"15日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `orm:"fans_t30_gmv_per_fans" json:"fansT30GmvPerFans" description:"30日新增粉丝人均销售额"`
// 涨粉成本字段
RecoFansCost *float64 `orm:"reco_fans_cost" json:"recoFansCost" description:"涨粉成本"`
// 智能优惠券字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `orm:"qcpx_whitebox_direct_order_payment_amt" json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"智能优惠券订单GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `orm:"qcpx_whitebox_direct_order_cnt" json:"qcpxWhiteboxDirectOrderCnt" description:"智能优惠券订单数"`
// 粉丝GMV字段
FansT0Gmv *float64 `orm:"fans_t0_gmv" json:"fansT0Gmv" description:"涨粉当日GMV"`
FansT1Gmv *float64 `orm:"fans_t1_gmv" json:"fansT1Gmv" description:"涨粉次日GMV"`
FansT7Gmv *float64 `orm:"fans_t7_gmv" json:"fansT7Gmv" description:"涨粉7日GMV"`
FansT15Gmv *float64 `orm:"fans_t15_gmv" json:"fansT15Gmv" description:"涨粉15日GMV"`
FansT30Gmv *float64 `orm:"fans_t30_gmv" json:"fansT30Gmv" description:"涨粉30日GMV"`
// 粉丝ROI字段
FansT0Roi *float64 `orm:"fans_t0_roi" json:"fansT0Roi" description:"涨粉当日ROI"`
FansT1Roi *float64 `orm:"fans_t1_roi" json:"fansT1Roi" description:"涨粉次日ROI"`
FansT7Roi *float64 `orm:"fans_t7_roi" json:"fansT7Roi" description:"涨粉7日ROI"`
FansT15Roi *float64 `orm:"fans_t15_roi" json:"fansT15Roi" description:"涨粉15日ROI"`
FansT30Roi *float64 `orm:"fans_t30_roi" json:"fansT30Roi" description:"涨粉30日ROI"`
// 新客GMV字段
T0ShopNewBuyerOrderPaymentAmt *float64 `orm:"t0_shop_new_buyer_order_payment_amt" json:"t0ShopNewBuyerOrderPaymentAmt" description:"当日新客GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `orm:"t1_shop_new_buyer_order_payment_amt" json:"t1ShopNewBuyerOrderPaymentAmt" description:"投后1日新客GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `orm:"t3_shop_new_buyer_order_payment_amt" json:"t3ShopNewBuyerOrderPaymentAmt" description:"投后3日新客GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `orm:"t7_shop_new_buyer_order_payment_amt" json:"t7ShopNewBuyerOrderPaymentAmt" description:"投后7日新客GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `orm:"t15_shop_new_buyer_order_payment_amt" json:"t15ShopNewBuyerOrderPaymentAmt" description:"投后15日新客GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `orm:"t30_shop_new_buyer_order_payment_amt" json:"t30ShopNewBuyerOrderPaymentAmt" description:"投后30日新客GMV"`
// 新客订单量字段
T0ShopNewBuyerOrderCnt *int64 `orm:"t0_shop_new_buyer_order_cnt" json:"t0ShopNewBuyerOrderCnt" description:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `orm:"t1_shop_new_buyer_order_cnt" json:"t1ShopNewBuyerOrderCnt" description:"投后1日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `orm:"t3_shop_new_buyer_order_cnt" json:"t3ShopNewBuyerOrderCnt" description:"投后3日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `orm:"t7_shop_new_buyer_order_cnt" json:"t7ShopNewBuyerOrderCnt" description:"投后7日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `orm:"t15_shop_new_buyer_order_cnt" json:"t15ShopNewBuyerOrderCnt" description:"投后15日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `orm:"t30_shop_new_buyer_order_cnt" json:"t30ShopNewBuyerOrderCnt" description:"投后30日新客成交订单量"`
// 新客复购率字段
T1NewBuyerRepurchaseRatio *float64 `orm:"t1_new_buyer_repurchase_ratio" json:"t1NewBuyerRepurchaseRatio" description:"投后1日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `orm:"t3_new_buyer_repurchase_ratio" json:"t3NewBuyerRepurchaseRatio" description:"投后3日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `orm:"t7_new_buyer_repurchase_ratio" json:"t7NewBuyerRepurchaseRatio" description:"投后7日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `orm:"t15_new_buyer_repurchase_ratio" json:"t15NewBuyerRepurchaseRatio" description:"投后15日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `orm:"t30_new_buyer_repurchase_ratio" json:"t30NewBuyerRepurchaseRatio" description:"投后30日新客复购率"`
// 新客ROI字段
T0ShopNewBuyerRoi *float64 `orm:"t0_shop_new_buyer_roi" json:"t0ShopNewBuyerRoi" description:"投后当日新客ROI"`
T1ShopNewBuyerRoi *float64 `orm:"t1_shop_new_buyer_roi" json:"t1ShopNewBuyerRoi" description:"投后1日新客ROI"`
T3ShopNewBuyerRoi *float64 `orm:"t3_shop_new_buyer_roi" json:"t3ShopNewBuyerRoi" description:"投后3日新客ROI"`
T7ShopNewBuyerRoi *float64 `orm:"t7_shop_new_buyer_roi" json:"t7ShopNewBuyerRoi" description:"投后7日新客ROI"`
T15ShopNewBuyerRoi *float64 `orm:"t15_shop_new_buyer_roi" json:"t15ShopNewBuyerRoi" description:"投后15日新客ROI"`
T30ShopNewBuyerRoi *float64 `orm:"t30_shop_new_buyer_roi" json:"t30ShopNewBuyerRoi" description:"投后30日新客ROI"`
// 电话卡相关字段
CreateCardOrderCnt *int64 `orm:"create_card_order_cnt" json:"createCardOrderCnt" description:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `orm:"forward_ts_create_card_order_cnt" json:"forwardTsCreateCardOrderCnt" description:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `orm:"create_card_order_cost" json:"createCardOrderCost" description:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `orm:"forward_ts_create_card_order_cost" json:"forwardTsCreateCardOrderCost" description:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `orm:"activate_card_order_cnt" json:"activateCardOrderCnt" description:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `orm:"forward_ts_activate_card_order_cnt" json:"forwardTsActivateCardOrderCnt" description:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `orm:"activate_card_order_cost" json:"activateCardOrderCost" description:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `orm:"forward_ts_activate_card_order_cost" json:"forwardTsActivateCardOrderCost" description:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `orm:"create_card_order_ratio" json:"createCardOrderRatio" description:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `orm:"forward_ts_create_card_order_ratio" json:"forwardTsCreateCardOrderRatio" description:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `orm:"activate_card_order_cnt_ratio" json:"activateCardOrderCntRatio" description:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `orm:"forward_ts_activate_card_order_ratio" json:"forwardTsActivateCardOrderRatio" description:"电话卡激活率(计费时间)"`
// 全站数据字段
LivePlayCnt *int64 `orm:"live_play_cnt" json:"livePlayCnt" description:"全站直播观看数"`
ItemEntranceClkCnt *int64 `orm:"item_entrance_clk_cnt" json:"itemEntranceClkCnt" description:"小黄车点击数"`
ShowCnt *int64 `orm:"show_cnt" json:"showCnt" description:"全站曝光"`
// 时间字段
ReportDateStr string `orm:"report_date_str" json:"reportDateStr" description:"时间"`
// 广告结构字段
CampaignId *int64 `orm:"campaign_id" json:"campaignId" description:"计划ID"`
CampaignName string `orm:"campaign_name" json:"campaignName" description:"计划名称"`
UnitId *int64 `orm:"unit_id" json:"unitId" description:"单元ID"`
UnitName string `orm:"unit_name" json:"unitName" description:"单元名称"`
CreativeId *int64 `orm:"creative_id" json:"creativeId" description:"创意ID"`
CreativeName string `orm:"creative_name" json:"creativeName" description:"创意名称"`
// 补贴相关字段
CidActualRoiAfterSubsidy *float64 `orm:"cid_actual_roi_after_subsidy" json:"cidActualRoiAfterSubsidy" description:"补贴后实际ROI"`
CidCouponAmount *int64 `orm:"cid_coupon_amount" json:"cidCouponAmount" description:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `orm:"cid_coupon_callback_paid_refund_amount" json:"cidCouponCallbackPaidRefundAmount" description:"退单有回传_核销券金额"`
CidVoucherCost *float64 `orm:"cid_voucher_cost" json:"cidVoucherCost" description:"券成本"`
}
// CidAccountReportDetailCol 广告数据报表详情表字段定义
type CidAccountReportDetailCol struct {
beans.SQLBaseCol
DataType string
T0OrderPaymentAmt string
CreativeMaterialType string
LiveName string
AuthorId string
PicUrl string
PicName string
PicId string
CoverUrl string
CoverId string
ItemOrderConversionRatio string
ItemCardClickRatio string
ItemCardClkCnt string
LivePlayCntCost string
AdMerchantFollowCost string
AdMerchantFollow string
NetT0OrderCnt string
NetT0Roi string
NetT0Gmv string
PhotoName string
PhotoIdStr string
PhotoId string
ModPriceSegment string
AgeSegment string
Province string
Gender string
AdPhotoPlayedFiveRatio string
AdPhotoPlayedThreeRatio string
OrderSubmitRoi string
OrderSubmitAmt string
EventOrderSubmitCost string
EventOrderSubmit string
EventOrderPaidRoi string
EventAppInvoked string
EventAddShoppingCart string
ConversionNumCost string
AdEffectivePlayNum string
AdItemClick string
MerchantProductId string
CostTotal string
AdShow string
AdShow1kCost string
Impression string
PhotoClick string
PhotoClickRatio string
Click string
ActionbarClick string
ActionbarClickCost string
EspClickRatio string
ActionRatio string
AdItemClickCount string
EspLivePlayedSeconds string
PlayedThreeSeconds string
Play3sRatio string
PlayedFiveSeconds string
Play5sRatio string
PlayedEnd string
PlayEndRatio string
Share string
Comment string
Likes string
Report string
Block string
ItemNegative string
LiveShare string
LiveComment string
LiveReward string
EffectivePlayCount string
EffectivePlayRatio string
ConversionNum string
ConversionCostEsp string
Roi string
Gmv string
T0Gmv string
T1Gmv string
T3Gmv string
T7Gmv string
T15Gmv string
T30Gmv string
T0Roi string
T1Roi string
T3Roi string
T7Roi string
T15Roi string
T30Roi string
PaiedOrder string
OrderRatio string
T0OrderCnt string
T0OrderCntCost string
T0OrderCntRatio string
T1OrderCnt string
T3OrderCnt string
T7OrderCnt string
T15OrderCnt string
T30OrderCnt string
MerchantRecoFans string
T1Retention string
T7Retention string
T15Retention string
T30Retention string
T1RetentionRatio string
T7RetentionRatio string
T15RetentionRatio string
T30RetentionRatio string
ReservationSuccess string
ReservationCost string
StandardLivePlayedStarted string
AdLivePlayCnt string
AdLivePlayCntCost string
LiveAudienceCost string
LiveEventGoodsView string
GoodsClickRatio string
DirectAttrPlatNewBuyerCnt string
T30AttrPlatTotalBuyerCnt string
DirectAttrSellerNewBuyerCnt string
T30AttrSellerTotalBuyerCnt string
T7IndirectOrderAmt string
T7IndirectOrderCnt string
FansT0GmvPerFans string
FansT3GmvPerFans string
FansT7GmvPerFans string
FansT15GmvPerFans string
FansT30GmvPerFans string
RecoFansCost string
QcpxWhiteboxDirectOrderPaymentAmt string
QcpxWhiteboxDirectOrderCnt string
FansT0Gmv string
FansT1Gmv string
FansT7Gmv string
FansT15Gmv string
FansT30Gmv string
FansT0Roi string
FansT1Roi string
FansT7Roi string
FansT15Roi string
FansT30Roi string
T0ShopNewBuyerOrderPaymentAmt string
T1ShopNewBuyerOrderPaymentAmt string
T3ShopNewBuyerOrderPaymentAmt string
T7ShopNewBuyerOrderPaymentAmt string
T15ShopNewBuyerOrderPaymentAmt string
T30ShopNewBuyerOrderPaymentAmt string
T0ShopNewBuyerOrderCnt string
T1ShopNewBuyerOrderCnt string
T3ShopNewBuyerOrderCnt string
T7ShopNewBuyerOrderCnt string
T15ShopNewBuyerOrderCnt string
T30ShopNewBuyerOrderCnt string
T1NewBuyerRepurchaseRatio string
T3NewBuyerRepurchaseRatio string
T7NewBuyerRepurchaseRatio string
T15NewBuyerRepurchaseRatio string
T30NewBuyerRepurchaseRatio string
T0ShopNewBuyerRoi string
T1ShopNewBuyerRoi string
T3ShopNewBuyerRoi string
T7ShopNewBuyerRoi string
T15ShopNewBuyerRoi string
T30ShopNewBuyerRoi string
CreateCardOrderCnt string
ForwardTsCreateCardOrderCnt string
CreateCardOrderCost string
ForwardTsCreateCardOrderCost string
ActivateCardOrderCnt string
ForwardTsActivateCardOrderCnt string
ActivateCardOrderCost string
ForwardTsActivateCardOrderCost string
CreateCardOrderRatio string
ForwardTsCreateCardOrderRatio string
ActivateCardOrderCntRatio string
ForwardTsActivateCardOrderRatio string
LivePlayCnt string
ItemEntranceClkCnt string
ShowCnt string
ReportDateStr string
CampaignId string
CampaignName string
UnitId string
UnitName string
CreativeId string
CreativeName string
CidActualRoiAfterSubsidy string
CidCouponAmount string
CidCouponCallbackPaidRefundAmount string
CidVoucherCost string
}
// TableName 返回表名
func (e *CidAccountReportDetail) TableName() string {
return "cid_account_report_detail"
}
// GetCols 获取所有字段名
func (e *CidAccountReportDetail) GetCols() *CidAccountReportDetailCol {
return &CidAccountReportDetailCol{
SQLBaseCol: beans.SQLBaseCol{
Id: "id",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
DataType: "data_type",
T0OrderPaymentAmt: "t0_order_payment_amt",
CreativeMaterialType: "creative_material_type",
LiveName: "live_name",
AuthorId: "author_id",
PicUrl: "pic_url",
PicName: "pic_name",
PicId: "pic_id",
CoverUrl: "cover_url",
CoverId: "cover_id",
ItemOrderConversionRatio: "item_order_conversion_ratio",
ItemCardClickRatio: "item_card_click_ratio",
ItemCardClkCnt: "item_card_clk_cnt",
LivePlayCntCost: "live_play_cnt_cost",
AdMerchantFollowCost: "ad_merchant_follow_cost",
AdMerchantFollow: "ad_merchant_follow",
NetT0OrderCnt: "net_t0_order_cnt",
NetT0Roi: "net_t0_roi",
NetT0Gmv: "net_t0_gmv",
PhotoName: "photo_name",
PhotoIdStr: "photo_id_str",
PhotoId: "photo_id",
ModPriceSegment: "mod_price_segment",
AgeSegment: "age_segment",
Province: "province",
Gender: "gender",
AdPhotoPlayedFiveRatio: "ad_photo_played_five_ratio",
AdPhotoPlayedThreeRatio: "ad_photo_played_three_ratio",
OrderSubmitRoi: "order_submit_roi",
OrderSubmitAmt: "order_submit_amt",
EventOrderSubmitCost: "event_order_submit_cost",
EventOrderSubmit: "event_order_submit",
EventOrderPaidRoi: "event_order_paid_roi",
EventAppInvoked: "event_app_invoked",
EventAddShoppingCart: "event_add_shopping_cart",
ConversionNumCost: "conversion_num_cost",
AdEffectivePlayNum: "ad_effective_play_num",
AdItemClick: "ad_item_click",
MerchantProductId: "merchant_product_id",
CostTotal: "cost_total",
AdShow: "ad_show",
AdShow1kCost: "ad_show1k_cost",
Impression: "impression",
PhotoClick: "photo_click",
PhotoClickRatio: "photo_click_ratio",
Click: "click",
ActionbarClick: "actionbar_click",
ActionbarClickCost: "actionbar_click_cost",
EspClickRatio: "esp_click_ratio",
ActionRatio: "action_ratio",
AdItemClickCount: "ad_item_click_count",
EspLivePlayedSeconds: "esp_live_played_seconds",
PlayedThreeSeconds: "played_three_seconds",
Play3sRatio: "play3s_ratio",
PlayedFiveSeconds: "played_five_seconds",
Play5sRatio: "play5s_ratio",
PlayedEnd: "played_end",
PlayEndRatio: "play_end_ratio",
Share: "share",
Comment: "comment",
Likes: "likes",
Report: "report",
Block: "block",
ItemNegative: "item_negative",
LiveShare: "live_share",
LiveComment: "live_comment",
LiveReward: "live_reward",
EffectivePlayCount: "effective_play_count",
EffectivePlayRatio: "effective_play_ratio",
ConversionNum: "conversion_num",
ConversionCostEsp: "conversion_cost_esp",
Roi: "roi",
Gmv: "gmv",
T0Gmv: "t0_gmv",
T1Gmv: "t1_gmv",
T3Gmv: "t3_gmv",
T7Gmv: "t7_gmv",
T15Gmv: "t15_gmv",
T30Gmv: "t30_gmv",
T0Roi: "t0_roi",
T1Roi: "t1_roi",
T3Roi: "t3_roi",
T7Roi: "t7_roi",
T15Roi: "t15_roi",
T30Roi: "t30_roi",
PaiedOrder: "paied_order",
OrderRatio: "order_ratio",
T0OrderCnt: "t0_order_cnt",
T0OrderCntCost: "t0_order_cnt_cost",
T0OrderCntRatio: "t0_order_cnt_ratio",
T1OrderCnt: "t1_order_cnt",
T3OrderCnt: "t3_order_cnt",
T7OrderCnt: "t7_order_cnt",
T15OrderCnt: "t15_order_cnt",
T30OrderCnt: "t30_order_cnt",
MerchantRecoFans: "merchant_reco_fans",
T1Retention: "t1_retention",
T7Retention: "t7_retention",
T15Retention: "t15_retention",
T30Retention: "t30_retention",
T1RetentionRatio: "t1_retention_ratio",
T7RetentionRatio: "t7_retention_ratio",
T15RetentionRatio: "t15_retention_ratio",
T30RetentionRatio: "t30_retention_ratio",
ReservationSuccess: "reservation_success",
ReservationCost: "reservation_cost",
StandardLivePlayedStarted: "standard_live_played_started",
AdLivePlayCnt: "ad_live_play_cnt",
AdLivePlayCntCost: "ad_live_play_cnt_cost",
LiveAudienceCost: "live_audience_cost",
LiveEventGoodsView: "live_event_goods_view",
GoodsClickRatio: "goods_click_ratio",
DirectAttrPlatNewBuyerCnt: "direct_attr_plat_new_buyer_cnt",
T30AttrPlatTotalBuyerCnt: "t30_attr_plat_total_buyer_cnt",
DirectAttrSellerNewBuyerCnt: "direct_attr_seller_new_buyer_cnt",
T30AttrSellerTotalBuyerCnt: "t30_attr_seller_total_buyer_cnt",
T7IndirectOrderAmt: "t7_indirect_order_amt",
T7IndirectOrderCnt: "t7_indirect_order_cnt",
FansT0GmvPerFans: "fans_t0_gmv_per_fans",
FansT3GmvPerFans: "fans_t3_gmv_per_fans",
FansT7GmvPerFans: "fans_t7_gmv_per_fans",
FansT15GmvPerFans: "fans_t15_gmv_per_fans",
FansT30GmvPerFans: "fans_t30_gmv_per_fans",
RecoFansCost: "reco_fans_cost",
QcpxWhiteboxDirectOrderPaymentAmt: "qcpx_whitebox_direct_order_payment_amt",
QcpxWhiteboxDirectOrderCnt: "qcpx_whitebox_direct_order_cnt",
FansT0Gmv: "fans_t0_gmv",
FansT1Gmv: "fans_t1_gmv",
FansT7Gmv: "fans_t7_gmv",
FansT15Gmv: "fans_t15_gmv",
FansT30Gmv: "fans_t30_gmv",
FansT0Roi: "fans_t0_roi",
FansT1Roi: "fans_t1_roi",
FansT7Roi: "fans_t7_roi",
FansT15Roi: "fans_t15_roi",
FansT30Roi: "fans_t30_roi",
T0ShopNewBuyerOrderPaymentAmt: "t0_shop_new_buyer_order_payment_amt",
T1ShopNewBuyerOrderPaymentAmt: "t1_shop_new_buyer_order_payment_amt",
T3ShopNewBuyerOrderPaymentAmt: "t3_shop_new_buyer_order_payment_amt",
T7ShopNewBuyerOrderPaymentAmt: "t7_shop_new_buyer_order_payment_amt",
T15ShopNewBuyerOrderPaymentAmt: "t15_shop_new_buyer_order_payment_amt",
T30ShopNewBuyerOrderPaymentAmt: "t30_shop_new_buyer_order_payment_amt",
T0ShopNewBuyerOrderCnt: "t0_shop_new_buyer_order_cnt",
T1ShopNewBuyerOrderCnt: "t1_shop_new_buyer_order_cnt",
T3ShopNewBuyerOrderCnt: "t3_shop_new_buyer_order_cnt",
T7ShopNewBuyerOrderCnt: "t7_shop_new_buyer_order_cnt",
T15ShopNewBuyerOrderCnt: "t15_shop_new_buyer_order_cnt",
T30ShopNewBuyerOrderCnt: "t30_shop_new_buyer_order_cnt",
T1NewBuyerRepurchaseRatio: "t1_new_buyer_repurchase_ratio",
T3NewBuyerRepurchaseRatio: "t3_new_buyer_repurchase_ratio",
T7NewBuyerRepurchaseRatio: "t7_new_buyer_repurchase_ratio",
T15NewBuyerRepurchaseRatio: "t15_new_buyer_repurchase_ratio",
T30NewBuyerRepurchaseRatio: "t30_new_buyer_repurchase_ratio",
T0ShopNewBuyerRoi: "t0_shop_new_buyer_roi",
T1ShopNewBuyerRoi: "t1_shop_new_buyer_roi",
T3ShopNewBuyerRoi: "t3_shop_new_buyer_roi",
T7ShopNewBuyerRoi: "t7_shop_new_buyer_roi",
T15ShopNewBuyerRoi: "t15_shop_new_buyer_roi",
T30ShopNewBuyerRoi: "t30_shop_new_buyer_roi",
CreateCardOrderCnt: "create_card_order_cnt",
ForwardTsCreateCardOrderCnt: "forward_ts_create_card_order_cnt",
CreateCardOrderCost: "create_card_order_cost",
ForwardTsCreateCardOrderCost: "forward_ts_create_card_order_cost",
ActivateCardOrderCnt: "activate_card_order_cnt",
ForwardTsActivateCardOrderCnt: "forward_ts_activate_card_order_cnt",
ActivateCardOrderCost: "activate_card_order_cost",
ForwardTsActivateCardOrderCost: "forward_ts_activate_card_order_cost",
CreateCardOrderRatio: "create_card_order_ratio",
ForwardTsCreateCardOrderRatio: "forward_ts_create_card_order_ratio",
ActivateCardOrderCntRatio: "activate_card_order_cnt_ratio",
ForwardTsActivateCardOrderRatio: "forward_ts_activate_card_order_ratio",
LivePlayCnt: "live_play_cnt",
ItemEntranceClkCnt: "item_entrance_clk_cnt",
ShowCnt: "show_cnt",
ReportDateStr: "report_date_str",
CampaignId: "campaign_id",
CampaignName: "campaign_name",
UnitId: "unit_id",
UnitName: "unit_name",
CreativeId: "creative_id",
CreativeName: "creative_name",
CidActualRoiAfterSubsidy: "cid_actual_roi_after_subsidy",
CidCouponAmount: "cid_coupon_amount",
CidCouponCallbackPaidRefundAmount: "cid_coupon_callback_paid_refund_amount",
CidVoucherCost: "cid_voucher_cost",
}
}
@@ -1,665 +0,0 @@
package copydata
import "gitea.com/red-future/common/beans"
// CidAccountReportSum 广告数据报表详情实体
type CidAccountReportSum struct {
beans.SQLBaseDO `orm:",inherit"`
// 基础信息字段
DataType string `orm:"data_type" json:"dataType" description:"类型type"`
// 核心指标字段
T0OrderPaymentAmt string `orm:"t0_order_payment_amt" json:"t0OrderPaymentAmt" description:"当日总成交订单金额"`
CreativeMaterialType string `orm:"creative_material_type" json:"creativeMaterialType" description:"视频素材类型"`
LiveName string `orm:"live_name" json:"liveName" description:"直播间名称"`
AuthorId string `orm:"author_id" json:"authorId" description:"直播用户快手Id"`
PicUrl string `orm:"pic_url" json:"picUrl" description:"图片URL"`
PicName string `orm:"pic_name" json:"picName" description:"图片名称"`
PicId string `orm:"pic_id" json:"picId" description:"图片Id"`
CoverUrl string `orm:"cover_url" json:"coverUrl" description:"封面URL"`
CoverId int64 `orm:"cover_id" json:"coverId" description:"封面Id"`
// 转化率相关字段
ItemOrderConversionRatio *float64 `orm:"item_order_conversion_ratio" json:"itemOrderConversionRatio" description:"转化率"`
ItemCardClickRatio *float64 `orm:"item_card_click_ratio" json:"itemCardClickRatio" description:"点击率"`
ItemCardClkCnt *int64 `orm:"item_card_clk_cnt" json:"itemCardClkCnt" description:"商品卡点击数"`
LivePlayCntCost *float64 `orm:"live_play_cnt_cost" json:"livePlayCntCost" description:"直播间观看成本"`
AdMerchantFollowCost *float64 `orm:"ad_merchant_follow_cost" json:"adMerchantFollowCost" description:"涨粉成本"`
AdMerchantFollow *int64 `orm:"ad_merchant_follow" json:"adMerchantFollow" description:"涨粉数"`
NetT0OrderCnt *int64 `orm:"net_t0_order_cnt" json:"netT0OrderCnt" description:"当日累计净成交订单数"`
NetT0Roi *float64 `orm:"net_t0_roi" json:"netT0Roi" description:"净成交ROI"`
NetT0Gmv *float64 `orm:"net_t0_gmv" json:"netT0Gmv" description:"净成交GMV"`
// 视频信息字段
PhotoName string `orm:"photo_name" json:"photoName" description:"视频名称"`
PhotoIdStr string `orm:"photo_id_str" json:"photoIdStr" description:"视频id"`
PhotoId string `orm:"photo_id" json:"photoId" description:"视频id"`
// 受众属性字段
ModPriceSegment string `orm:"mod_price_segment" json:"modPriceSegment" description:"设备价格区间"`
AgeSegment string `orm:"age_segment" json:"ageSegment" description:"年龄段"`
Province string `orm:"province" json:"province" description:"省份名称"`
Gender string `orm:"gender" json:"gender" description:"性别"`
// 播放率相关字段
AdPhotoPlayedFiveRatio *float64 `orm:"ad_photo_played_five_ratio" json:"adPhotoPlayedFiveRatio" description:"作品5秒播放率"`
AdPhotoPlayedThreeRatio *float64 `orm:"ad_photo_played_three_ratio" json:"adPhotoPlayedThreeRatio" description:"作品3秒播放率"`
// 订单相关字段
OrderSubmitRoi *float64 `orm:"order_submit_roi" json:"orderSubmitRoi" description:"订单提交ROI"`
OrderSubmitAmt *int64 `orm:"order_submit_amt" json:"orderSubmitAmt" description:"外部订单金额"`
EventOrderSubmitCost *float64 `orm:"event_order_submit_cost" json:"eventOrderSubmitCost" description:"订单提交成本"`
EventOrderSubmit *int64 `orm:"event_order_submit" json:"eventOrderSubmit" description:"订单提交数"`
EventOrderPaidRoi *float64 `orm:"event_order_paid_roi" json:"eventOrderPaidRoi" description:"订单支付率"`
EventAppInvoked *int64 `orm:"event_app_invoked" json:"eventAppInvoked" description:"唤起应用数"`
EventAddShoppingCart *int64 `orm:"event_add_shopping_cart" json:"eventAddShoppingCart" description:"添加购物车次数"`
ConversionNumCost *float64 `orm:"conversion_num_cost" json:"conversionNumCost" description:"转化成本"`
AdEffectivePlayNum *int64 `orm:"ad_effective_play_num" json:"adEffectivePlayNum" description:"有效播放数"`
AdItemClick *int64 `orm:"ad_item_click" json:"adItemClick" description:"行为数"`
// 商品信息字段
MerchantProductId string `orm:"merchant_product_id" json:"merchantProductId" description:"商品ID"`
// 花费相关字段
CostTotal *float64 `orm:"cost_total" json:"costTotal" description:"花费"`
AdShow *int64 `orm:"ad_show" json:"adShow" description:"曝光数"`
AdShow1kCost *float64 `orm:"ad_show1k_cost" json:"adShow1kCost" description:"平均千次广告曝光花费"`
// 封面相关字段
Impression *int64 `orm:"impression" json:"impression" description:"封面曝光数"`
PhotoClick *int64 `orm:"photo_click" json:"photoClick" description:"封面点击数"`
PhotoClickRatio *float64 `orm:"photo_click_ratio" json:"photoClickRatio" description:"封面点击率"`
// 点击相关字段
Click *int64 `orm:"click" json:"click" description:"素材曝光数"`
ActionbarClick *int64 `orm:"actionbar_click" json:"actionbarClick" description:"行为数"`
ActionbarClickCost *float64 `orm:"actionbar_click_cost" json:"actionbarClickCost" description:"行为成本"`
EspClickRatio *float64 `orm:"esp_click_ratio" json:"espClickRatio" description:"行为率"`
ActionRatio *float64 `orm:"action_ratio" json:"actionRatio" description:"素材点击率"`
AdItemClickCount *int64 `orm:"ad_item_click_count" json:"adItemClickCount" description:"预约组件点击数"`
// 直播时长字段
EspLivePlayedSeconds *int64 `orm:"esp_live_played_seconds" json:"espLivePlayedSeconds" description:"直播平均观看时长"`
// 播放数据字段
PlayedThreeSeconds *int64 `orm:"played_three_seconds" json:"playedThreeSeconds" description:"作品3秒播放数"`
Play3sRatio *float64 `orm:"play3s_ratio" json:"play3sRatio" description:"作品3秒播放率"`
PlayedFiveSeconds *int64 `orm:"played_five_seconds" json:"playedFiveSeconds" description:"作品5秒播放数"`
Play5sRatio *float64 `orm:"play5s_ratio" json:"play5sRatio" description:"作品5秒播放率"`
PlayedEnd *int64 `orm:"played_end" json:"playedEnd" description:"作品完播数"`
PlayEndRatio *float64 `orm:"play_end_ratio" json:"playEndRatio" description:"作品完播率"`
// 互动数据字段
Share *int64 `orm:"share" json:"share" description:"作品分享数"`
Comment *int64 `orm:"comment" json:"comment" description:"作品评论数"`
Likes *int64 `orm:"likes" json:"likes" description:"作品点赞数"`
Report *int64 `orm:"report" json:"report" description:"作品举报数"`
Block *int64 `orm:"block" json:"block" description:"作品拉黑数"`
ItemNegative *int64 `orm:"item_negative" json:"itemNegative" description:"详情页减少此类作品数"`
// 直播互动字段
LiveShare *int64 `orm:"live_share" json:"liveShare" description:"直播送礼数"`
LiveComment *int64 `orm:"live_comment" json:"liveComment" description:"直播评论数"`
LiveReward *int64 `orm:"live_reward" json:"liveReward" description:"直播送礼数"`
// 播放效果字段
EffectivePlayCount *int64 `orm:"effective_play_count" json:"effectivePlayCount" description:"有效播放数"`
EffectivePlayRatio *float64 `orm:"effective_play_ratio" json:"effectivePlayRatio" description:"有效播放率"`
// 转化字段
ConversionNum *int64 `orm:"conversion_num" json:"conversionNum" description:"转化数"`
ConversionCostEsp *float64 `orm:"conversion_cost_esp" json:"conversionCostEsp" description:"转化成本"`
Roi *float64 `orm:"roi" json:"roi" description:"直接ROI"`
Gmv *float64 `orm:"gmv" json:"gmv" description:"直接GMV"`
// GMV时间序列字段
T0Gmv *float64 `orm:"t0_gmv" json:"t0Gmv" description:"当日累计GMV"`
T1Gmv *float64 `orm:"t1_gmv" json:"t1Gmv" description:"次日累计GMV"`
T3Gmv *float64 `orm:"t3_gmv" json:"t3Gmv" description:"3日累计GMV"`
T7Gmv *float64 `orm:"t7_gmv" json:"t7Gmv" description:"7日累计GMV"`
T15Gmv *float64 `orm:"t15_gmv" json:"t15Gmv" description:"15日累计GMV"`
T30Gmv *float64 `orm:"t30_gmv" json:"t30Gmv" description:"30日累计GMV"`
// ROI时间序列字段
T0Roi *float64 `orm:"t0_roi" json:"t0Roi" description:"当日累计ROI"`
T1Roi *float64 `orm:"t1_roi" json:"t1Roi" description:"次日累计ROI"`
T3Roi *float64 `orm:"t3_roi" json:"t3Roi" description:"3日累计ROI"`
T7Roi *float64 `orm:"t7_roi" json:"t7Roi" description:"7日累计ROI"`
T15Roi *float64 `orm:"t15_roi" json:"t15Roi" description:"15日累计ROI"`
T30Roi *float64 `orm:"t30_roi" json:"t30Roi" description:"30日累计ROI"`
// 订单相关字段
PaiedOrder *int64 `orm:"paied_order" json:"paiedOrder" description:"直接订单数"`
OrderRatio *float64 `orm:"order_ratio" json:"orderRatio" description:"直接下单率"`
T0OrderCnt *int64 `orm:"t0_order_cnt" json:"t0OrderCnt" description:"当日累计订单数"`
T0OrderCntCost *float64 `orm:"t0_order_cnt_cost" json:"t0OrderCntCost" description:"当日累计订单成本"`
T0OrderCntRatio *float64 `orm:"t0_order_cnt_ratio" json:"t0OrderCntRatio" description:"累计订单下单率"`
T1OrderCnt *int64 `orm:"t1_order_cnt" json:"t1OrderCnt" description:"次日累计订单数"`
T3OrderCnt *int64 `orm:"t3_order_cnt" json:"t3OrderCnt" description:"3日累计订单数"`
T7OrderCnt *int64 `orm:"t7_order_cnt" json:"t7OrderCnt" description:"7日累计订单数"`
T15OrderCnt *int64 `orm:"t15_order_cnt" json:"t15OrderCnt" description:"15日累计订单数"`
T30OrderCnt *int64 `orm:"t30_order_cnt" json:"t30OrderCnt" description:"30日累计订单数"`
// 粉丝相关字段
MerchantRecoFans *int64 `orm:"merchant_reco_fans" json:"merchantRecoFans" description:"涨粉数"`
T1Retention *float64 `orm:"t1_retention" json:"t1Retention" description:"次日涨粉留存数"`
T7Retention *float64 `orm:"t7_retention" json:"t7Retention" description:"7日涨粉留存数"`
T15Retention *float64 `orm:"t15_retention" json:"t15Retention" description:"15日涨粉留存数"`
T30Retention *float64 `orm:"t30_retention" json:"t30Retention" description:"30日涨粉留存数"`
T1RetentionRatio *float64 `orm:"t1_retention_ratio" json:"t1RetentionRatio" description:"次日涨粉留存率"`
T7RetentionRatio *float64 `orm:"t7_retention_ratio" json:"t7RetentionRatio" description:"7日涨粉留存率"`
T15RetentionRatio *float64 `orm:"t15_retention_ratio" json:"t15RetentionRatio" description:"15日涨粉留存率"`
T30RetentionRatio *float64 `orm:"t30_retention_ratio" json:"t30RetentionRatio" description:"30日涨粉留存率"`
// 直播预约字段
ReservationSuccess *int64 `orm:"reservation_success" json:"reservationSuccess" description:"直播预约成功数"`
ReservationCost *float64 `orm:"reservation_cost" json:"reservationCost" description:"直播预约成功成本"`
// 直播观看字段
StandardLivePlayedStarted *int64 `orm:"standard_live_played_started" json:"standardLivePlayedStarted" description:"直播观看数"`
AdLivePlayCnt *int64 `orm:"ad_live_play_cnt" json:"adLivePlayCnt" description:"直播间人气数"`
AdLivePlayCntCost *float64 `orm:"ad_live_play_cnt_cost" json:"adLivePlayCntCost" description:"直播间人气成本"`
LiveAudienceCost *float64 `orm:"live_audience_cost" json:"liveAudienceCost" description:"直播观看成本"`
// 商品点击字段
LiveEventGoodsView *int64 `orm:"live_event_goods_view" json:"liveEventGoodsView" description:"直播间商品点击数"`
GoodsClickRatio *float64 `orm:"goods_click_ratio" json:"goodsClickRatio" description:"直播间商品点击率"`
// 新客相关字段
DirectAttrPlatNewBuyerCnt *int64 `orm:"direct_attr_plat_new_buyer_cnt" json:"directAttrPlatNewBuyerCnt" description:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `orm:"t30_attr_plat_total_buyer_cnt" json:"t30AttrPlatTotalBuyerCnt" description:"30日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `orm:"direct_attr_seller_new_buyer_cnt" json:"directAttrSellerNewBuyerCnt" description:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `orm:"t30_attr_seller_total_buyer_cnt" json:"t30AttrSellerTotalBuyerCnt" description:"30日累计店铺新客"`
// 间接订单字段
T7IndirectOrderAmt *float64 `orm:"t7_indirect_order_amt" json:"t7IndirectOrderAmt" description:"7日间接订单金额"`
T7IndirectOrderCnt *int64 `orm:"t7_indirect_order_cnt" json:"t7IndirectOrderCnt" description:"7日间接订单数"`
// 粉丝人均销售额字段
FansT0GmvPerFans *float64 `orm:"fans_t0_gmv_per_fans" json:"fansT0GmvPerFans" description:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `orm:"fans_t3_gmv_per_fans" json:"fansT3GmvPerFans" description:"3日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `orm:"fans_t7_gmv_per_fans" json:"fansT7GmvPerFans" description:"7日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `orm:"fans_t15_gmv_per_fans" json:"fansT15GmvPerFans" description:"15日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `orm:"fans_t30_gmv_per_fans" json:"fansT30GmvPerFans" description:"30日新增粉丝人均销售额"`
// 涨粉成本字段
RecoFansCost *float64 `orm:"reco_fans_cost" json:"recoFansCost" description:"涨粉成本"`
// 智能优惠券字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `orm:"qcpx_whitebox_direct_order_payment_amt" json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"智能优惠券订单GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `orm:"qcpx_whitebox_direct_order_cnt" json:"qcpxWhiteboxDirectOrderCnt" description:"智能优惠券订单数"`
// 粉丝GMV字段
FansT0Gmv *float64 `orm:"fans_t0_gmv" json:"fansT0Gmv" description:"涨粉当日GMV"`
FansT1Gmv *float64 `orm:"fans_t1_gmv" json:"fansT1Gmv" description:"涨粉次日GMV"`
FansT7Gmv *float64 `orm:"fans_t7_gmv" json:"fansT7Gmv" description:"涨粉7日GMV"`
FansT15Gmv *float64 `orm:"fans_t15_gmv" json:"fansT15Gmv" description:"涨粉15日GMV"`
FansT30Gmv *float64 `orm:"fans_t30_gmv" json:"fansT30Gmv" description:"涨粉30日GMV"`
// 粉丝ROI字段
FansT0Roi *float64 `orm:"fans_t0_roi" json:"fansT0Roi" description:"涨粉当日ROI"`
FansT1Roi *float64 `orm:"fans_t1_roi" json:"fansT1Roi" description:"涨粉次日ROI"`
FansT7Roi *float64 `orm:"fans_t7_roi" json:"fansT7Roi" description:"涨粉7日ROI"`
FansT15Roi *float64 `orm:"fans_t15_roi" json:"fansT15Roi" description:"涨粉15日ROI"`
FansT30Roi *float64 `orm:"fans_t30_roi" json:"fansT30Roi" description:"涨粉30日ROI"`
// 新客GMV字段
T0ShopNewBuyerOrderPaymentAmt *float64 `orm:"t0_shop_new_buyer_order_payment_amt" json:"t0ShopNewBuyerOrderPaymentAmt" description:"当日新客GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `orm:"t1_shop_new_buyer_order_payment_amt" json:"t1ShopNewBuyerOrderPaymentAmt" description:"投后1日新客GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `orm:"t3_shop_new_buyer_order_payment_amt" json:"t3ShopNewBuyerOrderPaymentAmt" description:"投后3日新客GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `orm:"t7_shop_new_buyer_order_payment_amt" json:"t7ShopNewBuyerOrderPaymentAmt" description:"投后7日新客GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `orm:"t15_shop_new_buyer_order_payment_amt" json:"t15ShopNewBuyerOrderPaymentAmt" description:"投后15日新客GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `orm:"t30_shop_new_buyer_order_payment_amt" json:"t30ShopNewBuyerOrderPaymentAmt" description:"投后30日新客GMV"`
// 新客订单量字段
T0ShopNewBuyerOrderCnt *int64 `orm:"t0_shop_new_buyer_order_cnt" json:"t0ShopNewBuyerOrderCnt" description:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `orm:"t1_shop_new_buyer_order_cnt" json:"t1ShopNewBuyerOrderCnt" description:"投后1日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `orm:"t3_shop_new_buyer_order_cnt" json:"t3ShopNewBuyerOrderCnt" description:"投后3日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `orm:"t7_shop_new_buyer_order_cnt" json:"t7ShopNewBuyerOrderCnt" description:"投后7日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `orm:"t15_shop_new_buyer_order_cnt" json:"t15ShopNewBuyerOrderCnt" description:"投后15日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `orm:"t30_shop_new_buyer_order_cnt" json:"t30ShopNewBuyerOrderCnt" description:"投后30日新客成交订单量"`
// 新客复购率字段
T1NewBuyerRepurchaseRatio *float64 `orm:"t1_new_buyer_repurchase_ratio" json:"t1NewBuyerRepurchaseRatio" description:"投后1日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `orm:"t3_new_buyer_repurchase_ratio" json:"t3NewBuyerRepurchaseRatio" description:"投后3日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `orm:"t7_new_buyer_repurchase_ratio" json:"t7NewBuyerRepurchaseRatio" description:"投后7日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `orm:"t15_new_buyer_repurchase_ratio" json:"t15NewBuyerRepurchaseRatio" description:"投后15日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `orm:"t30_new_buyer_repurchase_ratio" json:"t30NewBuyerRepurchaseRatio" description:"投后30日新客复购率"`
// 新客ROI字段
T0ShopNewBuyerRoi *float64 `orm:"t0_shop_new_buyer_roi" json:"t0ShopNewBuyerRoi" description:"投后当日新客ROI"`
T1ShopNewBuyerRoi *float64 `orm:"t1_shop_new_buyer_roi" json:"t1ShopNewBuyerRoi" description:"投后1日新客ROI"`
T3ShopNewBuyerRoi *float64 `orm:"t3_shop_new_buyer_roi" json:"t3ShopNewBuyerRoi" description:"投后3日新客ROI"`
T7ShopNewBuyerRoi *float64 `orm:"t7_shop_new_buyer_roi" json:"t7ShopNewBuyerRoi" description:"投后7日新客ROI"`
T15ShopNewBuyerRoi *float64 `orm:"t15_shop_new_buyer_roi" json:"t15ShopNewBuyerRoi" description:"投后15日新客ROI"`
T30ShopNewBuyerRoi *float64 `orm:"t30_shop_new_buyer_roi" json:"t30ShopNewBuyerRoi" description:"投后30日新客ROI"`
// 电话卡相关字段
CreateCardOrderCnt *int64 `orm:"create_card_order_cnt" json:"createCardOrderCnt" description:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `orm:"forward_ts_create_card_order_cnt" json:"forwardTsCreateCardOrderCnt" description:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `orm:"create_card_order_cost" json:"createCardOrderCost" description:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `orm:"forward_ts_create_card_order_cost" json:"forwardTsCreateCardOrderCost" description:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `orm:"activate_card_order_cnt" json:"activateCardOrderCnt" description:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `orm:"forward_ts_activate_card_order_cnt" json:"forwardTsActivateCardOrderCnt" description:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `orm:"activate_card_order_cost" json:"activateCardOrderCost" description:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `orm:"forward_ts_activate_card_order_cost" json:"forwardTsActivateCardOrderCost" description:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `orm:"create_card_order_ratio" json:"createCardOrderRatio" description:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `orm:"forward_ts_create_card_order_ratio" json:"forwardTsCreateCardOrderRatio" description:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `orm:"activate_card_order_cnt_ratio" json:"activateCardOrderCntRatio" description:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `orm:"forward_ts_activate_card_order_ratio" json:"forwardTsActivateCardOrderRatio" description:"电话卡激活率(计费时间)"`
// 全站数据字段
LivePlayCnt *int64 `orm:"live_play_cnt" json:"livePlayCnt" description:"全站直播观看数"`
ItemEntranceClkCnt *int64 `orm:"item_entrance_clk_cnt" json:"itemEntranceClkCnt" description:"小黄车点击数"`
ShowCnt *int64 `orm:"show_cnt" json:"showCnt" description:"全站曝光"`
// 时间字段
ReportDateStr string `orm:"report_date_str" json:"reportDateStr" description:"时间"`
// 广告结构字段
CampaignId *int64 `orm:"campaign_id" json:"campaignId" description:"计划ID"`
CampaignName string `orm:"campaign_name" json:"campaignName" description:"计划名称"`
UnitId *int64 `orm:"unit_id" json:"unitId" description:"单元ID"`
UnitName string `orm:"unit_name" json:"unitName" description:"单元名称"`
CreativeId *int64 `orm:"creative_id" json:"creativeId" description:"创意ID"`
CreativeName string `orm:"creative_name" json:"creativeName" description:"创意名称"`
// 补贴相关字段
CidActualRoiAfterSubsidy *float64 `orm:"cid_actual_roi_after_subsidy" json:"cidActualRoiAfterSubsidy" description:"补贴后实际ROI"`
CidCouponAmount *int64 `orm:"cid_coupon_amount" json:"cidCouponAmount" description:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `orm:"cid_coupon_callback_paid_refund_amount" json:"cidCouponCallbackPaidRefundAmount" description:"退单有回传_核销券金额"`
CidVoucherCost *float64 `orm:"cid_voucher_cost" json:"cidVoucherCost" description:"券成本"`
}
// CidAccountReportSumCol 广告数据报表详情表字段定义
type CidAccountReportSumCol struct {
beans.SQLBaseCol
DataType string
T0OrderPaymentAmt string
CreativeMaterialType string
LiveName string
AuthorId string
PicUrl string
PicName string
PicId string
CoverUrl string
CoverId string
ItemOrderConversionRatio string
ItemCardClickRatio string
ItemCardClkCnt string
LivePlayCntCost string
AdMerchantFollowCost string
AdMerchantFollow string
NetT0OrderCnt string
NetT0Roi string
NetT0Gmv string
PhotoName string
PhotoIdStr string
PhotoId string
ModPriceSegment string
AgeSegment string
Province string
Gender string
AdPhotoPlayedFiveRatio string
AdPhotoPlayedThreeRatio string
OrderSubmitRoi string
OrderSubmitAmt string
EventOrderSubmitCost string
EventOrderSubmit string
EventOrderPaidRoi string
EventAppInvoked string
EventAddShoppingCart string
ConversionNumCost string
AdEffectivePlayNum string
AdItemClick string
MerchantProductId string
CostTotal string
AdShow string
AdShow1kCost string
Impression string
PhotoClick string
PhotoClickRatio string
Click string
ActionbarClick string
ActionbarClickCost string
EspClickRatio string
ActionRatio string
AdItemClickCount string
EspLivePlayedSeconds string
PlayedThreeSeconds string
Play3sRatio string
PlayedFiveSeconds string
Play5sRatio string
PlayedEnd string
PlayEndRatio string
Share string
Comment string
Likes string
Report string
Block string
ItemNegative string
LiveShare string
LiveComment string
LiveReward string
EffectivePlayCount string
EffectivePlayRatio string
ConversionNum string
ConversionCostEsp string
Roi string
Gmv string
T0Gmv string
T1Gmv string
T3Gmv string
T7Gmv string
T15Gmv string
T30Gmv string
T0Roi string
T1Roi string
T3Roi string
T7Roi string
T15Roi string
T30Roi string
PaiedOrder string
OrderRatio string
T0OrderCnt string
T0OrderCntCost string
T0OrderCntRatio string
T1OrderCnt string
T3OrderCnt string
T7OrderCnt string
T15OrderCnt string
T30OrderCnt string
MerchantRecoFans string
T1Retention string
T7Retention string
T15Retention string
T30Retention string
T1RetentionRatio string
T7RetentionRatio string
T15RetentionRatio string
T30RetentionRatio string
ReservationSuccess string
ReservationCost string
StandardLivePlayedStarted string
AdLivePlayCnt string
AdLivePlayCntCost string
LiveAudienceCost string
LiveEventGoodsView string
GoodsClickRatio string
DirectAttrPlatNewBuyerCnt string
T30AttrPlatTotalBuyerCnt string
DirectAttrSellerNewBuyerCnt string
T30AttrSellerTotalBuyerCnt string
T7IndirectOrderAmt string
T7IndirectOrderCnt string
FansT0GmvPerFans string
FansT3GmvPerFans string
FansT7GmvPerFans string
FansT15GmvPerFans string
FansT30GmvPerFans string
RecoFansCost string
QcpxWhiteboxDirectOrderPaymentAmt string
QcpxWhiteboxDirectOrderCnt string
FansT0Gmv string
FansT1Gmv string
FansT7Gmv string
FansT15Gmv string
FansT30Gmv string
FansT0Roi string
FansT1Roi string
FansT7Roi string
FansT15Roi string
FansT30Roi string
T0ShopNewBuyerOrderPaymentAmt string
T1ShopNewBuyerOrderPaymentAmt string
T3ShopNewBuyerOrderPaymentAmt string
T7ShopNewBuyerOrderPaymentAmt string
T15ShopNewBuyerOrderPaymentAmt string
T30ShopNewBuyerOrderPaymentAmt string
T0ShopNewBuyerOrderCnt string
T1ShopNewBuyerOrderCnt string
T3ShopNewBuyerOrderCnt string
T7ShopNewBuyerOrderCnt string
T15ShopNewBuyerOrderCnt string
T30ShopNewBuyerOrderCnt string
T1NewBuyerRepurchaseRatio string
T3NewBuyerRepurchaseRatio string
T7NewBuyerRepurchaseRatio string
T15NewBuyerRepurchaseRatio string
T30NewBuyerRepurchaseRatio string
T0ShopNewBuyerRoi string
T1ShopNewBuyerRoi string
T3ShopNewBuyerRoi string
T7ShopNewBuyerRoi string
T15ShopNewBuyerRoi string
T30ShopNewBuyerRoi string
CreateCardOrderCnt string
ForwardTsCreateCardOrderCnt string
CreateCardOrderCost string
ForwardTsCreateCardOrderCost string
ActivateCardOrderCnt string
// model/entity/cid_account_report_detail.go (续)
ForwardTsActivateCardOrderCnt string
ActivateCardOrderCost string
ForwardTsActivateCardOrderCost string
CreateCardOrderRatio string
ForwardTsCreateCardOrderRatio string
ActivateCardOrderCntRatio string
ForwardTsActivateCardOrderRatio string
LivePlayCnt string
ItemEntranceClkCnt string
ShowCnt string
ReportDateStr string
CampaignId string
CampaignName string
UnitId string
UnitName string
CreativeId string
CreativeName string
CidActualRoiAfterSubsidy string
CidCouponAmount string
CidCouponCallbackPaidRefundAmount string
CidVoucherCost string
}
// TableName 返回表名
func (e *CidAccountReportSum) TableName() string {
return "cid_account_report_detail"
}
// GetCols 获取所有字段名
func (e *CidAccountReportSum) GetCols() *CidAccountReportSumCol {
return &CidAccountReportSumCol{
SQLBaseCol: beans.SQLBaseCol{
Id: "id",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
DataType: "data_type",
T0OrderPaymentAmt: "t0_order_payment_amt",
CreativeMaterialType: "creative_material_type",
LiveName: "live_name",
AuthorId: "author_id",
PicUrl: "pic_url",
PicName: "pic_name",
PicId: "pic_id",
CoverUrl: "cover_url",
CoverId: "cover_id",
ItemOrderConversionRatio: "item_order_conversion_ratio",
ItemCardClickRatio: "item_card_click_ratio",
ItemCardClkCnt: "item_card_clk_cnt",
LivePlayCntCost: "live_play_cnt_cost",
AdMerchantFollowCost: "ad_merchant_follow_cost",
AdMerchantFollow: "ad_merchant_follow",
NetT0OrderCnt: "net_t0_order_cnt",
NetT0Roi: "net_t0_roi",
NetT0Gmv: "net_t0_gmv",
PhotoName: "photo_name",
PhotoIdStr: "photo_id_str",
PhotoId: "photo_id",
ModPriceSegment: "mod_price_segment",
AgeSegment: "age_segment",
Province: "province",
Gender: "gender",
AdPhotoPlayedFiveRatio: "ad_photo_played_five_ratio",
AdPhotoPlayedThreeRatio: "ad_photo_played_three_ratio",
OrderSubmitRoi: "order_submit_roi",
OrderSubmitAmt: "order_submit_amt",
EventOrderSubmitCost: "event_order_submit_cost",
EventOrderSubmit: "event_order_submit",
EventOrderPaidRoi: "event_order_paid_roi",
EventAppInvoked: "event_app_invoked",
EventAddShoppingCart: "event_add_shopping_cart",
ConversionNumCost: "conversion_num_cost",
AdEffectivePlayNum: "ad_effective_play_num",
AdItemClick: "ad_item_click",
MerchantProductId: "merchant_product_id",
CostTotal: "cost_total",
AdShow: "ad_show",
AdShow1kCost: "ad_show1k_cost",
Impression: "impression",
PhotoClick: "photo_click",
PhotoClickRatio: "photo_click_ratio",
Click: "click",
ActionbarClick: "actionbar_click",
ActionbarClickCost: "actionbar_click_cost",
EspClickRatio: "esp_click_ratio",
ActionRatio: "action_ratio",
AdItemClickCount: "ad_item_click_count",
EspLivePlayedSeconds: "esp_live_played_seconds",
PlayedThreeSeconds: "played_three_seconds",
Play3sRatio: "play3s_ratio",
PlayedFiveSeconds: "played_five_seconds",
Play5sRatio: "play5s_ratio",
PlayedEnd: "played_end",
PlayEndRatio: "play_end_ratio",
Share: "share",
Comment: "comment",
Likes: "likes",
Report: "report",
Block: "block",
ItemNegative: "item_negative",
LiveShare: "live_share",
LiveComment: "live_comment",
LiveReward: "live_reward",
EffectivePlayCount: "effective_play_count",
EffectivePlayRatio: "effective_play_ratio",
ConversionNum: "conversion_num",
ConversionCostEsp: "conversion_cost_esp",
Roi: "roi",
Gmv: "gmv",
T0Gmv: "t0_gmv",
T1Gmv: "t1_gmv",
T3Gmv: "t3_gmv",
T7Gmv: "t7_gmv",
T15Gmv: "t15_gmv",
T30Gmv: "t30_gmv",
T0Roi: "t0_roi",
T1Roi: "t1_roi",
T3Roi: "t3_roi",
T7Roi: "t7_roi",
T15Roi: "t15_roi",
T30Roi: "t30_roi",
PaiedOrder: "paied_order",
OrderRatio: "order_ratio",
T0OrderCnt: "t0_order_cnt",
T0OrderCntCost: "t0_order_cnt_cost",
T0OrderCntRatio: "t0_order_cnt_ratio",
T1OrderCnt: "t1_order_cnt",
T3OrderCnt: "t3_order_cnt",
T7OrderCnt: "t7_order_cnt",
T15OrderCnt: "t15_order_cnt",
T30OrderCnt: "t30_order_cnt",
MerchantRecoFans: "merchant_reco_fans",
T1Retention: "t1_retention",
T7Retention: "t7_retention",
T15Retention: "t15_retention",
T30Retention: "t30_retention",
T1RetentionRatio: "t1_retention_ratio",
T7RetentionRatio: "t7_retention_ratio",
T15RetentionRatio: "t15_retention_ratio",
T30RetentionRatio: "t30_retention_ratio",
ReservationSuccess: "reservation_success",
ReservationCost: "reservation_cost",
StandardLivePlayedStarted: "standard_live_played_started",
AdLivePlayCnt: "ad_live_play_cnt",
AdLivePlayCntCost: "ad_live_play_cnt_cost",
LiveAudienceCost: "live_audience_cost",
LiveEventGoodsView: "live_event_goods_view",
GoodsClickRatio: "goods_click_ratio",
DirectAttrPlatNewBuyerCnt: "direct_attr_plat_new_buyer_cnt",
T30AttrPlatTotalBuyerCnt: "t30_attr_plat_total_buyer_cnt",
DirectAttrSellerNewBuyerCnt: "direct_attr_seller_new_buyer_cnt",
T30AttrSellerTotalBuyerCnt: "t30_attr_seller_total_buyer_cnt",
T7IndirectOrderAmt: "t7_indirect_order_amt",
T7IndirectOrderCnt: "t7_indirect_order_cnt",
FansT0GmvPerFans: "fans_t0_gmv_per_fans",
FansT3GmvPerFans: "fans_t3_gmv_per_fans",
FansT7GmvPerFans: "fans_t7_gmv_per_fans",
FansT15GmvPerFans: "fans_t15_gmv_per_fans",
FansT30GmvPerFans: "fans_t30_gmv_per_fans",
RecoFansCost: "reco_fans_cost",
QcpxWhiteboxDirectOrderPaymentAmt: "qcpx_whitebox_direct_order_payment_amt",
QcpxWhiteboxDirectOrderCnt: "qcpx_whitebox_direct_order_cnt",
FansT0Gmv: "fans_t0_gmv",
FansT1Gmv: "fans_t1_gmv",
FansT7Gmv: "fans_t7_gmv",
FansT15Gmv: "fans_t15_gmv",
FansT30Gmv: "fans_t30_gmv",
FansT0Roi: "fans_t0_roi",
FansT1Roi: "fans_t1_roi",
FansT7Roi: "fans_t7_roi",
FansT15Roi: "fans_t15_roi",
FansT30Roi: "fans_t30_roi",
T0ShopNewBuyerOrderPaymentAmt: "t0_shop_new_buyer_order_payment_amt",
T1ShopNewBuyerOrderPaymentAmt: "t1_shop_new_buyer_order_payment_amt",
T3ShopNewBuyerOrderPaymentAmt: "t3_shop_new_buyer_order_payment_amt",
T7ShopNewBuyerOrderPaymentAmt: "t7_shop_new_buyer_order_payment_amt",
T15ShopNewBuyerOrderPaymentAmt: "t15_shop_new_buyer_order_payment_amt",
T30ShopNewBuyerOrderPaymentAmt: "t30_shop_new_buyer_order_payment_amt",
T0ShopNewBuyerOrderCnt: "t0_shop_new_buyer_order_cnt",
T1ShopNewBuyerOrderCnt: "t1_shop_new_buyer_order_cnt",
T3ShopNewBuyerOrderCnt: "t3_shop_new_buyer_order_cnt",
T7ShopNewBuyerOrderCnt: "t7_shop_new_buyer_order_cnt",
T15ShopNewBuyerOrderCnt: "t15_shop_new_buyer_order_cnt",
T30ShopNewBuyerOrderCnt: "t30_shop_new_buyer_order_cnt",
T1NewBuyerRepurchaseRatio: "t1_new_buyer_repurchase_ratio",
T3NewBuyerRepurchaseRatio: "t3_new_buyer_repurchase_ratio",
T7NewBuyerRepurchaseRatio: "t7_new_buyer_repurchase_ratio",
T15NewBuyerRepurchaseRatio: "t15_new_buyer_repurchase_ratio",
T30NewBuyerRepurchaseRatio: "t30_new_buyer_repurchase_ratio",
T0ShopNewBuyerRoi: "t0_shop_new_buyer_roi",
T1ShopNewBuyerRoi: "t1_shop_new_buyer_roi",
T3ShopNewBuyerRoi: "t3_shop_new_buyer_roi",
T7ShopNewBuyerRoi: "t7_shop_new_buyer_roi",
T15ShopNewBuyerRoi: "t15_shop_new_buyer_roi",
T30ShopNewBuyerRoi: "t30_shop_new_buyer_roi",
CreateCardOrderCnt: "create_card_order_cnt",
ForwardTsCreateCardOrderCnt: "forward_ts_create_card_order_cnt",
CreateCardOrderCost: "create_card_order_cost",
ForwardTsCreateCardOrderCost: "forward_ts_create_card_order_cost",
ActivateCardOrderCnt: "activate_card_order_cnt",
ForwardTsActivateCardOrderCnt: "forward_ts_activate_card_order_cnt",
ActivateCardOrderCost: "activate_card_order_cost",
ForwardTsActivateCardOrderCost: "forward_ts_activate_card_order_cost",
CreateCardOrderRatio: "create_card_order_ratio",
ForwardTsCreateCardOrderRatio: "forward_ts_create_card_order_ratio",
ActivateCardOrderCntRatio: "activate_card_order_cnt_ratio",
ForwardTsActivateCardOrderRatio: "forward_ts_activate_card_order_ratio",
LivePlayCnt: "live_play_cnt",
ItemEntranceClkCnt: "item_entrance_clk_cnt",
ShowCnt: "show_cnt",
ReportDateStr: "report_date_str",
CampaignId: "campaign_id",
CampaignName: "campaign_name",
UnitId: "unit_id",
UnitName: "unit_name",
CreativeId: "creative_id",
CreativeName: "creative_name",
CidActualRoiAfterSubsidy: "cid_actual_roi_after_subsidy",
CidCouponAmount: "cid_coupon_amount",
CidCouponCallbackPaidRefundAmount: "cid_coupon_callback_paid_refund_amount",
CidVoucherCost: "cid_voucher_cost",
}
}
@@ -1,582 +0,0 @@
package copydata
import "gitea.com/red-future/common/beans"
// CreativeReportDetail 广告效果指标明细表实体
type CreativeReportDetail struct {
beans.SQLBaseDO `orm:",inherit"`
T0OrderPaymentAmt *float64 `orm:"t0_order_payment_amt" json:"t0OrderPaymentAmt" description:"当日总成交订单金额"`
CreativeMaterialType string `orm:"creative_material_type" json:"creativeMaterialType" description:"视频素材类型"`
LiveName string `orm:"live_name" json:"liveName" description:"直播间名称"`
AuthorId string `orm:"author_id" json:"authorId" description:"直播用户快手 Id"`
PicUrl string `orm:"pic_url" json:"picUrl" description:"图片 URL"`
PicName string `orm:"pic_name" json:"picName" description:"图片名称"`
PicId string `orm:"pic_id" json:"picId" description:"图片 Id"`
CoverUrl string `orm:"cover_url" json:"coverUrl" description:"封面 URL"`
CoverId *int64 `orm:"cover_id" json:"coverId" description:"封面 Id"`
ItemOrderConversionRatio *float64 `orm:"item_order_conversion_ratio" json:"itemOrderConversionRatio" description:"转化率"`
ItemCardClickRatio *float64 `orm:"item_card_click_ratio" json:"itemCardClickRatio" description:"点击率"`
ItemCardClkCnt *int64 `orm:"item_card_clk_cnt" json:"itemCardClkCnt" description:"商品卡点击数"`
LivePlayCntCost *float64 `orm:"live_play_cnt_cost" json:"livePlayCntCost" description:"直播间观看成本"`
AdMerchantFollowCost *float64 `orm:"ad_merchant_follow_cost" json:"adMerchantFollowCost" description:"涨粉成本"`
AdMerchantFollow *int64 `orm:"ad_merchant_follow" json:"adMerchantFollow" description:"涨粉数"`
NetT0OrderCnt *int64 `orm:"net_t0_order_cnt" json:"netT0OrderCnt" description:"当日累计净成交订单数"`
NetT0Roi *float64 `orm:"net_t0_roi" json:"netT0Roi" description:"净成交 ROI"`
NetT0Gmv *float64 `orm:"net_t0_gmv" json:"netT0Gmv" description:"净成交 GMV"`
PhotoName string `orm:"photo_name" json:"photoName" description:"视频名称"`
PhotoIdStr string `orm:"photo_id_str" json:"photoIdStr" description:"视频 id"`
PhotoId string `orm:"photo_id" json:"photoId" description:"视频 id"`
ModPriceSegment string `orm:"mod_price_segment" json:"modPriceSegment" description:"设备价格区间"`
AgeSegment string `orm:"age_segment" json:"ageSegment" description:"年龄段"`
Province string `orm:"province" json:"province" description:"省份名称"`
Gender string `orm:"gender" json:"gender" description:"性别"`
AdPhotoPlayedFiveRatio *float64 `orm:"ad_photo_played_five_ratio" json:"adPhotoPlayedFiveRatio" description:"作品 5 秒播放率"`
AdPhotoPlayedThreeRatio *float64 `orm:"ad_photo_played_three_ratio" json:"adPhotoPlayedThreeRatio" description:"作品 3 秒播放率"`
OrderSubmitRoi *float64 `orm:"order_submit_roi" json:"orderSubmitRoi" description:"订单提交 ROI"`
OrderSubmitAmt *int64 `orm:"order_submit_amt" json:"orderSubmitAmt" description:"外部订单金额"`
EventOrderSubmitCost *float64 `orm:"event_order_submit_cost" json:"eventOrderSubmitCost" description:"订单提交成本"`
EventOrderSubmit *int64 `orm:"event_order_submit" json:"eventOrderSubmit" description:"订单提交数"`
EventOrderPaidRoi *float64 `orm:"event_order_paid_roi" json:"eventOrderPaidRoi" description:"订单支付率"`
EventAppInvoked *int64 `orm:"event_app_invoked" json:"eventAppInvoked" description:"唤起应用数"`
EventAddShoppingCart *int64 `orm:"event_add_shopping_cart" json:"eventAddShoppingCart" description:"添加购物车次数"`
ConversionNumCost *float64 `orm:"conversion_num_cost" json:"conversionNumCost" description:"转化成本(回传时间)"`
AdEffectivePlayNum *int64 `orm:"ad_effective_play_num" json:"adEffectivePlayNum" description:"有效播放数"`
AdItemClick *int64 `orm:"ad_item_click" json:"adItemClick" description:"行为数"`
MerchantProductId string `orm:"merchant_product_id" json:"merchantProductId" description:"商品 ID"`
CostTotal *float64 `orm:"cost_total" json:"costTotal" description:"花费"`
AdShow *int64 `orm:"ad_show" json:"adShow" description:"曝光数"`
AdShow1kCost *float64 `orm:"ad_show1k_cost" json:"adShow1kCost" description:"平均千次广告曝光花费"`
Impression *int64 `orm:"impression" json:"impression" description:"封面曝光数"`
PhotoClick *int64 `orm:"photo_click" json:"photoClick" description:"封面点击数"`
PhotoClickRatio *float64 `orm:"photo_click_ratio" json:"photoClickRatio" description:"封面点击率"`
Click *int64 `orm:"click" json:"click" description:"素材曝光数"`
ActionbarClick *int64 `orm:"actionbar_click" json:"actionbarClick" description:"行为数"`
ActionbarClickCost *float64 `orm:"actionbar_click_cost" json:"actionbarClickCost" description:"行为成本"`
EspClickRatio *float64 `orm:"esp_click_ratio" json:"espClickRatio" description:"行为率"`
ActionRatio *float64 `orm:"action_ratio" json:"actionRatio" description:"素材点击率"`
AdItemClickCount *int64 `orm:"ad_item_click_count" json:"adItemClickCount" description:"预约组件点击数"`
EspLivePlayedSeconds *int64 `orm:"esp_live_played_seconds" json:"espLivePlayedSeconds" description:"直播平均观看时长"`
PlayedThreeSeconds *int64 `orm:"played_three_seconds" json:"playedThreeSeconds" description:"作品 3 秒播放数"`
Play3sRatio *float64 `orm:"play3s_ratio" json:"play3sRatio" description:"作品 3 秒播放率"`
PlayedFiveSeconds *int64 `orm:"played_five_seconds" json:"playedFiveSeconds" description:"作品 5 秒播放数"`
Play5sRatio *float64 `orm:"play5s_ratio" json:"play5sRatio" description:"作品 5 秒播放率"`
PlayedEnd *int64 `orm:"played_end" json:"playedEnd" description:"作品完播数"`
PlayEndRatio *float64 `orm:"play_end_ratio" json:"playEndRatio" description:"作品完播率"`
Share *int64 `orm:"share" json:"share" description:"作品分享数"`
Comment *int64 `orm:"comment" json:"comment" description:"作品评论数"`
Likes *int64 `orm:"likes" json:"likes" description:"作品点赞数"`
Report *int64 `orm:"report" json:"report" description:"作品举报数"`
Block *int64 `orm:"block" json:"block" description:"作品拉黑数"`
ItemNegative *int64 `orm:"item_negative" json:"itemNegative" description:"详情页减少此类作品数"`
LiveShare *int64 `orm:"live_share" json:"liveShare" description:"直播送礼数"`
LiveComment *int64 `orm:"live_comment" json:"liveComment" description:"直播评论数"`
LiveReward *int64 `orm:"live_reward" json:"liveReward" description:"直播送礼数"`
EffectivePlayCount *int64 `orm:"effective_play_count" json:"effectivePlayCount" description:"有效播放数"`
EffectivePlayRatio *float64 `orm:"effective_play_ratio" json:"effectivePlayRatio" description:"有效播放率"`
ConversionNum *int64 `orm:"conversion_num" json:"conversionNum" description:"转化数"`
ConversionCostEsp *float64 `orm:"conversion_cost_esp" json:"conversionCostEsp" description:"转化成本"`
Roi *float64 `orm:"roi" json:"roi" description:"直接 ROI"`
Gmv *float64 `orm:"gmv" json:"gmv" description:"直接 GMV"`
T0Gmv *float64 `orm:"t0_gmv" json:"t0Gmv" description:"当日累计 GMV"`
T1Gmv *float64 `orm:"t1_gmv" json:"t1Gmv" description:"次日累计 GMV"`
T7Gmv *float64 `orm:"t7_gmv" json:"t7Gmv" description:"7 日累计 GMV"`
T15Gmv *float64 `orm:"t15_gmv" json:"t15Gmv" description:"15 日累计 GMV"`
T30Gmv *float64 `orm:"t30_gmv" json:"t30Gmv" description:"30 日累计 GMV"`
T0Roi *float64 `orm:"t0_roi" json:"t0Roi" description:"当日累计 ROI"`
T1Roi *float64 `orm:"t1_roi" json:"t1Roi" description:"次日累计 ROI"`
T7Roi *float64 `orm:"t7_roi" json:"t7Roi" description:"7 日累计 ROI"`
T15Roi *float64 `orm:"t15_roi" json:"t15Roi" description:"15 日累计 ROI"`
T30Roi *float64 `orm:"t30_roi" json:"t30Roi" description:"30 日累计 ROI"`
PaiedOrder *int64 `orm:"paied_order" json:"paiedOrder" description:"直接订单数"`
OrderRatio *float64 `orm:"order_ratio" json:"orderRatio" description:"直接下单率"`
T0OrderCnt *int64 `orm:"t0_order_cnt" json:"t0OrderCnt" description:"当日累计订单数"`
T0OrderCntCost *float64 `orm:"t0_order_cnt_cost" json:"t0OrderCntCost" description:"当日累计订单成本"`
T0OrderCntRatio *float64 `orm:"t0_order_cnt_ratio" json:"t0OrderCntRatio" description:"累计订单下单率"`
T1OrderCnt *int64 `orm:"t1_order_cnt" json:"t1OrderCnt" description:"次日累计订单数"`
T7OrderCnt *int64 `orm:"t7_order_cnt" json:"t7OrderCnt" description:"7 日累计订单数"`
T15OrderCnt *int64 `orm:"t15_order_cnt" json:"t15OrderCnt" description:"15 日累计订单数"`
T30OrderCnt *int64 `orm:"t30_order_cnt" json:"t30OrderCnt" description:"30 日累计订单数"`
MerchantRecoFans *int64 `orm:"merchant_reco_fans" json:"merchantRecoFans" description:"涨粉数"`
T1Retention *float64 `orm:"t1_retention" json:"t1Retention" description:"次日涨粉留存数"`
T7Retention *float64 `orm:"t7_retention" json:"t7Retention" description:"7 日涨粉留存数"`
T15Retention *float64 `orm:"t15_retention" json:"t15Retention" description:"15 日涨粉留存数"`
T30Retention *float64 `orm:"t30_retention" json:"t30Retention" description:"30 日涨粉留存数"`
T1RetentionRatio *float64 `orm:"t1_retention_ratio" json:"t1RetentionRatio" description:"次日涨粉留存率"`
T7RetentionRatio *float64 `orm:"t7_retention_ratio" json:"t7RetentionRatio" description:"7 日涨粉留存率"`
T15RetentionRatio *float64 `orm:"t15_retention_ratio" json:"t15RetentionRatio" description:"15 日涨粉留存率"`
T30RetentionRatio *float64 `orm:"t30_retention_ratio" json:"t30RetentionRatio" description:"30 日涨粉留存率"`
ReservationSuccess *int64 `orm:"reservation_success" json:"reservationSuccess" description:"直播预约成功数"`
ReservationCost *float64 `orm:"reservation_cost" json:"reservationCost" description:"直播预约成功成本"`
StandardLivePlayedStarted *int64 `orm:"standard_live_played_started" json:"standardLivePlayedStarted" description:"直播观看数"`
AdLivePlayCnt *int64 `orm:"ad_live_play_cnt" json:"adLivePlayCnt" description:"直播间人气数"`
AdLivePlayCntCost *float64 `orm:"ad_live_play_cnt_cost" json:"adLivePlayCntCost" description:"直播间人气成本"`
LiveAudienceCost *float64 `orm:"live_audience_cost" json:"liveAudienceCost" description:"直播观看成本"`
LiveEventGoodsView *int64 `orm:"live_event_goods_view" json:"liveEventGoodsView" description:"直播间商品点击数"`
GoodsClickRatio *float64 `orm:"goods_click_ratio" json:"goodsClickRatio" description:"直播间商品点击率"`
DirectAttrPlatNewBuyerCnt *int64 `orm:"direct_attr_plat_new_buyer_cnt" json:"directAttrPlatNewBuyerCnt" description:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `orm:"t30_attr_plat_total_buyer_cnt" json:"t30AttrPlatTotalBuyerCnt" description:"30 日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `orm:"direct_attr_seller_new_buyer_cnt" json:"directAttrSellerNewBuyerCnt" description:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `orm:"t30_attr_seller_total_buyer_cnt" json:"t30AttrSellerTotalBuyerCnt" description:"30 日累计店铺新客"`
T3Gmv *float64 `orm:"t3_gmv" json:"t3Gmv" description:"3 日累计 GMV"`
T3OrderCnt *int64 `orm:"t3_order_cnt" json:"t3OrderCnt" description:"3 日累计订单数"`
T3Roi *float64 `orm:"t3_roi" json:"t3Roi" description:"3 日累计 ROI"`
T7IndirectOrderAmt *float64 `orm:"t7_indirect_order_amt" json:"t7IndirectOrderAmt" description:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `orm:"t7_indirect_order_cnt" json:"t7IndirectOrderCnt" description:"7 日间接订单数"`
FansT0GmvPerFans *float64 `orm:"fans_t0_gmv_per_fans" json:"fansT0GmvPerFans" description:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `orm:"fans_t3_gmv_per_fans" json:"fansT3GmvPerFans" description:"3 日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `orm:"fans_t7_gmv_per_fans" json:"fansT7GmvPerFans" description:"7 日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `orm:"fans_t15_gmv_per_fans" json:"fansT15GmvPerFans" description:"15 日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `orm:"fans_t30_gmv_per_fans" json:"fansT30GmvPerFans" description:"30 日新增粉丝人均销售额"`
RecoFansCost *float64 `orm:"reco_fans_cost" json:"recoFansCost" description:"涨粉成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `orm:"qcpx_whitebox_direct_order_payment_amt" json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `orm:"qcpx_whitebox_direct_order_cnt" json:"qcpxWhiteboxDirectOrderCnt" description:"智能优惠券订单数"`
FansT0Gmv *float64 `orm:"fans_t0_gmv" json:"fansT0Gmv" description:"涨粉当日 GMV"`
FansT1Gmv *float64 `orm:"fans_t1_gmv" json:"fansT1Gmv" description:"涨粉次日 GMV"`
FansT7Gmv *float64 `orm:"fans_t7_gmv" json:"fansT7Gmv" description:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `orm:"fans_t15_gmv" json:"fansT15Gmv" description:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `orm:"fans_t30_gmv" json:"fansT30Gmv" description:"涨粉 30 日 GMV"`
FansT0Roi *float64 `orm:"fans_t0_roi" json:"fansT0Roi" description:"涨粉当日 ROI"`
FansT1Roi *float64 `orm:"fans_t1_roi" json:"fansT1Roi" description:"涨粉次日 ROI"`
FansT7Roi *float64 `orm:"fans_t7_roi" json:"fansT7Roi" description:"涨粉 7 日 ROI"`
FansT15Roi *float64 `orm:"fans_t15_roi" json:"fansT15Roi" description:"涨粉 15 日 ROI"`
FansT30Roi *float64 `orm:"fans_t30_roi" json:"fansT30Roi" description:"涨粉 30 日 ROI"`
T0ShopNewBuyerOrderPaymentAmt *float64 `orm:"t0_shop_new_buyer_order_payment_amt" json:"t0ShopNewBuyerOrderPaymentAmt" description:"当日新客 GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `orm:"t1_shop_new_buyer_order_payment_amt" json:"t1ShopNewBuyerOrderPaymentAmt" description:"投后 1 日新客 GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `orm:"t3_shop_new_buyer_order_payment_amt" json:"t3ShopNewBuyerOrderPaymentAmt" description:"投后 3 日新客 GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `orm:"t7_shop_new_buyer_order_payment_amt" json:"t7ShopNewBuyerOrderPaymentAmt" description:"投后 7 日新客 GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `orm:"t15_shop_new_buyer_order_payment_amt" json:"t15ShopNewBuyerOrderPaymentAmt" description:"投后 15 日新客 GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `orm:"t30_shop_new_buyer_order_payment_amt" json:"t30ShopNewBuyerOrderPaymentAmt" description:"投后 30 日新客 GMV"`
T0ShopNewBuyerOrderCnt *int64 `orm:"t0_shop_new_buyer_order_cnt" json:"t0ShopNewBuyerOrderCnt" description:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `orm:"t1_shop_new_buyer_order_cnt" json:"t1ShopNewBuyerOrderCnt" description:"投后 1 日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `orm:"t3_shop_new_buyer_order_cnt" json:"t3ShopNewBuyerOrderCnt" description:"投后 3 日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `orm:"t7_shop_new_buyer_order_cnt" json:"t7ShopNewBuyerOrderCnt" description:"投后 7 日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `orm:"t15_shop_new_buyer_order_cnt" json:"t15ShopNewBuyerOrderCnt" description:"投后 15 日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `orm:"t30_shop_new_buyer_order_cnt" json:"t30ShopNewBuyerOrderCnt" description:"投后 30 日新客成交订单量"`
T1NewBuyerRepurchaseRatio *float64 `orm:"t1_new_buyer_repurchase_ratio" json:"t1NewBuyerRepurchaseRatio" description:"投后 1 日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `orm:"t3_new_buyer_repurchase_ratio" json:"t3NewBuyerRepurchaseRatio" description:"投后 3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `orm:"t7_new_buyer_repurchase_ratio" json:"t7NewBuyerRepurchaseRatio" description:"投后 7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `orm:"t15_new_buyer_repurchase_ratio" json:"t15NewBuyerRepurchaseRatio" description:"投后 15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `orm:"t30_new_buyer_repurchase_ratio" json:"t30NewBuyerRepurchaseRatio" description:"投后 30 日新客复购率"`
T0ShopNewBuyerRoi *float64 `orm:"t0_shop_new_buyer_roi" json:"t0ShopNewBuyerRoi" description:"投后当日新客 ROI"`
T1ShopNewBuyerRoi *float64 `orm:"t1_shop_new_buyer_roi" json:"t1ShopNewBuyerRoi" description:"投后 1 日新客 ROI"`
T3ShopNewBuyerRoi *float64 `orm:"t3_shop_new_buyer_roi" json:"t3ShopNewBuyerRoi" description:"投后 3 日新客 ROI"`
T7ShopNewBuyerRoi *float64 `orm:"t7_shop_new_buyer_roi" json:"t7ShopNewBuyerRoi" description:"投后 7 日新客 ROI"`
T15ShopNewBuyerRoi *float64 `orm:"t15_shop_new_buyer_roi" json:"t15ShopNewBuyerRoi" description:"投后 15 日新客 ROI"`
T30ShopNewBuyerRoi *float64 `orm:"t30_shop_new_buyer_roi" json:"t30ShopNewBuyerRoi" description:"投后 30 日新客 ROI"`
CreateCardOrderCnt *int64 `orm:"create_card_order_cnt" json:"createCardOrderCnt" description:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `orm:"forward_ts_create_card_order_cnt" json:"forwardTsCreateCardOrderCnt" description:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `orm:"create_card_order_cost" json:"createCardOrderCost" description:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `orm:"forward_ts_create_card_order_cost" json:"forwardTsCreateCardOrderCost" description:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `orm:"activate_card_order_cnt" json:"activateCardOrderCnt" description:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `orm:"forward_ts_activate_card_order_cnt" json:"forwardTsActivateCardOrderCnt" description:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `orm:"activate_card_order_cost" json:"activateCardOrderCost" description:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `orm:"forward_ts_activate_card_order_cost" json:"forwardTsActivateCardOrderCost" description:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `orm:"create_card_order_ratio" json:"createCardOrderRatio" description:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `orm:"forward_ts_create_card_order_ratio" json:"forwardTsCreateCardOrderRatio" description:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `orm:"activate_card_order_cnt_ratio" json:"activateCardOrderCntRatio" description:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `orm:"forward_ts_activate_card_order_ratio" json:"forwardTsActivateCardOrderRatio" description:"电话卡激活率(计费时间)"`
LivePlayCnt *int64 `orm:"live_play_cnt" json:"livePlayCnt" description:"全站直播观看数"`
ItemEntranceClkCnt *int64 `orm:"item_entrance_clk_cnt" json:"itemEntranceClkCnt" description:"小黄车点击数"`
ShowCnt *int64 `orm:"show_cnt" json:"showCnt" description:"全站曝光"`
ReportDateStr string `orm:"report_date_str" json:"reportDateStr" description:"时间"`
CampaignId *int64 `orm:"campaign_id" json:"campaignId" description:"计划 ID"`
CampaignName string `orm:"campaign_name" json:"campaignName" description:"计划名称"`
UnitId *int64 `orm:"unit_id" json:"unitId" description:"单元 ID"`
UnitName string `orm:"unit_name" json:"unitName" description:"单元名称"`
CreativeId *int64 `orm:"creative_id" json:"creativeId" description:"创意 ID"`
CreativeName string `orm:"creative_name" json:"creativeName" description:"创意名称"`
CidActualRoiAfterSubsidy *float64 `orm:"cid_actual_roi_after_subsidy" json:"cidActualRoiAfterSubsidy" description:"补贴后实际 ROI"`
CidCouponAmount *int64 `orm:"cid_coupon_amount" json:"cidCouponAmount" description:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `orm:"cid_coupon_callback_paid_refund_amount" json:"cidCouponCallbackPaidRefundAmount" description:"退单有回传_核销券金额"`
CidVoucherCost *float64 `orm:"cid_voucher_cost" json:"cidVoucherCost" description:"券成本"`
}
// CreativeReportDetailCol 广告效果指标明细表字段定义
type CreativeReportDetailCol struct {
beans.SQLBaseCol
T0OrderPaymentAmt string
CreativeMaterialType string
LiveName string
AuthorId string
PicUrl string
PicName string
PicId string
CoverUrl string
CoverId string
ItemOrderConversionRatio string
ItemCardClickRatio string
ItemCardClkCnt string
LivePlayCntCost string
AdMerchantFollowCost string
AdMerchantFollow string
NetT0OrderCnt string
NetT0Roi string
NetT0Gmv string
PhotoName string
PhotoIdStr string
PhotoId string
ModPriceSegment string
AgeSegment string
Province string
Gender string
AdPhotoPlayedFiveRatio string
AdPhotoPlayedThreeRatio string
OrderSubmitRoi string
OrderSubmitAmt string
EventOrderSubmitCost string
EventOrderSubmit string
EventOrderPaidRoi string
EventAppInvoked string
EventAddShoppingCart string
ConversionNumCost string
AdEffectivePlayNum string
AdItemClick string
MerchantProductId string
CostTotal string
AdShow string
AdShow1kCost string
Impression string
PhotoClick string
PhotoClickRatio string
Click string
ActionbarClick string
ActionbarClickCost string
EspClickRatio string
ActionRatio string
AdItemClickCount string
EspLivePlayedSeconds string
PlayedThreeSeconds string
Play3sRatio string
PlayedFiveSeconds string
Play5sRatio string
PlayedEnd string
PlayEndRatio string
Share string
Comment string
Likes string
Report string
Block string
ItemNegative string
LiveShare string
LiveComment string
LiveReward string
EffectivePlayCount string
EffectivePlayRatio string
ConversionNum string
ConversionCostEsp string
Roi string
Gmv string
T0Gmv string
T1Gmv string
T7Gmv string
T15Gmv string
T30Gmv string
T0Roi string
T1Roi string
T7Roi string
T15Roi string
T30Roi string
PaiedOrder string
OrderRatio string
T0OrderCnt string
T0OrderCntCost string
T0OrderCntRatio string
T1OrderCnt string
T7OrderCnt string
T15OrderCnt string
T30OrderCnt string
MerchantRecoFans string
T1Retention string
T7Retention string
T15Retention string
T30Retention string
T1RetentionRatio string
T7RetentionRatio string
T15RetentionRatio string
T30RetentionRatio string
ReservationSuccess string
ReservationCost string
StandardLivePlayedStarted string
AdLivePlayCnt string
AdLivePlayCntCost string
LiveAudienceCost string
LiveEventGoodsView string
GoodsClickRatio string
DirectAttrPlatNewBuyerCnt string
T30AttrPlatTotalBuyerCnt string
DirectAttrSellerNewBuyerCnt string
T30AttrSellerTotalBuyerCnt string
T3Gmv string
T3OrderCnt string
T3Roi string
T7IndirectOrderAmt string
T7IndirectOrderCnt string
FansT0GmvPerFans string
FansT3GmvPerFans string
FansT7GmvPerFans string
FansT15GmvPerFans string
FansT30GmvPerFans string
RecoFansCost string
QcpxWhiteboxDirectOrderPaymentAmt string
QcpxWhiteboxDirectOrderCnt string
FansT0Gmv string
FansT1Gmv string
FansT7Gmv string
FansT15Gmv string
FansT30Gmv string
FansT0Roi string
FansT1Roi string
FansT7Roi string
FansT15Roi string
FansT30Roi string
T0ShopNewBuyerOrderPaymentAmt string
T1ShopNewBuyerOrderPaymentAmt string
T3ShopNewBuyerOrderPaymentAmt string
T7ShopNewBuyerOrderPaymentAmt string
T15ShopNewBuyerOrderPaymentAmt string
T30ShopNewBuyerOrderPaymentAmt string
T0ShopNewBuyerOrderCnt string
T1ShopNewBuyerOrderCnt string
T3ShopNewBuyerOrderCnt string
T7ShopNewBuyerOrderCnt string
T15ShopNewBuyerOrderCnt string
T30ShopNewBuyerOrderCnt string
T1NewBuyerRepurchaseRatio string
T3NewBuyerRepurchaseRatio string
T7NewBuyerRepurchaseRatio string
T15NewBuyerRepurchaseRatio string
T30NewBuyerRepurchaseRatio string
T0ShopNewBuyerRoi string
T1ShopNewBuyerRoi string
T3ShopNewBuyerRoi string
T7ShopNewBuyerRoi string
T15ShopNewBuyerRoi string
T30ShopNewBuyerRoi string
CreateCardOrderCnt string
ForwardTsCreateCardOrderCnt string
CreateCardOrderCost string
ForwardTsCreateCardOrderCost string
ActivateCardOrderCnt string
ForwardTsActivateCardOrderCnt string
ActivateCardOrderCost string
ForwardTsActivateCardOrderCost string
CreateCardOrderRatio string
ForwardTsCreateCardOrderRatio string
ActivateCardOrderCntRatio string
ForwardTsActivateCardOrderRatio string
LivePlayCnt string
ItemEntranceClkCnt string
ShowCnt string
ReportDateStr string
CampaignId string
CampaignName string
UnitId string
UnitName string
CreativeId string
CreativeName string
CidActualRoiAfterSubsidy string
CidCouponAmount string
CidCouponCallbackPaidRefundAmount string
CidVoucherCost string
}
// TableName 返回表名
func (e *CreativeReportDetail) TableName() string {
return "creative_report_detail"
}
// GetCols 获取所有字段名
func (e *CreativeReportDetail) GetCols() *CreativeReportDetailCol {
return &CreativeReportDetailCol{
SQLBaseCol: beans.SQLBaseCol{
Id: "id",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
T0OrderPaymentAmt: "t0_order_payment_amt",
CreativeMaterialType: "creative_material_type",
LiveName: "live_name",
AuthorId: "author_id",
PicUrl: "pic_url",
PicName: "pic_name",
PicId: "pic_id",
CoverUrl: "cover_url",
CoverId: "cover_id",
ItemOrderConversionRatio: "item_order_conversion_ratio",
ItemCardClickRatio: "item_card_click_ratio",
ItemCardClkCnt: "item_card_clk_cnt",
LivePlayCntCost: "live_play_cnt_cost",
AdMerchantFollowCost: "ad_merchant_follow_cost",
AdMerchantFollow: "ad_merchant_follow",
NetT0OrderCnt: "net_t0_order_cnt",
NetT0Roi: "net_t0_roi",
NetT0Gmv: "net_t0_gmv",
PhotoName: "photo_name",
PhotoIdStr: "photo_id_str",
PhotoId: "photo_id",
ModPriceSegment: "mod_price_segment",
AgeSegment: "age_segment",
Province: "province",
Gender: "gender",
AdPhotoPlayedFiveRatio: "ad_photo_played_five_ratio",
AdPhotoPlayedThreeRatio: "ad_photo_played_three_ratio",
OrderSubmitRoi: "order_submit_roi",
OrderSubmitAmt: "order_submit_amt",
EventOrderSubmitCost: "event_order_submit_cost",
EventOrderSubmit: "event_order_submit",
EventOrderPaidRoi: "event_order_paid_roi",
EventAppInvoked: "event_app_invoked",
EventAddShoppingCart: "event_add_shopping_cart",
ConversionNumCost: "conversion_num_cost",
AdEffectivePlayNum: "ad_effective_play_num",
AdItemClick: "ad_item_click",
MerchantProductId: "merchant_product_id",
CostTotal: "cost_total",
AdShow: "ad_show",
AdShow1kCost: "ad_show1k_cost",
Impression: "impression",
PhotoClick: "photo_click",
PhotoClickRatio: "photo_click_ratio",
Click: "click",
ActionbarClick: "actionbar_click",
ActionbarClickCost: "actionbar_click_cost",
EspClickRatio: "esp_click_ratio",
ActionRatio: "action_ratio",
AdItemClickCount: "ad_item_click_count",
EspLivePlayedSeconds: "esp_live_played_seconds",
PlayedThreeSeconds: "played_three_seconds",
Play3sRatio: "play3s_ratio",
PlayedFiveSeconds: "played_five_seconds",
Play5sRatio: "play5s_ratio",
PlayedEnd: "played_end",
PlayEndRatio: "play_end_ratio",
Share: "share",
Comment: "comment",
Likes: "likes",
Report: "report",
Block: "block",
ItemNegative: "item_negative",
LiveShare: "live_share",
LiveComment: "live_comment",
LiveReward: "live_reward",
EffectivePlayCount: "effective_play_count",
EffectivePlayRatio: "effective_play_ratio",
ConversionNum: "conversion_num",
ConversionCostEsp: "conversion_cost_esp",
Roi: "roi",
Gmv: "gmv",
T0Gmv: "t0_gmv",
T1Gmv: "t1_gmv",
T7Gmv: "t7_gmv",
T15Gmv: "t15_gmv",
T30Gmv: "t30_gmv",
T0Roi: "t0_roi",
T1Roi: "t1_roi",
T7Roi: "t7_roi",
T15Roi: "t15_roi",
T30Roi: "t30_roi",
PaiedOrder: "paied_order",
OrderRatio: "order_ratio",
T0OrderCnt: "t0_order_cnt",
T0OrderCntCost: "t0_order_cnt_cost",
T0OrderCntRatio: "t0_order_cnt_ratio",
T1OrderCnt: "t1_order_cnt",
T7OrderCnt: "t7_order_cnt",
T15OrderCnt: "t15_order_cnt",
T30OrderCnt: "t30_order_cnt",
MerchantRecoFans: "merchant_reco_fans",
T1Retention: "t1_retention",
T7Retention: "t7_retention",
T15Retention: "t15_retention",
T30Retention: "t30_retention",
T1RetentionRatio: "t1_retention_ratio",
T7RetentionRatio: "t7_retention_ratio",
T15RetentionRatio: "t15_retention_ratio",
T30RetentionRatio: "t30_retention_ratio",
ReservationSuccess: "reservation_success",
ReservationCost: "reservation_cost",
StandardLivePlayedStarted: "standard_live_played_started",
AdLivePlayCnt: "ad_live_play_cnt",
AdLivePlayCntCost: "ad_live_play_cnt_cost",
LiveAudienceCost: "live_audience_cost",
LiveEventGoodsView: "live_event_goods_view",
GoodsClickRatio: "goods_click_ratio",
DirectAttrPlatNewBuyerCnt: "direct_attr_plat_new_buyer_cnt",
T30AttrPlatTotalBuyerCnt: "t30_attr_plat_total_buyer_cnt",
DirectAttrSellerNewBuyerCnt: "direct_attr_seller_new_buyer_cnt",
T30AttrSellerTotalBuyerCnt: "t30_attr_seller_total_buyer_cnt",
T3Gmv: "t3_gmv",
T3OrderCnt: "t3_order_cnt",
T3Roi: "t3_roi",
T7IndirectOrderAmt: "t7_indirect_order_amt",
T7IndirectOrderCnt: "t7_indirect_order_cnt",
FansT0GmvPerFans: "fans_t0_gmv_per_fans",
FansT3GmvPerFans: "fans_t3_gmv_per_fans",
FansT7GmvPerFans: "fans_t7_gmv_per_fans",
FansT15GmvPerFans: "fans_t15_gmv_per_fans",
FansT30GmvPerFans: "fans_t30_gmv_per_fans",
RecoFansCost: "reco_fans_cost",
QcpxWhiteboxDirectOrderPaymentAmt: "qcpx_whitebox_direct_order_payment_amt",
QcpxWhiteboxDirectOrderCnt: "qcpx_whitebox_direct_order_cnt",
FansT0Gmv: "fans_t0_gmv",
FansT1Gmv: "fans_t1_gmv",
FansT7Gmv: "fans_t7_gmv",
FansT15Gmv: "fans_t15_gmv",
FansT30Gmv: "fans_t30_gmv",
FansT0Roi: "fans_t0_roi",
FansT1Roi: "fans_t1_roi",
FansT7Roi: "fans_t7_roi",
FansT15Roi: "fans_t15_roi",
FansT30Roi: "fans_t30_roi",
T0ShopNewBuyerOrderPaymentAmt: "t0_shop_new_buyer_order_payment_amt",
T1ShopNewBuyerOrderPaymentAmt: "t1_shop_new_buyer_order_payment_amt",
T3ShopNewBuyerOrderPaymentAmt: "t3_shop_new_buyer_order_payment_amt",
T7ShopNewBuyerOrderPaymentAmt: "t7_shop_new_buyer_order_payment_amt",
T15ShopNewBuyerOrderPaymentAmt: "t15_shop_new_buyer_order_payment_amt",
T30ShopNewBuyerOrderPaymentAmt: "t30_shop_new_buyer_order_payment_amt",
T0ShopNewBuyerOrderCnt: "t0_shop_new_buyer_order_cnt",
T1ShopNewBuyerOrderCnt: "t1_shop_new_buyer_order_cnt",
T3ShopNewBuyerOrderCnt: "t3_shop_new_buyer_order_cnt",
T7ShopNewBuyerOrderCnt: "t7_shop_new_buyer_order_cnt",
T15ShopNewBuyerOrderCnt: "t15_shop_new_buyer_order_cnt",
T30ShopNewBuyerOrderCnt: "t30_shop_new_buyer_order_cnt",
T1NewBuyerRepurchaseRatio: "t1_new_buyer_repurchase_ratio",
T3NewBuyerRepurchaseRatio: "t3_new_buyer_repurchase_ratio",
T7NewBuyerRepurchaseRatio: "t7_new_buyer_repurchase_ratio",
T15NewBuyerRepurchaseRatio: "t15_new_buyer_repurchase_ratio",
T30NewBuyerRepurchaseRatio: "t30_new_buyer_repurchase_ratio",
T0ShopNewBuyerRoi: "t0_shop_new_buyer_roi",
T1ShopNewBuyerRoi: "t1_shop_new_buyer_roi",
T3ShopNewBuyerRoi: "t3_shop_new_buyer_roi",
T7ShopNewBuyerRoi: "t7_shop_new_buyer_roi",
T15ShopNewBuyerRoi: "t15_shop_new_buyer_roi",
T30ShopNewBuyerRoi: "t30_shop_new_buyer_roi",
CreateCardOrderCnt: "create_card_order_cnt",
ForwardTsCreateCardOrderCnt: "forward_ts_create_card_order_cnt",
CreateCardOrderCost: "create_card_order_cost",
ForwardTsCreateCardOrderCost: "forward_ts_create_card_order_cost",
ActivateCardOrderCnt: "activate_card_order_cnt",
ForwardTsActivateCardOrderCnt: "forward_ts_activate_card_order_cnt",
ActivateCardOrderCost: "activate_card_order_cost",
ForwardTsActivateCardOrderCost: "forward_ts_activate_card_order_cost",
CreateCardOrderRatio: "create_card_order_ratio",
ForwardTsCreateCardOrderRatio: "forward_ts_create_card_order_ratio",
ActivateCardOrderCntRatio: "activate_card_order_cnt_ratio",
ForwardTsActivateCardOrderRatio: "forward_ts_activate_card_order_ratio",
LivePlayCnt: "live_play_cnt",
ItemEntranceClkCnt: "item_entrance_clk_cnt",
ShowCnt: "show_cnt",
ReportDateStr: "report_date_str",
CampaignId: "campaign_id",
CampaignName: "campaign_name",
UnitId: "unit_id",
UnitName: "unit_name",
CreativeId: "creative_id",
CreativeName: "creative_name",
CidActualRoiAfterSubsidy: "cid_actual_roi_after_subsidy",
CidCouponAmount: "cid_coupon_amount",
CidCouponCallbackPaidRefundAmount: "cid_coupon_callback_paid_refund_amount",
CidVoucherCost: "cid_voucher_cost",
}
}
@@ -1,687 +0,0 @@
package copydata
import "gitea.com/red-future/common/beans"
// CreativeReportSum 广告效果指标表实体
type CreativeReportSum struct {
beans.SQLBaseDO `orm:",inherit"`
// 订单金额相关字段
T0OrderPaymentAmt *float64 `orm:"t0_order_payment_amt" json:"t0OrderPaymentAmt" description:"当日总成交订单金额"`
// 素材类型字段
CreativeMaterialType string `orm:"creative_material_type" json:"creativeMaterialType" description:"视频素材类型(视频:HORIZONTAL_SCREEN, VERTICAL_SCREEN, UNKNOWN_TYPE;图集:ATLAS;长图:ATLAS_VERTICAL"`
// 直播相关字段
LiveName string `orm:"live_name" json:"liveName" description:"直播间名称"`
AuthorId string `orm:"author_id" json:"authorId" description:"直播用户快手 Id"`
// 图片相关字段
PicUrl string `orm:"pic_url" json:"picUrl" description:"图片 URL"`
PicName string `orm:"pic_name" json:"picName" description:"图片名称"`
PicId string `orm:"pic_id" json:"picId" description:"图片 Id"`
// 封面相关字段
CoverUrl string `orm:"cover_url" json:"coverUrl" description:"封面 URL"`
CoverId *int64 `orm:"cover_id" json:"coverId" description:"封面 Id"`
// 转化率相关字段
ItemOrderConversionRatio *float64 `orm:"item_order_conversion_ratio" json:"itemOrderConversionRatio" description:"转化率"`
ItemCardClickRatio *float64 `orm:"item_card_click_ratio" json:"itemCardClickRatio" description:"点击率"`
ItemCardClkCnt *int64 `orm:"item_card_clk_cnt" json:"itemCardClkCnt" description:"商品卡点击数"`
// 成本相关字段
LivePlayCntCost *float64 `orm:"live_play_cnt_cost" json:"livePlayCntCost" description:"直播间观看成本"`
AdMerchantFollowCost *float64 `orm:"ad_merchant_follow_cost" json:"adMerchantFollowCost" description:"涨粉成本"`
AdMerchantFollow *int64 `orm:"ad_merchant_follow" json:"adMerchantFollow" description:"涨粉数"`
// 净成交相关字段
NetT0OrderCnt *int64 `orm:"net_t0_order_cnt" json:"netT0OrderCnt" description:"当日累计净成交订单数"`
NetT0Roi *float64 `orm:"net_t0_roi" json:"netT0Roi" description:"净成交 ROI"`
NetT0Gmv *float64 `orm:"net_t0_gmv" json:"netT0Gmv" description:"净成交 GMV"`
// 视频相关字段
PhotoName string `orm:"photo_name" json:"photoName" description:"视频名称"`
PhotoIdStr string `orm:"photo_id_str" json:"photoIdStr" description:"视频 id"`
PhotoId string `orm:"photo_id" json:"photoId" description:"视频 id"`
// 用户属性相关字段
ModPriceSegment string `orm:"mod_price_segment" json:"modPriceSegment" description:"设备价格区间"`
AgeSegment string `orm:"age_segment" json:"ageSegment" description:"年龄段"`
Province string `orm:"province" json:"province" description:"省份名称"`
Gender string `orm:"gender" json:"gender" description:"性别"`
// 播放率相关字段
AdPhotoPlayedFiveRatio *float64 `orm:"ad_photo_played_five_ratio" json:"adPhotoPlayedFiveRatio" description:"作品 5 秒播放率"`
AdPhotoPlayedThreeRatio *float64 `orm:"ad_photo_played_three_ratio" json:"adPhotoPlayedThreeRatio" description:"作品 3 秒播放率"`
// 订单提交相关字段
OrderSubmitRoi *float64 `orm:"order_submit_roi" json:"orderSubmitRoi" description:"订单提交 ROI"`
OrderSubmitAmt *int64 `orm:"order_submit_amt" json:"orderSubmitAmt" description:"外部订单金额"`
EventOrderSubmitCost *float64 `orm:"event_order_submit_cost" json:"eventOrderSubmitCost" description:"订单提交成本"`
EventOrderSubmit *int64 `orm:"event_order_submit" json:"eventOrderSubmit" description:"订单提交数"`
EventOrderPaidRoi *float64 `orm:"event_order_paid_roi" json:"eventOrderPaidRoi" description:"订单支付率"`
// 用户行为相关字段
EventAppInvoked *int64 `orm:"event_app_invoked" json:"eventAppInvoked" description:"唤起应用数"`
EventAddShoppingCart *int64 `orm:"event_add_shopping_cart" json:"eventAddShoppingCart" description:"添加购物车次数"`
// 转化成本相关字段
ConversionNumCost *float64 `orm:"conversion_num_cost" json:"conversionNumCost" description:"转化成本(回传时间)"`
AdEffectivePlayNum *int64 `orm:"ad_effective_play_num" json:"adEffectivePlayNum" description:"有效播放数"`
AdItemClick *int64 `orm:"ad_item_click" json:"adItemClick" description:"行为数"`
// 商品相关字段
MerchantProductId string `orm:"merchant_product_id" json:"merchantProductId" description:"商品 ID"`
// 花费相关字段
CostTotal *float64 `orm:"cost_total" json:"costTotal" description:"花费"`
// 曝光相关字段
AdShow *int64 `orm:"ad_show" json:"adShow" description:"曝光数"`
AdShow1kCost *float64 `orm:"ad_show1k_cost" json:"adShow1kCost" description:"平均千次广告曝光花费"`
Impression *int64 `orm:"impression" json:"impression" description:"封面曝光数"`
PhotoClick *int64 `orm:"photo_click" json:"photoClick" description:"封面点击数"`
PhotoClickRatio *float64 `orm:"photo_click_ratio" json:"photoClickRatio" description:"封面点击率"`
Click *int64 `orm:"click" json:"click" description:"素材曝光数"`
// 行为相关字段
ActionbarClick *int64 `orm:"actionbar_click" json:"actionbarClick" description:"行为数"`
ActionbarClickCost *float64 `orm:"actionbar_click_cost" json:"actionbarClickCost" description:"行为成本"`
EspClickRatio *float64 `orm:"esp_click_ratio" json:"espClickRatio" description:"行为率"`
ActionRatio *float64 `orm:"action_ratio" json:"actionRatio" description:"素材点击率"`
AdItemClickCount *int64 `orm:"ad_item_click_count" json:"adItemClickCount" description:"预约组件点击数"`
// 直播时长相关字段
EspLivePlayedSeconds *int64 `orm:"esp_live_played_seconds" json:"espLivePlayedSeconds" description:"直播平均观看时长"`
// 作品播放相关字段
PlayedThreeSeconds *int64 `orm:"played_three_seconds" json:"playedThreeSeconds" description:"作品 3 秒播放数"`
Play3sRatio *float64 `orm:"play3s_ratio" json:"play3sRatio" description:"作品 3 秒播放率"`
PlayedFiveSeconds *int64 `orm:"played_five_seconds" json:"playedFiveSeconds" description:"作品 5 秒播放数"`
Play5sRatio *float64 `orm:"play5s_ratio" json:"play5sRatio" description:"作品 5 秒播放率"`
PlayedEnd *int64 `orm:"played_end" json:"playedEnd" description:"作品完播数"`
PlayEndRatio *float64 `orm:"play_end_ratio" json:"playEndRatio" description:"作品完播率"`
// 作品互动相关字段
Share *int64 `orm:"share" json:"share" description:"作品分享数"`
Comment *int64 `orm:"comment" json:"comment" description:"作品评论数"`
Likes *int64 `orm:"likes" json:"likes" description:"作品点赞数"`
Report *int64 `orm:"report" json:"report" description:"作品举报数"`
Block *int64 `orm:"block" json:"block" description:"作品拉黑数"`
ItemNegative *int64 `orm:"item_negative" json:"itemNegative" description:"详情页减少此类作品数"`
// 直播互动相关字段
LiveShare *int64 `orm:"live_share" json:"liveShare" description:"直播送礼数"`
LiveComment *int64 `orm:"live_comment" json:"liveComment" description:"直播评论数"`
LiveReward *int64 `orm:"live_reward" json:"liveReward" description:"直播送礼数"`
// 有效播放相关字段
EffectivePlayCount *int64 `orm:"effective_play_count" json:"effectivePlayCount" description:"有效播放数"`
EffectivePlayRatio *float64 `orm:"effective_play_ratio" json:"effectivePlayRatio" description:"有效播放率"`
// 转化相关字段
ConversionNum *int64 `orm:"conversion_num" json:"conversionNum" description:"转化数"`
ConversionCostEsp *float64 `orm:"conversion_cost_esp" json:"conversionCostEsp" description:"转化成本"`
Roi *float64 `orm:"roi" json:"roi" description:"直接 ROI"`
Gmv *float64 `orm:"gmv" json:"gmv" description:"直接 GMV"`
// 累计 GMV 相关字段
T0Gmv *float64 `orm:"t0_gmv" json:"t0Gmv" description:"当日累计 GMV"`
T1Gmv *float64 `orm:"t1_gmv" json:"t1Gmv" description:"次日累计 GMV"`
T7Gmv *float64 `orm:"t7_gmv" json:"t7Gmv" description:"7 日累计 GMV"`
T15Gmv *float64 `orm:"t15_gmv" json:"t15Gmv" description:"15 日累计 GMV"`
T30Gmv *float64 `orm:"t30_gmv" json:"t30Gmv" description:"30 日累计 GMV"`
// 累计 ROI 相关字段
T0Roi *float64 `orm:"t0_roi" json:"t0Roi" description:"当日累计 ROI"`
T1Roi *float64 `orm:"t1_roi" json:"t1Roi" description:"次日累计 ROI"`
T7Roi *float64 `orm:"t7_roi" json:"t7Roi" description:"7 日累计 ROI"`
T15Roi *float64 `orm:"t15_roi" json:"t15Roi" description:"15 日累计 ROI"`
T30Roi *float64 `orm:"t30_roi" json:"t30Roi" description:"30 日累计 ROI"`
// 订单相关字段
PaiedOrder *int64 `orm:"paied_order" json:"paiedOrder" description:"直接订单数"`
OrderRatio *float64 `orm:"order_ratio" json:"orderRatio" description:"直接下单率"`
T0OrderCnt *int64 `orm:"t0_order_cnt" json:"t0OrderCnt" description:"当日累计订单数"`
T0OrderCntCost *float64 `orm:"t0_order_cnt_cost" json:"t0OrderCntCost" description:"当日累计订单成本"`
T0OrderCntRatio *float64 `orm:"t0_order_cnt_ratio" json:"t0OrderCntRatio" description:"累计订单下单率"`
T1OrderCnt *int64 `orm:"t1_order_cnt" json:"t1OrderCnt" description:"次日累计订单数"`
T7OrderCnt *int64 `orm:"t7_order_cnt" json:"t7OrderCnt" description:"7 日累计订单数"`
T15OrderCnt *int64 `orm:"t15_order_cnt" json:"t15OrderCnt" description:"15 日累计订单数"`
T30OrderCnt *int64 `orm:"t30_order_cnt" json:"t30OrderCnt" description:"30 日累计订单数"`
// 涨粉相关字段
MerchantRecoFans *int64 `orm:"merchant_reco_fans" json:"merchantRecoFans" description:"涨粉数"`
T1Retention *float64 `orm:"t1_retention" json:"t1Retention" description:"次日涨粉留存数"`
T7Retention *float64 `orm:"t7_retention" json:"t7Retention" description:"7 日涨粉留存数"`
T15Retention *float64 `orm:"t15_retention" json:"t15Retention" description:"15 日涨粉留存数"`
T30Retention *float64 `orm:"t30_retention" json:"t30Retention" description:"30 日涨粉留存数"`
T1RetentionRatio *float64 `orm:"t1_retention_ratio" json:"t1RetentionRatio" description:"次日涨粉留存率"`
T7RetentionRatio *float64 `orm:"t7_retention_ratio" json:"t7RetentionRatio" description:"7 日涨粉留存率"`
T15RetentionRatio *float64 `orm:"t15_retention_ratio" json:"t15RetentionRatio" description:"15 日涨粉留存率"`
T30RetentionRatio *float64 `orm:"t30_retention_ratio" json:"t30RetentionRatio" description:"30 日涨粉留存率"`
// 直播预约相关字段
ReservationSuccess *int64 `orm:"reservation_success" json:"reservationSuccess" description:"直播预约成功数"`
ReservationCost *float64 `orm:"reservation_cost" json:"reservationCost" description:"直播预约成功成本"`
// 直播观看相关字段
StandardLivePlayedStarted *int64 `orm:"standard_live_played_started" json:"standardLivePlayedStarted" description:"直播观看数"`
AdLivePlayCnt *int64 `orm:"ad_live_play_cnt" json:"adLivePlayCnt" description:"直播间人气数"`
AdLivePlayCntCost *float64 `orm:"ad_live_play_cnt_cost" json:"adLivePlayCntCost" description:"直播间人气成本"`
LiveAudienceCost *float64 `orm:"live_audience_cost" json:"liveAudienceCost" description:"直播观看成本"`
// 直播间商品相关字段
LiveEventGoodsView *int64 `orm:"live_event_goods_view" json:"liveEventGoodsView" description:"直播间商品点击数"`
GoodsClickRatio *float64 `orm:"goods_click_ratio" json:"goodsClickRatio" description:"直播间商品点击率"`
// 新客相关字段
DirectAttrPlatNewBuyerCnt *int64 `orm:"direct_attr_plat_new_buyer_cnt" json:"directAttrPlatNewBuyerCnt" description:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `orm:"t30_attr_plat_total_buyer_cnt" json:"t30AttrPlatTotalBuyerCnt" description:"30 日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `orm:"direct_attr_seller_new_buyer_cnt" json:"directAttrSellerNewBuyerCnt" description:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `orm:"t30_attr_seller_total_buyer_cnt" json:"t30AttrSellerTotalBuyerCnt" description:"30 日累计店铺新客"`
// 3 日累计相关字段
T3Gmv *float64 `orm:"t3_gmv" json:"t3Gmv" description:"3 日累计 GMV"`
T3OrderCnt *int64 `orm:"t3_order_cnt" json:"t3OrderCnt" description:"3 日累计订单数"`
T3Roi *float64 `orm:"t3_roi" json:"t3Roi" description:"3 日累计 ROI"`
// 7 日间接订单相关字段
T7IndirectOrderAmt *float64 `orm:"t7_indirect_order_amt" json:"t7IndirectOrderAmt" description:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `orm:"t7_indirect_order_cnt" json:"t7IndirectOrderCnt" description:"7 日间接订单数"`
// 粉丝人均销售额相关字段
FansT0GmvPerFans *float64 `orm:"fans_t0_gmv_per_fans" json:"fansT0GmvPerFans" description:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `orm:"fans_t3_gmv_per_fans" json:"fansT3GmvPerFans" description:"3 日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `orm:"fans_t7_gmv_per_fans" json:"fansT7GmvPerFans" description:"7 日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `orm:"fans_t15_gmv_per_fans" json:"fansT15GmvPerFans" description:"15 日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `orm:"fans_t30_gmv_per_fans" json:"fansT30GmvPerFans" description:"30 日新增粉丝人均销售额"`
// 涨粉成本相关字段
RecoFansCost *float64 `orm:"reco_fans_cost" json:"recoFansCost" description:"涨粉成本"`
// 智能优惠券相关字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `orm:"qcpx_whitebox_direct_order_payment_amt" json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `orm:"qcpx_whitebox_direct_order_cnt" json:"qcpxWhiteboxDirectOrderCnt" description:"智能优惠券订单数"`
// 粉丝 GMV 相关字段
FansT0Gmv *float64 `orm:"fans_t0_gmv" json:"fansT0Gmv" description:"涨粉当日 GMV"`
FansT1Gmv *float64 `orm:"fans_t1_gmv" json:"fansT1Gmv" description:"涨粉次日 GMV"`
FansT7Gmv *float64 `orm:"fans_t7_gmv" json:"fansT7Gmv" description:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `orm:"fans_t15_gmv" json:"fansT15Gmv" description:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `orm:"fans_t30_gmv" json:"fansT30Gmv" description:"涨粉 30 日 GMV"`
// 粉丝 ROI 相关字段
FansT0Roi *float64 `orm:"fans_t0_roi" json:"fansT0Roi" description:"涨粉当日 ROI"`
FansT1Roi *float64 `orm:"fans_t1_roi" json:"fansT1Roi" description:"涨粉次日 ROI"`
FansT7Roi *float64 `orm:"fans_t7_roi" json:"fansT7Roi" description:"涨粉 7 日 ROI"`
FansT15Roi *float64 `orm:"fans_t15_roi" json:"fansT15Roi" description:"涨粉 15 日 ROI"`
FansT30Roi *float64 `orm:"fans_t30_roi" json:"fansT30Roi" description:"涨粉 30 日 ROI"`
// 新客 GMV 相关字段
T0ShopNewBuyerOrderPaymentAmt *float64 `orm:"t0_shop_new_buyer_order_payment_amt" json:"t0ShopNewBuyerOrderPaymentAmt" description:"当日新客 GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `orm:"t1_shop_new_buyer_order_payment_amt" json:"t1ShopNewBuyerOrderPaymentAmt" description:"投后 1 日新客 GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `orm:"t3_shop_new_buyer_order_payment_amt" json:"t3ShopNewBuyerOrderPaymentAmt" description:"投后 3 日新客 GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `orm:"t7_shop_new_buyer_order_payment_amt" json:"t7ShopNewBuyerOrderPaymentAmt" description:"投后 7 日新客 GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `orm:"t15_shop_new_buyer_order_payment_amt" json:"t15ShopNewBuyerOrderPaymentAmt" description:"投后 15 日新客 GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `orm:"t30_shop_new_buyer_order_payment_amt" json:"t30ShopNewBuyerOrderPaymentAmt" description:"投后 30 日新客 GMV"`
// 新客订单数相关字段
T0ShopNewBuyerOrderCnt *int64 `orm:"t0_shop_new_buyer_order_cnt" json:"t0ShopNewBuyerOrderCnt" description:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `orm:"t1_shop_new_buyer_order_cnt" json:"t1ShopNewBuyerOrderCnt" description:"投后 1 日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `orm:"t3_shop_new_buyer_order_cnt" json:"t3ShopNewBuyerOrderCnt" description:"投后 3 日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `orm:"t7_shop_new_buyer_order_cnt" json:"t7ShopNewBuyerOrderCnt" description:"投后 7 日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `orm:"t15_shop_new_buyer_order_cnt" json:"t15ShopNewBuyerOrderCnt" description:"投后 15 日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `orm:"t30_shop_new_buyer_order_cnt" json:"t30ShopNewBuyerOrderCnt" description:"投后 30 日新客成交订单量"`
// 新客复购率相关字段
T1NewBuyerRepurchaseRatio *float64 `orm:"t1_new_buyer_repurchase_ratio" json:"t1NewBuyerRepurchaseRatio" description:"投后 1 日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `orm:"t3_new_buyer_repurchase_ratio" json:"t3NewBuyerRepurchaseRatio" description:"投后 3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `orm:"t7_new_buyer_repurchase_ratio" json:"t7NewBuyerRepurchaseRatio" description:"投后 7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `orm:"t15_new_buyer_repurchase_ratio" json:"t15NewBuyerRepurchaseRatio" description:"投后 15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `orm:"t30_new_buyer_repurchase_ratio" json:"t30NewBuyerRepurchaseRatio" description:"投后 30 日新客复购率"`
// 新客 ROI 相关字段
T0ShopNewBuyerRoi *float64 `orm:"t0_shop_new_buyer_roi" json:"t0ShopNewBuyerRoi" description:"投后当日新客 ROI"`
T1ShopNewBuyerRoi *float64 `orm:"t1_shop_new_buyer_roi" json:"t1ShopNewBuyerRoi" description:"投后 1 日新客 ROI"`
T3ShopNewBuyerRoi *float64 `orm:"t3_shop_new_buyer_roi" json:"t3ShopNewBuyerRoi" description:"投后 3 日新客 ROI"`
T7ShopNewBuyerRoi *float64 `orm:"t7_shop_new_buyer_roi" json:"t7ShopNewBuyerRoi" description:"投后 7 日新客 ROI"`
T15ShopNewBuyerRoi *float64 `orm:"t15_shop_new_buyer_roi" json:"t15ShopNewBuyerRoi" description:"投后 15 日新客 ROI"`
T30ShopNewBuyerRoi *float64 `orm:"t30_shop_new_buyer_roi" json:"t30ShopNewBuyerRoi" description:"投后 30 日新客 ROI"`
// 制卡订单相关字段
CreateCardOrderCnt *int64 `orm:"create_card_order_cnt" json:"createCardOrderCnt" description:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `orm:"forward_ts_create_card_order_cnt" json:"forwardTsCreateCardOrderCnt" description:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `orm:"create_card_order_cost" json:"createCardOrderCost" description:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `orm:"forward_ts_create_card_order_cost" json:"forwardTsCreateCardOrderCost" description:"有效制卡订单成本(计费时间)"`
// 电话卡激活相关字段
ActivateCardOrderCnt *int64 `orm:"activate_card_order_cnt" json:"activateCardOrderCnt" description:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `orm:"forward_ts_activate_card_order_cnt" json:"forwardTsActivateCardOrderCnt" description:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `orm:"activate_card_order_cost" json:"activateCardOrderCost" description:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `orm:"forward_ts_activate_card_order_cost" json:"forwardTsActivateCardOrderCost" description:"电话卡激活订单成本(计费时间)"`
// 制卡订单率相关字段
CreateCardOrderRatio *float64 `orm:"create_card_order_ratio" json:"createCardOrderRatio" description:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `orm:"forward_ts_create_card_order_ratio" json:"forwardTsCreateCardOrderRatio" description:"有效制卡订单率(计费时间)"`
// 电话卡激活率相关字段
ActivateCardOrderCntRatio *float64 `orm:"activate_card_order_cnt_ratio" json:"activateCardOrderCntRatio" description:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `orm:"forward_ts_activate_card_order_ratio" json:"forwardTsActivateCardOrderRatio" description:"电话卡激活率(计费时间)"`
// 直播观看相关字段
LivePlayCnt *int64 `orm:"live_play_cnt" json:"livePlayCnt" description:"全站直播观看数"`
ItemEntranceClkCnt *int64 `orm:"item_entrance_clk_cnt" json:"itemEntranceClkCnt" description:"小黄车点击数"`
ShowCnt *int64 `orm:"show_cnt" json:"showCnt" description:"全站曝光"`
// 报告日期字段
ReportDateStr string `orm:"report_date_str" json:"reportDateStr" description:"时间"`
// 广告计划相关字段
CampaignId *int64 `orm:"campaign_id" json:"campaignId" description:"计划 ID"`
CampaignName string `orm:"campaign_name" json:"campaignName" description:"计划名称"`
// 广告单元相关字段
UnitId *int64 `orm:"unit_id" json:"unitId" description:"单元 ID"`
UnitName string `orm:"unit_name" json:"unitName" description:"单元名称"`
// 广告创意相关字段
CreativeId *int64 `orm:"creative_id" json:"creativeId" description:"创意 ID"`
CreativeName string `orm:"creative_name" json:"creativeName" description:"创意名称"`
// 新增字段
CidActualRoiAfterSubsidy *float64 `orm:"cid_actual_roi_after_subsidy" json:"cidActualRoiAfterSubsidy" description:"补贴后实际 ROI"`
CidCouponAmount *int64 `orm:"cid_coupon_amount" json:"cidCouponAmount" description:"核销券金额"`
CidCouponCallbackPaidRefundAmount *int64 `orm:"cid_coupon_callback_paid_refund_amount" json:"cidCouponCallbackPaidRefundAmount" description:"退单有回传_核销券金额"`
CidVoucherCost *float64 `orm:"cid_voucher_cost" json:"cidVoucherCost" description:"券成本"`
}
// CreativeReportSumCol 广告效果指标表字段定义
type CreativeReportSumCol struct {
beans.SQLBaseCol
T0OrderPaymentAmt string
CreativeMaterialType string
LiveName string
AuthorId string
PicUrl string
PicName string
PicId string
CoverUrl string
CoverId string
ItemOrderConversionRatio string
ItemCardClickRatio string
ItemCardClkCnt string
LivePlayCntCost string
AdMerchantFollowCost string
AdMerchantFollow string
NetT0OrderCnt string
NetT0Roi string
NetT0Gmv string
PhotoName string
PhotoIdStr string
PhotoId string
ModPriceSegment string
AgeSegment string
Province string
Gender string
AdPhotoPlayedFiveRatio string
AdPhotoPlayedThreeRatio string
OrderSubmitRoi string
OrderSubmitAmt string
EventOrderSubmitCost string
EventOrderSubmit string
EventOrderPaidRoi string
EventAppInvoked string
EventAddShoppingCart string
ConversionNumCost string
AdEffectivePlayNum string
AdItemClick string
MerchantProductId string
CostTotal string
AdShow string
AdShow1kCost string
Impression string
PhotoClick string
PhotoClickRatio string
Click string
ActionbarClick string
ActionbarClickCost string
EspClickRatio string
ActionRatio string
AdItemClickCount string
EspLivePlayedSeconds string
PlayedThreeSeconds string
Play3sRatio string
PlayedFiveSeconds string
Play5sRatio string
PlayedEnd string
PlayEndRatio string
Share string
Comment string
Likes string
Report string
Block string
ItemNegative string
LiveShare string
LiveComment string
LiveReward string
EffectivePlayCount string
EffectivePlayRatio string
ConversionNum string
ConversionCostEsp string
Roi string
Gmv string
T0Gmv string
T1Gmv string
T7Gmv string
T15Gmv string
T30Gmv string
T0Roi string
T1Roi string
T7Roi string
T15Roi string
T30Roi string
PaiedOrder string
OrderRatio string
T0OrderCnt string
T0OrderCntCost string
T0OrderCntRatio string
T1OrderCnt string
T7OrderCnt string
T15OrderCnt string
T30OrderCnt string
MerchantRecoFans string
T1Retention string
T7Retention string
T15Retention string
T30Retention string
T1RetentionRatio string
T7RetentionRatio string
T15RetentionRatio string
T30RetentionRatio string
ReservationSuccess string
ReservationCost string
StandardLivePlayedStarted string
AdLivePlayCnt string
AdLivePlayCntCost string
LiveAudienceCost string
LiveEventGoodsView string
GoodsClickRatio string
DirectAttrPlatNewBuyerCnt string
T30AttrPlatTotalBuyerCnt string
DirectAttrSellerNewBuyerCnt string
T30AttrSellerTotalBuyerCnt string
T3Gmv string
T3OrderCnt string
T3Roi string
T7IndirectOrderAmt string
T7IndirectOrderCnt string
FansT0GmvPerFans string
FansT3GmvPerFans string
FansT7GmvPerFans string
FansT15GmvPerFans string
FansT30GmvPerFans string
RecoFansCost string
QcpxWhiteboxDirectOrderPaymentAmt string
QcpxWhiteboxDirectOrderCnt string
FansT0Gmv string
FansT1Gmv string
FansT7Gmv string
FansT15Gmv string
FansT30Gmv string
FansT0Roi string
FansT1Roi string
FansT7Roi string
FansT15Roi string
FansT30Roi string
T0ShopNewBuyerOrderPaymentAmt string
T1ShopNewBuyerOrderPaymentAmt string
T3ShopNewBuyerOrderPaymentAmt string
T7ShopNewBuyerOrderPaymentAmt string
T15ShopNewBuyerOrderPaymentAmt string
T30ShopNewBuyerOrderPaymentAmt string
T0ShopNewBuyerOrderCnt string
T1ShopNewBuyerOrderCnt string
T3ShopNewBuyerOrderCnt string
T7ShopNewBuyerOrderCnt string
T15ShopNewBuyerOrderCnt string
T30ShopNewBuyerOrderCnt string
T1NewBuyerRepurchaseRatio string
T3NewBuyerRepurchaseRatio string
T7NewBuyerRepurchaseRatio string
T15NewBuyerRepurchaseRatio string
T30NewBuyerRepurchaseRatio string
T0ShopNewBuyerRoi string
T1ShopNewBuyerRoi string
T3ShopNewBuyerRoi string
T7ShopNewBuyerRoi string
T15ShopNewBuyerRoi string
T30ShopNewBuyerRoi string
CreateCardOrderCnt string
ForwardTsCreateCardOrderCnt string
CreateCardOrderCost string
ForwardTsCreateCardOrderCost string
ActivateCardOrderCnt string
ForwardTsActivateCardOrderCnt string
ActivateCardOrderCost string
ForwardTsActivateCardOrderCost string
CreateCardOrderRatio string
ForwardTsCreateCardOrderRatio string
ActivateCardOrderCntRatio string
ForwardTsActivateCardOrderRatio string
LivePlayCnt string
ItemEntranceClkCnt string
ShowCnt string
ReportDateStr string
CampaignId string
CampaignName string
UnitId string
UnitName string
CreativeId string
CreativeName string
CidActualRoiAfterSubsidy string
CidCouponAmount string
CidCouponCallbackPaidRefundAmount string
CidVoucherCost string
}
// TableName 返回表名
func (e *CreativeReportSum) TableName() string {
return "creative_report_sum"
}
// GetCols 获取所有字段名
func (e *CreativeReportSum) GetCols() *CreativeReportSumCol {
return &CreativeReportSumCol{
SQLBaseCol: beans.SQLBaseCol{
Id: "id",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
T0OrderPaymentAmt: "t0_order_payment_amt",
CreativeMaterialType: "creative_material_type",
LiveName: "live_name",
AuthorId: "author_id",
PicUrl: "pic_url",
PicName: "pic_name",
PicId: "pic_id",
CoverUrl: "cover_url",
CoverId: "cover_id",
ItemOrderConversionRatio: "item_order_conversion_ratio",
ItemCardClickRatio: "item_card_click_ratio",
ItemCardClkCnt: "item_card_clk_cnt",
LivePlayCntCost: "live_play_cnt_cost",
AdMerchantFollowCost: "ad_merchant_follow_cost",
AdMerchantFollow: "ad_merchant_follow",
NetT0OrderCnt: "net_t0_order_cnt",
NetT0Roi: "net_t0_roi",
NetT0Gmv: "net_t0_gmv",
PhotoName: "photo_name",
PhotoIdStr: "photo_id_str",
PhotoId: "photo_id",
ModPriceSegment: "mod_price_segment",
AgeSegment: "age_segment",
Province: "province",
Gender: "gender",
AdPhotoPlayedFiveRatio: "ad_photo_played_five_ratio",
AdPhotoPlayedThreeRatio: "ad_photo_played_three_ratio",
OrderSubmitRoi: "order_submit_roi",
OrderSubmitAmt: "order_submit_amt",
EventOrderSubmitCost: "event_order_submit_cost",
EventOrderSubmit: "event_order_submit",
EventOrderPaidRoi: "event_order_paid_roi",
EventAppInvoked: "event_app_invoked",
EventAddShoppingCart: "event_add_shopping_cart",
ConversionNumCost: "conversion_num_cost",
AdEffectivePlayNum: "ad_effective_play_num",
AdItemClick: "ad_item_click",
MerchantProductId: "merchant_product_id",
CostTotal: "cost_total",
AdShow: "ad_show",
AdShow1kCost: "ad_show1k_cost",
Impression: "impression",
PhotoClick: "photo_click",
PhotoClickRatio: "photo_click_ratio",
Click: "click",
ActionbarClick: "actionbar_click",
ActionbarClickCost: "actionbar_click_cost",
EspClickRatio: "esp_click_ratio",
ActionRatio: "action_ratio",
AdItemClickCount: "ad_item_click_count",
EspLivePlayedSeconds: "esp_live_played_seconds",
PlayedThreeSeconds: "played_three_seconds",
Play3sRatio: "play3s_ratio",
PlayedFiveSeconds: "played_five_seconds",
Play5sRatio: "play5s_ratio",
PlayedEnd: "played_end",
PlayEndRatio: "play_end_ratio",
Share: "share",
Comment: "comment",
Likes: "likes",
Report: "report",
Block: "block",
ItemNegative: "item_negative",
LiveShare: "live_share",
LiveComment: "live_comment",
LiveReward: "live_reward",
EffectivePlayCount: "effective_play_count",
EffectivePlayRatio: "effective_play_ratio",
ConversionNum: "conversion_num",
ConversionCostEsp: "conversion_cost_esp",
Roi: "roi",
Gmv: "gmv",
T0Gmv: "t0_gmv",
T1Gmv: "t1_gmv",
T7Gmv: "t7_gmv",
T15Gmv: "t15_gmv",
T30Gmv: "t30_gmv",
T0Roi: "t0_roi",
T1Roi: "t1_roi",
T7Roi: "t7_roi",
T15Roi: "t15_roi",
T30Roi: "t30_roi",
PaiedOrder: "paied_order",
OrderRatio: "order_ratio",
T0OrderCnt: "t0_order_cnt",
T0OrderCntCost: "t0_order_cnt_cost",
T0OrderCntRatio: "t0_order_cnt_ratio",
T1OrderCnt: "t1_order_cnt",
T7OrderCnt: "t7_order_cnt",
T15OrderCnt: "t15_order_cnt",
T30OrderCnt: "t30_order_cnt",
MerchantRecoFans: "merchant_reco_fans",
T1Retention: "t1_retention",
T7Retention: "t7_retention",
T15Retention: "t15_retention",
T30Retention: "t30_retention",
T1RetentionRatio: "t1_retention_ratio",
T7RetentionRatio: "t7_retention_ratio",
T15RetentionRatio: "t15_retention_ratio",
T30RetentionRatio: "t30_retention_ratio",
ReservationSuccess: "reservation_success",
ReservationCost: "reservation_cost",
StandardLivePlayedStarted: "standard_live_played_started",
AdLivePlayCnt: "ad_live_play_cnt",
AdLivePlayCntCost: "ad_live_play_cnt_cost",
LiveAudienceCost: "live_audience_cost",
LiveEventGoodsView: "live_event_goods_view",
GoodsClickRatio: "goods_click_ratio",
DirectAttrPlatNewBuyerCnt: "direct_attr_plat_new_buyer_cnt",
T30AttrPlatTotalBuyerCnt: "t30_attr_plat_total_buyer_cnt",
DirectAttrSellerNewBuyerCnt: "direct_attr_seller_new_buyer_cnt",
T30AttrSellerTotalBuyerCnt: "t30_attr_seller_total_buyer_cnt",
T3Gmv: "t3_gmv",
T3OrderCnt: "t3_order_cnt",
T3Roi: "t3_roi",
T7IndirectOrderAmt: "t7_indirect_order_amt",
T7IndirectOrderCnt: "t7_indirect_order_cnt",
FansT0GmvPerFans: "fans_t0_gmv_per_fans",
FansT3GmvPerFans: "fans_t3_gmv_per_fans",
FansT7GmvPerFans: "fans_t7_gmv_per_fans",
FansT15GmvPerFans: "fans_t15_gmv_per_fans",
FansT30GmvPerFans: "fans_t30_gmv_per_fans",
RecoFansCost: "reco_fans_cost",
QcpxWhiteboxDirectOrderPaymentAmt: "qcpx_whitebox_direct_order_payment_amt",
QcpxWhiteboxDirectOrderCnt: "qcpx_whitebox_direct_order_cnt",
FansT0Gmv: "fans_t0_gmv",
FansT1Gmv: "fans_t1_gmv",
FansT7Gmv: "fans_t7_gmv",
FansT15Gmv: "fans_t15_gmv",
FansT30Gmv: "fans_t30_gmv",
FansT0Roi: "fans_t0_roi",
FansT1Roi: "fans_t1_roi",
FansT7Roi: "fans_t7_roi",
FansT15Roi: "fans_t15_roi",
FansT30Roi: "fans_t30_roi",
T0ShopNewBuyerOrderPaymentAmt: "t0_shop_new_buyer_order_payment_amt",
T1ShopNewBuyerOrderPaymentAmt: "t1_shop_new_buyer_order_payment_amt",
T3ShopNewBuyerOrderPaymentAmt: "t3_shop_new_buyer_order_payment_amt",
T7ShopNewBuyerOrderPaymentAmt: "t7_shop_new_buyer_order_payment_amt",
T15ShopNewBuyerOrderPaymentAmt: "t15_shop_new_buyer_order_payment_amt",
T30ShopNewBuyerOrderPaymentAmt: "t30_shop_new_buyer_order_payment_amt",
T0ShopNewBuyerOrderCnt: "t0_shop_new_buyer_order_cnt",
T1ShopNewBuyerOrderCnt: "t1_shop_new_buyer_order_cnt",
T3ShopNewBuyerOrderCnt: "t3_shop_new_buyer_order_cnt",
T7ShopNewBuyerOrderCnt: "t7_shop_new_buyer_order_cnt",
T15ShopNewBuyerOrderCnt: "t15_shop_new_buyer_order_cnt",
T30ShopNewBuyerOrderCnt: "t30_shop_new_buyer_order_cnt",
T1NewBuyerRepurchaseRatio: "t1_new_buyer_repurchase_ratio",
T3NewBuyerRepurchaseRatio: "t3_new_buyer_repurchase_ratio",
T7NewBuyerRepurchaseRatio: "t7_new_buyer_repurchase_ratio",
T15NewBuyerRepurchaseRatio: "t15_new_buyer_repurchase_ratio",
T30NewBuyerRepurchaseRatio: "t30_new_buyer_repurchase_ratio",
T0ShopNewBuyerRoi: "t0_shop_new_buyer_roi",
T1ShopNewBuyerRoi: "t1_shop_new_buyer_roi",
T3ShopNewBuyerRoi: "t3_shop_new_buyer_roi",
T7ShopNewBuyerRoi: "t7_shop_new_buyer_roi",
T15ShopNewBuyerRoi: "t15_shop_new_buyer_roi",
T30ShopNewBuyerRoi: "t30_shop_new_buyer_roi",
CreateCardOrderCnt: "create_card_order_cnt",
ForwardTsCreateCardOrderCnt: "forward_ts_create_card_order_cnt",
CreateCardOrderCost: "create_card_order_cost",
ForwardTsCreateCardOrderCost: "forward_ts_create_card_order_cost",
ActivateCardOrderCnt: "activate_card_order_cnt",
ForwardTsActivateCardOrderCnt: "forward_ts_activate_card_order_cnt",
ActivateCardOrderCost: "activate_card_order_cost",
ForwardTsActivateCardOrderCost: "forward_ts_activate_card_order_cost",
CreateCardOrderRatio: "create_card_order_ratio",
ForwardTsCreateCardOrderRatio: "forward_ts_create_card_order_ratio",
ActivateCardOrderCntRatio: "activate_card_order_cnt_ratio",
ForwardTsActivateCardOrderRatio: "forward_ts_activate_card_order_ratio",
LivePlayCnt: "live_play_cnt",
ItemEntranceClkCnt: "item_entrance_clk_cnt",
ShowCnt: "show_cnt",
ReportDateStr: "report_date_str",
CampaignId: "campaign_id",
CampaignName: "campaign_name",
UnitId: "unit_id",
UnitName: "unit_name",
CreativeId: "creative_id",
CreativeName: "creative_name",
CidActualRoiAfterSubsidy: "cid_actual_roi_after_subsidy",
CidCouponAmount: "cid_coupon_amount",
CidCouponCallbackPaidRefundAmount: "cid_coupon_callback_paid_refund_amount",
CidVoucherCost: "cid_voucher_cost",
}
}
-576
View File
@@ -1,576 +0,0 @@
package copydata
import "gitea.com/red-future/common/beans"
// MaterialReport 素材报表数据实体
type MaterialReport struct {
beans.SQLBaseDO `orm:",inherit"`
CreatedBy *string `orm:"created_by" json:"createdBy" description:"创建人"`
UpdatedBy *string `orm:"updated_by" json:"updatedBy" description:"更新人"`
T0OrderPaymentAmt *float64 `orm:"t0_order_payment_amt" json:"t0OrderPaymentAmt" description:"当日订单支付金额"`
CreativeMaterialType *string `orm:"creative_material_type" json:"creativeMaterialType" description:"创意素材类型"`
LiveName *string `orm:"live_name" json:"liveName" description:"直播间名称"`
AuthorId *string `orm:"author_id" json:"authorId" description:"作者 ID"`
PicUrl *string `orm:"pic_url" json:"picUrl" description:"图片 URL"`
PicName *string `orm:"pic_name" json:"picName" description:"图片名称"`
PicId *string `orm:"pic_id" json:"picId" description:"图片 ID"`
CoverUrl *string `orm:"cover_url" json:"coverUrl" description:"封面 URL"`
CoverId *int64 `orm:"cover_id" json:"coverId" description:"封面 ID"`
ItemOrderConversionRatio *float64 `orm:"item_order_conversion_ratio" json:"itemOrderConversionRatio" description:"商品订单转化率"`
ItemCardClickRatio *float64 `orm:"item_card_click_ratio" json:"itemCardClickRatio" description:"商品卡点击率"`
ItemCardClkCnt *int64 `orm:"item_card_clk_cnt" json:"itemCardClkCnt" description:"商品卡点击数"`
LivePlayCntCost *float64 `orm:"live_play_cnt_cost" json:"livePlayCntCost" description:"直播间观看成本"`
AdMerchantFollowCost *float64 `orm:"ad_merchant_follow_cost" json:"adMerchantFollowCost" description:"商家涨粉成本"`
AdMerchantFollow *int64 `orm:"ad_merchant_follow" json:"adMerchantFollow" description:"商家涨粉数"`
NetT0OrderCnt *int64 `orm:"net_t0_order_cnt" json:"netT0OrderCnt" description:"当日净成交订单数"`
NetT0Roi *float64 `orm:"net_t0_roi" json:"netT0Roi" description:"当日净成交 ROI"`
NetT0Gmv *float64 `orm:"net_t0_gmv" json:"netT0Gmv" description:"当日净成交 GMV"`
PhotoName *string `orm:"photo_name" json:"photoName" description:"照片名称"`
PhotoIdStr *string `orm:"photo_id_str" json:"photoIdStr" description:"照片 ID 字符串"`
PhotoId *string `orm:"photo_id" json:"photoId" description:"照片 ID"`
ModPriceSegment *string `orm:"mod_price_segment" json:"modPriceSegment" description:"价格区间"`
AgeSegment *string `orm:"age_segment" json:"ageSegment" description:"年龄段"`
Province *string `orm:"province" json:"province" description:"省份"`
Gender *string `orm:"gender" json:"gender" description:"性别"`
AdPhotoPlayedFiveRatio *float64 `orm:"ad_photo_played_five_ratio" json:"adPhotoPlayedFiveRatio" description:"广告照片播放 5 秒比率"`
AdPhotoPlayedThreeRatio *float64 `orm:"ad_photo_played_three_ratio" json:"adPhotoPlayedThreeRatio" description:"广告照片播放 3 秒比率"`
OrderSubmitRoi *float64 `orm:"order_submit_roi" json:"orderSubmitRoi" description:"提交订单 ROI"`
OrderSubmitAmt *int64 `orm:"order_submit_amt" json:"orderSubmitAmt" description:"提交订单金额"`
EventOrderSubmitCost *float64 `orm:"event_order_submit_cost" json:"eventOrderSubmitCost" description:"事件订单提交成本"`
EventOrderSubmit *int64 `orm:"event_order_submit" json:"eventOrderSubmit" description:"事件订单提交数"`
EventOrderPaidRoi *float64 `orm:"event_order_paid_roi" json:"eventOrderPaidRoi" description:"事件订单支付 ROI"`
EventAppInvoked *int64 `orm:"event_app_invoked" json:"eventAppInvoked" description:"事件 APP 调用数"`
EventAddShoppingCart *int64 `orm:"event_add_shopping_cart" json:"eventAddShoppingCart" description:"事件加购数"`
ConversionNumCost *float64 `orm:"conversion_num_cost" json:"conversionNumCost" description:"转化数量成本"`
AdEffectivePlayNum *int64 `orm:"ad_effective_play_num" json:"adEffectivePlayNum" description:"广告有效播放数"`
AdItemClick *int64 `orm:"ad_item_click" json:"adItemClick" description:"广告商品点击数"`
MerchantProductId *string `orm:"merchant_product_id" json:"merchantProductId" description:"商家商品 ID"`
CostTotal *float64 `orm:"cost_total" json:"costTotal" description:"总花费"`
AdShow *int64 `orm:"ad_show" json:"adShow" description:"广告展示数"`
AdShow1kCost *float64 `orm:"ad_show1k_cost" json:"adShow1kCost" description:"千次展示成本"`
Impression *int64 `orm:"impression" json:"impression" description:"曝光数"`
PhotoClick *int64 `orm:"photo_click" json:"photoClick" description:"照片点击数"`
PhotoClickRatio *float64 `orm:"photo_click_ratio" json:"photoClickRatio" description:"照片点击率"`
Click *int64 `orm:"click" json:"click" description:"点击数"`
ActionbarClick *int64 `orm:"actionbar_click" json:"actionbarClick" description:"操作栏点击数"`
ActionbarClickCost *float64 `orm:"actionbar_click_cost" json:"actionbarClickCost" description:"操作栏点击成本"`
EspClickRatio *float64 `orm:"esp_click_ratio" json:"espClickRatio" description:"ESP 点击率"`
ActionRatio *float64 `orm:"action_ratio" json:"actionRatio" description:"操作比率"`
AdItemCount *int64 `orm:"ad_item_click_count" json:"adItemCount" description:"广告商品点击次数"`
EspLivePlayedSeconds *int64 `orm:"esp_live_played_seconds" json:"espLivePlayedSeconds" description:"ESP 直播播放秒数"`
PlayedThreeSeconds *int64 `orm:"played_three_seconds" json:"playedThreeSeconds" description:"播放 3 秒数"`
Play3sRatio *float64 `orm:"play3s_ratio" json:"play3sRatio" description:"3 秒播放率"`
PlayedFiveSeconds *int64 `orm:"played_five_seconds" json:"playedFiveSeconds" description:"播放 5 秒数"`
Play5sRatio *float64 `orm:"play5s_ratio" json:"play5sRatio" description:"5 秒播放率"`
PlayedEnd *int64 `orm:"played_end" json:"playedEnd" description:"播放结束数"`
PlayEndRatio *float64 `orm:"play_end_ratio" json:"playEndRatio" description:"完播率"`
Share *int64 `orm:"share" json:"share" description:"分享数"`
Comment *int64 `orm:"comment" json:"comment" description:"评论数"`
Likes *int64 `orm:"likes" json:"likes" description:"点赞数"`
Report *int64 `orm:"report" json:"report" description:"举报数"`
Block *int64 `orm:"block" json:"block" description:"拉黑数"`
ItemNegative *int64 `orm:"item_negative" json:"itemNegative" description:"商品负反馈数"`
LiveShare *int64 `orm:"live_share" json:"liveShare" description:"直播分享数"`
LiveComment *int64 `orm:"live_comment" json:"liveComment" description:"直播评论数"`
LiveReward *int64 `orm:"live_reward" json:"liveReward" description:"直播打赏数"`
EffectivePlayCount *int64 `orm:"effective_play_count" json:"effectivePlayCount" description:"有效播放数"`
EffectivePlayRatio *float64 `orm:"effective_play_ratio" json:"effectivePlayRatio" description:"有效播放率"`
ConversionNum *int64 `orm:"conversion_num" json:"conversionNum" description:"转化数"`
ConversionCostEsp *float64 `orm:"conversion_cost_esp" json:"conversionCostEsp" description:"转化成本 ESP"`
Roi *float64 `orm:"roi" json:"roi" description:"ROI"`
Gmv *float64 `orm:"gmv" json:"gmv" description:"GMV"`
T0Gmv *float64 `orm:"t0_gmv" json:"t0Gmv" description:"当日 GMV"`
T1Gmv *float64 `orm:"t1_gmv" json:"t1Gmv" description:"次日 GMV"`
T7Gmv *float64 `orm:"t7_gmv" json:"t7Gmv" description:"7 日 GMV"`
T15Gmv *float64 `orm:"t15_gmv" json:"t15Gmv" description:"15 日 GMV"`
T30Gmv *float64 `orm:"t30_gmv" json:"t30Gmv" description:"30 日 GMV"`
T0Roi *float64 `orm:"t0_roi" json:"t0Roi" description:"当日 ROI"`
T1Roi *float64 `orm:"t1_roi" json:"t1Roi" description:"次日 ROI"`
T7Roi *float64 `orm:"t7_roi" json:"t7Roi" description:"7 日 ROI"`
T15Roi *float64 `orm:"t15_roi" json:"t15Roi" description:"15 日 ROI"`
T30Roi *float64 `orm:"t30_roi" json:"t30Roi" description:"30 日 ROI"`
PaiedOrder *int64 `orm:"paied_order" json:"paiedOrder" description:"支付订单数"`
OrderRatio *float64 `orm:"order_ratio" json:"orderRatio" description:"订单比率"`
T0OrderCnt *int64 `orm:"t0_order_cnt" json:"t0OrderCnt" description:"当日订单数"`
T0OrderCntCost *float64 `orm:"t0_order_cnt_cost" json:"t0OrderCntCost" description:"当日订单成本"`
T0OrderCntRatio *float64 `orm:"t0_order_cnt_ratio" json:"t0OrderCntRatio" description:"当日订单比率"`
T1OrderCnt *int64 `orm:"t1_order_cnt" json:"t1OrderCnt" description:"次日订单数"`
T7OrderCnt *int64 `orm:"t7_order_cnt" json:"t7OrderCnt" description:"7 日订单数"`
T15OrderCnt *int64 `orm:"t15_order_cnt" json:"t15OrderCnt" description:"15 日订单数"`
T30OrderCnt *int64 `orm:"t30_order_cnt" json:"t30OrderCnt" description:"30 日订单数"`
MerchantRecoFans *int64 `orm:"merchant_reco_fans" json:"merchantRecoFans" description:"商家推荐粉丝数"`
T1Retention *float64 `orm:"t1_retention" json:"t1Retention" description:"次日留存率"`
T7Retention *float64 `orm:"t7_retention" json:"t7Retention" description:"7 日留存率"`
T15Retention *float64 `orm:"t15_retention" json:"t15Retention" description:"15 日留存率"`
T30Retention *float64 `orm:"t30_retention" json:"t30Retention" description:"30 日留存率"`
T1RetentionRatio *float64 `orm:"t1_retention_ratio" json:"t1RetentionRatio" description:"次日留存比率"`
T7RetentionRatio *float64 `orm:"t7_retention_ratio" json:"t7RetentionRatio" description:"7 日留存比率"`
T15RetentionRatio *float64 `orm:"t15_retention_ratio" json:"t15RetentionRatio" description:"15 日留存比率"`
T30RetentionRatio *float64 `orm:"t30_retention_ratio" json:"t30RetentionRatio" description:"30 日留存比率"`
ReservationSuccess *int64 `orm:"reservation_success" json:"reservationSuccess" description:"预约成功数"`
ReservationCost *float64 `orm:"reservation_cost" json:"reservationCost" description:"预约成本"`
StandardLivePlayedStarted *int64 `orm:"standard_live_played_started" json:"standardLivePlayedStarted" description:"标准直播开始数"`
AdLivePlayCnt *int64 `orm:"ad_live_play_cnt" json:"adLivePlayCnt" description:"广告直播播放数"`
AdLivePlayCntCost *float64 `orm:"ad_live_play_cnt_cost" json:"adLivePlayCntCost" description:"广告直播播放成本"`
LiveAudienceCost *float64 `orm:"live_audience_cost" json:"liveAudienceCost" description:"直播观众成本"`
LiveEventGoodsView *int64 `orm:"live_event_goods_view" json:"liveEventGoodsView" description:"直播事件商品浏览数"`
GoodsClickRatio *float64 `orm:"goods_click_ratio" json:"goodsClickRatio" description:"商品点击率"`
DirectAttrPlatNewBuyerCnt *int64 `orm:"direct_attr_plat_new_buyer_cnt" json:"directAttrPlatNewBuyerCnt" description:"直接吸引平台新买家数"`
T30AttrPlatTotalBuyerCnt *int64 `orm:"t30_attr_plat_total_buyer_cnt" json:"t30AttrPlatTotalBuyerCnt" description:"30 日吸引平台总买家数"`
DirectAttrSellerNewBuyerCnt *int64 `orm:"direct_attr_seller_new_buyer_cnt" json:"directAttrSellerNewBuyerCnt" description:"直接吸引商家新买家数"`
T30AttrSellerTotalBuyerCnt *int64 `orm:"t30_attr_seller_total_buyer_cnt" json:"t30AttrSellerTotalBuyerCnt" description:"30 日吸引商家总买家数"`
T3Gmv *float64 `orm:"t3_gmv" json:"t3Gmv" description:"3 日 GMV"`
T3OrderCnt *int64 `orm:"t3_order_cnt" json:"t3OrderCnt" description:"3 日订单数"`
T3Roi *float64 `orm:"t3_roi" json:"t3Roi" description:"3 日 ROI"`
T7IndirectOrderAmt *float64 `orm:"t7_indirect_order_amt" json:"t7IndirectOrderAmt" description:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `orm:"t7_indirect_order_cnt" json:"t7IndirectOrderCnt" description:"7 日间接订单数"`
FansT0GmvPerFans *float64 `orm:"fans_t0_gmv_per_fans" json:"fansT0GmvPerFans" description:"粉丝当日人均 GMV"`
FansT3GmvPerFans *float64 `orm:"fans_t3_gmv_per_fans" json:"fansT3GmvPerFans" description:"粉丝 3 日人均 GMV"`
FansT7GmvPerFans *float64 `orm:"fans_t7_gmv_per_fans" json:"fansT7GmvPerFans" description:"粉丝 7 日人均 GMV"`
FansT15GmvPerFans *float64 `orm:"fans_t15_gmv_per_fans" json:"fansT15GmvPerFans" description:"粉丝 15 日人均 GMV"`
FansT30GmvPerFans *float64 `orm:"fans_t30_gmv_per_fans" json:"fansT30GmvPerFans" description:"粉丝 30 日人均 GMV"`
RecoFansCost *float64 `orm:"reco_fans_cost" json:"recoFansCost" description:"推荐粉丝成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `orm:"qcpx_whitebox_direct_order_payment_amt" json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"白盒直接订单支付金额"`
QcpxWhiteboxDirectOrderCnt *int64 `orm:"qcpx_whitebox_direct_order_cnt" json:"qcpxWhiteboxDirectOrderCnt" description:"白盒直接订单数"`
FansT0Gmv *float64 `orm:"fans_t0_gmv" json:"fansT0Gmv" description:"粉丝当日 GMV"`
FansT1Gmv *float64 `orm:"fans_t1_gmv" json:"fansT1Gmv" description:"粉丝次日 GMV"`
FansT7Gmv *float64 `orm:"fans_t7_gmv" json:"fansT7Gmv" description:"粉丝 7 日 GMV"`
FansT15Gmv *float64 `orm:"fans_t15_gmv" json:"fansT15Gmv" description:"粉丝 15 日 GMV"`
FansT30Gmv *float64 `orm:"fans_t30_gmv" json:"fansT30Gmv" description:"粉丝 30 日 GMV"`
FansT0Roi *float64 `orm:"fans_t0_roi" json:"fansT0Roi" description:"粉丝当日 ROI"`
FansT1Roi *float64 `orm:"fans_t1_roi" json:"fansT1Roi" description:"粉丝次日 ROI"`
FansT7Roi *float64 `orm:"fans_t7_roi" json:"fansT7Roi" description:"粉丝 7 日 ROI"`
FansT15Roi *float64 `orm:"fans_t15_roi" json:"fansT15Roi" description:"粉丝 15 日 ROI"`
FansT30Roi *float64 `orm:"fans_t30_roi" json:"fansT30Roi" description:"粉丝 30 日 ROI"`
T0ShopNewBuyerOrderPaymentAmt *float64 `orm:"t0_shop_new_buyer_order_payment_amt" json:"t0ShopNewBuyerOrderPaymentAmt" description:"当日店铺新客订单支付金额"`
T1ShopNewBuyerOrderPaymentAmt *float64 `orm:"t1_shop_new_buyer_order_payment_amt" json:"t1ShopNewBuyerOrderPaymentAmt" description:"次日店铺新客订单支付金额"`
T3ShopNewBuyerOrderPaymentAmt *float64 `orm:"t3_shop_new_buyer_order_payment_amt" json:"t3ShopNewBuyerOrderPaymentAmt" description:"3 日店铺新客订单支付金额"`
T7ShopNewBuyerOrderPaymentAmt *float64 `orm:"t7_shop_new_buyer_order_payment_amt" json:"t7ShopNewBuyerOrderPaymentAmt" description:"7 日店铺新客订单支付金额"`
T15ShopNewBuyerOrderPaymentAmt *float64 `orm:"t15_shop_new_buyer_order_payment_amt" json:"t15ShopNewBuyerOrderPaymentAmt" description:"15 日店铺新客订单支付金额"`
T30ShopNewBuyerOrderPaymentAmt *float64 `orm:"t30_shop_new_buyer_order_payment_amt" json:"t30ShopNewBuyerOrderPaymentAmt" description:"30 日店铺新客订单支付金额"`
T0ShopNewBuyerOrderCnt *int64 `orm:"t0_shop_new_buyer_order_cnt" json:"t0ShopNewBuyerOrderCnt" description:"当日店铺新客订单数"`
T1ShopNewBuyerOrderCnt *int64 `orm:"t1_shop_new_buyer_order_cnt" json:"t1ShopNewBuyerOrderCnt" description:"次日店铺新客订单数"`
T3ShopNewBuyerOrderCnt *int64 `orm:"t3_shop_new_buyer_order_cnt" json:"t3ShopNewBuyerOrderCnt" description:"3 日店铺新客订单数"`
T7ShopNewBuyerOrderCnt *int64 `orm:"t7_shop_new_buyer_order_cnt" json:"t7ShopNewBuyerOrderCnt" description:"7 日店铺新客订单数"`
T15ShopNewBuyerOrderCnt *int64 `orm:"t15_shop_new_buyer_order_cnt" json:"t15ShopNewBuyerOrderCnt" description:"15 日店铺新客订单数"`
T30ShopNewBuyerOrderCnt *int64 `orm:"t30_shop_new_buyer_order_cnt" json:"t30ShopNewBuyerOrderCnt" description:"30 日店铺新客订单数"`
T1NewBuyerRepurchaseRatio *float64 `orm:"t1_new_buyer_repurchase_ratio" json:"t1NewBuyerRepurchaseRatio" description:"次日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `orm:"t3_new_buyer_repurchase_ratio" json:"t3NewBuyerRepurchaseRatio" description:"3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `orm:"t7_new_buyer_repurchase_ratio" json:"t7NewBuyerRepurchaseRatio" description:"7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `orm:"t15_new_buyer_repurchase_ratio" json:"t15NewBuyerRepurchaseRatio" description:"15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `orm:"t30_new_buyer_repurchase_ratio" json:"t30NewBuyerRepurchaseRatio" description:"30 日新客复购率"`
T0ShopNewBuyerRoi *float64 `orm:"t0_shop_new_buyer_roi" json:"t0ShopNewBuyerRoi" description:"当日店铺新客 ROI"`
T1ShopNewBuyerRoi *float64 `orm:"t1_shop_new_buyer_roi" json:"t1ShopNewBuyerRoi" description:"次日店铺新客 ROI"`
T3ShopNewBuyerRoi *float64 `orm:"t3_shop_new_buyer_roi" json:"t3ShopNewBuyerRoi" description:"3 日店铺新客 ROI"`
T7ShopNewBuyerRoi *float64 `orm:"t7_shop_new_buyer_roi" json:"t7ShopNewBuyerRoi" description:"7 日店铺新客 ROI"`
T15ShopNewBuyerRoi *float64 `orm:"t15_shop_new_buyer_roi" json:"t15ShopNewBuyerRoi" description:"15 日店铺新客 ROI"`
T30ShopNewBuyerRoi *float64 `orm:"t30_shop_new_buyer_roi" json:"t30ShopNewBuyerRoi" description:"30 日店铺新客 ROI"`
CreateCardOrderCnt *int64 `orm:"create_card_order_cnt" json:"createCardOrderCnt" description:"创建卡片订单数"`
ForwardTsCreateCardOrderCnt *int64 `orm:"forward_ts_create_card_order_cnt" json:"forwardTsCreateCardOrderCnt" description:"转发 TS 创建卡片订单数"`
CreateCardOrderCost *float64 `orm:"create_card_order_cost" json:"createCardOrderCost" description:"创建卡片订单成本"`
ForwardTsCreateCardOrderCost *float64 `orm:"forward_ts_create_card_order_cost" json:"forwardTsCreateCardOrderCost" description:"转发 TS 创建卡片订单成本"`
ActivateCardOrderCnt *int64 `orm:"activate_card_order_cnt" json:"activateCardOrderCnt" description:"激活卡片订单数"`
ForwardTsActivateCardOrderCnt *int64 `orm:"forward_ts_activate_card_order_cnt" json:"forwardTsActivateCardOrderCnt" description:"转发 TS 激活卡片订单数"`
ActivateCardOrderCost *float64 `orm:"activate_card_order_cost" json:"activateCardOrderCost" description:"激活卡片订单成本"`
ForwardTsActivateCardOrderCost *float64 `orm:"forward_ts_activate_card_order_cost" json:"forwardTsActivateCardOrderCost" description:"转发 TS 激活卡片订单成本"`
CreateCardOrderRatio *float64 `orm:"create_card_order_ratio" json:"createCardOrderRatio" description:"创建卡片订单比率"`
ForwardTsCreateCardOrderRatio *float64 `orm:"forward_ts_create_card_order_ratio" json:"forwardTsCreateCardOrderRatio" description:"转发 TS 创建卡片订单比率"`
ActivateCardOrderCntRatio *float64 `orm:"activate_card_order_cnt_ratio" json:"activateCardOrderCntRatio" description:"激活卡片订单比率"`
ForwardTsActivateCardOrderRatio *float64 `orm:"forward_ts_activate_card_order_ratio" json:"forwardTsActivateCardOrderRatio" description:"转发 TS 激活卡片订单比率"`
LivePlayCnt *int64 `orm:"live_play_cnt" json:"livePlayCnt" description:"直播播放数"`
ItemEntranceClkCnt *int64 `orm:"item_entrance_clk_cnt" json:"itemEntranceClkCnt" description:"商品入口点击数"`
ShowCnt *int64 `orm:"show_cnt" json:"showCnt" description:"展示数"`
ReportDateStr string `orm:"report_date_str" json:"reportDateStr" description:"报告日期(格式:YYYY-MM-DD"`
CampaignId *int64 `orm:"campaign_id" json:"campaignId" description:"计划 ID"`
CampaignName *string `orm:"campaign_name" json:"campaignName" description:"计划名称"`
UnitId *int64 `orm:"unit_id" json:"unitId" description:"单元 ID"`
UnitName *string `orm:"unit_name" json:"unitName" description:"单元名称"`
CreativeId *int64 `orm:"creative_id" json:"creativeId" description:"创意 ID"`
CreativeName *string `orm:"creative_name" json:"creativeName" description:"创意名称"`
}
// MaterialReportCol 素材报表数据表字段定义
type MaterialReportCol struct {
beans.SQLBaseCol
CreatedBy string
UpdatedBy string
T0OrderPaymentAmt string
CreativeMaterialType string
LiveName string
AuthorId string
PicUrl string
PicName string
PicId string
CoverUrl string
CoverId string
ItemOrderConversionRatio string
ItemCardClickRatio string
ItemCardClkCnt string
LivePlayCntCost string
AdMerchantFollowCost string
AdMerchantFollow string
NetT0OrderCnt string
NetT0Roi string
NetT0Gmv string
PhotoName string
PhotoIdStr string
PhotoId string
ModPriceSegment string
AgeSegment string
Province string
Gender string
AdPhotoPlayedFiveRatio string
AdPhotoPlayedThreeRatio string
OrderSubmitRoi string
OrderSubmitAmt string
EventOrderSubmitCost string
EventOrderSubmit string
EventOrderPaidRoi string
EventAppInvoked string
EventAddShoppingCart string
ConversionNumCost string
AdEffectivePlayNum string
AdItemClick string
MerchantProductId string
CostTotal string
AdShow string
AdShow1kCost string
Impression string
PhotoClick string
PhotoClickRatio string
Click string
ActionbarClick string
ActionbarClickCost string
EspClickRatio string
ActionRatio string
AdItemCount string
EspLivePlayedSeconds string
PlayedThreeSeconds string
Play3sRatio string
PlayedFiveSeconds string
Play5sRatio string
PlayedEnd string
PlayEndRatio string
Share string
Comment string
Likes string
Report string
Block string
ItemNegative string
LiveShare string
LiveComment string
LiveReward string
EffectivePlayCount string
EffectivePlayRatio string
ConversionNum string
ConversionCostEsp string
Roi string
Gmv string
T0Gmv string
T1Gmv string
T7Gmv string
T15Gmv string
T30Gmv string
T0Roi string
T1Roi string
T7Roi string
T15Roi string
T30Roi string
PaiedOrder string
OrderRatio string
T0OrderCnt string
T0OrderCntCost string
T0OrderCntRatio string
T1OrderCnt string
T7OrderCnt string
T15OrderCnt string
T30OrderCnt string
MerchantRecoFans string
T1Retention string
T7Retention string
T15Retention string
T30Retention string
T1RetentionRatio string
T7RetentionRatio string
T15RetentionRatio string
T30RetentionRatio string
ReservationSuccess string
ReservationCost string
StandardLivePlayedStarted string
AdLivePlayCnt string
AdLivePlayCntCost string
LiveAudienceCost string
LiveEventGoodsView string
GoodsClickRatio string
DirectAttrPlatNewBuyerCnt string
T30AttrPlatTotalBuyerCnt string
DirectAttrSellerNewBuyerCnt string
T30AttrSellerTotalBuyerCnt string
T3Gmv string
T3OrderCnt string
T3Roi string
T7IndirectOrderAmt string
T7IndirectOrderCnt string
FansT0GmvPerFans string
FansT3GmvPerFans string
FansT7GmvPerFans string
FansT15GmvPerFans string
FansT30GmvPerFans string
RecoFansCost string
QcpxWhiteboxDirectOrderPaymentAmt string
QcpxWhiteboxDirectOrderCnt string
FansT0Gmv string
FansT1Gmv string
FansT7Gmv string
FansT15Gmv string
FansT30Gmv string
FansT0Roi string
FansT1Roi string
FansT7Roi string
FansT15Roi string
FansT30Roi string
T0ShopNewBuyerOrderPaymentAmt string
T1ShopNewBuyerOrderPaymentAmt string
T3ShopNewBuyerOrderPaymentAmt string
T7ShopNewBuyerOrderPaymentAmt string
T15ShopNewBuyerOrderPaymentAmt string
T30ShopNewBuyerOrderPaymentAmt string
T0ShopNewBuyerOrderCnt string
T1ShopNewBuyerOrderCnt string
T3ShopNewBuyerOrderCnt string
T7ShopNewBuyerOrderCnt string
T15ShopNewBuyerOrderCnt string
T30ShopNewBuyerOrderCnt string
T1NewBuyerRepurchaseRatio string
T3NewBuyerRepurchaseRatio string
T7NewBuyerRepurchaseRatio string
T15NewBuyerRepurchaseRatio string
T30NewBuyerRepurchaseRatio string
T0ShopNewBuyerRoi string
T1ShopNewBuyerRoi string
T3ShopNewBuyerRoi string
T7ShopNewBuyerRoi string
T15ShopNewBuyerRoi string
T30ShopNewBuyerRoi string
CreateCardOrderCnt string
ForwardTsCreateCardOrderCnt string
CreateCardOrderCost string
ForwardTsCreateCardOrderCost string
ActivateCardOrderCnt string
ForwardTsActivateCardOrderCnt string
ActivateCardOrderCost string
ForwardTsActivateCardOrderCost string
CreateCardOrderRatio string
ForwardTsCreateCardOrderRatio string
ActivateCardOrderCntRatio string
ForwardTsActivateCardOrderRatio string
LivePlayCnt string
ItemEntranceClkCnt string
ShowCnt string
ReportDateStr string
CampaignId string
CampaignName string
UnitId string
UnitName string
CreativeId string
CreativeName string
}
// TableName 返回表名
func (e *MaterialReport) TableName() string {
return "material_report"
}
// GetCols 获取所有字段名
func (e *MaterialReport) GetCols() *MaterialReportCol {
return &MaterialReportCol{
SQLBaseCol: beans.SQLBaseCol{
Id: "id",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
CreatedBy: "created_by",
UpdatedBy: "updated_by",
T0OrderPaymentAmt: "t0_order_payment_amt",
CreativeMaterialType: "creative_material_type",
LiveName: "live_name",
AuthorId: "author_id",
PicUrl: "pic_url",
PicName: "pic_name",
PicId: "pic_id",
CoverUrl: "cover_url",
CoverId: "cover_id",
ItemOrderConversionRatio: "item_order_conversion_ratio",
ItemCardClickRatio: "item_card_click_ratio",
ItemCardClkCnt: "item_card_clk_cnt",
LivePlayCntCost: "live_play_cnt_cost",
AdMerchantFollowCost: "ad_merchant_follow_cost",
AdMerchantFollow: "ad_merchant_follow",
NetT0OrderCnt: "net_t0_order_cnt",
NetT0Roi: "net_t0_roi",
NetT0Gmv: "net_t0_gmv",
PhotoName: "photo_name",
PhotoIdStr: "photo_id_str",
PhotoId: "photo_id",
ModPriceSegment: "mod_price_segment",
AgeSegment: "age_segment",
Province: "province",
Gender: "gender",
AdPhotoPlayedFiveRatio: "ad_photo_played_five_ratio",
AdPhotoPlayedThreeRatio: "ad_photo_played_three_ratio",
OrderSubmitRoi: "order_submit_roi",
OrderSubmitAmt: "order_submit_amt",
EventOrderSubmitCost: "event_order_submit_cost",
EventOrderSubmit: "event_order_submit",
EventOrderPaidRoi: "event_order_paid_roi",
EventAppInvoked: "event_app_invoked",
EventAddShoppingCart: "event_add_shopping_cart",
ConversionNumCost: "conversion_num_cost",
AdEffectivePlayNum: "ad_effective_play_num",
AdItemClick: "ad_item_click",
MerchantProductId: "merchant_product_id",
CostTotal: "cost_total",
AdShow: "ad_show",
AdShow1kCost: "ad_show1k_cost",
Impression: "impression",
PhotoClick: "photo_click",
PhotoClickRatio: "photo_click_ratio",
Click: "click",
ActionbarClick: "actionbar_click",
ActionbarClickCost: "actionbar_click_cost",
EspClickRatio: "esp_click_ratio",
ActionRatio: "action_ratio",
AdItemCount: "ad_item_click_count",
EspLivePlayedSeconds: "esp_live_played_seconds",
PlayedThreeSeconds: "played_three_seconds",
Play3sRatio: "play3s_ratio",
PlayedFiveSeconds: "played_five_seconds",
Play5sRatio: "play5s_ratio",
PlayedEnd: "played_end",
PlayEndRatio: "play_end_ratio",
Share: "share",
Comment: "comment",
Likes: "likes",
Report: "report",
Block: "block",
ItemNegative: "item_negative",
LiveShare: "live_share",
LiveComment: "live_comment",
LiveReward: "live_reward",
EffectivePlayCount: "effective_play_count",
EffectivePlayRatio: "effective_play_ratio",
ConversionNum: "conversion_num",
ConversionCostEsp: "conversion_cost_esp",
Roi: "roi",
Gmv: "gmv",
T0Gmv: "t0_gmv",
T1Gmv: "t1_gmv",
T7Gmv: "t7_gmv",
T15Gmv: "t15_gmv",
T30Gmv: "t30_gmv",
T0Roi: "t0_roi",
T1Roi: "t1_roi",
T7Roi: "t7_roi",
T15Roi: "t15_roi",
T30Roi: "t30_roi",
PaiedOrder: "paied_order",
OrderRatio: "order_ratio",
T0OrderCnt: "t0_order_cnt",
T0OrderCntCost: "t0_order_cnt_cost",
T0OrderCntRatio: "t0_order_cnt_ratio",
T1OrderCnt: "t1_order_cnt",
T7OrderCnt: "t7_order_cnt",
T15OrderCnt: "t15_order_cnt",
T30OrderCnt: "t30_order_cnt",
MerchantRecoFans: "merchant_reco_fans",
T1Retention: "t1_retention",
T7Retention: "t7_retention",
T15Retention: "t15_retention",
T30Retention: "t30_retention",
T1RetentionRatio: "t1_retention_ratio",
T7RetentionRatio: "t7_retention_ratio",
T15RetentionRatio: "t15_retention_ratio",
T30RetentionRatio: "t30_retention_ratio",
ReservationSuccess: "reservation_success",
ReservationCost: "reservation_cost",
StandardLivePlayedStarted: "standard_live_played_started",
AdLivePlayCnt: "ad_live_play_cnt",
AdLivePlayCntCost: "ad_live_play_cnt_cost",
LiveAudienceCost: "live_audience_cost",
LiveEventGoodsView: "live_event_goods_view",
GoodsClickRatio: "goods_click_ratio",
DirectAttrPlatNewBuyerCnt: "direct_attr_plat_new_buyer_cnt",
T30AttrPlatTotalBuyerCnt: "t30_attr_plat_total_buyer_cnt",
DirectAttrSellerNewBuyerCnt: "direct_attr_seller_new_buyer_cnt",
T30AttrSellerTotalBuyerCnt: "t30_attr_seller_total_buyer_cnt",
T3Gmv: "t3_gmv",
T3OrderCnt: "t3_order_cnt",
T3Roi: "t3_roi",
T7IndirectOrderAmt: "t7_indirect_order_amt",
T7IndirectOrderCnt: "t7_indirect_order_cnt",
FansT0GmvPerFans: "fans_t0_gmv_per_fans",
FansT3GmvPerFans: "fans_t3_gmv_per_fans",
FansT7GmvPerFans: "fans_t7_gmv_per_fans",
FansT15GmvPerFans: "fans_t15_gmv_per_fans",
FansT30GmvPerFans: "fans_t30_gmv_per_fans",
RecoFansCost: "reco_fans_cost",
QcpxWhiteboxDirectOrderPaymentAmt: "qcpx_whitebox_direct_order_payment_amt",
QcpxWhiteboxDirectOrderCnt: "qcpx_whitebox_direct_order_cnt",
FansT0Gmv: "fans_t0_gmv",
FansT1Gmv: "fans_t1_gmv",
FansT7Gmv: "fans_t7_gmv",
FansT15Gmv: "fans_t15_gmv",
FansT30Gmv: "fans_t30_gmv",
FansT0Roi: "fans_t0_roi",
FansT1Roi: "fans_t1_roi",
FansT7Roi: "fans_t7_roi",
FansT15Roi: "fans_t15_roi",
FansT30Roi: "fans_t30_roi",
T0ShopNewBuyerOrderPaymentAmt: "t0_shop_new_buyer_order_payment_amt",
T1ShopNewBuyerOrderPaymentAmt: "t1_shop_new_buyer_order_payment_amt",
T3ShopNewBuyerOrderPaymentAmt: "t3_shop_new_buyer_order_payment_amt",
T7ShopNewBuyerOrderPaymentAmt: "t7_shop_new_buyer_order_payment_amt",
T15ShopNewBuyerOrderPaymentAmt: "t15_shop_new_buyer_order_payment_amt",
T30ShopNewBuyerOrderPaymentAmt: "t30_shop_new_buyer_order_payment_amt",
T0ShopNewBuyerOrderCnt: "t0_shop_new_buyer_order_cnt",
T1ShopNewBuyerOrderCnt: "t1_shop_new_buyer_order_cnt",
T3ShopNewBuyerOrderCnt: "t3_shop_new_buyer_order_cnt",
T7ShopNewBuyerOrderCnt: "t7_shop_new_buyer_order_cnt",
T15ShopNewBuyerOrderCnt: "t15_shop_new_buyer_order_cnt",
T30ShopNewBuyerOrderCnt: "t30_shop_new_buyer_order_cnt",
T1NewBuyerRepurchaseRatio: "t1_new_buyer_repurchase_ratio",
T3NewBuyerRepurchaseRatio: "t3_new_buyer_repurchase_ratio",
T7NewBuyerRepurchaseRatio: "t7_new_buyer_repurchase_ratio",
T15NewBuyerRepurchaseRatio: "t15_new_buyer_repurchase_ratio",
T30NewBuyerRepurchaseRatio: "t30_new_buyer_repurchase_ratio",
T0ShopNewBuyerRoi: "t0_shop_new_buyer_roi",
T1ShopNewBuyerRoi: "t1_shop_new_buyer_roi",
T3ShopNewBuyerRoi: "t3_shop_new_buyer_roi",
T7ShopNewBuyerRoi: "t7_shop_new_buyer_roi",
T15ShopNewBuyerRoi: "t15_shop_new_buyer_roi",
T30ShopNewBuyerRoi: "t30_shop_new_buyer_roi",
CreateCardOrderCnt: "create_card_order_cnt",
ForwardTsCreateCardOrderCnt: "forward_ts_create_card_order_cnt",
CreateCardOrderCost: "create_card_order_cost",
ForwardTsCreateCardOrderCost: "forward_ts_create_card_order_cost",
ActivateCardOrderCnt: "activate_card_order_cnt",
ForwardTsActivateCardOrderCnt: "forward_ts_activate_card_order_cnt",
ActivateCardOrderCost: "activate_card_order_cost",
ForwardTsActivateCardOrderCost: "forward_ts_activate_card_order_cost",
CreateCardOrderRatio: "create_card_order_ratio",
ForwardTsCreateCardOrderRatio: "forward_ts_create_card_order_ratio",
ActivateCardOrderCntRatio: "activate_card_order_cnt_ratio",
ForwardTsActivateCardOrderRatio: "forward_ts_activate_card_order_ratio",
LivePlayCnt: "live_play_cnt",
ItemEntranceClkCnt: "item_entrance_clk_cnt",
ShowCnt: "show_cnt",
ReportDateStr: "report_date_str",
CampaignId: "campaign_id",
CampaignName: "campaign_name",
UnitId: "unit_id",
UnitName: "unit_name",
CreativeId: "creative_id",
CreativeName: "creative_name",
}
}
-578
View File
@@ -1,578 +0,0 @@
package copydata
import "gitea.com/red-future/common/beans"
// PopulationReport 人群报表实体
type PopulationReport struct {
beans.SQLBaseDO `orm:",inherit"`
// 基础信息字段
PhotoName string `orm:"photo_name" json:"photoName" description:"视频名称"`
PhotoIdStr string `orm:"photo_id_str" json:"photoIdStr" description:"视频 id"`
PhotoId string `orm:"photo_id" json:"photoId" description:"视频 id"`
ModPriceSegment string `orm:"mod_price_segment" json:"modPriceSegment" description:"设备价格区间"`
AgeSegment string `orm:"age_segment" json:"ageSegment" description:"年龄段"`
Province string `orm:"province" json:"province" description:"省份名称"`
Gender string `orm:"gender" json:"gender" description:"性别"`
MerchantProductId string `orm:"merchant_product_id" json:"merchantProductId" description:"商品 ID"`
ReportDateStr string `orm:"report_date_str" json:"reportDateStr" description:"时间"`
// 广告结构字段
CampaignId *int64 `orm:"campaign_id" json:"campaignId" description:"计划 ID"`
CampaignName string `orm:"campaign_name" json:"campaignName" description:"计划名称"`
UnitId *int64 `orm:"unit_id" json:"unitId" description:"单元 ID"`
UnitName string `orm:"unit_name" json:"unitName" description:"单元名称"`
CreativeId *int64 `orm:"creative_id" json:"creativeId" description:"创意 ID"`
CreativeName string `orm:"creative_name" json:"creativeName" description:"创意名称"`
// 播放率相关字段
AdPhotoPlayedFiveRatio *float64 `orm:"ad_photo_played_five_ratio" json:"adPhotoPlayedFiveRatio" description:"作品 5 秒播放率"`
AdPhotoPlayedThreeRatio *float64 `orm:"ad_photo_played_three_ratio" json:"adPhotoPlayedThreeRatio" description:"作品 3 秒播放率"`
// 订单相关字段
OrderSubmitRoi *float64 `orm:"order_submit_roi" json:"orderSubmitRoi" description:"订单提交 ROI"`
OrderSubmitAmt *int64 `orm:"order_submit_amt" json:"orderSubmitAmt" description:"外部订单金额"`
EventOrderSubmitCost *float64 `orm:"event_order_submit_cost" json:"eventOrderSubmitCost" description:"订单提交成本"`
EventOrderSubmit *int64 `orm:"event_order_submit" json:"eventOrderSubmit" description:"订单提交数"`
EventOrderPaidRoi *float64 `orm:"event_order_paid_roi" json:"eventOrderPaidRoi" description:"订单支付率"`
EventAppInvoked *int64 `orm:"event_app_invoked" json:"eventAppInvoked" description:"唤起应用数"`
EventAddShoppingCart *int64 `orm:"event_add_shopping_cart" json:"eventAddShoppingCart" description:"添加购物车次数"`
ConversionNumCost *float64 `orm:"conversion_num_cost" json:"conversionNumCost" description:"转化成本"`
AdEffectivePlayNum *int64 `orm:"ad_effective_play_num" json:"adEffectivePlayNum" description:"有效播放数"`
AdItemClick *int64 `orm:"ad_item_click" json:"adItemClick" description:"行为数"`
// 花费相关字段
CostTotal *float64 `orm:"cost_total" json:"costTotal" description:"花费"`
AdShow *int64 `orm:"ad_show" json:"adShow" description:"曝光数"`
AdShow1kCost *float64 `orm:"ad_show1k_cost" json:"adShow1kCost" description:"平均千次广告曝光花费"`
// 封面相关字段
Impression *int64 `orm:"impression" json:"impression" description:"封面曝光数"`
PhotoClick *int64 `orm:"photo_click" json:"photoClick" description:"封面点击数"`
PhotoClickRatio *float64 `orm:"photo_click_ratio" json:"photoClickRatio" description:"封面点击率"`
// 点击相关字段
Click *int64 `orm:"click" json:"click" description:"素材曝光数"`
ActionbarClick *int64 `orm:"actionbar_click" json:"actionbarClick" description:"行为数"`
ActionbarClickCost *float64 `orm:"actionbar_click_cost" json:"actionbarClickCost" description:"行为成本"`
EspClickRatio *float64 `orm:"esp_click_ratio" json:"espClickRatio" description:"行为率"`
ActionRatio *float64 `orm:"action_ratio" json:"actionRatio" description:"素材点击率"`
AdItemClickCount *int64 `orm:"ad_item_click_count" json:"adItemClickCount" description:"预约组件点击数"`
// 直播时长字段
EspLivePlayedSeconds *int64 `orm:"esp_live_played_seconds" json:"espLivePlayedSeconds" description:"直播平均观看时长"`
// 播放数据字段
PlayedThreeSeconds *int64 `orm:"played_three_seconds" json:"playedThreeSeconds" description:"作品 3 秒播放数"`
Play3sRatio *float64 `orm:"play3s_ratio" json:"play3sRatio" description:"作品 3 秒播放率"`
PlayedFiveSeconds *int64 `orm:"played_five_seconds" json:"playedFiveSeconds" description:"作品 5 秒播放数"`
Play5sRatio *float64 `orm:"play5s_ratio" json:"play5sRatio" description:"作品 5 秒播放率"`
PlayedEnd *int64 `orm:"played_end" json:"playedEnd" description:"作品完播数"`
PlayEndRatio *float64 `orm:"play_end_ratio" json:"playEndRatio" description:"作品完播率"`
// 互动数据字段
Share *int64 `orm:"share" json:"share" description:"作品分享数"`
Comment *int64 `orm:"comment" json:"comment" description:"作品评论数"`
Likes *int64 `orm:"likes" json:"likes" description:"作品点赞数"`
Report *int64 `orm:"report" json:"report" description:"作品举报数"`
Block *int64 `orm:"block" json:"block" description:"作品拉黑数"`
ItemNegative *int64 `orm:"item_negative" json:"itemNegative" description:"详情页减少此类作品数"`
// 直播互动字段
LiveShare *int64 `orm:"live_share" json:"liveShare" description:"直播送礼数"`
LiveComment *int64 `orm:"live_comment" json:"liveComment" description:"直播评论数"`
LiveReward *int64 `orm:"live_reward" json:"liveReward" description:"直播送礼数"`
// 播放效果字段
EffectivePlayCount *int64 `orm:"effective_play_count" json:"effectivePlayCount" description:"有效播放数"`
EffectivePlayRatio *float64 `orm:"effective_play_ratio" json:"effectivePlayRatio" description:"有效播放率"`
// 转化字段
ConversionNum *int64 `orm:"conversion_num" json:"conversionNum" description:"转化数"`
ConversionCostEsp *float64 `orm:"conversion_cost_esp" json:"conversionCostEsp" description:"转化成本"`
Roi *float64 `orm:"roi" json:"roi" description:"直接 ROI"`
Gmv *float64 `orm:"gmv" json:"gmv" description:"直接 GMV"`
// GMV 时间序列字段
T0Gmv *float64 `orm:"t0_gmv" json:"t0Gmv" description:"当日累计 GMV"`
T1Gmv *float64 `orm:"t1_gmv" json:"t1Gmv" description:"次日累计 GMV"`
T3Gmv *float64 `orm:"t3_gmv" json:"t3Gmv" description:"3 日累计 GMV"`
T7Gmv *float64 `orm:"t7_gmv" json:"t7Gmv" description:"7 日累计 GMV"`
T15Gmv *float64 `orm:"t15_gmv" json:"t15Gmv" description:"15 日累计 GMV"`
T30Gmv *float64 `orm:"t30_gmv" json:"t30Gmv" description:"30 日累计 GMV"`
// ROI 时间序列字段
T0Roi *float64 `orm:"t0_roi" json:"t0Roi" description:"当日累计 ROI"`
T1Roi *float64 `orm:"t1_roi" json:"t1Roi" description:"次日累计 ROI"`
T3Roi *float64 `orm:"t3_roi" json:"t3Roi" description:"3 日累计 ROI"`
T7Roi *float64 `orm:"t7_roi" json:"t7Roi" description:"7 日累计 ROI"`
T15Roi *float64 `orm:"t15_roi" json:"t15Roi" description:"15 日累计 ROI"`
T30Roi *float64 `orm:"t30_roi" json:"t30Roi" description:"30 日累计 ROI"`
// 订单相关字段
PaiedOrder *int64 `orm:"paied_order" json:"paiedOrder" description:"直接订单数"`
OrderRatio *float64 `orm:"order_ratio" json:"orderRatio" description:"直接下单率"`
T0OrderCnt *int64 `orm:"t0_order_cnt" json:"t0OrderCnt" description:"当日累计订单数"`
T0OrderCntCost *float64 `orm:"t0_order_cnt_cost" json:"t0OrderCntCost" description:"当日累计订单成本"`
T0OrderCntRatio *float64 `orm:"t0_order_cnt_ratio" json:"t0OrderCntRatio" description:"累计订单下单率"`
T1OrderCnt *int64 `orm:"t1_order_cnt" json:"t1OrderCnt" description:"次日累计订单数"`
T7OrderCnt *int64 `orm:"t7_order_cnt" json:"t7OrderCnt" description:"7 日累计订单数"`
T15OrderCnt *int64 `orm:"t15_order_cnt" json:"t15OrderCnt" description:"15 日累计订单数"`
T30OrderCnt *int64 `orm:"t30_order_cnt" json:"t30OrderCnt" description:"30 日累计订单数"`
// 粉丝相关字段
MerchantRecoFans *int64 `orm:"merchant_reco_fans" json:"merchantRecoFans" description:"涨粉数"`
T1Retention *float64 `orm:"t1_retention" json:"t1Retention" description:"次日涨粉留存数"`
T7Retention *float64 `orm:"t7_retention" json:"t7Retention" description:"7 日涨粉留存数"`
T15Retention *float64 `orm:"t15_retention" json:"t15Retention" description:"15 日涨粉留存数"`
T30Retention *float64 `orm:"t30_retention" json:"t30Retention" description:"30 日涨粉留存数"`
T1RetentionRatio *float64 `orm:"t1_retention_ratio" json:"t1RetentionRatio" description:"次日涨粉留存率"`
T7RetentionRatio *float64 `orm:"t7_retention_ratio" json:"t7RetentionRatio" description:"7 日涨粉留存率"`
T15RetentionRatio *float64 `orm:"t15_retention_ratio" json:"t15RetentionRatio" description:"15 日涨粉留存率"`
T30RetentionRatio *float64 `orm:"t30_retention_ratio" json:"t30RetentionRatio" description:"30 日涨粉留存率"`
// 直播预约字段
ReservationSuccess *int64 `orm:"reservation_success" json:"reservationSuccess" description:"直播预约成功数"`
ReservationCost *float64 `orm:"reservation_cost" json:"reservationCost" description:"直播预约成功成本"`
// 直播观看字段
StandardLivePlayedStarted *int64 `orm:"standard_live_played_started" json:"standardLivePlayedStarted" description:"直播观看数"`
AdLivePlayCnt *int64 `orm:"ad_live_play_cnt" json:"adLivePlayCnt" description:"直播间人气数"`
AdLivePlayCntCost *float64 `orm:"ad_live_play_cnt_cost" json:"adLivePlayCntCost" description:"直播间人气成本"`
LiveAudienceCost *float64 `orm:"live_audience_cost" json:"liveAudienceCost" description:"直播观看成本"`
// 商品点击字段
LiveEventGoodsView *int64 `orm:"live_event_goods_view" json:"liveEventGoodsView" description:"直播间商品点击数"`
GoodsClickRatio *float64 `orm:"goods_click_ratio" json:"goodsClickRatio" description:"直播间商品点击率"`
// 新客相关字段
DirectAttrPlatNewBuyerCnt *int64 `orm:"direct_attr_plat_new_buyer_cnt" json:"directAttrPlatNewBuyerCnt" description:"直接平台新客"`
T30AttrPlatTotalBuyerCnt *int64 `orm:"t30_attr_plat_total_buyer_cnt" json:"t30AttrPlatTotalBuyerCnt" description:"30 日累计平台新客"`
DirectAttrSellerNewBuyerCnt *int64 `orm:"direct_attr_seller_new_buyer_cnt" json:"directAttrSellerNewBuyerCnt" description:"直接店铺新客"`
T30AttrSellerTotalBuyerCnt *int64 `orm:"t30_attr_seller_total_buyer_cnt" json:"t30AttrSellerTotalBuyerCnt" description:"30 日累计店铺新客"`
// 间接订单字段
T7IndirectOrderAmt *float64 `orm:"t7_indirect_order_amt" json:"t7IndirectOrderAmt" description:"7 日间接订单金额"`
T7IndirectOrderCnt *int64 `orm:"t7_indirect_order_cnt" json:"t7IndirectOrderCnt" description:"7 日间接订单数"`
// 粉丝人均销售额字段
FansT0GmvPerFans *float64 `orm:"fans_t0_gmv_per_fans" json:"fansT0GmvPerFans" description:"新增粉丝人均销售额"`
FansT3GmvPerFans *float64 `orm:"fans_t3_gmv_per_fans" json:"fansT3GmvPerFans" description:"3 日新增粉丝人均销售额"`
FansT7GmvPerFans *float64 `orm:"fans_t7_gmv_per_fans" json:"fansT7GmvPerFans" description:"7 日新增粉丝人均销售额"`
FansT15GmvPerFans *float64 `orm:"fans_t15_gmv_per_fans" json:"fansT15GmvPerFans" description:"15 日新增粉丝人均销售额"`
FansT30GmvPerFans *float64 `orm:"fans_t30_gmv_per_fans" json:"fansT30GmvPerFans" description:"30 日新增粉丝人均销售额"`
// 涨粉成本字段
RecoFansCost *float64 `orm:"reco_fans_cost" json:"recoFansCost" description:"涨粉成本"`
// 智能优惠券字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `orm:"qcpx_whitebox_direct_order_payment_amt" json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `orm:"qcpx_whitebox_direct_order_cnt" json:"qcpxWhiteboxDirectOrderCnt" description:"智能优惠券订单数"`
// 粉丝 GMV 字段
FansT0Gmv *float64 `orm:"fans_t0_gmv" json:"fansT0Gmv" description:"涨粉当日 GMV"`
FansT1Gmv *float64 `orm:"fans_t1_gmv" json:"fansT1Gmv" description:"涨粉次日 GMV"`
FansT7Gmv *float64 `orm:"fans_t7_gmv" json:"fansT7Gmv" description:"涨粉 7 日 GMV"`
FansT15Gmv *float64 `orm:"fans_t15_gmv" json:"fansT15Gmv" description:"涨粉 15 日 GMV"`
FansT30Gmv *float64 `orm:"fans_t30_gmv" json:"fansT30Gmv" description:"涨粉 30 日 GMV"`
// 粉丝 ROI 字段
FansT0Roi *float64 `orm:"fans_t0_roi" json:"fansT0Roi" description:"涨粉当日 ROI"`
FansT1Roi *float64 `orm:"fans_t1_roi" json:"fansT1Roi" description:"涨粉次日 ROI"`
FansT7Roi *float64 `orm:"fans_t7_roi" json:"fansT7Roi" description:"涨粉 7 日 ROI"`
FansT15Roi *float64 `orm:"fans_t15_roi" json:"fansT15Roi" description:"涨粉 15 日 ROI"`
FansT30Roi *float64 `orm:"fans_t30_roi" json:"fansT30Roi" description:"涨粉 30 日 ROI"`
// 新客 GMV 字段
T0ShopNewBuyerOrderPaymentAmt *float64 `orm:"t0_shop_new_buyer_order_payment_amt" json:"t0ShopNewBuyerOrderPaymentAmt" description:"当日新客 GMV"`
T1ShopNewBuyerOrderPaymentAmt *float64 `orm:"t1_shop_new_buyer_order_payment_amt" json:"t1ShopNewBuyerOrderPaymentAmt" description:"投后 1 日新客 GMV"`
T3ShopNewBuyerOrderPaymentAmt *float64 `orm:"t3_shop_new_buyer_order_payment_amt" json:"t3ShopNewBuyerOrderPaymentAmt" description:"投后 3 日新客 GMV"`
T7ShopNewBuyerOrderPaymentAmt *float64 `orm:"t7_shop_new_buyer_order_payment_amt" json:"t7ShopNewBuyerOrderPaymentAmt" description:"投后 7 日新客 GMV"`
T15ShopNewBuyerOrderPaymentAmt *float64 `orm:"t15_shop_new_buyer_order_payment_amt" json:"t15ShopNewBuyerOrderPaymentAmt" description:"投后 15 日新客 GMV"`
T30ShopNewBuyerOrderPaymentAmt *float64 `orm:"t30_shop_new_buyer_order_payment_amt" json:"t30ShopNewBuyerOrderPaymentAmt" description:"投后 30 日新客 GMV"`
// 新客订单量字段
T0ShopNewBuyerOrderCnt *int64 `orm:"t0_shop_new_buyer_order_cnt" json:"t0ShopNewBuyerOrderCnt" description:"当日新客成交订单量"`
T1ShopNewBuyerOrderCnt *int64 `orm:"t1_shop_new_buyer_order_cnt" json:"t1ShopNewBuyerOrderCnt" description:"投后 1 日新客成交订单量"`
T3ShopNewBuyerOrderCnt *int64 `orm:"t3_shop_new_buyer_order_cnt" json:"t3ShopNewBuyerOrderCnt" description:"投后 3 日新客成交订单量"`
T7ShopNewBuyerOrderCnt *int64 `orm:"t7_shop_new_buyer_order_cnt" json:"t7ShopNewBuyerOrderCnt" description:"投后 7 日新客成交订单量"`
T15ShopNewBuyerOrderCnt *int64 `orm:"t15_shop_new_buyer_order_cnt" json:"t15ShopNewBuyerOrderCnt" description:"投后 15 日新客成交订单量"`
T30ShopNewBuyerOrderCnt *int64 `orm:"t30_shop_new_buyer_order_cnt" json:"t30ShopNewBuyerOrderCnt" description:"投后 30 日新客成交订单量"`
// 新客复购率字段
T1NewBuyerRepurchaseRatio *float64 `orm:"t1_new_buyer_repurchase_ratio" json:"t1NewBuyerRepurchaseRatio" description:"投后 1 日新客复购率"`
T3NewBuyerRepurchaseRatio *float64 `orm:"t3_new_buyer_repurchase_ratio" json:"t3NewBuyerRepurchaseRatio" description:"投后 3 日新客复购率"`
T7NewBuyerRepurchaseRatio *float64 `orm:"t7_new_buyer_repurchase_ratio" json:"t7NewBuyerRepurchaseRatio" description:"投后 7 日新客复购率"`
T15NewBuyerRepurchaseRatio *float64 `orm:"t15_new_buyer_repurchase_ratio" json:"t15NewBuyerRepurchaseRatio" description:"投后 15 日新客复购率"`
T30NewBuyerRepurchaseRatio *float64 `orm:"t30_new_buyer_repurchase_ratio" json:"t30NewBuyerRepurchaseRatio" description:"投后 30 日新客复购率"`
// 新客 ROI 字段
T0ShopNewBuyerRoi *float64 `orm:"t0_shop_new_buyer_roi" json:"t0ShopNewBuyerRoi" description:"投后当日新客 ROI"`
T1ShopNewBuyerRoi *float64 `orm:"t1_shop_new_buyer_roi" json:"t1ShopNewBuyerRoi" description:"投后 1 日新客 ROI"`
T3ShopNewBuyerRoi *float64 `orm:"t3_shop_new_buyer_roi" json:"t3ShopNewBuyerRoi" description:"投后 3 日新客 ROI"`
T7ShopNewBuyerRoi *float64 `orm:"t7_shop_new_buyer_roi" json:"t7ShopNewBuyerRoi" description:"投后 7 日新客 ROI"`
T15ShopNewBuyerRoi *float64 `orm:"t15_shop_new_buyer_roi" json:"t15ShopNewBuyerRoi" description:"投后 15 日新客 ROI"`
T30ShopNewBuyerRoi *float64 `orm:"t30_shop_new_buyer_roi" json:"t30ShopNewBuyerRoi" description:"投后 30 日新客 ROI"`
// 电话卡相关字段
CreateCardOrderCnt *int64 `orm:"create_card_order_cnt" json:"createCardOrderCnt" description:"有效制卡订单数(回传时间)"`
ForwardTsCreateCardOrderCnt *int64 `orm:"forward_ts_create_card_order_cnt" json:"forwardTsCreateCardOrderCnt" description:"有效制卡订单数(计费时间)"`
CreateCardOrderCost *float64 `orm:"create_card_order_cost" json:"createCardOrderCost" description:"有效制卡订单成本(回传时间)"`
ForwardTsCreateCardOrderCost *float64 `orm:"forward_ts_create_card_order_cost" json:"forwardTsCreateCardOrderCost" description:"有效制卡订单成本(计费时间)"`
ActivateCardOrderCnt *int64 `orm:"activate_card_order_cnt" json:"activateCardOrderCnt" description:"电话卡激活订单数(回传时间)"`
ForwardTsActivateCardOrderCnt *int64 `orm:"forward_ts_activate_card_order_cnt" json:"forwardTsActivateCardOrderCnt" description:"电话卡激活订单数(计费时间)"`
ActivateCardOrderCost *float64 `orm:"activate_card_order_cost" json:"activateCardOrderCost" description:"电话卡激活订单成本(回传时间)"`
ForwardTsActivateCardOrderCost *float64 `orm:"forward_ts_activate_card_order_cost" json:"forwardTsActivateCardOrderCost" description:"电话卡激活订单成本(计费时间)"`
CreateCardOrderRatio *float64 `orm:"create_card_order_ratio" json:"createCardOrderRatio" description:"有效制卡订单率(回传时间)"`
ForwardTsCreateCardOrderRatio *float64 `orm:"forward_ts_create_card_order_ratio" json:"forwardTsCreateCardOrderRatio" description:"有效制卡订单率(计费时间)"`
ActivateCardOrderCntRatio *float64 `orm:"activate_card_order_cnt_ratio" json:"activateCardOrderCntRatio" description:"电话卡激活率(回传时间)"`
ForwardTsActivateCardOrderRatio *float64 `orm:"forward_ts_activate_card_order_ratio" json:"forwardTsActivateCardOrderRatio" description:"电话卡激活率(计费时间)"`
// 全站数据字段
LivePlayCnt *int64 `orm:"live_play_cnt" json:"livePlayCnt" description:"全站直播观看数"`
ItemEntranceClkCnt *int64 `orm:"item_entrance_clk_cnt" json:"itemEntranceClkCnt" description:"小黄车点击数"`
ShowCnt *int64 `orm:"show_cnt" json:"showCnt" description:"全站曝光"`
}
// PopulationReportCol 人群报表字段定义
type PopulationReportCol struct {
beans.SQLBaseCol
PhotoName string
PhotoIdStr string
PhotoId string
ModPriceSegment string
AgeSegment string
Province string
Gender string
MerchantProductId string
ReportDateStr string
CampaignId string
CampaignName string
UnitId string
UnitName string
CreativeId string
CreativeName string
AdPhotoPlayedFiveRatio string
AdPhotoPlayedThreeRatio string
OrderSubmitRoi string
OrderSubmitAmt string
EventOrderSubmitCost string
EventOrderSubmit string
EventOrderPaidRoi string
EventAppInvoked string
EventAddShoppingCart string
ConversionNumCost string
AdEffectivePlayNum string
AdItemClick string
CostTotal string
AdShow string
AdShow1kCost string
Impression string
PhotoClick string
PhotoClickRatio string
Click string
ActionbarClick string
ActionbarClickCost string
EspClickRatio string
ActionRatio string
AdItemClickCount string
EspLivePlayedSeconds string
PlayedThreeSeconds string
Play3sRatio string
PlayedFiveSeconds string
Play5sRatio string
PlayedEnd string
PlayEndRatio string
Share string
Comment string
Likes string
Report string
Block string
ItemNegative string
LiveShare string
LiveComment string
LiveReward string
EffectivePlayCount string
EffectivePlayRatio string
ConversionNum string
ConversionCostEsp string
Roi string
Gmv string
T0Gmv string
T1Gmv string
T3Gmv string
T7Gmv string
T15Gmv string
T30Gmv string
T0Roi string
T1Roi string
T3Roi string
T7Roi string
T15Roi string
T30Roi string
PaiedOrder string
OrderRatio string
T0OrderCnt string
T0OrderCntCost string
T0OrderCntRatio string
T1OrderCnt string
T7OrderCnt string
T15OrderCnt string
T30OrderCnt string
MerchantRecoFans string
T1Retention string
T7Retention string
T15Retention string
T30Retention string
T1RetentionRatio string
T7RetentionRatio string
T15RetentionRatio string
T30RetentionRatio string
ReservationSuccess string
ReservationCost string
StandardLivePlayedStarted string
AdLivePlayCnt string
AdLivePlayCntCost string
LiveAudienceCost string
LiveEventGoodsView string
GoodsClickRatio string
DirectAttrPlatNewBuyerCnt string
T30AttrPlatTotalBuyerCnt string
DirectAttrSellerNewBuyerCnt string
T30AttrSellerTotalBuyerCnt string
T7IndirectOrderAmt string
T7IndirectOrderCnt string
FansT0GmvPerFans string
FansT3GmvPerFans string
FansT7GmvPerFans string
FansT15GmvPerFans string
FansT30GmvPerFans string
RecoFansCost string
QcpxWhiteboxDirectOrderPaymentAmt string
QcpxWhiteboxDirectOrderCnt string
FansT0Gmv string
FansT1Gmv string
FansT7Gmv string
FansT15Gmv string
FansT30Gmv string
FansT0Roi string
FansT1Roi string
FansT7Roi string
FansT15Roi string
FansT30Roi string
T0ShopNewBuyerOrderPaymentAmt string
T1ShopNewBuyerOrderPaymentAmt string
T3ShopNewBuyerOrderPaymentAmt string
T7ShopNewBuyerOrderPaymentAmt string
T15ShopNewBuyerOrderPaymentAmt string
T30ShopNewBuyerOrderPaymentAmt string
T0ShopNewBuyerOrderCnt string
T1ShopNewBuyerOrderCnt string
T3ShopNewBuyerOrderCnt string
T7ShopNewBuyerOrderCnt string
T15ShopNewBuyerOrderCnt string
T30ShopNewBuyerOrderCnt string
T1NewBuyerRepurchaseRatio string
T3NewBuyerRepurchaseRatio string
T7NewBuyerRepurchaseRatio string
T15NewBuyerRepurchaseRatio string
T30NewBuyerRepurchaseRatio string
T0ShopNewBuyerRoi string
T1ShopNewBuyerRoi string
T3ShopNewBuyerRoi string
T7ShopNewBuyerRoi string
T15ShopNewBuyerRoi string
T30ShopNewBuyerRoi string
CreateCardOrderCnt string
ForwardTsCreateCardOrderCnt string
CreateCardOrderCost string
ForwardTsCreateCardOrderCost string
ActivateCardOrderCnt string
ForwardTsActivateCardOrderCnt string
ActivateCardOrderCost string
ForwardTsActivateCardOrderCost string
CreateCardOrderRatio string
ForwardTsCreateCardOrderRatio string
ActivateCardOrderCntRatio string
ForwardTsActivateCardOrderRatio string
LivePlayCnt string
ItemEntranceClkCnt string
ShowCnt string
}
// TableName 返回表名
func (e *PopulationReport) TableName() string {
return "population_report"
}
// GetCols 获取所有字段名
func (e *PopulationReport) GetCols() *PopulationReportCol {
return &PopulationReportCol{
SQLBaseCol: beans.SQLBaseCol{
Id: "id",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
PhotoName: "photo_name",
PhotoIdStr: "photo_id_str",
PhotoId: "photo_id",
ModPriceSegment: "mod_price_segment",
AgeSegment: "age_segment",
Province: "province",
Gender: "gender",
MerchantProductId: "merchant_product_id",
ReportDateStr: "report_date_str",
CampaignId: "campaign_id",
CampaignName: "campaign_name",
UnitId: "unit_id",
UnitName: "unit_name",
CreativeId: "creative_id",
CreativeName: "creative_name",
AdPhotoPlayedFiveRatio: "ad_photo_played_five_ratio",
AdPhotoPlayedThreeRatio: "ad_photo_played_three_ratio",
OrderSubmitRoi: "order_submit_roi",
OrderSubmitAmt: "order_submit_amt",
EventOrderSubmitCost: "event_order_submit_cost",
EventOrderSubmit: "event_order_submit",
EventOrderPaidRoi: "event_order_paid_roi",
EventAppInvoked: "event_app_invoked",
EventAddShoppingCart: "event_add_shopping_cart",
ConversionNumCost: "conversion_num_cost",
AdEffectivePlayNum: "ad_effective_play_num",
AdItemClick: "ad_item_click",
CostTotal: "cost_total",
AdShow: "ad_show",
AdShow1kCost: "ad_show1k_cost",
Impression: "impression",
PhotoClick: "photo_click",
PhotoClickRatio: "photo_click_ratio",
Click: "click",
ActionbarClick: "actionbar_click",
ActionbarClickCost: "actionbar_click_cost",
EspClickRatio: "esp_click_ratio",
ActionRatio: "action_ratio",
AdItemClickCount: "ad_item_click_count",
EspLivePlayedSeconds: "esp_live_played_seconds",
PlayedThreeSeconds: "played_three_seconds",
Play3sRatio: "play3s_ratio",
PlayedFiveSeconds: "played_five_seconds",
Play5sRatio: "play5s_ratio",
PlayedEnd: "played_end",
PlayEndRatio: "play_end_ratio",
Share: "share",
Comment: "comment",
Likes: "likes",
Report: "report",
Block: "block",
ItemNegative: "item_negative",
LiveShare: "live_share",
LiveComment: "live_comment",
LiveReward: "live_reward",
EffectivePlayCount: "effective_play_count",
EffectivePlayRatio: "effective_play_ratio",
ConversionNum: "conversion_num",
ConversionCostEsp: "conversion_cost_esp",
Roi: "roi",
Gmv: "gmv",
T0Gmv: "t0_gmv",
T1Gmv: "t1_gmv",
T3Gmv: "t3_gmv",
T7Gmv: "t7_gmv",
T15Gmv: "t15_gmv",
T30Gmv: "t30_gmv",
T0Roi: "t0_roi",
T1Roi: "t1_roi",
T3Roi: "t3_roi",
T7Roi: "t7_roi",
T15Roi: "t15_roi",
T30Roi: "t30_roi",
PaiedOrder: "paied_order",
OrderRatio: "order_ratio",
T0OrderCnt: "t0_order_cnt",
T0OrderCntCost: "t0_order_cnt_cost",
T0OrderCntRatio: "t0_order_cnt_ratio",
T1OrderCnt: "t1_order_cnt",
T7OrderCnt: "t7_order_cnt",
T15OrderCnt: "t15_order_cnt",
T30OrderCnt: "t30_order_cnt",
MerchantRecoFans: "merchant_reco_fans",
T1Retention: "t1_retention",
T7Retention: "t7_retention",
T15Retention: "t15_retention",
T30Retention: "t30_retention",
T1RetentionRatio: "t1_retention_ratio",
T7RetentionRatio: "t7_retention_ratio",
T15RetentionRatio: "t15_retention_ratio",
T30RetentionRatio: "t30_retention_ratio",
ReservationSuccess: "reservation_success",
ReservationCost: "reservation_cost",
StandardLivePlayedStarted: "standard_live_played_started",
AdLivePlayCnt: "ad_live_play_cnt",
AdLivePlayCntCost: "ad_live_play_cnt_cost",
LiveAudienceCost: "live_audience_cost",
LiveEventGoodsView: "live_event_goods_view",
GoodsClickRatio: "goods_click_ratio",
DirectAttrPlatNewBuyerCnt: "direct_attr_plat_new_buyer_cnt",
T30AttrPlatTotalBuyerCnt: "t30_attr_plat_total_buyer_cnt",
DirectAttrSellerNewBuyerCnt: "direct_attr_seller_new_buyer_cnt",
T30AttrSellerTotalBuyerCnt: "t30_attr_seller_total_buyer_cnt",
T7IndirectOrderAmt: "t7_indirect_order_amt",
T7IndirectOrderCnt: "t7_indirect_order_cnt",
FansT0GmvPerFans: "fans_t0_gmv_per_fans",
FansT3GmvPerFans: "fans_t3_gmv_per_fans",
FansT7GmvPerFans: "fans_t7_gmv_per_fans",
FansT15GmvPerFans: "fans_t15_gmv_per_fans",
FansT30GmvPerFans: "fans_t30_gmv_per_fans",
RecoFansCost: "reco_fans_cost",
QcpxWhiteboxDirectOrderPaymentAmt: "qcpx_whitebox_direct_order_payment_amt",
QcpxWhiteboxDirectOrderCnt: "qcpx_whitebox_direct_order_cnt",
FansT0Gmv: "fans_t0_gmv",
FansT1Gmv: "fans_t1_gmv",
FansT7Gmv: "fans_t7_gmv",
FansT15Gmv: "fans_t15_gmv",
FansT30Gmv: "fans_t30_gmv",
FansT0Roi: "fans_t0_roi",
FansT1Roi: "fans_t1_roi",
FansT7Roi: "fans_t7_roi",
FansT15Roi: "fans_t15_roi",
FansT30Roi: "fans_t30_roi",
T0ShopNewBuyerOrderPaymentAmt: "t0_shop_new_buyer_order_payment_amt",
T1ShopNewBuyerOrderPaymentAmt: "t1_shop_new_buyer_order_payment_amt",
T3ShopNewBuyerOrderPaymentAmt: "t3_shop_new_buyer_order_payment_amt",
T7ShopNewBuyerOrderPaymentAmt: "t7_shop_new_buyer_order_payment_amt",
T15ShopNewBuyerOrderPaymentAmt: "t15_shop_new_buyer_order_payment_amt",
T30ShopNewBuyerOrderPaymentAmt: "t30_shop_new_buyer_order_payment_amt",
T0ShopNewBuyerOrderCnt: "t0_shop_new_buyer_order_cnt",
T1ShopNewBuyerOrderCnt: "t1_shop_new_buyer_order_cnt",
T3ShopNewBuyerOrderCnt: "t3_shop_new_buyer_order_cnt",
T7ShopNewBuyerOrderCnt: "t7_shop_new_buyer_order_cnt",
T15ShopNewBuyerOrderCnt: "t15_shop_new_buyer_order_cnt",
T30ShopNewBuyerOrderCnt: "t30_shop_new_buyer_order_cnt",
T1NewBuyerRepurchaseRatio: "t1_new_buyer_repurchase_ratio",
T3NewBuyerRepurchaseRatio: "t3_new_buyer_repurchase_ratio",
T7NewBuyerRepurchaseRatio: "t7_new_buyer_repurchase_ratio",
T15NewBuyerRepurchaseRatio: "t15_new_buyer_repurchase_ratio",
T30NewBuyerRepurchaseRatio: "t30_new_buyer_repurchase_ratio",
T0ShopNewBuyerRoi: "t0_shop_new_buyer_roi",
T1ShopNewBuyerRoi: "t1_shop_new_buyer_roi",
T3ShopNewBuyerRoi: "t3_shop_new_buyer_roi",
T7ShopNewBuyerRoi: "t7_shop_new_buyer_roi",
T15ShopNewBuyerRoi: "t15_shop_new_buyer_roi",
T30ShopNewBuyerRoi: "t30_shop_new_buyer_roi",
CreateCardOrderCnt: "create_card_order_cnt",
ForwardTsCreateCardOrderCnt: "forward_ts_create_card_order_cnt",
CreateCardOrderCost: "create_card_order_cost",
ForwardTsCreateCardOrderCost: "forward_ts_create_card_order_cost",
ActivateCardOrderCnt: "activate_card_order_cnt",
ForwardTsActivateCardOrderCnt: "forward_ts_activate_card_order_cnt",
ActivateCardOrderCost: "activate_card_order_cost",
ForwardTsActivateCardOrderCost: "forward_ts_activate_card_order_cost",
CreateCardOrderRatio: "create_card_order_ratio",
ForwardTsCreateCardOrderRatio: "forward_ts_create_card_order_ratio",
ActivateCardOrderCntRatio: "activate_card_order_cnt_ratio",
ForwardTsActivateCardOrderRatio: "forward_ts_activate_card_order_ratio",
LivePlayCnt: "live_play_cnt",
ItemEntranceClkCnt: "item_entrance_clk_cnt",
ShowCnt: "show_cnt",
}
}
@@ -1,181 +0,0 @@
package copydata
import "gitea.com/red-future/common/beans"
// StorewideReportDetail 广告效果指标表实体
type StorewideReportDetail struct {
beans.SQLBaseDO `orm:",inherit"`
// 订单金额相关字段
T0OrderPaymentAmt *float64 `orm:"t0_order_payment_amt" json:"t0OrderPaymentAmt" description:"当日总成交订单金额"`
// 素材类型字段
CreativeMaterialType string `orm:"creative_material_type" json:"creativeMaterialType" description:"视频素材类型(视频:HORIZONTAL_SCREEN, VERTICAL_SCREEN, UNKNOWN_TYPE;图集:ATLAS;长图:ATLAS_VERTICAL"`
// 直播相关字段
LiveName string `orm:"live_name" json:"liveName" description:"直播间名称"`
AuthorId string `orm:"author_id" json:"authorId" description:"直播用户快手 Id"`
// 图片相关字段
PicUrl string `orm:"pic_url" json:"picUrl" description:"图片 URL"`
PicName string `orm:"pic_name" json:"picName" description:"图片名称"`
PicId string `orm:"pic_id" json:"picId" description:"图片 Id"`
// 封面相关字段
CoverUrl string `orm:"cover_url" json:"coverUrl" description:"封面 URL"`
CoverId *int64 `orm:"cover_id" json:"coverId" description:"封面 Id"`
// 商品卡点击相关字段
ItemCardClkCnt *int64 `orm:"item_card_clk_cnt" json:"itemCardClkCnt" description:"商品卡点击数"`
// 净成交相关字段
NetT0OrderCnt *int64 `orm:"net_t0_order_cnt" json:"netT0OrderCnt" description:"当日累计净成交订单数"`
NetT0Roi *float64 `orm:"net_t0_roi" json:"netT0Roi" description:"净成交 ROI"`
NetT0Gmv *float64 `orm:"net_t0_gmv" json:"netT0Gmv" description:"净成交 GMV"`
// 视频相关字段
PhotoName string `orm:"photo_name" json:"photoName" description:"视频名称"`
PhotoIdStr string `orm:"photo_id_str" json:"photoIdStr" description:"视频 id"`
PhotoId string `orm:"photo_id" json:"photoId" description:"视频 id"`
// 行为相关字段
AdItemClick *int64 `orm:"ad_item_click" json:"adItemClick" description:"行为数"`
// 商品相关字段
MerchantProductId string `orm:"merchant_product_id" json:"merchantProductId" description:"商品 ID"`
// 花费相关字段
CostTotal *float64 `orm:"cost_total" json:"costTotal" description:"花费"`
// 行为率字段
EspClickRatio *float64 `orm:"esp_click_ratio" json:"espClickRatio" description:"行为率"`
// 当日 GMV 相关字段
T0Gmv *float64 `orm:"t0_gmv" json:"t0Gmv" description:"当日累计 GMV"`
T0Roi *float64 `orm:"t0_roi" json:"t0Roi" description:"当日累计 ROI"`
// 当日订单相关字段
T0OrderCnt *int64 `orm:"t0_order_cnt" json:"t0OrderCnt" description:"当日累计订单数"`
T0OrderCntCost *float64 `orm:"t0_order_cnt_cost" json:"t0OrderCntCost" description:"当日累计订单成本"`
// 智能优惠券相关字段
QcpxWhiteboxDirectOrderPaymentAmt *float64 `orm:"qcpx_whitebox_direct_order_payment_amt" json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `orm:"qcpx_whitebox_direct_order_cnt" json:"qcpxWhiteboxDirectOrderCnt" description:"智能优惠券订单数"`
// 直播观看相关字段
LivePlayCnt *int64 `orm:"live_play_cnt" json:"livePlayCnt" description:"全站直播观看数"`
// 商品入口点击相关字段
ItemEntranceClkCnt *int64 `orm:"item_entrance_clk_cnt" json:"itemEntranceClkCnt" description:"小黄车点击数"`
// 曝光相关字段
ShowCnt *int64 `orm:"show_cnt" json:"showCnt" description:"全站曝光"`
// 报告日期字段
ReportDateStr string `orm:"report_date_str" json:"reportDateStr" description:"时间"`
// 广告计划相关字段
CampaignId *int64 `orm:"campaign_id" json:"campaignId" description:"计划 ID"`
CampaignName string `orm:"campaign_name" json:"campaignName" description:"计划名称"`
// 广告单元相关字段
UnitId *int64 `orm:"unit_id" json:"unitId" description:"单元 ID"`
UnitName string `orm:"unit_name" json:"unitName" description:"单元名称"`
// 广告创意相关字段
CreativeId *int64 `orm:"creative_id" json:"creativeId" description:"创意 ID"`
CreativeName string `orm:"creative_name" json:"creativeName" description:"创意名称"`
}
// StorewideReportDetailCol 广告效果指标表表字段定义
type StorewideReportDetailCol struct {
beans.SQLBaseCol
T0OrderPaymentAmt string
CreativeMaterialType string
LiveName string
AuthorId string
PicUrl string
PicName string
PicId string
CoverUrl string
CoverId string
ItemCardClkCnt string
NetT0OrderCnt string
NetT0Roi string
NetT0Gmv string
PhotoName string
PhotoIdStr string
PhotoId string
AdItemClick string
MerchantProductId string
CostTotal string
EspClickRatio string
T0Gmv string
T0Roi string
T0OrderCnt string
T0OrderCntCost string
QcpxWhiteboxDirectOrderPaymentAmt string
QcpxWhiteboxDirectOrderCnt string
LivePlayCnt string
ItemEntranceClkCnt string
ShowCnt string
ReportDateStr string
CampaignId string
CampaignName string
UnitId string
UnitName string
CreativeId string
CreativeName string
}
// TableName 返回表名
func (e *StorewideReportDetail) TableName() string {
return "storewide_report_detail"
}
// GetCols 获取所有字段名
func (e *StorewideReportDetail) GetCols() *StorewideReportDetailCol {
return &StorewideReportDetailCol{
SQLBaseCol: beans.SQLBaseCol{
Id: "id",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
T0OrderPaymentAmt: "t0_order_payment_amt",
CreativeMaterialType: "creative_material_type",
LiveName: "live_name",
AuthorId: "author_id",
PicUrl: "pic_url",
PicName: "pic_name",
PicId: "pic_id",
CoverUrl: "cover_url",
CoverId: "cover_id",
ItemCardClkCnt: "item_card_clk_cnt",
NetT0OrderCnt: "net_t0_order_cnt",
NetT0Roi: "net_t0_roi",
NetT0Gmv: "net_t0_gmv",
PhotoName: "photo_name",
PhotoIdStr: "photo_id_str",
PhotoId: "photo_id",
AdItemClick: "ad_item_click",
MerchantProductId: "merchant_product_id",
CostTotal: "cost_total",
EspClickRatio: "esp_click_ratio",
T0Gmv: "t0_gmv",
T0Roi: "t0_roi",
T0OrderCnt: "t0_order_cnt",
T0OrderCntCost: "t0_order_cnt_cost",
QcpxWhiteboxDirectOrderPaymentAmt: "qcpx_whitebox_direct_order_payment_amt",
QcpxWhiteboxDirectOrderCnt: "qcpx_whitebox_direct_order_cnt",
LivePlayCnt: "live_play_cnt",
ItemEntranceClkCnt: "item_entrance_clk_cnt",
ShowCnt: "show_cnt",
ReportDateStr: "report_date_str",
CampaignId: "campaign_id",
CampaignName: "campaign_name",
UnitId: "unit_id",
UnitName: "unit_name",
CreativeId: "creative_id",
CreativeName: "creative_name",
}
}
@@ -1,141 +0,0 @@
package copydata
import "gitea.com/red-future/common/beans"
// StorewideReportSum 广告效果指标表实体
type StorewideReportSum struct {
beans.SQLBaseDO `orm:",inherit"`
// 新增字段
T0OrderPaymentAmt *float64 `orm:"t0_order_payment_amt" json:"t0OrderPaymentAmt" description:"当日总成交订单金额"`
CreativeMaterialType string `orm:"creative_material_type" json:"creativeMaterialType" description:"视频素材类型"`
LiveName string `orm:"live_name" json:"liveName" description:"直播间名称"`
AuthorId string `orm:"author_id" json:"authorId" description:"直播用户快手 Id"`
PicUrl string `orm:"pic_url" json:"picUrl" description:"图片 URL"`
PicName string `orm:"pic_name" json:"picName" description:"图片名称"`
PicId string `orm:"pic_id" json:"picId" description:"图片 Id"`
CoverUrl string `orm:"cover_url" json:"coverUrl" description:"封面 URL"`
CoverId *int64 `orm:"cover_id" json:"coverId" description:"封面 Id"`
ItemCardClkCnt *int64 `orm:"item_card_clk_cnt" json:"itemCardClkCnt" description:"商品卡点击数"`
NetT0OrderCnt *int64 `orm:"net_t0_order_cnt" json:"netT0OrderCnt" description:"当日累计净成交订单数"`
NetT0Roi *float64 `orm:"net_t0_roi" json:"netT0Roi" description:"净成交 ROI"`
NetT0Gmv *float64 `orm:"net_t0_gmv" json:"netT0Gmv" description:"净成交 GMV"`
// 原有字段
PhotoName string `orm:"photo_name" json:"photoName" description:"视频名称"`
PhotoIdStr string `orm:"photo_id_str" json:"photoIdStr" description:"视频 id"`
PhotoId string `orm:"photo_id" json:"photoId" description:"视频 id"`
AdItemClick *int64 `orm:"ad_item_click" json:"adItemClick" description:"行为数"`
MerchantProductId string `orm:"merchant_product_id" json:"merchantProductId" description:"商品 ID"`
CostTotal *float64 `orm:"cost_total" json:"costTotal" description:"花费"`
EspClickRatio *float64 `orm:"esp_click_ratio" json:"espClickRatio" description:"行为率"`
T0Gmv *float64 `orm:"t0_gmv" json:"t0Gmv" description:"当日累计 GMV"`
T0Roi *float64 `orm:"t0_roi" json:"t0Roi" description:"当日累计 ROI"`
T0OrderCnt *int64 `orm:"t0_order_cnt" json:"t0OrderCnt" description:"当日累计订单数"`
T0OrderCntCost *float64 `orm:"t0_order_cnt_cost" json:"t0OrderCntCost" description:"当日累计订单成本"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `orm:"qcpx_whitebox_direct_order_payment_amt" json:"qcpxWhiteboxDirectOrderPaymentAmt" description:"智能优惠券订单 GMV"`
QcpxWhiteboxDirectOrderCnt *int64 `orm:"qcpx_whitebox_direct_order_cnt" json:"qcpxWhiteboxDirectOrderCnt" description:"智能优惠券订单数"`
LivePlayCnt *int64 `orm:"live_play_cnt" json:"livePlayCnt" description:"全站直播观看数"`
ItemEntranceClkCnt *int64 `orm:"item_entrance_clk_cnt" json:"itemEntranceClkCnt" description:"小黄车点击数"`
ShowCnt *int64 `orm:"show_cnt" json:"showCnt" description:"全站曝光"`
ReportDateStr string `orm:"report_date_str" json:"reportDateStr" description:"时间"`
CampaignId *int64 `orm:"campaign_id" json:"campaignId" description:"计划 ID"`
CampaignName string `orm:"campaign_name" json:"campaignName" description:"计划名称"`
UnitId *int64 `orm:"unit_id" json:"unitId" description:"单元 ID"`
UnitName string `orm:"unit_name" json:"unitName" description:"单元名称"`
CreativeId *int64 `orm:"creative_id" json:"creativeId" description:"创意 ID"`
CreativeName string `orm:"creative_name" json:"creativeName" description:"创意名称"`
}
// StorewideReportSumCol 广告效果指标表表字段定义
type StorewideReportSumCol struct {
beans.SQLBaseCol
T0OrderPaymentAmt string
CreativeMaterialType string
LiveName string
AuthorId string
PicUrl string
PicName string
PicId string
CoverUrl string
CoverId string
ItemCardClkCnt string
NetT0OrderCnt string
NetT0Roi string
NetT0Gmv string
PhotoName string
PhotoIdStr string
PhotoId string
AdItemClick string
MerchantProductId string
CostTotal string
EspClickRatio string
T0Gmv string
T0Roi string
T0OrderCnt string
T0OrderCntCost string
QcpxWhiteboxDirectOrderPaymentAmt string
QcpxWhiteboxDirectOrderCnt string
LivePlayCnt string
ItemEntranceClkCnt string
ShowCnt string
ReportDateStr string
CampaignId string
CampaignName string
UnitId string
UnitName string
CreativeId string
CreativeName string
}
// TableName 返回表名
func (e *StorewideReportSum) TableName() string {
return "storewide_report_sum"
}
// GetCols 获取所有字段名
func (e *StorewideReportSum) GetCols() *StorewideReportSumCol {
return &StorewideReportSumCol{
SQLBaseCol: beans.SQLBaseCol{
Id: "id",
CreatedAt: "created_at",
UpdatedAt: "updated_at",
},
T0OrderPaymentAmt: "t0_order_payment_amt",
CreativeMaterialType: "creative_material_type",
LiveName: "live_name",
AuthorId: "author_id",
PicUrl: "pic_url",
PicName: "pic_name",
PicId: "pic_id",
CoverUrl: "cover_url",
CoverId: "cover_id",
ItemCardClkCnt: "item_card_clk_cnt",
NetT0OrderCnt: "net_t0_order_cnt",
NetT0Roi: "net_t0_roi",
NetT0Gmv: "net_t0_gmv",
PhotoName: "photo_name",
PhotoIdStr: "photo_id_str",
PhotoId: "photo_id",
AdItemClick: "ad_item_click",
MerchantProductId: "merchant_product_id",
CostTotal: "cost_total",
EspClickRatio: "esp_click_ratio",
T0Gmv: "t0_gmv",
T0Roi: "t0_roi",
T0OrderCnt: "t0_order_cnt",
T0OrderCntCost: "t0_order_cnt_cost",
QcpxWhiteboxDirectOrderPaymentAmt: "qcpx_whitebox_direct_order_payment_amt",
QcpxWhiteboxDirectOrderCnt: "qcpx_whitebox_direct_order_cnt",
LivePlayCnt: "live_play_cnt",
ItemEntranceClkCnt: "item_entrance_clk_cnt",
ShowCnt: "show_cnt",
ReportDateStr: "report_date_str",
CampaignId: "campaign_id",
CampaignName: "campaign_name",
UnitId: "unit_id",
UnitName: "unit_name",
CreativeId: "creative_id",
CreativeName: "creative_name",
}
}

Some files were not shown because too many files have changed in this diff Show More