using dy.net.dto; using dy.net.service; using dy.net.utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using System.Drawing.Printing; namespace dy.net.Controllers { [Route("api/[controller]")] [ApiController] public class VideoController : ControllerBase { private readonly DyCollectVideoService dyCollectVideoService; public VideoController(DyCollectVideoService dyCollectVideoService) { this.dyCollectVideoService = dyCollectVideoService; } /// /// 分页查询收藏视频 /// /// [HttpPost("paged")] public async Task GetPagedAsync(VideoPageRequestDTO dto) { // 调试日志:输出接收到的排序参数 Console.WriteLine($"排序参数 - SortField: {dto.SortField}, SortOrder: {dto.SortOrder}"); var (list, totalCount) = await dyCollectVideoService.GetPagedAsync(dto.PageIndex, dto.PageSize, dto.Tag, dto.Tags, dto.Author, dto.ViedoType, dto.Dates, dto.IsDeleted, dto.SortField, dto.SortOrder); return Ok(new { code = 0, data = new { data = list, total=totalCount, pageIndex=dto.PageIndex, pageSize=dto.PageSize } }); } /// /// 获取所有视频类型列表(用于下拉框) /// /// [HttpGet("categories")] public async Task GetAllCategories() { var categories = await dyCollectVideoService.GetAllCategoriesAsync(); return Ok(new { code = 0, data = categories }); } /// /// 查询统计数据 /// /// [HttpGet("statics")] public async Task GetStaticsAsync() { var data = await dyCollectVideoService.GetStatics(); return Ok(new { code = 0, data }); } /// /// 清理数据库中文件不存在的视频记录 /// /// [Authorize] [HttpPost("cleanup")] public async Task CleanupMissingFilesAsync() { try { Serilog.Log.Information("开始清理文件不存在的视频记录"); var deletedCount = await dyCollectVideoService.CleanupMissingFilesAsync(); Serilog.Log.Information($"清理完成,删除了 {deletedCount} 条记录"); return Ok(new { code = 0, data = new { deletedCount = deletedCount, message = $"清理完成,删除了 {deletedCount} 条文件不存在的记录" } }); } catch (Exception ex) { Serilog.Log.Error(ex, "清理文件不存在的记录失败"); return StatusCode(500, new { code = -1, error = "清理失败:" + ex.Message }); } } /// /// 删除指定作者的所有视频数据 /// /// 作者名称 /// [Authorize] [HttpPost("delete-author")] public async Task DeleteAuthorVideosAsync([FromBody] DeleteAuthorRequest request) { try { if (string.IsNullOrWhiteSpace(request?.AuthorName)) { return BadRequest(new { code = -1, error = "作者名称不能为空" }); } Serilog.Log.Information($"开始删除作者 {request.AuthorName} 的视频数据"); var deletedCount = await dyCollectVideoService.DeleteAuthorVideosAsync(request.AuthorName); Serilog.Log.Information($"删除完成,删除了 {deletedCount} 条记录"); return Ok(new { code = 0, data = new { deletedCount = deletedCount, message = $"已删除作者 {request.AuthorName} 的 {deletedCount} 条视频记录" } }); } catch (Exception ex) { Serilog.Log.Error(ex, $"删除作者视频失败: {request?.AuthorName}"); return StatusCode(500, new { code = -1, error = "删除失败:" + ex.Message }); } } /// /// 删除作者请求DTO /// public class DeleteAuthorRequest { public string AuthorName { get; set; } } /// /// 获取视频流(支持Range请求,用于视频播放) /// /// 视频ID /// [Authorize] [HttpGet("stream/{id}")] public async Task GetVideoStream(string id) { try { var video = await dyCollectVideoService.GetByIdAsync(id); if (video == null || string.IsNullOrWhiteSpace(video.VideoSavePath)) { return NotFound(new { code = -1, error = "视频不存在" }); } if (!System.IO.File.Exists(video.VideoSavePath)) { return NotFound(new { code = -1, error = "视频文件不存在" }); } var fileInfo = new FileInfo(video.VideoSavePath); var contentType = GetContentType(video.VideoSavePath); // 记录日志,便于调试 var rangeHeader = Request.Headers["Range"].ToString(); Serilog.Log.Debug($"视频流请求: ID={id}, 路径={video.VideoSavePath}, Content-Type={contentType}, Range={rangeHeader}"); var fileStream = new FileStream(video.VideoSavePath, FileMode.Open, FileAccess.Read, FileShare.Read); // 使用ASP.NET Core内置的Range支持 Response.Headers.Add("Accept-Ranges", "bytes"); // 添加缓存控制头,确保浏览器可以缓存视频元数据 // 但允许 Range 请求,这样浏览器可以快速开始播放 Response.Headers.Add("Cache-Control", "public, max-age=3600"); // 添加 CORS 头(如果需要) Response.Headers.Add("Access-Control-Allow-Origin", "*"); Response.Headers.Add("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS"); Response.Headers.Add("Access-Control-Allow-Headers", "Range"); Response.Headers.Add("Access-Control-Expose-Headers", "Content-Range, Accept-Ranges, Content-Length"); return File(fileStream, contentType, enableRangeProcessing: true); } catch (Exception ex) { Serilog.Log.Error(ex, $"获取视频流失败: {id}"); return StatusCode(500, new { code = -1, error = "获取视频流失败" }); } } /// /// 获取视频封面图片 /// /// 视频ID /// [Authorize] [HttpGet("cover/{id}")] public async Task GetVideoCover(string id) { try { var video = await dyCollectVideoService.GetByIdAsync(id); if (video == null) { return GetDefaultCoverImage(); } // 优先使用本地保存的封面 if (!string.IsNullOrWhiteSpace(video.VideoCoverSavePath) && System.IO.File.Exists(video.VideoCoverSavePath)) { var fileInfo = new FileInfo(video.VideoCoverSavePath); var contentType = GetImageContentType(video.VideoCoverSavePath); var fileStream = System.IO.File.OpenRead(video.VideoCoverSavePath); return File(fileStream, contentType); } // 如果没有本地封面,返回默认图片 return GetDefaultCoverImage(); } catch (Exception ex) { Serilog.Log.Error(ex, $"获取视频封面失败: {id}"); return GetDefaultCoverImage(); } } /// /// 返回默认封面图片(SVG 占位图) /// private IActionResult GetDefaultCoverImage() { var svgContent = @" 暂无封面 "; var svgBytes = System.Text.Encoding.UTF8.GetBytes(svgContent); return File(svgBytes, "image/svg+xml"); } /// /// 根据文件扩展名获取图片 Content-Type /// private string GetImageContentType(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { return "image/jpeg"; } var extension = Path.GetExtension(fileName).ToLowerInvariant(); return extension switch { ".jpg" or ".jpeg" => "image/jpeg", ".png" => "image/png", ".gif" => "image/gif", ".webp" => "image/webp", ".svg" => "image/svg+xml", ".bmp" => "image/bmp", _ => "image/jpeg" }; } /// /// 根据文件扩展名获取Content-Type /// private string GetContentType(string fileName) { if (string.IsNullOrWhiteSpace(fileName)) { Serilog.Log.Warning($"文件名为空,使用默认 video/mp4"); return "video/mp4"; // 抖音视频默认是 MP4 } var extension = Path.GetExtension(fileName).ToLowerInvariant(); // 如果没有扩展名,尝试从文件名推断(抖音视频格式通常是 mp4) if (string.IsNullOrEmpty(extension)) { Serilog.Log.Warning($"文件 {fileName} 没有扩展名,使用默认 video/mp4"); return "video/mp4"; } var contentType = extension switch { ".mp4" => "video/mp4", ".webm" => "video/webm", ".ogg" => "video/ogg", ".mov" => "video/quicktime", ".avi" => "video/x-msvideo", ".mkv" => "video/x-matroska", ".flv" => "video/x-flv", ".wmv" => "video/x-ms-wmv", ".3gp" => "video/3gpp", ".m3u8" => "application/x-mpegURL", // HLS 流 _ => null }; // 如果无法识别扩展名,默认使用 video/mp4(抖音视频通常是 MP4) if (contentType == null) { Serilog.Log.Warning($"无法识别的视频扩展名: {extension},文件: {fileName},使用默认 video/mp4"); return "video/mp4"; } return contentType; } [AllowAnonymous] [HttpGet] public IActionResult Index() { List myClasses = new List(); foreach (var drive in DriveInfo.GetDrives()) { myClasses.Add(new MyClass { name=drive.Name, x1= drive.TotalSize, x2= drive.TotalFreeSpace }); Serilog.Log.Debug($"Drive: {drive.Name}, Total Size: {drive.TotalSize}, Free Space: {drive.TotalFreeSpace}"); } //var disk=DiskInfoHelper.GetDockerHostTotalDiskSpaceGB(); return Ok(JsonConvert.SerializeObject(myClasses)+"\r\n"+JsonConvert.SerializeObject(myClasses.Sum(x=>x.x1)+"\r\n"+ JsonConvert.SerializeObject(myClasses.Sum(x => x.x2)))); } public class MyClass { public string name { get; set; } public long x1 { get; set; } public long x2 { get; set; } } } }