云盘全自动备份

大类
技术标签
优先级
状态
开始日期
最后更新
Public

开发Prompt定型

关键问题:到底要不要把文件和Drive划分为两个不同的表存储

两个选择:
  • 分为两种不同的Entity:Scope和Entry
    • Scope对应Drive,也有层级,Entry对应File,也有层级
    • 好处:绝大部分云盘都有这样的namespace/scope概念
      • google drive:team drive
      • ms:Sharepoint site
      • s3:bucket
    • 好处:scope和entry确实是两种完全不同的东西
      • scope往往存的是权限列表,而entry则完全是文件信息
    • 坏处:bookkeeping逻辑更复杂
      • 如移动的时候,移动了父级文件夹,在gdrive里面就是直接更新文件夹的parent。因此如果跨scope移动,所有子文件的scopeid也需要跟着动
      • 坏处:AI容易混乱
        • ID是唯一的,而且通过ParentID链表必然能直接推导出当前所在的Scope
        • 所以AI很可能经常忘记更新Scope
        • 哪怕人可能也会经常忘
  • 直接共用一种,都叫Entry,通过type字段来动态读取信息
    • 好处:
      • 实现简单,bookkeeping难度低,很不容易犯错
    • 坏处:
      • 一旦api需要传入scope的信息,或者需要scope的信息来判断东西,那么就需要在数据库中遍历
  • 如果我们用一个cache缓存了当前entry的scope信息呢?
    • 内存占用是小事,但是又会有缓存有效性问题,文件移动的时候需要invalidate掉这些缓存
    • 不过这么看,invalidate其实是简单的,因为在内存里做Tree的树枝操作是很简单的,但是在数据库这种扁平链表里面对整个树枝做操作是困难的
  • 所以综合仔细思考来看,确实不应该把scope和entry分开
 
  • 但是不对,我们上面讨论的其实并不是要不要分表,而是要不要把ScopeID跟着Entry一起存
    • 上面的结果实际上结论是scope ID不能和entry一起存
 
  • 所以接下来实际讨论一下要不要分表:
  • 分表:
    • 好处:
      • scope和entry的结构清晰,方便直接查出来就丢给unmarshaler
      • scope有专门的逻辑(遍历scope和遍历entry的逻辑不一样)
    • 坏处:
      • 查询逻辑变得复杂:
        • scope怎么查所有的child?
          • 只有leaf scope才能拿着scope ID去entry表查东西,所以scope表需要表明谁是leaf
        • child怎么通过parent ID向上找到自己在哪个scope
          • parentID需要区分scope ID和普通的entry ID
            • 思路:通过将ParentID存储为一个mapping:{“type”: “scope”, “id”: XXX}
            • 复杂度增加
  • 不分表:
    • 好处:
      • 确实好写,大家是一大个树
    • 坏处:
      • 需要定义一个ID编解码逻辑吗?
        • scope和file的id会撞上吗
        • 特殊情况的scope的ID怎么办?比如个人盘
      • 还是需要区分谁是leaf scope
      • scope往往涉及到credential调度、资源调度,所以反而过度抽象为目录树会让逻辑更复杂
  • 问题核心:
    • 可以注意到,scope一般来说是无法移动、无法修改的,层级也不多
    • 所以其实在scope这一层,可以放弃链表形式,而是可以直接把整个路径写起来
      • scope这一层一般是domain → drive,总数量应该在一万以下,所以没必要这么麻烦,直接只存leaf scope,然后一次性全部查询重建为一个完整的scope tree
      • 但是这样又涉及到了数据库与内存同步的问题
    • 仔细想想,我们本来就用了mapstructrure,其实没有必要非得分表
      • 或许ID应当被定义为一个mapping?
        • {“type”: “XXX”, “provider”: “XXX”, “ID”: “XXX”}
        • 定为mapping还是拼接字符串呢?还是mapping吧,我觉得这种链表不会有人想在数据库里面自己看的
      • 直接定义多种type
        • scopetree:scope的父节点
          • 其实scope tree都不需要
          • 因为在entry里面我们定义了entry_type来区分文件、文件夹、链接
          • 所以我们同样应该在scope里面定义scope_type来区分domain、leaf等
            • 统一定义为最末层的类型为leaf,其他类型则由provider自己定义
        • scope:scope本身
        • entry:文件/文件夹
      • 共享属性:
        • ID:{“type”: “XXX”, “provider”: “XXX”, “ID”: “XXX”}
        • parentID
        • data:原始字段值
      • 还是分表吧,要不然modtime这种也做不了
 
使用go语言编写一个云盘备份工具,module名称为github.com/NyaMisty/cloud-mass-backup,严格按照下面的要求开发模块,你可以优化插件的架构,但是必须正确实现全部的功能,不得遗漏,并将各个模块的功能导出为单个的cmd程序。 同时编写cursor规则,指导所有agent严格遵守这里规定的开发规则: 1. 遵守go语言最佳开发实践,适量注释,并为每个模块编写开发文档,在用户需求或逻辑更改时同步修改文档 2. 在项目根目录下编写DESIGN.md,简洁概括当前存在的顶层模块的功能,以及他们之间的关系 3. 各个子模块同样需要设计文档,设计文档放置在每个模块代码目录下,统一命名为DESIGN.md 4. 需求或设计决策更新时,自动更新相关的DESIGN.md 5. 除了云盘api调用,在其余任何逻辑中都应当尽全力使用library,严禁自主实现任何成熟算法、成熟数据结构 6. mongodb需要强制使用`json:"XXX"`来作为bson的字段名,而不是bson tag,善用搜索引擎来寻找正确的配置方法 技术选型: - 开发语言:Go 1.23 - 数据库:MongoDB - 前端:Svelte 5,完全前后端分离 # 云盘快照微服务 - 核心概念: - Scope:Scope指代云盘自然的namespace划分概念,如google drive的shared drive,以及Sharepoint的spo site等。 - Scope也是带有层级信息的,在google drive中,典型的层级如domain -> team drive,domain -> user-personoal drive - Entry:指代云盘中的文件夹或文件 - 核心数据结构: - ItemID:特殊结构,{ "type": "entry"/"scope", "provider": "gd" / "od" / ..., "id": "XXX" } - Item基类(是scope与Entry的基类): { ID ItemID, ParentID *ItemID, # 可以为null,作为顶层节点 } - Scope类:{ Item ScopeType string Data interface{} ... } - Entry类:{ Item EntryType string Data interface{} ... } - 原始field单独在Data字段中完整存储(原始格式如map[string]interface{}),provider使用时通过mapstructure来动态映射到一个模型,从而保证能够完整存储原始字段,但又能正常使用go的struct类型 - 关系如下,最顶层为Scope,然后从Scope type为leaf开始,转为在entry表中查询,规定只有leaf的scope节点才能拥有entry子节点。 Scope1(scope_type = [any value]) -> ... -> ScopeLeaf(scope_type=leaf) -> Entry(entry_type=folder) -> ... -> Entry(entry_type=file) - Scope管理子模块 - 逻辑:利用云盘api,遍历整个domain中的全部Scope(也就是遍历所有的team drive与personal drive)。 - 要求: - 目前只需要实现google drive provider - 你需要完整获取Scope的权限信息,并存储在原始字段里 - 按照上面给出的结构正确抽象scope,scope条目中只存储当前文件最基础信息: - ID与父ID直接存储在Item中 - scope_type由各个provider自主定义,但叶子结点的类型一定为leaf - Credential管理子模块 - 逻辑:管理并为其他模块提供直接可用的access token等header - 这个模块主要负责提供相应的封装工具,如从SA获取token、刷新oauth token等等工具函数 - 所有的header生成逻辑从外部动态传入,而不是直接在模块内部处理。你需要预留好外部Credential Store的interface,通过interface来在每次请求时请求Credential。在目前阶段你只要实现一个简单的提供单个SA的store即可 - 全量快照子模块 - 逻辑:利用云盘api,高并发遍历全部的文件和目录,导出快照 - 要求: 1. 快照会非常巨大,不要导出为一个大结构体,而是使用channel动态传输 2. 需要抽象设计为支持多种不同的云盘,每个云盘设计一个provider,来兼容多种不同的云盘,目前只需要实现google drive provider 3. 正确抽象快照中的entry,entry只存储当前文件最基础信息: - ID与父ID存在Item中 - entry_type:folder文件夹/file文件/link链接/... - 文件基础信息:文件名、创建时间、修改时间、大小 5. 允许不完整文件/文件夹(dirty)存在(也就是云端更新了,但是还没有来得及再次刷新数据) - 变更流(change stream)子模块 - 逻辑:实现两套监控逻辑,通过google gsuite api监控全域文件变动,以及通过team drive id来监控共享盘的变动 - 要求: 1. 通过channel来输出 2. 基于上面的entry构建change event结构体 - 元信息存取模块 - 逻辑: 1. 基于前两个子模块实现实时云盘监控,并实时更新元信息到数据库 2. 能够高效统计一些动态值,如文件夹大小,文件夹中文件数量等等,带缓存+自动invalidate 3. 能够高效处理文件变化事件 - 要求: - 正确将entry存储到mongodb数据库中 - 监控变更流,同时定期全量快照 - 谨慎融合全量快照和变更流,并正确处理快照处理与变更流的合并,所有的操作最终应当与直接执行全量快照的输出相同 - 允许其他模块注册回调,或通过channel来接收变更事件 - 元信息API服务 - 逻辑:实现一套完善的API逻辑,提供元信息的查询、可靠polling/tailing等 # 简易前端云盘dashboard 1. 展示云盘目录树(可交互) 2. 统计云盘各个目录容量 3. 统计全局文件数量 4. 实时显示文件变化