节点在线、应用在线、配置在线使用令牌查询
大石头 authored at 2021-12-16 19:49:30
5.05 KiB
Stardust
using System.ComponentModel;
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NewLife;
using NewLife.Remoting;
using NewLife.Remoting.Extensions;
using Stardust.Data.Deployment;
using Stardust.Extensions;
using Stardust.Server.Services;
using Stardust.Storages;

namespace Stardust.Server.Controllers;

/// <summary>魔方前端数据接口</summary>
[DisplayName("数据接口")]
[ApiController]
[ApiFilter]
[Route("{controller}/{action}")]
public class CubeController(IFileStorage fileStorage, StarServerSetting setting, DownloadTokenService downloadTokenService) : ControllerBase
{
    #region 附件
    /// <summary>校验附件访问。ValidateAttachment开启时,公开分类可匿名,其余需携带短时下载令牌(安全V-11)</summary>
    /// <param name="att">附件</param>
    private void CheckAttachment(Attachment att)
    {
        if (!setting.ValidateAttachment) return;

        var cat = att.Category;
        if (!cat.IsNullOrEmpty())
        {
            var cats = (setting.PublicAttachmentCategories + "").Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
            if (cats.Any(e => e.EqualIgnoreCase(cat))) return;
        }

        var token = HttpContext.Request.Query["token"] + "";
        if (!downloadTokenService.Valid(att.Id, token))
            throw new ApiException(ApiCode.Unauthorized, L.T("Not authorized to download the attachment, or the download token is invalid or expired."));
    }

    private async Task<(Attachment att, String filePath)> GetFile(String id)
    {
        if (id.IsNullOrEmpty()) throw new ApiException(ApiCode.NotFound, L.T("Invalid attachment ID"));

        // 去掉仅用于装饰的后缀名
        var p = id.IndexOf('.');
        if (p > 0) id = id[..p];

        var att = Attachment.FindById(id.ToLong());
        if (att == null) throw new ApiException(ApiCode.NotFound, L.T("Attachment information not found"));

        // 安全V-11:附件下载鉴权(公开分类 / 短时下载令牌)
        CheckAttachment(att);

        //var set = StarServerSetting.Current;

        // 如果附件不存在,则抓取
        var filePath = att.GetFilePath(setting.UploadPath);
        if (!filePath.IsNullOrEmpty() && !System.IO.File.Exists(filePath) && setting.FileStorageFetch)
        {
            // 如果本地文件不存在,则从分布式文件存储获取
            await fileStorage.RequestFileAsync(att.Id, att.FilePath, "file not found");
            await Task.Delay(5_000);
        }
        if (filePath.IsNullOrEmpty() || !System.IO.File.Exists(filePath))
        {
            var url = att.Source;
            if (url.IsNullOrEmpty()) throw new ApiException(ApiCode.NotFound, L.T("Attachment file not found"));

            var rs = await att.Fetch(url, setting.UploadPath);
            if (!rs) throw new ApiException(ApiCode.NotFound, L.T("Failed to fetch the attachment remotely"));

            filePath = att.GetFilePath(setting.UploadPath);
        }
        if (filePath.IsNullOrEmpty() || !System.IO.File.Exists(filePath)) throw new ApiException(ApiCode.NotFound, L.T("Attachment file not found"));

        return (att, filePath);
    }

    /// <summary>访问图片</summary>
    /// <param name="id">附件编号</param>
    /// <returns></returns>
    [AllowAnonymous]
    public async Task<ActionResult> Image(String id)
    {
        if (id.IsNullOrEmpty()) return NotFound(L.T("Invalid attachment ID"));

        try
        {
            var (att, filePath) = await GetFile(id);

            att.Downloads++;
            att.LastDownload = DateTime.Now;
            att.SaveAsync(5_000);

            // 设置文件哈希相关响应头
            Response.SetFileHashHeaders(att.Hash);

            if (!att.ContentType.IsNullOrEmpty())
                return PhysicalFile(filePath, att.ContentType, att.FileName);
            else
                return PhysicalFile(filePath, "image/png", att.FileName);
        }
        catch (ApiException ex) when (ex.Code == 404)
        {
            return NotFound(ex.Message);
        }
    }

    /// <summary>访问附件</summary>
    /// <param name="id">附件编号</param>
    /// <returns></returns>
    [AllowAnonymous]
    public async Task<ActionResult> File(String id)
    {
        if (id.IsNullOrEmpty()) return NotFound(L.T("Invalid attachment ID"));

        try
        {
            var (att, filePath) = await GetFile(id);

            att.Downloads++;
            att.LastDownload = DateTime.Now;
            att.SaveAsync(5_000);

            // 设置文件哈希相关响应头
            Response.SetFileHashHeaders(att.Hash);

            if (!att.ContentType.IsNullOrEmpty() && !att.ContentType.EqualIgnoreCase("application/octet-stream"))
                return PhysicalFile(filePath, att.ContentType, att.FileName);
            else
                return PhysicalFile(filePath, "application/octet-stream", att.FileName, true);
        }
        catch (ApiException ex) when (ex.Code == 404)
        {
            return NotFound(ex.Message);
        }
    }
    #endregion
}