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; using System.Text.RegularExpressions; using System.Web; using TEAMModelOS.SDK.DI; namespace HTEX.Screen.Controllers { [ApiController] [Route("screen")] public class ScreenController : ControllerBase { private readonly AzureStorageFactory _azureStorage; private readonly HttpClient _httpClient; private readonly IConfiguration _configuration; // private readonly HttpContext _httpContext; public ScreenController(HttpClient httpClient, AzureStorageFactory azureStorage, IConfiguration configuration) { _httpClient = httpClient; _azureStorage = azureStorage; _configuration = configuration; // _httpContext=httpContext; } /// /// 上传到blob /// /// /// [HttpPost("from-miniapp-delete")] [Authorize(Roles = "AClassONE")] [RequestSizeLimit(102_400_000_00)] //最大10000m左右 public async Task FromMiniAPPDelete(JsonElement json) { List? urls = json.GetProperty("urls").Deserialize>(); 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); } /// /// 上传到blob /// /// /// [HttpPost("from-miniapp-v2")] [Authorize(Roles = "AClassONE")] [RequestSizeLimit(102_400_000_00)] //最大10000m左右 public async Task FromMiniAPPV2() { try { List urls = new List(); 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).UploadFileByContainerBName(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 }); } } /// /// 上传到blob /// /// /// [HttpPost("from-miniapp")] [Authorize(Roles = "AClassONE")] [RequestSizeLimit(102_400_000_00)] //最大10000m左右 public async Task FromMiniAPP([FromForm] IFormFile[] files, [FromForm] string path, [FromForm] string cnt, [FromForm] string test) { List urls = new List(); 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).UploadFileByContainerBName(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 }); } [HttpGet("download")] public async Task Download([FromQuery] ScreenshotDto screenshot) { try { HttpResponseMessage response = await _httpClient.GetAsync(screenshot.url); if (!string.IsNullOrWhiteSpace(screenshot?.url)) { string? url = screenshot?.url; string[] path = url.Split("/"); string fileName = path[path.Length - 1]; fileName=HttpUtility.UrlDecode(fileName); Stream stream = response.Content.ReadAsStream(); if (!Directory.Exists("Download")) { Directory.CreateDirectory("Download"); } FileStream fs = new FileStream($"Download/{fileName}", FileMode.Create); byte[] bytes = new byte[stream.Length]; stream.Read(bytes, 0, bytes.Length); stream.Seek(0, SeekOrigin.Begin); BinaryWriter bw = new BinaryWriter(fs); bw.Write(bytes); bw.Close(); fs.Close(); return Ok(screenshot); } else { return Ok("链接为空"); } } catch (Exception ex) { return Ok($"{ex.Message}{ex.StackTrace}"); } } /// /// C#使用Puppeteer http://t.zoukankan.com/zhaotianff-p-13528507.html /// 文档https://learnku.com/docs/puppeteer/3.1.0/class-request/8559 /// https://www.w3cschool.cn/puppeteer/puppeteer-gp1737se.html /// /// /// [HttpGet("screenshot-png")] public async Task ScreenshotPng([FromQuery] ScreenshotDto screenshot) { // 进入容器的命令 docker exec -it f9e27d175498 /bin/bash //依赖包 https://blog.csdn.net/weixin_45447477/article/details/115188938 //sudo apt-get install libgdk-pixbuf2.0-0 libgdk-pixbuf-xlib-2.0-0 libdbusmenu-gtk3-4 libdbusmenu-glib4 libindicator3-7 ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils -y //解决ubuntu18上使用puppeteer https://blog.csdn.net/qq_42414062/article/details/114539378 //https://www.hardkoded.com/blog/running-puppeteer-sharp-azure-functions 使用。 //string url = "https://teammodelos.blob.core.chinacloudapi.cn/0-public/pie-borderRadius.html"; try { var bfOptions = new BrowserFetcherOptions(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { string dir = "/app"; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } bfOptions.Path = dir; } var bf = new BrowserFetcher(bfOptions); var revisionInfo = bf.DownloadAsync(BrowserFetcher.DefaultChromiumRevision).Result; string BrowserExecutablePath = revisionInfo.ExecutablePath; var browser = await Puppeteer.LaunchAsync(new LaunchOptions { ExecutablePath = BrowserExecutablePath, Headless = true, Args = new string[] { "--no-sandbox", "--disable-setuid-sandbox" } }); var page = await browser.NewPageAsync(); bool fullPage = true; await page.SetViewportAsync(new ViewPortOptions { Width = screenshot.width, Height = screenshot.height }); ; fullPage = false; await page.GoToAsync(System.Web.HttpUtility.UrlDecode(screenshot.url)); await Task.Delay(screenshot.delay); string base64 = await page.ScreenshotBase64Async(new ScreenshotOptions { FullPage = fullPage, BurstMode = true }); //关闭浏览器 await browser.CloseAsync(); await browser.DisposeAsync(); return Ok(new { url = base64, type = "data:image/png;base64," }); } catch (Exception ex) { return BadRequest($"{ex.Message}\n{ex.StackTrace}"); } } [HttpPost("screenshot-pdf")] public async Task ScreenshotPdf(ScreenshotDto screenshot) { //W3C School教程 https://www.w3cschool.cn/puppeteer/puppeteer-rip537tj.html // 进入容器的命令 docker exec -it f9e27d175498 /bin/bash //依赖包 https://blog.csdn.net/weixin_45447477/article/details/115188938 //sudo apt-get install libgdk-pixbuf2.0-0 libgdk-pixbuf-xlib-2.0-0 libdbusmenu-gtk3-4 libdbusmenu-glib4 libindicator3-7 ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils -y //解决ubuntu18上使用puppeteer https://blog.csdn.net/qq_42414062/article/details/114539378 //https://www.hardkoded.com/blog/running-puppeteer-sharp-azure-functions 使用。 //string url = "https://teammodelos.blob.core.chinacloudapi.cn/0-public/pie-borderRadius.html"; Browser browser = null; try { var bfOptions = new BrowserFetcherOptions(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { string dir = "/app"; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } bfOptions.Path = dir; } var bf = new BrowserFetcher(bfOptions); var revisionInfo = bf.DownloadAsync(BrowserFetcher.DefaultChromiumRevision).Result; string BrowserExecutablePath = revisionInfo.ExecutablePath; browser = await Puppeteer.LaunchAsync(new LaunchOptions { ExecutablePath = BrowserExecutablePath, Headless = true, Args = new string[] { "--no-sandbox", "--disable-setuid-sandbox" } }); double unitPX = 37.7813; ViewPortOptions viewPortOptions = new ViewPortOptions { // Width = (int)Math.Ceiling(unitPX * 21), // Height = (int)Math.Ceiling(unitPX * 29.7 * 1) }; //ViewPortOptions viewPortOptions = new ViewPortOptions //{ // Width = screenshot.width, // Height = screenshot.height //}; PdfOptions pdfOptions = new PdfOptions { DisplayHeaderFooter = true, FooterTemplate = "", PreferCSSPageSize = true, Format = PaperFormat.A4 }; //ScreenshotOptions screenshotOptions= new ScreenshotOptions { FullPage = fullPage, BurstMode = true }; List<(string name, string url)> urls = new List<(string name, string url)>(); if (screenshot.urls.Count <= screenshot.pagesize) { urls.AddRange(await PageToPdfStream(screenshot.urls, screenshot.fileNameKey, screenshot.cnt, screenshot.root, screenshot.env, browser, viewPortOptions, pdfOptions)); } else { List>> tasks = new List>>(); int pages = (screenshot.urls.Count + screenshot.pagesize) / screenshot.pagesize; for (int i = 0; i < pages; i++) { var lists = screenshot.urls.Skip((i) * screenshot.pagesize).Take(screenshot.pagesize).ToList(); tasks.Add(PageToPdfStream(lists, screenshot.fileNameKey, screenshot.cnt, screenshot.root, screenshot.env, browser, viewPortOptions, pdfOptions)); } var tsk = await Task.WhenAll(tasks); foreach (var ts in tsk) { urls.AddRange(ts); } } //browser.NewPageAsync(); //foreach (var url in screenshot.urls) { // var page = await browser.NewPageAsync(); // await page.SetViewportAsync(viewPortOptions); // string file = $"E://pdfs//{Guid.NewGuid().ToString()}.pdf"; // var respons = await page.GoToAsync(System.Web.HttpUtility.UrlDecode(url), WaitUntilNavigation.Networkidle2); // if (respons.Ok) // { // await page.PdfAsync(file, pdfOptions); // // string base64 = await page.ScreenshotBase64Async(screenshotOptions); // } //} //关闭浏览器 await browser.CloseAsync(); await browser.DisposeAsync(); return Ok(new { urls = urls.Select(z => z.url) }); } catch (Exception ex) { StreamWriter file = new StreamWriter("erorr_log.txt", append: true); await file.WriteLineAsync($"{this.GetType().Name} {JsonSerializer.Serialize(screenshot)}-----{ex.Message}----{ex.StackTrace}"); file.Close(); return BadRequest($"{ex.Message}\n{ex.StackTrace}"); } finally { if (browser != null && !browser.IsClosed) { await browser.CloseAsync(); await browser.DisposeAsync(); } } } private async Task> PageToPdfStream(List urls, string fileNameKey, string cnt, string root, string env, Browser browser, ViewPortOptions viewPortOptions, PdfOptions pdfOptions) { string name = env.Equals("release") ? "Default" : "Test"; List> pages = new List>(); urls.ForEach(x => { pages.Add(browser.NewPageAsync()); }); var page_tasks = await Task.WhenAll(pages); List> responses = new List>(); page_tasks.ToList().ForEach(x => { x.SetViewportAsync(viewPortOptions); }); for (int i = 0; i < urls.Count; i++) { responses.Add(page_tasks[i].GoToAsync(urls[i], 30000, new WaitUntilNavigation[] { WaitUntilNavigation.Networkidle2 })); } var responses_tasks = await Task.WhenAll(responses); //List> streams = new List>(); List tasks = new List(); List> uploads = new List>(); foreach (var page_task in page_tasks) { string url = page_task.Url; string[] paths = HttpUtility.UrlDecode(url).Split("/"); var reg = $"(?<=\\b{fileNameKey}=)[^&]*"; Regex regex = new Regex(reg); string decode = HttpUtility.UrlDecode(url); Match match = Regex.Match(decode, reg); string id = ""; while (match.Success) { id = id + $"{match.Value}"; match = match.NextMatch(); } //需要解析参数。paths[paths.Length-1] Stream stream = await page_task.PdfStreamAsync(pdfOptions); if (string.IsNullOrWhiteSpace(cnt)) { uploads.Add(_azureStorage.GetBlobContainerClient("teammodelos", name).UploadFileByContainerBName(stream, root, $"{id}.pdf", true)); } else { uploads.Add(_azureStorage.GetBlobContainerClient(cnt, name).UploadFileByContainerBName(stream, root, $"{id}.pdf", true)); } } (string name, string url)[] uploadUrls = await Task.WhenAll(uploads); page_tasks.ToList().ForEach(x => { tasks.Add(x.DisposeAsync().AsTask()); }); await Task.WhenAll(tasks); return uploadUrls.ToList(); } private async Task PageToPdf(List urls, Browser browser, ViewPortOptions viewPortOptions, PdfOptions pdfOptions) { List> pages = new List>(); urls.ForEach(x => { pages.Add(browser.NewPageAsync()); }); var page_tasks = await Task.WhenAll(pages); List> responses = new List>(); page_tasks.ToList().ForEach(x => { x.SetViewportAsync(viewPortOptions); }); for (int i = 0; i < urls.Count; i++) { responses.Add(page_tasks[i].GoToAsync(System.Web.HttpUtility.UrlDecode(urls[i]), WaitUntilNavigation.Networkidle2)); } var responses_tasks = await Task.WhenAll(responses); List tasks = new List(); page_tasks.ToList().ForEach(x => { string file = $"{Guid.NewGuid()}.pdf"; tasks.Add(x.PdfAsync(file, pdfOptions)); }); await Task.WhenAll(tasks); tasks.Clear(); page_tasks.ToList().ForEach(x => { tasks.Add(x.DisposeAsync().AsTask()); }); await Task.WhenAll(tasks); } } }