CrazyIter_Bin %!s(int64=2) %!d(string=hai) anos
pai
achega
42a7edf33c

+ 348 - 3
TEAMModelOS.FunctionV4/HttpTrigger/IESHttpTrigger.cs

@@ -1,7 +1,10 @@
 using Azure.Cosmos;
 using Azure.Cosmos;
 using Azure.Storage.Blobs.Models;
 using Azure.Storage.Blobs.Models;
 using Azure.Storage.Sas;
 using Azure.Storage.Sas;
+using DocumentFormat.OpenXml.Drawing.Wordprocessing;
+using DocumentFormat.OpenXml.Office2013.Excel;
 using DocumentFormat.OpenXml.Spreadsheet;
 using DocumentFormat.OpenXml.Spreadsheet;
+using HTEXLib;
 using HTEXLib.COMM.Helpers;
 using HTEXLib.COMM.Helpers;
 using HTEXLib.Models;
 using HTEXLib.Models;
 using Microsoft.AspNetCore.Components;
 using Microsoft.AspNetCore.Components;
@@ -36,9 +39,9 @@ using TEAMModelOS.SDK.Models;
 using TEAMModelOS.SDK.Models.Service;
 using TEAMModelOS.SDK.Models.Service;
 using TEAMModelOS.SDK.Models.Table;
 using TEAMModelOS.SDK.Models.Table;
 using static TEAMModelOS.SDK.Models.Teacher;
 using static TEAMModelOS.SDK.Models.Teacher;
+using static TEAMModelOS.SDK.Services.ActivityStudentService;
 using static TEAMModelOS.SDK.Services.BlobService;
 using static TEAMModelOS.SDK.Services.BlobService;
-using Path = System.IO.Path;
-using System.Runtime.InteropServices;
+using Path = System.IO.Path; 
 
 
 namespace TEAMModelOS.FunctionV4
 namespace TEAMModelOS.FunctionV4
 {
 {
@@ -65,8 +68,350 @@ namespace TEAMModelOS.FunctionV4
             _option = option?.Value;
             _option = option?.Value;
             _configuration = configuration;
             _configuration = configuration;
         }
         }
+
        
        
-        
+        /// <summary>
+        /// </summary>
+        /// <param name="req"></param>
+        /// <param name="log"></param>
+        /// <returns></returns>
+        [Function("delete-unlinked-resource")]
+
+        public async Task<HttpResponseData> DeleteUnlinkedResource([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequestData req) {
+            var response = req.CreateResponse(HttpStatusCode.OK);
+            string data = await new StreamReader(req.Body).ReadToEndAsync();
+            var json = JsonDocument.Parse(data).RootElement;
+            if (!json.TryGetProperty("containerName", out JsonElement containerName) || !json.TryGetProperty("scope", out JsonElement scope)) {
+                return response;
+            }
+            List<UnLink> unLinks = new List<UnLink>();
+            //文件名(关联路径)导向的文件夹  内容(audio doc image video other  thum 缩略图) 学生头像(avatar)
+            string[] prefixFile = new string[] { "audio","doc", "image" , "video" , "other"  , "thum", "avatar" };
+            foreach (string prefix in prefixFile) {
+                string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                switch (true)
+                {
+                    //Bloblog
+
+                    case bool when prefix.Equals("doc", StringComparison.OrdinalIgnoreCase)
+                        || prefix.Equals("image", StringComparison.OrdinalIgnoreCase)
+                        || prefix.Equals("video", StringComparison.OrdinalIgnoreCase)
+                        || prefix.Equals("audio", StringComparison.OrdinalIgnoreCase)
+                        || prefix.Equals("other", StringComparison.OrdinalIgnoreCase):
+                        {
+                            //   audio/最愛情歌80-89-這些日子以來 (張清芳+范怡文).mp3
+                            List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                            await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                            {
+                                blobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                            }
+                            if (blobs.IsNotEmpty()) {
+                                string code = $"Bloblog-{containerName}";
+                                string sql = $"select value c from c where c.url in ({string.Join(",", blobs.Select(z => $"'{z.Key}'"))})";
+                                var result =await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code);
+                                if (result.list.IsNotEmpty()) {
+                                    var urls=  result.list.Select(x => x.url).ToHashSet();
+                                    var unlink  =  blobs.ExceptBy(urls, x => x.Key).ToList();
+                                    long?  size = unlink.Select(z => z.Value).Sum();
+                                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlink , size= size });
+                                }
+                            }
+                            break;
+                        }
+                    case bool when prefix.Equals("avatar", StringComparison.OrdinalIgnoreCase) && scope.ToString().Equals("school"):
+                        {
+                            List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                            await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                            {
+                                blobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                            }
+                            if (blobs.IsNotEmpty()) {
+                                string code = $"Base-{containerName}";
+                                List<KeyValuePair<string, long?>> unlink = new List<KeyValuePair<string, long?>>();
+                                foreach (var blob in blobs) {
+                                    string sql = $"select value c from c  where contains(c.picture,'{blob.Key}')";
+                                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code,pageSize:1);
+                                    //找不到对应的数据存储
+                                    if (!result.list.IsNotEmpty())
+                                    {
+                                        unlink.Add(blob);
+                                    }
+                                }
+                                long? size = unlink.Select(z => z.Value).Sum();
+                                unLinks.Add(new UnLink { prefix = prefix, blobs = unlink, size = size });
+                            }
+                            break; 
+                        }
+                    default:break;
+                       
+                }
+            }
+            //文件夹(关联id)导向的文件夹 exam  homework art records  survey  vote  item
+            string[] prefixDirId = new string[] { "exam"  , "homework" , "art" , "records"  , "survey"  , "vote"  ,"item" };
+            foreach (string prefix in prefixDirId) {
+                string tbname = string.Empty;
+                HashSet<string> ids = new HashSet<string>();
+                Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
+                await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                {
+                    var path = blobItem.Name.Split("/");
+                    if (path.Length >3)
+                    {
+                        string id = path[1];
+                        ids.Add(id);
+                        if (recordUrls.ContainsKey(id))
+                        {
+                            recordUrls[id].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+                        else
+                        {
+                            recordUrls[id] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
+                        }
+                    }
+                    if (path.Length == 3) {
+                        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 });
+                    }
+                }
+                string code=string.Empty;
+                if (ids.Any() && ids.Count>0) {
+                    string sql = $"select value c.id  from c where   c.id in ({string.Join(",", ids.Select(x => $"'{x}'"))})";
+                    switch (true)
+                    {
+                        case bool when prefix.Equals("exam", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = Constant.Common;
+                                code = $"Exam-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("homework", StringComparison.OrdinalIgnoreCase) :
+                            {
+                                tbname = Constant.Common;
+                                code = $"Homework-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("art", StringComparison.OrdinalIgnoreCase) :
+                            {
+                                tbname = Constant.Common;
+                                code = $"Art-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("survey", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = Constant.Common;
+                                code = $"Survey-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("vote", StringComparison.OrdinalIgnoreCase) :
+                            {
+                                tbname = Constant.Common;
+                                code = $"Vote-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("item", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                                code = $"Item-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("records", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                                code = scope.ToString().Equals("school") ? $"LessonRecord-{containerName}" : "LessonRecord";
+                                break;
+                            }
+                    }
+                    IEnumerable<string> unlink = null;
+                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
+                    if (result != null && result.list.Any())
+                    {
+                        unlink = ids.Except(result.list);
+                    }
+                    else
+                    {
+                        unlink = ids;
+                    }
+                    var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
+                    var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z=>z.Value).Sum() });
+                }
+            }
+            //名字导向 试卷(paper) 
+            {
+                string prefix = "paper";
+                //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                HashSet<string> paperNames  = new HashSet<string>();
+                Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
+                await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                {
+                    var path = blobItem.Name.Split("/");
+                    if (path.Length > 3)
+                    {
+                        string paperName = path[1];
+                        paperNames.Add(paperName);
+                        if (recordUrls.ContainsKey(paperName))
+                        {
+                            recordUrls[paperName].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+                        else
+                        {
+                            recordUrls[paperName] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
+                        }
+                    }
+                    if (path.Length == 3)
+                    {
+                        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 });
+                    }
+                }
+                string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                string code = $"Paper-{containerName}";
+                if (paperNames.Any() && paperNames.Count > 0)
+                {
+                    IEnumerable<string> unlink = null;
+                    string sql = $"select value c from c where   c.name in ({string.Join(",", paperNames.Select(x => $"'{x}'"))})";
+                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
+                    if (result != null && result.list.Any())
+                    {
+                        unlink = paperNames.Except(result.list);
+                    }
+                    else
+                    {
+                        unlink = paperNames;
+                    }
+                    var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
+                    var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
+                }
+            }
+            //htex hte (res)
+            {
+                string prefix = "res";
+               
+                //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                HashSet<string> htexNames = new HashSet<string>();
+                List<KeyValuePair<string, long?>> hteBlobs = new List<KeyValuePair<string, long?>>();
+                Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
+                await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                {
+                    var path = blobItem.Name.Split("/");
+                    if (path.Length > 3)
+                    {
+                        string htexName = path[1];
+                        htexNames.Add(htexName);
+                        if (recordUrls.ContainsKey(htexName))
+                        {
+                            recordUrls[htexName].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+                        else
+                        {
+                            recordUrls[htexName] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
+                        }
+                    }
+                    if (path.Length == 3)
+                    {
+                        string blobName = "";
+                        if (blobItem.Name.EndsWith(".htex", StringComparison.OrdinalIgnoreCase) ) {
+                            blobName= blobItem.Name.Substring(0, blobItem.Name.Length - 5);
+                        }
+                        if (blobItem.Name.EndsWith(".hte", StringComparison.OrdinalIgnoreCase)) {
+                            blobName = blobItem.Name.Substring(0, blobItem.Name.Length - 4);
+                        }
+                        if (!string.IsNullOrWhiteSpace(blobName))
+                        {
+                            hteBlobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+
+                        //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 });
+                    }
+                }
+                string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                string code = $"Bloblog-{containerName}";
+                if (htexNames.Any() && htexNames.Count > 0)
+                {
+                    List<string> unlink = new List<string>() ;
+                    foreach (var htexName in htexNames)
+                    {
+
+                        string sql = $"select value c from c    where contains(c.url,'{htexName}')  ";
+                        var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code,pageSize:1);
+                        //找不到对应的数据存储
+                        if (!result.list.IsNotEmpty())
+                        {
+                            unlink.Add(htexName);
+                        }
+                    }
+                    var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
+                    var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
+                }
+
+                if (hteBlobs.IsNotEmpty()) {
+                    List<KeyValuePair<string, long?>> unlink = new List<KeyValuePair<string, long?>>();
+                    foreach (var hteName in hteBlobs)
+                    {
+                        string sql = $"select value c from c    where contains(c.url,'{hteName}')  ";
+                        var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code, pageSize: 1);
+                        //找不到对应的数据存储
+                        if (!result.list.IsNotEmpty())
+                        {
+                            unlink.Add(hteName);
+                        }
+                    }
+                    long? size = unlink.Select(z => z.Value).Sum();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlink, size = size });
+                }
+            }
+            // 课纲(syllabus)  节点id  fc4b7ac5-24e7-4eba-b0f2-7eccd007b4cd
+            {
+                string prefix = "syllabus";
+                //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                HashSet<string> rnodeIds = new HashSet<string>();
+                Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
+                await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                {
+                    var path = blobItem.Name.Split("/");
+                    if (path.Length > 3)
+                    {
+                        string rnodeId = path[1];
+                        rnodeIds.Add(rnodeId);
+                        if (recordUrls.ContainsKey(rnodeId))
+                        {
+                            recordUrls[rnodeId].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+                        else
+                        {
+                            recordUrls[rnodeId] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
+                        }
+                    }
+                    if (path.Length == 3)
+                    {
+                        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 });
+                    }
+                }
+
+                if (rnodeIds.Any() && rnodeIds.Count > 0) {
+                    string code = $"Syllabus-{containerName}";
+                    string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher; ;
+                    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}'"))})";
+                    IEnumerable<string> unlink = null;
+                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
+                    if (result != null && result.list.Any())
+                    {
+                        unlink = rnodeIds.Except(result.list);
+                    }
+                    else
+                    {
+                        unlink = rnodeIds;
+                    }
+                    var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
+                    var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
+                }
+            }
+
+            //待确认  德育风采 (elegant)  public    研修培训(train) 研修平台  yxpt
+            //直接删除的 test  jyzx
+            return response;
+        }
         /// <summary>
         /// <summary>
         /// </summary>
         /// </summary>
         /// <param name="req"></param>
         /// <param name="req"></param>

+ 15 - 0
TEAMModelOS.SDK/Models/Dtos/UnLink.cs

@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TEAMModelOS.SDK 
+{
+    public class UnLink
+    {
+        public string? prefix { get; set; }
+        public List<KeyValuePair<string, long?>> blobs { get; set; } = new List<KeyValuePair<string, long?>>();
+        public long? size { get; set; }
+    }
+}

+ 419 - 0
TEAMModelOS/Controllers/System/BlobController.cs

@@ -845,5 +845,424 @@ namespace TEAMModelOS.Controllers
             }
             }
            return Ok(new { paths ,status=1});
            return Ok(new { paths ,status=1});
         }
         }
+
+
+        [ProducesDefaultResponseType]
+        [Authorize(Roles = "IES")]
+        [AuthToken(Roles = "teacher,admin")]
+        [HttpPost("delete-unlink")]
+        public async Task<IActionResult> DeleteUnlink(JsonElement json) {
+            var (userid, _, _, school) = HttpContext.GetAuthTokenInfo();
+            int status = 0;
+            if (!json.TryGetProperty("scope", out JsonElement scope))
+            {
+                return Ok(new { status, msg = "参数错误" });
+            }
+            string containerName = scope.ToString().Equals("school", StringComparison.OrdinalIgnoreCase) ? school : userid;
+            bool exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync($"Blob:ScanResult:{scope}:{containerName}");
+            if (exists)
+            {
+                return Ok(new { status = 2, msg = "暂无清理项!" });
+            }
+
+            return Ok(new { status, msg = "暂无清理项!" });
+        }
+        [ProducesDefaultResponseType]
+        [Authorize(Roles = "IES")]
+        [AuthToken(Roles = "teacher,admin")]
+        [HttpPost("scan-unlink")]
+        public async Task<IActionResult> ScanUnlink(JsonElement json)
+        {
+            var (userid, _, _, school) = HttpContext.GetAuthTokenInfo();
+            int status = 0;
+            if ( !json.TryGetProperty("scope", out JsonElement scope))
+            {
+                return Ok(new { status, msg = "参数错误" });
+            }
+            string containerName = scope.ToString().Equals("school", StringComparison.OrdinalIgnoreCase) ? school : userid;
+            bool exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync($"Blob:ScanResult:{scope}:{containerName}");
+            if (exists)
+            {
+                var result = await _azureRedis.GetRedisClient(8).StringGetAsync($"Blob:ScanResult:{scope}:{containerName}");
+                if (result.HasValue)
+                {
+                    List<UnLink> unLinksData =  result.ToString().ToObject<List<UnLink>>();
+                    if (unLinksData.IsNotEmpty())
+                    {
+                        var groupData = unLinksData.GroupBy(x => x.prefix).ToList();
+                        List<dynamic> summaryData = new List<dynamic>();
+                        long totalCountData = 0;
+                        long? totalSizeData = 0;
+                        groupData.ForEach(x =>
+                        {
+                            long count = x.ToList().SelectMany(z => z.blobs).Count();
+                            long? size = x.Select(y => y.size).Sum();
+                            totalSizeData += size;
+                            totalCountData += count;
+                            summaryData.Add(new { prefix = x.Key, count = count, size = size });
+                        });
+                        //为节省服务器开销, 限制只能一天清理一次
+
+                        return Ok(new { status = 1, totalCount = totalCountData, totalSize = totalSizeData, summary = summaryData, unLinks = unLinksData });
+                    }
+                    else {
+                        return Ok(new { status = 2, msg = "最近清理过,暂无清理项!" });
+                    }
+                }
+                else {
+                    return Ok(new { status = 2, msg = "最近清理过,暂无清理项!" });
+                }
+                
+            }
+            List<UnLink> unLinks = new List<UnLink>();
+            //文件名(关联路径)导向的文件夹  内容(audio doc image video other  thum 缩略图) 学生头像(avatar)
+            string[] prefixFile = new string[] { "audio", "doc", "image", "video", "other", "thum", "avatar" };
+            foreach (string prefix in prefixFile)
+            {
+                string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                switch (true)
+                {
+                    //Bloblog
+
+                    case bool when prefix.Equals("doc", StringComparison.OrdinalIgnoreCase)
+                        || prefix.Equals("image", StringComparison.OrdinalIgnoreCase)
+                        || prefix.Equals("video", StringComparison.OrdinalIgnoreCase)
+                        || prefix.Equals("audio", StringComparison.OrdinalIgnoreCase)
+                        || prefix.Equals("other", StringComparison.OrdinalIgnoreCase):
+                        {
+                            //   audio/最愛情歌80-89-這些日子以來 (張清芳+范怡文).mp3
+                            List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                            await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                            {
+                                blobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                            }
+                            if (blobs.IsNotEmpty())
+                            {
+                                string code = $"Bloblog-{containerName}";
+                                string sql = $"select value c from c where c.url in ({string.Join(",", blobs.Select(z => $"'{z.Key}'"))})";
+                                var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code);
+                                if (result.list.IsNotEmpty())
+                                {
+                                    var urls = result.list.Select(x => x.url).ToHashSet();
+                                    var unlink = blobs.ExceptBy(urls, x => x.Key).ToList();
+                                    long? size = unlink.Select(z => z.Value).Sum();
+                                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlink, size = size });
+                                }
+                            }
+                            break;
+                        }
+                    case bool when prefix.Equals("avatar", StringComparison.OrdinalIgnoreCase) && scope.ToString().Equals("school"):
+                        {
+                            List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                            await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                            {
+                                blobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                            }
+                            if (blobs.IsNotEmpty())
+                            {
+                                string code = $"Base-{containerName}";
+                                List<KeyValuePair<string, long?>> unlink = new List<KeyValuePair<string, long?>>();
+                                foreach (var blob in blobs)
+                                {
+                                    string sql = $"select value c from c  where contains(c.picture,'{blob.Key}')";
+                                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student").GetList<Student>(sql, code, pageSize: 1);
+                                    //找不到对应的数据存储
+                                    if (!result.list.IsNotEmpty())
+                                    {
+                                        unlink.Add(blob);
+                                    }
+                                }
+                                long? size = unlink.Select(z => z.Value).Sum();
+                                unLinks.Add(new UnLink { prefix = prefix, blobs = unlink, size = size });
+                            }
+                            break;
+                        }
+                    default: break;
+
+                }
+            }
+            //文件夹(关联id)导向的文件夹 exam  homework art records  survey  vote  item
+            string[] prefixDirId = new string[] { "exam", "homework", "art", "records", "survey", "vote", "item" };
+            foreach (string prefix in prefixDirId)
+            {
+                string tbname = string.Empty;
+                HashSet<string> ids = new HashSet<string>();
+                Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
+                await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                {
+                    var path = blobItem.Name.Split("/");
+                    if (path.Length > 2)
+                    {
+                        string id = path[1];
+                        ids.Add(id);
+                        if (recordUrls.ContainsKey(id))
+                        {
+                            recordUrls[id].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+                        else
+                        {
+                            recordUrls[id] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
+                        }
+                    }
+                    if (path.Length == 2)
+                    {
+                        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 });
+                    }
+                }
+                string code = string.Empty;
+                if (ids.Any() && ids.Count > 0)
+                {
+                    string sql = $"select value c.id  from c where   c.id in ({string.Join(",", ids.Select(x => $"'{x}'"))})";
+                    switch (true)
+                    {
+                        case bool when prefix.Equals("exam", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = Constant.Common;
+                                code = $"Exam-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("homework", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = Constant.Common;
+                                code = $"Homework-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("art", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = Constant.Common;
+                                code = $"Art-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("survey", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = Constant.Common;
+                                code = $"Survey-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("vote", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = Constant.Common;
+                                code = $"Vote-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("item", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                                code = $"Item-{containerName}";
+                                break;
+                            }
+                        case bool when prefix.Equals("records", StringComparison.OrdinalIgnoreCase):
+                            {
+                                tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                                code = scope.ToString().Equals("school") ? $"LessonRecord-{containerName}" : "LessonRecord";
+                                break;
+                            }
+                    }
+                    IEnumerable<string> unlink = null;
+                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
+                    if (result != null && result.list.Any())
+                    {
+                        unlink = ids.Except(result.list);
+                    }
+                    else
+                    {
+                        unlink = ids;
+                    }
+                    var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
+                    var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
+                }
+            }
+            //名字导向 试卷(paper) 
+            {
+                string prefix = "paper";
+                //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                HashSet<string> paperNames = new HashSet<string>();
+                Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
+                await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                {
+                    var path = blobItem.Name.Split("/");
+                    if (path.Length > 2)
+                    {
+                        string paperName = path[1];
+                        paperNames.Add(paperName);
+                        if (recordUrls.ContainsKey(paperName))
+                        {
+                            recordUrls[paperName].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+                        else
+                        {
+                            recordUrls[paperName] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
+                        }
+                    }
+                    if (path.Length == 2)
+                    {
+                        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 });
+                    }
+                }
+                string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                string code = $"Paper-{containerName}";
+                if (paperNames.Any() && paperNames.Count > 0)
+                {
+                    IEnumerable<string> unlink = null;
+                    string sql = $"select value c.name  from c where   c.name in ({string.Join(",", paperNames.Select(x => $"'{x}'"))})";
+                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
+                    if (result != null && result.list.Any())
+                    {
+                        unlink = paperNames.Except(result.list);
+                    }
+                    else
+                    {
+                        unlink = paperNames;
+                    }
+                    var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
+                    var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
+                }
+            }
+            //htex hte (res)
+            {
+                string prefix = "res";
+
+                //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                HashSet<string> htexNames = new HashSet<string>();
+                List<KeyValuePair<string, long?>> hteBlobs = new List<KeyValuePair<string, long?>>();
+                Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
+                await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                {
+                    var path = blobItem.Name.Split("/");
+                    if (path.Length > 2)
+                    {
+                        string htexName = path[1];
+                        htexNames.Add(htexName);
+                        if (recordUrls.ContainsKey(htexName))
+                        {
+                            recordUrls[htexName].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+                        else
+                        {
+                            recordUrls[htexName] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
+                        }
+                    }
+                    if (path.Length == 2)
+                    {
+                        string blobName = "";
+                        if (blobItem.Name.EndsWith(".htex", StringComparison.OrdinalIgnoreCase))
+                        {
+                            blobName = blobItem.Name.Substring(0, blobItem.Name.Length - 5);
+                        }
+                        if (blobItem.Name.EndsWith(".hte", StringComparison.OrdinalIgnoreCase))
+                        {
+                            blobName = blobItem.Name.Substring(0, blobItem.Name.Length - 4);
+                        }
+                        if (!string.IsNullOrWhiteSpace(blobName))
+                        {
+                            hteBlobs.Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+
+                        //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 });
+                    }
+                }
+                string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher;
+                string code = $"Bloblog-{containerName}";
+                if (htexNames.Any() && htexNames.Count > 0)
+                {
+                    List<string> unlink = new List<string>();
+                    foreach (var htexName in htexNames)
+                    {
+
+                        string sql = $"select value c from c    where contains(c.url,'{htexName}')  ";
+                        var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code, pageSize: 1);
+                        //找不到对应的数据存储
+                        if (!result.list.IsNotEmpty())
+                        {
+                            unlink.Add(htexName);
+                        }
+                    }
+                    var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
+                    var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
+                }
+
+                if (hteBlobs.IsNotEmpty())
+                {
+                    List<KeyValuePair<string, long?>> unlink = new List<KeyValuePair<string, long?>>();
+                    foreach (var hteName in hteBlobs)
+                    {
+                        string sql = $"select value c from c    where contains(c.url,'{hteName}')  ";
+                        var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<Bloblog>(sql, code, pageSize: 1);
+                        //找不到对应的数据存储
+                        if (!result.list.IsNotEmpty())
+                        {
+                            unlink.Add(hteName);
+                        }
+                    }
+                    long? size = unlink.Select(z => z.Value).Sum();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlink, size = size });
+                }
+            }
+            // 课纲(syllabus)  节点id  fc4b7ac5-24e7-4eba-b0f2-7eccd007b4cd
+            {
+                string prefix = "syllabus";
+                //List<KeyValuePair<string, long?>> blobs = new List<KeyValuePair<string, long?>>();
+                HashSet<string> rnodeIds = new HashSet<string>();
+                Dictionary<string, List<KeyValuePair<string, long?>>> recordUrls = new Dictionary<string, List<KeyValuePair<string, long?>>>();
+                await foreach (BlobItem blobItem in _azureStorage.GetBlobContainerClient(containerName.ToString()).GetBlobsAsync(BlobTraits.None, BlobStates.None, prefix: prefix))
+                {
+                    var path = blobItem.Name.Split("/");
+                    if (path.Length > 2)
+                    {
+                        string rnodeId = path[1];
+                        rnodeIds.Add(rnodeId);
+                        if (recordUrls.ContainsKey(rnodeId))
+                        {
+                            recordUrls[rnodeId].Add(new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength));
+                        }
+                        else
+                        {
+                            recordUrls[rnodeId] = new List<KeyValuePair<string, long?>> { new KeyValuePair<string, long?>(blobItem.Name, blobItem.Properties.ContentLength) };
+                        }
+                    }
+                    if (path.Length == 2)
+                    {
+                        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 });
+                    }
+                }
+
+                if (rnodeIds.Any() && rnodeIds.Count > 0)
+                {
+                    string code = $"Syllabus-{containerName}";
+                    string tbname = scope.ToString().Equals("school") ? Constant.School : Constant.Teacher; ;
+                    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}'"))})";
+                    IEnumerable<string> unlink = null;
+                    var result = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, tbname).GetList<string>(sql, code);
+                    if (result != null && result.list.Any())
+                    {
+                        unlink = rnodeIds.Except(result.list);
+                    }
+                    else
+                    {
+                        unlink = rnodeIds;
+                    }
+                    var unlinkData = recordUrls.Where(x => unlink.Contains(x.Key));
+                    var unlinkPaths = unlinkData.SelectMany(z => z.Value).ToList();
+                    unLinks.Add(new UnLink { prefix = prefix, blobs = unlinkPaths, size = unlinkPaths.Select(z => z.Value).Sum() });
+                }
+            }
+            var group = unLinks.GroupBy(x => x.prefix).ToList();
+            List<dynamic> summary = new List<dynamic>();
+            long totalCount = 0;
+            long? totalSize = 0;
+            group.ForEach(x => {
+                long count = x.ToList().SelectMany(z => z.blobs).Count();
+                long? size = x.Select(y => y.size).Sum();
+                totalSize += size;
+                totalCount += count;
+                summary.Add(new { prefix = x.Key, count = count, size = size });
+            });
+            //为节省服务器开销, 限制只能一天清理一次
+            _azureRedis.GetRedisClient(8).StringSet($"Blob:ScanResult:{scope}:{containerName}", unLinks.ToJsonString(), expiry: new TimeSpan(24, 0, 0));
+            return Ok(new { status = 1, totalCount, totalSize, summary, unLinks });
+        }
     }
     }
 }
 }

+ 5 - 2
TEAMModelOS/Controllers/Teacher/InitController.cs

@@ -110,16 +110,19 @@ namespace TEAMModelOS.Controllers
 
 
 
 
         /// <summary>
         /// <summary>
-        /// 修改教师信息
+        /// 清理未关联资源
         /// </summary>
         /// </summary>
         /// <param name="request"></param>
         /// <param name="request"></param>
         /// <returns></returns>
         /// <returns></returns>
         [ProducesDefaultResponseType]
         [ProducesDefaultResponseType]
-        [HttpPost("delete-unlinked-record")]
+        [HttpPost("delete-unlinked-resource")]
         [AuthToken(Roles = "admin,teacher,area")]
         [AuthToken(Roles = "admin,teacher,area")]
         [Authorize(Roles = "IES")]
         [Authorize(Roles = "IES")]
         public async Task<IActionResult> DeleteUnlinkedRecord(JsonElement request) {
         public async Task<IActionResult> DeleteUnlinkedRecord(JsonElement request) {
             var (userid, name, _, school) = HttpContext.GetAuthTokenInfo();
             var (userid, name, _, school) = HttpContext.GetAuthTokenInfo();
+
+            //处理课例
+
             List<string> recordsUrls =  await _azureStorage.GetBlobContainerClient(userid).List("records");
             List<string> recordsUrls =  await _azureStorage.GetBlobContainerClient(userid).List("records");
             HashSet<string> recordIds= new HashSet<string>();
             HashSet<string> recordIds= new HashSet<string>();
             Dictionary<string, List<string>> recordUrls = new Dictionary<string, List<string>>();
             Dictionary<string, List<string>> recordUrls = new Dictionary<string, List<string>>();

+ 9 - 0
TEAMModelOS/Controllers/XTest/TestController.cs

@@ -2,9 +2,11 @@ using Azure;
 using Azure.Core;
 using Azure.Core;
 using Azure.Cosmos;
 using Azure.Cosmos;
 using Azure.Messaging.ServiceBus;
 using Azure.Messaging.ServiceBus;
+using Azure.Storage.Blobs;
 using Azure.Storage.Blobs.Models;
 using Azure.Storage.Blobs.Models;
 using DinkToPdf;
 using DinkToPdf;
 using DinkToPdf.Contracts;
 using DinkToPdf.Contracts;
+using DocumentFormat.OpenXml.Drawing.Wordprocessing;
 using DocumentFormat.OpenXml.Office2010.Excel;
 using DocumentFormat.OpenXml.Office2010.Excel;
 using DocumentFormat.OpenXml.Office2016.Excel;
 using DocumentFormat.OpenXml.Office2016.Excel;
 using DocumentFormat.OpenXml.Presentation;
 using DocumentFormat.OpenXml.Presentation;
@@ -85,6 +87,13 @@ namespace TEAMModelOS.Controllers
             _searcher = searcher;
             _searcher = searcher;
         }
         }
 
 
+       
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="json"></param>
+        /// <returns></returns>
         [ProducesDefaultResponseType]
         [ProducesDefaultResponseType]
         [HttpPost("check-art-result")]
         [HttpPost("check-art-result")]
         public async Task<IActionResult> CheckArtResult(JsonElement json)
         public async Task<IActionResult> CheckArtResult(JsonElement json)