using System.Drawing;
using Dpz.Core.Shard.Service;
using Dpz.Core.Web.Models.Video;
namespace Dpz.Core.Web.Controllers;
public class VideoController(
IBarrageService barrageService,
ILogger<VideoController> logger,
IVideoShardService videoShardService
) : Controller
{
public async Task<IActionResult> Index()
{
this.SetTitle("视频");
var videos = await videoShardService.GetVideosAsync();
return View(videos);
}
[Route("video/watch/{id}.html")]
public async Task<IActionResult> Watch(string id)
{
var video = await videoShardService.GetVideoAsync(id);
if (video == null)
{
return NotFound();
}
this.SetTitle(video.VideoTitle ?? "");
return View(video);
}
[HttpPatch("play/{id}")]
public async Task<IActionResult> Play(string id)
{
await videoShardService.AddPlayAsync(id);
return NoContent();
}
[Route("history/chat/v3/"), HttpGet]
public async Task<IActionResult> HistoryChat(string id)
{
var dbDanmaku = await barrageService.GetGroupBarragesAsync(id);
var allDanmaku = dbDanmaku.Select(x =>
{
var result = int.TryParse(x.Color, out var color);
return new object[]
{
x.Time,
x.Position,
result ? color : x.Color ?? "#FFF",
"阿胖",
x.Text ?? "",
};
});
return Json(new { code = 0, data = allDanmaku });
}
[Route("history/danmaku/{id}"), HttpGet]
public async Task<IActionResult> HistoryDanmaku(string id)
{
var dbDanmaku = await barrageService.GetGroupBarragesAsync(id);
var allDanmaku = dbDanmaku
.OrderBy(x => x.Time)
.Select(x =>
{
var result = int.TryParse(x.Color, out var color);
var colorRgb = Color.FromArgb(color);
return new
{
color = result ? $"rgb({colorRgb.R},{colorRgb.G},{colorRgb.B})" : x.Color,
text = x.Text,
time = x.Time,
type = x.Position switch
{
0 => "scroll",
1 => "top",
2 => "bottom",
_ => "",
},
};
})
.ToList();
return Json(allDanmaku);
}
[Route("history/danmaku"), HttpPost]
public async Task<IActionResult> HistoryDanmaku([FromBody] VideoDanmaku2 danmaku)
{
if (string.IsNullOrWhiteSpace(danmaku.Text))
{
return Json(new ResultInfo(true));
}
var color = ColorTranslator.FromHtml("#FF0000");
var vmBarrage = new VmBarrage
{
Color = color.ToArgb().ToString(),
Group = danmaku.Id,
Position = danmaku.Type switch
{
"scroll" => 0,
"top" => 1,
"bottom" => 2,
_ => 0,
},
SendTime = DateTime.Now,
Size = 0,
Text = danmaku.Text,
Time = danmaku.Time,
};
await barrageService.AddBarrageAsync(vmBarrage);
return Json(new ResultInfo(true));
}
[Route("history/chat/v3/"), HttpPost]
public async Task<IActionResult> HistoryChat([FromBody] VideoDanmaku danmaku)
{
if (string.IsNullOrWhiteSpace(danmaku.Text))
{
return Json(new { code = 0, data = 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 Json(new { code = 0, data = danmaku });
}
[Route("history/danmaku/v2/{id}"), HttpGet]
public async Task<IActionResult> HistoryDanmakuV2(string id)
{
var dbDanmaku = await barrageService.GetGroupBarragesAsync(id);
var data = dbDanmaku
.Select(x =>
{
var result = int.TryParse(x.Color, out var color);
var colorRgb = Color.FromArgb(color);
return new
{
text = x.Text,
time = x.Time,
color = result ? $"rgb({colorRgb.R},{colorRgb.G},{colorRgb.B})" : x.Color,
border = false,
mode = x.Position switch
{
0 => 0,
1 => 1,
2 => 1,
_ => 0,
},
};
})
.ToList();
return Json(data);
}
[Route("send/danmaku/v2"), HttpPost]
public async Task<IActionResult> SendDanmaku([FromBody] SendDanmaku danmaku)
{
if (!ModelState.IsValid)
{
return Json(new ResultInfo(false, ModelState));
}
Color color;
try
{
color = ColorTranslator.FromHtml(danmaku.Color);
}
catch (Exception e)
{
color = ColorTranslator.FromHtml("#FFFFFF");
logger.LogError(e, "color translator fail,in color:{Color}", danmaku.Color);
}
var vmBarrage = new VmBarrage
{
Color = color.ToArgb().ToString(),
Group = danmaku.Id,
Position = danmaku.Mode,
SendTime = DateTime.Now,
Size = 0,
Text = danmaku.Text,
Time = danmaku.Time,
};
await barrageService.AddBarrageAsync(vmBarrage);
return Json(new ResultInfo(true));
}
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
这是一段 ASP.NET MVC 控制器代码(VideoController),用于处理视频页面与弹幕(barrage/danmaku)相关的请求。总体上依赖以下服务/组件:
- IBarrageService:用于获取/添加弹幕数据。
- IVideoShardService:用于获取视频列表/单个视频、增加播放量等。
- ILogger:记录日志。
- 使用 System.Drawing 处理颜色(Color、ColorTranslator)。
下面按方法逐一解释其功能与返回的数据结构,并补充注意事项与可能的问题。
总体
- 控制器继承自 Controller,使用路由特性为各个动作定义 URL。
- 多个路由对同一路径分别用 GET/POST(或其他方法)实现读写操作(例如 history/chat/v3/ 既有 GET 也有 POST)。
- 弹幕在内部以 VmBarrage(包含 Color、Group、Position、Text、Time 等)存储/传递;颜色在数据库里以字符串形式存储,这里有时是颜色的 ARGB 整数字符串(ToArgb().ToString()),有时可能是 HTML 颜色字符串。
具体动作
- Index()
- 路径默认(控制器 Index)。
- 从 videoShardService 获取视频列表(GetVideosAsync),并把结果传到视图。
- 同时调用 this.SetTitle("视频")(设置页面标题,可能是某个扩展方法)。
- Watch(string id)
- 路径:video/watch/{id}.html
- 根据 id 获取单个视频(GetVideoAsync)。若为空返回 404 NotFound。
- 将视频的标题设置为页面标题,并返回视图(传入 video 模型)。
- Play(string id)
- 路径:PATCH play/{id}
- 调用 videoShardService.AddPlayAsync(id) 增加播放计数(或记录播放),然后返回 204 NoContent。
- HistoryChat (GET) — [Route("history/chat/v3/"), HttpGet]
- 根据 id 从 barrageService 获取该组弹幕(GetGroupBarragesAsync)。
- 将每条弹幕投影为 object[] 数组,内容为 [Time, Position, Color, "阿胖", Text]:
- Color 字段先尝试 int.TryParse(x.Color, out color),若能解析则传色值(int),否则传 x.Color 或 "#FFF"(默认)。
- 固定用户名为 "阿胖"(写死)。
- 返回 JSON:{ code = 0, data = allDanmaku }。
- HistoryDanmaku (GET) — [Route("history/danmaku/{id}"), HttpGet]
- 获取该组弹幕,按 x.Time 排序。
- 将每条弹幕映射为对象 { color, text, time, type }:
- x.Color 尝试解析为 int(ARGB),用 Color.FromArgb(color) 得到 RGB,然后格式化为 "rgb(R,G,B)";若解析失败则直接使用 x.Color。
- type 根据 Position 映射为 "scroll"/"top"/"bottom"(0/1/2)。
- 返回 JSON 列表(List)。
- HistoryDanmaku (POST) — [Route("history/danmaku"), HttpPost]
- 接收 VideoDanmaku2(假定包含 Id、Text、Type、Time 等)。
- 如果 Text 为空白,直接返回 ResultInfo(true)(看起来表示成功,但不保存弹幕)。
- 否则构造 VmBarrage:
- 默认颜色固定为 "#FF0000"(用 ColorTranslator.FromHtml,然后 ToArgb().ToString() 存入 Color 字段)。
- Group = danmaku.Id,Position 根据字符串 Type 转为 0/1/2,SendTime = DateTime.Now 等。
- 调用 barrageService.AddBarrageAsync 添加弹幕,返回 ResultInfo(true)。
- HistoryChat (POST) — [Route("history/chat/v3/"), HttpPost]
- 接收 VideoDanmaku(含 Color、Id、Type(可能为枚举或数值)、Text、Time)。
- 如果 Text 为空白,直接返回 { code = 0, data = danmaku }。
- 构造 VmBarrage,将 danmaku.Color.ToString() 作为存储的 Color(注意:这里直接存传入的 Color),Position = (int)danmaku.Type,SendTime = Now,等。
- 添加弹幕后返回 { code = 0, data = danmaku }。
- HistoryDanmakuV2 (GET) — [Route("history/danmaku/v2/{id}"), HttpGet]
- 获取该组弹幕并映射为客户端 v2 格式:
- 字段:text、time、color(同样尝试解析 ARGB int 并格式化为 "rgb(...)",否则直接用原字符串)、border = false 固定、mode 映射(0/1)。
- Position 0 -> mode 0,1/2 -> mode 1(将 top/bottom 归为同一 mode)。
- 返回 JSON 列表。
- SendDanmaku (POST) — [Route("send/danmaku/v2"), HttpPost]
- 接收 SendDanmaku(有 Id、Text、Time、Color、Mode 等),先检查 ModelState,若无效返回错误信息 ResultInfo(false, ModelState)。
- 尝试用 ColorTranslator.FromHtml(danmaku.Color) 转换颜色字符串,若失败记录日志并默认白色 (#FFFFFF)。
- 构造 VmBarrage,将颜色保存为 color.ToArgb().ToString(),Group = danmaku.Id,Position = danmaku.Mode 等。
- 保存弹幕并返回 ResultInfo(true)。
实现细节与注意事项(可能的问题)
- 颜色处理:
- 数据库中 Color 字段在不同地方既可能是 HTML 颜色字符串(如 "#FF0000"),也可能是 ARGB 整数字符串(ToArgb().ToString())。代码中有多种解析方式(int.TryParse -> Color.FromArgb,或 ColorTranslator.FromHtml),混合使用可能导致不一致或解析失败。
- 在某些方法中,int.TryParse 解析失败时直接返回 x.Color(未保证是客户端想要的格式)。在少数地方返回的默认颜色为 "#FFF" 或 "#FFFFFF",不统一。
- 在服务器端使用 System.Drawing(Color)在跨平台(Linux)环境下可能有兼容性/依赖问题,生产中通常建议使用轻量的颜色处理库或直接操作字符串。
- 返回结构不完全一致:
- 不同接口(v1/v2/chat)返回的 JSON 结构格式不同,客户端需要对应适配。
- 部分 POST 接口对空文本直接返回成功(不保存)——这可能是设计决定,但也可能导致前端误判(应返回 400/错误信息更直观)。
- 安全性与防刷:
- 接口允许匿名发送弹幕(代码中没有看到身份校验或频率限制),需要考虑反垃圾、验证、速率限制、内容过滤等。
- 日志与错误处理:
- 在 ColorTranslator.FromHtml 捕获异常并记录了错误,比较好。但其他地方(如 Color.FromArgb 传入非法 int)没有额外异常处理,按现状 Color.FromArgb 接受 int 通常不会抛异常,但若传入的 int 语义错误可能导致颜色异常。
- 路由/方法设计:
- 同一路径根据 HTTP 方法区分 GET/POST(如 history/chat/v3/)是常见做法,但要注意前端调用时使用正确的 HTTP 动词。
- Play 使用 PATCH,很贴切用于“局部更新”(增加播放数)。
总结
- 该控制器主要实现视频列表与观看页面,以及多种弹幕的读取(不同格式/版本)与发送接口,并支持增加播放量功能。
- 关键流程是:从视频/弹幕服务读取数据,转换为客户端需要的 JSON 或视图模型,或接收前端弹幕请求并持久化为 VmBarrage。
- 需留意颜色格式统一、跨平台 System.Drawing 问题、输入验证与防刷/内容审核等实际生产考虑。
如需,我可以:
- 绘制每个接口的示例请求/响应 JSON;
- 指出并给出改善颜色处理、统一返回格式与验证的具体代码改进建议。需要哪个请告诉我。
评论加载中...