• C# 基于腾讯云人脸核身和百度云证件识别技术相结合的 API 实现


    目录

    腾讯云人脸核身技术

    Craneoffice.net 采用的识别方式

            1、活体人脸核身(权威库):

            2、活体人脸比对:

            3、照片人脸核身(权威库):

    调用成本

    百度云身份证识别

    调用成本

    相关结合点

    核心代码

    实现调用人脸核身API的示例 

    实现调用身份证识别API的示例 

    小结


    腾讯云人脸核身技术

    根据腾讯云的官方介绍,其慧眼人脸核身是一组对用户身份信息真实性进行验证审核的服务套件,提供人脸核身、身份信息核验、银行卡要素核验和运营商类要素核验等各类实名信息认证能力,以解决行业内大量对用户身份信息核实的需求。

    Craneoffice.net 采用的识别方式

    由于其产品众多,考虑一些综合因素,我们在 Craneoffice.net 架构里主要实现以下三种识别方式:

            1、活体人脸核身(权威库):

             流程为通过录制一段人脸活体静态视频,与大数据权威库身份证信息进行比对,判断是否为 同一人。

            2、活体人脸比对:

             流程为通过上传正确、清晰的身份证正面图片,截取头像图片,再通过录制人脸活体静态视频进行比对,判断是否为同一人。

            3、照片人脸核身(权威库):

             流程为上传正确的身份证正面图片,截取头像图片,传递身份证号与姓名,与大数据权威库身份证信息进行比对,判断是否为同一人。

    调用成本

    申请开发账号及具体费用情况请访问腾讯云人脸核身产品首页:

    https://cloud.tencent.com/act/pro/huiyandiscount

    我们的产品调用成本如下表,可参照一下比例,在此仅供参考:

    识别方式调用成功的成本
    活体人脸核身(权威库)1元 / 每次
    活体人脸比对0.15元 / 每次
    照片人脸核身(权威库)1元 / 每次

    总之,在腾讯云商城购买越大的产品包调用成本越低,如果有优惠活动则更为合适。

    百度云身份证识别

    其官方宣传可以结构化识别二代居民身份证正反面所有8个字段,识别准确率超过99%;支持识别混贴身份证,适用于同一张图上有多张身份证正反面的场景;支持检测身份证正面头像,并返回头像切片的base64编码及位置信息,其具体详细产品介绍请访问如下地址:

    https://ai.baidu.com/tech/ocr_cards/idcard

    调用成本

    我们使用的是企业申请,一个月应该可以享受2000次免费调用,后期调用应该是0.02元左右每次,具体可参照:

    https://ai.baidu.com/ai-doc/OCR/fk3h7xune#%E8%BA%AB%E4%BB%BD%E8%AF%81%E8%AF%86%E5%88%AB

    相关结合点

    在人脸核身方面,虽然我们可以直接提供身份证号、姓名、自拍抠图的头像BASE64编码等参数传递给腾讯云识别接口,但考虑到实际应用场景中,更加规范、有效的验证有助于提升应用程序数据的质量和精准性,也更加保障了识别结果的准确性。

    因此身份证的识别功能和人脸核身功能即可以单独独立运行,又可以利用产品特性相结合,实现数据采集、校验的双保险。

    具体流程如下图:

    核心代码

    实现调用人脸核身API的示例 

    该示例代码以上小节的介绍的三种识别方式实现,仅供参考:

    1. //定义人脸识别类
    2. public class FaceR
    3. {
    4. public string ResultJson = ""; //记录返回 json 结果
    5. public string apiurl = "faceid.tencentcloudapi.com"; //腾讯人脸识别API地址
    6. public string debuginfo = ""; //调试信息
    7. public string ErrorMessage = ""; //错误信息
    8. string[] signHeaders = null; //头部签名数组
    9. public FaceR()
    10. {
    11. }
    12. //活体人脸核身方法,参数为身份证号;检验类型,这里传固定值 SILENT;姓名;活体的静态视频编码;方法返回相似度值等信息
    13. public string LivenessRecognition(string IdCard,string LivenessType,string Name,string VideoBase64)
    14. {
    15. string content = "{ \"IdCard\":\"" + IdCard + "\",\"LivenessType\":\"" + LivenessType + "\", \"Name\":\"" + Name + "\" ,\"VideoBase64\":\"" + VideoBase64 + "\"}";
    16. // 密钥参数
    17. string SECRET_ID = 你申请的ID
    18. string SECRET_KEY = 你申请的KEY
    19. string service = "faceid";
    20. string endpoint = "faceid.tencentcloudapi.com";
    21. string region = "ap-guangzhou";
    22. string action = "LivenessRecognition";
    23. string version = "2018-03-01";
    24. // 注意时区,建议此时间统一采用UTC时间戳,否则容易出错
    25. DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(1551113065);
    26. date = DateTime.UtcNow;
    27. string requestPayload = content;
    28. Dictionary<string, string> headers = BuildHeaders(SECRET_ID, SECRET_KEY, service
    29. , endpoint, region, action, version, date, requestPayload);
    30. string rv = "POST https://faceid.tencentcloudapi.com\n";
    31. ArrayList hs = new ArrayList();
    32. foreach (KeyValuePair<string, string> kv in headers)
    33. {
    34. rv += (kv.Key + ": " + kv.Value) + "\n";
    35. hs.Add(kv.Key + ": " + kv.Value);
    36. }
    37. rv += "\n";
    38. hs.Add("");
    39. rv += requestPayload + "\n";
    40. string[] hss = new string[hs.Count];
    41. debuginfo = "";
    42. for (int i = 0; i < hs.Count; i++)
    43. {
    44. hss[i] = hs[i].ToString();
    45. debuginfo += hss[i] + "\r\n";
    46. }
    47. signHeaders = hss;
    48. string rvs = "";
    49. rvs=GetResponseResult("https://faceid.tencentcloudapi.com", Encoding.UTF8, "POST",content, signHeaders);
    50. return rvs;
    51. }
    52. //活体人脸比对方法,参数为传递检验类型,这里传固定值 SILEN;身份证头像图片编码;活体的静态视频编码,方法返回相似度值等信息
    53. public string LivenessCompare(string LivenessType, string ImageBase64, string VideoBase64)
    54. {
    55. string content = "{ \"LivenessType\":\"" + LivenessType + "\", \"ImageBase64\":\"" + ImageBase64 + "\" ,\"VideoBase64\":\"" + VideoBase64 + "\"}";
    56. // 密钥参数
    57. string SECRET_ID = 你申请的ID
    58. string SECRET_KEY = 你申请的KEY
    59. string service = "faceid";
    60. string endpoint = "faceid.tencentcloudapi.com";
    61. string region = "ap-guangzhou";
    62. string action = "LivenessCompare";
    63. string version = "2018-03-01";
    64. // 注意时区,建议此时间统一采用UTC时间戳,否则容易出错
    65. DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(1551113065);
    66. date = DateTime.UtcNow;
    67. string requestPayload = content;
    68. Dictionary<string, string> headers = BuildHeaders(SECRET_ID, SECRET_KEY, service
    69. , endpoint, region, action, version, date, requestPayload);
    70. string rv = "POST https://faceid.tencentcloudapi.com\n";
    71. ArrayList hs = new ArrayList();
    72. foreach (KeyValuePair<string, string> kv in headers)
    73. {
    74. rv += (kv.Key + ": " + kv.Value) + "\n";
    75. hs.Add(kv.Key + ": " + kv.Value);
    76. }
    77. rv += "\n";
    78. hs.Add("");
    79. rv += requestPayload + "\n";
    80. string[] hss = new string[hs.Count];
    81. debuginfo = "";
    82. for (int i = 0; i < hs.Count; i++)
    83. {
    84. hss[i] = hs[i].ToString();
    85. debuginfo += hss[i] + "\r\n";
    86. }
    87. signHeaders = hss;
    88. string rvs = "";
    89. rvs = GetResponseResult("https://faceid.tencentcloudapi.com", Encoding.UTF8, "POST", content, signHeaders);
    90. return rvs;
    91. }
    92. //照片人脸核身方法,参数传递身份证号;姓名;截取的身份证头像图片编码;方法返回相似度值等信息
    93. public string ImageRecognition(string IdCard,string Name, string ImageBase64)
    94. {
    95. string content = "{ \"IdCard\":\"" + IdCard + "\", \"Name\":\"" + HttpUtility.UrlDecode(Name, Encoding.UTF8) + "\" ,\"ImageBase64\":\"" + ImageBase64 + "\"}";
    96. // 密钥参数
    97. string SECRET_ID = 你申请的ID
    98. string SECRET_KEY = 你申请的KEY
    99. string service = "faceid";
    100. string endpoint = "faceid.tencentcloudapi.com";
    101. string region = "ap-guangzhou";
    102. string action = "ImageRecognition";
    103. string version = "2018-03-01";
    104. // 注意时区,建议此时间统一采用UTC时间戳,否则容易出错
    105. DateTime date = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(1551113065);
    106. date = DateTime.UtcNow;
    107. string requestPayload = content;
    108. Dictionary<string, string> headers = BuildHeaders(SECRET_ID, SECRET_KEY, service
    109. , endpoint, region, action, version, date, requestPayload);
    110. string rv = "POST https://faceid.tencentcloudapi.com\n";
    111. ArrayList hs = new ArrayList();
    112. foreach (KeyValuePair<string, string> kv in headers)
    113. {
    114. rv += (kv.Key + ": " + kv.Value) + "\n";
    115. hs.Add(kv.Key + ": " + kv.Value);
    116. }
    117. rv += "\n";
    118. hs.Add("");
    119. rv += requestPayload + "\n";
    120. string[] hss = new string[hs.Count];
    121. debuginfo = "";
    122. for (int i = 0; i < hs.Count; i++)
    123. {
    124. hss[i] = hs[i].ToString();
    125. debuginfo += hss[i] + "\r\n";
    126. }
    127. signHeaders = hss;
    128. string rvs = "";
    129. rvs = GetResponseResult("https://faceid.tencentcloudapi.com", Encoding.UTF8, "POST", content, signHeaders);
    130. return rvs;
    131. }
    132. //SHA256Hex算法
    133. public static string SHA256Hex(string s)
    134. {
    135. using (SHA256 algo = SHA256.Create())
    136. {
    137. byte[] hashbytes = algo.ComputeHash(Encoding.UTF8.GetBytes(s));
    138. StringBuilder builder = new StringBuilder();
    139. for (int i = 0; i < hashbytes.Length; ++i)
    140. {
    141. builder.Append(hashbytes[i].ToString("x2"));
    142. }
    143. return builder.ToString();
    144. }
    145. }
    146. //HMAC-SHA256算法
    147. public static byte[] HmacSHA256(byte[] key, byte[] msg)
    148. {
    149. using (HMACSHA256 mac = new HMACSHA256(key))
    150. {
    151. return mac.ComputeHash(msg);
    152. }
    153. }
    154. //构造头部签名
    155. public static Dictionary BuildHeaders(string secretid,
    156. string secretkey, string service, string endpoint, string region,
    157. string action, string version, DateTime date, string requestPayload)
    158. {
    159. string datestr = date.ToString("yyyy-MM-dd");
    160. DateTime startTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    161. long requestTimestamp = (long)Math.Round((date - startTime).TotalMilliseconds, MidpointRounding.AwayFromZero) / 1000;
    162. // ************* 步骤 1:拼接规范请求串 *************
    163. string algorithm = "TC3-HMAC-SHA256";
    164. string httpRequestMethod = "POST";
    165. string canonicalUri = "/";
    166. string canonicalQueryString = "";
    167. string contentType = "application/json";
    168. string canonicalHeaders = "content-type:" + contentType + "; charset=utf-8\n"
    169. + "host:" + endpoint + "\n"
    170. + "x-tc-action:" + action.ToLower() + "\n";
    171. string signedHeaders = "content-type;host;x-tc-action";
    172. string hashedRequestPayload = SHA256Hex(requestPayload);
    173. string canonicalRequest = httpRequestMethod + "\n"
    174. + canonicalUri + "\n"
    175. + canonicalQueryString + "\n"
    176. + canonicalHeaders + "\n"
    177. + signedHeaders + "\n"
    178. + hashedRequestPayload;
    179. Console.WriteLine(canonicalRequest);
    180. // ************* 步骤 2:拼接待签名字符串 *************
    181. string credentialScope = datestr + "/" + service + "/" + "tc3_request";
    182. string hashedCanonicalRequest = SHA256Hex(canonicalRequest);
    183. string stringToSign = algorithm + "\n"
    184. + requestTimestamp.ToString() + "\n"
    185. + credentialScope + "\n"
    186. + hashedCanonicalRequest;
    187. Console.WriteLine(stringToSign);
    188. // ************* 步骤 3:计算签名 *************
    189. byte[] tc3SecretKey = Encoding.UTF8.GetBytes("TC3" + secretkey);
    190. byte[] secretDate = HmacSHA256(tc3SecretKey, Encoding.UTF8.GetBytes(datestr));
    191. byte[] secretService = HmacSHA256(secretDate, Encoding.UTF8.GetBytes(service));
    192. byte[] secretSigning = HmacSHA256(secretService, Encoding.UTF8.GetBytes("tc3_request"));
    193. byte[] signatureBytes = HmacSHA256(secretSigning, Encoding.UTF8.GetBytes(stringToSign));
    194. string signature = BitConverter.ToString(signatureBytes).Replace("-", "").ToLower();
    195. Console.WriteLine(signature);
    196. // ************* 步骤 4:拼接 Authorization *************
    197. string authorization = algorithm + " "
    198. + "Credential=" + secretid + "/" + credentialScope + ", "
    199. + "SignedHeaders=" + signedHeaders + ", "
    200. + "Signature=" + signature;
    201. Console.WriteLine(authorization);
    202. Dictionary<string, string> headers = new Dictionary<string, string>();
    203. headers.Add("Authorization", authorization);
    204. headers.Add("Host", endpoint);
    205. headers.Add("Content-Type", contentType + "; charset=utf-8");
    206. headers.Add("X-TC-Timestamp", requestTimestamp.ToString());
    207. headers.Add("X-TC-Version", version);
    208. headers.Add("X-TC-Action", action);
    209. headers.Add("X-TC-Region", region);
    210. return headers;
    211. }
    212. //调用API地址,传递参数并获取返回值的通用方法
    213. public string GetResponseResult(string url, System.Text.Encoding encoding, string method, string postData, string[] headers, string ContentType = "application/x-www-form-urlencoded")
    214. {
    215. method = method.ToUpper();
    216. System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12;
    217. if (method == "GET")
    218. {
    219. try
    220. {
    221. WebRequest request2 = WebRequest.Create(@url);
    222. request2.Method = method;
    223. WebResponse response2 = request2.GetResponse();
    224. Stream stream = response2.GetResponseStream();
    225. StreamReader reader = new StreamReader(stream, encoding);
    226. string content = reader.ReadToEnd();
    227. return content;
    228. }
    229. catch (Exception ex)
    230. {
    231. ErrorMessage = ex.Message;
    232. return "";
    233. }
    234. }
    235. Stream outstream = null;
    236. Stream instream = null;
    237. StreamReader sr = null;
    238. HttpWebResponse response = null;
    239. HttpWebRequest request = null;
    240. byte[] data = encoding.GetBytes(postData);
    241. // 准备请求...
    242. try
    243. {
    244. // 设置参数
    245. request = WebRequest.Create(url) as HttpWebRequest;
    246. CookieContainer cookieContainer = new CookieContainer();
    247. request.CookieContainer = cookieContainer;
    248. request.AllowAutoRedirect = true;
    249. request.Method = method;
    250. debuginfo = headers.GetLength(0).ToString()+"\r\n";
    251. if (headers != null)
    252. {
    253. for (int i = 0; i < headers.GetLength(0); i++)
    254. {
    255. if (headers[i].Split(':').Length < 2)
    256. {
    257. continue;
    258. }
    259. debuginfo += i.ToString()+headers[i]+"\r\n";
    260. if (headers[i].Split(':').Length > 1)
    261. {
    262. if (headers[i].Split(':')[0] == "Host")
    263. {
    264. request.Host = headers[i].Split(':')[1].Trim();
    265. continue;
    266. }
    267. else if (headers[i].Split(':')[0] == "Content-Type")
    268. {
    269. request.ContentType = headers[i].Split(':')[1].Trim();
    270. ContentType = headers[i].Split(':')[1].Trim();
    271. continue;
    272. }
    273. }
    274. request.Headers.Add(headers[i].Trim());
    275. }
    276. debuginfo += "sd2" + "\r\n";
    277. }
    278. request.ContentType = ContentType;
    279. request.ContentLength = data.Length;
    280. outstream = request.GetRequestStream();
    281. outstream.Write(data, 0, data.Length);
    282. outstream.Close();
    283. //发送请求并获取相应回应数据
    284. response = request.GetResponse() as HttpWebResponse;
    285. //直到request.GetResponse()程序才开始向目标网页发送Post请求
    286. instream = response.GetResponseStream();
    287. sr = new StreamReader(instream, encoding);
    288. //返回结果网页(html)代码
    289. string content = sr.ReadToEnd();
    290. return content;
    291. }
    292. catch (Exception ex)
    293. {
    294. ErrorMessage = ex.Message;
    295. return "";
    296. }
    297. }//get response result
    298. }

    实现调用身份证识别API的示例 

    1. public class IdCard
    2. {
    3. public string name = ""; //姓名
    4. public string sex = ""; //性别
    5. public string photo_base64 = ""; //截取的身份证头像图像的编码值
    6. public string nation = ""; //民族
    7. public string address = ""; //住址
    8. public string IDNumber = ""; //身份证号
    9. public string birthday = ""; //生日
    10. public string org = ""; //发证机关
    11. public string startDate = ""; //有效期起
    12. public string endDate = ""; //有效期止
    13. public string ResultJson = ""; //记录返回的JSON值
    14. public string ErrorMessage = ""; //记录错误信息
    15. public string direction = ""; //上传时图片的方向
    16. public string image_status = ""; //上传图片的识别状态
    17. public string risk_type = ""; //上传图片的识别类型
    18. public string edit_tool = ""; //上传图片是否P图
    19. public string idcard_number_type = ""; //上传图片的识别错误信息
    20. public IdCard()
    21. {
    22. }
    23. //得到指定文件的 byte[],参数为文件绝对路径值
    24. private byte[] getImageByte(string imagePath)
    25. {
    26. FileStream files = new FileStream(imagePath, FileMode.Open);
    27. byte[] imgByte = new byte[files.Length];
    28. files.Read(imgByte, 0, imgByte.Length);
    29. files.Close();
    30. return imgByte;
    31. }
    32. //识别身份证信息方法,参数为文件绝对路径值;正反面值:正面传 front,反面传 back
    33. public void valid(string imagePath, string id_card_side)
    34. {
    35. name = "";
    36. sex = "";
    37. photo_base64 = "";
    38. nation = "";
    39. address = "";
    40. IDNumber = "";
    41. birthday = "";
    42. org = "";
    43. startDate = "";
    44. endDate = "";
    45. direction="";
    46. image_status = "";
    47. risk_type = "";
    48. edit_tool = "";
    49. idcard_number_type = "";
    50. byte[] image = getImageByte(imagePath);
    51. var APP_ID = 申请的开发ID;
    52. var API_KEY = 申请的开发KEY;
    53. var SECRET_KEY = 开发密钥;
    54. var client = new Baidu.Aip.Ocr.Ocr(API_KEY, SECRET_KEY);
    55. client.Timeout = 60000; // 修改超时时间
    56. Newtonsoft.Json.Linq.JObject result = new JObject();
    57. var options = new Dictionary<string, object>{
    58. {"detect_risk", "true"},
    59. {"detect_direction", "true"},
    60. {"detect_photo", "true"}
    61. };
    62. try
    63. {
    64. result = client.Idcard(image, id_card_side, options);
    65. ResultJson = result.ToString();
    66. if (id_card_side == "front")
    67. {
    68. name = result["words_result"]["姓名"]["words"].ToString();
    69. sex = result["words_result"]["性别"]["words"].ToString();
    70. nation = result["words_result"]["民族"]["words"].ToString();
    71. address = result["words_result"]["住址"]["words"].ToString();
    72. IDNumber = result["words_result"]["公民身份号码"]["words"].ToString();
    73. photo_base64 = result["photo"].ToString();
    74. birthday = result["words_result"]["出生"]["words"].ToString();
    75. birthday = birthday.Substring(0, 4) + "-" + birthday.Substring(4, 2) + "-" + birthday.Substring(6, 2);
    76. }
    77. if (id_card_side == "back")
    78. {
    79. org = result["words_result"]["签发机关"]["words"].ToString();
    80. startDate = result["words_result"]["签发日期"]["words"].ToString();
    81. startDate = startDate.Substring(0, 4) + "-" + startDate.Substring(4, 2) + "-" + startDate.Substring(6, 2);
    82. endDate = result["words_result"]["失效日期"]["words"].ToString();
    83. endDate = endDate.Substring(0, 4) + "-" + endDate.Substring(4, 2) + "-" + endDate.Substring(6, 2);
    84. }
    85. direction = result["direction"].ToString();
    86. switch (direction)
    87. {
    88. case "-1":
    89. direction = "未定义";
    90. break;
    91. case "0":
    92. direction = "正向";
    93. break;
    94. case "1":
    95. direction = "逆时针90度";
    96. break;
    97. case "2":
    98. direction = "逆时针180度";
    99. break;
    100. case "3":
    101. direction = "逆时针270度";
    102. break;
    103. }
    104. image_status = result["image_status"].ToString();
    105. switch (image_status)
    106. {
    107. case "normal":
    108. image_status = "识别正常";
    109. break;
    110. case "reversed_side":
    111. image_status = "身份证正反面颠倒";
    112. break;
    113. case "non_idcard":
    114. image_status = "上传的图片中不包含身份证";
    115. break;
    116. case "blurred":
    117. image_status = "身份证模糊";
    118. break;
    119. case "other_type_card":
    120. image_status = "其他类型证照";
    121. break;
    122. case "over_exposure":
    123. image_status = "身份证关键字段反光或过曝";
    124. break;
    125. case "over_dark":
    126. image_status = "身份证欠曝(亮度过低)";
    127. break;
    128. case "unknown":
    129. image_status = "未知状态";
    130. break;
    131. }
    132. risk_type = result["risk_type"].ToString();
    133. switch (risk_type)
    134. {
    135. case "normal":
    136. risk_type = "正常身份证";
    137. break;
    138. case "copy":
    139. risk_type = "复印件";
    140. break;
    141. case "temporary":
    142. risk_type = "临时身份证";
    143. break;
    144. case "screen":
    145. risk_type = "翻拍";
    146. break;
    147. case "unknown":
    148. risk_type = "其他未知情况";
    149. break;
    150. }
    151. if (ResultJson.IndexOf("edit_tool") != -1)
    152. {
    153. edit_tool = result["edit_tool"].ToString();
    154. }
    155. else
    156. {
    157. edit_tool = "未P图";
    158. }
    159. if (ResultJson.IndexOf("idcard_number_type") != -1)
    160. {
    161. idcard_number_type = result["idcard_number_type"].ToString();
    162. switch (idcard_number_type)
    163. {
    164. case "-1":
    165. idcard_number_type = "身份证正面所有字段全为空";
    166. break;
    167. case "0":
    168. idcard_number_type = "身份证证号识别错误";
    169. break;
    170. case "1":
    171. idcard_number_type = "身份证证号和性别、出生信息一致";
    172. break;
    173. case "2":
    174. idcard_number_type = "身份证证号和性别、出生信息都不一致";
    175. break;
    176. case "3":
    177. idcard_number_type = "身份证证号和出生信息不一致";
    178. break;
    179. case "4":
    180. idcard_number_type = "身份证证号和性别信息不一致";
    181. break;
    182. }
    183. }
    184. }
    185. catch (Exception e)
    186. {
    187. ErrorMessage = e.Message;
    188. }
    189. }
    190. }// idcard

    小结

    采用哪种识别方式,要根据我们在实际的应用场景中进行选择,而且也需要考虑调用的成本(本文涉及的调用成本仅供参考)。这里讲述的几种方案是我们自研产品中所采用的方式,腾讯云的人脸核身产品分支很多,大家可以根据具体需求进行选择、扩充自己的产品功能。

    再次感谢您的阅读,欢迎大家讨论指正。

  • 相关阅读:
    bootstrap-table固定左侧列+表头和内容对齐
    经典算法之折半查找(Binary Search)
    数据结构与算法简介
    【iMessage苹果相册推位置推】“MFI授权计划”是指一个零丁的苹果程序开发
    解密Docker容器网络
    设计模式 - 享元模式
    C++ 实战Mongodb CRUD操作基本用法
    分析股票怎么进行量化交易?
    BMS系统项目
    SCA Nacos 服务注册和配置中心(二)
  • 原文地址:https://blog.csdn.net/michaelline/article/details/134013152