上架元服务-味寻纪 技术分享

项目概述

味寻纪-元服务是一款基于鸿蒙HarmonyOS Next的美食应用,集成了鸿蒙官方推荐的zrouter路由管理方案,并实现了完整的用户认证体系包括静默登录功能。项目采用现代的组件化架构,提供流畅的用户体验。

zrouter核心特性与功能

1. 统一路由管理

zrouter作为鸿蒙官方推荐的路由管理方案,为应用提供了强大的页面导航能力。在味寻纪项目中,zrouter承担了以下核心功能:

1.1 自动路由生成

通过router-register-plugin插件自动扫描生成路由构建器,项目中已自动生成了11个页面的路由文件:

less 复制代码
// _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                    // 测试页面
1.2 生命周期管理

zrouter内置了完整的页面生命周期管理,支持页面创建、销毁、返回等状态控制:

typescript 复制代码
// 生命周期管理示例
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 基本参数传递
scss 复制代码
// 从首页跳转到菜谱详情页,并传递参数
ZRouter.getInstance().setParam({ recipeId: recipe.id }).push('RecipeDetailPage');

// 从筛选页面跳转到列表页面,传递筛选条件
ZRouter.getInstance().setParam(params).push('RecipeListPage');
2.2 复杂参数传递
scss 复制代码
// 传递多个筛选参数
const filterParams = {
  category: '川菜',
  difficulty: '简单',
  flavor: '香辣',
  includeIngredients: '牛肉,青椒',
  excludeIngredients: '洋葱',
  minTime: 10,
  maxTime: 60,
  keyword: '麻婆豆腐'
};

ZRouter.getInstance().setParam(filterParams).push('RecipeListPage');
2.3 参数获取与类型转换
typescript 复制代码
// 在目标页面中获取参数
@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支持简洁的装饰器语法,让路由配置更加优雅:

typescript 复制代码
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依赖:

perl 复制代码
{
  "dependencies": {
    "@hzw/zrouter": "^1.8.2"
  }
}

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

2. 初始化配置

EntryAbilityonCreate方法中初始化zrouter:

arduino 复制代码
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的导航栈:

typescript 复制代码
import { ZRouter } from '@hzw/zrouter';

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

4. 代码混淆适配

在混淆规则文件obfuscation-rules.txt中配置zrouter相关的保护规则:

erlang 复制代码
# ZRouter 混淆配置
-keep-file-name
Index
_generated
ZR*

这确保了zrouter生成的页面名称和路由配置在混淆后仍能正常工作。

静默登录功能实现

除了zrouter路由管理,项目还实现了完整的用户认证体系,其中静默登录是一大亮点功能。

1. 静默登录核心逻辑

typescript 复制代码
/**
 * 静默登录 - 不显示登录界面
 * @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. 用户状态更新:更新全局用户状态管理
csharp 复制代码
/**
 * 应用启动时的登录逻辑
 * 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));
  }
}

image-20251121103306865

3. 手动登录支持

除了静默登录,项目还支持手动登录,用户可以主动触发登录流程:

csharp 复制代码
/**
 * 手动登录 - 显示登录界面
 * @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在实际项目中的强大功能和良好体验。

相关推荐
想不明白的过度思考者1 小时前
Spring Web MVC从入门到实战
java·前端·spring·mvc
AAA简单玩转程序设计1 小时前
C++进阶小技巧:让代码从"能用"变"优雅"
前端·c++
子洋1 小时前
群晖 DSM 更新后 Cloudflare DDNS 失效的排查记录
前端·后端·dns
一颗烂土豆2 小时前
告别 Vue 多分辨率适配烦恼:vfit 让元素定位 “丝滑” 跨设备
前端·vue.js
ArkPppp2 小时前
牛刀小试:Vue 3的响应式系统和Proxy?
前端
venton2 小时前
Next.js SSR 实战:从零到一,构建服务端渲染应用
前端
大雷神2 小时前
windows中flutter开发鸿蒙实操
harmonyos
萌狼蓝天2 小时前
[Vue]性能优化:动态首行与动态列的匹配,表格数据格式处理性能优化
前端·javascript·vue.js·性能优化·ecmascript