IESTimerTrigger.cs 34 KB

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