该记录对FW项目Core迁移的一些坑、调整等变动和遇到的问题。

一为了迁移学习记录
二是方便后续迁移工作,尽量避免多次遇到同个文档导致项目迁移工作卡顿和测试、线上问题

迁移常见问题:


1.Core3.1 Json序列化使用Newtonsoft.Json

原因可参考 Net Core 3.1 json序列号时间和emoji格式相关问题
使用的包为Microsoft.AspNetCore.Mvc.NewtonsoftJson
注意项目版本若为3.1,则使用 3.1.18 版本


2.中文返回编码问题,时间格式问题:

这两行设置时间输入和输出格式及中文被编码

1.Json序列化使用Newtonsoft.Json解决特殊字符编码编码问题

2.因为Newtonsoft时间转换IsoDateTimeConverter设置了DefaultDateTimeFormat值得情况下,是直接调用了DateTime.ParseExact,如果前端同时会传2020-01-012020-01-01 12:00:00两种格式的话,前者会无法反序列化
所以要加一个解析类

Startup.cs
services.AddControllers(op =>
{
    //过滤器
    op.Filters.Add<Attribute>();
    op.Filters.Add<Attribute>();
}).AddNewtonsoftJson(options =>
{
    options.SerializerSettings.ContractResolver = new DefaultContractResolver();//移除默认驼峰格式
    options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
    options.SerializerSettings.Converters.Add(new DatetimeJsonConverter());
});
DatetimeJsonConverter.cs
    /// <summary>
    /// Newtonsoft.Json自定义时间格式转换
    /// </summary>
    public class DatetimeJsonConverter : DateTimeConverterBase
    {
        //读取对象,DateTimeFormat为空时就不按DateTimeFormat的格式强制序列化,"2021-08-29"这种只有日期的字符串也能序列化成功
        private static IsoDateTimeConverter isoDateTimeConverterRaad = new IsoDateTimeConverter() { };
        //写对象,输出时间格式为"yyyy-MM-dd HH:mm:ss"
        private static IsoDateTimeConverter isoDateTimeConverterWrite = new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" };

        /// <summary>
        /// 重写输入时的时间序列化格式
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="objectType"></param>
        /// <param name="existingValue"></param>
        /// <param name="serializer"></param>
        /// <returns></returns>
        public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
        {
            object result = null;
            try
            {
                result = isoDateTimeConverterRaad.ReadJson(reader, objectType, existingValue, serializer);
            }
            catch (Exception)
            {
            }
            return result;
        }
        /// <summary>
        /// 重写输出时的时间格式
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="value"></param>
        /// <param name="serializer"></param>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            isoDateTimeConverterWrite.WriteJson(writer, value, serializer);
        }

    }

IsoDateTimeConverter的实现


3.Linux - Docker环境中的时区问题

在部分系统中,应用内部DateTime.Now会获取到UTC时间,在部署到Docker时,需要设置手动显式设置时区
这里可以自己拷一下locltime然后run的时候做映射
-v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro


4.解决URL中的双斜杠问题

Core 3.1不支持多余的斜杠

这边的项目很多其他项目调用,有不确定的多斜杠问题,所以目前解决方式是加管道,移除请求进来时多余的斜杠

需要注意管道添加的位置,最好在进入时处理(放到Configure第一行)

RewriteRouteRule.cs

/// <summary>
/// 重写url规则
/// </summary>
public class RewriteRouteRule
{
    /// <summary>
    /// 处理url中的多斜杠("//")问题
    /// </summary>
    /// <param name="context"></param>
    public static void ReWriteRequests(RewriteContext context)
    {
        var request = context.HttpContext.Request;
        if (request.Path.Value.Contains("//"))
        {
            string[] splitlist = request.Path.Value.Split("/", StringSplitOptions.RemoveEmptyEntries);
            var newpath = "/" + string.Join("/", splitlist);
            request.Path = newpath;
        }
    }
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseRewriter(new RewriteOptions().Add(RewriteRouteRule.ReWriteRequests));
}

5.System.Web.Security哈希

Core没有了System.Web.Security,自己实现差不多得功能就行

Encoding.UTF8.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "SHA1").Substring(0, 8));
换成
Encoding.UTF8.GetBytes(SHA(sKey).Substring(0, 8));

EncryptHelper.cs


/// <summary>
/// 摘要数据
/// </summary>
/// <param name="str">摘要数据</param> 
/// <param name="type"></param> 
/// <returns></returns> 
public static string EncryptHash(string str, string type)
{
    byte[] hashBytes;
    switch (type)
    {
        case "SHA1":
            using (var sha = SHA1.Create())
                hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(str)); break;
        case "MD5":
            using (var md5 = MD5.Create())
                hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(str)); break;
        default: return "";
    }
    var hashStr = BitConverter.ToString(hashBytes);
    return hashStr.Replace("-", "").ToUpper();
}

/// <summary>
/// 加密数据
/// </summary>
/// <param name="encryptStr">加密字符</param>
/// <returns></returns>
public static string Encrypt(string encryptStr)
{
    return Encrypt(encryptStr, "EncryptKey");
}

/// <summary> 
/// 加密数据
/// </summary> 
/// <param name="EncryptStr">加密数据</param> 
/// <param name="sKey">自定义密匙</param> 
/// <returns></returns> 
public static string Encrypt(string encryptStr, string encryptKey)
{
    try
    {
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();
        byte[] inputByteArray;
        inputByteArray = Encoding.Default.GetBytes(encryptStr);
        des.Key = Encoding.UTF8.GetBytes(EncryptHash(encryptKey, "SHA1").Substring(0, 8));
        des.IV = Encoding.UTF8.GetBytes(EncryptHash(encryptKey, "SHA1").Substring(0, 8));
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
        cs.Write(inputByteArray, 0, inputByteArray.Length);
        cs.FlushFinalBlock();
        StringBuilder ret = new StringBuilder();
        foreach (byte b in ms.ToArray())
            ret.AppendFormat("{0:X2}", b);
        return ret.ToString();
    }
    catch (Exception)
    {
        return "加密数据出错,请检查加密数据";
    }
}


public static string Decrypt(string decryptStr)
{
    return Decrypt(decryptStr, "EncryptKey");
}

public static string Decrypt(string decryptStr, string encryptKey)
{
    try
    {
        DESCryptoServiceProvider des = new DESCryptoServiceProvider();
        int len;
        len = decryptStr.Length / 2;
        byte[] inputByteArray = new byte[len];
        int x, i;
        for (x = 0; x < len; x++)
        {
            i = Convert.ToInt32(decryptStr.Substring(x * 2, 2), 16);
            inputByteArray[x] = (byte)i;
        }
        des.Key = Encoding.UTF8.GetBytes(EncryptHash(encryptKey, "SHA1").Substring(0, 8));
        des.IV = Encoding.UTF8.GetBytes(EncryptHash(encryptKey, "SHA1").Substring(0, 8));
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
        cs.Write(inputByteArray, 0, inputByteArray.Length);
        cs.FlushFinalBlock();
        return Encoding.Default.GetString(ms.ToArray());
    }
    catch (Exception)
    {
        return "解密数据出错,请检查密匙.";
    }
}


6.设置统一的Controller

Core对控制器的使用跟FW有些不同,需要对控制器添加 [ApiController] 标签,不然打开后,Swagger会出现下面异常

Action require a unique method/path ...

所以推荐用一个BaseControllerClass去做兜底,而其他控制器全部继承它

一来统一了控制器的父类,好做后续子的调整,二来避免改漏

[ApiController]
[Route("api/[controller]/[action]")]
public class BaseController : ControllerBase{}

7.ModelState和部分项目接口返回400的问题

对于使用了 ModelState 需要注意在Core中自带就有一套验证,但我们已经有自己的一套验证,所以需要禁用了MVC自带的验证返回

services.Configure<ApiBehaviorOptions>((o) =>
{
    o.SuppressModelStateInvalidFilter = true;
});

8.使用环境变量控制 非开发环境 不开启Swagger

原因如题,统一再Configure方法UseSwaggerConfigureServices方法AddSwaggerGen加上以下统一的开关

if (Environment.GetEnvironmentVariable("Key") != "online")

docker run :-e Key="online"
这里使用了环境变量(Environment.GetEnvironmentVariable("Key"))
这样使用的话,可以在docker执行run的时候传入Key参数来控制运行的环境


9.Linux环境DateTime.ToString()格式不对

对于Core的时间格式,是获取系统的设置进行格式化的
在一些环境下,时间格式跟win不一样,比如Liunx执行DataTime.Now.ToString()出来的就可能是5/20/2022 15:00
有些时候我们不能使用这种格式,那么就可以调整这个默认的格式
这里需要注意下,ToString的格式是ShortDatePattern+LongTimePattern不要调整错参数了,不然就没效果了

Main加上以下配置即可
该配置是整个程序的全局配置,不一定需要放到Main,放到Main只是为了第一时间生效

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("zh-CN", true)
{
    DateTimeFormat = {
        ShortTimePattern = "H:mm:ss",
        ShortDatePattern = "yyyy/M/d",
        FullDateTimePattern = "yyyy/M/d HH:mm:ss",
        LongDatePattern = "yyyy/M/d",
        LongTimePattern = "HH:mm:ss"
    }
};

Q.E.D.


随意游世