CrazyIter_Bin 1 rok temu
rodzic
commit
6dba561063

+ 3 - 3
HTEXLib/HTEXLib.csproj

@@ -4,9 +4,9 @@
 		<Description>修改解析</Description>
 		<PackageReleaseNotes></PackageReleaseNotes>
 		<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
-		<Version>5.2203.232</Version>
-		<AssemblyVersion>5.2203.232.1</AssemblyVersion>
-		<FileVersion>5.2203.232.1</FileVersion>
+		<Version>5.2308.1515</Version>
+		<AssemblyVersion>5.2308.1515.1</AssemblyVersion>
+		<FileVersion>5.2308.1515.1</FileVersion>
 	</PropertyGroup>
 	<ItemGroup>
 		<PackageReference Include="DocumentFormat.OpenXml" Version="2.13.1" />

+ 132 - 7
HTEXScreen/Controllers/ScreenController.cs

@@ -1,8 +1,12 @@
 using HTEXScreen.Service;
+using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
  
 using PuppeteerSharp;
 using PuppeteerSharp.Media;
+using System.Configuration;
+using System.Drawing;
+using System.IO;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Text.Json;
@@ -15,12 +19,133 @@ namespace HTEXScreen.Controllers
     [Route("screen")]
     public class ScreenController : ControllerBase
     {
+
+
+
         private readonly AzureStorageFactory _azureStorage;
         private readonly  HttpClient _httpClient;
-        public ScreenController(HttpClient httpClient, AzureStorageFactory  azureStorage) {
+        private readonly IConfiguration _configuration;
+      //  private readonly HttpContext _httpContext;
+        public ScreenController(HttpClient httpClient, AzureStorageFactory  azureStorage, IConfiguration configuration)
+        {
             _httpClient = httpClient;
             _azureStorage = azureStorage;
+            _configuration = configuration;
+        //   _httpContext=httpContext;
+        }
+        /// <summary>
+        ///  上传到blob
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [HttpPost("from-miniapp-delete")]
+        [Authorize(Roles = "AClassONE")]
+        [RequestSizeLimit(102_400_000_00)] //最大10000m左右
+        public async Task<IActionResult> FromMiniAPPDelete(JsonElement json)
+        {
+
+            List<string>? urls = json.GetProperty("urls").Deserialize<List<string>>();
+            string? cnt = json.GetProperty("cnt").GetString();
+            string? test = json.GetProperty("test").GetString();
+            if (urls!=null) {
+                foreach (var url in urls)
+                {
+                    string dev = "Default";
+                    if (test.Equals("Test", StringComparison.OrdinalIgnoreCase))
+                    {
+                        dev="Test";
+                    }
+                    string uri = url.Split("?")[0];
+                    var blobinfo = BlobUrlString(uri);
+                    if (blobinfo.ContainerName.Equals(cnt))
+                    {
+                       var uld= HttpUtility.UrlDecode(blobinfo.blob);
+                       bool ds= await _azureStorage.GetBlobContainerClient(cnt, dev).DeleteBlobIfExistsAsync(uld);
+                    }
+                }
+            }
+            return Ok();
+        }
+        private static (string ContainerName, string blob) BlobUrlString(string sasUrl)
+        {
+            sasUrl = sasUrl.Substring(8);
+            string[] sasUrls = sasUrl.Split("/");
+            string ContainerName;
+            ContainerName = sasUrls[1].Clone().ToString();
+            string item = sasUrls[0] + "/" + sasUrls[1] + "/";
+            string blob = sasUrl.Replace(item, "");
+            return (ContainerName, blob);
+        }
+        /// <summary>
+        ///  上传到blob
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [HttpPost("from-miniapp-v2")]
+        [Authorize(Roles = "AClassONE")]
+        [RequestSizeLimit(102_400_000_00)] //最大10000m左右
+        public async Task<IActionResult> FromMiniAPPV2()
+        {
+            
+            try
+            {
+                List<dynamic> urls = new List<dynamic>();
+                var request = HttpContext.Request;
+                var files =request.Form.Files;
+                var cnt = request.Form["cnt"].ToString();
+                var test = request.Form["test"].ToString();
+                var path = request.Form["path"].ToString();
+                foreach (var file in files)
+                {
+
+                    string dev = "Default";
+                    if (test.Equals("Test", StringComparison.OrdinalIgnoreCase))
+                    {
+                        dev="Test";
+                    }
+                    if (path.EndsWith("/"))
+                    {
+                        path=path.Substring(0, path.Length -1);
+                    }
+                    (string name, string url) = await _azureStorage.GetBlobContainerClient(cnt, dev).UploadFileByContainer(file.OpenReadStream(), path, $"{file.FileName}", true);
+                    var auth = _azureStorage.GetBlobContainerClient(cnt, dev).GetBlobSasUriRead(_configuration, cnt, name, dev);
+                    var ext= file.FileName.Split(".");
+                    urls.Add(new { url= $"{url}?{auth.sas}",name =file.FileName, size=file.Length, extension= ext[ext.Length-1] , type = file.ContentType.Split("/")[0] ,blob= name ,cnt=cnt}) ;
+                }
+                return Ok(new { code=200,urls, });
+            }
+            catch (Exception vErr)
+            {
+                return Ok(new { code=500,msg=vErr.Message});
+            }
         }
+        /// <summary>
+        ///  上传到blob
+        /// </summary>
+        /// <param name="request"></param>
+        /// <returns></returns>
+        [HttpPost("from-miniapp")]
+        [Authorize(Roles = "AClassONE")]
+        [RequestSizeLimit(102_400_000_00)] //最大10000m左右
+        public async Task<IActionResult> FromMiniAPP([FromForm] IFormFile [] files,[FromForm] string path , [FromForm] string cnt, [FromForm] string test)
+        {
+            List<string> urls = new List<string>();
+            foreach (var file in files)
+            {
+                string dev = "Default";
+                if (test.Equals("Test", StringComparison.OrdinalIgnoreCase)) {
+                    dev="Test";
+                }
+                if (path.EndsWith("/")) {
+                    path=path.Substring(0, path.Length -1);
+                }
+               (string name, string url) = await _azureStorage.GetBlobContainerClient(cnt, dev).UploadFileByContainer(file.OpenReadStream(), path, $"{file.FileName}", true);
+                var auth=  _azureStorage.GetBlobContainerClient(cnt, dev).GetBlobSasUriRead(_configuration,cnt, name,dev);
+                urls.Add($"{url}?{auth.sas}" );
+            }
+            return Ok(new { urls });
+        }
+
         [HttpPost("testbase64")]
         public async Task<IActionResult> TestBase64(JsonElement json ) {
             JsonSerializerOptions option = new System.Text.Json.JsonSerializerOptions
@@ -190,7 +315,7 @@ namespace HTEXScreen.Controllers
                 //};
                 PdfOptions pdfOptions = new PdfOptions { DisplayHeaderFooter = true, FooterTemplate = "", PreferCSSPageSize = true, Format = PaperFormat.A4 };
                 //ScreenshotOptions screenshotOptions= new ScreenshotOptions { FullPage = fullPage, BurstMode = true };
-                List<string> urls = new List<string>();
+                List<(string name ,string url )> urls = new List<(string name, string url)>();
 
                 if (screenshot.urls.Count <= screenshot.pagesize)
                 {
@@ -198,7 +323,7 @@ namespace HTEXScreen.Controllers
                 }
                 else
                 {
-                    List<Task<List<string>>> tasks = new List<Task<List<string>>>();
+                    List<Task<List<(string name, string url)>>> tasks = new List<Task<List<(string name, string url)>>>();
                     int pages = (screenshot.urls.Count + screenshot.pagesize) / screenshot.pagesize;
                     for (int i = 0; i < pages; i++)
                     {
@@ -229,7 +354,7 @@ namespace HTEXScreen.Controllers
                 //关闭浏览器
                 await browser.CloseAsync();
                 await browser.DisposeAsync();
-                return Ok(new { urls });
+                return Ok(new { urls= urls.Select(z=>z.url) });
             }
             catch (Exception ex)
             {
@@ -245,7 +370,7 @@ namespace HTEXScreen.Controllers
                 }
             }
         }
-        private async Task<List<string>> PageToPdfStream(List<string> urls,string fileNameKey,string cnt,string root,string env  , Browser browser, ViewPortOptions viewPortOptions, PdfOptions pdfOptions)
+        private async Task<List<(string name ,string url )>> PageToPdfStream(List<string> urls,string fileNameKey,string cnt,string root,string env  , Browser browser, ViewPortOptions viewPortOptions, PdfOptions pdfOptions)
         {
             string name=  env.Equals("release")? "Default" : "Test";
             List<Task<Page>> pages = new List<Task<Page>>();
@@ -265,7 +390,7 @@ namespace HTEXScreen.Controllers
             var responses_tasks = await Task.WhenAll(responses);
             //List<Task<Stream>> streams = new List<Task<Stream>>();
             List<Task> tasks = new List<Task>();
-            List<Task<string>> uploads = new List<Task<string>>();
+            List<Task<(string name , string url )>> uploads = new List<Task<(string name, string url)>>();
             foreach (var page_task in page_tasks) {
                 string url = page_task.Url;
                 string[] paths=  HttpUtility.UrlDecode(url).Split("/");
@@ -289,7 +414,7 @@ namespace HTEXScreen.Controllers
                     uploads.Add(_azureStorage.GetBlobContainerClient(cnt, name).UploadFileByContainer(stream, root, $"{id}.pdf", true));
                 }
             }
-            string[]  uploadUrls=   await Task.WhenAll(uploads);
+            (string name, string url) []uploadUrls=   await Task.WhenAll(uploads);
             page_tasks.ToList().ForEach(x => {
                 tasks.Add(x.DisposeAsync().AsTask());
             });

+ 1 - 0
HTEXScreen/HTEXScreen.csproj

@@ -12,6 +12,7 @@
     <PackageReference Include="Azure.Cosmos" Version="4.0.0-preview3" />
     <PackageReference Include="Azure.Storage.Blobs.Batch" Version="12.10.0" />
     <PackageReference Include="Azure.Storage.Queues" Version="12.11.1" />
+    <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.23" />
     <PackageReference Include="Microsoft.Azure.Cosmos.Table" Version="2.0.0-preview" />
     <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.16.1" />
     <PackageReference Include="PuppeteerSharp" Version="7.1.0" />

+ 43 - 4
HTEXScreen/Program.cs

@@ -1,17 +1,53 @@
 using HTEXScreen.Service;
 using HTEXScreen.Service.AzureRedis;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
 using Microsoft.Extensions.Configuration;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
 using System.Text.Json.Nodes;
 using TEAMModelOS.SDK.DI;
 
 var builder = WebApplication.CreateBuilder(args);
 
 // Add services to the container.
-
+JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
+builder.Services.AddAuthentication(options => options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme)
+    .AddJwtBearer(options => //AzureADJwtBearer
+    {
+        //options.SaveToken = true; //驗證令牌由服務器生成才有效,不適用於服務重啟或分布式架構
+        options.Authority ="https://login.chinacloudapi.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/v2.0";// builder.Configuration["Option:Authority"];
+        options.Audience = "72643704-b2e7-4b26-b881-bd5865e7a7a5";//builder.Configuration["Option:Audience"];
+        options.RequireHttpsMetadata = true;
+        options.TokenValidationParameters = new TokenValidationParameters
+        {
+            RoleClaimType = "roles",
+            //ValidAudiences = new string[] { builder.Configuration["Option:Audience"], $"api://{builder.Configuration["Option:Audience"]}" }
+            ValidAudiences = new string[] { "72643704-b2e7-4b26-b881-bd5865e7a7a5", $"api://72643704-b2e7-4b26-b881-bd5865e7a7a5" }
+        };
+        options.Events = new JwtBearerEvents();
+        //下列事件有需要紀錄則打開
+        //options.Events.OnMessageReceived = async context => { await Task.FromResult(0); };
+        //options.Events.OnForbidden = async context => { await Task.FromResult(0); };
+        //options.Events.OnChallenge = async context => { await Task.FromResult(0); };
+        //options.Events.OnAuthenticationFailed = async context => { await Task.FromResult(0); };
+        options.Events.OnTokenValidated = async context =>
+        {
+            if (!context.Principal.Claims.Any(x => x.Type.Equals("http://schemas.microsoft.com/identity/claims/scope")) //ClaimConstants.Scope
+            && !context.Principal.Claims.Any(y => y.Type.Equals("roles"))) //ClaimConstants.Roles //http://schemas.microsoft.com/ws/2008/06/identity/claims/role
+            {
+                //TODO 需處理額外授權非角色及範圍的訪問異常紀錄
+                throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
+            }
+            await Task.FromResult(0);
+        };
+    });
 builder.Services.AddControllers();
+#if DEBUG
+builder.WebHost.UseUrls(new[] { "https://*:7298" });
+#endif
 // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
 builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
+//builder.Services.AddSwaggerGen();
 builder.Services.AddHttpClient();
 string StorageConnectionString = builder.Configuration.GetValue<string>("Azure:Storage:ConnectionString");
 string StorageConnectionStringTest = builder.Configuration.GetValue<string>("Azure:Storage:ConnectionString-Test");
@@ -41,13 +77,16 @@ builder.Services.AddAzureCosmos(CosmosConnectionStringTest, "Test");
 
 builder.Services.AddHostedService<ScreenPDFSub>();
 builder.Services.AddHostedService<ScreenPDFSubTest>();
+builder.Services.AddHttpContextAccessor();
+
 var app = builder.Build();
 if (app.Environment.IsDevelopment())
 {
-    app.UseSwagger();
-    app.UseSwaggerUI();
+    //app.UseSwagger();
+    //app.UseSwaggerUI();
 }
 app.UseHttpsRedirection();
+app.UseAuthentication();
 app.UseAuthorization();
 app.MapControllers();
 app.Run();

+ 69 - 3
HTEXScreen/Service/AzureStorage/AzureStorageBlobExtensions.cs

@@ -1,6 +1,9 @@
-using Azure.Storage.Blobs;
+using Azure.Storage;
+using Azure.Storage.Blobs;
 using Azure.Storage.Blobs.Models;
+using Azure.Storage.Sas;
 using Microsoft.Extensions.DependencyInjection.Extensions;
+using System.Configuration;
 
 namespace HTEXScreen.Service
 {
@@ -32,7 +35,7 @@ namespace HTEXScreen.Service
         /// <param name="fileName">文件名</param>
         /// <param name="contentTypeDefault">是否存放文件后缀对应的contentType</param>
         /// <returns></returns>
-        public static async Task<string> UploadFileByContainer(this BlobContainerClient blobContainer, Stream stream, string root, string blobpath, bool contentTypeDefault = true)
+        public static async Task<(string blobName ,string url )> UploadFileByContainer(this BlobContainerClient blobContainer, Stream stream, string root, string blobpath, bool contentTypeDefault = true)
         {
             //BlobContainerClient blobContainer = azureStorage.GetBlobContainerClient(name.ToLower().Replace("#", "")); //blobClient.GetContainerReference(groupName); 
             Uri url = blobContainer.Uri;
@@ -49,8 +52,71 @@ namespace HTEXScreen.Service
             }
             await blockBlob.UploadAsync(stream, true);
             blockBlob.SetHttpHeaders(new BlobHttpHeaders { ContentType = content_type });
-            return blockBlob.Uri.ToString();
+          
+            return (blockBlob.Name,blockBlob.Uri.ToString());
         }
 
+        /// <summary>
+        /// 取得Blob SAS (有效期預設一天)
+        /// </summary>
+        /// <param name="containerName">容器名稱</param>
+        /// <param name="blobName"></param>
+        /// <param name="blobSasPermissions"></param>
+        /// <param name="name"></param>
+        /// <returns></returns>
+        public static BlobAuth GetBlobSasUriRead(this BlobContainerClient blobContainer,IConfiguration _configuration , string containerName, string blobName, string name = "Default")
+        {
+            try
+            {
+                var ConnectionString =   name.Equals("Default") ? _configuration.GetSection("Azure:Storage:ConnectionString").Value : _configuration.GetSection("Azure:Storage:ConnectionString-Test").Value;
+                var keys = ParseConnectionString(ConnectionString);
+                var accountname = keys["AccountName"];
+                var accountkey = keys["AccountKey"];
+                var endpoint = keys["EndpointSuffix"];
+                DateTimeOffset dateTime = DateTimeOffset.UtcNow.Add(new TimeSpan(365 * 99, 0, 15, 0));
+                long time = dateTime.ToUnixTimeMilliseconds();
+                var blobSasBuilder = new BlobSasBuilder
+                {
+                    StartsOn = DateTimeOffset.UtcNow.Subtract(new TimeSpan(0, 15, 0)),
+                    ExpiresOn = dateTime,
+                    BlobContainerName = containerName.ToLower(),
+                    BlobName = blobName
+                };
+
+                blobSasBuilder.SetPermissions(BlobSasPermissions.Read);
+                var sskc = new StorageSharedKeyCredential(accountname, accountkey);
+                BlobSasQueryParameters sasQueryParameters = blobSasBuilder.ToSasQueryParameters(sskc);
+                UriBuilder fullUri = new UriBuilder()
+                {
+                    Scheme = "https",
+                    Host = $"{accountname}.blob.{endpoint}",
+                    Path = $"{containerName.ToLower()}/{blobName}",
+                    Query = sasQueryParameters.ToString()
+                };
+                return new BlobAuth { url = fullUri.Uri.ToString(), sas = sasQueryParameters.ToString(), timeout = time };
+                // return fullUri.Uri.ToString();
+            }
+            catch
+            {
+                return null;
+            }
+        }
+        public static Dictionary<string, string> ParseConnectionString(string connectionString)
+        {
+            var d = new Dictionary<string, string>();
+            foreach (var item in connectionString.Split(';', StringSplitOptions.RemoveEmptyEntries))
+            {
+                var a = item.IndexOf('=');
+                d.Add(item.Substring(0, a), item.Substring(a + 1));
+            }
+            return d;
+        }
+        public class BlobAuth
+        {
+            public string url { get; set; }
+            public string sas { get; set; }
+            public long timeout { get; set; }
+            public string name { get; set; }
+        }
     }
 }

+ 2 - 2
HTEXScreen/Service/ScreenPDFSub.cs

@@ -41,8 +41,8 @@ namespace HTEXScreen.Service
                         string body = receivedMessage.Body.ToString();
                         ScreenshotDto? screenshot = JsonSerializer.Deserialize<ScreenshotDto>(body);
                         await _receiver.CompleteMessageAsync(receivedMessage);
-                        List<string> urls=  await ScreenService.ScreenshotPdf(screenshot, _azureStorage);
-                        await ScreenService.UpdateStuArtPDF(urls, screenshot, _azureRedisFactory, _azureCosmosFactory);
+                        List<(string name ,string url )> urls=  await ScreenService.ScreenshotPdf(screenshot, _azureStorage);
+                        await ScreenService.UpdateStuArtPDF(urls.Select(z => z.url), screenshot, _azureRedisFactory, _azureCosmosFactory);
                        
                     } catch
                     {

+ 2 - 2
HTEXScreen/Service/ScreenPDFSubTest.cs

@@ -72,8 +72,8 @@ namespace HTEXScreen.Service
                         string body = receivedMessage.Body.ToString();
                         ScreenshotDto? screenshot = JsonSerializer.Deserialize<ScreenshotDto>(body);
                         await _receiver.CompleteMessageAsync(receivedMessage);
-                        List<string> urls=  await ScreenService.ScreenshotPdf(screenshot, _azureStorage);
-                        await ScreenService.UpdateStuArtPDF(urls, screenshot, _azureRedisFactory, _azureCosmosFactory);
+                        List<(string name ,string url)> urls=  await ScreenService.ScreenshotPdf(screenshot, _azureStorage);
+                        await ScreenService.UpdateStuArtPDF(urls.Select(z=>z.url), screenshot, _azureRedisFactory, _azureCosmosFactory);
                     } catch
                     {
                         //失败则放回队列死信中 

+ 8 - 8
HTEXScreen/Service/ScreenService.cs

@@ -13,7 +13,7 @@ namespace HTEXScreen.Service
 {
     public class ScreenService
     {
-        public static async Task  UpdateStuArtPDF(List<string> urls, ScreenshotDto screenshot, AzureRedisFactory _azureRedisFactory, AzureCosmosFactory _azureCosmosFactory) {
+        public static async Task  UpdateStuArtPDF(IEnumerable<string> urls, ScreenshotDto screenshot, AzureRedisFactory _azureRedisFactory, AzureCosmosFactory _azureCosmosFactory) {
             var env = screenshot.env.Equals("release") ? "Default" : "Test";
             long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
             List<Task<Azure.Response>> responses = new List<Task<Azure.Response>>();
@@ -68,7 +68,7 @@ namespace HTEXScreen.Service
         }
 
 
-        public static async Task<List<string>> ScreenshotPdf(ScreenshotDto screenshot, AzureStorageFactory _azureStorage) {
+        public static async Task<List<(string name ,string url )>> ScreenshotPdf(ScreenshotDto screenshot, AzureStorageFactory _azureStorage) {
 
             //W3C School教程 https://www.w3cschool.cn/puppeteer/puppeteer-rip537tj.html
             //  进入容器的命令 docker exec -it f9e27d175498 /bin/bash
@@ -114,7 +114,7 @@ namespace HTEXScreen.Service
                 //};
                 PdfOptions pdfOptions = new PdfOptions { DisplayHeaderFooter = true, FooterTemplate = "", PreferCSSPageSize = true, Format = PaperFormat.A4 };
                 //ScreenshotOptions screenshotOptions= new ScreenshotOptions { FullPage = fullPage, BurstMode = true };
-                List<string> urls = new List<string>();
+                List<(string anme ,string url )> urls = new List<(string name, string url)>();
 
                 if (screenshot.urls.Count <= screenshot.pagesize)
                 {
@@ -122,7 +122,7 @@ namespace HTEXScreen.Service
                 }
                 else
                 {
-                    List<Task<List<string>>> tasks = new List<Task<List<string>>>();
+                    List<Task<List<(string name ,string url )>>> tasks = new List<Task<List<(string name, string url)>>>();
                     int pages = (screenshot.urls.Count + screenshot.pagesize) / screenshot.pagesize;
                     for (int i = 0; i < pages; i++)
                     {
@@ -160,7 +160,7 @@ namespace HTEXScreen.Service
                 StreamWriter file = new StreamWriter("erorr_log.txt", append: true);
                 await file.WriteLineAsync($"ScreenService {JsonSerializer.Serialize(screenshot)}-----{ex.Message}----{ex.StackTrace}");
                 file.Close();
-                return new List<string>();
+                return new List<(string name, string url)>();
                 //return BadRequest($"{ex.Message}\n{ex.StackTrace}");
             }
             finally
@@ -174,7 +174,7 @@ namespace HTEXScreen.Service
 
         }
 
-        private static async Task<List<string>> PageToPdfStream(AzureStorageFactory _azureStorage,List<string> urls, string fileNameKey, string cnt, string root, string env, Browser browser, ViewPortOptions viewPortOptions, PdfOptions pdfOptions)
+        private static async Task<List<(string name ,string url )>> PageToPdfStream(AzureStorageFactory _azureStorage,List<string> urls, string fileNameKey, string cnt, string root, string env, Browser browser, ViewPortOptions viewPortOptions, PdfOptions pdfOptions)
         {
             string name = env.Equals("release") ? "Default" : "Test";
             List<Task<Page>> pages = new List<Task<Page>>();
@@ -194,7 +194,7 @@ namespace HTEXScreen.Service
             var responses_tasks = await Task.WhenAll(responses);
             //List<Task<Stream>> streams = new List<Task<Stream>>();
             List<Task> tasks = new List<Task>();
-            List<Task<string>> uploads = new List<Task<string>>();
+            List<Task<(string name , string url )>> uploads = new List<Task<(string name, string url)>>();
             foreach (var page_task in page_tasks)
             {
                 string url = page_task.Url;
@@ -220,7 +220,7 @@ namespace HTEXScreen.Service
                     uploads.Add(_azureStorage.GetBlobContainerClient(cnt, name).UploadFileByContainer(stream, root, $"{id}.pdf", true));
                 }
             }
-            string[] uploadUrls = await Task.WhenAll(uploads);
+            (string name ,string url )[]  uploadUrls = await Task.WhenAll(uploads);
             page_tasks.ToList().ForEach(x => {
                 tasks.Add(x.DisposeAsync().AsTask());
             });