مقدمة
تحكي هذه المقالة بشكل أساسي قصة Springboot التي تدمج MyBatis و Druid و Pagehelper وتنفيذ مصادر بيانات متعددة وترقيم ترقيمها. من بينها ، يدمج Springboot MyBatis ، الذي تم وصفه في مقال سابق ، لذلك لن أشرح ذلك كثيرًا هنا. ينصب التركيز على كيفية تكوين Druid و Pagehelper في مصادر بيانات متعددة.
مقدمة والاستخدام Druid
قبل استخدام Druid ، دعونا نلقي نظرة قصيرة على Druid.
Druid هو تجمع اتصال قاعدة البيانات. يمكن القول أن Druid هو أفضل مجموعة اتصال قاعدة بيانات في الوقت الحاضر! يفضله المطورين بشكل كبير لميزاتها الممتازة والأدائيات وقابلية التوسع.
نشرت Druid أكثر من 600 طلب على Alibaba ، وقد مر باختبار صارم للنشر على نطاق واسع في بيئات الإنتاج لأكثر من عام. Druid هو تجمع اتصال قاعدة بيانات تم تطويره بواسطة Alibaba يسمى المراقبة!
في الوقت نفسه ، فإن Druid ليس مجرد تجمع اتصالات قاعدة بيانات ، ويشمل جوهر Druid أساسًا ثلاثة أجزاء:
الوظائف الرئيسية للدرايد هي كما يلي:
لن أتحدث عن المقدمة ، يرجى الرجوع إلى الوثائق الرسمية للحصول على التفاصيل.
ثم لنبدأ في تقديم كيفية استخدام Druid.
بادئ ذي بدء ، تبعية Maven ، فقط أضف جرة Druid.
<Rependency> <roupeD> com.alibaba </rougiD> <StifactId> druid </shintifactid> <splection>
من حيث التكوين ، فإن الشيء الرئيسي هو إضافته في التطبيق.
ملاحظة: لأنني أستخدم مصدرين للبيانات هنا ، فهذا يختلف قليلاً. تعليمات تكوين Druid مفصلة بالفعل أدناه ، لذلك لن أشرح ذلك هنا.
## مصدر البيانات الافتراضي master.datasource.url = jdbc: mysql: // localhost: 3306/springboot؟ useUnicode = true & nichereding = utf8 & loomtliqueries = tru emaster.datasource.username = rootmaster.datasource.password = 123456master.datasource.driverClassName = com.mysql.jdbc.driver ## مجموعة أخرى من مصدر بيانات cluster.datasource.url = jdbc: mysql: // localhost: 3306/springboot_test؟ useUnicode = true & directionDing = utf8cluster.dataRce.username = cyluster.datasource.password = 123456cluster.datasource. معلومات التكوين لمجموعة الاتصال # تهيئة الحجم ، الحد الأدنى ، الحد الأقصى لـ spring.datasource.type = com.alibaba.druid.pool.druiddatasourcespring.datasource.initialsize = 5 spring.datasource.minidle = 5 spring.datasource.maxacive = 20 # spring.datasource.maxwait = 60000 # تكوين المدة التي يستغرقها إجراء فاصل زمني للكشف لاكتشاف الاتصالات الخاملة التي يجب إغلاقها ، في milliseconds spring.dataSource.TimeBetwEnevictionRunsmillis = 60000 # تكوين الوقت الحد الأدنى للبقاء في البليار spring.datasource.validationQuery = حدد 1 من spring.datasource.testwhileIdle = true spring.datasource.testonborrow spring.datasource.maxpoolpropedStateMentPerConnectionize = 20 # تكوين المرشحات لمراقبة إحصائيات تم اعتراضها. بعد إزالته ، لا يمكن حساب واجهة المراقبة SQL. يتم استخدام "الجدار" في جدار الحماية spring.datasource.filters = stat ، الجدار ، وظيفة log4j # open mergesql من خلال خاصية connectProperties ؛ سجلات SQL البطيئة spring.datasource.connectionProperties = druid.stat.mergesql = true ؛ druid.stat.slowsqlmillis = 5000
بعد إضافة ملف التكوين بنجاح ، دعنا نكتب الفئات المتعلقة بالدرويد.
أولاً ، فئة MasterDataSourCeConfig.java ، وهي فئة تكوين مصدر البيانات الافتراضية.
@configuration@mapperscan (basePackages = masterDataSourCeConfig.package ، sqlsessionfactoryref = "mastersqlsessionfactory") الفئة العامة masterdatasourceConfig {Static Final String package = "com.pancm.dao.master" ؛ Static Final String mapper_location = "classpath: mapper/master/*. xml" ؛ Value ("$ {master.datasource.url}") url سلسلة خاصة ؛ value ("$ {master.datasource.username}") اسم المستخدم الخاص بالسلسلة الخاصة ؛ Value ("$ {master.datasource.password}") كلمة مرور السلسلة الخاصة ؛ value ("$ {master.datasource.driverClassName}") سلسلة خاصة driverClassName ؛ Value ("$ {spring.datasource.initialsize}") private int initalsize ؛ value ("$ {spring.datasource.minidle}") private int minidle ؛ Value ("$ {spring.datasource.maxactive}") private int maxaCyt ؛ value ("$ {spring.datasource.maxwait}") private int maxwait ؛ Value ("$ {spring.datasource.TimeBetweenevictionRunsmillis}") private int ttreateDeenevictionRunsmillis ؛ value ("$ {spring.datasource.minevictableDletimEmillis}") private int minevictableDletimemillis ؛ Value ("$ {spring.datasource.validationQuery}") سلسلة خاصة بالسلسلة ؛ Value ("$ {spring.datasource.testwhileIdle}") اختبار منطقي خاص ؛ Value ("$ {spring.datasource.testonborrow}") private boolean testonborrow ؛ Value ("$ {spring.datasource.testonreturn}") private boolean testOnreturn ؛ Value ("$ {spring.datasource.poolpreparedstatements}") pollepreparedStatements الخاصة ؛ Value ("$ {spring.datasource.maxpoolpreparedStatePerConnectionSize}") private int maxpoolpropedstatePerConnectionsize ؛ Value ("$ {spring.datasource.filters}") مرشحات السلسلة الخاصة ؛ value ("{spring.datasource.connectionProperties}") سلسلة connectionProperties الخاصة ؛ bean (name = "masterDataSource") primary public dataSource masterDataSource () {druiddatasource dataSource = new DruidDataSource () ؛ datasource.seturl (url) ؛ datasource.setUserName (اسم المستخدم) ؛ datasource.setPassword (كلمة المرور) ؛ datasource.setDriverClassName (driverClassName) ؛ // configuration dataSource.setInitialSize (initialsize) ؛ datasource.setminidle (minidle) ؛ datasource.setMaxActive (maxaCive) ؛ datasource.setMaxWait (maxwait) ؛ datasource.SettimeBetweenevictionRunsmillis (timeBetweenevictionRunsmillis) ؛ datasource.setMineVictableDletimemillis (MineVictableIdletimemillis) ؛ datasource.setValidationQuery (ValideDquery) ؛ datasource.settesthileIdle (testhileIdle) ؛ datasource.settestonborrow (testOnBorrow) ؛ datasource.settestonreturn (testOnreturn) ؛ datasource.setPoolPrepedStatements (poolpreparedstatements) ؛ datasource.setMaxPoolPrepedStatePerConnectionSize (maxpoolprepedstateMentPerConnectionize) ؛ حاول {datasource.setFilters (المرشحات) ؛ } catch (sqlexception e) {E.PrintStackTrace () ؛ } datasource.setConnectionProperties (connectionProperties) ؛ إرجاع مصدر البيانات ؛ } bean (name = "MasterTransactionManager") primary public dataSourCetransActionManager masterTransActionManager () {return new datasourcetransactionManager (masterDataSource ()) ؛ } bean (name = "mastersqlsessionfactory") primary public sqlsessionfactory mastersqlsessionfactory ( @QALIFIER ("masterdataSource") dataSource masterdataSource) يلقي استثناءً {Final SqlsessionFactoryBean Sessionfactory = new sqlsatorybean () ؛ SessionFactory.setDatasource (MasterDataSource) ؛ SessionFactory.setMapperLocations (New PathMatchingResourceToRnresolver () .getResources (MasterDatasourCeConfig.mapper_location)) ؛ return sessionfactory.getObject () ؛ }}موضحة هذين التعليقات التوضيحية أدناه:
**@الأساسي **: شعار هذا الفاصوليا إذا كان هناك عدة مرشحين بين الفاصوليا
تعتبر الأولوية. عند تكوين مصادر بيانات متعددة ، احرص على أن يكون هناك مصدر بيانات أساسي واستخدام promary للاحتفال بالفاصوليا.
**@mapperscan **: مسح واجهة Mapper وإدارة الحاويات.
تجدر الإشارة إلى أن SQLSessionFactoryRef يمثل تحديد مثيل فريد من SQLSessionFactory.
بعد اكتمال التكوين أعلاه ، يمكن استخدام Druid كمجموعة اتصال. ومع ذلك ، فإن Druid ليس مجرد تجمع اتصال. يمكن أيضًا أن يقال إنه تطبيق مراقبة. يأتي مع واجهة مراقبة الويب ، والتي يمكن أن ترى بوضوح المعلومات المتعلقة بـ SQL.
باستخدام وظيفة مراقبة Druid في Springboot ، تحتاج فقط إلى كتابة فئات StatViewServlet و WebStatFilter لتنفيذ خدمات التسجيل وتصفية القواعد. هنا يمكننا كتابة هذين معًا ، باستخدام **@التكوين ** و **@Bean **.
من أجل الفهم السهل ، تتم كتابة تعليمات التكوين ذات الصلة أيضًا في الكود ، لذلك لن أخوض في التفاصيل هنا.
الرمز كما يلي:
ConfigurationPublic class druidConfiguration {bean public servlegistrationBean druidStatViewServle () {// registrice servletregistrationbean servleTregistrationBean = new ServleTregistrationBean (New StatViewServlet () ، "/druid/*") ؛ // القائمة البيضاء (يمثل فارغًا ، يمكن الوصول إليها جميعًا ، مفصولة بفواصل لعدة IPs) servletregistrationbean.addinitparameter ("السماح" ، "127.0.0.1") ؛ // IP Blacklist (تتبع Dony الأسبقية على السماح عندما يكون هناك وجود مشترك) ServleTregistrationBean.AddinitParameter ("Deny" ، "127.0.0.2") ؛ // قم بتعيين اسم مستخدم تسجيل الدخول وكلمة المرور servletregistrationbean.addinitparameter ("loginuserName" ، "pancm") ؛ ServleTregistrationBean.AddinitParameter ("LoginPassword" ، "123456") ؛ // ما إذا كان من الممكن إعادة تعيين البيانات. servletregistrationBean.addinitParameter ("إعادة ضبط" ، "خطأ") ؛ إرجاع servletregistrationBean ؛ } bean publicregistrationbean druidstatfilter () {filterregistrationBean filterregistrationBean = new filterregistrationBean (webstatfilter () جديد) ؛ // إضافة قواعد التصفية filterregistrationBean.addurlpatterns ("/*") ؛ // إضافة معلومات التنسيق التي لا تحتاج إلى تجاهل filterregistrationbean.addinitparameter ("الاستثناءات" ، "*.js ،*. gif ،*. jpg ،*. png ،*. System.out.println ("التهيئة druid بنجاح!") ؛ إرجاع FilterregistrationBean ؛ }}بعد الكتابة ، ابدأ البرنامج ، أدخل: http://127.0.0.1:8084/druid/index.html في المتصفح ، ثم أدخل اسم المستخدم وكلمة المرور للوصول إلى واجهة الويب.
تكوين مصدر البيانات المتعددة
قبل تنفيذ تكوين مصدر البيانات المتعددة ، قم بتنفيذ البرامج النصية التالية في قواعد بيانات MySQL الخاصة بـ Springboot و Springboot_test على التوالي.
-البرنامج النصي لمكتبة Springboot إنشاء جدول "T_USER` (` id` int (11) not null auto_increment التعليق "معرف الإثبات الذاتي" ، `name` varchar (10) التعليق الفارغ مكتبة springboot_test إنشاء جدول `t_student` (` id` int (11) not null auto_increment ، `name` varchar (16) default null ،` age` int (11) default null ، key in `id`)) المحرك = innodb auto_increment = 2 default charset = utf8
ملاحظة: لكي تكون كسولًا ، يتم تصنيع هيكل الجدولين! لكنه لن يؤثر على الاختبار!
تم تكوين معلومات حول هذين مصدر البيانات في التطبيق.
سنركز هنا على تكوين مصدر البيانات الثاني. إنه مشابه لـ MasterDataSourCeConfig.java أعلاه ، والفرق هو أنه يختلف عن التعليقات التوضيحية والاسم الأساسي **@@@Primary ** دون استخدام التعليقات التوضيحية **@Primary **. تجدر الإشارة إلى أن حزمة MasterDataSourCeConfig.java و Mapper بدقة إلى الدليل ، وينطبق الشيء نفسه على مصدر البيانات الثاني هنا. ثم الرمز كما يلي:
@configuration@mapperscan (basePackages = clusterDataSourCeConfig.package ، sqlsessionfactoryref = "clustersqlsessionfactory") clusterdatasourceConfig {Static Final String package = "com.pancm.dao.cluster" ؛ Static Final String mapper_location = "classpath: mapper/cluster/*. xml" ؛ Value ("$ {cluster.datasource.url}") url سلسلة خاصة ؛ Value ("$ {cluster.datasource.username}") اسم المستخدم الخاص ؛ Value ("$ {cluster.datasource.password}") proview proview provia ؛ Value ("$ {cluster.datasource.driverClassName}") سلسلة خاصة // مثل MasterDataSourCeConfig ، هنا bean (name = "clusterdatasource") public dataSource clusterdataSource () {druiddatasource dataSource = new DruidDataSource () ؛ datasource.seturl (url) ؛ datasource.setUserName (اسم المستخدم) ؛ datasource.setPassword (كلمة المرور) ؛ datasource.setDriverClassName (driverClass) ؛ // مثل MasterDataSourCeConfig ، هنا ... إرجاع مصدر البيانات ؛ } bean (name = "clustertransactionManager") dataSourCetransActionManager clustertransactionManager () {return dataSourCetransactionManager (clusterdatasource ()) ؛ } bean (name = "clustersqlsessionfactory") sqlsessionfactory clustersqlsessionfactory (QAlifier ("clusterdatasource") dataSource ClusterDataSource) يلقي استثناء {Final SqlsessionFactorybean sessionfactory = new sqlsastoryfean () ؛ SessionFactory.setDatasource (ClusterDataSource) ؛ SessionFactory.setMapperLocations (New PathMatchingResourceToRnresolver (). return sessionfactory.getObject () ؛ }} بعد كتابة التكوين بنجاح ، ابدأ البرنامج وإجراء اختبارات.
استخدم واجهات لإضافة بيانات في مكتبات Springboot و Springboot_test ، على التوالي.
T_USER
post http: // localhost: 8084/api/user {"name": "Zhang San" ، "Age": 25} {"name":t_student
post http: // localhost: 8084/api/student {"name": "student a" ، "age": 16} {"name":بعد إضافة البيانات بنجاح ، اتصل واجهات مختلفة للاستعلام.
بسأل:
احصل على http: // localhost: 8084/api/user؟ name = li si
يعود:
{"id": 2 ، "name": "Li Si" ، "Age": 25}بسأل:
احصل على http: // localhost: 8084/api/student؟ name = student c
يعود:
{"id": 1 ، "name": "Student C" ، "Age": 16}من البيانات ، يمكننا أن نرى أنه تم تكوين مصادر بيانات متعددة بنجاح.
تنفيذ صفحات الصفحة
Pagehelper هو مكون إضافي للترحيل لـ MyBatis ، وهو مفيد جدًا! يوصى بشدة هنا! ! !
Pagehelper بسيط للغاية للاستخدام ، تحتاج فقط إلى إضافة تبعية Heelper في Maven.
تبعيات مافن هي كما يلي:
<Rependency> <roupeD> com.github.pageHelper </rougiD> <intifactid> pagehelper-spring-boot-starter </shintifactid> <الإصدار> 1.2.3 </version> </dependency>
ملاحظة: أستخدم إصدار Springboot هنا! يمكن أيضًا استخدام الإصدارات الأخرى.
بعد إضافة التبعيات ، تحتاج فقط إلى إضافة التكوين أو التعليمات البرمجية التالية.
تتم إضافة النوع الأول في application.properties application.yml
helper: helperdialect: mysql offsetaspagenum: true rowboundswithcount: true alcal: false
تتم إضافة النوع الثاني في تكوين mybatis.xml
<bean id = "sqlsessionfactory"> <property name = "datasource" ref = "datasource"/> <!-مسح ملف mapping.xml-> <property name = "mapperlocations" value = "classpath: mapper/*. <value> helperdialect = mysql offsetaspagenum = true rowboundswithcount = true alcord = false </value> </propert
تتم إضافة النوع الثالث في الكود وتهيئته عند بدء تشغيل البرنامج باستخدام التعليق التوضيحي **@Bean **.
bean public pagehelper pagehelper () {pagehelper pagehelper = new pagehelper () ؛ خصائص الخصائص = خصائص جديدة () ؛ // properties.setProperty ("helperdialect" ، "mysql") ؛ // ما إذا كان يجب استخدام إزاحة المعلمة كخصائص pagenum.setProperty ("OffsetAspagenum" ، "true") ؛ // ما إذا كان يجب الاستعلام عن خصائص العد. // ما إذا كان لترشيد خصائص ترقيم الصفحات ("معقول" ، "خطأ") ؛ pagehelper.setProperties (الخصائص) ؛ }نظرًا لأننا نستخدم مصادر بيانات متعددة هنا ، فإن التكوين هنا مختلف قليلاً. نحن بحاجة إلى تكوينه في SessionFactory. هنا نقوم بإجراء تعديلات مماثلة لـ MasterDataSourceConfig.java. في طريقة MasterSqlSessionFactory ، أضف الكود التالي.
bean (name = "mastersqlsessionfactory") primary public sqlsessionfactory mastersqlsessionfactory ( @Qalifier ("masterdatasource") dataSource MasterDataSource) يلقي استثناء {FinalsessionFactorybean sessionfactory = new sqlsessionbean () ؛ SessionFactory.setDatasource (MasterDataSource) ؛ SessionFactory.setMapperLocations (New PathMatchingResourceToRnresolver () .getResources (MasterDatasourCeConfig.mapper_location)) ؛ // pagination plug-in interceptor interceptor = new pageInterceptor () ؛ خصائص الخصائص = خصائص جديدة () ؛ // properties.setProperty ("helperdialect" ، "mysql") ؛ // ما إذا كان يجب استخدام إزاحة المعلمة كخصائص pagenum.setProperty ("OffsetAspagenum" ، "true") ؛ // ما إذا كان يجب الاستعلام عن خصائص العد. // ما إذا كان لترشيد خصائص ترقيم الصفحات ("معقول" ، "خطأ") ؛ Interceptor.SetProperties (الخصائص) ؛ SessionFactory.setPlugins (اعتراض جديد [] {اعتراض}) ؛ return sessionfactory.getObject () ؛ }ملاحظة: عندما تريد مصادر البيانات الأخرى أيضًا الترحيل ، يرجى الرجوع إلى الرمز أعلاه.
ما تحتاج إلى ملاحظته هنا هو المعلمة المعقولة ، مما يعني ترشيد الترحيل ، والقيمة الافتراضية خاطئة. إذا تم تعيين هذه المعلمة على TRUE ، فسيتم الاستعلام عن الصفحة الأولى عند Pagenum <= 0 ، وصفحات Pagenum> (عندما يتجاوز العدد الإجمالي) ، سيتم الاستعلام عن الصفحة الأخيرة. عندما يكون خطأ بشكل افتراضي ، يعتمد الاستعلام مباشرة على المعلمات.
بعد تعيين helper ، إذا كنت تستخدمه ، فأنت بحاجة فقط إلى إضافة PageHelper.startPage(pageNum,pageSize); أمام الاستعلام SQL. إذا كنت ترغب في معرفة العدد الإجمالي ، فقم بشرائه بعد بيان الاستعلام SQL وأضف page.getTotal() .
مثال رمز:
القائمة العامة <T> findByListentity (t intity) {list <t> list = null ؛ حاول {page <؟> page = pagehelper.startPage (1،2) ؛ System.out.println (getClassName (الكيان)+"اضبط بيانات اثنين على الصفحة الأولى!") ؛ list = getMapper (). findByListentity (الكيان) ؛ System.out.println ("هناك ما مجموعه:"+page.getTotal ()+"البيانات ، والعودة الفعلية هي:"+list.size ()+"بياناتان!") ؛ } catch (استثناء e) {logger.error ("query"+getClassName (untity)+"فشل! السبب هو:" ، e) ؛ } قائمة الإرجاع ؛ }بعد كتابة الكود ، يبدأ الاختبار النهائي.
الاستعلام عن جميع البيانات في جدول T_USER وربطها.
بسأل:
احصل على http: // localhost: 8084/api/user
يعود:
[{"id": 1 ، "name": "Zhang San" ، "Age": 25} ، {"id": 2 ، "name": "li si" ، "Age": 25}]طباعة وحدة التحكم:
ابدأ في الاستعلام ...
يقوم المستخدم بتعيين قطعتين من البيانات على الصفحة الأولى!
2018-04-27 19: 55: 50.769 Debug 6152 --- [IO-8084-EXEC-10]
2018-04-27 19: 55: 50.770 Debug 6152 --- [IO-8084-EXEC-10] CPDMUSERDAO.FindByListentity_Count: ==> المعلمات:
2018-04-27 19: 55: 50.771 Debug 6152 --- [IO-8084-EXEC-10]
2018-04-27 19: 55: 50.772 Debug 6152 --- [IO-8084-EXEC-10] cpdao.master.userdao.findbylistentity: ==> preparing: Select id ، name ، age from t_user حيث 1 = 1 حد؟
2018-04-27 19: 55: 50.773 debug 6152 --- [IO-8084-EXEC-10]
2018-04-27 19: 55: 50.774 Debug 6152 --- [IO-8084-EXEC-10]
هناك ما مجموعه: 3 قطع من البيانات ، والعائد الفعلي هو: 2 قطعتان من البيانات!
الاستعلام عن جميع البيانات في الجدول t_student وربطها.
بسأل:
احصل على http: // localhost: 8084/api/student
يعود:
[{"id": 1 ، "name": "Student A" ، "Age": 16} ، {"id": 2 ، "name": "student B" ، "Age": 17}]طباعة وحدة التحكم:
ابدأ في الاستعلام ...
يقوم Studnet بتعيين قطعتين من البيانات على الصفحة الأولى!
2018-04-27 19: 54: 56.155 Debug 6152 --- [Nio-8084-EXEC-8]
2018-04-27 19: 54: 56.155 Debug 6152 --- [NIO-8084-EXEC-8]
2018-04-27 19: 54: 56.156 Debug 6152 --- [NIO-8084-EXEC-8]
2018-04-27 19: 54: 56.157 Debug 6152 --- [NIO-8084-EXEC-8] CPDCSTUDENTDAO.FindByListentity: ==> التحضير: تحديد المعرف ، الاسم ، العمر من T_STUDENT حيث 1 = 1 حد؟
2018-04-27 19: 54: 56.157 Debug 6152 --- [Nio-8084-EXEC-8]
2018-04-27 19: 54: 56.157 Debug 6152 --- [NIO-8084-EXEC-8]
هناك ما مجموعه: 3 قطع من البيانات ، والعائد الفعلي هو: 2 قطعتان من البيانات!
بعد اكتمال الاستعلام ، دعونا نلقي نظرة على واجهة مراقبة Druid. أدخل المتصفح: http://127.0.0.1:8084/druid/index.html
يمكن رؤية سجلات التشغيل بوضوح!
إذا كنت تريد معرفة المزيد عن Druid ، فيمكنك التحقق من الوثائق الرسمية!
خاتمة
تم الانتهاء من هذا المقال أخيرًا. عند كتابة الرمز ، واجهت العديد من المشكلات ، ثم حاولت ببطء ووجدت معلومات لحلها. تقدم هذه المقالة هذه الاستخدامات ذات الصلة فقط بإيجاز شديد ، وقد تكون أكثر تعقيدًا في التطبيقات الفعلية.
المقالة المرجعية: https://www.bysocket.com/؟p=1712
العنوان الرسمي Durid: https://github.com/alibaba/druid
عنوان الصفحة الرسمي: https://github.com/pagehelper/mybatis-pagehelper
لقد وضعت المشروع على github: https://github.com/xuwujing/springboot ، يمكنك أيضًا تنزيله محليًا: انقر هنا
لخص
ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.