#星光不负 码向未来# 从“写页面”到“做能力”:一个大学生自学上岸的鸿蒙实战复盘 原创
一、我的转向:从前端的“页面交付”到鸿蒙的“能力交付”
大学时我靠着网课入门前端,做过 H5、小程序、Flutter 外包。那时我的目标是“把页面做出来”;进入 HarmonyOS 之后,我第一次把目标改成了“把能力做出来”——比如把预约、设备发现、能力开放、数据一致性、故障自愈这些“系统级事情”真正落到业务。

我长得比较老成,当然啦(=゚ω゚)ノ这篇文章复盘一个落地项目:「校园自习室预约」原子化服务(元服务)。它不是“把网页搬到端上”,而是面向 快进快出、无学习成本、可离线缓存、云端对账 的能力组合拳。也顺便聊聊我如何一边做、一边把 Java 补起来,完成从“前端视角”到“全链路视角”的跃迁。
顺便秀一下,下面我最近刚做的外包项目( ´ ▽ ` )ノ。

二、业务场景与目标
场景痛点
- 入口碎片化:同学在群里点链接、扫海报码、看活动页,层层跳转。
- 高峰抢占:饭点 & 晚自习 10 分钟内流量陡增,容易超卖。
- 使用心智:预约只是临时动作,用户不想“打开 app → 找入口 → 登录 → 搜索房间”。
目标设计
- 以原子化服务(元服务)+ 桌面卡片实现0 学习成本即点即用;
- 以AGC Cloud DB + 云函数 做并发校验与一致性;
- 用App Linking 统一入口(H5/海报/短信一键直达预约详情);
- 关键链路毫秒级反馈:首交互 < 300ms,提交排队有可视化进度。
三、架构与能力选型
- 入口层:二维码 / H5 链接(App Linking) → 拉起元服务或 App 指定 Page
- 端侧数据层:Cloud DB 本地缓存(弱网读缓存)
- 服务编排:Cloud Functions(并发校验、配额控制、可撤销队列)
- 呈现形态:元服务(Atomic Service) + 服务卡片(Form) + 常规页面
- 埋点:应用分析(打点预约成功率、冲突占比、P95 延时)
与我上一次做的项目完全不同:本项目不依赖近场/软总线,主打元服务 + 云开发的“轻入口、强后端校验”路径。
四、关键实现
4.1 App Linking:海报/短信一键直达预约详情
module.json5(片段,按你项目实际域名与字段适配)
{
"module": {
"abilities": [{
"name": "EntryAbility",
"skills": [{
"actions": [ "action.view" ],
"uris": [{
"scheme": "https",
"host": "study.自己的网页.com",
"paths": [ "/room/*" ]
}]
}]
}]
}
}解析:二维码指向 https://study.自己的网页.com/room/ROOM_1203?slot=20-22
系统校验通过后,直达对应房间与时段的预约页/元服务,无需“先找入口”。
4.2 元服务(原子化服务)+ 服务卡片:不打开 App 也能预约/取消
卡片 UI(ArkTS 示意,核心是动作事件透传到 FormExtensionAbility)
// form/Card.ets
@Component
export struct RoomCard {
@Prop roomId: string
@State slot: string = ''
@State brief: string = '点击预约 / 查看状态'
build() {
Column({ space: 8 }) {
Text(`自习室 ${this.roomId}`).fontSize(18).fontWeight(FontWeight.Medium)
Text(this.brief).fontSize(14)
Row({ space: 12 }) {
Button('预约').onClick(() => postCardEvent('reserve', { roomId: this.roomId, slot: this.slot }))
Button('取消').onClick(() => postCardEvent('cancel', { roomId: this.roomId, slot: this.slot }))
}
}.padding(16)
}
}
// 将事件抛给卡片宿主(由 FormExtensionAbility 接收)
function postCardEvent(type: string, payload: Record<string, string | number>) {
// 实际用 FormExtensionAbility 的 onEvent 接收,以下为示意
(globalThis as any).__FORM_EVENT__?.({ type, payload })
}卡片扩展(FormExtensionAbility 处理事件 → 调用云函数)
// form/FormExtAbility.ets(示意)
export default class FormExtAbility extends FormExtensionAbility {
async onAddForm(formId: string, want: Want): Promise<void> {
// 初始化卡片数据:最近可预约时间段等
await this.updateForm(formId, { slot: nextAvailableSlot() })
}
async onEvent(formId: string, message: string): Promise<void> {
const evt = JSON.parse(message) // { type, payload }
if (evt.type === 'reserve') {
const ok = await reserveRoom(evt.payload.roomId, evt.payload.slot) // 调云函数
await this.updateForm(formId, { brief: ok ? '预约成功' : '名额已满' })
} else if (evt.type === 'cancel') {
const ok = await cancelRoom(evt.payload.roomId, evt.payload.slot)
await this.updateForm(formId, { brief: ok ? '已取消' : '取消失败' })
}
}
}4.3 端侧数据读写:Cloud DB(弱网下走本地缓存)
// data/clouddb.ts(示意,以实际 AGC 接口为准)
import agconnect from '@hw-agconnect/core'
import clouddb from '@hw-agconnect/clouddb'
let zone: any
export async function initCloudDB() {
await agconnect.instance().config({ /* your agc options */ })
const cloudDB = clouddb.AGConnectCloudDB.getInstance(agconnect.instance())
await cloudDB.initialize()
zone = await cloudDB.openCloudDBZone('room_booking', {
syncMode: clouddb.CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
accessProperty: clouddb.CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC
})
}
export async function queryRoomStatus(roomId: string, slot: string) {
const query = clouddb.CloudDBZoneQuery.where('Booking')
.equalTo('roomId', roomId).and().equalTo('slot', slot)
const result = await zone.executeQuery(query)
return result.getSnapshotObjects() // 本地有缓存,弱网也能读
}4.4 并发校验与超卖防护:云函数里的“单时段互斥”
我最初只在端上做“剩余名额计算”,结果高峰期并发冲突仍然发生。后来把校验挪到云函数里,用事务(或乐观锁)保证同一 roomId+slot 的原子更新。
Node.js 云函数(逻辑示意)
// functions/reserveRoom.js
const { getDB, withTransaction } = require('./_db') // 你自己的封装
exports.main = async (event, context) => {
const { roomId, slot, uid } = event
const db = await getDB()
return withTransaction(db, async (tx) => {
const key = `${roomId}@${slot}`
const quota = await tx.get('Quota', key) // { total, used, version }
if (!quota || quota.used >= quota.total) {
return { ok: false, reason: 'FULL' }
}
// 幂等:同一人重复提交直接返回成功
const existing = await tx.get('Booking', `${key}#${uid}`)
if (existing) return { ok: true, dup: true }
// 乐观锁:版本号或 compare-and-set
await tx.update('Quota', key, { used: quota.used + 1, version: quota.version + 1 }, { matchVersion: quota.version })
await tx.put('Booking', `${key}#${uid}`, { roomId, slot, uid, ts: Date.now() })
return { ok: true }
})
}这一步是我“补 Java/后端思维”的分水岭:先用 Node 实现事务校验,随后我又用 Java 写了一个管理端小服务(导出报表、手动解锁异常名额、查看冲突日志),从只写 UI 的我,走到了能把业务闭环跑起来。
Java(管理端控制器示例,Spring Web 轮廓)
@RestController
@RequestMapping("/admin/booking")
public class BookingAdminController {
private final BookingService bookingService;
public BookingAdminController(BookingService bookingService) {
this.bookingService = bookingService;
}
@PostMapping("/unlock")
public Resp unlock(@RequestParam String roomId, @RequestParam String slot) {
bookingService.unlockQuota(roomId, slot); // 释放异常卡死的占用
return Resp.ok();
}
@GetMapping("/export")
public void export(@RequestParam String date, HttpServletResponse res) {
res.setHeader("Content-Disposition", "attachment; filename=booking-"+date+".csv");
bookingService.exportCsv(date, res);
}
}五、上线与指标
- 入口:App Linking 直达落地页/元服务,点开即见可预约时段;
- 峰值并发:晚间高峰 10 分钟内请求数较上月提升 3.2 倍,但超卖为 0;
- 端侧体验:弱网下读本地缓存,首交互 P95 < 280ms;
- 订单一致性:云函数事务化校验 + 定时对账(Java 管理端),异常单自动回收;
- 用户反馈:学生会说“像点开闹钟卡片一样快”,辅导员的投诉量下降。
六、我的成长曲线(给还在观望的你)
- 从组件到系统:不再停留在页面美观,而是会问“这件事应该由系统能力承担到哪一层?”
- 从单端到闭环:端侧缓存、云端事务、对账回收、管理端导出——缺一环都落不到“可用”。
- 从 JS 到 Java:哪怕只是做了“导出报表 + 异常解锁”,也让我真正理解“业务的最终形态是流程”,而流程强依赖后端治理。
- 从 KPI 到体验指标:不再口说“很快”,而是用 P95/P99、成功率、冲突率来驱动优化。
七、经验与坑
- 元服务/卡片设计要“只做一件事”:预约/取消之外的复杂流程,跳转 App 页面完成。
- App Linking 的域验证提前做(证书、跳转校验),否则 H5 无法直达端内能力。
- 云端防超卖必须放在事务里(或乐观锁更新),端上只能提示与缓存。
- 弱网体验靠缓存与延迟同步:读缓存 + 后台刷新,用可见的加载状态降低焦虑。
- 埋点分层:系统层(冷启动、ANR)与业务层(预约成功率、冲突率)分开看。
八、最后小总结
“以二进制刻录世界,用代码编织未来”。
这段从“学生—自学前端—自学鸿蒙—补 Java”的路,让我真正理解 HarmonyOS 的价值:把能力交到终端、把复杂留在云端、把入口做成“触手可及”的元服务。
如果你也在观望,不妨先做一个一屏一事的小原子化服务——当你把“快、准、稳”交付给用户的那一刻,你也会和我一样,喜欢上鸿蒙。



















