Flutter三方库适配OpenHarmony【password_generator】密码生成器项目完整实战
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
password_generator 是一个基于 Flutter 的本地密码生成器项目,核心代码位于 lib/main.dart。项目使用 math.Random.secure() 生成随机密码,通过 Slider 控制密码长度,通过四个 SwitchListTile 控制大写字母、小写字母、数字和符号字符集,并使用 LinearProgressIndicator 展示规则型密码强度。生成结果可以通过 Clipboard 复制到剪贴板,复制后使用 SnackBar 给出反馈。
这个项目适合讲解 Flutter 工具类应用在 OpenHarmony 上的适配方式。它覆盖了 安全随机数 、字符集组合 、长度滑块 、开关状态管理 、强度评分 、剪贴板交互 、只读输入框 和 Material 3 组件渲染。

图片说明:本文围绕 Flutter Material 组件、Dart 安全随机数和 OpenHarmony 承载工程展开,所有关键代码均来自 password_generator 的真实源码。
密码生成器不是简单的字符串拼接。随机数来源、字符集兜底、长度范围、强度提示和复制反馈都会影响工具的可靠性和使用体验。
一、项目背景与目标
1.1 项目定位
password_generator 是一个轻量密码生成工具。用户进入页面后会自动得到一段默认密码,也可以调整长度和字符集开关重新生成。页面顶部显示生成结果、刷新按钮、复制按钮和强度进度条,页面中部提供长度滑块,页面下方提供四类字符开关和重新生成按钮。
当前项目真实支持的功能包括:
- 启动时自动生成密码。
- 默认密码长度为 12。
- 长度范围为 4 到 32。
- 支持大写字母
A-Z。 - 支持小写字母
a-z。 - 支持数字
0-9。 - 支持符号
!@#$%^&*()_+-=[]{}|;:,.<>?。 - 四类字符集都关闭时回退到小写字母。
- 使用
math.Random.secure()生成随机索引。 - 使用只读
TextField展示密码。 - 使用刷新图标重新生成密码。
- 使用复制图标写入系统剪贴板。
- 使用
SnackBar提示复制成功。 - 使用规则型分数展示 Weak、Medium、Strong、Very Strong。
- 使用不同颜色展示强度级别。
1.2 技术目标
本文围绕真实源码拆解以下技术点:
- Flutter 应用入口和蓝灰色 Material 3 主题。
TextEditingController如何保存生成结果。_length如何通过Slider控制。- 四个布尔开关如何组成候选字符集。
math.Random.secure()如何生成随机字符索引。- 字符集为空时为什么要设置兜底字符集。
_calculateStrength如何计算规则型强度。_getStrengthColor如何映射强度颜色。Clipboard.setData如何复制密码。- OpenHarmony 侧如何验证开关、滑块、复制和 SnackBar。
1.3 核心实现速览
| 能力 | 当前实现 | 适配关注点 |
|---|---|---|
| 应用入口 | runApp(const PasswordGeneratorApp()) |
确认首屏正常显示 |
| 主题 | ColorScheme.fromSeed(seedColor: Colors.blueGrey) |
确认蓝灰色 Material 3 样式 |
| 密码展示 | 只读 TextField |
确认等宽字体和只读状态 |
| 密码长度 | Slider,范围 4 到 32 |
确认拖动和数值刷新 |
| 字符集 | 四个 SwitchListTile |
确认开关状态和重新生成 |
| 随机数 | math.Random.secure() |
确认安全随机 API 可用 |
| 强度条 | LinearProgressIndicator |
确认进度和颜色 |
| 复制 | Clipboard.setData |
确认剪贴板能力 |
| 反馈 | SnackBar |
确认复制提示展示 |
二、环境准备与工程结构
2.1 工程结构
项目保持 Flutter 标准结构,同时包含 OpenHarmony 平台工程。
| 文件或目录 | 作用 |
|---|---|
lib/main.dart |
应用入口、密码生成、强度计算、复制和 UI |
pubspec.yaml |
SDK 约束、Flutter 依赖和 Material 图标配置 |
analysis_options.yaml |
Flutter lint 规则 |
test/ |
Flutter 测试目录 |
ohos/ |
OpenHarmony 平台承载工程 |
README.md |
项目说明文件 |
当前业务逻辑集中在 lib/main.dart,没有额外网络请求和数据库依赖。
2.2 依赖配置
项目使用 Dart SDK ^3.9.2,依赖 Flutter SDK 和 cupertino_icons。
yaml
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
密码生成使用 Dart 标准库 dart:math,复制能力来自 Flutter SDK 的 package:flutter/services.dart。
2.3 常用命令
bash
flutter pub get
flutter analyze
flutter test
flutter run
| 命令 | 用途 |
|---|---|
flutter pub get |
获取依赖 |
flutter analyze |
执行静态分析 |
flutter test |
执行测试 |
flutter run |
在目标设备运行 |
OpenHarmony 运行还需要结合本地 Flutter OpenHarmony 工具链完成平台构建和设备调试。
三、应用入口与主题配置
3.1 import 依赖
项目引入了三个依赖。
dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:math' as math;
material.dart 提供 UI 组件,services.dart 提供剪贴板能力,dart:math 提供安全随机数。
3.2 main 函数
入口函数启动根组件。
dart
void main() {
runApp(const PasswordGeneratorApp());
}
PasswordGeneratorApp 是应用根节点。
3.3 PasswordGeneratorApp
根组件创建 MaterialApp。
dart
class PasswordGeneratorApp extends StatelessWidget {
const PasswordGeneratorApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Password Generator',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blueGrey),
useMaterial3: true,
),
home: const PasswordGeneratorHomePage(title: 'Password Generator'),
);
}
}
这段代码包含三个关键点:
- 应用标题为
Password Generator。 - 使用
Colors.blueGrey作为主题种子色。 - 首页为
PasswordGeneratorHomePage。
四、页面状态设计
4.1 StatefulWidget
密码生成器需要维护密码文本、长度、字符集开关和强度状态,因此页面使用 StatefulWidget。
dart
class PasswordGeneratorHomePage extends StatefulWidget {
const PasswordGeneratorHomePage({super.key, required this.title});
final String title;
@override
State<PasswordGeneratorHomePage> createState() => _PasswordGeneratorHomePageState();
}
title 用于 AppBar 展示。
4.2 状态字段
状态类包含密码生成器所需的全部可变数据。
dart
class _PasswordGeneratorHomePageState extends State<PasswordGeneratorHomePage> {
final TextEditingController _passwordController = TextEditingController();
int _length = 12;
bool _useUppercase = true;
bool _useLowercase = true;
bool _useNumbers = true;
bool _useSymbols = true;
double _strength = 0;
String _strengthLabel = '';
}
| 字段 | 初始值 | 作用 |
|---|---|---|
_passwordController |
空文本 | 保存并展示生成密码 |
_length |
12 |
控制密码长度 |
_useUppercase |
true |
是否包含大写字母 |
_useLowercase |
true |
是否包含小写字母 |
_useNumbers |
true |
是否包含数字 |
_useSymbols |
true |
是否包含符号 |
_strength |
0 |
强度进度值 |
_strengthLabel |
空字符串 | 强度文案 |
4.3 生命周期
页面初始化时立即生成一段密码。
dart
@override
void initState() {
super.initState();
_generatePassword();
}
页面销毁时释放文本控制器。
dart
@override
void dispose() {
_passwordController.dispose();
super.dispose();
}
TextEditingController属于需要释放的对象。当前项目在dispose中释放控制器,生命周期处理是完整的。
五、字符集组合逻辑
5.1 生成方法入口
_generatePassword 是密码生成核心方法。
dart
void _generatePassword() {
String chars = '';
if (_useUppercase) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if (_useLowercase) chars += 'abcdefghijklmnopqrstuvwxyz';
if (_useNumbers) chars += '0123456789';
if (_useSymbols) chars += '!@#\$%^&*()_+-=[]{}|;:,.<>?';
if (chars.isEmpty) {
chars = 'abcdefghijklmnopqrstuvwxyz';
}
final random = math.Random.secure();
final password = List.generate(_length, (_) => chars[random.nextInt(chars.length)]).join();
_passwordController.text = password;
_calculateStrength(password);
}
这段代码先组合候选字符集,再用安全随机数从字符集中取字符。
5.2 四类字符
| 开关字段 | 字符集 | 示例 |
|---|---|---|
_useUppercase |
ABCDEFGHIJKLMNOPQRSTUVWXYZ |
A-Z |
_useLowercase |
abcdefghijklmnopqrstuvwxyz |
a-z |
_useNumbers |
0123456789 |
0-9 |
_useSymbols |
`!@#$%^&*()_±=\[\]{} | ;:,.<>?` |
四个开关都打开时,候选字符集覆盖大小写字母、数字和符号。
5.3 字符集兜底
当四个开关都关闭时,chars 会变成空字符串。此时如果继续调用 random.nextInt(chars.length),会因为长度为 0 而出错。因此代码设置了兜底字符集。
dart
if (chars.isEmpty) {
chars = 'abcdefghijklmnopqrstuvwxyz';
}
这样即使用户关闭所有开关,应用仍然能生成小写字母密码。
六、安全随机数生成
6.1 Random.secure
项目使用的是 math.Random.secure()。
dart
final random = math.Random.secure();
相比普通 Random(),Random.secure() 更适合密码、令牌等安全敏感场景。它表示使用平台提供的安全随机源。
6.2 List.generate
密码字符串通过 List.generate 构造。
dart
final password = List.generate(
_length,
(_) => chars[random.nextInt(chars.length)],
).join();
这段代码的含义是:
- 生成长度为
_length的列表。 - 每个位置随机选择一个字符。
- 使用
join()拼接成字符串。
6.3 长度范围
当前长度由滑块控制,范围是 4 到 32。
dart
Slider(
min: 4,
max: 32,
divisions: 28,
label: '$_length',
)
divisions: 28 对应 4 到 32 的整数步进。
七、强度评分逻辑
7.1 评分方法
_calculateStrength 根据密码长度和字符种类计算分数。
dart
void _calculateStrength(String password) {
int score = 0;
if (password.length >= 8) score++;
if (password.length >= 12) score++;
if (password.length >= 16) score++;
if (RegExp(r'[A-Z]').hasMatch(password)) score++;
if (RegExp(r'[a-z]').hasMatch(password)) score++;
if (RegExp(r'[0-9]').hasMatch(password)) score++;
if (RegExp(r'[!@#\$%^&*()_+\-=\[\]{}|;:,.<>?]').hasMatch(password)) score++;
setState(() {
_strength = score / 8;
if (_strength < 0.3) {
_strengthLabel = 'Weak';
} else if (_strength < 0.6) {
_strengthLabel = 'Medium';
} else if (_strength < 0.8) {
_strengthLabel = 'Strong';
} else {
_strengthLabel = 'Very Strong';
}
});
}
7.2 评分规则
| 条件 | 加分 |
|---|---|
| 长度 >= 8 | +1 |
| 长度 >= 12 | +1 |
| 长度 >= 16 | +1 |
| 包含大写字母 | +1 |
| 包含小写字母 | +1 |
| 包含数字 | +1 |
| 包含符号 | +1 |
代码最后使用 score / 8 作为进度值。由于当前最多累积 7 分,最高进度值为 0.875。
7.3 强度标签
_strength 范围 |
文案 |
|---|---|
< 0.3 |
Weak |
< 0.6 |
Medium |
< 0.8 |
Strong |
>= 0.8 |
Very Strong |
这是规则型强度提示,不是密码学熵计算。它适合给用户提供直观反馈。
7.4 强度颜色
颜色由 _getStrengthColor 决定。
dart
Color _getStrengthColor() {
if (_strength < 0.3) return Colors.red;
if (_strength < 0.6) return Colors.orange;
if (_strength < 0.8) return Colors.yellow.shade700;
return Colors.green;
}
| 强度 | 颜色 |
|---|---|
| Weak | 红色 |
| Medium | 橙色 |
| Strong | 黄色 |
| Very Strong | 绿色 |
八、密码展示卡片
8.1 顶部 Card
密码展示区使用带渐变背景的卡片。
dart
Card(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
gradient: LinearGradient(
colors: [Colors.blueGrey.shade100, Colors.blueGrey.shade50],
),
),
),
)
卡片使用蓝灰色渐变,与应用主题保持一致。
8.2 只读 TextField
密码结果显示在只读输入框中。
dart
TextField(
controller: _passwordController,
readOnly: true,
style: const TextStyle(
fontSize: 20,
fontFamily: 'monospace',
letterSpacing: 2,
),
decoration: InputDecoration(
border: InputBorder.none,
hintText: 'Generated password',
),
)
只读输入框的好处是既能展示文本,又能保持输入框的可选中文本语义。
8.3 等宽字体
密码使用等宽字体和字间距。
dart
fontFamily: 'monospace',
letterSpacing: 2,
这让字符更容易区分,尤其是数字、大小写字母和符号混合时。
九、刷新与复制交互
9.1 suffixIcon 区域
密码输入框右侧放置两个图标按钮。
dart
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: _generatePassword,
icon: const Icon(Icons.refresh),
),
IconButton(
onPressed: _copyToClipboard,
icon: const Icon(Icons.copy),
),
],
)
mainAxisSize: MainAxisSize.min 可以让图标区域只占用必要宽度。
9.2 重新生成
刷新按钮直接调用 _generatePassword。
dart
IconButton(
onPressed: _generatePassword,
icon: const Icon(Icons.refresh),
)
每次重新生成后,密码文本和强度状态都会同步更新。
9.3 复制到剪贴板
复制逻辑使用 Clipboard.setData。
dart
void _copyToClipboard() {
Clipboard.setData(ClipboardData(text: _passwordController.text));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Password copied to clipboard')),
);
}
复制完成后,页面通过 SnackBar 提示 Password copied to clipboard。
9.4 OpenHarmony 剪贴板验证
| 验证项 | 操作 | 预期 |
|---|---|---|
| 复制按钮 | 点击复制图标 | 显示 SnackBar |
| 剪贴板内容 | 粘贴到输入框或其他应用 | 内容为当前密码 |
| 重新生成后复制 | 点击刷新再复制 | 复制新密码 |
| 空文本保护 | 启动后直接复制 | 因初始化已生成密码,复制内容非空 |
十、强度条 UI
10.1 LinearProgressIndicator
强度条使用 LinearProgressIndicator。
dart
LinearProgressIndicator(
value: _strength,
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation(_getStrengthColor()),
minHeight: 8,
borderRadius: BorderRadius.circular(4),
)
value 使用 0 到 1 的进度值,颜色来自 _getStrengthColor()。
10.2 强度文案
强度文案与强度颜色保持一致。
dart
Text(
'Strength: $_strengthLabel',
style: TextStyle(
color: _getStrengthColor(),
fontWeight: FontWeight.bold,
),
)
用户能同时通过颜色、进度条和文字判断密码强度。
10.3 UI 状态联动
| 状态变化 | 触发源 | 页面变化 |
|---|---|---|
| 改变长度 | Slider | 密码重新生成,强度更新 |
| 关闭大写 | SwitchListTile | 字符集变化,强度可能下降 |
| 关闭数字 | SwitchListTile | 字符集变化,强度可能下降 |
| 点击刷新 | IconButton 或按钮 | 密码重新生成 |
十一、长度滑块实现
11.1 长度标题
滑块上方显示固定文案和当前长度。
dart
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Password Length', style: TextStyle(fontSize: 16)),
Text(
'$_length',
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
)
右侧数字会随着滑块变化同步更新。
11.2 Slider 配置
dart
Slider(
min: 4,
max: 32,
divisions: 28,
label: '$_length',
onChanged: (value) {
setState(() {
_length = value.toInt();
});
_generatePassword();
},
)
onChanged 中先更新长度,再生成新密码。
11.3 长度边界
| 配置 | 值 |
|---|---|
| 最小长度 | 4 |
| 最大长度 | 32 |
| 默认长度 | 12 |
| 步进数量 | 28 |
当前实现适合常见账号密码场景。更复杂场景可以把最大长度继续提高。
十二、字符集开关实现
12.1 开关区域
四个字符集开关放在同一张 Card 中。
dart
Card(
child: Column(
children: [
SwitchListTile(
title: const Text('Uppercase (A-Z)'),
value: _useUppercase,
onChanged: (value) {
setState(() {
_useUppercase = value;
});
_generatePassword();
},
),
// Lowercase、Numbers、Symbols
],
),
)
每个开关切换后都会重新生成密码。
12.2 大写字母开关
dart
SwitchListTile(
title: const Text('Uppercase (A-Z)'),
value: _useUppercase,
onChanged: (value) {
setState(() {
_useUppercase = value;
});
_generatePassword();
},
)
关闭后,候选字符集中不再追加 ABCDEFGHIJKLMNOPQRSTUVWXYZ。
12.3 小写字母开关
dart
SwitchListTile(
title: const Text('Lowercase (a-z)'),
value: _useLowercase,
onChanged: (value) {
setState(() {
_useLowercase = value;
});
_generatePassword();
},
)
小写字母也是兜底字符集。当四个开关都关闭时,代码仍会用小写字母生成密码。
12.4 数字和符号开关
数字和符号开关分别影响候选字符集。
dart
SwitchListTile(
title: const Text('Numbers (0-9)'),
value: _useNumbers,
onChanged: (value) {
setState(() {
_useNumbers = value;
});
_generatePassword();
},
)
dart
SwitchListTile(
title: const Text('Symbols (!@#\$%)'),
value: _useSymbols,
onChanged: (value) {
setState(() {
_useSymbols = value;
});
_generatePassword();
},
)
符号开关标题只展示部分符号,实际字符集包含更多符号。
十三、页面布局与滚动
13.1 Scaffold 结构
页面使用 Scaffold 提供 AppBar 和 Body。
dart
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 密码卡片、长度滑块、开关、按钮
],
),
),
);
13.2 SingleChildScrollView
内容区域使用 SingleChildScrollView。
dart
SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [],
),
)
在 OpenHarmony 小屏设备上,滚动容器可以保证底部按钮和开关区域可访问。
13.3 生成按钮
页面底部还有一个完整宽度的重新生成按钮。
dart
ElevatedButton.icon(
onPressed: _generatePassword,
icon: const Icon(Icons.refresh),
label: const Text('Generate New Password'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.all(16),
backgroundColor: Colors.blueGrey,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
)
这个按钮与顶部刷新图标调用同一个生成方法。
十四、OpenHarmony 适配要点
14.1 基础组件验证
当前项目使用的 Flutter 组件较多。
| 组件 | 作用 | OpenHarmony 关注点 |
|---|---|---|
MaterialApp |
应用根组件 | 首屏加载 |
Scaffold |
页面结构 | AppBar 与 Body |
TextField |
密码展示 | 只读状态、文本显示 |
IconButton |
刷新和复制 | 点击响应 |
LinearProgressIndicator |
强度条 | 进度和颜色 |
Slider |
长度控制 | 拖动和步进 |
SwitchListTile |
字符集开关 | 开关状态 |
ElevatedButton.icon |
重新生成按钮 | 点击响应 |
SnackBar |
复制提示 | 显示位置和停留时间 |
14.2 剪贴板能力
复制密码依赖 Clipboard.setData。
dart
Clipboard.setData(ClipboardData(text: _passwordController.text));
OpenHarmony 侧需要验证复制后能否在其他输入位置粘贴同一字符串。如果平台权限或剪贴板桥接存在问题,最容易在这一步暴露。
14.3 滑块交互
滑块适配需要观察:
- 拖动是否流畅。
- 数值是否从 4 到 32。
- 每次拖动是否生成新密码。
- 顶部长度数字是否同步。
- 强度进度条是否同步变化。
14.4 开关交互
开关适配需要观察:
- 点击 Switch 是否能改变状态。
- 切换后密码是否立即重新生成。
- 四个开关都关闭时是否仍能生成小写字母密码。
- 强度标签是否随字符集变化。
OpenHarmony 验证不能只看页面能打开。密码生成器必须覆盖刷新、复制、滑块、开关、强度条和 SnackBar 完整链路。
十五、测试与验证
15.1 初始页面测试
Widget 测试可以验证首屏结构。
dart
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('password generator shows initial widgets', (tester) async {
await tester.pumpWidget(const PasswordGeneratorApp());
expect(find.text('Password Generator'), findsWidgets);
expect(find.text('Password Length'), findsOneWidget);
expect(find.text('Uppercase (A-Z)'), findsOneWidget);
expect(find.text('Lowercase (a-z)'), findsOneWidget);
expect(find.text('Numbers (0-9)'), findsOneWidget);
expect(find.text('Generate New Password'), findsOneWidget);
});
}
这类测试不依赖具体随机密码,稳定性较好。
15.2 长度边界测试
可以抽取密码生成函数后测试长度。
dart
String generatePassword({
required int length,
required String chars,
required math.Random random,
}) {
return List.generate(length, (_) => chars[random.nextInt(chars.length)]).join();
}
测试示例:
dart
void main() {
test('generated password length equals input length', () {
final password = generatePassword(
length: 12,
chars: 'abcdefghijklmnopqrstuvwxyz',
random: math.Random(1),
);
expect(password.length, 12);
});
}
15.3 强度评分测试
强度评分也可以抽成纯函数。
dart
int calculatePasswordScore(String password) {
int score = 0;
if (password.length >= 8) score++;
if (password.length >= 12) score++;
if (password.length >= 16) score++;
if (RegExp(r'[A-Z]').hasMatch(password)) score++;
if (RegExp(r'[a-z]').hasMatch(password)) score++;
if (RegExp(r'[0-9]').hasMatch(password)) score++;
if (RegExp(r'[!@#\$%^&*()_+\-=\[\]{}|;:,.<>?]').hasMatch(password)) score++;
return score;
}
测试示例:
dart
void main() {
test('score increases with character variety', () {
expect(calculatePasswordScore('abcdefgh'), 2);
expect(calculatePasswordScore('Abcdef12!xyz'), greaterThan(4));
});
}
15.4 手工验证矩阵
| 场景 | 操作 | 预期 |
|---|---|---|
| 首次打开 | 启动应用 | 自动生成默认密码 |
| 刷新密码 | 点击刷新图标 | 密码变化 |
| 复制密码 | 点击复制图标 | 显示复制成功提示 |
| 调整长度 | 拖动 Slider | 密码长度跟随变化 |
| 关闭数字 | 关闭 Numbers | 新密码不包含数字 |
| 关闭符号 | 关闭 Symbols | 新密码不包含符号 |
| 全部关闭 | 关闭四个开关 | 仍生成小写字母密码 |
| 底部按钮 | 点击 Generate New Password | 密码重新生成 |
十六、常见问题与优化建议
16.1 为什么使用 Random.secure
密码属于安全敏感内容。项目使用 math.Random.secure() 生成随机索引,比普通伪随机数更符合密码生成场景。
dart
final random = math.Random.secure();
如果只是小游戏或普通抽样,Random() 足够;如果生成密码、令牌或密钥,应优先考虑安全随机源。
16.2 为什么要有字符集兜底
四个开关都关闭时,候选字符集为空。没有兜底会导致 nextInt(0) 出错。
dart
if (chars.isEmpty) {
chars = 'abcdefghijklmnopqrstuvwxyz';
}
当前兜底策略让应用始终可用。
16.3 为什么强度最高除以 8
当前评分最多可以得到 7 分,但代码使用 score / 8。
dart
_strength = score / 8;
这会让进度条最高显示到 0.875。由于 0.875 >= 0.8,仍然可以进入 Very Strong。如果希望进度条满格,可以改为 score / 7。
16.4 如何保证每类字符至少出现一次
当前算法是从组合后的字符集中随机抽取,每类字符"可能出现",但不保证一定出现。如果需要保证勾选的字符类型都至少出现一次,可以先为每个启用的字符集抽一个字符,再打乱结果。
dart
final requiredChars = <String>[];
if (_useUppercase) requiredChars.add('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
if (_useLowercase) requiredChars.add('abcdefghijklmnopqrstuvwxyz');
if (_useNumbers) requiredChars.add('0123456789');
if (_useSymbols) requiredChars.add('!@#\$%^&*()_+-=[]{}|;:,.<>?');
然后从每个集合中取一个字符,再补足剩余长度。
16.5 如何增加密码可读性
可以移除容易混淆的字符,例如 0、O、1、l、I。
dart
const readableLowercase = 'abcdefghijkmnopqrstuvwxyz';
const readableUppercase = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
const readableNumbers = '23456789';
这适合需要人工输入密码的场景。
16.6 如何增加复制安全提示
复制后可以在一段时间后提醒用户清理剪贴板。当前项目只负责复制并提示成功。
dart
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Password copied to clipboard')),
);
是否自动清空剪贴板取决于平台能力和应用策略。
十七、工程扩展方向
17.1 抽取生成逻辑
可以把密码生成逻辑抽成独立工具函数。
dart
String buildPassword({
required int length,
required String chars,
required math.Random random,
}) {
return List.generate(length, (_) => chars[random.nextInt(chars.length)]).join();
}
抽取后更容易写单元测试,也便于后续复用。
17.2 抽取强度模型
强度结果可以建模为对象。
dart
class PasswordStrength {
final double value;
final String label;
const PasswordStrength({
required this.value,
required this.label,
});
}
这样 UI 不需要关心评分细节,只负责展示。
17.3 增加生成历史
可以保存最近生成的密码摘要或生成配置。出于安全考虑,不建议直接长期保存明文密码。
dart
class PasswordGenerationConfig {
final int length;
final bool uppercase;
final bool lowercase;
final bool numbers;
final bool symbols;
const PasswordGenerationConfig({
required this.length,
required this.uppercase,
required this.lowercase,
required this.numbers,
required this.symbols,
});
}
保留配置比保留明文密码更稳妥。
17.4 增加更多选项
后续可以扩展:
- 排除相似字符。
- 强制每类字符至少出现一次。
- 自定义符号集合。
- 批量生成多条密码。
- 一键复制指定序号密码。
- 根据用途保存配置模板。
这些能力都可以基于当前状态结构继续演进。
十八、相关链接与延伸阅读
18.1 Flutter 官方资料
| 资料 | 链接 |
|---|---|
| Flutter 官方文档 | https://docs.flutter.dev/ |
| Flutter API 文档 | https://api.flutter.dev/ |
| TextField | https://api.flutter.dev/flutter/material/TextField-class.html |
| Slider | https://api.flutter.dev/flutter/material/Slider-class.html |
| SwitchListTile | https://api.flutter.dev/flutter/material/SwitchListTile-class.html |
| LinearProgressIndicator | https://api.flutter.dev/flutter/material/LinearProgressIndicator-class.html |
| SnackBar | https://api.flutter.dev/flutter/material/SnackBar-class.html |
18.2 Dart 与 OpenHarmony 资料
| 资料 | 链接 |
|---|---|
| Dart 官方文档 | https://dart.dev/ |
| Dart API 文档 | https://api.dart.dev/ |
| Random API | https://api.dart.dev/stable/dart-math/Random-class.html |
| Clipboard API | https://api.flutter.dev/flutter/services/Clipboard-class.html |
| RegExp API | https://api.dart.dev/stable/dart-core/RegExp-class.html |
| pub.dev | https://pub.dev/ |
| OpenHarmony docs | https://github.com/openharmony/docs |
| 开源鸿蒙跨平台社区 | https://openharmonycrossplatform.csdn.net |
总结
password_generator 是一个完整的 Flutter 本地密码生成器案例。它通过 math.Random.secure() 生成随机字符索引,通过 _length 控制密码长度,通过四个布尔字段控制候选字符集,通过 TextEditingController 展示生成结果,通过 _calculateStrength 和 _getStrengthColor 展示规则型强度,再通过 Clipboard.setData 和 SnackBar 完成复制反馈。
从 OpenHarmony 适配角度看,这个项目适合验证 Flutter 基础组件、只读输入框、滑块、开关、图标按钮、进度条、剪贴板和 SnackBar。由于业务逻辑集中在 Dart 和 Flutter SDK 中,排查路径也比较清晰:密码不变时看生成逻辑,强度异常时看正则和评分,复制失败时看剪贴板桥接,交互异常时看对应 Widget 的事件回调。
掌握这个项目后,可以继续扩展可读字符集、强制字符类型覆盖、批量密码生成、配置模板和更细致的强度模型,让它从演示工具逐步变成更实用的跨平台密码生成应用。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源: