HtmlToPdf/mvp/HtmlToPdfService.Api/Controllers/ImageController.cs
2025-12-11 23:35:52 +08:00

268 lines
8.5 KiB
C#

using Microsoft.AspNetCore.Mvc;
using PuppeteerSharp;
using HtmlToPdfService.Api.Models;
using HtmlToPdfService.Core.Services;
using HtmlToPdfService.Core.Storage;
namespace HtmlToPdfService.Api.Controllers;
/// <summary>
/// 图片转换控制器
/// </summary>
[ApiController]
[Route("api/image")]
public class ImageController : ControllerBase
{
private readonly IImageService _imageService;
private readonly IFileStorage _fileStorage;
private readonly ILogger<ImageController> _logger;
public ImageController(
IImageService imageService,
IFileStorage fileStorage,
ILogger<ImageController> logger)
{
_imageService = imageService;
_fileStorage = fileStorage;
_logger = logger;
}
/// <summary>
/// 从 HTML 内容生成图片
/// </summary>
[HttpPost("convert/html")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> ConvertHtml(
[FromBody] ConvertHtmlToImageRequest request,
CancellationToken cancellationToken)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
_logger.LogInformation("收到 HTML 转图片请求");
// 构建截图选项
var screenshotOptions = BuildScreenshotOptions(request.Options);
// 执行转换
var result = await _imageService.ConvertHtmlToImageAsync(
request.Html,
screenshotOptions,
request.Options?.Width,
request.Options?.Height,
request.DelayAfterLoad,
request.Callback?.Url,
request.Callback?.Headers,
request.Callback?.IncludePdfData,
request.SaveLocal,
cancellationToken);
// 返回图片文件
var contentType = GetContentType(request.Options?.Format);
var fileName = $"screenshot.{request.Options?.Format ?? "png"}";
return File(result.PdfData!, contentType, fileName);
}
catch (ArgumentException ex)
{
_logger.LogWarning(ex, "请求参数无效");
return BadRequest(new ProblemDetails
{
Status = StatusCodes.Status400BadRequest,
Title = "请求参数无效",
Detail = ex.Message
});
}
catch (TimeoutException ex)
{
_logger.LogError(ex, "转换超时");
return StatusCode(StatusCodes.Status503ServiceUnavailable, new ProblemDetails
{
Status = StatusCodes.Status503ServiceUnavailable,
Title = "服务繁忙",
Detail = ex.Message
});
}
catch (Exception ex)
{
_logger.LogError(ex, "HTML 转图片失败");
return StatusCode(StatusCodes.Status500InternalServerError, new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "转换失败",
Detail = ex.Message
});
}
}
/// <summary>
/// 从 URL 生成图片
/// </summary>
[HttpPost("convert/url")]
[ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
public async Task<IActionResult> ConvertUrl(
[FromBody] ConvertUrlToImageRequest request,
CancellationToken cancellationToken)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
try
{
_logger.LogInformation("收到 URL 转图片请求: {Url}", request.Url);
// 构建截图选项
var screenshotOptions = BuildScreenshotOptions(request.Options);
// 解析等待条件
WaitUntilNavigation[]? waitUntil = null;
if (!string.IsNullOrEmpty(request.WaitUntil))
{
waitUntil = ParseWaitUntil(request.WaitUntil);
}
// 执行转换
var result = await _imageService.ConvertUrlToImageAsync(
request.Url,
screenshotOptions,
request.Options?.Width,
request.Options?.Height,
waitUntil,
request.Timeout,
request.DelayAfterLoad,
request.Callback?.Url,
request.Callback?.Headers,
request.Callback?.IncludePdfData,
request.SaveLocal,
cancellationToken);
// 返回图片文件
var contentType = GetContentType(request.Options?.Format);
var fileName = $"screenshot.{request.Options?.Format ?? "png"}";
return File(result.PdfData!, contentType, fileName);
}
catch (ArgumentException ex)
{
_logger.LogWarning(ex, "请求参数无效");
return BadRequest(new ProblemDetails
{
Status = StatusCodes.Status400BadRequest,
Title = "请求参数无效",
Detail = ex.Message
});
}
catch (TimeoutException ex)
{
_logger.LogError(ex, "转换超时");
return StatusCode(StatusCodes.Status503ServiceUnavailable, new ProblemDetails
{
Status = StatusCodes.Status503ServiceUnavailable,
Title = "服务繁忙",
Detail = ex.Message
});
}
catch (Exception ex)
{
_logger.LogError(ex, "URL 转图片失败");
return StatusCode(StatusCodes.Status500InternalServerError, new ProblemDetails
{
Status = StatusCodes.Status500InternalServerError,
Title = "转换失败",
Detail = ex.Message
});
}
}
/// <summary>
/// 构建截图选项
/// </summary>
private ScreenshotOptions BuildScreenshotOptions(ImageOptionsDto? options)
{
if (options == null)
{
return new ScreenshotOptions { FullPage = true };
}
var screenshotOptions = new ScreenshotOptions
{
FullPage = options.FullPage,
Type = ParseScreenshotType(options.Format),
OmitBackground = options.OmitBackground ?? false
};
// JPEG 和 WebP 支持质量设置
if (options.Quality.HasValue &&
(screenshotOptions.Type == ScreenshotType.Jpeg || screenshotOptions.Type == ScreenshotType.Webp))
{
screenshotOptions.Quality = options.Quality.Value;
}
// 如果指定了截图区域
if (options.Clip != null)
{
screenshotOptions.Clip = new PuppeteerSharp.Media.Clip
{
X = options.Clip.X,
Y = options.Clip.Y,
Width = options.Clip.Width,
Height = options.Clip.Height
};
}
return screenshotOptions;
}
/// <summary>
/// 解析截图类型
/// </summary>
private ScreenshotType ParseScreenshotType(string? format)
{
return format?.ToLowerInvariant() switch
{
"jpeg" or "jpg" => ScreenshotType.Jpeg,
"webp" => ScreenshotType.Webp,
_ => ScreenshotType.Png
};
}
/// <summary>
/// 获取 Content-Type
/// </summary>
private string GetContentType(string? format)
{
return format?.ToLowerInvariant() switch
{
"jpeg" or "jpg" => "image/jpeg",
"webp" => "image/webp",
_ => "image/png"
};
}
/// <summary>
/// 解析等待条件
/// </summary>
private WaitUntilNavigation[] ParseWaitUntil(string waitUntil)
{
return waitUntil.ToLowerInvariant() switch
{
"load" => new[] { WaitUntilNavigation.Load },
"domcontentloaded" => new[] { WaitUntilNavigation.DOMContentLoaded },
"networkidle0" => new[] { WaitUntilNavigation.Networkidle0 },
"networkidle2" => new[] { WaitUntilNavigation.Networkidle2 },
_ => new[] { WaitUntilNavigation.Networkidle2 }
};
}
}