diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj
new file mode 100644
index 000000000..f9f7278ba
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN.Abp.FileStorage.Qiniu.csproj
@@ -0,0 +1,16 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/AbpQiniuFileStorageModule.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/AbpQiniuFileStorageModule.cs
new file mode 100644
index 000000000..2568f6cd5
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/AbpQiniuFileStorageModule.cs
@@ -0,0 +1,10 @@
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.FileStorage.Qiniu
+{
+ [DependsOn(typeof(AbpFileStorageModule))]
+ public class AbpQiniuFileStorageModule : AbpModule
+ {
+
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/QiniuFileStorageOptions.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/QiniuFileStorageOptions.cs
new file mode 100644
index 000000000..7a7838a0b
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/QiniuFileStorageOptions.cs
@@ -0,0 +1,40 @@
+namespace LINGYUN.Abp.FileStorage.Qiniu
+{
+ public class QiniuFileStorageOptions
+ {
+ ///
+ /// Api授权码
+ ///
+ public string AccessKey { get; set; }
+ ///
+ /// Api密钥
+ ///
+ public string SecretKey { get; set; }
+ ///
+ /// 默认自动删除该文件天数
+ /// 默认 0,不删除
+ ///
+ public int DeleteAfterDays { get; set; }
+ ///
+ /// 上传成功后,七牛云向业务服务器发送 POST 请求的 URL。
+ /// 必须是公网上可以正常进行 POST 请求并能响应 HTTP/1.1 200 OK 的有效 URL
+ ///
+ public string UploadCallbackUrl { get; set; }
+ ///
+ /// 上传成功后,七牛云向业务服务器发送回调通知时的 Host 值。
+ /// 与 callbackUrl 配合使用,仅当设置了 callbackUrl 时才有效。
+ ///
+ public string UploadCallbackHost { get; set; }
+ ///
+ /// 上传成功后,七牛云向业务服务器发送回调通知 callbackBody 的 Content-Type。
+ /// 默认为 application/x-www-form-urlencoded,也可设置为 application/json。
+ ///
+ public string UploadCallbackBodyType { get; set; }
+ ///
+ /// 上传成功后,自定义七牛云最终返回給上传端(在指定 returnUrl 时是携带在跳转路径参数中)的数据。
+ /// 支持魔法变量和自定义变量。returnBody 要求是合法的 JSON 文本。
+ /// 例如 {"key": $(key), "hash": $(etag), "w": $(imageInfo.width), "h": $(imageInfo.height)}。
+ ///
+ public string UploadCallbackBody { get; set; }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/QiniuFileStorageProvider.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/QiniuFileStorageProvider.cs
new file mode 100644
index 000000000..2a19b2d4e
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage.Qiniu/LINGYUN/Abp/FileStorage/Qiniu/QiniuFileStorageProvider.cs
@@ -0,0 +1,111 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Qiniu.IO;
+using Qiniu.IO.Model;
+using Qiniu.RS;
+using Qiniu.Util;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Volo.Abp.DependencyInjection;
+
+namespace LINGYUN.Abp.FileStorage.Qiniu
+{
+ [Dependency(ServiceLifetime.Transient, ReplaceServices = true)]
+ [ExposeServices(typeof(IFileStorageProvider), typeof(FileStorageProvider))]
+ public class QiniuFileStorageProvider : FileStorageProvider
+ {
+ protected QiniuFileStorageOptions Options { get; }
+ public QiniuFileStorageProvider(
+ IFileStore store,
+ IOptions options)
+ : base(store)
+ {
+ Options = options.Value;
+ }
+
+ protected override async Task DownloadFileAsync(FileInfo fileInfo, string saveLocalPath)
+ {
+ Mac mac = new Mac(Options.AccessKey, Options.SecretKey);
+
+ int expireInSeconds = 3600;
+ string accUrl = DownloadManager.CreateSignedUrl(mac, fileInfo.Url, expireInSeconds);
+
+ var saveLocalFile = Path.Combine(saveLocalPath, fileInfo.Name);
+ var httpResult = await DownloadManager.DownloadAsync(accUrl, saveLocalFile);
+ if(httpResult.Code == 200)
+ {
+ using (var fs = new FileStream(saveLocalFile, FileMode.Open, FileAccess.Read))
+ {
+ fileInfo.Data = new byte[fs.Length];
+
+ await fs.ReadAsync(fileInfo.Data, 0, fileInfo.Data.Length).ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ // TODO: 处理响应代码
+
+ Console.WriteLine(httpResult.Code);
+ }
+
+ return fileInfo;
+ }
+
+ protected override async Task RemoveFileAsync(FileInfo fileInfo, CancellationToken cancellationToken = default)
+ {
+ Mac mac = new Mac(Options.AccessKey, Options.SecretKey);
+
+ var bucket = fileInfo.Directory + ":" + fileInfo.Name;
+ var backetManager = new BucketManager(mac);
+ await backetManager.DeleteAsync(bucket, fileInfo.Name);
+
+ throw new NotImplementedException();
+ }
+
+ protected override async Task UploadFileAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default)
+ {
+ Mac mac = new Mac(Options.AccessKey, Options.SecretKey);
+
+ PutPolicy putPolicy = new PutPolicy
+ {
+ Scope = fileInfo.Directory + ":" + fileInfo.Name,
+ CallbackBody = Options.UploadCallbackBody,
+ CallbackBodyType = Options.UploadCallbackBodyType,
+ CallbackHost = Options.UploadCallbackHost,
+ CallbackUrl = Options.UploadCallbackUrl
+ };
+ if (expireIn.HasValue)
+ {
+ putPolicy.SetExpires(expireIn.Value);
+ }
+ if (Options.DeleteAfterDays > 0)
+ {
+ putPolicy.DeleteAfterDays = Options.DeleteAfterDays;
+ }
+
+
+ string jstr = putPolicy.ToJsonString();
+ string token = Auth.CreateUploadToken(mac, jstr);
+
+ UploadProgressHandler handler = (uploadByte, totalByte) =>
+ {
+ OnFileUploadProgressChanged(uploadByte, totalByte);
+ };
+
+ // 带进度的上传
+ ResumableUploader uploader = new ResumableUploader();
+ var httpResult = await uploader.UploadDataAsync(fileInfo.Data, fileInfo.Name, token, handler);
+
+ // 普通上传
+ //FormUploader fu = new FormUploader();
+ //var httpResult = await fu.UploadDataAsync(fileInfo.Data, fileInfo.Name, token);
+
+ // TODO: 处理响应代码
+
+ Console.WriteLine(httpResult.Code);
+ }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN.Abp.FileStorage.csproj b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN.Abp.FileStorage.csproj
new file mode 100644
index 000000000..76ce15181
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN.Abp.FileStorage.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netstandard2.0
+
+
+
+
+
+
+
+
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/AbpFileStorageModule.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/AbpFileStorageModule.cs
new file mode 100644
index 000000000..57147498e
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/AbpFileStorageModule.cs
@@ -0,0 +1,8 @@
+using Volo.Abp.Modularity;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ public class AbpFileStorageModule : AbpModule
+ {
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileDownloadCompletedEventArges.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileDownloadCompletedEventArges.cs
new file mode 100644
index 000000000..8d41bafa5
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileDownloadCompletedEventArges.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ public class FileDownloadCompletedEventArges : EventArgs
+ {
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileDownloadProgressEventArges.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileDownloadProgressEventArges.cs
new file mode 100644
index 000000000..d254b4461
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileDownloadProgressEventArges.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ public class FileDownloadProgressEventArges : EventArgs
+ {
+
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileInfo.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileInfo.cs
new file mode 100644
index 000000000..7376e7297
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileInfo.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ ///
+ /// 文件信息
+ ///
+ public class FileInfo
+ {
+ ///
+ /// 名称
+ ///
+ public string Name { get; set; }
+ ///
+ /// 大小
+ ///
+ public long Size { get; set; }
+ ///
+ /// 文件路径
+ ///
+ public string Directory { get; set; }
+ ///
+ /// 文件扩展名
+ ///
+ public string Extension { get; set; }
+ ///
+ /// 文件哈希码,用于唯一标识
+ ///
+ public string Hash { get; set; }
+ ///
+ /// 文件链接
+ ///
+ public string Url { get; set; }
+ ///
+ /// 文件数据
+ ///
+ public byte[] Data { get; set; }
+ ///
+ /// 媒体类型
+ ///
+ public MediaType MediaType { get; set; }
+ ///
+ /// 创建时间
+ ///
+ public DateTime CreationTime { get; set; }
+ ///
+ /// 创建人
+ ///
+ public Guid? CreatorId { get; set; }
+ ///
+ /// 上次变更时间
+ ///
+ public DateTime? LastModificationTime { get; set; }
+ ///
+ /// 上次变更人
+ ///
+ public Guid? LastModifierId { get; set; }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileStorageProvider.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileStorageProvider.cs
new file mode 100644
index 000000000..57d33ca5e
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileStorageProvider.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ public abstract class FileStorageProvider : IFileStorageProvider
+ {
+ public event EventHandler FileDownloadProgressChanged;
+ public event EventHandler FileDownloadCompleted;
+ public event EventHandler FileUploadProgressChanged;
+ public event EventHandler FileUploadCompleted;
+
+ protected IFileStore Store { get; }
+
+ public FileStorageProvider(IFileStore store)
+ {
+ Store = store;
+ }
+
+ public async Task DeleteFileAsync(string hash, CancellationToken cancellationToken = default)
+ {
+ // 获取文件信息
+ var file = await Store.GetFileAsync(hash);
+ // 删除文件
+ await RemoveFileAsync(file, cancellationToken);
+ // 删除文件信息
+ await Store.DeleteFileAsync(hash, cancellationToken);
+ }
+
+ public async Task GetFileAsync(string hash, string saveLocalPath)
+ {
+ // 获取文件信息
+ var file = await Store.GetFileAsync(hash);
+ // 下载文件
+ return await DownloadFileAsync(file, saveLocalPath);
+ }
+
+ public async Task StorageAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default)
+ {
+ // step1 上传文件
+ await UploadFileAsync(fileInfo, expireIn, cancellationToken);
+ // step2 保存文件信息
+ await Store.StorageAsync(fileInfo, expireIn, cancellationToken);
+ }
+
+ protected abstract Task UploadFileAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default);
+
+ protected abstract Task DownloadFileAsync(FileInfo fileInfo, string saveLocalPath);
+
+ protected abstract Task RemoveFileAsync(FileInfo fileInfo, CancellationToken cancellationToken = default);
+
+ protected virtual void OnFileUploadProgressChanged(long sent, long total)
+ {
+ FileUploadProgressChanged?.Invoke(this, new FileUploadProgressEventArges(sent, total));
+ }
+
+ protected virtual void OnFileUploadConpleted()
+ {
+ FileUploadCompleted?.Invoke(this, new FileUploadCompletedEventArges());
+ }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileUploadCompletedEventArges.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileUploadCompletedEventArges.cs
new file mode 100644
index 000000000..eb23f111d
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileUploadCompletedEventArges.cs
@@ -0,0 +1,8 @@
+using System;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ public class FileUploadCompletedEventArges : EventArgs
+ {
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileUploadProgressEventArges.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileUploadProgressEventArges.cs
new file mode 100644
index 000000000..9ae7def26
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/FileUploadProgressEventArges.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ public class FileUploadProgressEventArges : EventArgs
+ {
+ ///
+ /// 上传数据大小
+ ///
+ public long BytesSent { get; }
+ ///
+ /// 总数据大小
+ ///
+ public long TotalBytesSent { get; }
+ public FileUploadProgressEventArges(long sent, long total)
+ {
+ BytesSent = sent;
+ TotalBytesSent = total;
+ }
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/IFileStorageProvider.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/IFileStorageProvider.cs
new file mode 100644
index 000000000..b7c892b45
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/IFileStorageProvider.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ ///
+ /// 文件存储提供者
+ ///
+ public interface IFileStorageProvider
+ {
+ event EventHandler FileDownloadProgressChanged;
+ event EventHandler FileDownloadCompleted;
+
+ event EventHandler FileUploadProgressChanged;
+ event EventHandler FileUploadCompleted;
+
+ ///
+ /// 存储文件
+ ///
+ /// 文件信息
+ /// 过期时间,单位(s)
+ ///
+ ///
+ Task StorageAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default);
+ ///
+ /// 获取文件
+ ///
+ /// 文件唯一标识
+ /// 保存到本地路径
+ ///
+ Task GetFileAsync(string hash, string saveLocalPath);
+ ///
+ /// 删除文件
+ ///
+ /// 文件唯一标识
+ ///
+ ///
+ Task DeleteFileAsync(string hash, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/IFileStore.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/IFileStore.cs
new file mode 100644
index 000000000..f4b12a87b
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/IFileStore.cs
@@ -0,0 +1,39 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace LINGYUN.Abp.FileStorage
+{
+ ///
+ /// 文件存储接口
+ ///
+ public interface IFileStore
+ {
+ ///
+ /// 存储文件
+ ///
+ /// 文件信息
+ /// 过期时间,单位(s)
+ ///
+ ///
+ Task StorageAsync(FileInfo fileInfo, int? expireIn = null, CancellationToken cancellationToken = default);
+ ///
+ /// 获取文件
+ ///
+ /// 文件唯一标识
+ ///
+ Task GetFileAsync(string hash);
+ ///
+ /// 文件是否存在
+ ///
+ /// 文件唯一标识
+ ///
+ Task FileHasExistsAsync(string hash);
+ ///
+ /// 删除文件
+ ///
+ /// 文件唯一标识
+ ///
+ ///
+ Task DeleteFileAsync(string hash, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/MediaType.cs b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/MediaType.cs
new file mode 100644
index 000000000..ebabe6fda
--- /dev/null
+++ b/aspnet-core/modules/common/LINGYUN.Abp.FileStorage/LINGYUN/Abp/FileStorage/MediaType.cs
@@ -0,0 +1,25 @@
+namespace LINGYUN.Abp.FileStorage
+{
+ ///
+ /// 媒体类型
+ ///
+ public enum MediaType
+ {
+ ///
+ /// 文档
+ ///
+ Document = 0,
+ ///
+ /// 图像
+ ///
+ Image = 2,
+ ///
+ /// 影像
+ ///
+ Video = 3,
+ ///
+ /// 音乐
+ ///
+ Music = 4
+ }
+}