该记录对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-01
和2020-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);
}
}
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
方法UseSwagger
和ConfigureServices
方法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.