vivo 企业云盘服务端实现简介

作者:来自 vivo 互联网存储团队- Cheng Zhi

本文将介绍企业云盘的基本功能以及服务端实现。

一、背景

vivo 企业云盘是一个企业级文件数据管理服务,解决办公数据的存储、共享、审计等文件管理需求;同时便于团队成员快速共享、管理文件,帮助集中管理企业数字资产,提升办公效率,实现内部数据资源的共享以及与外部客户之间的文件安全交换。

二、功能介绍

目前 vivo 企业云盘有 3 个空间:个人空间,团队空间和备份空间。

2.1 个人空间

个人空间用于存储用户个人的文件数据,其他用户不可见;容量默认为 100GB。个人空间支持文件的分享、下载、移动、重命名、星标、机房下载和删除操作,如下图所示:

图片1.png
图片2.png
图片3.png

2.2 团队空间

团队空间用于多人协作,团队中可容纳多名成员,每个成员都可以向团队空间中上传文件并与其他人共享这些文件,也可以下载其他人上传到该团队空间的文件;团队空间没有容量限制。

用户可以在如下位置创建团队空间:

图片4.png

团队空间的创建者默认为该空间的管理员,管理员可以在左边菜单栏中的团队空间下看到“团队设置”和“成员管理”,在“团队设置”页可以修改该团队空间的名称和团队描述信息:

图片5.png

在“成员管理”页可以添加成员并修改已有成员的权限:

图片6.png

2.3 合作伙伴

团队空间中除了内部员工还可以加入外部合作伙伴,管理员可在如下页面申请合作伙伴账号:

图片7.png

点击“新增”后在弹出的“申请外部用户账号”页填写合作伙伴相关信息即可提交直接上级领导审批,审批通过后会在该团队空间中生成一个合作伙伴账号,账号及初始密码会以邮件形式发送到合作伙伴邮箱,合作伙伴登录后即可上传文件或下载分享给他的文件。

出于数据安全的考虑,合作伙伴无法看到团队空间中内部员工上传的文件,只能看到自己上传的文件以及分享给他的文件。

管理员可以在”成员管理“页禁用合作伙伴账号:

图片8.png

2.4 备份空间

备份空间用于备份用户本地电脑上的文件。目前企业云盘网页端只能查看已有的备份策略,新建备份策略需要在企业云盘客户端进行;用户可以在企业云盘网页端右上角的“客户端下载”下载企业云盘客户端:

图片9.png

在客户端的“备份同步”页点击“新增备份”,然后在弹出的对话框中选择想要备份的本地文件夹即可创建备份记录:

图片10.png

企业云盘客户端将按用户设置的频率将指定文件夹下的文件上传到对象存储以实现文件备份;对于实时备份,企业云盘客户端会每 3 分钟扫描一次本地文件夹,并与远程的文件进行对比,将新增的文件上传到对象存储。

三、功能实现

企业云盘的存储分为元数据和对象存储两部分,元数据存储使用的是 MySQL,保存的是用户,群组以及文件等实体的元数据,文件的实际数据是以对象的形式保存在对象存储中。企业云盘架构如下:

图片11.png

下面介绍一下各个功能是如何实现的:

3.1 用户认证鉴权

企业云盘在用户的身份验证中使用了非对称加密,前端持有一个公钥,后端持有一个私钥,用户登录时,前端首先获取浏览器指纹 webFinger,同时生成一个随机数种子 seed,然后用公钥计算出一个特征字符串 RSA(webFinger+seed),然后将此字符串放入请求 header 中的 finger 字段,传递给服务端;另外企业云盘接入了 uuc 单点登录系统,uuc 登录成功后会在请求的 cookie 字段中放置 uuc-token 和 uuc-uuid,这两个值也会传给后端。

服务端收到登录请求后,先使用 cookie 中的 uuc-token 以及 uuc-uuid 调用 uuc 接口查询得到用户 uid, 然后尝试从 user 表中查询用户信息,如果查询不到那么说明用户是第一次登录企业云盘,那么服务端会从 uuc 获取用户信息并存储在 user 表中;然后服务端利用私钥解密登录请求中的特征字符串,得到 webFinger,再根据 webFinger + 当前时间 + uid 进行 AES 加密得到一个字符串 clouddisk-token,并将 clouddisk-token 放置在 cookie 中,返回给客户端。在发送后续请求时,客户端需要将 clouddisk-token 保持在 cookie 中。

在后续请求中,客户端以同样的方式生成 finger,并且在请求中携带 clouddisk-token;服务端接收到请求后,将 clouddisk-token 进行AES解密,获取 uid + 时间 + webFinger,同时服务端根据自身持有的私钥,对 header 中的 finger 解密,获取此 finger 对应的 webFinger,与解密 token 得到的 webFinger 对比,如果相等,则验证通过。以上过程如下图所示:

图片12.png
图片13.png

团队空间的数据保存在 groups 表中,该表会记录团队名称、创建人等信息;用户与团队空间的归属关系保存在 group_usrs 表中,该表会记录每个团队空间有哪些用户,以及这些用户在团队空间中的权限。

在个人空间中用户对文件有最高权限,可以任意操作;当用户操作的文件属于某个团队空间时前端会在请求中携带 group_id,服务端会根据 group_id 查询 group_usrs 表,从而获取该用户在该团队空间中的权限,进而判断用户是否有权限执行相应操作。

3.2 文件上传

用户可以通过点击页面的上传按钮然后选择本地文件或拖拽文件/文件夹到企业云盘页面的方式上传文件,除此之外开启备份策略时也会调用上传接口;用户发起上传后,前端会判断文件大小,如果在 10MB 以内则直接上传,否则,对于备份的文件将文件按 10MB 大小分片进行分片上传,其他文件按 5MB 进行分片上传。

所有文件的元数据都保存在 files 表中,该表会记录文件名、文件路径、文件所在空间、文件数据在对象存储中的 key、文件所属用户等信息;所有文件夹的元数据都保存在 folder 表中,该表会记录文件夹的名称、路径、文件夹所在空间、文件夹所属用户等信息。

3.2.1 小文件上传

小文件上传的逻辑如下:

  1. 查数据库获取用户及其所在空间的空间信息;

  2. 空间用量校验;

  3. 查找文件夹,如果文件夹不存在则新建文件夹;

  4. 查找文件,构造新 files 记录:如果文件不存在,则使用原始文件名;如果文件已存在,则在文件名后面拼接序号以区别于原文件;

  5. 上传文件数据到对象存储;

  6. 生成随机字符串作为 file_mark,将第 4 步中的 files 记录插入 files 表。

3.2.2 大文件上传

大文件指采用分片方式上传的文件,文件分片的信息保存在 multi 表中,multi 表会记录分片对应的文件、上传者、分片总数、当前分片编号、upload id 等信息。

大文件分片上传分 3 个步骤:

start 阶段

  1. 查数据库获取用户及所在空间信息,认证鉴权;

  2. 判断文件是否已经存在;

  3. 查找文件夹,如果文件夹不存在则新建文件夹;

  4. 查找文件,构造新 files 记录:如果文件不存在,则使用原始文件名;如果文件已存在,则在文件名后面拼接序号以区别于原文件;

  5. 从对象存储获取用于分片上传的 upload id;

  6. 生成随机字符串作为 file_mark,将第 4 步中的 files 记录插入 files 表;

  7. 将分片记录插入 multi 表;

  8. 将 upload id 返回给客户端,用于后续关联分片;将 file_mark 返回给客户端,用于后续关联文件。

upload 阶段

  1. 查数据库获取用户及所在空间信息,认证鉴权;

  2. 通过 file_mark 获取文件信息;

  3. 通过 upload id 获取文件的分片信息;

  4. 为当前分片生成 multi 表记录;

  5. 将当前分片数据上传到对象存储;

  6. 将第 4 步中的 multi 记录插入 files 表。

complete 阶段

  1. 查数据库获取用户及所在空间信息,认证鉴权;

  2. 通过 file_mark 获取文件信息;

  3. 通过 upload id 获取文件的分片信息;

  4. 通知对象存储进行分片合并操作;

  5. 删除该文件所有分片记录;

  6. 更新目录用量及文件状态。

3.2.3 元数据与对象的对应

以下是使用对象存储 SDK 从对象存储获取对象的示例代码:

params := &s3.GetObjectInput{
    Bucket: aws.String("BucketName"), // bucket名称
    Key: aws.String("ObjectKey"),     // object key
}
 
resp, err := client.GetObject(params)
if err != nil{
    panic(err)
}
 
//读取返回结果中body的前20个字节
b := make([]byte, 20)
n, err := resp.Body.Read(b)
fmt.Printf("%-20s %-2v %v\n", b[:n], n, err)

可以看到为了从对象存储获取对象只需要提供一个桶名(bucket name)和键名(object key)即可。桶名信息在配置文件中,服务端启动后即会加载到内存中;object key 是通过 “用户工号 + 路径 + 时间戳 + _ + 文件名” 格式拼接成的字符串。

例如:

用户 11*****9 在 2023-12-19 14:15:40 将文件 test.txt 上传到个人空间中 /a/b/c/ 目录下,那么这个文件对应的 object key 就是

11*****9/a/b/c/2023-12-19T14:15:40+08:00_test.txt;

如果这个字符串长度小于 128 字节那么就用这个字符串作为文件的 object key。如果拼接后的字符串长度大于 128 字节,那么服务端会先计算文件路径的 md5 值,记为 md5(path),然后拼接字符串:用户工号 + / + md5(path) + 时间戳 + _ + 文件名,该 object key 生成之后会存入 files 表的 path 字段。

3.2.4 外链上传

企业云盘还支持通过外链将文件从 Linux 机器上传到企业云盘。使用外链上传需要先申请权限,申请通过后企业云盘页面可以看到”机房上传“按钮:

图片14.png

点击该按钮会将命令行复制到剪切板,命令行格式如下:

file="在此输入文件名称!";curl -s -X PUT "http://******/clouddisk-prd/******?Expires=******&AWSAccessKeyId=******&Signature=******" -H "x-amz-acl: public-read" -H "x-amz-content-maxlength: 200000000000000000" -H "Content-Type: application/octet-stream" --data-binary "@$file";curl -s -X POST "pan-idc.vivo.xyz/api/file/sync" -H "clouddisk-token: ******"  -H "finger: ******" -H "Content-Type: application/json" -H "path: ******" -H "hashname: ******" -H "filename: $file"

将 “在此输入文件名称!” 部分修改为要上传的文件名然后执行命令行即可上传文件。

该功能实现原理如下:

  1. 查数据库获取用户及所在空间信息,认证鉴权;

  2. 判断文件夹是否存在,不存在则返回错误;

  3. 生成外链。用户点击机房上传时服务端会为文件构造 object key,首先拼接字符串:clouddisk_ + 用户工号 + _ + 当前时间时间戳,然后计算该字符串的 SHA1 哈希值,记为 SHA(ut),然后拼接字符串 ”用户工号 + 文件路径 + / + SHA(ut)“ 作为将上传的文件的 object key;然后用这个 object key 调用对象存储 sdk 生成预签名 URL 用于上传,这个预签名 URL 就是外链中第一个 curl 命令行请求的 URL。第二个 curl 用于调用企业云盘服务端接口将文件元数据写入 MySQL,包括将 object key 写入 files 表的 path 字段。

可以看到在用户使用外链上传文件时,时间戳起到了关联文件数据与文件元数据的作用,因此用户每次上传都必须重新拷贝链接,而不能复用之前的链接,否则会导致已上传的文件被覆盖。

3.3 文件下载

用户在企业云盘界面选中文件即可下载文件,流程如下:

  1. 查数据库获取用户及所在空间信息,认证鉴权

  2. 判断文件是否存在

  3. 用文件的元数据中的 path 作为 object key 调用对象 SDK 获取文件的预签名 URL

  4. 将预签名 URL 返回给前端,前端根据链接下载文件

另外用户也可以通过机房链接将文件下载到 Linux 的机器上:

图片15.png

或者获取办公网链接,该链接可以在办公网下载文件;这两个链接的获取也是调用的下载文件的接口,只是为了方便在 Linux 系统上下载文件而在前面拼接了 wget。

四、总结

本文简单介绍了 vivo 企业云盘的基本功能,并介绍了这些功能在服务端具体的实现原理,其中重点介绍了认证鉴权和文件的上传下载。希望读者阅读后对 vivo 企业云盘能有更深入的了解,也希望本文能在应用的认证鉴权及文件的上传下载逻辑方面对读者有所启发。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容