CloudDrive 域把企业网盘和个人云盘的文件操作能力收拢到一个接口背后。6 家厂商(阿里云盘、百度网盘、坚果云、Microsoft365 OneDrive、够快云库、爱数 AnyShare)共享同一个 ICloudDriveProvider,涵盖文件读写、文件夹管理、搜索、共享权限和版本管理五个区域。
域结构
Bitzsoft.Integrations.CloudDrive ← 抽象层├── Bitzsoft.Integrations.CloudDrive.Aliyun ← 阿里云盘与相册(PDS)├── Bitzsoft.Integrations.CloudDrive.Baidu ← 百度网盘├── Bitzsoft.Integrations.CloudDrive.Nutstore ← 坚果云├── Bitzsoft.Integrations.CloudDrive.Microsoft365 ← OneDrive(Microsoft Graph)├── Bitzsoft.Integrations.CloudDrive.Gokuai ← 够快云库├── Bitzsoft.Integrations.CloudDrive.AnyShare ← 爱数 AnyShare└── Bitzsoft.Integrations.CloudDrive.All ← 聚合包标准三层。
统一接口(18 方法,五区)
ICloudDriveProvider 按操作类型分成五个区域,共约 18 个方法:
public interface ICloudDriveProvider{ string ProviderName { get; }
// ① 文件操作(6) Task<UploadFileResult> UploadAsync(UploadFileRequest request, CancellationToken ct = default); Task<Stream> DownloadAsync(string fileId, CancellationToken ct = default); Task DeleteFileAsync(string fileId, CancellationToken ct = default); Task<DriveFileInfo> MoveFileAsync(string fileId, string targetFolderId, string? newName = null, CancellationToken ct = default); Task<DriveFileInfo> CopyFileAsync(string fileId, string targetFolderId, string? newName = null, CancellationToken ct = default); Task<DriveFileInfo> GetFileInfoAsync(string fileId, CancellationToken ct = default);
// ② 文件夹操作(3) Task<ListFilesResult> ListFilesAsync(string folderId, int pageIndex = 1, int pageSize = 50, CancellationToken ct = default); Task<DriveFileInfo> CreateFolderAsync(CreateFolderRequest request, CancellationToken ct = default); Task DeleteFolderAsync(string folderId, CancellationToken ct = default);
// ③ 搜索(1) Task<ListFilesResult> SearchAsync(SearchFilesRequest request, CancellationToken ct = default);
// ④ 共享与权限(5) Task<DriveShareLink> CreateShareLinkAsync(CreateShareLinkRequest request, CancellationToken ct = default); Task DeleteShareLinkAsync(string fileId, string linkId, CancellationToken ct = default); Task<IReadOnlyList<DrivePermission>> GetPermissionsAsync(string fileId, CancellationToken ct = default); Task<DrivePermission> SetPermissionAsync(string fileId, DrivePermission permission, CancellationToken ct = default); Task DeletePermissionAsync(string fileId, string permissionId, CancellationToken ct = default);
// ⑤ 版本管理(3) Task<IReadOnlyList<DriveFileVersion>> ListVersionsAsync(string fileId, CancellationToken ct = default); Task<Stream> DownloadVersionAsync(string fileId, string versionId, CancellationToken ct = default); Task<DriveFileInfo> RestoreVersionAsync(string fileId, string versionId, CancellationToken ct = default);}接口的 XML 文档明确说明:受限于供应商 API 能力,部分操作可能抛 NotSupportedException,调用方应在使用前确认供应商支持所需操作。
错误处理:无 Result 包装
CloudDrive 域没有 Result 包装——接口直接返回模型对象,失败时抛 CloudDriveException:
public sealed class CloudDriveException : IntegrationException{ public string ProviderName => Provider ?? string.Empty; public new string? ErrorCode => base.ErrorCode; public new string? ProviderMessage => base.ProviderMessage;
public CloudDriveException(string providerName, string message, string? errorCode = null, string? providerMessage = null, Exception? innerException = null) : base(domain: "CloudDrive", message: $"[{providerName}] {message}", ...) { }}上传与搜索请求模型
UploadFileRequest 的 FolderId / FileName / Content 是 required 成员,连接器不会释放 Content 流(生命周期由调用方管理)。ContentLength 用于大文件上传策略判断,ConflictBehavior 控制重名时重命名/覆盖/跳过。
SearchFilesRequest 支持按关键词全局或指定文件夹范围搜索,可按文件类型、扩展名筛选,排序默认按修改时间:
public sealed class SearchFilesRequest{ public required string Keyword { get; init; } public string? FolderId { get; init; } // null = 全局搜索 public DriveFileType? FileType { get; init; } // File / Folder 筛选 public string? Extension { get; init; } // 扩展名筛选 public FileSortOrder SortBy { get; init; } = FileSortOrder.ModifiedTime; public int PageIndex { get; init; } = 1; public int PageSize { get; init; } = 50;}共享链接与权限
CreateShareLinkRequest 用 ShareLinkAccessLevel 枚举(不是字符串)控制访问级别:
public enum ShareLinkAccessLevel{ View = 0, // 仅查看 Edit = 1, // 可编辑 Upload = 2, // 仅上传 Download = 3, // 仅下载}DriveShareLink 返回的链接可带密码、过期时间、最大访问次数(null 表示无限制),但不同厂商支持程度不一。DrivePermission 用 PermissionRole(Reader / Writer / Owner)描述角色,部分厂商支持独立的下载/预览权限开关(CanDownload / CanPreview)。
厂商清单
| 厂商 | Provider ID | 鉴权 | 定位 |
|---|---|---|---|
| 阿里云盘 | aliyun | OAuth 2.0 Bearer | 个人云盘 + 相册(PDS v2) |
| 百度网盘 | baidu | OAuth 2.0 | 个人云盘 |
| 坚果云 | nutstore | 账号密码 / API Token | 个人与团队同步 |
| OneDrive | microsoft365 | OAuth 2.0(MS Graph) | 企业网盘 |
| 够快云库 | gokuai | API Token | 企业文档协作 |
| 爱数 AnyShare | anyshare | 账号密码 / OAuth | 企业内容管理 |
各家对共享链接和权限模型的支持程度不一,阿里云 Descriptor 明确记录 PDS API 不提供独立权限管理接口,文件版本管理契约尚未确认。全部 Preview 稳定性。
注册
单厂商
// ① 坚果云builder.Services.AddBitzsoftNutstoreCloudDrive(builder.Configuration.GetSection("CloudDrive:Nutstore"));
// ② OneDrive(走 Microsoft Graph)builder.Services.AddBitzsoftMicrosoft365CloudDrive(builder.Configuration.GetSection("CloudDrive:Microsoft365"));多厂商聚合
builder.Services.AddBitzsoftCloudDriveAll(builder.Configuration, "CloudDrive");配置:
{ "CloudDrive": { "Aliyun": { "ClientId": "...", "ClientSecret": "...", "RedirectUri": "..." }, "Microsoft365": { "TenantId": "...", "ClientId": "...", "ClientSecret": "..." } }}消费
文件上传与列表
public class DriveService(ICloudDriveProvider drive){ // ① 上传文件到指定文件夹 public async Task<DriveFileInfo> UploadAsync(string folderId, string fileName, Stream content) { var result = await drive.UploadAsync(new UploadFileRequest { FolderId = folderId, FileName = fileName, Content = content, }); return result.FileInfo; }
// ② 列出文件夹内容(分页) public async Task<ListFilesResult> ListAsync(string folderId, int page = 1) => await drive.ListFilesAsync(folderId, pageIndex: page, pageSize: 50);}共享链接与版本回滚
public class SharingService(ICloudDriveProvider drive){ // ① 创建只读共享链接 public async Task<DriveShareLink> ShareAsync(string fileId) { return await drive.CreateShareLinkAsync(new CreateShareLinkRequest { FileId = fileId, AccessLevel = ShareLinkAccessLevel.View, // 仅查看 // Password / ExpireTime / MaxAccessCount 部分厂商不支持 }); }
// ② 回滚到历史版本 public async Task<DriveFileInfo> RollbackAsync(string fileId, string versionId) => await drive.RestoreVersionAsync(fileId, versionId);}多厂商路由
public class DriveRouter(IIntegrationProviderResolver<ICloudDriveProvider> drives){ public async Task<DriveFileInfo> UploadAsync(string platform, string folderId, string fileName, Stream content) { var drive = drives.GetRequired(platform); // "aliyun" / "nutstore" / "microsoft365" ... var result = await drive.UploadAsync(new UploadFileRequest { FolderId = folderId, FileName = fileName, Content = content }); return result.FileInfo; }}