├── .gitignore ├── LICENSE ├── README.md ├── UpYun.NETCore.sln ├── UpYun.NETCore ├── FolderItem.cs ├── UpYun.NETCore.csproj ├── UpYunClient.cs └── UpYunResult.cs └── UpYun.NetCore.Tests ├── Program.cs └── UpYun.NetCore.Tests.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UpYun.NETCore 2 | 又拍云 SDK .net Core版 3 | 4 | Nuget安装指令: 5 | ```C# 6 | Install-Package UpYun.NETCore 7 | ``` 8 | 示例代码: 9 | ```C# 10 | UpYunClient upyun = new UpYunClient(bucketName, username, password, httpClientFactory); 11 | byte[] bytes = Encoding.UTF8.GetBytes("www.youzack.com"); 12 | var a = await upyun.WriteFileAsync("/test.txt", bytes, true); 13 | ``` -------------------------------------------------------------------------------- /UpYun.NETCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.7 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpYun.NETCore", "UpYun.NETCore\UpYun.NETCore.csproj", "{FFF759BE-B5C5-4891-9EC8-C76C6B329595}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpYun.NetCore.Tests", "UpYun.NetCore.Tests\UpYun.NetCore.Tests.csproj", "{771E11FB-2AC9-48DE-A01B-9BB01A227CC9}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {FFF759BE-B5C5-4891-9EC8-C76C6B329595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {FFF759BE-B5C5-4891-9EC8-C76C6B329595}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {FFF759BE-B5C5-4891-9EC8-C76C6B329595}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {FFF759BE-B5C5-4891-9EC8-C76C6B329595}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {771E11FB-2AC9-48DE-A01B-9BB01A227CC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {771E11FB-2AC9-48DE-A01B-9BB01A227CC9}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {771E11FB-2AC9-48DE-A01B-9BB01A227CC9}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {771E11FB-2AC9-48DE-A01B-9BB01A227CC9}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /UpYun.NETCore/FolderItem.cs: -------------------------------------------------------------------------------- 1 | namespace UpYun.NETCore 2 | { 3 | //目录条目类 4 | public class FolderItem 5 | { 6 | public string filename; 7 | public string filetype; 8 | public int size; 9 | public int number; 10 | public FolderItem(string filename, string filetype, int size, int number) 11 | { 12 | this.filename = filename; 13 | this.filetype = filetype; 14 | this.size = size; 15 | this.number = number; 16 | } 17 | 18 | public override string ToString() 19 | { 20 | return $"filename={filename},filetype={filetype},size={size},number={number}"; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /UpYun.NETCore/UpYun.NETCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | UpYun .net core SDK. http://www.upyun.com by yangzhongke 6 | https://github.com/yangzhongke/UpYun.NETCore 7 | True 8 | https://github.com/yangzhongke/UpYun.NETCore 9 | 1.1.2 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /UpYun.NETCore/UpYunClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.Net.Http; 6 | using System.Security.Cryptography; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | 11 | namespace UpYun.NETCore 12 | { 13 | public class UpYunClient 14 | { 15 | private IHttpClientFactory httpClientFactory; 16 | 17 | private string bucketname; 18 | private string username; 19 | private string password; 20 | private bool upAuth = false; 21 | private string api_domain = "v0.api.upyun.com"; 22 | private string DL = "/"; 23 | private Dictionary tmp_infos = new Dictionary(); 24 | private string file_secret; 25 | private string content_md5; 26 | private bool auto_mkdir = false; 27 | 28 | 29 | public string version() { return "1.0.1"; } 30 | 31 | /** 32 | * 初始化 UpYun 存储接口 33 | * @param $bucketname 空间名称 34 | * @param $username 操作员名称 35 | * @param $password 密码 36 | * return UpYun object 37 | */ 38 | public UpYunClient(string bucketname, string username, string password, IHttpClientFactory httpClientFactory) 39 | { 40 | this.bucketname = bucketname; 41 | this.username = username; 42 | this.password = password; 43 | this.httpClientFactory = httpClientFactory; 44 | } 45 | 46 | /// 47 | /// 是否使用Https协议 48 | /// 49 | public bool IsHttps { get; set; }=true; 50 | 51 | /** 52 | * 切换 API 接口的域名 53 | * @param $domain {默认 v0.api.upyun.com 自动识别, v1.api.upyun.com 电信, v2.api.upyun.com 联通, v3.api.upyun.com 移动} 54 | * return null; 55 | */ 56 | public void setApiDomain(string domain) 57 | { 58 | this.api_domain = domain; 59 | } 60 | 61 | /** 62 | * 是否启用 又拍签名认证 63 | * @param upAuth {默认 false 不启用(直接使用basic auth),true 启用又拍签名认证} 64 | * return null; 65 | */ 66 | public void setAuthType(bool upAuth) 67 | { 68 | this.upAuth = upAuth; 69 | } 70 | 71 | private async Task UpYunAuthAsync(ByteArrayContent requestContent,string method,string uri, CancellationToken cancellationToken = default) 72 | { 73 | DateTime dt = DateTime.UtcNow; 74 | string date = dt.ToString("ddd, dd MMM yyyy HH':'mm':'ss 'GMT'", new CultureInfo("en-US")); 75 | 76 | requestContent.Headers.Add("Date", date); 77 | string body = await requestContent.ReadAsStringAsync(); 78 | string auth; 79 | if (!string.IsNullOrEmpty(body)) 80 | { 81 | byte[] bytesContent = await requestContent.ReadAsByteArrayAsync(); 82 | auth = md5(method + '&' + uri + '&' + date + '&' + bytesContent.Length + '&' + md5(this.password)); 83 | } 84 | else 85 | { 86 | auth = md5(method + '&' + uri + '&' + date + '&' + 0 + '&' + md5(this.password)); 87 | } 88 | requestContent.Headers.Add("Authorization", "UpYun " + this.username + ':' + auth); 89 | } 90 | 91 | private string md5(string str) 92 | { 93 | using (MD5 m = MD5.Create()) 94 | { 95 | byte[] s = m.ComputeHash(Encoding.UTF8.GetBytes(str)); 96 | string resule = BitConverter.ToString(s); 97 | resule = resule.Replace("-", ""); 98 | return resule.ToLower(); 99 | } 100 | } 101 | private async Task DeleteAsync(string path, Dictionary headers, CancellationToken cancellationToken = default) 102 | { 103 | var resp = await NewWorkAsync("DELETE", DL + this.bucketname + path, null, headers,cancellationToken); 104 | if (resp.StatusCode == System.Net.HttpStatusCode.OK) 105 | { 106 | return UpYunResult.OK; 107 | } 108 | else 109 | { 110 | return await UpYunResult.CreateErrorAsync(resp); 111 | } 112 | } 113 | 114 | 115 | private async Task NewWorkAsync(string method, string url, byte[] postData, Dictionary headers, 116 | CancellationToken cancellationToken=default) 117 | { 118 | HttpClient httpClient = httpClientFactory.CreateClient(); 119 | if(postData==null) 120 | { 121 | postData = new byte[0]; 122 | } 123 | using (ByteArrayContent byteContent = new ByteArrayContent(postData)) 124 | { 125 | string protocol = IsHttps ? "https://" : "http://"; 126 | httpClient.BaseAddress = new Uri(protocol + api_domain); 127 | if (this.auto_mkdir == true) 128 | { 129 | byteContent.Headers.Add("mkdir", "true"); 130 | this.auto_mkdir = false; 131 | } 132 | 133 | if (postData != null) 134 | { 135 | if (this.content_md5 != null) 136 | { 137 | byteContent.Headers.Add("Content-MD5", this.content_md5); 138 | this.content_md5 = null; 139 | } 140 | if (this.file_secret != null) 141 | { 142 | byteContent.Headers.Add("Content-Secret", this.file_secret); 143 | this.file_secret = null; 144 | } 145 | } 146 | 147 | if (this.upAuth) 148 | { 149 | await UpYunAuthAsync(byteContent, method, url, cancellationToken); 150 | } 151 | else 152 | { 153 | //byteContent.Headers.Add("Authorization", "Basic " + 154 | //Convert.ToBase64String(new System.Text.ASCIIEncoding().GetBytes(this.username + ":" + this.password))); 155 | var value = Convert.ToBase64String(new System.Text.ASCIIEncoding().GetBytes(this.username + ":" + this.password)); 156 | httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", value); 157 | } 158 | foreach (var kv in headers) 159 | { 160 | byteContent.Headers.Add(kv.Key, kv.Value.ToString()); 161 | } 162 | 163 | HttpResponseMessage responseMsg; 164 | if ("Get".Equals(method, StringComparison.OrdinalIgnoreCase)) 165 | { 166 | responseMsg = await httpClient.GetAsync(url, cancellationToken); 167 | } 168 | else if ("Post".Equals(method, StringComparison.OrdinalIgnoreCase)) 169 | { 170 | responseMsg = await httpClient.PostAsync(url, byteContent, cancellationToken); 171 | } 172 | else if ("PUT".Equals(method, StringComparison.OrdinalIgnoreCase)) 173 | { 174 | responseMsg = await httpClient.PutAsync(url, byteContent, cancellationToken); 175 | } 176 | else if ("Delete".Equals(method, StringComparison.OrdinalIgnoreCase)) 177 | { 178 | responseMsg = await httpClient.DeleteAsync(url, cancellationToken); 179 | } 180 | else 181 | { 182 | throw new Exception("未知method:" + method); 183 | } 184 | 185 | this.tmp_infos = new Dictionary(); 186 | foreach (var header in responseMsg.Headers) 187 | { 188 | if (header.Key.Length > 7 && header.Key.Substring(0, 7) == "x-upyun") 189 | { 190 | this.tmp_infos.Add(header.Key, header.Value); 191 | } 192 | } 193 | 194 | return responseMsg; 195 | } 196 | } 197 | 198 | /** 199 | * 获取总体空间的占用信息 200 | * return 空间占用量,失败返回 null 201 | */ 202 | 203 | public async Task> GetFolderUsageAsync(string url, CancellationToken cancellationToken = default) 204 | { 205 | Dictionary headers = new Dictionary(); 206 | using (var resp = await NewWorkAsync("GET", DL + this.bucketname + url + "?usage", null, headers, cancellationToken)) 207 | { 208 | if (resp.StatusCode == System.Net.HttpStatusCode.OK) 209 | { 210 | string strhtml = await resp.Content.ReadAsStringAsync(); 211 | long size = long.Parse(strhtml); 212 | return size; 213 | } 214 | else 215 | { 216 | return await UpYunResult.CreateErrorAsync(resp); 217 | } 218 | } 219 | 220 | } 221 | 222 | /** 223 | * 获取某个子目录的占用信息 224 | * @param $path 目标路径 225 | * return 空间占用量,失败返回 null 226 | */ 227 | public Task> GetBucketUsageAsync(CancellationToken cancellationToken = default) 228 | { 229 | return GetFolderUsageAsync("/", cancellationToken); 230 | } 231 | /** 232 | * 创建目录 233 | * @param $path 目录路径 234 | * return true or false 235 | */ 236 | public async Task MkDirAsync(string path, bool auto_mkdir, CancellationToken cancellationToken = default) 237 | { 238 | this.auto_mkdir = auto_mkdir; 239 | Dictionary headers = new Dictionary(); 240 | headers.Add("folder", "create"); 241 | 242 | using (var resp = await NewWorkAsync("POST", DL + this.bucketname + path, null, headers, cancellationToken)) 243 | { 244 | if (resp.StatusCode == System.Net.HttpStatusCode.OK) 245 | { 246 | return UpYunResult.OK; 247 | } 248 | else 249 | { 250 | return await UpYunResult.CreateErrorAsync(resp); 251 | } 252 | } 253 | } 254 | 255 | /** 256 | * 删除目录 257 | * @param $path 目录路径 258 | * return true or false 259 | */ 260 | public Task RmDirAsync(string path, CancellationToken cancellationToken = default) 261 | { 262 | Dictionary headers = new Dictionary(); 263 | return DeleteAsync(path, headers, cancellationToken); 264 | } 265 | 266 | /** 267 | * 读取目录列表 268 | * @param $path 目录路径 269 | * return array 数组 或 null 270 | */ 271 | public async Task>> ReadDirAsync(string url, CancellationToken cancellationToken = default) 272 | { 273 | Dictionary headers = new Dictionary(); 274 | //headers["Accept"] = "application/json"; 275 | byte[] a = null; 276 | using (var resp = await NewWorkAsync("GET", DL + this.bucketname + url, a, headers, cancellationToken)) 277 | { 278 | if (resp.StatusCode == System.Net.HttpStatusCode.OK) 279 | { 280 | string strhtml = await resp.Content.ReadAsStringAsync(); 281 | strhtml = strhtml.Replace("\t", "\\"); 282 | strhtml = strhtml.Replace("\n", "\\"); 283 | string[] ss = strhtml.Split('\\', StringSplitOptions.RemoveEmptyEntries); 284 | int i = 0; 285 | List list = new List(); 286 | while (i < ss.Length) 287 | { 288 | FolderItem fi = new FolderItem(ss[i], ss[i + 1], int.Parse(ss[i + 2]), int.Parse(ss[i + 3])); 289 | list.Add(fi); 290 | i += 4; 291 | } 292 | return list; 293 | } 294 | else 295 | { 296 | return await UpYunResult>.CreateErrorAsync(resp); 297 | } 298 | } 299 | 300 | } 301 | 302 | 303 | /** 304 | * 上传文件 305 | * @param $file 文件路径(包含文件名) 306 | * @param $datas 文件内容 或 文件IO数据流 307 | * return true or false 308 | */ 309 | public async Task WriteFileAsync(string path, byte[] data, bool auto_mkdir, CancellationToken cancellationToken = default) 310 | { 311 | Dictionary headers = new Dictionary(); 312 | this.auto_mkdir = auto_mkdir; 313 | using (var resp = await NewWorkAsync("POST", DL + this.bucketname + path, data, headers, cancellationToken)) 314 | { 315 | if (resp.StatusCode == System.Net.HttpStatusCode.OK) 316 | { 317 | return UpYunResult.OK; 318 | } 319 | else 320 | { 321 | return await UpYunResult.CreateErrorAsync(resp); 322 | } 323 | } 324 | 325 | } 326 | /** 327 | * 删除文件 328 | * @param $file 文件路径(包含文件名) 329 | * return true or false 330 | */ 331 | public Task DeleteFileAsync(string path, CancellationToken cancellationToken = default) 332 | { 333 | Dictionary headers = new Dictionary(); 334 | return DeleteAsync(path, headers, cancellationToken); 335 | } 336 | 337 | /// 338 | /// 移动文件 339 | /// 340 | /// 源路径 341 | /// 目标路径 342 | /// 343 | /// 344 | public async Task MoveFileAsync(string path, string dest, CancellationToken cancellationToken = default) 345 | { 346 | Dictionary headers = new Dictionary(); 347 | headers["X-Upyun-Move-Source"] = DL + this.bucketname + Uri.EscapeUriString(path); 348 | var resp = await NewWorkAsync("PUT", DL + this.bucketname + Uri.EscapeUriString(dest), null, headers, cancellationToken); 349 | if (resp.StatusCode == System.Net.HttpStatusCode.OK) 350 | { 351 | return UpYunResult.OK; 352 | } 353 | else 354 | { 355 | return await UpYunResult.CreateErrorAsync(resp); 356 | } 357 | } 358 | 359 | /// 360 | /// 重命名文件 361 | /// 362 | /// 源路径 363 | /// 目标路径 364 | /// 365 | /// 366 | 367 | public Task RenameFileAsync(string path, string dest, CancellationToken cancellationToken = default) 368 | { 369 | return MoveFileAsync(path, dest, cancellationToken); 370 | } 371 | 372 | 373 | /** 374 | * 读取文件 375 | * @param $file 文件路径(包含文件名) 376 | * @param $output_file 可传递文件IO数据流(默认为 null,结果返回文件内容,如设置文件数据流,将返回 true or false) 377 | * return 文件内容 或 null 378 | */ 379 | public async Task> ReadFileAsync(string path, CancellationToken cancellationToken = default) 380 | { 381 | Dictionary headers = new Dictionary(); 382 | byte[] a = null; 383 | 384 | using (var resp = await NewWorkAsync("GET", DL + this.bucketname + path, a, headers, cancellationToken)) 385 | { 386 | if (resp.StatusCode == System.Net.HttpStatusCode.OK) 387 | { 388 | return await resp.Content.ReadAsByteArrayAsync(); 389 | } 390 | else 391 | { 392 | return await UpYunResult.CreateErrorAsync(resp); 393 | } 394 | } 395 | } 396 | /** 397 | * 设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误) 398 | * @param $str (文件 MD5 校验码) 399 | * return null; 400 | */ 401 | public void SetContentMD5(string str) 402 | { 403 | this.content_md5 = str; 404 | } 405 | /** 406 | * 设置待上传文件的 访问密钥(注意:仅支持图片空!,设置密钥后,无法根据原文件URL直接访问,需带 URL 后面加上 (缩略图间隔标志符+密钥) 进行访问) 407 | * 如缩略图间隔标志符为 ! ,密钥为 bac,上传文件路径为 /folder/test.jpg ,那么该图片的对外访问地址为: http://空间域名/folder/test.jpg!bac 408 | * @param $str (文件 MD5 校验码) 409 | * return null; 410 | */ 411 | public void SetFileSecret(string str) 412 | { 413 | this.file_secret = str; 414 | } 415 | /** 416 | * 获取文件信息 417 | * @param $file 文件路径(包含文件名) 418 | * return array('type'=> file | folder, 'size'=> file size, 'date'=> unix time) 或 null 419 | */ 420 | public async Task>> GetFileInfoAsync(string file, CancellationToken cancellationToken = default) 421 | { 422 | Dictionary headers = new Dictionary(); 423 | byte[] a = null; 424 | using (var resp = await NewWorkAsync("HEAD", DL + this.bucketname + file, a, headers, cancellationToken)) 425 | { 426 | if (resp.StatusCode == System.Net.HttpStatusCode.OK) 427 | { 428 | Dictionary respHeaders =new Dictionary(); 429 | if(tmp_infos.ContainsKey("x-upyun-file-type")&&tmp_infos.ContainsKey("x-upyun-file-size") 430 | && tmp_infos.ContainsKey("x-upyun-file-date")) 431 | { 432 | respHeaders.Add("type", tmp_infos["x-upyun-file-type"]); 433 | respHeaders.Add("size", tmp_infos["x-upyun-file-size"]); 434 | respHeaders.Add("date", tmp_infos["x-upyun-file-date"]); 435 | } 436 | return respHeaders; 437 | } 438 | else 439 | { 440 | return await UpYunResult>.CreateErrorAsync(resp); 441 | } 442 | } 443 | } 444 | //获取上传后的图片信息(仅图片空间有返回数据) 445 | public object GetWritedFileInfo(string key) 446 | { 447 | if (this.tmp_infos == new Dictionary()) return ""; 448 | return this.tmp_infos[key]; 449 | } 450 | //计算文件的MD5码 451 | public static string md5_file(string pathName) 452 | { 453 | string strResult; 454 | string strHashData; 455 | 456 | byte[] arrbytHashValue; 457 | using (var md5 = MD5.Create()) 458 | using (FileStream oFileStream = new FileStream(pathName, System.IO.FileMode.Open, 459 | System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite)) 460 | { 461 | arrbytHashValue = md5.ComputeHash(oFileStream);//计算指定Stream 对象的哈希值 462 | //由以连字符分隔的十六进制对构成的String,其中每一对表示value 中对应的元素;例如“F-2C-4A” 463 | strHashData = System.BitConverter.ToString(arrbytHashValue); 464 | //替换- 465 | strHashData = strHashData.Replace("-", ""); 466 | strResult = strHashData; 467 | return strResult.ToLower(); 468 | } 469 | } 470 | } 471 | 472 | } 473 | -------------------------------------------------------------------------------- /UpYun.NETCore/UpYunResult.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Text.Json; 3 | using System.Threading.Tasks; 4 | 5 | namespace UpYun.NETCore 6 | { 7 | public class UpYunResult 8 | { 9 | public bool IsOK { get; set; } 10 | public string Msg { get; set; } 11 | public int Code { get; set; } 12 | public string Id { get; set; } 13 | 14 | public static readonly UpYunResult OK = new UpYunResult { IsOK=true}; 15 | public static async Task CreateErrorAsync(HttpResponseMessage resp) 16 | { 17 | string body = await resp.Content.ReadAsStringAsync(); 18 | JsonSerializerOptions options = new JsonSerializerOptions(); 19 | options.PropertyNameCaseInsensitive = true; 20 | UpYunResult result = JsonSerializer.Deserialize(body, options); 21 | result.IsOK = false; 22 | return result; 23 | } 24 | 25 | public override string ToString() 26 | { 27 | return $"IsOk={IsOK},Msg={Msg},Code={Code},Id={Id}"; 28 | } 29 | } 30 | 31 | public class UpYunResult 32 | { 33 | public bool IsOK { get; set; } 34 | public string Msg { get; set; } 35 | public int Code { get; set; } 36 | public string Id { get; set; } 37 | 38 | public T Value { get; set; } 39 | 40 | public static UpYunResult OK(T value) 41 | { 42 | UpYunResult result = new UpYunResult(); 43 | result.IsOK = true; 44 | result.Value = value; 45 | return result; 46 | } 47 | 48 | public static async Task> CreateErrorAsync(HttpResponseMessage resp) 49 | { 50 | string body = await resp.Content.ReadAsStringAsync(); 51 | var result = JsonSerializer.Deserialize>(body); 52 | result.IsOK = false; 53 | return result; 54 | } 55 | 56 | public static implicit operator UpYunResult(T value) 57 | { 58 | return new UpYunResult { IsOK=true,Value=value}; 59 | } 60 | 61 | public override string ToString() 62 | { 63 | return $"IsOk={IsOK},Msg={Msg},Code={Code},Id={Id},Value={Value}"; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /UpYun.NetCore.Tests/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using UpYun.NETCore; 8 | 9 | namespace UpYun.NetCore.Tests 10 | { 11 | class Program 12 | { 13 | static async Task Main(string[] args) 14 | { 15 | string bucketName = args[0]; 16 | string username = args[1]; 17 | string password = args[2]; 18 | 19 | ServiceCollection services = new ServiceCollection(); 20 | services.AddHttpClient(); 21 | using (var sp = services.BuildServiceProvider()) 22 | { 23 | var httpClientFactory = sp.GetRequiredService(); 24 | UpYunClient upyun = new UpYunClient(bucketName, username, password, httpClientFactory); 25 | /* 26 | byte[] bytes = Encoding.UTF8.GetBytes("www.youzack.com"); 27 | var a = await upyun.WriteFileAsync("/test.txt", bytes, true); 28 | Console.WriteLine(a);*/ 29 | /* 30 | var r = await upyun.RenameFileAsync("/02b2b5f0-3484-11e6-81f3-ccb34c23190a%20%5Blow%5D.mp3", "/02b2b5f0-3484-11e6-81f3-ccb34c23190a.mp3"); 31 | Console.WriteLine(r);*/ 32 | await ListAsync(upyun, "/"); 33 | //var r = await upyun.RenameFileAsync("/电脑02b2b5f0-3484-11e6-81f3-ccb34c23190a [low].mp3", "/电脑02b2b5f0-3484-11e6-81f3-ccb34c23190a.mp3"); 34 | //Console.WriteLine(r); 35 | } 36 | Console.ReadLine(); Console.ReadLine(); 37 | } 38 | 39 | static void WriteLog(string s) 40 | { 41 | File.AppendAllText("d:/1.txt",s+"\r\n"); 42 | } 43 | 44 | static async Task ListAsync(UpYunClient upyun, string folder) 45 | { 46 | var items = await upyun.ReadDirAsync(folder); 47 | foreach (var item in items.Value) 48 | { 49 | string fullPath; 50 | if(!folder.EndsWith('/')) 51 | { 52 | folder = folder + "/"; 53 | } 54 | fullPath=folder + item.filename; 55 | //Console.WriteLine(fullPath); 56 | if(fullPath.Contains(" [low]")) 57 | { 58 | string newPath = fullPath.Replace(" [low]", ""); 59 | try 60 | { 61 | var r = await upyun.RenameFileAsync(fullPath, newPath); 62 | if (r.IsOK) 63 | { 64 | Console.WriteLine("成功:" + fullPath + "," + newPath); 65 | } 66 | else 67 | { 68 | Console.ForegroundColor = ConsoleColor.Red; 69 | Console.BackgroundColor = ConsoleColor.White; 70 | Console.WriteLine("失败:" + fullPath + "," + newPath + "," + r); 71 | WriteLog("失败:" + fullPath + "," + newPath + "," + r); 72 | Console.ResetColor(); 73 | } 74 | } 75 | catch(Exception ex) 76 | { 77 | Console.ForegroundColor = ConsoleColor.Red; 78 | Console.BackgroundColor = ConsoleColor.White; 79 | Console.WriteLine("失败:" + fullPath + "," + newPath + "," + ex); 80 | WriteLog("失败:" + fullPath + "," + newPath + "," + ex); 81 | Console.ResetColor(); 82 | } 83 | } 84 | if(item.filetype=="F") 85 | { 86 | await ListAsync(upyun, fullPath); 87 | } 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /UpYun.NetCore.Tests/UpYun.NetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------