企业微信消息推送接口

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Text;
 4 
 5 namespace Nany.Web.Trans
 6 {
 7     public class AccessToken
 8     {
 9         private string Access_token;
10 
11         public string access_token
12         {
13             get { return Access_token; }
14             set { Access_token = value; }
15         }
16         private int Expires_in;
17 
18         public int expires_in
19         {
20             get { return Expires_in; }
21             set { Expires_in = value; }
22         }
23 
24         private int Errcode;
25 
26         public int errcode
27         {
28             get
29             {
30                 return Errcode;
31             }
32             set
33             {
34                 Errcode = value;
35             }
36         }
37 
38         private string Errmsg;
39 
40         public string errmsg
41         {
42             get
43             {
44                 return Errmsg;
45             }
46             set
47             {
48                 Errmsg = value;
49             }
50         }
51 
52 
53     }
54 }
AccessToken
 1 using System;
 2 using System.Collections.Generic;
 3 using System.Text;
 4 
 5 namespace Nany.Web.Trans
 6 {
 7     public class ThumbMedia
 8     {
 9         private string Type;
10 
11         public string type
12         {
13             get { return Type; }
14             set { Type = value; }
15         }
16         private string Media_Id;
17 
18         public string media_id
19         {
20             get { return Media_Id; }
21             set { Media_Id = value; }
22         }
23 
24         private string Created_At;
25 
26         public string created_at
27         {
28             get { return Created_At; }
29             set { Created_At = value; }
30         }
31 
32         private int Errcode;
33 
34         public int errcode
35         {
36             get
37             {
38                 return Errcode;
39             }
40             set
41             {
42                 Errcode = value;
43             }
44         }
45 
46         private string Errmsg;
47 
48         public string errmsg
49         {
50             get
51             {
52                 return Errmsg;
53             }
54             set
55             {
56                 Errmsg = value;
57             }
58         }
59 
60     }
61 }
ThumbMedia
  1 using System;
  2 using System.IO;
  3 using System.Net;
  4 using System.Text;
  5 using System.Web;
  6 
  7 namespace Nany.Web.Trans
  8 {
  9     /// <summary>
 10     /// HTTP请求工具类
 11 
 12     /// </summary>
 13     public class HttpRequestUtil
 14     {
 15         #region 请求Url
 16         #region 请求Url,不发送数据
 17 
 18         /// <summary>
 19         /// 请求Url,不发送数据
 20 
 21         /// </summary>
 22         public static string RequestUrl(string url)
 23         {
 24             return RequestUrl(url, "POST");
 25         }
 26         #endregion
 27 
 28         #region 请求Url,不发送数据
 29 
 30 
 31         public static string GetAppPage(string url, int httpTimeout, Encoding postEncoding)
 32         {
 33             string rStr = "";
 34             System.Net.WebRequest req = null;
 35             System.Net.WebResponse resp = null;
 36             System.IO.Stream os = null;
 37             System.IO.StreamReader sr = null;
 38             try
 39             {
 40                 //创建连接
 41                 req = System.Net.WebRequest.Create(url);
 42                 req.ContentType = "application/x-www-form-urlencoded";
 43                 req.Method = "GET";
 44 
 45                 //时间
 46                 if (httpTimeout > 0)
 47                 {
 48                     req.Timeout = httpTimeout;
 49                 }
 50 
 51                 //读取返回结果
 52                 resp = req.GetResponse();
 53                 sr = new System.IO.StreamReader(resp.GetResponseStream(), postEncoding);
 54                 rStr = sr.ReadToEnd();
 55                 rStr = rStr.Trim();         //除去空格
 56             }
 57             catch
 58             {
 59 
 60 
 61             }
 62             finally
 63             {
 64                 try
 65                 {
 66                     //关闭资源
 67                     if (os != null)
 68                     {
 69                         os.Dispose();
 70                         os.Close();
 71                     }
 72                     if (sr != null)
 73                     {
 74                         sr.Dispose();
 75                         sr.Close();
 76                     }
 77                     if (resp != null)
 78                     {
 79                         resp.Close();
 80                     }
 81                     if (req != null)
 82                     {
 83                         req.Abort();
 84                         req = null;
 85                     }
 86                 }
 87                 catch
 88                 {
 89 
 90                 }
 91             }
 92             return rStr;
 93 
 94         }
 95 
 96         public static string HttpPosturl(string filePath, string url)
 97         {
 98             string returnStr = string.Empty;
 99             using (WebClient client = new WebClient())
100             {
101                 byte[] data = client.UploadFile(url, filePath);
102                 returnStr = Encoding.Default.GetString(data);
103             }
104 
105             return returnStr;
106         }
107 
108         /// <summary>
109         /// 请求Url,不发送数据
110 
111         /// </summary>
112         public static string RequestUrl(string url, string method)
113         {
114             // 设置参数
115             HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
116             request.Timeout = 600000;
117             CookieContainer cookieContainer = new CookieContainer();
118             request.CookieContainer = cookieContainer;
119             request.AllowAutoRedirect = true;
120             request.Method = method;
121             request.ContentType = "text/html";
122             request.Headers.Add("charset", "utf-8");
123 
124             //发送请求并获取相应回应数据
125             HttpWebResponse response = request.GetResponse() as HttpWebResponse;
126             //直到request.GetResponse()程序才开始向目标网页发送Post请求
127             Stream responseStream = response.GetResponseStream();
128             StreamReader sr = new StreamReader(responseStream, Encoding.UTF8);
129             //返回结果网页(html)代码
130 
131             string content = sr.ReadToEnd();
132             return content;
133         }
134         #endregion
135 
136         #region 请求Url,发送数据
137 
138         /// <summary>
139         /// 请求Url,发送数据
140 
141         /// </summary>
142         public static string PostUrl(string url, string postData)
143         {
144             byte[] data = Encoding.UTF8.GetBytes(postData);
145 
146             // 设置参数
147             HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
148             CookieContainer cookieContainer = new CookieContainer();
149             request.Timeout = 600000;
150             request.CookieContainer = cookieContainer;
151             request.AllowAutoRedirect = true;
152             request.Method = "POST";
153             request.ContentType = "application/x-www-form-urlencoded";
154             request.ContentLength = data.Length;
155             Stream outstream = request.GetRequestStream();
156             outstream.Write(data, 0, data.Length);
157             outstream.Close();
158 
159             //发送请求并获取相应回应数据
160             HttpWebResponse response = request.GetResponse() as HttpWebResponse;
161             //直到request.GetResponse()程序才开始向目标网页发送Post请求
162             Stream instream = response.GetResponseStream();
163             StreamReader sr = new StreamReader(instream, Encoding.UTF8);
164             //返回结果网页(html)代码
165 
166             string content = sr.ReadToEnd();
167             return content;
168         }
169         #endregion
170         #endregion
171 
172         #region Http下载文件
173         /// <summary>
174         /// Http下载文件
175         /// </summary>
176         public static string HttpDownloadFile(string url, string path)
177         {
178             // 设置参数
179             HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
180             request.Timeout = 600000;
181             //发送请求并获取相应回应数据
182             HttpWebResponse response = request.GetResponse() as HttpWebResponse;
183             //直到request.GetResponse()程序才开始向目标网页发送Post请求
184             Stream responseStream = response.GetResponseStream();
185 
186             //创建本地文件写入流
187 
188             Stream stream = new FileStream(path, FileMode.Create);
189 
190             byte[] bArr = new byte[1024];
191             int size = responseStream.Read(bArr, 0, (int)bArr.Length);
192             while (size > 0)
193             {
194                 stream.Write(bArr, 0, size);
195                 size = responseStream.Read(bArr, 0, (int)bArr.Length);
196             }
197             stream.Close();
198             responseStream.Close();
199             return path;
200         }
201         #endregion
202 
203         #region Http上传文件
204         /// <summary>
205         /// Http上传文件
206         /// </summary>
207         public static string HttpUploadFile(string url, string path)
208         {
209             // 设置参数
210             HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
211             request.Timeout = 600000;
212             CookieContainer cookieContainer = new CookieContainer();
213             request.CookieContainer = cookieContainer;
214             request.AllowAutoRedirect = true;
215             request.Method = "POST";
216             string boundary = DateTime.Now.Ticks.ToString("X"); // 随机分隔线
217 
218             request.ContentType = "multipart/form-data;charset=utf-8;boundary=" + boundary;
219             byte[] itemBoundaryBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
220             byte[] endBoundaryBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
221 
222             int pos = path.LastIndexOf("\\");
223             string fileName = path.Substring(pos + 1);
224 
225             //请求头部信息 
226             StringBuilder sbHeader = new StringBuilder(string.Format("Content-Disposition:form-data;name=\"file\";filename=\"{0}\"\r\nContent-Type:application/octet-stream\r\n\r\n", fileName));
227             byte[] postHeaderBytes = Encoding.UTF8.GetBytes(sbHeader.ToString());
228 
229             FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
230             byte[] bArr = new byte[fs.Length];
231             fs.Read(bArr, 0, bArr.Length);
232             fs.Close();
233 
234             Stream postStream = request.GetRequestStream();
235             postStream.Write(itemBoundaryBytes, 0, itemBoundaryBytes.Length);
236             postStream.Write(postHeaderBytes, 0, postHeaderBytes.Length);
237             postStream.Write(bArr, 0, bArr.Length);
238             postStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
239             postStream.Close();
240 
241             //发送请求并获取相应回应数据
242             HttpWebResponse response = request.GetResponse() as HttpWebResponse;
243             //直到request.GetResponse()程序才开始向目标网页发送Post请求
244             Stream instream = response.GetResponseStream();
245             StreamReader sr = new StreamReader(instream, Encoding.UTF8);
246             //返回结果网页(html)代码
247 
248             string content = sr.ReadToEnd();
249             return content;
250         }
251         #endregion
252 
253     }
254 }
HTTP请求工具类
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Web.Security;
  4 using System.Text;
  5 using System.IO;
  6 using System.Net;
  7 using System.Xml;
  8 using System.Data;
  9 using System.Collections;
 10 using Newtonsoft.Json;
 11 using TWays.Core.DBAccess;
 12 
 13 namespace Nany.Web.Trans
 14 {
 15     public class WeixinManager
 16     {
 17         #region 公共方法
 18 
 19         /// <summary>
 20         /// 记录日志
 21         /// </summary>
 22         /// <param name="strMsg"></param>
 23         private void Logger(string strMsg)
 24         {
 25             string strFilePath = System.Windows.Forms.Application.StartupPath + "\\" + StaticConst.WxSendLog;
 26             TWays.Core.Loger.LogMessage(strFilePath, strMsg, true);
 27         }
 28 
 29         private DataSet GetValue(string type)
 30         {
 31             DataSet dr = DataAdapter.Query(string.Format(SqlText.selectGetReprotTypeValue.ToUpper(),type));
 32 
 33             return dr;
 34         }
 35 
 36         #endregion
 37 
 38         #region 获取配置信息
 39 
 40 
 41         /// <summary>
 42         /// 获取报表
 43         /// </summary>
 44         /// <returns></returns>
 45         public string GetReprotName(string type)
 46         {
 47             DataSet ds = GetValue(type);
 48             if (ds.Tables[0].Rows.Count < 0) return null;
 49 
 50             return TWays.Utils.ToString(ds.Tables[0].Rows[0]["DIC_NAME"]);
 51         }
 52 
 53         /// <summary>
 54         /// 获取APP_ID
 55         /// </summary>
 56         /// <returns></returns>
 57         public string GetWeixinAppId(string type)
 58         {
 59             //return GetConfigValue(StaticConst.WX_APP_ID);
 60             DataSet ds = GetValue(type);
 61             if (ds.Tables[0].Rows.Count < 0) return null;
 62 
 63             return TWays.Utils.ToString(ds.Tables[0].Rows[0]["APP_ID"]);
 64         }
 65 
 66         /// <summary>
 67         /// 获取APP_SECRET
 68         /// </summary>
 69         /// <returns></returns>
 70         public string GetWeixinAppSecret(string type)
 71         {
 72             //return GetConfigValue(StaticConst.WX_APP_SECRET); 在.config中获取配置信息
 73             DataSet ds = GetValue(type);
 74             if (ds.Tables[0].Rows.Count < 0) return null;
 75 
 76             return TWays.Utils.ToString(ds.Tables[0].Rows[0]["APP_SECRT"]);
 77         }
 78 
 79         /// <summary>
 80         /// 获取企业号CorpId
 81         /// </summary>
 82         /// <returns></returns>
 83         public string GetWeixinQyCorpId(string type)
 84         {
 85             //return GetConfigValue(StaticConst.WX_QY_CORP_ID);
 86             DataSet ds = GetValue(type);
 87             if (ds.Tables[0].Rows.Count < 0) return null;
 88 
 89             return TWays.Utils.ToString(ds.Tables[0].Rows[0]["CORP_ID"]);
 90         }
 91 
 92        
 93 
 94 
 95         /// <summary>
 96         /// 获取配置信息
 97         /// </summary>
 98         /// <param name="strKey"></param>
 99         /// <returns></returns>
100         public string GetConfigValue(string strKey)
101         {
102             string strKeyValue = string.Empty;
103 
104             strKeyValue = TWays.Utils.ToString(System.Configuration.ConfigurationManager.AppSettings[strKey]);
105 
106             return strKeyValue;
107         }
108 
109         /// <summary>
110         /// 获取各报表的上传图片路径
111         /// </summary>
112         /// <param name="strReprot"></param>
113         /// <returns></returns>
114         public string GetWeixinMediaPath(string strReprot)
115         {
116             string strAgentId = System.Windows.Forms.Application.StartupPath + "\\Logo\\";
117 
118             string strPicName = "Null.jpg";
119 
120             strAgentId += strPicName;
121             return strAgentId;
122         }
123         
124         #endregion
125 
126         #region 获取POST消息
127 
128         /// <summary>
129         /// 获取post返回来的数据
130         /// </summary>
131         /// <returns></returns>
132         public static string PostInput()
133         {
134             Stream s = System.Web.HttpContext.Current.Request.InputStream;
135             byte[] b = new byte[s.Length];
136             s.Read(b, 0, (int)s.Length);
137             return Encoding.UTF8.GetString(b);
138         }
139 
140         #endregion
141 
142         #region 获取企业号AccessToken
143 
144         public string GetQyAccessToken(string type)
145         {
146             string QY_CorpID = this.GetWeixinQyCorpId(type);
147             string QY_AppSecret = this.GetWeixinAppSecret(type);
148 
149             string url = string.Format("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={0}&corpsecret={1}", QY_CorpID, QY_AppSecret);
150             return ToAccessTokenJson(HttpRequestUtil.GetAppPage(url, 5000, Encoding.UTF8));
151         }
152 
153         public string ToAccessTokenJson(string val)
154         {
155             AccessToken deserializedToken = (AccessToken)JavaScriptConvert.DeserializeObject(val, typeof(AccessToken));
156             return deserializedToken.access_token;
157         }
158 
159         #endregion
160 
161         #region 获取MediaId
162 
163         public string GetQyMediaId(string strReprot)
164         {
165             string QY_MediaId = string.Empty;
166 
167             string strAccessToken = this.GetQyAccessToken(strReprot);
168 
169             string url = string.Format("https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token={0}&type=image", strAccessToken);
170 
171             string postData = GetWeixinMediaPath(strReprot);
172 
173             QY_MediaId = ToAccessMediaId(HttpRequestUtil.HttpPosturl(postData, url));
174 
175             return QY_MediaId;
176         }
177         public string ToAccessMediaId(string val)
178         {
179             ThumbMedia deserializedMediaId = (ThumbMedia)JavaScriptConvert.DeserializeObject(val, typeof(ThumbMedia));
180             return deserializedMediaId.media_id;
181         }
182         #endregion
183 
184     }
185 
186 }
获取配置信息,我这里是把需要配置的信息存在数据库中的,也可以直接在.config中获取但是先要在.config中先配置好
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Data;
  4 using System.Web;
  5 using TWays.Data;
  6 using TWays.Core;
  7 using TWays.Core.DBAccess;
  8 using TWays.Core.Exceptions;
  9 using TWays;
 10 using System.Text;
 11 
 12 
 13 namespace Nany.Web.Trans
 14 {
 15     /// <summary>
 16     ///  传输日销售数据
 17     /// </summary>
 18     public class TransSaleSumInfo : ITransTask
 19     {
 20 
 21         public TransSaleSumInfo()
 22         {
 23             _Result = "";
 24         }
 25 
 26         private string _Result;    // 结果消息
 27         public string Result
 28         {
 29             get
 30             {
 31                 return _Result;
 32             }
 33             set
 34             {
 35                 _Result = value;
 36             }
 37         }
 38 
 39         public bool Process()
 40         {
 41             bool boolReturn = false;
 42 
 43             try
 44             {
 45                 string strUserId = string.Empty;
 46                 string sysDate = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd");
 47                 string week = DateTime.Now.AddDays(-1).ToString("dddd");//周几
 48 
 49                 DataSet dsUser = DataAdapter.Query(string.Format(SqlText.selectCanSheetUserId.ToUpper(), Constant.CST_SHEET_TYPE_SALE_SUM));
 50                 if (dsUser == null || dsUser.Tables[0].Rows.Count <= 0)
 51                 {
 52                     Result = "没有需要发送的人员";
 53                     return false;
 54                 }
 55 
 56                 for (int i = 0; i < dsUser.Tables[0].Rows.Count; i++)
 57                 {
 58                     DataRow dr = dsUser.Tables[0].Rows[i];
 59 
 60                     DataSet dsSaleInfo = DataAdapter.Query(string.Format(SqlText.selectSaleSumReprot.ToUpper(), sysDate, TWays.Utils.ToString(dr["STAFF_CODE"])));
 61                     string posData = string.Empty;
 62 
 63                     if (dsSaleInfo != null && dsSaleInfo.Tables[0].Rows.Count > 0)
 64                     {
 65                         decimal ysAmt = 0;
 66                         decimal ssAmt = 0;
 67                         decimal yhAmt = 0;
 68                         decimal countOrg = 0;
 69                         decimal countCust = 0;//客单数
 70                         decimal price = 0;//客单价
 71                         decimal custPrice = 0;//平均客单价
 72 
 73                         for (int t = 0; t < dsSaleInfo.Tables[0].Rows.Count; t++)
 74                         {
 75                             DataRow drSale = dsSaleInfo.Tables[0].Rows[t];
 76                             ysAmt += TWays.Utils.ToDecimal(drSale["YS_AMT"]);
 77                             ssAmt += TWays.Utils.ToDecimal(drSale["SS_AMT"]);
 78                             yhAmt += TWays.Utils.ToDecimal(drSale["YH_AMT"]);
 79                             countOrg = TWays.Utils.ToDecimal(dsSaleInfo.Tables[0].Rows[0]["ROWNUM"]);
 80                             price += TWays.Utils.ToDecimal(drSale["COUNT_AMT"]);
 81                             countCust += TWays.Utils.ToDecimal(drSale["COUNT"]);
 82                         }
 83 
 84                         if (price == 0 || countCust == 0)
 85                         {
 86                             custPrice = 0;
 87                         }
 88                         else
 89                         {
 90                             custPrice = price / countCust;
 91                         }
 92 
 93                         #region 拼接消息内容
 94                         posData += "<p>日期:[" + sysDate + "](" + week + ")";
 95 
 96                         posData += "<br>1.总店铺数:" + TWays.Utils.ToString(countOrg);
 97                         posData += "<br>2.应收金额:" + ysAmt.ToString("f2");
 98                         posData += "<br>3.实收金额:" + ssAmt.ToString("f2");
 99                         posData += "<br>4.优惠金额:" + yhAmt.ToString("f2");
100                         posData += "<br>5.客单量:&nbsp;" + TWays.Utils.ToString(countCust);
101                         posData += "<br>6.客单价:&nbsp;" + custPrice.ToString("f2");
102                         posData += "</p><br>";
103                         #endregion
104                     }
105                     else
106                     {
107                         #region 拼接消息内容
108                         posData += "<p>日期:[" + sysDate + "](" + week + ")";
109 
110                         posData += "<br>1.总店铺数:暂无数据";
111                         posData += "<br>2.应收金额:暂无数据";
112                         posData += "<br>3.实收金额:暂无数据";
113                         posData += "<br>4.优惠金额:暂无数据";
114                         posData += "<br>5.客单量:&nbsp;&nbsp;暂无数据";
115                         posData += "<br>6.客单价:&nbsp;&nbsp;暂无数据";
116                         posData += "</p><br>";
117                         #endregion
118 
119                     }
120 
121                     WeixinManager weiXin = new WeixinManager();
122 
123                     string strPostTitle = weiXin.GetReprotName(Constant.CST_SHEET_TYPE_SALE_SUM) + "[" + sysDate + "]";
124                     string strPostDesc = "[" + sysDate + "](" + week + ")";
125 
126                     string access_token = weiXin.GetQyAccessToken(Constant.CST_SHEET_TYPE_SALE_SUM);
127                     string strResult = string.Empty;
128 
129                     System.Text.StringBuilder strSendMsg = new StringBuilder();
130                     strSendMsg.Append("{\"touser\": \"" + dr["STAFF_CODE"].ToString() + "\",");
131                     strSendMsg.Append("\"toparty\": \"@all\",");
132                     strSendMsg.Append("\"totag\":\"@all\",");
133                     strSendMsg.Append("\"msgtype\": \"mpnews\",");
134                     strSendMsg.Append("\"agentid\": \"" + weiXin.GetWeixinAppId(Constant.CST_SHEET_TYPE_SALE_SUM) + "\",");
135 
136                     strSendMsg.Append("\"mpnews\": {\"articles\":[{");
137                     strSendMsg.Append("\"title\": \"" + strPostTitle + "\",");   //标题
138                     strSendMsg.Append("\"thumb_media_id\": \"" + weiXin.GetQyMediaId(Constant.CST_SHEET_TYPE_SALE_SUM) + "\",");   //MeidaId
139                     strSendMsg.Append("\"author\": \"\",");   //作者
140 
141 
142                     // strSendMsg.Append("\"content_source_url\": \"http://www.lenle.com/weixin/MsgContent.aspx?MASTER_ID=" + strMasterId + "\",");  //阅读全文的链接
143 
144 
145                     strSendMsg.Append("\"content\": \"" + posData + "\",");    //图文内容
146                     strSendMsg.Append("\"digest\": \"" + strPostDesc + "\",");  //图文消息描述
147                     strSendMsg.Append("\"show_cover_pic\": \"0\"");
148 
149                     strSendMsg.Append("}]},\"safe\":\"1\"}");
150 
151                     strResult = HttpRequestUtil.PostUrl(string.Format("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={0}", access_token), strSendMsg.ToString());
152 
153                     int isSend = 0;
154                     if (strResult.Contains("\"errmsg\":\"ok\""))
155                     {
156                         Result = "success";
157                         boolReturn = true;
158                         isSend = 1;
159                     }
160                     else
161                     {
162                         Result = "no records!";
163                         boolReturn = true;
164                     }
165 
166                     //新增微信推送消息内容表
167                     //string s = string.Format(SqlText.insertWeiXinDayReprotContent.ToUpper(),
168                     //    dr["STAFF_ID"].ToString(), sysDate, week, isSend), new object[] { Encoding.UTF8.GetBytes(posData) };
169                     DataAdapter.Execute(string.Format(SqlText.insertWeiXinDayReprotContent.ToUpper(),
170                         dr["STAFF_ID"].ToString(), sysDate, week, isSend), new object[] { Encoding.UTF8.GetBytes(posData) });
171 
172                     strSendMsg = null;
173                 }
174 
175             }
176             catch (Exception ex)
177             {
178                 Result = ex.Message;
179             }
180 
181             return boolReturn;
182         }
183 
184         public void Dispose()
185         {
186             GC.SuppressFinalize(this);
187         }
188     }
189 }
传输日销售数据

猜你喜欢

转载自www.cnblogs.com/Swaggy-yyq/p/12067665.html