极狐GitLab 中的文件存储
我们使用 CarrierWave gem 来处理文件上传、存储和检索。
文件上传应该由 Workhorse 加速,详情请参考上传开发文档。
根据上下文,文件上传在许多地方都有使用:
- 系统
- 实例 Logo(在登录/注册页面可见的 Logo)
- 头部 Logo(导航栏中显示的 Logo)
- 群组
- 群组头像
- 用户
- 用户头像
- 用户代码片段附件
- 项目
- 项目头像
- 议题/合并请求/备注 Markdown 附件
- 议题/合并请求/备注旧版 Markdown 附件
- CI 产物(归档、元数据、追踪)
- LFS 对象
- 合并请求差异
- 设计管理设计缩略图
- 主题
- 主题头像
磁盘存储
极狐GitLab 最初将所有内容保存在本地磁盘上。虽然目录位置与以往版本有所不同,但它们仍然没有 100% 标准化。你可以在下表中看到它们:
| 描述 | 在 DB 中? | 相对路径(从 CarrierWave.root 开始) | 上传器类 | 模型类型 |
|---|---|---|---|---|
| 实例 Logo | 是 | uploads/-/system/appearance/logo/:id/:filename | AttachmentUploader | Appearance |
| 头部 Logo | 是 | uploads/-/system/appearance/header_logo/:id/:filename | AttachmentUploader | Appearance |
| 群组头像 | 是 | uploads/-/system/group/avatar/:id/:filename | AvatarUploader | Group |
| 用户头像 | 是 | uploads/-/system/user/avatar/:id/:filename | AvatarUploader | User |
| 用户代码片段附件 | 是 | uploads/-/system/personal_snippet/:id/:random_hex/:filename | PersonalFileUploader | Snippet |
| 项目头像 | 是 | uploads/-/system/project/avatar/:id/:filename | AvatarUploader | Project |
| 主题头像 | 是 | uploads/-/system/projects/topic/avatar/:id/:filename | AvatarUploader | Topic |
| 议题/合并请求/备注 Markdown 附件 | 是 | uploads/:hash_project_id/:random_hex/:filename | FileUploader | Project |
| 设计管理设计缩略图 | 是 | uploads/-/system/design_management/action/image_v432x230/:id/:filename | DesignManagement::DesignV432x230Uploader | DesignManagement::Action |
| CI 产物 (基础版) | 是 | shared/artifacts/:disk_hash[0..1]/:disk_hash[2..3]/:disk_hash/:year_:month_:date/:job_id/:job_artifact_id (:disk_hash 是 project_id 的 SHA256 摘要) | JobArtifactUploader | Ci::JobArtifact |
| LFS 对象 (基础版) | 是 | shared/lfs-objects/:hex/:hex/:object_hash | LfsObjectUploader | LfsObject |
| 外部合并请求差异 | 是 | shared/external-diffs/merge_request_diffs/mr-:parent_id/diff-:id | ExternalDiffUploader | MergeRequestDiff |
| Issuable 指标图片 | 是 | uploads/-/system/issuable_metric_image/file/:id/:filename | IssuableMetricImageUploader | IssuableMetricImage |
CI 产物和 LFS 对象在基础版和旗舰版中行为有所不同。在基础版中,它们继承 GitlabUploader;而在旗舰版中,它们继承 ObjectStorage 并将文件存储在兼容 S3 API 的对象存储中。
Markdown 中议题、合并请求和备注的附件使用项目 ID 的哈希进行哈希存储。
我们提供了一个一体化的 Rake 任务,用于一次性将所有上传迁移到对象存储。如果引入了新的上传器类或模型类型,请确保在类别列表中为其添加相应的 Rake 任务调用。
路径段
文件存储在多个位置,并使用不同的路径方案。所有从 GitlabUploader 派生的类都应遵守此路径段架构:
plaintext1| GitlabUploader 2| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- | 3| `<gitlab_root>/public/` | `uploads/-/system/` | `user/avatar/:id/` | `:filename` | 4| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- | 5| `CarrierWave.root` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` | 6| | `CarrierWave::Uploader#store_dir` | | 7 8| FileUploader 9| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- | 10| `<gitlab_root>/shared/` | `artifacts/` | `:year_:month/:id` | `:filename` | 11| `<gitlab_root>/shared/` | `snippets/` | `:secret/` | `:filename` | 12| ----------------------- + ------------------------- + --------------------------------- + -------------------------------- | 13| `CarrierWave.root` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` | 14| | `CarrierWave::Uploader#store_dir` | | 15| | | `FileUploader#upload_path` | 16 17| ObjectStore::Concern (store = remote) 18| ----------------------- + ------------------------- + ----------------------------------- + -------------------------------- | 19| `<bucket_name>` | <ignored> | `user/avatar/:id/` | `:filename` | 20| ----------------------- + ------------------------- + ----------------------------------- + -------------------------------- | 21| `#fog_dir` | `GitlabUploader.base_dir` | `GitlabUploader#dynamic_segment` | `CarrierWave::Uploader#filename` | 22| | | `ObjectStorage::Concern#store_dir` | | 23| | | `ObjectStorage::Concern#upload_path` |
RecordsUploads::Concern 关注点为每个由 GitlabUploader 存储的文件创建一个 Upload 条目,并使用 GitlabUploader#dynamic_path 持久化路径的动态部分。然后你可以使用 Upload#build_uploader 方法来操作文件。
对象存储
通过在 GitlabUploader 派生类中包含 ObjectStorage::Concern,你可以为此上传器启用对象存储。要在你的上传器中启用对象存储,你需要 1) 包含 RecordsUpload::Concern 并前置 ObjectStorage::Extension::RecordsUploads,或者 2) 挂载上传器并创建一个名为 <mount>_store 的新字段。
CarrierWave::Uploader#store_dir 被重写为:
- 当存储为 LOCAL 时,值为 GitlabUploader.base_dir + GitlabUploader.dynamic_segment
- 当存储为 REMOTE 时,值为 GitlabUploader.dynamic_segment(使用存储桶名称进行命名空间隔离)
使用 ObjectStorage::Extension::RecordsUploads
如果尚未包含,此关注点会包含 RecordsUploads::Concern。
ObjectStorage::Concern 上传器会搜索匹配的 Upload 来选择正确的对象存储。Upload 通过 #store_dirs + identifier 为每个存储(LOCAL/REMOTE)进行映射。
ruby1class SongUploader < GitlabUploader 2 include RecordsUploads::Concern 3 include ObjectStorage::Concern 4 prepend ObjectStorage::Extension::RecordsUploads 5 6 ... 7end 8 9class Thing < ActiveRecord::Base 10 mount :theme, SongUploader # 我们有一首很棒的主题曲! 11 12 ... 13end
使用挂载的上传器
ObjectStorage::Concern 查询 model.<mount>_store 属性来选择正确的对象存储。该列必须存在于模型架构中。
ruby1class SongUploader < GitlabUploader 2 include ObjectStorage::Concern 3 4 ... 5end 6 7class Thing < ActiveRecord::Base 8 attr_reader :theme_store # 这是一个 ActiveRecord 属性 9 mount :theme, SongUploader # 我们有一首很棒的主题曲! 10 11 def theme_store 12 super || ObjectStorage::Store::LOCAL 13 end 14 15 ... 16end