From 5b42d8ec6ef8d150f763f18b4422d2154ceafb30 Mon Sep 17 00:00:00 2001 From: lihsai0 Date: Tue, 27 Aug 2024 20:37:54 +0800 Subject: [PATCH 1/5] feat: reorder default query regions hosts --- src/Qiniu/Storage/Config.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Qiniu/Storage/Config.cs b/src/Qiniu/Storage/Config.cs index ecbb803..815256e 100644 --- a/src/Qiniu/Storage/Config.cs +++ b/src/Qiniu/Storage/Config.cs @@ -18,14 +18,14 @@ public class Config /// /// 默认查询区域域名 /// - 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" }; /// From 04def037940fc7abcbd8f759f7e57db7c68d7a86 Mon Sep 17 00:00:00 2001 From: lihsai0 Date: Thu, 29 Aug 2024 14:45:19 +0800 Subject: [PATCH 2/5] feat: add `VerifyRequest` to check if request signed by qn --- src/Qiniu/Util/Signature.cs | 45 ++++++++++++++++++++++++++++++++ src/QiniuTests/Util/Signature.cs | 8 ++++++ src/QiniuTests/Util/TestCases.cs | 34 +++++++++++++++++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/Qiniu/Util/Signature.cs b/src/Qiniu/Util/Signature.cs index 02c2aad..915cb65 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/Util/Signature.cs b/src/QiniuTests/Util/Signature.cs index ae5e501..2bbfa75 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 3a52b62..1914979 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); + } + } + } +} From 7b6cd35b475532dbf4ba43161663fca94c345ed6 Mon Sep 17 00:00:00 2001 From: lihsai0 Date: Tue, 27 Aug 2024 20:35:59 +0800 Subject: [PATCH 3/5] feat: more fop support - add idle-time fop support - add getting fop status - add configurable for pfop api host config --- 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/QiniuTests/Storage/FormUploaderTests.cs | 51 ++++++++++++++++++ .../Storage/OperationManagerTests.cs | 52 +++++++++++++++---- 6 files changed, 126 insertions(+), 11 deletions(-) diff --git a/src/Qiniu/Storage/OperationManager.cs b/src/Qiniu/Storage/OperationManager.cs index 8a3b8dd..07bac22 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 f3a95e3..2dfbd0a 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 bd248bd..2b5a733 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 79be623..715ef64 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/QiniuTests/Storage/FormUploaderTests.cs b/src/QiniuTests/Storage/FormUploaderTests.cs index 0eb2a6d..bb145ec 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 6b26175..3150676 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); + } } } From e72a3fd141ec0f32b86c046b0b3cec142b55fa25 Mon Sep 17 00:00:00 2001 From: lihsai0 Date: Thu, 5 Sep 2024 14:16:49 +0800 Subject: [PATCH 4/5] chore: bump version info to v8.6.0 and update changelog --- CHANGELOG.md | 10 ++++++++++ src/Qiniu/Qiniu.csproj | 2 +- src/Qiniu/QiniuCSharpSDK.cs | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cca2f38..1ad8f3b 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 a166e1a..bd2b8fd 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 65e023a..eef1201 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"; } From 2f7479b48622cc91d03dd9628d33197cabe3d677 Mon Sep 17 00:00:00 2001 From: lihsai0 Date: Thu, 19 Sep 2024 19:36:58 +0800 Subject: [PATCH 5/5] feat: change default uc host --- src/Qiniu/Storage/Config.cs | 2 +- src/QiniuTests/Storage/ConfigTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Qiniu/Storage/Config.cs b/src/Qiniu/Storage/Config.cs index 815256e..949f4e9 100644 --- a/src/Qiniu/Storage/Config.cs +++ b/src/Qiniu/Storage/Config.cs @@ -14,7 +14,7 @@ public class Config /// /// 默认空间管理域名 /// - public static string DefaultUcHost = "uc.qbox.me"; + public static string DefaultUcHost = "uc.qiniuapi.com"; /// /// 默认查询区域域名 /// diff --git a/src/QiniuTests/Storage/ConfigTests.cs b/src/QiniuTests/Storage/ConfigTests.cs index 8ea1f67..1215a15 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);