极狐 GitLab

极狐GitLab 中的文件存储

我们使用 CarrierWave gem 来处理文件上传、存储和检索。

文件上传应该由 Workhorse 加速,详情请参考上传开发文档

根据上下文,文件上传在许多地方都有使用:

  • 系统
    • 实例 Logo(在登录/注册页面可见的 Logo)
    • 头部 Logo(导航栏中显示的 Logo)
  • 群组
    • 群组头像
  • 用户
    • 用户头像
    • 用户代码片段附件
  • 项目
    • 项目头像
    • 议题/合并请求/备注 Markdown 附件
    • 议题/合并请求/备注旧版 Markdown 附件
    • CI 产物(归档、元数据、追踪)
    • LFS 对象
    • 合并请求差异
    • 设计管理设计缩略图
  • 主题
    • 主题头像

磁盘存储#

极狐GitLab 最初将所有内容保存在本地磁盘上。虽然目录位置与以往版本有所不同,但它们仍然没有 100% 标准化。你可以在下表中看到它们:

描述在 DB 中?相对路径(从 CarrierWave.root 开始)上传器类模型类型
实例 Logouploads/-/system/appearance/logo/:id/:filenameAttachmentUploaderAppearance
头部 Logouploads/-/system/appearance/header_logo/:id/:filenameAttachmentUploaderAppearance
群组头像uploads/-/system/group/avatar/:id/:filenameAvatarUploaderGroup
用户头像uploads/-/system/user/avatar/:id/:filenameAvatarUploaderUser
用户代码片段附件uploads/-/system/personal_snippet/:id/:random_hex/:filenamePersonalFileUploaderSnippet
项目头像uploads/-/system/project/avatar/:id/:filenameAvatarUploaderProject
主题头像uploads/-/system/projects/topic/avatar/:id/:filenameAvatarUploaderTopic
议题/合并请求/备注 Markdown 附件uploads/:hash_project_id/:random_hex/:filenameFileUploaderProject
设计管理设计缩略图uploads/-/system/design_management/action/image_v432x230/:id/:filenameDesignManagement::DesignV432x230UploaderDesignManagement::Action
CI 产物 (基础版)shared/artifacts/:disk_hash[0..1]/:disk_hash[2..3]/:disk_hash/:year_:month_:date/:job_id/:job_artifact_id (:disk_hashproject_id 的 SHA256 摘要)JobArtifactUploaderCi::JobArtifact
LFS 对象 (基础版)shared/lfs-objects/:hex/:hex/:object_hashLfsObjectUploaderLfsObject
外部合并请求差异shared/external-diffs/merge_request_diffs/mr-:parent_id/diff-:idExternalDiffUploaderMergeRequestDiff
Issuable 指标图片uploads/-/system/issuable_metric_image/file/:id/:filenameIssuableMetricImageUploaderIssuableMetricImage

CI 产物和 LFS 对象在基础版和旗舰版中行为有所不同。在基础版中,它们继承 GitlabUploader;而在旗舰版中,它们继承 ObjectStorage 并将文件存储在兼容 S3 API 的对象存储中。

Markdown 中议题、合并请求和备注的附件使用项目 ID 的哈希进行哈希存储

我们提供了一个一体化的 Rake 任务,用于一次性将所有上传迁移到对象存储。如果引入了新的上传器类或模型类型,请确保在类别列表中为其添加相应的 Rake 任务调用。

路径段#

文件存储在多个位置,并使用不同的路径方案。所有从 GitlabUploader 派生的类都应遵守此路径段架构:

plaintext
1| 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)进行映射。

ruby
1class 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 属性来选择正确的对象存储。该列必须存在于模型架构中。

ruby
1class 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