码农日常:花一下午撸了个密码生成器,顺便学了鸿蒙开发

周末闲着没事,寻思着学点新东西。正好最近鸿蒙挺火的,就想着搞个实战项目练练手。目标很简单:一个密码生成器。别看功能不复杂,但五脏俱全,从UI到逻辑到交互,该有的都有。

这篇文章就当是我的开发笔记,记录一下整个过程。


一、为啥选密码生成器?

选项目就像选对象,得看对眼。

考虑过几个方向:

  • 记账App?太复杂,数据库那一套够喝一壶的
  • 天气预报?需要调API,网络请求还不太熟
  • 计算器?太简单,学不到啥东西

最后选定密码生成器,原因有三:

  1. 功能聚焦:核心就一个——生成随机密码
  2. 技术点全:状态管理、UI布局、用户交互都有
  3. 实用性强:自己也能用,不完全是玩具

而且我还有个私心:朋友的账号被盗了,原因就是密码太简单。所以这玩意儿还真有市场需求。


二、开发环境搭建

2.1 安装DevEco Studio

到华为开发者官网下载DevEco Studio,安装过程就不废话了,一路Next就行。

有个坑要提醒:一定要选对API版本

HarmonyOS NEXT是API 12+,别用老版本。我一开始选了API 10,结果发现好多新特性用不了,又重新创建项目。

2.2 创建项目

选择 Empty Ability 模板,这就是个空项目,啥都没有,正好从头开始写。

项目配置:

  • Project name: MyApplication
  • Bundle name: com.example.myapplication
  • Compile SDK: API 12
  • Model: Stage

2.3 项目结构初探

项目创建完成后,目录结构是这样的:

MyApplication/
├── AppScope/              # 全局配置
│   └── app.json5         # 应用配置
├── entry/                 # 主模块
│   └── src/main/
│       ├── ets/          # 源码目录
│       │   ├── entryability/
│       │   │   └── EntryAbility.ets  # 入口
│       │   └── pages/
│       │       └── Index.ets         # 主页面 ⭐
│       └── resources/     # 资源文件
└── build-profile.json5    # 构建配置

说实话,一开始看着挺懵的,一堆配置文件。但写着写着就明白了,其实就两个核心文件:

  1. EntryAbility.ets:应用入口,生命周期管理
  2. Index.ets:主页面,所有UI和逻辑都在这

三、UI设计:丑是丑了点,但能用

我不是设计师,审美也就那样。所以UI设计遵循一个原则:简洁、实用

3.1 布局结构

整体采用纵向布局,从上到下:

┌────────────────────────┐
│   🔐 密码生成器        │  标题
│   生成高强度随机密码    │
├────────────────────────┤
│   [密码显示区]         │  结果
│   [强度指示]           │
├────────────────────────┤
│   密码长度             │
│   [-] 12 [+]          │  长度控制
├────────────────────────┤
│   字符类型             │
│   A-Z      [开关]     │
│   a-z      [开关]     │  类型选择
│   0-9      [开关]     │
│   !@#$%    [开关]     │
├────────────────────────┤
│   [生成密码]           │  按钮
│   [复制密码]           │
└────────────────────────┘

3.2 标题实现

代码很简单:

Text('🔐 密码生成器')
  .fontSize(26)
  .fontWeight(FontWeight.Bold)
  .fontColor('#1C1C1E')
  .margin({ top: 30, bottom: 4 })

Text('生成高强度随机密码')
  .fontSize(14)
  .fontColor('#8E8E93')
  .margin({ bottom: 24 })

这里有个小技巧:emoji可以直接放在字符串里,会自动渲染成图标。🔐这个锁的emoji就挺合适的。

3.3 密码显示区

这是整个应用的核心视觉区域,我用卡片样式:

Column() {
  Text(this.password)
    .fontSize(20)
    .fontWeight(FontWeight.Bold)
    .textAlign(TextAlign.Center)
    .width('100%')

  // 强度指示条
  if (this.password !== '点击生成' && !this.password.startsWith('⚠️')) {
    Row() {
      Row() {
        Text(this.getStrengthText())
          .fontSize(12)
          .fontColor('#FFFFFF')
          .fontWeight(FontWeight.Bold)
      }
      .width(this.getStrengthWidth() + 20)
      .height(22)
      .backgroundColor(this.getStrengthColor())
      .borderRadius(11)
      .justifyContent(FlexAlign.Center)
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}
.width('100%')
.padding(20)
.backgroundColor('#F2F2F7')
.borderRadius(16)

注意那个条件渲染:if (this.password !== '点击生成' && !this.password.startsWith('⚠️'))

只有当密码是有效值时,才显示强度指示条。这个细节一开始没注意,结果"点击生成"几个字下面也跟着个强度条,看着很蠢。

3.4 长度调节

用两个圆形按钮加中间的数字显示:

Row() {
  Button('-')
    .type(ButtonType.Circle)
    .width(36)
    .height(36)
    .backgroundColor('#007AFF')
    .onClick(() => {
      if (this.passwordLength > 4) this.passwordLength--;
    })

  Text(`${this.passwordLength}`)
    .fontSize(28)
    .fontWeight(FontWeight.Bold)
    .width(60)

  Button('+')
    .type(ButtonType.Circle)
    .width(36)
    .height(36)
    .backgroundColor('#007AFF')
    .onClick(() => {
      if (this.passwordLength < 24) this.passwordLength++;
    })
}
.justifyContent(FlexAlign.Center)

这里有个交互逻辑:长度限制在4-24之间,不能无限加减。

3.5 字符类型选择

用Toggle组件实现开关效果:

Column({ space: 10 }) {
  Row() {
    Text('A-Z').fontSize(16)
    Blank()
    Toggle({ type: ToggleType.Switch, isOn: this.hasUpper })
      .selectedColor('#34C759')
      .onChange((value: boolean) => {
        this.hasUpper = value;
      })
  }.width('100%')

  // ... 其他类型类似
}
.padding(16)
.backgroundColor('#F2F2F7')
.borderRadius(16)

Blank()是个很有用的组件,它会自动填充剩余空间,把后面的元素推到右边。一开始我不知道这个组件,用了很多嵌套Row才实现同样的效果,代码又臭又长。

3.6 操作按钮

两个按钮,一个生成,一个复制:

Button('🔄 生成密码')
  .type(ButtonType.Capsule)
  .width(240)
  .height(52)
  .backgroundColor('#007AFF')
  .onClick(() => {
    this.generatePassword();
  })

Button('📋 复制密码')
  .type(ButtonType.Capsule)
  .width(240)
  .height(44)
  .backgroundColor('#34C759')
  .margin({ top: 10 })
  .onClick(() => {
    this.copyPassword();
  })

四、核心逻辑:算法其实很简单

4.1 密码生成算法

说穿了就三步:

  1. 把用户选择的字符类型拼成一个字符串(字符池)
  2. 随机从字符池中取字符
  3. 重复N次,得到N位密码

代码实现:

generatePassword(): void {
  // 第一步:组装字符池
  let chars: string = '';
  if (this.hasUpper) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  if (this.hasLower) chars += 'abcdefghijklmnopqrstuvwxyz';
  if (this.hasNumber) chars += '0123456789';
  if (this.hasSymbol) chars += '!@#$%^&*()_+-=[]{}|;:,.<>?';

  // 第二步:校验(重要!)
  if (chars.length === 0) {
    this.password = '⚠️ 请选择至少一种字符类型';
    return;
  }

  // 第三步:随机生成
  let result: string = '';
  for (let i: number = 0; i < this.passwordLength; i++) {
    const randomIndex: number = Math.floor(Math.random() * chars.length);
    result += chars[randomIndex];
  }
  this.password = result;
}

踩坑提醒:第二步的校验非常重要!

一开始我没加这个校验,结果用户把所有开关都关掉后,点击生成直接崩溃。因为字符池为空,chars.length是0,取模运算会出问题。

4.2 密码强度检测

这个规则是我自己定的,参考了一些安全规范:

强度 条件 颜色
长度≥16 且 包含≥3种字符类型 🟢 绿色
长度≥10 且 包含≥2种字符类型 🟠 橙色
其他情况 🔴 红色

代码:

getStrengthLevel(): string {
  let types: number = 0;
  if (this.hasUpper) types++;
  if (this.hasLower) types++;
  if (this.hasNumber) types++;
  if (this.hasSymbol) types++;

  if (this.passwordLength >= 16 && types >= 3) return 'strong';
  if (this.passwordLength >= 10 && types >= 2) return 'medium';
  return 'weak';
}

4.3 复制功能

这个功能其实有点"伪",因为模拟器的剪贴板不一定能用。但我还是做了个提示效果:

copyPassword(): void {
  if (this.password === '' || 
      this.password === '点击生成' || 
      this.password.startsWith('⚠️')) {
    return;
  }
  
  this.showCopyTip = true;
  
  // 2秒后隐藏提示
  const timer = setTimeout(() => {
    this.showCopyTip = false;
    clearTimeout(timer);
  }, 2000);
}

真要做剪贴板功能,需要用@ohos.pasteboard模块,这个在真机上才能正常工作。


五、状态管理:@State是核心

ArkTS的状态管理真的很方便,@State装饰器一加,变量变化时UI自动更新。

我的状态变量:

@State password: string = '点击生成';           // 生成的密码
@State passwordLength: number = 12;            // 密码长度
@State hasUpper: boolean = true;               // 大写开关
@State hasLower: boolean = true;               // 小写开关
@State hasNumber: boolean = true;              // 数字开关
@State hasSymbol: boolean = false;             // 符号开关
@State showCopyTip: boolean = false;           // 复制提示

这些变量一变化,UI立刻响应。比如点击Toggle开关,hasUppertrue变成false,对应的UI状态马上更新。

这就是声明式UI的好处:你只管改数据,UI自己会跟上


六、调试过程:踩坑无数

坑1:Toggle状态不同步

现象:Toggle开关显示的状态和实际值不一致。

原因onChange回调里忘了更新状态变量。

解决

Toggle({ type: ToggleType.Switch, isOn: this.hasUpper })
  .onChange((value: boolean) => {
    this.hasUpper = value;  // 必须加上这行
  })

坑2:强度指示条不显示

现象:生成密码后,强度指示条有时出现有时不出现。

原因:条件判断不完整。

解决:加上完整的判断条件:

if (this.password !== '点击生成' && !this.password.startsWith('⚠️')) {
  // 渲染强度指示条
}

坑3:按钮样式不一致

现象:两个按钮,一个圆角一个方形,看着很别扭。

原因:没有显式指定type属性。

解决

Button('生成密码')
  .type(ButtonType.Capsule)  // 统一用胶囊样式

坑4:字符池为空崩溃

现象:关闭所有开关后点击生成,程序崩溃。

原因:字符池为空,随机数生成失败。

解决:添加校验:

if (chars.length === 0) {
  this.password = '⚠️ 请选择至少一种字符类型';
  return;
}

七、运行效果展示

在这里插入图片描述

八、总结:学到了啥

技术层面

  1. ArkTS语法:和TypeScript基本一样,上手很快
  2. 声明式UI:改数据就完事了,UI自动更新
  3. 组件使用:Column、Row、Button、Toggle这些基础组件玩明白了
  4. 状态管理@State是核心,理解了响应式编程的思想

项目经验

  1. 从小项目开始:别一上来就想做大而全的应用
  2. 边做边学:遇到问题再查文档,效率更高
  3. 多写多练:看再多教程都不如自己动手写一遍
  4. 注意细节:空值校验、条件判断这些细节很容易被忽略

代码量

最后统计了一下,核心代码大概200行左右(Index.ets单文件)。对于一个完整的应用来说,这个量级刚刚好——既不太简单显得敷衍,也不太复杂让人望而却步。


九、后续改进方向

应用虽然完成了,但还有很多可以改进的地方:

  1. 密码历史:保存生成过的密码,方便查看
  2. 自定义符号集:让用户自己选择要包含的特殊字符
  3. 真随机数:用加密API生成更安全的随机数
  4. 暗黑模式:适配系统深色主题
  5. 密码规则预设:提供"银行级"、"网站级"等一键配置

这些功能留着以后慢慢迭代吧,先把基础打牢。


写在最后

一下午的时间,从零开始撸了个密码生成器。虽然功能简单,但麻雀虽小五脏俱全,整个开发流程都走了一遍。

最大的感受是:鸿蒙开发的门槛其实没想象中那么高。ArkTS语法和TypeScript几乎一样,会前端的同学上手很快。声明式UI也比传统的命令式UI好理解,数据驱动视图的思想很直观。

如果你也想学鸿蒙开发,建议:

  • 从小项目开始,别贪大
  • 多看官方文档,它是最权威的
  • 遇到问题多搜、多问
  • 坚持写代码,保持手感

希望这篇流水账能给你一些参考。有问题欢迎评论区交流!


开发时间:约4小时
代码行数:约200行
难度等级:⭐⭐(适合新手)

Logo

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

更多推荐