🔐 版本亮点:新增本地账号注册/登录/退出 + 启动鉴权(未登录先登录)+ OpenHarmony 端可持久化(解决 MissingPluginException),并整理了 Windows/DevEco 构建过程中的关键踩坑与修复手段。

一、版本信息

  • 版本号:v1.0.4

  • 发布日期:2026 年

  • 核心更新:本地登录注册、登录态持久化、启动鉴权入口(AuthGate)、OpenHarmony 插件适配(shared_preferences_ohos)、Windows 路径与 DevEco 缓存问题处理

  • 仓库地址:gitcode_pocket_tool - AtomGit | GitCode


二、页面展示

为了方便大家学习,做了一个登录注册的小案例:

本文以一个实际 Flutter 项目为例,完整记录如何在 OpenHarmony 设备上实现一个本地账号登录/注册功能(离线可用),并做到:

  • 注册/登录/退出

  • 密码不明文保存(salt + sha256)

  • 登录态持久化(重启仍保持登录)

  • 启动时强制登录(未登录先进入登录页)

  • 解决 OpenHarmony 上常见的 shared_preferences MissingPluginException

  • 解决 Windows 下拉取 OpenHarmony 插件仓库引发的 Filename too long

本文偏“工程实践”,适合:Flutter + OpenHarmony 开发者、需要离线账号体系/本地存储的 App。


三、需求与目标

我们希望实现一个简单、可用、可落地的本地账号体系:

  • 注册:输入用户名/密码,创建本地用户,并自动登录。

  • 登录:输入用户名/密码,校验成功后写入登录态。

  • 退出:清除登录态。

  • 启动鉴权:进入应用先检查是否已登录;未登录则只能进入登录/注册页面。

  • 数据持久化:用户表/登录态落盘保存。

为什么需要本地数据持久化?

在 OpenHarmony 上,持久化常见用途包括:

  • 用户偏好设置(主题/语言等)

  • 应用状态(登录态、阅读进度)

  • 离线缓存(接口响应、列表数据)

如果不做持久化,登录态会随进程退出而丢失,用户体验较差。


四、核心实现概览

本次功能拆分为 4 个模块:

  1. 依赖与 OpenHarmony 适配:通过 dependency_overrides 让 OpenHarmony 端拥有 shared_preferences 的 OHOS 实现

  2. LocalAuthService:本地用户表/会话的读写与校验

  3. Login/Register UI:表单校验 + 错误提示 + 注册后自动登录

  4. AuthGate:启动入口页,未登录先进入登录页,已登录进入主导航


五、方案选型

5.1 账号数据存储

本例选择“最轻量”的键值存储,存两类数据:

  • local_auth_users_v1:用户表(JSON)

  • local_auth_current_user_v1:当前登录用户(会话)

数据量很小(<= 几 KB),键值存储即可满足。

5.2 密码安全

密码不保存明文,使用:

  • salt:随机 16 字节

  • hashsha256(salt + '::' + password)

这样即使本地数据被读取也不会直接泄露密码。


六、OpenHarmony 的关键坑:shared_preferences MissingPluginException

在 OpenHarmony 环境中,直接使用 Flutter 生态常见插件(如 shared_preferences)可能遇到:

MissingPluginException(No implementation found for method getAll on channel plugins.flutter.io/shared_preferences)

这通常意味着:当前 OpenHarmony 运行时没有该插件的平台实现

6.1 正确做法:使用 OpenHarmony-SIG 的 ohos 实现(推荐)

OpenHarmony 社区通常会维护一套 flutter_packages 仓库,提供插件的 OHOS 适配实现。

本文做法是(重点):

  • 代码层继续使用标准 shared_preferences API(Dart 代码不写平台分支)

  • 通过 dependency_overridesshared_preferences / shared_preferences_ohos 指向 OpenHarmony-SIG 的 OHOS 实现目录

这样 Dart 代码无需写平台判断,OpenHarmony 上会自动使用 OHOS 平台实现。

GitCode对应仓库连接:flutter_packages - AtomGit | GitCode

Gitee仓库对应链接:flutter_packages


七、Windows 的关键坑:Filename too long

如果直接用:

dependency_overrides:
  shared_preferences:
    git:
      url: https://gitee.com/openharmony-sig/flutter_packages.git
      ref: xxx
      path: ...

在 Windows 下,pub 会把大仓库拉到 .pub-cache 深层目录,容易触发:

Filename too long

7.1 解决方案:本地路径 clone + path 覆盖

推荐两种方式(按稳定性排序):

  1. 克隆到短路径(最稳,避免 Windows Filename too long

  2. 克隆到项目当前目录(更方便,但项目路径很长时可能触发 Filename too long

如果你使用“项目当前目录”方案并遇到 Filename too long

  • 方案 A:改用短路径(例如 E:\fp\flutter_packages

  • 方案 B:启用 Windows 长路径支持,并设置:git config --global core.longpaths true


八、实现本地登录/注册(代码结构)

本文实现分为四块:

  1. 依赖配置

  2. Auth Service(本地认证与持久化)

  3. 登录/注册 UI

  4. 启动鉴权 AuthGate


8.1 添加依赖

8.1.1 密码哈希依赖

pubspec.yaml 增加:

dependencies:
  crypto: ^3.0.3
  shared_preferences: ^2.3.2
8.1.2 OpenHarmony 持久化:dependency_overrides(关键)

在项目 pubspec.yaml 底部加入(示例:克隆到项目当前目录 ./flutter_packages):

dependency_overrides:
  shared_preferences:
    path: ./flutter_packages/packages/shared_preferences/shared_preferences
  shared_preferences_ohos:
    path: ./flutter_packages/packages/shared_preferences/shared_preferences_ohos

然后在本机执行:

重要:以下命令都需要在「项目根目录」执行,也就是看到类似提示符:

  • E:\FlutterOpenHarmony\gitcode_pocket_tool>

8.1.2.1 Windows CMD(一行复制版)推荐

git clone -b br_shared_preferences-v2.3.2_ohos https://gitee.com/openharmony-sig/flutter_packages.git flutter_packages
flutter pub get
flutter analyze
8.1.2.2 Windows PowerShell(一行复制版)
git clone -b br_shared_preferences-v2.3.2_ohos https://gitee.com/openharmony-sig/flutter_packages.git .\flutter_packages
flutter pub get
flutter analyze
8.1.2.3 Bash / Git-Bash(支持 \ 续行)
git clone -b br_shared_preferences-v2.3.2_ohos \
  https://gitee.com/openharmony-sig/flutter_packages.git \
  ./flutter_packages
flutter pub get
flutter analyze

如果你在 Windows 上遇到 Filename too long

  • 方案 A:把仓库克隆到短路径(例如 E:\fp\flutter_packages),然后把 dependency_overridespath: 改成短路径

  • 方案 B:启用 Windows 长路径支持,并设置 git config --global core.longpaths true

8.1.3 快速自检:确认 overrides 生效

执行 flutter pub get 后,终端应该出现类似输出(关键是 overridden):

! shared_preferences ... (overridden)
! shared_preferences_ohos ... (overridden)

如果你使用的是别的分支名,请先 git branch -a 查看。


8.2 实现 LocalAuthService(注册/登录/退出/持久化)

新建文件:

  • lib/core/local_auth_service.dart

核心要点:

  • 用户表以 JSON 存在 shared_preferences

  • 当前用户(会话)以 JSON 存在 shared_preferences

  • 密码使用 salt + sha256

数据结构示例:

{
  "yixuan": {
    "salt": "...",
    "hash": "...",
    "createdAt": "2026-01-26T..."
  }
}

关键 API 设计:

  • Future<LocalUser?> currentUser()

  • Future<LocalUser> register({username, password})

  • Future<LocalUser> login({username, password})

  • Future<void> logout()

8.2.1 关键代码(可直接复用)

下面给出最关键的实现片段(完整实现见 lib/core/local_auth_service.dart)。

8.2.1.1 数据 Key
static const _usersKey = 'local_auth_users_v1';
static const _currentUserKey = 'local_auth_current_user_v1';
8.2.1.2 密码 hash:salt + sha256
String _generateSalt() {
  final random = Random.secure();
  final bytes = List<int>.generate(16, (_) => random.nextInt(256));
  return base64UrlEncode(bytes);
}
​
String _hashPassword({required String password, required String salt}) {
  final bytes = utf8.encode('$salt::$password');
  return sha256.convert(bytes).toString();
}
8.2.1.3 注册(写入 users + currentUser)
Future<LocalUser> register({required String username, required String password}) async {
  final normalized = username.trim();
  if (normalized.length < 3) throw const LocalAuthException('用户名至少 3 位');
  if (password.length < 6) throw const LocalAuthException('密码至少 6 位');
​
  final users = await _loadUsers();
  if (users.containsKey(normalized)) throw const LocalAuthException('该用户名已存在');
​
  final salt = _generateSalt();
  final hash = _hashPassword(password: password, salt: salt);
​
  users[normalized] = {
    'salt': salt,
    'hash': hash,
    'createdAt': DateTime.now().toIso8601String(),
  };
  await _saveUsers(users);
​
  final user = LocalUser(username: normalized);
  await _setString(_currentUserKey, jsonEncode(user.toJson()));
  return user;
}
8.2.1.4 登录(校验 hash + 写入 currentUser)
Future<LocalUser> login({required String username, required String password}) async {
  final normalized = username.trim();
  if (normalized.isEmpty) throw const LocalAuthException('用户名不能为空');
  if (password.isEmpty) throw const LocalAuthException('密码不能为空');
​
  final users = await _loadUsers();
  final record = users[normalized];
  if (record is! Map<String, dynamic>) throw const LocalAuthException('用户名或密码错误');
​
  final salt = record['salt'];
  final hash = record['hash'];
  if (salt is! String || hash is! String) throw const LocalAuthException('用户数据损坏,请重新注册');
​
  final inputHash = _hashPassword(password: password, salt: salt);
  if (inputHash != hash) throw const LocalAuthException('用户名或密码错误');
​
  final user = LocalUser(username: normalized);
  await _setString(_currentUserKey, jsonEncode(user.toJson()));
  return user;
}
8.2.1.5 读取当前登录用户(启动鉴权依赖它)
Future<LocalUser?> currentUser() async {
  final raw = await _getString(_currentUserKey);
  if (raw == null || raw.isEmpty) return null;
  try {
    final map = jsonDecode(raw);
    if (map is! Map<String, dynamic>) return null;
    return LocalUser.fromJson(map);
  } catch (_) {
    return null;
  }
}

8.3 实现登录/注册页面

新增文件:

  • lib/pages/login_page.dart

  • lib/pages/register_page.dart

实现点:

  • 表单校验:用户名、密码长度

  • Loading 状态

  • 错误展示(LocalAuthException.message

  • 注册成功后自动登录

8.3.1 让 LoginPage 既能“push/pop”也能当“应用入口”

为了实现“启动必须登录”,我们给 LoginPage 增加可选回调:

  • ValueChanged<LocalUser>? onLoggedIn

行为规则:

  • 如果 onLoggedIn != null:登录/注册成功时调用回调,不执行 Navigator.pop()

  • 否则:保持原逻辑 Navigator.pop(user)

8.3.1.1 关键代码:onLoggedIn 回调
class LoginPage extends StatefulWidget {
  const LoginPage({super.key, this.onLoggedIn});
  final ValueChanged<LocalUser>? onLoggedIn;
  // ...
}
​
void _complete(LocalUser user) {
  final callback = widget.onLoggedIn;
  if (callback != null) {
    callback(user);
    return;
  }
  Navigator.of(context).pop<LocalUser>(user);
}

8.4 实现启动鉴权 AuthGate(启动必须登录)

lib/main.dart 里实现:

  • AuthGatePage

逻辑:

  1. App 启动时进入 AuthGatePage

  2. AuthGatePage 调用 LocalAuthService.currentUser()

  3. 若返回 null:展示 LoginPage(onLoggedIn: ...)

  4. 若返回用户:进入 MainNavigationPage

并将 MaterialApp(home: ...) 改为:

  • home: const AuthGatePage()

这样用户无法绕过登录页。

8.4.1 关键代码:AuthGatePage(启动检查 currentUser)

以下为核心逻辑片段(完整实现见 lib/main.dart):

class AuthGatePage extends StatefulWidget {
  const AuthGatePage({super.key});
  @override
  State<AuthGatePage> createState() => _AuthGatePageState();
}
​
class _AuthGatePageState extends State<AuthGatePage> {
  final _auth = LocalAuthService();
  bool _checking = true;
  LocalUser? _user;
  String? _error;
​
  @override
  void initState() {
    super.initState();
    _check();
  }
​
  Future<void> _check() async {
    setState(() {
      _checking = true;
      _error = null;
    });
    try {
      final user = await _auth.currentUser();
      if (!mounted) return;
      setState(() {
        _user = user;
        _checking = false;
      });
    } catch (e) {
      if (!mounted) return;
      setState(() {
        _error = '$e';
        _checking = false;
      });
    }
  }
​
  void _handleLoggedIn(LocalUser user) {
    setState(() {
      _user = user;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    if (_checking) return const Scaffold(body: Center(child: CircularProgressIndicator()));
    if (_error != null) return Scaffold(body: Center(child: Text(_error!)));
    if (_user == null) return LoginPage(onLoggedIn: _handleLoggedIn);
    return const MainNavigationPage();
  }
}

九、验证与测试清单

9.1 功能验证

  • 首次启动:进入登录页

  • 注册:创建用户并自动登录

  • 登录:校验成功进入主页面

  • 退出:清除登录态

  • 重启:若未退出,仍保持登录态

9.2 OpenHarmony 持久化验证

9.2.1 执行指令:依赖与静态检查
flutter pub get
flutter analyze
9.2.2 运行与验证步骤
  1. 安装并启动 App

  2. 由于启用了启动鉴权(AuthGate),未登录会直接进入登录页

  3. 注册或登录成功后进入主页面

  4. 完全退出 App(从后台划掉)

  5. 再次启动 App

  6. 预期:直接进入主页面(登录态已持久化)

  • 在 OHOS 设备上注册并登录

  • 杀进程 / 重启 App

  • 应用应直接进入主页面(AuthGate 检测到 currentUser 不为空)


十、验证与工具

  • flutter pub get:拉取依赖并生成插件映射

  • flutter analyze:确保 0 issue

  • DevEco Studio:Clean Project + Rebuild Project 用于清理 hvigor 缓存路径


十一、常见问题(FAQ)

11.1 仍然出现 MissingPluginException?

  • 检查 dependency_overrides 是否生效(flutter pub get 输出中应看到 overridden)

  • 确认本地 flutter_packages 的路径是否正确

  • 确认 OHOS 分支是否与你 Flutter/OHOS 版本匹配

11.2 Windows 还是 Filename too long?

  • 不要用 git: 覆盖(会进入 .pub-cache 深目录)

  • 一定使用本地短路径 clone + path: 覆盖

11.3 登录态没保持?

  • 检查 LocalAuthService 是否在登录/注册成功后写入 _currentUserKey

  • 在 OHOS 上优先确保 shared_preferences 插件实现已正确接入


十二、总结

通过以上步骤,我们在 Flutter(OpenHarmony) 项目里实现了:

  • 本地注册/登录/退出

  • 密码安全存储(salt + sha256)

  • OpenHarmony 可用的 shared_preferences 持久化

  • 启动鉴权(未登录先登录)

  • Windows/镜像/插件适配的工程踩坑与解决方案

你可以在此基础上继续扩展:

  • 账号删除/修改密码

  • 更严格的密码策略

  • 敏感数据加密(结合 OHOS 安全组件)

  • 本地缓存(Hive/SQLite)


十三、关键改动一览

模块/文件 主要工作
pubspec.yaml 增加 dependency_overrides 指向 ./flutter_packages/...,让 OpenHarmony 使用 OHOS 实现;并补充 Windows/CMD/PowerShell 命令说明
lib/core/local_auth_service.dart 本地用户表/会话存储,密码 salt + sha256,提供 register/login/logout/currentUser
lib/pages/login_page.dart 增加 onLoggedIn 回调,既支持“页面 pop 返回”也支持“作为启动入口”
lib/pages/register_page.dart 注册后自动登录返回用户
lib/main.dart 新增 AuthGatePage 作为 home,启动检查登录态
lib/pages/main_navigation/profile_page.dart “我的”页展示本地账号模块,并调整到个人信息卡片上方
DevEco/hvigor 增加清理策略:Clean/Rebuild + flutter clean 解决旧路径缓存

十四、升级指南(从旧版本升级到 v1.0.4)

  1. git pull 获取最新代码

  2. 在项目根目录执行(Windows CMD 一行版):

git clone -b br_shared_preferences-v2.3.2_ohos https://gitee.com/openharmony-sig/flutter_packages.git flutter_packages
flutter pub get
flutter analyze

DevEco Studio:

  • Build -> Clean Project

  • Build -> Rebuild Project

运行验证:

  • 首次启动进入登录页

  • 注册/登录后进入主页面

  • 杀进程重启仍保持登录态


十五、下一站

  • 退出登录后全局回到登录页(并清理主页面堆栈)
  • 支持修改密码/删除账号
  • 敏感信息加密存储(结合 OpenHarmony 安全组件)
  • 本地缓存(Hive/SQLite)与离线模式

欢迎加入开源鸿蒙跨平台社区:开源鸿蒙跨平台开发者社区

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐