ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

茶器艺科智造HarmonyOS应用实战-26-订单日期与ORD-2026写死,跨年演示为何仍像旧数据:注入Clock与IdFactory

2026/9/16 3:33:11 拓冰建站 浏览量
茶器艺科智造HarmonyOS应用实战-26-订单日期与ORD-2026写死,跨年演示为何仍像旧数据:注入Clock与IdFactory 茶器艺科智造HarmonyOS应用实战-26-订单日期与ORD-2026写死跨年演示为何仍像旧数据注入Clock与IdFactory应用运行到 2027 年商城首屏仍显示ORD-2026-0118与2026-03-15 14:30。用户从切片页新建订单时ID 又变成ORD-SL-毫秒时间戳日期取另一次new Date()。同一个列表里一部分像历史快照一部分像实时业务数据测试若想覆盖跨年只能依赖执行机器的当前时钟。指定工程的演示订单确实全部写死在 2026 年。HSP 新建切片订单则在一个方法里多次读取时间一次Date.now()拼 ID一次new Date()格式化日期随后再一次Date.now()计算状态转换截止时刻。三次读取通常接近但不是同一业务快照快速连点还可能让毫秒 ID 碰撞。本文建议把“现在是什么时候”和“订单 ID 怎么生成”变成依赖由Clock与IdFactory注入。生产使用系统时钟测试使用固定时钟订单创建全程只取一次 now。本文聚焦可重复的时间与身份生成区分固定演示快照、相对当前时间的演示数据和真实订单三种语义。用 Clock 固定跨年、月末和时钟回拨用例。用 IdFactory 处理同毫秒创建与已有 ID 冲突。说明端侧时钟与端侧 ID 的证据上限避免把演示唯一性当服务端业务唯一性。一、源码里同时存在三种互不统一的时间写法ChaqiModels.ets:133-164中打印中与待处理订单逐条写ORD-2026-...和 2026 年日期已完成循环也把 ID 年份固定为 2026、日期固定为 2026-02{ id: ORD-2026-0118, date: 2026-03-15 14:30, /* ... */ } // 已完成订单循环 id: ORD-2026-${pad}, date: 2026-02-${dayText} ${hourText}:${minuteText}HSP 的ChaqiExperiencePage.ets:619-631则使用当前时间与随机延迟const orderId ORD-SL-${Date.now()}; const sliceOrder: ShopOrder { id: orderId, status: pending, progress: 0, name: ${presetName}·切片, material: 陶瓷树脂, date: this.formatOrderDateNow() }; const delayMs 2000 Math.floor(Math.random() * 3001); this.pendingSlicePrintFireAtMs Date.now() delayMs;当前代码没有把这些订单声明为“固定历史样例”还是“仿实时数据”。如果它们只是教程截图 fixture固定 2026 未必错误但界面应明确标注演示快照若期望每次打开都像近期订单就需要相对 Clock 生成。不能只把字符串中的 2026 手工改成 2027因为下一年同样会过期。二、先选择演示时间语义再写生成器演示数据常见三种策略策略时间来源优点代价固定快照fixture 自带 2026 日期截图与测试完全稳定跨年后显旧必须标注“演示快照”相对现在Clock.now 减若干小时/天每次打开都像近期列表相同版本截图会变化服务端样例后端返回带环境标记的数据接近真实协议依赖网络与服务环境本文选择“相对现在 测试固定 Clock”作为建议因为标题场景要求跨年仍保持近期感。第 25 篇的 fixture 仍然成立样例的名称、材质、状态、进度和时间偏移是固定的绝对日期由注入 Clock 派生。若产品选择固定快照则无需 Clock 生成日期只需改用DEMO-ID 并在界面明确说明。三、Clock接口只暴露业务需要的当前毫秒一个最小 Clock 足够不需要引入完整日期框架export interface Clock { nowMs(): number; } export class SystemClock implements Clock { nowMs(): number { return Date.now(); } } export class FixedClock implements Clock { private valueMs: number; constructor(valueMs: number) { this.valueMs valueMs; } nowMs(): number { return this.valueMs; } }生产代码注入 SystemClock单测注入 2026-12-31 23:59 或 2027-01-01 00:01 对应的毫秒值。Clock 返回 epoch 毫秒不决定展示时区。格式化层应明确采用设备本地时区还是业务固定时区当前getFullYear/getMonth/getDate使用设备本地时区这一行为可保留但测试应显式提供时区策略避免不同环境断言不同日期。四、相对时间fixture保存offset不保存过期日期把第 25 篇订单 fixture 的date改成相对偏移。例如打印中订单发生在 now 前 30 分钟待处理订单可以在 now 前后若干分钟是否允许“未来待处理时间”需要产品定义。export interface TimedOrderFixture { readonly sequence: number; readonly ageMinutes: number; readonly status: OrderStatus; readonly progress: number; readonly name: string; readonly material: string; } const PRINTING_FIXTURES: readonly TimedOrderFixture[] [ { sequence: 118, ageMinutes: 30, status: printing, progress: 67, name: 罗汉杯·兔毫釉, material: 陶瓷树脂 } ];构建时只读一次 anchorMs再对所有行计算绝对时间。这样列表内部拥有一致参照不会生成到一半跨过午夜export function buildDemoOrders( clock: Clock, idFactory: OrderIdFactory ): ShopOrder[] { const anchorMs clock.nowMs(); return ALL_FIXTURES.map((item: TimedOrderFixture) { const createdAtMs anchorMs - item.ageMinutes * 60 * 1000; return { id: idFactory.demoId(createdAtMs, item.sequence), status: item.status, progress: item.progress, name: item.name, material: item.material, date: formatOrderTime(createdAtMs) }; }); }建议长期把createdAtMs存进 ShopOrderUI 再格式化避免 date string 承担排序与时区语义为缩小迁移本片段仍返回当前模型需要的 date。五、IdFactory统一演示ID和切片订单IDID 生成不应散落模板字符串。接口可以分别表达 demo 与用户触发订单export interface OrderIdFactory { demoId(createdAtMs: number, sequence: number): string; sliceId(createdAtMs: number, occupied: readonly string[]): string; } function fourDigits(value: number): string { if (value 10) return 000${value}; if (value 100) return 00${value}; if (value 1000) return 0${value}; return ${value}; }演示 ID 可从 createdAt 的年份生成DEMO-2027-0118明确它不是后端订单若 UI 必须保留 ORD 前缀也要有单独 demo 标记。切片 ID 以时间为基底并在已有订单中检查冲突export class LocalOrderIdFactory implements OrderIdFactory { demoId(createdAtMs: number, sequence: number): string { const year new Date(createdAtMs).getFullYear(); return DEMO-${year}-${fourDigits(sequence)}; } sliceId(createdAtMs: number, occupied: readonly string[]): string { const base ORD-SL-${createdAtMs}; let candidate base; let suffix 1; while (occupied.includes(candidate)) { candidate ${base}-${suffix}; suffix; } return candidate; } }冲突循环解决当前列表内同毫秒创建不证明跨设备、跨进程或服务端全局唯一。真实订单应由服务端或正式唯一 ID 协议分配端侧临时 ID 还需在同步后与服务端 ID 建立映射。六、一次订单创建只能取一次now快照新建切片订单时把 clock 和 factory 放到服务层先读一次 nowexport function createSliceOrder( clock: Clock, idFactory: OrderIdFactory, occupiedIds: readonly string[], presetName: string, delayMs: number ): PendingSliceOrder { const nowMs clock.nowMs(); const order: ShopOrder { id: idFactory.sliceId(nowMs, occupiedIds), status: pending, progress: 0, name: ${presetName}·切片, material: 陶瓷树脂, date: formatOrderTime(nowMs) }; return { order: order, fireAtMs: nowMs delayMs }; }ID、显示时间和 deadline 都基于同一个 nowMs不会出现跨分钟或跨年不一致。随机 delay 仍会影响测试可再注入 DelayPolicy这不是 Clock 的职责不应把随机数塞进时钟接口。页面只负责把结果写入状态与启动定时器。到期判断中的Date.now()也应改为clock.nowMs()否则创建测试可控、恢复判断仍不可控。七、跨年与碰撞用例无需等待真实时间it(uses injected year after new year, 0, () { const clock new FixedClock(localEpoch(2027, 1, 2, 9, 30)); const orders buildDemoOrders(clock, new LocalOrderIdFactory()); expect(orders[0].id.startsWith(DEMO-2027-)).assertTrue(); expect(orders[0].date.startsWith(2027-)).assertTrue(); }); it(avoids same millisecond collision, 0, () { const ids: string[] [ORD-SL-1800000000000]; const factory new LocalOrderIdFactory(); const next factory.sliceId(1800000000000, ids); expect(next).assertEqual(ORD-SL-1800000000000-1); });localEpoch应由测试工具按明确时区构造或直接使用审阅过的 epoch 常量。还要覆盖月末、闰日、设备时区切换和系统时钟回拨。时钟回拨时occupied 检查仍保证当前列表不重号但排序若依赖 createdAt 可能倒退需要产品定义。八、验证矩阵分开演示新鲜度与业务唯一性场景注入预期2026-12-31FixedClockID/日期采用 20262027-01-01FixedClock不再硬编码 2026同一 now 连续创建固定时钟 occupied后一 ID 带冲突后缀不同 now 创建递增 ClockID 不同日期对应各自快照跨午夜创建一次 now 快照同一订单 ID 年份与日期一致到期判断可推进 ClockfireAt 前不转换到达后转换真机改时区SystemClock展示策略符合产品约定服务端同步正式环境本地 ID 被映射不冒充全局 ID纯单测可证明固定时刻的字符串与冲突策略页面计时器需要假时钟或调度器真实时区和系统时钟变化需要设备验证服务端唯一性只能由服务协议和回读证明。九、故障排查与证据边界现象首查常见根因修正2028年仍显示2027fixture 是否保存绝对日期又手改了年份保存offset并注入ClockID年份与date年份不同now读取次数跨边界多次取时创建开始只读一次测试在不同时区失败formatter时区策略依赖机器本地设置测试显式时区快速连点出现相同IDIdFactory是否查occupied只拼Date.now冲突后缀或序列到期用例需要真实等待判断仍调用Date.nowClock只接入创建所有时间读取统一依赖本地ID被当正式订单号前缀与同步协议演示/临时/正式身份混合明确DEMO与本地临时标记源码审计能确认演示 ID/日期固定为 2026切片订单 ID 使用一次 Date.now显示时间使用另一次 new Datedeadline 又读取 Date.now当前格式按设备本地时间输出。本文的 Clock、FixedClock、IdFactory、相对时间 fixture 和创建服务均为建议没有修改只读项目。本文未运行单测、HAP 构建、跨年设备、时区切换、服务端同步或 CSDN 发布端侧用例即使通过也只能证明本地生成契约不能证明真实订单的服务端唯一性与权威时间。