使用JsonConverter处理上传文件的路径

时间:2022-12-23 11:13:53

场景

我们上传一个文件,把文件保存到服务器上,会有一个明确的物理路径,由于需要从前端访问这个文件,还需要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" 。
这样如果域名变了,修改一下配置的前缀即可。