Compare commits
No commits in common. "745ab3510ea384b6322bed0c6d524b54f05f5c38" and "14161727d5a4fbc3f8dd2db0fee0581ccea95576" have entirely different histories.
745ab3510e
...
14161727d5
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
// 使用 IntelliSense 了解相关属性。
|
|
||||||
// 悬停以查看现有属性的描述。
|
|
||||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"name": "yx_tracking_flutter",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "dart"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "yx_tracking_flutter (profile mode)",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "dart",
|
|
||||||
"flutterMode": "profile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "yx_tracking_flutter (release mode)",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "dart",
|
|
||||||
"flutterMode": "release"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "example",
|
|
||||||
"cwd": "example",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "dart"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "example (profile mode)",
|
|
||||||
"cwd": "example",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "dart",
|
|
||||||
"flutterMode": "profile"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "example (release mode)",
|
|
||||||
"cwd": "example",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "dart",
|
|
||||||
"flutterMode": "release"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
- `POST /api/ExternalEventlogs/AddEventListLog`
|
- `POST /api/ExternalEventlogs/AddEventListLog`
|
||||||
- `POST /api/ExternalEventlogs/AddEventLog`
|
- `POST /api/ExternalEventlogs/AddEventLog`
|
||||||
|
|
||||||
需要基于上述接口,开发一个独立的 Flutter 埋点 SDK(Flutter 运行环境使用,非纯 Dart 运行时),并规划三阶段能力演进。
|
需要基于上述接口,开发一个独立的 Flutter 埋点 SDK(纯 Dart 实现),并规划三阶段能力演进。
|
||||||
|
|
||||||
### 1.2 目标
|
### 1.2 目标
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ class AnalyticsConfig {
|
||||||
this.maxRetryCount = 3,
|
this.maxRetryCount = 3,
|
||||||
this.connectTimeout = const Duration(seconds: 5),
|
this.connectTimeout = const Duration(seconds: 5),
|
||||||
this.readTimeout = const Duration(seconds: 5),
|
this.readTimeout = const Duration(seconds: 5),
|
||||||
this.useIsolateStorage = true, // 默认开启 Isolate(仅 Flutter 环境可用)
|
this.useIsolateStorage = true, // 默认开启 Isolate
|
||||||
this.maxEventAge = const Duration(days: 7), // 事件过期时间
|
this.maxEventAge = const Duration(days: 7), // 事件过期时间
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,6 @@
|
||||||
- [x] 已接入 `GET /GetSystemAllDimInfo?system_code=...`
|
- [x] 已接入 `GET /GetSystemAllDimInfo?system_code=...`
|
||||||
- [x] 已在 `AnalyticsCore.init` 中异步拉取(失败不影响埋点)
|
- [x] 已在 `AnalyticsCore.init` 中异步拉取(失败不影响埋点)
|
||||||
- [x] 周期刷新已补齐(定时器按 refreshInterval 拉取)
|
- [x] 周期刷新已补齐(定时器按 refreshInterval 拉取)
|
||||||
- [x] Example 联调支持 HTTP(新增 allowInsecureHttp 配置)
|
|
||||||
|
|
||||||
### 3. Validator(事件校验)
|
### 3. Validator(事件校验)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
# Example App 集成实施方案
|
|
||||||
# Example App Integration Implementation Plan
|
|
||||||
|
|
||||||
> **目标 (Goal)**: 使 example app 能够完整运行并对接真实后端
|
|
||||||
> **执行者 (Executor)**: Codex
|
|
||||||
> **日期 (Date)**: 2026-01-28
|
|
||||||
> **执行记录 (Execution)**: 已执行(参数名修复、Example 配置更新、生命周期监听接入、HTTP 放行)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、后端配置信息 | Backend Configuration
|
|
||||||
|
|
||||||
| 项目 | 值 |
|
|
||||||
|------|---|
|
|
||||||
| **API 基础地址** | `http://192.168.2.7:18828/api/ExternalEventlogs` |
|
|
||||||
| **系统标识** | `SDK-TEST-FLUTTER` |
|
|
||||||
| **系统名称** | Flutter SDK测试 |
|
|
||||||
| **认证方式** | 无需认证 |
|
|
||||||
| **协议** | HTTP(生产建议 HTTPS) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、需要修改的文件 | Files to Modify
|
|
||||||
|
|
||||||
### 2.1 修复参数命名问题
|
|
||||||
|
|
||||||
#### [MODIFY] `lib/src/config/config_manager.dart`
|
|
||||||
|
|
||||||
**问题**: SDK 使用 `system_code`,但后端期望 `systemCode`
|
|
||||||
|
|
||||||
**修改位置**: 第 59 行
|
|
||||||
|
|
||||||
```diff
|
|
||||||
- queryParameters: <String, dynamic>{'system_code': config.systemCode},
|
|
||||||
+ queryParameters: <String, dynamic>{'systemCode': config.systemCode},
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2.2 更新 Example App 配置
|
|
||||||
|
|
||||||
#### [MODIFY] `example/lib/main.dart`
|
|
||||||
|
|
||||||
**修改位置**: 第 10-17 行
|
|
||||||
|
|
||||||
```diff
|
|
||||||
await Analytics.init(
|
|
||||||
const AnalyticsConfig(
|
|
||||||
- systemCode: 'DEMO_APP',
|
|
||||||
- endpointBaseUrl: 'https://example.com/api/ExternalEventlogs',
|
|
||||||
+ systemCode: 'SDK-TEST-FLUTTER',
|
|
||||||
+ endpointBaseUrl: 'http://192.168.2.7:18828/api/ExternalEventlogs',
|
|
||||||
clientType: 3,
|
|
||||||
enableDebug: true,
|
|
||||||
batchSize: 5,
|
|
||||||
flushInterval: 30,
|
|
||||||
allowInsecureHttp: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**修改位置**: 第 161-164 行(更新说明文字)
|
|
||||||
|
|
||||||
```diff
|
|
||||||
- const Text(
|
|
||||||
- '说明:Demo 使用 example.com 作为占位地址,'
|
|
||||||
- '实际联调时请替换为真实 HTTPS 域名。',
|
|
||||||
- ),
|
|
||||||
+ const Text(
|
|
||||||
+ '说明:已对接 SDK-TEST-FLUTTER 系统,'
|
|
||||||
+ '点击 Track 按钮记录事件,点击 Flush 上报。',
|
|
||||||
+ ),
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2.3 添加生命周期监听(可选优化)
|
|
||||||
|
|
||||||
#### [MODIFY] `example/lib/main.dart`
|
|
||||||
|
|
||||||
**修改位置**: 第 18 行之后添加
|
|
||||||
|
|
||||||
```dart
|
|
||||||
// 绑定生命周期监听,后台时自动 flush
|
|
||||||
Analytics.bindLifecycleObserver();
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2.4 放行 HTTP(开发/测试环境)
|
|
||||||
|
|
||||||
#### [MODIFY] `lib/src/config/analytics_config.dart`
|
|
||||||
|
|
||||||
**说明**: 为联调环境允许 HTTP,新增 `allowInsecureHttp` 配置项。
|
|
||||||
|
|
||||||
```diff
|
|
||||||
const AnalyticsConfig({
|
|
||||||
...
|
|
||||||
this.allowInsecureHttp = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// validate()
|
|
||||||
if (scheme == 'http' && allowInsecureHttp) {
|
|
||||||
// 允许开发/测试环境使用 HTTP
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、验证步骤 | Verification Steps
|
|
||||||
|
|
||||||
### 3.1 运行测试
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /Volumes/Workspace/SourceCode/yuanxuan/yx_tracking_flutter
|
|
||||||
flutter test
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 运行 Example App
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /Volumes/Workspace/SourceCode/yuanxuan/yx_tracking_flutter/example
|
|
||||||
flutter run -d macos # 或 chrome / android / ios
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 功能验证清单
|
|
||||||
|
|
||||||
- [ ] 启动 App 无报错
|
|
||||||
- [ ] 点击 "Track Demo Event",缓存数量 +1
|
|
||||||
- [ ] 点击 "Flush Now",事件成功上报(HTTP 200)
|
|
||||||
- [ ] 点击 "Refresh Config",从后端拉取配置成功
|
|
||||||
- [ ] 在管理后台查看上报的事件数据
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、API 接口参考 | API Reference
|
|
||||||
|
|
||||||
### 4.1 事件上报
|
|
||||||
|
|
||||||
```http
|
|
||||||
POST /api/ExternalEventlogs/AddEventListLog
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"system_code": "SDK-TEST-FLUTTER",
|
|
||||||
"eventType": "DEMO_BUTTON_CLICK",
|
|
||||||
"userInfo": null,
|
|
||||||
"clientType": 3,
|
|
||||||
"clientTimestamp": 1706000000000,
|
|
||||||
"timestamp": "2026-01-28T12:00:00.000Z",
|
|
||||||
"deviceInfo": {
|
|
||||||
"os": "macOS 14.0",
|
|
||||||
"model": "MacBook Pro",
|
|
||||||
"screenResolution": "1920x1080"
|
|
||||||
},
|
|
||||||
"eventParams": {"page": "demo"},
|
|
||||||
"customTags": {"tenantId": "t1", "feature": "demo"}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 获取配置
|
|
||||||
|
|
||||||
```http
|
|
||||||
GET /api/ExternalEventlogs/GetSystemAllDimInfo?systemCode=SDK-TEST-FLUTTER
|
|
||||||
```
|
|
||||||
|
|
||||||
**响应示例**:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"systemInfo": {
|
|
||||||
"system_code": "SDK-TEST-FLUTTER",
|
|
||||||
"system_name": "Flutter SDK测试"
|
|
||||||
},
|
|
||||||
"systemEventTypes": [
|
|
||||||
{"event_code": "DEMO_BUTTON_CLICK", "event_name": "Demo按钮点击"}
|
|
||||||
],
|
|
||||||
"systemCustonTas": [
|
|
||||||
{"tag_name": "tenantId", "tag_type": "string"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、数据模型对齐 | Data Model Alignment
|
|
||||||
|
|
||||||
| 后端字段 (EventlogAddDto) | SDK 字段 (Event) | 状态 |
|
|
||||||
|---------------------------|------------------|:----:|
|
|
||||||
| `system_code` | `systemCode` | ✅ |
|
|
||||||
| `eventType` | `eventType` | ✅ |
|
|
||||||
| `userInfo` | `userInfo` | ✅ |
|
|
||||||
| `clientType` (1=Android, 2=iOS, 3=Flutter) | `clientType` | ✅ 使用 3 |
|
|
||||||
| `clientTimestamp` (int64 毫秒) | `clientTimestamp` | ✅ |
|
|
||||||
| `timestamp` (ISO8601) | `timestamp` | ✅ |
|
|
||||||
| `deviceInfo` | `deviceInfo` | ✅ |
|
|
||||||
| `eventParams` | `eventParams` | ✅ |
|
|
||||||
| `customTags` | `customTags` | ✅ |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 六、注意事项 | Notes
|
|
||||||
|
|
||||||
1. **网络环境**: 确保开发机器可访问 `192.168.2.7:18828`
|
|
||||||
2. **事件类型**: 如需校验事件类型,需在管理后台配置 `DEMO_BUTTON_CLICK`
|
|
||||||
3. **自定义标签**: 如需校验标签,需在管理后台配置 `tenantId`、`feature`
|
|
||||||
4. **HTTP 仅限联调**: 生产环境请务必使用 HTTPS,并关闭 `allowInsecureHttp`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**请 Codex 按照以上方案执行修改。**
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# yx_tracking_flutter
|
# yx_tracking_flutter
|
||||||
|
|
||||||
企业级 Flutter 埋点 SDK(Flutter 运行环境使用,非纯 Dart 运行时),对齐后端接口:
|
企业级 Flutter 埋点 SDK(纯 Dart 实现),对齐后端接口:
|
||||||
|
|
||||||
- `GET /api/ExternalEventlogs/GetSystemAllDimInfo`
|
- `GET /api/ExternalEventlogs/GetSystemAllDimInfo`
|
||||||
- `POST /api/ExternalEventlogs/AddEventListLog`
|
- `POST /api/ExternalEventlogs/AddEventListLog`
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
## 功能特性
|
## 功能特性
|
||||||
|
|
||||||
- 统一事件模型,自动补齐公共字段(systemCode、deviceInfo、时间戳等)
|
- 统一事件模型,自动补齐公共字段(systemCode、deviceInfo、时间戳等)
|
||||||
- 本地持久化队列(sqflite),默认运行在独立 Isolate 中,避免阻塞 UI(需 Flutter 环境)
|
- 本地持久化队列(sqflite),默认运行在独立 Isolate 中,避免阻塞 UI
|
||||||
- 应用生命周期监听(进入后台/销毁时自动 flush)
|
- 应用生命周期监听(进入后台/销毁时自动 flush)
|
||||||
- 批量上报 + 重试退避 + 队列上限裁剪
|
- 批量上报 + 重试退避 + 队列上限裁剪
|
||||||
- 配置下发(`GetSystemAllDimInfo`)与本地缓存
|
- 配置下发(`GetSystemAllDimInfo`)与本地缓存
|
||||||
|
|
@ -61,8 +61,7 @@ await Analytics.flush(force: true);
|
||||||
## 关键配置项(AnalyticsConfig)
|
## 关键配置项(AnalyticsConfig)
|
||||||
|
|
||||||
除了文档中的 Phase 1 配置项,还新增了以下能力配置:
|
除了文档中的 Phase 1 配置项,还新增了以下能力配置:
|
||||||
- `useIsolateStorage`: 是否使用 Isolate 进行存储操作(默认 `true`,强烈建议在 Flutter 环境开启)
|
- `useIsolateStorage`: 是否使用 Isolate 进行存储操作(默认 `true`,强烈建议开启)
|
||||||
- `allowInsecureHttp`:是否允许使用 HTTP(默认 `false`,仅用于开发/测试环境)
|
|
||||||
- `enableMetrics`:是否启用 SDK 自监控指标(默认 `true`)
|
- `enableMetrics`:是否启用 SDK 自监控指标(默认 `true`)
|
||||||
- `metricsReportInterval`:指标上报周期(默认 10 分钟)
|
- `metricsReportInterval`:指标上报周期(默认 10 分钟)
|
||||||
- `blockOnValidationError`:Debug 下遇到校验 error 是否阻断发送(默认 `false`)
|
- `blockOnValidationError`:Debug 下遇到校验 error 是否阻断发送(默认 `false`)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
10C79C360DE55F92A491C2EB /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D0325B7C115751406A2CCA7 /* Pods_RunnerTests.framework */; };
|
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
|
@ -15,7 +14,6 @@
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
B82F0DBA26E4C6FF84DE80BC /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8CB685FF82E44B570FFF566 /* Pods_Runner.framework */; };
|
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
|
@ -42,14 +40,11 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
127F9D35E6FC15E57EB026A7 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
561FD5B8376A263AABDC43F7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
6D0325B7C115751406A2CCA7 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
|
@ -60,27 +55,13 @@
|
||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
99B0C1196048B43435272445 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
A5F818923BFCE77BE58D099D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
CFFBDFA0A7DFEC84FAF6C9E5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
F043EE09D6AAF3802C8BF291 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
|
||||||
F8CB685FF82E44B570FFF566 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
0055AA437E8F768D68AD33FE /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
10C79C360DE55F92A491C2EB /* Pods_RunnerTests.framework in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
B82F0DBA26E4C6FF84DE80BC /* Pods_Runner.framework in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
@ -95,15 +76,6 @@
|
||||||
path = RunnerTests;
|
path = RunnerTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
56450A33E60164813EC2B769 /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
F8CB685FF82E44B570FFF566 /* Pods_Runner.framework */,
|
|
||||||
6D0325B7C115751406A2CCA7 /* Pods_RunnerTests.framework */,
|
|
||||||
);
|
|
||||||
name = Frameworks;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
@ -122,8 +94,6 @@
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
F946EF3DDF6321BC391B8495 /* Pods */,
|
|
||||||
56450A33E60164813EC2B769 /* Frameworks */,
|
|
||||||
);
|
);
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
|
@ -151,20 +121,6 @@
|
||||||
path = Runner;
|
path = Runner;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
F946EF3DDF6321BC391B8495 /* Pods */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
A5F818923BFCE77BE58D099D /* Pods-Runner.debug.xcconfig */,
|
|
||||||
561FD5B8376A263AABDC43F7 /* Pods-Runner.release.xcconfig */,
|
|
||||||
99B0C1196048B43435272445 /* Pods-Runner.profile.xcconfig */,
|
|
||||||
F043EE09D6AAF3802C8BF291 /* Pods-RunnerTests.debug.xcconfig */,
|
|
||||||
CFFBDFA0A7DFEC84FAF6C9E5 /* Pods-RunnerTests.release.xcconfig */,
|
|
||||||
127F9D35E6FC15E57EB026A7 /* Pods-RunnerTests.profile.xcconfig */,
|
|
||||||
);
|
|
||||||
name = Pods;
|
|
||||||
path = Pods;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
/* Begin PBXNativeTarget section */
|
||||||
|
|
@ -172,10 +128,8 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
61DF17F1CEED61F4D4BCEAE3 /* [CP] Check Pods Manifest.lock */,
|
|
||||||
331C807D294A63A400263BE5 /* Sources */,
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
331C807F294A63A400263BE5 /* Resources */,
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
0055AA437E8F768D68AD33FE /* Frameworks */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
|
@ -191,14 +145,12 @@
|
||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
98678C9D0961C6E0D7564335 /* [CP] Check Pods Manifest.lock */,
|
|
||||||
9740EEB61CF901F6004384FC /* Run Script */,
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
97C146EA1CF9000F007C117D /* Sources */,
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
97C146EC1CF9000F007C117D /* Resources */,
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
E75446E1D1B4687890B630C3 /* [CP] Embed Pods Frameworks */,
|
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
|
@ -286,28 +238,6 @@
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
};
|
};
|
||||||
61DF17F1CEED61F4D4BCEAE3 /* [CP] Check Pods Manifest.lock */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
||||||
"${PODS_ROOT}/Manifest.lock",
|
|
||||||
);
|
|
||||||
name = "[CP] Check Pods Manifest.lock";
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
|
|
@ -323,45 +253,6 @@
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
98678C9D0961C6E0D7564335 /* [CP] Check Pods Manifest.lock */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
|
||||||
"${PODS_ROOT}/Manifest.lock",
|
|
||||||
);
|
|
||||||
name = "[CP] Check Pods Manifest.lock";
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
E75446E1D1B4687890B630C3 /* [CP] Embed Pods Frameworks */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
|
||||||
outputFileListPaths = (
|
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
|
||||||
showEnvVarsInLog = 0;
|
|
||||||
};
|
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
|
@ -488,7 +379,6 @@
|
||||||
};
|
};
|
||||||
331C8088294A63A400263BE5 /* Debug */ = {
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = F043EE09D6AAF3802C8BF291 /* Pods-RunnerTests.debug.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
|
@ -506,7 +396,6 @@
|
||||||
};
|
};
|
||||||
331C8089294A63A400263BE5 /* Release */ = {
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = CFFBDFA0A7DFEC84FAF6C9E5 /* Pods-RunnerTests.release.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
|
@ -522,7 +411,6 @@
|
||||||
};
|
};
|
||||||
331C808A294A63A400263BE5 /* Profile */ = {
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 127F9D35E6FC15E57EB026A7 /* Pods-RunnerTests.profile.xcconfig */;
|
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,4 @@
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "group:Runner.xcodeproj">
|
location = "group:Runner.xcodeproj">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
<FileRef
|
|
||||||
location = "group:Pods/Pods.xcodeproj">
|
|
||||||
</FileRef>
|
|
||||||
</Workspace>
|
</Workspace>
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,15 @@ Future<void> main() async {
|
||||||
|
|
||||||
await Analytics.init(
|
await Analytics.init(
|
||||||
const AnalyticsConfig(
|
const AnalyticsConfig(
|
||||||
systemCode: 'SDK-TEST-FLUTTER',
|
systemCode: 'DEMO_APP',
|
||||||
endpointBaseUrl: 'http://192.168.2.7:18828/api/',
|
endpointBaseUrl: 'https://example.com/api/ExternalEventlogs',
|
||||||
clientType: 3,
|
clientType: 3,
|
||||||
enableDebug: true,
|
enableDebug: true,
|
||||||
batchSize: 5,
|
batchSize: 5,
|
||||||
flushInterval: 30,
|
flushInterval: 30,
|
||||||
allowInsecureHttp: true,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Analytics.bindLifecycleObserver();
|
|
||||||
|
|
||||||
runApp(const DemoApp());
|
runApp(const DemoApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +25,9 @@ class DemoApp extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const MaterialApp(home: DemoPage());
|
return const MaterialApp(
|
||||||
|
home: DemoPage(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +80,10 @@ class _DemoPageState extends State<DemoPage> {
|
||||||
await Analytics.track(
|
await Analytics.track(
|
||||||
'DEMO_BUTTON_CLICK',
|
'DEMO_BUTTON_CLICK',
|
||||||
eventParams: const <String, dynamic>{'page': 'demo'},
|
eventParams: const <String, dynamic>{'page': 'demo'},
|
||||||
customTags: const <String, dynamic>{'tenantId': 't1', 'feature': 'demo'},
|
customTags: const <String, dynamic>{
|
||||||
|
'tenantId': 't1',
|
||||||
|
'feature': 'demo',
|
||||||
|
},
|
||||||
);
|
);
|
||||||
await _refreshCount();
|
await _refreshCount();
|
||||||
}
|
}
|
||||||
|
|
@ -151,22 +153,17 @@ class _DemoPageState extends State<DemoPage> {
|
||||||
),
|
),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: _refreshingConfig ? null : _refreshConfig,
|
onPressed: _refreshingConfig ? null : _refreshConfig,
|
||||||
child: Text(
|
child: Text(_refreshingConfig ? 'Refreshing...' : 'Refresh Config'),
|
||||||
_refreshingConfig ? 'Refreshing...' : 'Refresh Config',
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
const Text(
|
const Text(
|
||||||
'说明:已对接 SDK-TEST-FLUTTER 系统,'
|
'说明:Demo 使用 example.com 作为占位地址,'
|
||||||
'点击 Track 按钮记录事件,点击 Flush 上报。',
|
'实际联调时请替换为真实 HTTPS 域名。',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text('最近事件(最多 20 条)', style: Theme.of(context).textTheme.titleSmall),
|
||||||
'最近事件(最多 20 条)',
|
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _recent.isEmpty
|
child: _recent.isEmpty
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ class AnalyticsConfig {
|
||||||
this.readTimeout = const Duration(seconds: 5),
|
this.readTimeout = const Duration(seconds: 5),
|
||||||
this.maxEventAge = const Duration(days: 7),
|
this.maxEventAge = const Duration(days: 7),
|
||||||
this.useIsolateStorage = true,
|
this.useIsolateStorage = true,
|
||||||
this.allowInsecureHttp = false,
|
|
||||||
this.enableMetrics = true,
|
this.enableMetrics = true,
|
||||||
this.metricsReportInterval = const Duration(minutes: 10),
|
this.metricsReportInterval = const Duration(minutes: 10),
|
||||||
this.blockOnValidationError = false,
|
this.blockOnValidationError = false,
|
||||||
|
|
@ -58,9 +57,6 @@ class AnalyticsConfig {
|
||||||
/// 是否使用 Isolate 执行存储操作。
|
/// 是否使用 Isolate 执行存储操作。
|
||||||
final bool useIsolateStorage;
|
final bool useIsolateStorage;
|
||||||
|
|
||||||
/// 是否允许 HTTP(仅用于开发/测试环境)。
|
|
||||||
final bool allowInsecureHttp;
|
|
||||||
|
|
||||||
/// 是否开启 SDK 指标上报。
|
/// 是否开启 SDK 指标上报。
|
||||||
final bool enableMetrics;
|
final bool enableMetrics;
|
||||||
|
|
||||||
|
|
@ -86,17 +82,12 @@ class AnalyticsConfig {
|
||||||
'endpointBaseUrl 不是合法的 URL',
|
'endpointBaseUrl 不是合法的 URL',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final scheme = uri.scheme.toLowerCase();
|
if (uri.scheme.toLowerCase() != 'https') {
|
||||||
if (scheme != 'https') {
|
throw ArgumentError.value(
|
||||||
if (scheme == 'http' && allowInsecureHttp) {
|
endpointBaseUrl,
|
||||||
// 允许开发/测试环境使用 HTTP。
|
'endpointBaseUrl',
|
||||||
} else {
|
'endpointBaseUrl 必须使用 HTTPS',
|
||||||
throw ArgumentError.value(
|
);
|
||||||
endpointBaseUrl,
|
|
||||||
'endpointBaseUrl',
|
|
||||||
'endpointBaseUrl 必须使用 HTTPS',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientType <= 0) {
|
if (clientType <= 0) {
|
||||||
|
|
@ -165,15 +156,13 @@ class AnalyticsConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `/AddEventListLog` 的完整地址。
|
/// `/AddEventListLog` 的完整地址。
|
||||||
Uri get addEventListLogUri =>
|
Uri get addEventListLogUri => _appendPath('AddEventListLog');
|
||||||
_appendPath('ExternalEventlogs/AddEventListLog');
|
|
||||||
|
|
||||||
/// `/AddEventLog` 的完整地址(可选降级)。
|
/// `/AddEventLog` 的完整地址(可选降级)。
|
||||||
Uri get addEventLogUri => _appendPath('ExternalEventlogs/AddEventLog');
|
Uri get addEventLogUri => _appendPath('AddEventLog');
|
||||||
|
|
||||||
/// `/GetSystemAllDimInfo` 的完整地址(Phase 2+)。
|
/// `/GetSystemAllDimInfo` 的完整地址(Phase 2+)。
|
||||||
Uri get getSystemAllDimInfoUri =>
|
Uri get getSystemAllDimInfoUri => _appendPath('GetSystemAllDimInfo');
|
||||||
_appendPath('ExternalEventlogs/GetSystemAllDimInfo');
|
|
||||||
|
|
||||||
Uri _appendPath(String leaf) {
|
Uri _appendPath(String leaf) {
|
||||||
final base = Uri.parse(endpointBaseUrl);
|
final base = Uri.parse(endpointBaseUrl);
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,8 @@ class ConfigManager {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _httpClient.get<dynamic>(
|
final response = await _httpClient.get<dynamic>(
|
||||||
config.getSystemAllDimInfoUri.toString(),
|
'GetSystemAllDimInfo',
|
||||||
queryParameters: <String, dynamic>{'systemCode': config.systemCode},
|
queryParameters: <String, dynamic>{'system_code': config.systemCode},
|
||||||
);
|
);
|
||||||
|
|
||||||
final payload = _extractPayloadMap(response.data);
|
final payload = _extractPayloadMap(response.data);
|
||||||
|
|
@ -116,8 +116,7 @@ class ConfigManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.warn('配置响应结构无法识别,已忽略');
|
return root;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _looksLikeDimInfo(Map<String, dynamic> map) {
|
bool _looksLikeDimInfo(Map<String, dynamic> map) {
|
||||||
|
|
|
||||||
|
|
@ -114,19 +114,10 @@ class AnalyticsCore {
|
||||||
Logger.info('AnalyticsCore 初始化开始');
|
Logger.info('AnalyticsCore 初始化开始');
|
||||||
|
|
||||||
final deviceInfo = await _deviceInfoCollector();
|
final deviceInfo = await _deviceInfoCollector();
|
||||||
EventStorage storage =
|
final storage = _useDefaultStorageFactory
|
||||||
_useDefaultStorageFactory ? _buildDefaultStorage(config) : _storageFactory();
|
? _buildDefaultStorage(config)
|
||||||
try {
|
: _storageFactory();
|
||||||
await storage.init();
|
await storage.init();
|
||||||
} on Object catch (e, st) {
|
|
||||||
if (_useDefaultStorageFactory && config.useIsolateStorage) {
|
|
||||||
Logger.error('Isolate 存储初始化失败,降级为默认存储', e, st);
|
|
||||||
storage = SqfliteEventStorage();
|
|
||||||
await storage.init();
|
|
||||||
} else {
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final configManager = _configManagerFactory(config);
|
final configManager = _configManagerFactory(config);
|
||||||
await configManager.init();
|
await configManager.init();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,11 @@ class ApiClient {
|
||||||
ApiClient(
|
ApiClient(
|
||||||
AnalyticsConfig config, {
|
AnalyticsConfig config, {
|
||||||
HttpClient? httpClient,
|
HttpClient? httpClient,
|
||||||
}) : _httpClient = httpClient ?? HttpClient(config),
|
}) : _httpClient = httpClient ?? HttpClient(config);
|
||||||
_config = config;
|
|
||||||
|
static const String _addEventListLogPath = 'AddEventListLog';
|
||||||
|
|
||||||
final HttpClient _httpClient;
|
final HttpClient _httpClient;
|
||||||
final AnalyticsConfig _config;
|
|
||||||
|
|
||||||
/// 批量发送事件。
|
/// 批量发送事件。
|
||||||
///
|
///
|
||||||
|
|
@ -57,7 +57,7 @@ class ApiClient {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final response = await _httpClient.post<dynamic>(
|
final response = await _httpClient.post<dynamic>(
|
||||||
_config.addEventListLogUri.toString(),
|
_addEventListLogPath,
|
||||||
data: payload,
|
data: payload,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'package:dio/dio.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
import 'package:yx_tracking_flutter/src/config/analytics_config.dart';
|
import 'package:yx_tracking_flutter/src/config/analytics_config.dart';
|
||||||
import 'package:yx_tracking_flutter/src/util/logger.dart';
|
|
||||||
|
|
||||||
/// 对 Dio 的轻量封装,统一超时与基础配置。
|
/// 对 Dio 的轻量封装,统一超时与基础配置。
|
||||||
class HttpClient {
|
class HttpClient {
|
||||||
|
|
@ -10,7 +9,8 @@ class HttpClient {
|
||||||
HttpClient(
|
HttpClient(
|
||||||
AnalyticsConfig config, {
|
AnalyticsConfig config, {
|
||||||
HttpClientAdapter? httpClientAdapter,
|
HttpClientAdapter? httpClientAdapter,
|
||||||
}) : _dio = Dio(
|
})
|
||||||
|
: _dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl: _normalizeBaseUrl(config.endpointBaseUrl),
|
baseUrl: _normalizeBaseUrl(config.endpointBaseUrl),
|
||||||
connectTimeout: config.connectTimeout,
|
connectTimeout: config.connectTimeout,
|
||||||
|
|
@ -38,29 +38,13 @@ class HttpClient {
|
||||||
Map<String, dynamic>? queryParameters,
|
Map<String, dynamic>? queryParameters,
|
||||||
Map<String, Object?>? headers,
|
Map<String, Object?>? headers,
|
||||||
CancelToken? cancelToken,
|
CancelToken? cancelToken,
|
||||||
}) {
|
}) =>
|
||||||
// 如果 path 是完整 URL(以 http 开头),则直接使用,否则基于 baseUrl 拼接。
|
_dio.get<T>(
|
||||||
final isAbsolutePath = path.toLowerCase().startsWith('http');
|
path,
|
||||||
final String requestPath = isAbsolutePath ? path : path;
|
queryParameters: queryParameters,
|
||||||
|
options: _withHeaders(headers),
|
||||||
// 日志
|
cancelToken: cancelToken,
|
||||||
if (isAbsolutePath) {
|
);
|
||||||
Logger.info('GET Request: $requestPath query=$queryParameters');
|
|
||||||
} else {
|
|
||||||
final baseUrl = _dio.options.baseUrl;
|
|
||||||
final fullUri = Uri.parse(baseUrl)
|
|
||||||
.resolve(path)
|
|
||||||
.replace(queryParameters: queryParameters);
|
|
||||||
Logger.info('GET Request: $fullUri');
|
|
||||||
}
|
|
||||||
|
|
||||||
return _dio.get<T>(
|
|
||||||
requestPath,
|
|
||||||
queryParameters: queryParameters,
|
|
||||||
options: _withHeaders(headers),
|
|
||||||
cancelToken: cancelToken,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 发送 POST 请求。
|
/// 发送 POST 请求。
|
||||||
Future<Response<T>> post<T>(
|
Future<Response<T>> post<T>(
|
||||||
|
|
@ -86,6 +70,9 @@ class HttpClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
static String _normalizeBaseUrl(String baseUrl) {
|
static String _normalizeBaseUrl(String baseUrl) {
|
||||||
|
if (baseUrl.endsWith('/')) {
|
||||||
|
return baseUrl.substring(0, baseUrl.length - 1);
|
||||||
|
}
|
||||||
return baseUrl;
|
return baseUrl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -256,28 +256,16 @@ Future<void> _storageWorkerEntry(_WorkerInit init) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
final commandPort = ReceivePort();
|
final commandPort = ReceivePort();
|
||||||
|
init.responsePort.send(<String, Object?>{
|
||||||
|
'type': _msgReady,
|
||||||
|
'sendPort': commandPort.sendPort,
|
||||||
|
});
|
||||||
|
|
||||||
final storage = switch (init.backend) {
|
final storage = switch (init.backend) {
|
||||||
IsolateStorageBackend.sqlite => SqfliteEventStorage(),
|
IsolateStorageBackend.sqlite => SqfliteEventStorage(),
|
||||||
IsolateStorageBackend.memory => _MemoryEventStorage(),
|
IsolateStorageBackend.memory => _MemoryEventStorage(),
|
||||||
};
|
};
|
||||||
|
await storage.init();
|
||||||
try {
|
|
||||||
await storage.init();
|
|
||||||
} on Object catch (e, st) {
|
|
||||||
Logger.error('Isolate 存储初始化失败', e, st);
|
|
||||||
// 初始化失败时,不发送 ready 信号,或发送错误信号(当前协议简单,暂不发送 ready 即视为失败/超时)。
|
|
||||||
// 但为了让主 isolate 能够尽快失败而不是超时,我们可以考虑扩展协议,但目前最稳妥的是让主 isolate 超时抛错。
|
|
||||||
// 这里简单地关闭端口退出。
|
|
||||||
commandPort.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化成功后再发送 ready 信号。
|
|
||||||
init.responsePort.send(<String, Object?>{
|
|
||||||
'type': _msgReady,
|
|
||||||
'sendPort': commandPort.sendPort,
|
|
||||||
});
|
|
||||||
|
|
||||||
await for (final message in commandPort) {
|
await for (final message in commandPort) {
|
||||||
if (message is! Map) {
|
if (message is! Map) {
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,9 @@ class SqfliteEventStorage implements EventStorage {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final directory = await (_documentsDirectoryProvider?.call() ??
|
final directory =
|
||||||
getApplicationDocumentsDirectory());
|
await (_documentsDirectoryProvider?.call() ??
|
||||||
|
getApplicationDocumentsDirectory());
|
||||||
final dbPath = p.join(directory.path, DbConstants.dbName);
|
final dbPath = p.join(directory.path, DbConstants.dbName);
|
||||||
|
|
||||||
final factory = _databaseFactory ?? databaseFactory;
|
final factory = _databaseFactory ?? databaseFactory;
|
||||||
|
|
@ -55,15 +56,6 @@ class SqfliteEventStorage implements EventStorage {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// coverage:ignore-end
|
// coverage:ignore-end
|
||||||
onConfigure: (Database db) async {
|
|
||||||
// 启用 WAL 模式以支持更高并发读写,减少 SQLITE_BUSY。
|
|
||||||
// 注意:PRAGMA journal_mode 会返回结果,使用 execute 在 Android 上可能会报错,需改用 rawQuery。
|
|
||||||
await db.rawQuery('PRAGMA journal_mode = WAL;');
|
|
||||||
},
|
|
||||||
onOpen: (Database db) async {
|
|
||||||
// 确保表存在(针对多个 Store 共享同一 DB 文件且部分表可能未创建的情况)。
|
|
||||||
await _createTables(db);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -80,7 +72,8 @@ class SqfliteEventStorage implements EventStorage {
|
||||||
);
|
);
|
||||||
''');
|
''');
|
||||||
|
|
||||||
const createIndexSql = 'CREATE INDEX IF NOT EXISTS idx_events_create_time '
|
const createIndexSql =
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_events_create_time '
|
||||||
'ON $_tableEvents(create_time);';
|
'ON $_tableEvents(create_time);';
|
||||||
await db.execute(createIndexSql);
|
await db.execute(createIndexSql);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ AnalyticsConfig _base({
|
||||||
Duration? readTimeout,
|
Duration? readTimeout,
|
||||||
Duration? maxEventAge,
|
Duration? maxEventAge,
|
||||||
bool? useIsolateStorage,
|
bool? useIsolateStorage,
|
||||||
bool? allowInsecureHttp,
|
|
||||||
bool? enableMetrics,
|
bool? enableMetrics,
|
||||||
Duration? metricsReportInterval,
|
Duration? metricsReportInterval,
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -29,7 +28,6 @@ AnalyticsConfig _base({
|
||||||
readTimeout: readTimeout ?? const Duration(seconds: 1),
|
readTimeout: readTimeout ?? const Duration(seconds: 1),
|
||||||
maxEventAge: maxEventAge ?? const Duration(days: 7),
|
maxEventAge: maxEventAge ?? const Duration(days: 7),
|
||||||
useIsolateStorage: useIsolateStorage ?? false,
|
useIsolateStorage: useIsolateStorage ?? false,
|
||||||
allowInsecureHttp: allowInsecureHttp ?? false,
|
|
||||||
enableMetrics: enableMetrics ?? true,
|
enableMetrics: enableMetrics ?? true,
|
||||||
metricsReportInterval:
|
metricsReportInterval:
|
||||||
metricsReportInterval ?? const Duration(minutes: 1),
|
metricsReportInterval ?? const Duration(minutes: 1),
|
||||||
|
|
@ -56,15 +54,6 @@ void main() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('允许 http 时不抛错', () {
|
|
||||||
expect(
|
|
||||||
() =>
|
|
||||||
_base(endpointBaseUrl: 'http://example.com', allowInsecureHttp: true)
|
|
||||||
.validate(),
|
|
||||||
returnsNormally,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('clientType 必须为正数', () {
|
test('clientType 必须为正数', () {
|
||||||
expect(() => _base(clientType: 0).validate(), throwsArgumentError);
|
expect(() => _base(clientType: 0).validate(), throwsArgumentError);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue