BlobController.cs 72 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401
  1. using Microsoft.AspNetCore.Mvc;
  2. using Microsoft.Extensions.Configuration;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. using System.Text.Json;
  7. using System.Threading.Tasks;
  8. using TEAMModelOS.SDK;
  9. using TEAMModelOS.SDK.Module.AzureBlob.Configuration;
  10. using TEAMModelOS.SDK.DI;
  11. using System.Net.Http;
  12. using TEAMModelOS.SDK.Helper.Security.ShaHash;
  13. using TEAMModelOS.SDK.Extension;
  14. using System.IdentityModel.Tokens.Jwt;
  15. using Microsoft.AspNetCore.Authorization;
  16. using TEAMModelOS.Filter;
  17. using StackExchange.Redis;
  18. using Azure.Messaging.ServiceBus;
  19. using static TEAMModelOS.SDK.DI.AzureStorageBlobExtensions;
  20. using System.Linq;
  21. using Microsoft.AspNetCore.Http;
  22. using HTEXLib.COMM.Helpers;
  23. using TEAMModelOS.Models;
  24. using Microsoft.Extensions.Options;
  25. using TEAMModelOS.SDK.Models;
  26. using Azure.Cosmos;
  27. using Azure;
  28. using System.IO;
  29. using Azure.Storage.Blobs.Models;
  30. using Azure.Storage.Sas;
  31. using ContentTypeDict = TEAMModelOS.SDK.ContentTypeDict;
  32. using TEAMModelOS.SDK.Services;
  33. using DocumentFormat.OpenXml.Wordprocessing;
  34. using OpenXmlPowerTools;
  35. using Azure.Storage.Blobs;
  36. using Azure.Storage.Blobs.Specialized;
  37. using DocumentFormat.OpenXml.Drawing.Wordprocessing;
  38. namespace TEAMModelOS.Controllers
  39. {
  40. [Route("blob")]
  41. [ApiController]
  42. public class BlobController : ControllerBase
  43. {
  44. private readonly AzureStorageFactory _azureStorage;
  45. private readonly IHttpClientFactory _clientFactory;
  46. private readonly AzureRedisFactory _azureRedis;
  47. private readonly AzureServiceBusFactory _serviceBus;
  48. private readonly DingDing _dingDing;
  49. private readonly Option _option;
  50. private readonly AzureCosmosFactory _azureCosmos;
  51. private readonly HttpTrigger _httpTrigger;
  52. public IConfiguration _configuration { get; set; }
  53. public BlobController(HttpTrigger httpTrigger, AzureStorageFactory azureStorage, AzureServiceBusFactory serviceBus, IHttpClientFactory clientFactory, AzureRedisFactory azureRedis, IConfiguration configuration,
  54. DingDing dingDing,
  55. IOptionsSnapshot<Option> option, AzureCosmosFactory azureCosmos)
  56. {
  57. _azureStorage = azureStorage;
  58. _clientFactory = clientFactory;
  59. _serviceBus = serviceBus;
  60. _azureRedis = azureRedis;
  61. _configuration = configuration;
  62. _dingDing = dingDing;
  63. _option = option?.Value;
  64. _azureCosmos = azureCosmos;
  65. _httpTrigger = httpTrigger;
  66. }
  67. /// <summary>
  68. /// 上传文件到指定的0-public
  69. /// </summary>
  70. /// <param name="request"></param>
  71. /// <returns></returns>
  72. [HttpPost("public-upload")]
  73. [Authorize(Roles = "IES")]
  74. [AuthToken(Roles = "teacher,admin,business")]
  75. [RequestSizeLimit(102_400_000_00)] //最大10000m左右
  76. public async Task<IActionResult> PublicUpload([FromForm] IFormFile file,[FromForm]string path, [FromForm] int self=0 )
  77. {
  78. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  79. string fileExt = FileType.GetExtention(file.FileName).ToLower();
  80. if (string.IsNullOrWhiteSpace(path)) {
  81. path="school";
  82. }
  83. if (ContentTypeDict.dict.ContainsKey($".{fileExt}"))
  84. {
  85. string cnt = self==0 ? "0-public" : school;
  86. var url = await _azureStorage.GetBlobContainerClient(cnt).UploadFileByContainer(file.OpenReadStream(), path, $"{Guid.NewGuid()}.{fileExt}", false);
  87. return Ok(new { url });
  88. }
  89. else {
  90. return BadRequest();
  91. }
  92. }
  93. /// <summary>
  94. /// 上传文件到指定的0-public 课例报告
  95. /// </summary>
  96. /// <param name="request"></param>
  97. /// <returns></returns>
  98. [HttpPost("public-upload-lesson-report")]
  99. [Authorize(Roles = "IES")]
  100. [AuthToken(Roles = "teacher,admin")]
  101. [RequestSizeLimit(102_400_000_00)] //最大10000m左右
  102. public async Task<IActionResult> PublicUploadLessonReport([FromForm] IFormFile file)
  103. {
  104. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  105. string fileExt = FileType.GetExtention(file.FileName).ToLower();
  106. if (ContentTypeDict.dict.ContainsKey($".{fileExt}"))
  107. {
  108. var url = await _azureStorage.GetBlobContainerClient("0-public").UploadFileByContainer(file.OpenReadStream(), $"lesson-report/{school}", $"{file.FileName}", false);
  109. return Ok(new { url });
  110. }
  111. else
  112. {
  113. return BadRequest();
  114. }
  115. }
  116. /// <summary>
  117. /// 获取某个容器的只读权限
  118. /// </summary>
  119. /// <param name="request"></param>
  120. /// <returns></returns>
  121. [HttpPost("sas-r")]
  122. [AuthToken(Roles = "teacher,admin,student")]
  123. [Authorize(Roles = "IES")]
  124. public IActionResult BlobSasR(BlobSas request)
  125. {
  126. ///返回金钥过期时间
  127. // Dictionary<string, object> dict = await azureBlobDBRepository.GetBlobSasUri(request.@params,true);
  128. // dict.Add(d.Key, d.Value);
  129. return Ok(_azureStorage.GetContainerSasUri(request, true));
  130. } /// <summary>
  131. /// 获取某个容器的只读权限
  132. /// </summary>
  133. /// <param name="request"></param>
  134. /// <returns></returns>
  135. [HttpPost("sas-r-99")]
  136. [AuthToken(Roles = "teacher,admin,student")]
  137. [Authorize(Roles = "IES")]
  138. public IActionResult BlobSasR99(JsonElement json )
  139. {
  140. ///返回金钥过期时间
  141. // Dictionary<string, object> dict = await azureBlobDBRepository.GetBlobSasUri(request.@params,true);
  142. // dict.Add(d.Key, d.Value);
  143. var blob = _azureStorage.GetBlobContainerSAS99Year(json.GetProperty("containerName").GetString(), blobContainerSasPermissions: BlobContainerSasPermissions.Read|BlobContainerSasPermissions.List);
  144. return Ok(new { blob.uri, blob.sas});
  145. }
  146. /// <summary>
  147. /// 某个文件的上传SAS rcw权限
  148. /// </summary>
  149. /// <param name="request"></param>
  150. /// <returns></returns>
  151. [HttpPost("sas-rcwld")]
  152. [AuthToken(Roles = "teacher,admin,student")]
  153. [Authorize(Roles = "IES")]
  154. public IActionResult BlobSasRCW(BlobSas request)
  155. {
  156. ///返回金钥过期时间
  157. // Dictionary<string,object> dict= await azureBlobDBRepository.GetBlobSasUri(request.@params,false);
  158. // Dictionary<string, object> dict = ;
  159. //dict.Add(d.Key, d.Value);
  160. return Ok(_azureStorage.GetContainerSasUri(request, false));
  161. }
  162. /// <summary>
  163. /// 链接只读(读)
  164. /// </summary>
  165. /// <param name="azureBlobSASDto"></param>
  166. /// <returns></returns>
  167. [HttpPost("sas-url-r")]
  168. [AuthToken(Roles = "teacher,admin,student")]
  169. [Authorize(Roles = "IES")]
  170. public IActionResult GetContainerSASRead(JsonElement azureBlobSASDto)
  171. {
  172. azureBlobSASDto.TryGetProperty("url", out JsonElement azureBlobSAS);
  173. //string azureBlobSAS = azureBlobSASDto;
  174. (string, string) a = BlobUrlString(azureBlobSAS.ToString());
  175. string ContainerName = a.Item1;
  176. string BlobName = a.Item2;
  177. bool flg = IsBlobName(BlobName);
  178. if (flg)
  179. {
  180. return Ok(_azureStorage.GetBlobSasUriRead(ContainerName, BlobName));
  181. }
  182. else
  183. {
  184. return Ok(new BlobAuth { url = $"{azureBlobSAS}", sas = "", name = $"{azureBlobSAS}" });
  185. };
  186. }
  187. /// <summary>
  188. /// 第三方企业上传logo到指定的0-public
  189. /// </summary>
  190. /// <param name="file"></param>
  191. /// <returns></returns>
  192. [HttpPost("biz-upload-public")]
  193. [RequestSizeLimit(102_400_000_00)] //最大10000m左右
  194. [AuthToken(Roles = "teacher,admin,student")]
  195. [Authorize(Roles = "IES")]
  196. public async Task<IActionResult> UploadPublic([FromForm] IFormFile file)
  197. {
  198. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  199. string fileExt = FileType.GetExtention(file.FileName).ToLower();
  200. if (ContentTypeDict.dict.ContainsKey($".{fileExt}"))
  201. {
  202. var url = await _azureStorage.GetBlobContainerClient("0-public").UploadFileByContainer(file.OpenReadStream(), "business", $"{Guid.NewGuid()}.{fileExt}", false);
  203. return Ok(new { url });
  204. }
  205. else
  206. {
  207. return BadRequest();
  208. }
  209. }
  210. /// <summary>
  211. /// 获取文件内容
  212. /// </summary>
  213. /// <param name="azureBlobSASDto"></param>
  214. /// <returns></returns>
  215. [HttpPost("get-text")]
  216. [AuthToken(Roles = "teacher,admin,student")]
  217. [Authorize(Roles = "IES")]
  218. public async Task<IActionResult> GetText(JsonElement request)
  219. {
  220. request.TryGetProperty("code", out JsonElement code);
  221. string azureBlobSAS = System.Web.HttpUtility.UrlDecode(code.ToString(), Encoding.UTF8);
  222. (string, string) a = BlobUrlString(azureBlobSAS);
  223. string ContainerName = a.Item1;
  224. string BlobName = a.Item2;
  225. bool flg = IsBlobName(BlobName);
  226. if (flg)
  227. {
  228. //TODO 需驗證
  229. BlobAuth blobAuth = _azureStorage.GetBlobSasUriRead(ContainerName, BlobName);
  230. var response = await _clientFactory.CreateClient().GetAsync(new Uri(blobAuth.url + blobAuth.sas));
  231. response.EnsureSuccessStatusCode();
  232. using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
  233. return Ok(json.RootElement);
  234. }
  235. else
  236. {
  237. return BadRequest("文件名错误");
  238. };
  239. }
  240. /// <summary>
  241. /// {"containerName":"hbcn","scope":"school"}
  242. /// 获取容器的 分类及总量
  243. /// </summary>
  244. /// <param name="azureBlobSASDto"></param>
  245. /// <returns></returns>
  246. [HttpPost("used-space")]
  247. [AuthToken(Roles = "teacher,admin,student")]
  248. [Authorize(Roles = "IES")]
  249. public async Task<ActionResult> UsedSpace(JsonElement request)
  250. {
  251. request.TryGetProperty("scope", out JsonElement _scope);
  252. if (!request.TryGetProperty("containerName", out JsonElement containerName)) return BadRequest();
  253. (long usedSize, long teach, long total, long surplus, Dictionary<string, double?> catalog)space =await BlobService.GetSurplusSpace(containerName.ToString(), $"{_scope}",_option.Location, _azureCosmos, _azureRedis, _azureStorage,_dingDing,_httpTrigger);
  254. return Ok(new { size = space.usedSize, catalog = space.catalog, teach=space. teach , surplus = space.surplus, total=space.total });
  255. }
  256. private static (string, string) BlobUrlString(string sasUrl)
  257. {
  258. sasUrl = sasUrl.Substring(8);
  259. string[] sasUrls = sasUrl.Split("/");
  260. string ContainerName;
  261. ContainerName = sasUrls[1].Clone().ToString();
  262. string item = sasUrls[0] + "/" + sasUrls[1] + "/";
  263. string blob = sasUrl.Replace(item, "");
  264. return (ContainerName, blob);
  265. }
  266. public static bool IsBlobName(string BlobName)
  267. {
  268. return System.Text.RegularExpressions.Regex.IsMatch(BlobName,
  269. @"(?!((^(con)$)|^(con)\\..*|(^(prn)$)|^(prn)\\..*|(^(aux)$)|^(aux)\\..*|(^(nul)$)|^(nul)\\..*|(^(com)[1-9]$)|^(com)[1-9]\\..*|(^(lpt)[1-9]$)|^(lpt)[1-9]\\..*)|^\\s+|.*\\s$)(^[^\\\\\\:\\<\\>\\*\\?\\\\\\""\\\\|]{1,255}$)");
  270. }
  271. /// <summary>
  272. ///
  273. /// </summary>
  274. /// <param name="request"></param>
  275. /// <returns></returns>
  276. [HttpPost("bloblog-list")]
  277. [AuthToken(Roles = "teacher,admin,student")]
  278. [Authorize(Roles = "IES")]
  279. public async Task<ActionResult> BloblogList(JsonElement request)
  280. {
  281. List<Bloblog> bloblogs = new List<Bloblog>();
  282. try
  283. {
  284. request.TryGetProperty("name", out JsonElement name);
  285. request.TryGetProperty("type", out JsonElement type);
  286. request.TryGetProperty("scope", out JsonElement scope);
  287. request.TryGetProperty("periodId", out JsonElement periodId);
  288. request.TryGetProperty("ext", out JsonElement _ext);
  289. var client = _azureCosmos.GetCosmosClient();
  290. if (scope.GetString().Equals("school"))
  291. {
  292. var queryslt = new StringBuilder($"SELECT value(c) FROM c WHERE c.type='{type}' ");
  293. if (!string.IsNullOrEmpty($"{periodId}")) {
  294. queryslt = new StringBuilder($"SELECT value(c) FROM c join A1 in c.periodId WHERE c.type='{type}' and A1 in ('{periodId}') ");
  295. }
  296. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Bloblog>(queryText: queryslt.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
  297. {
  298. bloblogs.Add(item);
  299. }
  300. }
  301. else if (scope.GetString().Equals("private"))
  302. {
  303. var queryslt = new StringBuilder($"SELECT value(c) FROM c WHERE c.type='{type}' ");
  304. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<Bloblog>(queryText: queryslt.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
  305. {
  306. bloblogs.Add(item);
  307. }
  308. }
  309. bloblogs.ForEach(x => {
  310. string ext = x.type;
  311. if (x.url.LastIndexOf(".") > 0)
  312. {
  313. ext = x.url.Substring(x.url.LastIndexOf(".") > 0 ? x.url.LastIndexOf(".") : 0);
  314. ext = ext.Replace(".","").ToLower();
  315. }
  316. x.ext = ext;
  317. });
  318. if (!string.IsNullOrWhiteSpace($"{_ext}")) {
  319. bloblogs= bloblogs.Where(x => x.ext.Equals($"{_ext}", StringComparison.OrdinalIgnoreCase)).ToList();
  320. }
  321. return Ok(new { bloblogs = bloblogs });
  322. }
  323. catch (Exception ex)
  324. {
  325. return Ok(new { bloblogs = bloblogs });
  326. }
  327. }
  328. /// <summary>
  329. /// 重命名
  330. /// {"scope":"school","cntr":"hbcn","id":"bbf24ca7-487e-4196-8c99-b2c418a2d1b1","newName":"video/核能1.mp4"}
  331. /// </summary>
  332. /// <param name="json"></param>
  333. /// <returns></returns>
  334. [HttpPost("bloblog-rename")]
  335. [AuthToken(Roles = "teacher,admin,student")]
  336. [Authorize(Roles = "IES")]
  337. public async Task<ActionResult> BloblogRename(JsonElement request)
  338. {
  339. var client = _azureCosmos.GetCosmosClient();
  340. if (!request.TryGetProperty("scope", out JsonElement _scope)) return BadRequest();
  341. if(!request.TryGetProperty("cntr", out JsonElement _cntr))return BadRequest();
  342. if (!request.TryGetProperty("id", out JsonElement _id))return BadRequest();
  343. if (!request.TryGetProperty("newName", out JsonElement _newName))return BadRequest();
  344. string tbname= $"{_scope}".Equals("school",StringComparison.OrdinalIgnoreCase) ? "School": "Teacher";
  345. try
  346. {
  347. Bloblog bloblog = await client.GetContainer(Constant.TEAMModelOS, tbname).ReadItemAsync<Bloblog>($"{_id}", new PartitionKey($"Bloblog-{_cntr}"));
  348. string oldUrl = bloblog.url;
  349. if (oldUrl.Equals($"{_newName}")) {
  350. return Ok();
  351. }
  352. if (oldUrl.StartsWith("res", StringComparison.OrdinalIgnoreCase) && oldUrl.EndsWith(".htex", StringComparison.OrdinalIgnoreCase)) {
  353. oldUrl = oldUrl.Replace(".htex", "", StringComparison.OrdinalIgnoreCase);
  354. }
  355. string newName = $"{_newName}";
  356. if (newName.StartsWith("res", StringComparison.OrdinalIgnoreCase) && newName.EndsWith(".htex", StringComparison.OrdinalIgnoreCase))
  357. {
  358. newName = newName.Replace(".htex", "", StringComparison.OrdinalIgnoreCase);
  359. }
  360. bloblog.name = $"{_newName}";
  361. bloblog.url = $"{_newName}";
  362. var bcc = _azureStorage.GetBlobContainerClient($"{_cntr}");
  363. string px = oldUrl;
  364. if (oldUrl.StartsWith("/"))
  365. {
  366. px = oldUrl.Substring(1);
  367. }
  368. List<BlobItem> blobItems = new List<BlobItem>();
  369. await foreach (var item in bcc.GetBlobsAsync(BlobTraits.None, BlobStates.None, px)) {
  370. blobItems.Add(item);
  371. }
  372. foreach (var item in blobItems)
  373. {
  374. if (item.Name.StartsWith("image") || item.Name.StartsWith("video"))
  375. {
  376. var thum = $"thum{ item.Name.Substring(5)}";
  377. var tnewName = $"thum{ newName.Substring(5)}";
  378. var tpx = $"thum{ px.Substring(5)}";
  379. string tname = thum.Replace(tpx, tnewName);
  380. if (item.Name.StartsWith("video")) {
  381. string fileexturl = thum.Substring(thum.LastIndexOf(".") > 0 ? thum.LastIndexOf(".") : 0);
  382. thum = thum.Replace(fileexturl, ".png");
  383. string fileextname = tname.Substring(tname.LastIndexOf(".") > 0 ? tname.LastIndexOf(".") : 0);
  384. tname = tname.Replace(fileextname, ".png");
  385. }
  386. var turl = _azureStorage.GetBlobSAS($"{_cntr}", thum, BlobSasPermissions.Read | BlobSasPermissions.List);
  387. try
  388. {
  389. bcc.GetBlobClient(tname).SyncCopyFromUri(new Uri(turl));
  390. await _azureStorage.GetBlobServiceClient().DeleteBlobs(_dingDing, $"{_cntr}", new List<string> { thum });
  391. }
  392. catch (Exception)
  393. {
  394. continue;
  395. }
  396. }
  397. string targetName = item.Name.Replace(px, newName);
  398. var url = _azureStorage.GetBlobSAS($"{_cntr}", item.Name, BlobSasPermissions.Read | BlobSasPermissions.List);
  399. bcc.GetBlobClient(targetName).SyncCopyFromUri(new Uri(url));
  400. };
  401. await _azureStorage.GetBlobServiceClient().DeleteBlobs(_dingDing, $"{_cntr}", new List<string> { px });
  402. await client.GetContainer(Constant.TEAMModelOS, tbname).ReplaceItemAsync<Bloblog>(bloblog, $"{_id}", new PartitionKey($"Bloblog-{_cntr}"));
  403. string u = "";
  404. string[] uls = System.Web.HttpUtility.UrlDecode(px, Encoding.UTF8).Split("/");
  405. if (uls != null)
  406. {
  407. u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
  408. }
  409. if (!string.IsNullOrEmpty(u)) {
  410. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = u, name = $"{_cntr}" }, _serviceBus, _configuration, _azureRedis);
  411. }
  412. return Ok(new { status = true });
  413. }
  414. catch (CosmosException ex)
  415. {
  416. return BadRequest();
  417. }
  418. catch (Exception ex )
  419. {
  420. return BadRequest();
  421. }
  422. }
  423. /*
  424. 新增 编辑接口
  425. {
  426. "periodId": "",
  427. "scope": "school",
  428. "name": "hbcn",
  429. "url": "video/xxx.png",
  430. "opt": "add",
  431. }
  432. */
  433. /*
  434. {
  435. "scope": "school",
  436. "name": "hbcn",
  437. "opt": "del",
  438. "id": "19ccce98-c524-4ea7-aabc-887d1391e551"
  439. }
  440. */
  441. /// <summary>
  442. ///
  443. /// </summary>
  444. /// <param name="request"></param>
  445. /// <returns></returns>
  446. [HttpPost("bloblog-upsert")]
  447. [AuthToken(Roles = "teacher,admin,student")]
  448. [Authorize(Roles = "IES")]
  449. public async Task<ActionResult> BloblogOpt(JsonElement request) {
  450. try
  451. {
  452. request.TryGetProperty("periodId", out JsonElement periodId);
  453. request.TryGetProperty("subjectId", out JsonElement subjectId);
  454. request.TryGetProperty("gradeId", out JsonElement gradeId);
  455. request.TryGetProperty("scope", out JsonElement scope);
  456. request.TryGetProperty("name", out JsonElement name);
  457. request.TryGetProperty("url", out JsonElement jurls);
  458. //request.TryGetProperty("id", out JsonElement ids);
  459. //获取文件的大小
  460. var client = _azureCosmos.GetCosmosClient();
  461. List<string> urls = jurls.ToObject<List<string>>();
  462. HashSet<string> root = new HashSet<string>();
  463. List<Bloblog> bloblog = new List<Bloblog>();
  464. foreach (var uri in urls) {
  465. var url = System.Web.HttpUtility.UrlDecode(uri, Encoding.UTF8);
  466. string[] uls = url.Split("/");
  467. var u = "";
  468. if (uls != null)
  469. {
  470. u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
  471. root.Add(u);
  472. }
  473. long? size = 0;
  474. if (url.StartsWith("res/") && url.EndsWith("/index.json"))
  475. {
  476. var prefix= url.Substring(0, url.Length - 11);
  477. size = await _azureStorage.GetBlobContainerClient($"{name}").GetBlobsSize(prefix);
  478. }
  479. else {
  480. size = await _azureStorage.GetBlobContainerClient($"{name}").GetBlobsSize(url);
  481. }
  482. long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
  483. //地址相同的,直接更新
  484. bool exsit = false;
  485. try
  486. {
  487. QueryDefinition queryDefinition = new QueryDefinition("SELECT value(c) FROM c WHERE c.url=@url");
  488. queryDefinition.WithParameter("@url", url);
  489. if (scope.GetString().Equals("school"))
  490. {
  491. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<Bloblog>(queryDefinition: queryDefinition, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
  492. {
  493. item.time = now;
  494. item.size = size != null && size.HasValue ? size.Value : 0;
  495. item.periodId = periodId.ValueKind.Equals(JsonValueKind.Array) ? periodId.ToObject<List<string>>() : new List<string> { "" };
  496. item.subjectId = subjectId.ValueKind.Equals(JsonValueKind.Array) ? subjectId.ToObject<List<string>>() : new List<string> { "" };
  497. item.gradeId = gradeId.ValueKind.Equals(JsonValueKind.Array) ? gradeId.ToObject<List<string>>() : new List<string> { "" };
  498. await client.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<Bloblog>(item, item.id, new Azure.Cosmos.PartitionKey(item.code));
  499. bloblog.Add(item);
  500. exsit = true;
  501. }
  502. }
  503. else if (scope.GetString().Equals("private"))
  504. {
  505. await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "Teacher").GetItemQueryIterator<Bloblog>(queryDefinition: queryDefinition, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Bloblog-{name}") }))
  506. {
  507. item.time = now;
  508. item.size = size != null && size.HasValue ? size.Value : 0;
  509. item.periodId = periodId.ValueKind.Equals(JsonValueKind.Array) ? periodId.ToObject<List<string>>() : new List<string> { "" };
  510. item.subjectId = subjectId.ValueKind.Equals(JsonValueKind.Array) ? subjectId.ToObject<List<string>>() : new List<string> { "" };
  511. item.gradeId = gradeId.ValueKind.Equals(JsonValueKind.Array) ? gradeId.ToObject<List<string>>() : new List<string> { "" };
  512. await client.GetContainer(Constant.TEAMModelOS, "Teacher").ReplaceItemAsync<Bloblog>(item, item.id, new Azure.Cosmos.PartitionKey(item.code));
  513. bloblog.Add(item);
  514. exsit = true;
  515. }
  516. }
  517. }
  518. catch (Exception ex)
  519. {
  520. await _dingDing.SendBotMsg($"IES5,{_option.Location},blob/bloblog-blob()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  521. }
  522. if (!exsit)
  523. {
  524. string ext = u;
  525. if (url.LastIndexOf(".") > 0) {
  526. ext = url.Substring(url.LastIndexOf(".") > 0 ? url.LastIndexOf(".") : 0);
  527. ext= ext.ToLower();
  528. }
  529. var blob = new Bloblog
  530. {
  531. id = Guid.NewGuid().ToString(),
  532. pk = "Bloblog",
  533. code = $"Bloblog-{name}",
  534. url = url,
  535. time = now,
  536. size = size != null && size.HasValue ? size.Value : 0,
  537. periodId = periodId.ValueKind.Equals(JsonValueKind.Array) ? periodId.ToObject<List<string>>() : new List<string> { "" },
  538. subjectId = subjectId.ValueKind.Equals(JsonValueKind.Array) ? subjectId.ToObject<List<string>>() : new List<string> { "" },
  539. gradeId = gradeId.ValueKind.Equals(JsonValueKind.Array) ? gradeId.ToObject<List<string>>() : new List<string> { "" },
  540. type = u,
  541. ext= ext
  542. };
  543. if (scope.GetString().Equals("school"))
  544. {
  545. await client.GetContainer(Constant.TEAMModelOS, "School").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
  546. }
  547. else if (scope.GetString().Equals("private"))
  548. {
  549. await client.GetContainer(Constant.TEAMModelOS, "Teacher").CreateItemAsync(blob, new Azure.Cosmos.PartitionKey(blob.code));
  550. }
  551. bloblog.Add(blob);
  552. }
  553. }
  554. root.ToList().ForEach(async x => {
  555. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = x, name = $"{name}" }, _serviceBus, _configuration, _azureRedis);
  556. });
  557. return Ok(new { bloblog, status = 200 });
  558. }
  559. catch (Exception ex)
  560. {
  561. await _dingDing.SendBotMsg($"IES5,{_option.Location},blob/bloblog-blob()\n{ex.Message}\n{ex.StackTrace}\n{request.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
  562. }
  563. return Ok(new { status = 200 });
  564. }
  565. /// <summary>
  566. /// 删除prefix 不管是内容 模块还是其他试题试卷 评测 问卷投票等都在使用。
  567. ///
  568. /// {"cntr":"","prefix":"res/test"}
  569. /// </summary>
  570. /// <param name="request"></param>
  571. /// <returns></returns>
  572. [HttpPost("delete-prefix-student")]
  573. [Authorize(Roles = "IES")]
  574. [AuthToken(Roles = "teacher,student")]
  575. public async Task<IActionResult> DeletePrefixStudent(JsonElement json)
  576. {
  577. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  578. string blobContainerName = null;
  579. string prefix = null;
  580. if (json.TryGetProperty("cntr", out JsonElement cntr) && cntr.ValueKind.Equals(JsonValueKind.String))
  581. {
  582. var cntrs = cntr.GetString();
  583. blobContainerName = $"{cntrs}";
  584. }
  585. if (json.TryGetProperty("prefix", out JsonElement prefixjson) && prefixjson.ValueKind.Equals(JsonValueKind.String))
  586. {
  587. prefix = prefixjson.GetString();
  588. }
  589. if (!prefix.Contains(id)) {
  590. return Ok(new { error = 400, msg = "学生端操作删除文件只能删除自己所在的文件夹或文件。" });
  591. }
  592. if (prefix != null && blobContainerName != null)
  593. {
  594. var status = await _azureStorage.GetBlobServiceClient().DeleteBlobs(_dingDing, blobContainerName, new List<string> { prefix });
  595. string u = "";
  596. string[] uls = System.Web.HttpUtility.UrlDecode($"{prefixjson}", Encoding.UTF8).Split("/");
  597. if (uls != null)
  598. {
  599. u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
  600. }
  601. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = u, name = $"{blobContainerName}" }, _serviceBus, _configuration, _azureRedis);
  602. return Ok(new { status });
  603. }
  604. else
  605. {
  606. return Ok(new { error=400,msg="参数错误!" });
  607. }
  608. }
  609. /// <summary>
  610. /// 删除prefix 不管是内容 模块还是其他试题试卷 评测 问卷投票等都在使用。
  611. ///
  612. /// {"cntr":"","prefix":"res/test"}
  613. /// </summary>
  614. /// <param name="request"></param>
  615. /// <returns></returns>
  616. [HttpPost("delete-prefix")]
  617. [Authorize(Roles = "IES")]
  618. [AuthToken(Roles = "teacher,admin")]
  619. public async Task<IActionResult> DeletePrefix(JsonElement json)
  620. {
  621. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  622. string blobContainerName = null;
  623. string prefix = null;
  624. if (json.TryGetProperty("cntr", out JsonElement cntr) && cntr.ValueKind.Equals(JsonValueKind.String))
  625. {
  626. var keyToken = HttpContext.GetAuthTokenKey("area");
  627. var cntrs = cntr.GetString();
  628. if (cntrs.Equals(id))
  629. {
  630. blobContainerName = id;
  631. }
  632. else if (cntrs.Equals(school))
  633. {
  634. blobContainerName = school;
  635. }else if (cntrs.Equals("teammodelos"))
  636. {
  637. blobContainerName = "teammodelos";
  638. }
  639. else
  640. {
  641. if (!string.IsNullOrWhiteSpace(keyToken.area) && keyToken.area.Equals(cntrs))
  642. {
  643. blobContainerName =cntrs;
  644. }
  645. else {
  646. if (json.TryGetProperty("prefix", out JsonElement _prefixjson) && _prefixjson.ValueKind.Equals(JsonValueKind.String))
  647. {
  648. if (_prefixjson.GetString().Contains(id))
  649. {
  650. blobContainerName=cntrs;
  651. }
  652. else { return BadRequest("只能删除本人管理的文件夹"); }
  653. }
  654. else {
  655. return BadRequest("只能删除本人管理的文件夹");
  656. }
  657. }
  658. }
  659. }
  660. if (json.TryGetProperty("prefix", out JsonElement prefixjson) && prefixjson.ValueKind.Equals(JsonValueKind.String))
  661. {
  662. prefix = prefixjson.GetString();
  663. }
  664. if (prefix != null && blobContainerName != null)
  665. {
  666. var status = await _azureStorage.GetBlobServiceClient().DeleteBlobs(_dingDing, blobContainerName, new List<string> { prefix });
  667. string u = "";
  668. string[] uls = System.Web.HttpUtility.UrlDecode($"{prefixjson}", Encoding.UTF8).Split("/");
  669. if (uls != null)
  670. {
  671. u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
  672. }
  673. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = u, name = $"{blobContainerName}" }, _serviceBus, _configuration, _azureRedis);
  674. return Ok(new { status });
  675. }
  676. else
  677. {
  678. return BadRequest();
  679. }
  680. }
  681. public record ContBlob {
  682. public string path { get; set; }
  683. public string id { get; set; }
  684. }
  685. /// <summary>
  686. /// 删除多个Url,只会在内容模块使用该接口
  687. ///
  688. ///
  689. /// {"scope":"school","cntr":"hbcn","blobs":[{"path":"other/test/1.json","id":"c107069d-4553-46c2-8c81-b3e6b4599393"},{"path":"res/test","id":"09d59b87-68c0-45fa-8221-9931a4190a2f"}]}
  690. /// </summary>
  691. /// <param name="request"></param>
  692. /// <returns></returns>
  693. [HttpPost("bloblog-delete")]
  694. [Authorize(Roles = "IES")]
  695. [AuthToken(Roles = "teacher,admin")]
  696. public async Task<IActionResult> DeleteBlobs(JsonElement json)
  697. {
  698. ///BlobBaseClient copy
  699. try {
  700. var (id, _, _, school) = HttpContext.GetAuthTokenInfo();
  701. string blobContainerName = null;
  702. if (!json.TryGetProperty("scope", out JsonElement _scope)) return BadRequest();
  703. if (!json.TryGetProperty("blobs", out JsonElement _blobs)) return BadRequest();
  704. if (json.TryGetProperty("cntr", out JsonElement cntr))
  705. {
  706. var cntrs = cntr.ToString();
  707. if (cntrs.Equals(id))
  708. {
  709. blobContainerName = id;
  710. }
  711. else if (cntrs.Equals(school))
  712. {
  713. blobContainerName = school;
  714. }
  715. else
  716. {
  717. return BadRequest("只能删除本人管理的文件");
  718. }
  719. }
  720. bool flag = true;
  721. List<ContBlob> blobs = _blobs.ToObject<List<ContBlob>>();
  722. try
  723. {
  724. var client = _azureCosmos.GetCosmosClient();
  725. List<string> ids = blobs.Where(b=>!string.IsNullOrWhiteSpace(b.id)).Select(x => x.id).ToList();
  726. string containerId = "School";
  727. if (_scope.GetString().Equals("school"))
  728. {
  729. containerId = "School";
  730. }
  731. else if (_scope.GetString().Equals("private"))
  732. {
  733. containerId = "Teacher";
  734. }
  735. await client.GetContainer(Constant.TEAMModelOS, containerId).DeleteItemsAsync<Bloblog>(ids, $"Bloblog-{blobContainerName}");
  736. }
  737. catch (CosmosException ex )
  738. {
  739. //仅处理 cosmos不存在 但容器又存在的
  740. }
  741. if (flag)
  742. {
  743. var urls = blobs.Select(x => x.path).ToList();
  744. List<string> deleteUrl = new List<string>();
  745. urls.ForEach(x => {
  746. string delUrl = x;
  747. if (x.StartsWith("res/") && x.EndsWith(".HTEX", StringComparison.OrdinalIgnoreCase))
  748. {
  749. delUrl = x.Substring(0, x.Length - 4);
  750. }
  751. if (x.StartsWith("res/") && x.EndsWith("/index.json", StringComparison.OrdinalIgnoreCase))
  752. {
  753. delUrl = x.Substring(0, x.Length -11);
  754. }
  755. //自动删除视频和图片的缩略图
  756. if (x.StartsWith("image")||x.StartsWith("video")) {
  757. var thum = $"thum{ x.Substring(5)}" ;
  758. if (x.StartsWith("video"))
  759. {
  760. string fileexturl = thum.Substring(thum.LastIndexOf(".") > 0 ? thum.LastIndexOf(".") : 0);
  761. thum = thum.Replace(fileexturl, ".png");
  762. }
  763. deleteUrl.Add(thum);
  764. }
  765. deleteUrl.Add(delUrl);
  766. });
  767. var status = await _azureStorage.GetBlobServiceClient().DeleteBlobs(_dingDing, blobContainerName, deleteUrl);
  768. //释放的空间
  769. HashSet<string> root = new HashSet<string>();
  770. foreach (var x in deleteUrl)
  771. {
  772. string url = System.Web.HttpUtility.UrlDecode(x, Encoding.UTF8);
  773. string[] uls = url.Split("/");
  774. if (uls != null)
  775. {
  776. string u = !string.IsNullOrEmpty(uls[0]) ? uls[0] : uls[1];
  777. root.Add(u);
  778. }
  779. }
  780. root.ToList().ForEach(async x => {
  781. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = x, name = $"{blobContainerName}" }, _serviceBus, _configuration, _azureRedis);
  782. });
  783. return Ok(new { status });
  784. }
  785. else
  786. {
  787. return BadRequest("只能删除本人管理的文件");
  788. }
  789. } catch (Exception ex) {
  790. await _dingDing.SendBotMsg($"IES5,{_option.Location},blob/delete-blobs\n{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
  791. return BadRequest();
  792. }
  793. }
  794. /// <summary>
  795. /// 列出blob的
  796. ///
  797. /// {"cntr":"","urls":["res/test/1.json","res/test/2.json"]}
  798. /// {"scope":"school","cntr":"hbcn","blobs":[{"path":"other/test/1.json","id":"c107069d-4553-46c2-8c81-b3e6b4599393"},{"path":"res/test","id":"09d59b87-68c0-45fa-8221-9931a4190a2f"}]}
  799. /// </summary>
  800. /// <param name="request"></param>
  801. /// <returns></returns>
  802. [HttpPost("blob-list")]
  803. [Authorize(Roles = "IES")]
  804. [AuthToken(Roles = "teacher,admin,student")]
  805. public async Task<IActionResult> BlobList(JsonElement json) {
  806. var (userid, _, _, school) = HttpContext.GetAuthTokenInfo();
  807. List<string> paths = new List<string>();
  808. //文件的容器
  809. if (!json.TryGetProperty("cntr", out JsonElement _cntr)) return BadRequest();
  810. //业务存取类型 exam,vote,survey,item,paper,syllabus,records,avatar,content(doc,image,res,video,audio,other),thum,train,temp,jyzx
  811. if (!json.TryGetProperty("type", out JsonElement _type)) return BadRequest();
  812. //文件前缀prefix
  813. switch (true)
  814. {
  815. case bool when $"{_type}".Equals("exam", StringComparison.OrdinalIgnoreCase)
  816. || $"{_type}".Equals("vote", StringComparison.OrdinalIgnoreCase)
  817. || $"{_type}".Equals("survey", StringComparison.OrdinalIgnoreCase)
  818. || $"{_type}".Equals("item", StringComparison.OrdinalIgnoreCase)
  819. || $"{_type}".Equals("paper", StringComparison.OrdinalIgnoreCase):
  820. string type = $"{_type}".Substring(0, 1).ToUpper() + $"{_type}".Substring(1);
  821. //业务存取id
  822. if (!json.TryGetProperty("id", out JsonElement _aid)) return BadRequest();
  823. //业务存取分区键
  824. if (!json.TryGetProperty("code", out JsonElement _acode)) return BadRequest();
  825. //业务存取分区键
  826. if (!json.TryGetProperty("scope", out JsonElement _ascope)) return BadRequest();
  827. break;
  828. case bool when $"{_type}".Equals("doc", StringComparison.OrdinalIgnoreCase)
  829. || $"{_type}".Equals("image", StringComparison.OrdinalIgnoreCase)
  830. || $"{_type}".Equals("res", StringComparison.OrdinalIgnoreCase)
  831. || $"{_type}".Equals("video", StringComparison.OrdinalIgnoreCase)
  832. || $"{_type}".Equals("audio", StringComparison.OrdinalIgnoreCase)
  833. || $"{_type}".Equals("other", StringComparison.OrdinalIgnoreCase):
  834. //业务存取id
  835. if (!json.TryGetProperty("id", out JsonElement _bid)) return BadRequest();
  836. //业务存取分区键
  837. if (!json.TryGetProperty("code", out JsonElement _bcode)) return BadRequest();
  838. //业务存取分区键
  839. if (!json.TryGetProperty("scope", out JsonElement _bscope)) return BadRequest();
  840. break;
  841. case bool when $"{_type}".Equals("records", StringComparison.OrdinalIgnoreCase)
  842. || $"{_type}".Equals("syllabus", StringComparison.OrdinalIgnoreCase)
  843. || $"{_type}".Equals("thum", StringComparison.OrdinalIgnoreCase)
  844. || $"{_type}".Equals("temp", StringComparison.OrdinalIgnoreCase)
  845. || $"{_type}".Equals("jyzx", StringComparison.OrdinalIgnoreCase)
  846. || $"{_type}".Equals("avatar", StringComparison.OrdinalIgnoreCase)
  847. || $"{_type}".Equals("train", StringComparison.OrdinalIgnoreCase):
  848. break;
  849. default:
  850. break;
  851. }
  852. return Ok(new { paths ,status=1});
  853. }
  854. [ProducesDefaultResponseType]
  855. [Authorize(Roles = "IES")]
  856. [AuthToken(Roles = "teacher,admin")]
  857. [HttpPost("delete-unlink")]
  858. public async Task<IActionResult> DeleteUnlink(JsonElement json) {
  859. try {
  860. var (userid, _, _, school) = HttpContext.GetAuthTokenInfo();
  861. int status = 0;
  862. if (!json.TryGetProperty("scope", out JsonElement scope))
  863. {
  864. return Ok(new { status, msg = "参数错误" });
  865. }
  866. string containerName = scope.ToString().Equals("school", StringComparison.OrdinalIgnoreCase) ? school : userid;
  867. bool exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync($"Blob:ScanResult:{scope}:{containerName}");
  868. if (exists)
  869. {
  870. var result = await _azureRedis.GetRedisClient(8).StringGetAsync($"Blob:ScanResult:{scope}:{containerName}");
  871. if (result.HasValue)
  872. {
  873. List<UnLink> unLinksData = result.ToString().ToObject<List<UnLink>>();
  874. if (unLinksData.IsNotEmpty())
  875. {
  876. var uri = _azureStorage.GetBlobContainerClient(containerName).Uri;
  877. BlobBatchClient blobBatch = _azureStorage.GetBlobContainerClient(containerName).GetBlobBatchClient();
  878. foreach (var unLink in unLinksData)
  879. {
  880. var urls = unLink.blobs.Select(z => z.Key).Select(z => new Uri(Path.Combine(uri.ToString(), z)));
  881. int len = 100;
  882. if (urls.Count() > 0)
  883. {
  884. if (urls.Count() <= len)
  885. {
  886. try
  887. {
  888. await blobBatch.DeleteBlobsAsync(urls);
  889. }
  890. catch (Exception ex) { }
  891. }
  892. else
  893. {
  894. int pages = (urls.Count() + len) / len; //256是批量操作最大值,pages = (total + max -1) / max;
  895. for (int i = 0; i < pages; i++)
  896. {
  897. List<Uri> lists = urls.Skip((i) * len).Take(len).ToList();
  898. try
  899. {
  900. await blobBatch.DeleteBlobsAsync(lists);
  901. }
  902. catch (Exception ex) { }
  903. }
  904. }
  905. }
  906. }
  907. //为节省服务器开销, 限制只能一天清理一次
  908. #if DEBUG
  909. _azureRedis.GetRedisClient(8).StringSet($"Blob:ScanResult:{scope}:{containerName}", new List<UnLink>().ToJsonString(), expiry: new TimeSpan(0, 0, 30));
  910. #else
  911. _azureRedis.GetRedisClient(8).StringSet($"Blob:ScanResult:{scope}:{containerName}", new List<UnLink>().ToJsonString(), expiry: new TimeSpan(24, 0, 0));
  912. #endif
  913. HashSet<string> root = null;
  914. if (_option.Location.Contains("Test", StringComparison.OrdinalIgnoreCase) || _option.Location.Contains("Dep", StringComparison.OrdinalIgnoreCase))
  915. {
  916. root = unLinksData.Select(x => x.prefix).ToHashSet();
  917. }
  918. else
  919. {
  920. root = unLinksData.Where(z => z.size > 0).Select(x => x.prefix).ToHashSet();
  921. }
  922. if (root != null)
  923. {
  924. root.ToList().ForEach(async x => {
  925. await BlobService.RefreshBlobRoot(new BlobRefreshMessage { progress = "update", root = x, name = $"{containerName}" }, _serviceBus, _configuration, _azureRedis);
  926. });
  927. }
  928. return Ok(new { status = 3, msg = "清理成功!" });
  929. }
  930. else
  931. {
  932. return Ok(new { status = 2, msg = "最近清理过,暂无清理项!" });
  933. }
  934. }
  935. else
  936. {
  937. return Ok(new { status = 2, msg = "最近清理过,暂无清理项!" });
  938. }
  939. }
  940. else
  941. {
  942. return Ok(new { status = 4, msg = "请重新检查清理项!" });
  943. }
  944. } catch (Exception ex ) {
  945. await _dingDing.SendBotMsg($"{_option.Location},{DeleteUnlink}\n{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
  946. }
  947. return Ok(new { status = 4, msg = "请重新检查清理项!" });
  948. }
  949. [ProducesDefaultResponseType]
  950. [Authorize(Roles = "IES")]
  951. [AuthToken(Roles = "teacher,admin")]
  952. [HttpPost("scan-unlink")]
  953. public async Task<IActionResult> ScanUnlink(JsonElement json)
  954. {
  955. var (userid, _, _, school) = HttpContext.GetAuthTokenInfo();
  956. int status = 0;
  957. if ( !json.TryGetProperty("scope", out JsonElement scope))
  958. {
  959. return Ok(new { status, msg = "参数错误" });
  960. }
  961. string containerName = scope.ToString().Equals("school", StringComparison.OrdinalIgnoreCase) ? school : userid;
  962. if (string.IsNullOrWhiteSpace(containerName)) {
  963. return Ok(new { status, msg = "参数错误" });
  964. }
  965. bool exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync($"Blob:ScanResult:{scope}:{containerName}");
  966. if (exists)
  967. {
  968. var result = await _azureRedis.GetRedisClient(8).StringGetAsync($"Blob:ScanResult:{scope}:{containerName}");
  969. if (result.HasValue)
  970. {
  971. List<UnLink> unLinksData = result.ToString().ToObject<List<UnLink>>();
  972. if (unLinksData.IsNotEmpty())
  973. {
  974. var groupData = unLinksData.GroupBy(x => x.prefix).ToList();
  975. List<dynamic> summaryData = new List<dynamic>();
  976. long totalCountData = 0;
  977. long? totalSizeData = 0;
  978. groupData.ForEach(x =>
  979. {
  980. long count = x.ToList().SelectMany(z => z.blobs).Count();
  981. long? size = x.Select(y => y.size).Sum();
  982. totalSizeData += size;
  983. totalCountData += count;
  984. summaryData.Add(new { prefix = x.Key, count = count, size = size });
  985. });
  986. //为节省服务器开销, 限制只能一天清理一次
  987. return Ok(new { status = 1, totalCount = totalCountData, totalSize = totalSizeData, summary = summaryData, unLinks = unLinksData });
  988. }
  989. else {
  990. return Ok(new { status = 2, msg = "最近清理过,暂无清理项!" });
  991. }
  992. }
  993. else {
  994. return Ok(new { status = 2, msg = "最近清理过,暂无清理项!" });
  995. }
  996. }
  997. List<UnLink> unLinks = new List<UnLink>();
  998. //文件名(关联路径)导向的文件夹 内容(audio doc image video other thum 缩略图) 学生头像(avatar)
  999. string[] prefixFile = new string[] { "audio", "doc", "image", "video", "other", "thum", "avatar" };
  1000. foreach (string prefix in prefixFile)
  1001. {
  1002. string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
  1003. switch (true)
  1004. {
  1005. //Bloblog
  1006. case bool when prefix.Equals("doc", StringComparison.OrdinalIgnoreCase)
  1007. || prefix.Equals("image", StringComparison.OrdinalIgnoreCase)
  1008. || prefix.Equals("video", StringComparison.OrdinalIgnoreCase)
  1009. || prefix.Equals("audio", StringComparison.OrdinalIgnoreCase)
  1010. || prefix.Equals("other", StringComparison.OrdinalIgnoreCase):
  1011. {
  1012. // audio/最愛情歌80-89-這些日子以來 (張清芳+范怡文).mp3
  1013. List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
  1014. await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
  1015. {
  1016. blobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
  1017. }
  1018. if (blobs.IsNotEmpty())
  1019. {
  1020. string code = $"Bloblog-{containerName}";
  1021. string sql = $"select value c from c where c.url in ({string.Join(",", blobs.Select(z => $"'{z.Key}'"))})";
  1022. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code);
  1023. if (result.list.IsNotEmpty())
  1024. {
  1025. var urls = result.list.Select(x => x.url).ToHashSet();
  1026. var unlink = blobs.ExceptBy(urls, x => x.Key).ToList();
  1027. long? size = unlink.Select(z => z.Value).Sum();
  1028. unLinks.Add(new UnLink { prefix = prefix, blobs = unlink, size = size });
  1029. }
  1030. else {
  1031. long? size = blobs.Select(z => z.Value).Sum();
  1032. unLinks.Add(new UnLink { prefix = prefix, blobs = blobs, size = size });
  1033. }
  1034. }
  1035. break;
  1036. }
  1037. case bool when prefix.Equals("avatar", StringComparison.OrdinalIgnoreCase) && scope.ToString().Equals("school"):
  1038. {
  1039. List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
  1040. await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
  1041. {
  1042. blobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
  1043. }
  1044. if (blobs.IsNotEmpty())
  1045. {
  1046. string code = $"Base-{containerName}";
  1047. List<KeyValuePair<string, long?>> unlink = new List<KeyValuePair<string, long?>>();
  1048. foreach (var blob in blobs)
  1049. {
  1050. string sql = $"select value c from c where contains(c.picture,'{blob.Key}')";
  1051. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student").GetList<Student>(sql, code, pageSize: 1);
  1052. //找不到对应的数据存储
  1053. if (!result.list.IsNotEmpty())
  1054. {
  1055. unlink.Add(blob);
  1056. }
  1057. }
  1058. long? size = unlink.Select(z => z.Value).Sum();
  1059. unLinks.Add(new UnLink { prefix = prefix, blobs = unlink, size = size });
  1060. }
  1061. break;
  1062. }
  1063. default: break;
  1064. }
  1065. }
  1066. //文件夹(关联id)导向的文件夹 exam homework art records survey vote item
  1067. string[] prefixDirId = new string[] { "exam", "homework", "art", "records", "survey", "vote", "item" };
  1068. foreach (string prefix in prefixDirId)
  1069. {
  1070. string tbname = string.Empty;
  1071. HashSet<string> ids = new HashSet<string>();
  1072. Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
  1073. await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
  1074. {
  1075. var path = blobItem.Name.Split("/");
  1076. if (path.Length > 2)
  1077. {
  1078. string id = path[1];
  1079. ids.Add(id);
  1080. if (recordUrls.ContainsKey(id))
  1081. {
  1082. recordUrls[id].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
  1083. }
  1084. else
  1085. {
  1086. recordUrls[id] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
  1087. }
  1088. }
  1089. if (path.Length == 2)
  1090. {
  1091. unLinks.Add(new UnLink { prefix = prefix, blobs = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) }, size = blobItem.Properties.ContentLength });
  1092. }
  1093. }
  1094. string code = string.Empty;
  1095. if (ids.Any() && ids.Count > 0)
  1096. {
  1097. string sql = $"select value c.id from c where c.id in ({string.Join(",", ids.Select(x => $"'{x}'"))}) ";
  1098. switch (true)
  1099. {
  1100. case bool when prefix.Equals("exam", StringComparison.OrdinalIgnoreCase):
  1101. {
  1102. tbname = Constant.Common;
  1103. code = $"Exam-{containerName}";
  1104. break;
  1105. }
  1106. case bool when prefix.Equals("homework", StringComparison.OrdinalIgnoreCase):
  1107. {
  1108. tbname = Constant.Common;
  1109. code = $"Homework-{containerName}";
  1110. break;
  1111. }
  1112. case bool when prefix.Equals("art", StringComparison.OrdinalIgnoreCase):
  1113. {
  1114. tbname = Constant.Common;
  1115. code = $"Art-{containerName}";
  1116. break;
  1117. }
  1118. case bool when prefix.Equals("survey", StringComparison.OrdinalIgnoreCase):
  1119. {
  1120. tbname = Constant.Common;
  1121. code = $"Survey-{containerName}";
  1122. break;
  1123. }
  1124. case bool when prefix.Equals("vote", StringComparison.OrdinalIgnoreCase):
  1125. {
  1126. tbname = Constant.Common;
  1127. code = $"Vote-{containerName}";
  1128. break;
  1129. }
  1130. case bool when prefix.Equals("item", StringComparison.OrdinalIgnoreCase):
  1131. {
  1132. tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
  1133. code = $"Item-{containerName}";
  1134. break;
  1135. }
  1136. case bool when prefix.Equals("records", StringComparison.OrdinalIgnoreCase):
  1137. {
  1138. tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
  1139. code = scope.ToString().Equals("school") ? $"LessonRecord-{containerName}" : "LessonRecord";
  1140. sql =$"{sql} and c.status<>404 ";
  1141. break;
  1142. }
  1143. }
  1144. IEnumerable<string> unlink = null;
  1145. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
  1146. if (result != null && result.list.Any())
  1147. {
  1148. unlink = ids.Except(result.list);
  1149. }
  1150. else
  1151. {
  1152. unlink = ids;
  1153. }
  1154. var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
  1155. var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
  1156. unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
  1157. }
  1158. }
  1159. //名字导向 试卷(paper)
  1160. {
  1161. string prefix = "paper";
  1162. //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
  1163. HashSet<string> paperNames = new HashSet<string>();
  1164. Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
  1165. await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
  1166. {
  1167. var path = blobItem.Name.Split("/");
  1168. if (path.Length > 2)
  1169. {
  1170. string paperName = path[1];
  1171. paperNames.Add(paperName);
  1172. if (recordUrls.ContainsKey(paperName))
  1173. {
  1174. recordUrls[paperName].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
  1175. }
  1176. else
  1177. {
  1178. recordUrls[paperName] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
  1179. }
  1180. }
  1181. if (path.Length == 2)
  1182. {
  1183. unLinks.Add(new UnLink { prefix = prefix, blobs = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) }, size = blobItem.Properties.ContentLength });
  1184. }
  1185. }
  1186. string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
  1187. string code = $"Paper-{containerName}";
  1188. if (paperNames.Any() && paperNames.Count > 0)
  1189. {
  1190. IEnumerable<string> unlink = null;
  1191. string sql = $"select value c.name from c where c.name in ({string.Join(",", paperNames.Select(x => $"'{x}'"))})";
  1192. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
  1193. if (result != null && result.list.Any())
  1194. {
  1195. unlink = paperNames.Except(result.list);
  1196. }
  1197. else
  1198. {
  1199. unlink = paperNames;
  1200. }
  1201. var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
  1202. var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
  1203. unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
  1204. }
  1205. }
  1206. //htex hte (res)
  1207. {
  1208. string prefix = "res";
  1209. //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
  1210. HashSet<string> htexNames = new HashSet<string>();
  1211. List<KeyValuePair<string, long?>> hteBlobs = new List<KeyValuePair<string, long?>>();
  1212. Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
  1213. await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
  1214. {
  1215. var path = blobItem.Name.Split("/");
  1216. if (path.Length > 2)
  1217. {
  1218. string htexName = path[1];
  1219. htexNames.Add(htexName);
  1220. if (recordUrls.ContainsKey(htexName))
  1221. {
  1222. recordUrls[htexName].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
  1223. }
  1224. else
  1225. {
  1226. recordUrls[htexName] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
  1227. }
  1228. }
  1229. if (path.Length == 2)
  1230. {
  1231. string blobName = "";
  1232. if (blobItem.Name.EndsWith(".htex", StringComparison.OrdinalIgnoreCase))
  1233. {
  1234. blobName = blobItem.Name.Substring(0, blobItem.Name.Length - 5);
  1235. }
  1236. if (blobItem.Name.EndsWith(".hte", StringComparison.OrdinalIgnoreCase))
  1237. {
  1238. blobName = blobItem.Name.Substring(0, blobItem.Name.Length - 4);
  1239. }
  1240. if (!string.IsNullOrWhiteSpace(blobName))
  1241. {
  1242. hteBlobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
  1243. }
  1244. //unLinks.Add(new UnLink { prefix = prefix, blobs = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) }, size = blobItem.Properties.ContentLength });
  1245. }
  1246. }
  1247. string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
  1248. string code = $"Bloblog-{containerName}";
  1249. if (htexNames.Any() && htexNames.Count > 0)
  1250. {
  1251. List<string> unlink = new List<string>();
  1252. foreach (var htexName in htexNames)
  1253. {
  1254. string sql = $"select value c from c where contains(c.url,'{htexName}') ";
  1255. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code, pageSize: 1);
  1256. //找不到对应的数据存储
  1257. if (!result.list.IsNotEmpty())
  1258. {
  1259. unlink.Add(htexName);
  1260. }
  1261. }
  1262. var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
  1263. var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
  1264. unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
  1265. }
  1266. if (hteBlobs.IsNotEmpty())
  1267. {
  1268. List<KeyValuePair<string, long?>> unlink = new List<KeyValuePair<string, long?>>();
  1269. foreach (var hteName in hteBlobs)
  1270. {
  1271. string sql = $"select value c from c where contains(c.url,'{hteName}') ";
  1272. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code, pageSize: 1);
  1273. //找不到对应的数据存储
  1274. if (!result.list.IsNotEmpty())
  1275. {
  1276. unlink.Add(hteName);
  1277. }
  1278. }
  1279. long? size = unlink.Select(z => z.Value).Sum();
  1280. unLinks.Add(new UnLink { prefix = prefix, blobs = unlink, size = size });
  1281. }
  1282. }
  1283. // 课纲(syllabus) 节点id fc4b7ac5-24e7-4eba-b0f2-7eccd007b4cd
  1284. {
  1285. string prefix = "syllabus";
  1286. //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
  1287. HashSet<string> rnodeIds = new HashSet<string>();
  1288. Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
  1289. await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
  1290. {
  1291. var path = blobItem.Name.Split("/");
  1292. if (path.Length > 2)
  1293. {
  1294. string rnodeId = path[1];
  1295. rnodeIds.Add(rnodeId);
  1296. if (recordUrls.ContainsKey(rnodeId))
  1297. {
  1298. recordUrls[rnodeId].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
  1299. }
  1300. else
  1301. {
  1302. recordUrls[rnodeId] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
  1303. }
  1304. }
  1305. if (path.Length == 2)
  1306. {
  1307. unLinks.Add(new UnLink { prefix = prefix, blobs = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) }, size = blobItem.Properties.ContentLength });
  1308. }
  1309. }
  1310. if (rnodeIds.Any() && rnodeIds.Count > 0)
  1311. {
  1312. string code = $"Syllabus-{containerName}";
  1313. string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher; ;
  1314. string sql = $"select distinct value b.id from c join b in c.children where c.pk='Syllabus' and b.id in ({string.Join(",", rnodeIds.Select(x => $"'{x}'"))})";
  1315. IEnumerable<string> unlink = null;
  1316. var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
  1317. if (result != null && result.list.Any())
  1318. {
  1319. unlink = rnodeIds.Except(result.list);
  1320. }
  1321. else
  1322. {
  1323. unlink = rnodeIds;
  1324. }
  1325. var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
  1326. var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
  1327. unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
  1328. }
  1329. }
  1330. var group = unLinks.GroupBy(x => x.prefix).ToList();
  1331. List<dynamic> summary = new List<dynamic>();
  1332. long totalCount = 0;
  1333. long? totalSize = 0;
  1334. group.ForEach(x => {
  1335. long count = x.ToList().SelectMany(z => z.blobs).Count();
  1336. long? size = x.Select(y => y.size).Sum();
  1337. totalSize += size;
  1338. totalCount += count;
  1339. summary.Add(new { prefix = x.Key, count = count, size = size });
  1340. });
  1341. //为节省服务器开销, 限制只能一天清理一次
  1342. #if DEBUG
  1343. _azureRedis.GetRedisClient(8).StringSet($"Blob:ScanResult:{scope}:{containerName}", unLinks.ToJsonString(), expiry: new TimeSpan(0, 0,30));
  1344. #else
  1345. _azureRedis.GetRedisClient(8).StringSet($"Blob:ScanResult:{scope}:{containerName}", unLinks.ToJsonString(), expiry: new TimeSpan(24, 0, 0));
  1346. #endif
  1347. return Ok(new { status = 1, totalCount, totalSize, summary, unLinks });
  1348. }
  1349. }
  1350. }