using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Nodes;
using Dpz.Core.Entity.Base;
using Dpz.Core.Infrastructure;
using Dpz.Core.Public.ViewModel.Response;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace Dpz.Core.Service.ObjectStorage.Services.Impl;
public class VideoCloudService : IVideoCloudService
{
private readonly HttpClient _httpClient;
private readonly ILogger<VideoCloudService> _logger;
private readonly UpyunOperator _upyunOperator;
public VideoCloudService(
HttpClient httpClient,
IConfiguration configuration,
ILogger<VideoCloudService> logger
)
{
_httpClient = httpClient;
_logger = logger;
var upyunOperator = configuration.GetSection("upyun").Get<UpyunOperator>();
_upyunOperator =
upyunOperator
?? throw new BusinessException("configuration error,need upyun config node.");
}
public async Task<ResponseResult<string?>> VideoScreenshotAsync(
string pathToFile,
TimeSpan time
)
{
var result = new ResponseResult<string?>();
if (string.IsNullOrEmpty(pathToFile))
{
throw new ArgumentNullException(nameof(pathToFile));
}
var parentPath = pathToFile[..pathToFile.LastIndexOf('/')];
var request = new HttpRequestMessage(HttpMethod.Post, $"{_upyunOperator.Bucket}/snapshot");
await request.SignatureAsync(_upyunOperator);
request.Content = JsonContent.Create(
new
{
source = pathToFile,
save_as = $"{parentPath}/video.webp",
point = $"{time.TotalHours:00}:{time.Minutes:00}:{time.Seconds:00}",
format = "webp",
}
);
try
{
var response = await _httpClient.SendAsync(request);
int? statusCode = (int)response.StatusCode;
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
#if DEBUG
Console.WriteLine(json);
#endif
var root = JsonNode.Parse(json);
statusCode = root?["status_code"]?.GetValue<int>();
if (statusCode == 200)
{
var savePath = root?["save_as"]?.GetValue<string>();
if (string.IsNullOrEmpty(savePath))
{
return result.FailResult("没有获取到缩略图地址");
}
return result.SuccessResult(_upyunOperator.Host + savePath);
}
}
return result.FailResult($"设置缩略图失败,响应码:{statusCode}");
}
catch (Exception e)
{
_logger.LogError(e, "screenshot fail");
return result.FailResult($"截图失败:{e.Message}");
}
}
public async Task<ResponseResult<VideoMetaDataResponse?>> GetVideoMetaAsync(string pathToFile)
{
var result = new ResponseResult<VideoMetaDataResponse?>();
if (string.IsNullOrEmpty(pathToFile))
{
throw new ArgumentNullException(nameof(pathToFile));
}
var request = new HttpRequestMessage(
HttpMethod.Post,
$"{_upyunOperator.Bucket}/m3u8er/get_meta"
);
await request.SignatureAsync(_upyunOperator);
request.Content = JsonContent.Create(new { m3u8 = pathToFile });
try
{
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_logger.LogError(
"get video meta fail,response status code:{StatusCode}",
response.StatusCode
);
return result.FailResult($"获取视频元数据失败,响应状态码:{response.StatusCode}");
}
var json = await response.Content.ReadAsStringAsync();
var root = JsonNode.Parse(json);
var data = root?["meta"]?.Deserialize(
typeof(VideoMetaDataResponse),
new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }
);
if (data is not VideoMetaDataResponse meta)
{
return result.FailResult("获取视频元数据失败");
}
return result.SuccessResult(meta);
}
catch (Exception e)
{
_logger.LogError(e, "get video meta fail");
return result.FailResult($"获取视频元数据失败:{e.Message}");
}
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这段代码是一个与又拍云(UpYun)对象存储/视频服务交互的服务实现类 VideoCloudService,负责调用又拍云的 API 来对存储在又拍云上的视频做截图和获取视频元数据。下面分部分解释其作用与流程。
总体结构与依赖
- 命名空间:Dpz.Core.Service.ObjectStorage.Services.Impl,类实现接口 IVideoCloudService(接口定义未给出)。
- 主要成员:
- _httpClient:用于发送 HTTP 请求(通过依赖注入注入)。
- _logger:用于记录日志。
- _upyunOperator:从配置中读取的又拍云相关配置(类型 UpyunOperator),包含 Bucket、Host 等(具体字段根据 UpyunOperator 定义)。
- 构造函数:从 IConfiguration 的 "upyun" 节点反序列化 UpyunOperator,若不存在则抛出 BusinessException,保证配置存在。
VideoScreenshotAsync(string pathToFile, TimeSpan time)
- 作用:对给定路径的远程视频在指定时间点截取一帧并保存为 webp 缩略图,返回生成图片的访问 URL(带上又拍云的 Host 前缀)。
- 校验:pathToFile 不能为空,否则抛 ArgumentNullException。
- 逻辑:
- 计算 parentPath = pathToFile 去掉最后一个 '/' 之后的部分,用于生成保存路径 save_as(保存为 parentPath/video.webp)。
- 构造 POST 请求到 {_upyunOperator.Bucket}/snapshot(又拍云 snapshot API)。
- 调用 await request.SignatureAsync(_upyunOperator) 给请求签名(Sign 方法在代码中未展示,是扩展方法或工具方法,用于添加认证头)。
- 请求体为 JSON,包含 source(源视频路径)、save_as(保存路径)、point(时间点字符串,格式如 "HH:mm:ss")、format("webp")。
- 发送请求并读取响应:
- 如果响应成功,解析响应 JSON,读取 status_code 字段;若为 200,读取 save_as 字段并返回成功的 URL(_upyunOperator.Host + save_as)。
- 否则返回失败消息,包含响应码或错误信息。
- 异常处理:捕获 Exception,记录日志并返回失败结果。
- 返回值:ResponseResult<string?>,封装成功/失败信息与数据(成功时 data 为完整图像 URL)。
GetVideoMetaAsync(string pathToFile)
- 作用:调用又拍云 m3u8 元数据接口来获取视频的元数据信息(返回 VideoMetaDataResponse 对象)。
- 校验:pathToFile 不能为空,否则抛 ArgumentNullException。
- 逻辑:
- 构造 POST 请求到 {_upyunOperator.Bucket}/m3u8er/get_meta。
- 给请求签名并设置 JSON body:{ m3u8 = pathToFile }。
- 发送请求,若 HTTP 状态非成功,记录日志并返回失败。
- 读取响应 JSON,解析 root["meta"] 并用 System.Text.Json 反序列化成 VideoMetaDataResponse(使用驼峰命名策略)。
- 如果反序列化成功则返回封装的成功结果,否则返回失败。
- 异常处理:捕获 Exception,记录日志并返回失败结果。
- 返回值:ResponseResult<VideoMetaDataResponse?>。
其它要点与注意事项
- ResponseResult
:一个自定义的响应封装类型(未给出实现),用于统一返回成功/失败状态和消息。 - request.SignatureAsync(...):代码中调用该方法给请求签名,签名逻辑在本片段中未展示,是又拍云认证所必需的。
- JSON 解析采用 System.Text.Json / JsonNode。
- 日志:在失败或异常时通过 ILogger 记录错误。
- 可能的边界/改进点:
- parentPath 的计算依赖于 pathToFile 中包含 '/';若没有会抛出 IndexOf 相关异常,应考虑处理单一文件名或最后一个 '/' 未找到的情况。
- 时间点格式使用 $"{time.TotalHours:00}:{time.Minutes:00}:{time.Seconds:00}",TotalHours 可能产生两位以上的小时数(例如 25 小时),根据 API 要求可能需要用 time.Hours 或自行格式化 TimeSpan 为 hh:mm:ss。
- 对响应 JSON 的字段访问比较宽松(使用 JsonNode),若 API 返回结构变化会返回失败信息,已通过日志记录错误。
- 未显示请求超时、重试等网络鲁棒性处理,这些可根据需求补充。
总结
- 这个类封装了两个与又拍云视频相关的功能:截图(VideoScreenshotAsync)和获取视频元数据(GetVideoMetaAsync),通过 HttpClient 调用又拍云接口,基于配置进行签名与主机拼接,并使用 ResponseResult 统一返回结果与错误信息,同时有基本的日志与异常捕获。
评论加载中...