ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Polar 文件服务(File Service)详解:基于 AWS S3 的分片上传、预签名下载与恶意文件检测实现

Polar 文件服务(File Service)详解:基于 AWS S3 的分片上传、预签名下载与恶意文件检测实现 Polar 文件服务File Service详解基于 AWS S3 的分片上传、预签名下载与恶意文件检测实现【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar导读Polar 是一个面向智能时代的开源计费平台其文件服务File Service承载着托管下载权益downloadables benefit、产品媒体图、组织头像、工单附件等全部文件类能力。本篇文章以 server/polar/file/README.md 为主体骨架完整还原其本地开发环境从零配置 AWS S3 的四个步骤建桶 → 配 CORS → 建 IAM 用户与策略 → 配置 Polar 环境变量并深入到仓库源码层file/service.py、s3.py、endpoints.py、integrations/aws/s3/service.py以及 Web 端Upload.ts剖析其分片上传、预签名 URL、四类文件服务类型与 GuardDuty 恶意文件检测的完整实现原理。读完本文你将掌握如何在本地为 Polar 配置 AWS S3 存储并理解其文件生命周期从创建到下载的完整数据流。一、文件服务的定位与架构概览从 server/polar/file/README.md 开头可以看到文件服务的职责非常聚焦在 AWS S3 上完成文件的存储、管理与访问目前主要用于 Polar 托管的下载权益hosted downloads benefit。在仓库中文件服务以独立 Python 模块形式存在于 server/polar/file/由以下核心文件构成service.py业务编排层负责列出、查询、创建、完成上传、下载与删除文件s3.pyS3 服务注册表按文件服务类型映射到不同的存储桶endpoints.pyFastAPI 路由层暴露/v1/files/*的 REST 接口schemas.pyPydantic 请求/响应模型含四种文件类型的差异化校验repository.py基于 SQLAlchemy 的持久化查询层tasks.py异步任务处理 AWS GuardDuty 恶意文件扫描结果auth.py接口鉴权依赖files_read/files_write两个 OAuth scope。从数据模型看文件元数据持久化在 PostgreSQL 的files表见 server/polar/models/file.py而文件内容本身存放在 S3 对象存储中数据库字段path、checksum_etag、checksum_sha256_base64、storage_version等与 S3 对象一一对应是一种典型的元数据入库、二进制入桶的双存储架构。四类文件服务类型server/polar/models/file.py 通过FileServiceTypes枚举定义了四种服务类型它们分别映射到不同的存储桶与访问策略服务类型用途存储桶默认访问方式downloadable托管下载权益付费后发放的下载文件polar-s3私有桶预签名 URLproduct_media商品媒体图polar-s3-public公开桶公开 URLorganization_avatar组织头像polar-s3-public公开桶公开 URLsupport_case_attachment工单附件敏感不对外开放polar-s3私有桶仅预签名 URL禁止经文件 API 下载/修改/删除这种类型到桶的映射定义在 server/polar/file/s3.py私有数据可下载文件、工单附件统一进私有桶通过短期预签名 URL 访问公开媒体产品图、头像进公开桶可直接拼出公开 URL 供 CDN/浏览器加载。从源码注释可以看到support_case_attachment属于敏感附件必须走预签名 URL不能暴露公开链接。二、本地开发环境配置 AWS S3README 原文步骤完整展开README 的Development Configuration章节给出了一套可选的本地开发配置流程。下面按原文的四个步骤完整展开并补充必要的细节说明。第 1 步创建 AWS 账号这是所有后续步骤的前提。你需要一个 AWS 账号来完成 S3 桶、IAM 用户与访问密钥的创建。该步骤在 README 中仅一行带过但它是本地联调 S3 文件上传/下载的硬性前置条件。第 2 步创建 S3 存储桶Bucket创建存储桶时README 给出了两个关键要求Block all public access阻止所有公共访问这是安全默认项。即使你的文件最终通过公开 URL访问桶级别也应默认全部封闭通过对象级策略或预签名 URL 精确控制访问范围避免桶被整体暴露。配置 CORS跨域资源共享浏览器端分片上传PUT请求是跨域直连 S3 的因此桶必须允许来自本地开发服务器的跨域请求。README 提供的 CORS 配置如下原样[ { AllowedHeaders: [ * ], AllowedMethods: [ PUT, HEAD ], AllowedOrigins: [ http://127.0.0.1:3000 ], ExposeHeaders: [ ETag ] } ]逐项说明这张配置的含义AllowedMethods只开放PUT与HEADPUT用于把分片直传 S3HEAD用于服务端完成上传后校验对象元数据见 integrations/aws/s3/service.py 的get_head_or_raise。这里刻意不放GET因为读取一律走预签名 URL 或公开 URL不需要也不应该开启浏览器直读。AllowedOrigins指向http://127.0.0.1:3000这是本地 Web 开发服务器Next.js的默认地址。若你的前端跑在其他端口需要同步修改。ExposeHeaders暴露ETagWeb 端完成每个分片的PUT后需要从响应头读取ETag作为该分片的校验标记见下文 Web 端上传流程因此必须显式暴露该响应头否则浏览器脚本读不到。第 3 步创建 IAM 用户与策略创建 IAM 策略PolicyREADME 要求先创建一个 IAM 策略策略名称与 S3 桶名保持一致策略 JSON 如下原样{ Version: 2012-10-17, Statement: [ { Sid: VisualEditor0, Effect: Allow, Action: [ s3:PutObject, s3:GetObjectAttributes, s3:GetObject, s3:GetObjectVersion, s3:GetObjectVersionAttributes, s3:DeleteObject, s3:DeleteObjectVersion ], Resource: arn:aws:s3:::S3_BUCKET_NAME/* } ] }注意Resource中的S3_BUCKET_NAME需要替换为你的真实桶名。对照源码这些 Action 与 Polar 文件服务的实际调用一一对应s3:PutObject直传分片upload_part以及服务端直接上传service.py 的upload用于发票、导出文件等后端生成文件都会用到s3:GetObject/s3:GetObjectVersion预签名下载 URL 与公开 URL 的底层操作s3:DeleteObject删除文件时调用service.py 的delete_files3:GetObjectAttributes/s3:GetObjectVersionAttributes读取对象属性完成上传后获取ETag、LastModified等元数据。策略作用范围限定在arn:aws:s3:::S3_BUCKET_NAME/*桶内所有对象遵循最小权限原则。创建 IAM 用户User名称与 S3 桶名保持一致策略选择上一步创建的策略直接附加凭据用户创建完成后进入Security credentials安全凭据页面滚动到Access keys访问密钥区域生成访问密钥对Access Key ID Secret Access Key。生成的访问密钥对将填入 Polar 的环境变量中作为服务端访问 S3 的凭证。第 4 步配置 Polar 设置.env最后一步是把上面创建的桶名、访问密钥等写入 Polar 的.env覆盖POLAR_AWS_*相关配置。README 原文如下Update your.envfor thePOLAR_AWS_*settings to contain the credentials, bucket name etc from above.结合 server/polar/config.py 的源码这一组配置项的实际名称与默认值如下环境变量默认值说明POLAR_AWS_ACCESS_KEY_IDpolar-developmentIAM 用户的 Access Key IDPOLAR_AWS_SECRET_ACCESS_KEYpolar123456789IAM 用户的 Secret Access KeyPOLAR_AWS_REGIONus-east-2S3 桶所在区域需与桶保持一致POLAR_AWS_SIGNATURE_VERSIONv4AWS 签名版本SigV4POLAR_S3_FILES_BUCKET_NAMEpolar-s3私有文件桶名POLAR_S3_FILES_PUBLIC_BUCKET_NAMEpolar-s3-public公开文件桶名POLAR_S3_FILES_PRESIGN_TTL360060 分钟预签名 URL 有效期秒POLAR_S3_ENDPOINT_URL空本地可指向 MinIOhttp://127.0.0.1:9000等 S3 兼容服务POLAR_S3_PUBLIC_ENDPOINT_URL空生成给浏览器使用的预签名 URL 的端点本地开发时需与S3_ENDPOINT_URL区分见下文说明需要特别说明的是环境变量前缀机制Polar 的Settings类通过SettingsConfigDict(env_prefixpolar_, case_sensitiveFalse)读取配置见 config.py所以源码中的S3_FILES_BUCKET_NAME在.env里对应POLAR_S3_FILES_BUCKET_NAME依此类推。本地默认值可以直接连通一个开箱即用的本地 S3 环境polar-development/polar123456789这意味着仓库还提供了基于 MinIO 的本地联调方案。关于S3_ENDPOINT_URL与S3_PUBLIC_ENDPOINT_URL的区别config.py 的注释解释得很清楚在本地用 Docker 起 MinIO 时服务端在容器网络内访问 MinIO 用http://minio:9000而生成给浏览器用的预签名 URL 必须让宿主机也能访问因此要用http://localhost:9000。生产环境两者都是真实的 AWS 端点无需区分。三、文件生命周期从创建到下载的完整链路配置好 AWS 之后文件服务在运行时是如何工作的结合 endpoints.py 与 service.py 可以还原完整的数据流。阶段一创建文件生成预签名分片上传客户端调用POST /v1/files/endpoints.py携带FileCreate负载。服务端 generate_presigned_upload 的执行流程为按create_schema.service从S3_SERVICES选出对应的 S3 服务调用create_multipart_upload向 S3 发起多段上传初始化integrations/aws/s3/service.py生成对象路径{namespace}/{organization_id}/{file_uuid}/{name}——每个组织一个目录、每个文件一个 UUID 目录因此允许不同文件同名共存为客户端声明的每个分片生成一个预签名 PUT URLgenerate_presigned_upload_parts有效期为presign_ttl在files表写入一条is_uploadedFalse的元数据记录随响应返回上传 ID 与分片 URL 列表。该步骤要求调用方具备products_manage组织权限assert_organization_permission且 OAuth 需要files_writescope见 auth.py。阶段二客户端直传分片到 S3拿到预签名 URL 后浏览器绕过 Polar 后端直接把文件分片PUT到 S3这就是第 2 步配置 CORS 的原因。Web 端实现位于 clients/apps/web/src/components/FileUpload/Upload.ts以CHUNK_SIZE 10MB将文件切块Upload.ts逐块计算 SHA-256在浏览器端用 hash-wasm 计算整文件 SHA-256 与每个分片的 SHA-256getMultiparts串行上传各分片uploadMultiparts。注释明确解释了为何不能并发S3 会对 SHA-256 做校验且按文档要求分片必须按顺序接收乱序会返回 400Upload.ts每个分片成功后从响应头读取ETag与分片号、分片 SHA-256 一起保存最后调用POST /v1/files/{id}/uploaded完成整个上传。阶段三完成上传服务端校验并落库POST /v1/files/{id}/uploadedendpoints.py调用 complete_upload通过complete_multipart_upload告知 S3 所有分片已就绪integrations/aws/s3/service.pyS3 据此组装完整对象服务端head_object取回最终对象元数据把ETag、LastModified、storage_version写回files表将is_uploaded置为True——此后文件才对外可见可下载list接口默认只返回is_uploaded的文件见 service.py。对应地server/tests/file/test_endpoints.py 验证了未完成上传时 S3 对象不可用的行为只创建不上传、不 complete读取对象会抛出S3FileError。阶段四下载与公开访问私有文件downloadable调用GET /v1/files/{id}/downloadendpoints.py获得一个预签名下载 URL。服务端 generate_presigned_download_url 在签名参数中携带ResponseContentDisposition保证浏览器以下载而非内联方式处理正确处理非 ASCII 文件名与ResponseContentTypeURL 有效期默认 60 分钟POLAR_S3_FILES_PRESIGN_TTL。公开文件product_media / organization_avatar响应模型通过public_url计算字段直接生成公开 URL见 schemas.py。底层用UNSIGNED签名客户端生成ExpiresIn0的 URLintegrations/aws/s3/service.py本质是拼出一个不带签名的公开访问地址。值得注意的安全细节下载接口在返回 URL 前还会检查flagged_malicious_at被判定为恶意的文件直接拒绝下载endpoints.py。阶段五更新与删除PATCH /v1/files/{id}仅允许修改name与version两个元数据字段service.pyDELETE /v1/files/{id}执行软删除置deleted_at同时删除ProductMedia关联记录并调用 S3delete_object清理对象service.py以上两个接口均通过_assert_mutable拒绝工单附件support_case_attachment的修改与删除——工单附件属于工单记录的一部分只能经工单接口创建不通过文件 API 变更endpoints.py。四、四种文件类型的差异化校验规则不同服务类型对 MIME 类型和大小有着完全不同的约束这些约束以 Pydantic 校验形式定义在 schemas.py创建文件时后端即会强校验服务类型允许的 MIME 类型大小上限downloadable不限不限仅受 S3 与分片数约束product_media仅图片image/jpeg、image/png、image/gif、image/webp、image/svgxml10 MBorganization_avatar仅图片image/jpeg、image/png、image/gif、image/webp、image/svgxml1 MBsupport_case_attachment图片、视频mp4/quicktime/webm、PDF、CSV、纯文本、Word、Excel250 MB这些规则分别定义在 schemas.py商品媒体图、L49-L64头像与 L67-L92工单附件。此外创建请求对分片数量也有上限约束——test_endpoints.py 验证了提交超过 1 万个分片会被拒绝HTTP 422too_long校验错误防止恶意构造超大分片列表。FileCreate与FileRead均采用 Pydantic判别联合Discriminated Union以service字段作为判别器客户端传什么service就自动套用对应的校验规则与响应模型schemas.py。数据模型层面File基类通过polymorphic_on: service实现了 SQLAlchemy 单表继承四个子类DownloadableFile、ProductMediaFile、OrganizationAvatarFile、SupportCaseAttachmentFile共用files表models/file.py。五、安全机制GuardDuty 恶意文件检测文件服务内置了与 AWS GuardDutyS3 恶意文件检测联动的闭环GuardDuty 扫描上传到 S3 桶的对象发现风险后投递扫描结果事件Polar worker 通过异步任务file.guardduty_scan_resulttasks.py接收事件任务首先校验事件中的桶名是否属于POLAR_S3_FILES_BUCKET_NAME或POLAR_S3_FILES_PUBLIC_BUCKET_NAME防止伪造/误投递再按对象 key 反查files表命中后调用 flag_malicious将flagged_malicious_at与扫描详情写入数据库并向组织成员发送maintainer_file_flagged_malicious通知。文件被标记后即被禁用消费下载接口直接拒绝HTTP 403商品媒体选择接口也通过flagged_malicious_at.is_(None)条件排除被标记文件service.py。该任务以TaskPriority.MEDIUM优先级投递tasks.py对应的异步任务测试位于 server/tests/file/test_tasks.py。六、接口与权限一览文件服务的所有接口都以/v1/files为前缀endpoints.py汇总如下方法路径摘要权限GET/v1/files/列出文件支持按组织、按 ID 过滤与分页files_read/files_writePOST/v1/files/创建文件并返回预签名分片上传参数files_writeproducts_managePOST/v1/files/{id}/uploaded完成分片上传files_writeproducts_manageGET/v1/files/{id}/download获取预签名下载 URLfiles_read/files_writePATCH/v1/files/{id}更新文件名/版本工单附件除外files_writeproducts_manageDELETE/v1/files/{id}软删除并清理 S3 对象工单附件除外files_writeproducts_manage鉴权上auth.py 定义了两个依赖FileRead需要files_read或files_writescope 之一FileWrite严格要求files_write主体subject可以是User或Organization。写操作还需组织级products_manage权限endpoints.py未认证请求一律 401见 test_endpoints.py。列表接口还通过get_accessible_org_ids做数据隔离普通用户只能看到自己有权限组织的文件service.py。结语Polar 的文件服务是一个典型的小而完整的 S3 集成案例四个步骤即可在本地完成 AWS 配置建桶 → CORS → IAM →.env运行时则通过服务端签发预签名分片 URL 浏览器直传 S3 服务端完成校验的架构既避免了文件流量经过应用服务器又通过 SHA-256 校验与 GuardDuty 检测保障了数据完整性与安全性。如果你想深入阅读实现细节建议从 server/polar/file/s3.py桶映射和 clients/apps/web/src/components/FileUpload/Upload.ts浏览器端分片上传这两个端点入手配合 server/tests/file/test_endpoints.py 中的测试用例理解完整行为。【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表