IESTimerTrigger.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  1. using Azure.Cosmos;
  2. using Azure.Storage.Blobs.Models;
  3. using Azure.Storage.Blobs.Specialized;
  4. using DinkToPdf;
  5. using DinkToPdf.Contracts;
  6. using DocumentFormat.OpenXml.Spreadsheet;
  7. using HTEXLib.COMM.Helpers;
  8. using Microsoft.Azure.Cosmos.Table;
  9. using Microsoft.Azure.Functions.Worker;
  10. using Microsoft.Azure.Functions.Worker.Http;
  11. using Microsoft.Extensions.Configuration;
  12. using Microsoft.Extensions.Logging;
  13. using Microsoft.Extensions.Options;
  14. using Microsoft.OData.Edm;
  15. using StackExchange.Redis;
  16. using System;
  17. using System.Collections.Concurrent;
  18. using System.Collections.Generic;
  19. using System.IO;
  20. using System.Linq;
  21. using System.Net;
  22. using System.Net.Http;
  23. using System.Reflection;
  24. using System.Text;
  25. using System.Text.Json;
  26. using System.Threading.Tasks;
  27. using System.Web;
  28. using TEAMModelOS.Models;
  29. using TEAMModelOS.SDK;
  30. using TEAMModelOS.SDK.DI;
  31. using TEAMModelOS.SDK.DI.Mail;
  32. using TEAMModelOS.SDK.Extension;
  33. using TEAMModelOS.SDK.Models;
  34. using TEAMModelOS.SDK.Models.Cosmos.School;
  35. using TEAMModelOS.SDK.Models.Cosmos.Teacher;
  36. using TEAMModelOS.SDK.Models.Service;
  37. using TEAMModelOS.SDK.Models.Service.BI;
  38. using TEAMModelOS.SDK.Models.Table;
  39. using static TEAMModelOS.SDK.Models.Service.SystemService;
  40. using static TEAMModelOS.SDK.Models.Teacher;
  41. namespace TEAMModelOS.FunctionV4.TimeTrigger
  42. {
  43. public class IESTimerTrigger
  44. {
  45. /// <summary>
  46. /// 文档。https://docs.microsoft.com/zh-cn/azure/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-csharp
  47. /// Timer 在线测试 https://ncrontab.swimburger.net/
  48. /// </summary>
  49. private readonly AzureCosmosFactory _azureCosmos;
  50. private readonly DingDing _dingDing;
  51. private readonly AzureStorageFactory _azureStorage;
  52. private readonly AzureRedisFactory _azureRedis;
  53. private readonly IConverter _converter;
  54. private readonly SnowflakeId _snowflakeId;
  55. private readonly IHttpClientFactory _httpClient;
  56. private readonly CoreAPIHttpService _coreAPIHttpService;
  57. private IPSearcher _ipSearcher;
  58. private readonly Region2LongitudeLatitudeTranslator _longitudeLatitudeTranslator;
  59. private readonly MailFactory _mailFactory;
  60. private readonly IConfiguration _configuration;
  61. public IESTimerTrigger(IConfiguration configuration,MailFactory mailFactory, CoreAPIHttpService coreAPIHttpService, Region2LongitudeLatitudeTranslator longitudeLatitudeTranslator, IPSearcher ipSearcher, IHttpClientFactory httpClient, SnowflakeId snowflakeId, IConverter converter, AzureCosmosFactory azureCosmos, DingDing dingDing, AzureStorageFactory azureStorage, AzureRedisFactory azureRedis)
  62. {
  63. _azureCosmos = azureCosmos;
  64. _dingDing = dingDing;
  65. _azureStorage = azureStorage;
  66. _azureRedis = azureRedis;
  67. _converter = converter;
  68. _snowflakeId=snowflakeId;
  69. _httpClient = httpClient;
  70. _ipSearcher = ipSearcher;
  71. _coreAPIHttpService = coreAPIHttpService;
  72. _longitudeLatitudeTranslator = longitudeLatitudeTranslator;
  73. _mailFactory = mailFactory;
  74. _configuration= configuration;
  75. }
  76. /// <summary>
  77. /// 防火墙日志记录文件
  78. /// </summary>
  79. /// <param name="req"></param>
  80. /// <param name="log"></param>
  81. /// <returns></returns>
  82. [Function("DailyReport")]
  83. //0 1 * * * * 一天中每小时的第 1 分钟
  84. //0 */10 * * * * 每五分钟一次
  85. public async Task DailyReport([TimerTrigger("0 05 * * * *")] TimerInfo myTimer, ILogger log)
  86. {
  87. try
  88. {
  89. await _dingDing.SendBotMsg($"定时任务{DateTimeOffset.Now.ToString("yyyy-MM-dd HH:mm:ss")}", GroupNames.成都开发測試群組);
  90. string local = Environment.GetEnvironmentVariable("Option:Location");
  91. if (local.Contains("Test")||local.Contains("Dep"))
  92. {
  93. int am =8;
  94. int pm = 20;
  95. //int am = DateTimeOffset.Now.Hour;
  96. //int pm = DateTimeOffset.Now.Hour;
  97. var url = Environment.GetEnvironmentVariable("HaBookAuth:CoreAPI");
  98. var clientID = Environment.GetEnvironmentVariable("HaBookAuth:CoreService:clientID");
  99. var clientSecret = Environment.GetEnvironmentVariable("HaBookAuth:CoreService:clientSecret");
  100. string location = "";
  101. if (local.Contains("China"))
  102. {
  103. location = "China";
  104. }
  105. else if (local.Contains("Global"))
  106. {
  107. location = "Global";
  108. }
  109. var client = _httpClient.CreateClient();
  110. var token = CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, location).Result;
  111. if (client.DefaultRequestHeaders.Contains("Authorization"))
  112. {
  113. client.DefaultRequestHeaders.Remove("Authorization");
  114. client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
  115. }
  116. else
  117. {
  118. client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.AccessToken}");
  119. }
  120. var data = await TEAMModelOS.SDK.Models.Service.SystemService.AccumulateDaily(_configuration,_azureRedis, _azureCosmos, _coreAPIHttpService, _dingDing, client, _snowflakeId, url, _mailFactory, am, pm);
  121. await _dingDing.SendBotMsg($"返回数据{data.ToJsonString()}", GroupNames.成都开发測試群組);
  122. }
  123. }
  124. catch (Exception ex)
  125. {
  126. await _dingDing.SendBotMsg($"{DailyReport}\n{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
  127. }
  128. }
  129. /// <summary>
  130. /// 防火墙日志记录文件
  131. /// </summary>
  132. /// <param name="req"></param>
  133. /// <param name="log"></param>
  134. /// <returns></returns>
  135. [Function("FireWallFileLog")]
  136. //https://docs.azure.cn/zh-cn/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-csharp
  137. //0 1 * * * * 一天中每小时的第 1 分钟
  138. //0 */10 * * * * 每五分钟一次
  139. public async Task FireWallFileLog([TimerTrigger("0 1 * * * *")] TimerInfo myTimer, ILogger log)
  140. {
  141. try
  142. {
  143. string location = Environment.GetEnvironmentVariable("Option:Location");
  144. var datetime = DateTimeOffset.Now.AddHours(-1);
  145. var y = datetime.Year;
  146. var m = datetime.Month >= 10 ? $"{datetime.Month}" : $"0{datetime.Month}";
  147. var d = datetime.Day >= 10 ? $"{datetime.Day}" : $"0{datetime.Day}";
  148. var h = datetime.Hour >= 10 ? $"{datetime.Hour}" : $"0{datetime.Hour}";
  149. #if DEBUG
  150. if (location.Equals("China-Dep"))
  151. #else
  152. if (location.Equals("China"))
  153. #endif
  154. {
  155. //string path = $"resourceId=/SUBSCRIPTIONS/73B7F9EF-D8B7-4444-9E8D-D80B43BF3CD4/RESOURCEGROUPS/TEAMMODELCHENGDU/PROVIDERS/MICROSOFT.NETWORK/APPLICATIONGATEWAYS/OSFIREWARE/y={y}/m={m}/d={d}/h={h}/m=00/PT1H.json";
  156. string path = $"resourceId=/SUBSCRIPTIONS/73B7F9EF-D8B7-4444-9E8D-D80B43BF3CD4/RESOURCEGROUPS/TEAMMODELCHENGDU/PROVIDERS/MICROSOFT.WEB/SITES/TEAMMODELOS/y={y}/m={m}/d={d}/h={h}/m=00/PT1H.json";
  157. var retn = await BILogAnalyseService.GetPathAnalyse(_azureStorage, _ipSearcher, _dingDing, path, "LogStorage");
  158. if (retn.recCnts.IsNotEmpty())
  159. {
  160. //https://teammodelos.blob.core.chinacloudapi.cn/0-public/pie-borderRadius.html
  161. string publishUrl = $"https://teammodelos.blob.core.chinacloudapi.cn/0-public/api-count.html?url={HttpUtility.UrlEncode(retn.saveUrls.First(), Encoding.UTF8)}&time={HttpUtility.UrlEncode(datetime.AddHours(8).ToString("yyyy年MM月dd日 HH时"), Encoding.UTF8)}";
  162. string ulrs = $"https://teammodelos.blob.core.chinacloudapi.cn/0-public/api-count.html?url={retn.saveUrls.First()}&time={datetime.AddHours(8).ToString("yyyy年MM月dd日 HH时")}";
  163. string ulr = $"http://cdhabook.teammodel.cn:8805/screen/screenshot-png?width=1920&height=1450&url={HttpUtility.UrlEncode(ulrs, Encoding.UTF8)}&delay=5000";
  164. string image = "";
  165. try
  166. {
  167. string strs = await _httpClient.CreateClient().GetStringAsync(ulr);
  168. if (!string.IsNullOrWhiteSpace(strs))
  169. {
  170. JsonElement json = strs.ToObject<JsonElement>();
  171. json.TryGetProperty("url", out JsonElement base64);
  172. using (MemoryStream ms = new MemoryStream(Convert.FromBase64String($"{base64}")))
  173. {
  174. TimeZoneInfo localTimezone = TimeZoneInfo.Local;
  175. var Hours = localTimezone.BaseUtcOffset.Hours;
  176. var nowTime = DateTimeOffset.UtcNow;
  177. if (Hours!=0)
  178. {
  179. //有时差
  180. nowTime = DateTimeOffset.UtcNow.AddHours(8-Hours);
  181. }
  182. image = await _azureStorage.GetBlobContainerClient("0-public").UploadFileByContainer(ms, $"visitCnt/{nowTime.ToString("yyyyMMdd")}", $"{nowTime.ToString("yyyyMMddHH")}.png", false);
  183. //image = await _azureStorage.GetBlobContainerClient("0-public").UploadFileByContainer(ms, $"visitCnt/{y}{m}{d}", $"{y}{m}{d}{h}.png", false);
  184. }
  185. }
  186. }
  187. catch (Exception ex)
  188. {
  189. }
  190. await _dingDing.SendBotMarkdown("防火墙日志记录", $"#### 防火墙日志记录(小时)\n> 记录时间:{datetime.AddHours(8).ToString("yyyy-MM-dd HH")}\n> ![screenshot]({image})\n> ###### 发布时间:{DateTime.Now.AddHours(8).ToString("yyyy-MM-dd HH:mm:ss")}" +
  191. $" [发布地址]({publishUrl}) \n", GroupNames.醍摩豆服務運維群組);
  192. }
  193. //处理昨天的防火墙日志
  194. var yesterday = datetime.AddHours(8).Hour >= 10 ? $"{datetime.AddHours(8).Hour}" : $"0{datetime.AddHours(8).Hour}";
  195. if (yesterday.Equals("00"))
  196. {
  197. var pastTime = datetime.AddHours(-1);
  198. var ptY = pastTime.Year;
  199. var ptM = pastTime.Month >= 10 ? $"{pastTime.Month}" : $"0{pastTime.Month}";
  200. var ptD = pastTime.Day >= 10 ? $"{pastTime.Day}" : $"0{pastTime.Day}";
  201. string dayPath = $"resourceId=/SUBSCRIPTIONS/73B7F9EF-D8B7-4444-9E8D-D80B43BF3CD4/RESOURCEGROUPS/TEAMMODELCHENGDU/PROVIDERS/MICROSOFT.WEB/SITES/TEAMMODELOS/y={ptY}/m={ptM}/d={ptD}";
  202. var retnDay = await BILogAnalyseService.GetPathAnalyse(_azureStorage, _ipSearcher, _dingDing, dayPath, "LogStorage", timeType: "Day");
  203. if (retn.recCnts.IsNotEmpty())
  204. {
  205. //一天的统计
  206. string dayPublishUrl = $"https://teammodelos.blob.core.chinacloudapi.cn/0-public/api-count.html?url={HttpUtility.UrlEncode(retnDay.saveUrls.First(), Encoding.UTF8)}&time={HttpUtility.UrlEncode(pastTime.ToString("yyyy年MM月dd日"), Encoding.UTF8)}";
  207. string dayUrls = $"https://teammodelos.blob.core.chinacloudapi.cn/0-public/api-count.html?url={retnDay.saveUrls.First()}&time={pastTime.ToString("yyyy年MM月dd日")}";
  208. string dayUrl = $"http://cdhabook.teammodel.cn:8805/screen/screenshot-png?width=1920&height=1450&url={HttpUtility.UrlEncode(dayUrls, Encoding.UTF8)}&delay=6000";
  209. string dayImage = "";
  210. try
  211. {
  212. string dayStr = await _httpClient.CreateClient().GetStringAsync(dayUrl);
  213. if (!string.IsNullOrWhiteSpace(dayStr))
  214. {
  215. JsonElement dayJson = dayStr.ToObject<JsonElement>();
  216. dayJson.TryGetProperty("url", out JsonElement dayBase64);
  217. using (MemoryStream dayMs = new(Convert.FromBase64String($"{dayBase64}")))
  218. {
  219. TimeZoneInfo localTimezone = TimeZoneInfo.Local;
  220. var Hours = localTimezone.BaseUtcOffset.Hours;
  221. var nowTime = DateTimeOffset.UtcNow;
  222. if (Hours!=0)
  223. {
  224. //有时差
  225. nowTime = DateTimeOffset.UtcNow.AddHours(8-Hours);
  226. }
  227. dayImage = await _azureStorage.GetBlobContainerClient("0-public").UploadFileByContainer(dayMs, $"visitCnt/{nowTime.ToString("yyyyMMdd")}", "days.png", false);
  228. }
  229. }
  230. }
  231. catch (Exception ex) { }
  232. await _dingDing.SendBotMarkdown("防火墙日志记录", $"#### 防火墙日志记录(天)\n> 记录时间:{pastTime.ToString("yyyy-MM-dd")}\n> ![screenshot]({dayImage})\n> ###### 发布时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}" +
  233. $" [发布地址]({dayPublishUrl}) \n", GroupNames.醍摩豆服務運維群組);
  234. }
  235. }
  236. }
  237. else if (location.Contains("Global"))
  238. {
  239. }
  240. }
  241. catch (Exception ex)
  242. {
  243. // await _dingDing.SendBotMsg($"FireWallFileLog 防火墙日志记录: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}\n{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
  244. }
  245. }
  246. /// <summary>
  247. /// 每天执行 一次清零动作
  248. /// </summary>
  249. /// <param name="myTimer"></param>
  250. /// <param name="log"></param>
  251. /// <returns></returns>
  252. [Function("BIStatsDayDefault")]
  253. //https://docs.azure.cn/zh-cn/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-csharp
  254. //0 1 0 * * * 一天中00的第 1 分钟
  255. //0 1 * * * * 一天中每小时的第 1 分钟
  256. //0 */10 * * * * 每五分钟一次
  257. public async Task BIStatsDayDefault([TimerTrigger("0 1 0 * * *")] TimerInfo myTimer, ILogger log)
  258. {
  259. try
  260. {
  261. _ = BIStats.SetStatsZeroPoint(_azureCosmos, _dingDing);
  262. }
  263. catch (Exception ex)
  264. {
  265. await _dingDing.SendBotMsg($"BIStatsDayDefault 定时清理每天的数据: {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}\n{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
  266. }
  267. }
  268. /// <summary>
  269. /// 清理HiTeach教研类型的课例文件上传在Blob空间的文件。 0 */10 22-23 * * *
  270. /// 该代码执行之后 需要删除或者取消Timer任务。
  271. /// </summary>
  272. /// <param name="myTimer"></param>
  273. /// <param name="log"></param>
  274. /// <returns></returns>
  275. //[Function("CleanUnusedLessonRecord")]
  276. //5分钟一次
  277. public async Task CleanUnusedLessonRecord(
  278. //[TimerTrigger("0 */10 15-23 * * *")] TimerInfo myTimer, ILogger log
  279. )
  280. {
  281. string location = Environment.GetEnvironmentVariable("Option:Location");
  282. try
  283. {
  284. if (location.Equals("China") || location.Equals("Global"))
  285. {
  286. bool lockKey = await _azureRedis.GetRedisClient(8).KeyExistsAsync($"LessonRecord:Unused:Lock:{location}");
  287. bool keyLock = await _azureRedis.GetRedisClient(8).KeyExistsAsync($"LessonRecord:Unused:Lock:Lock");
  288. if (keyLock)
  289. {
  290. return;
  291. }
  292. //key不存在的时候 。
  293. if (!lockKey)
  294. {
  295. var schoolKeys = new List<IdCodeCount>();
  296. var teacherKeys = new List<IdCodeCount>();
  297. string sqlT = "select c.id,c.name, 'Teacher' as code from c where c.code='Base'";
  298. string sqlS = "select c.id,c.name, 'School' as code from c where c.code='Base'";
  299. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).
  300. GetItemQueryIterator<IdCodeCount>(queryText: sqlT, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey("Base") }))
  301. {
  302. teacherKeys.Add(item);
  303. }
  304. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).
  305. GetItemQueryIterator<IdCodeCount>(queryText: sqlS, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey("Base") }))
  306. {
  307. schoolKeys.Add(item);
  308. }
  309. string sqlcount = "select value count(1) from c where c.pk='LessonRecord'";
  310. foreach (var key in schoolKeys)
  311. {
  312. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).
  313. GetItemQueryIterator<int>(queryText: sqlcount, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"LessonRecord-{key.id}") }))
  314. {
  315. key.count = item;
  316. break;
  317. }
  318. }
  319. foreach (var key in teacherKeys)
  320. {
  321. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).
  322. GetItemQueryIterator<int>(queryText: $"select value count(1) from c where c.pk='LessonRecord' and c.tmdid='{key.id}'",
  323. requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"LessonRecord") }))
  324. {
  325. key.count = item;
  326. break;
  327. }
  328. }
  329. schoolKeys.AddRange(teacherKeys);
  330. //以下代码作用,用于根据当前拥有的课例数判断区分学校和个人使用课例频率越多的,
  331. //并均分每次需要处理的学校和个人容器个数,避免每次处理批次花费时间相差太大。
  332. //将每个批次处理数量级控制在500-1000左右。
  333. List<IEnumerable<IdCodeCount>> counts = new List<IEnumerable<IdCodeCount>>();
  334. //501-∞
  335. var count501_ = schoolKeys.Where(x => x.count >= 501); //至少500条
  336. counts.AddRange(count501_.Page(1));//1个学校或个人
  337. //201-500
  338. var count201_500 = schoolKeys.Where(x => x.count >= 201 && x.count <= 500); //至少603条-1500条
  339. counts.AddRange(count201_500.Page(3));//2个学校或个人
  340. //100-200
  341. var count100_200 = schoolKeys.Where(x => x.count >= 100 && x.count <= 200);//至少500条-1000条
  342. counts.AddRange(count100_200.Page(10));//5个学校或个人
  343. //51-99
  344. var count51_99 = schoolKeys.Where(x => x.count >= 51 && x.count <= 99);//至少500条-990条
  345. counts.AddRange(count51_99.Page(10));//10个学校或个人
  346. //21-50
  347. var count21_50 = schoolKeys.Where(x => x.count >= 21 && x.count <= 50);//至少410条-1000条
  348. counts.AddRange(count21_50.Page(20));//20个学校或个人
  349. //10-20
  350. var count10_20 = schoolKeys.Where(x => x.count >= 10 && x.count <= 20);//至少500条-1000条
  351. counts.AddRange(count10_20.Page(50));//50个学校或个人
  352. //5-9
  353. var count0_9 = schoolKeys.Where(x => x.count >= 5 && x.count <= 9);//至少500条-900条
  354. counts.AddRange(count0_9.Page(100));//100个学校或个人
  355. //2-4
  356. var count2_4 = schoolKeys.Where(x => x.count >= 2 && x.count <= 4);//至少600条-1200条
  357. counts.AddRange(count2_4.Page(300));
  358. //0-1,已经处理过一次,不用再处理
  359. var count0_1 = schoolKeys.Where(x => x.count ==1);//至少500,就算没有课例也算一次。
  360. counts.AddRange(count0_1.Page(1000));
  361. int field = 1;
  362. foreach (var item in counts)
  363. {
  364. if (item.Any())
  365. {
  366. bool stuallstatus = await _azureRedis.GetRedisClient(8).HashSetAsync($"LessonRecord:Unused:Lock:{location}", field,
  367. new UnusedLock { field = field, status = 0, item = item }.ToJsonString());
  368. field += 1;
  369. }
  370. }
  371. await _dingDing.SendBotMsg($"{location},获取到:{schoolKeys.Count} 个学校和个人的容器\n" +
  372. $"大概要处理:{schoolKeys.Sum(x => x.count) + schoolKeys.Where(x => x.count >= 0).Count()} 条数据\n" +
  373. $"将数据分为:{field - 1} 次处理,每次处理500-1000条数据。", GroupNames.醍摩豆服務運維群組);
  374. }
  375. //key存在的时候 开始进行数据处理
  376. var records = await _azureRedis.GetRedisClient(8).HashGetAllAsync($"LessonRecord:Unused:Lock:{location}");
  377. List<UnusedLock> unuseds = new List<UnusedLock>();
  378. foreach (var rcd in records)
  379. {
  380. var value = rcd.Value.ToString().ToObject<UnusedLock>();
  381. unuseds.Add(value);
  382. }
  383. int max = unuseds.Max(x => x.field);
  384. int index = max + 1;
  385. var unusedLock = unuseds.Where(s => s.status == 0)?.OrderBy(x => x.field)?.First();
  386. if (unusedLock != null)
  387. {
  388. var stime = DateTimeOffset.UtcNow;
  389. long s = stime.ToUnixTimeMilliseconds();
  390. string sdata = stime.ToString("yyyy-MM-dd HH:mm:ss");
  391. await _dingDing.SendBotMsg($"{location}-开始第{unusedLock.field}轮清理冗余的课例记录文件...\n开始时间:{sdata}\n清理容器有{unusedLock.item.Select(x => $"{x.name}-{x.id}").ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  392. //将当前的标记为1锁定。
  393. bool stuallstatus = await _azureRedis.GetRedisClient(8).HashSetAsync($"LessonRecord:Unused:Lock:{location}", unusedLock.field,
  394. new UnusedLock { field = unusedLock.field, status = 1, item = unusedLock.item }.ToJsonString());
  395. List<KeyValuePair<string, List<string>>> deleteUrls = new List<KeyValuePair<string, List<string>>>();
  396. foreach (IdCodeCount idCode in unusedLock.item)
  397. {
  398. try
  399. {
  400. List<string> urls = new List<string>();
  401. var ContainerClient = _azureStorage.GetBlobContainerClient(idCode.id);
  402. List<string> items = await ContainerClient.List("records");
  403. if (items.IsNotEmpty())
  404. {
  405. HashSet<string> set = new HashSet<string>();
  406. items.ForEach(z =>
  407. {
  408. var uri = z.Split("/");
  409. if (uri.Length > 1)
  410. {
  411. set.Add(uri[1]);
  412. }
  413. });
  414. if (set.Any())
  415. {
  416. string tbname = idCode.code.Equals("Teacher") ? Constant.Teacher : Constant.School;
  417. string pk = idCode.code.Equals("Teacher") ? "LessonRecord" : $"LessonRecord-{idCode.id}";
  418. string sql = $"select value c.id from c where c.pk='LessonRecord' and c.id in ({string.Join(",", set.Select(m => $"'{m}'"))})";
  419. List<string> ids = new List<string>();
  420. await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname)
  421. .GetItemQueryIterator<string>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey(pk) }))
  422. {
  423. ids.Add(item);
  424. }
  425. var notin = set.Except(ids);
  426. if (notin.Any())
  427. {
  428. foreach (var not in notin)
  429. {
  430. if (!string.IsNullOrWhiteSpace(not))
  431. {
  432. string url = $"records/{not}";
  433. long id = -1;
  434. long.TryParse(not, out id);
  435. if (id > -1)
  436. {
  437. id = _snowflakeId.ParseIdToTimeStamp(id);
  438. }
  439. if (id > -1)
  440. {
  441. //72小时之外的数据。需要被删除 72 * 60 * 60 *1000= 259200000
  442. //当前时间-课例产生的时间戳
  443. if (s - id > 259200000)
  444. {
  445. urls.Add(url);
  446. await _azureStorage.GetBlobServiceClient().DeleteBlobs(_dingDing, idCode.id, new List<string> { url });
  447. }
  448. }
  449. }
  450. }
  451. }
  452. }
  453. }
  454. if (urls.IsNotEmpty())
  455. {
  456. deleteUrls.Add(new KeyValuePair<string, List<string>>($"{idCode.name}-{idCode.id}", urls));
  457. }
  458. }
  459. catch (Exception ex)
  460. {
  461. //异常的学校,需要将数据放回redis 重新处理。
  462. await _azureRedis.GetRedisClient(8).HashSetAsync($"LessonRecord:Unused:Lock:{location}", index,
  463. new UnusedLock { field = index, status = 0, item = new List<IdCodeCount>() { idCode } }.ToJsonString());
  464. index += 1;
  465. await _dingDing.SendBotMsg($"{location}-第{unusedLock.field}轮清理时容器:{idCode.id},类型:{idCode.code}出现异常。\n将{idCode.id}重新加入队列,队列编号:{index}\n" +
  466. $"异常信息:{ex.Message},{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  467. }
  468. }
  469. await _azureRedis.GetRedisClient(8).HashDeleteAsync($"LessonRecord:Unused:Lock:{location}", unusedLock.field);
  470. var etime = DateTimeOffset.UtcNow;
  471. long e = etime.ToUnixTimeMilliseconds();
  472. string edata = etime.ToString("yyyy-MM-dd HH:mm:ss");
  473. long d = (e - s) / 1000;
  474. string timeStr = d >= 60 ? Math.Round(d / 60.0) + "分" : d + "秒";
  475. List<string> content = new List<string>();
  476. foreach (var url in deleteUrls)
  477. {
  478. if (url.Value.IsNotEmpty())
  479. {
  480. content.Add($"{url.Key}\n {string.Join(" \t\n", url.Value)}");
  481. }
  482. }
  483. if (max == unusedLock.field)
  484. {
  485. //锁定15至少小时。
  486. await _azureRedis.GetRedisClient(8).StringSetAsync($"LessonRecord:Unused:Lock:Lock", "用于在清理完所有Blob容器后,锁定不再进行多次清理。");
  487. await _azureRedis.GetRedisClient(8).KeyExpireAsync($"LessonRecord:Unused:Lock:Lock", DateTime.UtcNow.AddHours(15));
  488. }
  489. if (content.IsNotEmpty())
  490. {
  491. string str = string.Join("\n", content);
  492. await _dingDing.SendBotMsg($"{location}-结束第{unusedLock.field}轮清理冗余的课例记录文件...\n结束时间:{edata}\n耗时:{timeStr}\n清理内容:\n{str}", GroupNames.醍摩豆服務運維群組);
  493. }
  494. else
  495. {
  496. // await _dingDing.SendBotMsg($"{location}-结束第{unusedLock.field}轮清理冗余的课例记录文件...\n结束时间:{edata}\n耗时:{timeStr}\n清理内容:暂无", GroupNames.醍摩豆服務運維群組);
  497. }
  498. }
  499. }
  500. }
  501. catch (Exception ex)
  502. {
  503. await _dingDing.SendBotMsg($"{location} 清理时容器出现异常。\n异常信息:{ex.Message},{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  504. }
  505. }
  506. /// <summary>
  507. /// 每天執行 取得IOT TeachingData 並統計昨日每校Redis資料 執行時間:每日凌晨1時1分
  508. /// </summary>
  509. [Function("BICrtDailyAnal")]
  510. //0 1 0 * * * 一天中00的第 1 分钟
  511. //0 1 * * * * 一天中每小时的第 1 分钟
  512. //0 */10 * * * * 每五分钟一次
  513. public async Task BICreatDailyAnalData([TimerTrigger("0 1 1 * * *")] TimerInfo myTimer, ILogger log)
  514. {
  515. var _azureCosmosClient = _azureCosmos.GetCosmosClient();
  516. var _azureCosmosClientCsv2 = _azureCosmos.GetCosmosClient(name: "CoreServiceV2");
  517. var _azureCosmosClientCsv2CnRead = _azureCosmos.GetCosmosClient(name: "CoreServiceV2CnRead");
  518. var datetime = DateTimeOffset.UtcNow.AddHours(-12); //統計昨天的數據
  519. var y = $"{datetime.Year}";
  520. var m = datetime.Month >= 10 ? $"{datetime.Month}" : $"0{datetime.Month}";
  521. var d = datetime.Day >= 10 ? $"{datetime.Day}" : $"0{datetime.Day}";
  522. //生成學校IOT數據
  523. await BIProdAnalysis.BICreatDailyAnalData(_azureRedis, _azureCosmosClient, _azureCosmosClientCsv2, _azureCosmosClientCsv2CnRead, _dingDing, y, m, d);
  524. //刪除三個月以前的Redis數據 [待做]
  525. }
  526. /// <summary>
  527. /// 每天執行 計算各學生各科錯題庫的數量,記入Redis 執行時間:每日2時1分
  528. /// 「新增錯題數」取得方法改由智慧錯題的時間戳記判斷,每日統計錯題數已不需要
  529. /// </summary>
  530. //[Function("CntStuErrorItems")]
  531. //public async Task CntStuErrorItems([TimerTrigger("0 1 2 * * *")] TimerInfo myTimer, ILogger log)
  532. //{
  533. // var _azureCosmosClient = _azureCosmos.GetCosmosClient();
  534. // await ErrorItemsService.cntStuErrorItemsAsync(_azureRedis, _azureCosmosClient, _dingDing);
  535. //}
  536. public class UnusedLock
  537. {
  538. public int field { get; set; }
  539. /// <summary>
  540. /// 0默认未被执行,1 正在执行中
  541. /// </summary>
  542. public int status { get; set; }
  543. public IEnumerable<IdCodeCount> item { get; set; } = new List<IdCodeCount>();
  544. }
  545. }
  546. }