使用asp.net开发钉钉群机器人全过程
集团是使用钉钉进行工作交流的, 发现群里有很多问题其实是重复的,就在想是不是可以使用钉钉的群机器人,虽然说的确是可以部分实现,但是感觉还是差点什么,而且公司内部很多东西也不方便放上去,所以就想开发一个群机器人,然后就看钉钉开发文档,发现是有这个功能的,就开始研究,官方文档使用的语言主要是Java,并没有c#或者asp.net的相关文档,这就意味着要从头开始开发, 所幸的是他是有c#的SDK开发包,开发包里是有DLL的,这样能省下不少事,废话不多说,上链接
https://open.dingtalk.com/document/resourcedownload/download-server-sdk
打开页面后往下拉,知道如图所示处
我下载的是.net版本,下载下来后,导入到项目中即可
然后是配置机器人,这些在往上教程很多就不多赘诉了,直接上图
一开始我是在页面上面写的,看到官方文档上面说到了header,考虑到可能要使用到request 获取,就直接在页面写了,
后来在页面上通过以后改到了WebService中,毕竟感觉上webservice 会好一些,
把消息接收地址改成了这样,其实两者代码类似,只是我可能更喜欢在接口里写
1 protected string secret = 改成你自己的机器人的appSecret;2 #region 机器人操作类3 [WebMethod]4 public void Reboot()5 {6 string result = "";7 using (StreamReader reader = new StreamReader(HttpContext.Current.Request.InputStream, Encoding.UTF8))8 {9 result = reader.ReadToEnd(); 10 } 11 try 12 { 13 string sign = HttpContext.Current.Request.Headers.GetValues("sign")[0].ToString(); 14 string timestamp = HttpContext.Current.Request.Headers.GetValues("timestamp")[0].ToString(); 15 string json = result; 16 CommonJsonModel model = SymmetricMethod.DeSerialize(json); 17 string text = model.GetModel("text").GetValue("content"); 18 string sessionWebhook = model.GetValue("sessionWebhook"); 19 string senderStaffId = model.GetValue("senderStaffId"); 20 DBHelper.InsertRebootLog(HttpContext.Current.Request, HttpContext.Current.Request.Url.ToString(), "调用机器人", text + "--" + sessionWebhook + "--" + senderStaffId, sign + "----------" + timestamp, result, HttpContext.Current.Request.Headers, "调用机器人"); 21 } 22 catch (Exception ex) 23 { 24 DBHelper.InsertRebootLog(HttpContext.Current.Request, HttpContext.Current.Request.Url.ToString(), "调用机器人", result, ex.Message, "接口调用来源不正确","", "调用机器人"); 25 } 26 } 27 #endregion
这是webservice 接口的
1 string result = "";2 using (StreamReader reader = new StreamReader(Request.InputStream, Encoding.UTF8))3 {4 result = reader.ReadToEnd();5 }6 try7 {8 string sign = Request.Headers.GetValues("sign")[0].ToString();9 string timestamp = Request.Headers.GetValues("timestamp")[0].ToString(); 10 string json = result; 11 CommonJsonModel model = SymmetricMethod.DeSerialize(json); 12 string text = model.GetModel("text").GetValue("content"); 13 string sessionWebhook = model.GetValue("sessionWebhook"); 14 string senderStaffId = model.GetValue("senderStaffId"); 15 DBHelper.InsertRebootLog(Request, Request.Url.ToString(), "调用机器人", text + "--" + sessionWebhook + "--" + senderStaffId, sign + "----------" + timestamp, result, Request.Headers, "调用机器人"); 16 } 17 catch (Exception ex) 18 { 19 DBHelper.InsertRebootLog(Request, Request.Url.ToString(), "调用机器人", result, ex.Message, "", "", "调用机器人"); 20 }
这是写在页面Page_Load方法里面的,因为只要执行到这个页面,就是直接执行,没有任何其他操作,所以一定要写在Page_Load方法里
那么json 解析的源码我也放后面,也就是 CommonJsonModel 这个方法的代码
直接建两个类,名字分别是CommonJsonModelAnalyzer 和 CommonJsonModel
1 using System;2 using System.Collections.Generic;3 using System.Web;4 using System.Text;5 6 /// <summary>7 ///CommonJsonModelAnalyzer 的摘要说明8 /// </summary>9 public class CommonJsonModelAnalyzer10 {11 public CommonJsonModelAnalyzer()12 {13 //14 //TODO: 在此处添加构造函数逻辑15 //16 17 }18 protected string _GetKey(string rawjson)19 {20 if (string.IsNullOrEmpty(rawjson))21 return rawjson;22 23 rawjson = rawjson.Trim();24 25 string[] jsons = rawjson.Split(new char[] { ':' });26 27 if (jsons.Length < 2)28 return rawjson;29 30 return jsons[0].Replace("\"", "").Trim();31 }32 33 protected string _GetValue(string rawjson)34 {35 if (string.IsNullOrEmpty(rawjson))36 return rawjson;37 38 rawjson = rawjson.Trim();39 40 string[] jsons = rawjson.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);41 42 if (jsons.Length < 2)43 return rawjson;44 45 StringBuilder builder = new StringBuilder();46 47 for (int i = 1; i < jsons.Length; i++)48 {49 builder.Append(jsons[i]);50 51 builder.Append(":");52 }53 54 if (builder.Length > 0)55 builder.Remove(builder.Length - 1, 1);56 57 string value = builder.ToString();58 59 if (value.StartsWith("\""))60 value = value.Substring(1);61 62 if (value.EndsWith("\""))63 value = value.Substring(0, value.Length - 1);64 65 return value;66 }67 68 protected List<string> _GetCollection(string rawjson)69 {70 //[{},{}]71 72 List<string> list = new List<string>();73 74 if (string.IsNullOrEmpty(rawjson))75 return list;76 77 rawjson = rawjson.Trim();78 79 StringBuilder builder = new StringBuilder();80 81 int nestlevel = -1;82 83 int mnestlevel = -1;84 85 for (int i = 0; i < rawjson.Length; i++)86 {87 if (i == 0)88 continue;89 else if (i == rawjson.Length - 1)90 continue;91 92 char jsonchar = rawjson[i];93 94 if (jsonchar == '{')95 {96 nestlevel++;97 }98 99 if (jsonchar == '}') 100 { 101 nestlevel--; 102 } 103 104 if (jsonchar == '[') 105 { 106 mnestlevel++; 107 } 108 109 if (jsonchar == ']') 110 { 111 mnestlevel--; 112 } 113 114 if (jsonchar == ',' && nestlevel == -1 && mnestlevel == -1) 115 { 116 list.Add(builder.ToString()); 117 118 builder = new StringBuilder(); 119 } 120 else 121 { 122 builder.Append(jsonchar); 123 } 124 } 125 126 if (builder.Length > 0) 127 list.Add(builder.ToString()); 128 129 return list; 130 } 131 }
1 using System;2 using System.Collections.Generic;3 using System.Web;4 5 /// <summary>6 ///CommonJsonModel 的摘要说明7 /// </summary>8 public class CommonJsonModel : CommonJsonModelAnalyzer9 {10 private string rawjson;11 12 private bool isValue = false;13 14 private bool isModel = false;15 16 private bool isCollection = false;17 private string json;18 19 internal CommonJsonModel(string rawjson)20 {21 this.rawjson = rawjson;22 23 if (string.IsNullOrEmpty(rawjson))24 throw new Exception("missing rawjson");25 26 rawjson = rawjson.Trim();27 28 if (rawjson.StartsWith("{"))29 {30 isModel = true;31 }32 else if (rawjson.StartsWith("["))33 {34 isCollection = true;35 }36 else37 {38 isValue = true;39 }40 }41 42 public string Rawjson43 {44 get { return rawjson; }45 }46 47 public bool IsValue()48 {49 return isValue;50 }51 public bool IsValue(string key)52 {53 if (!isModel)54 return false;55 56 if (string.IsNullOrEmpty(key))57 return false;58 59 foreach (string subjson in base._GetCollection(this.rawjson))60 {61 CommonJsonModel model = new CommonJsonModel(subjson);62 63 if (!model.IsValue())64 continue;65 66 if (model.Key == key)67 {68 CommonJsonModel submodel = new CommonJsonModel(model.Value);69 70 return submodel.IsValue();71 }72 }73 74 return false;75 }76 public bool IsModel()77 {78 return isModel;79 }80 public bool IsModel(string key)81 {82 if (!isModel)83 return false;84 85 if (string.IsNullOrEmpty(key))86 return false;87 88 foreach (string subjson in base._GetCollection(this.rawjson))89 {90 CommonJsonModel model = new CommonJsonModel(subjson);91 92 if (!model.IsValue())93 continue;94 95 if (model.Key == key)96 {97 CommonJsonModel submodel = new CommonJsonModel(model.Value);98 99 return submodel.IsModel(); 100 } 101 } 102 103 return false; 104 } 105 public bool IsCollection() 106 { 107 return isCollection; 108 } 109 public bool IsCollection(string key) 110 { 111 if (!isModel) 112 return false; 113 114 if (string.IsNullOrEmpty(key)) 115 return false; 116 117 foreach (string subjson in base._GetCollection(this.rawjson)) 118 { 119 CommonJsonModel model = new CommonJsonModel(subjson); 120 121 if (!model.IsValue()) 122 continue; 123 124 if (model.Key == key) 125 { 126 CommonJsonModel submodel = new CommonJsonModel(model.Value); 127 128 return submodel.IsCollection(); 129 } 130 } 131 132 return false; 133 } 134 135 136 /// <summary> 137 /// 当模型是对象,返回拥有的key 138 /// </summary> 139 /// <returns></returns> 140 public List<string> GetKeys() 141 { 142 if (!isModel) 143 return null; 144 145 List<string> list = new List<string>(); 146 147 foreach (string subjson in base._GetCollection(this.rawjson)) 148 { 149 string key = new CommonJsonModel(subjson).Key; 150 151 if (!string.IsNullOrEmpty(key)) 152 list.Add(key); 153 } 154 155 return list; 156 } 157 158 /// <summary> 159 /// 当模型是对象,key对应是值,则返回key对应的值 160 /// </summary> 161 /// <param name="key"></param> 162 /// <returns></returns> 163 public string GetValue(string key) 164 { 165 if (!isModel) 166 return null; 167 168 if (string.IsNullOrEmpty(key)) 169 return null; 170 171 foreach (string subjson in base._GetCollection(this.rawjson)) 172 { 173 CommonJsonModel model = new CommonJsonModel(subjson); 174 175 if (!model.IsValue()) 176 continue; 177 178 if (model.Key == key) 179 return model.Value; 180 } 181 182 return null; 183 } 184 185 /// <summary> 186 /// 模型是对象,key对应是对象,返回key对应的对象 187 /// </summary> 188 /// <param name="key"></param> 189 /// <returns></returns> 190 public CommonJsonModel GetModel(string key) 191 { 192 if (!isModel) 193 return null; 194 195 if (string.IsNullOrEmpty(key)) 196 return null; 197 198 foreach (string subjson in base._GetCollection(this.rawjson)) 199 { 200 CommonJsonModel model = new CommonJsonModel(subjson); 201 202 if (!model.IsValue()) 203 continue; 204 205 if (model.Key == key) 206 { 207 CommonJsonModel submodel = new CommonJsonModel(model.Value); 208 209 if (!submodel.IsModel()) 210 return null; 211 else 212 return submodel; 213 } 214 } 215 216 return null; 217 } 218 219 /// <summary> 220 /// 模型是对象,key对应是集合,返回集合 221 /// </summary> 222 /// <param name="key"></param> 223 /// <returns></returns> 224 public CommonJsonModel GetCollection(string key) 225 { 226 if (!isModel) 227 return null; 228 229 if (string.IsNullOrEmpty(key)) 230 return null; 231 232 foreach (string subjson in base._GetCollection(this.rawjson)) 233 { 234 CommonJsonModel model = new CommonJsonModel(subjson); 235 236 if (!model.IsValue()) 237 continue; 238 239 if (model.Key == key) 240 { 241 CommonJsonModel submodel = new CommonJsonModel(model.Value); 242 243 if (!submodel.IsCollection()) 244 return null; 245 else 246 return submodel; 247 } 248 } 249 250 return null; 251 } 252 253 /// <summary> 254 /// 模型是集合,返回自身 255 /// </summary> 256 /// <returns></returns> 257 public List<CommonJsonModel> GetCollection() 258 { 259 List<CommonJsonModel> list = new List<CommonJsonModel>(); 260 261 if (IsValue()) 262 return list; 263 264 foreach (string subjson in base._GetCollection(rawjson)) 265 { 266 list.Add(new CommonJsonModel(subjson)); 267 } 268 269 return list; 270 } 271 272 273 274 275 /// <summary> 276 /// 当模型是值对象,返回key 277 /// </summary> 278 private string Key 279 { 280 get 281 { 282 if (IsValue()) 283 return base._GetKey(rawjson); 284 285 return null; 286 } 287 } 288 /// <summary> 289 /// 当模型是值对象,返回value 290 /// </summary> 291 private string Value 292 { 293 get 294 { 295 if (!IsValue()) 296 return null; 297 298 return base._GetValue(rawjson); 299 } 300 } 301 }
另外还要再建一个调用json解析方法的类 我的名称叫做SymmetricMethod,你们就随意起
在这个类里面写一个方法
1 public static CommonJsonModel DeSerialize(string json) 2 { 3 return new CommonJsonModel(json); 4 }
一定要静态类,方便调用
其实到这一步一些关键内容的核心已经全部写完了,接下来就是如何使用
按照官方文档的说法,是需要对信息进行验证的
开发者需对header中的timestamp和sign进行验证,以判断是否是来自钉钉的合法请求,避免其他仿冒钉钉调用开发者的HTTPS服务传送数据,具体验证逻辑如下:timestamp 与系统当前时间戳如果相差1小时以上,则认为是非法的请求。sign 与开发者自己计算的结果不一致,则认为是非法的请求。必须当timestamp和sign同时验证通过,才能认为是来自钉钉的合法请求。
其中会有sign 计算方法,那么我们就按照文档说的做,
sign的计算方法header中的timestamp + "\n" + 机器人的appSecret当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,得到最终的签名值。
1 //获得时间戳2 public static long ToUTC(DateTime time)3 {4 var zts = TimeZoneInfo.Local.BaseUtcOffset;5 var yc = new DateTime(1970, 1, 1).Add(zts);6 return (long)(DateTime.Now - yc).TotalMilliseconds;7 }8 //计算签名值9 public static string GetHmac(string message, string secret) 10 { 11 byte[] keyByte = Encoding.UTF8.GetBytes(secret); 12 byte[] messageBytes = Encoding.UTF8.GetBytes(message); 13 using (var hmacsha256 = new HMACSHA256(keyByte)) 14 { 15 byte[] hashmessage = hmacsha256.ComputeHash(messageBytes); 16 string hash = Convert.ToBase64String(hashmessage).Replace("+"," "); 17 return hash; 18 } 19 }
以上两段代码网上就能搜到,其中计算签名值网上写的并不完全,因为我们计算出来的签名值与钉钉的实际签名值就差一个“+”和“ ”,所以在最后直接替换就可以了
1 private bool GetSign(string timestamp, string secret, string sign)2 {3 try4 {5 //获取当前时间的时间戳6 long currentTime = SymmetricMethod.ToUTC(DateTime.Now);7 long dingTimestamp = long.Parse(timestamp);8 long time = currentTime - dingTimestamp;9 string stringToSign = SymmetricMethod.GetHmac(dingTimestamp + "\n" + secret, secret).ToString(); 10 if (time < 3600000 && sign.Equals(stringToSign)) 11 { 12 return true; 13 } 14 return false; 15 } 16 catch (Exception ex) 17 { 18 return false; 19 } 20 }
这样我们就获得了钉钉返回的sign 和timestamp 和我们自己计算出来的sign ,然后根据规则进行判断即可
那么最终合在一起形成这样一段代码
1 #region 机器人操作类2 [WebMethod]3 public void Reboot()4 {5 string result = "";6 using (StreamReader reader = new StreamReader(HttpContext.Current.Request.InputStream, Encoding.UTF8))7 {8 result = reader.ReadToEnd();9 } 10 try 11 { 12 string sign = HttpContext.Current.Request.Headers.GetValues("sign")[0].ToString(); 13 string timestamp = HttpContext.Current.Request.Headers.GetValues("timestamp")[0].ToString(); 14 string json = result; 15 CommonJsonModel model = SymmetricMethod.DeSerialize(json); 16 string text = model.GetModel("text").GetValue("content"); 17 string sessionWebhook = model.GetValue("sessionWebhook"); 18 string senderStaffId = model.GetValue("senderStaffId"); 19 DBHelper.InsertRebootLog(HttpContext.Current.Request, HttpContext.Current.Request.Url.ToString(), "调用机器人", text + "--" + sessionWebhook + "--" + senderStaffId, sign + "----------" + timestamp, result, HttpContext.Current.Request.Headers, "调用机器人"); 20 21 if (GetSign(timestamp, secret, sign))//验证,如果不通过另行操作或者不返回都可以 22 { 23 DefaultDingTalkClient client = new DefaultDingTalkClient(sessionWebhook); 24 text(client, userid, "返回文本测试效果"); 25 markdown(client, userid, "测试markdown", "返回markdown测试效果"); 26 actionCard(client, userid, "测试actionCard", "返回actionCard测试效果", "点击详情", "https://www.taiwei6.com"); 27 } 28 } 29 catch (Exception ex) 30 { 31 DBHelper.InsertRebootLog(HttpContext.Current.Request, HttpContext.Current.Request.Url.ToString(), "调用机器人", result, ex.Message, "接口调用来源不正确","", "调用机器人"); 32 } 33 }
钉钉机器人总共是能够范围三种类型的分别是text ,markdown,actioncard ,
上源码
1 /**2 * 实现@人员3 * @param client4 * @param userId5 * 返回文本6 */7 private void text(DefaultDingTalkClient client, String userId, string textcontent)8 {9 try10 {11 OapiRobotSendRequest request = new OapiRobotSendRequest();12 request.Msgtype = "text";13 OapiRobotSendRequest.TextDomain text = new OapiRobotSendRequest.TextDomain();14 text.Content = " @" + userId + " \n " + textcontent;15 request.Text_ = text;16 OapiRobotSendRequest.AtDomain at = new OapiRobotSendRequest.AtDomain();17 18 List<string> userids = new List<string>();19 userids.Add(userId);20 at.AtUserIds = userids;21 // isAtAll类型如果不为Boolean,请升级至最新SDK22 at.IsAtAll = false;23 request.At_ = at;24 OapiRobotSendResponse response = client.Execute(request);25 int code = Convert.ToInt32(response.Errcode);26 string msg = response.Errmsg;27 }28 catch (Exception e)29 {30 31 }32 }33 34 /**35 * markdown@人员效果36 *37 * @param client38 * @param userId39 * 40 * 返回markdown41 * 42 */43 private void markdown(DefaultDingTalkClient client, String userId, string title, string textcontent)44 {45 try46 {47 OapiRobotSendRequest request = new OapiRobotSendRequest();48 request.Msgtype = "markdown";49 OapiRobotSendRequest.MarkdownDomain markdown = new OapiRobotSendRequest.MarkdownDomain();50 markdown.Title = title;51 markdown.Text = " @" + userId + " \n " + textcontent;52 request.Markdown_ = markdown;53 OapiRobotSendRequest.AtDomain at = new OapiRobotSendRequest.AtDomain();54 List<string> userids = new List<string>();55 userids.Add(userId);56 at.AtUserIds = userids;57 58 at.IsAtAll = false;59 request.At_ = at;60 OapiRobotSendResponse response = client.Execute(request);61 int code = Convert.ToInt32(response.Errcode);62 string msg = response.Errmsg;63 }64 catch (Exception e)65 {66 67 }68 }69 /**70 * actionCard@人员效果71 * @param client72 * @param userId73 */74 private void actionCard(DefaultDingTalkClient client, String userId, string title, string textcontent, string SingleTitle, string url)75 {76 try77 {78 OapiRobotSendRequest request = new OapiRobotSendRequest();79 request.Msgtype = "actionCard";80 OapiRobotSendRequest.ActioncardDomain actionCard = new OapiRobotSendRequest.ActioncardDomain();81 actionCard.Title = title;82 actionCard.Text = " @" + userId + " \n " + textcontent;83 ;84 actionCard.SingleTitle = SingleTitle;85 actionCard.SingleURL = url;86 request.ActionCard_ = actionCard;87 OapiRobotSendRequest.AtDomain at = new OapiRobotSendRequest.AtDomain();88 List<string> userids = new List<string>();89 userids.Add(userId);90 at.AtUserIds = userids;91 92 at.IsAtAll = false;93 request.At_ = at;94 OapiRobotSendResponse response = client.Execute(request);95 int code = Convert.ToInt32(response.Errcode);96 string msg = response.Errmsg;97 }98 catch (Exception e)99 { 100 101 } 102 }
文档中还提到有几种markdown 的用法,分别是标题,引用,字体,链接,图片,有序列表,无序列表的使用,从他的案例中可以看出,只是传入的text加上特殊符号即可
标题 # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### 五级标题 ###### 六级标题引用 > A man who stands for nothing will fall for anything.文字加粗、斜体 **bold** *italic*链接 [this is a link](https://www.dingtalk.com/)图片 ![](http://name.com/pic.jpg)无序列表 - item1 - item2有序列表 1. item1 2. item2换行(建议\n前后各添加两个空格)\n
至此,开发钉钉群机器人的所有开发过程写完了。
使用asp.net开发钉钉群机器人全过程相关推荐
- 使用禅道或Jira系统对接钉钉的群机器人消息管理,为什么没有艾特 @人呢?
首先,这个功能的方法如下: 禅道系统的Bug动态,对接钉钉软件,实时进行钉钉群内提醒,机器人并@ 艾特指派的开发人员. https://blog.csdn.net/woshiyigerenlaide/ ...
- 钉钉群机器人定时发送消息并@所有人
1.添加钉钉自定义群机器人 参考文章如下: 官方网址:https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.p2lr6t&a ...
- golang 钉钉群机器人
目录 1. 钉钉SDK 2. 代码示例 3. 消息类型 3.1 text类型 3.2 link类型 3.3 markdown类型 3.4 整体跳转ActionCard类型 3.5 独立跳转Action ...
- 利用PHP实现钉钉群机器人的webhook自定义通知
前言 这阵子除了写PHP, 还在写C#的socket服务器端, 第一次写软件, 所以bug总是特别的多. 放在远程服务器上, 说不准什么时候软件就出异常了. 于是在PHP端写了个监测程序, 如果服务器 ...
- python企业微信群聊_给企业微信加个群机器人
现在很多企业在使用企业微信或钉钉进行工作交流,我们可以在群里添加一个自定义群机器人,定时发送一些提醒或咨询信息,它可以作为一个小组手,也为工作增加一点乐趣. 群机器人 下面是企业微信和钉钉的群机器人文 ...
- python + ldap +jira 发送 钉钉@艾特人
jira内容变更后,发送钉钉到群,能@艾特对应的指定人提醒. 前提依赖:openldap:python3:ldap3:Django 关于部署openLDAP请观看上一篇文章:https://blog. ...
- 机器人聊天软件c#_使用python3.7配置开发钉钉群自定义机器人(2020年新版攻略)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_132 最近疫情比较严重,很多公司依靠阿里旗下的办公软件钉钉来进行远程办公,当然了,钉钉这个产品真的是让人一言难尽,要多难用有多难用 ...
- Python 3 开发钉钉群机器人
转载自「刘悦的技术博客」 链接: v3u.cn/a_id_132 最近疫情比较严重,很多公司依靠阿里旗下的办公软件钉钉来进行远程办公,当然了,钉钉这个产品真的是让人一言难尽,要多难用有多难用,真的让人 ...
- Asp.Net Core对接钉钉群机器人
钉钉作为企业办公越来越常用的软件,对于企业内部自研系统提供接口支持,以此来打通多平台下的数据,本次先使用最简单的钉钉群机器人完成多种形式的消息推送,参考钉钉开发文档中自定义机器人环节,此次尝试所花的时 ...
最新文章
- win 复制linux文件命令行,windows与Linux间远程拷贝文件(pscp命令)
- python中用来占位的语句是_python占位语句
- 赞!《Python面试大全》PDF版来啦!
- 智慧显示:5G时代的新机遇
- 【Python科学计算系列】矩阵
- java时间格式转js_使用jquery或java脚本将日期时间转换为rfc3339格式
- nginx epoll详解
- Bitcoin 0.7.0 发布, P2P网络的匿名数字货币
- 20190903每日一句
- c语言程序设计mooc作业平台答案,C语言程序设计下mooc答案.docx
- qt 禁止alt+f4_禁止上下关闭按钮和Alt + F4
- 树莓派(raspberry pi)学习12: 树莓派创意无限,无所不能
- 明明可以靠脸吃饭偏要靠才华_你身边有女神程序员吗?
- SpringCache-redis缓存学习记录
- 程序员裸辞三个月,终于拿到大厂offer!网友:不应该!
- Keil 编译前后 自动将 hex 转 bin
- mysql格式化日期和时间
- MariaDB Galera Cluster 集群部署
- 【MySQL】逻辑库与数据表相关操作
- 笨方法学python练习7.更多打印