項目概述

味尋紀-元服務是一款基於鴻蒙HarmonyOS Next的美食應用,集成了鴻蒙官方推薦的zrouter路由管理方案,並實現了完整的用户認證體系包括靜默登錄功能。項目採用現代的組件化架構,提供流暢的用户體驗。

上架元服務-味尋紀 技術分享_鴻蒙


zrouter核心特性與功能

上架元服務-味尋紀 技術分享_鴻蒙_02


1. 統一路由管理

zrouter作為鴻蒙官方推薦的路由管理方案,為應用提供了強大的頁面導航能力。在味尋紀項目中,zrouter承擔了以下核心功能:

1.1 自動路由生成

通過router-register-plugin插件自動掃描生成路由構建器,項目中已自動生成了11個頁面的路由文件:

// _generated目錄下的自動生成的路由文件
entry\src\main\ets\_generated\
├── ZRBrowseHistoryPage.ets      // 瀏覽歷史頁面
├── ZRCookingTechniqueDetailPage.ets  // 烹飪技法詳情
├── ZRCookingToolDetailPage.ets       // 烹飪工具詳情
├── ZREditProfilePage.ets             // 編輯資料頁面
├── ZRFamilyMemberEditPage.ets        // 家庭成員編輯
├── ZRFamilyMemberListPage.ets        // 家庭成員列表
├── ZRFamilyMemberRecipesPage.ets     // 家庭成員菜譜
├── ZRFavoriteListPage.ets            // 收藏列表
├── ZRIngredientDetailPage.ets        // 食材詳情
├── ZRRecipeDetailPage.ets            // 菜譜詳情
├── ZRRecipeFilterPage.ets            // 菜譜篩選
├── ZRRecipeListPage.ets              // 菜譜列表
└── ZRTestPage.ets                    // 測試頁面

上架元服務-味尋紀 技術分享_鴻蒙_03


1.2 生命週期管理

zrouter內置了完整的頁面生命週期管理,支持頁面創建、銷燬、返回等狀態控制:

// 生命週期管理示例
import { Lifecycle, LifecycleEvent, LifecycleRegistry, ZRouter } from "@hzw/zrouter";

@Builder
export function ZRTestPageBuilder() {
  ZRTestPage()
}

@ComponentV2
export struct ZRTestPage {
  @Local navDestinationId: string = '';
  private pageStack: NavPathStack | null = null;

  @Lifecycle
  onCreate(want: Want, navStack: NavPathStack) {
    this.navDestinationId = want.parameters?.['navDestinationId'] as string || '';
    this.pageStack = navStack;
    // 註冊模板管理器
    ZRouter.templateMgr().register(this.navDestinationId)
  }

  @Lifecycle
  onShow() {
    // 頁面顯示時的邏輯
    if (this.pageStack) {
      const router = new LifecycleRegistry(this.navDestinationId);
      ZRouter.templateMgr().dispatch(this.navDestinationId, LifecycleEvent.ON_SHOW, router)
    }
  }

  @Lifecycle
  onBackPress(): boolean {
    // 處理返回鍵邏輯
    const router = new LifecycleRegistry(this.navDestinationId);
    const r = ZRouter.templateMgr().dispatch(this.navDestinationId, LifecycleEvent.ON_BACK_PRESS, router)
    return r || false;
  }
}

2. 強大的參數傳遞機制

zrouter提供了類型安全的參數傳遞功能,支持複雜的參數類型和結構。

2.1 基本參數傳遞

// 從首頁跳轉到菜譜詳情頁,並傳遞參數
ZRouter.getInstance().setParam({ recipeId: recipe.id }).push('RecipeDetailPage');

// 從篩選頁面跳轉到列表頁面,傳遞篩選條件
ZRouter.getInstance().setParam(params).push('RecipeListPage');

2.2 複雜參數傳遞

// 傳遞多個篩選參數
const filterParams = {
  category: '川菜',
  difficulty: '簡單',
  flavor: '香辣',
  includeIngredients: '牛肉,青椒',
  excludeIngredients: '洋葱',
  minTime: 10,
  maxTime: 60,
  keyword: '麻婆豆腐'
};

ZRouter.getInstance().setParam(filterParams).push('RecipeListPage');

2.3 參數獲取與類型轉換

// 在目標頁面中獲取參數
@ComponentV2
export struct RecipeListPage {
  @Local selectMode: boolean = false;
  @Local memberId: number = 0;

  aboutToAppear() {
    // 獲取路由參數
    this.selectMode = ZRouter.getInstance().getParamByKey('selectMode') as boolean;
    this.memberId = ZRouter.getInstance().getParamByKey('memberId') as number;
    
    // 獲取篩選參數
    const category = ZRouter.getInstance().getParamByKey('category') as string;
    const difficulty = ZRouter.getInstance().getParamByKey('difficulty') as string;
    const flavor = ZRouter.getInstance().getParamByKey('flavor') as string;
    const includeIngredients = ZRouter.getInstance().getParamByKey('includeIngredients') as string;
    const excludeIngredients = ZRouter.getInstance().getParamByKey('excludeIngredients') as string;
    const minTime = ZRouter.getInstance().getParamByKey('minTime') as number;
    const maxTime = ZRouter.getInstance().getParamByKey('maxTime') as number;
    const keyword = ZRouter.getInstance().getParamByKey('keyword') as string;
  }
}

3. 聲明式路由配置

zrouter支持簡潔的裝飾器語法,讓路由配置更加優雅:

import { Route } from '@hzw/zrouter';

@Route({ name: 'TestPage', useTemplate: true })
@Component
export struct TestPage {
  @State message: string = 'This is Test Page';

  build() {
    NavDestination() {
      Column({ space: 20 }) {
        Text(this.message)
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        
        Text('ZRouter 引入成功!')
          .fontSize(18)
          .fontColor('#4CAF50')
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
    }
    .title('測試頁面')
    .width('100%')
    .height('100%')
  }
}

項目集成實踐

1. 依賴配置

在項目的oh-package.json5中添加zrouter依賴:

{
  "dependencies": {
    "@hzw/zrouter": "^1.8.2"
  }
}

![依賴配置示意圖]圖3:oh-package.json5中的zrouter依賴配置

2. 初始化配置

EntryAbilityonCreate方法中初始化zrouter:

import { ZRouter } from '@hzw/zrouter';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    // 初始化 ZRouter
    ZRouter.initialize((config) => {
      config.context = this.context;
      config.isLoggingEnabled = true; // 開發環境開啓日誌
      config.isHSPModuleDependent = false; // 如果有HSP模塊設置為true
    });
  }
}

3. 導航容器配置

在主頁面中使用zrouter的導航棧:

import { ZRouter } from '@hzw/zrouter';

@ComponentV2
export struct Index {
  build() {
    Navigation(ZRouter.getNavStack()) {
      HomePage()
    }
    .mode(NavigationMode.Stack)
  }
}

4. 代碼混淆適配

在混淆規則文件obfuscation-rules.txt中配置zrouter相關的保護規則:

# ZRouter 混淆配置
-keep-file-name
Index
_generated
ZR*

這確保了zrouter生成的頁面名稱和路由配置在混淆後仍能正常工作。

上架元服務-味尋紀 技術分享_鴻蒙_04

靜默登錄功能實現

除了zrouter路由管理,項目還實現了完整的用户認證體系,其中靜默登錄是一大亮點功能。

上架元服務-味尋紀 技術分享_鴻蒙_05


1. 靜默登錄核心邏輯

/**
 * 靜默登錄 - 不顯示登錄界面
 * @param enableSilentLogin 是否啓用靜默登錄
 * @returns 登錄是否成功
 */
async performSilentLogin(enableSilentLogin: boolean = true): Promise<boolean> {
  // 如果未啓用靜默登錄,直接返回
  if (!enableSilentLogin) {
    hilog.info(DOMAIN, 'AuthManager', '靜默登錄已關閉');
    return false;
  }

  try {
    hilog.info(DOMAIN, 'AuthManager', '開始靜默登錄');

    // 創建靜默登錄請求
    const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
    loginRequest.forceLogin = false; // 靜默登錄
    loginRequest.state = util.generateRandomUUID();

    const controller = new authentication.AuthenticationController();
    const response = await controller.executeRequest(loginRequest);

    const loginWithHuaweiIDResponse = response as authentication.LoginWithHuaweiIDResponse;
    const state = loginWithHuaweiIDResponse.state;

    // 驗證 state
    if (state && loginRequest.state !== state) {
      hilog.error(DOMAIN, 'AuthManager', '狀態驗證失敗');
      return false;
    }

    const credential = loginWithHuaweiIDResponse.data;
    if (!credential) {
      hilog.error(DOMAIN, 'AuthManager', '未獲取到憑證');
      return false;
    }

    // 調用後端API完成登錄
    const loginResponse = await huaweiLogin({
      authorizationCode: credential.authorizationCode || '',
      openID: credential.openID || '',
      unionID: credential.unionID || '',
      idToken: credential.idToken
    });

    // 保存登錄狀態
    await this.saveLoginState(loginResponse);

    // 更新全局用户狀態
    this.userStore.setUser(loginResponse.user);
    this.userStore.setLoggedIn(true);

    hilog.info(DOMAIN, 'AuthManager', '靜默登錄成功: %{public}s', 
      loginResponse.user.nickname || loginResponse.user.username);
    
    return true;

  } catch (error) {
    const err = error as BusinessError;
    if (err.code === 1001502001) {
      // 用户未登錄華為賬號,這是正常情況
      hilog.info(DOMAIN, 'AuthManager', '用户未登錄華為賬號');
    } else {
      hilog.error(DOMAIN, 'AuthManager', '靜默登錄失敗: %{public}s', JSON.stringify(error));
    }
    return false;
  }
}

2. 啓動登錄流程

在應用啓動時,系統會自動執行以下登錄邏輯:

  1. 本地狀態恢復:優先從本地存儲恢復登錄狀態
  2. 靜默登錄:如果本地恢復失敗且開啓靜默登錄,則執行靜默登錄
  3. 用户狀態更新:更新全局用户狀態管理
/**
 * 應用啓動時的登錄邏輯
 * 1. 先嚐試從本地恢復登錄狀態
 * 2. 如果本地恢復成功,不再執行靜默登錄
 * 3. 如果本地恢復失敗且開啓了靜默登錄,則執行靜默登錄
 */
private async performStartupLogin(): Promise<void> {
  try {
    hilog.info(DOMAIN, 'EntryAbility', '開始執行啓動登錄邏輯');

    // 1. 先嚐試從本地恢復登錄狀態
    const restored = await this.authManager.restoreLoginState();
    if (restored) {
      hilog.info(DOMAIN, 'EntryAbility', '從本地恢復登錄狀態成功');
      return;
    }

    // 2. 檢查是否啓用靜默登錄
    const enableSilentLogin = await this.storageManager.getBoolean(
      StorageKeys.ENABLE_SILENT_LOGIN, 
      true // 默認開啓
    );

    if (!enableSilentLogin) {
      hilog.info(DOMAIN, 'EntryAbility', '靜默登錄已關閉,跳過');
      return;
    }

    // 3. 執行靜默登錄
    const success = await this.authManager.performSilentLogin(enableSilentLogin);
    if (success) {
      hilog.info(DOMAIN, 'EntryAbility', '靜默登錄成功');
    } else {
      hilog.info(DOMAIN, 'EntryAbility', '靜默登錄失敗或用户未登錄');
    }

  } catch (error) {
    hilog.error(DOMAIN, 'EntryAbility', '啓動登錄邏輯執行失敗: %{public}s', JSON.stringify(error));
  }
}

上架元服務-味尋紀 技術分享_鴻蒙_06

image-20251121103306865

3. 手動登錄支持

除了靜默登錄,項目還支持手動登錄,用户可以主動觸發登錄流程:

/**
 * 手動登錄 - 顯示登錄界面
 * @returns 登錄響應數據
 */
async performManualLogin(): Promise<LoginResponse | null> {
  try {
    hilog.info(DOMAIN, 'AuthManager', '開始手動登錄');

    // 創建登錄請求
    const loginRequest = new authentication.HuaweiIDProvider().createLoginWithHuaweiIDRequest();
    loginRequest.forceLogin = true; // 強制顯示登錄頁面
    loginRequest.state = util.generateRandomUUID();

    const controller = new authentication.AuthenticationController();
    const response = await controller.executeRequest(loginRequest);

    const loginWithHuaweiIDResponse = response as authentication.LoginWithHuaweiIDResponse;
    const state = loginWithHuaweiIDResponse.state;

    if (state && loginRequest.state !== state) {
      hilog.error(DOMAIN, 'AuthManager', '狀態驗證失敗');
      return null;
    }

    const credential = loginWithHuaweiIDResponse.data;
    if (!credential) {
      hilog.error(DOMAIN, 'AuthManager', '未獲取到憑證');
      return null;
    }

    // 調用後端API完成登錄
    const loginResponse = await huaweiLogin({
      authorizationCode: credential.authorizationCode || '',
      openID: credential.openID || '',
      unionID: credential.unionID || '',
      idToken: credential.idToken
    });

    // 保存登錄狀態
    await this.saveLoginState(loginResponse);

    // 更新全局用户狀態
    this.userStore.setUser(loginResponse.user);
    this.userStore.setLoggedIn(true);

    hilog.info(DOMAIN, 'AuthManager', '手動登錄成功: %{public}s',
      loginResponse.user.nickname || loginResponse.user.username);

    return loginResponse;

  } catch (error) {
    const err = error as BusinessError;
    hilog.error(DOMAIN, 'AuthManager', '手動登錄失敗: %{public}s', JSON.stringify(error));
    return null;
  }
}

核心亮點與優勢

1. 零配置路由管理

  • 自動生成路由:通過插件自動掃描生成路由配置,無需手動維護路由表
  • 類型安全:參數傳遞和獲取過程中提供完整的類型支持
  • 聲明式配置:使用裝飾器語法,讓路由配置更加簡潔直觀

2. 強大的頁面生命週期管理

  • 完整的生命週期鈎子:支持頁面創建、顯示、隱藏、銷燬等完整生命週期
  • 靈活的事件處理:內置多種事件類型,便於處理複雜的頁面交互邏輯
  • 返回鍵管理:提供統一的返回鍵處理機制

3. 智能參數傳遞機制

  • 類型轉換支持:自動處理基本類型和複雜對象的序列化/反序列化
  • 參數驗證:內置參數驗證機制,提高應用穩定性
  • 狀態管理集成:與鴻蒙的AppStorageV2等狀態管理方案完美集成

4. 生產級混淆適配

  • 混淆保護規則:提供完整的混淆配置建議
  • 文件名稱保護:確保混淆後路由名稱的正確映射
  • 編譯時優化:支持編譯時的路由優化和壓縮

5. 無縫認證集成

  • 靜默登錄體驗:應用啓動時自動完成身份驗證,用户無感知
  • 本地狀態恢復:優先使用本地存儲的用户信息,提升啓動速度
  • 靈活認證策略:支持靜默登錄和手動登錄的靈活切換

性能與優化

1. 路由性能優化

  • 懶加載支持:zrouter支持頁面懶加載,減少應用啓動時間
  • 路由緩存:內置路由狀態緩存,提升頁面切換性能
  • 內存管理:智能的內存管理機制,避免內存泄漏

2. 開發體驗優化

  • 開發日誌:開發模式下提供詳細的路由操作日誌
  • 熱更新支持:支持開發時的路由熱更新
  • 調試工具:提供豐富的調試工具和狀態監控

3. 兼容性保障

  • 版本兼容:支持不同版本的HarmonyOS系統
  • API適配:自動適配不同API版本的功能差異
  • 向後兼容:確保老版本代碼的兼容性問題

最佳實踐建議

1. 路由命名規範

  • 使用語義化的頁面名稱
  • 統一命名風格(如駝峯命名法)
  • 避免使用特殊字符

2. 參數設計原則

  • 傳遞最小必要信息
  • 避免傳遞過大的對象
  • 合理設計參數結構

3. 頁面生命週期管理

  • 合理使用生命週期鈎子
  • 避免在生命週期中執行耗時操作
  • 及時清理資源,避免內存泄漏

4. 安全考慮

  • 對敏感參數進行加密處理
  • 驗證參數的合法性
  • 避免在URL中暴露敏感信息

總結

味尋紀-元服務項目成功集成了zrouter路由管理方案,並實現了完善的靜默登錄功能。通過zrouter的強大功能,項目獲得了:

  • 統一且強大的路由管理:簡化了頁面導航邏輯,提升了開發效率
  • 類型安全的參數傳遞:減少了運行時錯誤,提升了代碼質量
  • 完整的生活週期管理:提供了更好的用户體驗
  • 生產級的混淆支持:確保了應用的安全性
  • 無縫的認證體驗:靜默登錄提升了用户使用體驗

zrouter作為鴻蒙官方推薦的路由解決方案,為HarmonyOS Next應用開發提供了強大而穩定的路由管理能力,是構建高質量鴻蒙應用的重要工具。通過味尋紀項目的實踐,我們可以看到zrouter在實際項目中的強大功能和良好體驗。