From 21dd025ab804e2fe608dc4cd8404343c719d91c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=83=E9=9C=B2=E5=B0=A7?= Date: Thu, 19 Jan 2023 15:49:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=8A=E4=BC=A0=E5=8D=8E?= =?UTF-8?q?=E4=B8=BA=E4=BA=91obs=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Ewide.Web.Core/Ewide.Web.Core.csproj | 1 + 20220330_Vote/Ewide.Web.Core/Startup.cs | 2 + 20220330_Vote/Ewide.Web.Entry/Program.cs | 4 + .../Ewide.Web.Entry/appsettings.json | 10 +- 20220330_Vote/Ewide.Web.Entry/web.config | 10 + .../ApiController/HuaweiService.cs | 278 ++++++++++++++++++ 20220330_Vote/Vote.Services/Dto/ObsInput.cs | 49 +++ .../Vote.Services/Tools/ZipHelper.cs | 145 +++++++++ .../Vote.Services/Vote.Services.csproj | 1 + 20220330_Vote/Vote.Services/Vote.Services.xml | 103 +++++++ 10 files changed, 602 insertions(+), 1 deletion(-) create mode 100644 20220330_Vote/Ewide.Web.Entry/web.config create mode 100644 20220330_Vote/Vote.Services/ApiController/HuaweiService.cs create mode 100644 20220330_Vote/Vote.Services/Dto/ObsInput.cs create mode 100644 20220330_Vote/Vote.Services/Tools/ZipHelper.cs diff --git a/20220330_Vote/Ewide.Web.Core/Ewide.Web.Core.csproj b/20220330_Vote/Ewide.Web.Core/Ewide.Web.Core.csproj index ca21423..9c47a70 100644 --- a/20220330_Vote/Ewide.Web.Core/Ewide.Web.Core.csproj +++ b/20220330_Vote/Ewide.Web.Core/Ewide.Web.Core.csproj @@ -14,6 +14,7 @@ + diff --git a/20220330_Vote/Ewide.Web.Core/Startup.cs b/20220330_Vote/Ewide.Web.Core/Startup.cs index 7239c9d..d2a9dfc 100644 --- a/20220330_Vote/Ewide.Web.Core/Startup.cs +++ b/20220330_Vote/Ewide.Web.Core/Startup.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using OnceMi.AspNetCore.OSS; using Serilog; using System.Text.Json; using System.Threading.Tasks; @@ -67,6 +68,7 @@ namespace Ewide.Web.Core // 设置雪花id的workerId,确保每个实例workerId都应不同 //var workerId = ushort.Parse(App.Configuration["SnowId:WorkerId"] ?? "1"); //IDGenerator.SetIdGenerator(new IDGeneratorOptions { WorkerId = workerId }); + services.AddOSSService("HuaweiCloud", "OSSProvider"); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) diff --git a/20220330_Vote/Ewide.Web.Entry/Program.cs b/20220330_Vote/Ewide.Web.Entry/Program.cs index 0bc9fe8..7f7ce45 100644 --- a/20220330_Vote/Ewide.Web.Entry/Program.cs +++ b/20220330_Vote/Ewide.Web.Entry/Program.cs @@ -16,6 +16,10 @@ namespace Ewide.Web.Entry { webBuilder.Inject() .UseStartup(); + //.UseKestrel(options => + //{ + // options.Limits.MaxRequestBodySize = null; // null Dzƣֵ M + //}); }) .UseSerilogDefault(); } diff --git a/20220330_Vote/Ewide.Web.Entry/appsettings.json b/20220330_Vote/Ewide.Web.Entry/appsettings.json index b0d8f31..fce28ee 100644 --- a/20220330_Vote/Ewide.Web.Entry/appsettings.json +++ b/20220330_Vote/Ewide.Web.Entry/appsettings.json @@ -33,7 +33,15 @@ "WithOrigins": [ "http://localhost:8080", "https://mapi.zjzwfw.gov.cn" ] }, "AppSettings": { - "InjectSpecificationDocument": false + "InjectSpecificationDocument1": false + }, + "OSSProvider": { + "Provider": "HuaweiCloud", //枚举值支持:Minio/Aliyun/QCloud + "Endpoint": "http://10.74.25.87:6020", //腾讯云中表示AppId + "Region": "", //地域 + "AccessKey": "C4D30C2801D928AAF687", + "SecretKey": "ooZVXaB1tqIz7DHTv53RILD7o5cAAAGAAdkoqlR2", + "IsEnableCache": true //是否启用缓存,推荐开启 } } \ No newline at end of file diff --git a/20220330_Vote/Ewide.Web.Entry/web.config b/20220330_Vote/Ewide.Web.Entry/web.config new file mode 100644 index 0000000..0d07ddd --- /dev/null +++ b/20220330_Vote/Ewide.Web.Entry/web.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/20220330_Vote/Vote.Services/ApiController/HuaweiService.cs b/20220330_Vote/Vote.Services/ApiController/HuaweiService.cs new file mode 100644 index 0000000..4422d9f --- /dev/null +++ b/20220330_Vote/Vote.Services/ApiController/HuaweiService.cs @@ -0,0 +1,278 @@ +using Ewide.Core; +using Furion.DynamicApiController; +using Furion.VirtualFileServer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using NPOI.OpenXml4Net.OPC.Internal; +using OBS; +using OBS.Model; +using OnceMi.AspNetCore.OSS; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using Vote.Services.Dto; + +namespace Vote.Services.ApiController +{ + /// + /// OBS + /// + [ApiDescriptionSettings("huawei", Order = 0)] + [Route("/huawei")] + [AllowAnonymous] + public class HuaweiService : IDynamicApiController + { + private readonly IOSSService _OSSService; + private readonly string _bucketName; + private readonly string filePrefix; + /// + /// + /// + /// + public HuaweiService(IOSSService OSSService) + { + _OSSService = OSSService; + _bucketName = "94.229"; + filePrefix = "D:\\obsFile\\"; + } + /// + /// 列出当前账号下允许访问的所有储存桶。 + /// + /// + public async Task ListBuckets() + { + try + { + var result = await _OSSService.ListBucketsAsync(); + return result; + } + catch (Exception ex) + { + throw; + } + } + /// + /// 获取储存桶的外部访问权限。 + /// + /// + public async Task GetBucketAclAsync() + { + try + { + var result = await _OSSService.GetBucketAclAsync(_bucketName); + return result; + } + catch (Exception ex) + { + throw; + } + } + /// + /// 获取指定储存桶中指定对象是否存在。 + /// + /// + public async Task ObjectsExistsAsync([FromBody] GetObjectInput args) + { + if (args == null || string.IsNullOrWhiteSpace(args.objectName)) + return await Display(false, "error"); + try + { + var objectName = args.objectName; + if (objectName.ToLower().EndsWith(".dwg")) + { + objectName += ".zip"; + } + var result = await _OSSService.ObjectsExistsAsync(_bucketName, objectName); + return result; + } + catch (Exception ex) + { + throw; + } + } + /// + /// 获取文件的数据流。 大小写敏感! + /// + /// + [HttpPost] + public async Task DownloadObjectAsync([FromBody] GetObjectInput args) + { + if (args == null || string.IsNullOrWhiteSpace(args.objectName)) + return await Display(false, "error"); + var objectName = args.objectName; + if (objectName.ToLower().EndsWith(".dwg")) + { + objectName += ".zip"; + } + #region MyRegion + //FileStreamResult fileStreamResult = null; + //await _OSSService.GetObjectAsync(_bucketName, objectName, (stream) => + //{ + // if (FS.TryGetContentType(objectName, out var contentType) || GetContentType(objectName, out contentType)) + // fileStreamResult = new FileStreamResult(stream, contentType) { FileDownloadName = objectName }; + //}); + //if (fileStreamResult != null) + // return fileStreamResult; + //else + // return await Display(false, "文件格式有问题"); + //var localPath = filePrefix + _bucketName + '/' + args.objectName; + //await _OSSService.GetObjectAsync(_bucketName, args.objectName, localPath); + //if (FS.TryGetContentType(args.objectName, out var contentType) || GetContentType(args.objectName, out contentType)) + //{ + // return new FileStreamResult(new FileStream(localPath, FileMode.Open), contentType) { FileDownloadName = args.objectName }; + //} + //else + //{ + // return await Display(false, "文件格式有问题"); + //} + #endregion + #region 原始华为sdk + try + { + //var objectName = "/files/coc/202011/11/2020kdfj0075_5.pdf"; + ObsClient client = new ObsClient(_OSSService.Options.AccessKey, _OSSService.Options.SecretKey, _OSSService.Options.Endpoint); + GetObjectRequest request = new GetObjectRequest() + { + BucketName = _bucketName, + ObjectKey = objectName, + }; + using (GetObjectResponse response = client.GetObject(request)) + { + if (FS.TryGetContentType(objectName, out var contentType) || GetContentType(objectName, out contentType)) + { + //var localPath = filePrefix + _bucketName + '/' + objectName; + //if (!File.Exists(localPath)) + //{ + // response.WriteResponseStreamToFile(localPath); + //} + //return new FileStreamResult(new FileStream(localPath, FileMode.Open), contentType) { FileDownloadName = objectName }; + var ms = new MemoryStream(); + await response.OutputStream.CopyToAsync(ms); + ms.Position = 0; + response.OutputStream.Close(); + response.OutputStream.Dispose(); + return new FileStreamResult(ms, contentType) { FileDownloadName = objectName }; + + } + else + { + return await Display(false, "文件格式有问题"); + } + } + } + catch (ObsException ex) + { + throw; + } + #endregion + + } + private bool GetContentType(string filename, out string contenttype) + { + contenttype = ""; + bool rslt = true; + if (filename.ToLower().EndsWith(".dwg")) + { + contenttype = "application/octet-stream"; + } + else + rslt = false; + return rslt; + } + /// + /// 上传文件的数据流。 大小写敏感! + /// + /// + [HttpPost] + [DisableRequestSizeLimit] + public async Task PutObjectAsync([FromBody] PutObjectInput args) + { + if (args == null || string.IsNullOrWhiteSpace(args.path)) + return new ObsApiOutput(false, "参数为空"); + var file = new FileInfo(args.path); + if (!file.Exists) + return new ObsApiOutput(false, "文件不存在"); + var objectName = args.objectName; + var path = args.path; + if (string.IsNullOrWhiteSpace(args.objectName)) + objectName = file.Name; + try + { + #region dwg压缩处理 + var zipRslt = false; + if (path.ToLower().EndsWith(".dwg")) + { + if (new Tools.ZipHelper().ZipOneFile(path, 0)) + { + zipRslt = true; + path += ".zip"; + objectName += ".zip"; + } + } + #endregion + var r = await _OSSService.PutObjectAsync(_bucketName, objectName, path); + if (r && zipRslt) + { + new FileInfo(path).Delete(); + } + return new ObsApiOutput(r, r ? "上传成功" : "上传失败"); + } + catch (ObsException ex) + { + return new ObsApiOutput(false, "错误状态码:" + ex.StatusCode); + } + catch (Exception ex) + { + return new ObsApiOutput(false, "异常信息:" + ex.Message + ex.StackTrace); + } + } + private async Task Display(bool isSuccess, object data) + { + return DisplayJson(new RestfulResult + { + Code = isSuccess ? 200 : 500, // 处理没有返回值情况 204 + Success = true, + Data = data, + Message = "请求成功", + Extras = null, + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }); + } + private IActionResult DisplayJson(object data) + { + return new ContentResult + { + Content = JsonConvert.SerializeObject(data, Formatting.Indented, new JsonSerializerSettings + { + ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(), + DateFormatString = "yyyy-MM-dd HH:mm:ss" + }), + ContentType = "application/json" + }; + } + //public async Task Test() + //{ + // // 创建ObsClient实例 + // ObsClient client = new ObsClient("C4D30C2801D928AAF687", "ooZVXaB1tqIz7DHTv53RILD7o5cAAAGAAdkoqlR2", "http://10.74.25.87:6020"); + // // 列举桶 + // try + // { + // ListBucketsRequest request = new ListBucketsRequest(); + // ListBucketsResponse response = client.ListBuckets(request); + // return response; + // } + // catch (ObsException ex) + // { + // throw; + // } + //} + + } +} diff --git a/20220330_Vote/Vote.Services/Dto/ObsInput.cs b/20220330_Vote/Vote.Services/Dto/ObsInput.cs new file mode 100644 index 0000000..aca8932 --- /dev/null +++ b/20220330_Vote/Vote.Services/Dto/ObsInput.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Vote.Services.Dto +{ + public class ObsInput + { + } + /// + /// + /// + public class GetObjectInput + { + /// + /// 大小写敏感! + /// + public string objectName { get; set; } + } + /// + /// + /// + public class PutObjectInput + { + /// + /// 本地文件全路径 + /// + public string path { get; set; } + /// + /// 希望上传到的目录和文件名 , 如果为空则上传到根目录 ,大小写敏感! ,斜杠必须使用/ , eg: files/COC/202011/11/2020KDFJ0075.pdf + /// + public string objectName { get; set; } + } + public class ObsApiOutput + { + public ObsApiOutput(bool _bizIsSuccess, string _message, object _data = null) + { + this.bizIsSuccess = _bizIsSuccess; + this.message = _message; + this.data = _data; + } + public bool bizIsSuccess { get; set; } + public string message { get; set; } + public object data { get; set; } + + } +} diff --git a/20220330_Vote/Vote.Services/Tools/ZipHelper.cs b/20220330_Vote/Vote.Services/Tools/ZipHelper.cs new file mode 100644 index 0000000..dd6462b --- /dev/null +++ b/20220330_Vote/Vote.Services/Tools/ZipHelper.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using ICSharpCode.SharpZipLib.Checksums; +using ICSharpCode.SharpZipLib.Zip; + +namespace Vote.Services.Tools +{ + /// + /// + /// + public class ZipHelper + { + + /// + /// 所有文件缓存 + /// + List files = new List(); + /// + /// 所有空目录缓存 + /// + List paths = new List(); + /// + /// 取得目录下所有文件及文件夹,分别存入files及paths + /// + /// 根目录 + private void GetAllDirectories(string rootPath) + { + string[] subPaths = Directory.GetDirectories(rootPath);//得到所有子目录 + foreach (string path in subPaths) + { + GetAllDirectories(path);//对每一个字目录做与根目录相同的操作:即找到子目录并将当前目录的文件名存入List + } + string[] files = Directory.GetFiles(rootPath); + foreach (string file in files) + { + this.files.Add(file);//将当前目录中的所有文件全名存入文件List + } + if (subPaths.Length == files.Length && files.Length == 0)//如果是空目录 + { + this.paths.Add(rootPath);//记录空目录 + } + } + + + /// + /// 压缩目录(包括子目录及所有文件) + /// + /// 要压缩的根目录 + /// 保存路径 + /// 压缩程度,范围0-9,数值越大,压缩程序越高 + public void ZipFileFromDirectory(string rootPath, string destinationPath, int compressLevel) + { + this.files.Clear(); + GetAllDirectories(rootPath); + + string rootMark = rootPath + "\\";//得到当前路径的位置,以备压缩时将所压缩内容转变成相对路径。 + Crc32 crc = new Crc32(); + using ZipOutputStream outPutStream = new ZipOutputStream(System.IO.File.Create(destinationPath)); + outPutStream.SetLevel(compressLevel); // 0 - store only to 9 - means best compression + foreach (string file in files) + { + using FileStream fileStream = System.IO.File.OpenRead(file);//打开压缩文件 + byte[] buffer = new byte[fileStream.Length]; + fileStream.Read(buffer, 0, buffer.Length); + ZipEntry entry = new ZipEntry(file.Replace(rootMark, string.Empty)); + entry.DateTime = DateTime.Now; + + entry.Size = fileStream.Length; + fileStream.Close(); + crc.Reset(); + crc.Update(buffer); + entry.Crc = crc.Value; + outPutStream.PutNextEntry(entry); + outPutStream.Write(buffer, 0, buffer.Length); + } + + this.files.Clear(); + + foreach (string emptyPath in paths) + { + ZipEntry entry = new ZipEntry(emptyPath.Replace(rootMark, string.Empty) + "/"); + outPutStream.PutNextEntry(entry); + } + + this.paths.Clear(); + outPutStream.Finish(); + outPutStream.Close(); + GC.Collect(); + } + /// + /// 压缩单个文件 + /// 要压缩的文件路径 + /// 压缩程度,范围0-9,数值越大,压缩程序越高 + /// 输出zip文件路径 + /// + public bool ZipOneFile(string filepath, int compressLevel) + { + var r = false; + try + { + var file = new FileInfo(filepath); + var dwgZipPath = GetZipFileName(filepath); + Crc32 crc = new Crc32(); + using ZipOutputStream outPutStream = new ZipOutputStream(System.IO.File.Create(dwgZipPath)); + outPutStream.SetLevel(compressLevel); // 0 - store only to 9 - means best compression + using FileStream fileStream = System.IO.File.OpenRead(filepath);//打开压缩文件 + byte[] buffer = new byte[fileStream.Length]; + fileStream.Read(buffer, 0, buffer.Length); + ZipEntry entry = new ZipEntry(filepath.Replace(file.DirectoryName + "\\", string.Empty)); + entry.DateTime = DateTime.Now; + entry.Size = fileStream.Length; + fileStream.Close(); + crc.Reset(); + crc.Update(buffer); + entry.Crc = crc.Value; + outPutStream.PutNextEntry(entry); + outPutStream.Write(buffer, 0, buffer.Length); + this.files.Clear(); + this.paths.Clear(); + outPutStream.Finish(); + outPutStream.Close(); + GC.Collect(); + r = true; + } + catch (Exception ex) + { + r = false; + } + return r; + } + private string GetZipFileName(string filepath) + { + //if (System.IO.File.Exists(filepath + ".zip")) + //{ + // return GetZipFileName(filepath + ".zip"); + //} + return filepath + ".zip"; + } + + } +} diff --git a/20220330_Vote/Vote.Services/Vote.Services.csproj b/20220330_Vote/Vote.Services/Vote.Services.csproj index f263008..bd52417 100644 --- a/20220330_Vote/Vote.Services/Vote.Services.csproj +++ b/20220330_Vote/Vote.Services/Vote.Services.csproj @@ -12,6 +12,7 @@ + diff --git a/20220330_Vote/Vote.Services/Vote.Services.xml b/20220330_Vote/Vote.Services/Vote.Services.xml index 71c38d9..a5b71c9 100644 --- a/20220330_Vote/Vote.Services/Vote.Services.xml +++ b/20220330_Vote/Vote.Services/Vote.Services.xml @@ -4,6 +4,47 @@ Vote.Services + + + OBS + + + + + + + + + + + 列出当前账号下允许访问的所有储存桶。 + + + + + + 获取储存桶的外部访问权限。 + + + + + + 获取指定储存桶中指定对象是否存在。 + + + + + + 获取文件的数据流。 大小写敏感! + + + + + + 上传文件的数据流。 大小写敏感! + + + 项目 @@ -56,6 +97,31 @@ + + + + + + + + 大小写敏感! + + + + + + + + + + 本地文件全路径 + + + + + 希望上传到的目录和文件名 , 如果为空则上传到根目录 ,大小写敏感! ,斜杠必须使用/ , eg: files/COC/202011/11/2020KDFJ0075.pdf + + 项目类型 @@ -343,5 +409,42 @@ + + + + + + + + 所有文件缓存 + + + + + 所有空目录缓存 + + + + + 取得目录下所有文件及文件夹,分别存入files及paths + + 根目录 + + + + 压缩目录(包括子目录及所有文件) + + 要压缩的根目录 + 保存路径 + 压缩程度,范围0-9,数值越大,压缩程序越高 + + + + 压缩单个文件 + 要压缩的文件路径 + 压缩程度,范围0-9,数值越大,压缩程序越高 + 输出zip文件路径 + +