44岁被裁后用AI写鸿蒙App(3):AGC 认证 + 云函数 + 端到端加密实战

第二篇:用 AI Agent 做全栈鸿蒙应用------AGC 认证 + 云函数一条龙

上一篇44岁被裁后用AI写鸿蒙App(2):Hermes Agent 环境搭建实战被裁后我用 Hermes Agent + D - 掘金

一个人搞定一整套后端,零服务器运维成本。 Hermes Agent 帮我写完了大部分代码。

前言:独立开发者最怕的其实是后端

我做了二十年开发,很清楚一件事------写前端页面不难,难的是后端

一个 App 要上线,至少需要:

  • 用户注册登录(手机号/邮箱验证、会话管理)
  • 数据存储(用户信息、家庭关系、加密数据)
  • 后端业务逻辑(用户同步、密钥管理、家庭成员绑定)

传统做法:买云服务器 → 搭后端框架 → 写 API → 连数据库 → 部署运维。

两个人干一个月,一个人干......三个月起步。

但我没有三个月。而且我没有预算买服务器。

华为 AGC(AppGallery Connect)就是来解决这个问题的。它提供的服务体系中,我实际用到的是:

服务 作用 费用
AGC Auth@hw-agconnect/auth 手机号/邮箱/华为账号登录 免费
Cloud Function@kit.CloudFoundationKit 在华为服务器上跑后端逻辑 按调用计费,免费额度够用
CloudDB(云函数服务端) 数据库由云函数操作,客户端不直连 免费额度

关键点:我的项目中,客户端不直接操作 CloudDB。所有数据库操作都封装在云函数里,App 通过 cloudFunction.call() 调用。

这样做的原因很实际:安全(数据库操作在服务端不暴露给客户端)、简单(客户端只管调接口,不管 SQL)。


一、AGC Auth:用户登录

登录方式

my-parents-helper 支持三种登录方式:邮箱验证码、手机号验证码、华为账号一键登录。

老人最常用的是手机号验证码------发一条短信,输 6 位数字,完事。不需要记密码。

配置步骤

1. 在 AGC 控制台开启认证服务

登录 AppGallery Connect → 选择应用 → 构建 → 认证服务 → 启用需要的认证方式(手机号、邮箱、华为账号)。

2. 下载 agconnect-services.json

这个文件是 AGC 的身份凭证,包含了 App 的 AGC 配置信息。下载后放到项目的 AppScope/ 目录下。

3. 添加依赖

oh-package.json5 中添加:

json 复制代码
{
  "dependencies": {
    "@hw-agconnect/auth": "1.0.5"
  }
}

然后执行 ohpm install

4. 初始化 AGC(EntryAbility)

在应用的入口 Ability 中读取配置文件并初始化:

typescript 复制代码
// EntryAbility.ets
import auth from '@hw-agconnect/auth';

async onCreate(want, launchParam): Promise<void> {
  const value = await this.context.resourceManager.getRawFileContent('agconnect-services.json');
  const config = JSON.parse(value.toString());
  await auth.AGCInitializer.initialize(this.context, config);
}

实际登录代码

以手机号登录为例,流程是:请求验证码 → 用户输入 → 调 signIn 登录。

typescript 复制代码
// LoginPage.ets(简化)
import auth, { VerifyCodeAction } from '@hw-agconnect/auth';

// 1️⃣ 发送验证码
auth.requestVerifyCode({
  verifyCodeType: { kind: 'phone', phoneNumber: '138xxxx', countryCode: '86' },
  action: VerifyCodeAction.REGISTER_LOGIN,
  lang: 'zh_CN',
  sendInterval: 60
}).then(() => { /* 验证码已发送 */ });

// 2️⃣ 用户输入验证码后,调 signIn 登录
const result = await auth.signIn({
  autoCreateUser: true,
  credentialInfo: {
    kind: 'phone',
    countryCode: '86',
    phoneNumber: '138xxxx',
    verifyCode: '123456'   // 用户输入的验证码
  }
});
const agcUid: string = result.getUser().getUid();

邮箱登录同理,kind 改为 'email',参数相应调整:

typescript 复制代码
auth.requestVerifyCode({
  verifyCodeType: { kind: 'email', email: 'user@example.com' },
  action: VerifyCodeAction.REGISTER_LOGIN,
  lang: 'zh_CN',
  sendInterval: 60
});

const result = await auth.signIn({
  autoCreateUser: true,
  credentialInfo: {
    kind: 'email',
    email: 'user@example.com',
    verifyCode: '123456'
  }
});

登录成功后的关键一步:桥接到云服务

登录成功后,需要把 AGC Auth 的认证凭据桥接到云函数/云存储服务。这一步很多人会漏掉:

typescript 复制代码
import { cloudCommon } from '@kit.CloudFoundationKit';
import auth from '@hw-agconnect/auth';

let authProvider = auth.getAuthProvider();
cloudCommon.init({
  region: cloudCommon.CloudRegion.CHINA,
  authProvider: authProvider,
  storageOptions: {
    mode: request.agent.Mode.BACKGROUND,
    network: request.agent.Network.ANY
  }
});

不调用 cloudCommon.init,后续的云函数调用可能会因为认证缺失而失败。

会话恢复(无感重登)

App 关闭后重新打开,如果 AGC 会话还没过期,应该让用户无感恢复登录:

typescript 复制代码
// 检查是否有已有 AGC 会话
const currentUser = await auth.getCurrentUser();
if (currentUser) {
  await currentUser.getToken(true);
  initCloudCommonAfterLogin();
  // 同步用户数据 → 自动跳转
}

可选的隐私增强:生物识别锁

这是针对华为账号登录用户的可选增强功能。开启后,每次打开 App 都要通过人脸或指纹验证才能使用------手机丢了也不怕别人看到家庭信息。

实现方式:使用 @kit.UserAuthenticationKituserAuth 模块。

用户在「设置」中开启生物识别锁后,MainPage 启动时弹出认证窗口:

typescript 复制代码
import { userAuth } from '@kit.UserAuthenticationKit';

private async requireBiometricAuth(): Promise<boolean> {
  const authParam: userAuth.AuthParam = {
    challenge: generateLoginChallenge(),  // 随机 challenge,防重放
    authType: [
      userAuth.UserAuthType.PIN,
      userAuth.UserAuthType.FINGERPRINT,
      userAuth.UserAuthType.FACE
    ],
    authTrustLevel: userAuth.AuthTrustLevel.ATL3,
  };
  const widgetParam: userAuth.WidgetParam = {
    title: '请验证身份以登录'
  };
  
  return new Promise((resolve, reject) => {
    const instance = userAuth.getUserAuthInstance(authParam, widgetParam);
    instance.on('result', {
      onResult: (result) => {
        instance.off('result');
        resolve(result.result === userAuth.UserAuthResultCode.SUCCESS);
      }
    });
    instance.start();
    // 30 秒超时
    setTimeout(() => { reject(new Error('认证超时')); }, 30000);
  });
}

设计原则:这是"可选增强",不是强制。老人如果觉得每次刷脸麻烦,可以关掉。它跟 AGC Auth 的登录是两层独立的认证------AGC 管"你是谁",生物识别管"是不是你本人在用这个手机"。


二、云函数:真正的后端逻辑

架构模式:App → 云函数 → CloudDB

很多独立开发者会有一个误区:以为云数据库 SDK 可以直接在客户端调用。

我的做法是所有数据库操作都走云函数 。客户端只调 cloudFunction.call(),增删改查由云函数在服务端执行。

这样做的原因:

1. 业务逻辑可以随时更新,不需要发版

独立开发者最怕什么?App 已经上架了,突然发现后端逻辑有个 bug,或者想加一个功能。如果你把数据库操作写在客户端代码里:

  • 修复 bug → 改客户端代码 → 提审 → 等 1-7 天审核 → 用户更新
  • 加个功能 → 同上

如果写在云函数里:

  • 修复 bug → 在 AGC 控制台改云函数代码 → 保存 → 立即生效
  • 加个功能 → 同上

用户无感知。不需要他们更新 App,不需要经过应用市场审核。对一个人维护的项目来说,这个优势太大了。

2. 敏感数据和业务逻辑不被逆向

这是安全层面的考虑。鸿蒙 App 发布后,hap 包是可以被反编译的。如果你把 CloudDB 的查询语句、加密逻辑、密钥分配规则写在客户端 JavaScript 里,同行拿到你的安装包,拆开一看,什么业务逻辑全暴露了。

但云函数的代码跑在华为的服务器上,客户端只传参数、收结果,中间的业务逻辑黑盒不可见。像家族密钥分配这种核心逻辑,放在云函数里是最基本的安全底线。

3. 参数校验和权限控制不可绕过

客户端发来的请求可以被篡改。如果有人抓包改了请求参数(比如把 role: 'child' 改成 role: 'admin'),直接操作数据库的 App 可能就中招了。

走云函数的话,服务端代码可以做二次校验------确认调用者身份、检查权限、验证参数合法性------这些是客户端代码无法绕过的。

对比项 客户端直连 CloudDB 通过云函数(我的方案)
更新功能 ❌ 必须发版+审核 ✅ 改云函数,立即生效
代码安全 ❌ 可被反编译 ✅ 服务端不可见
权限控制 ❌ 依赖客户端传参 ✅ 服务端二次校验

云函数调用封装

先封装一个通用的请求类。这是我从 Hermes 那里拿到的最实用的工具代码之一:

typescript 复制代码
// commons/network → agc/Request.ets
import { cloudFunction } from '@kit.CloudFoundationKit';
import { connection } from '@kit.NetworkKit';

export class Request {
  async call(trigger: string, params?: Object): Promise<Object> {
    // 先检查网络
    const hasNet = await new Promise<boolean>(resolve => {
      connection.hasDefaultNet((_, data) => resolve(!!data));
    });
    if (!hasNet) throw new Error('网络连接失败');

    // 调用云函数
    const result = await cloudFunction.call({
      name: trigger,
      version: '$latest',
      timeout: 5000,
      data: params
    });
    return result.result;
  }
}

export const request = new Request();

使用方式:

typescript 复制代码
// 调用云函数 'user-sync'
const res = await request.call('user-sync', {
  agcUid: 'xxx',
  nickname: '张三',
  role: 'child'
});

实际业务示例:用户注册同步

当用户第一次登录成功后,需要做几件事:

  1. 在 CloudDB 中创建用户档案(user profile)
  2. 检查是否需要创建家庭
  3. 返回用户信息和家庭信息

这些逻辑全部在云函数 user-sync 中完成。客户端发起调用:

typescript 复制代码
// AuthNetFunc.ets
export class AuthNetFunc {
  /** 新用户首次登录------服务端在 CloudDB 创建 profile */
  syncNewUser(agcUid: string, nickname: string, role: string): Promise<Object> {
    return request.call('user-sync', { agcUid, nickname, role });
  }

  /** 老用户再次登录------服务端返回已有 profile + 家庭信息 */
  syncReturningUser(agcUid: string, email?: string): Promise<Object> {
    const params: Object = email ? { agcUid, email } : { agcUid };
    return request.call('user-sync', params);
  }

  /** 创建家庭 */
  createFamily(creatorId: string, familyName: string): Promise<Object> {
    return request.call('family-create', { creatorId, familyName });
  }

  /** 通过邀请码加入家庭 */
  bindFamily(userId: string, inviteCode: string): Promise<Object> {
    return request.call('family-bind', { userId, inviteCode });
  }
}

云函数返回的数据结构示例:

typescript 复制代码
interface SyncResponse {
  code: number;
  message: string;
  data?: {
    user: { id: string; agcUid: string; nickname: string; role: string; familyId: string };
    family: { id: string; name: string; inviteCode: string } | null;
  };
}

客户端拿到返回数据后,更新本地状态(AuthModel 的 @Trace 属性),UI 自动响应。

完整的登录-同步-跳转流程

css 复制代码
LoginPage.ets
   │
   ├─ 用户输入手机号 → 发送验证码
   │   auth.requestVerifyCode({...})
   │
   ├─ 用户输验证码 → 调 signIn
   │   auth.signIn({credentialInfo: {kind:'phone', ...}})
   │
   ├─ 桥接云服务
   │   cloudCommon.init({authProvider, region, ...})
   │
   ├─ 调云函数 user-sync
   │   request.call('user-sync', {agcUid})
   │
   ├─ 判断返回
   │   ├─ code=0 + data → 已有用户 → 跳转主页面
   │   └─ code!=0 → 新用户 → 跳转注册页面
   │
   └─ 注册/创建家庭后 → 进入主页面

整个流程,客户端不需要知道 CloudDB 的存在。一切 CRUD 都封装在云函数的服务端代码里。


三、端到端加密:数据隐私的最终防线

上面讲的云函数解决了「谁能访问」的问题,但还有一个更底层的问题:数据在服务器上是以什么形式存储的?

华为的服务器确实做了存储加密,但这不够。因为云函数的运维人员、华为内部有权限的人、甚至如果服务器被入侵------理论上都可以看到数据库里的明文内容。

对于 my-parents-helper 这种涉及家庭健康信息、日常习惯、家庭成员关系的 App,这不够安全。

所以我的做法是:在客户端加密数据,再把密文发给云函数存到 CloudDB。服务器只存储密文,永远拿不到明文。

端到端加密的核心原则:加密和解密只在用户设备上进行,服务端不持有解密密钥。

加密架构

整个加密方案分为三层:

scss 复制代码
用户设备                            华为云
   │                                  │
   ├─ 生成家族密钥 (随机256位)          │
   ├─ 用邀请码派生包装密钥              │
   ├─ 用包装密钥加密家族密钥            │
   ├─ 密文 → 云函数                    │ → CloudDB 存储
   │                                  │
   ├─ 再次打开 App                     │
   ├─ 从 CloudDB 下载密文              │
   ├─ 用邀请码重新派生包装密钥 → 解密   │
   ├─ 得到家族密钥(内存中)            │
   │                                  │
   ├─ 写任务:                         │
   │  生成临时 DEK (Data Encryption Key)│
   │  用 DEK 加密任务内容              │
   │  用家族密钥加密 DEK               │
   │  (密文 + 加密后DEK) → 云函数      │ → CloudDB 存储
   │                                  │
   ├─ 读任务:                         │
   │  从 CloudDB 下载 (密文 + DEK)     │
   │  用家族密钥解密 DEK               │
   │  用 DEK 解密任务内容              │
   │  得到明文 → 显示                  │

关键角色:

概念 是什么 谁持有
家族密钥 (Family Key) 256 位随机数,家族成员共享 每个家庭成员设备内存中
包装密钥 (Wrapping Key) 从邀请码派生 临时生成,用完即弃
DEK (Data Encryption Key) 每条数据独立的加密密钥 随密文一起存储(用家族密钥加密后)

具体实现

使用的是鸿蒙 @kit.CryptoArchitectureKit,AES-256-GCM 模式。

1. 创建家庭时生成家族密钥

typescript 复制代码
import { cryptoFramework } from '@kit.CryptoArchitectureKit';

// 生成 256 位随机家族密钥 + 盐值
const rand = cryptoFramework.createRandom();
const familyKey = rand.generateRandomSync(32).data;  // 256-bit
const salt = rand.generateRandomSync(16).data;

2. 邀请码派生包装密钥,加密家族密钥上传

typescript 复制代码
// 用邀请码派生包装密钥 (HKDF-SHA256)
const wrappingKey = await FamilyCryptoService.deriveWrappingKey(inviteCode, salt);

// 用包装密钥加密家族密钥 → base64 → 上传 CloudDB
const encryptedFamilyKey = await FamilyCryptoService.wrapFamilyKey(familyKey, wrappingKey);
await request.call('family-key-init', {
  familyId: 'xxx',
  encryptedFamilyKey: encryptedFamilyKey,
  keySalt: salt,
  // ...
});

deriveWrappingKey 的具体实现:

typescript 复制代码
static async deriveWrappingKey(inviteCode: string, salt: Uint8Array): Promise<Uint8Array> {
  const keyData = stringToUint8Array(inviteCode);
  const spec: cryptoFramework.HKDFSpec = {
    algName: 'HKDF',
    key: keyData,
    salt: salt,
    info: new Uint8Array(0),
    keySize: 32
  };
  const kdf = cryptoFramework.createKdf('HKDF|SHA256|EXTRACT_AND_EXPAND');
  const secret = await kdf.generateSecret(spec);
  return new Uint8Array(secret.data);
}

3. 写入数据时:DEK 加密

每条任务数据都有自己的 DEK(Data Encryption Key),用家族密钥加密后随数据存储:

typescript 复制代码
// encryptContent 的简化逻辑
static async encryptContent(content: TaskContent): Promise<EncryptedContent> {
  const plainJson = JSON.stringify(content);
  const rand = cryptoFramework.createRandom();
  const dek = rand.generateRandomSync(32).data;   // 每个任务独享的 DEK
  const ciphertext = await aesGcmEncrypt(plainJson, dek);          // 用 DEK 加密内容
  const encryptedDek = await aesGcmEncrypt(dek, familyKey);        // 用家族密钥加密 DEK
  return { contentJson: ciphertext, encryptedDek: encryptedDek };
}

4. 读取数据时:逆序解密

typescript 复制代码
static async decryptContent(contentJson: string, encryptedDek: string): Promise<TaskContent> {
  const dekRaw = await aesGcmDecrypt(encryptedDek, familyKey);    // 先解 DEK
  const dek = new Uint8Array(dekRaw);
  const plainJson = await aesGcmDecryptWithKey(contentJson, dek); // 再用 DEK 解内容
  return JSON.parse(plainJson);
}

AES-256-GCM 的具体加解密实现,使用了 cryptoFramework.createCipher('AES256|GCM|PKCS7'),配合随机 IV,每次加密结果不同,防止重放攻击。

完整的加解密代码在 FamilyCryptoService.ets 中,约 200 行。上面是核心逻辑,实际还包含了 URL-safe base64 编码、异常处理等细节。

这套方案的几个设计要点

设计 目的
每任务独立 DEK 一个 DEK 泄露只影响一条任务,不波及其他数据
家族密钥 + DEK 双层 更换家族密钥时不需要重新加密所有数据(只需用新密钥重加密 DEK)
邀请码派生包装密钥 新成员通过邀请码加入时,可以安全地获取到加密后的家族密钥
HKDF-SHA256 密钥派生 即使邀请码强度不够,100000 次 PBKDF2 迭代也大幅增加了暴力破解成本
AES-256-GCM 带认证标签 密文被篡改后,解密时会自动校验失败,不会产生乱码数据

小结

这套端到端加密让 my-parents-helper 做到了:即使华为的 CloudDB 被拖库,攻击者拿到的也只是一堆无法解密的无意义密文。 每个家庭的加密密钥不同,每条任务的 DEK 不同,解密只发生在用户设备上。

做到这些,依赖的是鸿蒙 @kit.CryptoArchitectureKit 提供的 AES-256-GCM、HKDF、随机数生成等基础能力------这些 API 是系统级的,调用起来比自己造轮子安全得多。


四、Hermes Agent 在整个过程中帮了什么

这一节直接回答很多人会问的问题:「你说用 AI 写代码,具体帮你写了什么?」

1. 登录流程的代码框架

AGC Auth 的接入文档是有的,但步骤分散在好几处:初始化 SDK、请求验证码、处理回调、桥接云服务、会话恢复......

Hermes 帮我梳理出一个清晰的代码结构:

  • AuthModel:管理登录状态(@Trace isLoggedIn, @Trace agcUid 等)
  • AuthNetFunc:封装所有云函数调用
  • LoginPage:UI + 登录逻辑,三种登录方式的 tab 切换
  • 错误处理:验证码过期重发、网络超时、会话恢复失败等

2. 云函数调用封装

那个 Request 类------网络检查 + 超时 + 重试 + 统一返回------就是 Hermes 帮我写的第一版。我只需要改改参数,就变成了项目通用的调用入口。

3. 端到端加密代码

FamilyCryptoService.ets 的核心结构------DEK 生成、AES-256-GCM 加解密、HKDF 密钥派生------是 Hermes 帮我搭的框架。我重点校验的是密钥管理逻辑(谁持有什么密钥、密钥怎么流转、怎么防泄露)。

4. 生物识别认证

userAuth 模块的事件驱动模型比较绕------on('result') 监听回调 + start() 启动 + 超时处理。Hermes 帮我写成了 Promise 封装,用起来就一行 await requireBiometricAuth()

5. 状态管理(PersistenceV2 + 会话恢复)

登录状态的持久化和恢复是 Hermes 帮我写的关键部分。细节:

  • 登录成功后把 agcUiduserId 等信息写入 PersistenceV2
  • App 启动时先读 PersistenceV2 缓存,有缓存再尝试恢复 AGC 会话
  • 会话恢复成功后自动跳转主页面,用户感觉不到"登录"
  • 登出时清除本地缓存 + 调 auth.signOut()

6. 踩坑记录

坑一:cloudCommon.init 必须在 signIn 之后立即调用

登录成功后如果不调 cloudCommon.init,后续云函数调用会报认证错误。这个坑靠 AI 没帮我避开------是跑一遍真机测试发现云函数返回 401,才追查到是少了这一步。

坑二:PersistenceV2 key 不一致

AuthModel 和 SplashPage 如果用了不同的 PersistenceV2 key,登录状态永远读不到。这个坑我已经记在 harmonyos-arkts-rules 技能里了。


五、费用:这套后端花了多少钱

服务 费用 说明
AGC Auth 0 元 免费额度对个人项目足够
Cloud Function 约 0 元 个人用量免费额度内
CloudDB 0 元(云函数调用) 不直接暴露给客户端
Cryptography API 0 元 系统内置
UserAuth API 0 元 系统内置
服务器 0 元 不需要自建
合计 约 0 元

真正的"0 元后端"。


下篇预告

下一篇写前端的视觉部分:

第三篇:UIDesignKit UI------光感玻璃 + 流光交互实战

这是 my-parents-helper 的视觉风格------如何用鸿蒙的视觉特效 API 做出「像玻璃一样通透、有流光」的界面。

关注我 seal_jing,一起见证一个 44 岁独立开发者的鸿蒙 App 诞生之路。


发布时间:2026年6月 作者:seal_jing,一个被裁后自己写 App 的中年程序员

相关推荐
程序猿追6 小时前
画个饼,给数据点颜色看看——在 HarmonyOS 模拟器上手搓一个饼图/环形图组件
深度学习·算法·harmonyos
不爱吃糖的程序媛7 小时前
鸿蒙 6 新华字典实战:从零到一用 ArkTS 开发原生鸿蒙应用
华为·ar·harmonyos
再见6588 小时前
HarmonyOS NEXT 实战:开发一个实用的文本分析器应用
华为·harmonyos
程序猿追9 小时前
给手机做一次“体检”——我在 HarmonyOS 上写了个存储空间与内存查看器
华为·智能手机·harmonyos
nashane9 小时前
HarmonyOS 6学习:HAR包跨平台编译陷阱与架构优化实战
学习·华为·harmonyos
坚果的博客9 小时前
ycium_plusplus 项目全景解读:OpenHarmony 三方库构建的“大管家“
华为·harmonyos
李二。9 小时前
鸿蒙原生ArkTS-系外行星百科AI
人工智能·华为·harmonyos
想你依然心痛10 小时前
HarmonyOS 6(API 23)实战:构建“光愈冥想舱“——智能情绪疗愈系统
华为·ar·harmonyos·智能体
坚果的博客10 小时前
鸿蒙PC三方库适配OAT.xml 与 SHA512SUM 解读:开源合规与源码校验
xml·开源·harmonyos