鸿蒙端云一体:用 Appwrite 自托管后端做一个单词打卡 App
鸿蒙端云一体:用 Appwrite 自托管后端,做一个单词打卡 App
背单词 App 满地都是,但真正自己从零搭一个「端侧 + 自托管 Serverless 后端」的完整链路,能学到的远比想象多。这篇用 HarmonyOS 的 ArkTS + 自托管 Appwrite,做一个卡片式单词打卡应用:注册登录、闪卡学习、看词选义测试、连续打卡,云侧六个云函数把数据全部收口。
和常见的 FunctionGraph + APIG 方案不同,Appwrite 用「内建 HTTP 路由 + 文档型 NoSQL」把网关层省掉了——端侧只认一个公开的项目 ID,业务校验全在云函数里。
一、为什么选自托管 Appwrite
FunctionGraph 方案要自己配 API 网关、写路由映射、管理函数权限,链路长、排查难。Appwrite 把这几件事内置了:
- 自带 HTTP 路由和执行入口
- 自带文档数据库(集合 + 文档),不用另外开 RDS
- 云函数里用 Server SDK 拿 API Key 读写数据库,端侧完全碰不到密钥
代价是要自己部署和运维。但对个人项目和教学场景,一台 ECS 跑 docker-compose 就够了。
二、数据模型:三个集合
users_col # 用户:email / password_hash / nickname
words_col # 58 词词库:word / meaning / level
learn_records_col # 学习记录:user_id / word_id / status / date
连续打卡的本质是「按 user_id + date 聚合 learn_records」:某天有记录就算打卡,连续天数从今天往前数不间断的日期。把聚合放在云函数里算,端侧只渲染结果,口径只有一处。
三、六个云函数的职责划分
| 函数 | 职责 |
|---|---|
| register | 邮箱查重 + scrypt 加盐哈希落库 |
| login | 常量时间比对密码,签发会话 |
| list-words | 按用户进度返回今日 10 个未学词 |
| submit-answer | 校验答案,答对写入 learn_records |
| get-streak | 云端聚合连续打卡天数与进度 |
| reset-progress | 清空指定用户的学习记录 |
每个函数都是 ESM 默认导出、node-appwrite v11、execute 设为 any(端侧用公开 ID 直接调)。
四、云函数里怎么安全处理密码
不存明文是最低要求。用 Node 内置 crypto 的 scrypt:
import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
function hashPassword(pw) {
const salt = randomBytes(16).toString('hex');
const hash = scryptSync(pw, salt, 64).toString('hex');
return 'scrypt$' + salt + '$' + hash;
}
function verifyPassword(pw, stored) {
const parts = stored.split('$');
const salt = parts[1];
const hash = parts[2];
const calc = scryptSync(pw, salt, 64).toString('hex');
return timingSafeEqual(Buffer.from(calc, 'hex'), Buffer.from(hash, 'hex'));
}
登录用 timingSafeEqual 做常量时间比对,避免响应耗时侧信道泄露信息。
五、端侧网络层只留一个 HttpUtil
端侧所有请求都收敛到一个 HttpUtil,只带公开的项目 ID:
async function callFunction(name: string, payload: object): Promise<any> {
const res = await http.createHttp().request(
ENDPOINT + '/v1/functions/' + name + '/executions',
{
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json', 'X-Appwrite-Project': PROJECT_ID },
extraData: JSON.stringify({ body: JSON.stringify(payload) })
}
);
return JSON.parse(res.result as string);
}
项目 ID 是公开的,密钥只存在云函数环境的 API Key 里,端侧永远拿不到——这是自托管方案比「端侧直连数据库」安全的关键。
六、闪卡与看词选义的交互实现
闪卡用 ArkUI 的 rotate 属性做 3D 翻转,点击卡片切换正反面;看词选义是 4 选 1,答对调用 submit-answer 写入记录并更新进度条,答错给一次高亮提示。进度条和外层状态用 State 驱动,学习状态一变界面即时响应。
关键点:逻辑与 UI 解耦。冲突校验、连续打卡计算都是纯 TypeScript 函数,能单独测;UI 只负责把状态画出来。
七、自托管部署里的排障经验
- tar.gz 打包上传:Appwrite 部署云函数支持上传压缩包,运行时选 node-16.0。
- API Key 权限:云函数要读写数据库,得给 API Key 配对应集合的读写权限,否则 401。
- execute 权限:设为 any 让端侧用公开 ID 能直接调;要接入正式鉴权就改成 users,配合端侧会话。
- 401 排查:先看 API Key 是否属于该项目、权限是否含目标集合,再看函数运行时版本对不对。
八、这个案例能学到什么
- 端云一体化里「密钥边界」怎么划:公开 ID 给端侧,敏感密钥锁在云端
- 文档型 NoSQL 的数据建模:集合 + 文档,比关系型更贴合这类简单业务
- 连续打卡、进度聚合这类统计,放在云端算一次,比端侧各算各的可靠
- 自托管 Serverless 的部署与排障,能复用到其他 Appwrite 后端场景
小结
这个案例真正的价值不在记单词本身,而在于跑通「端侧 → 自托管后端 → 云函数 → NoSQL」这条完整链路,并且把安全边界想清楚。对想掌握端云一体化的鸿蒙开发者来说,是个小而完整的练手项目。
案例仓库和云端实践环境在鸿蒙开发实践中心都能找到,配套的建集合、配 API Key、部署函数步骤都在案例文档里。
- 点赞
- 收藏
- 关注作者
评论(0)