BlobController.cs 77 KB

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