Compare commits
2 Commits
2134f54010
...
56ad003e04
| Author | SHA1 | Date | |
|---|---|---|---|
| 56ad003e04 | |||
| f947f3d2f8 |
@@ -1 +1,3 @@
|
||||
{}
|
||||
{
|
||||
"vimMode": true
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
{}
|
||||
{
|
||||
"theme": "moonstone"
|
||||
}
|
||||
@@ -176,7 +176,7 @@
|
||||
}
|
||||
],
|
||||
"direction": "horizontal",
|
||||
"width": 300
|
||||
"width": 326.390625
|
||||
},
|
||||
"left-ribbon": {
|
||||
"hiddenItems": {
|
||||
@@ -191,6 +191,29 @@
|
||||
},
|
||||
"active": "9775ada2a6c4ad3e",
|
||||
"lastOpenFiles": [
|
||||
"HarmonyOS 启动性能监控接入方案_20260805.md",
|
||||
"CrashDiagnosticsDemo/entry/src/main/ets/pages/CrashHistory.ets.tmp.64840.c1c356d07c03",
|
||||
"CrashDiagnosticsDemo/entry/src/main/ets/pages/CrashHistory.ets.tmp.64840.e9cfc48c9755",
|
||||
"CrashDiagnosticsDemo/entry/build/default/outputs/default/entry-default-signed.hap",
|
||||
"CrashDiagnosticsDemo/entry/build/default/intermediates/default/temp/signConfigCheckJson.json",
|
||||
"CrashDiagnosticsDemo/entry/build/default/intermediates/loader_out/default/ets/modules.abc",
|
||||
"CrashDiagnosticsDemo/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/compiler.cache/modules/new-CrashHistory.ets-3362d7b55dd443f6915a35dcdf55398d52857eca3d1670c4e3aca017d6197ce1.msgpack",
|
||||
"CrashDiagnosticsDemo/entry/build/default/intermediates/loader_out/default/ets/sourceMaps.map",
|
||||
"CrashDiagnosticsDemo/entry/src/main/ets/pages/CrashHistory.ets.tmp.64840.e796abd1053c",
|
||||
"CrashDiagnosticsDemo/entry/src/main/ets/pages/CrashHistory.ets.tmp.64840.d67860e3655a",
|
||||
"CrashDiagnosticsDemo/entry/build/default/cache/default/default@CompileArkTS/esmodule/debug/compiler.cache/modules/CrashHistory.ets-3362d7b55dd443f6915a35dcdf55398d52857eca3d1670c4e3aca017d6197ce1.msgpack",
|
||||
"CrashDiagnosticsDemo/CLAUDE.md",
|
||||
"CrashDiagnosticsDemo/entry/build/default/intermediates/res/default/resources/base/media/background.png",
|
||||
"CrashDiagnosticsDemo/entry/build/default/intermediates/res/default/resources/base/media/startIcon.png",
|
||||
"CrashDiagnosticsDemo/entry/build/default/intermediates/res/default/resources/base/media/foreground.png",
|
||||
"CrashDiagnosticsDemo/entry/src/main/resources/base/media/startIcon.png",
|
||||
"CrashDiagnosticsDemo/entry/src/main/resources/base/media/foreground.png",
|
||||
"CrashDiagnosticsDemo/entry/src/main/resources/base/media/background.png",
|
||||
"CrashDiagnosticsDemo/AppScope/resources/base/media/foreground.png",
|
||||
"CrashDiagnosticsDemo/AppScope/resources/base/media/background.png",
|
||||
"report-assets/10-home-first-frame.png",
|
||||
"report-assets/09-profiler-first-frame-time-check.jpg",
|
||||
"AGENTS.md",
|
||||
"未命名.md",
|
||||
"HarmonyOS APM分析报告_20260731.md"
|
||||
]
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Repository Overview
|
||||
|
||||
Mixed-purpose repository with three distinct areas:
|
||||
|
||||
1. **Root level** — Obsidian-friendly documentation workspace for HarmonyOS APM (Application Performance Management) crash analysis of 同花顺期货 (THS Futures). The main report is `HarmonyOS APM分析报告_20260731.md` with supporting screenshots in `report-assets/`.
|
||||
2. **`CrashDiagnosticsDemo/`** — Standalone HarmonyOS demo app demonstrating HiAppEvent crash subscription, persistent crash records, and native crash simulation via NAPI. Full project with its own `CLAUDE.md`.
|
||||
3. **`future_entry_demo/`** — Extracted `entry` HAP module from THS Futures. NOT a full workspace — depends on sibling modules (`biz_common`, `biz_trade`, etc.) via `file:../...`. Has its own `CLAUDE.md` and `AGENTS.md`.
|
||||
|
||||
**When working in a subdirectory**, read that subdirectory's own `CLAUDE.md` first — it contains build commands, architecture details, and constraints specific to that project. This file covers the repo-level conventions and cross-cutting concerns.
|
||||
|
||||
## Build & Development Commands
|
||||
|
||||
There is no top-level build system. Work in the appropriate subdirectory:
|
||||
|
||||
- **CrashDiagnosticsDemo**: Full HarmonyOS project — `devecocli build` from its root.
|
||||
- **future_entry_demo**: Entry module only — build from the parent workspace with `devecocli build --modules entry@default`.
|
||||
- **Root documentation**: No build step. Edit Markdown in Obsidian or any CommonMark viewer.
|
||||
|
||||
The `.claude/settings.local.json` pre-approves `devecocli build *`, `devecocli device *`, and `devecocli run *` commands.
|
||||
|
||||
## Cross-Cutting Conventions
|
||||
|
||||
### HarmonyOS / ArkTS
|
||||
|
||||
All HarmonyOS projects in this repo use the **Hvigor** build system with **DevEco Studio** as the IDE. Use the **deveco-cli skill** for build, run, and device operations. ArkTS is a strict subset of TypeScript — load the **arkts-grammar-standards** skill before writing `.ets` files.
|
||||
|
||||
### Documentation (Markdown)
|
||||
|
||||
- UTF-8 Markdown with ATX headings (`#`, `##`), fenced code blocks with language tags, pipe tables with right-aligned numeric columns.
|
||||
- Name screenshots with a two-digit sequence and short lowercase description (e.g., `08-grafana-native-detail.png`).
|
||||
- New reports use descriptive filenames with a date (`YYYYMMDD`). Place images in `report-assets/` or a report-specific subdirectory.
|
||||
- Validate manually: preview all headings/tables/images, recalculate totals against source screenshots, verify no absolute paths or sensitive data leak in.
|
||||
|
||||
### Git
|
||||
|
||||
- Concise, imperative commit subjects (e.g., `docs: clarify OOM crash accounting`, `assets: add native crash evidence`).
|
||||
- Keep report text and supporting screenshots in the same commit.
|
||||
- Avoid committing personal Obsidian workspace-state changes unless intentionally shared.
|
||||
@@ -0,0 +1,12 @@
|
||||
/node_modules
|
||||
/oh_modules
|
||||
/local.properties
|
||||
/.idea
|
||||
**/build
|
||||
/.hvigor
|
||||
.cxx
|
||||
/.clangd
|
||||
/.clang-format
|
||||
/.clang-tidy
|
||||
**/.test
|
||||
/.appanalyzer
|
||||
@@ -0,0 +1,81 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
HarmonyOS (鸿蒙) crash diagnostics demo app — `com.example.crashdiagnosticsdemo`. Targets HarmonyOS 6.1.0 (API 23), Stage Mode, phone devices only. Uses the Hvigor build system with DevEco Studio as the IDE.
|
||||
|
||||
## Build & Development Commands
|
||||
|
||||
This is a HarmonyOS project; use the **deveco-cli skill** for all build, run, and device operations.
|
||||
|
||||
- **Build (debug)**: `devecocli build` or via DevEco Studio
|
||||
- **Run on device/emulator**: DevEco Studio → Run, or `devecocli run`
|
||||
- **Lint ArkTS**: `code-linter` is configured via `code-linter.json5` (triggers on `.ets` files, enforces security rules like `@security/no-unsafe-aes`, `@security/no-unsafe-hash`, etc.)
|
||||
- **CLI docs lookup**: use the `deveco-cli` skill to fetch HarmonyOS API docs and samples
|
||||
|
||||
### Testing
|
||||
|
||||
The project includes `@ohos/hypium` (test framework) and `@ohos/hamock` (mocking) as dev dependencies. Test targets are configured in `entry/build-profile.json5` under the `ohosTest` target.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Entry Point & Page Routing
|
||||
|
||||
```
|
||||
EntryAbility (entryability/EntryAbility.ets)
|
||||
└── loads pages/Index (main page)
|
||||
└── navigates to pages/CrashHistory (push route)
|
||||
```
|
||||
|
||||
- `EntryAbility` initializes `CrashDiagnostics` on `onCreate`, sets color mode, and loads `pages/Index` on `onWindowStageCreate`.
|
||||
- Page routes are declared in `entry/src/main/resources/base/profile/main_pages.json`.
|
||||
|
||||
### Core Diagnostic Layer — `CrashDiagnostics` (diagnostics/CrashDiagnostics.ets)
|
||||
|
||||
A **static utility class** (never instantiated) that is the backbone of the app. Key responsibilities:
|
||||
|
||||
1. **HiAppEvent subscription**: Registers an `hiAppEvent.Watcher` named `debugCrashWatcher` that listens for system `APP_CRASH` fault events. The watcher is created in `initialize()` — because it reuses the same name, it picks up un-consumed crash events from a previous process exit on the next launch.
|
||||
|
||||
2. **Persistent crash records**: Stores up to 10 crash record strings in `preferences` (key: `recent_hiappevent_crash_records`). New records are prepended; duplicates are filtered out.
|
||||
|
||||
3. **Dismiss-until-new-crash pattern**: Tracks which crash the user dismissed (key: `dismissed_hiappevent_crash_id`). `shouldShowLastCrashDialog()` returns `true` only when the current latest crash differs from the dismissed one.
|
||||
|
||||
4. **Listener callback**: `recordsUpdatedListener` — a single callback set by the active UI page; fires when cached records change.
|
||||
|
||||
5. **Caught error logging**: `recordCaughtError()` logs to hilog only — it does NOT mix caught exceptions into the APP_CRASH history. The history exclusively contains system-generated crash events.
|
||||
|
||||
### Pages
|
||||
|
||||
- **`pages/Index`** — Main debug screen. Displays crash record count/status, buttons to simulate a caught error (logged only) and a native CPP crash (via NAPI, kills the app), and a button to view crash history. On `aboutToAppear`, sets itself as the `recordsUpdatedListener` and optionally shows an alert dialog for the last un-dismissed crash.
|
||||
|
||||
- **`pages/CrashHistory`** — Lists crash records from `CrashDiagnostics.getRecentRecords()`, with detail view, clear-all, and export-to-ZIP (JSON inside ZIP, saved via `DocumentViewPicker`).
|
||||
|
||||
### Native Layer (NAPI)
|
||||
|
||||
```
|
||||
entry/src/main/cpp/napi_init.cpp → libentry.so
|
||||
entry/src/main/cpp/types/libentry/index.d.ts → ArkTS type declarations
|
||||
```
|
||||
|
||||
Exports a single function `simulateCppCrash()` that calls `std::abort()`. Used by `pages/Index` to demonstrate a native crash that gets picked up by HiAppEvent on the next launch. Built via CMake (`CMakeLists.txt`) targeting `arm64-v8a` only, linked against `libace_napi.z.so`.
|
||||
|
||||
### Key Data Flow
|
||||
|
||||
```
|
||||
App crashes (native/JS)
|
||||
→ HarmonyOS writes APP_CRASH event to HiAppEvent
|
||||
→ Next app launch: EntryAbility.onCreate → CrashDiagnostics.initialize()
|
||||
→ hiAppEvent.addWatcher("debugCrashWatcher") picks up events
|
||||
→ collectFromHolder() formats and caches records to preferences
|
||||
→ recordsUpdatedListener fires → Index page updates UI
|
||||
→ Optionally shows AlertDialog for un-dismissed crash
|
||||
```
|
||||
|
||||
## Important Constraints
|
||||
|
||||
- **ArkTS is NOT TypeScript**: ArkTS is a strict subset of TypeScript with its own compiler rules. Use the `arkts-grammar-standards` skill before writing `.ets` files. Key differences: no `any`/`unknown` types in certain contexts, restricted structural typing for `@Component` structs, specific decorator rules (`@State`, `@Entry`, `@Component`).
|
||||
- **Target ABI is arm64-v8a only** (no x86_64 emulator support in the build config).
|
||||
- **Obfuscation is disabled** even in release mode (`entry/build-profile.json5`).
|
||||
- **Strict mode enabled**: `caseSensitiveCheck: true`, `useNormalizedOHMUrl: true`.
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"app": {
|
||||
"signingConfigs": [
|
||||
{
|
||||
"name": "default",
|
||||
"type": "HarmonyOS",
|
||||
"material": {
|
||||
"certpath": "/Users/clz/.ohos/config/default_CrashDiagnosticsDemo_dgC3A3ef-okflXeLnrBZ6af9o2i9T_iaO4bxUMOFv2k=.cer",
|
||||
"keyAlias": "debugKey",
|
||||
"keyPassword": "0000001AE4AAFBA570B8D487B590836743677A433F6DD14A7A6D0B26155D832D115352AC4E27D625EDE1",
|
||||
"profile": "/Users/clz/.ohos/config/default_CrashDiagnosticsDemo_dgC3A3ef-okflXeLnrBZ6af9o2i9T_iaO4bxUMOFv2k=.p7b",
|
||||
"signAlg": "SHA256withECDSA",
|
||||
"storeFile": "/Users/clz/.ohos/config/default_CrashDiagnosticsDemo_dgC3A3ef-okflXeLnrBZ6af9o2i9T_iaO4bxUMOFv2k=.p12",
|
||||
"storePassword": "0000001A5B9B9B6E48CF67A085861EF0102D3E508E9C395FA0B293A616EFBD3790576635D0A3852EA471"
|
||||
}
|
||||
}
|
||||
],
|
||||
"products": [
|
||||
{
|
||||
"name": "default",
|
||||
"signingConfig": "default",
|
||||
"targetSdkVersion": "6.1.0(23)",
|
||||
"compatibleSdkVersion": "6.1.0(23)",
|
||||
"runtimeOS": "HarmonyOS",
|
||||
"buildOption": {
|
||||
"strictMode": {
|
||||
"caseSensitiveCheck": true,
|
||||
"useNormalizedOHMUrl": true
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildModeSet": [
|
||||
{
|
||||
"name": "debug",
|
||||
},
|
||||
{
|
||||
"name": "release"
|
||||
}
|
||||
]
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"name": "entry",
|
||||
"srcPath": "./entry",
|
||||
"targets": [
|
||||
{
|
||||
"name": "default",
|
||||
"applyToProducts": [
|
||||
"default"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# HarmonyOS 启动性能监控接入方案
|
||||
|
||||
## 1. 目标
|
||||
|
||||
建立冷启动耗时的线上采集、聚合和看板能力,用于发现版本或分支引入的启动性能劣化。统计口径以“用户点击图标”至“首页首帧完成”为准,单位为毫秒。
|
||||
|
||||
## 2. 总体链路
|
||||
|
||||
`APP_LAUNCH` 系统事件提供启动类型和系统耗时;首页完成首次有效绘制后,通过 `reportDrawnCompleted` 标记首帧终点。客户端将两者组合为自定义启动指标,由 APM SDK 本地采样、聚合后上传;后台按模块配置阈值和采样策略,并在 ELK/Grafana 展示趋势与分位数。
|
||||
|
||||
## 3. 客户端接入
|
||||
|
||||
在 `future_entry_demo/src/main/ets/monitor/APMHelper.ets` 中初始化与现有 `HXMonitor` 同体系的 HarmonyOS 自定义 APM 事件能力;不要直接复用 Android 文档中的 Java API。
|
||||
|
||||
在 `EntryAbility.ets` 中订阅 `APP_LAUNCH`,读取:
|
||||
|
||||
- `start_type`:启动类型;仅 `0` 纳入冷启动统计。
|
||||
- `extend_time`:从抬手到应用报告首帧完成的耗时。
|
||||
- `animation_finish_time`、`icon_input_time`、`bundle_version`:用于分析和问题追溯。
|
||||
|
||||
在首页根节点完成第一次有效绘制时仅上报一次 `reportDrawnCompleted`。隐私页、网络异常页等可能成为首次可见页面的入口也应覆盖,否则对应场景的首帧时间会失真。
|
||||
|
||||
## 4. 指标定义
|
||||
|
||||
| 项目 | 建议值 |
|
||||
|---|---|
|
||||
| 模块 | `launch` |
|
||||
| 指标名 | `cold_start_time` |
|
||||
| 指标值 | 冷启动耗时,单位 ms |
|
||||
| 维度 | `start_type`、`entry_page`、`app_version` |
|
||||
| 分桶 | `[500, 800, 1200, 2000, 3000]` |
|
||||
|
||||
模块名使用小写字母和连字符;指标名不包含连字符。维度控制在必要范围内,避免将用户 ID、完整 URL 等高基数字段作为维度。
|
||||
|
||||
## 5. 后台与看板配置
|
||||
|
||||
按新增 PDF 的自定义 APM 模块流程,在 APM 后台创建 `launch` 模块并启用:全量采样(`sampling_rate: 1000`)、60 秒聚合(`aggre_time: 60`)、累计 100 条触发上报(`aggre_count: 100`)。随后为该模块补充 ELK 索引模板,在 Grafana 建立按版本、启动类型和时间聚合的 P50/P95、超 2 秒占比及分桶分布面板。
|
||||
|
||||
## 6. 兼容与兜底
|
||||
|
||||
现有 `AppEvent.ets` 的 `ElkService` 可以上传结构化启动日志,但它不具备 PDF 所述的指标聚合语义。优先确认 HarmonyOS 是否提供与 Android 自定义 APM SDK 对应的包和 API;若暂未提供,则先通过 `ElkService` 上传 `cold_start_time`、维度和原始系统字段,并由后端补充索引、聚合规则和看板。
|
||||
|
||||
`APP_LAUNCH` 的事件解析必须与崩溃事件分开处理:崩溃事件依赖异常和日志字段,启动事件不包含这些字段。上传应异步执行,不阻塞首页渲染;以 `time + icon_input_time + start_type + process_name` 去重,兼容系统重新投递。
|
||||
|
||||
## 7. 验收标准
|
||||
|
||||
1. 每次冷启动只产生一条可查询记录,且 `extend_time` 大于 0。
|
||||
2. 本地日志/性能工具的耗时与后台数据量级一致。
|
||||
3. 看板可按版本和冷启动维度查询 P50、P95、超阈值占比及分桶分布。
|
||||
4. 断网、重复回调或上传失败不影响启动流程,网络恢复后按既有上传机制重试。
|
||||
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 698 KiB |
@@ -0,0 +1,6 @@
|
||||
/node_modules
|
||||
/oh_modules
|
||||
/.preview
|
||||
/build
|
||||
/.cxx
|
||||
/.test
|
||||
@@ -0,0 +1,31 @@
|
||||
# `entry` HAP Module
|
||||
|
||||
## Scope and layout
|
||||
|
||||
- This checkout is the HarmonyOS stage-model `entry` HAP module, not the complete application workspace.
|
||||
- Production ArkTS: `src/main/ets/`. Existing areas include `pages/`, `widget/`, `entryability/`, `communication/`, `initialize/`, and `register/`.
|
||||
- Resources: `src/main/resources/`.
|
||||
- Local Hypium tests: `src/test/`; device/ability Hypium tests: `src/ohosTest/`.
|
||||
- Module metadata: `src/main/module.json5`; build targets/configuration: `build-profile.json5`.
|
||||
- `hvigorfile.ts` registers the HAP tasks and HMRouter plugin.
|
||||
|
||||
## Workspace and commands
|
||||
|
||||
`oh-package.json5` uses sibling `file:../...` dependencies, and imports resolve across those siblings. Invoke builds and runs only from the complete parent workspace; its exact root is not verified in this checkout.
|
||||
|
||||
```bash
|
||||
# Parent-workspace commands
|
||||
devecocli build --modules entry@default
|
||||
devecocli build --modules entry@ohosTest
|
||||
devecocli build --modules entry@Official --build-mode release
|
||||
devecocli device list
|
||||
devecocli run --module entry
|
||||
```
|
||||
|
||||
- Configured targets are `default`, `ohosTest`, `OEM`, `Official`, and `forTest`.
|
||||
- Device/ability tests require a connected device or emulator.
|
||||
- No test-runner command is verified here.
|
||||
|
||||
## Generated files
|
||||
|
||||
- `oh-package-lock.json5` is autogenerated; do not edit it manually.
|
||||
@@ -0,0 +1,112 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project identity
|
||||
|
||||
This is the HarmonyOS stage-model `entry` HAP module for **同花顺期货 (THS Futures)**, a financial futures trading app. It is NOT a complete workspace — sibling modules (`biz_common`, `biz_trade`, `biz_quote`, etc.) live alongside this directory and are consumed via `file:../...` dependencies. Always invoke builds and runs from the parent workspace, not this directory.
|
||||
|
||||
## Build and run commands
|
||||
|
||||
```bash
|
||||
# Build specific targets (run from parent workspace)
|
||||
devecocli build --modules entry@default
|
||||
devecocli build --modules entry@ohosTest
|
||||
devecocli build --modules entry@Official --build-mode release
|
||||
|
||||
# List connected devices/emulators
|
||||
devecocli device list
|
||||
|
||||
# Run on device
|
||||
devecocli run --module entry
|
||||
```
|
||||
|
||||
Configured targets: `default`, `ohosTest`, `OEM`, `Official`, `forTest`.
|
||||
|
||||
- `oh-package-lock.json5` is autogenerated — never edit it.
|
||||
- Device/ability tests (`src/ohosTest/`) require a connected device or emulator.
|
||||
|
||||
## Architecture overview
|
||||
|
||||
### App lifecycle and initialization chain
|
||||
|
||||
`EntryAbility` (`src/main/ets/entryability/EntryAbility.ets`) is the main `UIAbility` (singleton, launch type `singleton`). Its `onCreate` runs a carefully ordered initialization sequence:
|
||||
|
||||
1. Push message listener (trade daily notifications via `pushService.receiveMessage`)
|
||||
2. Custom figure registration (`FigureRegistry.registerAll()`)
|
||||
3. Global context initialization (multiple `ContextUtils` variants for different modules, plus `GlobalContext`, `AppUtil`)
|
||||
4. Security monitor, gray test init, cloud store log init
|
||||
5. HMRouter init
|
||||
6. Analytics/ELK upload, UGC client, auth listener, event subscription
|
||||
7. RCP HTTP header configuration
|
||||
8. Future market init chain: `TabConfigManager` → `CommonIndicatorsManage` → code table → `MainContractManager` → `HQTabManager` → `FutureMarketDataManager` → `InactiveContractManager` → `DelayMarketManager` → `TradeInitializor` → `CurveStorage`
|
||||
9. Ads, AI group client, font loading, want caching
|
||||
|
||||
`onWindowStageCreate` then branches on three conditions:
|
||||
- **No network** → `loadNetworkAnomalyPage`
|
||||
- **Privacy not accepted** → `loadPrivacyPage` (LauncherPage)
|
||||
- **Normal** → `loadIndexPage` (the main Index page)
|
||||
|
||||
### Navigation and page registry
|
||||
|
||||
All pages must be registered in `src/main/resources/base/profile/main_pages.json`. The app uses **HMRouter** (`@hadss/hmrouter`) for navigation. The main UI uses a `Tabs` component in `Index.ets` with configurable tab sets (`IndexTabManager` via `TabManager`). On large screens (`lg` breakpoint), tabs render vertically on the left.
|
||||
|
||||
### Communication layer
|
||||
|
||||
`src/main/ets/communication/` manages long-lived TCP socket connections to the trading backend:
|
||||
|
||||
- **`ConnectionInitiator`** — initializes site configuration (IP/port from `Config.ets`), supports inner-network debug mode, and handles debug-tool server overrides.
|
||||
- **`ServiceHandlerImpl`** — registers two connection configurations:
|
||||
- `HqConnectionConfig` (行情): authenticates via `HXAuthManager`, pipes data through `AuthJsonStructObserver`, `RealDataStructObserver`, `SidInstanceIdReceiver`
|
||||
- `CBASConnectionConfig` (analytics): separate connection for CBAS analytics with trade ELK log observer
|
||||
- **`HXSocketMonitor`** — monitors socket health across app foreground/background transitions.
|
||||
|
||||
### Group subscription system (real-time sync)
|
||||
|
||||
`GroupSubManager` (from `base_mobilepush`) manages push-based data synchronization. Handlers in `src/main/ets/groupsub/business/` handle specific data domains:
|
||||
|
||||
- **User-scoped** (`addUserGroupHandler`): self-code lists, YYB account lists, trade settings, indicators, draw lines, AI group, SID data, function authority
|
||||
- **All-user-scoped** (`addAllGroupHandler`): ad strategies, gray test, account analysis, futures companies, stock/variety storage updates, main contract changes
|
||||
|
||||
Each handler receives push notifications and drives local state updates.
|
||||
|
||||
### Service registration pattern
|
||||
|
||||
Services in `src/main/ets/register/` follow a singleton-then-init pattern:
|
||||
- **`cbasService`** (CbasServiceRegister) — analytics/event capture with `HxCBASAgentProvider`
|
||||
- **`UgcClient`** — user-generated content client
|
||||
- **`ElkUploadRegister`** — error log upload
|
||||
- **`SecurityMonitorInit`** — crash protection
|
||||
|
||||
### Tabs and page structure
|
||||
|
||||
The main UI has four tabs (configurable, some may be hidden):
|
||||
1. **首页** (FirstPage) — `@b2c/first_page`
|
||||
2. **行情** (HQPage) — `biz_market`, renders self-codes, futures quotes, option chains
|
||||
3. **交易** (TradeBiz) — `biz_trade`, conditionally hidden via `HexinVersionControl.hideTrade()`
|
||||
4. **发现** (DiscoverPage) — `@b2c/biz_discover`
|
||||
|
||||
Each tab has its own `TabManager` sub-manager. Tab visibility is controlled by `IndexTabManager` which extends `TabManager`.
|
||||
|
||||
### Multi-device adaptation
|
||||
|
||||
The module targets phone, tablet, and 2in1. Responsive breakpoints are handled through `updateBreakpoint()` (from `biz_common`) which broadcasts breakpoint changes (`sm`, `md`, `lg`). Window size changes are monitored in `EntryAbility.onWindowStageCreate`.
|
||||
|
||||
### Key sibling dependencies
|
||||
|
||||
All resolved via `file:../...` in `oh-package.json5`:
|
||||
- `biz_common` — shared utilities, constants, tab management, preferences
|
||||
- `biz_trade` — trading functionality, account management, notifications
|
||||
- `biz_quote` — quote/chart rendering, indicators, settings
|
||||
- `biz_market` — market data, HQ tabs, contracts
|
||||
- `biz_option`, `biz_forecast`, `biz_indicator` — option/forecast/indicator features
|
||||
- `service_push`, `service_watchlist`, `service_aigroup` — push, watchlist, AI services
|
||||
- `biz_debug` — debug tools, server switching
|
||||
- `@kernel/lib_communication` — TCP socket connection library
|
||||
- `@kernel/theme_manager` — theming and color mode
|
||||
- `@hadss/hmrouter` — route management
|
||||
- `@ohos/hypium` — test framework
|
||||
|
||||
### Debug infrastructure
|
||||
|
||||
In non-release builds, a `DebugFloatingBall` overlay is created via `windowStage.createSubWindow()`. Debug pages live under `pages/debug/` and support server switching, gray test configuration, cloud storage inspection, URL modification, and memory leak monitoring.
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"apiType": "stageMode",
|
||||
"buildOption": {
|
||||
"arkOptions": {
|
||||
// "apPath": "./modules.ap" /* Profile used for profile-guided optimization (PGO), a compiler optimization technique to improve app runtime performance. */
|
||||
}
|
||||
},
|
||||
"buildOptionSet": [
|
||||
{
|
||||
"name": "release",
|
||||
"arkOptions": {
|
||||
"obfuscation": {
|
||||
"ruleOptions": {
|
||||
"enable": true,
|
||||
"files": [
|
||||
"./obfuscation-rules.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"name": "default"
|
||||
},
|
||||
{
|
||||
"name": "ohosTest",
|
||||
},{
|
||||
"name": "OEM",
|
||||
"output": {"artifactName": "OEM"}
|
||||
},{
|
||||
"name": "Official",
|
||||
"output": {"artifactName": "futures-Official"}
|
||||
},{
|
||||
"name": "forTest",
|
||||
"output": {"artifactName": "futures-forTest"}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
-keep-file-name
|
||||
-keep-property-name
|
||||
-keep-global-name
|
||||
@@ -0,0 +1,7 @@
|
||||
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
|
||||
import { hapPlugin } from '@hadss/hmrouter-plugin';
|
||||
|
||||
export default {
|
||||
system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
|
||||
plugins:[hapPlugin()] /* Custom plugin to extend the functionality of Hvigor. */
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Define project specific obfuscation rules here.
|
||||
# You can include the obfuscation configuration files in the current module's build-profile.json5.
|
||||
#
|
||||
# For more details, see
|
||||
# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md
|
||||
|
||||
# Obfuscation options:
|
||||
# -disable-obfuscation: disable all obfuscations
|
||||
# -enable-property-obfuscation: obfuscate the property names
|
||||
# -enable-toplevel-obfuscation: obfuscate the names in the global scope
|
||||
# -compact: remove unnecessary blank spaces and all line feeds
|
||||
# -remove-log: remove all console.* statements
|
||||
# -print-namecache: print the name cache that contains the mapping from the old names to new names
|
||||
# -apply-namecache: reuse the given cache file
|
||||
|
||||
# Keep options:
|
||||
# -keep-property-name: specifies property names that you want to keep
|
||||
# -keep-global-name: specifies names that you want to keep in the global scope
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"license": "",
|
||||
"devDependencies": {},
|
||||
"author": "",
|
||||
"name": "entry",
|
||||
"description": "Please describe the basic information.",
|
||||
"main": "",
|
||||
"version": "1.0.0",
|
||||
"dynamicDependencies": {},
|
||||
"dependencies": {
|
||||
"@ohos/hypium": "1.0.18",
|
||||
"@kernel/theme_manager": "1.0.2-beta.17",
|
||||
"biz_common": "file:../biz_common",
|
||||
"biz_forecast": "file:../biz_forecast",
|
||||
"biz_option": "file:../biz_option",
|
||||
"biz_indicator": "file:../biz_indicator",
|
||||
"@b2c-f/billparsemodule": "1.0.0",
|
||||
"@b2c-f/accountanalysis": "1.3.0",
|
||||
"biz_debug": "file:../biz_debug",
|
||||
"biz_tradecondition": "file:../biz_tradecondition",
|
||||
"service_aigroup": "file:../service_aigroup",
|
||||
"@b2c-f/lib_fuhm_theme_res": "1.0.9-alpha.2",
|
||||
"@kernel/ohos_leak_watcher": "1.0.0-beta.8"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import identifier from '@ohos.identifier.oaid';
|
||||
import hilog from '@ohos.hilog';
|
||||
import { BusinessError } from '@ohos.base';
|
||||
|
||||
export class DeviceInfoHolder {
|
||||
|
||||
init() {
|
||||
try {
|
||||
identifier.getOAID().then((data) => {
|
||||
const oaid: string = data;
|
||||
hilog.info(0x0000, 'mhy', '%{public}s', `get oaid by promise success, oaid: ${oaid}`);
|
||||
}).catch((err: BusinessError) => {
|
||||
hilog.info(0x0000, 'mhy', '%{public}s', `get oaid by promise failed, code: ${err.code}, message: ${err.message}`);
|
||||
})
|
||||
|
||||
} catch(err) {
|
||||
let e: BusinessError = err as BusinessError;
|
||||
console.error('createDeviceManager errCode:' + e.code + ',errMessage:' + e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/**
|
||||
* 交易日志转换管理器
|
||||
* 负责将推送数据库中的数据转换到交易日志数据库
|
||||
*/
|
||||
|
||||
import { TradeDailyPushDataManager } from './TradeDailyPushDataManager';
|
||||
import { TradeDailyPushModel } from './TradeDailyPushModel';
|
||||
import { TradeDailyDataManager, TradeDailyModel, TradeMsgUniqueIdManager } from 'biz_trade';
|
||||
import { PushType } from 'biz_trade/src/main/ets/service/push/PushType';
|
||||
import { AppUtil } from '@pura/harmony-utils';
|
||||
import { HXLog } from 'biz_common';
|
||||
import { CodeTableManager } from 'lib_codetablemanager';
|
||||
import { AbilityConstant, ApplicationStateChangeCallback, common, Context } from '@kit.AbilityKit';
|
||||
|
||||
const TAG = 'TradeDailyConvertManager';
|
||||
|
||||
/**
|
||||
* 解析结果接口
|
||||
*/
|
||||
interface ParseResult {
|
||||
dailyTitle: string;
|
||||
dailyContent: string;
|
||||
dailyTime: string;
|
||||
}
|
||||
|
||||
export class TradeDailyConvertManager {
|
||||
private static instance: TradeDailyConvertManager;
|
||||
|
||||
private applicationStateChangeCallBack: ApplicationStateChangeCallback = {
|
||||
onApplicationForeground: (): void => {
|
||||
this.convertDailyFromPush();
|
||||
},
|
||||
onApplicationBackground: (): void => {
|
||||
}
|
||||
}
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
static getInstance(): TradeDailyConvertManager {
|
||||
if (!TradeDailyConvertManager.instance) {
|
||||
TradeDailyConvertManager.instance = new TradeDailyConvertManager();
|
||||
}
|
||||
return TradeDailyConvertManager.instance;
|
||||
}
|
||||
|
||||
async registerForeground(context: Context): Promise<void> {
|
||||
context.getApplicationContext().on('applicationStateChange', this.applicationStateChangeCallBack)
|
||||
await this.convertDailyFromPush();
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用进入前台时转换推送数据
|
||||
* 将推送数据库中的数据转换到交易日志数据库
|
||||
*/
|
||||
async convertDailyFromPush(): Promise<void> {
|
||||
try {
|
||||
HXLog.i(TAG, '开始转换推送数据到交易日志');
|
||||
|
||||
// 查询所有推送日志
|
||||
const pushDailys = await TradeDailyPushDataManager.getInstance().queryDaily();
|
||||
|
||||
if (pushDailys.length === 0) {
|
||||
HXLog.i(TAG, '没有需要转换的推送数据');
|
||||
return;
|
||||
}
|
||||
|
||||
HXLog.i(TAG, `查询到 ${pushDailys.length} 条推送数据`);
|
||||
|
||||
// 转换每条推送日志
|
||||
for (const pushDaily of pushDailys) {
|
||||
// 过滤放弃行权的推送(单号不变,无法去重)
|
||||
if (this.shouldSkipPushDaily(pushDaily)) {
|
||||
HXLog.i(TAG, `跳过放弃行权推送: ${pushDaily.body}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await this.convertPushDailyToTradeDaily(pushDaily);
|
||||
}
|
||||
|
||||
// 删除已转换的推送日志
|
||||
await TradeDailyPushDataManager.getInstance().deleteDaily(pushDailys);
|
||||
HXLog.i(TAG, '推送数据转换完成');
|
||||
} catch (error) {
|
||||
HXLog.e(TAG, `转换推送数据失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否应该跳过该推送日志
|
||||
* @param pushDaily 推送日志
|
||||
*/
|
||||
private shouldSkipPushDaily(pushDaily: TradeDailyPushModel): boolean {
|
||||
const type = pushDaily.type
|
||||
|
||||
// 放弃行权push不记录(单号不变,无法去重)
|
||||
const abandonExercise = AppUtil.getContext().resourceManager.getStringSync($r('app.string.trade_daily_abandon_exercise'));
|
||||
if ((type == PushType.TY_OPTION_EXEC_WEITUO ||
|
||||
type == PushType.TY_OPTION_EXEC_CHEDAN) &&
|
||||
pushDaily.body.includes(abandonExercise)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将推送日志转换为交易日志
|
||||
* @param pushDaily 推送日志
|
||||
*/
|
||||
private async convertPushDailyToTradeDaily(pushDaily: TradeDailyPushModel): Promise<void> {
|
||||
try {
|
||||
const isSame = await TradeMsgUniqueIdManager.getInstance().isSameIdentify(
|
||||
pushDaily.time,
|
||||
pushDaily.iconType,
|
||||
pushDaily.type,
|
||||
pushDaily.orderNo
|
||||
);
|
||||
|
||||
if (isSame) {
|
||||
HXLog.i(TAG, `推送已存在,跳过: ${pushDaily.tradeDate} ${pushDaily.time}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const body = pushDaily.body;
|
||||
const title = pushDaily.title;
|
||||
|
||||
// 解析推送内容
|
||||
const parseResult = await this.parsePushContent(body, title, pushDaily);
|
||||
const dailyTitle = parseResult.dailyTitle;
|
||||
const dailyContent = parseResult.dailyContent;
|
||||
const dailyTime = parseResult.dailyTime;
|
||||
|
||||
// 创建交易日志模型
|
||||
const dailyModel = TradeDailyModel.createWithDateTime(
|
||||
pushDaily.tradeDate,
|
||||
dailyTime,
|
||||
dailyTitle,
|
||||
dailyContent
|
||||
);
|
||||
|
||||
// 插入到所有匹配的账户表中
|
||||
await this.insertToMatchingAccounts(dailyModel, pushDaily.tradeNo, pushDaily.qsid);
|
||||
|
||||
HXLog.i(TAG, `转换推送日志成功: ${pushDaily.tradeDate} ${dailyTime} ${dailyTitle}`);
|
||||
} catch (error) {
|
||||
HXLog.e(TAG, `转换推送日志失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析推送内容
|
||||
* @param body 推送内容
|
||||
* @param title 推送标题
|
||||
* @param pushDaily 推送日志
|
||||
*/
|
||||
private async parsePushContent(body: string, title: string, pushDaily: TradeDailyPushModel): Promise<ParseResult> {
|
||||
let dailyTitle = '';
|
||||
let dailyContent = '';
|
||||
let dailyTime = pushDaily.tradeTime;
|
||||
|
||||
const iconType = pushDaily.iconType;
|
||||
const orderNo = this.getOrderNo(pushDaily);
|
||||
const orderNoPrefix = AppUtil.getContext().resourceManager.getStringSync($r('app.string.trade_daily_order_no_prefix_with_comma'));
|
||||
const transactionReport = AppUtil.getContext().resourceManager.getStringSync($r('app.string.trade_daily_transaction_report'));
|
||||
|
||||
// 错误类型处理(iconType == 3 表示错误)
|
||||
if (iconType == '3') {
|
||||
dailyContent = body;
|
||||
dailyTime = pushDaily.tradeTime;
|
||||
|
||||
// 提取时间 [15:01:30]
|
||||
const firstIndex = body.indexOf('[');
|
||||
const lastIndex = body.indexOf(']');
|
||||
if (firstIndex > -1 && lastIndex > -1) {
|
||||
const timeStr = body.substring(firstIndex + 1, lastIndex);
|
||||
dailyTime = timeStr.replace(/:/g, '');
|
||||
// 从 body 末��减去时间部分(包括括号)
|
||||
dailyContent = body.substring(0, body.length - timeStr.length - 4);
|
||||
}
|
||||
|
||||
if (orderNo.length > 0) {
|
||||
dailyContent += orderNoPrefix + orderNo;
|
||||
}
|
||||
} else {
|
||||
// 正常类型处理
|
||||
const firstIndex = body.indexOf('(');
|
||||
const lastIndex = body.indexOf(')');
|
||||
|
||||
if (firstIndex > -1 && lastIndex > -1) {
|
||||
// 标题处理
|
||||
if (!title.includes(transactionReport)) {
|
||||
dailyTitle += title;
|
||||
}
|
||||
dailyTitle += body.substring(0, firstIndex);
|
||||
|
||||
// 内容处理
|
||||
const alertContent = body.substring(firstIndex + 1, lastIndex);
|
||||
const alertContentArr = alertContent.split(',');
|
||||
|
||||
if (alertContentArr.length > 0) {
|
||||
const tradeCode = alertContentArr[0];
|
||||
const stockInfo = await CodeTableManager.get().getStockInfoByTradeCode(tradeCode);
|
||||
const stockName = stockInfo?.stockName;
|
||||
|
||||
if (stockName && stockName.length > 0) {
|
||||
// 将合约代码替换为合约名
|
||||
dailyContent += stockName;
|
||||
for (let i = 1; i < alertContentArr.length; i++) {
|
||||
dailyContent += ',' + alertContentArr[i];
|
||||
}
|
||||
} else {
|
||||
dailyContent = alertContent;
|
||||
}
|
||||
} else {
|
||||
dailyContent = alertContent;
|
||||
}
|
||||
|
||||
if (orderNo.length > 0) {
|
||||
dailyContent += orderNoPrefix + orderNo;
|
||||
}
|
||||
|
||||
// 时间处理
|
||||
// iOS: timeFromBody = body.substring(loc: last + 2, len: body.count - last - 3)
|
||||
dailyTime = pushDaily.tradeTime;
|
||||
if (dailyTime.length === 0 && lastIndex + 2 < body.length && body.length - lastIndex - 3 > 0) {
|
||||
const startPos = lastIndex + 2;
|
||||
const length = body.length - lastIndex - 3;
|
||||
dailyTime = body.substring(startPos, startPos + length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除时间中的冒号
|
||||
dailyTime = dailyTime.replace(/:/g, '');
|
||||
|
||||
return { dailyTitle, dailyContent, dailyTime };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单号
|
||||
* @param pushDaily 推送日志
|
||||
*/
|
||||
private getOrderNo(pushDaily: TradeDailyPushModel): string {
|
||||
const type = pushDaily.type;
|
||||
let orderNo = '';
|
||||
|
||||
// 期货成交2,期权成交8,需要用orderSysId
|
||||
if (type == PushType.TY_JIAOYI_CHENGGONG || type == PushType.TY_OPTION_CHEGNJIAO) {
|
||||
orderNo = pushDaily.orderSysId || pushDaily.orderNo;
|
||||
}
|
||||
// 条件单触发,preOrderNo有值则是附加条件单,用母单编号
|
||||
else if (type == PushType.TY_CONDITION && pushDaily.preOrderNo) {
|
||||
orderNo = pushDaily.preOrderNo;
|
||||
}
|
||||
else {
|
||||
orderNo = pushDaily.orderNo;
|
||||
}
|
||||
|
||||
// 移除空格
|
||||
return orderNo.replace(/\s/g, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入到所有匹配的账户表中
|
||||
* @param model 交易日志模型
|
||||
* @param accountId 账户ID
|
||||
* @param qsId 券商ID
|
||||
*/
|
||||
private async insertToMatchingAccounts(
|
||||
model: TradeDailyModel,
|
||||
accountId: string,
|
||||
qsId: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
// 获取所有表名
|
||||
const tableNames = await TradeDailyDataManager.getInstance().getAllTableNames();
|
||||
|
||||
// 匹配 trade_daily_{accountId}_{qsId}_{wtId} 格式
|
||||
const prefix = `trade_daily_${accountId}_${qsId}_`;
|
||||
|
||||
for (const tableName of tableNames) {
|
||||
if (tableName.startsWith(prefix)) {
|
||||
const wtId = tableName.substring(prefix.length);
|
||||
await TradeDailyDataManager.getInstance().insertDailyToAccount(
|
||||
model,
|
||||
accountId,
|
||||
qsId,
|
||||
wtId
|
||||
);
|
||||
HXLog.i(TAG, `插入到账户表: ${tableName}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
HXLog.e(TAG, `插入到匹配账户失败: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* 交易推送日志数据管理器
|
||||
*/
|
||||
|
||||
import relationalStore from '@ohos.data.relationalStore';
|
||||
import { TradeDailyPushModel } from './TradeDailyPushModel';
|
||||
import { BusinessError } from '@ohos.base';
|
||||
import hilog from '@ohos.hilog';
|
||||
|
||||
const DB_NAME = 'trade_push_daily.db';
|
||||
const TAG = 'TradeDailyPushDataManager'
|
||||
const DOMAIN = 0xFF01
|
||||
const FORMAT = '%{public}s'
|
||||
|
||||
export class TradeDailyPushDataManager {
|
||||
private static instance: TradeDailyPushDataManager;
|
||||
private rdbStore: relationalStore.RdbStore | null = null;
|
||||
private isInitialized: boolean = false;
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
static getInstance(): TradeDailyPushDataManager {
|
||||
if (!TradeDailyPushDataManager.instance) {
|
||||
TradeDailyPushDataManager.instance = new TradeDailyPushDataManager();
|
||||
}
|
||||
return TradeDailyPushDataManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据库
|
||||
*/
|
||||
async initDB(context: ESObject): Promise<void> {
|
||||
if (this.isInitialized && this.rdbStore) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config: relationalStore.StoreConfig = {
|
||||
name: DB_NAME,
|
||||
securityLevel: relationalStore.SecurityLevel.S1,
|
||||
customDir: 'trade'
|
||||
};
|
||||
|
||||
this.rdbStore = await relationalStore.getRdbStore(context, config);
|
||||
await this.createTable();
|
||||
this.isInitialized = true;
|
||||
} catch (err) {
|
||||
const error = err as BusinessError;
|
||||
hilog.error(DOMAIN, TAG, FORMAT, `初始化数据库失败: ${error.code}, ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表
|
||||
*/
|
||||
private async createTable(): Promise<void> {
|
||||
if (!this.rdbStore) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const sql = TradeDailyPushModel.getTableSQL();
|
||||
await this.rdbStore.executeSql(sql);
|
||||
} catch (err) {
|
||||
const error = err as BusinessError;
|
||||
hilog.error(DOMAIN, TAG, FORMAT, `创建表失败: ${error.code}, ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入数据
|
||||
*/
|
||||
async insertDaily(daily: TradeDailyPushModel): Promise<void> {
|
||||
if (!this.rdbStore) {
|
||||
hilog.error(DOMAIN, TAG, FORMAT, '数据库未初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const valueBucket = daily.toDbRecord();
|
||||
await this.rdbStore.insert(TradeDailyPushModel.getTableName(), valueBucket);
|
||||
hilog.info(DOMAIN, TAG, FORMAT, '插入数据成功')
|
||||
} catch (err) {
|
||||
const error = err as BusinessError;
|
||||
hilog.error(DOMAIN, TAG, FORMAT, `插入数据失败: ${error.code}, ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
*/
|
||||
async deleteDaily(models: TradeDailyPushModel[]): Promise<void> {
|
||||
if (!this.rdbStore) {
|
||||
hilog.error(DOMAIN, TAG, FORMAT, '数据库未初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const tableName = TradeDailyPushModel.getTableName();
|
||||
|
||||
for (const model of models) {
|
||||
if (!model.uniqueId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const predicates = new relationalStore.RdbPredicates(tableName);
|
||||
predicates.equalTo('uniqueId', model.uniqueId);
|
||||
await this.rdbStore.delete(predicates);
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as BusinessError;
|
||||
hilog.error(DOMAIN, TAG, FORMAT, `删除数据失败: ${error.code}, ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有数据
|
||||
*/
|
||||
async queryDaily(): Promise<TradeDailyPushModel[]> {
|
||||
if (!this.rdbStore) {
|
||||
hilog.error(DOMAIN, TAG, FORMAT, '数据库未初始化');
|
||||
return [];
|
||||
}
|
||||
|
||||
let resultSet: relationalStore.ResultSet | null = null;
|
||||
try {
|
||||
const tableName = TradeDailyPushModel.getTableName();
|
||||
const predicates = new relationalStore.RdbPredicates(tableName);
|
||||
resultSet = await this.rdbStore.query(predicates, TradeDailyPushModel.getColumns());
|
||||
|
||||
const models: TradeDailyPushModel[] = [];
|
||||
|
||||
if (resultSet.rowCount === 0) {
|
||||
return models;
|
||||
}
|
||||
|
||||
resultSet.goToFirstRow();
|
||||
do {
|
||||
const record: relationalStore.ValuesBucket = {};
|
||||
const columns = TradeDailyPushModel.getColumns();
|
||||
|
||||
for (const column of columns) {
|
||||
const columnIndex = resultSet.getColumnIndex(column);
|
||||
if (columnIndex >= 0) {
|
||||
record[column] = resultSet.getString(columnIndex);
|
||||
}
|
||||
}
|
||||
|
||||
const model = TradeDailyPushModel.fromDbRecord(record);
|
||||
models.push(model);
|
||||
} while (resultSet.goToNextRow());
|
||||
|
||||
return models;
|
||||
} catch (err) {
|
||||
const error = err as BusinessError;
|
||||
hilog.error(DOMAIN, TAG, FORMAT, `查询数据失败: ${error.code}, ${error.message}`);
|
||||
return [];
|
||||
} finally {
|
||||
resultSet?.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 pushInfo 转换为 TradeDailyPushModel
|
||||
* @param pushInfo 推送信息
|
||||
* @returns 转换后的模型,若转换失败则返回 null
|
||||
*/
|
||||
convertToTradeDailyPushModel(pushInfo: ESObject): TradeDailyPushModel | null {
|
||||
const body = pushInfo['DESC'] as string;
|
||||
const msg = pushInfo['APPMSG'] as Map<string, object>;
|
||||
|
||||
if (!msg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tradeNo = msg['tradeno'] as string;
|
||||
const qsid = msg['qsid'] as string;
|
||||
|
||||
if (!tradeNo || !qsid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const model = new TradeDailyPushModel();
|
||||
model.body = body ?? '';
|
||||
model.tradeNo = tradeNo;
|
||||
model.qsid = qsid;
|
||||
|
||||
const orderNo = msg['orderNumber'] as string;
|
||||
if (orderNo) {
|
||||
model.orderNo = orderNo;
|
||||
}
|
||||
|
||||
// 期货成交2,期权成交8,需要用orderSysId
|
||||
const orderSysId = msg['orderSysId'] as string;
|
||||
if (orderSysId) {
|
||||
model.orderSysId = orderSysId;
|
||||
}
|
||||
|
||||
// 条件单触发, preOrderNo有值则是附加条件单,用母单编号
|
||||
const preOrderNo = msg['preOrderNumber'] as string;
|
||||
if (preOrderNo) {
|
||||
model.preOrderNo = preOrderNo;
|
||||
}
|
||||
|
||||
const uniqueid = msg['uniqueid'] as string;
|
||||
if (uniqueid) {
|
||||
model.uniqueId = uniqueid;
|
||||
}
|
||||
|
||||
const tradeTime = msg['ST2'] as string;
|
||||
if (tradeTime) {
|
||||
model.tradeTime = tradeTime;
|
||||
model.time = tradeTime;
|
||||
}
|
||||
|
||||
// 日期用收到通知的日期
|
||||
const now = new Date();
|
||||
model.tradeDate = this.formatDate(now, 'yyyyMMdd');
|
||||
|
||||
const iconType = msg['IC'] as string;
|
||||
if (iconType) {
|
||||
model.iconType = iconType;
|
||||
}
|
||||
|
||||
const type = msg['TY'] as string;
|
||||
if (type) {
|
||||
model.type = type;
|
||||
// 期货撤单3、期权撤单9、期权行权撤单52,这几种后端给的委托时间,不对,需要用收到回报的时间
|
||||
const typeNum = parseInt(type);
|
||||
if (typeNum === 3 || typeNum === 9 || typeNum === 52) {
|
||||
model.tradeTime = this.formatDate(now, 'HH:mm:ss');
|
||||
// 推送中的已撤单给过滤了,和标题重复,产品不想要
|
||||
model.body = body.replace(',已撤单', '');
|
||||
}
|
||||
}
|
||||
|
||||
const title = msg['ST'] as string;
|
||||
if (title) {
|
||||
model.title = title;
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录推送日志
|
||||
*/
|
||||
async recordPushDaily(model: TradeDailyPushModel): Promise<void> {
|
||||
hilog.info(DOMAIN, TAG, FORMAT, `[daily] notification-extension push daily: ${model.tradeDate} ${model.time} ${model.title} ${model.body}`);
|
||||
|
||||
const type = parseInt(model.type);
|
||||
// 过滤条件单(5)和盈亏(6)类型
|
||||
if (type === 5 || type === 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.insertDaily(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
*/
|
||||
private formatDate(date: Date, format: string): string {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
if (format === 'yyyyMMdd') {
|
||||
return `${year}${month}${day}`;
|
||||
} else if (format === 'HH:mm:ss') {
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 交易推送日报数据模型
|
||||
* Created by wenzhe on 2022/6/6.
|
||||
* Copyright © 2022 hexin. All rights reserved.
|
||||
*/
|
||||
|
||||
import relationalStore from '@ohos.data.relationalStore';
|
||||
|
||||
export class TradeDailyPushModel {
|
||||
/** 交易账号 */
|
||||
tradeNo: string = '';
|
||||
/** qsId */
|
||||
qsid: string = '';
|
||||
/** 订单号 */
|
||||
orderNo: string = '';
|
||||
/** 订单号(已成交) */
|
||||
orderSysId: string = '';
|
||||
/** 母单号(附加条件单) */
|
||||
preOrderNo: string = '';
|
||||
/** 消息唯一号 */
|
||||
uniqueId: string = '';
|
||||
/** 交易类型 */
|
||||
type: string = '';
|
||||
/** 交易时间(app显示) */
|
||||
tradeTime: string = '';
|
||||
/** 交易时间(后端推的时间) */
|
||||
time: string = '';
|
||||
/** 交易日期 */
|
||||
tradeDate: string = '';
|
||||
/** 标题 */
|
||||
title: string = '';
|
||||
/** 消息内容 */
|
||||
body: string = '';
|
||||
/** icon类型 */
|
||||
iconType: string = '';
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表名
|
||||
*/
|
||||
static getTableName(): string {
|
||||
return 'trade_push_daily';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表结构SQL
|
||||
*/
|
||||
static getTableSQL(): string {
|
||||
return `CREATE TABLE IF NOT EXISTS ${TradeDailyPushModel.getTableName()} (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
body TEXT,
|
||||
tradeNo TEXT,
|
||||
qsid TEXT,
|
||||
orderNo TEXT,
|
||||
orderSysId TEXT,
|
||||
preOrderNo TEXT,
|
||||
uniqueId TEXT,
|
||||
type TEXT,
|
||||
tradeTime TEXT,
|
||||
time TEXT,
|
||||
tradeDate TEXT,
|
||||
title TEXT,
|
||||
iconType TEXT
|
||||
)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有列名
|
||||
*/
|
||||
static getColumns(): string[] {
|
||||
return [
|
||||
'id',
|
||||
'body',
|
||||
'tradeNo',
|
||||
'qsid',
|
||||
'orderNo',
|
||||
'orderSysId',
|
||||
'preOrderNo',
|
||||
'uniqueId',
|
||||
'type',
|
||||
'tradeTime',
|
||||
'time',
|
||||
'tradeDate',
|
||||
'title',
|
||||
'iconType'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库记录转换为模型对象
|
||||
*/
|
||||
static fromDbRecord(record: relationalStore.ValuesBucket): TradeDailyPushModel {
|
||||
const model = new TradeDailyPushModel();
|
||||
model.body = (record['body'] as string) ?? '';
|
||||
model.tradeNo = (record['tradeNo'] as string) ?? '';
|
||||
model.qsid = (record['qsid'] as string) ?? '';
|
||||
model.orderNo = (record['orderNo'] as string) ?? '';
|
||||
model.orderSysId = (record['orderSysId'] as string) ?? '';
|
||||
model.preOrderNo = (record['preOrderNo'] as string) ?? '';
|
||||
model.uniqueId = (record['uniqueId'] as string) ?? '';
|
||||
model.type = (record['type'] as string) ?? '';
|
||||
model.tradeTime = (record['tradeTime'] as string) ?? '';
|
||||
model.time = (record['time'] as string) ?? '';
|
||||
model.tradeDate = (record['tradeDate'] as string) ?? '';
|
||||
model.title = (record['title'] as string) ?? '';
|
||||
model.iconType = (record['iconType'] as string) ?? '';
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为数据库记录
|
||||
*/
|
||||
toDbRecord(): relationalStore.ValuesBucket {
|
||||
const record: relationalStore.ValuesBucket = {};
|
||||
record['body'] = this.body;
|
||||
record['tradeNo'] = this.tradeNo;
|
||||
record['qsid'] = this.qsid;
|
||||
record['orderNo'] = this.orderNo;
|
||||
record['orderSysId'] = this.orderSysId;
|
||||
record['preOrderNo'] = this.preOrderNo;
|
||||
record['uniqueId'] = this.uniqueId;
|
||||
record['type'] = this.type;
|
||||
record['tradeTime'] = this.tradeTime;
|
||||
record['time'] = this.time;
|
||||
record['tradeDate'] = this.tradeDate;
|
||||
record['title'] = this.title;
|
||||
record['iconType'] = this.iconType;
|
||||
return record;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { HXLog, PreferenceService } from 'biz_common';
|
||||
import { JSBaseEvent } from 'biz_webview';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
|
||||
/**
|
||||
* 撤回同意隐私政策协议
|
||||
*/
|
||||
export class PrivacyWithDrawEvent extends JSBaseEvent {
|
||||
|
||||
getName(): string {
|
||||
return "privacyWithdraw";
|
||||
}
|
||||
|
||||
getHandler(): Function {
|
||||
return (data: object, handler: Function) => {
|
||||
//TODO这里提了ir单确认能否改成自定义弹窗,否则UI效果不统一
|
||||
AlertDialog.show(
|
||||
{
|
||||
title: '撤回同意将退出同花顺,我们无法再为您提供服务',
|
||||
//subtitle: 'subtitle',
|
||||
message: '',
|
||||
autoCancel: true,
|
||||
alignment: DialogAlignment.Center,
|
||||
gridCount: 4,
|
||||
showInSubWindow: true,
|
||||
isModal: true,
|
||||
offset: { dx: 0, dy: -20 },
|
||||
buttonDirection: DialogButtonDirection.HORIZONTAL,
|
||||
buttons: [
|
||||
{
|
||||
value: '撤回并退出',
|
||||
action: () => {
|
||||
PreferenceService.putVal('Privacy', 'isUserAgree', false, true);
|
||||
(getContext(this) as common.UIAbilityContext).terminateSelf(() => {});
|
||||
}
|
||||
},
|
||||
{
|
||||
value: '继续使用',
|
||||
action: () => {
|
||||
|
||||
}
|
||||
},
|
||||
],
|
||||
cancel: () => {
|
||||
console.info('Closed callbacks')
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dialogController: CustomDialogController = new CustomDialogController({
|
||||
builder: CustomDialogExample({
|
||||
cancel: ()=> { this.onCancel() },
|
||||
confirm: ()=> { this.onAccept() },
|
||||
}),
|
||||
})
|
||||
|
||||
onCancel() {
|
||||
console.info('Callback when the first button is clicked')
|
||||
}
|
||||
|
||||
onAccept() {
|
||||
console.info('Callback when the second button is clicked')
|
||||
}
|
||||
}
|
||||
|
||||
@CustomDialog
|
||||
struct CustomDialogExample {
|
||||
cancel?: () => void
|
||||
confirm?: () => void
|
||||
controller: CustomDialogController
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text('我是内容').fontSize(20).margin({ top: 10, bottom: 10 })
|
||||
Flex({ justifyContent: FlexAlign.SpaceAround }) {
|
||||
Button('cancel')
|
||||
.onClick(() => {
|
||||
this.controller.close()
|
||||
if (this.cancel) {
|
||||
this.cancel()
|
||||
}
|
||||
}).backgroundColor(0xffffff).fontColor(Color.Black)
|
||||
Button('confirm')
|
||||
.onClick(() => {
|
||||
this.controller.close()
|
||||
if (this.confirm) {
|
||||
this.confirm()
|
||||
}
|
||||
}).backgroundColor(0xffffff).fontColor(Color.Red)
|
||||
}.margin({ bottom: 10 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import BackupExtensionAbility, {BundleVersion} from '@ohos.application.BackupExtensionAbility';
|
||||
import MmkvTransferManager from './MMKVManager'
|
||||
|
||||
const TAG = `FileBackupExtensionAbility`;
|
||||
export default class BackupExtension extends BackupExtensionAbility {
|
||||
async onBackup () {
|
||||
console.info(TAG, `onBackup ok`);
|
||||
}
|
||||
|
||||
async onRestore (bundleVersion : BundleVersion) {
|
||||
console.info(TAG, `onRestore ok ${JSON.stringify(bundleVersion)}`);
|
||||
console.info(TAG, `onRestore end`);
|
||||
new MmkvTransferManager().transfer(this.context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
import common from '@ohos.app.ability.common';
|
||||
import fs from '@ohos.file.fs';
|
||||
import { MMKV} from '@ohos/mmkv'
|
||||
import dataPreferences from '@ohos.data.preferences';
|
||||
|
||||
// mmkv值类型定义
|
||||
|
||||
type MmkvValueType = number | Set<string> | string | boolean;
|
||||
|
||||
/**
|
||||
* 双->单 mmkv文件迁移转换管理
|
||||
*
|
||||
* @since 2024-04-20
|
||||
*/
|
||||
export default class MmkvTransferManager {
|
||||
/**
|
||||
* 双->单 mmkv文件转换
|
||||
*当前例子中:
|
||||
*
|
||||
XXAJmmkvX177): source_mmkv source_
|
||||
mmkv. crc
|
||||
* 单的mmkv文件名为:target_
|
||||
mmkv
|
||||
*
|
||||
* @param context context上下文
|
||||
*/
|
||||
public async transfer(context: common.Context): Promise<void> {
|
||||
//第1步,move双框架mmkv文件到单框架指定目录
|
||||
let result = await this.moveMmkvFile(context);
|
||||
console.log("FileBackupExtensionAbility", "startMove")
|
||||
if (!result) {
|
||||
console.log("FileBackupExtensionAbility", "moveFailed")
|
||||
return;
|
||||
}
|
||||
// 第2步,解析双的mmkv的内容
|
||||
let dataMap = this.parseSourceMmkvInfo(context);
|
||||
console.log("FileBackupExtensionAbility", "dataMap" + JSON.stringify(dataMap.get("stringData")));
|
||||
// 第3步,将解析的双的mmkv中的内容保存到单的mmkv中
|
||||
if (!this.insertToTargetMmkv(context, dataMap)) {
|
||||
console.log("FileBackupExtensionAbility", "failed to insert target")
|
||||
// 数据插入失败的处理逻辑,自行实现
|
||||
return;
|
||||
}
|
||||
// 第4步,从单的mmkv中读取数据
|
||||
let resultMap = this.getDataFromTarget(context);
|
||||
let mmkvData = resultMap.get("legacyUserData")
|
||||
if (typeof mmkvData === 'string') {
|
||||
|
||||
// // 新建并打开文件
|
||||
// let file = fs.openSync(context.filesDir + '/userinfo.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
|
||||
// // 写入一段内容至文件
|
||||
// fs.writeSync(file.fd, mmkvData as string);
|
||||
|
||||
let options: dataPreferences.Options = { name: 'backupUserInfo' };
|
||||
let preferences = dataPreferences.getPreferencesSync(context.getApplicationContext(), options);
|
||||
preferences.putSync('data', mmkvData as string);
|
||||
preferences.flush()
|
||||
}
|
||||
// 处理查询结果,根据情况自行实现
|
||||
console.log("FileBackupExtensionAbility", "result" + JSON.stringify(resultMap.get("stringData")))
|
||||
}
|
||||
|
||||
//双mmkv文件搬迁
|
||||
/**
|
||||
* 移动双框架mmkv文件到单框架指定目录
|
||||
* 文件参考@link FileTransferManager.moveFiles
|
||||
*
|
||||
* @param context context上下文
|
||||
* @return 成功返回true, 否则false
|
||||
*/
|
||||
private async moveMmkvFile(context: common.Context): Promise<boolean> {
|
||||
try {
|
||||
if (fs.accessSync("/data/storage/el2/backup/restore/com.hexin.plat.android/ce/files/mmkv_migratable")) {
|
||||
fs.listFileSync("/data/storage/el2/backup/restore/com.hexin.plat.android/ce/files/mmkv_migratable", {
|
||||
recursion: true
|
||||
}).forEach(item => {
|
||||
console.log("FileBackupExtensionAbility", "name = " + item);
|
||||
})
|
||||
}
|
||||
// 双升单数据迁移后,mmkv默认在files/mmkv目录下
|
||||
let sourceMmkvPath: string = '/data/storage/el2/backup/restore/com.hexin.plat.android/ce/files/mmkv_migratable';
|
||||
if (!fs.accessSync(sourceMmkvPath)) {
|
||||
console.log("FileBackupExtensionAbility", "fsaccess failed")
|
||||
return false;
|
||||
}
|
||||
// 得到源路径下面所有的文件和目录
|
||||
let listFileNames: string[] = fs.listFileSync(sourceMmkvPath);
|
||||
if (!listFileNames || listFileNames.length === 0) {
|
||||
console.log("FileBackupExtensionAbility", "listFile empty")
|
||||
return false;
|
||||
}
|
||||
|
||||
// 单mmkv路径, 根据实际路径指定
|
||||
let targetPath: string = context.filesDir + '/mmkv';
|
||||
// 目标目录不存在,则需要创建
|
||||
if (!fs.accessSync(targetPath)) {
|
||||
fs.mkdirSync(targetPath);
|
||||
}
|
||||
|
||||
// 遍历源路径下面的文件或目录,进行移动
|
||||
listFileNames.forEach((fileName: string) => {
|
||||
let srcPath: string = `${sourceMmkvPath}/${fileName}`;
|
||||
let destPath: string = `${targetPath}/${fileName}`;
|
||||
if (fs.statSync(srcPath).isFile()) {
|
||||
fs.moveFileSync(srcPath, destPath);
|
||||
} else {
|
||||
fs.moveDirSync(srcPath, targetPath);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
// 异常处理逻辑,自行实现
|
||||
console.log("FileBackupExtensionAbility", "catch error")
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析双框架mmkv文件
|
||||
* 当前例子中:
|
||||
* 双的mmkv文件名为:source_mmkv, source_mmkv.crc
|
||||
* 其中设置的属性有:intData 整型, boolData bool型, stringData 字符串型,floatData 浮点型,doubleData 双精度浮点型, setData Set<string>类型
|
||||
* 单的mmkv文件名为:target_mmkv
|
||||
*
|
||||
* @param context context上下文
|
||||
* @return map 包含了双mmkv中的数据
|
||||
*/
|
||||
private parseSourceMmkvInfo(context: common.Context): Map<string, MmkvValueType> {
|
||||
let dataMap = new Map<string, MmkvValueType>();
|
||||
let mmkv: MMKV | null = null;
|
||||
try {
|
||||
// 初始化双的mmkv, mmkv文件移动到context.filesDir + '/mmkv' 目录下了
|
||||
MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv');
|
||||
// mmkv文件名
|
||||
let files = this.findDeseriredFile(context);
|
||||
let mmapID: string = files[0];
|
||||
console.log("FileBackupExtensionAbility", "mmapId = " + mmapID);
|
||||
// 秘钥,根据实际情况指定秘钥, 当前未设置秘钥
|
||||
let crpKey: string = '';
|
||||
mmkv = MMKV.getBackedUpMMKVWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, '');
|
||||
// 注:当前mmkv对于双框架mmkv设置的int long float的类型暂不支持,待后续更新
|
||||
dataMap.set('stringData', mmkv.decodeString('key_history_user_info', ''));
|
||||
dataMap.forEach((value, key, map) => {
|
||||
console.log("FileBackupExtensionAbility", "datamap key = " + key + "value = " + value.toString());
|
||||
})
|
||||
} catch (error) {
|
||||
// 异常处理逻辑,自行实现
|
||||
} finally {
|
||||
mmkv?.close();
|
||||
}
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从mmkv_migratable目录中找到所有的文件名
|
||||
*/
|
||||
private findDeseriredFile(context: common.Context) : string[] {
|
||||
let result : string[] = [];
|
||||
let mmkvFilePath = context.filesDir + '/mmkv';
|
||||
let listFileNames: string[] = fs.listFileSync(mmkvFilePath);
|
||||
for (let index = 0; index < listFileNames.length; index++) {
|
||||
const element = listFileNames[index];
|
||||
console.log("FileBackupExtensionAbility", "mmkv files are = " + element)
|
||||
if (element.endsWith(".crc")) {
|
||||
const filePrefix = element.split(".crc")[0];
|
||||
result.push(filePrefix);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入数据到单的mmkv中
|
||||
*
|
||||
* @param context context上下文
|
||||
* @param dataMap 待插入的数据
|
||||
* @return 成功返回true, 否则false
|
||||
*/
|
||||
private insertToTargetMmkv(context: Context, dataMap: Map<string, MmkvValueType>): boolean {
|
||||
let mmkv: MMKV | null = null;
|
||||
try {
|
||||
// 初始化单框架mmkv, 获取mmkv的实例可以根据实际情况自行封装成工具类调用
|
||||
MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv');
|
||||
// mmkv文件名
|
||||
let mmapID: string = 'history_user_info';
|
||||
// 秘钥,根据实际情况指定秘钥, 当前未设置秘钥
|
||||
let crpKey: string = '';
|
||||
mmkv = MMKV.getMMKVWithMMapID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, '');
|
||||
// // 设置请求参数中的值
|
||||
mmkv.encodeString('history_user_info', dataMap.get('stringData') as string);
|
||||
} catch (error) {
|
||||
console.log("FileBackupExtensionAbility", "insertError" + JSON.stringify(error))
|
||||
// 异常处理逻辑,自行实现
|
||||
return false;
|
||||
} finally {
|
||||
mmkv?.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取单框架中mmkv中的数据
|
||||
*
|
||||
* @param context context上下文
|
||||
* @return map 包含单框架mmkv中的数据
|
||||
*/
|
||||
private getDataFromTarget(context: common.Context): Map<string, MmkvValueType> {
|
||||
let dataMap = new Map<string, MmkvValueType>();
|
||||
let mmkv: MMKV | null = null;
|
||||
try {
|
||||
// 初始化单框架mmkv
|
||||
MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv');
|
||||
// mmkv文件名
|
||||
let files = this.findDeseriredFile(context);
|
||||
let mmapID: string = "history_user_info";
|
||||
console.log("FileBackupExtensionAbility", "mmapId = " + mmapID);
|
||||
// 秘钥,根据实际情况指定秘钥, 当前未设置秘钥
|
||||
let crpKey: string = '';
|
||||
mmkv = MMKV.getMMKVWithMMapID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, '');
|
||||
|
||||
// 取值,根据mmkv中实际存在的key来取值
|
||||
dataMap.set('legacyUserData', mmkv.decodeString('history_user_info', ''));
|
||||
} catch (error) {
|
||||
// 异常处理逻辑,自行实现
|
||||
} finally {
|
||||
mmkv?.close();
|
||||
}
|
||||
return dataMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
export const DEFAULT_CONNECT_SITE = '116.63.213.206';
|
||||
export const DEFAULT_CONNECT_SITE_PORT = 9528;
|
||||
|
||||
export const TEST_ENV_CONNECT_SITE = '121.52.252.9';
|
||||
export const TEST_ENV_CONNECT_SITE_PORT = 9628;
|
||||
|
||||
export const TEST_ENV_SITE = '101.71.41.211';
|
||||
export const TEST_ENV_PORT = 9628;
|
||||
@@ -0,0 +1,63 @@
|
||||
import { ConnectionServiceHelper, SiteEntity, SiteManager } from "@kernel/lib_communication";
|
||||
import { AppInfoConfig, PreferenceService } from "biz_common";
|
||||
import { DebugToolUtil } from "biz_debug";
|
||||
import { DEBUG_TOOL, ENABLE_FIX_SERVER,
|
||||
SERVER_FIX_ADDRESS,
|
||||
SERVER_FIX_PORT} from "biz_debug/src/main/ets/debug/servertool/constant";
|
||||
import WTInit from "biz_trade/src/main/ets/utils/WTInit";
|
||||
import BuildProfile from "BuildProfile";
|
||||
import { ConfigInitializer } from "../initialize/ConfigInitializor";
|
||||
import { TEST_ENV_PORT, TEST_ENV_SITE } from "./Config";
|
||||
import { ServiceHandlerImpl } from "./impl/ServiceHandlerImpl";
|
||||
import deviceInfo from "@ohos.deviceInfo";
|
||||
|
||||
export class ConnectionInitiator {
|
||||
|
||||
/**
|
||||
* 初始化连接库相关配置
|
||||
*/
|
||||
static initConnectionService(context : Context) {
|
||||
ConfigInitializer.init();
|
||||
|
||||
// SiteManager.getInstance().init(TEST_ENV_CONNECT_SITE, TEST_ENV_CONNECT_SITE_PORT)
|
||||
//SiteManager.getInstance().init(DEFAULT_CONNECT_SITE, DEFAULT_CONNECT_SITE_PORT)
|
||||
//SiteManager.getInstance().setDomainHost(AppInfoConfig.FUTURES_DOMAIN_HOST);
|
||||
|
||||
const initEntity = SiteManager.getInstance().getHQSiteEntity()
|
||||
initEntity?.setDomainHost(AppInfoConfig.FUTURES_DOMAIN_HOST)
|
||||
initEntity?.setSitePort(AppInfoConfig.FUTURES_HOST_PORT)
|
||||
if (DebugToolUtil.isInnerNetSimulateMode()) {
|
||||
// debug模拟器自动切内网环境
|
||||
ConnectionInitiator.updateServerOnDebugMode();
|
||||
}
|
||||
ConnectionInitiator.updateServerByDebugTool();
|
||||
ConnectionServiceHelper.getInstance().init(new ServiceHandlerImpl());
|
||||
WTInit.init(context.getApplicationContext());
|
||||
|
||||
}
|
||||
|
||||
private static updateServerOnDebugMode() {
|
||||
if (isDebugMode()) {
|
||||
let siteEntity = new SiteEntity();
|
||||
siteEntity.init(TEST_ENV_SITE, TEST_ENV_PORT);
|
||||
SiteManager.getInstance().updateHQSiteEntity(siteEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据调试工具保存的站点信息初始化行情站点
|
||||
*/
|
||||
private static updateServerByDebugTool() {
|
||||
if (PreferenceService.getBooleanValueSync(DEBUG_TOOL, ENABLE_FIX_SERVER, false)) {
|
||||
let siteEntity = new SiteEntity();
|
||||
const siteIp = PreferenceService.getStringValueSync(DEBUG_TOOL, SERVER_FIX_ADDRESS, TEST_ENV_SITE)
|
||||
const sitePort = PreferenceService.getNumberValueSync(DEBUG_TOOL, SERVER_FIX_PORT, TEST_ENV_PORT);
|
||||
siteEntity.init(siteIp, sitePort);
|
||||
SiteManager.getInstance().updateHQSiteEntity(siteEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isDebugMode() {
|
||||
return BuildProfile.BUILD_MODE_NAME.toString() === 'debug'
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import {
|
||||
CBASConnectionConfig,
|
||||
HqConnectionConfig,
|
||||
ITcpApmMonitorProxy,
|
||||
SocketConnection,
|
||||
TcpApmMonitorProxyHolder,
|
||||
TcpLogConnectionInfo
|
||||
} from '@kernel/lib_communication';
|
||||
import {
|
||||
IConnectionService,
|
||||
IServiceHandler,
|
||||
ServiceConfig
|
||||
} from '@kernel/lib_communication/src/main/ets/connection/IConnectionService';
|
||||
import hilog from '@ohos.hilog';
|
||||
// import TradeJsonStructObserver from 'biz_trade/src/main/ets/features/wtmodule/TradeJsonStructObserver'
|
||||
import { AuthJsonStructObserver } from '../interceptors/AuthJsonStructObserver';
|
||||
import { HXAuthManager } from 'biz_auth';
|
||||
import { RealDataStructObserver } from '../../groupsub/RealDataStructObserver';
|
||||
import { SelfCodeChangeObserver } from '../interceptors/SelfCodeChangeObserver';
|
||||
import { SidInstanceIdReceiver } from '../interceptors/SidInstanceIdReceiver';
|
||||
import { BaseTcpLogInfo, getTcpService, TCPConnectLogInfo } from '@kernel/app_monitor_tcp';
|
||||
import { DEFAULT_SITE } from '@kernel/lib_communication/src/main/ets/site/SiteConstants';
|
||||
import connection from '@ohos.net.connection';
|
||||
import { TradeELKLogStructObserver } from 'biz_trade';
|
||||
import { HXEventMonitor } from '@kernel/app_monitor_event';
|
||||
import { Action, HxCBASAgentProvider, logMapValue } from '@b2c-spi/hx_cbas_api';
|
||||
import { HXLog } from 'biz_common';
|
||||
|
||||
|
||||
export class ServiceHandlerImpl implements IServiceHandler {
|
||||
onConfig(serviceConfig: ServiceConfig): void {
|
||||
let hqConnectionConfig = new HqConnectionConfig();
|
||||
hqConnectionConfig.setAuth(new HXAuthManager());
|
||||
hqConnectionConfig.addStructReceiver(new SidInstanceIdReceiver());
|
||||
hqConnectionConfig.addStructReceiver(new AuthJsonStructObserver());
|
||||
// hqConnectionConfig.addStructReceiver(new TradeJsonStructObserver());
|
||||
hqConnectionConfig.addStructReceiver(new RealDataStructObserver());
|
||||
//hqConnectionConfig.addStructReceiver(new SelfCodeChangeObserver());
|
||||
serviceConfig.registerConnection(hqConnectionConfig);
|
||||
|
||||
let cbasConnectionConfig = new CBASConnectionConfig();
|
||||
cbasConnectionConfig.setAuth(new HXAuthManager());
|
||||
cbasConnectionConfig.addStructReceiver(new TradeELKLogStructObserver());
|
||||
serviceConfig.registerConnection(cbasConnectionConfig);
|
||||
|
||||
this.registerTcpApmProxy()
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册tcp的APM监控代理
|
||||
*/
|
||||
registerTcpApmProxy() {
|
||||
TcpApmMonitorProxyHolder.get().tcpLogMonitorProxy = new HXTcpApmMonitorProxy()
|
||||
}
|
||||
|
||||
onPreInit(connService: IConnectionService): void {
|
||||
initCommonConfig()
|
||||
}
|
||||
|
||||
onInitEnd(connService: IConnectionService): void {
|
||||
hilog.info(0xff11, "ServiceHandlerImpl", "onInitEnd")
|
||||
}
|
||||
|
||||
onDestroy(connService: IConnectionService): void {
|
||||
hilog.info(0xff11, "ServiceHandlerImpl", "onDestroy")
|
||||
}
|
||||
}
|
||||
|
||||
function initCommonConfig() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* tcp APM性能监控代理类
|
||||
*/
|
||||
class HXTcpApmMonitorProxy implements ITcpApmMonitorProxy {
|
||||
private TCP_LOG_MONITOR_MODULE = 'tcp-log';
|
||||
|
||||
/**
|
||||
* 开始解析dns
|
||||
* @param domainName
|
||||
*/
|
||||
dnsStart(domainName: string): void {
|
||||
const logMap: Map<string, logMapValue> = new Map()
|
||||
logMap.set('result', domainName)
|
||||
HxCBASAgentProvider.getInstance().doEvent("mob_systemgeneral_dnsresolve_start", Action.SHOW, logMap)
|
||||
HXLog.d("HXDNS", `dnsStart,domainName=${domainName}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* dns解析结束
|
||||
*/
|
||||
dnsEnd(isSuccess: boolean, domainName: string, ipList: connection.NetAddress[] | null, cost: number,
|
||||
errorMsg: string): void {
|
||||
const logMap: Map<string, logMapValue> = new Map()
|
||||
logMap.set('type', isSuccess ? "1" : "0")
|
||||
//dns解析结果
|
||||
const result = `domainName=${domainName}^ipList=${this.convertNetAddressToIpListString(ipList)}^cost=${cost}^errorMsg=${errorMsg}`
|
||||
logMap.set('result', result)
|
||||
HxCBASAgentProvider.getInstance().doEvent("mob_systemgeneral_dnsresolve_result", Action.SHOW, logMap)
|
||||
HXLog.d("HXDNS", `dnsEnd,success=${isSuccess},result=${result}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换ip列表字符串格式
|
||||
*/
|
||||
private convertNetAddressToIpListString(ipList: connection.NetAddress[] | null): string {
|
||||
if (ipList == null || ipList.length == 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const ipArray = ipList.map((addr) => addr.address)
|
||||
//去重
|
||||
const uniqueIps = Array.from(new Set(ipArray))
|
||||
return uniqueIps.join(",")
|
||||
}
|
||||
|
||||
setSas(sas: string | null): void {
|
||||
getTcpService()?.setSas(sas)
|
||||
}
|
||||
|
||||
sendTcpConnectLog(info: TcpLogConnectionInfo): void {
|
||||
let cost = info.finishTime - info.startTime
|
||||
if (cost < 0) {
|
||||
cost = 0
|
||||
}
|
||||
//这里需要转换一下
|
||||
const logInfo = new TCPConnectLogInfo(
|
||||
cost, //连接耗时
|
||||
this.getTcpConnectResult(info.state, cost), //统一result字段
|
||||
this.getTcpConnectErrorCode(info.state, cost), //统一errorCode字段
|
||||
info.errorCode.toString(), //errorMsg
|
||||
0, //连接类型 目前没有竞速,默认0
|
||||
info.siteEntity.getSiteAddress(), //连接的服务器地址
|
||||
0, //网络栈,目前没有判断
|
||||
false, //是否ipv6
|
||||
false, //是否v6优先连接
|
||||
info.connectSeq, //站点类型
|
||||
0,
|
||||
info.siteEntity.getSiteAddress() === DEFAULT_SITE ? 1 : 0// 是否为默认站点
|
||||
)
|
||||
getTcpService()?.getTcpConnectLogMonitor()?.addUploadInfo(logInfo)
|
||||
}
|
||||
|
||||
getTcpConnectErrorCode(state: SocketConnection, cost: number): number | undefined {
|
||||
switch (state) {
|
||||
case SocketConnection.CONNECTED:
|
||||
return 4
|
||||
case SocketConnection.ERROR:
|
||||
if (cost > 10000) {
|
||||
return 10
|
||||
} else {
|
||||
return 8
|
||||
}
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
getTcpConnectResult(state: SocketConnection, cost: number): string | null | undefined {
|
||||
switch (state) {
|
||||
case SocketConnection.CONNECTED:
|
||||
return BaseTcpLogInfo.SUCCESS
|
||||
case SocketConnection.ERROR:
|
||||
if (cost > 10000) {
|
||||
return BaseTcpLogInfo.TIMEOUT
|
||||
} else {
|
||||
return BaseTcpLogInfo.FAIL
|
||||
}
|
||||
default:
|
||||
return BaseTcpLogInfo.FAIL
|
||||
}
|
||||
}
|
||||
|
||||
buildRequest(frameId: number, packageId: number, traceId: string | null,
|
||||
pageIdMappingInstanceId: Map<number, number> | null): void {
|
||||
getTcpService()?.getTcpReqMonitor()?.buildRequest(frameId, packageId, traceId, pageIdMappingInstanceId)
|
||||
}
|
||||
|
||||
addRequestToQueue(packageId: number): void {
|
||||
getTcpService()?.getTcpReqMonitor()?.addRequestToQueue(packageId)
|
||||
}
|
||||
|
||||
sendBegin(packageId: number): void {
|
||||
getTcpService()?.getTcpReqMonitor()?.sendBegin(packageId)
|
||||
}
|
||||
|
||||
sendEnd(packageId: number): void {
|
||||
getTcpService()?.getTcpReqMonitor()?.sendEnd(packageId)
|
||||
}
|
||||
|
||||
receiveResponse(packageId: number, instanceId: number, rst: number): void {
|
||||
getTcpService()?.getTcpReqMonitor()?.receiveResponse(packageId, instanceId, rst)
|
||||
}
|
||||
|
||||
addRspToQueue(packageId: number, instanceId: number): void {
|
||||
getTcpService()?.getTcpReqMonitor()?.addRspToQueue(packageId, instanceId)
|
||||
}
|
||||
|
||||
processReceiveData(packageId: number, instanceId: number): void {
|
||||
getTcpService()?.getTcpReqMonitor()?.processReceiveData(packageId, instanceId)
|
||||
}
|
||||
|
||||
sendErrorLog(tag: string, msg: string): void {
|
||||
//在这里将tcp的错误日志发送到apm
|
||||
HXEventMonitor.onEvent(this.TCP_LOG_MONITOR_MODULE, tag, Object({
|
||||
errMsg: msg
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import { getConnectionManager, HXLog } from 'biz_common';
|
||||
import {
|
||||
IStructReceiver,
|
||||
ProtocolDef,
|
||||
StuffBaseStruct,
|
||||
StuffResourceStruct,
|
||||
ConnectionServiceHelper
|
||||
} from '@kernel/lib_communication';
|
||||
import { StringUtils } from 'hxutil';
|
||||
import { ConnectionSeq } from '@kernel/lib_communication';
|
||||
import { ServerResourceManager, ServerTimeProvider } from 'biz_hxservice';
|
||||
import { getTcpService } from '@kernel/app_monitor_tcp';
|
||||
import { APMHelper } from '../../monitor/APMHelper';
|
||||
|
||||
/**
|
||||
* 认证包拦截器
|
||||
* 这个拦截器用于接受认证后的session数据包以及事件包
|
||||
* @author menghuiyuan@myhexin.com
|
||||
*/
|
||||
export class AuthJsonStructObserver implements IStructReceiver {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
onIntercept(stuffBaseStruct: StuffBaseStruct): boolean {
|
||||
if (stuffBaseStruct == null) {
|
||||
return false;
|
||||
}
|
||||
const headId = stuffBaseStruct.packageId;
|
||||
if (headId == ProtocolDef.DATAID_NEW_REALTIME) {
|
||||
const dataType = stuffBaseStruct.headType & ProtocolDef.DATA_TYPE_MASK;
|
||||
if (dataType === ProtocolDef.DATA_TYPE_JSON) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
handleStruct(stuffBaseStruct: StuffBaseStruct): boolean {
|
||||
if (!(stuffBaseStruct instanceof StuffResourceStruct)) {
|
||||
return false;
|
||||
}
|
||||
let tmpBuffer = stuffBaseStruct.buffer;
|
||||
const decodedStr = StringUtils.decode(tmpBuffer, "GBK")
|
||||
|
||||
try {
|
||||
const parsedObj: AuthStruct = JSON.parse(decodedStr)
|
||||
ServerResourceManager.getInstance().parseResource(decodedStr);
|
||||
if (parsedObj.srvTime && (typeof parsedObj.srvTime === 'number')) {
|
||||
ServerTimeProvider.getInstance().initServiceTime(parseInt(parsedObj.srvTime) * 1000)
|
||||
}
|
||||
const hasSessionId = StringUtils.isNotEmpty(parsedObj.sessionId);
|
||||
//处理sessionId
|
||||
if (hasSessionId) {
|
||||
ConnectionServiceHelper.getInstance()
|
||||
.getConnectionService()
|
||||
.setSessionId(ConnectionSeq.HQ, parsedObj.sessionId)
|
||||
|
||||
//设置tcp监控的sas
|
||||
getTcpService()?.setSas(`${parsedObj.sessionId}_${getConnectionManager()?.getConnectedIp()}`)
|
||||
}
|
||||
const hasClientIP = StringUtils.isNotEmpty(parsedObj.clientIp);
|
||||
if (hasClientIP) {
|
||||
APMHelper.clientIp = parsedObj.clientIp
|
||||
AppStorage.setOrCreate('clientIp', parsedObj.clientIp)
|
||||
}
|
||||
if (hasSessionId || hasClientIP) {
|
||||
//WTLoginAdapterService.receiveSessionIdHandle()
|
||||
}
|
||||
//todo 处理账户事件,如强制下线等
|
||||
if (StringUtils.isNotEmpty(parsedObj.events)) {
|
||||
HXLog.i("AuthJSONStruct", "error parsing")
|
||||
}
|
||||
} catch (e) {
|
||||
HXLog.e("AuthJSONStruct", "Error parsing " + JSON.stringify(e))
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
getReceiverId(): string {
|
||||
return "AuthJsonStructObserver"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证包结构
|
||||
*/
|
||||
export class AuthStruct {
|
||||
srvTime: string = "";
|
||||
clientIp: string = "";
|
||||
clientPort: string = "";
|
||||
serverIp: string = "";
|
||||
releaseName: string = "";
|
||||
AuthCountryCodeMd5: string = "";
|
||||
events: string = "";
|
||||
sessionId: string = "";
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { StringUtils } from 'hxutil';
|
||||
import { IStructReceiver, ITCPClient, ProtocolDef, StuffBaseStruct, StuffResourceStruct } from '@kernel/lib_communication';
|
||||
import { SelfCodeEvent } from '../../selfcode/SelfCodeEvents';
|
||||
import { UgcClient } from '../../register/ugcregister/UgcClient';
|
||||
|
||||
/**
|
||||
* 自选股更新订阅拦截
|
||||
* @author menghuiyuan@myhexin.com
|
||||
*/
|
||||
export class SelfCodeChangeObserver implements IStructReceiver {
|
||||
|
||||
constructor() { }
|
||||
|
||||
onIntercept(stuffBaseStruct: StuffBaseStruct): boolean {
|
||||
if (stuffBaseStruct == null) {
|
||||
return false;
|
||||
}
|
||||
const headId = stuffBaseStruct.packageId;
|
||||
if (headId == ProtocolDef.DATAID_NEW_REALTIME) {
|
||||
const dataType = stuffBaseStruct.headType & ProtocolDef.DATA_TYPE_MASK;
|
||||
if (dataType === ProtocolDef.DATA_TYPE_JSON) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//自选股版本变更推送id
|
||||
private readonly UPDATE_SIG_VERSION = '00000009';
|
||||
//自选股更新强制推送id
|
||||
private readonly UPDATE_SIG_FORCE = '00000010';
|
||||
|
||||
handleStruct(stuffBaseStruct: StuffBaseStruct): boolean {
|
||||
if (!(stuffBaseStruct instanceof StuffResourceStruct)) {
|
||||
return false;
|
||||
}
|
||||
const resStruct = stuffBaseStruct as StuffResourceStruct;
|
||||
const tmpBuffer = resStruct.buffer;
|
||||
const decodedStr = StringUtils.decode(tmpBuffer, "GBK")
|
||||
try {
|
||||
const parsedObj : SelfCodeEvent = JSON.parse(decodedStr);
|
||||
if (parsedObj) {
|
||||
const eventArrayLen = parsedObj.events.length;
|
||||
for (let index = 0; index < eventArrayLen; index++) {
|
||||
const eventObj = parsedObj.events[index];
|
||||
const eventId = eventObj.eventID;
|
||||
switch (eventId) {
|
||||
//版本号推送
|
||||
case this.UPDATE_SIG_VERSION:
|
||||
const selfCodeVersionData = eventObj.eventData;
|
||||
//TODO 推送版本号:
|
||||
UgcClient.getInstance().onSelfCodeRemoteVersion(Number(selfCodeVersionData.version))
|
||||
return true;
|
||||
case this.UPDATE_SIG_FORCE:
|
||||
//TODO 推送强制更新版本
|
||||
const forceUpdateVersionData = JSON.stringify(eventObj.eventData);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getReceiverId(): string {
|
||||
return "SelfCodeChangeObserver";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
IStructReceiver,
|
||||
RequestQueueManagement,
|
||||
StuffBaseStruct,
|
||||
StuffResourceStruct
|
||||
} from '@kernel/lib_communication';
|
||||
import { SidPermissionRefreshClient } from 'biz_auth';
|
||||
|
||||
const USER_RESPONSE_TYPE_NOTHING = 0;
|
||||
|
||||
/**
|
||||
* SID 权限刷新特殊包接收器。
|
||||
*
|
||||
* 对齐 Android SIDInstanceIdReicever:主动刷新 SID 的回包使用固定 instanceId=13,
|
||||
* 服务端可能按 type=0 返回普通 body,连接库会先解成 StuffBaseStruct。
|
||||
* 这里只按包头条件识别该特殊包,把原始 body 包装成 LOGIN_AUTH 资源后继续投递给请求 client。
|
||||
*/
|
||||
export class SidInstanceIdReceiver implements IStructReceiver {
|
||||
onIntercept(stuffBaseStruct: StuffBaseStruct): boolean {
|
||||
return this.isSidRefreshBaseStruct(stuffBaseStruct);
|
||||
}
|
||||
|
||||
handleStruct(stuffBaseStruct: StuffBaseStruct): boolean {
|
||||
if (!this.isSidRefreshBaseStruct(stuffBaseStruct)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// IStructReceiver 返回 true 后会截断原始 StuffBaseStruct 的普通分发流程,
|
||||
// 这里需要把包装后的 LOGIN_AUTH 资源主动投递给注册在 REFRESH_SID 上的请求 client。
|
||||
const sidStruct = this.createSidResourceStruct(stuffBaseStruct);
|
||||
const clients = RequestQueueManagement.getInstance().getNetworkClient(SidPermissionRefreshClient.REFRESH_SID);
|
||||
if (!clients || clients.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const client of clients) {
|
||||
client?.receive(sidStruct);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getReceiverId(): string {
|
||||
return 'SidInstanceIdReceiver';
|
||||
}
|
||||
|
||||
private isSidRefreshBaseStruct(stuffBaseStruct: StuffBaseStruct | null): boolean {
|
||||
if (!stuffBaseStruct || stuffBaseStruct instanceof StuffResourceStruct) {
|
||||
return false;
|
||||
}
|
||||
// 对齐 Android filter:只通过包头 type=0 和固定 instanceId=13 识别 SID 刷新特殊包。
|
||||
// SID 内容不参与判断,避免把权限字段格式差异误判为客户端解析问题。
|
||||
const rawBuffer = stuffBaseStruct.rawBuffer;
|
||||
return stuffBaseStruct.instanceId === SidPermissionRefreshClient.REFRESH_SID &&
|
||||
stuffBaseStruct.headType === USER_RESPONSE_TYPE_NOTHING &&
|
||||
rawBuffer !== null &&
|
||||
rawBuffer.length > 0;
|
||||
}
|
||||
|
||||
private createSidResourceStruct(stuffBaseStruct: StuffBaseStruct): StuffResourceStruct {
|
||||
// 这里不解码、不解析 SID,只复制主站原始 body 并包装成 LOGIN_AUTH。
|
||||
// 后续 SID 字符串解析和权限更新仍交给 SidPermissionRefreshClient 统一处理。
|
||||
const rawBuffer = stuffBaseStruct.rawBuffer as Uint8Array;
|
||||
const sidBuffer = rawBuffer.slice(0, rawBuffer.length);
|
||||
const sidStruct = new StuffResourceStruct(StuffResourceStruct.LOGIN_AUTH, sidBuffer);
|
||||
stuffBaseStruct.copyHead(sidStruct);
|
||||
sidStruct.connectionSeq = stuffBaseStruct.connectionSeq;
|
||||
sidStruct.rawBuffer = sidBuffer;
|
||||
return sidStruct;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { HXLog } from 'biz_common';
|
||||
import { ICheckCallback } from '@kernel/lib_communication/src/main/ets/connection/ICheckCallback';
|
||||
|
||||
/**
|
||||
* 连接库检查回调类
|
||||
* @author menghuiyuan@myhexin.com
|
||||
*/
|
||||
export class ConnectionCheckImpl implements ICheckCallback {
|
||||
|
||||
private readonly TAG = "SocketConnectionCheckImpl"
|
||||
onChannelOk(): void {
|
||||
HXLog.i(this.TAG, "onChannelOk");
|
||||
}
|
||||
|
||||
onChannelBad(badType: number): void {
|
||||
HXLog.i(this.TAG, "onChannelBad" + badType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要重新连接
|
||||
* @returns true,默认开启重连
|
||||
*/
|
||||
needReconnect(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { getConnectionManager } from 'biz_common';
|
||||
import { UgcClient } from '../../register/ugcregister/UgcClient';
|
||||
|
||||
/**
|
||||
* 主动关闭当前的socket连接
|
||||
*/
|
||||
export function closeSocketConnection() : void {
|
||||
if (getConnectionManager()) {
|
||||
getConnectionManager()?.disconnect(false);
|
||||
UgcClient.getInstance().onHXSocketClose()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { getConnectionManager, HXLog, RuntimeStatus } from 'biz_common';
|
||||
import { ConnectionCheckImpl } from './ConnectionCheckImpl';
|
||||
import { closeSocketConnection } from './ConnectionServiceUtil';
|
||||
|
||||
/**
|
||||
* socket的监控管理
|
||||
*/
|
||||
export class HXSocketMonitor {
|
||||
|
||||
private readonly TAG : string = "HXSocketMonitor";
|
||||
|
||||
//当前App是否处于前台
|
||||
private isAppForeground = false;
|
||||
|
||||
onAppForeground() {
|
||||
HXLog.i(this.TAG, "onAppForeground")
|
||||
this.isAppForeground = true;
|
||||
if (getConnectionManager()) {
|
||||
HXLog.i(this.TAG, "onAppForeground checkConnection")
|
||||
getConnectionManager()?.checkConnection(new ConnectionCheckImpl());
|
||||
}
|
||||
}
|
||||
|
||||
onAppBackground() {
|
||||
HXLog.i(this.TAG, "onAppBackground")
|
||||
this.isAppForeground = false;
|
||||
closeSocketConnection()
|
||||
RuntimeStatus.getInstance().setTcpDisConnected()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export class EntryConstant {
|
||||
//微信分享id
|
||||
static readonly WX_SHARE_ID = 'wx6ba37430b7916a86';
|
||||
}
|
||||
@@ -0,0 +1,480 @@
|
||||
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
|
||||
import hilog from '@ohos.hilog';
|
||||
import UIAbility from '@ohos.app.ability.UIAbility';
|
||||
import Want from '@ohos.app.ability.Want';
|
||||
import window from '@ohos.window';
|
||||
import {
|
||||
AppConfigManager,
|
||||
buildHeader,
|
||||
CodeTableMonitor,
|
||||
GlobalContext,
|
||||
HXLog,
|
||||
jumpToQuote,
|
||||
PreferenceService,
|
||||
RuntimeStatus,
|
||||
ScreenUtil,
|
||||
StartManager,
|
||||
updateBreakpoint,
|
||||
updateCurrentWindowWith,
|
||||
WebJumpLinkWhiteManager
|
||||
} from 'biz_common';
|
||||
import { AuthProviderService } from 'biz_auth';
|
||||
import { ContextUtils, StringUtils } from 'hxutil';
|
||||
import { ContextUtils as ContextUtils2b } from '@b2b/hxutil';
|
||||
import { ConnectionSeq } from '@kernel/lib_communication/src/main/ets/data/CommunicationConstants';
|
||||
import { SelfCodeGroupHandler } from '../groupsub/business/SelfCodeGroupHandler';
|
||||
import { HXSocketMonitor } from '../communication/socketmonitor/HXSocketMonitor';
|
||||
import { Configuration, ConfigurationConstant, wantConstant } from '@kit.AbilityKit';
|
||||
import { UgcClient } from '../register/ugcregister/UgcClient';
|
||||
import { ContinuousDataHelper, CurveNaviModel } from 'biz_quote/src/main/ets/data/ContinuousDataHelper';
|
||||
import { DelayMarketManager, FutureMarketDataManager, HQTabManager, InactiveContractManager,
|
||||
MainContractGroupHandler } from 'biz_market';
|
||||
import { MainContractManager } from 'biz_market/src/main/ets/manager/MainContractManager';
|
||||
import { SelfCodeMainListHandler } from '../groupsub/business/SelfCodeMainListHandler';
|
||||
import { UgcAuthListener } from '../register/ugcregister/UgcAuthListener';
|
||||
import { AppUtil } from '@pura/harmony-utils';
|
||||
import { ElkUploadRegister } from '../register/elkregister/ElkUploadRegister';
|
||||
import { HexinVersionControl } from 'biz_trade/src/main/ets/utils/HexinVersionControl';
|
||||
import { SecurityMonitorInit } from '../monitor/SecurityMonitorInit';
|
||||
import { TabConfigManager } from '@b2c/biz_discover';
|
||||
import { ShareConfig } from '@b2c-spi/hx_share_api/src/main/ets/config/ShareConfig';
|
||||
import { HMRouterMgr } from '@hadss/hmrouter';
|
||||
import { EntryConstant } from '../constant/EntryConstant';
|
||||
import { ConnectionInitiator } from '../communication/ConnectionInitiator';
|
||||
import { APMHelper } from '../monitor/APMHelper';
|
||||
import { cbasService } from '../register/cbasregister/CbasServiceRegister';
|
||||
import { eventSubscription } from '../monitor/AppEvent';
|
||||
import { StockGroupConstants } from 'biz_selfcode';
|
||||
import { StockGroupHandler } from '../groupsub/business/StockGroupHandler';
|
||||
import { VarietyGroupHandler } from '../groupsub/business/VarietyGroupHandler';
|
||||
import { SidGroupHandler } from '../groupsub/business/SidGroupHandler';
|
||||
import { FuturesCompanyGroupHandler, YYBListGroupHandler, TradeSettingGroupHandler, TradeCloudSyncConfig,
|
||||
TradeMsgUniqueIdManager,
|
||||
TradeNotificationTrigger } from 'biz_trade';
|
||||
import { GroupSubManager } from 'base_mobilepush';
|
||||
import { CodeTableManager } from 'lib_codetablemanager';
|
||||
import BuildProfile from 'BuildProfile';
|
||||
import { GrayTestPushHandler } from '../groupsub/business/GrayTestPushHandler';
|
||||
import { AccountAnalysisLuaPushHandler } from '../groupsub/business/AccountAnalysisLuaPushHandler';
|
||||
import { QhStrategyAdPushHandler } from '../groupsub/business/QhStrategyAdPushHandler';
|
||||
import { GrayTestInitializor } from '../initialize/GrayTestInitializor';
|
||||
import { CloudStoreLogInitializor } from '../initialize/CloudStoreLogInitializor';
|
||||
import { HXPushService } from 'service_push';
|
||||
import { WantCacheManager } from './WantManager';
|
||||
import { pushService } from '@kit.PushKit';
|
||||
import { HXNetwork } from '../network/HXNetwork';
|
||||
import { AdvertisingMonitorService, KaihuFlagManager, ServerTimeProvider, UserSourceManager } from 'biz_hxservice';
|
||||
import { UnifyUrlManager } from '@b2c-f/url-modify-config';
|
||||
import { FontManager } from '../font/FontManager';
|
||||
import { BizType, IndicatorGroupHandler, CommonIndicatorsManage, IndicatorParamsManage } from 'biz_indicator';
|
||||
import { FigureRegistry } from 'biz_chart_data_processor';
|
||||
import { TradeInitializor } from '../initialize/TradeInitializor';
|
||||
import { AdsInitializer } from '../initialize/AdsInitializor';
|
||||
import { AIGroupClient } from 'service_aigroup'
|
||||
import { AIGroupPushHandler } from '../groupsub/business/AIGroupPushHandler';
|
||||
import { DrawLineGroupHandler } from 'biz_quote';
|
||||
import { CurveStorage } from 'biz_quote';
|
||||
import { OpenAccountManager } from 'biz_openaccount/src/main/ets/manager/OpenAccountManager';
|
||||
import { HttpUtils } from '@kernel/rcp';
|
||||
import { DebugToolUtil } from 'biz_debug';
|
||||
import { TradeDailyPushDataManager } from '../NotificationExtension/TradeDaily/TradeDailyPushDataManager';
|
||||
import { TradeDailyConvertManager } from '../NotificationExtension/TradeDaily/TradeDailyConvertManager';
|
||||
import { PushMessageManager } from 'biz_trade/src/main/ets/service/push/PushMessageManager';
|
||||
import { ColourModeStatus } from '@kernel/theme_manager';
|
||||
import { VarietyCodeTransformManager } from 'biz_chart_data_processor/src/main/ets/utils/VarietyCodeTransformManager';
|
||||
import { ContextUtils as ContextUtils2c } from '@b2c-m/ohos_hx_utils';
|
||||
import { FunctionAuthorityGroupHandler } from '../groupsub/business/FunctionAuthorityGroupHandler';
|
||||
import { FunctionAuthorityRequestManager } from '../functionauthority/FunctionAuthorityRequester';
|
||||
import { QhStrategyAdPagePushHandler } from '../groupsub/business/QhStrategyAdPagePushHandler';
|
||||
|
||||
/**
|
||||
* 应用的主Ability
|
||||
* @author menghuiyuan@myhexin.com
|
||||
*/
|
||||
export default class EntryAbility extends UIAbility {
|
||||
private readonly tag: string = "EntryAbility"
|
||||
private windowObj?: window.Window
|
||||
private curBp: string = ''
|
||||
//socket连接监控,需要接收应用前后台事件
|
||||
private socketMonitor: HXSocketMonitor = new HXSocketMonitor();
|
||||
private curNaviModel: CurveNaviModel | null = null;
|
||||
|
||||
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
|
||||
|
||||
pushService.receiveMessage('IM', this, (payload) => {
|
||||
// todo 应用前台弹窗接收push逻辑,待补充。
|
||||
try {
|
||||
const data = payload.data;
|
||||
const pushInfo: string = JSON.parse(data)?.data
|
||||
HXLog.d(this.tag, `Succeeded in getting notification,data=${JSON.stringify(pushInfo)}`);
|
||||
|
||||
const pushModel = TradeDailyPushDataManager.getInstance().convertToTradeDailyPushModel(JSON.parse(pushInfo))
|
||||
if (!pushModel) {
|
||||
return
|
||||
}
|
||||
// 推送去重判断
|
||||
TradeMsgUniqueIdManager.getInstance().isSameIdentify(
|
||||
pushModel.time,
|
||||
pushModel.iconType,
|
||||
pushModel.type,
|
||||
pushModel.orderNo
|
||||
).then((hasSame) => {
|
||||
if (!hasSame) {
|
||||
TradeDailyPushDataManager.getInstance().recordPushDaily(JSON.parse(pushInfo)).then(() => {
|
||||
TradeDailyConvertManager.getInstance().convertDailyFromPush()
|
||||
})
|
||||
PushMessageManager.getInstance().processReceiveHXPushMessage(pushInfo, false)
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
HXLog.e(this.tag, `Parse push data failed: ${error}`)
|
||||
}
|
||||
})
|
||||
|
||||
hilog.info(0x0000, this.tag, '%{public}s', 'Ability onCreate');
|
||||
|
||||
// ========== 注册自定义 Figure ==========
|
||||
// 注册所有 iOS 迁移过来的自定义 Figure
|
||||
// 包括:bar, barDouble, polygon, tower, american, trade, yAxisRange
|
||||
FigureRegistry.registerAll()
|
||||
hilog.info(0x0000, this.tag, 'Custom Figures registered successfully');
|
||||
// =====================================
|
||||
|
||||
DebugToolUtil.TARGET_NAME = BuildProfile.TARGET_NAME
|
||||
DebugToolUtil.PRODUCT_NAME = BuildProfile.PRODUCT_NAME
|
||||
DebugToolUtil.BUILD_MODE_NAME = BuildProfile.BUILD_MODE_NAME
|
||||
// Context初始化前移
|
||||
GlobalContext.initContext(this.context.getApplicationContext())
|
||||
ContextUtils.initContext(this.context.getApplicationContext())
|
||||
ContextUtils2c.initContext(this.context.getApplicationContext())
|
||||
ContextUtils2b.initContext(this.context.getApplicationContext())
|
||||
AppUtil.init(this.context)
|
||||
// 交易通知触发器初始化
|
||||
TradeNotificationTrigger.getInstance().init(this.context)
|
||||
//崩溃保护代码
|
||||
SecurityMonitorInit.getInstance().init();
|
||||
GrayTestInitializor.initGrayTest()
|
||||
CloudStoreLogInitializor.initCloudStoreLog()
|
||||
HMRouterMgr.init({ context: this.context });
|
||||
UnifyUrlManager.getInstance().init(this.context);
|
||||
// 启动管理:是否为安装后第一次进入APP
|
||||
StartManager.getInstance().init()
|
||||
RuntimeStatus.getInstance().init()
|
||||
ElkUploadRegister.getInstance().init();
|
||||
UgcClient.getInstance().init()
|
||||
AuthProviderService.getInstance().setUgcAuthListener(UgcAuthListener.getInstance())
|
||||
this.context.setMissionContinueState(AbilityConstant.ContinueState.INACTIVE, (result) => {
|
||||
console.info(`setMissionContinueState: ${JSON.stringify(result)}`);
|
||||
});
|
||||
eventSubscription()
|
||||
this.initRcp()
|
||||
ShareConfig.setAppId(EntryConstant.WX_SHARE_ID);
|
||||
this.initFuture()
|
||||
AdsInitializer.initAds()
|
||||
// 初始化 AI 分组客户端
|
||||
AIGroupClient.getInstance().init(DebugToolUtil.isDebugMode())
|
||||
WantCacheManager.getInstance().stashWant(want)
|
||||
|
||||
FontManager.getInstance().loadTHSFonts()
|
||||
HXLog.init()
|
||||
OpenAccountManager.getInstance().upCfmmcRecord()
|
||||
UserSourceManager.getInstance().initUserSource()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Rcp库配置
|
||||
*/
|
||||
initRcp() {
|
||||
HttpUtils.setGlobalHeaders((url) => {
|
||||
return buildHeader(true)
|
||||
})
|
||||
}
|
||||
|
||||
initFuture() {
|
||||
//大内容tab配置初始化
|
||||
TabConfigManager.getInstance().initDiscoverTabConfig()
|
||||
// 初始化常用指标管理(参考 iOS MePool.mm:338 userIdLoginSuccess)
|
||||
CommonIndicatorsManage.getInstance().init(this.context)
|
||||
// 初始化指标参数管理(参考 iOS MePool.mm:340)
|
||||
IndicatorParamsManage.getInstance().init(this.context)
|
||||
FunctionAuthorityRequestManager.requestAuthorityDataAfterLocalLoad()
|
||||
//初始化码表
|
||||
const codeTableInstance = CodeTableManager.get()
|
||||
codeTableInstance.setInnerDebugMode(DebugToolUtil.isInnerNetSimulateMode())
|
||||
codeTableInstance.initCodeTable(this.context, HexinVersionControl.disableCodeTableIncrementUpdate(),
|
||||
CodeTableMonitor.getInstance(), () => {
|
||||
return ServerTimeProvider.getInstance().getServerTime()
|
||||
}, (isJson?: boolean) => {
|
||||
return buildHeader(isJson)
|
||||
}).then(() => {
|
||||
//主力合约初始化
|
||||
MainContractManager.get().init()
|
||||
//初始化期货行情页面的tab配置
|
||||
HQTabManager.get().init()
|
||||
})
|
||||
//期货市场配置
|
||||
FutureMarketDataManager.get().init()
|
||||
//不活跃合约
|
||||
InactiveContractManager.get().init()
|
||||
// 初始化延时市场配置
|
||||
DelayMarketManager.get().init()
|
||||
// 初始化品种码转换映射表(如果还没加载)
|
||||
VarietyCodeTransformManager.init()
|
||||
// 交易管理初始化
|
||||
TradeInitializor.init(this.context)
|
||||
// 分时持久化存储UI状态存储初始化
|
||||
CurveStorage.getInstance().initPersistentStorage()
|
||||
}
|
||||
|
||||
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
|
||||
HXLog.i(this.tag, `onNewWant ${AbilityConstant.LaunchReason.CONTINUATION}`)
|
||||
WantCacheManager.getInstance().stashWant(want)
|
||||
if (launchParam.launchReason == AbilityConstant.LaunchReason.CONTINUATION) {
|
||||
// 将上述的保存的数据取出恢复
|
||||
let continueInput = '';
|
||||
if (want.parameters != undefined) {
|
||||
continueInput = want.parameters.curveData as string;
|
||||
|
||||
HXLog.i(this.tag, "onNewWant continue input = " + continueInput)
|
||||
this.curNaviModel = JSON.parse(continueInput) as CurveNaviModel;
|
||||
} else {
|
||||
HXLog.i(this.tag, "onNewWant continue input is empty")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
OpenAccountManager.getInstance().upCfmmcRecord()
|
||||
PreferenceService.putVal('Install', 'isFirstTimeEntering', false, true);
|
||||
hilog.info(0x0000, this.tag, '%{public}s', 'Ability onDestroy');
|
||||
}
|
||||
|
||||
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult | Promise<AbilityConstant.OnContinueResult> {
|
||||
let versionDst = wantParam.version; // 获取迁移对端应用的版本号
|
||||
let versionSrc: number = 1000600; // 获取迁移源端即本端应用的版本号
|
||||
HXLog.i(this.tag, "versionDst = " + versionDst + "versionSrc = " + versionSrc)
|
||||
if (versionDst < versionSrc) { // 兼容性校验
|
||||
// 兼容性校验不满足
|
||||
HXLog.w(this.tag, "onContinue, dst not match the minumum version, abort continuation process")
|
||||
return AbilityConstant.OnContinueResult.MISMATCH;
|
||||
}
|
||||
if (ContinuousDataHelper.getInstance().getCurrentNavi()) {
|
||||
hilog.info(0x0000, this.tag, "onContinue set success = ");
|
||||
wantParam["curveData"] = JSON.stringify(ContinuousDataHelper.getInstance().getCurrentNavi()!);
|
||||
}
|
||||
wantParam[wantConstant.Params.SUPPORT_CONTINUE_PAGE_STACK_KEY] = false;
|
||||
return AbilityConstant.OnContinueResult.AGREE;
|
||||
}
|
||||
|
||||
onWindowStageCreate(windowStage: window.WindowStage) {
|
||||
//存储程序当前的 windowStage,用于创建子窗口。
|
||||
AppStorage.setOrCreate('EntryAbilityWindowStage', windowStage);
|
||||
// 埋点调试工具需要使用的 windowStage
|
||||
AppStorage.setOrCreate('windowStage', windowStage);
|
||||
|
||||
//初始化App的一些公共资源和窗口设置
|
||||
let windowClass: window.Window = windowStage.getMainWindowSync();
|
||||
let avoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR);
|
||||
GlobalContext.initNavigationBarHeight(avoidArea.bottomRect.height)
|
||||
|
||||
|
||||
let statusBarHeight = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height;
|
||||
GlobalContext.initStatusBarHeight(statusBarHeight);
|
||||
// Main window is created, set main page for this ability
|
||||
hilog.info(0x0000, this.tag, '%{public}s', 'Ability onWindowStageCreate');
|
||||
windowStage.getMainWindow().then((windowObj) => {
|
||||
this.windowObj = windowObj
|
||||
// 获取应用启动时的窗口尺寸
|
||||
this.curBp = updateBreakpoint(windowObj.getWindowProperties().windowRect.width, this.curBp)
|
||||
// 注册回调函数,监听窗口尺寸变化
|
||||
windowObj.on('windowSizeChange', (windowSize) => {
|
||||
this.curBp = updateBreakpoint(windowSize.width, this.curBp)
|
||||
updateCurrentWindowWith(windowSize.width)
|
||||
})
|
||||
});
|
||||
this.registerGroupHandlers();
|
||||
ColourModeStatus.initColourModeSetting(this.context)
|
||||
HexinVersionControl.recordFirstInstallVersion();
|
||||
HexinVersionControl.getInstance().requestVersionConfig();
|
||||
|
||||
// 网络不可用
|
||||
const netAvailable = HXNetwork.getInstance().detectNet();
|
||||
if (!netAvailable) {
|
||||
this.loadNetworkAnomalyPage(windowStage);
|
||||
return;
|
||||
}
|
||||
|
||||
this.initializeCbasService().then(() => {
|
||||
// 正常流程
|
||||
}).catch((error: Error) => {
|
||||
// 即使失败也记录日志,但继续加载页面
|
||||
HXLog.e(this.tag, 'cbasService init failed: %{public}s');
|
||||
}).finally(() => {
|
||||
// 确保页面一定会加载
|
||||
if (!PreferenceService.getBooleanValueSync('Privacy', 'isUserAgree', false)) {
|
||||
this.loadPrivacyPage(windowStage);
|
||||
} else {
|
||||
this.loadIndexPage(windowStage);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onWindowStageRestore(windowStage: window.WindowStage): void {
|
||||
//应用窗口恢复时,如果有接续的数据就说明当前为接续开启应用,打开分时页
|
||||
HXLog.i(this.tag, "onWindowStageRestore")
|
||||
let parsedData: CurveNaviModel;
|
||||
if (this.curNaviModel) {
|
||||
HXLog.i(this.tag, "onWindowStageRestore data is valid")
|
||||
parsedData = this.curNaviModel;
|
||||
jumpToQuote(parsedData.code, parsedData.marketId, parsedData.codeList, parsedData.marketIdList);
|
||||
this.curNaviModel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private registerGroupHandlers() {
|
||||
GroupSubManager.getInstance().addUserGroupHandler(SelfCodeMainListHandler.GROUP_HANDLE_NAME, new SelfCodeMainListHandler());
|
||||
GroupSubManager.getInstance().addUserGroupHandler(SelfCodeGroupHandler.GROUP_HANDLE_NAME, new SelfCodeGroupHandler());
|
||||
GroupSubManager.getInstance().addUserGroupHandler(YYBListGroupHandler.GROUP_HANDLE_NAME,new YYBListGroupHandler())
|
||||
GroupSubManager.getInstance().addUserGroupHandler(TradeCloudSyncConfig.FILE_NAME, new TradeSettingGroupHandler())
|
||||
const indicatorChannels: string[] = [
|
||||
BizType.COMMON_INDICATORS,
|
||||
BizType.CUSTOM_INDICATORS,
|
||||
BizType.INDICATOR_PARAMS,
|
||||
BizType.SQUARE_INDICATORS
|
||||
]
|
||||
for (const channel of indicatorChannels) {
|
||||
GroupSubManager.getInstance().addUserGroupHandler(channel, new IndicatorGroupHandler(channel))
|
||||
}
|
||||
// 接入画线同步 push 分组,收到远端画线后驱动本地刷新与重绘。
|
||||
GroupSubManager.getInstance().addUserGroupHandler(DrawLineGroupHandler.GROUP_KEY, new DrawLineGroupHandler())
|
||||
GroupSubManager.getInstance().addUserGroupHandler(AIGroupPushHandler.GROUP_HANDLE_NAME, new AIGroupPushHandler())
|
||||
GroupSubManager.getInstance().addUserGroupHandler(SidGroupHandler.GROUP_HANDLE_NAME, new SidGroupHandler())
|
||||
GroupSubManager.getInstance().addUserGroupHandler(
|
||||
FunctionAuthorityGroupHandler.GROUP_HANDLE_NAME,
|
||||
new FunctionAuthorityGroupHandler()
|
||||
)
|
||||
GroupSubManager.getInstance().addUserGroupHandler(QhStrategyAdPushHandler.GROUP_HANDLE_NAME, new QhStrategyAdPushHandler())
|
||||
GroupSubManager.getInstance().addAllGroupHandler(QhStrategyAdPagePushHandler.GROUP_HANDLE_NAME, new QhStrategyAdPagePushHandler())
|
||||
GroupSubManager.getInstance().addAllGroupHandler(GrayTestPushHandler.GROUP_HANDLE_NAME, new GrayTestPushHandler());
|
||||
GroupSubManager.getInstance().addAllGroupHandler(AccountAnalysisLuaPushHandler.GROUP_HANDLE_NAME, new AccountAnalysisLuaPushHandler());
|
||||
GroupSubManager.getInstance().addAllGroupHandler(FuturesCompanyGroupHandler.GROUP_HANDLE_NAME,new FuturesCompanyGroupHandler())
|
||||
GroupSubManager.getInstance().addAllGroupHandler(StockGroupConstants.KEY_STOCK_STORAGE_UPDATE, new StockGroupHandler())
|
||||
GroupSubManager.getInstance().addAllGroupHandler(StockGroupConstants.KEY_VARIETY_STORAGE_UPDATE, new VarietyGroupHandler())
|
||||
GroupSubManager.getInstance().addAllGroupHandler(MainContractGroupHandler.GROUP_HANDLE_NAME, new MainContractGroupHandler())
|
||||
|
||||
}
|
||||
|
||||
onWindowStageDestroy() {
|
||||
hilog.info(0x0000, this.tag, '%{public}s', 'Ability onWindowStageDestroy');
|
||||
}
|
||||
|
||||
onForeground() {
|
||||
// Ability has brought to foreground
|
||||
AppStorage.setOrCreate('isAbilityForeGround', true);
|
||||
cbasService.onForeground()
|
||||
this.socketMonitor.onAppForeground();
|
||||
HXPushService.getInstance().checkPushPermissionStatus()
|
||||
ColourModeStatus.updateSystemColourModeOnAbilityForeground(this.context)
|
||||
}
|
||||
|
||||
onConfigurationUpdate(newConfig: Configuration): void {
|
||||
ColourModeStatus.onSystemConfigurationUpdate(newConfig.colorMode)
|
||||
}
|
||||
|
||||
onBackground() {
|
||||
// Ability has back to background
|
||||
AppStorage.setOrCreate('isAbilityForeGround', false);
|
||||
cbasService.onBackground()
|
||||
this.socketMonitor.onAppBackground();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载应用主页面
|
||||
* @param windowStage
|
||||
*/
|
||||
private loadIndexPage(windowStage: window.WindowStage) {
|
||||
//三方标题栏风险提示,白名单
|
||||
WebJumpLinkWhiteManager.getInstance().loadWhiteJumpLink()
|
||||
|
||||
windowStage.loadContent('pages/Index', (err, data) => {
|
||||
if (err.code) {
|
||||
hilog.error(0x0000, this.tag, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
|
||||
return;
|
||||
}
|
||||
onAppStartLoading(this.context,true);
|
||||
hilog.info(0x0000, this.tag, 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载隐私政策页面
|
||||
* @param windowStage
|
||||
*/
|
||||
private loadPrivacyPage(windowStage: window.WindowStage) {
|
||||
windowStage.loadContent('pages/LauncherPage', (err, data) => {
|
||||
if (err.code) {
|
||||
hilog.error(0x0000, this.tag, 'Failed to load loadPrivacyPage. Cause: %{public}s', JSON.stringify(err) ?? '');
|
||||
return;
|
||||
}
|
||||
onAppStartLoading(this.context);
|
||||
hilog.info(0x0000, this.tag, 'Succeeded in loadPrivacyPage. Data: %{public}s', JSON.stringify(data) ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载网络异常页面
|
||||
* @param windowStage
|
||||
*/
|
||||
private loadNetworkAnomalyPage(windowStage: window.WindowStage) {
|
||||
windowStage.loadContent('network/NetworkAnomalyPage', (err, data) => {
|
||||
if (err.code) {
|
||||
hilog.error(0x0000, this.tag, 'Failed to load NetworkAnomalyPage. Cause: %{public}s', JSON.stringify(err) ?? '');
|
||||
return;
|
||||
}
|
||||
onAppStartLoading(this.context);
|
||||
hilog.info(0x0000, this.tag, 'Succeeded in NetworkAnomalyPage. Data: %{public}s', JSON.stringify(data) ?? '');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载埋点初始化
|
||||
*/
|
||||
private async initializeCbasService() {
|
||||
try {
|
||||
await AppConfigManager.getInstance().fetchIMEI()
|
||||
let udid = AppConfigManager.getInstance().getUdidSync()
|
||||
if(!StringUtils.isNotEmpty(udid)) {
|
||||
udid = AppConfigManager.UDID_NONE
|
||||
}
|
||||
await cbasService.init(udid)
|
||||
HXLog.i(this.tag, 'cbasService initialized successfully');
|
||||
} catch (error) {
|
||||
HXLog.i( this.tag, 'cbasService init failed');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function onAppStartLoading(context:Context,isAgreePrivacy:boolean = false) {
|
||||
ConnectionInitiator.initConnectionService(context)
|
||||
AppConfigManager.getInstance().fetchIMEI().then((isSuccess: boolean) => {
|
||||
if (isSuccess) {
|
||||
AuthProviderService.getInstance().sendAuthRequest(ConnectionSeq.HQ);
|
||||
}
|
||||
let udid = AppConfigManager.getInstance().getUdidSync()
|
||||
if(!StringUtils.isNotEmpty(udid)) {
|
||||
udid = AppConfigManager.UDID_NONE
|
||||
}
|
||||
cbasService.init(udid)
|
||||
if (isAgreePrivacy) {
|
||||
AdvertisingMonitorService.getInstance().init(context)
|
||||
}
|
||||
});
|
||||
//初始化APM
|
||||
APMHelper.init(context);
|
||||
//初始化窗口工具
|
||||
ScreenUtil.get().init();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { MDataModel, AppPushMessage } from "service_push";
|
||||
|
||||
export function ProtocolJumpTest() {
|
||||
let appMsg1: MDataModel = {
|
||||
'A': '10',
|
||||
'F': '2205',
|
||||
'C': 'FG2601',
|
||||
'ACT': '70',
|
||||
'TY': '20',
|
||||
'ST': '预警通知'
|
||||
}
|
||||
|
||||
let appMsg2: MDataModel = {
|
||||
'A': '10',
|
||||
'ACT': '70',
|
||||
"F": "2804",
|
||||
"U": "https://news.10jqka.com.cn/20251111/c672408397.shtml"
|
||||
}
|
||||
|
||||
let appMsg3: MDataModel = {
|
||||
// 跳转个股
|
||||
'A': '10',
|
||||
'F': '2205',
|
||||
'C': 'USDIND',
|
||||
'ACT': '60',
|
||||
'ST': '美元指数预警'
|
||||
}
|
||||
|
||||
let appMsg4: MDataModel = {
|
||||
// 跳转外部浏览器
|
||||
'A': '30',
|
||||
'ACT': '70',
|
||||
"F": "2804",
|
||||
"U": "https://news.10jqka.com.cn/20251111/c672408397.shtml"
|
||||
}
|
||||
|
||||
let appMsg5: MDataModel = {
|
||||
// 拉起其他应用
|
||||
'A': '40',
|
||||
'ACT': '70',
|
||||
'PN':'com.hexin.hmn.sjcg',
|
||||
'CN':'EntryAbility',
|
||||
}
|
||||
|
||||
let protocol1: AppPushMessage = {
|
||||
APPID: '207',
|
||||
ID: '604900',
|
||||
TIPTYPE: '1',
|
||||
TITLE: '标题',
|
||||
DESC: '内容',
|
||||
APPMSG: appMsg1
|
||||
}
|
||||
|
||||
let data: Record<string, string> = {};
|
||||
data['payload'] = JSON.stringify(protocol1);
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { RemoteNotificationExtensionAbility, pushCommon } from '@kit.PushKit';
|
||||
import { hilog } from '@kit.PerformanceAnalysisKit';
|
||||
import { TradeDailyPushDataManager } from '../NotificationExtension/TradeDaily/TradeDailyPushDataManager';
|
||||
|
||||
const DOMAIN = 0x0000;
|
||||
|
||||
export default class RemoteNotificationExtAbility extends RemoteNotificationExtensionAbility {
|
||||
async onReceiveMessage(remoteNotificationInfo: pushCommon.RemoteNotificationInfo):
|
||||
Promise<pushCommon.RemoteNotificationContent> {
|
||||
const data = remoteNotificationInfo.data
|
||||
try {
|
||||
const pushInfo: string = JSON.parse(data)?.data
|
||||
await TradeDailyPushDataManager.getInstance().initDB(this.context)
|
||||
const pushModel = TradeDailyPushDataManager.getInstance().convertToTradeDailyPushModel(JSON.parse(pushInfo))
|
||||
if (pushModel) {
|
||||
hilog.info(DOMAIN, 'RemoteNotification', 'info: %{public}s', pushModel.body);
|
||||
await TradeDailyPushDataManager.getInstance().recordPushDaily(pushModel)
|
||||
}
|
||||
} catch (e) {
|
||||
hilog.error(DOMAIN, 'RemoteNotification', 'error: %{public}s', e);
|
||||
}
|
||||
|
||||
await Promise.resolve();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import Want from '@ohos.app.ability.Want';
|
||||
import { Queue } from '@kit.ArkTS';
|
||||
import { ClipUtils, GlobalContext, HXLog, jumpToWebPage } from 'biz_common';
|
||||
import { AppPushMessage, DoPushCbas, PushConstants, PushMessageManager } from 'service_push';
|
||||
import { ShareManager } from '@b2c-spi/hx_share_api/src/main/ets/manager/ShareManager';
|
||||
import { ELK_LEVEL, sendElk } from 'service_monitor';
|
||||
|
||||
const TAG: string = 'WantCacheManager'
|
||||
const BACK_ACTION: string = 'ohos.want.action.viewData'
|
||||
const BACK_ENTITY: string = 'entity.system.browsable'
|
||||
|
||||
|
||||
export const FIRST_PAGE_ONAPPEAR_EVENT : string = 'FirstPage OnAppear'
|
||||
/**
|
||||
* WantCacheManager 类用于管理 Want 对象的缓存和处理。
|
||||
* 采用单例模式,确保整个应用中只有一个实例。
|
||||
* 该管理器会缓存接收到的 Want 对象,并在合适的时机进行处理。
|
||||
*/
|
||||
export class WantCacheManager {
|
||||
// 单例模式下的唯一实例
|
||||
private static instance: WantCacheManager
|
||||
|
||||
private pushMessageManager : PushMessageManager = PushMessageManager.getInstance();
|
||||
private clipUtils: ClipUtils = ClipUtils.getInstance()
|
||||
|
||||
/**
|
||||
* 获取 WantCacheManager 的单例实例。
|
||||
* 如果实例不存在,则创建一个新实例。
|
||||
* @returns WantCacheManager 的单例实例
|
||||
*/
|
||||
public static getInstance(): WantCacheManager {
|
||||
if (!WantCacheManager.instance) {
|
||||
WantCacheManager.instance = new WantCacheManager()
|
||||
}
|
||||
return WantCacheManager.instance
|
||||
}
|
||||
|
||||
// 标志位,指示是否可以处理缓存的 Want 对象
|
||||
private enabled: boolean = false
|
||||
/**
|
||||
* 首页加载完成的回调函数。
|
||||
* 当首页加载完成后,将 enabled 标志位设置为 true,并尝试处理缓存的 Want 对象。
|
||||
*/
|
||||
private firstPageFinishedCb = () => {
|
||||
this.enabled = true
|
||||
this.popWant()
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有构造函数,确保只能通过 getInstance 方法获取实例。
|
||||
* 注册首页加载完成的事件监听器。
|
||||
*/
|
||||
private constructor() {
|
||||
// 监听首页加载完成事件,当事件触发时调用 firstPageFinishedCb 回调函数
|
||||
GlobalContext.get().eventHub.on(FIRST_PAGE_ONAPPEAR_EVENT, this.firstPageFinishedCb)
|
||||
}
|
||||
|
||||
// 存储 Want 对象的队列
|
||||
private wantQueue: Queue<Want> = new Queue()
|
||||
|
||||
/**
|
||||
* 存储 Want 对象到队列中。
|
||||
* 如果 enabled 标志位为 true,则尝试处理队列中的 Want 对象。
|
||||
* @param want 要存储的 Want 对象
|
||||
*/
|
||||
stashWant(want: Want) {
|
||||
// 将 Want 对象添加到队列中
|
||||
this.wantQueue.add(want)
|
||||
// 记录日志,提示接收到新的 Want 对象
|
||||
HXLog.i(TAG, ' New Want received, want.param : ')
|
||||
// 如果可以处理 Want 对象,则尝试处理队列中的元素
|
||||
if (this.enabled) {
|
||||
this.popWant()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从队列中取出并处理一个 Want 对象。
|
||||
* 如果队列不为空,则调用不同业务方进行处理,业务方处理保证不更改原有对象
|
||||
*/
|
||||
private popWant() {
|
||||
if (this.wantQueue.length > 0) {
|
||||
const w = this.wantQueue.pop()
|
||||
|
||||
// push协议
|
||||
this.handlePayload(w)
|
||||
|
||||
// 微信分享
|
||||
this.handleWeChat(w)
|
||||
|
||||
// 浏览器回流
|
||||
this.handleBackWash(w)
|
||||
|
||||
// appLinking回流
|
||||
this.handleBackWashByBrowsable(w)
|
||||
|
||||
this.handleReflow(w)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理推送协议对象。
|
||||
* 根据want中解析到的推送协议的来源,获取相应的策略并处理。
|
||||
* @param want 要处理的want
|
||||
*/
|
||||
private handlePayload(want : Want) {
|
||||
if (want?.action === 'com.push.action') {
|
||||
const param = want?.parameters
|
||||
if (param) {
|
||||
const payloadJson = param['payload'] as string
|
||||
HXLog.i(PushConstants.AM_PUSH, payloadJson)
|
||||
const payload = JSON.parse(payloadJson) as AppPushMessage
|
||||
DoPushCbas.pushArriveCbas(payload)
|
||||
DoPushCbas.pushShowCbas(payload)
|
||||
this.pushMessageManager.handlerPush(payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 处理wechat分享协议对象。
|
||||
* 根据want中解析到的分享协议,进行处理
|
||||
* @param want 要处理的want
|
||||
*/
|
||||
private handleWeChat(want: Want) {
|
||||
// 判断service是否有效
|
||||
const shareService = ShareManager.getShareService()
|
||||
if (!shareService) {
|
||||
HXLog.e("HXShare", 'shareService is null');
|
||||
return
|
||||
}
|
||||
shareService.getWXApi().handleWant(want, ShareManager.getShareService()?.getWXEventHandler())
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 端外跳转至期货通
|
||||
*/
|
||||
private async handleBackWash(want: Want) {
|
||||
if (want.action === BACK_ACTION && want.uri && want.entities?.length === 0) {
|
||||
const params = this.extractUrlParams(want.uri)
|
||||
if (params['url']) {
|
||||
jumpToWebPage(params)
|
||||
}
|
||||
await this.dealPasteboard()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async dealPasteboard(){
|
||||
if (this.clipUtils.getClipBoardSwitchState()) {
|
||||
// 请求剪切板权限后再读取
|
||||
const hasPermission = await this.clipUtils.checkAndRequestPermission()
|
||||
if (hasPermission) {
|
||||
const clipboardContent = this.clipUtils.getClipboardContent()
|
||||
if (this.clipUtils.isValidClipPassword(clipboardContent)) {
|
||||
this.clipUtils.parseClipboard(clipboardContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 浏览器跳转至期货通
|
||||
*/
|
||||
private handleBackWashByBrowsable(want: Want) {
|
||||
if (want.action === BACK_ACTION && want.uri && want.entities &&
|
||||
want.entities[0] === BACK_ENTITY) {
|
||||
const params = this.extractUrlParams(want.uri)
|
||||
if (params['url']) {
|
||||
jumpToWebPage(params)
|
||||
}
|
||||
this.dealPasteboard()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理回流埋点。
|
||||
* @param want 要处理的want
|
||||
*/
|
||||
private handleReflow(want: Want) {
|
||||
if (want.parameters) {
|
||||
const callerBundleName = want.parameters['ohos.aafwk.param.callerBundleName'] as string
|
||||
if (callerBundleName === 'com.hexin.hmn.sjcg') {
|
||||
// 手抄跳转
|
||||
sendElk(TAG, `callerBundleName ${callerBundleName}`, ELK_LEVEL.INFO, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extractUrlParams(input: string): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
// 提取所有形如 key=value 的参数(以 &、^ 或字符串结尾为边界)
|
||||
const matches = Array.from(input.matchAll(/([a-zA-Z_][a-zA-Z0-9_]*)=([^&\^]*)/gi));
|
||||
if (matches.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 迭代解码(兼容含 & 的转义值 %26)
|
||||
matches.forEach(match => {
|
||||
const key = match[1];
|
||||
let decoded = match[2];
|
||||
let prev = '';
|
||||
do {
|
||||
prev = decoded;
|
||||
try {
|
||||
decoded = decodeURIComponent(decoded);
|
||||
} catch {
|
||||
break; // 非法编码终止
|
||||
}
|
||||
} while (decoded !== prev && /%[0-9A-Fa-f]{2}/.test(decoded));
|
||||
result[key] = decoded;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { TextMetricsMeasurer } from "@b2b/hq_table";
|
||||
import { text } from "@kit.ArkGraphics2D";
|
||||
import { HXLog } from "biz_common";
|
||||
|
||||
/**
|
||||
* author:liuqingliang
|
||||
* created on 2025/12/19
|
||||
* desc:全局字体管理
|
||||
*/
|
||||
export class FontManager {
|
||||
private static readonly TAG = "FontManager"
|
||||
private static instance: FontManager
|
||||
|
||||
public static getInstance(): FontManager {
|
||||
if (!FontManager.instance) {
|
||||
FontManager.instance = new FontManager()
|
||||
}
|
||||
return FontManager.instance
|
||||
}
|
||||
|
||||
registerFont(uiContext: UIContext) {
|
||||
try {
|
||||
let font = uiContext.getFont();
|
||||
font.registerFont({
|
||||
familyName: 'bold',
|
||||
familySrc: $rawfile('fonts/THSJinRongTi_bold.otf')
|
||||
});
|
||||
font.registerFont({
|
||||
familyName: 'Regular',
|
||||
familySrc: $rawfile('fonts/THSJinRongTi_Regular.otf')
|
||||
});
|
||||
font.registerFont({
|
||||
familyName: 'Medium',
|
||||
familySrc: $rawfile('fonts/THSJinRongTi_Medium.otf')
|
||||
});
|
||||
|
||||
TextMetricsMeasurer.getInstance().init()
|
||||
} catch (e) {
|
||||
HXLog.e(FontManager.TAG, "registerFont failed:" + JSON.stringify(e))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private loadFontSync(fontFamily: 'bold' | 'Regular' | 'Medium') {
|
||||
let fontCollection = text.FontCollection.getGlobalInstance()
|
||||
fontCollection.loadFontSync(fontFamily, $rawfile(`fonts/THSJinRongTi_${fontFamily}.otf`))
|
||||
}
|
||||
|
||||
loadTHSFonts() {
|
||||
this.loadFontSync('bold');
|
||||
this.loadFontSync('Regular');
|
||||
this.loadFontSync('Medium');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
CookieManager,
|
||||
FunctionAuthorityRequester,
|
||||
HxServiceExportConstants,
|
||||
HXUserService,
|
||||
ProductAuthorityManager
|
||||
} from 'biz_hxservice'
|
||||
import { AppConfigManager } from 'biz_common'
|
||||
import { http } from '@kit.NetworkKit'
|
||||
import { JSON } from '@kit.ArkTS'
|
||||
import { UnifyUrlManager } from '@b2c-f/url-modify-config'
|
||||
import emitter from '@ohos.events.emitter'
|
||||
|
||||
interface EmptyFunctionAuthorityResponse {
|
||||
code: number
|
||||
data: Object[]
|
||||
}
|
||||
|
||||
class AppFunctionAuthorityRequester implements FunctionAuthorityRequester {
|
||||
|
||||
async requestAuthorityData(): Promise<object> {
|
||||
if (HXUserService.getInstance().isTemporaryUser()) {
|
||||
const response: EmptyFunctionAuthorityResponse = { code: 0, data: [] }
|
||||
return response
|
||||
}
|
||||
|
||||
const httpRequest = http.createHttp()
|
||||
try {
|
||||
const requestOptions: http.HttpRequestOptions = {
|
||||
method: http.RequestMethod.GET,
|
||||
header: this.buildHeaders(),
|
||||
connectTimeout: 30000,
|
||||
readTimeout: 30000,
|
||||
expectDataType: http.HttpDataType.STRING
|
||||
}
|
||||
const response = await httpRequest.request(UnifyUrlManager.getRealUrl('custom_authority_url'), requestOptions)
|
||||
if (response.responseCode !== http.ResponseCode.OK) {
|
||||
throw new Error(`http error: ${response.responseCode}`)
|
||||
}
|
||||
const parseOptions: JSON.ParseOptions = {
|
||||
bigIntMode: JSON.BigIntMode.PARSE_AS_BIGINT
|
||||
}
|
||||
return JSON.parse(response.result as string, null, parseOptions) as object
|
||||
} finally {
|
||||
httpRequest.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
private buildHeaders(): Record<string, string> {
|
||||
const headers: Record<string, string> = {}
|
||||
headers['Content-Type'] = 'application/json'
|
||||
const cookie = CookieManager.getInstance().getCookieSync()
|
||||
if (cookie) {
|
||||
headers['Cookie'] = cookie
|
||||
}
|
||||
return headers
|
||||
}
|
||||
}
|
||||
|
||||
export class FunctionAuthorityRequestManager {
|
||||
private static initialized: boolean = false
|
||||
private static cookieUpdateListener?: () => void
|
||||
private static requestingAfterCookieUpdate: boolean = false
|
||||
private static userId: string = ''
|
||||
|
||||
static loadAuthorityFromLocal(): boolean {
|
||||
FunctionAuthorityRequestManager.ensureInitialized()
|
||||
return ProductAuthorityManager.getInstance().loadAuthorityFromLocal()
|
||||
}
|
||||
|
||||
static requestAuthorityDataAfterLocalLoad(): Promise<boolean> {
|
||||
FunctionAuthorityRequestManager.loadAuthorityFromLocal()
|
||||
return FunctionAuthorityRequestManager.requestAuthorityData()
|
||||
}
|
||||
|
||||
static requestAuthorityData(): Promise<boolean> {
|
||||
FunctionAuthorityRequestManager.ensureInitialized()
|
||||
return ProductAuthorityManager.getInstance().requestAuthorityData()
|
||||
}
|
||||
|
||||
static requestAuthorityDataForPush(pushTimestamp: number): Promise<boolean> {
|
||||
FunctionAuthorityRequestManager.ensureInitialized()
|
||||
return ProductAuthorityManager.getInstance().requestAuthorityDataForPush(pushTimestamp)
|
||||
}
|
||||
|
||||
private static ensureInitialized(): void {
|
||||
if (FunctionAuthorityRequestManager.initialized) {
|
||||
return
|
||||
}
|
||||
ProductAuthorityManager.getInstance().setRequester(new AppFunctionAuthorityRequester())
|
||||
FunctionAuthorityRequestManager.registerCookieUpdateListener()
|
||||
FunctionAuthorityRequestManager.initialized = true
|
||||
}
|
||||
|
||||
private static registerCookieUpdateListener(): void {
|
||||
if (FunctionAuthorityRequestManager.cookieUpdateListener) {
|
||||
return
|
||||
}
|
||||
FunctionAuthorityRequestManager.cookieUpdateListener = () => {
|
||||
const newUserId = HXUserService.getInstance().getUserIdFromLocal()
|
||||
if (FunctionAuthorityRequestManager.requestingAfterCookieUpdate ||
|
||||
FunctionAuthorityRequestManager.userId == newUserId) {
|
||||
return
|
||||
}
|
||||
FunctionAuthorityRequestManager.userId = newUserId
|
||||
FunctionAuthorityRequestManager.requestingAfterCookieUpdate = true
|
||||
FunctionAuthorityRequestManager.requestAuthorityDataAfterLocalLoad()
|
||||
.finally(() => {
|
||||
FunctionAuthorityRequestManager.requestingAfterCookieUpdate = false
|
||||
})
|
||||
}
|
||||
emitter.on(HxServiceExportConstants.COOKIE_UPDATE, FunctionAuthorityRequestManager.cookieUpdateListener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { StringUtils } from "@b2c-m/ohos_hx_utils";
|
||||
import { IStructReceiver, ProtocolDef, StuffBaseStruct, StuffResourceStruct } from '@kernel/lib_communication';
|
||||
import { GroupSubManager, L2DowngradeManager } from 'base_mobilepush';
|
||||
import { HashMap } from '@kit.ArkTS';
|
||||
import { emitter } from '@kit.BasicServicesKit';
|
||||
import { EmitterConstants } from 'biz_common'
|
||||
import { SubscribedPushManager } from "biz_trade";
|
||||
|
||||
export class RealDataStructObserver implements IStructReceiver {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
onIntercept(stuffBaseStruct: StuffBaseStruct): boolean {
|
||||
if (stuffBaseStruct == null) {
|
||||
return false;
|
||||
}
|
||||
const headId = stuffBaseStruct.packageId;
|
||||
if (headId == ProtocolDef.DATAID_NEW_REALTIME) {
|
||||
const dataType = stuffBaseStruct.headType & ProtocolDef.DATA_TYPE_MASK;
|
||||
if (dataType === ProtocolDef.DATA_TYPE_JSON) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
handleStruct(stuffBaseStruct: StuffBaseStruct): boolean {
|
||||
if (!(stuffBaseStruct instanceof StuffResourceStruct)) {
|
||||
return false;
|
||||
}
|
||||
const resStruct = stuffBaseStruct as StuffResourceStruct;
|
||||
const tmpBuffer = resStruct.buffer;
|
||||
const decodedStr = StringUtils.decode(tmpBuffer, "GBK")
|
||||
try {
|
||||
const jsonObject: ESObject = JSON.parse(decodedStr);
|
||||
//解析到分组订阅数据
|
||||
const parsedObj: ESObject = jsonObject["groupdata"];
|
||||
if (parsedObj) {
|
||||
GroupSubManager.getInstance().onReceiveGroupData(JSON.stringify(parsedObj), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 解析到Push订阅数据
|
||||
if (jsonObject[SubscribedPushManager.PARAM_CHANNEL]) {
|
||||
SubscribedPushManager.getInstance().onReceivePushData(decodedStr)
|
||||
}
|
||||
|
||||
const parsedEvents: HashMap<string, object>[] = jsonObject["events"]
|
||||
if (parsedEvents && parsedEvents.length > 0) {
|
||||
for (let event of parsedEvents) {
|
||||
if (event["eventID"] == "00000011") {
|
||||
L2DowngradeManager.getInstance().onReceiveGroupData(JSON.stringify(event));
|
||||
emitter.emit({ eventId: EmitterConstants.EVENT_ID_GET_PERMISSIONSID })
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getReceiverId(): string {
|
||||
return "RealDataStructObserver";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ISubDataHandler } from 'base_mobilepush'
|
||||
import { AIGroupClient, AI_GROUP_BIZ_NAME } from 'service_aigroup'
|
||||
|
||||
/**
|
||||
* AI分组的数据分组订阅处理器
|
||||
* 参考 iOS LJAIGroupManager 中的 MobiPush 注册机制
|
||||
* ctrlid = future_aigroupconfig
|
||||
*/
|
||||
export class AIGroupPushHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = AI_GROUP_BIZ_NAME
|
||||
|
||||
onReceiveGroupData(data: string): void {
|
||||
try {
|
||||
const pushData: AIGroupPushData = JSON.parse(data) as AIGroupPushData
|
||||
AIGroupClient.getInstance().onAIGroupPushNotification(pushData.ver)
|
||||
} catch (e) {
|
||||
// 解析失败时也触发更新,不传版本号
|
||||
AIGroupClient.getInstance().onAIGroupPushNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface AIGroupPushData {
|
||||
ver?: number
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ISubDataHandler } from 'base_mobilepush'
|
||||
import { GlobalContext, HXLog } from 'biz_common'
|
||||
import { ScriptUpdateService } from '@b2c-f/billparsemodule'
|
||||
|
||||
/**
|
||||
* 账户分析 Lua 脚本更新推送处理器
|
||||
*
|
||||
* 处理来自服务端的账户分析 Lua 脚本版本更新通知
|
||||
*/
|
||||
export class AccountAnalysisLuaPushHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = "qhtCapitalLuaUpdate"
|
||||
private static readonly TAG = "AccountAnalysisLuaPush"
|
||||
|
||||
onReceiveGroupData(data: string): void {
|
||||
HXLog.i(AccountAnalysisLuaPushHandler.TAG, `receive: ${data}`)
|
||||
|
||||
try {
|
||||
const pushData: AccountAnalysisLuaPushData = JSON.parse(data) as AccountAnalysisLuaPushData
|
||||
|
||||
if (pushData && pushData.AccountAnalysisLua) {
|
||||
const version = pushData.AccountAnalysisLua.version
|
||||
if (version) {
|
||||
HXLog.i(AccountAnalysisLuaPushHandler.TAG, `收到 Lua 脚本版本更新通知: version=${version}`)
|
||||
this.handleScriptUpdate(version)
|
||||
} else {
|
||||
HXLog.w(AccountAnalysisLuaPushHandler.TAG, '版本号为空')
|
||||
}
|
||||
} else {
|
||||
HXLog.w(AccountAnalysisLuaPushHandler.TAG, '推送数据格式错误')
|
||||
}
|
||||
} catch (error) {
|
||||
HXLog.e(AccountAnalysisLuaPushHandler.TAG, `解析推送数据失败: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理脚本更新
|
||||
*/
|
||||
private handleScriptUpdate(version: string): void {
|
||||
try {
|
||||
const context = GlobalContext.get()
|
||||
if (!context) {
|
||||
HXLog.e(AccountAnalysisLuaPushHandler.TAG, 'Context 为空,无法更新脚本')
|
||||
return
|
||||
}
|
||||
|
||||
const updateService = new ScriptUpdateService(context)
|
||||
|
||||
// 异步执行更新操作
|
||||
updateService.downloadAndInstall(version)
|
||||
.then((result) => {
|
||||
if (result.success) {
|
||||
if (result.updated) {
|
||||
HXLog.i(AccountAnalysisLuaPushHandler.TAG,
|
||||
`Lua 脚本更新成功: version=${result.version}, scriptPath=${result.scriptPath}`)
|
||||
} else {
|
||||
HXLog.i(AccountAnalysisLuaPushHandler.TAG,
|
||||
`Lua 脚本已是最新版本: version=${result.version}`)
|
||||
}
|
||||
} else {
|
||||
HXLog.e(AccountAnalysisLuaPushHandler.TAG,
|
||||
`Lua 脚本更新失败: ${result.errorMessage}`)
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
HXLog.e(AccountAnalysisLuaPushHandler.TAG, `Lua 脚本更新异常: ${error.message}`)
|
||||
})
|
||||
} catch (error) {
|
||||
HXLog.e(AccountAnalysisLuaPushHandler.TAG, `处理脚本更新失败: ${error}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户分析 Lua 脚本版本信息
|
||||
*/
|
||||
export interface AccountAnalysisLuaVersion {
|
||||
version: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户分析 Lua 脚本推送数据格式
|
||||
*/
|
||||
export interface AccountAnalysisLuaPushData {
|
||||
AccountAnalysisLua: AccountAnalysisLuaVersion
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { RandomUtil } from "@pura/harmony-utils";
|
||||
import { ISubDataHandler } from "base_mobilepush";
|
||||
import { HXLog } from "biz_common";
|
||||
import { StringUtil } from "biz_common/src/main/ets/utils/StringUtil";
|
||||
import { HexinVersionControl } from "biz_trade/src/main/ets/utils/HexinVersionControl";
|
||||
import { CodeTableManager } from "lib_codetablemanager";
|
||||
|
||||
export abstract class BaseStorageHandler implements ISubDataHandler {
|
||||
/**
|
||||
* 获取日志tag
|
||||
*/
|
||||
abstract getTag(): string
|
||||
|
||||
/**
|
||||
* 获取推送中的数据更新时间key
|
||||
*/
|
||||
abstract getTimeKey(): string
|
||||
|
||||
/**
|
||||
* 处理升级
|
||||
*/
|
||||
abstract update()
|
||||
|
||||
/**
|
||||
* 获取数据库版本信息
|
||||
*/
|
||||
abstract getDbVersion(): Promise<number>
|
||||
|
||||
private readonly RANDOM_REQUEST_TIME = 10 * 1000
|
||||
|
||||
private mTimeoutId: number | null = null
|
||||
|
||||
async onReceiveGroupData(data: string) {
|
||||
if (HexinVersionControl.disableCodeTableIncrementUpdate()) {
|
||||
return
|
||||
}
|
||||
HXLog.d(CodeTableManager.TAG, `${this.getTag()} onReveiveGroupData: ${data}`)
|
||||
const obj = JSON.parse(data) as Record<string, string>
|
||||
if (!obj) {
|
||||
HXLog.e(CodeTableManager.TAG, `${this.getTag()} onReceiveGroupData obj parse invalid`)
|
||||
return
|
||||
}
|
||||
const time: string = obj[this.getTimeKey()]
|
||||
const dbVersion = await this.getDbVersion()
|
||||
const remoteVersion = StringUtil.toSafeNumber(time, 0)
|
||||
HXLog.d(CodeTableManager.TAG, `${this.getTag()} remoteVersion: ${remoteVersion}, dbVersion: ${dbVersion}`)
|
||||
|
||||
if (StringUtil.isNumber(time) && remoteVersion > dbVersion) {
|
||||
this.randomDelayRequest(remoteVersion)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机延时请求
|
||||
*/
|
||||
private randomDelayRequest(remoteVersion: number) {
|
||||
// 取消之前的延迟任务
|
||||
if (this.mTimeoutId !== null) {
|
||||
clearTimeout(this.mTimeoutId)
|
||||
this.mTimeoutId = null
|
||||
}
|
||||
|
||||
const requestDelay: number = RandomUtil.getRandomInt(0, this.RANDOM_REQUEST_TIME)
|
||||
console.debug(CodeTableManager.TAG, `${this.getTag()} requestDelay ${requestDelay}`)
|
||||
|
||||
// 设置延迟任务
|
||||
this.mTimeoutId = setTimeout(() => {
|
||||
this.mTimeoutId = null
|
||||
// 延迟的这段时间,数据库可能被更新了,因此执行前再判断一次,减少不必要的请求
|
||||
this.getDbVersion().then((dbVersion: number) => {
|
||||
HXLog.d(CodeTableManager.TAG, `before update, remoteVersion: ${remoteVersion}, dbVersion: ${dbVersion}`)
|
||||
if (remoteVersion > dbVersion) {
|
||||
this.update()
|
||||
}
|
||||
})
|
||||
}, requestDelay)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ISubDataHandler } from 'base_mobilepush'
|
||||
import { FunctionAuthorityRequestManager } from '../../functionauthority/FunctionAuthorityRequester'
|
||||
|
||||
interface FunctionAuthorityGroupData {
|
||||
timestamp?: string | number
|
||||
timeStamp?: string | number
|
||||
}
|
||||
|
||||
export class FunctionAuthorityGroupHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = 'futures_custom_authority'
|
||||
|
||||
onReceiveGroupData(data: string, key?: string, isRealPush?: boolean): void {
|
||||
const timestamp = this.parseTimestamp(data)
|
||||
if (timestamp <= 0) {
|
||||
return
|
||||
}
|
||||
FunctionAuthorityRequestManager.requestAuthorityDataForPush(timestamp)
|
||||
}
|
||||
|
||||
private parseTimestamp(data: string | object): number {
|
||||
try {
|
||||
const payload = typeof data === 'string'
|
||||
? JSON.parse(data) as FunctionAuthorityGroupData
|
||||
: data as FunctionAuthorityGroupData
|
||||
return Number(payload.timestamp ?? payload.timeStamp ?? 0)
|
||||
} catch (error) {
|
||||
console.error(`[FunctionAuthorityGroupHandler] 解析权限推送失败: ${JSON.stringify(error)}`)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ISubDataHandler } from 'base_mobilepush'
|
||||
import { HXLog } from 'biz_common'
|
||||
import { GrayTestManager } from 'lib_graytestmanager'
|
||||
|
||||
export class GrayTestPushHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = "futures_new_gray_test_config_upgrade_notice"
|
||||
|
||||
onReceiveGroupData(data: string): void {
|
||||
HXLog.i("GrayTest", `GrayTestPushHandler receive: ${data}`)
|
||||
let verTimestamp: PushTimesTamp = JSON.parse(data) as PushTimesTamp
|
||||
if (verTimestamp && verTimestamp.timestamp > 0) {
|
||||
GrayTestManager.getInstance().dealReceiveMobilePush(verTimestamp.timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface PushTimesTamp {
|
||||
timestamp: number
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { AdsPopupManager } from "@b2c-f/fuhm-ads"
|
||||
import { AdsPopupHost } from "@b2c/first_page"
|
||||
import { ISubDataHandler } from "base_mobilepush"
|
||||
import { HXLog } from "biz_common"
|
||||
|
||||
/**
|
||||
* author : liuqingliang@myhexin.com
|
||||
* time : created on 2026/6/9
|
||||
* desc : 全域运营位-只会推送一次,不会实时推送
|
||||
*/
|
||||
export class QhStrategyAdPagePushHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = 'app_page_info'
|
||||
private static readonly KEY_TIME = "app_6"
|
||||
|
||||
onReceiveGroupData(data: string) {
|
||||
try {
|
||||
const pushData = JSON.parse(data) as Record<string, string>
|
||||
const timeStamp = pushData[QhStrategyAdPagePushHandler.KEY_TIME] ?? ''
|
||||
|
||||
if (timeStamp === AdsPopupManager.pushTimestamp) {
|
||||
return
|
||||
}
|
||||
AdsPopupHost.getInstance().requestPageInfo(timeStamp)
|
||||
} catch (error) {
|
||||
HXLog.e('QhStrategyAdPushHandler', `Failed to process push: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { AdsManager, ADS_TYPE_INDEX_POPUP, ADS_TYPE_QHTMKTPOP } from "@b2c-f/fuhm-ads"
|
||||
import { AdsPopupHost, FirstPagePopupAdHelper } from "@b2c/first_page"
|
||||
import { ISubDataHandler } from "base_mobilepush"
|
||||
import { HXLog, PreferenceService } from "biz_common"
|
||||
import { HXUserService } from "biz_hxservice"
|
||||
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* author: ME
|
||||
* desc: 期货策略广告推送处理器,对齐 Android QhStragyAdPushHandler
|
||||
* createTime: 2026/06/01
|
||||
* </pre>
|
||||
*/
|
||||
export class QhStrategyAdPushHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = 'qh_strategy_ad_push'
|
||||
private static readonly PUSH_STRATEGY_AD_KEY = 'push_stragy_ad'
|
||||
private static readonly SP_STRATEGY_AD = "sp_stragy_ad"
|
||||
private static readonly KEY_AD_TYPE = "adtype"
|
||||
private static readonly KEY_AD_IDS = "id"
|
||||
|
||||
onReceiveGroupData(data: string) {
|
||||
try {
|
||||
const pushData = JSON.parse(data) as Record<string, string>
|
||||
const adType = pushData[QhStrategyAdPushHandler.KEY_AD_TYPE] ?? ''
|
||||
const adId = pushData[QhStrategyAdPushHandler.KEY_AD_IDS] ?? ''
|
||||
|
||||
if (!adType || !adId) {
|
||||
HXLog.w('QhStrategyAdPushHandler', 'Invalid push data, missing adType or adId')
|
||||
return
|
||||
}
|
||||
|
||||
const pushKey = `${adType}_${adId}`
|
||||
const userId = HXUserService.getInstance().getUserIdFromLocal()
|
||||
|
||||
const preferenceKey = `${QhStrategyAdPushHandler.PUSH_STRATEGY_AD_KEY}_${userId}`
|
||||
const localPushKey = PreferenceService.getStringValueSync(QhStrategyAdPushHandler.SP_STRATEGY_AD,preferenceKey, '')
|
||||
if (pushKey === localPushKey) {
|
||||
HXLog.d('QhStrategyAdPushHandler', `Already pushed: ${pushKey}`)
|
||||
return
|
||||
}
|
||||
PreferenceService.putVal(QhStrategyAdPushHandler.SP_STRATEGY_AD,preferenceKey, pushKey, true)
|
||||
|
||||
if (adType === ADS_TYPE_INDEX_POPUP) {
|
||||
// 收到策略广告后重置启动弹窗过的标志
|
||||
FirstPagePopupAdHelper.getInstance().isShowedPopupAds = false
|
||||
}
|
||||
|
||||
if (adType === ADS_TYPE_QHTMKTPOP) {
|
||||
// 期货营销悬浮球-处理策略广告推送
|
||||
AdsPopupHost.getInstance().handlePush()
|
||||
}
|
||||
|
||||
AdsManager.refreshAdsAfterPushByAdType(adType, adId)
|
||||
} catch (error) {
|
||||
HXLog.e('QhStrategyAdPushHandler', `Failed to process push: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { ISubDataHandler } from 'base_mobilepush';
|
||||
import { UgcClient } from '../../register/ugcregister/UgcClient';
|
||||
|
||||
/**
|
||||
* 自选分组的数据分组订阅
|
||||
* @author menghuiyuan@myhexin.com
|
||||
*/
|
||||
export class SelfCodeGroupHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = "blockstock"
|
||||
onReceiveGroupData(data: string): void {
|
||||
let version:groupUpdate = JSON.parse(data) as groupUpdate
|
||||
UgcClient.getInstance().onSelfGroupRemoteVersion(version.ver)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface groupUpdate{
|
||||
ver:number
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ISubDataHandler } from 'base_mobilepush';
|
||||
import { UgcClient } from '../../register/ugcregister/UgcClient';
|
||||
import { groupUpdate } from './SelfCodeGroupHandler';
|
||||
/**
|
||||
* 默认自选数据分组订阅
|
||||
* @author zhangshan2@myhexin.com
|
||||
*/
|
||||
export class SelfCodeMainListHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = "futuresselfcode"
|
||||
onReceiveGroupData(data: string): void {
|
||||
let version:groupUpdate = JSON.parse(data) as groupUpdate
|
||||
UgcClient.getInstance().onSelfCodeRemoteVersion(version.ver)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { ISubDataHandler } from 'base_mobilepush'
|
||||
import { WxPaySidPermissionMarker } from 'biz_auth'
|
||||
import { HXLog, PreferenceService } from 'biz_common'
|
||||
import { SidPermissionRefreshClient } from 'biz_auth/src/main/ets/auth/sid/SidPermissionRefreshClient'
|
||||
import { HXUserService } from 'biz_hxservice'
|
||||
|
||||
const TAG = 'SidGroupHandler'
|
||||
|
||||
/**
|
||||
* SID权限更新分组推送数据。
|
||||
* 对应分组key: futures_app_sid_update_notice
|
||||
* timeStamp为服务端下发的更新标记,用于和本地已处理标记比较,变化时主动刷新SID权限。
|
||||
*/
|
||||
interface SidGroupData {
|
||||
timeStamp?: string | number
|
||||
}
|
||||
|
||||
export class SidGroupHandler implements ISubDataHandler {
|
||||
static readonly GROUP_HANDLE_NAME = 'futures_app_sid_update_notice'
|
||||
private static readonly PREFERENCES_NAME = 'sid_update_notice'
|
||||
private static readonly KEY_PREFIX = 'sid_update_'
|
||||
|
||||
onReceiveGroupData(data: string): void {
|
||||
try {
|
||||
// 主站只用timeStamp表达“SID权限有新版本”,客户端不解析具体权限内容。
|
||||
const flag = this.parsePushFlag(data)
|
||||
if (!flag || flag.length === 0) {
|
||||
return
|
||||
}
|
||||
const userId = HXUserService.getInstance().getUserId()
|
||||
// 本地仅记录已成功处理的timeStamp,避免同一条分组推送重复触发主动请求。
|
||||
const savedFlag = PreferenceService.getStringValueSync(
|
||||
SidGroupHandler.PREFERENCES_NAME,
|
||||
SidGroupHandler.KEY_PREFIX + userId,
|
||||
''
|
||||
)
|
||||
if (flag === savedFlag) {
|
||||
return
|
||||
}
|
||||
// 主动请求成功且SID发生变化后,SidPermissionRefreshClient会写入本次flag。
|
||||
WxPaySidPermissionMarker.clear()
|
||||
SidPermissionRefreshClient.requestPermission(true, flag)
|
||||
} catch (error) {
|
||||
HXLog.e(TAG, `handle sid group push failed: ${JSON.stringify(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private parsePushFlag(data: string | object): string {
|
||||
if (!data) {
|
||||
return ''
|
||||
}
|
||||
let payload: SidGroupData | null = null
|
||||
if (typeof data === 'string') {
|
||||
try {
|
||||
payload = JSON.parse(data) as SidGroupData
|
||||
} catch (error) {
|
||||
HXLog.e(TAG, `parse sid group push failed: ${JSON.stringify(error)}`)
|
||||
return ''
|
||||
}
|
||||
} else {
|
||||
// 分组分发链路可能已把payload解析为对象,这里兼容两种输入形态。
|
||||
payload = data as SidGroupData
|
||||
}
|
||||
const flag = payload?.timeStamp
|
||||
if (flag === undefined || flag === null) {
|
||||
return ''
|
||||
}
|
||||
return flag.toString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { AppUtil } from "@pura/harmony-utils";
|
||||
import { HXLog } from "biz_common";
|
||||
import { CodeTableManager } from "lib_codetablemanager";
|
||||
import { BaseStorageHandler } from "./BaseStorageHandler";
|
||||
|
||||
export class StockGroupHandler extends BaseStorageHandler {
|
||||
getTag(): string {
|
||||
return 'StockGroupHandler'
|
||||
}
|
||||
|
||||
getTimeKey(): string {
|
||||
return 'time'
|
||||
}
|
||||
|
||||
update(): void {
|
||||
HXLog.d(CodeTableManager.TAG, `${this.getTag()} update`)
|
||||
CodeTableManager.get().requestStock(AppUtil.getContext(), this.getTag())
|
||||
}
|
||||
|
||||
async getDbVersion(): Promise<number> {
|
||||
const version = await CodeTableManager.get().getStockInfoDbVersion()
|
||||
HXLog.d(CodeTableManager.TAG, `${this.getTag()} getDbVersion ${version}`)
|
||||
return version
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { AppUtil } from "@pura/harmony-utils";
|
||||
import { HXLog } from "biz_common";
|
||||
import { CodeTableManager } from "lib_codetablemanager";
|
||||
import { BaseStorageHandler } from "./BaseStorageHandler";
|
||||
|
||||
export class VarietyGroupHandler extends BaseStorageHandler {
|
||||
getTag(): string {
|
||||
return 'VarietyGroupHandler'
|
||||
}
|
||||
|
||||
getTimeKey(): string {
|
||||
return 'timestamp'
|
||||
}
|
||||
|
||||
update(): void {
|
||||
HXLog.d(CodeTableManager.TAG, `${this.getTag()} update`)
|
||||
CodeTableManager.get().requestVariety(AppUtil.getContext(), this.getTag())
|
||||
}
|
||||
|
||||
async getDbVersion(): Promise<number> {
|
||||
const version = await CodeTableManager.get().getVarietyInfoDbVersion()
|
||||
HXLog.d(CodeTableManager.TAG, `${this.getTag()} getDbVersion ${version}`)
|
||||
return version
|
||||
}
|
||||
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 486 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 197 B |
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 316 B |
|
After Width: | Height: | Size: 1011 B |
|
After Width: | Height: | Size: 948 B |
|
After Width: | Height: | Size: 930 B |
|
After Width: | Height: | Size: 950 B |
|
After Width: | Height: | Size: 930 B |
|
After Width: | Height: | Size: 919 B |
|
After Width: | Height: | Size: 698 B |
|
After Width: | Height: | Size: 693 B |
|
After Width: | Height: | Size: 435 B |
|
After Width: | Height: | Size: 384 B |
|
After Width: | Height: | Size: 439 B |
|
After Width: | Height: | Size: 389 B |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,103 @@
|
||||
|
||||
import {
|
||||
AdsManager,
|
||||
AdRuntimeConfig
|
||||
} from '@b2c-f/fuhm-ads'
|
||||
import { AppConfigManager, HXLog, ScreenUtil } from 'biz_common'
|
||||
import { CookieManager, HXUserService, KaihuFlagManager, NewUserManager, ServerTimeProvider,
|
||||
UserSourceManager } from 'biz_hxservice'
|
||||
import { AppUtil } from '@pura/harmony-utils'
|
||||
|
||||
const TAG = 'AdsInitializer'
|
||||
|
||||
/**
|
||||
* FuHmAds 运行时配置
|
||||
* 提供广告组件所需的上下文、用户信息和业务能力
|
||||
*/
|
||||
class AdsRuntimeConfig implements AdRuntimeConfig {
|
||||
|
||||
|
||||
getContext(): Object | null {
|
||||
return AppUtil.getContext()
|
||||
}
|
||||
|
||||
getScreenWidth(): string {
|
||||
return ScreenUtil.get().getScreenWidth().toString()
|
||||
}
|
||||
|
||||
getScreenHeight(): string {
|
||||
return ScreenUtil.get().getScreenHeight().toString()
|
||||
}
|
||||
|
||||
getInnerVersion(): string {
|
||||
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionMiddleNumberStr()??''
|
||||
}
|
||||
|
||||
getComeFrom(): string {
|
||||
return KaihuFlagManager.getInstance().getKaihuFlag()
|
||||
}
|
||||
|
||||
getTrade(): string {
|
||||
let userSource = UserSourceManager.getInstance().getUserSource()
|
||||
if (UserSourceManager.NO_SOURCE != userSource) {
|
||||
return userSource
|
||||
}else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
getDeviceCode(): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
getUserId(): string {
|
||||
const userId = HXUserService.getInstance().getUserId()
|
||||
return userId || ''
|
||||
}
|
||||
|
||||
getUserIdFromPassport(): string {
|
||||
const userId = HXUserService.getInstance().getUserIdFromLocal()
|
||||
return userId || ''
|
||||
}
|
||||
|
||||
isNewUser(): boolean {
|
||||
return NewUserManager.getInstance().isNewUser()
|
||||
}
|
||||
|
||||
getChannelId(): string {
|
||||
return NewUserManager.getChannelId()
|
||||
}
|
||||
|
||||
getCurrentTimeMillis(): number {
|
||||
return ServerTimeProvider.getInstance().getServerTimeMillis()
|
||||
}
|
||||
|
||||
|
||||
getUserAgent(): string {
|
||||
return AppConfigManager.getInstance().getUA()
|
||||
}
|
||||
|
||||
getCookie(): string {
|
||||
return CookieManager.getInstance().getCookieSync()
|
||||
}
|
||||
|
||||
|
||||
uploadElk(message: string): void {
|
||||
HXLog.w(TAG, `Ads elk ${message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 广告模块初始化器
|
||||
*/
|
||||
export class AdsInitializer {
|
||||
/**
|
||||
* 初始化广告模块
|
||||
*/
|
||||
static initAds(): void {
|
||||
HXLog.i(TAG, 'initAds start')
|
||||
const adsConfig = new AdsRuntimeConfig()
|
||||
AdsManager.initAdContext(adsConfig)
|
||||
HXLog.i(TAG, 'initAds done')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { HXLog } from "biz_common"
|
||||
import { CloudStoreLogManager, ICloudStoreLogger } from "lib_cloud_store"
|
||||
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from '@b2c-m/ohos_elk_logger_upload'
|
||||
import { HXUserService } from 'biz_hxservice'
|
||||
|
||||
const CLOUD_STORE = "cloud_store"
|
||||
|
||||
/**
|
||||
* 云存储错误消息构建类
|
||||
*/
|
||||
class CloudStoreErrMessageBuilder extends ElkUploadMessageBuilder {
|
||||
constructor(bizLevel: string) {
|
||||
super(Date.now(), CLOUD_STORE, bizLevel)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传云存储错误信息到 ELK
|
||||
* @param errorMessage 错误信息
|
||||
*/
|
||||
function uploadCloudStoreError(error: string) {
|
||||
const errorMessageBuilder = new CloudStoreErrMessageBuilder('e')
|
||||
errorMessageBuilder.userid(HXUserService.getInstance().getUserId())
|
||||
errorMessageBuilder.messageKey('CloudStore')
|
||||
errorMessageBuilder.messageValue(error)
|
||||
const message = new ElkUploadMessage(errorMessageBuilder)
|
||||
ElkService.getInstance().pushBusinessLog(message, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* HXLog 适配器
|
||||
* 将 lib_cloud_store 的日志桥接到 HXLog
|
||||
*/
|
||||
class HXLogAdapter implements ICloudStoreLogger {
|
||||
debug(module: string, message: string): void {
|
||||
HXLog.d(module, message)
|
||||
}
|
||||
|
||||
info(module: string, message: string): void {
|
||||
HXLog.i(module, message)
|
||||
}
|
||||
|
||||
warn(module: string, message: string): void {
|
||||
HXLog.w(module, message)
|
||||
}
|
||||
|
||||
error(module: string, message: string): void {
|
||||
HXLog.e(module, message)
|
||||
uploadCloudStoreError(`[${module}] ${message}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 云存储日志初始化器
|
||||
*/
|
||||
export class CloudStoreLogInitializor {
|
||||
/**
|
||||
* 初始化云存储日志
|
||||
*/
|
||||
static initCloudStoreLog() {
|
||||
const hxLogAdapter = new HXLogAdapter()
|
||||
CloudStoreLogManager.getInstance().setLogger(hxLogAdapter)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import {AppInfoConfig, IAppConfig, IDeviceConfig } from 'biz_common';
|
||||
export class AppConfigProvider implements IAppConfig {
|
||||
|
||||
private static readonly BEGIN_INDEX = 5
|
||||
getBundleName(): string {
|
||||
return AppInfoConfig.getBundleName();
|
||||
}
|
||||
|
||||
|
||||
getInnerVersionFull(): string {
|
||||
return AppInfoConfig.APPLET_INNER_VERSION_FULL;
|
||||
}
|
||||
|
||||
getInnerVersionMiddle(): string {
|
||||
return AppInfoConfig.APPLET_INNER_VERSION.substring(AppConfigProvider.BEGIN_INDEX -1)
|
||||
}
|
||||
|
||||
getInnerVersionMiddleNumberStr(): string {
|
||||
return AppInfoConfig.APPLET_INNER_VERSION.substring(AppConfigProvider.BEGIN_INDEX).replaceAll(".","")
|
||||
}
|
||||
|
||||
getOuterVersion(): string {
|
||||
|
||||
return AppInfoConfig.getVersionName();
|
||||
}
|
||||
|
||||
getVersionCode(): string {
|
||||
return AppInfoConfig.getVersionCode();
|
||||
}
|
||||
|
||||
getAppPlatform(): string {
|
||||
return AppInfoConfig.APP_PLATFORM;
|
||||
}
|
||||
|
||||
getAppSign(): string {
|
||||
return AppInfoConfig.APP_FOR;
|
||||
}
|
||||
|
||||
getProgramId(): string {
|
||||
return AppInfoConfig.APP_PROGID;
|
||||
}
|
||||
|
||||
getChannelId(): string {
|
||||
return AppInfoConfig.APP_CHANNEL_ID;
|
||||
}
|
||||
|
||||
getSourceId(): string {
|
||||
return AppInfoConfig.APP_CHANNEL_ID;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DeviceConfigProvider implements IDeviceConfig {
|
||||
getDeviceWidth(): number {
|
||||
return 720;
|
||||
}
|
||||
|
||||
getDeviceHeight(): number {
|
||||
return 1440;
|
||||
}
|
||||
|
||||
getDensity(): number {
|
||||
return 3.0;
|
||||
}
|
||||
|
||||
getDeviceType(): string {
|
||||
return "android";
|
||||
}
|
||||
|
||||
getIMEI(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getIMSI(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
getMACAddress(): string {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { AppConfigManager } from 'biz_common'
|
||||
import { AppConfigProvider, DeviceConfigProvider } from './ConfigImpl';
|
||||
|
||||
/**
|
||||
* 宿主App初始化配置信息
|
||||
*/
|
||||
export class ConfigInitializer {
|
||||
|
||||
static init() {
|
||||
AppConfigManager.getInstance().registerConfigProvider(new AppConfigProvider());
|
||||
AppConfigManager.getInstance().registerDeviceConfigProvider(new DeviceConfigProvider());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { AppConfigManager, AppInfoConfig, } from "biz_common"
|
||||
import { DebugToolUtil } from "biz_debug"
|
||||
import { CookieManager, HXUserService, ServerTimeProvider } from "biz_hxservice"
|
||||
import { GrayTestManager, GrayTestInjectInterface } from "lib_graytestmanager"
|
||||
|
||||
export class GrayTestInitializor {
|
||||
/**
|
||||
* 初始化灰度配置
|
||||
*/
|
||||
static initGrayTest() {
|
||||
const injectParams = new GrayTestInjectInterface()
|
||||
injectParams.getUserId = () => {
|
||||
return HXUserService.getInstance().getUserIdFromLocal()
|
||||
}
|
||||
injectParams.getCookie = () => {
|
||||
return CookieManager.getInstance().getCookieSync()
|
||||
}
|
||||
injectParams.getUserAgent = () => {
|
||||
return AppConfigManager.getInstance().getUA()
|
||||
}
|
||||
injectParams.getAppVersion = () => {
|
||||
return AppInfoConfig.getCurrentInnerVersionNumber().toString()
|
||||
}
|
||||
injectParams.getServerTimestamp = () => {
|
||||
return ServerTimeProvider.getInstance().getServerTimeMillis()
|
||||
}
|
||||
injectParams.getDebugMode = () => {
|
||||
return DebugToolUtil.isDebugMode()
|
||||
}
|
||||
|
||||
GrayTestManager.getInstance().config(injectParams)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import { ContractChooseManager, ContractDirectionType, ContractInfo, ContractMainType,
|
||||
FavoriteGroup } from "@b2c-f/contractchoose";
|
||||
import { ContractBeanFactory, ContractCode, MarketId, PositionCenter } from "@b2c-f/futures_ohos_trade_sdk";
|
||||
import { Context } from "@kit.AbilityKit";
|
||||
import { Markets } from "biz_common";
|
||||
import { InactiveContractManager } from "biz_market";
|
||||
import { AccountAnalysisTradeLoginListener,
|
||||
AccountProvider,
|
||||
FuturesAccountAnalysisHttpProvider, FuturesBillParseModuleHttpProvider,
|
||||
TradeUtils, TradeReportDateManager } from "biz_trade";
|
||||
import { TradeDailyDataManager, TradeDailyManager, TradeHighFrequentManager, TradeLoginOnlineManager,
|
||||
TradeMsgUniqueIdManager, TradeSettlementBillManager } from "biz_trade";
|
||||
import { TradeType } from "biz_trade/src/main/ets/trade/manager/TradeCustomTabManager";
|
||||
import { CodeTableManager, StockRegularUtils } from "lib_codetablemanager";
|
||||
import { loadCustomGroups } from "service_watchlist";
|
||||
import { TradeDailyConvertManager } from "../NotificationExtension/TradeDaily/TradeDailyConvertManager";
|
||||
import { TradeDailyPushDataManager } from "../NotificationExtension/TradeDaily/TradeDailyPushDataManager";
|
||||
|
||||
export class TradeInitializor {
|
||||
/**
|
||||
* 初始化高频交易管理器
|
||||
*/
|
||||
static async init(context: Context) {
|
||||
TradeInitializor.initContractChoose()
|
||||
|
||||
TradeHighFrequentManager.getInstance().initManager(context)
|
||||
TradeLoginOnlineManager.getInstance().initManager(context)
|
||||
AccountAnalysisTradeLoginListener.getInstance().start()
|
||||
TradeSettlementBillManager.getInstance()
|
||||
FuturesAccountAnalysisHttpProvider.initializeHttpProvider()
|
||||
FuturesBillParseModuleHttpProvider.initializeHttpProvider()
|
||||
TradeSettlementBillManager.getInstance()
|
||||
|
||||
// 交易日志
|
||||
TradeDailyManager.getInstance()
|
||||
await TradeMsgUniqueIdManager.getInstance().initDB(context)
|
||||
await TradeDailyDataManager.getInstance().init(context)
|
||||
await TradeDailyPushDataManager.getInstance().initDB(context)
|
||||
await TradeDailyConvertManager.getInstance().registerForeground(context)
|
||||
TradeReportDateManager.getInstance().updateTradingDate()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化合约选择方法注入
|
||||
*/
|
||||
static initContractChoose() {
|
||||
ContractChooseManager.getInstance().setIsOptionContractProvider((showCode, marketId) => {
|
||||
return StockRegularUtils.isOption(showCode)
|
||||
})
|
||||
|
||||
ContractChooseManager.getInstance().setIsMainLinkContractProvider(async (showCode, marketId) => {
|
||||
let contractBean = await ContractBeanFactory.getInstance()
|
||||
.buildContract(new ContractCode(showCode), new MarketId(marketId))
|
||||
return TradeUtils.isCanTradeMainLink(contractBean)
|
||||
})
|
||||
|
||||
ContractChooseManager.getInstance().setIsMainContractProvider(async (showCode, marketId) => {
|
||||
return TradeUtils.isZhuliCode(showCode)
|
||||
})
|
||||
|
||||
ContractChooseManager.getInstance().setPositionProvider(async () => {
|
||||
const accountIdentify = AccountProvider.getInstance().getCurrentAccount()?.accountIdentify()
|
||||
if (!accountIdentify) {
|
||||
return []
|
||||
}
|
||||
const positions = PositionCenter.getInstance().getAllPositionSummary(accountIdentify)?.getPositionInfos()
|
||||
return positions?.flatMap((position) => {
|
||||
const stockCode = CodeTableManager.get().getStockInfoByShowCodeSync(position.contractCode.code, position.marketId.code)?.stockCode ?? ''
|
||||
return new ContractInfo(
|
||||
position.contractCode.code,
|
||||
position.marketId.code,
|
||||
stockCode,
|
||||
position.contractName,
|
||||
position.isOptionPosition,
|
||||
ContractMainType.NONE,
|
||||
position.isLongPosition() ? ContractDirectionType.BUY : ContractDirectionType.SELL
|
||||
)
|
||||
}) ?? []
|
||||
})
|
||||
|
||||
ContractChooseManager.getInstance().setFavoritesProvider(async () => {
|
||||
const favorites = await TradeUtils.getSelfContractForTrade(TradeType.NONE)
|
||||
return favorites.flatMap((favorite) => {
|
||||
if (favorite.contractCode && favorite.marketId) {
|
||||
const stockCode = CodeTableManager.get().getStockInfoByShowCodeSync(favorite.contractCode.code, favorite.marketId.code)?.stockCode ?? ''
|
||||
return new ContractInfo(
|
||||
favorite.contractCode.code,
|
||||
favorite.marketId.code,
|
||||
stockCode,
|
||||
favorite.contractName ?? '',
|
||||
favorite.isOptionContract(),
|
||||
TradeUtils.isZhuliCode(stockCode) ? ContractMainType.MAIN : ContractMainType.NOT_MAIN,
|
||||
ContractDirectionType.NONE,
|
||||
InactiveContractManager.get().isInactiveContract(stockCode) && !Markets.isArbitrageMarket(favorite.marketId.code)
|
||||
)
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
})
|
||||
|
||||
ContractChooseManager.getInstance().setFavoritesGroupProvider(async () => {
|
||||
const groups = loadCustomGroups(true)
|
||||
const favoriteGroup = new FavoriteGroup()
|
||||
for (const group of groups) {
|
||||
const contracts = group.stocks
|
||||
if (contracts.length > 0) {
|
||||
favoriteGroup.addGroup(group.name, contracts.flatMap((contract) => {
|
||||
const stockInfo = CodeTableManager.get().getStockInfoSync(contract.code, contract.market)
|
||||
const showCode = stockInfo?.showCode
|
||||
const stockName = stockInfo?.stockName
|
||||
return new ContractInfo(
|
||||
showCode,
|
||||
contract.market,
|
||||
contract.code,
|
||||
contract.name ?? stockName,
|
||||
StockRegularUtils.isOption(showCode),
|
||||
TradeUtils.isZhuliCode(contract.code) ? ContractMainType.MAIN : ContractMainType.NOT_MAIN,
|
||||
ContractDirectionType.NONE,
|
||||
InactiveContractManager.get().isInactiveContract(contract.code) && !Markets.isArbitrageMarket(contract.market)
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
return favoriteGroup
|
||||
})
|
||||
|
||||
ContractChooseManager.getInstance().setMainContractProvider(async (showCode, marketId) => {
|
||||
const stockCode = CodeTableManager.get().getStockInfoByShowCodeSync(showCode, marketId)?.stockCode ?? ''
|
||||
const contract = await TradeUtils.changeMainLinkToMainContract2(stockCode, marketId)
|
||||
if (contract) {
|
||||
return new ContractInfo(
|
||||
contract.contractCode?.code,
|
||||
contract.marketId?.code,
|
||||
stockCode,
|
||||
contract.contractName ?? '',
|
||||
StockRegularUtils.isOption(showCode),
|
||||
ContractMainType.NONE,
|
||||
ContractDirectionType.NONE
|
||||
)
|
||||
}
|
||||
return new ContractInfo()
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
import { HXEventMonitorPlugin } from "@kernel/app_monitor_event";
|
||||
import {
|
||||
HXMonitor,
|
||||
HXMonitorLogger,
|
||||
IElkService,
|
||||
IMonitorAppConfig,
|
||||
IMonitorOnStartCallback
|
||||
} from "@kernel/app_monitor_lib";
|
||||
import { HXPerformancePlugin } from "@kernel/app_monitor_performance";
|
||||
import { HXWebMonitorPlugin } from '@kernel/app_monitor_web';
|
||||
import { HXUserService } from "biz_hxservice";
|
||||
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from "service_monitor";
|
||||
import {
|
||||
AuthLogMonitor,
|
||||
DefaultLogParamConfig,
|
||||
HXTcpMonitorPlugin,
|
||||
LogParamsConfig,
|
||||
TcpMonitorConfig
|
||||
} from "@kernel/app_monitor_tcp";
|
||||
import { HXAuthLogMonitor } from "biz_auth";
|
||||
import { HXHttpMonitorPlugin } from "@kernel/app_monitor_http";
|
||||
import { HXCrashMonitorPlugin, ICrashMonitorConfig } from "@kernel/app_monitor_crash";
|
||||
import { router } from "@kit.ArkUI";
|
||||
import { GIT_COMMIT } from "biz_common/src/main/ets/utils/debug/GitBuildConfig";
|
||||
import { AppConfigManager, getConnectionManager } from "biz_common";
|
||||
import { HxCBASAgentProvider } from "@b2c-spi/hx_cbas_api";
|
||||
import {
|
||||
AppContextLike,
|
||||
type LeakWatcherConfig,
|
||||
initializeWatcherFromPreference
|
||||
} from '@kernel/ohos_leak_watcher';
|
||||
import BuildProfile from "BuildProfile";
|
||||
|
||||
const APP_ID = "mobile-archive-am-qihuo-harmony"
|
||||
const DEBUG_VERSION = "FUHM037.08.999.10.32"
|
||||
|
||||
export class APMHelper {
|
||||
static clientIp: string = "";
|
||||
|
||||
/**
|
||||
* 判断是否为测试版本(debug 或 forTest)
|
||||
*/
|
||||
static isDebugOrTestBuild(): boolean {
|
||||
const modeName = BuildProfile.BUILD_MODE_NAME.toString()
|
||||
const targetName = BuildProfile.TARGET_NAME.toString()
|
||||
return modeName === 'debug' || targetName === 'forTest'
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化APM
|
||||
* @param context
|
||||
*/
|
||||
static init(context: Context) {
|
||||
//目前先只在debug包下打印日志
|
||||
const modeName: string = BuildProfile.BUILD_MODE_NAME.toString()
|
||||
HXMonitorLogger._logOpen = modeName === 'debug'
|
||||
|
||||
HXMonitor.builder(context)
|
||||
.setAppId(APP_ID)
|
||||
.hostParameter(new HXMonitorAppConfig())
|
||||
.setElkService(new APMElkService())
|
||||
.plugin(new HXPerformancePlugin())
|
||||
.plugin(new HXEventMonitorPlugin())
|
||||
.plugin(new HXTcpMonitorPlugin(new HXTcpMonitorConfig()))
|
||||
.plugin(new HXWebMonitorPlugin())
|
||||
.plugin(new HXHttpMonitorPlugin())
|
||||
.plugin(new HXCrashMonitorPlugin(new HXCrashMonitorConfig()))
|
||||
.build()
|
||||
.startAllPlugins(new APMMonitorOnStartCallback())
|
||||
APMHelper.initLeakWatcher(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化内存泄漏监控
|
||||
* @param context 应用上下文
|
||||
*/
|
||||
private static initLeakWatcher(context: Context) {
|
||||
let appContext: AppContextLike = {
|
||||
filesDir: context.getApplicationContext().filesDir || ''
|
||||
}
|
||||
let config: LeakWatcherConfig = {
|
||||
enabled: true,
|
||||
appContext: appContext,
|
||||
baseDir: `${appContext.filesDir}/jsleak_watcher`,
|
||||
leakObjectTypes: [],
|
||||
resourceWatcher: {
|
||||
enabled: true,
|
||||
row: 1,
|
||||
size: 10240,
|
||||
timeOut: 1
|
||||
},
|
||||
retention: { maxArtifacts: 50, maxDays: 7, maxTotalBytes: 536870912 },
|
||||
upload: {
|
||||
enabled: false,
|
||||
url: '',
|
||||
method: 'POST',
|
||||
headers: []
|
||||
}
|
||||
};
|
||||
initializeWatcherFromPreference(config);
|
||||
}
|
||||
}
|
||||
|
||||
class APMMonitorOnStartCallback implements IMonitorOnStartCallback {
|
||||
onStarted(code: number): void {
|
||||
//暂时不做操作
|
||||
HXMonitorLogger.d("APMHelper", `APM started:${code}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* apm异常日志回调
|
||||
*/
|
||||
class APMElkService implements IElkService {
|
||||
pushBusinessLog(logType: string, bt: string, message: string | null): void;
|
||||
pushBusinessLog(logType: string, bt: string, message: string | null, extraData: object | null): void;
|
||||
pushBusinessLog(logType: string, bt: string, message: string | null, extraData?: object | null): void {
|
||||
try {
|
||||
const builder = new APMElkBuilder(logType)
|
||||
builder.userid(HXUserService.getInstance().getUserId())
|
||||
builder.messageKey(bt)
|
||||
builder.messageValue(message || "")
|
||||
if (extraData) {
|
||||
builder.messageExt(JSON.stringify(extraData))
|
||||
}
|
||||
const elkMessage = new ElkUploadMessage(builder)
|
||||
ElkService.getInstance().pushBusinessLog(elkMessage)
|
||||
} catch (e) {
|
||||
console.error("APMElkService upload message fail", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class APMElkBuilder extends ElkUploadMessageBuilder {
|
||||
constructor(biz_level: string) {
|
||||
super(Date.now(), "apm", biz_level);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 崩溃采集配置
|
||||
*/
|
||||
class HXCrashMonitorConfig implements ICrashMonitorConfig {
|
||||
provideUserName(): string {
|
||||
return HXUserService.getInstance().getUserName();
|
||||
}
|
||||
|
||||
provideBuildType(): string {
|
||||
return BuildProfile.BUILD_MODE_NAME.toString()
|
||||
}
|
||||
|
||||
provideAppName(): string {
|
||||
return getContext().applicationInfo.name
|
||||
}
|
||||
|
||||
async provideCBASInfo(): Promise<string> {
|
||||
const result = await HxCBASAgentProvider.getInstance().getRecentCBASData()
|
||||
return result || "none"
|
||||
}
|
||||
|
||||
provideCurrentPageId(): string {
|
||||
return router.getState().name
|
||||
}
|
||||
|
||||
provideGitVersion(): string {
|
||||
return GIT_COMMIT
|
||||
}
|
||||
|
||||
providePackageName(): string {
|
||||
return getContext().applicationInfo.name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* APM配置
|
||||
*/
|
||||
class HXMonitorAppConfig extends IMonitorAppConfig {
|
||||
getUserId(): string | null {
|
||||
return HXUserService.getInstance().getUserIdFromLocal()
|
||||
}
|
||||
|
||||
getDeviceCode(): string | null {
|
||||
//deviceInfo.ODID 目前华为获取设备id的api已经不可靠,现改为userid。
|
||||
return this.getUserId()
|
||||
}
|
||||
|
||||
getVersion(): string {
|
||||
if (APMHelper.isDebugOrTestBuild()) {
|
||||
return DEBUG_VERSION
|
||||
}
|
||||
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionFull()??DEBUG_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* tcp监控配置
|
||||
*/
|
||||
class HXTcpMonitorConfig extends TcpMonitorConfig {
|
||||
getLogParamConfig(): LogParamsConfig {
|
||||
return new HXTcpLogParamsConfig()
|
||||
}
|
||||
|
||||
getAuthLogMonitor(): AuthLogMonitor {
|
||||
return HXAuthLogMonitor.getInstance()
|
||||
}
|
||||
}
|
||||
|
||||
class HXTcpLogParamsConfig extends DefaultLogParamConfig {
|
||||
getAppId(): string | null {
|
||||
return APP_ID
|
||||
}
|
||||
|
||||
getAppVersion(): string | null {
|
||||
if (APMHelper.isDebugOrTestBuild()) {
|
||||
return DEBUG_VERSION
|
||||
}
|
||||
return AppConfigManager.getInstance().getConfigProvider()?.getInnerVersionFull()??DEBUG_VERSION;
|
||||
}
|
||||
|
||||
getPlatform(): string | null {
|
||||
return AppConfigManager.getInstance().getConfigProvider()?.getAppPlatform()??APP_ID
|
||||
}
|
||||
|
||||
getServerIp(): string | null {
|
||||
return getConnectionManager()?.getConnectedIp() || ""
|
||||
}
|
||||
|
||||
getUid(): string | null {
|
||||
return HXUserService.getInstance().getUserIdFromLocal()
|
||||
}
|
||||
|
||||
getCip(): string | null {
|
||||
return APMHelper.clientIp
|
||||
}
|
||||
|
||||
isUserVIP(): boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import hiAppEvent from '@ohos.hiviewdfx.hiAppEvent';
|
||||
import { HXLog } from 'biz_common/src/main/ets/logger/HXLog';
|
||||
import { HXUserService } from 'biz_hxservice';
|
||||
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from 'service_monitor';
|
||||
|
||||
const TAG: string = 'eventSubscription';
|
||||
|
||||
export function eventSubscription() {
|
||||
hiAppEvent.addWatcher({
|
||||
name: "futures",
|
||||
appEventFilters: [//APP_CRASH, APP_FREEZE事件将在下次启动时获取
|
||||
{
|
||||
domain: hiAppEvent.domain.OS,
|
||||
names: [hiAppEvent.event.APP_CRASH, hiAppEvent.event.APP_FREEZE, hiAppEvent.event.MAIN_THREAD_JANK,
|
||||
hiAppEvent.event.CPU_USAGE_HIGH, hiAppEvent.event.RESOURCE_OVERLIMIT, hiAppEvent.event.ADDRESS_SANITIZER]
|
||||
},
|
||||
],
|
||||
onReceive: async (domain: string, appEventGroups: Array<hiAppEvent.AppEventGroup>) => {
|
||||
HXLog.i(TAG, `HiAppEvent onReceive: domain=${domain}`)
|
||||
setTimeout(() => {
|
||||
scheduleHiAppEventUpload(appEventGroups)
|
||||
}, 1000)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function scheduleHiAppEventUpload(appEventGroups: Array<hiAppEvent.AppEventGroup>) {
|
||||
try {
|
||||
let uploadMessage: Array<HiAppEventUploadInfo> = getFaultMessage(appEventGroups)
|
||||
if (uploadMessage.length > 0) {
|
||||
uploadMessage.forEach((message: HiAppEventUploadInfo, index: number) => {
|
||||
uploadAppEventElkMessage("e", message, true)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
const error : string = ' Error while scheduleHiAppEventUpload, the appEventGroups is ' + JSON.stringify(appEventGroups)
|
||||
HXLog.e(TAG, error)
|
||||
uploadAppEventElkMessage('e',{message_key: 'scheduleHiAppEventUpload', message_value:error},true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getFaultMessage(appEventGroups: Array<hiAppEvent.AppEventGroup>) {
|
||||
const messageArr: Array<HiAppEventUploadInfo> = []
|
||||
if (appEventGroups && appEventGroups.length > 0) {
|
||||
// 遍历事件组
|
||||
appEventGroups.forEach((eventGroup: hiAppEvent.AppEventGroup) => {
|
||||
// 遍历事件对象集合
|
||||
eventGroup.appEventInfos.forEach((eventInfo: hiAppEvent.AppEventInfo) => {
|
||||
let message: string = '';
|
||||
message += `HiAppEvent eventInfo.domain=${eventInfo.domain}\n` // 事件领域
|
||||
+ `HiAppEvent eventInfo.name=${eventInfo.name}\n` // 事件名称
|
||||
+ `HiAppEvent eventInfo.eventType=${eventInfo.eventType}\n` // 事件名称
|
||||
+ `HiAppEvent eventInfo.params.time=${eventInfo.params['time']}\n` // 事件发生的时间
|
||||
+ `HiAppEvent eventInfo.params.crash_type=${eventInfo.params['crash_type']}\n`
|
||||
+ `HiAppEvent eventInfo.params.foreground=${eventInfo.params['foreground']}\n`
|
||||
+ `HiAppEvent eventInfo.params.bundle_version=${eventInfo.params['bundle_version']}\n`
|
||||
+ `HiAppEvent eventInfo.params.bundle_name=${eventInfo.params['bundle_name']}\n`
|
||||
+ `HiAppEvent eventInfo.params.exception=${JSON.stringify(eventInfo.params['exception'])}\n`
|
||||
+ `HiAppEvent eventInfo.params.hilog.size=${eventInfo.params['hilog'].length}\n`;
|
||||
const uploadInfo: HiAppEventUploadInfo = {
|
||||
message_key: eventInfo.domain,
|
||||
message_value: eventInfo.name,
|
||||
message_ext: message
|
||||
}
|
||||
messageArr.push(uploadInfo)
|
||||
})
|
||||
})
|
||||
}
|
||||
return messageArr
|
||||
}
|
||||
|
||||
function uploadAppEventElkMessage(biz_level: string, info: HiAppEventUploadInfo, isInstant: boolean) {
|
||||
const messageBuilder = new HiAppEventElkBuilder(biz_level)
|
||||
messageBuilder.userid(HXUserService.getInstance().getUserId())
|
||||
if (info) {
|
||||
messageBuilder.messageKey(info?.message_key ? info.message_key : '')
|
||||
messageBuilder.messageValue(info?.message_value ? info.message_value : '')
|
||||
messageBuilder.messageExt(info?.message_ext ? info.message_ext : '')
|
||||
}
|
||||
const message = new ElkUploadMessage(messageBuilder)
|
||||
ElkService.getInstance().pushBusinessLog(message, isInstant)
|
||||
}
|
||||
|
||||
|
||||
interface HiAppEventUploadInfo {
|
||||
message_key?: string,
|
||||
message_value?: string,
|
||||
message_ext?: string
|
||||
}
|
||||
|
||||
class HiAppEventElkBuilder extends ElkUploadMessageBuilder {
|
||||
constructor(biz_level: string = 'e') {
|
||||
super(Date.now(), "HiAppEvent", biz_level);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { HXUserService } from 'biz_hxservice';
|
||||
import { ElkService, ElkUploadMessage, ElkUploadMessageBuilder } from 'service_monitor';
|
||||
|
||||
export class EntryMonitor {
|
||||
private static instance: EntryMonitor;
|
||||
public static getInstance() : EntryMonitor{
|
||||
if(!EntryMonitor.instance) {
|
||||
EntryMonitor.instance = new EntryMonitor()
|
||||
}
|
||||
return EntryMonitor.instance
|
||||
}
|
||||
public UploadElkMessage(biz_level:string,info: EntryUploadInfo, isInstant:boolean) {
|
||||
const messageBuilder = new EntryElkBuilder(biz_level)
|
||||
messageBuilder.userid(HXUserService.getInstance().getUserId())
|
||||
if(info) {
|
||||
messageBuilder.messageKey(info?.message_key ? info.message_key : '' )
|
||||
messageBuilder.messageValue(info?.message_value ? info.message_value : '')
|
||||
messageBuilder.messageExt(info?.message_ext ? info.message_ext : '')
|
||||
}
|
||||
const message = new ElkUploadMessage(messageBuilder)
|
||||
|
||||
ElkService.getInstance().pushBusinessLog(message,isInstant)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function buildEntryUploadBasicInfo(message:string,caller:string) {
|
||||
let info : EntryUploadInfo = {
|
||||
message_key: caller,
|
||||
message_value: `${caller} upload message ${message} `,
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
export interface EntryUploadInfo {
|
||||
message_key?:string,
|
||||
message_value?:string,
|
||||
message_ext?:string
|
||||
}
|
||||
|
||||
|
||||
class EntryElkBuilder extends ElkUploadMessageBuilder {
|
||||
constructor(biz_level: string) {
|
||||
super(Date.now(), "entry", biz_level);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { PassportManager } from "biz_auth/src/main/ets/auth/PassportManager";
|
||||
import { CookieManager, HXUserService } from "biz_hxservice";
|
||||
import { SecurityMonitorProxy } from "lib_apm_security";
|
||||
import { bundleManager } from "@kit.AbilityKit";
|
||||
import { AppConfigManager, AppInfoConfig } from "biz_common";
|
||||
import { StringBuilder } from "hxutil";
|
||||
import { StrUtil } from "@pura/harmony-utils";
|
||||
|
||||
/**
|
||||
* author:zhangshan2
|
||||
* created on 2025/1/3
|
||||
* desc:
|
||||
*/
|
||||
export class SecurityMonitorInit{
|
||||
private static instance:SecurityMonitorInit
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
static getInstance(){
|
||||
if (!SecurityMonitorInit.instance) {
|
||||
SecurityMonitorInit.instance = new SecurityMonitorInit()
|
||||
}
|
||||
return SecurityMonitorInit.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* 崩溃保护初始化
|
||||
*/
|
||||
init(){
|
||||
if (!AppInfoConfig.Official){
|
||||
return;
|
||||
}
|
||||
|
||||
SecurityMonitorProxy.getInstance().commonInfInfo=()=>{
|
||||
let userId = HXUserService.getInstance().getUserIdFromLocal();
|
||||
let info = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT);
|
||||
let sb = new StringBuilder();
|
||||
|
||||
sb.append("userId:").append(userId).append("\r\n")
|
||||
.append("bundleName:").append(info.name).append("\r\n")
|
||||
.append("versionName:").append(info.versionName).append("\r\n")
|
||||
.append("innerVersion:").append(AppInfoConfig.APPLET_INNER_VERSION)
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
SecurityMonitorProxy.getInstance().agent=()=>{
|
||||
return AppConfigManager.getInstance().getUA();
|
||||
}
|
||||
SecurityMonitorProxy.getInstance().innerVersion = ()=>{
|
||||
return AppInfoConfig.APPLET_INNER_VERSION_FULL;
|
||||
}
|
||||
|
||||
SecurityMonitorProxy.getInstance().isUserTemp = ()=>{return HXUserService.getInstance().isTemporaryUser()} ;
|
||||
SecurityMonitorProxy.getInstance().cooke = ()=>{
|
||||
let cookie = CookieManager.getInstance().getCookieSync();
|
||||
if (!cookie) {
|
||||
return "";
|
||||
}
|
||||
return cookie;
|
||||
};
|
||||
|
||||
//初始化启动崩溃保护
|
||||
SecurityMonitorProxy.getInstance().initSecurityMonitor();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { connection } from "@kit.NetworkKit";
|
||||
import { Want } from "@kit.AbilityKit";
|
||||
import { AppUtil } from "@pura/harmony-utils";
|
||||
import { HXLog } from "biz_common";
|
||||
|
||||
export class HXNetwork {
|
||||
private static instance: HXNetwork;
|
||||
private TAG: string = "HXNetwork";
|
||||
|
||||
public static getInstance(): HXNetwork {
|
||||
if (!HXNetwork.instance) {
|
||||
HXNetwork.instance = new HXNetwork()
|
||||
}
|
||||
return HXNetwork.instance
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测网络是否可用
|
||||
*/
|
||||
detectNet(): boolean {
|
||||
try {
|
||||
let handle = connection.getDefaultNetSync();
|
||||
if (!handle || handle.netId === 0) {
|
||||
// 是否获取到有效网络连接
|
||||
HXLog.e(this.TAG, "Can\'t get available connection!");
|
||||
return false;
|
||||
}
|
||||
|
||||
let capabilities = connection.getNetCapabilitiesSync(handle);
|
||||
let cap = capabilities.networkCap || [];
|
||||
|
||||
// 网络是否可用
|
||||
if (!cap.includes(connection.NetCap.NET_CAPABILITY_VALIDATED) && !cap.includes(connection.NetCap.NET_CAPABILITY_INTERNET)) {
|
||||
HXLog.e(this.TAG, "The network is not available.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
HXLog.e(this.TAG, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 网络异常(不可用)弹窗
|
||||
*/
|
||||
@Preview
|
||||
@Component
|
||||
export struct NetworkAnomalyDialog {
|
||||
exitApp?: () => void;
|
||||
setup?: () => void;
|
||||
retry?: () => void;
|
||||
exitBtnText: string | ResourceStr = "退出";
|
||||
setupBtnText: string | ResourceStr = "设置网络";
|
||||
retryBtnText: string | ResourceStr = "重试";
|
||||
//弹窗标题
|
||||
title?: string | ResourceStr = "提示";
|
||||
//弹窗正文
|
||||
content?: string | ResourceStr = "联网可能异常,请检查您的网络!";
|
||||
borderW: number = 0.7;
|
||||
borderR: number = 10;
|
||||
marginTop: number = 20;
|
||||
|
||||
build() {
|
||||
Column() {
|
||||
Text(this.title).fontSize(18).margin({ top: this.marginTop }).fontWeight(FontWeight.Bold)
|
||||
.fontColor($r('app.color.elements_text_primary_02'))
|
||||
Text(this.content).fontSize(14).margin({ top: this.marginTop })
|
||||
.fontColor($r('app.color.elements_text_primary_02'))
|
||||
|
||||
Row() {
|
||||
Button(this.exitBtnText, { type: ButtonType.Normal, stateEffect: true })
|
||||
.onClick(() => {
|
||||
if (this.exitApp) {
|
||||
this.exitApp();
|
||||
}
|
||||
})
|
||||
.fontColor($r('app.color.elements_text_secondary'))
|
||||
.backgroundColor($r('app.color.surface_layer1_foreground'))
|
||||
.layoutWeight(1)
|
||||
.border({
|
||||
width: { top: this.borderW, right: this.borderW },
|
||||
color: $r('app.color.elements_others_divider_primary'),
|
||||
radius: { bottomLeft: this.borderR }
|
||||
})
|
||||
Button(this.setupBtnText, { type: ButtonType.Normal, stateEffect: true })
|
||||
.onClick(() => {
|
||||
if (this.setup) {
|
||||
this.setup();
|
||||
}
|
||||
})
|
||||
.fontColor($r('app.color.elements_text_primary_01'))
|
||||
.backgroundColor($r('app.color.surface_layer1_foreground'))
|
||||
.layoutWeight(1)
|
||||
.border({
|
||||
width: { top: this.borderW },
|
||||
color: $r('app.color.elements_others_divider_primary'),
|
||||
})
|
||||
Button(this.retryBtnText, { type: ButtonType.Normal, stateEffect: true })
|
||||
.onClick(() => {
|
||||
if (this.retry) {
|
||||
this.retry();
|
||||
}
|
||||
})
|
||||
.fontColor($r('app.color.elements_text_primary_01'))
|
||||
.backgroundColor($r('app.color.surface_layer1_foreground'))
|
||||
.layoutWeight(1)
|
||||
.border({
|
||||
width: { top: this.borderW, left: this.borderW },
|
||||
color: $r('app.color.elements_others_divider_primary'),
|
||||
radius: { bottomRight: this.borderR }
|
||||
})
|
||||
}
|
||||
.margin({ top: this.marginTop })
|
||||
}
|
||||
.backgroundColor($r('app.color.surface_layer1_foreground'))
|
||||
.width('75%')
|
||||
.borderRadius(this.borderR)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { AppConfigManager, HXLog, PreferenceService } from 'biz_common'
|
||||
import { common, Want } from '@kit.AbilityKit'
|
||||
import router from '@ohos.router'
|
||||
import { window } from '@kit.ArkUI'
|
||||
import { deviceInfo } from '@kit.BasicServicesKit'
|
||||
import { NetworkAnomalyDialog } from './NetworkAnomalyDialog'
|
||||
import { HXNetwork } from './HXNetwork'
|
||||
import { ConnectionSeq } from '@kernel/lib_communication'
|
||||
import { AuthProviderService } from 'biz_auth'
|
||||
import { AdvertisingMonitorService } from 'biz_hxservice'
|
||||
|
||||
const SettingWant: Want = {
|
||||
bundleName: 'com.huawei.hmos.settings',
|
||||
abilityName: 'com.huawei.hmos.settings.MainAbility',
|
||||
}
|
||||
|
||||
/**
|
||||
* 网络异常页面
|
||||
* 用于承接空白Logo页面,网络异常弹窗
|
||||
*/
|
||||
@Entry
|
||||
@Component
|
||||
struct NetworkAnomalyPage {
|
||||
private TAG: string = "HXNetwork";
|
||||
private systemBarColor = "#00000000";
|
||||
private systemBarContentColor = "#FFFFFFFF";
|
||||
|
||||
async setSystemBar() {
|
||||
let context = (this.getUIContext().getHostContext() as common.UIAbilityContext);
|
||||
// 获取当前应用窗口
|
||||
let windowClass: window.Window = await window.getLastWindow(context);
|
||||
// 前提:设置窗口为全屏窗口
|
||||
if ((deviceInfo.deviceType !== '2in1')) {
|
||||
windowClass.setWindowLayoutFullScreen(true);
|
||||
} else {
|
||||
windowClass.setWindowLayoutFullScreen(false);
|
||||
}
|
||||
|
||||
await windowClass.setWindowSystemBarProperties({
|
||||
// 颜色属性为ARGB,将蒙层设置为0%使其透明
|
||||
navigationBarColor: this.systemBarColor,
|
||||
statusBarColor: this.systemBarColor,
|
||||
navigationBarContentColor: this.systemBarContentColor,
|
||||
statusBarContentColor: this.systemBarContentColor
|
||||
})
|
||||
}
|
||||
|
||||
aboutToAppear(): void {
|
||||
this.setSystemBar()
|
||||
}
|
||||
|
||||
onExit() {
|
||||
HXLog.i(this.TAG, "EXIT APP!!");
|
||||
// 用户退出应用
|
||||
(this.getUIContext().getHostContext() as common.UIAbilityContext).terminateSelf(() => {
|
||||
});
|
||||
}
|
||||
|
||||
onJumpToSetup() {
|
||||
HXLog.i(this.TAG, "JumpSetup!!");
|
||||
let context = (this.getUIContext().getHostContext() as common.UIAbilityContext);
|
||||
context.startAbility(SettingWant, (err, data) => {
|
||||
HXLog.i(this.TAG, `startAbility : err : ${JSON.stringify(err) ?? ''}`);
|
||||
HXLog.i(this.TAG, `startAbility : data : ${JSON.stringify(data) ?? ''}`);
|
||||
})
|
||||
}
|
||||
|
||||
onRetry() {
|
||||
const netAvailable = HXNetwork.getInstance().detectNet();
|
||||
if (netAvailable) {
|
||||
// 用户是否同意隐私政策
|
||||
const userAgree = PreferenceService.getBooleanValueSync('Privacy', 'isUserAgree', false);
|
||||
const jumpUrl = userAgree ? 'pages/Index' : 'pages/LauncherPage';
|
||||
router.replaceUrl({ url: jumpUrl }, router.RouterMode.Single, (err) => {
|
||||
if (err) {
|
||||
console.error("Failed to start Index Page: " + err)
|
||||
return;
|
||||
}
|
||||
if (userAgree) {
|
||||
AdvertisingMonitorService.getInstance().init(this.getUIContext().getHostContext() as common.UIAbilityContext)
|
||||
AppConfigManager.getInstance().fetchIMEI().then((isSuccess: boolean) => {
|
||||
if (isSuccess) {
|
||||
AuthProviderService.getInstance().sendAuthRequest(ConnectionSeq.HQ);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
build() {
|
||||
RelativeContainer() {
|
||||
NetworkAnomalyDialog({
|
||||
exitApp: this.onExit,
|
||||
setup: this.onJumpToSetup,
|
||||
retry: this.onRetry,
|
||||
title: '提示',
|
||||
content: '联网可能异常,请检查您的网络!'
|
||||
})
|
||||
.zIndex(4)
|
||||
.align(Alignment.Center)
|
||||
.alignRules(
|
||||
{
|
||||
'top': { 'anchor': '__container__', 'align': VerticalAlign.Top },
|
||||
'bottom': { 'anchor': '__container__', 'align': VerticalAlign.Bottom },
|
||||
'left': { 'anchor': '__container__', 'align': HorizontalAlign.Start },
|
||||
'right': { 'anchor': '__container__', 'align': HorizontalAlign.End }
|
||||
})
|
||||
.backgroundColor($r('app.color.dialog_bg_99666666'))
|
||||
Image($r('app.media.launch_logo'))
|
||||
.objectFit(ImageFit.Auto)
|
||||
.width('80%')
|
||||
.height(205)
|
||||
.alignRules({
|
||||
'bottom': {
|
||||
'anchor': "__container__",
|
||||
'align': VerticalAlign.Bottom
|
||||
},
|
||||
'middle': { 'anchor': '__container__', 'align': HorizontalAlign.Center },
|
||||
})
|
||||
}.width('100%').height('100%')
|
||||
}
|
||||
}
|
||||