vivo 企业云盘服务端实现简介
作者:来自 vivo 互联网存储团队- Cheng Zhi
本文将介绍企业云盘的基本功能以及服务端实现。
一、背景
vivo 企业云盘是一个企业级文件数据管理服务,解决办公数据的存储、共享、审计等文件管理需求;同时便于团队成员快速共享、管理文件,帮助集中管理企业数字资产,提升办公效率,实现内部数据资源的共享以及与外部客户之间的文件安全交换。
二、功能介绍
目前 vivo 企业云盘有 3 个空间:个人空间,团队空间和备份空间。
2.1 个人空间
个人空间用于存储用户个人的文件数据,其他用户不可见;容量默认为 100GB。个人空间支持文件的分享、下载、移动、重命名、星标、机房下载和删除操作,如下图所示:
2.2 团队空间
团队空间用于多人协作,团队中可容纳多名成员,每个成员都可以向团队空间中上传文件并与其他人共享这些文件,也可以下载其他人上传到该团队空间的文件;团队空间没有容量限制。
用户可以在如下位置创建团队空间:
团队空间的创建者默认为该空间的管理员,管理员可以在左边菜单栏中的团队空间下看到“团队设置”和“成员管理”,在“团队设置”页可以修改该团队空间的名称和团队描述信息:
在“成员管理”页可以添加成员并修改已有成员的权限:
2.3 合作伙伴
团队空间中除了内部员工还可以加入外部合作伙伴,管理员可在如下页面申请合作伙伴账号:
点击“新增”后在弹出的“申请外部用户账号”页填写合作伙伴相关信息即可提交直接上级领导审批,审批通过后会在该团队空间中生成一个合作伙伴账号,账号及初始密码会以邮件形式发送到合作伙伴邮箱,合作伙伴登录后即可上传文件或下载分享给他的文件。
出于数据安全的考虑,合作伙伴无法看到团队空间中内部员工上传的文件,只能看到自己上传的文件以及分享给他的文件。
管理员可以在”成员管理“页禁用合作伙伴账号:
2.4 备份空间
备份空间用于备份用户本地电脑上的文件。目前企业云盘网页端只能查看已有的备份策略,新建备份策略需要在企业云盘客户端进行;用户可以在企业云盘网页端右上角的“客户端下载”下载企业云盘客户端:
在客户端的“备份同步”页点击“新增备份”,然后在弹出的对话框中选择想要备份的本地文件夹即可创建备份记录:
企业云盘客户端将按用户设置的频率将指定文件夹下的文件上传到对象存储以实现文件备份;对于实时备份,企业云盘客户端会每 3 分钟扫描一次本地文件夹,并与远程的文件进行对比,将新增的文件上传到对象存储。
三、功能实现
企业云盘的存储分为元数据和对象存储两部分,元数据存储使用的是 MySQL,保存的是用户,群组以及文件等实体的元数据,文件的实际数据是以对象的形式保存在对象存储中。企业云盘架构如下:
下面介绍一下各个功能是如何实现的:
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 对比,如果相等,则验证通过。以上过程如下图所示:
团队空间的数据保存在 groups 表中,该表会记录团队名称、创建人等信息;用户与团队空间的归属关系保存在 group_usrs 表中,该表会记录每个团队空间有哪些用户,以及这些用户在团队空间中的权限。
在个人空间中用户对文件有最高权限,可以任意操作;当用户操作的文件属于某个团队空间时前端会在请求中携带 group_id,服务端会根据 group_id 查询 group_usrs 表,从而获取该用户在该团队空间中的权限,进而判断用户是否有权限执行相应操作。
3.2 文件上传
用户可以通过点击页面的上传按钮然后选择本地文件或拖拽文件/文件夹到企业云盘页面的方式上传文件,除此之外开启备份策略时也会调用上传接口;用户发起上传后,前端会判断文件大小,如果在 10MB 以内则直接上传,否则,对于备份的文件将文件按 10MB 大小分片进行分片上传,其他文件按 5MB 进行分片上传。
所有文件的元数据都保存在 files 表中,该表会记录文件名、文件路径、文件所在空间、文件数据在对象存储中的 key、文件所属用户等信息;所有文件夹的元数据都保存在 folder 表中,该表会记录文件夹的名称、路径、文件夹所在空间、文件夹所属用户等信息。
3.2.1 小文件上传
小文件上传的逻辑如下:
查数据库获取用户及其所在空间的空间信息;
空间用量校验;
查找文件夹,如果文件夹不存在则新建文件夹;
查找文件,构造新 files 记录:如果文件不存在,则使用原始文件名;如果文件已存在,则在文件名后面拼接序号以区别于原文件;
上传文件数据到对象存储;
生成随机字符串作为 file_mark,将第 4 步中的 files 记录插入 files 表。
3.2.2 大文件上传
大文件指采用分片方式上传的文件,文件分片的信息保存在 multi 表中,multi 表会记录分片对应的文件、上传者、分片总数、当前分片编号、upload id 等信息。
大文件分片上传分 3 个步骤:
start 阶段
查数据库获取用户及所在空间信息,认证鉴权;
判断文件是否已经存在;
查找文件夹,如果文件夹不存在则新建文件夹;
查找文件,构造新 files 记录:如果文件不存在,则使用原始文件名;如果文件已存在,则在文件名后面拼接序号以区别于原文件;
从对象存储获取用于分片上传的 upload id;
生成随机字符串作为 file_mark,将第 4 步中的 files 记录插入 files 表;
将分片记录插入 multi 表;
将 upload id 返回给客户端,用于后续关联分片;将 file_mark 返回给客户端,用于后续关联文件。
upload 阶段
查数据库获取用户及所在空间信息,认证鉴权;
通过 file_mark 获取文件信息;
通过 upload id 获取文件的分片信息;
为当前分片生成 multi 表记录;
将当前分片数据上传到对象存储;
将第 4 步中的 multi 记录插入 files 表。
complete 阶段
查数据库获取用户及所在空间信息,认证鉴权;
通过 file_mark 获取文件信息;
通过 upload id 获取文件的分片信息;
通知对象存储进行分片合并操作;
删除该文件所有分片记录;
更新目录用量及文件状态。
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 机器上传到企业云盘。使用外链上传需要先申请权限,申请通过后企业云盘页面可以看到”机房上传“按钮:
点击该按钮会将命令行复制到剪切板,命令行格式如下:
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"
将 “在此输入文件名称!” 部分修改为要上传的文件名然后执行命令行即可上传文件。
该功能实现原理如下:
查数据库获取用户及所在空间信息,认证鉴权;
判断文件夹是否存在,不存在则返回错误;
生成外链。用户点击机房上传时服务端会为文件构造 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 文件下载
用户在企业云盘界面选中文件即可下载文件,流程如下:
查数据库获取用户及所在空间信息,认证鉴权
判断文件是否存在
用文件的元数据中的 path 作为 object key 调用对象 SDK 获取文件的预签名 URL
将预签名 URL 返回给前端,前端根据链接下载文件
另外用户也可以通过机房链接将文件下载到 Linux 的机器上:
或者获取办公网链接,该链接可以在办公网下载文件;这两个链接的获取也是调用的下载文件的接口,只是为了方便在 Linux 系统上下载文件而在前面拼接了 wget。
四、总结
本文简单介绍了 vivo 企业云盘的基本功能,并介绍了这些功能在服务端具体的实现原理,其中重点介绍了认证鉴权和文件的上传下载。希望读者阅读后对 vivo 企业云盘能有更深入的了解,也希望本文能在应用的认证鉴权及文件的上传下载逻辑方面对读者有所启发。
vivo 企业云盘服务端实现简介的更多相关文章
- NAS设备部署后采用Mobox企业云盘来提升管理功能
首先NAS介绍 网络接入存储(Network-Attached Storage,简称NAS)是存储设备通过标准的网络拓扑结构(例如以太网)添加到一群计算机上.NAS是文件级的存储方法,它的重 ...
- (原创)高仿360云盘android端的UI实现
前些日子几大互联网巨头展开了一轮网盘空间大战.一下子从G级别提高到了T级别.以后谁的空间没有1T估计都不好意思开口了~~~ 试用了一下360云盘的客户端,比较小清新(不是给360打广告~~~).刚好U ...
- 阿里云OSS 服务端签名后直传之分片上传(结合element-ui的upload组件)
分片上传(结合element-ui的upload组件实现自定义上传) async uploadFree(content){ let data = await this.getOssToken(); / ...
- 哭瞎!360云盘将关停,你的几十T照片和文件该怎么办
IDO老徐刚得到了一个非常不开心的消息,360云盘将停止个人云盘服务...进行业务转型,在网盘存储.传播内容的合法性和安全性得到彻底解决之前不再考虑恢复,之后转型企业云服务. 而且之前共享的所有资料, ...
- vivo 手机云服务建设之路-平台产品系列04
作者:vivo 互联网平台产品研发团队 - He Zhichuang.Han Lei 手机云服务目前作为每家手机厂商必备的一项基础服务,其服务能力和服务质量对用户来说可以说是非常重要.用户将自己大量的 ...
- vivo 服务端监控架构设计与实践
一.业务背景 当今时代处在信息大爆发的时代,信息借助互联网的潮流在全球自由的流动,产生了各式各样的平台系统和软件系统,越来越多的业务也会导致系统的复杂性. 当核心业务出现了问题影响用户体验,开发人员没 ...
- 整合SPRING CLOUD云服务架构 - 企业分布式微服务云架构构建
整合SPRING CLOUD云服务架构 - 企业分布式微服务云架构构建 1. 介绍 Commonservice-system是一个大型分布式.微服务.面向企业的JavaEE体系快速研发平台,基于模 ...
- RPC学习--C#使用Thrift简介,C#客户端和Java服务端相互交互
本文主要介绍两部分内容: C#中使用Thrift简介 用Java创建一个服务端,用C#创建一个客户端通过thrift与其交互. 用纯C#实现Client和Server C#服务端,Java客户端 其中 ...
- 百度云推送-服务端 C# SDK
思路: 1.公司有项目需要做android和ios手机端推送消息的功能: 2.没有接触过这方面的知识,一头雾水,开始在网上一顿搜,网上倒是有不少解决方案,首先搜的是android的解决方案,因为ios ...
- angulaijs中的ng-upload-file与阿里云oss服务的结合,实现在浏览器端上传文件到阿里云(速度可以达到1.5M)
2015-10-26 angularjs结合aliyun浏览器端oos文件上传加临时身份验证例子 在服务端获取sts 源码: public class StsServiceSample { // 目前 ...
随机推荐
- CD、VCD、DVD、BD 傻傻分不清楚?
CD 激光唱片(Compact Disk, CD),于 1982 年面世,最初用于存储数字音频.容量约 700 MB(80 分钟音频). 激光唱片 | 维基百科 VCD 影音光盘(Video Comp ...
- manim边学边做--带箭头直线
带箭头的直线就是有方向的直线,既可以用来表示矢量,也可以用来标记某个关键位置.manim中提供了4种常用的带箭头的直线模块: Arrow:单箭头的直线 DoubleArrow:双箭头的直线 Label ...
- Spark - Pandas UDF
spark 本身对 dataframe 的操作支持没有像pandas 那样强大,所有我们可以把spark dataframe 转化成 pandas dataframe 来利用pandas datafr ...
- net core中byte数组如何高效转换为16进制字符串
在 .NET Core 中,如何把 byte[] 转换为 16 进制字符串?你能想到哪些方法?什么方式性能最好?今天和大家分享几种转换方式. 往往在处理字符串性能问题时,首先应该想到的是怎么想办法减少 ...
- jQuery - 不同版本的差异對比
jQuery 一共分了 1.x.2.x.3.x 这三个大版本. jQuery 的版本都是不向后兼容的! jQuery 的版本都是不向后兼容的! jQuery 的版本都是不向后兼容的!重要的事情说三遍哈 ...
- 一篇文章讲清楚synchronized关键字的作用及原理
概述 在应用Sychronized关键字时需要把握如下注意点: 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待: 每个实例都对应有自己的一把锁(this),不同实例之间互不影响:例外:锁对象是 ...
- 分析ueventd Coldboot耗时问题
安卓go平台启动时间发现如下ueventd耗时1.907s问题: 01-11 00:20:02.854 0 0 I init : Parsing file /odm/etc/init... 01-11 ...
- android 性能优化 -systrace
简介: Systrace允许监视和跟踪Android系统的行为(trace).它会指明系统都在哪些工作上花费时间.CPU周期都用在哪里,甚至可以看到每个线程.进程在指定时间内都在干嘛.它同时还会突出观 ...
- yaml.load与yaml.dump的用法
import yaml #向yaml文件中写 with open("E:\个人\ rename.yaml", 'w') as f: project = {'在远方':"1 ...
- 嘟嘟牛在线登陆加密分析-RPC调用
加密参数 JADX反编译后搜索代码 user/login 基本可以确定就是从这里发起网络请求 跟进 addRequestMap 方法分析 这一看逻辑就清晰了 先添加一个时间戳,在对sign进行加密后在 ...