HarmonyOS APP《画伴梦工厂》开发第23篇:图片上传服务设计(存根模式)

发布时间:2026/7/3 17:35:36
HarmonyOS APP《画伴梦工厂》开发第23篇:图片上传服务设计(存根模式) 第3.8篇图片上传服务设计存根模式难度⭐⭐ 进阶前置知识3.1 HTTP 网络请求涉及源文件products/default/src/main/ets/services/ImageUploadService.ets概述在画伴梦工厂的 AI 处理流程中用户拍摄或从相册选择的图片需要经过多个环节——压缩、识别、生成动画。在某些场景下图片需要被上传到远程服务器进行存储或后续处理。然而在项目开发的早期阶段后端上传接口可能尚未就绪或者我们希望在离线状态下也能验证前端流程的完整性。存根模式Stub Pattern正是为了解决这个问题而生的。它是一种测试替身Test Double技术用一个轻量级的替身对象替换真实的依赖服务让调用方在无需真实后端的情况下完成开发和调试。本文将通过项目中的ImageUploadService深入讲解存根模式的设计思想、实现方式以及它在鸿蒙应用开发中的实际价值。一、为什么需要存根模式在典型的 Client-Server 架构中前端开发往往依赖后端的接口就绪。传统的开发流程是这样的后端设计 API → 编写接口文档前端等待后端完成开发 → 联调测试发现问题 → 后端修改 → 重新部署 → 再次联调这种方式存在几个明显的痛点串行阻塞前端开发被后端进度卡脖子环境依赖需要网络可达的后端服务器离线环境下无法开发调试困难难以模拟各种异常场景超时、错误码、网络断开反馈周期长每次改动都要等待后端部署存根模式彻底改变了这一局面。前端开发者可以先定义好服务接口契约然后用一个极简的存根实现来模拟后端行为。这个存根返回伪造但结构正确的数据让前端流程可以完整跑通。等到后端接口就绪后再替换为真实的网络实现——整个过程不需要修改调用方的任何代码。二、UploadedImage 接口定义服务的起点是一个清晰的数据模型。ImageUploadService.ets中首先定义了上传结果的接口exportinterfaceUploadedImage{localUri:string;remoteUrl:string;}这个接口包含两个字段字段类型含义localUristring图片的本地文件 URI如相册路径或沙箱路径remoteUrlstring上传成功后服务器返回的远程访问地址接口设计的精妙之处在于它只暴露了调用方真正关心的信息——“这张图片的本地来源是什么和它在远程怎么访问”。至于上传过程是 HTTP 还是 FTP、用了什么认证方式、服务器地址是什么对调用方完全透明。这种面向接口编程的思想是存根模式能够成立的前提只要调用方依赖的是接口而非具体实现我们就可以随时替换背后的实现逻辑。三、存根实现把本地路径当作远程地址有了接口定义来看看项目中实际的存根实现exportclassImageUploadService{staticasyncuploadImage(localUri:string):PromiseUploadedImage{return{localUri:localUri,remoteUrl:localUri};}}这个实现只有 6 行代码它的行为非常直接接收一个localUri字符串参数返回一个UploadedImage对象将localUri同时赋值给localUri和remoteUrl两个字段关键就在remoteUrl: localUri这一行。存根模式的本质逻辑是“既然还没有真正的远程服务器那就把本地文件路径当作远程地址来用。”对于调用方而言它拿到UploadedImage后无论是想展示图片、还是将 URL 传递给下一个服务都可以直接使用remoteUrl字段——只不过在存根模式下这个远程地址实际上指向的是本地文件。这种做法带来的好处是显而易见的调用方无需区分真实和存根代码路径完全一致图片可以正常显示本地 URI 当然可以在 Image 组件中渲染后续服务可以正常处理如果下游服务需要读取图片内容本地路径同样有效切换真实实现时零改动在调用方眼中接口契约没有变化四、如何实现真实上传扩展思路存根的真正价值在于——它给出了一个最小可用实现而在此基础上扩展为真实服务的路径是清晰的。下面我们探讨一下ImageUploadService可能的发展方向。4.1 真实 HTTP 上传实现当后端接口就绪后只需在同一个类中新增一个真实上传方法或者直接替换uploadImage的实现// 真实上传的伪代码示意import{http}fromkit.NetworkKit;exportclassImageUploadService{privatestaticreadonlyUPLOAD_URLhttps://api.example.com/upload;staticasyncuploadImage(localUri:string):PromiseUploadedImage{consthttpRequesthttp.createHttp();try{// 构造 multipart/form-data 请求constresponseawaithttpRequest.request(ImageUploadService.UPLOAD_URL,{method:http.RequestMethod.POST,extraData:{file:{uri:localUri,name:image.jpg,type:image/jpeg}},header:{Content-Type:multipart/form-data},connectTimeout:30000,readTimeout:60000});// 解析响应提取 remoteUrlconstresultJSON.parse(response.resultasstring);return{localUri:localUri,remoteUrl:result.data.url};}finally{httpRequest.destroy();}}}注意看返回的类型依然是PromiseUploadedImage调用方不需要做任何修改。这就是面向接口编程的魅力。4.2 上传进度回调真实上传场景中用户往往需要看到上传进度。可以在接口层面增加进度回调的支持exportinterfaceUploadProgress{bytesWritten:number;totalBytes:number;percentage:number;}exportclassImageUploadService{staticasyncuploadImage(localUri:string,onProgress?:(progress:UploadProgress)void):PromiseUploadedImage{// 在 http 请求的 on(progress) 中回传进度// onProgress({ bytesWritten, totalBytes, percentage: bytesWritten / totalBytes * 100 });// ...}}存根模式下onProgress参数可以直接忽略或立即回传 100% 完成——无论哪种方式都不会影响调用方的逻辑。4.3 批量上传队列在画伴梦工厂中用户可能会一次性选择多张图片进行批量处理。此时可以扩展一个批量上传队列exportclassImageUploadService{staticasyncuploadMultiple(localUris:string[],onProgress?:(index:number,total:number,uri:string)void):PromiseUploadedImage[]{constresults:UploadedImage[][];for(leti0;ilocalUris.length;i){constresultawaitthis.uploadImage(localUris[i]);results.push(result);onProgress?.(i1,localUris.length,localUris[i]);}returnresults;}}存根模式下这个批量上传就是循环调用存根方法——瞬时返回完全不需要等待。五、本地优先策略Local-First存根模式带出了另一个重要的设计思想本地优先Local-First策略。在画伴梦工厂的架构中图片上传本质上是一个渐进增强的能力离线/开发模式存根 ↓ 网络可达但服务未部署存根 ↓ 服务上线替换为真实实现 ↓ 网络中断降级为存根或本地缓存这种设计意味着应用的核心功能——将用户图画转换为动画——不依赖于网络连接。即使用户在飞机上、在地下室、在没有蜂窝网络的平板设备上只要图片已经在本地流程就可以继续。本地优先策略的实现通常包含以下几个层次层次说明项目中对应本地存储图片保存到应用沙箱fileIo 沙箱路径本地索引以本地 URI 作为唯一标识localUri字段存根服务模拟远程服务的行为ImageUploadService存根远程同步网络可用时上传到服务器真实的uploadImage实现冲突处理本地与远程数据的一致性维护项目暂未涉及存根模式正是本地优先策略在服务层的具体体现——在无法或不必要访问远程服务时用本地能力替代。六、可替换服务架构设计ImageUploadService采用的静态类 统一接口模式本质上是一种轻量级的服务定位器Service Locator模式。下面是这种架构的整体设计┌─────────────────────────────────────────────┐ │ 调用方调用者 │ │ ImageUploadService.uploadImage(localUri) │ └────────────────────┬────────────────────────┘ │ 依赖接口契约不依赖实现 ▼ ┌─────────────────────────────────────────────┐ │ ImageUploadService服务门面 │ │ static async uploadImage(): UploadedImage │ └────────────────────┬────────────────────────┘ │ 可替换的实现策略 ▼ ┌─────────────────────┐ │ 存根实现开发/测试 │ │ StubUploadStrategy │ └─────────────────────┘ ┌─────────────────────┐ │ 真实实现生产环境 │ │ HttpUploadStrategy │ └─────────────────────┘ ┌─────────────────────┐ │ 缓存实现离线降级 │ │ CacheUploadStrategy │ └─────────────────────┘在更复杂的场景中我们可以将上传策略抽象为接口通过依赖注入的方式在运行时切换exportinterfaceUploadStrategy{upload(localUri:string):PromiseUploadedImage;}exportclassImageUploadService{privatestaticstrategy:UploadStrategynewStubUploadStrategy();staticsetStrategy(strategy:UploadStrategy){this.strategystrategy;}staticasyncuploadImage(localUri:string):PromiseUploadedImage{returnthis.strategy.upload(localUri);}}这种设计让策略切换成为运行时的一行代码调用。在aboutToAppear中根据网络状态选择策略aboutToAppear(){if(canIUse(SystemCapability.Communication.Network)){ImageUploadService.setStrategy(newHttpUploadStrategy());}else{ImageUploadService.setStrategy(newStubUploadStrategy());}}七、在 AI 处理管线中的位置画伴梦工厂的 AI 处理流程是一条完整的管线PipelineImageUploadService位于流程中承上启下的位置用户拍照/选图 │ ▼ 图片压缩3.4 图片压缩与 Base64 编解码 │ ▼ 图片上传 ←── ImageUploadService本文 │ ├── 存根模式 → 直接使用 localUri 继续 │ └── 真实上传 → 拿到 remoteUrl 后继续 │ ▼ GPT-4o-mini 图像识别3.5 │ ▼ Seedream 文生图3.2 │ ▼ 图生视频3.3 │ ▼ 用户查看结果在这个管线中图片上传服务的关键作用是统一图片访问方式不论图片来自相机、相册还是网络都统一为UploadedImage结构解耦前后处理环节上游拍照/选图不需要知道下游如何处理图片下游识别/生成不需要关心图片从哪来提供切换点在存根和真实实现之间无缝切换不影响上下游的任何逻辑存根模式确保了整条管线可以在完全没有网络连接的情况下完整走通——这对于开发阶段的调试、自动化测试、以及离线演示场景至关重要。八、存根模式在鸿蒙开发中的实践意义结合鸿蒙生态和画伴梦工厂项目的实际经验存根模式带来了以下几个层面的收益8.1 开发效率提升在 HarmonyOS 应用开发中真机调试的资源往往比较稀缺需要注册开发者、申请设备、配置签名。存根模式让开发者可以在预览器Previewer中就跑通包含网络请求的完整流程无需真机、无需后端。8.2 并行开发解耦团队中前端 UI 开发者、AI 服务集成者、后端 API 开发者可以并行工作。前端工程师只需要知道ImageUploadService.uploadImage(localUri)返回PromiseUploadedImage这个契约就可以独立完成 UI 开发和联调。8.3 自动化测试友好存根服务的确定性输出让单元测试变得简单可靠// 测试用例示例constresultawaitImageUploadService.uploadImage(file://test.jpg);expect(result.localUri).toBe(file://test.jpg);expect(result.remoteUrl).toBe(file://test.jpg);// 存根模式下的行为测试不依赖网络环境不需要 mock 框架也不需要测试服务器。8.4 渐进式增强鸿蒙生态覆盖了从手机、平板到智慧屏、车机的多种设备不同设备的网络能力差异巨大。存根模式天然支持能力降级有网络时使用真实上传无网络时透明降级为本地存根用户体验不受影响。九、项目中的其他存根实践存根模式的思路在画伴梦工厂中并非孤例。实际上项目的多个服务都采用了类似的设计哲学服务存根行为真实行为切换触发ImageUploadService返回localUri作为remoteUrlHTTP 上传到服务器后端就绪后替换AIGenerationService可配置为返回固定示例结果调用火山引擎 API配置开关/网络状态VideoExportService直接返回本地路径拷贝到用户指定目录环境区分这种一致的架构风格降低了团队成员的认知负担——每个服务都有一个轻量级的替身实现成为一种约定俗成的模式。总结本文通过画伴梦工厂中仅 13 行代码的ImageUploadService深入探讨了存根模式的设计思想与应用实践。知识点说明存根模式Stub Pattern用轻量级替身代替真实服务让开发不依赖后端就绪面向接口编程通过UploadedImage接口定义契约调用方不依赖具体实现本地优先策略将 localUri 作为 remoteUrl离线状态下流程依然完整可替换服务架构静态类方法封装运行时可以无缝切换实现策略AI 管线集成上传服务在拍照→识别→生成的流程中承上启下渐进式增强从存根到真实的演进路径清晰不破坏调用方代码下一篇第 3.9 篇将整合本篇和前面所有 AI 服务呈现画伴梦工厂从拍照到动画的完整 AI 编排流程——看看多个服务如何无缝协同工作。参考源码本文所有代码均来自项目文件products/default/src/main/ets/services/ImageUploadService.ets— 图片上传服务的接口定义与存根实现