重构数据引擎和报表引擎
This commit is contained in:
@@ -73,11 +73,15 @@ auth_config: { "app_key": "...", "app_secret": "..." }
|
||||
| `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
|
||||
@@ -198,6 +202,15 @@ WARN 接口 [kuaishou/order_list] 正在同步中,跳过重复请求
|
||||
|
||||
**调度器时序**:使用 `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 → 逐级递归下级 → 防重复 |
|
||||
|
||||
## 新增平台 + 接口的操作步骤
|
||||
|
||||
### 操作步骤
|
||||
@@ -461,6 +474,69 @@ INSERT INTO api_interface (
|
||||
```
|
||||
注意:POST 方法且无 `parameters_location: "query"` 时,参数以 JSON body 形式发送。
|
||||
|
||||
#### 模板 E:OAuth2 + 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步,否则先新增平台(参考对应认证类型的模板)
|
||||
@@ -474,6 +550,67 @@ INSERT INTO api_interface (
|
||||
- 是否需要遍历实体(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()` 可在任意业务服务中直接调用
|
||||
|
||||
|
||||
Reference in New Issue
Block a user