From b70f8f25d3bba0b4fbe7a8a21d523c225ea04233 Mon Sep 17 00:00:00 2001 From: lihsai0 Date: Fri, 20 Sep 2024 22:03:43 +0800 Subject: [PATCH] Bump to v8.6.0 (#264) * feat: reorder default query regions hosts * feat: add `VerifyRequest` to check if request signed by qn * feat: more fop support - add idle-time fop support - add getting fop status - add configurable for pfop api host config * chore: bump version info to v8.6.0 and update changelog * feat: change default uc host --- CHANGELOG.md | 10 ++++ src/Qiniu/Qiniu.csproj | 2 +- src/Qiniu/QiniuCSharpSDK.cs | 2 +- src/Qiniu/Storage/Config.cs | 8 +-- src/Qiniu/Storage/OperationManager.cs | 15 +++++- src/Qiniu/Storage/PfopInfo.cs | 10 ++++ src/Qiniu/Storage/PrefopResult.cs | 2 +- src/Qiniu/Storage/PutPolicy.cs | 7 +++ src/Qiniu/Util/Signature.cs | 45 ++++++++++++++++ src/QiniuTests/Storage/ConfigTests.cs | 4 +- src/QiniuTests/Storage/FormUploaderTests.cs | 51 ++++++++++++++++++ .../Storage/OperationManagerTests.cs | 52 +++++++++++++++---- src/QiniuTests/Util/Signature.cs | 8 +++ src/QiniuTests/Util/TestCases.cs | 34 +++++++++++- 14 files changed, 230 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cca2f38e..1ad8f3b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +**2024-09-05** + +v8.6.0 + +新增:验证 Qbox, Qiniu 签名的辅助方法 + +新增:持久化处理,支持闲时任务 + +更改:对象存储,默认空间管理域名,查询区域主备域名 + **2024-08-23** v8.5.1 diff --git a/src/Qiniu/Qiniu.csproj b/src/Qiniu/Qiniu.csproj index a166e1a8..bd2b8fd8 100644 --- a/src/Qiniu/Qiniu.csproj +++ b/src/Qiniu/Qiniu.csproj @@ -28,7 +28,7 @@ Qiniu - 8.5.1 + 8.6.0 Rong Zhou, Qiniu SDK Shanghai Qiniu Information Technology Co., Ltd. Qiniu Resource (Cloud) Storage SDK for C# diff --git a/src/Qiniu/QiniuCSharpSDK.cs b/src/Qiniu/QiniuCSharpSDK.cs index 65e023a2..eef1201e 100644 --- a/src/Qiniu/QiniuCSharpSDK.cs +++ b/src/Qiniu/QiniuCSharpSDK.cs @@ -37,6 +37,6 @@ public class QiniuCSharpSDK /// /// SDK版本号 /// - public const string VERSION = "8.5.1"; + public const string VERSION = "8.6.0"; } diff --git a/src/Qiniu/Storage/Config.cs b/src/Qiniu/Storage/Config.cs index ecbb8030..949f4e9a 100644 --- a/src/Qiniu/Storage/Config.cs +++ b/src/Qiniu/Storage/Config.cs @@ -14,18 +14,18 @@ public class Config /// /// 默认空间管理域名 /// - public static string DefaultUcHost = "uc.qbox.me"; + public static string DefaultUcHost = "uc.qiniuapi.com"; /// /// 默认查询区域域名 /// - public static string DefaultQueryRegionHost = "kodo-config.qiniuapi.com"; + public static string DefaultQueryRegionHost = "uc.qiniuapi.com"; /// /// 默认备用查询区域域名 /// public static List DefaultBackupQueryRegionHosts = new List { - "uc.qbox.me", - "api.qiniu.com" + "kodo-config.qiniuapi.com", + "uc.qbox.me" }; /// diff --git a/src/Qiniu/Storage/OperationManager.cs b/src/Qiniu/Storage/OperationManager.cs index 8a3b8dd0..07bac22e 100644 --- a/src/Qiniu/Storage/OperationManager.cs +++ b/src/Qiniu/Storage/OperationManager.cs @@ -41,8 +41,17 @@ public OperationManager(Mac mac, Config config) /// 私有队列 /// 通知url /// forece参数 + /// 为 1 时开启闲时任务 /// pfop操作返回结果,正确返回结果包含persistentId - public PfopResult Pfop(string bucket, string key, string fops, string pipeline, string notifyUrl, bool force) + public PfopResult Pfop( + string bucket, + string key, + string fops, + string pipeline, + string notifyUrl, + bool force, + int type = 0 + ) { PfopResult result = new PfopResult(); @@ -65,6 +74,10 @@ public PfopResult Pfop(string bucket, string key, string fops, string pipeline, { sb.AppendFormat("&pipeline={0}", pipeline); } + if (type > 0) + { + sb.AppendFormat("&type={0}", type); + } byte[] data = Encoding.UTF8.GetBytes(sb.ToString()); string token = auth.CreateManageToken(pfopUrl, data); diff --git a/src/Qiniu/Storage/PfopInfo.cs b/src/Qiniu/Storage/PfopInfo.cs index f3a95e3a..2dfbd0af 100644 --- a/src/Qiniu/Storage/PfopInfo.cs +++ b/src/Qiniu/Storage/PfopInfo.cs @@ -13,6 +13,16 @@ public class PfopInfo [JsonProperty("id")] public string Id; /// + /// 任务类型,为 1 代表为闲时任务 + /// + [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] + public int? Type; + /// + /// 任务创建时间 + /// + [JsonProperty("creationDate", NullValueHandling = NullValueHandling.Ignore)] + public string CreationDate; + /// /// 任务结果状态码 /// [JsonProperty("code")] diff --git a/src/Qiniu/Storage/PrefopResult.cs b/src/Qiniu/Storage/PrefopResult.cs index bd248bd4..2b5a733a 100644 --- a/src/Qiniu/Storage/PrefopResult.cs +++ b/src/Qiniu/Storage/PrefopResult.cs @@ -19,7 +19,7 @@ public PfopInfo Result if ((Code == (int)HttpCode.OK) && (!string.IsNullOrEmpty(Text))) { - info= JsonConvert.DeserializeObject(Text); + info= JsonConvert.DeserializeObject(Text); } return info; } diff --git a/src/Qiniu/Storage/PutPolicy.cs b/src/Qiniu/Storage/PutPolicy.cs index 79be6237..715ef64f 100644 --- a/src/Qiniu/Storage/PutPolicy.cs +++ b/src/Qiniu/Storage/PutPolicy.cs @@ -111,6 +111,13 @@ public class PutPolicy [JsonProperty("persistentPipeline", NullValueHandling = NullValueHandling.Ignore)] public string PersistentPipeline { get; set; } + /// + /// [可选]持久化任务类型,为 1 时开启闲时任务 + /// + [JsonProperty("persistentType", NullValueHandling = NullValueHandling.Ignore)] + public int? PersistentType { get; set; } + + /// /// [可选]上传文件大小限制:最小值,单位Byte /// diff --git a/src/Qiniu/Util/Signature.cs b/src/Qiniu/Util/Signature.cs index 02c2aad3..915cb651 100644 --- a/src/Qiniu/Util/Signature.cs +++ b/src/Qiniu/Util/Signature.cs @@ -200,5 +200,50 @@ public string SignRequestV2(string method, string url, StringDictionary headers, { return SignRequestV2(method, url, headers, Encoding.UTF8.GetString(body)); } + + public bool VerifyRequest( + string method, + string url, + StringDictionary headers, + string body = null + ) + { + byte[] bodyBytes = null; + if (!string.IsNullOrEmpty(body)) { + bodyBytes = Encoding.UTF8.GetBytes(body); + } + return VerifyRequest( + method, + url, + headers, + bodyBytes + ); + } + + public bool VerifyRequest( + string method, + string url, + StringDictionary headers, + byte[] body = null + ) + { + if (!headers.ContainsKey("Authorization")) + { + return false; + } + + string authString = headers["Authorization"]; + if (authString.StartsWith("QBox ")) + { + return authString == "QBox " + SignRequest(url, body); + } + + if (authString.StartsWith("Qiniu ")) + { + return authString == "Qiniu " + SignRequestV2(method, url, headers, body); + } + + return false; + } } } diff --git a/src/QiniuTests/Storage/ConfigTests.cs b/src/QiniuTests/Storage/ConfigTests.cs index 8ea1f671..1215a157 100644 --- a/src/QiniuTests/Storage/ConfigTests.cs +++ b/src/QiniuTests/Storage/ConfigTests.cs @@ -11,7 +11,7 @@ public void UcHostTest() { Config config = new Config(); string ucHost = config.UcHost(); - Assert.AreEqual("http://uc.qbox.me", ucHost); + Assert.AreEqual("http://uc.qiniuapi.com", ucHost); config.SetUcHost("uc.example.com"); ucHost = config.UcHost(); Assert.AreEqual("http://uc.example.com", ucHost); @@ -19,7 +19,7 @@ public void UcHostTest() config = new Config(); config.UseHttps = true; ucHost = config.UcHost(); - Assert.AreEqual("https://uc.qbox.me", ucHost); + Assert.AreEqual("https://uc.qiniuapi.com", ucHost); config.SetUcHost("uc.example.com"); ucHost = config.UcHost(); Assert.AreEqual("https://uc.example.com", ucHost); diff --git a/src/QiniuTests/Storage/FormUploaderTests.cs b/src/QiniuTests/Storage/FormUploaderTests.cs index 0eb2a6d3..bb145ece 100644 --- a/src/QiniuTests/Storage/FormUploaderTests.cs +++ b/src/QiniuTests/Storage/FormUploaderTests.cs @@ -1,6 +1,8 @@ using NUnit.Framework; using Qiniu.Http; using System; +using System.Collections.Generic; +using Newtonsoft.Json; using Qiniu.Util; using Qiniu.Tests; @@ -80,5 +82,54 @@ public void UploadFileV2Test() System.IO.File.Delete(filePath); } + [Test] + public void UploadFileWithPersistTypeTest() + { + Mac mac = new Mac(AccessKey, SecretKey); + Random rand = new Random(); + string key = string.Format("UploadFileTest_{0}.dat", rand.Next()); + + string tempPath = System.IO.Path.GetTempPath(); + int rnd = new Random().Next(1, 100000); + string filePath = tempPath + "resumeFile" + rnd.ToString(); + char[] testBody = new char[4 * 1024 * 1024]; + System.IO.FileStream stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create); + System.IO.StreamWriter sw = new System.IO.StreamWriter(stream, System.Text.Encoding.Default); + sw.Write(testBody); + sw.Close(); + stream.Close(); + + PutPolicy putPolicy = new PutPolicy(); + putPolicy.Scope = Bucket + ":" + key; + putPolicy.SetExpires(3600); + putPolicy.DeleteAfterDays = 1; + string saveEntry = Base64.UrlSafeBase64Encode(Bucket + ":pfop-test_avinfo"); + putPolicy.PersistentOps = "avinfo|saveas/" + saveEntry; + putPolicy.PersistentType = 1; + string token = Auth.CreateUploadToken(mac, putPolicy.ToJsonString()); + Config config = new Config(); + config.Zone = Zone.ZONE_CN_East; + config.UseHttps = true; + config.UseCdnDomains = true; + FormUploader target = new FormUploader(config); + PutExtra extra = new PutExtra(); + extra.Version = "v2"; + HttpResult result = target.UploadFile(filePath, key, token, extra); + Console.WriteLine("form upload result: " + result.ToString()); + Assert.AreEqual((int)HttpCode.OK, result.Code); + System.IO.File.Delete(filePath); + + Dictionary dict = JsonConvert.DeserializeObject>(result.Text.ToString()); + Assert.IsTrue(dict.ContainsKey("persistentId")); + OperationManager manager = new OperationManager(mac, config); + PrefopResult prefopRet = manager.Prefop(dict["persistentId"].ToString()); + if (prefopRet.Code != (int)HttpCode.OK) + { + Assert.Fail("prefop error: " + prefopRet.ToString()); + } + Assert.AreEqual(1, prefopRet.Result.Type); + Assert.IsNotNull(prefopRet.Result.CreationDate); + Assert.IsNotEmpty(prefopRet.Result.CreationDate); + } } } diff --git a/src/QiniuTests/Storage/OperationManagerTests.cs b/src/QiniuTests/Storage/OperationManagerTests.cs index 6b26175b..31506768 100644 --- a/src/QiniuTests/Storage/OperationManagerTests.cs +++ b/src/QiniuTests/Storage/OperationManagerTests.cs @@ -11,23 +11,30 @@ namespace Qiniu.Storage.Tests [TestFixture] public class OperationManagerTests :TestEnv { + private OperationManager getOperationManager() + { + Mac mac = new Mac(AccessKey, SecretKey); + Config config = new Config(); + // config.UseHttps = true; + + OperationManager manager = new OperationManager(mac, config); + return manager; + } [Test] - public void PfopTest() + public void PfopAndPrefopTest() { + string key = "qiniu.mp4"; + bool force = true; + string pipeline = "sdktest"; + string notifyUrl = "http://api.example.com/qiniu/pfop/notify"; string saveMp4Entry = Base64.UrlSafeBase64Encode(Bucket + ":avthumb_test_target.mp4"); string saveJpgEntry = Base64.UrlSafeBase64Encode(Bucket + ":vframe_test_target.jpg"); string avthumbMp4Fop = "avthumb/mp4|saveas/" + saveMp4Entry; string vframeJpgFop = "vframe/jpg/offset/1|saveas/" + saveJpgEntry; string fops = string.Join(";", new string[] { avthumbMp4Fop, vframeJpgFop }); - Mac mac = new Mac(AccessKey, SecretKey); - Config config = new Config(); - config.UseHttps = true; - OperationManager manager = new OperationManager(mac, config); - string pipeline = "sdktest"; - string notifyUrl = "http://api.example.com/qiniu/pfop/notify"; - string key = "qiniu.mp4"; - bool force = true; + + OperationManager manager = getOperationManager(); PfopResult pfopRet = manager.Pfop(Bucket, key, fops, pipeline, notifyUrl, force); if (pfopRet.Code != (int)HttpCode.OK) { @@ -42,5 +49,32 @@ public void PfopTest() } Console.WriteLine(ret.ToString()); } + + [Test] + public void PfopWithIdleTimeTest() + { + string key = "qiniu.mp4"; + bool force = true; + int type = 1; + string pipeline = null; + string saveJpgEntry = Base64.UrlSafeBase64Encode(Bucket + ":vframe_test_target.jpg"); + string vframeJpgFop = "vframe/jpg/offset/1|saveas/" + saveJpgEntry; + + OperationManager manager = getOperationManager(); + PfopResult pfopRet = manager.Pfop(Bucket, key, vframeJpgFop, pipeline, null, force, type); + if (pfopRet.Code != (int)HttpCode.OK) + { + Assert.Fail("pfop error: " + pfopRet.ToString()); + } + + PrefopResult prefopRet = manager.Prefop(pfopRet.PersistentId); + if (prefopRet.Code != (int)HttpCode.OK) + { + Assert.Fail("prefop error: " + prefopRet.ToString()); + } + Assert.AreEqual(1, prefopRet.Result.Type); + Assert.IsNotNull(prefopRet.Result.CreationDate); + Assert.IsNotEmpty(prefopRet.Result.CreationDate); + } } } diff --git a/src/QiniuTests/Util/Signature.cs b/src/QiniuTests/Util/Signature.cs index ae5e5015..2bbfa755 100644 --- a/src/QiniuTests/Util/Signature.cs +++ b/src/QiniuTests/Util/Signature.cs @@ -23,5 +23,13 @@ public string SignatureV2ByBytesTest(string method, string url, StringDictionary { return string.Format("Qiniu {0}", sign.SignRequestV2(method, url, headers, Encoding.UTF8.GetBytes(body))); } + + [TestCaseSource(typeof(VerifyRequestDataClass), nameof(VerifyRequestDataClass.TestCases))] + public bool VerifyRequestTest(string method, string url, StringDictionary headers, string body) + { + Mac mac = new Mac("abcdefghklmnopq", "1234567890"); + Signature mockSign = new Signature(mac); + return mockSign.VerifyRequest(method, url, headers, body); + } } } \ No newline at end of file diff --git a/src/QiniuTests/Util/TestCases.cs b/src/QiniuTests/Util/TestCases.cs index 3a52b623..1914979a 100644 --- a/src/QiniuTests/Util/TestCases.cs +++ b/src/QiniuTests/Util/TestCases.cs @@ -211,4 +211,36 @@ public static IEnumerable TestCases } } } -} \ No newline at end of file + + public class VerifyRequestDataClass + { + public static IEnumerable TestCases + { + get + { + yield return new TestCaseData( + "", + "https://test.qiniu.com/callback", + new StringDictionary + { + {"Authorization", "QBox abcdefghklmnopq:T7F-SjxX7X2zI4Fc1vANiNt1AUE="}, + {"Content-Type", "application/x-www-form-urlencoded"} + }, + "name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123" + ).Returns(true); + + yield return new TestCaseData( + "GET", + "https://test.qiniu.com/callback", + new StringDictionary + { + {"Authorization", "Qiniu abcdefghklmnopq:ZqS7EZuAKrhZaEIxqNGxDJi41IQ="}, + {"X-Qiniu-Bbb", "BBB"}, + {"Content-Type", "application/x-www-form-urlencoded"} + }, + "name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123" + ).Returns(true); + } + } + } +}