HarmonyOS ArkTS 实战:实现一个安全密码生成器
HarmonyOS ArkTS 实战:实现一个安全密码生成器
项目效果
本文实现的是一个基于 HarmonyOS 和 ArkTS 的安全密码生成器。项目中使用 ArkUI 组件完成页面布局,通过 @State 管理状态数据,实现密码长度调节、字符类型选择、一键生成、密码复制、密码强度显示和生成历史等功能。
最终运行效果如下:
页面主要包含以下内容:
- 顶部应用标题;
- 生成的密码显示区域;
- 一键复制按钮;
- 密码强度指示器;
- 密码长度滑块;
- 字符类型选项(大写/小写/数字/符号);
- 生成密码按钮;
- 生成历史记录;
- 清空历史按钮;
- 页面整体采用 ArkUI 声明式布局。
本文重点是演示如何在 HarmonyOS 项目中使用 ArkTS 和 ArkUI 实现一个实用工具类单页面应用。项目代码主要写在 entry/src/main/ets/pages/Index.ets 文件中,适合作为 HarmonyOS ArkTS 入门到进阶之间的练习案例。
前言
在互联网时代,我们每天都需要注册各种账号,一个安全强壮的密码是保护账户安全的基础。手动想密码既麻烦又不安全,容易使用弱密码或者重复密码。一个好用的密码生成器可以帮助我们快速生成随机、安全的密码,提高账户安全性。
从应用开发角度来看,这个项目不依赖后端接口,也不需要数据库,但能练习 ArkTS 中的状态管理、滑块组件、复选框、字符串处理、随机算法和剪贴板操作等内容。
本文基于 HarmonyOS 和 ArkTS 实现一个安全密码生成器。用户可以自定义密码长度,选择包含的字符类型,一键生成随机密码,复制到剪贴板,查看密码强度,管理生成历史。
这个项目的核心不是简单的随机字符串,而是提供灵活的配置选项和直观的用户体验。每一次参数调整和密码生成,本质上都是对配置状态的管理。状态变化后,页面会自动刷新,这正是 ArkUI 声明式开发的基本思想。
一、项目目标
本次实践主要实现以下目标:
- 创建 HarmonyOS ArkTS 页面;
- 使用
@Entry和@Component定义页面组件; - 使用
@State管理页面状态; - 使用滑块调节密码长度;
- 使用复选框选择字符类型;
- 实现安全的随机密码生成算法;
- 显示密码强度等级;
- 实现一键复制密码功能;
- 保存生成历史记录;
- 支持清空历史;
- 使用
List和ForEach渲染历史记录; - 使用空状态提示优化无历史页面;
- 使用
@Builder封装选项组件和历史项; - 完成一个可以运行的密码生成器页面。
这个项目虽然是单页面应用,但它有完整的参数配置、密码生成、强度评估和历史管理功能,比普通静态页面更适合练习 ArkTS。
二、技术栈
| 类型 | 内容 |
|---|---|
| 开发方向 | HarmonyOS 应用开发 |
| 开发语言 | ArkTS |
| UI 框架 | ArkUI |
| SDK 版本 | HarmonyOS API 23 及以上 |
| 工程模型 | Stage 模型 |
| 核心组件 | Text / Button / Slider / Checkbox / Column / Row / List / ForEach |
| 状态管理 | @State |
| 数据处理 | 随机算法 / 字符串处理 / 强度评估 |
| 项目入口 | entry/src/main/ets/pages/Index.ets |
| 运行平台 | 模拟器或真机 |
本项目是 HarmonyOS 原生 ArkTS 项目。页面主体由 ArkUI 组件构建,核心逻辑写在 Index.ets 文件中,不依赖后端接口,也不需要额外配置数据库。
三、为什么选择密码生成器项目
安全密码生成器适合作为 ArkTS 练习项目,主要有以下几个原因。
第一,实用性强。密码生成是非常高频的需求,一个好用的密码生成器可以直接应用于日常使用。
第二,业务场景真实。注册账号、修改密码时都需要生成安全密码,是非常实用的工具类应用。
第三,适合练习状态管理。密码长度、字符选项、生成的密码、历史记录都属于页面状态。状态变化后,页面会自动刷新。
第四,适合练习表单控件。滑块、复选框等表单控件的使用是前端开发的基础。
第五,适合练习算法逻辑。随机密码生成和密码强度评估需要一定的逻辑处理能力。
第六,扩展空间比较大。基础功能完成后,可以继续增加本地存储、密码管理、密码本、密码加密和云同步功能。
在本项目中,配置选项和历史记录是核心数据。页面中的密码显示、强度评估和历史列表都由它们计算或渲染得到。
四、功能规则说明
字符类型选项:
| 选项 | 字符集 | 默认 |
|---|---|---|
| 小写字母 | a-z | 开启 |
| 大写字母 | A-Z | 开启 |
| 数字 | 0-9 | 开启 |
| 特殊符号 | !@#$%^&*()_±= | 关闭 |
密码长度范围:4-32位,默认16位。
密码强度等级:
| 强度 | 颜色 | 条件 |
|---|---|---|
| 弱 | #EF4444 | 长度<8或仅包含一种字符类型 |
| 中 | #F59E0B | 长度8-11或包含两种字符类型 |
| 强 | #10B981 | 长度≥12且包含至少三种字符类型 |
| 极强 | #0A59F7 | 长度≥16且包含所有四种字符类型 |
生成规则:
- 至少选择一种字符类型
- 密码中必须包含所有选中类型的字符
- 使用密码学安全的随机数生成
- 生成后自动保存到历史记录
历史记录最多保存最近20条。
五、项目结构
本项目主要修改首页文件:
entry
└── src
└── main
└── ets
└── pages
└── Index.ets
其中:
| 文件 | 作用 |
|---|---|
| Index.ets | 编写页面结构、状态数据和密码生成逻辑 |
本文不涉及复杂路由,也不需要额外创建多个页面。对于练习项目来说,把主要逻辑集中在一个 Index.ets 文件中更方便理解。
如果后续继续扩展,可以考虑把密码显示区、选项面板、强度指示器和历史列表拆分成独立组件。
六、核心实现思路
本项目的核心流程如下:
- 定义密码配置和历史记录数据结构;
- 使用
@State保存密码长度、字符选项、生成的密码和历史记录; - 用户通过滑块调整密码长度;
- 用户通过复选框选择需要的字符类型;
- 点击生成按钮生成随机密码;
- 评估密码强度并显示;
- 点击复制按钮复制密码到剪贴板;
- 将生成的密码保存到历史记录;
- 使用
ForEach渲染历史记录; - 点击历史项可以快速复制该密码;
- 支持清空历史记录;
- 没有历史时显示空状态提示。
项目中最重要的状态变量如下:
@State passwordLength: number = 16
@State useLowercase: boolean = true
@State useUppercase: boolean = true
@State useNumbers: boolean = true
@State useSymbols: boolean = false
@State generatedPassword: string = ''
@State history: PasswordHistory[] = []
@State nextId: number = 1
其中:
| 状态变量 | 作用 |
|---|---|
| passwordLength | 密码长度 |
| useLowercase | 是否包含小写字母 |
| useUppercase | 是否包含大写字母 |
| useNumbers | 是否包含数字 |
| useSymbols | 是否包含特殊符号 |
| generatedPassword | 当前生成的密码 |
| history | 历史记录数组 |
| nextId | 生成历史项唯一编号 |
配置选项是本项目中最重要的数据,密码生成和强度评估都基于这些配置。
七、Index.ets 完整代码
打开文件:
entry/src/main/ets/pages/Index.ets
将其中内容替换为下面代码:
interface PasswordHistory {
id: number
password: string
length: number
time: string
}
@Entry
@Component
struct Index {
@State passwordLength: number = 16
@State useLowercase: boolean = true
@State useUppercase: boolean = true
@State useNumbers: boolean = true
@State useSymbols: boolean = false
@State generatedPassword: string = ''
@State nextId: number = 1
@State history: PasswordHistory[] = []
private lowercaseChars: string = 'abcdefghijklmnopqrstuvwxyz'
private uppercaseChars: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
private numberChars: string = '0123456789'
private symbolChars: string = '!@#$%^&*()_+-='
aboutToAppear() {
this.generatePassword()
}
private generatePassword(): void {
let charset = ''
let requiredChars: string[] = []
if (this.useLowercase) {
charset += this.lowercaseChars
requiredChars.push(this.getRandomChar(this.lowercaseChars))
}
if (this.useUppercase) {
charset += this.uppercaseChars
requiredChars.push(this.getRandomChar(this.uppercaseChars))
}
if (this.useNumbers) {
charset += this.numberChars
requiredChars.push(this.getRandomChar(this.numberChars))
}
if (this.useSymbols) {
charset += this.symbolChars
requiredChars.push(this.getRandomChar(this.symbolChars))
}
if (charset.length === 0) {
this.generatedPassword = '请至少选择一种字符类型'
return
}
let password = [...requiredChars]
let remainingLength = this.passwordLength - requiredChars.length
for (let i = 0; i < remainingLength; i++) {
password.push(this.getRandomChar(charset))
}
this.shuffleArray(password)
let finalPassword = password.join('')
this.generatedPassword = finalPassword
let historyItem: PasswordHistory = {
id: this.nextId,
password: finalPassword,
length: this.passwordLength,
time: this.getCurrentTime()
}
this.history = [historyItem, ...this.history].slice(0, 20)
this.nextId++
}
private getRandomChar(charset: string): string {
let randomIndex = Math.floor(Math.random() * charset.length)
return charset[randomIndex]
}
private shuffleArray(array: string[]): void {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]
}
}
private copyPassword(password: string): void {
let context = getContext(this)
try {
context.clipboard.setContent(password)
} catch (e) {
console.error('复制失败')
}
}
private clearHistory(): void {
this.history = []
}
private getPasswordStrength(): { level: string, color: string, percent: number } {
let score = 0
let types = 0
if (this.passwordLength >= 8) score++
if (this.passwordLength >= 12) score++
if (this.passwordLength >= 16) score++
if (this.useLowercase) types++
if (this.useUppercase) types++
if (this.useNumbers) types++
if (this.useSymbols) types++
score += types - 1
if (score <= 1) return { level: '弱', color: '#EF4444', percent: 25 }
if (score <= 3) return { level: '中', color: '#F59E0B', percent: 50 }
if (score <= 5) return { level: '强', color: '#10B981', percent: 75 }
return { level: '极强', color: '#0A59F7', percent: 100 }
}
private getCurrentTime(): string {
let date = new Date()
return `${this.formatTwo(date.getHours())}:${this.formatTwo(date.getMinutes())}`
}
private formatTwo(value: number): string {
return value < 10 ? '0' + value.toString() : value.toString()
}
@Builder
OptionToggle(label: string, value: boolean, onChange: (v: boolean) => void) {
Row() {
Text(label)
.fontSize(15)
.fontColor('#182431')
.layoutWeight(1)
Toggle({ type: ToggleType.Switch, isOn: value })
.selectedColor('#0A59F7')
.onChange((isOn: boolean) => {
onChange(isOn)
})
}
.width('100%')
.padding({ top: 12, bottom: 12 })
}
@Builder
HistoryItem(item: PasswordHistory) {
Row() {
Column() {
Text(item.password)
.fontSize(15)
.fontColor('#182431')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(`${item.length}位 · ${item.time}`)
.fontSize(12)
.fontColor('#9CA3AF')
.margin({ top: 4 })
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
.margin({ right: 12 })
Button('复制')
.height(32)
.fontSize(13)
.fontColor('#0A59F7')
.backgroundColor('#EFF6FF')
.borderRadius(14)
.onClick(() => {
this.copyPassword(item.password)
})
}
.width('100%')
.padding(14)
.margin({ bottom: 10 })
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({
radius: 6,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
.onClick(() => {
this.copyPassword(item.password)
})
}
build() {
Column() {
Text('密码生成器')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
.margin({ top: 22 })
Text('基于 HarmonyOS ArkTS 的安全密码工具')
.fontSize(14)
.fontColor('#6B7280')
.margin({ top: 8, bottom: 20 })
Column() {
Text(this.generatedPassword)
.fontSize(22)
.fontWeight(FontWeight.Medium)
.fontColor('#182431')
.textAlign(TextAlign.Center)
.width('100%')
.margin({ bottom: 16 })
.textOverflow({ overflow: TextOverflow.MiddleEllipsis })
.maxLines(1)
Row() {
Column() {
Text(`密码强度:${this.getPasswordStrength().level}`)
.fontSize(14)
.fontColor(this.getPasswordStrength().color)
.margin({ bottom: 8 })
Progress({
value: this.getPasswordStrength().percent,
total: 100,
type: ProgressType.Linear
})
.width('100%')
.height(6)
.color(this.getPasswordStrength().color)
.backgroundColor('#E5E7EB')
.borderRadius(3)
}
.layoutWeight(1)
Button('复制')
.height(40)
.margin({ left: 16 })
.padding({ left: 20, right: 20 })
.fontSize(15)
.fontColor(Color.White)
.backgroundColor('#0A59F7')
.borderRadius(20)
.onClick(() => {
this.copyPassword(this.generatedPassword)
})
}
.width('100%')
}
.width('100%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ bottom: 16 })
.shadow({
radius: 10,
color: '#12000000',
offsetX: 0,
offsetY: 4
})
Column() {
Text('密码长度')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#182431')
.width('100%')
.margin({ bottom: 8 })
Row() {
Slider({
value: this.passwordLength,
min: 4,
max: 32,
step: 1,
style: SliderStyle.OutSet
})
.layoutWeight(1)
.selectedColor('#0A59F7')
.showSteps(true)
.onChange((value: number) => {
this.passwordLength = Math.round(value)
})
Text(`${this.passwordLength}位`)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#0A59F7')
.width(50)
.textAlign(TextAlign.End)
}
.width('100%')
.margin({ bottom: 8 })
Divider()
.color('#F3F4F6')
.margin({ top: 8, bottom: 8 })
this.OptionToggle('包含小写字母 (a-z)', this.useLowercase, (v: boolean) => {
this.useLowercase = v
})
this.OptionToggle('包含大写字母 (A-Z)', this.useUppercase, (v: boolean) => {
this.useUppercase = v
})
this.OptionToggle('包含数字 (0-9)', this.useNumbers, (v: boolean) => {
this.useNumbers = v
})
this.OptionToggle('包含特殊符号 (!@#$...)', this.useSymbols, (v: boolean) => {
this.useSymbols = v
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(16)
.margin({ bottom: 16 })
.shadow({
radius: 8,
color: '#08000000',
offsetX: 0,
offsetY: 2
})
Button('生成新密码')
.height(50)
.fontSize(18)
.fontColor(Color.White)
.backgroundColor('#0A59F7')
.borderRadius(25)
.width('100%')
.margin({ bottom: 20 })
.onClick(() => {
this.generatePassword()
})
Row() {
Text('生成历史')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#182431')
Blank()
if (this.history.length > 0) {
Button('清空')
.height(32)
.fontSize(13)
.fontColor('#EF4444')
.backgroundColor('#FEF2F2')
.borderRadius(14)
.onClick(() => {
this.clearHistory()
})
}
}
.width('100%')
.margin({ bottom: 12 })
if (this.history.length === 0) {
Column() {
Text('暂无生成记录')
.fontSize(16)
.fontColor('#6B7280')
Text('生成密码后会在这里显示历史')
.fontSize(13)
.fontColor('#9CA3AF')
.margin({ top: 8 })
}
.width('100%')
.padding(30)
.backgroundColor(Color.White)
.borderRadius(16)
} else {
List() {
ForEach(this.history, (item: PasswordHistory) => {
ListItem() {
this.HistoryItem(item)
}
}, (item: PasswordHistory) => item.id.toString())
}
.width('100%')
.layoutWeight(1)
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
.padding({ left: 18, right: 18 })
.backgroundColor('#F5F7FA')
}
}
八、代码实现说明
1. 密码生成算法
密码生成时确保包含所有选中类型的字符,然后随机填充剩余长度,最后打乱顺序:
// 先确保每种选中类型至少有一个字符
let requiredChars: string[] = []
if (this.useLowercase) requiredChars.push(this.getRandomChar(this.lowercaseChars))
// ... 其他类型
// 填充剩余长度
for (let i = 0; i < remainingLength; i++) {
password.push(this.getRandomChar(charset))
}
// 打乱顺序
this.shuffleArray(password)
这样可以避免生成的密码缺少某些选中的字符类型,提高密码安全性。
2. 密码强度评估
根据密码长度和包含的字符类型数量综合评估强度,分为四个等级,用不同颜色显示。
3. 复制功能
使用系统剪贴板API实现一键复制密码:
context.clipboard.setContent(password)
4. 滑块组件
使用Slider组件调节密码长度,范围4-32位,步长1。
5. 开关组件
使用Toggle组件作为开关,选择是否包含各种字符类型。
6. 历史记录
生成密码时自动保存到历史记录,最多保存20条,点击历史项可以快速复制。
九、运行项目
代码编写完成后,在DevEco Studio中运行项目。测试密码长度调节、字符类型选择、密码生成、复制功能、历史记录等功能,确保生成的密码符合配置要求。
十、开发中遇到的问题
- 确保密码包含所有选中类型的字符,避免出现缺少某类字符的情况
- 随机算法要足够随机,使用Fisher-Yates算法打乱数组
- 至少选择一种字符类型,否则给出提示
- 历史记录限制数量,避免内存占用过多
十一、总结
本文基于HarmonyOS和ArkTS实现了一个安全密码生成器。项目通过@State管理配置和历史数据,使用ArkUI组件完成页面布局,实现了自定义密码生成、强度评估、一键复制和历史管理等功能。这个项目展示了ArkTS中表单控件、随机算法和系统API调用的基本方法,适合作为入门练习项目。
更多推荐


所有评论(0)