using Dpz.Core.Public.ViewModel.Response;
using Dpz.Core.Shard.Service;
using Dpz.Core.WebApi.Models.Request;
using Color = System.Drawing.Color;

namespace Dpz.Core.WebApi.Controllers;

/// <summary>
/// 视频
/// </summary>
[ApiController, Route("api/[controller]")]
public class VideoController(
    IBarrageService barrageService,
    IVideoService videoService,
    IFusionCache fusionCache,
    IVideoShardService videoShardService,
    IVideoCloudService videoCloudService
) : ControllerBase
{
    /// <summary>
    /// 获取视频列表
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    [ProducesResponseType<List<VmVideo>>(StatusCodes.Status200OK)]
    public async Task<IActionResult> GetVideos()
    {
        var videos = await videoShardService.GetVideosAsync();
        return Ok(videos);
    }

    /// <summary>
    /// 发送弹幕
    /// </summary>
    /// <param name="danmaku"></param>
    /// <returns></returns>
    [HttpPost("danmaku")]
    [ProducesResponseType<VmBarrage>(StatusCodes.Status200OK)]
    public async Task<IActionResult> Danmaku([FromBody] VideoDanmakuDto danmaku)
    {
        var vmBarrage = new VmBarrage
        {
            Color = danmaku.Color.ToString(),
            Group = danmaku.Id,
            Position = (int)danmaku.Type,
            SendTime = DateTime.Now,
            Size = 0,
            Text = danmaku.Text,
            Time = danmaku.Time,
        };
        await barrageService.AddBarrageAsync(vmBarrage);
        return Ok(vmBarrage);
    }

    /// <summary>
    /// 获取视频弹幕
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpGet("danmaku")]
    [ProducesResponseType<List<VmBarrage>>(StatusCodes.Status200OK)]
    public async Task<IActionResult> Danmaku(string id)
    {
        var danmakuList = await barrageService.GetGroupBarragesAsync(id);
        danmakuList.ForEach(x =>
        {
            if (int.TryParse(x.Color, out var color))
            {
                var colorRgb = Color.FromArgb(color);
                x.Color = $"rgb({colorRgb.R},{colorRgb.G},{colorRgb.B})";
            }
        });
        return Ok(danmakuList);
    }

    /// <summary>
    /// 获取视频详情列表
    /// </summary>
    /// <returns></returns>
    [HttpGet("details"), Authorize(Policy = "System")]
    [ProducesResponseType<List<VmVideo>>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> GetVideoListDetails()
    {
        var videos = await videoShardService.GetVideosAsync();

        return Ok(videos);
    }

    /// <summary>
    /// 保存视频信息
    /// </summary>
    /// <returns></returns>
    [HttpPost, Authorize(Policy = "System")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> SaveInformation([FromBody] VmVideo video)
    {
        await videoService.SaveInformationAsync(video);
        await fusionCache.RemoveByTagAsync(CacheKey.VideoListKey);
        return NoContent();
    }

    /// <summary>
    /// 添加一次播放次数
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpPatch("play/{id}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    public async Task<IActionResult> AddPlayer(string id)
    {
        await videoShardService.AddPlayAsync(id);
        return NoContent();
    }

    /// <summary>
    /// 获取视频元数据
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpGet("meta/{id}"), Authorize(Policy = "System")]
    [ProducesResponseType<VideoMetaDataResponse>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetVideoMetaData(string id)
    {
        var videMetaKey = $"Video-Meta:{id}";
        var cacheValue = await fusionCache.TryGetAsync<VideoMetaDataResponse>(videMetaKey);
        if (cacheValue is { HasValue: true, Value: not null })
        {
            return Ok(cacheValue.Value);
        }
        var video = await videoShardService.GetVideoAsync(id);
        if (video == null)
        {
            return NotFound();
        }

        var meta = await videoCloudService.GetVideoMetaAsync(
            $"/Video/{video.Title}/1080p.m3u8",
            HttpContext.RequestAborted
        );
        if (meta.Success)
        {
            await fusionCache.SetAsync(videMetaKey, meta.Data, TimeSpan.FromDays(90));
            return Ok(meta.Data);
        }
        return BadRequest(meta.Message);
    }

    /// <summary>
    /// 设置视频缩略图
    /// </summary>
    /// <param name="id"></param>
    /// <param name="request"></param>
    /// <returns></returns>
    [HttpPatch("screenshot/{id}"), Authorize(Policy = "System")]
    [ProducesResponseType<string>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> SetVideoScreenshot(
        string id,
        [FromBody] ScreenshotRequest request
    )
    {
        var video = await videoShardService.GetVideoAsync(id);
        if (video == null)
        {
            return NotFound();
        }

        var meta = await videoCloudService.GetVideoMetaAsync(
            $"/Video/{video.Title}/1080p.m3u8",
            HttpContext.RequestAborted
        );
        if (!meta.Success)
        {
            return BadRequest(meta.Message);
        }

        if (request.Seconds > meta.Data?.Duration || request.Seconds < 0)
        {
            return BadRequest("时间超出范围");
        }

        var screenshot = await videoCloudService.VideoScreenshotAsync(
            $"/Video/{video.Title}/1080p.m3u8",
            TimeSpan.FromSeconds(request.Seconds),
            HttpContext.RequestAborted
        );
        if (!screenshot.Success)
        {
            return BadRequest(screenshot.Message);
        }

        return Ok(screenshot.Data);
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个基于 ASP.NET Core 的视频管理 WebAPI 控制器,提供了视频和弹幕相关的功能接口。

整体架构

控制器定义

  • 使用主构造函数(Primary Constructor)注入5个服务依赖
  • 路由前缀:/api/video
  • 包含视频管理、弹幕管理、播放统计等核心功能

主要功能接口

1. GetVideos - 获取视频列表

[HttpGet]
  • 公开接口,无需授权
  • 通过 videoShardService 获取视频列表
  • 返回 List<VmVideo>

2. Danmaku (POST) - 发送弹幕

[HttpPost("danmaku")]
  • 接收弹幕数据并转换为 VmBarrage 对象
  • 关键字段映射:颜色、分组(视频ID)、位置、时间等
  • 保存到数据库并返回弹幕对象

3. Danmaku (GET) - 获取视频弹幕

[HttpGet("danmaku")]
  • 根据视频ID获取弹幕列表
  • 颜色转换:将整数颜色值转为 RGB 格式字符串(如 rgb(255,0,0)

4. GetVideoListDetails - 获取视频详情列表

[HttpGet("details"), Authorize(Policy = "System")]
  • 需要系统权限
  • 返回完整视频详情列表

5. SaveInformation - 保存视频信息

[HttpPost, Authorize(Policy = "System")]
  • 需要系统权限
  • 保存视频信息后清除缓存(通过 Tag 方式)
  • 返回 204 NoContent

6. AddPlayer - 增加播放次数

[HttpPatch("play/{id}")]
  • 公开接口
  • 统计视频播放量

7. GetVideoMetaData - 获取视频元数据

[HttpGet("meta/{id}"), Authorize(Policy = "System")]
  • 需要系统权限
  • 实现缓存策略:90天缓存周期
  • 从云服务获取 m3u8 视频元数据(分辨率、时长等)
  • 缓存 Key 格式:Video-Meta:{id}

8. SetVideoScreenshot - 设置视频缩略图

[HttpPatch("screenshot/{id}"), Authorize(Policy = "System")]
  • 需要系统权限
  • 验证时间参数是否在视频时长范围内
  • 调用云服务在指定时间点截图
  • 返回截图数据

技术特点

  1. 依赖注入:使用 C# 12 主构造函数语法简化依赖注入
  2. 缓存策略:使用 FusionCache 实现分布式缓存
  3. 授权控制:部分接口需要 "System" 策略授权
  4. 响应规范:使用 ProducesResponseType 明确 API 响应类型
  5. 分层架构:控制器调用 Service 层处理业务逻辑
  6. 取消令牌:支持请求取消(HttpContext.RequestAborted

业务场景

适用于在线视频平台的后台管理,包括:

  • 视频内容管理
  • 实时弹幕系统
  • 播放数据统计
  • 视频云存储集成(基于 m3u8 流媒体协议)
评论加载中...