📖 前言

在前三篇教程中,我们完成了 HMRouter 的类型封装工具类封装编译插件配置测试页面搭建拦截器封装。本篇我们将深入探讨 HMRouter Service 服务路由的封装和使用。

HMRouter Service 是 HMRouter 框架提供的服务路由功能,它允许你通过 @HMService@HMServiceProvider 注解将类或方法标记为可调用的服务,实现跨模块、跨页面的服务调用。       


🎯 Service 封装目标

  • 类型统一:封装 Service 相关类型,保持项目代码风格一致
  • 简化使用:提供清晰的 Service 定义和使用示例
  • 功能验证:通过 Service 调用路由跳转,验证 Service 与路由的结合使用
  • 完整测试:创建完整的测试页面,覆盖各种 Service 使用场景

📁 Service 封装结构

entry/src/main/ets/hmRouter/
├── service/                              # Service 相关封装目录
│   ├── Index.ets                          # 统一导出入口
│   ├── HMRouterUtilServiceParam.ets      # Service 参数类型别名
│   ├── HMRouterUtilServiceConstants.ets  # Service 常量定义
│   └── HMRouterUtilIClassService.ets     # Service 接口定义
├── serviceInstance/                      # Service 实例目录
│   ├── Index.ets                          # 统一导出入口
│   └── HMRouterUtilTestService.ets       # Service 示例实现
└── model/                                # 类型定义目录
    ├── HMRouterUtilServiceResp.ets       # Service 响应类型别名
    └── Index.ets                          # 统一导出(包含 ServiceResp)

🚀 Service 封装步骤

步骤 1:封装 Service 类型定义

1.1 封装 Service 参数类型

文件: service/HMRouterUtilServiceParam.ets

import { HMServiceParam } from '@hadss/hmrouter/src/main/ets/annotation/HMService';

/**
 * HMRouter 服务参数类型别名
 * 用于项目内部统一使用,定义服务配置参数
 *
 * 主要属性:
 * - serviceName: 服务名称(必填)
 * - singleton: 是否单例模式(可选)
 */
export type HMRouterUtilServiceParam = HMServiceParam;

说明:

  • serviceName:服务的唯一标识,用于调用时定位服务
  • singleton:是否单例模式,默认为 false。单例模式下,服务实例会被缓存复用
1.2 封装 Service 响应类型

文件: model/HMRouterUtilServiceResp.ets

import { HMServiceResp } from '@hadss/hmrouter';

/**
 * HMRouter 服务响应类型别名
 * 用于项目内部统一使用,定义服务执行响应
 */
export type HMRouterUtilServiceResp = HMServiceResp;

HMServiceResp 结构:

class HMServiceResp {
  code: number;      // 响应码,0 表示成功
  msg?: string;      // 响应消息
  data?: Object;     // 响应数据
  detail?: Error;    // 错误详情(如果有)
}

步骤 2:定义 Service 常量

文件: service/HMRouterUtilServiceConstants.ets

/**
 * 服务名称常量
 */
export class HMRouterUtilServiceConstants {
  // 类级别服务名称
  static readonly CLASS_SERVICE: string = 'HMRouterUtilClassService';
  
  // 方法级别服务名称
  static readonly METHOD_CONSOLE: string = 'HMRouterUtilMethodConsole';
  static readonly METHOD_WITH_RETURN: string = 'HMRouterUtilMethodWithReturn';
  static readonly METHOD_WITH_PARAMS: string = 'HMRouterUtilMethodWithParams';
  static readonly METHOD_ASYNC: string = 'HMRouterUtilMethodAsync';
  
  // 路由跳转服务名称
  static readonly METHOD_ROUTER_PUSH: string = 'HMRouterUtilMethodRouterPush';
  static readonly METHOD_ROUTER_PUSH_WITH_PARAM: string = 'HMRouterUtilMethodRouterPushWithParam';
  static readonly METHOD_ROUTER_REPLACE: string = 'HMRouterUtilMethodRouterReplace';
  static readonly METHOD_ROUTER_POP: string = 'HMRouterUtilMethodRouterPop';
}

设计思路:

  • 使用常量类统一管理服务名称,避免硬编码
  • 服务名称建议使用有意义的命名,便于识别和维护
  • 分类管理:类级别服务、方法级别服务、路由跳转服务

步骤 3:定义 Service 接口

文件: service/HMRouterUtilIClassService.ets

/**
 * 类级别服务接口
 * 用于 @HMServiceProvider 装饰的类
 */
export interface HMRouterUtilIClassService {
  getData(): string;
  
  processData(data: string): string;
  
  asyncGetData(): Promise<string>;
}

说明:

  • 接口定义了类级别服务需要实现的方法
  • 支持同步和异步方法
  • 使用接口可以保证类型安全,便于后续扩展

步骤 4:实现类级别服务(@HMServiceProvider)

文件: serviceInstance/HMRouterUtilTestService.ets

4.1 类级别服务示例
import { HMServiceProvider } from '@hadss/hmrouter';
import { HMRouterUtilServiceConstants } from '../service/HMRouterUtilServiceConstants';
import { HMRouterUtilIClassService } from '../service/HMRouterUtilIClassService';
import { HMRouterUtil } from '../util/HMRouterUtil';

/**
 * 示例1:类级别服务(使用 @HMServiceProvider)
 *
 * 整个类作为服务提供者,通过 HMRouterUtil.getService() 获取实例
 */
@HMServiceProvider({
  serviceName: HMRouterUtilServiceConstants.CLASS_SERVICE,
  singleton: true
})
export class HMRouterUtilClassService implements HMRouterUtilIClassService {
  /**
   * 获取数据
   */
  getData(): string {
    return 'Data from ClassService';
  }

  /**
   * 处理数据
   */
  processData(data: string): string {
    return `Processed: ${data}`;
  }

  /**
   * 异步获取数据
   */
  async asyncGetData(): Promise<string> {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve('Async Data from ClassService');
      }, 100);
    });
  }
}

关键点:

  • 使用 @HMServiceProvider 装饰器标记类为服务提供者
  • serviceName 必须与常量中定义的一致
  • singleton: true 表示单例模式,服务实例会被缓存
  • 类必须实现对应的接口,保证类型安全
4.2 获取类级别服务实例
/**
 * 获取类级别服务实例的辅助函数
 */
export function getClassService(): HMRouterUtilIClassService | undefined {
  return HMRouterUtil.getService<HMRouterUtilIClassService>(
    HMRouterUtilServiceConstants.CLASS_SERVICE
  );
}

使用方式:

// 方式1:使用辅助函数
const service = getClassService();
if (service) {
  const data = service.getData();
  const processed = service.processData('test');
  const asyncData = await service.asyncGetData();
}

// 方式2:直接使用 HMRouterUtil.getService
const service = HMRouterUtil.getService<HMRouterUtilIClassService>(
  HMRouterUtilServiceConstants.CLASS_SERVICE
);

步骤 5:实现方法级别服务(@HMService)

5.1 方法级别服务示例
import { HMService } from '@hadss/hmrouter';
import { HMRouterUtilServiceConstants } from '../service/HMRouterUtilServiceConstants';
import { HMRouterUtil } from '../util/HMRouterUtil';

/**
 * 示例2:方法级别服务(使用 @HMService)
 *
 * 类中的方法作为独立服务,通过 HMRouterUtil.request() 直接调用
 */
export class HMRouterUtilMethodService {
  /**
   * 无返回值服务方法
   */
  @HMService({ serviceName: HMRouterUtilServiceConstants.METHOD_CONSOLE })
  testConsole(): void {
    console.log('Calling service: testConsole');
  }

  /**
   * 有返回值服务方法
   */
  @HMService({ serviceName: HMRouterUtilServiceConstants.METHOD_WITH_RETURN })
  testFunWithReturn(): string {
    return 'Calling service: testFunWithReturn';
  }

  /**
   * 带参数服务方法
   */
  @HMService({
    serviceName: HMRouterUtilServiceConstants.METHOD_WITH_PARAMS,
    singleton: true
  })
  testFunWithParams(str: string, num: number, bool: boolean, obj: TestDataObj): string {
    console.log('参数1:', str);
    console.log('参数2:', num);
    console.log('参数3:', bool);
    console.log('参数4:', JSON.stringify(obj));
    return obj.paramA || obj.paramB.toString();
  }

  /**
   * 异步服务方法
   */
  @HMService({
    serviceName: HMRouterUtilServiceConstants.METHOD_ASYNC,
    singleton: true
  })
  async testAsyncFunction(): Promise<string> {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve('Calling async service: testAsyncFunction');
      }, 100);
    });
  }
}

关键点:

  • 使用 @HMService 装饰器标记方法为服务
  • 每个方法都有独立的 serviceName
  • 支持同步和异步方法
  • 支持带参数的方法调用
5.2 调用方法级别服务
// 无返回值调用
HMRouterUtil.request(HMRouterUtilServiceConstants.METHOD_CONSOLE);

// 有返回值调用
const resp = HMRouterUtil.request(HMRouterUtilServiceConstants.METHOD_WITH_RETURN);
if (resp.code === 0) {
  console.log('返回值:', resp.data);
}

// 带参数调用
const obj: TestDataObj = { paramA: 'test', paramB: 100 };
const resp = HMRouterUtil.request(
  HMRouterUtilServiceConstants.METHOD_WITH_PARAMS,
  'str',
  123,
  true,
  obj
);

// 异步调用
const resp = HMRouterUtil.request(HMRouterUtilServiceConstants.METHOD_ASYNC);

// 使用回调调用
HMRouterUtil.requestWithCallback(
  HMRouterUtilServiceConstants.METHOD_WITH_RETURN,
  (resp: HMRouterUtilServiceResp) => {
    if (resp.code === 0) {
      console.log('回调返回值:', resp.data);
    }
  }
);

步骤 6:通过 Service 调用路由跳转

Service 不仅可以执行业务逻辑,还可以调用路由跳转,实现更灵活的路由控制。

6.1 路由跳转服务方法
export class HMRouterUtilMethodService {
  /**
   * 路由跳转服务方法(Push)
   */
  @HMService({
    serviceName: HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH,
    singleton: true
  })
  routerPush(pageUrl: string): string {
    HMRouterUtil.push(pageUrl);
    return `路由跳转到: ${pageUrl}`;
  }

  /**
   * 路由跳转服务方法(Push with Param)
   */
  @HMService({
    serviceName: HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH_WITH_PARAM,
    singleton: true
  })
  routerPushWithParam(pageUrl: string, param: Object): string {
    HMRouterUtil.push(pageUrl, param);
    return `路由跳转到: ${pageUrl}, 参数: ${JSON.stringify(param)}`;
  }

  /**
   * 路由替换服务方法(Replace)
   */
  @HMService({
    serviceName: HMRouterUtilServiceConstants.METHOD_ROUTER_REPLACE,
    singleton: true
  })
  routerReplace(pageUrl: string, param?: Object): string {
    if (param) {
      HMRouterUtil.replace(pageUrl, param);
      return `路由替换到: ${pageUrl}, 参数: ${JSON.stringify(param)}`;
    } else {
      HMRouterUtil.replace(pageUrl);
      return `路由替换到: ${pageUrl}`;
    }
  }

  /**
   * 路由返回服务方法(Pop)
   */
  @HMService({
    serviceName: HMRouterUtilServiceConstants.METHOD_ROUTER_POP,
    singleton: true
  })
  routerPop(): string {
    HMRouterUtil.pop();
    return '执行路由返回操作';
  }
}
6.2 使用 Service 调用路由跳转
// 通过 Service 调用 Push 跳转
const resp = HMRouterUtil.request(
  HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH,
  'pages/DetailPage'
);

// 通过 Service 调用 Push(带参数)
const resp = HMRouterUtil.request(
  HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH_WITH_PARAM,
  'pages/DetailPage',
  { id: 123, name: 'test' }
);

// 通过 Service 调用 Replace
const resp = HMRouterUtil.request(
  HMRouterUtilServiceConstants.METHOD_ROUTER_REPLACE,
  'pages/ReplaceResultPage'
);

// 通过 Service 调用 Pop
const resp = HMRouterUtil.request(
  HMRouterUtilServiceConstants.METHOD_ROUTER_POP
);

应用场景:

  • 统一路由管理:将路由跳转逻辑封装在 Service 中,便于统一管理和维护
  • 权限控制:在 Service 中添加权限检查,只有通过权限验证才能跳转
  • 日志记录:在 Service 中记录路由跳转日志,便于问题排查
  • 参数验证:在 Service 中对路由参数进行验证和转换

🧪 Service 测试页面

为了验证 Service 功能的正确性,我们创建了完整的测试页面。

测试页面功能

文件: pages/ServiceTestPage.ets

测试页面包含 13 个测试场景

类级别服务测试(@HMServiceProvider)
  1. 测试1:获取类级别服务实例 - 使用辅助函数获取服务实例
  2. 测试2:直接使用 HMRouterUtil.getService - 直接调用 API 获取服务实例
  3. 测试3:调用异步方法 - 测试类级别服务的异步方法调用
方法级别服务测试(@HMService)
  1. 测试4:无返回值服务 - 测试无返回值的服务方法
  2. 测试5:有返回值服务 - 测试有返回值的服务方法
  3. 测试6:带参数服务 - 测试带多个参数的服务方法
  4. 测试7:异步服务 - 测试异步服务方法
  5. 测试8:使用 requestWithCallback - 测试回调方式调用服务
  6. 测试9:使用辅助函数 - 测试辅助函数调用服务
路由跳转服务测试(通过 Service 调用路由)
  1. 测试10:Service 调用 Push 跳转 - 通过 Service 执行路由跳转
  2. 测试11:Service 调用 Push(带参数) - 通过 Service 执行带参数的路由跳转
  3. 测试12:Service 调用 Replace 替换 - 通过 Service 执行路由替换
  4. 测试13:Service 调用 Pop 返回 - 通过 Service 执行路由返回

测试页面代码示例

@HMRouter({ pageUrl: 'pages/ServiceTestPage' })
@Component
export struct ServiceTestPage {
  @State logText: string = 'Service 测试日志:\n';

  /**
   * 测试1:获取类级别服务实例
   */
  testGetClassService() {
    this.addLog('--- 测试1:获取类级别服务实例 ---');
    const service = getClassService();
    if (service) {
      this.addLog('✓ 成功获取服务实例');
      const data = service.getData();
      this.addLog(`✓ 调用 getData(): ${data}`);
    } else {
      this.addLog('✗ 获取服务实例失败');
    }
  }

  /**
   * 测试10:通过 Service 调用路由跳转(Push)
   */
  testRouterPushByService() {
    this.addLog('--- 测试10:通过 Service 调用路由跳转(Push) ---');
    const resp = HMRouterUtil.request(
      HMRouterUtilServiceConstants.METHOD_ROUTER_PUSH,
      'pages/DetailPage'
    );
    if (resp.code === 0) {
      this.addLog('✓ 路由跳转服务调用成功');
      this.addLog(`返回值: ${resp.data}`);
      this.addLog('页面应该已跳转到 DetailPage');
    } else {
      this.addLog(`✗ 路由跳转服务调用失败: ${resp.msg}`);
    }
  }

  // ... 其他测试方法
}

💡 使用示例

示例 1:定义数据服务

// 定义服务接口
export interface IDataService {
  fetchData(page: number, size: number): Promise<any[]>;
  saveData(data: any): Promise<boolean>;
}

// 实现服务提供者
@HMServiceProvider({
  serviceName: 'DataService',
  singleton: true
})
export class DataServiceImpl implements IDataService {
  async fetchData(page: number, size: number): Promise<any[]> {
    // 实现数据获取逻辑
    return [];
  }

  async saveData(data: any): Promise<boolean> {
    // 实现数据保存逻辑
    return true;
  }
}

// 使用服务
const dataService = HMRouterUtil.getService<IDataService>('DataService');
if (dataService) {
  const data = await dataService.fetchData(1, 20);
  console.log('获取的数据:', data);
}

示例 2:定义工具服务

export class UtilService {
  @HMService({ serviceName: 'formatDate' })
  formatDate(date: Date): string {
    return date.toLocaleDateString();
  }

  @HMService({ serviceName: 'calculateSum' })
  calculateSum(...numbers: number[]): number {
    return numbers.reduce((sum, num) => sum + num, 0);
  }
}

// 使用服务
const resp = HMRouterUtil.request('formatDate', new Date());
const sum = HMRouterUtil.request('calculateSum', 1, 2, 3, 4, 5);

示例 3:通过 Service 实现路由权限控制

export class RouterService {
  @HMService({ serviceName: 'navigateWithAuth' })
  navigateWithAuth(pageUrl: string, userRole: string): string {
    // 权限检查
    if (userRole === 'admin') {
      HMRouterUtil.push(pageUrl);
      return '跳转成功';
    } else {
      return '权限不足,无法跳转';
    }
  }
}

// 使用服务
const resp = HMRouterUtil.request('navigateWithAuth', 'pages/AdminPage', 'admin');

⚠️ 注意事项

1. 装饰器必须使用原始名称

HMRouter 编译插件只识别原始的装饰器名称,不能封装:

// ✅ 正确:使用原始装饰器
@HMServiceProvider({ serviceName: 'MyService' })
export class MyService { }

@HMService({ serviceName: 'myMethod' })
myMethod() { }

// ❌ 错误:封装后的装饰器不会被识别
@HMRouterServiceProviderDecorator({ serviceName: 'MyService' })
export class MyService { }

2. 服务名称必须唯一

每个服务的 serviceName 必须在整个应用中唯一,否则会导致服务冲突。

3. 单例模式的使用

// 单例模式:服务实例会被缓存,多次调用返回同一实例
@HMServiceProvider({
  serviceName: 'MyService',
  singleton: true  // 单例模式
})

// 非单例模式:每次调用都会创建新实例
@HMServiceProvider({
  serviceName: 'MyService',
  singleton: false  // 或省略,默认为 false
})

4. 服务响应码检查

const resp = HMRouterUtil.request('myService');
if (resp.code === 0) {
  // 服务调用成功
  console.log('返回值:', resp.data);
} else {
  // 服务调用失败
  console.error('错误:', resp.msg);
}

5. 异步服务处理

// 异步服务返回 Promise
@HMService({ serviceName: 'asyncService' })
async asyncMethod(): Promise<string> {
  return new Promise((resolve) => {
    setTimeout(() => resolve('result'), 1000);
  });
}

// 调用异步服务
const resp = HMRouterUtil.request('asyncService');
// resp.data 包含 Promise 的返回值

📊 Service vs 直接调用对比

直接调用路由

// 直接调用,代码分散,难以统一管理
HMRouterUtil.push('pages/DetailPage', { id: 123 });

通过 Service 调用路由

// 通过 Service 调用,可以添加统一逻辑
const resp = HMRouterUtil.request('routerPush', 'pages/DetailPage', { id: 123 });

优势:

  • 统一管理:路由跳转逻辑集中管理
  • 权限控制:可以在 Service 中添加权限检查
  • 日志记录:统一记录路由跳转日志
  • 参数验证:统一验证和转换路由参数
  • 易于测试:Service 方法可以单独测试

🎯 设计原则

原则

说明

类型安全

使用接口定义服务,保证类型安全

命名规范

服务名称使用常量管理,避免硬编码

单一职责

每个服务专注于特定功能

易于测试

Service 方法可以独立测试

统一管理

通过 Service 统一管理业务逻辑和路由跳转


📝 总结

本文介绍了 HMRouter Service 服务路由的封装和使用,包括:

  1. 类型封装:封装了 HMRouterUtilServiceParamHMRouterUtilServiceResp
  2. 常量管理:使用 HMRouterUtilServiceConstants 统一管理服务名称
  3. 接口定义:使用接口定义服务,保证类型安全
  4. 类级别服务:使用 @HMServiceProvider 实现类级别服务
  5. 方法级别服务:使用 @HMService 实现方法级别服务
  6. 路由跳转:通过 Service 调用路由跳转,实现统一管理
  7. 完整测试:创建了包含 13 个测试场景的测试页面

通过 Service 封装,我们可以:

  • 实现跨模块、跨页面的服务调用
  • 统一管理业务逻辑和路由跳转
  • 添加权限控制、日志记录等统一逻辑
  • 提高代码的可维护性和可测试性

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐