使用JsonConverter處理上傳檔案的路徑

2022-12-23 12:00:28

場景

我們上傳一個檔案,把檔案儲存到伺服器上,會有一個明確的物理路徑,由於需要從前端存取這個檔案,還需要web伺服器中的一個虛擬路徑。這個虛擬路徑的儲存會有一個問題,我們應該在資料庫裡存什麼?是帶域名的全路徑,還是相對於web根目錄的相對路徑?

現在很多架構都是前後分離的,所以前端存取的url是全路徑比較好,不像之前前後都是一個專案中,資料庫存相對路徑,前端也使用相對路徑存取沒有問題。
如果存全路徑,域名更換的時候就比較麻煩,需要手動把資料庫裡的資料替換一下。如果存相對路徑,返回前端的時候需要手動的補全路徑,也不是太好。

解決方法

我們可以使用JsonConverter 來自動處理一下,具體方法是儲存相對路徑,返回的時候自動加上字首組成全路徑。

/// <summary>
/// 處理圖片檔案字首,資料庫中存相對路徑即可
/// </summary>
public class JsonUrlPrefixConverter : JsonConverter
{
    private string urlPrefix;
    public JsonUrlPrefixConverter()
    {
        urlPrefix ="htttp://www.abc.com"; //這裡字首可以做成設定,換域名時改一下設定即可
    }

    public JsonUrlPrefixConverter(string flag)
    {
        if (flag == "something")
        {
            urlPrefix = "htttp://www.123.com";
        }
    }

    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        if (value != null)
        {
            if (value is IEnumerable<string> arr)
            {
                writer.WriteStartArray();
                foreach (var str in arr.Select(x =>
                             x.StartsWith("http", StringComparison.OrdinalIgnoreCase) ? x : urlPrefix + x))
                {
                    writer.WriteValue(str);
                }
                writer.WriteEndArray();
            }
            else if (value is string str && !str.StartsWith("http", StringComparison.OrdinalIgnoreCase))
            {
                if (str.HasValue())
                    str = urlPrefix + str;
                writer.WriteValue(str);
            }
            else
            {
                writer.WriteValue(value);
            }
        }
        else
        {
            writer.WriteValue(value);
        }
    }

    public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
        JsonSerializer serializer)
    {
        if (objectType == typeof(string))
        {
            var str = serializer.Deserialize<string>(reader);
            return str;
        }

        if (objectType == typeof(List<string>))
        {
            var list = serializer.Deserialize<List<string>>(reader);

            return list.Select(x => x.Replace(urlPrefix, "")).ToList();
        }

        if (objectType == typeof(string[]))
        {
            var arr = serializer.Deserialize<string[]>(reader);
            return arr.Select(x => x.Replace(urlPrefix, "")).ToArray();
        }

        return reader.Value;
    }

    public override bool CanConvert(Type objectType)
    {
        //請自覺用於 string[]、string、List<string>型別
        return true;
    }
}

使用

JsonUrlPrefixConverter 使用了 Newtonsoft.Json,所以在新的 dotnet 專案中需要指定一下序列話還使用 Newtonsoft.Json

builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
    //忽略迴圈參照問題
    options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});

然後在上傳路徑的欄位上加JsonUrlPrefixConverter 便可以自動處理。
如頭像欄位:

    /// <summary>
    /// 頭像
    /// </summary>
    [JsonConverter(typeof(JsonUrlPrefixConverter))]
    public string Avatar { get; set; } = "";

如前端儲存時傳給後端的是 "http://www.abc.com/upload/avatar.jpg" ,經過 JsonUrlPrefixConverter 處理,會得到 "/upload/avatar.jpg" 儲存到資料庫,當從後端返回到前端的時候,會自動加上字首,又變成了 "http://www.abc.com/upload/avatar.jpg" 。
這樣如果域名變了,修改一下設定的字首即可。