目标
将本地文件上传到网盘
实现逻辑
上传流程是指,用户将本地文件上传到百度网盘云端服务器的过程。
文件上传分为三个阶段:预上传、分片上传、创建文件。
第二个阶段分片上传依赖第一个阶段预上传的结果,
第三个阶段创建文件依赖第一个阶段预上传和第二阶段分片上传的结果,串行完成这三个阶段任务后,本地文件成功上传到网盘服务器。
限制条件
目录限制
每个第三方应用在网盘只能拥有一个文件夹用于存储上传文件,该文件夹必须位于/apps目录下,apps下的文件夹名称为申请接入时填写的申请接入的产品名称。如申请接入的产品名称为云存储,那么该文件夹为/apps/云存储,用户看到的文件夹为/我的应用数据/云存储。
大小限制
所有开发者均可接入使用接口,但可上传单个文件大小根据授权用户的身份有不同的限制:
普通用户单个上传文件大小上限为4GB
会员用户单个上传文件大小上限为10GB
超级会员用户单个上传文件大小上限为20GB
注:分片数量不得超过1024个
类型限制
普通用户在网盘APP端无法上传视频、Live Photo类型的文件。
频次限制
开放平台仅对异常行为进行相应限制,不会影响到您的正常使用。
实现逻辑
预上传
预上传是通知网盘云端新建一个上传任务,网盘云端返回唯一ID uploadid 来标识此上传任务。
# 预上传是通知网盘云端新建一个上传任务,网盘云端返回唯一ID uploadid 来标识此上传任务。
def file_precreate(self, path, size, block_list, rtype=3):
api_instance = fileupload_api.FileuploadApi(self.client)
access_token = self._access_token
# path = path # 上传后使用的文件绝对路径,需要urlencode | 对于一般的第三方软件应用,路径以 "/apps/your-app-name/" 开头。对于小度等硬件应用,路径一般 "/来自:小度设备/" 开头。对于定制化配置的硬件应用,根据配置情况进行填写。
# size = size # 文件和目录两种情况:上传文件时,表示文件的大小,单位B;上传目录时,表示目录的大小,目录的话大小默认为0
block_list_str = json.dumps(block_list) # 文件各分片MD5数组的json串。block_list的含义如下,如果上传的文件小于4MB,其md5值(32位小写)即为block_list字符串数组的唯一元素;如果上传的文件大于4MB,需要将上传的文件按照4MB大小在本地切分成分片,不足4MB的分片自动成为最后一个分片,所有分片的md5值(32位小写)组成的字符串数组即为block_list。
isdir = 0 # 是否为目录,0 文件,1 目录
autoinit = 1 # 固定值1
# rtype = 3 # 文件命名策略 1。1 表示当path冲突时,进行重命名 2 表示当path冲突且block_list不同时,进行重命名 3 当云端存在同名文件时,对该文件进行覆盖
try:
api_response = api_instance.xpanfileprecreate(
access_token, path, isdir, size, autoinit, block_list_str, rtype=rtype)
# upload_id = api_response['uploadid']
api_response['path'] = path
pre_data = api_response
logging.info(f'文件预上传成功,路径为:{path},预计上传{len(block_list)}个block')
# 文件预上传成功,信息为: {'path': '/baidu/a.txt', 'uploadid': 'N1-MTE1LjIxNi4xMjQuMTY3OjE3MTAwNjMyMTU6NDY0MzUyNTE5NzU5MTgwMzk3', 'return_type': 1, 'block_list': [0], 'errno': 0, 'request_id': 464352519759180397}
return pre_data
except openapi_client.ApiException as e:
print("文件预上传失败: %s\n" % e)
分片上传
本接口用于将本地文件上传到网盘云端服务器。
文件分两种类型:
小文件,是指文件大小小于等于4MB的文件,成功调用一次本接口后,表示分片上传阶段完成;
大文件,是指文件大小大于4MB的文件,需要先将文件按照4MB大小进行切分,然后针对切分后的分片列表,逐个分片进行上传,分片列表的分片全部成功上传后,表示分片上传阶段完成。
注意事项:
- 执行示例代码时,请将示例代码中的access_token参数值替换为自行获取的access_token
- 如果文件大小小于等于4MB,无需切片,直接上传即可
- 授权用户为普通用户时,单个分片大小固定为4MB,单文件总大小上限为4GB
- 授权用户为普通会员时,单个分片大小上限为16MB,单文件总大小上限为10GB
- 授权用户为超级会员时,用户单个分片大小上限为32MB,单文件总大小上限为20GB
# 本接口用于将本地文件上传到网盘云端服务器。
# 文件分两种类型:小文件,是指文件大小小于等于4MB的文件,成功调用一次本接口后,表示分片上传阶段完成;大文件,是指文件大小大于4MB的文件,需要先将文件按照4MB大小进行切分,然后针对切分后的分片列表,逐个分片进行上传,分片列表的分片全部成功上传后,表示分片上传阶段完成。
def file_upload(self, pre_data, block_list, block_data):
api_instance = fileupload_api.FileuploadApi(self.client)
access_token = self._access_token
path = pre_data['path'] # 上传后使用的文件绝对路径,需要urlencode,需要与上一个阶段预上传precreate接口中的path保持一致
uploadid = pre_data['uploadid'] # 上一个阶段预上传precreate接口下发的uploadid
# partseq = "0" # 文件分片的位置序号,从0开始,参考上一个阶段预上传precreate接口返回的block_list
file = '' # 上传的文件内容
type = "tmpfile" # 固定值 tmpfile
block_upload_md5s = []
for i in pre_data['block_list']:
bytes_io = io.BytesIO(block_data[i])
bytes_io.name = block_list[i]
bytes_io.mode = 'rb'
file = io.BufferedReader(bytes_io)
api_response = api_instance.pcssuperfile2(
access_token, str(i), path, uploadid, type, file=file
)
block_upload_md5s.append(api_response['md5'])
# print(api_response)
logging.info(f'文件上传成功,生成了{len(block_upload_md5s)}个block')
return block_upload_md5s
创建文件
本接口用于将多个文件分片合并成一个文件,生成文件基本信息,完成文件的上传最后一步。
def file_create(self,path, size, block_list,uploadid):
api_instance = fileupload_api.FileuploadApi(self.client)
access_token = self._access_token
# path = pre_data['path'] # 与precreate的path值保持一致
isdir = 0 # int | isdir
# size = pre_data['size'] # int | 与precreate的size值保持一致
# uploadid = pre_data['uploadid'] # str | precreate返回的uploadid
block_list = json.dumps(block_list) # str | 与precreate的block_list值保持一致
rtype = 3 # int | rtype (optional)
try:
api_response = api_instance.xpanfilecreate(
access_token, path, isdir, size, uploadid, block_list, rtype=rtype)
# pprint(api_response)
logging.info(f'文件上传成功:{api_response["path"]}')
file_info = {'fs_id': api_response['fs_id'],
'filename': api_response["path"].rsplit('/')[-1],
'path': api_response["path"],
'category': api_response['category'],
'size': api_response['size'],
'ctime': api_response["ctime"],
'mtime': api_response["mtime"],
'isdir': api_response["isdir"], # 是否目录,0 文件、1 目录
}
return file_info
except openapi_client.ApiException as e:
print("Exception when calling FileuploadApi->xpanfilecreate: %s\n" % e)
整合函数
由于我们日常操作不需要关心分片部分,故整合为一个常用函数,用来支持上传,入参我们只需要关心本地文件路径和最终网盘目录即可
# 文件上传
def upload_create(self,file_path, remote_dir='/apps/rank/'):
file_info = self.get_file_info(file_path)
remote_dir = remote_dir if remote_dir.endswith('/') else remote_dir + '/'
if file_info is None: # 目录
is_dir = 0
size = 0
pass
else: # 文件
remote_path = remote_dir + file_info['file_name']
size = file_info['file_size']
block_data = file_info['block_data']
block_list = file_info['block_list']
# 预上传
pre_data = self.file_precreate(remote_path, size, block_list)
upload_id = pre_data['uploadid']
# print(pre_data)
block_upload_md5s = self.file_upload(pre_data,block_list, block_data)
if block_upload_md5s == block_list:
logging.info('文件上传md5校验成功')
else:
logging.error(f'md5校验验证失败:原md5值:{block_list},上传后返回的md5值:{block_upload_md5s}')
# print(block_upload_md5s)
result_info = self.file_create(remote_path, size, block_list,upload_id)
return result_info
总结
上述方法中,我们对文件进行分片,这部分其实是在内存中实现的,对于非常大的文件,这种方式可能不太适用。对于大文件,我们可以考虑把分片文件临时保存到本地,之后再分片上传,但是这种方式需要注意文件最好是在上传的时候进行切分,且上传完成后要及时清理掉。同时对于分片很多的时候,需要考虑并发上传,来提高上传速率。这部分内容后续会慢慢优化出来。