BlobService.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using Azure;
  2. using Microsoft.Azure.Cosmos;
  3. using Azure.Messaging.ServiceBus;
  4. using Microsoft.Extensions.Configuration;
  5. using StackExchange.Redis;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Text;
  9. using System.Text.Json;
  10. using System.Threading.Tasks;
  11. using TEAMModelOS.SDK.DI;
  12. using TEAMModelOS.SDK.Extension;
  13. using TEAMModelOS.SDK.Models;
  14. namespace TEAMModelOS.SDK.Services
  15. {
  16. public static class BlobService
  17. {
  18. public static async Task RefreshBlobRoot(BlobRefreshMessage message, AzureServiceBusFactory _serviceBus,IConfiguration _configuration,AzureRedisFactory _azureRedis) {
  19. if (!string.IsNullOrWhiteSpace(message.root) && !string.IsNullOrWhiteSpace(message.name)) {
  20. string changeKey = $"Blob:Change:{message.name}";
  21. string lockKey = $"Blob:Lock:{message.name}:{message.root}";
  22. bool exist = await _azureRedis.GetRedisClient(8).KeyExistsAsync(lockKey);
  23. //不存在Blob:Lock:hbcn:video 文件夹在队列中 则加入队列
  24. if (!exist)
  25. {
  26. //保持一天的时间
  27. TimeSpan timeSpan = new TimeSpan(1,0,0);
  28. //加入队列的时间
  29. long action = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  30. await _azureRedis.GetRedisClient(8).StringSetAsync(lockKey, action, expiry: timeSpan);
  31. var messageBlob = new ServiceBusMessage(message.ToJsonString());
  32. messageBlob.ApplicationProperties.Add("name", "BlobRoot");
  33. var ActiveTask = _configuration.GetValue<string>("Azure:ServiceBus:ActiveTask");
  34. await _serviceBus.GetServiceBusClient().SendMessageAsync(ActiveTask, messageBlob);
  35. }
  36. //如果已经存在,则忽略,不加入队列。
  37. }
  38. }
  39. /// <summary>
  40. /// 获取学校或者个人的剩余空间
  41. /// </summary>
  42. /// <param name="name">学校id 或者醍摩豆id</param>
  43. /// <param name="scope"></param>
  44. /// <param name="_azureCosmos"></param>
  45. /// <param name="_azureRedis"></param>
  46. public static async Task<(long usedSize, long teach, long total, long surplus, Dictionary<string, double?> catalog)>
  47. GetSurplusSpace(string name, string scope,string location, AzureCosmosFactory _azureCosmos, AzureRedisFactory _azureRedis, AzureStorageFactory _azureStorage,DingDing _dingDing, HttpTrigger _httpTrigger)
  48. {
  49. //已经存储的空间
  50. long usedSize = 0;
  51. //分配给教师的
  52. long teach = 0;
  53. //总空间
  54. long total = 0;
  55. // 剩余的
  56. long surplus = 0;
  57. Teacher teacher = null;
  58. Dictionary<string, double?> catalog = new Dictionary<string, double?>();
  59. try
  60. {
  61. if (!string.IsNullOrWhiteSpace(name))
  62. {
  63. if ("school".Equals(scope, StringComparison.OrdinalIgnoreCase))
  64. {
  65. School school = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<School>(name, new PartitionKey("Base"));
  66. total = school.size;
  67. teach = school.tsize;
  68. //用tsize代替
  69. //await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryStreamIteratorSql(queryText: $"SELECT sum(c.size) as size FROM c ",
  70. // requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Teacher-{name}") }))
  71. //{
  72. // var json = await JsonDocument.ParseAsync(item.Content);
  73. // foreach (var elmt in json.RootElement.GetProperty("Documents").EnumerateArray())
  74. // {
  75. // if (elmt.TryGetProperty("size", out JsonElement _size) && _size.ValueKind.Equals(JsonValueKind.Number))
  76. // {
  77. // teach = _size.GetInt32();
  78. // break;
  79. // }
  80. // }
  81. //}
  82. }
  83. else
  84. {
  85. teacher = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<Teacher>(name, new PartitionKey("Base"));
  86. total = teacher.size;
  87. foreach (var school in teacher.schools)
  88. {
  89. try
  90. {
  91. SchoolTeacher schoolTeacher = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<SchoolTeacher>(name, new PartitionKey($"Teacher-{school.schoolId}"));
  92. total += schoolTeacher.size;
  93. }
  94. catch (Exception ex) { }
  95. }
  96. }
  97. long blobsize = 0;
  98. RedisValue value = default;
  99. value = _azureRedis.GetRedisClient(8).HashGet($"Blob:Record", name);
  100. if (value != default && !value.IsNullOrEmpty)
  101. {
  102. JsonElement record = value.ToString().ToObject<JsonElement>();
  103. if (record.TryGetInt64(out blobsize))
  104. {
  105. }
  106. }
  107. else
  108. {
  109. var client = _azureStorage.GetBlobContainerClient(name);
  110. var size = await client.GetBlobsCatalogSize();
  111. if (size.Item1 > 0)
  112. {
  113. await _azureRedis.GetRedisClient(8).HashSetAsync($"Blob:Record", name, size.Item1);
  114. }
  115. foreach (var key in size.Item2.Keys)
  116. {
  117. await _azureRedis.GetRedisClient(8).SortedSetRemoveAsync($"Blob:Catalog:{name}", key);
  118. await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Blob:Catalog:{name}", key, size.Item2[key].HasValue ? size.Item2[key].Value : 0);
  119. }
  120. usedSize = size.Item1.Value;
  121. //1024 * 1024 * 1024=1073741824 =1G
  122. surplus = (total - teach) * 1073741824 - usedSize;
  123. catalog=size.Item2;
  124. //return (usedSize, teach, total, surplus, catalog = size.Item2);
  125. }
  126. SortedSetEntry[] Scores = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Blob:Catalog:{name}");
  127. if (Scores != null)
  128. {
  129. foreach (var score in Scores)
  130. {
  131. double val = score.Score;
  132. string key = score.Element.ToString();
  133. if (!string.IsNullOrWhiteSpace(key))
  134. {
  135. catalog[key] = val;
  136. }
  137. }
  138. usedSize = blobsize;
  139. //1024 * 1024 * 1024=1073741824 =1G
  140. surplus = (total - teach) * 1073741824 - usedSize;
  141. //return (usedSize, teach, total, surplus, catalog);
  142. }
  143. else
  144. {
  145. await _dingDing.SendBotMsg($"{location}Blob空间计算没有缓存{name}", GroupNames.成都开发測試群組);
  146. //没有缓存
  147. var client = _azureStorage.GetBlobContainerClient(name);
  148. var size = await client.GetBlobsCatalogSize();
  149. if (size.Item1 > 0)
  150. {
  151. await _azureRedis.GetRedisClient(8).HashSetAsync($"Blob:Record", name, size.Item1);
  152. }
  153. foreach (var key in size.Item2.Keys)
  154. {
  155. await _azureRedis.GetRedisClient(8).SortedSetRemoveAsync($"Blob:Catalog:{name}", key);
  156. await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Blob:Catalog:{name}", key, size.Item2[key].HasValue ? size.Item2[key].Value : 0);
  157. }
  158. usedSize = size.Item1.Value;
  159. //1024 * 1024 * 1024=1073741824 =1G
  160. //surplus 可能为负数
  161. surplus = (total - teach) * 1073741824 - usedSize;
  162. catalog = size.Item2;
  163. //return (usedSize, teach, total, surplus, catalog = size.Item2);
  164. }
  165. }
  166. }
  167. catch (Exception ex)
  168. {
  169. await _dingDing.SendBotMsg($"IES5,{location},GetSurplusSpace \n{ex.Message}\n{ex.StackTrace}\n{name},\n{scope}", GroupNames.醍摩豆服務運維群組);
  170. return (usedSize, teach, total, surplus, catalog);
  171. }
  172. if (usedSize > 0 && total > 0) {
  173. double percent = (surplus * 1.0 * 100 / 1073741824 / total );
  174. if (percent < 10) {
  175. _ = _httpTrigger.RequestHttpTrigger(new { name, scope, percent }, location, "surplus-space-notify");
  176. }
  177. }
  178. return (usedSize, teach, total, surplus, catalog);
  179. }
  180. /// <summary>
  181. /// 记录在redis,使用ttl 进行过期管理
  182. /// </summary>
  183. public class BlobSpaceNotify
  184. {
  185. /// <summary>
  186. /// id=$"{tmdid}-{tag}"
  187. /// </summary>
  188. public string? id { get; set; }
  189. public int tag { get; set; }
  190. public string? containerName { get; set; }
  191. public string? scope { get; set; }
  192. public string? notifyIndex { get; set; }
  193. }
  194. /// <param name="scope">school or private</param>
  195. /// <param name="containerName">school code or tmid</param>
  196. /// <param name="type">doc, res, item... Empty if no need.</param>
  197. /// <param name="periodId">Only for school</param>
  198. public static async Task<List<BlobCount>> BloblogCount(CosmosClient clientc, string scope, string containerName, string type, string periodId)
  199. {
  200. List<BlobCount> bloblogcnt = new List<BlobCount>();
  201. try
  202. {
  203. //必須項檢查
  204. if(scope.Equals("school") && string.IsNullOrWhiteSpace(periodId))
  205. {
  206. return bloblogcnt;
  207. }
  208. //資料取得
  209. var queryslt = new StringBuilder();
  210. queryslt.Append($"SELECT COUNT(1) AS count, c.type FROM c ");
  211. if (scope.Equals("school"))
  212. {
  213. queryslt.Append($"JOIN A1 IN c.periodId WHERE A1 IN ('{periodId}') ");
  214. if(!string.IsNullOrWhiteSpace(type)) queryslt.Append($"AND c.type='{type}'");
  215. queryslt.Append("GROUP BY c.type");
  216. await foreach (var item in clientc.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIteratorSql<BlobCount>(queryText: queryslt.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{containerName}") }))
  217. {
  218. bloblogcnt.Add(item);
  219. }
  220. }
  221. else if (scope.Equals("private"))
  222. {
  223. if (!string.IsNullOrWhiteSpace(type)) queryslt.Append($"AND c.type='{type}'");
  224. queryslt.Append("GROUP BY c.type");
  225. await foreach (var item in clientc.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIteratorSql<BlobCount>(queryText: queryslt.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{containerName}") }))
  226. {
  227. bloblogcnt.Add(item);
  228. }
  229. }
  230. return bloblogcnt;
  231. }
  232. catch (Exception ex)
  233. {
  234. return bloblogcnt;
  235. }
  236. }
  237. public class UsedBlob
  238. {
  239. public long teach { get; set; }
  240. public long? size { get; set; }
  241. public Dictionary<string, double?> catalog { get; set; }
  242. }
  243. public class BlobCount
  244. {
  245. public string type { get; set; }
  246. public int count { get; set; }
  247. }
  248. }
  249. public class BlobRefreshMessage{
  250. public string id { get; set; }= Guid.NewGuid().ToString();
  251. public string name { get; set; }
  252. public string progress { get; set; }
  253. public string root { get; set; }
  254. }
  255. }