HarmonyOS社交通讯应用开发 27 :数据模型定义与附件打包
数据模型定义与附件打包引言分布式数据对象同步的是可序列化的键值属性分布式文件系统同步的是文件实体。两者之间需要一个桥梁把正文 附件文件组织成一个能被分布式数据对象完整同步的数据结构同时把文件在哪个目录、叫什么名字、多大描述清楚让接收端能按图索骥地把文件找回来。这个桥梁就是本工程的数据模型层——entry/src/main/ets/model/ContentInfo.ets里的ContentInfo类、MediaInfo接口、MediaType枚举以及配套的getAssetInfo工具函数。本文把这三个文件串起来讲清楚三件事ContentInfo的七个字段分别代表什么MediaInfo/MediaType如何描述一个媒体附件flatAssets()与getAssetInfo()如何把对象数组压平成可同步的键值。一、ContentInfo一篇文章的全部现场先看entry/src/main/ets/model/ContentInfo.ets中的类定义exportclassContentInfo{mainTitle:string|undefined;// 标题textContent:string|undefined;// 正文mediaUriArray:ArrayMediaInfo |undefined;// 媒体列表PixelMap / 视频 uriisShowLocalInfo:boolean|undefined;// 是否展示位置信息列表isAddLocalInfo:boolean|undefined;// 是否处于添加位置状态selectLocalInfo:string|undefined;// 已选中的发布位置attachments: commonType.Assets|undefined;// 附件描述集合Asset 数组constructor(...) {/* 七个字段逐一赋值 */}flatAssets():object{ ... } }七个字段完整对应编辑页面的全部状态字段类型含义对应 UImainTitlestring文章标题标题输入框textContentstring文章正文正文输入区mediaUriArrayArrayMediaInfo内存态媒体列表媒体九宫格isShowLocalInfoboolean是否显示位置列表位置列表开关isAddLocalInfoboolean是否处于添加位置流程添加位置按钮状态selectLocalInfostring选中的发布位置位置展示文本attachmentscommonType.Assets落盘态附件描述接续时传给对端注意一个有意思的分工mediaUriArray是内存态装着 PixelMap、uri用于本地 UI 渲染attachments是落盘态装着 Asset 描述用于跨设备传递。发送端onContinue里两者都进了ContentInfo——mediaUriArray被JSON.stringify进wantParam做轻量备份attachments经flatAssets()进分布式数据对象做主通道。为什么要把编辑现场收敛成一个模型类而不是在onContinue里临时拼键值三个理由单一数据源——发送端打包与接收端还原共用同一个模型字段名不会因两端手写而漂移可序列化边界清晰——模型层明确区分了能进分布式数据对象的字段与只在本端使用的字段如 PixelMap可扩展——未来新增一个编辑状态比如话题标签只需要给ContentInfo加一个字段两端代码同步感知。模型层是接续工程的地基值得为它单独建文件。二、MediaInfo 与 MediaType一个附件的自我描述MediaInfo描述编辑区里的一个媒体项MediaType区分类型export interface MediaInfo { imagePixelMap?: PixelMap;//图片的位图对象图片时存在 mediaName: string;//媒体文件名去扩展名 mediaType: MediaType;//类型image或videovideoUri?: string;//视频的 uri视频时存在 } export enum MediaType { MEDIA_IMAGE image, MEDIA_VIDEO video}MediaInfo是可选项区分类型的设计图片项带imagePixelMap视频项带videoUri两个可选字段互斥出现mediaType则明确标注类型。UI 渲染时AddMedia.ets的addMediaBuilder正是用if (item.imagePixelMap) ... else if (item.videoUri)来分叉展示图片与视频的。每个字段在编辑期都有明确的产生来源追踪一下AddMedia.ets中的赋值点**mediaName**图片取asset.displayName去掉扩展名的部分asset.displayName.substring(0, displayName.indexOf(.))视频及拖拽/粘贴来的媒体取util.generateRandomUUID()。它是后续写文件与解析 Asset.name 的核心。**imagePixelMap**相册选图通过asset.getThumbnail得到拖拽/粘贴通过image.createImageSource(buffer).createPixelMap()得到。**videoUri**粘贴/跨端投递视频时取自pasteData.getPrimaryUri()或投递数据解码出的 uri 字符串。可见MediaInfo是编辑期各类媒体入口的归一化出口——不管图片来自相册、拖拽还是粘贴最终都归一为{ imagePixelMap, mediaName, mediaType }视频同理归一为{ videoUri, mediaName, mediaType }。归一化让后续的 UI 渲染、写分布式文件、打包 Asset 都只面对一种结构这是接口设计带来的简洁性。MediaType枚举值值得注意MEDIA_IMAGE image、MEDIA_VIDEO video是普通字符串而非数字。这并非随意为之——它直接决定了第 28 篇里attachment.name的解析方式name形如image_xxx/video_xxx按_分割就能还原出类型也让类型信息在 JSON 序列化、日志打印、跨设备传递时保持可读。三、flatAssets把数组压平成可同步键值flatAssets是数据模型里最巧妙的一个方法flatAssets():object{ let obj:objectthis;if(!this.attachments) {returnobj; }for(let i 0; i this.attachments.length; i) { obj[attachments${i}] this.attachments[i]; }returnobj; }它做了什么把attachments数组的每个元素拆出来以attachments0、attachments1、attachments2……为键挂到对象自身上最后返回this本身。为什么要这么做这要从分布式数据对象的同步机制说起第 24 篇。分布式数据对象以属性键值对为同步单位对象的每个字符串键对应一个可序列化的值框架按属性粒度做合并与冲突处理。数组本身可以作为属性值同步但数组内元素的更新粒度很粗——整数组被当作一个值整体覆盖不利于框架按附件维度管理数据。而把数组展开成attachments0、attachments1这样的独立属性后每个附件成为独立的同步单元属性级同步粒度更细键名即索引接收端重建时obj[attachments i]即可按序取回序列化结构扁平跨设备传输与日志排查都更直观。因此发送端onContinue里let source contentInfo.flatAssets();之后distributedDataObject.create(this.context, source)创建出的分布式数据对象的属性就是mainTitle、textContent、isShowLocalInfo、isAddLocalInfo、selectLocalInfo、attachments0、attachments1……接收端restored后取this.distributedObject[attachments]得到 Asset 数组对象属性访问自动支持attachments键——flatAssets 保留了原attachments字段两者并存数组完整版 逐项展开版。实现上还有一个容易被忽略的细节let obj: object this;直接复用了ContentInfo实例本身flatAssets是在原对象上追加键而不是新建对象。这样create(source)之后分布式数据对象同时拥有七个原始字段 attachmentsN 展开键属性完整、无复制开销。if (!this.attachments)的早退保证了没有附件时对象干净利落——接收端restored后取到attachments为undefined时for循环自然不执行媒体列表为空数组逻辑依旧正确。attachmentsN这类动态键名还有一层好处接收端不需要知道总共有几个附件。它只读attachments数组遍历即可而展开键attachments0/1/2...更像是框架层同步粒度的体现业务代码基本不直接碰它们。换言之flatAssets的展开主要是为了分布式数据对象好同步而非为了业务可读性——理解这一点就能明白为什么说它是打包而不是序列化。四、getAssetInfo把 MediaInfo 转成 AssetMediaInfo是内存态描述跨设备传递需要落盘态描述——commonType.Asset。转换函数在entry/src/main/ets/utils/FileUtil.etsexportfunctiongetAssetInfo(context: common.UIAbilityContext, append: MediaInfo): commonType.Asset{letfilePath context.distributedFilesDir/ append.mediaName;letattachment: commonType.Asset;try{ fileIo.statSync(filePath);// 确认文件存在leturi: string fileUri.getUriFromPath(filePath);// 路径转 uriletstat fileIo.statSync(filePath);// 取文件元信息attachment {name:${append.mediaType}_${append.mediaName},// 形如 image_uuid / video_uuiduri: uri,path: filePath,createTime: stat.ctime.toString(),modifyTime: stat.ctime.toString(),size: stat.size.toString() }; hilog.info(DOMAIN,TAG,FORMAT,[getAssetInfo] attachments:${JSON.stringify(attachment)}); }catch(err) { hilog.error(DOMAIN,TAG,FORMAT,StatSync failed. Cause code:${err.code}, message:${err.message}); }returnattachment!; }commonType.Asset是分布式数据管理提供的标准资产描述类型kit.ArkData的commonType命名空间字段包括name、uri、path、createTime、modifyTime、size等。getAssetInfo做了四件事确认文件存在fileIo.statSync(filePath)检查分布式文件目录下确实存在该媒体文件这是第 26 篇writeDistributedFile写入的成果。路径转 urifileUri.getUriFromPath(filePath)生成标准文件 uri供远端通过 uri 直接访问分布式文件。提取元信息再次statSync拿到ctime创建时间与size字节数字符串化后填入 Asset。构造 name${append.mediaType}_${append.mediaName}把类型和文件名拼成一个键——这就是第 28 篇fileCopy里attachment.name.substring(0, indexOf(_))还原类型的依据。例如图片myphoto生成image_myphoto视频 UUIDa1b2...生成video_a1b2...。name的拼装看似简单实则同时完成了三件事类型标记image/video、文件名传递接收端按后半段去distributedFilesDir找文件、与 MediaInfo.mediaType 的字符串约定呼应枚举值恰好是可拼接的普通字符串。再把commonType.Asset的六个字段逐个说明理解它们在还原阶段各自的用途字段取值接收端用途nameimage_uuid/video_uuid解析类型与文件名第 28 篇fileCopyurifileUri.getUriFromPath(filePath)结果视频直接播放图片作后备访问通道path发送端分布式目录的绝对路径日志排查、路径溯源createTimestat.ctime字符串展示/排序示例未深度使用modifyTimestat.ctime字符串同上sizestat.size字符串接收端分配读取 Buffer 的大小依据注意size是字符串——这是commonType.Asset的类型约定便于跨端传输接收端在fileCopy里用Number(attachment.size)转回数字来new ArrayBuffer(...)这正是第 28 篇要讲的细节。字段的类型一致性是跨设备协议的基本功两端对size 是字符串有共同认知才不会出现ArrayBuffer分配失败的隐性 bug。五、打包全流程onContinue 里的组装把模型层串进第 23 篇的onContinue看完整组装过程// 1. 遍历内存态媒体列表逐个转成 AssetletmediaUriArray AppStorage.getArrayMediaInfo(mediaUriArray);letassets: commonType.Assets [];if(mediaUriArray) {for(leti 0; i mediaUriArray.length; i) {letappend mediaUriArray[i];letattachment: commonType.Asset getAssetInfo(this.context, append); assets.push(attachment); } }// 2. 七字段组装 ContentInfoletcontentInfo: ContentInfo newContentInfo( AppStorage.get(mainTitle),// 标题AppStorage.get(textContent),// 正文AppStorage.get(mediaUriArray),// 内存态媒体AppStorage.get(isShowLocalInfo),// 位置列表开关AppStorage.get(isAddLocalInfo),// 添加位置状态AppStorage.get(selectLocalInfo),// 选中位置assets// 落盘态附件描述);// 3. 压平后创建分布式数据对象letsource contentInfo.flatAssets();this.distributedObject distributedDataObject.create(this.context, source);三个步骤对应模型层的三个角色MediaInfo → Asset 的转换由getAssetInfo完成七字段聚合由ContentInfo完成数组压平由flatAssets完成。至此一篇文章的完整现场变成了一组扁平的键值属性可以交给分布式数据对象跨设备同步了。顺带解释一个类型选择ContentInfo用class、MediaInfo用interface。这是刻意的——ContentInfo需要承载行为flatAssets方法且要被new实例化class 正合适MediaInfo只是纯数据结构、由各处字面量构造如{ imagePixelMap, mediaName, mediaType }interface 更轻。另外 class 实例的属性在分布式数据对象里天然可枚举、可序列化flatAssets直接复用this也正是受益于此。六、收发两端的数据流对照把模型层放在完整链路里对照能一眼看清打包与还原如何围绕同一套结构对称运作阶段发送端设备 A接收端设备 B媒体内存态MediaInfoPixelMap/uri 名字 类型重建后的MediaInfofileCopypush 进数组媒体落盘态getAssetInfo→Assetname 拼image_/video_前缀fileCopy从attachment.name拆回类型与文件名正文与状态ContentInfo七字段 flatAssets()展开restored后读distributedObject[mainTitle]等键键名约定mainTitle/textContent/isShowLocalInfo/isAddLocalInfo/selectLocalInfo/attachments与发送端完全一致同一模型保证这张表的要点是两端不是各写一套逻辑而是共享同一份契约——ContentInfo的字段名、MediaType的字符串值、Asset.name 的拼装规则。契约在entry/src/main/ets/model/ContentInfo.ets与FileUtil.ets中一次性定义两端代码都从契约出发因此发送端怎么拼、接收端就怎么拆永远不会失配。这也是把数据模型独立成文件的终极收益契约集中、两端共识。小结数据模型层是接续的翻译官负责把 UI 状态翻译成可同步的数据结构ContentInfo用七个字段收拢一篇编辑内容的全部现场MediaInfo用可选项区分图片/视频并携带文件名MediaType用可读字符串标记类型与 Asset 的name约定呼应flatAssets()把附件数组展开为attachments0/1/2...独立键换取更细的同步粒度getAssetInfo()把内存态MediaInfo转成落盘态Asset其中name形如image_uuid/video_uuid既是类型标签又是文件索引。有了这套模型发送端一次flatAssets()就能把全部现场交给分布式数据对象接收端则依据同样的约定逆向还原——下一篇《接续后媒体还原》就来讲这个逆向过程。

相关新闻

不用显卡也能跑大模型?GGUF 量化与 llama.cpp 本地推理实战

不用显卡也能跑大模型?GGUF 量化与 llama.cpp 本地推理实战

不用显卡也能跑大模型?GGUF 量化与 llama.cpp 本地推理实战 📖 摘要:云端 API 有隐私、成本、限流三道坎,本地推理正成为 2026 年开源最热方向之一。本文用量化直觉讲清"4-bit 为什么质量不崩",并用纯 Pytho…

2026/8/25 6:13:48 阅读更多 →
OpenCV计算机视觉开发入门与实践<十七>:点运算与灰度变换概述

OpenCV计算机视觉开发入门与实践<十七>:点运算与灰度变换概述

灰度化是图像处理中的一个基本步骤,其目的是将彩色图像转换为灰度图像。灰度图像是一种仅包含亮度信息而不包含颜色信息的图像,其像素值通常用一个字节(即0~255的范围)来表示,这个值代表了该像素的灰度等级&#xff0c…

2026/8/25 6:13:48 阅读更多 →
普通人如何利用信息差赚钱?互联网广告代理利润解析

普通人如何利用信息差赚钱?互联网广告代理利润解析

广告凭借全行业刚需、零囤货、无场地压力的优势脱颖而出成为普通人创业首选项目,无需复杂技术和大量人脉,仅需两三万即可完整落地启动,单人就能操作,依托成熟运营模式,轻松对接商家线上推广需求。广告业务利润空间如何…

2026/8/25 6:13:48 阅读更多 →

最新新闻

AI创业者通识日报 | 2026年8月5日

AI创业者通识日报 | 2026年8月5日

AI创业者通识日报 | 2026年8月5日 01 今日头条 TOP STORY重磅 阿里Qwen3.8-Max正式发布:2.4万亿参数,16天自主编程,下周开源 8月3日,阿里巴巴正式发布新一代旗舰大模型 Qwen3.8-Max ,总参数量达2.4万亿(稀疏…

2026/8/25 12:48:12 阅读更多 →
AI创业者通识日报 | 2026年8月13日

AI创业者通识日报 | 2026年8月13日

AI创业者通识日报 | 2026年8月13日AI竞争的下半场已悄然换轨——不再是"比谁模型大",而是"比谁用得好、用得起、用得安全"。今日聚焦AI编程赛道估值狂飙、前沿模型安全审查趋严、欧盟AI法案全面执法、以及创业者如何在巨头看不上的地方找到机会…

2026/8/25 12:48:12 阅读更多 →
共享电动车运维端用什么框架? Vue 2 + Vant + 高德地图落地方案

共享电动车运维端用什么框架? Vue 2 + Vant + 高德地图落地方案

深圳猎吧科技租车运维端介绍:车辆调度与换电管理的移动端方案 在租赁(电动车)行业,商家每天要面对三件事:车在哪、电够不够、订单有没有异常。深圳猎吧科技推出的租车运维端(sass-moblie-admin)…

2026/8/25 12:48:12 阅读更多 →
VSCode_Remote-SSH_免密登录Ubuntu24.04完整教程

VSCode_Remote-SSH_免密登录Ubuntu24.04完整教程

VS Code Remote-SSH 免密登录 Ubuntu 24.04 完整配置教程(Windows 端) 前言 在嵌入式开发、服务端开发场景中,我们常使用 VS Code 的 Remote-SSH 插件连接远程 Ubuntu 服务器进行代码开发。每次连接都手动输入密码既繁琐又影响效率,而 SSH 公钥认证 是官方推荐的免密登录…

2026/8/25 12:48:12 阅读更多 →
评测prompt效果

评测prompt效果

可以把评测拆成三件事: 是否遵守提示词要求是否忠实于原始文档周报本身是否完整、清晰、可用 不要只用“整体看起来不错”这种单一主观评分。更可靠的方法是:规则检测 LLM 裁判 人工抽检。 一、先把提示词转成可检查项 假设提示词是:根据文…

2026/8/25 12:48:12 阅读更多 →
KANO模型结果解读:需求属性的Better-Worse系数

KANO模型结果解读:需求属性的Better-Worse系数

KANO模型结果解读一、分析方法概述KANO模型是由日本学者狩野纪昭(Noriaki Kano)提出的一种产品质量分类理论,用于分析功能/服务属性与用户满意度之间的非线性关系。该模型通过正向题(如果有某功能时的评价)和负向题&am…

2026/8/25 12:47:11 阅读更多 →

日新闻

洛谷 P7912:[CSP-J 2021 T4] 小熊的果篮 ← 双向链表

洛谷 P7912:[CSP-J 2021 T4] 小熊的果篮 ← 双向链表

【题目来源】 https://www.luogu.com.cn/problem/P7912 【题目描述】 小熊的水果店里摆放着一排 n 个水果。每个水果只可能是苹果或桔子,从左到右依次用正整数 1,2,…,n 编号。连续排在一起的同一种水果称为一个“块”。小熊要把这一排水果挑到若干个果篮里&#x…

2026/8/25 0:00:34 阅读更多 →
Transformers.js 网页端图像抠图实战:零后端 3 行代码返回透明 PNG

Transformers.js 网页端图像抠图实战:零后端 3 行代码返回透明 PNG

Transformers.js 网页端图像抠图实战:零后端 3 行代码返回透明 PNG 【免费下载链接】transformers.js State-of-the-art Machine Learning for the web. Run 🤗 Transformers directly in your browser, with no need for a server! 项目地址: https:/…

2026/8/25 0:00:34 阅读更多 →
数学建模竞赛论文写作指南:从模型构建到学术表达的核心技能

数学建模竞赛论文写作指南:从模型构建到学术表达的核心技能

1. 项目概述:从“会做”到“会写”的竞赛核心跃迁“全国大学生数学建模竞赛”,这个名字对理工科学生来说,分量极重。每年,无数团队在三天三夜的时间里,为一个开放性问题绞尽脑汁,从建立模型、求解算法到编程…

2026/8/25 0:00:34 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/25 3:38:12 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/25 3:38:18 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/25 3:38:23 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/24 20:22:44 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/25 10:31:12 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/24 11:20:22 阅读更多 →