从登录到数据管理:HarmonyOS全栈应用开发实战

在移动应用开发领域,用户系统是最基础也最具代表性的功能模块之一。一个完整的用户系统不仅需要友好的界面交互,更需要可靠的数据存储和管理机制。本文将带你使用HarmonyOS的ArkUI框架和关系型数据库,从零构建一个包含登录、注册和用户管理功能的完整应用。

1. 环境准备与项目初始化

开发HarmonyOS应用首先需要配置好开发环境。最新版本的DevEco Studio提供了完善的工具链支持,包括代码编辑、调试和模拟器管理等功能。安装完成后,我们可以创建一个新的"UserManager"项目,选择"Empty Ability"模板。

项目初始化后,目录结构如下:

UserManager
├── entry
│   ├── src/main
│   │   ├── ets
│   │   │   ├── Application
│   │   │   ├── entryability
│   │   │   └── pages
│   │   ├── resources
│   │   └── module.json5
└── oh-package.json5

在开始编码前,我们需要在oh-package.json5中添加数据库依赖:

"dependencies": {
  "@kit.ArkData": "^1.0.0"
}

2. 数据库设计与实现

关系型数据库是用户系统的核心,HarmonyOS提供了@kit.ArkData模块来操作SQLite数据库。我们首先设计用户表结构:

字段名类型说明
idINTEGER主键,自增长
usernameTEXT用户名,非空
passwordTEXT密码,非空
mobileTEXT手机号

创建数据库工具类DBTools.ets,封装常用操作:

import { relationalStore } from '@kit.ArkData';

export default class DBTools {
  private static rdbStore: relationalStore.RdbStore;
  
  // 初始化数据库
  static async initDB(context: Context) {
    const config = {
      name: 'user_db.db',
      securityLevel: relationalStore.SecurityLevel.S3
    };
    
    this.rdbStore = await relationalStore.getRdbStore(context, config);
    await this.rdbStore.executeSql(
      'CREATE TABLE IF NOT EXISTS USER(' +
      'ID INTEGER PRIMARY KEY AUTOINCREMENT, ' +
      'USERNAME TEXT NOT NULL, ' +
      'PASSWORD TEXT NOT NULL, ' +
      'MOBILE TEXT)'
    );
  }
  
  // 用户注册
  static async register(user: {username: string, password: string}) {
    const valueBucket = {
      'username': user.username,
      'password': user.password
    };
    return this.rdbStore.insert('USER', valueBucket);
  }
  
  // 用户登录验证
  static async login(username: string, password: string) {
    const predicates = new relationalStore.RdbPredicates('USER');
    predicates.equalTo('username', username)
             .and()
             .equalTo('password', password);
    return this.rdbStore.query(predicates);
  }
}

3. 页面设计与实现

3.1 登录页面

登录页面是用户系统的入口,我们使用TextInput组件接收用户输入,Button组件触发登录操作:

@Entry
@Component
struct LoginPage {
  @State username: string = '';
  @State password: string = '';
  
  build() {
    Column() {
      Text('用户登录').fontSize(26).margin({bottom: 30})
      
      TextInput({placeholder: '请输入用户名'})
        .width('80%')
        .onChange((value) => {
          this.username = value;
        })
      
      TextInput({placeholder: '请输入密码'})
        .type(InputType.Password)
        .width('80%')
        .onChange((value) => {
          this.password = value;
        })
      
      Button('登录', {type: ButtonType.Capsule})
        .width('80%')
        .margin({top: 20})
        .onClick(async () => {
          const result = await DBTools.login(this.username, this.password);
          if (result.rowCount > 0) {
            router.pushUrl({url: 'pages/UserList'});
          } else {
            promptAction.showToast({message: '用户名或密码错误'});
          }
        })
      
      Button('去注册', {type: ButtonType.Text})
        .margin({top: 10})
        .onClick(() => {
          router.pushUrl({url: 'pages/Register'});
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

3.2 注册页面

注册页面结构与登录类似,但增加了密码确认和手机号字段:

@Entry
@Component
struct RegisterPage {
  @State username: string = '';
  @State password: string = '';
  @State confirmPwd: string = '';
  @State mobile: string = '';
  
  build() {
    Column() {
      Text('用户注册').fontSize(26).margin({bottom: 30})
      
      // 用户名输入框
      TextInput({placeholder: '设置用户名'})
        .width('80%')
        .onChange((value) => {
          this.username = value;
        })
      
      // 密码输入框
      TextInput({placeholder: '设置密码'})
        .type(InputType.Password)
        .width('80%')
        .onChange((value) => {
          this.password = value;
        })
      
      // 确认密码
      TextInput({placeholder: '确认密码'})
        .type(InputType.Password)
        .width('80%')
        .onChange((value) => {
          this.confirmPwd = value;
        })
      
      // 手机号
      TextInput({placeholder: '手机号'})
        .width('80%')
        .onChange((value) => {
          this.mobile = value;
        })
      
      Button('注册', {type: ButtonType.Capsule})
        .width('80%')
        .margin({top: 20})
        .onClick(async () => {
          if (this.password !== this.confirmPwd) {
            promptAction.showToast({message: '两次密码不一致'});
            return;
          }
          
          try {
            await DBTools.register({
              username: this.username,
              password: this.password
            });
            promptAction.showToast({message: '注册成功'});
            router.back();
          } catch (e) {
            promptAction.showToast({message: '注册失败'});
          }
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

3.3 用户列表页面

登录成功后展示用户列表,使用Grid组件实现卡片式布局:

@Entry
@Component
struct UserListPage {
  @State users: Array<{
    id: number,
    username: string,
    mobile: string
  }> = [];
  
  aboutToAppear() {
    this.loadUsers();
  }
  
  async loadUsers() {
    const predicates = new relationalStore.RdbPredicates('USER');
    const result = await DBTools.rdbStore.query(predicates, ['id', 'username', 'mobile']);
    
    this.users = [];
    while (result.goToNextRow()) {
      this.users.push({
        id: result.getLong(result.getColumnIndex('id')),
        username: result.getString(result.getColumnIndex('username')),
        mobile: result.getString(result.getColumnIndex('mobile')) || '未设置'
      });
    }
  }
  
  build() {
    Column() {
      Text('用户列表').fontSize(26).margin({bottom: 20})
      
      Grid() {
        ForEach(this.users, (user) => {
          GridItem() {
            Column() {
              Text(user.username).fontSize(18)
              Text(user.mobile).fontSize(14).margin({top: 5})
            }
            .padding(10)
            .width('90%')
            .borderRadius(10)
            .backgroundColor(Color.White)
          }
        })
      }
      .columnsTemplate('1fr 1fr')
      .rowsTemplate('1fr 1fr')
      .height('70%')
      .width('90%')
      
      Button('退出登录', {type: ButtonType.Capsule})
        .width('80%')
        .margin({top: 20})
        .onClick(() => {
          router.back();
        })
    }
    .width('100%')
    .height('100%')
    .padding(10)
    .backgroundColor('#f5f5f5')
  }
}

4. 功能优化与扩展

4.1 数据加密存储

用户密码应该加密存储,我们可以使用HarmonyOS的安全子系统提供的加密能力:

import { cryptoFramework } from '@kit.CryptoArchitectureKit';

async function encryptPassword(password: string) {
  const cipher = cryptoFramework.createCipher('AES256|ECB|PKCS7');
  await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, 
    await cryptoFramework.createSymKeyGenerator('AES256').generateSymKey());
  
  const input = { data: stringToUint8Array(password) };
  const output = await cipher.doFinal(input);
  return uint8ArrayToString(output.data);
}

4.2 表单验证

在注册和登录时增加表单验证逻辑:

function validateRegister(user: {
  username: string,
  password: string,
  confirmPwd: string,
  mobile?: string
}) {
  const errors = [];
  
  if (!user.username || user.username.length < 4) {
    errors.push('用户名至少4个字符');
  }
  
  if (!user.password || user.password.length < 6) {
    errors.push('密码至少6个字符');
  }
  
  if (user.password !== user.confirmPwd) {
    errors.push('两次密码不一致');
  }
  
  if (user.mobile && !/^1[3-9]\d{9}$/.test(user.mobile)) {
    errors.push('手机号格式不正确');
  }
  
  return errors;
}

4.3 记住登录状态

使用Preferences实现记住登录状态功能:

import { preferences } from '@kit.ArkData';

const PREFERENCES_NAME = 'user_preferences';

async function saveLoginState(username: string) {
  const prefs = await preferences.getPreferences(getContext(), PREFERENCES_NAME);
  await prefs.put('lastLoginUser', username);
  await prefs.flush();
}

async function getLastLoginUser() {
  const prefs = await preferences.getPreferences(getContext(), PREFERENCES_NAME);
  return prefs.get('lastLoginUser', '');
}

5. 项目构建与测试

完成开发后,我们可以通过DevEco Studio的模拟器进行测试:

  1. 在DevEco Studio工具栏选择"Tools > Device Manager"
  2. 创建或启动一个HarmonyOS模拟器
  3. 点击运行按钮,选择目标设备

测试流程建议:

  1. 首次启动应显示登录页面
  2. 点击"去注册"跳转到注册页面
  3. 注册新用户后自动返回登录页面
  4. 使用新注册的用户登录
  5. 验证用户列表是否正确显示

在开发过程中,可以使用hilog输出调试信息:

import { hilog } from '@kit.PerformanceAnalysisKit';

hilog.info(0x0000, 'UserManager', 'User login: %{public}s', username);
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐