# NovaStorage 使用规则（cc 编程工具）

面向 cc 的编程规则，帮助你在任意页面仅通过 `cid` 即可加载与使用存储工具，无需关注 Provider 细节。

## 快速约定
- 仅需 `cid`：使用 `NovaStorage.withCid(cid)` 自动选择企业账套 Provider。
- 统一返回：上传结果为 `NovaFile`，含 `key`, `url`, `name`, `type`, `size`, `metadata`, `md5` 等。
- 生成 Key：默认格式 `storage/company/<cid>/<prefixKey?>/<YYYYMMDD>/<HHmmss-rand>-<name>`。
- 可选前缀：通过 `prefixKey` 在公司段后、日期目录前插入自定义目录（自动去除首尾斜杠）。
- 查重与保存：封装 `getAttachmentByMd5` 与 `saveAttachment`，避免重复上传并统一写入 `Attachment`。

## 依赖导入
```ts
import { NovaStorage, NovaFile } from 'fmode-ng/core';
```

## 初始化（仅凭 cid）
```ts
const cid = localStorage.getItem('company')!; // 或服务获取
const storage = await NovaStorage.withCid(cid);
```

## 上传文件（含进度与 prefixKey）
```ts
const file = (event.target as HTMLInputElement).files![0];
const uploaded: NovaFile = await storage.upload(file, {
  prefixKey: 'project/pid/', // 项目页需要加项目前缀
  onProgress: (p) => console.log('progress %', p.total.percent),
});
console.log(uploaded.key, uploaded.url);
```

# Attachment 数据范式说明
# Parse 服务 `Attachment` 表结构分析
`Attachment` 表是 Parse 服务中用于**存储文件附件核心信息**的业务表，主要关联文件元数据、上传用户、所属公司/分类等维度，支撑附件的管理、查询与关联业务（如文档附件、媒体文件关联等）。


## 一、表核心定位
用于记录附件的基础属性（名称、大小、URL）、校验信息（MD5）、关联主体（用户、公司、分类）及文件元数据（分辨率、时长等），是附件与业务模块（如内容、用户系统）的关联枢纽。


## 二、字段详情
### 1. 核心业务字段（自定义字段）
以下字段为代码中明确通过 `attachment.set(...)` 定义的业务字段，涵盖附件的核心属性与关联关系：

| 字段名       | 数据类型                  | 赋值逻辑                                                                 | 字段含义                                                                 | 可选性 |
|--------------|---------------------------|--------------------------------------------------------------------------|--------------------------------------------------------------------------|--------|
| `size`       | Number（整数）            | 取自 `file.size`（`NovaFile` 或传入对象的 `size` 字段，单位：字节）       | 附件文件的实际大小，用于前端展示、容量统计等                             | 必选   |
| `url`        | String                    | 1. 优先取 `file.url`；<br>2. 若 `url` 非 HTTP 开头，拼接 `domain` 前缀；<br>3. 清除 `undefined/` 无效前缀 | 附件的**可访问地址**（绝对 URL），用于前端下载、预览附件                 | 必选   |
| `name`       | String                    | 取自 `file.name`（`NovaFile` 或传入对象的文件名，含扩展名）               | 附件的原始文件名（如 `test.jpg`），用于前端展示、文件识别                 | 必选   |
| `mime`       | String                    | 取自 `file.type`（MIME 类型，如 `image/jpeg`、`audio/mp3`、`video/mp4`）  | 附件的文件类型，用于判断文件类别（图片/音视频/文档）、前端渲染方式        | 必选   |
| `md5`        | String                    | 1. 优先取 `file.md5`；<br>2. 若有 `srcFile`（原始 `File/Blob`），通过 `calcFileMd5` 计算 | 附件文件的 MD5 哈希值，用于**文件完整性校验**（避免传输损坏）、去重判断   | 可选   |
| `metadata`   | Object（嵌套结构）        | 取自 `file.metadata`（若传入则直接存储，无则不主动生成）                 | 附件的**文件元数据**，随文件类型不同包含不同子字段（见下方补充）         | 可选   |
| `company`    | Parse Pointer（关联 `Company` 表） | 取值优先级：`categoryId` > `this.cid` > localStorage 的 `company` 值       | 关联附件所属的公司，用于数据隔离（不同公司只能查看自己的附件）           | 可选   |
| `category`   | Parse Pointer（关联 `Category` 表） | 取自传入的 `categoryId` 参数                                             | 关联附件所属的分类（如“产品图片”“合同附件”），用于附件分类管理           | 可选   |
| `user`       | Parse Pointer（关联 `_User` 表） | 取自 `Parse.User.current()`（当前登录用户），生成指向 `_User` 表的指针    | 记录附件的**上传用户**，用于追溯上传人、权限控制（如仅上传者可编辑）     | 可选（无当前用户时不设置） |


#### 补充：`metadata` 字段的常见子字段
`metadata` 存储文件的个性化元数据，具体子字段由文件类型决定（依赖 `getFileMetadata` 函数提取）：
| 子字段         | 数据类型 | 适用文件类型       | 含义                                                                 |
|----------------|----------|--------------------|----------------------------------------------------------------------|
| `width`        | Number   | 图片、视频         | 分辨率宽度（如图片 1920px，视频 1280px）                             |
| `height`       | Number   | 图片、视频         | 分辨率高度（如图片 1080px，视频 720px）                             |
| `duration`     | Number   | 音频、视频         | 媒体时长（单位：秒，如音频 120s，视频 300s）                         |
| `lastModified` | Number   | 所有文件           | 文件最后修改时间戳（取自 `file.lastModified` 或当前时间）             |
| `bitrate`      | Number   | 音频、视频（预留） | 媒体码率（代码类型定义中存在，暂未在 `extractMediaMetadata` 中实现） |
| `codec`        | String   | 音频、视频（预留） | 媒体编码格式（如 H.264、MP3，暂未实现）                              |
| `colorDepth`   | Number   | 图片（预留）       | 图片色深（如 24 位真彩色，暂未实现）                                 |


### 2. Parse 系统默认字段
所有 Parse Object（包括 `Attachment`）都会自动生成以下系统字段，无需手动赋值：

| 字段名         | 数据类型   | 含义                                                                 |
|----------------|------------|----------------------------------------------------------------------|
| `objectId`     | String     | 表的**主键**（唯一标识），由 Parse 自动生成（如 `X7y8Z9a0B1c2D3e4`） |
| `createdAt`    | Date       | 附件记录的**创建时间**（上传时间），自动生成                          |
| `updatedAt`    | Date       | 附件记录的**最后更新时间**（如修改分类、更新 URL），自动更新          |
| `ACL`          | Parse ACL  | 附件的权限控制列表（默认继承 Parse 全局配置，可自定义读写权限）       |


## 三、关键关联关系
`Attachment` 表通过 **Pointer 类型**与其他核心表关联，形成业务数据链路：
1. **与 `_User` 表**：通过 `user` 字段关联，追溯附件的上传用户，支持“个人附件列表”“按用户筛选附件”等场景。
2. **与 `Company` 表**：通过 `company` 字段关联，实现“公司级数据隔离”，确保不同公司用户仅能访问本公司的附件。
3. **与 `Category` 表**：通过 `category` 字段关联，支持附件的分类管理（如“产品素材”“财务文档”），便于筛选与归类。


## 四、特殊处理逻辑
1. **URL 兼容性处理**：若传入的 `file.url` 是相对路径（非 HTTP 开头），自动拼接 `domain` 生成绝对 URL，避免前端无法访问。
2. **MD5 动态计算**：若未传入 `file.md5`，但提供了原始文件 `srcFile`，则通过 `calcFileMd5` 函数动态计算 MD5，保证校验信息的可用性。
3. **公司 ID 多来源 fallback**：`company` 字段优先取显式传入的 `companyId`，其次取当前实例的 `cid`、localStorage 缓存的 `company` 值，确保关联关系尽可能完整。
