using Dpz.Core.Infrastructure.Imaging;
using Dpz.Core.Public.ViewModel.Request;
using Dpz.Core.Public.ViewModel.Response;

namespace Dpz.Core.WebApi.Controllers;

/// <summary>
/// 音乐管理
/// </summary>
[ApiController, Route("api/[controller]")]
public class MusicController(
    IMusicService musicService,
    IObjectStorageOperation objectStorageService,
    ILogger<MusicController> logger,
    IImageFormatDetector imageFormatDetector,
    IFusionCache fusionCache
) : ControllerBase
{
    /// <summary>
    /// 音乐列表
    /// </summary>
    /// <returns></returns>
    [HttpGet]
    [ProducesResponseType<List<MusicResponse>>(StatusCodes.Status200OK)]
    public async Task<IActionResult> GetMusics([FromQuery] MusicQueryParameterDto parameter)
    {
        var musics = await musicService.GetPagesAsync(
            parameter.PageIndex,
            parameter.PageSize,
            parameter.Title
        );
        musics.AddPaginationMetadata(Response.Headers);
        return Ok(musics);
    }

    /// <summary>
    /// 单个音乐
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpGet("{id}")]
    [ProducesResponseType<MusicResponse>(StatusCodes.Status200OK)]
    public async Task<IActionResult> GetMusic(string id)
    {
        var music = await musicService.GetAsync(id);
        return Ok(music);
    }

    /// <summary>
    /// 获取所有分组
    /// </summary>
    /// <returns></returns>
    [HttpGet("groups")]
    [ProducesResponseType<List<string>>(StatusCodes.Status200OK)]
    public async Task<ActionResult<List<string>>> GetGroups()
    {
        var groups = await musicService.GetGroupsAsync();
        return Ok(groups);
    }

    /// <summary>
    /// 获取歌词
    /// </summary>
    /// <param name="id"></param>
    /// <param name="httpClientFactory"></param>
    /// <returns></returns>
    [HttpGet("lrc/{id}")]
    [ProducesResponseType<string>(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    public async Task<IActionResult> GetLyric(
        string id,
        [FromServices] IHttpClientFactory httpClientFactory
    )
    {
        var lrcCacheKey = $"music-lrc:{id}";
        var cacheValue = await fusionCache.TryGetAsync<string>(lrcCacheKey);
        if (cacheValue.HasValue)
        {
            return Content(cacheValue.Value, "application/lrc;charset=utf-8;");
        }
        if (string.IsNullOrEmpty(id))
        {
            return NotFound();
        }

        var music = await musicService.GetAsync(id);
        if (music == null)
        {
            return NotFound();
        }

        if (string.IsNullOrEmpty(music.LyricUrl))
        {
            return NoContent();
        }

        string lrc;
        var httpClient = httpClientFactory.CreateClient("edge");
        try
        {
            var request = new HttpRequestMessage(HttpMethod.Get, music.LyricUrl);
            var response = await httpClient.SendAsync(request);
            if (!response.IsSuccessStatusCode)
            {
                return NoContent();
            }
            lrc = await response.Content.ReadAsStringAsync();
        }
        catch (Exception e)
        {
            logger.LogError(e, "get lrc fail");
            lrc = music.LyricContent ?? "";
        }
        return Content(lrc, "application/lrc;charset=utf-8;");
    }

    /// <summary>
    /// 更改音乐信息
    /// </summary>
    /// <param name="model"></param>
    /// <returns></returns>
    [HttpPatch("information"), Authorize(Policy = "System")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> EditInformation([FromForm] ChangeMusicRequest model)
    {
        var information = new EditMusicInformationRequest
        {
            Id = model.Id,
            Lyric = model.Lyric,
            Cover = model.Cover,
            Group = model.Group,
            UploadLyric = async (bytes, music) =>
            {
                if (!string.IsNullOrEmpty(music.LyricUrl))
                {
                    await objectStorageService.DeleteAsync(music.LyricUrl);
                }

                return await HandleUploadAsync(bytes, $"{Guid.NewGuid():N}.lrc,", "lyric");
            },
            UploadCover = async (bytes, music) =>
            {
                if (!string.IsNullOrEmpty(music.CoverUrl))
                {
                    await objectStorageService.DeleteAsync(music.CoverUrl);
                }

                string coverExt;
                try
                {
                    var info = imageFormatDetector.Detect(bytes);
                    coverExt = info.IsKnown ? info.DefaultExtension : "jpg";
                }
                catch (Exception ex)
                {
                    coverExt = "jpg";
                    logger.LogError(ex, "get image format fail");
                }
                return await HandleUploadAsync(bytes, $"{Guid.NewGuid():N}.{coverExt}", "cover");
            },
        };
        await musicService.EditMusicInformationAsync(information);
        return NoContent();
    }

    /// <summary>
    /// 新增音乐
    /// </summary>
    /// <param name="upload"></param>
    /// <returns></returns>
    [HttpPost, Authorize(Policy = "System")]
    [RequestSizeLimit(104_857_600)]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> Save([FromForm] UploadMusicRequest upload)
    {
        var from = string.IsNullOrWhiteSpace(upload.From) ? "Web Api" : upload.From;
        var request = new MusicUploadActionRequest
        {
            From = from,
            Lyric = upload.Lyrics,
            Cover = upload.Cover,
            Music = upload.Music,
            UploadMusic = async (bytes, filename) => await HandleUploadAsync(bytes, filename, null),
            UploadCover = async (bytes, filename) =>
                await HandleUploadAsync(bytes, filename, "cover"),
            UploadLyric = async (bytes, filename) =>
                await HandleUploadAsync(bytes, filename, "lyric"),
            Group = upload.Group,
        };
        await musicService.SaveAsync(request);
        return NoContent();
    }

    [NonAction]
    private async Task<string> HandleUploadAsync(byte[] bytes, string filename, string? type)
    {
#if DEBUG
        var list = new List<string> { "Test", "music" };
#else
        var list = new List<string> { "music" };
#endif
        if (!string.IsNullOrEmpty(type))
        {
            list.Add(type);
        }

        var result = await objectStorageService.UploadAsync(bytes, list, filename);
        return result.Url;
    }

    /// <summary>
    /// 删除音乐
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    [HttpDelete("{id}"), Authorize(Policy = "System")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> Delete(string id)
    {
        await musicService.DeleteAsync(
            id,
            async x => await objectStorageService.DeleteMusicAsync(x)
        );
        return NoContent();
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个 ASP.NET Core Web API 控制器,用于管理音乐相关的操作。以下是详细解释:

控制器概述

MusicController - 音乐管理控制器,使用主构造函数(C# 12特性)注入了5个依赖项:

  • IMusicService - 音乐业务服务
  • IObjectStorageOperation - 对象存储服务
  • ILogger - 日志记录器
  • IImageFormatDetector - 图片格式检测器
  • IFusionCache - 融合缓存服务

API 接口

1. GetMusics - 获取音乐列表

[HttpGet]
  • 支持分页查询(页码、页大小、标题筛选)
  • 返回音乐列表,并将分页元数据添加到响应头

2. GetMusic - 获取单个音乐

[HttpGet("{id}")]
  • 根据ID获取音乐详情

3. GetGroups - 获取所有分组

[HttpGet("groups")]
  • 返回所有音乐分组列表

4. GetLyric - 获取歌词

[HttpGet("lrc/{id}")]
  • 先尝试从缓存中获取歌词
  • 若缓存未命中,则从数据库获取音乐信息
  • 通过HTTP客户端从外部URL下载歌词文件
  • 如果下载失败,使用数据库存储的歌词内容
  • 返回LRC格式的歌词文本

5. EditInformation - 更改音乐信息

[HttpPatch("information"), Authorize(Policy = "System")]
  • 需要系统权限认证
  • 支持更新:歌词、封面、分组
  • 上传歌词时:
    • 删除旧的歌词文件
    • 生成新的GUID文件名上传
  • 上传封面时:
    • 删除旧的封面文件
    • 检测图片格式(默认jpg)
    • 生成新文件名上传

6. Save - 新增音乐

[HttpPost, Authorize(Policy = "System")]
[RequestSizeLimit(104_857_600)] // 限制100MB
  • 需要系统权限认证
  • 限制请求体大小为100MB
  • 支持上传:音乐文件、封面、歌词
  • 记录上传来源(默认"Web Api")
  • 将文件上传到对象存储

7. Delete - 删除音乐

[HttpDelete("{id}"), Authorize(Policy = "System")]
  • 需要系统权限认证
  • 删除音乐记录及关联的对象存储文件

辅助方法

HandleUploadAsync - 处理文件上传

[NonAction]
  • 根据编译模式设置存储路径(Debug模式包含"Test"目录)
  • 将文件上传到对象存储
  • 支持可选的类型分类(如"cover"、"lyric")
  • 返回上传后的文件URL

技术特点

  1. 依赖注入:使用主构造函数简化依赖注入
  2. 授权策略:使用"System"策略保护敏感操作
  3. 缓存机制:歌词使用FusionCache缓存
  4. 文件管理:自动删除旧文件,避免存储浪费
  5. 异常处理:优雅处理HTTP请求失败和图片格式检测失败
  6. 文档化:使用XML注释和ProducesResponseType标注返回类型
评论加载中...