diff --git a/common/Index.ets b/common/Index.ets index 8475c22..0855031 100644 --- a/common/Index.ets +++ b/common/Index.ets @@ -1,3 +1,8 @@ -// common HAR —— 横切基础设施(导出入口 SSOT) -// 各子模块实现完成后在此统一 export。占位常量确保 HAR 可编译。 -export const common_MODULE_VERSION: string = '1.0.0'; +// common HAR —— 横切基础设施(框架 §10) +// Logger/BridgeTracer/EventBus/DIContainer/ErrorCenter。Result/Errors 在 contracts 层。 + +export { Logger, LOG_DOMAIN } from './src/main/ets/log/Logger'; +export { BridgeTracer } from './src/main/ets/log/BridgeTracer'; +export { EventBus, EventPayload } from './src/main/ets/event/EventBus'; +export { DIContainer, Factory } from './src/main/ets/di/DIContainer'; +export { ErrorCenter, ErrorStrategy, ErrorSink } from './src/main/ets/error/ErrorCenter'; diff --git a/common/src/main/ets/di/DIContainer.ets b/common/src/main/ets/di/DIContainer.ets new file mode 100644 index 0000000..1bd4748 --- /dev/null +++ b/common/src/main/ets/di/DIContainer.ets @@ -0,0 +1,49 @@ +/** + * 轻量依赖注入容器(框架 §10)。组装根 AppModule 集中装配,避免在 struct/类内 + * 散落 new 业务对象。支持单例与工厂两种注册。 + */ +export type Factory = () => T; + +export class DIContainer { + private readonly singletons: Map = new Map(); + private readonly factories: Map> = new Map>(); + + /** 注册单例实例(立即持有)。 */ + registerInstance(key: string, instance: T): void { + this.singletons.set(key, instance); + } + + /** 注册工厂(懒创建;lazySingleton=true 时首次 resolve 后缓存为单例)。 */ + registerFactory(key: string, factory: Factory, lazySingleton: boolean = true): void { + if (lazySingleton) { + this.factories.set(key, () => { + const existing: Object | undefined = this.singletons.get(key); + if (existing !== undefined) { + return existing; + } + const created: Object = factory(); + this.singletons.set(key, created); + return created; + }); + } else { + this.factories.set(key, factory as Factory); + } + } + + /** 解析依赖;未注册抛错。 */ + resolve(key: string): T { + const instance: Object | undefined = this.singletons.get(key); + if (instance !== undefined) { + return instance as T; + } + const factory: Factory | undefined = this.factories.get(key); + if (factory !== undefined) { + return factory() as T; + } + throw new Error(`DIContainer: no provider registered for key "${key}"`); + } + + has(key: string): boolean { + return this.singletons.has(key) || this.factories.has(key); + } +} diff --git a/common/src/main/ets/error/ErrorCenter.ets b/common/src/main/ets/error/ErrorCenter.ets new file mode 100644 index 0000000..66f1799 --- /dev/null +++ b/common/src/main/ets/error/ErrorCenter.ets @@ -0,0 +1,51 @@ +/** + * 错误中心(框架 §10)。收敛全局异常,按策略处置:弹窗 / 静默 / 上报 / 阻断。 + * + * common 层不直接依赖 UI,弹窗等需 UI 的处置由上层(entry)注入 sink 实现; + * 本中心负责统一记录日志 + 分类 + 把错误分发给已注入的 sink。 + */ +import { AppError, ErrorKind } from 'contracts'; +import { Logger } from '../log/Logger'; + +/** 处置策略。 */ +export enum ErrorStrategy { + /** 轻提示用户(Toast/弹窗,由上层 sink 实现) */ + Notify = 'notify', + /** 仅记录日志,不打扰用户 */ + Silent = 'silent', + /** 上报到 APM/Crash(由上层 sink 实现) */ + Report = 'report', + /** 阻断流程(如启动期 showmessage 公告) */ + Block = 'block', +} + +/** 上层注入的处置接收器。 */ +export type ErrorSink = (error: AppError, strategy: ErrorStrategy) => void; + +export class ErrorCenter { + private static readonly log: Logger = Logger.tag('ErrorCenter'); + private static sink: ErrorSink | undefined = undefined; + + /** 由 entry 在启动时注入 UI 相关处置(弹窗/上报)。 */ + static setSink(sink: ErrorSink): void { + ErrorCenter.sink = sink; + } + + /** 捕获并处置一个错误。默认策略:Notify。 */ + static capture(error: AppError, strategy: ErrorStrategy = ErrorStrategy.Notify): void { + const line: string = `[${error.kind}]${error.code !== undefined ? '(' + error.code + ')' : ''} ${error.message}`; + if (strategy === ErrorStrategy.Silent) { + ErrorCenter.log.w(line); + } else { + ErrorCenter.log.e(line); + } + if (ErrorCenter.sink !== undefined) { + ErrorCenter.sink(error, strategy); + } + } + + /** 便捷:从原始异常收敛为 AppError 并处置。 */ + static captureException(kind: ErrorKind, message: string, cause?: Object, strategy?: ErrorStrategy): void { + ErrorCenter.capture({ kind, message, cause }, strategy ?? ErrorStrategy.Notify); + } +} diff --git a/common/src/main/ets/event/EventBus.ets b/common/src/main/ets/event/EventBus.ets new file mode 100644 index 0000000..046adf9 --- /dev/null +++ b/common/src/main/ets/event/EventBus.ets @@ -0,0 +1,38 @@ +/** + * 事件总线(框架 §10)。封装 @ohos.events.emitter,用于前后台、网络变化、 + * 容器间回传、TaskPool 进度等跨线程/跨模块解耦。 + * + * 关键用途(框架 §5.5 / §9.1):能力在非 UI 线程产生的结果,可经 EventBus + * emit,由 UI 线程订阅后再 callHandler 下发;TaskPool 进度只 emit Sendable 数据。 + */ +import { emitter } from '@kit.BasicServicesKit'; + +/** 事件载荷:键值对,值为 Object(需可跨线程时只放 Sendable/基本类型)。 */ +export type EventPayload = Record; + +export class EventBus { + /** 持续订阅。 */ + static on(eventId: string, callback: (payload?: EventPayload) => void): void { + emitter.on(eventId, (ev: emitter.EventData) => { + callback(ev.data); + }); + } + + /** 单次订阅,触发后自动取消。 */ + static once(eventId: string, callback: (payload?: EventPayload) => void): void { + emitter.once(eventId, (ev: emitter.EventData) => { + callback(ev.data); + }); + } + + /** 发布事件。 */ + static emit(eventId: string, payload?: EventPayload): void { + const data: emitter.EventData = { data: payload }; + emitter.emit(eventId, data); + } + + /** 取消该事件的全部订阅。 */ + static off(eventId: string): void { + emitter.off(eventId); + } +} diff --git a/common/src/main/ets/log/BridgeTracer.ets b/common/src/main/ets/log/BridgeTracer.ets new file mode 100644 index 0000000..128594e --- /dev/null +++ b/common/src/main/ets/log/BridgeTracer.ets @@ -0,0 +1,23 @@ +/** + * 桥消息全链路追踪(框架 §10 / §13)。 + * + * 每条桥消息分配一个 traceId,各阶段(拦截 yy:// → 解析 → 分发 handler → + * 回执 → 出站下发)以同一 traceId 打点,可在日志里还原"H5 调用 → handler → 回传"。 + */ +import { Logger } from './Logger'; + +export class BridgeTracer { + private static seq: number = 0; + private static readonly log: Logger = Logger.tag('BridgeTrace'); + + /** 生成新的 traceId(进程内自增唯一)。 */ + static next(): string { + BridgeTracer.seq += 1; + return `BR-${BridgeTracer.seq}`; + } + + /** 记录某条消息在某阶段的事件。 */ + static step(traceId: string, stage: string, detail: string): void { + BridgeTracer.log.d(`[${traceId}] ${stage} | ${detail}`); + } +} diff --git a/common/src/main/ets/log/Logger.ets b/common/src/main/ets/log/Logger.ets new file mode 100644 index 0000000..27ebf88 --- /dev/null +++ b/common/src/main/ets/log/Logger.ets @@ -0,0 +1,43 @@ +/** + * 分级日志封装(框架 §10 可观测)。基于 HiLog。 + * + * 用法:`const log = Logger.tag('BridgeController'); log.i('xxx');` + * 日志结果默认以 %{public} 明文输出(release 包对敏感字段务必先脱敏,见 ErrorCenter/调用方)。 + */ +import { hilog } from '@kit.PerformanceAnalysisKit'; + +/** TSGame 业务域标识(0x0000~0xFFFF)。 */ +export const LOG_DOMAIN: number = 0x9527; + +export class Logger { + private readonly tag: string; + + constructor(tag: string) { + // HiLog tag 最多 31 字节,超出截断 + this.tag = tag.length > 31 ? tag.substring(0, 31) : tag; + } + + static tag(tag: string): Logger { + return new Logger(tag); + } + + d(message: string): void { + hilog.debug(LOG_DOMAIN, this.tag, '%{public}s', message); + } + + i(message: string): void { + hilog.info(LOG_DOMAIN, this.tag, '%{public}s', message); + } + + w(message: string): void { + hilog.warn(LOG_DOMAIN, this.tag, '%{public}s', message); + } + + e(message: string): void { + hilog.error(LOG_DOMAIN, this.tag, '%{public}s', message); + } + + f(message: string): void { + hilog.fatal(LOG_DOMAIN, this.tag, '%{public}s', message); + } +}