StringExtensions.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. using DnsClient;
  2. using Masuit.Tools.DateTimeExt;
  3. using Masuit.Tools.Strings;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.Globalization;
  8. using System.Linq;
  9. using System.Net;
  10. using System.Net.Sockets;
  11. using System.Numerics;
  12. using System.Text;
  13. using System.Text.RegularExpressions;
  14. namespace Masuit.Tools
  15. {
  16. public static partial class StringExtensions
  17. {
  18. /// <summary>
  19. /// 字符串转时间
  20. /// </summary>
  21. /// <param name="value"></param>
  22. /// <returns></returns>
  23. public static DateTime ToDateTime(this string value)
  24. {
  25. DateTime.TryParse(value, out var result);
  26. return result;
  27. }
  28. /// <summary>
  29. /// 字符串转Guid
  30. /// </summary>
  31. /// <param name="s"></param>
  32. /// <returns></returns>
  33. public static Guid ToGuid(this string s)
  34. {
  35. return Guid.Parse(s);
  36. }
  37. /// <summary>
  38. /// 根据正则替换
  39. /// </summary>
  40. /// <param name="input"></param>
  41. /// <param name="regex">正则表达式</param>
  42. /// <param name="replacement">新内容</param>
  43. /// <returns></returns>
  44. public static string Replace(this string input, Regex regex, string replacement)
  45. {
  46. return regex.Replace(input, replacement);
  47. }
  48. /// <summary>
  49. /// 生成唯一短字符串
  50. /// </summary>
  51. /// <param name="str"></param>
  52. /// <param name="chars">可用字符数数量,0-9,a-z,A-Z</param>
  53. /// <returns></returns>
  54. public static string CreateShortToken(this string str, byte chars = 36)
  55. {
  56. var nf = new NumberFormater(chars);
  57. return nf.ToString((DateTime.Now.Ticks - 630822816000000000) * 100 + Stopwatch.GetTimestamp() % 100);
  58. }
  59. /// <summary>
  60. /// 任意进制转十进制
  61. /// </summary>
  62. /// <param name="str"></param>
  63. /// <param name="bin">进制</param>
  64. /// <returns></returns>
  65. public static long FromBinary(this string str, int bin)
  66. {
  67. var nf = new NumberFormater(bin);
  68. return nf.FromString(str);
  69. }
  70. /// <summary>
  71. /// 任意进制转大数十进制
  72. /// </summary>
  73. /// <param name="str"></param>
  74. /// <param name="bin">进制</param>
  75. /// <returns></returns>
  76. public static BigInteger FromBinaryBig(this string str, int bin)
  77. {
  78. var nf = new NumberFormater(bin);
  79. return nf.FromStringBig(str);
  80. }
  81. #region 检测字符串中是否包含列表中的关键词
  82. /// <summary>
  83. /// 检测字符串中是否包含列表中的关键词
  84. /// </summary>
  85. /// <param name="s">源字符串</param>
  86. /// <param name="keys">关键词列表</param>
  87. /// <param name="ignoreCase">忽略大小写</param>
  88. /// <returns></returns>
  89. public static bool Contains(this string s, IEnumerable<string> keys, bool ignoreCase = true)
  90. {
  91. if (!keys.Any() || string.IsNullOrEmpty(s))
  92. {
  93. return false;
  94. }
  95. if (ignoreCase)
  96. {
  97. return Regex.IsMatch(s, string.Join("|", keys), RegexOptions.IgnoreCase);
  98. }
  99. return Regex.IsMatch(s, string.Join("|", keys));
  100. }
  101. /// <summary>
  102. /// 判断是否包含符号
  103. /// </summary>
  104. /// <param name="str"></param>
  105. /// <param name="symbols"></param>
  106. /// <returns></returns>
  107. public static bool ContainsSymbol(this string str, params string[] symbols)
  108. {
  109. return str switch
  110. {
  111. null => false,
  112. string a when string.IsNullOrEmpty(a) => false,
  113. string a when a == string.Empty => false,
  114. _ => symbols.Any(t => str.Contains(t))
  115. };
  116. }
  117. #endregion 检测字符串中是否包含列表中的关键词
  118. /// <summary>
  119. /// 判断字符串是否为空或""
  120. /// </summary>
  121. /// <param name="s"></param>
  122. /// <returns></returns>
  123. public static bool IsNullOrEmpty(this string s)
  124. {
  125. return string.IsNullOrEmpty(s);
  126. }
  127. /// <summary>
  128. /// 字符串掩码
  129. /// </summary>
  130. /// <param name="s">字符串</param>
  131. /// <param name="mask">掩码符</param>
  132. /// <returns></returns>
  133. public static string Mask(this string s, char mask = '*')
  134. {
  135. if (string.IsNullOrWhiteSpace(s?.Trim()))
  136. {
  137. return s;
  138. }
  139. s = s.Trim();
  140. string masks = mask.ToString().PadLeft(4, mask);
  141. return s.Length switch
  142. {
  143. _ when s.Length >= 11 => Regex.Replace(s, "(.{3}).*(.{4})", $"$1{masks}$2"),
  144. _ when s.Length == 10 => Regex.Replace(s, "(.{3}).*(.{3})", $"$1{masks}$2"),
  145. _ when s.Length == 9 => Regex.Replace(s, "(.{2}).*(.{3})", $"$1{masks}$2"),
  146. _ when s.Length == 8 => Regex.Replace(s, "(.{2}).*(.{2})", $"$1{masks}$2"),
  147. _ when s.Length == 7 => Regex.Replace(s, "(.{1}).*(.{2})", $"$1{masks}$2"),
  148. _ when s.Length >= 2 && s.Length < 7 => Regex.Replace(s, "(.{1}).*(.{1})", $"$1{masks}$2"),
  149. _ => s + masks
  150. };
  151. }
  152. #region Email
  153. /// <summary>
  154. /// 匹配Email
  155. /// </summary>
  156. /// <param name="s">源字符串</param>
  157. /// <param name="valid">是否验证有效性</param>
  158. /// <returns>匹配对象;是否匹配成功,若返回true,则会得到一个Match对象,否则为null</returns>
  159. public static (bool isMatch, Match match) MatchEmail(this string s, bool valid = false)
  160. {
  161. if (string.IsNullOrEmpty(s) || s.Length < 7)
  162. {
  163. return (false, null);
  164. }
  165. Match match = Regex.Match(s, @"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
  166. var isMatch = match.Success;
  167. if (isMatch && valid)
  168. {
  169. var nslookup = new LookupClient();
  170. var query = nslookup.Query(s.Split('@')[1], QueryType.MX).Answers.MxRecords().SelectMany(r => Dns.GetHostAddresses(r.Exchange.Value)).ToList();
  171. isMatch = query.Any(ip => !ip.IsPrivateIP());
  172. }
  173. return (isMatch, match);
  174. }
  175. /// <summary>
  176. /// 邮箱掩码
  177. /// </summary>
  178. /// <param name="s">邮箱</param>
  179. /// <param name="mask">掩码</param>
  180. /// <returns></returns>
  181. public static string MaskEmail(this string s, char mask = '*')
  182. {
  183. return !MatchEmail(s).isMatch ? s : s.Replace(s.Substring(0, s.LastIndexOf("@")), Mask(s.Substring(0, s.LastIndexOf("@")), mask));
  184. }
  185. #endregion Email
  186. #region 匹配完整的URL
  187. /// <summary>
  188. /// 匹配完整格式的URL
  189. /// </summary>
  190. /// <param name="s">源字符串</param>
  191. /// <param name="isMatch">是否匹配成功,若返回true,则会得到一个Match对象,否则为null</param>
  192. /// <returns>匹配对象</returns>
  193. public static Uri MatchUrl(this string s, out bool isMatch)
  194. {
  195. try
  196. {
  197. var uri = new Uri(s);
  198. isMatch = Dns.GetHostAddresses(uri.Host).Any(ip => !ip.IsPrivateIP());
  199. return uri;
  200. }
  201. catch
  202. {
  203. isMatch = false;
  204. return null;
  205. }
  206. }
  207. /// <summary>
  208. /// 匹配完整格式的URL
  209. /// </summary>
  210. /// <param name="s">源字符串</param>
  211. /// <returns>是否匹配成功</returns>
  212. public static bool MatchUrl(this string s)
  213. {
  214. MatchUrl(s, out var isMatch);
  215. return isMatch;
  216. }
  217. #endregion 匹配完整的URL
  218. #region 权威校验身份证号码
  219. /// <summary>
  220. /// 根据GB11643-1999标准权威校验中国身份证号码的合法性
  221. /// </summary>
  222. /// <param name="s">源字符串</param>
  223. /// <returns>是否匹配成功</returns>
  224. public static bool MatchIdentifyCard(this string s)
  225. {
  226. return s.Length switch
  227. {
  228. 18 => CheckChinaID18(s),
  229. 15 => CheckChinaID15(s),
  230. _ => false
  231. };
  232. }
  233. private static readonly string[] ChinaIDProvinceCodes = {
  234. "11", "12", "13", "14", "15",
  235. "21", "22", "23",
  236. "31", "32", "33", "34", "35", "36", "37",
  237. "41", "42", "43", "44", "45", "46",
  238. "50", "51", "52", "53", "54",
  239. "61", "62", "63", "64", "65",
  240. "71",
  241. "81", "82",
  242. "91"
  243. };
  244. private static bool CheckChinaID18(string ID)
  245. {
  246. ID = ID.ToUpper();
  247. Match m = Regex.Match(ID, @"\d{17}[\dX]", RegexOptions.IgnoreCase);
  248. if (!m.Success)
  249. {
  250. return false;
  251. }
  252. if (!ChinaIDProvinceCodes.Contains(ID.Substring(0, 2)))
  253. {
  254. return false;
  255. }
  256. CultureInfo zhCN = new CultureInfo("zh-CN", true);
  257. if (!DateTime.TryParseExact(ID.Substring(6, 8), "yyyyMMdd", zhCN, DateTimeStyles.None, out DateTime birthday))
  258. {
  259. return false;
  260. }
  261. if (!birthday.In(new DateTime(1800, 1, 1), DateTime.Today))
  262. {
  263. return false;
  264. }
  265. int[] factors = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
  266. int sum = 0;
  267. for (int i = 0; i < 17; i++)
  268. {
  269. sum += (ID[i] - '0') * factors[i];
  270. }
  271. int n = (12 - sum % 11) % 11;
  272. return n < 10 ? ID[17] - '0' == n : ID[17].Equals('X');
  273. }
  274. private static bool CheckChinaID15(string ID)
  275. {
  276. Match m = Regex.Match(ID, @"\d{15}", RegexOptions.IgnoreCase);
  277. if (!m.Success)
  278. {
  279. return false;
  280. }
  281. if (!ChinaIDProvinceCodes.Contains(ID.Substring(0, 2)))
  282. {
  283. return false;
  284. }
  285. CultureInfo zhCN = new CultureInfo("zh-CN", true);
  286. if (!DateTime.TryParseExact("19" + ID.Substring(6, 6), "yyyyMMdd", zhCN, DateTimeStyles.None, out DateTime birthday))
  287. {
  288. return false;
  289. }
  290. return birthday.In(new DateTime(1800, 1, 1), new DateTime(2000, 1, 1));
  291. }
  292. #endregion 权威校验身份证号码
  293. #region IP地址
  294. /// <summary>
  295. /// 校验IP地址的正确性,同时支持IPv4和IPv6
  296. /// </summary>
  297. /// <param name="s">源字符串</param>
  298. /// <param name="isMatch">是否匹配成功,若返回true,则会得到一个Match对象,否则为null</param>
  299. /// <returns>匹配对象</returns>
  300. public static IPAddress MatchInetAddress(this string s, out bool isMatch)
  301. {
  302. isMatch = IPAddress.TryParse(s, out var ip);
  303. return ip;
  304. }
  305. /// <summary>
  306. /// 校验IP地址的正确性,同时支持IPv4和IPv6
  307. /// </summary>
  308. /// <param name="s">源字符串</param>
  309. /// <returns>是否匹配成功</returns>
  310. public static bool MatchInetAddress(this string s)
  311. {
  312. MatchInetAddress(s, out var success);
  313. return success;
  314. }
  315. /// <summary>
  316. /// IP地址转换成数字
  317. /// </summary>
  318. /// <param name="addr">IP地址</param>
  319. /// <returns>数字,输入无效IP地址返回0</returns>
  320. public static uint IPToID(this string addr)
  321. {
  322. if (!IPAddress.TryParse(addr, out var ip))
  323. {
  324. return 0;
  325. }
  326. byte[] bInt = ip.GetAddressBytes();
  327. if (BitConverter.IsLittleEndian)
  328. {
  329. Array.Reverse(bInt);
  330. }
  331. return BitConverter.ToUInt32(bInt, 0);
  332. }
  333. /// <summary>
  334. /// 判断IP是否是私有地址
  335. /// </summary>
  336. /// <param name="ip"></param>
  337. /// <returns></returns>
  338. public static bool IsPrivateIP(this string ip)
  339. {
  340. if (MatchInetAddress(ip))
  341. {
  342. return IPAddress.Parse(ip).IsPrivateIP();
  343. }
  344. return false;
  345. }
  346. /// <summary>
  347. /// 判断IP地址在不在某个IP地址段
  348. /// </summary>
  349. /// <param name="input">需要判断的IP地址</param>
  350. /// <param name="begin">起始地址</param>
  351. /// <param name="ends">结束地址</param>
  352. /// <returns></returns>
  353. public static bool IpAddressInRange(this string input, string begin, string ends)
  354. {
  355. uint current = input.IPToID();
  356. return current >= begin.IPToID() && current <= ends.IPToID();
  357. }
  358. #endregion IP地址
  359. #region 校验手机号码的正确性
  360. /// <summary>
  361. /// 匹配手机号码
  362. /// </summary>
  363. /// <param name="s">源字符串</param>
  364. /// <param name="isMatch">是否匹配成功,若返回true,则会得到一个Match对象,否则为null</param>
  365. /// <returns>匹配对象</returns>
  366. public static Match MatchPhoneNumber(this string s, out bool isMatch)
  367. {
  368. if (string.IsNullOrEmpty(s))
  369. {
  370. isMatch = false;
  371. return null;
  372. }
  373. Match match = Regex.Match(s, @"^((1[3,5,6,8][0-9])|(14[5,7])|(17[0,1,3,6,7,8])|(19[8,9]))\d{8}$");
  374. isMatch = match.Success;
  375. return isMatch ? match : null;
  376. }
  377. /// <summary>
  378. /// 匹配手机号码
  379. /// </summary>
  380. /// <param name="s">源字符串</param>
  381. /// <returns>是否匹配成功</returns>
  382. public static bool MatchPhoneNumber(this string s)
  383. {
  384. MatchPhoneNumber(s, out bool success);
  385. return success;
  386. }
  387. #endregion 校验手机号码的正确性
  388. #region Url
  389. /// <summary>
  390. /// 判断url是否是外部地址
  391. /// </summary>
  392. /// <param name="url"></param>
  393. /// <returns></returns>
  394. public static bool IsExternalAddress(this string url)
  395. {
  396. var uri = new Uri(url);
  397. switch (uri.HostNameType)
  398. {
  399. case UriHostNameType.Dns:
  400. var ipHostEntry = Dns.GetHostEntry(uri.DnsSafeHost);
  401. if (ipHostEntry.AddressList.Where(ipAddress => ipAddress.AddressFamily == AddressFamily.InterNetwork).Any(ipAddress => !ipAddress.IsPrivateIP()))
  402. {
  403. return true;
  404. }
  405. break;
  406. case UriHostNameType.IPv4:
  407. return !IPAddress.Parse(uri.DnsSafeHost).IsPrivateIP();
  408. }
  409. return false;
  410. }
  411. #endregion Url
  412. /// <summary>
  413. /// 转换成字节数组
  414. /// </summary>
  415. /// <param name="this"></param>
  416. /// <returns></returns>
  417. public static byte[] ToByteArray(this string @this)
  418. {
  419. return Activator.CreateInstance<ASCIIEncoding>().GetBytes(@this);
  420. }
  421. #region Crc32
  422. /// <summary>
  423. /// 获取字符串crc32签名
  424. /// </summary>
  425. /// <param name="s"></param>
  426. /// <returns></returns>
  427. public static string Crc32(this string s)
  428. {
  429. return string.Join(string.Empty, new Security.Crc32().ComputeHash(Encoding.UTF8.GetBytes(s)).Select(b => b.ToString("x2")));
  430. }
  431. /// <summary>
  432. /// 获取字符串crc64签名
  433. /// </summary>
  434. /// <param name="s"></param>
  435. /// <returns></returns>
  436. public static string Crc64(this string s)
  437. {
  438. return string.Join(string.Empty, new Security.Crc64().ComputeHash(Encoding.UTF8.GetBytes(s)).Select(b => b.ToString("x2")));
  439. }
  440. #endregion Crc32
  441. #region 权威校验中国专利申请号/专利号
  442. /// <summary>
  443. /// 中国专利申请号(授权以后就是专利号)由两种组成
  444. /// 2003年9月30号以前的9位(不带校验位是8号),校验位之前可能还会有一个点,例如:00262311, 002623110 或 00262311.0
  445. /// 2003年10月1号以后的13位(不带校验位是12号),校验位之前可能还会有一个点,例如:200410018477, 2004100184779 或200410018477.9
  446. /// http://www.sipo.gov.cn/docs/pub/old/wxfw/zlwxxxggfw/hlwzljsxt/hlwzljsxtsyzn/201507/P020150713610193194682.pdf
  447. /// 上面的文档中均不包括校验算法,但是下面的校验算法没有问题
  448. /// </summary>
  449. /// <param name="patnum">源字符串</param>
  450. /// <returns>是否匹配成功</returns>
  451. public static bool MatchCNPatentNumber(this string patnum)
  452. {
  453. Regex patnumWithCheckbitPattern = new Regex(@"^
  454. (?<!\d)
  455. (?<patentnum>
  456. (?<basenum>
  457. (?<year>(?<old>8[5-9]|9[0-9]|0[0-3])|(?<new>[2-9]\d{3}))
  458. (?<sn>
  459. (?<patenttype>[12389])
  460. (?(old)\d{5}|(?(new)\d{7}))
  461. )
  462. )
  463. (?:
  464. \.?
  465. (?<checkbit>[0-9X])
  466. )?
  467. )
  468. (?!\d)
  469. $", RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Multiline);
  470. Match m = patnumWithCheckbitPattern.Match(patnum);
  471. if (!m.Success)
  472. {
  473. return false;
  474. }
  475. bool isPatnumTrue = true;
  476. patnum = patnum.ToUpper().Replace(".", "");
  477. if (patnum.Length == 9 || patnum.Length == 8)
  478. {
  479. byte[] factors8 = new byte[8] { 2, 3, 4, 5, 6, 7, 8, 9 };
  480. int year = Convert.ToUInt16(patnum.Substring(0, 2));
  481. year += (year >= 85) ? (ushort)1900u : (ushort)2000u;
  482. if (year >= 1985 || year <= 2003)
  483. {
  484. int sum = 0;
  485. for (byte i = 0; i < 8; i++)
  486. {
  487. sum += factors8[i] * (patnum[i] - '0');
  488. }
  489. char checkbit = "0123456789X"[sum % 11];
  490. if (patnum.Length == 9)
  491. {
  492. if (checkbit != patnum[8])
  493. {
  494. isPatnumTrue = false;
  495. }
  496. }
  497. else
  498. {
  499. patnum += checkbit;
  500. }
  501. }
  502. else
  503. {
  504. isPatnumTrue = false;
  505. }
  506. }
  507. else if (patnum.Length == 13 || patnum.Length == 12)
  508. {
  509. byte[] factors12 = new byte[12] { 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5 };
  510. int year = Convert.ToUInt16(patnum.Substring(0, 4));
  511. if (year >= 2003 && year <= DateTime.Now.Year)
  512. {
  513. int sum = 0;
  514. for (byte i = 0; i < 12; i++)
  515. {
  516. sum += factors12[i] * (patnum[i] - '0');
  517. }
  518. char checkbit = "0123456789X"[sum % 11];
  519. if (patnum.Length == 13)
  520. {
  521. if (checkbit != patnum[12])
  522. {
  523. isPatnumTrue = false;
  524. }
  525. }
  526. else
  527. {
  528. patnum += checkbit;
  529. }
  530. }
  531. else
  532. {
  533. isPatnumTrue = false;
  534. }
  535. }
  536. else
  537. {
  538. isPatnumTrue = false;
  539. }
  540. return isPatnumTrue;
  541. }
  542. }
  543. #endregion
  544. }