StringExtensions.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  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. using System.Threading;
  15. using System.Threading.Tasks;
  16. using Masuit.Tools.Security;
  17. namespace Masuit.Tools
  18. {
  19. public static partial class StringExtensions
  20. {
  21. public static string Join(this IEnumerable<string> strs, string separate = ", ", bool removeEmptyEntry = false) => string.Join(separate, removeEmptyEntry ? strs.Where(s => !string.IsNullOrEmpty(s)) : strs);
  22. public static string Join<T>(this IEnumerable<T> strs, string separate = ", ", bool removeEmptyEntry = false) => string.Join(separate, removeEmptyEntry ? strs.Where(t => t != null) : strs);
  23. /// <summary>
  24. /// 字符串转时间
  25. /// </summary>
  26. /// <param name="value"></param>
  27. /// <returns></returns>
  28. public static DateTime ToDateTime(this string value)
  29. {
  30. DateTime.TryParse(value, out var result);
  31. return result;
  32. }
  33. /// <summary>
  34. /// 字符串转Guid
  35. /// </summary>
  36. /// <param name="s"></param>
  37. /// <returns></returns>
  38. public static Guid ToGuid(this string s)
  39. {
  40. return Guid.Parse(s);
  41. }
  42. /// <summary>
  43. /// 根据正则替换
  44. /// </summary>
  45. /// <param name="input"></param>
  46. /// <param name="regex">正则表达式</param>
  47. /// <param name="replacement">新内容</param>
  48. /// <returns></returns>
  49. public static string Replace(this string input, Regex regex, string replacement)
  50. {
  51. return regex.Replace(input, replacement);
  52. }
  53. /// <summary>
  54. /// 生成唯一短字符串
  55. /// </summary>
  56. /// <param name="str"></param>
  57. /// <param name="chars">可用字符数数量,0-9,a-z,A-Z</param>
  58. /// <returns></returns>
  59. public static string CreateShortToken(this string str, byte chars = 36)
  60. {
  61. var nf = new NumberFormater(chars);
  62. return nf.ToString((DateTime.Now.Ticks - 630822816000000000) * 100 + Stopwatch.GetTimestamp() % 100);
  63. }
  64. /// <summary>
  65. /// 任意进制转十进制
  66. /// </summary>
  67. /// <param name="str"></param>
  68. /// <param name="base">进制</param>
  69. /// <returns></returns>
  70. public static long FromBase(this string str, byte @base)
  71. {
  72. var nf = new NumberFormater(@base);
  73. return nf.FromString(str);
  74. }
  75. /// <summary>
  76. /// 任意进制转大数十进制
  77. /// </summary>
  78. /// <param name="str"></param>
  79. /// <param name="base">进制</param>
  80. /// <returns></returns>
  81. public static BigInteger FromBaseBig(this string str, byte @base)
  82. {
  83. var nf = new NumberFormater(@base);
  84. return nf.FromStringBig(str);
  85. }
  86. #region 检测字符串中是否包含列表中的关键词
  87. /// <summary>
  88. /// 检测字符串中是否包含列表中的关键词(快速匹配)
  89. /// </summary>
  90. /// <param name="s">源字符串</param>
  91. /// <param name="keys">关键词列表</param>
  92. /// <param name="ignoreCase">忽略大小写</param>
  93. /// <returns></returns>
  94. public static bool Contains(this string s, IEnumerable<string> keys, bool ignoreCase = true)
  95. {
  96. if (keys is not ICollection<string> array)
  97. {
  98. array = keys.ToArray();
  99. }
  100. if (array.Count == 0 || string.IsNullOrEmpty(s))
  101. {
  102. return false;
  103. }
  104. return ignoreCase ? array.Any(item => s.IndexOf(item, StringComparison.InvariantCultureIgnoreCase) >= 0) : array.Any(s.Contains);
  105. }
  106. /// <summary>
  107. /// 检测字符串中是否包含列表中的关键词(安全匹配)
  108. /// </summary>
  109. /// <param name="s">源字符串</param>
  110. /// <param name="keys">关键词列表</param>
  111. /// <param name="ignoreCase">忽略大小写</param>
  112. /// <returns></returns>
  113. public static bool ContainsSafety(this string s, IEnumerable<string> keys, bool ignoreCase = true)
  114. {
  115. if (keys is not ICollection<string> array)
  116. {
  117. array = keys.ToArray();
  118. }
  119. if (array.Count == 0 || string.IsNullOrEmpty(s))
  120. {
  121. return false;
  122. }
  123. bool flag = false;
  124. if (ignoreCase)
  125. {
  126. foreach (var item in array)
  127. {
  128. if (s.Contains(item))
  129. {
  130. flag = true;
  131. }
  132. }
  133. }
  134. else
  135. {
  136. foreach (var item in array)
  137. {
  138. if (s.IndexOf(item, StringComparison.InvariantCultureIgnoreCase) >= 0)
  139. {
  140. flag = true;
  141. }
  142. }
  143. }
  144. return flag;
  145. }
  146. /// <summary>
  147. /// 检测字符串中是否以列表中的关键词结尾
  148. /// </summary>
  149. /// <param name="s">源字符串</param>
  150. /// <param name="keys">关键词列表</param>
  151. /// <param name="ignoreCase">忽略大小写</param>
  152. /// <returns></returns>
  153. public static bool EndsWith(this string s, IEnumerable<string> keys, bool ignoreCase = true)
  154. {
  155. if (keys is not ICollection<string> array)
  156. {
  157. array = keys.ToArray();
  158. }
  159. if (array.Count == 0 || string.IsNullOrEmpty(s))
  160. {
  161. return false;
  162. }
  163. var pattern = $"({array.Select(Regex.Escape).Join("|")})$";
  164. return ignoreCase ? Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase) : Regex.IsMatch(s, pattern);
  165. }
  166. /// <summary>
  167. /// 检测字符串中是否以列表中的关键词开始
  168. /// </summary>
  169. /// <param name="s">源字符串</param>
  170. /// <param name="keys">关键词列表</param>
  171. /// <param name="ignoreCase">忽略大小写</param>
  172. /// <returns></returns>
  173. public static bool StartsWith(this string s, IEnumerable<string> keys, bool ignoreCase = true)
  174. {
  175. if (keys is not ICollection<string> array)
  176. {
  177. array = keys.ToArray();
  178. }
  179. if (array.Count == 0 || string.IsNullOrEmpty(s))
  180. {
  181. return false;
  182. }
  183. var pattern = $"^({array.Select(Regex.Escape).Join("|")})";
  184. return ignoreCase ? Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase) : Regex.IsMatch(s, pattern);
  185. }
  186. /// <summary>
  187. /// 检测字符串中是否包含列表中的关键词
  188. /// </summary>
  189. /// <param name="s">源字符串</param>
  190. /// <param name="regex">关键词列表</param>
  191. /// <param name="ignoreCase">忽略大小写</param>
  192. /// <returns></returns>
  193. public static bool RegexMatch(this string s, string regex, bool ignoreCase = true)
  194. {
  195. if (string.IsNullOrEmpty(regex) || string.IsNullOrEmpty(s))
  196. {
  197. return false;
  198. }
  199. if (ignoreCase)
  200. {
  201. return Regex.IsMatch(s, regex, RegexOptions.IgnoreCase);
  202. }
  203. return Regex.IsMatch(s, regex);
  204. }
  205. /// <summary>
  206. /// 检测字符串中是否包含列表中的关键词
  207. /// </summary>
  208. /// <param name="s">源字符串</param>
  209. /// <param name="regex">关键词列表</param>
  210. /// <returns></returns>
  211. public static bool RegexMatch(this string s, Regex regex) => !string.IsNullOrEmpty(s) && regex.IsMatch(s);
  212. #endregion 检测字符串中是否包含列表中的关键词
  213. /// <summary>
  214. /// 判断字符串是否为空或""
  215. /// </summary>
  216. /// <param name="s"></param>
  217. /// <returns></returns>
  218. public static bool IsNullOrEmpty(this string s)
  219. {
  220. return string.IsNullOrWhiteSpace(s) || s.Equals("null", StringComparison.CurrentCultureIgnoreCase);
  221. }
  222. /// <summary>
  223. /// 判断字符串不为空或""
  224. /// </summary>
  225. /// <param name="s"></param>
  226. /// <returns></returns>
  227. public static bool NotNullOrEmpty(this string s)
  228. {
  229. return !string.IsNullOrWhiteSpace(s) && !s.Equals("null", StringComparison.CurrentCultureIgnoreCase);
  230. }
  231. /// <summary>
  232. /// 转成非null
  233. /// </summary>
  234. /// <param name="s"></param>
  235. /// <returns></returns>
  236. public static string AsNotNull(this string s)
  237. {
  238. return string.IsNullOrEmpty(s) ? "" : s;
  239. }
  240. /// <summary>
  241. /// 转成非null
  242. /// </summary>
  243. /// <param name="s"></param>
  244. /// <param name="value">为空时的替换值</param>
  245. /// <returns></returns>
  246. public static string IfNullOrEmpty(this string s, string value)
  247. {
  248. return string.IsNullOrEmpty(s) ? value : s;
  249. }
  250. /// <summary>
  251. /// 转成非null
  252. /// </summary>
  253. /// <param name="s"></param>
  254. /// <param name="valueFactory">为空时的替换值函数</param>
  255. /// <returns></returns>
  256. public static string IfNullOrEmpty(this string s, Func<string> valueFactory)
  257. {
  258. return string.IsNullOrEmpty(s) ? valueFactory() : s;
  259. }
  260. /// <summary>
  261. /// 字符串掩码
  262. /// </summary>
  263. /// <param name="s">字符串</param>
  264. /// <param name="mask">掩码符</param>
  265. /// <returns></returns>
  266. public static string Mask(this string s, char mask = '*')
  267. {
  268. if (string.IsNullOrWhiteSpace(s?.Trim()))
  269. {
  270. return s;
  271. }
  272. s = s.Trim();
  273. string masks = mask.ToString().PadLeft(4, mask);
  274. return s.Length switch
  275. {
  276. >= 11 => Regex.Replace(s, "(.{3}).*(.{4})", $"$1{masks}$2"),
  277. 10 => Regex.Replace(s, "(.{3}).*(.{3})", $"$1{masks}$2"),
  278. 9 => Regex.Replace(s, "(.{2}).*(.{3})", $"$1{masks}$2"),
  279. 8 => Regex.Replace(s, "(.{2}).*(.{2})", $"$1{masks}$2"),
  280. 7 => Regex.Replace(s, "(.{1}).*(.{2})", $"$1{masks}$2"),
  281. 6 => Regex.Replace(s, "(.{1}).*(.{1})", $"$1{masks}$2"),
  282. _ => Regex.Replace(s, "(.{1}).*", $"$1{masks}")
  283. };
  284. }
  285. #region Email
  286. /// <summary>
  287. /// 匹配Email
  288. /// </summary>
  289. /// <param name="s">源字符串</param>
  290. /// <param name="valid">是否验证有效性</param>
  291. /// <returns>匹配对象;是否匹配成功,若返回true,则会得到一个Match对象,否则为null</returns>
  292. public static (bool isMatch, Match match) MatchEmail(this string s, bool valid = false)
  293. {
  294. if (string.IsNullOrEmpty(s) || s.Length < 7)
  295. {
  296. return (false, null);
  297. }
  298. var match = Regex.Match(s, @"[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+");
  299. var isMatch = match.Success;
  300. if (isMatch && valid)
  301. {
  302. var nslookup = new LookupClient();
  303. var task = nslookup.Query(s.Split('@')[1], QueryType.MX).Answers.MxRecords().SelectAsync(r => Dns.GetHostAddressesAsync(r.Exchange.Value).ContinueWith(t =>
  304. {
  305. if (t.IsCanceled || t.IsFaulted)
  306. {
  307. return new[]
  308. {
  309. IPAddress.Loopback
  310. };
  311. }
  312. return t.Result;
  313. }));
  314. isMatch = task.Result.SelectMany(a => a).Any(ip => !ip.IsPrivateIP());
  315. }
  316. return (isMatch, match);
  317. }
  318. /// <summary>
  319. /// 匹配Email
  320. /// </summary>
  321. /// <param name="s">源字符串</param>
  322. /// <param name="valid">是否验证有效性</param>
  323. /// <returns>匹配对象;是否匹配成功,若返回true,则会得到一个Match对象,否则为null</returns>
  324. public static async Task<(bool isMatch, Match match)> MatchEmailAsync(this string s, bool valid = false)
  325. {
  326. if (string.IsNullOrEmpty(s) || s.Length < 7)
  327. {
  328. return (false, null);
  329. }
  330. var match = Regex.Match(s, @"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$");
  331. var isMatch = match.Success;
  332. if (isMatch && valid)
  333. {
  334. var nslookup = new LookupClient();
  335. using var cts = new CancellationTokenSource(100);
  336. var query = await nslookup.QueryAsync(s.Split('@')[1], QueryType.MX, cancellationToken: cts.Token);
  337. var result = await query.Answers.MxRecords().SelectAsync(r => Dns.GetHostAddressesAsync(r.Exchange.Value).ContinueWith(t =>
  338. {
  339. if (t.IsCanceled || t.IsFaulted)
  340. {
  341. return new[] { IPAddress.Loopback };
  342. }
  343. return t.Result;
  344. }));
  345. isMatch = result.SelectMany(a => a).Any(ip => !ip.IsPrivateIP());
  346. }
  347. return (isMatch, match);
  348. }
  349. /// <summary>
  350. /// 邮箱掩码
  351. /// </summary>
  352. /// <param name="s">邮箱</param>
  353. /// <param name="mask">掩码</param>
  354. /// <returns></returns>
  355. public static string MaskEmail(this string s, char mask = '*')
  356. {
  357. var index = s.LastIndexOf("@");
  358. var oldValue = s.Substring(0, index);
  359. if (!MatchEmail(s).isMatch)
  360. {
  361. return s;
  362. }
  363. var newValue = Mask(oldValue, mask);
  364. return newValue + s.Substring(index, s.Length - index);
  365. }
  366. #endregion Email
  367. #region 匹配完整的URL
  368. /// <summary>
  369. /// 匹配完整格式的URL
  370. /// </summary>
  371. /// <param name="s">源字符串</param>
  372. /// <param name="isMatch">是否匹配成功,若返回true,则会得到一个Match对象,否则为null</param>
  373. /// <returns>匹配对象</returns>
  374. public static Uri MatchUrl(this string s, out bool isMatch)
  375. {
  376. try
  377. {
  378. var uri = new Uri(s);
  379. isMatch = Dns.GetHostAddresses(uri.Host).Any(ip => !ip.IsPrivateIP());
  380. return uri;
  381. }
  382. catch
  383. {
  384. isMatch = false;
  385. return null;
  386. }
  387. }
  388. /// <summary>
  389. /// 匹配完整格式的URL
  390. /// </summary>
  391. /// <param name="s">源字符串</param>
  392. /// <returns>是否匹配成功</returns>
  393. public static bool MatchUrl(this string s)
  394. {
  395. MatchUrl(s, out var isMatch);
  396. return isMatch;
  397. }
  398. #endregion 匹配完整的URL
  399. #region 权威校验身份证号码
  400. /// <summary>
  401. /// 根据GB11643-1999标准权威校验中国身份证号码的合法性
  402. /// </summary>
  403. /// <param name="s">源字符串</param>
  404. /// <returns>是否匹配成功</returns>
  405. public static bool MatchIdentifyCard(this string s)
  406. {
  407. return s.Length switch
  408. {
  409. 18 => CheckChinaID18(s),
  410. 15 => CheckChinaID15(s),
  411. _ => false
  412. };
  413. }
  414. private static readonly string[] ChinaIDProvinceCodes = {
  415. "11", "12", "13", "14", "15",
  416. "21", "22", "23",
  417. "31", "32", "33", "34", "35", "36", "37",
  418. "41", "42", "43", "44", "45", "46",
  419. "50", "51", "52", "53", "54",
  420. "61", "62", "63", "64", "65",
  421. "71",
  422. "81",
  423. "82",
  424. "91"
  425. };
  426. private static bool CheckChinaID18(string ID)
  427. {
  428. ID = ID.ToUpper();
  429. Match m = Regex.Match(ID, @"\d{17}[\dX]", RegexOptions.IgnoreCase);
  430. if (!m.Success)
  431. {
  432. return false;
  433. }
  434. if (!ChinaIDProvinceCodes.Contains(ID.Substring(0, 2)))
  435. {
  436. return false;
  437. }
  438. CultureInfo zhCN = new CultureInfo("zh-CN", true);
  439. if (!DateTime.TryParseExact(ID.Substring(6, 8), "yyyyMMdd", zhCN, DateTimeStyles.None, out DateTime birthday))
  440. {
  441. return false;
  442. }
  443. if (!birthday.In(new DateTime(1800, 1, 1), DateTime.Today))
  444. {
  445. return false;
  446. }
  447. int[] factors = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
  448. int sum = 0;
  449. for (int i = 0; i < 17; i++)
  450. {
  451. sum += (ID[i] - '0') * factors[i];
  452. }
  453. int n = (12 - sum % 11) % 11;
  454. return n < 10 ? ID[17] - '0' == n : ID[17].Equals('X');
  455. }
  456. private static bool CheckChinaID15(string ID)
  457. {
  458. Match m = Regex.Match(ID, @"\d{15}", RegexOptions.IgnoreCase);
  459. if (!m.Success)
  460. {
  461. return false;
  462. }
  463. if (!ChinaIDProvinceCodes.Contains(ID.Substring(0, 2)))
  464. {
  465. return false;
  466. }
  467. CultureInfo zhCN = new CultureInfo("zh-CN", true);
  468. if (!DateTime.TryParseExact("19" + ID.Substring(6, 6), "yyyyMMdd", zhCN, DateTimeStyles.None, out DateTime birthday))
  469. {
  470. return false;
  471. }
  472. return birthday.In(new DateTime(1800, 1, 1), new DateTime(2000, 1, 1));
  473. }
  474. #endregion 权威校验身份证号码
  475. #region IP地址
  476. /// <summary>
  477. /// 校验IP地址的正确性,同时支持IPv4和IPv6
  478. /// </summary>
  479. /// <param name="s">源字符串</param>
  480. /// <param name="isMatch">是否匹配成功,若返回true,则会得到一个Match对象,否则为null</param>
  481. /// <returns>匹配对象</returns>
  482. public static IPAddress MatchInetAddress(this string s, out bool isMatch)
  483. {
  484. isMatch = IPAddress.TryParse(s, out var ip);
  485. return ip;
  486. }
  487. /// <summary>
  488. /// 校验IP地址的正确性,同时支持IPv4和IPv6
  489. /// </summary>
  490. /// <param name="s">源字符串</param>
  491. /// <returns>是否匹配成功</returns>
  492. public static bool MatchInetAddress(this string s)
  493. {
  494. MatchInetAddress(s, out var success);
  495. return success;
  496. }
  497. /// <summary>
  498. /// IP地址转换成数字
  499. /// </summary>
  500. /// <param name="addr">IP地址</param>
  501. /// <returns>数字,输入无效IP地址返回0</returns>
  502. public static uint IPToID(this string addr)
  503. {
  504. if (!IPAddress.TryParse(addr, out var ip))
  505. {
  506. return 0;
  507. }
  508. byte[] bInt = ip.GetAddressBytes();
  509. if (BitConverter.IsLittleEndian)
  510. {
  511. Array.Reverse(bInt);
  512. }
  513. return BitConverter.ToUInt32(bInt, 0);
  514. }
  515. /// <summary>
  516. /// IP地址转换成数字
  517. /// </summary>
  518. /// <param name="ip">IP地址</param>
  519. /// <returns>数字,输入无效IP地址返回0</returns>
  520. public static uint ToUInt32(this IPAddress ip)
  521. {
  522. byte[] bInt = ip.GetAddressBytes();
  523. if (BitConverter.IsLittleEndian)
  524. {
  525. Array.Reverse(bInt);
  526. }
  527. return BitConverter.ToUInt32(bInt, 0);
  528. }
  529. /// <summary>
  530. /// 判断IP是否是私有地址
  531. /// </summary>
  532. /// <param name="ip"></param>
  533. /// <returns></returns>
  534. public static bool IsPrivateIP(this string ip)
  535. {
  536. var address = MatchInetAddress(ip, out var b);
  537. return b && address.IsPrivateIP();
  538. }
  539. /// <summary>
  540. /// 判断IP地址在不在某个IP地址段
  541. /// </summary>
  542. /// <param name="input">需要判断的IP地址</param>
  543. /// <param name="begin">起始地址</param>
  544. /// <param name="ends">结束地址</param>
  545. /// <returns></returns>
  546. public static bool IpAddressInRange(this string input, string begin, string ends)
  547. {
  548. uint current = input.IPToID();
  549. return current >= begin.IPToID() && current <= ends.IPToID();
  550. }
  551. /// <summary>
  552. /// 判断IP地址在不在某个IP地址段
  553. /// </summary>
  554. /// <param name="input">需要判断的IP地址</param>
  555. /// <param name="begin">起始地址</param>
  556. /// <param name="ends">结束地址</param>
  557. /// <returns></returns>
  558. public static bool IpAddressInRange(this IPAddress input, IPAddress begin, IPAddress ends)
  559. {
  560. uint current = input.ToUInt32();
  561. return current >= begin.ToUInt32() && current <= ends.ToUInt32();
  562. }
  563. #endregion IP地址
  564. #region 校验手机号码的正确性
  565. /// <summary>
  566. /// 匹配手机号码
  567. /// </summary>
  568. /// <param name="s">源字符串</param>
  569. /// <returns>是否匹配成功</returns>
  570. public static bool MatchPhoneNumber(this string s)
  571. {
  572. return !string.IsNullOrEmpty(s) && s.Length == 11 && s[0] == '1' && (s[1] > '2' || s[1] <= '9') && long.TryParse(s, out _);
  573. }
  574. /// <summary>
  575. /// 匹配固话号码
  576. /// </summary>
  577. /// <param name="s">源字符串</param>
  578. /// <returns>是否匹配成功</returns>
  579. public static bool MatchLandline(this string s)
  580. {
  581. return Regex.IsMatch(s, @"^0\d{2,3}(?:-?\d{8}|-?\d{7})$");
  582. }
  583. /// <summary>
  584. /// 匹配企业的统一社会信用代码
  585. /// </summary>
  586. /// <param name="s">源字符串</param>
  587. /// <returns>是否匹配成功</returns>
  588. public static bool MatchUSCC(this string s)
  589. {
  590. return Regex.IsMatch(s, @"^([0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}|[1-9]\d{14})$");
  591. }
  592. #endregion 校验手机号码的正确性
  593. #region Url
  594. /// <summary>
  595. /// 判断url是否是外部地址
  596. /// </summary>
  597. /// <param name="url"></param>
  598. /// <returns></returns>
  599. public static bool IsExternalAddress(this string url)
  600. {
  601. var uri = new Uri(url);
  602. switch (uri.HostNameType)
  603. {
  604. case UriHostNameType.Dns:
  605. var ipHostEntry = Dns.GetHostEntry(uri.DnsSafeHost);
  606. if (ipHostEntry.AddressList.Where(ipAddress => ipAddress.AddressFamily == AddressFamily.InterNetwork).Any(ipAddress => !ipAddress.IsPrivateIP()))
  607. {
  608. return true;
  609. }
  610. break;
  611. case UriHostNameType.IPv4:
  612. return !IPAddress.Parse(uri.DnsSafeHost).IsPrivateIP();
  613. }
  614. return false;
  615. }
  616. #endregion Url
  617. /// <summary>
  618. /// 转换成字节数组
  619. /// </summary>
  620. /// <param name="this"></param>
  621. /// <returns></returns>
  622. public static byte[] ToByteArray(this string @this)
  623. {
  624. return Encoding.UTF8.GetBytes(@this);
  625. }
  626. #region Crc32
  627. /// <summary>
  628. /// 获取字符串crc32签名
  629. /// </summary>
  630. /// <param name="s"></param>
  631. /// <returns></returns>
  632. public static string Crc32(this string s)
  633. {
  634. using var crc32 = new Crc32();
  635. return string.Join(string.Empty, crc32.ComputeHash(Encoding.UTF8.GetBytes(s)).Select(b => b.ToString("x2")));
  636. }
  637. /// <summary>
  638. /// 获取字符串crc64签名
  639. /// </summary>
  640. /// <param name="s"></param>
  641. /// <returns></returns>
  642. public static string Crc64(this string s)
  643. {
  644. using var crc64 = new Crc64();
  645. return string.Join(string.Empty, crc64.ComputeHash(Encoding.UTF8.GetBytes(s)).Select(b => b.ToString("x2")));
  646. }
  647. #endregion Crc32
  648. #region 权威校验中国专利申请号/专利号
  649. /// <summary>
  650. /// 中国专利申请号(授权以后就是专利号)由两种组成
  651. /// 2003年9月30号以前的9位(不带校验位是8号),校验位之前可能还会有一个点,例如:00262311, 002623110 或 00262311.0
  652. /// 2003年10月1号以后的13位(不带校验位是12号),校验位之前可能还会有一个点,例如:200410018477, 2004100184779 或200410018477.9
  653. /// http://www.sipo.gov.cn/docs/pub/old/wxfw/zlwxxxggfw/hlwzljsxt/hlwzljsxtsyzn/201507/P020150713610193194682.pdf
  654. /// 上面的文档中均不包括校验算法,但是下面的校验算法没有问题
  655. /// </summary>
  656. /// <param name="patnum">源字符串</param>
  657. /// <returns>是否匹配成功</returns>
  658. public static bool MatchCNPatentNumber(this string patnum)
  659. {
  660. var patnumWithCheckbitPattern = new Regex(@"^
  661. (?<!\d)
  662. (?<patentnum>
  663. (?<basenum>
  664. (?<year>(?<old>8[5-9]|9[0-9]|0[0-3])|(?<new>[2-9]\d{3}))
  665. (?<sn>
  666. (?<patenttype>[12389])
  667. (?(old)\d{5}|(?(new)\d{7}))
  668. )
  669. )
  670. (?:
  671. \.?
  672. (?<checkbit>[0-9X])
  673. )?
  674. )
  675. (?!\d)
  676. $", RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Multiline);
  677. var m = patnumWithCheckbitPattern.Match(patnum);
  678. if (!m.Success)
  679. {
  680. return false;
  681. }
  682. bool isPatnumTrue = true;
  683. patnum = patnum.ToUpper().Replace(".", "");
  684. if (patnum.Length == 9 || patnum.Length == 8)
  685. {
  686. byte[] factors8 = { 2, 3, 4, 5, 6, 7, 8, 9 };
  687. int year = Convert.ToUInt16(patnum.Substring(0, 2));
  688. year += year >= 85 ? (ushort)1900u : (ushort)2000u;
  689. if (year is >= 1985 or <= 2003)
  690. {
  691. int sum = 0;
  692. for (byte i = 0; i < 8; i++)
  693. {
  694. sum += factors8[i] * (patnum[i] - '0');
  695. }
  696. char checkbit = "0123456789X"[sum % 11];
  697. if (patnum.Length == 9 && checkbit != patnum[8])
  698. {
  699. isPatnumTrue = false;
  700. }
  701. }
  702. else
  703. {
  704. isPatnumTrue = false;
  705. }
  706. }
  707. else if (patnum.Length is 13 or 12)
  708. {
  709. byte[] factors12 = { 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5 };
  710. int year = Convert.ToUInt16(patnum.Substring(0, 4));
  711. if (year >= 2003 && year <= DateTime.Now.Year)
  712. {
  713. int sum = 0;
  714. for (byte i = 0; i < 12; i++)
  715. {
  716. sum += factors12[i] * (patnum[i] - '0');
  717. }
  718. char checkbit = "0123456789X"[sum % 11];
  719. if (patnum.Length == 13)
  720. {
  721. if (checkbit != patnum[12])
  722. {
  723. isPatnumTrue = false;
  724. }
  725. }
  726. else
  727. {
  728. patnum += checkbit;
  729. }
  730. }
  731. else
  732. {
  733. isPatnumTrue = false;
  734. }
  735. }
  736. else
  737. {
  738. isPatnumTrue = false;
  739. }
  740. return isPatnumTrue;
  741. }
  742. #endregion 权威校验中国专利申请号/专利号
  743. /// <summary>
  744. /// 取字符串前{length}个字
  745. /// </summary>
  746. /// <param name="s"></param>
  747. /// <param name="length"></param>
  748. /// <returns></returns>
  749. public static string Take(this string s, int length)
  750. {
  751. return s.Length > length ? s.Substring(0, length) : s;
  752. }
  753. /// <summary>
  754. /// 对比字符串的汉明距离
  755. /// </summary>
  756. /// <param name="this"></param>
  757. /// <param name="that"></param>
  758. /// <returns></returns>
  759. public static int HammingDistance(this string @this, string that) => new SimHash(@this).HammingDistance(new SimHash(that));
  760. /// <summary>
  761. /// 匹配字符串是否包含emoji字符
  762. /// </summary>
  763. /// <param name="s"></param>
  764. /// <returns></returns>
  765. public static bool MatchEmoji(this string s)
  766. {
  767. return Regex.IsMatch(s, @"(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])");
  768. }
  769. /// <summary>
  770. /// 获取字符串的字符数
  771. /// </summary>
  772. /// <param name="str"></param>
  773. /// <returns></returns>
  774. public static int CharacterCount(this string str)
  775. {
  776. var enumerator = StringInfo.GetTextElementEnumerator(str);
  777. int length = 0;
  778. while (enumerator.MoveNext())
  779. {
  780. length++;
  781. }
  782. return length;
  783. }
  784. /// <summary>
  785. /// 获取字符串的字节数
  786. /// </summary>
  787. /// <param name="str"></param>
  788. /// <returns></returns>
  789. public static int BytesCount(this string str)
  790. {
  791. return Encoding.UTF8.GetByteCount(str);
  792. }
  793. /// <summary>
  794. /// 转半角(DBC case)
  795. /// </summary>
  796. /// <param name="input">任意字符串</param>
  797. /// <returns>半角字符串</returns>
  798. ///<remarks>
  799. ///全角空格为12288,半角空格为32(此处不必转空格)
  800. ///其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248
  801. ///</remarks>
  802. public static string ToDBC(this string input)
  803. {
  804. char[] c = input.ToCharArray();
  805. for (int i = 0; i < c.Length; i++)
  806. {
  807. if (c[i] == 12288)
  808. {
  809. c[i] = (char)32;
  810. continue;
  811. }
  812. if (c[i] > 65280 && c[i] < 65375)
  813. {
  814. c[i] = (char)(c[i] - 65248);
  815. }
  816. }
  817. return new string(c);
  818. }
  819. /// <summary>
  820. /// 转全角(SBC case)
  821. /// </summary>
  822. /// <param name="input">任意字符串</param>
  823. /// <returns>全角字符串</returns>
  824. ///<remarks>
  825. ///全角空格为12288,半角空格为32
  826. ///其他字符半角(33-126)与全角(65281-65374)的对应关系是:均相差65248
  827. ///</remarks>
  828. public static string ToSBC(this string input)
  829. {
  830. //半角转全角:
  831. var c = input.ToCharArray();
  832. for (int i = 0; i < c.Length; i++)
  833. {
  834. if (c[i] == 32)
  835. {
  836. c[i] = (char)12288;
  837. continue;
  838. }
  839. if (c[i] < 127 && c[i] > 32)
  840. {
  841. c[i] = (char)(c[i] + 65248);
  842. }
  843. }
  844. return new string(c);
  845. }
  846. }
  847. }