C#本地應用向釘釘推送信息並在釘釘上創建審批流完成審批


1.創建應用(https://open.dingtalk.com/document/isv/create-isvapp)
2.VS2019創建WEB API項目並部署到公網可以訪問的IIS上

Nuget安裝System.Text.Json;

using System.Net.Http;
using System.Text;
using System.Web.Http;

namespace Multek.DingTalkService.WebApi.Controllers
{
    [RoutePrefix("api/AppEvent")]
    public class AppEventController : ApiController
    {
        //public IHttpActionResult Post()
        //{
        //    AppService app = new AppService();
        //    var r = app.DingtalkCallback("", "66caf4181a353e39b7c186e923ecaf5c", "MAiNC53WwilSzJYfvyNluAa6UHtV7PBMoeGnWOeBgXt", "1467996111");
        //    var obj = new { success = r };
        //    return Json(obj);
        //}
        [HttpGet]
        public object GetAll()
        {
            return Ok<string>("Success");
        }

        [HttpPost]
        public HttpResponseMessage PostData(int id)
        {
            return Request.CreateResponse();
        }

        [Route("Order/SaveData")]
        [HttpPost]
        public object SaveData()
        {
            var a = "Ben";
            return Ok<string>("Success");
        }

        [Route("Order/DingdingCallback")]
        [HttpPost]
        public HttpResponseMessage DingdingCallback(string signature, string timestamp, string nonce)
        { 
            JeffSoft.Logger.Error("DingdingCallback Success:" + signature + "," + timestamp + "");
            //第一部分
            //這兩句代碼是為了接收body體中傳入的加密json串
            Request.Content.ReadAsStreamAsync().Result.Seek(0, System.IO.SeekOrigin.Begin);
            string content = Request.Content.ReadAsStringAsync().Result;
            //反序列化json串拿去加密字符串
            JToken json = JToken.Parse(content);
            string ever = json["encrypt"].ToString();
            //實例化釘釘解密類構造參數為對應的 應用中的token、aes_key、AppKey值
            DingTalkEncryptor dingTalkEncryptor = new DingTalkEncryptor("896caad7fcfd398abc6b333c6156c769", "MAiNC53WwilSzJYfvyNluAa6UHtV7PBMoeGnWOeBgXt", "dinglqafhlqtb8zreom5");  
            //定義字符串接收解密后的值
            string text = dingTalkEncryptor.getDecryptMsg(signature, timestamp, nonce, ever);
            JeffSoft.Logger.Error("dingTalkEncryptor.getDecryptMsg:" + text + "");
            JToken jToken = JToken.Parse(text);
            //取出事件類型字段
            string EventType = jToken["EventType"].ToString();
            JeffSoft.Logger.Error("DingdingCallback Success,EventType:" + EventType + "");
            //第二部分
            //  { //判斷事件類型是否是日程事件
            if ("bpms_instance_change" == EventType)
            {
                var DDtitle = jToken["title"].ToString();
                var DDresult= jToken["result"].ToString();
                var mes = string.Format("bpms_instance_change Success,title:{0},result:{1}", DDtitle, DDresult);
                JeffSoft.Logger.Error(mes);
            }

            //第三部分
            //返回加密字符串          
            var msg = dingTalkEncryptor.getEncryptedMap("success");
            var msg_signature = msg["msg_signature"];
            var encrypt = msg["encrypt"];
            var timeStamp = msg["timeStamp"];
            var nonce1 = msg["nonce"];
            var v = new
            {
                msg_signature = msg["msg_signature"],
                encrypt = msg["encrypt"],
                timeStamp = msg["timeStamp"],
                nonce = msg["nonce"],
            };
            //var obj = new { success = r };
            //    return Json(obj);
            var data = JsonConvert.SerializeObject(v); 

            //返回json數
            return new HttpResponseMessage()
            {
                Content = new StringContent(data, Encoding.UTF8, "application/json"),
            };
        }
    }
}
View Code

DingTalkEncryptor.cs

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Web;
  5 using System.Text;
  6 using System.Security.Cryptography;
  7 using System.Text.Json;
  8 
  9 namespace DingDingWebAPI.Controllers
 10 {
 11     /**
 12     * 釘釘開放平台加解密方法
 13     */
 14     public class DingTalkEncryptor
 15     {
 16         //private static readonly Charset CHARSET = Charset.forName("utf-8");
 17         //private static readonly Base64         base64  = new Base64();
 18         private byte[] aesKey;
 19         private String token;
 20         private String corpId;
 21         /**ask getPaddingBytes key固定長度**/
 22         private static readonly int AES_ENCODE_KEY_LENGTH = 43;
 23         /**加密隨機字符串字節長度**/
 24         private static readonly int RANDOM_LENGTH = 16;
 25 
 26         /**
 27          * 構造函數
 28          * @param token            釘釘開放平台上,開發者設置的token
 29          * @param encodingAesKey   釘釘開放台上,開發者設置的EncodingAESKey
 30          * @param corpId           企業自建應用-事件訂閱, 使用appKey
 31          *                         企業自建應用-注冊回調地址, 使用corpId
 32          *                         第三方企業應用, 使用suiteKey
 33          *
 34          * @throws DingTalkEncryptException 執行失敗,請查看該異常的錯誤碼和具體的錯誤信息
 35          */
 36         public DingTalkEncryptor(String token, String encodingAesKey, String corpId)
 37         {
 38             if (null == encodingAesKey || encodingAesKey.Length != AES_ENCODE_KEY_LENGTH)
 39             {
 40                 throw new DingTalkEncryptException(DingTalkEncryptException.AES_KEY_ILLEGAL);
 41             }
 42             this.token = token;
 43             this.corpId = corpId;
 44             aesKey = Convert.FromBase64String(encodingAesKey + "=");
 45         }
 46 
 47         /**
 48          * 將和釘釘開放平台同步的消息體加密,返回加密Map
 49          */
 50         public Dictionary<String, String> getEncryptedMap(String plaintext)
 51         {
 52 
 53             var time = DateTime.Now.Millisecond;
 54             return getEncryptedMap(plaintext, time);
 55         }
 56 
 57         /**
 58          * 將和釘釘開放平台同步的消息體加密,返回加密Map
 59          * @param plaintext     傳遞的消息體明文
 60          * @param timeStamp      時間戳
 61          * @param nonce           隨機字符串
 62          * @return
 63          * @throws DingTalkEncryptException
 64          */
 65         public Dictionary<String, String> getEncryptedMap(String plaintext, long timeStamp)
 66         {
 67             if (null == plaintext)
 68             {
 69                 throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_PLAINTEXT_ILLEGAL);
 70             }
 71             var nonce = Utils.getRandomStr(RANDOM_LENGTH);
 72             if (null == nonce)
 73             {
 74                 throw new DingTalkEncryptException(DingTalkEncryptException.ENCRYPTION_NONCE_ILLEGAL);
 75             }
 76 
 77             String encrypt = this.encrypt(nonce, plaintext);
 78             String signature = getSignature(token, timeStamp.ToString(), nonce, encrypt);
 79             Dictionary<String, String> resultMap = new Dictionary<String, String>();
 80             resultMap["msg_signature"] = signature;
 81             resultMap["encrypt"] = encrypt;
 82             resultMap["timeStamp"] = timeStamp.ToString();
 83             resultMap["nonce"] = nonce;
 84             return resultMap;
 85         }
 86 
 87         /**
 88          * 密文解密
 89          * @param msgSignature     簽名串
 90          * @param timeStamp        時間戳
 91          * @param nonce             隨機串
 92          * @param encryptMsg       密文
 93          * @return                  解密后的原文
 94          * @throws DingTalkEncryptException
 95          */
 96         public String getDecryptMsg(String msgSignature, String timeStamp, String nonce, String encryptMsg)
 97         {
 98             //校驗簽名
 99             String signature = getSignature(token, timeStamp, nonce, encryptMsg);
100             if (!signature.Equals(msgSignature))
101             {
102                 throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
103             }
104             // 解密
105             String result = decrypt(encryptMsg);
106             return result;
107         }
108 
109 
110         /*
111          * 對明文加密.
112          * @param text 需要加密的明文
113          * @return 加密后base64編碼的字符串
114          */
115         private String encrypt(String random, String plaintext)
116         {
117             try
118             {
119                 byte[] randomBytes = System.Text.Encoding.UTF8.GetBytes(random);// random.getBytes(CHARSET);
120                 byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plaintext);// plaintext.getBytes(CHARSET);
121                 byte[] lengthByte = Utils.int2Bytes(plainTextBytes.Length);
122                 byte[] corpidBytes = System.Text.Encoding.UTF8.GetBytes(corpId);// corpId.getBytes(CHARSET);
123                 //MemoryStream byteStream = new MemoryStream();
124                 var bytestmp = new List<byte>();
125                 bytestmp.AddRange(randomBytes);
126                 bytestmp.AddRange(lengthByte);
127                 bytestmp.AddRange(plainTextBytes);
128                 bytestmp.AddRange(corpidBytes);
129                 byte[] padBytes = PKCS7Padding.getPaddingBytes(bytestmp.Count);
130                 bytestmp.AddRange(padBytes);
131                 byte[] unencrypted = bytestmp.ToArray();
132 
133                 RijndaelManaged rDel = new RijndaelManaged();
134                 rDel.Mode = CipherMode.CBC;
135                 rDel.Padding = PaddingMode.Zeros;
136                 rDel.Key = aesKey;
137                 rDel.IV = aesKey.ToList().Take(16).ToArray();
138                 ICryptoTransform cTransform = rDel.CreateEncryptor();
139                 byte[] resultArray = cTransform.TransformFinalBlock(unencrypted, 0, unencrypted.Length);
140                 return Convert.ToBase64String(resultArray, 0, resultArray.Length);
141 
142 
143                 //Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
144                 //SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
145                 //IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
146                 //cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
147                 //byte[] encrypted = cipher.doFinal(unencrypted);
148                 //String result = base64.encodeToString(encrypted);
149                 //return result;
150             }
151             catch (Exception e)
152             {
153                 throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_ENCRYPT_TEXT_ERROR);
154             }
155         }
156 
157         /*
158          * 對密文進行解密.
159          * @param text 需要解密的密文
160          * @return 解密得到的明文
161          */
162         private String decrypt(String text)
163         {
164             byte[] originalArr;
165             try
166             {
167                 byte[] toEncryptArray = Convert.FromBase64String(text);
168                 RijndaelManaged rDel = new RijndaelManaged();
169                 rDel.Mode = CipherMode.CBC;
170                 rDel.Padding = PaddingMode.Zeros;
171                 rDel.Key = aesKey;
172                 rDel.IV = aesKey.ToList().Take(16).ToArray();
173                 ICryptoTransform cTransform = rDel.CreateDecryptor();
174                 originalArr = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
175                 //return System.Text.UTF8Encoding.UTF8.GetString(resultArray);
176 
177                 //// 設置解密模式為AES的CBC模式
178                 //Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
179                 //SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
180                 //IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
181                 //cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
182                 //// 使用BASE64對密文進行解碼
183                 //byte[] encrypted = Base64.decodeBase64(text);
184                 //// 解密
185                 //originalArr = cipher.doFinal(encrypted);
186             }
187             catch (Exception e)
188             {
189                 throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_ERROR);
190             }
191 
192             String plainText;
193             String fromCorpid;
194             try
195             {
196                 // 去除補位字符
197                 byte[] bytes = PKCS7Padding.removePaddingBytes(originalArr);
198                 Console.Out.WriteLine("bytes size:" + bytes.Length);
199 
200                 // 分離16位隨機字符串,網絡字節序和corpId
201                 byte[] networkOrder = bytes.Skip(16).Take(4).ToArray();// Arrays.copyOfRange(bytes, 16, 20);
202                 for (int i = 0; i < 4; i++)
203                 {
204                     Console.Out.WriteLine("networkOrder size:" + (int)networkOrder[i]);
205                 }
206 
207                 Console.Out.WriteLine("bytes plainText:" + networkOrder.Length + " " + JsonSerializer.Serialize(networkOrder));
208                 int plainTextLegth = Utils.bytes2int(networkOrder);
209                 Console.Out.WriteLine("bytes size:" + plainTextLegth);
210 
211                 plainText = System.Text.UTF8Encoding.UTF8.GetString(bytes.Skip(20).Take(plainTextLegth).ToArray()); // new String(Arrays.copyOfRange(bytes, 20, 20 + plainTextLegth), CHARSET);
212                 fromCorpid = System.Text.UTF8Encoding.UTF8.GetString(bytes.Skip(20 + plainTextLegth).ToArray()); //new String(Arrays.copyOfRange(bytes, 20 + plainTextLegth, bytes.length), CHARSET);
213                 Console.Out.WriteLine("bytes plainText:" + plainText);
214 
215             }
216             catch (Exception e)
217             {
218                 throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_LENGTH_ERROR);
219             }
220             Console.Out.WriteLine(fromCorpid + "=====" + corpId);
221 
222 
223             // corpid不相同的情況
224             if (!fromCorpid.Equals(corpId))
225             {
226                 throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_DECRYPT_TEXT_CORPID_ERROR);
227             }
228             return plainText;
229         }
230 
231         /**
232          * 數字簽名
233          * @param token         isv token
234          * @param timestamp     時間戳
235          * @param nonce          隨機串
236          * @param encrypt       加密文本
237          * @return
238          * @throws DingTalkEncryptException
239          */
240         public String getSignature(String token, String timestamp, String nonce, String encrypt)
241         {
242             try
243             {
244                 Console.Out.WriteLine(encrypt);
245 
246                 String[] array = new String[] { token, timestamp, nonce, encrypt };
247                 Array.Sort(array, StringComparer.Ordinal);
248                 //var tmparray = array.ToList();
249                 //tmparray.Sort(new JavaStringComper());
250                 //array = tmparray.ToArray();
251                 Console.Out.WriteLine("array:" + JsonSerializer.Serialize(array));
252                 StringBuilder sb = new StringBuilder();
253                 for (int i = 0; i < 4; i++)
254                 {
255                     sb.Append(array[i]);
256                 }
257                 String str = sb.ToString();
258                 Console.Out.WriteLine(str);
259                 //MessageDigest md = MessageDigest.getInstance("SHA-1");
260                 //md.update(str.getBytes());
261                 //byte[] digest = md.digest();
262                 System.Security.Cryptography.SHA1 hash = System.Security.Cryptography.SHA1.Create();
263                 System.Text.Encoding encoder = System.Text.Encoding.ASCII;
264                 byte[] combined = encoder.GetBytes(str);
265                 ////byte 轉換
266                 //sbyte[] myByte = new sbyte[]
267                 //byte[] mySByte = new byte[myByte.Length];
268 
269 
270 
271                 //for (int i = 0; i < myByte.Length; i++)
272 
273                 //{
274 
275                 //    if (myByte[i] > 127)
276 
277                 //        mySByte[i] = (sbyte)(myByte[i] - 256);
278 
279                 //    else
280 
281                 //        mySByte[i] = (sbyte)myByte[i];
282 
283                 //}
284 
285                 byte[] digest = hash.ComputeHash(combined);
286                 StringBuilder hexstr = new StringBuilder();
287                 String shaHex = "";
288                 for (int i = 0; i < digest.Length; i++)
289                 {
290                     shaHex = ((int)digest[i]).ToString("x");// Integer.toHexString(digest[i] & 0xFF);
291                     if (shaHex.Length < 2)
292                     {
293                         hexstr.Append(0);
294                     }
295                     hexstr.Append(shaHex);
296                 }
297                 return hexstr.ToString();
298             }
299             catch (Exception e)
300             {
301                 throw new DingTalkEncryptException(DingTalkEncryptException.COMPUTE_SIGNATURE_ERROR);
302             }
303         }
304     }
305 
306 
307     /**
308  * 釘釘開放平台加解密異常類
309  */
310     public class DingTalkEncryptException : Exception
311     {
312         /**成功**/
313         public static readonly int SUCCESS = 0;
314         /**加密明文文本非法**/
315         public readonly static int ENCRYPTION_PLAINTEXT_ILLEGAL = 900001;
316         /**加密時間戳參數非法**/
317         public readonly static int ENCRYPTION_TIMESTAMP_ILLEGAL = 900002;
318         /**加密隨機字符串參數非法**/
319         public readonly static int ENCRYPTION_NONCE_ILLEGAL = 900003;
320         /**不合法的aeskey**/
321         public readonly static int AES_KEY_ILLEGAL = 900004;
322         /**簽名不匹配**/
323         public readonly static int SIGNATURE_NOT_MATCH = 900005;
324         /**計算簽名錯誤**/
325         public readonly static int COMPUTE_SIGNATURE_ERROR = 900006;
326         /**計算加密文字錯誤**/
327         public readonly static int COMPUTE_ENCRYPT_TEXT_ERROR = 900007;
328         /**計算解密文字錯誤**/
329         public readonly static int COMPUTE_DECRYPT_TEXT_ERROR = 900008;
330         /**計算解密文字長度不匹配**/
331         public readonly static int COMPUTE_DECRYPT_TEXT_LENGTH_ERROR = 900009;
332         /**計算解密文字corpid不匹配**/
333         public readonly static int COMPUTE_DECRYPT_TEXT_CORPID_ERROR = 900010;
334 
335         private static Dictionary<int, String> msgMap = new Dictionary<int, String>();
336         static DingTalkEncryptException()
337         {
338             msgMap[SUCCESS] = "成功";
339             msgMap[ENCRYPTION_PLAINTEXT_ILLEGAL] = "加密明文文本非法";
340             msgMap[ENCRYPTION_TIMESTAMP_ILLEGAL] = "加密時間戳參數非法";
341             msgMap[ENCRYPTION_NONCE_ILLEGAL] = "加密隨機字符串參數非法";
342             msgMap[SIGNATURE_NOT_MATCH] = "簽名不匹配";
343             msgMap[COMPUTE_SIGNATURE_ERROR] = "簽名計算失敗";
344             msgMap[AES_KEY_ILLEGAL] = "不合法的aes key";
345             msgMap[COMPUTE_ENCRYPT_TEXT_ERROR] = "計算加密文字錯誤";
346             msgMap[COMPUTE_DECRYPT_TEXT_ERROR] = "計算解密文字錯誤";
347             msgMap[COMPUTE_DECRYPT_TEXT_LENGTH_ERROR] = "計算解密文字長度不匹配";
348             msgMap[COMPUTE_DECRYPT_TEXT_CORPID_ERROR] = "計算解密文字corpid不匹配";
349         }
350 
351         private int code;
352         public DingTalkEncryptException(int exceptionCode) : base(msgMap[exceptionCode])
353         {
354             this.code = exceptionCode;
355         }
356     }
357 
358     /*
359      * PKCS7算法的加密填充
360      */
361     public class PKCS7Padding
362     {
363         //private readonly static Charset CHARSET = Charset.forName("utf-8");
364         private readonly static int BLOCK_SIZE = 32;
365 
366         /**
367          * 填充mode字節
368          * @param count
369          * @return
370          */
371         public static byte[] getPaddingBytes(int count)
372         {
373             int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
374             if (amountToPad == 0)
375             {
376                 amountToPad = BLOCK_SIZE;
377             }
378             char padChr = chr(amountToPad);
379             String tmp = string.Empty; ;
380             for (int index = 0; index < amountToPad; index++)
381             {
382                 tmp += padChr;
383             }
384             return System.Text.Encoding.UTF8.GetBytes(tmp);
385         }
386 
387         /**
388          * 移除mode填充字節
389          * @param decrypted
390          * @return
391          */
392         public static byte[] removePaddingBytes(byte[] decrypted)
393         {
394             int pad = (int)decrypted[decrypted.Length - 1];
395             if (pad < 1 || pad > BLOCK_SIZE)
396             {
397                 pad = 0;
398             }
399             //Array.Copy()
400             var output = new byte[decrypted.Length - pad];
401             Array.Copy(decrypted, output, decrypted.Length - pad);
402             return output;
403         }
404 
405         private static char chr(int a)
406         {
407             byte target = (byte)(a & 0xFF);
408             return (char)target;
409         }
410 
411     }
412 
413     /**
414  * 加解密工具類
415  */
416     public class Utils
417     {
418         /**
419      *
420      * @return
421      */
422         public static String getRandomStr(int count)
423         {
424             String baset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
425             Random random = new Random();
426             StringBuilder sb = new StringBuilder();
427             for (int i = 0; i < count; i++)
428             {
429                 int number = random.Next(baset.Length);
430                 sb.Append(baset[number]);
431             }
432             return sb.ToString();
433         }
434 
435 
436         /*
437          * int轉byte數組,高位在前
438          */
439         public static byte[] int2Bytes(int count)
440         {
441             byte[] byteArr = new byte[4];
442             byteArr[3] = (byte)(count & 0xFF);
443             byteArr[2] = (byte)(count >> 8 & 0xFF);
444             byteArr[1] = (byte)(count >> 16 & 0xFF);
445             byteArr[0] = (byte)(count >> 24 & 0xFF);
446             return byteArr;
447         }
448 
449         /**
450          * 高位在前bytes數組轉int
451          * @param byteArr
452          * @return
453          */
454         public static int bytes2int(byte[] byteArr)
455         {
456             int count = 0;
457             for (int i = 0; i < 4; ++i)
458             {
459                 count <<= 8;
460                 count |= byteArr[i] & 255;
461             }
462             return count;
463         }
464     }
465 
466     public class JavaStringComper : IComparer<string>
467     {
468         public int Compare(string x, string y)
469         {
470             return String.Compare(x, y);
471         }
472     }
473 
474 
475 }
View Code

3.配置事件訂閱(https://open.dingtalk.com/document/orgapp-server/configure-event-subcription)

4.進入釘釘管理后台(https://oa.dingtalk.com/)創建審批表單模板(https://open.dingtalk.com/document/isvapp-server/create-or-modify-an-approval-form-template)

https://oa.dingtalk.com/  工作台--》應用程序--》OA審批--》進入--》創建

5.發起審批實例(https://open.dingtalk.com/document/orgapp-server/initiate-approval)

得到得到accessToken(https://api.dingtalk.com/v1.0/oauth2/accessToken)

post:

{
    "appKey":"dinglqafhlqtb8zreom5",
    "appSecret":"SE93VaqKzjSoCoyhiPmigPrrZO7J5fR0-efFlsJXg7OrZPiqH6C-LlGX0gy4QpD1"
}
返回
{
    "expireIn": 7200,
    "accessToken": "9015791f471e34aa89804286dee0a1eb"
}
發起審批實例
https://oapi.dingtalk.com/topapi/processinstance/create?access_token=9015791f471e34aa89804286dee0a1eb
POST:

{
"form_component_values": [
{
"name": "單號",
"value": "B1000006"
},
{
"name": "設備編號",
"value": "AOI10002"
},
{
"name": "維修員",
"value": "Ben"
},

],
"agent_id": 1496597138,
"process_code": "PROC-9B39B0F8-C8EA-4401-87A8-4FCBCA0FE13F", //對接審批流的process_code
//"cc_position": "FINISH",
//"approvers": "6526336,8181347",
//"cc_list": "6526336,8181347",
"dept_id": -1,
"approvers_v2": [ //設置審批人,會簽、或簽設置的審批人必須大於等於2個人
{
"task_action_type": "OR",
"user_ids": [
"8181347" ,
"6526336"
]
}

],

"originator_user_id": "8181347"
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM