Compare commits

...

2 Commits

Author SHA1 Message Date
clz 56ad003e04 chore: save current workspace changes 2026-08-05 14:11:51 +08:00
clz f947f3d2f8 docs: add startup monitoring integration plan 2026-08-05 14:06:55 +08:00
330 changed files with 27627 additions and 3 deletions
+3 -1
View File
@@ -1 +1,3 @@
{}
{
"vimMode": true
}
+3 -1
View File
@@ -1 +1,3 @@
{}
{
"theme": "moonstone"
}
+24 -1
View File
@@ -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"
]
+42
View File
@@ -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.
+12
View File
@@ -0,0 +1,12 @@
/node_modules
/oh_modules
/local.properties
/.idea
**/build
/.hvigor
.cxx
/.clangd
/.clang-format
/.clang-tidy
**/.test
/.appanalyzer
+81
View File
@@ -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`.
+56
View File
@@ -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. 断网、重复回调或上传失败不影响启动流程,网络恢复后按既有上传机制重试。
Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 KiB

Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test
+31
View File
@@ -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.
+112
View File
@@ -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.
+40
View File
@@ -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
+7
View File
@@ -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. */
}
+18
View File
@@ -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
+25
View File
@@ -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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

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%')
}
}

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