using Dpz.Core.Backup;
using Dpz.Core.Service.Mediator.Features.Storage.Commands;
using Dpz.Core.WebApi.Models.Request;
using Microsoft.AspNetCore.RateLimiting;

namespace Dpz.Core.WebApi.Controllers;

/// <summary>
/// 系统设置
/// </summary>
[ApiController, Route("api/[controller]")]
public class SysController(
    IRestoreService restoreService,
    ILogger<SysController> logger,
    IMediator mediator,
    IConfiguration configuration
) : ControllerBase
{
    /// <summary>
    /// 还原最新数据
    /// </summary>
    /// <returns></returns>
    [HttpPost("restore"), Authorize(nameof(Permissions.System))]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    public async Task<IActionResult> Restore([FromForm] RestoreRequest request)
    {
        try
        {
            var restoreFilePath = await SaveAsAsync(request.RestoreFile);
            await restoreService.RestoreAsync(
                request.ConnectionString,
                restoreFilePath,
                request.FilePassword
            );
        }
        catch (Exception e)
        {
            logger.LogError(e, "restore fail");
            return BadRequest();
        }

        return NoContent();
    }

    private static async Task<string> SaveAsAsync(IFormFile file)
    {
        if (file.Length <= 0)
        {
            throw new ArgumentException($"{nameof(file)} length must be greater than 0");
        }

        var restorePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "restore");
        if (!Directory.Exists(restorePath))
        {
            Directory.CreateDirectory(restorePath);
        }

        await using var stream = file.OpenReadStream();
        var restoreFilePath = Path.Combine(restorePath, $"{DateTime.Now:yyyyMMdd}.zip");
        await using var fileStream = new FileStream(
            restoreFilePath,
            FileMode.Create,
            FileAccess.Write,
            FileShare.Write,
            1 << 12
        );
        await stream.CopyToAsync(fileStream);
        await fileStream.FlushAsync();
        return restoreFilePath;
    }

    /// <summary>
    /// 接收腾讯云媒体处理回调
    /// </summary>
    [HttpPost("receive/tencent/media-process")]
    [AllowAnonymous]
    [EnableRateLimiting("callback")]
    [Consumes("application/json", "application/xml", "text/xml")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    [ProducesResponseType(StatusCodes.Status401Unauthorized)]
    [ProducesResponseType(StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> ReceiveTencentMediaProcessCallback(
        [FromQuery] string token = "",
        CancellationToken cancellationToken = default
    )
    {
        if (!IsCallbackTokenValid(token))
        {
            return Unauthorized("token invalid");
        }

        var result = await mediator.Send(
            new TencentMediaProcessCallbackRequest { Request = Request },
            cancellationToken
        );
        if (result.Success)
        {
            return Ok();
        }

        var statusCode = result.Code is >= 400 and <= 599
            ? result.Code
            : StatusCodes.Status400BadRequest;
        return StatusCode(statusCode, result);
    }

    /// <summary>
    /// 校验回调令牌
    /// </summary>
    private bool IsCallbackTokenValid(string token)
    {
        var configuredToken = configuration["TencentCallbackToken"];
        if (string.IsNullOrWhiteSpace(configuredToken) || string.IsNullOrWhiteSpace(token))
        {
            return false;
        }

        var configuredHash = SHA256.HashData(Encoding.UTF8.GetBytes(configuredToken));
        var providedHash = SHA256.HashData(Encoding.UTF8.GetBytes(token));
        return CryptographicOperations.FixedTimeEquals(configuredHash, providedHash);
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个 ASP.NET Core Web API 控制器,名为 SysController,用于处理系统级别的操作。以下是详细解释:

控制器概述

  • 路由: api/Sys
  • 用途: 提供系统设置相关的 API 端点
  • 依赖注入: 使用主构造函数注入了4个依赖项
    • IRestoreService: 数据恢复服务
    • ILogger<SysController>: 日志记录器
    • IMediator: 中介者模式实现(可能用于 CQRS)
    • IConfiguration: 配置访问接口

主要功能

1. Restore 方法 - 数据还原功能

[HttpPost("restore"), Authorize(nameof(Permissions.System))]
  • 端点: POST /api/Sys/restore
  • 权限: 需要系统权限认证
  • 功能: 从上传的备份文件还原数据
  • 流程:
    1. 接收包含备份文件的表单数据
    2. 将上传的文件保存到本地 restore 目录
    3. 调用 restoreService 执行还原操作
    4. 异常处理:记录日志并返回 400 错误

2. SaveAsAsync 方法 - 文件保存辅助方法

  • 私有静态方法,用于保存上传的文件
  • 功能:
    • 验证文件大小必须大于 0
    • 创建 restore 目录(如果不存在)
    • 将文件保存为 yyyyMMdd.zip 格式(按日期命名)
    • 使用 4KB 缓冲区进行文件流操作

3. ReceiveTencentMediaProcessCallback 方法 - 腾讯云媒体处理回调接口

[HttpPost("receive/tencent/media-process")]
[AllowAnonymous]
[EnableRateLimiting("callback")]
  • 端点: POST /api/Sys/receive/tencent/media-process
  • 特点:
    • 允许匿名访问(不需要认证)
    • 启用了名为 "callback" 的限流策略
    • 支持多种内容类型:JSON、XML
  • 安全机制: 通过查询参数 token 进行身份验证
  • 流程:
    1. 验证回调令牌(token)
    2. 使用 Mediator 模式处理请求
    3. 根据处理结果返回相应的 HTTP 状态码

4. IsCallbackTokenValid 方法 - 令牌验证

  • 安全实现:
    • 从配置中读取预设的令牌
    • 使用 SHA256 对令牌进行哈希
    • 使用 CryptographicOperations.FixedTimeEquals 进行恒定时间比较,防止时序攻击
  • 验证逻辑: 只有当配置的令牌和提供的令牌都非空且哈希值相等时才通过验证

技术亮点

  1. 使用主构造函数 (C# 12 特性) 简化依赖注入
  2. 异步编程: 所有 I/O 操作都使用 async/await
  3. 安全性考虑:
    • 使用恒定时间比较防止时序攻击
    • SHA256 哈希验证
  4. 限流保护: 为回调接口添加限流策略
  5. 结构化日志: 使用 ILogger 记录错误
  6. 中介者模式: 使用 MediatR 处理复杂业务逻辑

潜在的改进建议

  1. SaveAsAsync 方法中的文件名可能会覆盖同一天的多次上传
  2. 可以添加文件大小限制验证
  3. 考虑添加更详细的错误信息返回给客户端
评论加载中...