using DnsClient; using Masuit.Tools.DateTimeExt; using Masuit.Tools.Strings; using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; using System.Numerics; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Masuit.Tools { public static partial class StringExtensions { public static string Join(this IEnumerable strs, string separate = ", ", bool removeEmptyEntry = false) => string.Join(separate, removeEmptyEntry ? strs.Where(s => !string.IsNullOrEmpty(s)) : strs); public static string Join(this IEnumerable strs, string separate = ", ", bool removeEmptyEntry = false) => string.Join(separate, removeEmptyEntry ? strs.Where(t => t != null) : strs); /// /// 字符串转时间 /// /// /// public static DateTime ToDateTime(this string value) { DateTime.TryParse(value, out var result); return result; } /// /// 字符串转Guid /// /// /// public static Guid ToGuid(this string s) { return Guid.Parse(s); } /// /// 根据正则替换 /// /// /// 正则表达式 /// 新内容 /// public static string Replace(this string input, Regex regex, string replacement) { return regex.Replace(input, replacement); } /// /// 生成唯一短字符串 /// /// /// 可用字符数数量,0-9,a-z,A-Z /// public static string CreateShortToken(this string str, byte chars = 36) { var nf = new NumberFormater(chars); return nf.ToString((DateTime.Now.Ticks - 630822816000000000) * 100 + Stopwatch.GetTimestamp() % 100); } /// /// 任意进制转十进制 /// /// /// 进制 /// public static long FromBase(this string str, byte @base) { var nf = new NumberFormater(@base); return nf.FromString(str); } /// /// 任意进制转大数十进制 /// /// /// 进制 /// public static BigInteger FromBaseBig(this string str, byte @base) { var nf = new NumberFormater(@base); return nf.FromStringBig(str); } #region 检测字符串中是否包含列表中的关键词 /// /// 检测字符串中是否包含列表中的关键词(快速匹配) /// /// 源字符串 /// 关键词列表 /// 忽略大小写 /// public static bool Contains(this string s, IEnumerable keys, bool ignoreCase = true) { if (keys is not ICollection array) { array = keys.ToArray(); } if (array.Count == 0 || string.IsNullOrEmpty(s)) { return false; } return ignoreCase ? array.Any(item => s.IndexOf(item, StringComparison.InvariantCultureIgnoreCase) >= 0) : array.Any(s.Contains); } /// /// 检测字符串中是否包含列表中的关键词(安全匹配) /// /// 源字符串 /// 关键词列表 /// 忽略大小写 /// public static bool ContainsSafety(this string s, IEnumerable keys, bool ignoreCase = true) { if (keys is not ICollection array) { array = keys.ToArray(); } if (array.Count == 0 || string.IsNullOrEmpty(s)) { return false; } bool flag = false; if (ignoreCase) { foreach (var item in array) { if (s.Contains(item)) { flag = true; } } } else { foreach (var item in array) { if (s.IndexOf(item, StringComparison.InvariantCultureIgnoreCase) >= 0) { flag = true; } } } return flag; } /// /// 检测字符串中是否以列表中的关键词结尾 /// /// 源字符串 /// 关键词列表 /// 忽略大小写 /// public static bool EndsWith(this string s, IEnumerable keys, bool ignoreCase = true) { if (keys is not ICollection array) { array = keys.ToArray(); } if (array.Count == 0 || string.IsNullOrEmpty(s)) { return false; } var pattern = $"({array.Select(Regex.Escape).Join("|")})$"; return ignoreCase ? Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase) : Regex.IsMatch(s, pattern); } /// /// 检测字符串中是否以列表中的关键词开始 /// /// 源字符串 /// 关键词列表 /// 忽略大小写 /// public static bool StartsWith(this string s, IEnumerable keys, bool ignoreCase = true) { if (keys is not ICollection array) { array = keys.ToArray(); } if (array.Count == 0 || string.IsNullOrEmpty(s)) { return false; } var pattern = $"^({array.Select(Regex.Escape).Join("|")})"; return ignoreCase ? Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase) : Regex.IsMatch(s, pattern); } /// /// 检测字符串中是否包含列表中的关键词 /// /// 源字符串 /// 关键词列表 /// 忽略大小写 /// public static bool RegexMatch(this string s, string regex, bool ignoreCase = true) { if (string.IsNullOrEmpty(regex) || string.IsNullOrEmpty(s)) { return false; } if (ignoreCase) { return Regex.IsMatch(s, regex, RegexOptions.IgnoreCase); } return Regex.IsMatch(s, regex); } /// /// 检测字符串中是否包含列表中的关键词 /// /// 源字符串 /// 关键词列表 /// public static bool RegexMatch(this string s, Regex regex) => !string.IsNullOrEmpty(s) && regex.IsMatch(s); #endregion 检测字符串中是否包含列表中的关键词 /// /// 判断字符串是否为空或"" /// /// /// public static bool IsNullOrEmpty(this string s) { return string.IsNullOrWhiteSpace(s) || s.Equals("null", StringComparison.CurrentCultureIgnoreCase); } /// /// 判断字符串不为空或"" /// /// /// public static bool NotNullOrEmpty(this string s) { return !string.IsNullOrWhiteSpace(s) && !s.Equals("null", StringComparison.CurrentCultureIgnoreCase); } /// /// 转成非null /// /// /// public static string AsNotNull(this string s) { return string.IsNullOrEmpty(s) ? "" : s; } /// /// 转成非null /// /// /// 为空时的替换值 /// public static string IfNullOrEmpty(this string s, string value) { return string.IsNullOrEmpty(s) ? value : s; } /// /// 转成非null /// /// /// 为空时的替换值函数 /// public static string IfNullOrEmpty(this string s, Func valueFactory) { return string.IsNullOrEmpty(s) ? valueFactory() : s; } /// /// 字符串掩码 /// /// 字符串 /// 掩码符 /// public static string Mask(this string s, char mask = '*') { if (string.IsNullOrWhiteSpace(s?.Trim())) { return s; } s = s.Trim(); string masks = mask.ToString().PadLeft(4, mask); return s.Length switch { >= 11 => Regex.Replace(s, "(.{3}).*(.{4})", $"$1{masks}$2"), 10 => Regex.Replace(s, "(.{3}).*(.{3})", $"$1{masks}$2"), 9 => Regex.Replace(s, "(.{2}).*(.{3})", $"$1{masks}$2"), 8 => Regex.Replace(s, "(.{2}).*(.{2})", $"$1{masks}$2"), 7 => Regex.Replace(s, "(.{1}).*(.{2})", $"$1{masks}$2"), 6 => Regex.Replace(s, "(.{1}).*(.{1})", $"$1{masks}$2"), _ => Regex.Replace(s, "(.{1}).*", $"$1{masks}") }; } #region Email /// /// 匹配Email /// /// 源字符串 /// 是否验证有效性 /// 匹配对象;是否匹配成功,若返回true,则会得到一个Match对象,否则为null public static (bool isMatch, Match match) MatchEmail(this string s, bool valid = false) { if (string.IsNullOrEmpty(s) || s.Length < 7) { return (false, null); } var match = Regex.Match(s, @"[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+"); var isMatch = match.Success; if (isMatch && valid) { var nslookup = new LookupClient(); var task = nslookup.Query(s.Split('@')[1], QueryType.MX).Answers.MxRecords().SelectAsync(r => Dns.GetHostAddressesAsync(r.Exchange.Value).ContinueWith(t => { if (t.IsCanceled || t.IsFaulted) { return new[] { IPAddress.Loopback }; } return t.Result; })); isMatch = task.Result.SelectMany(a => a).Any(ip => !ip.IsPrivateIP()); } return (isMatch, match); } /// /// 匹配Email /// /// 源字符串 /// 是否验证有效性 /// 匹配对象;是否匹配成功,若返回true,则会得到一个Match对象,否则为null public static async Task<(bool isMatch, Match match)> MatchEmailAsync(this string s, bool valid = false) { if (string.IsNullOrEmpty(s) || s.Length < 7) { return (false, null); } var match = Regex.Match(s, @"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"); var isMatch = match.Success; if (isMatch && valid) { var nslookup = new LookupClient(); using var cts = new CancellationTokenSource(100); var query = await nslookup.QueryAsync(s.Split('@')[1], QueryType.MX, cancellationToken: cts.Token); var result = await query.Answers.MxRecords().SelectAsync(r => Dns.GetHostAddressesAsync(r.Exchange.Value).ContinueWith(t => { if (t.IsCanceled || t.IsFaulted) { return new[] { IPAddress.Loopback }; } return t.Result; })); isMatch = result.SelectMany(a => a).Any(ip => !ip.IsPrivateIP()); } return (isMatch, match); } /// /// 邮箱掩码 /// /// 邮箱 /// 掩码 /// public static string MaskEmail(this string s, char mask = '*') { var index = s.LastIndexOf("@"); var oldValue = s.Substring(0, index); return !MatchEmail(s).isMatch ? s : s.Replace(oldValue, Mask(oldValue, mask)); } #endregion Email #region 匹配完整的URL /// /// 匹配完整格式的URL /// /// 源字符串 /// 是否匹配成功,若返回true,则会得到一个Match对象,否则为null /// 匹配对象 public static Uri MatchUrl(this string s, out bool isMatch) { try { var uri = new Uri(s); isMatch = Dns.GetHostAddresses(uri.Host).Any(ip => !ip.IsPrivateIP()); return uri; } catch { isMatch = false; return null; } } /// /// 匹配完整格式的URL /// /// 源字符串 /// 是否匹配成功 public static bool MatchUrl(this string s) { MatchUrl(s, out var isMatch); return isMatch; } #endregion 匹配完整的URL #region 权威校验身份证号码 /// /// 根据GB11643-1999标准权威校验中国身份证号码的合法性 /// /// 源字符串 /// 是否匹配成功 public static bool MatchIdentifyCard(this string s) { return s.Length switch { 18 => CheckChinaID18(s), 15 => CheckChinaID15(s), _ => false }; } private static readonly string[] ChinaIDProvinceCodes = { "11", "12", "13", "14", "15", "21", "22", "23", "31", "32", "33", "34", "35", "36", "37", "41", "42", "43", "44", "45", "46", "50", "51", "52", "53", "54", "61", "62", "63", "64", "65", "71", "81", "82", "91" }; private static bool CheckChinaID18(string ID) { ID = ID.ToUpper(); Match m = Regex.Match(ID, @"\d{17}[\dX]", RegexOptions.IgnoreCase); if (!m.Success) { return false; } if (!ChinaIDProvinceCodes.Contains(ID.Substring(0, 2))) { return false; } CultureInfo zhCN = new CultureInfo("zh-CN", true); if (!DateTime.TryParseExact(ID.Substring(6, 8), "yyyyMMdd", zhCN, DateTimeStyles.None, out DateTime birthday)) { return false; } if (!birthday.In(new DateTime(1800, 1, 1), DateTime.Today)) { return false; } int[] factors = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 }; int sum = 0; for (int i = 0; i < 17; i++) { sum += (ID[i] - '0') * factors[i]; } int n = (12 - sum % 11) % 11; return n < 10 ? ID[17] - '0' == n : ID[17].Equals('X'); } private static bool CheckChinaID15(string ID) { Match m = Regex.Match(ID, @"\d{15}", RegexOptions.IgnoreCase); if (!m.Success) { return false; } if (!ChinaIDProvinceCodes.Contains(ID.Substring(0, 2))) { return false; } CultureInfo zhCN = new CultureInfo("zh-CN", true); if (!DateTime.TryParseExact("19" + ID.Substring(6, 6), "yyyyMMdd", zhCN, DateTimeStyles.None, out DateTime birthday)) { return false; } return birthday.In(new DateTime(1800, 1, 1), new DateTime(2000, 1, 1)); } #endregion 权威校验身份证号码 #region IP地址 /// /// 校验IP地址的正确性,同时支持IPv4和IPv6 /// /// 源字符串 /// 是否匹配成功,若返回true,则会得到一个Match对象,否则为null /// 匹配对象 public static IPAddress MatchInetAddress(this string s, out bool isMatch) { isMatch = IPAddress.TryParse(s, out var ip); return ip; } /// /// 校验IP地址的正确性,同时支持IPv4和IPv6 /// /// 源字符串 /// 是否匹配成功 public static bool MatchInetAddress(this string s) { MatchInetAddress(s, out var success); return success; } /// /// IP地址转换成数字 /// /// IP地址 /// 数字,输入无效IP地址返回0 public static uint IPToID(this string addr) { if (!IPAddress.TryParse(addr, out var ip)) { return 0; } byte[] bInt = ip.GetAddressBytes(); if (BitConverter.IsLittleEndian) { Array.Reverse(bInt); } return BitConverter.ToUInt32(bInt, 0); } /// /// IP地址转换成数字 /// /// IP地址 /// 数字,输入无效IP地址返回0 public static uint ToUInt32(this IPAddress ip) { byte[] bInt = ip.GetAddressBytes(); if (BitConverter.IsLittleEndian) { Array.Reverse(bInt); } return BitConverter.ToUInt32(bInt, 0); } /// /// 判断IP是否是私有地址 /// /// /// public static bool IsPrivateIP(this string ip) { var address = MatchInetAddress(ip, out var b); return b && address.IsPrivateIP(); } /// /// 判断IP地址在不在某个IP地址段 /// /// 需要判断的IP地址 /// 起始地址 /// 结束地址 /// public static bool IpAddressInRange(this string input, string begin, string ends) { uint current = input.IPToID(); return current >= begin.IPToID() && current <= ends.IPToID(); } /// /// 判断IP地址在不在某个IP地址段 /// /// 需要判断的IP地址 /// 起始地址 /// 结束地址 /// public static bool IpAddressInRange(this IPAddress input, IPAddress begin, IPAddress ends) { uint current = input.ToUInt32(); return current >= begin.ToUInt32() && current <= ends.ToUInt32(); } #endregion IP地址 #region 校验手机号码的正确性 /// /// 匹配手机号码 /// /// 源字符串 /// 是否匹配成功 public static bool MatchPhoneNumber(this string s) { return !string.IsNullOrEmpty(s) && s.Length == 11 && s[0] == '1' && (s[1] > '2' || s[1] <= '9') && long.TryParse(s, out _); } /// /// 匹配固话号码 /// /// 源字符串 /// 是否匹配成功 public static bool MatchLandline(this string s) { return Regex.IsMatch(s, @"^0\d{2,3}(?:-?\d{8}|-?\d{7})$"); } #endregion 校验手机号码的正确性 #region Url /// /// 判断url是否是外部地址 /// /// /// public static bool IsExternalAddress(this string url) { var uri = new Uri(url); switch (uri.HostNameType) { case UriHostNameType.Dns: var ipHostEntry = Dns.GetHostEntry(uri.DnsSafeHost); if (ipHostEntry.AddressList.Where(ipAddress => ipAddress.AddressFamily == AddressFamily.InterNetwork).Any(ipAddress => !ipAddress.IsPrivateIP())) { return true; } break; case UriHostNameType.IPv4: return !IPAddress.Parse(uri.DnsSafeHost).IsPrivateIP(); } return false; } #endregion Url /// /// 转换成字节数组 /// /// /// public static byte[] ToByteArray(this string @this) { return Encoding.UTF8.GetBytes(@this); } #region Crc32 /// /// 获取字符串crc32签名 /// /// /// public static string Crc32(this string s) { return string.Join(string.Empty, new Security.Crc32().ComputeHash(Encoding.UTF8.GetBytes(s)).Select(b => b.ToString("x2"))); } /// /// 获取字符串crc64签名 /// /// /// public static string Crc64(this string s) { return string.Join(string.Empty, new Security.Crc64().ComputeHash(Encoding.UTF8.GetBytes(s)).Select(b => b.ToString("x2"))); } #endregion Crc32 #region 权威校验中国专利申请号/专利号 /// /// 中国专利申请号(授权以后就是专利号)由两种组成 /// 2003年9月30号以前的9位(不带校验位是8号),校验位之前可能还会有一个点,例如:00262311, 002623110 或 00262311.0 /// 2003年10月1号以后的13位(不带校验位是12号),校验位之前可能还会有一个点,例如:200410018477, 2004100184779 或200410018477.9 /// http://www.sipo.gov.cn/docs/pub/old/wxfw/zlwxxxggfw/hlwzljsxt/hlwzljsxtsyzn/201507/P020150713610193194682.pdf /// 上面的文档中均不包括校验算法,但是下面的校验算法没有问题 /// /// 源字符串 /// 是否匹配成功 public static bool MatchCNPatentNumber(this string patnum) { Regex patnumWithCheckbitPattern = new Regex(@"^ (? (? (?(?8[5-9]|9[0-9]|0[0-3])|(?[2-9]\d{3})) (? (?[12389]) (?(old)\d{5}|(?(new)\d{7})) ) ) (?: \.? (?[0-9X]) )? ) (?!\d) $", RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Multiline); Match m = patnumWithCheckbitPattern.Match(patnum); if (!m.Success) { return false; } bool isPatnumTrue = true; patnum = patnum.ToUpper().Replace(".", ""); if (patnum.Length == 9 || patnum.Length == 8) { byte[] factors8 = new byte[] { 2, 3, 4, 5, 6, 7, 8, 9 }; int year = Convert.ToUInt16(patnum.Substring(0, 2)); year += (year >= 85) ? (ushort)1900u : (ushort)2000u; if (year >= 1985 || year <= 2003) { int sum = 0; for (byte i = 0; i < 8; i++) { sum += factors8[i] * (patnum[i] - '0'); } char checkbit = "0123456789X"[sum % 11]; if (patnum.Length == 9) { if (checkbit != patnum[8]) { isPatnumTrue = false; } } } else { isPatnumTrue = false; } } else if (patnum.Length == 13 || patnum.Length == 12) { byte[] factors12 = { 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5 }; int year = Convert.ToUInt16(patnum.Substring(0, 4)); if (year >= 2003 && year <= DateTime.Now.Year) { int sum = 0; for (byte i = 0; i < 12; i++) { sum += factors12[i] * (patnum[i] - '0'); } char checkbit = "0123456789X"[sum % 11]; if (patnum.Length == 13) { if (checkbit != patnum[12]) { isPatnumTrue = false; } } else { patnum += checkbit; } } else { isPatnumTrue = false; } } else { isPatnumTrue = false; } return isPatnumTrue; } #endregion 权威校验中国专利申请号/专利号 /// /// 取字符串前{length}个字 /// /// /// /// public static string Take(this string s, int length) { return s.Length > length ? s.Substring(0, length) : s; } /// /// 对比字符串的汉明距离 /// /// /// /// public static int HammingDistance(this string @this, string that) => new SimHash(@this).HammingDistance(new SimHash(that)); /// /// 匹配字符串是否包含emoji字符 /// /// /// public static bool MatchEmoji(this string s) { return Regex.IsMatch(s, @"(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])"); } /// /// 获取字符串的字符数 /// /// /// public static int CharacterCount(this string str) { var enumerator = StringInfo.GetTextElementEnumerator(str); int length = 0; while (enumerator.MoveNext()) { length++; } return length; } /// /// 获取字符串的字节数 /// /// /// public static int BytesCount(this string str) { return Encoding.UTF8.GetByteCount(str); } /// /// 转半角(DBC case) /// /// 任意字符串 /// 半角字符串 /// ///全角空格为12288,半角空格为32(此处不必转空格) ///其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 /// public static string ToDBC(this string input) { char[] c = input.ToCharArray(); for (int i = 0; i < c.Length; i++) { if (c[i] == 12288) { c[i] = (char)32; continue; } if (c[i] > 65280 && c[i] < 65375) { c[i] = (char)(c[i] - 65248); } } return new string(c); } /// /// 转全角(SBC case) /// /// 任意字符串 /// 全角字符串 /// ///全角空格为12288,半角空格为32 ///其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248 /// public static string ToSBC(this string input) { //半角转全角: var c = input.ToCharArray(); for (int i = 0; i < c.Length; i++) { if (c[i] == 32) { c[i] = (char)12288; continue; } if (c[i] < 127 && c[i] > 32) { c[i] = (char)(c[i] + 65248); } } return new string(c); } } }