บทคัดย่อ: เมื่อเร็ว ๆ นี้มีข้อกำหนดที่จะให้ลูกค้าด้วยอินเทอร์เฟซ API ที่เหลือ QA ใช้บุรุษไปรษณีย์สำหรับการทดสอบ แต่อินเทอร์เฟซทดสอบของบุรุษไปรษณีย์นั้นคล้ายคลึงกัน แต่ไม่เหมือนกับการเรียก Java ดังนั้นฉันต้องการเขียนโปรแกรมเพื่อทดสอบอินเทอร์เฟซ RESTFUL API ด้วยตัวเอง เนื่องจากใช้ HTTPS ฉันจึงต้องพิจารณาการประมวลผลของ HTTPS เนื่องจากฉันใช้ Java เพื่อโทรหาอินเทอร์เฟซ RESTFUL เป็นครั้งแรกฉันยังต้องศึกษาดังนั้นฉันจึงปรึกษาข้อมูลบางอย่างโดยธรรมชาติ
การวิเคราะห์: ปัญหานี้แตกต่างจากการโทรระหว่างโมดูล ตัวอย่างเช่นฉันมีสองโมดูลด้านหน้าและด้านหลังปลายด้านหน้าให้การแสดงผลส่วนหน้าและด้านหลังให้การสนับสนุนข้อมูล ฉันใช้ Hession เพื่อลงทะเบียนบริการที่ได้รับจาก Back End เป็นบริการระยะไกล ด้านหน้าบริการระยะไกลนี้สามารถปรับได้โดยตรงกับอินเทอร์เฟซด้านหลังผ่านบริการระยะไกลประเภทนี้ แต่นี่ไม่มีปัญหาสำหรับ บริษัท ที่จะใช้เมื่อโครงการของตัวเองมีการเชื่อมโยงกันอย่างมาก อย่างไรก็ตามหากคุณลงทะเบียนบริการระยะไกลดังกล่าวกับลูกค้าดูเหมือนว่าจะไม่ดีและการมีเพศสัมพันธ์สูงเกินไป ดังนั้นฉันจึงพิจารณาใช้วิธีการต่อไปนี้
1. httpClient
ทุกคนอาจคุ้นเคยกับ httpClient แต่ไม่คุ้นเคยกับมัน มันคุ้นเคยกับมันเพราะสามารถโทรจากระยะไกลได้เช่นขอ URL จากนั้นรับสถานะการส่งคืนและส่งคืนข้อมูลในการตอบกลับ อย่างไรก็ตามวันนี้ฉันกำลังพูดถึงเรื่องนี้ซับซ้อนขึ้นเล็กน้อยเพราะหัวข้อของวันนี้คือ HTTPS ซึ่งเกี่ยวข้องกับปัญหาของใบรับรองหรือการตรวจสอบผู้ใช้
หลังจากยืนยันว่าการใช้ HTTPClient ฉันค้นหาข้อมูลที่เกี่ยวข้องและพบว่า HTTPClient เวอร์ชันใหม่นั้นแตกต่างจากรุ่นเก่าและเข้ากันได้กับเวอร์ชันเก่า แต่ก็ไม่แนะนำอีกต่อไปว่าจะใช้เวอร์ชันเก่าอีกต่อไป มีวิธีการหรือคลาสมากมายที่ถูกทำเครื่องหมายว่าล้าสมัย วันนี้เราจะใช้เวอร์ชันเก่า 4.2 และเวอร์ชันล่าสุด 4.5.3 เพื่อเขียนรหัสตามลำดับ
เวอร์ชันเก่า 4.2
ต้องการการรับรอง
เลือกการใช้การรับรองความถูกต้องของใบรับรองระหว่างขั้นตอนการเตรียมใบรับรอง
แพ็คเกจ com.darren.test.https.v42; นำเข้า Java.io.File; นำเข้า Java.io.FileInputStream; นำเข้า java.security.keystore; นำเข้า org.apache.http.conn.ssl.sslsocketfactory; ชั้นเรียนสาธารณะ httpscertifiedClient ขยาย httpsClient {public httpscertifiedClient () {} @Override โมฆะสาธารณะ preperecertificate () พ่นข้อยกเว้น {// รับคีย์คีย์คีย์คีย์ Truststore = keystore.getInstance (keystore.getDefaulttype (); fileInputStream input = new FileInputStream (ไฟล์ใหม่ ("C: /USERS/ZHDA6001/DOWNLOADS/SOFTWARE/XXX.KEYSTORE")); // fileInputStream input = new FileInputStream (ไฟล์ใหม่ ("C: /USERS/ZHDA6001/DOWNLOADS/XXX.KEYSTORE")); // รหัสผ่านของคีย์คีย์ไลบรารี Truststore.load (Instream, "รหัสผ่าน" .tochararray ()); // ลงทะเบียนคีย์ไลบรารี this.socketFactory = ใหม่ SSLSocketFactory (TrustStore); // อย่าตรวจสอบชื่อโดเมน SocketFactory.Sethostnameverifier (SSLSocketFactory.allow_all_hostname_Verifier); -ข้ามการรับรอง
ตัวเลือกในการข้ามการรับรองความถูกต้องในระหว่างขั้นตอนการเตรียมใบรับรองคือ
แพ็คเกจ com.darren.test.https.v42; นำเข้า java.security.cert.certificateException; นำเข้า java.security.cert.x509certificate; นำเข้า javax.net.ssl.sslcontext; นำเข้า Javax.net.ssl.TrustManager; นำเข้า Javax.net.ssl.x509trustManager; นำเข้า org.apache.http.conn.ssl.sslsocketfactory; ชั้นเรียนสาธารณะ HttpStrustClient ขยาย httpsClient {public httpStrustClient () {} @Override โมฆะสาธารณะ preperecertificate () โยนข้อยกเว้น {// skip การตรวจสอบใบรับรอง sslcontext ctx = sslcontext.getInstance ("TLS"); X509TrustManager TM = ใหม่ x509TrustManager () {@Override โมฆะสาธารณะ checkClientTrusted (x509Certificate [] โซ่, สตริง AuthType) พ่นใบรับรอง {} @Override public checkserverTrusted (x509Certificate [] x509Certificate [] getacceptedissuers () {return null; - // ตั้งค่าเป็นใบรับรองที่เชื่อถือได้ ctx.init (null, ใหม่ TrustManager [] {tm}, null); // วางโรงงานซ็อกเก็ต SSL และตั้งค่าเพื่อไม่ตรวจสอบชื่อโฮสต์ this.socketFactory = ใหม่ SSLSocketFactory (CTX, SSLSocketFactory.allow_all_hostname_verifier); -สรุป
ตอนนี้พบว่าทั้งสองคลาสสืบทอดคลาส HTTPSClient เดียวกันและ HTTPSClient สืบทอดคลาส defaulthttpClient จะพบได้ว่ารูปแบบวิธีการเทมเพลตถูกใช้ที่นี่
แพ็คเกจ com.darren.test.https.v42; นำเข้า org.apache.http.conn.ClientConnectionManager; นำเข้า org.apache.http.conn.scheme.scheme; นำเข้า org.apache.http.conn.ssl.sslsocketfactory; นำเข้า org.apache.http.impl.client.defaulthttpClient; บทคัดย่อระดับสาธารณะ HttpsClient ขยาย defaulthttpClient {ป้องกัน sslsocketfactory socketFactory; / ** * เริ่มต้น httpsclient * * @return ส่งคืนอินสแตนซ์ปัจจุบัน * @throws Exception */ public httpsclient init () โยนข้อยกเว้น {this.prepreperCertificate (); this.regist (); คืนสิ่งนี้; } / ** * เตรียมการตรวจสอบใบรับรอง * * @throws Exception * / Public Public Void Preperecertificate () โยนข้อยกเว้น; / *** ลงทะเบียนโปรโตคอลและพอร์ตวิธีนี้สามารถเขียนใหม่ได้โดย subclasses*/ void regist ที่ได้รับการป้องกัน () {clientConnectionManager ccm = this.getConnectionManager (); Schemeregistry SR = CCM.GetSchemeregistry (); Sr.Register (โครงการใหม่ ("HTTPS", 443, SocketFactory)); - ด้านล่างคือคลาสเครื่องมือ
แพ็คเกจ com.darren.test.https.v42; นำเข้า java.util.arraylist; นำเข้า java.util.list; นำเข้า java.util.map; นำเข้า java.util.set; นำเข้า org.apache.http.htttentity; นำเข้า org.apache.http.httpresponse; นำเข้า org.apache.http.namevaluePair; นำเข้า org.apache.http.client.entity.urlencodedformentity; นำเข้า org.apache.http.client.methods.httpget; นำเข้า org.apache.http.client.methods.httppost; นำเข้า org.apache.http.client.methods.httprequestbase; นำเข้า org.apache.http.message.basicnamevaluepair; นำเข้า org.apache.http.util.entityutils; ชั้นเรียนสาธารณะ httpsClientutil {สตริงคงสุดท้ายคงที่เริ่มต้น chaold_charset = "utf-8"; สตริงคงที่สาธารณะ dopost (httpsclient httpsclient, rl string, map <string, string> paramheader, map <string, string> parambody) โยนข้อยกเว้น {return dopost (httpsclient, url, paramheader, parambody, default_charset); } สตริงคงที่สาธารณะ dopost (httpsclient httpsclient, url สตริง, แผนที่ <สตริง, สตริง> paramheader, แผนที่ <สตริง, สตริง> parambody, สตริง charset) โยนข้อยกเว้น {สตริงผลลัพธ์ = null; httppost httppost = ใหม่ httppost (url); Setheader (httppost, paramheader); setBody (httppost, parambody, charset); การตอบสนอง httpresponse = httpsclient.execute (httppost); if (resentity! = null) {httpentity resentity = response.getEntity (); if (resentity! = null) {result = entityUtils.toString (resentity, charset); }} ผลการส่งคืน; } สตริงคงที่สาธารณะ doget (httpsClient httpsClient, url สตริง, แผนที่ <สตริง, สตริง> paramheader, แผนที่ <สตริง, สตริง> parambody) โยนข้อยกเว้น {return doget (httpsClient, url, paramheader, parambody, default_charset); } สตริงคงที่สาธารณะ doget (httpsClient httpsClient, url สตริง, แผนที่ <สตริง, สตริง> paramheader, แผนที่ <สตริง, สตริง> parambody, สตริง charset) พ่นข้อยกเว้น {สตริงผลลัพธ์ = null; httpget httpget = new httpget (url); Setheader (httpget, paramheader); การตอบสนอง httpresponse = httpsclient.execute (httpget); if (ตอบกลับ! = null) {httpentity resentity = response.getEntity (); if (resentity! = null) {result = entityUtils.toString (resentity, charset); }} ผลการส่งคืน; } โมฆะแบบคงที่ส่วนตัว setheader (คำขอ httprequestbase, แผนที่ <สตริง, สตริง> paramheader) {// set header ถ้า (paramheader! = null) {ตั้งค่า <string> keyset = paramheader.keyset (); สำหรับ (คีย์สตริง: ปุ่ม) {request.addHeader (คีย์, paramheader.get (คีย์)); }}} โมฆะคงที่ส่วนตัว setbody (httppost httppost, แผนที่ <สตริง, สตริง> parambody, สตริง charset) โยนข้อยกเว้น {// ตั้งค่าพารามิเตอร์ถ้า (parambody! = null) {รายการ <namevaluePair> list = new ArrayList <namevaluePair> ตั้งค่า <string> keyset = parambody.keyset (); สำหรับ (คีย์สตริง: คีย์เซ็ต) {list.add (ใหม่ BasicAnmAneValuePair (คีย์, parambody.get (คีย์))); } if (list.size ()> 0) {urlencodedFormentity entity = ใหม่ urlencodedFormentity (list, charset); httppost.setEntity (เอนทิตี); - จากนั้นมีคลาสทดสอบ:
แพ็คเกจ com.darren.test.https.v42; นำเข้า java.util.hashmap; นำเข้า java.util.map; คลาสสาธารณะ httpsClientTest {โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่นข้อยกเว้น {httpsClient httpsClient = null; httpsClient = new httpStrustClient (). init (); // httpsClient = ใหม่ httpscertifiedClient (). init (); string url = "https://1.2.6.2:8011/xxx/api/gettoken"; // string url = "https://1.2.6.2:8011/xxx/api/gethealth"; แผนที่ <สตริงสตริง> paramheader = ใหม่ hashmap <> (); //paramheader.put("Content-type "," Application/JSON "); paramheader.put ("ยอมรับ", "แอปพลิเคชัน/xml"); แผนที่ <สตริงสตริง> parambody = ใหม่ hashmap <> (); parambody.put ("client_id", "[email protected]"); parambody.put ("client_secret", "p@ssword_1"); String result = httpsClientutil.dopost (httpsClient, url, paramheader, parambody); // string result = httpsClientutil.doget (httpsClient, url, null, null); System.out.println (ผลลัพธ์); - ข้อมูลส่งคืน:
<? xml version = "1.0" encoding = "utf-8"?>?
<token> JKF8RL0SW+SKKFLJ8RBKI5HP1BEQK8PRCUTZPPBINQMYKRMXY1KWCJMCFT191ZPP88VV1AGHW8OYNWJEYS 0AxPlugax89EJCOWNBIKCC1UVFYESXHLKTCJQYUFIVEVHREQXJPHNCLQYWP+XSE5OD9X8VKFKK7INNTMRZQK7YBTZ /e3U7GSWM/5CVAHFL6O9REQ9CWPXAVZNOHYVNXSOHSZDO+BXATXXA1XPALYALLY/8H/UAP4N4DLZDJJJJ3B8T1XH+CRRIOM OPXF7C5WKHHHTOKEOEXW+XOPQKKSX5CKWWJPPUGIIFWF/PAQWG+JUOSVT7QGDPV8PMWJ9DWEWJTDXGUDG == </Token>
เวอร์ชันใหม่ 4.5.3
ต้องการการรับรอง
แพ็คเกจ com.darren.test.https.v45; นำเข้า Java.io.File; นำเข้า Java.io.FileInputStream; นำเข้า java.security.keystore; นำเข้า javax.net.ssl.sslcontext; นำเข้า org.apache.http.conn.ssl.sslconnectionsocketFactory; นำเข้า org.apache.http.conn.ssl.sslconnectionsocketFactory; นำเข้า org.apache.http.conn.ssl.trustselfsignedstrategy; นำเข้า org.apache.http.ssl.sslcontexts; ชั้นเรียนสาธารณะ httpscertifiedClient ขยาย httpsClient {public httpscertifiedClient () {} @Override โมฆะสาธารณะ preperecertificate () พ่นข้อยกเว้น {// รับคีย์คีย์คีย์คีย์ Truststore = keystore.getInstance (keystore.getDefaulttype (); FileInputStream Instream = ใหม่ FileInputStream (ไฟล์ใหม่ ("C: /USERS/ZHDA6001/DOWNLOADS/SOFTWARE/XXX.KEYSTORE")); // fileInputStream instream = new FileInputStream (ไฟล์ใหม่ ("C: /USERS/ZHDA6001/DOWNLOADS/XXX.KEYSTORE")); ลอง {// รหัสผ่านของคีย์ไลบรารี Truststore.load (Instream, "รหัสผ่าน" .tochararray ()); } ในที่สุด {enterstream.close (); } sslContext sslContext = sslContexts.custom (). loadtrustmaterial (Truststore, TrustselfsignedStrategy.instance) .build (); this.connectionsocketFactory = ใหม่ sslconnectionsocketFactory (SSLContext); -ข้ามการรับรอง
แพ็คเกจ com.darren.test.https.v45; นำเข้า java.security.cert.certificateException; นำเข้า java.security.cert.x509certificate; นำเข้า javax.net.ssl.sslcontext; นำเข้า Javax.net.ssl.TrustManager; นำเข้า Javax.net.ssl.x509trustManager; นำเข้า org.apache.http.conn.ssl.sslconnectionsocketFactory; ชั้นเรียนสาธารณะ HttpStrustClient ขยาย httpsClient {public httpStrustClient () {} @Override โมฆะสาธารณะ preperecertificate () โยนข้อยกเว้น {// skip การตรวจสอบใบรับรอง sslcontext ctx = sslcontext.getInstance ("TLS"); X509TrustManager TM = ใหม่ x509TrustManager () {@Override โมฆะสาธารณะ checkClientTrusted (x509Certificate [] โซ่, สตริง AuthType) พ่นใบรับรอง {} @Override public checkserverTrusted (x509Certificate [] x509Certificate [] getacceptedissuers () {return null; - // ตั้งค่าเป็นใบรับรองที่เชื่อถือได้ ctx.init (null, ใหม่ TrustManager [] {tm}, null); this.connectionsocketFactory = ใหม่ sslconnectionsocketFactory (CTX); -สรุป
แพ็คเกจ com.darren.test.https.v45; นำเข้า org.apache.http.config.registry; นำเข้า org.apache.http.config.registryBuilder; นำเข้า org.apache.http.conn.socket.connectionsocketFactory; นำเข้า org.apache.http.conn.socket.plainconnectionsocketFactory; นำเข้า org.apache.http.impl.client.closeablehttpClient; นำเข้า org.apache.http.impl.client.httpclientbuilder; นำเข้า org.apache.http.impl.client.httpclients; นำเข้า org.apache.http.impl.conn.poolinghttpClientConnectionManager; บทคัดย่อคลาสสาธารณะ HTTPSClient ขยาย HTTPClientBuilder {ไคลเอ็นต์ closeinghttpClient ส่วนตัว; การเชื่อมต่อที่ได้รับการป้องกันการเชื่อมต่อที่มีการเชื่อมต่อ / ** * เริ่มต้น httpsClient * * @return ส่งคืนอินสแตนซ์ปัจจุบัน * @throws Exception */ public closeablehttpclient init () พ่นข้อยกเว้น {this.preprepercertificate (); this.regist (); ส่งคืนสิ่งนี้ client; } / ** * เตรียมการตรวจสอบใบรับรอง * * @throws Exception * / Public Public Void Preperecertificate () โยนข้อยกเว้น; / *** ลงทะเบียนโปรโตคอลและพอร์ตวิธีนี้ยังสามารถเขียนใหม่ได้โดย subclasses*/ void regist ที่ได้รับการป้องกัน () {// ตั้งค่าวัตถุที่สอดคล้องกับโปรโตคอล http และ https ที่จัดการรีจิสทรีโรงงานของซ็อกเก็ต PlainConnectionsocketFactory.Instance). register ("https", this.connectionsocketFactory) .build (); PoolinghttpClientConnectionManager ConnManager = ใหม่ PoolinghttpClientConnectionManager (SocketFactoryRegistry); httpClients.custom (). setConnectionManager (ConnManager); // สร้างวัตถุ httpClient ที่กำหนดเอง this.client = httpClients.custom (). setConnectionManager (ConnManager) .build (); // closeablehttpClient client = httpClients.createdefault (); - เครื่องมือ:
แพ็คเกจ com.darren.test.https.v45; นำเข้า java.util.arraylist; นำเข้า java.util.list; นำเข้า java.util.map; นำเข้า java.util.set; นำเข้า org.apache.http.htttentity; นำเข้า org.apache.http.httpresponse; นำเข้า org.apache.http.namevaluePair; นำเข้า org.apache.http.client.httpClient; นำเข้า org.apache.http.client.entity.urlencodedformentity; นำเข้า org.apache.http.client.methods.httpget; นำเข้า org.apache.http.client.methods.httppost; นำเข้า org.apache.http.client.methods.httprequestbase; นำเข้า org.apache.http.message.basicnamevaluepair; นำเข้า org.apache.http.util.entityutils; ชั้นเรียนสาธารณะ httpsClientutil {สตริงคงสุดท้ายคงที่เริ่มต้น chaold_charset = "utf-8"; สตริงคงที่สาธารณะ dopost (httpClient httpClient, url สตริง, แผนที่ <สตริง, สตริง> paramheader, แผนที่ <สตริง, สตริง> parambody) โยนข้อยกเว้น {return dopost (httpClient, url, paramheader, parambody, default_charset); } สตริงคงที่สาธารณะ dopost (httpClient httpClient, url สตริง, แผนที่ <สตริง, สตริง> paramheader, แผนที่ <สตริง, สตริง> parambody, สตริง charset) โยนข้อยกเว้น {string result = null; httppost httppost = ใหม่ httppost (url); Setheader (httppost, paramheader); setBody (httppost, parambody, charset); การตอบสนอง httpresponse = httpClient.execute (httppost); if (resentity! = null) {httpentity resentity = response.getEntity (); if (resentity! = null) {result = entityUtils.toString (resentity, charset); }} ผลการส่งคืน; } สตริงคงที่สาธารณะ doget (httpClient httpClient, url สตริง, แผนที่ <สตริง, สตริง> paramheader, แผนที่ <สตริง, สตริง> parambody) โยนข้อยกเว้น {return doget (httpClient, url, paramheader, parambody, default_charset); } สตริงคงที่สาธารณะ doGE (httpClient httpClient, url สตริง, แผนที่ <สตริง, สตริง> paramheader, แผนที่ <สตริง, สตริง> parambody, สตริง charset) พ่นข้อยกเว้น {สตริงผลลัพธ์ = null; httpget httpget = new httpget (url); Setheader (httpget, paramheader); การตอบสนอง httpresponse = httpClient.execute (httpget); if (ตอบกลับ! = null) {httpentity resentity = response.getEntity (); if (resentity! = null) {result = entityUtils.toString (resentity, charset); }} ผลการส่งคืน; } โมฆะแบบคงที่ส่วนตัว setheader (คำขอ httprequestbase, แผนที่ <สตริง, สตริง> paramheader) {// set header ถ้า (paramheader! = null) {ตั้งค่า <string> keyset = paramheader.keyset (); สำหรับ (คีย์สตริง: ปุ่ม) {request.addHeader (คีย์, paramheader.get (คีย์)); }}} โมฆะคงที่ส่วนตัว setbody (httppost httppost, แผนที่ <สตริง, สตริง> parambody, สตริง charset) โยนข้อยกเว้น {// ตั้งค่าพารามิเตอร์ถ้า (parambody! = null) {รายการ <namevaluePair> list = new ArrayList <namevaluePair> ตั้งค่า <string> keyset = parambody.keyset (); สำหรับ (คีย์สตริง: คีย์เซ็ต) {list.add (ใหม่ BasicAnmAneValuePair (คีย์, parambody.get (คีย์))); } if (list.size ()> 0) {urlencodedFormentity entity = ใหม่ urlencodedFormentity (list, charset); httppost.setEntity (เอนทิตี); - คลาสทดสอบ:
แพ็คเกจ com.darren.test.https.v45; นำเข้า java.util.hashmap; นำเข้า java.util.map; นำเข้า org.apache.http.client.httpClient; คลาสสาธารณะ httpsClientTest {โมฆะคงที่สาธารณะหลัก (สตริง [] args) พ่นข้อยกเว้น {httpClient httpClient = null; // httpClient = ใหม่ httpStrustClient (). init (); httpClient = ใหม่ httpscertifiedClient (). init (); string url = "https://1.2.6.2:8011/xxx/api/gettoken"; // string url = "https://1.2.6.2:8011/xxx/api/gethealth"; แผนที่ <สตริงสตริง> paramheader = ใหม่ hashmap <> (); paramheader.put ("ยอมรับ", "แอปพลิเคชัน/xml"); แผนที่ <สตริงสตริง> parambody = ใหม่ hashmap <> (); parambody.put ("client_id", "[email protected]"); parambody.put ("client_secret", "p@ssword_1"); สตริงผลลัพธ์ = httpsClientutil.dopost (httpClient, url, paramheader, parambody); // string result = httpsClientutil.doget (httpsClient, url, null, null); System.out.println (ผลลัพธ์); - ผลลัพธ์:
<? xml version = "1.0" encoding = "utf-8"?>?
<token> rxitf9 // 7nxwxjs2cjijyhltvzunvmzxxeqtgn0u07sc9ysjeibpqte3hcjulskoxpeuyguveweei9jv7/wi klrzxykc3ospatsm0kcbckphu0tb2cn/nfzv9fmlueowfbdyz+n0seii9k+0gp7920dfencn17wujvmc0u2jwvm5fa JQKMILWODXZ6A0DQ+D7DQDJWVCWXBVJ2ILHYIB3PR805VPPMI9ATXRVAKO0ODA006WEJFOFCGYG5P70WPJ5RBLBLBL85V FY9WCVKD1R7J6NVJHXGH2GNIMHKJEJORMJDXW2GKIUSIWSELI/XPSWAO7/CTWNWTNCTGK8PX2ZUB0ZFA == </token>
2. httpurlconnection
3. สปริงของสปริง
วิธีการอื่น ๆ จะได้รับการเสริมในภายหลัง
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น