从一句话需求到端云一体:鸿蒙员工签到 App 实战记录

举报
yd_278231280 发表于 2026/11/07 10:22:09 2026/11/07
【摘要】 端云一体应用真正的难点在一致性、鉴权与“今天”的算法。本文以鸿蒙员工签到 App 案例为线索,整理从 Agent 生成 PRD/HLD、ArkTS 端侧请求封装,到 Express + MySQL 幂等签到接口的完整实践,并总结时区、重复提交、地址映射等联调坑位。

为什么挑这个案例

端云一体的项目,难点通常不在“能不能跑起来”,而在三件事:端侧状态与服务端数据一致、鉴权链路清楚、以及“今天”这个业务日到底怎么算。华为云鸿蒙开发实践中心里的“员工签到 App”案例正好把这三件事串起来了:端侧 HarmonyOS ArkTS,服务端 Node.js + Express + MySQL,中间还有 CodeArts Agent 把一句话需求扩成 PRD/HLD。

整体架构

按案例页的说明,这个系统分三层:

  • 端侧:ArkTS 页面 + 网络请求封装,负责签到按钮、今日状态、历史记录;
  • 服务端:Express 提供登录、签到、记录查询接口,MySQL 存用户与签到流水;
  • 环境:实践中心提供云端容器(AI DevSpace),编辑器、编译器、模拟器开箱即用,不必在本地配 HarmonyOS SDK。

它的价值在于:不是只写一个页面 Demo,而是真的把端、云、库连起来。

用 Agent 生成 PRD/HLD 的写法

案例里用 CodeArts Agent 从一句话需求生成 PRD/HLD 和初版代码。实下来,提示词给得越像验收标准,产出越能直接用。我习惯四段式:

  • 角色与目标:一句话说清给谁做什么;
  • 技术栈与约束:ArkTS、Express、MySQL 版本,禁止引入哪些依赖;
  • 接口契约:路径、方法、入参、返回结构、错误码;
  • 非功能要求:幂等、时区、鉴权方式、日志字段。

生成完之后一定要人工过三处:鉴权中间件、时区处理、重复提交。这三处直接照抄,后面联调会很难受。

端侧:把请求封装收口

ArkTS 里最容易写散的是 http 调用。我的做法是收口到一个函数,统一超时、统一错误、统一带 token:

import http from '@ohos.net.http';

const BASE_URL = 'https://your-service.example.com';

export async function post(path: string, body: object, token?: string) {
  const req = http.createHttp();
  try {
    const res = await req.request(BASE_URL + path, {
      method: http.RequestMethod.POST,
      header: {
        'Content-Type': 'application/json',
        ...(token ? { Authorization: 'Bearer ' + token } : {})
      },
      extraData: JSON.stringify(body),
      connectTimeout: 10000,
      readTimeout: 10000
    });
    if (res.responseCode !== 200) {
      throw new Error('HTTP ' + res.responseCode);
    }
    return JSON.parse(res.result as string);
  } finally {
    req.destroy();
  }
}

注意 finally 里的 destroy()。不销毁的话,请求多了之后连接会堆积,表现就是“前几次能签到,后面一直转圈”。

服务端:幂等靠唯一索引,不靠先查后插

签到天然是会重复点击的接口。表结构上直接加唯一键,把“一人一天一条”交给数据库保证:

CREATE TABLE checkin (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id BIGINT NOT NULL,
  work_date DATE NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  UNIQUE KEY uk_user_date (user_id, work_date)
);

接口里用日期做业务键,不用 DATETIME:

app.post('/api/checkin', auth, async (req, res) => {
  const workDate = dayjs().tz('Asia/Shanghai').format('YYYY-MM-DD');
  try {
    await pool.execute(
      'INSERT INTO checkin (user_id, work_date) VALUES (?, ?)',
      [req.user.id, workDate]
    );
    res.json({ ok: true, date: workDate });
  } catch (e) {
    if (e.code === 'ER_DUP_ENTRY') {
      return res.status(409).json({ ok: false, code: 'ALREADY_CHECKED_IN' });
    }
    throw e;
  }
});

这样重复提交会被数据库拦住,返回 409 让端侧提示“今天已签到”。先 SELECT 再 INSERT 在并发下一定会双写。

联调时踩到的几个坑

  • 时区:服务端统一用 Asia/Shanghai,不要用服务器本地时区,否则 23:30 的签到可能落到第二天。
  • 本地时间不可信:端侧只负责展示,“今天是否已签到”以服务端返回为准。
  • 访问地址:模拟器访问宿主机服务不能写 localhost;云端容器里用平台给的映射地址。
  • 401 统一拦截:token 过期在一处处理,别每个页面写一遍。
  • 日志:至少记 userId、接口、耗时、业务日期,出问题时能一眼看出是端上没发还是服务端算错。

小结

这个案例最值得借的不是某一页代码,而是它把“需求 → 文档 → 端侧 → 服务端 → 联调 → 部署”整条链路摆在同一个云端容器里。对刚入鸿蒙的开发者来说,先把链路跑通、再把幂等和时区这两件事做对,比堆功能更有性价比。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。