中國電動汽車產業展覽會

电动汽车是提高我国汽车产业竞争力、保障能源安全和发展低碳经济的重要途径,同时发展电动汽车也是我国汽车工业技术转型和培育战略性新兴产业的必然决择。
为深入贯彻《节能与新能源汽车产业发展规划(2012-2020)》和《“十二五”国家战略性新兴产业发展规划》等文件精神,培育战略性新兴产业,加快电动汽车产业化进程和示范推广普及应用步伐,引领产业发展,壮大产业集群,推动我国电动汽车产业快速健康发展。由重庆市商业委员会、中国汽车工程研究院、重庆市发展和改革委员会、重庆市社会科学院等单位举办的中国电动汽车产业展览会将于2013年3月在重庆举办。
本届活动以“绿色科技,畅想梦幻未来”为主题,紧紧围绕电动汽车产业发展,以展示展览为主线,力争通过万人试乘试驾、电动汽车爬坡拉力大赛、经销商大会,市长论坛、技术发展报告等活动。强化科技创新能力建设,提升产业核心竞争力,促进产业发展,加大电动汽车推广使用力度,积极发展配套产业,建立健全充换电等公共服务平台,完善销售流通渠道,抢占未来汽车产业战略制高点,进一步推动我国由汽车工业大国向汽车工业强国迈进。
中国电动汽车产业展览会
时间:2013年3月22-24日 地点:重庆国际博览中心

主办单位: 重庆市商业委员会 中国汽车工程研究院
重庆市发展和改革委员会 重庆社会科学院等
联合主办:博鳌国际汽车论坛组委会/中国化学与物理电源行业协会电源配件分会/中国化学与物理电源行业协会酸性蓄电池分会/ 北京汽车工程学会/河南省电动车辆工程协会/成都市新能源汽车产业发展联盟/佛山市南海区汽车行业协会/烟台市汽车工业协会/福建省汽车工业行业协会/广州汽车工业行业协会/湖北省汽车流通协会/南京汽车行业协会/泉州市汽车同业协会/陕西省汽车行业协会/陕西省汽车工程学会/四川省电力电子学会/四川省汽车产业协会/四川省汽车工程学会/威海市汽车流通行业协会/潍坊市汽车流通行业协会/无锡市机械汽车工业协会/厦门市出租汽车暨汽车租赁协会/重庆市电力行业协会/重庆市交通运输协会/重庆汽车工程学会/重庆市汽车摩托车运动协会等。
承办单位:重庆市立嘉会议展览有限公司
主要内容:
1、中国电动汽车产业展览会
本届活动以“绿色科技,畅想梦幻未来”为主题,紧紧围绕电动汽车产业发展,以展示展览为主线,力争通过万人试乘试驾、电动汽车爬坡拉力大赛、经销商大会,市长论坛、技术发展报告等活动。强化科技创新能力建设,提升产业核心竞争力,促进产业发展,加大电动汽车推广使用力度,积极发展配套产业,建立健全充换电等公共服务平台,完善销售流通渠道,抢占未来汽车产业战略制高点,进一步推动我国由汽车工业大国向汽车工业强国迈进。
整车集成展区:混合动力汽车(微混、轻混、中混、重混和插电式混合)、纯电动汽车、燃料电池汽车、电动客车、电动公交车、电动轿车、电动清洁车、电动观光车、电动货车、电动高尔夫球车、电动牵引车、电动警用巡逻车、电动房车、电动叉车、电动医疗车、电动邮政车特种电动车及其它各种新能源汽车等。
电池电机展区:动力电池、燃料电池、锂离子电池、锂聚合物电池、铅酸蓄电池、超级电容器产品设备等相关原材料;制造设备、测试仪器、各种动力电池与管理系统;低排放节能型发动机、混合动力发动机及清洁燃料发动机、电机、电机保护与控制技术电机电控系统等。
充电设施及公共平台建设展区:充电站智能网络项目规划及成果、 充电站项目规划及成果展示;充电站、充电机、充电桩、配电设备、变压器、电缆等相关基础设施;充换电池及电池管理系统;电能监视系统;供电解决方案、充电站配电设备、直接充电设备 、管理辅助设备、停车场充电设施、智能监控充电站供电解决方案、智能电网、输电并网技术解决方案。
零部件展区:整车总线与控制系统;储能装置等;能源管理系统;电力电容器、飞轮、逆变器、电热泵、电动助力转向、电动空调、功率模块等;相关材料、工艺、技术;相关检测、监控、试验、安全防护装备;维修、制造设备和工具;
电动车示范、试点城市成果展区:电动汽车推广应用经验与成果、电动汽车产业发展战略与规划。
其他展区:各地方政府有关机构、产业园区、金融机构、研究机构、大专院校以及相关企业。
2、2013中国重庆电动汽车万人试乘试驾活动
电动汽车普及难题除充电基础设施建设滞后等主因外,广大消费者对电动汽车的了解认知程度也是制约推广普及进程的又一主因。为了提高消费者对电动汽车的了解认知程度,强化推广力度,促进电动汽车产业化发展,进一步推动两江新区汽车城建设,更好的办好本届活动,组委会决定在中国电动汽车产业展览会期间举办中国(重庆)电动汽车万人试乘试驾活动。
3、2013中国重庆电动汽车爬坡拉力大赛
为了推广普及电动汽车,丰富中国电动汽车产业展览会内容。推动两江新区汽车城建设,培育电动汽车产业集群,促进电动汽车产业化进程。更好的办好本届活动。组委会拟定在中国电动汽车产业展览会期间举办中国重庆汽车爬坡拉力大赛。
本次赛事主要设定续航里程、加速、安全 性能速度测评频次主要指标的评比,力争通过此赛提升我国电动汽车产业的科技水平,带动相关产业发展,培育产业集群,进一步提升重庆两江汽车城在全球的影响力。宣传重庆、吸引优秀企业落户重庆,加速重庆电动汽车产业化进程。
4、首届中国电动汽车经销商大会
当前中国乃至世界范围内电动汽车产业异军突起,产业发展正处于十分关键的历史机遇期,同时我国也迎来电动汽车车型最高密度的投放期,市场需求强劲,尤其是其购车养车成本低廉、车型时尚观赏性强、低碳零排放促进环保消费等特点吸引了年轻消费者的目光,消费潜力巨大,市场前景广阔。为了进一步推广普及电动汽车,共同打造销售流通产业链,探索适合中国电动汽车发展的商业模式。组委会拟定在中国电动汽车产业展览会期间举办首届中国电动汽车经销商大会。

地址:重庆市南岸区开发路31号科尔国际商务大厦28-5 
电话:023-61221989 
传真:023-88638520
 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

2013第九屆中原(鄭州)自行車電動車博覽會

展會時間:2011年4月8日—4月10日

展會地點:中國·鄭州·中原國際博覽中心

主辦單位:河南省發展和改革委員會 河南日報報業集團

承辦單位:大河報 社 中原國際博覽中心

新一輪的發展機遇

節能、環保、便捷的電動車越來越受消費者歡迎;電動車下鄉,帶來了更廣闊的市場空間;自行車作為「健身工具」的逐漸普及帶來新的投資機會;從「經營網絡」到「全面品牌營銷」的轉變又賦予品牌擴張新的活力;鋰電製造規模化將推動產業升級換代;電動汽車技術獲得突破,行業潛力凸顯;市場保有量的增加帶來了電動車維修和電池維護的巨大商機。誰能抓住新一輪的發展機遇,誰就能立於品牌競爭不敗之地並引領電動車發展的潮流!

最大的市場 最好的大本營

河南,全國最大的電動車市場之一。中西部九省也成為全國最具發展潛力的消費市場。河南,縱貫南北、連接東西、十省通衢、輻射八方,區位優勢無可替代。佔據中原,圖謀天下方成。河南已成為開拓中西部廣闊市場的橋頭堡、大本營。

超80%的企業重複參展率給予展會效果最好的證明和信任!

「中原電動車招商洽談會」已經成功舉辦八屆。先後有2300餘家國內外知名整車、電池、配件等相關企業參展,重複參展率達80%,吸引來自河南及周邊10餘個省份18萬人次經銷商、專業人士到會參觀洽談,現場看車、選車、購車市民則達60萬人次。為企業開拓中西部市場,提升品牌知名度提供了良好的招商宣傳捷徑,深受政府相關部門、參會企業、到會觀眾、媒體、消費者等的一致好評。

宣傳組織保證 十萬客商匯聚

1、主流媒體更多 宣傳更加強勢

①《大河報》將聯合中西部主流媒體《楚天都市報》、《新安晚報》、《華商報》、《燕趙都市報》、《江南都市報》、《山西晚報》等共同為展會宣傳造勢,邀中西部各省觀眾相聚鄭州。②《大河報》——世界日報發行百強,中國報業四強,河南報業航母,日發行量超過100萬份,高密度覆蓋河南及周邊省份近1.8億人口,是中部地區最具影響力、號召力和傳播力的主流強勢媒體。《大河報》展會報導記者小組,將投入近40個版面持續6個月對展會進展、行業動態進行全方位、多角度、大篇幅報導。

2、觀眾範圍更廣 效果更加保障

秉承「觀眾至上」的辦展理念,打造最具實效的區域展會品牌。在前八屆積累數萬專業經銷商資料庫的基礎上,專門增加40名經驗豐富、工作紮實工作人員奔赴河南、河北、山東、安徽、湖北、陝西、山西、甘肅、內蒙古等各省、市、縣,上門派送100萬份參觀門票並電話、短信多次邀請專業觀眾參觀。

3、媒體合作更廣泛 覆蓋更加全面

擬充分整合40多家全國性專業報紙雜誌、專業網站和大眾媒體的渠道優勢,刊登展會廣告,並做專題報導,全面覆蓋各相關目標客戶,廣泛深入宣傳本次活動。

4、宣傳方式更多 服務更加周到

①與協會合作,通過其組織參觀採購團。②將投放戶外廣告、高速廣告、車體廣告等。③各大行業展會實地宣傳推廣。④各地設立免費觀眾接待車直達現場。⑤豐富多彩活動吸引更多經銷商到場參觀。

■ 時間安排

布展:2011年4月6日-7日 展期:2011年4月8日-4月10日

■ 參會範圍

◆各類電動自行車、電動三輪車、燃油助力車及殘疾人專用電動車、電動滑板車等特種電動車;

◆各類自行車、摺疊車、童車等;

◆各類電動汽車、觀光車、電瓶車、休閒旅遊車、巡邏車等;

◆電動車電池及電池維護、電機、充電器、控制器、輪胎、塑殼及其它零配件和維修工具與設備,電動車用防盜鎖具、報警設備等;

■ 精彩活動

◆「消費者信得過電動車品牌」評比活動◆「金牌售後服務品牌」評比活動◆自行車特技表演◆行業最新產品、技術發佈及交流會◆中原電動車行業發展高峰論壇暨單店品牌營銷知識講座◆電動汽車、鋰電池電動車市場發展論壇◆電動車維修及電池維護市場分析及技術交流會◆電動車維修技術擂台賽

■ 收費標準

①標準展位:4800元/個。中廳特位:6800元/個。

註:標準展位每個規格9平方米(長3米×寬3米×高2.45米),包含三面圍板,中文刻字楣板,一桌兩椅,兩盞射燈,220V、5A電源插座。展架改動和增加配置費用自理。

②光地:550元/平方米,36平方米起租,不提供任何配置。

註:特裝展位場地管理費,施工電費及電箱租用所產生的費用等,需向展館方自行支付。

■ 廣告規格及價格:(詳情備索)

■ 組委會辦公室(河南省鄭州市鄭汴路96號中原國際博覽中心306室)

聯繫人:劉康生 電話:0371-66759152 66759259 66759151 傳 真:0371-66759136 / 7 / 8

中原電動車網: http://www.dhbhz.com 大 河 報 網:http://www.dahebao.cn

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

2013第五屆中國(臨沂)新能源汽車、電動車及零部件展覽會

主辦單位:中國國際貿易促進委員會臨沂市委員會、山東省環保產業協會、山東省環保產品認證中心 、臨沂市資源節約型環境友好型社會建設改革試點辦公室

承辦單位:臨沂市格益傳媒有限公司

抓住機遇2012 New energy vehicles Exhibition 激情魯南,物流業大市的優勢催生山東商業「二次飛躍」新格局2012 New energy vehicles Exhibition送給您一個千載難逢的商機!山東是國內第三大市場,臨沂是山東最重要的產品集散地,同時是國內第二大商貿城和物流之都,500公里半徑覆蓋華北市場,全國十大工業品市場中名列第三。作為魯東南中心城市,凝聚力、輻射力、帶動力逐步提升,現已成為全國繼江蘇、浙江、天津之後新崛起的電動車產業基地,山東省十大影響力產業集群。

「一年之計在於春」,由格益傳媒有限公司承辦的「第五屆中國(臨沂)新能源汽車、電動車及零部件展覽會」定於2013年4月舉行, 4月既是商家銷售產品的有利時機也是商家採購產品的旺季,同時也是廠家促銷產品、推出新品、打造品牌的最佳時機。在這個時候新能源電動車及零部件展覽會有利於促進廠商之間的交流,實現共同發展。我們誠摯的邀請廣大新能源電動汽車、電動車及零部件廠商到商貿物流城-臨沂參展、參會,共享成功展會帶來的巨大商機!

【展會日程安排】展覽時間: 2013年4月19-21日 閉幕撤展時間:2013年4月21日下午15:00
  
【參展範圍】新能源車輛展區:整 車:電動(混合動力)汽車、電動旅遊觀光車、電動高爾夫車,電動吉普車、太陽能電動車、電動客貨車、電動清潔車、電動叉車、電動升降車;氫能源、天然氣等各種新能源車輛;各種低排放、環保節能型汽車臨沂特色電動客運三輪車展區電動兩輪、三輪車展區:各類電動自行車、電動三輪車、燃油助力車及殘疾人專用電動車、電動滑板車等特種電動車特種車輛展區:清障車、環保車輛、改裝車輛等。配套電池、配件、零部件和相關技術資料展區

【收費標準】 國際標準展位:展位3m×3m 3600元 /個 國際標準展位(雙面開口) 4200元/個標準展位配套設施:三面或兩面展板、一張洽談桌、兩把椅子、兩盞射燈、一個220V的電源插座、參展單位楣板文字製作以及展館內衛生、安全保衛等。

特展:最少以36平方米起租,國內企業:400元/平方米,國外企業100美元/平方米;(註:室內空地無配套設施:展具由參展商自行設計搭建。)

【參展手續】◆填寫參展申請書,加蓋公章後郵寄或傳真至大會組委會,參展申請被接收後組委會將通過傳真或郵寄方式發放「參展確認書」確認您的申請;展位按「先報名,先分配,先付款」的原則安排。

◆申請被確認後7天內將所需費用匯入大會指定帳號,逾期不確認參展資格,展位按「先申請,先付款,先分配」的原則安排。

◆會前一個月組委會將協助參展企業完成參展後續服務。參展一經確認,參展商不得撤消參展和轉讓展位。因某種特殊原因或不可抗拒因素時,組委會有權將展期和展場更改,若因某種原因取消本次會議,參展商所交費用全部退回;

◆組委會根據現場實際情況有權對極少數參展企業的展位予以現場調整。

【展會特色及亮點】

1、通過山東省的宣傳媒體,在本地區進行密集宣傳推介;
2、各專業報刊、雜誌媒體宣傳推介本屆展會信息及互聯網推廣;
3、發函邀請各行業內專業協會、團體;並邀約海內外客商及中間商參觀;
4、山東省首家涉足新能源汽車展並連續成功舉辦四屆的專業展會;
5、山東省行業內唯一採用「大巴車免費迎接經銷商」參展模式的專業展會;
6、組委會同時組織了電動車行業發展論壇,資深業內專業人士深入探討交流;
7、組委會安排專人走訪市場並邀請安徽、江蘇、河南、河北、山東、浙江、天 津等地的電動車及零部件產業類生產企業,進一步廣泛邀請專業經銷商。
8、發函邀請友好城市政府機構、行業組織;

大會組委會聯繫方式:

聯繫人:2013第五屆中國(臨沂)新能源汽車、電動車及零部件展覽會組委會
電 話:0539-8059156 15725997759 15653999229 張經理
傳 真:0539-8059156

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

2013年郑州(国际)新能源汽车及校车展览会

主办单位:河南省电动汽车产业发展领导小组、河南省电动汽车产业联盟、河南省电动车辆工程协会、中国国际贸易促进委员会郑州支会、中国国际商会郑州商会、河南大成展览服务有限公司
承办单位:中国国际商会郑州商会、河南大成展览服务有限公司

新能源汽车作为国家战略性新兴产业,被看做中国汽车行业今后五年发展的重中之重,新能源汽车也将迎来动力系统转型发展的战略机遇期。

河南省在新能源汽车方面面临着重大发展机遇。为稳定汽车消费,加快结构调整,推动产业升级,国家制定了《汽车产业调整和振兴规划》,从扩大消费需求、增强自主创新能力、推进企业战略重组、加快新能源汽车发展等方面出台了一系列政策措施,为汽车产业更好更快发展提供了良好环境和新的机遇。特别是在资源、环境的约束下,汽车动力系统进入转型期,新能源汽车和节能型汽车成为国家支持的重要领域。汽车动力系统的电动化和经济适用型汽车成为汽车产业持续、快速发展的关键,为河南省在新能源汽车和节能经济型微车方面的发展提供了机遇。抓住国家实施新能源汽车战略的机遇,坚持自主研发与战略合作相结合,实施边路突破战略,着力做强电池、电机等关键零部件,提升新能源汽车整车水平,形成较为完整的新能源汽车产业链。到2013年,动力电池产业率先实现突破,混合动力、纯电动整车实现规模化生产。到2015年,在电动汽车动力系统、控制系统、管理系统、专用底盘等方面形成核心技术,所有整车企业具备新能源汽车制造能力。到2020年,建设成为我国重要的新能源汽车生产基地。

综上所述,河南新能源汽车产业发展潜力巨大,同时国家对校车的鼓励政策以及在该领域内陆续实施的一系列重点工程,给该产业发展带来了无限商机和激烈的国内外竞争,也使校车产业在信息沟通、技术交流、成果宣传等方面的需求日趋强烈,为迎合新能源汽车与校车迅猛的发展趋势,满足国内外相关企业在应用技术与装备领域的生产和贸易需求,进一步推动我省新能源汽车及校车安全技术的提高,兹有河南省电动汽车产业发展领导小组、河南省电动汽车产业联盟、河南省电动车辆工程协会、中国国际贸易促进委员会郑州支会、中国国际商会郑州商会、河南大成展览服务有限公司定于2013年5月27-29日在郑州国际会展中心举“ “2013年郑州(国际)新能源汽车及校车展览会”。

组委会联系方式:

电话:0371-55627056 55627057 传真:0371-55627058

手机:13598889948

联系人:沈家军

邮箱:dachengexpo@163.com

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

三文搞懂學會Docker容器技術(中)

接着上面一篇:三文搞懂學會Docker容器技術(上)

6,Docker容器

  6.1 創建並啟動容器

docker run [OPTIONS] IMAGE [COMMAND] [ARG…]

–name=”容器新名字”:為容器指定一個名稱;

-i:以交互模式運行容器,通常與-t或者-d同時使用;

-t:為容器重新分配一個偽輸入終端,通常與-i同時使用;

-d: 後台運行容器,並返回容器ID;

-P: 隨機端口映射,容器內部端口隨機映射到主機的端口

-p: 指定端口映射,格式為:主機(宿主)端口:容器端口

 啟動普通容器: docker run –name 別名 鏡像ID  

 啟動交互式容器:  docker run -it –name 別名 鏡像ID   來運行一個容器,取別名,交互模式運行,以及分配一個偽終端

 守護式方式創建並啟動容器

 docker run -di –name 別名 鏡像ID 

執行完命令后,終端依然再宿主機上;

 

啟動容器,並執行/bin/bash命令;

 docker run -it –name 別名 鏡像ID  /bin/bash命令

端口映射;

docker run -it -p 8888:8080 tomcat

docker run -it -P tomcat

  6.2 列出容器

docker ps [OPTIONS]

OPTIONS說明:

-a :显示所有的容器,包括未運行的。

-f :根據條件過濾显示的內容。

–format :指定返回值的模板文件。

-l :显示最近創建的容器。

-n :列出最近創建的n個容器。

–no-trunc :不截斷輸出。

-q :靜默模式,只显示容器編號。

-s :显示總的文件大小。

docker ps 查看正在運行的容器

docker ps -a 查看所有容器

docker ps -n 2  显示最近創建的2個容器

docker ps -f status=exited 查看停止的容器

  6.3 退出容器

exit 容器停止退出

ctrl+P+Q 容器不停止退出

  6.4 進入容器

docker attach 容器ID or 容器名 

  6.5 啟動容器

docker start 容器ID or 容器名

  6.6 重啟容器

docker restart 容器ID or 容器名

  6.7 停止容器

docker stop 容器ID or 容器名

暴力刪除,直接殺掉進程 (不推薦)

docker kill 容器ID or 容器名

  6.8 刪除容器

docker rm 容器ID  

如果刪除正在運行的容器,會報錯,我們假如需要刪除的話,需要強制刪除;

強制刪除docker rm -f 容器ID

刪除多個容器 

docker rm -f 容器ID1  容器ID2 中間空格隔開

刪除所有容器

docker rm -f $(docker ps -qa)

  6.9 宿主機和容器之間文件拷貝

宿主機文件 copy to 容器內

docker cp 需要拷貝的文件或者目錄   容器名稱:容器目錄

容器內 copy to 宿主機

docker cp 容器名稱:容器目錄    宿主機目錄

  6.10 查看容器日誌

$ docker logs [OPTIONS] CONTAINER

  Options:

        –details        显示更多的信息

    -f, –follow         跟蹤實時日誌

        –since string   显示自某個timestamp之後的日誌,或相對時間,如42m(即42分鐘)

        –tail string    從日誌末尾显示多少行日誌, 默認是all

    -t, –timestamps     显示時間戳

        –until string   显示自某個timestamp之前的日誌,或相對時間,如42m(即42分鐘)

(以上了解)

 

鋒哥推薦,簡單粗糙方式,直接去docker容器文件里找;

具體未知:/var/lib/docker/containers/

每個容器對應一堆文件,然後有個log結尾的,就是日誌文件;

我們打開;

很直觀 假如時間長了 日誌文件很大,直接自己操刀處理即可;

  6.11 查看容器進程

docker top 容器ID

 

  6.12 進入容器執行命令

docker exec -it 容器名稱 或者 容器ID 執行命令

直接操作容器,執行完 回到 宿主主機終端;

 我們一般用於 啟動容器里的應用 比如 tomcat nginx redis elasticsearch等等

  6.13 提交運行時容器成為鏡像

docker commit

docker commit -a=’作者’ -m=’備註’ 運行時容器ID 新鏡像名稱

 

  6.14 推送鏡像到hub服務器

我們可以通過docker push命令 把自己本地定製的鏡像推送到Hub服務器,方便全球開發者使用,包括自己;

 

上一講,我們定製了一個鏡像 java1234/tomcat7 tag是1.1

我們把這個鏡像發布到hub服務器;

 

步驟一:

https://hub.docker.com/ 註冊下 得到docker id和密碼

 

步驟二:

我們用docker login登陸hub服務器

 

步驟三:

docker push推送

docker push java1234/tomcat7:1.1

 

推送成功:

登陸 https://hub.docker.com/   點擊 Repositories 菜單

 

已經显示這個鏡像;

點擊:

我們加簡介和描述信息;

點Tags:

我們可以刪除掉;

 

  6.15 推送鏡像到阿里雲

很多時候,中小公司為了方便搭建私有倉庫方便,直接使用穩定的阿里雲鏡像倉庫,方便公司內部業務系統直接拉取鏡像;

步驟一:

進入:https://cr.console.aliyun.com  阿里雲鏡像控制台  需要註冊  用戶名就是你的淘寶或者支付寶 賬號名稱 ,鏡像控制台密碼單獨設置;

步驟二:

進入控制台,我們先創建命名空間,再創建鏡像;

然後我們可以根據阿里雲官方提示說明來進行鏡像遠程登錄,提交,以及拉取操作,簡單易用;

  6.16 查看容器元信息

docker inspect 容器ID

 

 

——————————————————————————————————————————

作者: java1234_小鋒

出處:https://www.cnblogs.com/java688/p/13174646.html

版權:本站使用「CC BY 4.0」創作共享協議,轉載請在文章明顯位置註明作者及出處。

——————————————————————————————————————————

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

.Net Core微服務入門全紀錄(六)——EventBus-事件總線

前言

上一篇【.Net Core微服務入門全紀錄(五)——Ocelot-API網關(下)】中已經完成了Ocelot + Consul的搭建,這一篇簡單說一下EventBus。

EventBus-事件總線

  • 首先,什麼是事件總線呢?

貼一段引用:

事件總線是對觀察者(發布-訂閱)模式的一種實現。它是一種集中式事件處理機制,允許不同的組件之間進行彼此通信而又不需要相互依賴,達到一種解耦的目的。

如果沒有接觸過EventBus,可能不太好理解。其實EventBus在客戶端開發中應用非常廣泛(android,ios,web前端等),用於多個組件(或者界面)之間的相互通信,懂的人都懂。。。

  • 那麼,我們為什麼要用EventBus呢?

就拿當前的項目舉例,我們有一個訂單服務,一個產品服務。客戶端有一個下單功能,當用戶下單時,調用訂單服務的下單接口,那麼下單接口需要調用產品服務的減庫存接口,這涉及到服務與服務之間的調用。那麼服務之間又怎麼調用呢?直接RESTAPI?或者效率更高的gRPC?可能這兩者各有各的使用場景,但是他們都存在一個服務之間的耦合問題,或者難以做到異步調用。

試想一下:假設我們下單時調用訂單服務,訂單服務需要調用產品服務,產品服務又要調用物流服務,物流服務再去調用xx服務 等等。。。如果每個服務處理時間需要2s,不使用異步的話,那這種體驗可想而知。

如果使用EventBus的話,那麼訂單服務只需要向EventBus發一個“下單事件”就可以了。產品服務會訂閱“下單事件”,當產品服務收到下單事件時,自己去減庫存就好了。這樣就避免了兩個服務之間直接調用的耦合性,並且真正做到了異步調用。

既然涉及到多個服務之間的異步調用,那麼就不得不提分佈式事務。分佈式事務並不是微服務獨有的問題,而是所有的分佈式系統都會存在的問題。
關於分佈式事務,可以查一下“CAP原則”和“BASE理論”了解更多。當今的分佈式系統更多的會追求事務的最終一致性。

下面使用國人開發的優秀項目“CAP”,來演示一下EventBus的基本使用。之所以使用“CAP”是因為它既能解決分佈式系統的最終一致性,同時又是一個EventBus,它具備EventBus的所有功能!
作者介紹:https://www.cnblogs.com/savorboard/p/cap.html

CAP使用

  • 環境準備

在Docker中準備一下需要的環境,首先是數據庫,數據庫我使用PostgreSQL,用別的也行。CAP支持:SqlServer,MySql,PostgreSql,MongoDB。
關於在Docker中運行PostgreSQL可以看我的另一篇博客:https://www.cnblogs.com/xhznl/p/13155054.html

然後是MQ,這裏我使用RabbitMQ,Kafka也可以。
Docker運行RabbitMQ:

docker pull rabbitmq:management
docker run -d -p 15672:15672 -p 5672:5672 --name rabbitmq rabbitmq:management

默認用戶:guest,密碼:guest

環境準備就完成了,Docker就是這麼方便。。。

  • 代碼修改:

為了模擬以上業務,需要修改大量代碼,下面代碼如有遺漏的直接去github找。

NuGet安裝:

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Tools
Npgsql.EntityFrameworkCore.PostgreSQL

CAP相關:

DotNetCore.CAP
DotNetCore.CAP.RabbitMQ
DotNetCore.CAP.PostgreSql

Order.API/Controllers/OrdersController.cs增加下單接口:

[Route("[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
    private readonly ILogger<OrdersController> _logger;
    private readonly IConfiguration _configuration;
    private readonly ICapPublisher _capBus;
    private readonly OrderContext _context;

    public OrdersController(ILogger<OrdersController> logger, IConfiguration configuration, ICapPublisher capPublisher, OrderContext context)
    {
        _logger = logger;
        _configuration = configuration;
        _capBus = capPublisher;
        _context = context;
    }

    [HttpGet]
    public IActionResult Get()
    {
        string result = $"【訂單服務】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}——" +
            $"{Request.HttpContext.Connection.LocalIpAddress}:{_configuration["ConsulSetting:ServicePort"]}";
        return Ok(result);
    }

    /// <summary>
    /// 下單 發布下單事件
    /// </summary>
    /// <param name="order"></param>
    /// <returns></returns>
    [Route("Create")]
    [HttpPost]
    public async Task<IActionResult> CreateOrder(Models.Order order)
    {
        using (var trans = _context.Database.BeginTransaction(_capBus, autoCommit: true))
        {
            //業務代碼
            order.CreateTime = DateTime.Now;
            _context.Orders.Add(order);

            var r = await _context.SaveChangesAsync() > 0;

            if (r)
            {
                //發布下單事件
                await _capBus.PublishAsync("order.services.createorder", new CreateOrderMessageDto() { Count = order.Count, ProductID = order.ProductID });
                return Ok();
            }
            return BadRequest();
        }

    }

}

Order.API/MessageDto/CreateOrderMessageDto.cs:

/// <summary>
/// 下單事件消息
/// </summary>
public class CreateOrderMessageDto
{
    /// <summary>
    /// 產品ID
    /// </summary>
    public int ProductID { get; set; }

    /// <summary>
    /// 購買數量
    /// </summary>
    public int Count { get; set; }
}

Order.API/Models/Order.cs訂單實體類:

public class Order
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    /// <summary>
    /// 下單時間
    /// </summary>
    [Required]
    public DateTime CreateTime { get; set; }

    /// <summary>
    /// 產品ID
    /// </summary>
    [Required]
    public int ProductID { get; set; }

    /// <summary>
    /// 購買數量
    /// </summary>
    [Required]
    public int Count { get; set; }
}

Order.API/Models/OrderContext.cs數據庫Context:

public class OrderContext : DbContext
{
    public OrderContext(DbContextOptions<OrderContext> options)
       : base(options)
    {

    }

    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {

    }
}

Order.API/appsettings.json增加數據庫連接字符串:

"ConnectionStrings": {
  "OrderContext": "User ID=postgres;Password=pg123456;Host=host.docker.internal;Port=5432;Database=Order;Pooling=true;"
}

Order.API/Startup.cs修改ConfigureServices方法,添加Cap配置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<OrderContext>(opt => opt.UseNpgsql(Configuration.GetConnectionString("OrderContext")));

    //CAP
    services.AddCap(x =>
    {
        x.UseEntityFramework<OrderContext>();

        x.UseRabbitMQ("host.docker.internal");
    });
}

以上是訂單服務的修改。

Product.API/Controllers/ProductsController.cs增加減庫存接口:

[Route("[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    private readonly ILogger<ProductsController> _logger;
    private readonly IConfiguration _configuration;
    private readonly ICapPublisher _capBus;
    private readonly ProductContext _context;

    public ProductsController(ILogger<ProductsController> logger, IConfiguration configuration, ICapPublisher capPublisher, ProductContext context)
    {
        _logger = logger;
        _configuration = configuration;
        _capBus = capPublisher;
        _context = context;
    }

    [HttpGet]
    public IActionResult Get()
    {
        string result = $"【產品服務】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}——" +
            $"{Request.HttpContext.Connection.LocalIpAddress}:{_configuration["ConsulSetting:ServicePort"]}";
        return Ok(result);
    }

    /// <summary>
    /// 減庫存 訂閱下單事件
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    [NonAction]
    [CapSubscribe("order.services.createorder")]
    public async Task ReduceStock(CreateOrderMessageDto message)
    {
        //業務代碼
        var product = await _context.Products.FirstOrDefaultAsync(p => p.ID == message.ProductID);
        product.Stock -= message.Count;

        await _context.SaveChangesAsync();
    }

}

Product.API/MessageDto/CreateOrderMessageDto.cs:

/// <summary>
/// 下單事件消息
/// </summary>
public class CreateOrderMessageDto
{
    /// <summary>
    /// 產品ID
    /// </summary>
    public int ProductID { get; set; }

    /// <summary>
    /// 購買數量
    /// </summary>
    public int Count { get; set; }
}

Product.API/Models/Product.cs產品實體類:

public class Product
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    /// <summary>
    /// 產品名稱
    /// </summary>
    [Required]
    [Column(TypeName = "VARCHAR(16)")]
    public string Name { get; set; }

    /// <summary>
    /// 庫存
    /// </summary>
    [Required]
    public int Stock { get; set; }
}

Product.API/Models/ProductContext.cs數據庫Context:

public class ProductContext : DbContext
{
    public ProductContext(DbContextOptions<ProductContext> options)
       : base(options)
    {

    }

    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        //初始化種子數據
        modelBuilder.Entity<Product>().HasData(new Product
        {
            ID = 1,
            Name = "產品1",
            Stock = 100
        },
        new Product
        {
            ID = 2,
            Name = "產品2",
            Stock = 100
        });
    }
}

Product.API/appsettings.json增加數據庫連接字符串:

"ConnectionStrings": {
  "ProductContext": "User ID=postgres;Password=pg123456;Host=host.docker.internal;Port=5432;Database=Product;Pooling=true;"
}

Product.API/Startup.cs修改ConfigureServices方法,添加Cap配置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<ProductContext>(opt => opt.UseNpgsql(Configuration.GetConnectionString("ProductContext")));

    //CAP
    services.AddCap(x =>
    {
        x.UseEntityFramework<ProductContext>();

        x.UseRabbitMQ("host.docker.internal");
    });
}

以上是產品服務的修改。

訂單服務和產品服務的修改到此就完成了,看着修改很多,其實功能很簡單。就是各自增加了自己的數據庫表,然後訂單服務增加了下單接口,下單接口會發出“下單事件”。產品服務增加了減庫存接口,減庫存接口會訂閱“下單事件”。然後客戶端調用下單接口下單時,產品服務會減去相應的庫存,功能就這麼簡單。

關於EF數據庫遷移之類的基本使用就不介紹了。使用Docker重新構建鏡像,運行訂單服務,產品服務:

docker build -t orderapi:1.1 -f ./Order.API/Dockerfile .
docker run -d -p 9060:80 --name orderservice orderapi:1.1 --ConsulSetting:ServicePort="9060"
docker run -d -p 9061:80 --name orderservice1 orderapi:1.1 --ConsulSetting:ServicePort="9061"
docker run -d -p 9062:80 --name orderservice2 orderapi:1.1 --ConsulSetting:ServicePort="9062"

docker build -t productapi:1.1 -f ./Product.API/Dockerfile .
docker run -d -p 9050:80 --name productservice productapi:1.1 --ConsulSetting:ServicePort="9050"
docker run -d -p 9051:80 --name productservice1 productapi:1.1 --ConsulSetting:ServicePort="9051"
docker run -d -p 9052:80 --name productservice2 productapi:1.1 --ConsulSetting:ServicePort="9052"

最後 Ocelot.APIGateway/ocelot.json 增加一條路由配置:

好了,進行到這裏,整個環境就有點複雜了。確保我們的PostgreSQL,RabbitMQ,Consul,Gateway,服務實例都正常運行。

服務實例運行成功后,數據庫應該是這樣的:

產品表種子數據:

cap.published表和cap.received表是由CAP自動生成的,它內部是使用本地消息表+MQ來實現異步確保。

運行測試

這次使用Postman作為客戶端調用下單接口(9070是之前的Ocelot網關端口):

訂單庫published表:

訂單庫order表:

產品庫received表:

產品庫product表:

再試一下:

OK,完成。雖然功能很簡單,但是我們實現了服務的解耦,異步調用,和最終一致性。

總結

注意,上面的例子純粹是為了說明EventBus的使用,實際中的下單流程絕對不會這麼做的!希望大家不要較真。。。

可能有人會說如果下單成功,但是庫存不足導致減庫存失敗了怎麼辦,是不是要回滾訂單表的數據?如果產生這種想法,說明還沒有真正理解最終一致性的思想。首先下單前肯定會檢查一下庫存數量,既然允許下單那麼必然是庫存充足的。這裏的事務是指:訂單保存到數據庫,和下單事件保存到cap.published表(保存到cap.published表理論上就能夠發送到MQ)這兩件事情,要麼一同成功,要麼一同失敗。如果這個事務成功,那麼就可以認為這個業務流程是成功的,至於產品服務的減庫存是否成功那就是產品服務的事情了(理論上也應該是成功的,因為消息已經確保發到了MQ,產品服務必然會收到消息),CAP也提供了失敗重試,和失敗回調機制。

如果非要數據回滾也是能實現的,CAP的ICapPublisher.Publish方法提供一個callbackName參數,當減庫存時,可以觸發這個回調。其本質也是通過發布訂閱完成,這是不推薦的做法,就不詳細說了,有興趣自己研究一下。
另外,CAP無法保證消息不重複,實際使用中需要自己考慮一下消息的重複過濾和冪等性。

這一篇內容有點多,不知道有沒有表達清楚,有問題歡迎評論交流,如有不對之處還望大家指出。

下一篇計劃寫一下授權認證相關的內容。

代碼放在:https://github.com/xiajingren/NetCoreMicroserviceDemo

未完待續…

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

面試官:十問泛型,你能扛住嗎?

問題一:為什麼需要泛型?

答:

使用泛型機制編寫的代碼要比那些雜亂的使用Object變量,然後再進行強制類型轉換的代碼具有更好的安全性和可讀性,也就是說使用泛型機制編寫的代碼可以被很多不同類型的對象所重用。

問題二:從ArrayList的角度說一下為什麼要用泛型?

答:

在Java增加泛型機制之前就已經有一個ArrayList類,這個ArrayList類的泛型概念是使用繼承來實現的。

public class ArrayList {
    private Object[] elementData;
    public Object get(int i) {....}
    public void add(Object o) {....}
}

這個類存在兩個問題:

  1. 當獲取一個值的時候必須進行強制類型轉換
  2. 沒有錯誤檢查,可以向數組中添加任何類的對象
ArrayList files = new ArrayList();
files.add(new File(""));
String filename = (String)files.get(0);

對於這個調用,編譯和運行都不會出錯,但是當我們在其他地方使用get方法獲取剛剛存入的這個File對象強轉為String類型的時候就會產生一個錯誤。

泛型對於這種問題的解決方案是提供一個類型參數

ArrayList<String> files = new ArrayList<>();

這樣可以使代碼具有更好的可讀性,我們一看就知道這個數據列表中包含的是String對象。 編譯器也可以很好地利用這個信息,當我們調用get的時候,不需要再使用強制類型轉換,編譯器就知道返回值類型為String,而不是Object

String filename = files.get(0);

編譯器還知道ArrayList<String>add方法中有一個類型為String的參數。這將比使用Object類型的參數安全一些,現在編譯器可以檢查,避免插入錯誤類型的對象:

files.add(new File(""));

這樣的代碼是無法通過編譯的,出現編譯錯誤比類在運行時出現類的強制類型轉換異常要好得多

問題三:說說泛型類吧

一個泛型類就是具有一個或多個類型變量的類,對於這個類來說,我們只關注泛型,而不會為數據存儲的細節煩惱。

public class Couple<T{
   private T one;
   private T two;
}

Singer類引入了一個類型變量T,用尖括號括起來,並放在類名的後面。泛型類可以有多個類型變量:

public class Couple<TU{...}

類定義中的類型變量是指定方法的返回類型以及域和局部變量的類型

//域
private T one;
//返回類型
public T getOne() return one; }
//局部變量
public void setOne(T newValue) { one = newValue; }

使用具體的類型代替類型變量就可以實例化泛型類型:

Couple<Rapper>

泛型類可以看成是普通類的工廠,打個比方:我用泛型造了一個模型,具體填充什麼樣的材質,由使用者去做決定。

問題四: 說說泛型方法的定義和使用

答:

泛型方法可以定義在普通類中,也可以定義在泛型類中,類型變量是放在修飾符的後面返回類型的前面

我們來看一個泛型方法的實例:

class ArrayUtil {

    public static <T> getMiddle(T...a){
        return a[a.length / 2];
    }
}

當調用一個泛型方法時,在方法名前的尖括號中放入具體的類型:

String middle = ArrayUtil.<String>getMiddle("a","b","c");

在這種情況下,方法調用中可以省略<String>類型參數,編譯器會使用類型推斷來推斷出所調用的方法,也就是說可以這麼寫:

String middle = ArrayAlg.getMiddle("a","b","c");

問題五:E V T K ? 這些是什麼

答:

  • E——Element 表示元素 特性是一種枚舉
  • T——Type 類,是指Java類型
  • K—— Key 鍵
  • V——Value 值
  • ——在使用中表示不確定類型

問題六:了解過類型變量的限定嗎?

答:

一個類型變量或通配符可以有多個限定,例如:

<T extends Serializable & Cloneable>

單個類型變量的多個限定類型使用&分隔,而,用來分隔多個類型變量。

<T extends Serializable,Cloneable>

在類型變量的繼承中,可以根據需要擁有多個接口超類型,但是限定中至多有一個類。如果用一個類作為限定,它必定是限定列表中的第一個

類型變量的限定是為了限制泛型的行為,指定了只有實現了特定接口的類才可以作為類型變量去實例化一個類。

問題七:泛型與繼承你知道多少?

答:

首先,我們來看一個類和它的子類,比如 SingerRapper。但是Couple<Rapper>卻並不是Couple<Singer>的一個子類。

無論S和T有什麼聯繫,Couple<S>Couple<T>沒有什麼聯繫。

這裏需要注意泛型和Java數組之間的區別,可以將一個Rapper[]數組賦給一個類型為Singer[]的變量:

Rapper[] rappers = ...;
Singer[] singer = rappers;

然而,數組帶有特別的保護,如果試圖將一個超類存儲到一個子類數組中,虛擬機會拋出ArrayStoreException異常。

問題八:聊聊通配符吧

答:

通配符類型中,允許類型參數變化。比如,通配符類型:

Couple<? extends Singer>

表示任何泛型類型,它的類型參數是Singer的子類,如Couple<Rapper>,但不會是Couple<Dancer>

假如現在我們需要編寫一個方法去打印一些東西:

public static void printCps(Couple<Rapper> cps) {
      Rapper one = cp.getOne();
      Rapper two = cp.getTwo();
      System.out.println(one.getName() + " & " + two.getName() + " are cps.");
}

正如前面所講到的,不能將Couple<Rapper>傳遞給這個方法,這一點很受限制。解決的方案很簡單,使用通配符類型:

public static void printCps(Couple< ? extends Singer> cps) 

Couple<Rapper>Couple< ? extends Singer>的子類型。

我們接下來來考慮另外一個問題,使用通配符會通過Couple< ? extends Singer>的引用破壞Couple<Rapper>嗎?

Couple<Rapper> rapper = new Couple<>(rapper1, rapper2);
Couple<? extends Singer> singer = rapper;
player.setOne(reader);

這樣可能會引起破壞,但是當我們調用setOne的時候,如果調用的不是Singer的子類Rapper類的對象,而是其他Singer子類的對象,就會出錯。 我們來看一下Couple<? extends Singer>的方法:

extends Singer getOne();
void setOne(? extends Singer);

這樣就會看的很明顯,因為如果我們去調用setOne()方法,編譯器之可以知道是某個Singer的子類型,而不能確定具體是什麼類型,它拒絕傳遞任何特定的類型,因為 ? 不能用來匹配。 但是使用getOne就不存在這個問題,因為我們無需care它獲取到的類型是什麼,但一定是Singer的子類。

通配符限定與類型變量限定非常相似,但是通配符類型還有一個附加的能力,即可以指定一個超類型限定:

super Rapper

這個通配符限製為Rapper的所有父類,為什麼要這麼做呢?帶有超類型限定的通配符的行為與子類型限定的通配符行為完全相反,可以為方法提供參數,但是卻不能獲取具體的值,即訪問器是不安全的,而更改器方法是安全的

編譯器無法知道setOne方法的具體類型,因此調用這個方法時不能接收類型為SingerObject的參數。只能傳遞Rapper類型的對象,或者某個子類型(Reader)對象。而且,如果調用getOne,不能保證返回對象的類型。

總結一下:

帶有超類型限定的通配符可以向泛型對象寫入,帶有子類型限定的通配符可以從泛型對象讀取。

問題九:泛型在虛擬機中是什麼樣呢?

答:

  1. 虛擬機沒有泛型類型對象,所有的對象都屬於普通類。 無論何時定義一個泛型類型,都自動提供了一個相應的原始類型。原始類型的名字就是刪去類型參數后的泛型類型名。擦除類型變量,並替換成限定類型(沒有限定的變量用Object)。這樣做的目的是為了讓非泛型的Java程序在後續支持泛型的 jvm 上還可以運行(向後兼容)

  2. 當程序調用泛型方法時,如果擦除返回類型,編譯器插入強制類型轉換。

Couple<Singer> cps = ...;
Singer one = cp.getOne();

擦除cp.getOne的返回類型后將返回Object類型。編譯器自動插入Singer的強制類型轉換。也就是說,編譯器把這個方法調用編譯為兩條虛擬機指令:

對原始方法cp.getOne的調用 將返回的Object類型強制轉換為Singer類型。

  1. 當存取一個公有泛型域時也要插入強制類型轉換。
//我們寫的代碼
Singer one = cps.one;
//編譯器做的事情
Singer one = (Singer)cps.one;

問題十:關於泛型擦除,你知道多少?

答:

類型擦除會出現在泛型方法中,程序員通常認為下述的泛型方法

public static <T extends Comparable> min(T[] a)

是一個完整的方法族,而擦除類型之後,只剩下一個方法:

public static Comparable min(Comparable[] a)

這個時候類型參數T已經被擦除了,只留下了限定類型Comparable

但是方法的擦除會帶來一些問題:

class Coupling extends Couple<People{
    public void setTwo(People people) {
            super.setTwo(people);
    }
}

擦除后:

class Coupling extends Couple {
    public void setTwo(People People) {...}
}

這時,問題出現了,存在另一個從Couple類繼承的setTwo方法,即:

public void setTwo(Object two)

這顯然是一個不同的方法,因為它有一個不同類型的參數(Object),而不是People

Coupling coupling = new Coupling(...);
Couple<People> cp = interval;
cp.setTwo(people);

這裏,希望對setTwo的調用具有多態性,並調用最合適的那個方法。由於cp引用Coupling對象,所以應該調用Coupling.setTwo。問題在於類型擦除與多態發生了衝突。要解決這個問題,就需要編譯器在Coupling類中生成一個橋方法:

public void setTwo(Object second) {
    setTwo((People)second);
}

變量cp已經聲明為類型Couple<LocalDate>,並且這個類型只有一個簡單的方法叫setTwo,即setTwo(Object)。虛擬機用cp引用的對象調用這個方法。這個對象是Coupling類型的,所以會調用Coupling.setTwo(Object)方法。這個方法是合成的橋方法。它會調用Coupling.setTwo(Date),這也正是我們所期望的結果。

所以,我們要記住關於Java泛型轉換的幾個點:

  1. 虛擬機中沒有泛型,只有普通的類和方法
  2. 所有的類型參數都用它們的限定類型替換
  3. 橋方法被合成來保持多態
  4. 為保持類型安全性,必要時插入強制類型轉換

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

再看rabbitmq的交換器和隊列的關係

最近又要用到rabbitmq,業務上要求服務器只發一次消息,需要多個客戶端都去單獨消費。但我們知道rabbitmq的機制里,每個隊列里的消息只能消費一次,所以客戶端要單獨消費信息,就必須得每個客戶端單獨監聽一個queue。所以我最終想實現的是服務端只聲明exchange,客戶端來創建queue和綁定exchange。但是在看各種rabbitmq博文和討論的時候,我覺得對exchange的模式和queue間的關係講的都不是很清楚。所以我決定自己驗證一下

fanout模式和direct模式

本文主要驗證fanout模式和direct模式下以上猜想是否可行。fanout模式就是大名鼎鼎的廣播模式了,只要queue綁定了fanout的交換器,就可以直接的收到消息,無需routingkey的參与。而direct模式就是通過routing key直接發送到綁定了同樣routing key的隊列中。那麼,在這兩種exchange的模式下,是否都可以實現服務端僅創建exchange,客戶端創建queue並綁定exchange呢?

Direct模式驗證

我們先把交換器、routingkey、隊列的名稱定義好:

  1. 交換器為directTest
  2. routingkey為direct_routing_key
  3. 隊列測試3個,首先測試Direct_test_queue_1,再行測試Direct_test_queue_2,再行測試Direct_test_queue_3

代碼使用spring boot框架快速搭建。我們先規劃好需要幾個類來完成這個事情:

  1. 針對生產者,需要RabbitmqConfig,用來配置exchange的
  2. 針對生產者,需要DirectRabbitSender,用來實現Direct模式的消息發送
  3. 針對消費者,需要DirectConsumerOne,來測試第一個隊列Direct_test_queue_1生成和消息接收
  4. 針對消費者,需要DirectConsumerTwo,來測試第二個隊列Direct_test_queue_2生成和消息接收
  5. 針對消費者,需要DirectConsumerThree,來測試第三個隊列Direct_test_queue_3生成和消息接收
  6. 我們還需要一個測試類RabbitmqApplicationTests,用於測試消息的發送和接收

rabbitmq先配置一個DirectExchange

@Bean
DirectExchange directExchange(){
    return new DirectExchange("directTest", true, false);
}

我們可以看到Direct交換器的名稱定義為了directTest,這時候還未綁定任何的隊列。啟動程序,若我們的設想沒錯,則rabbitmq中應該已經生成了directTest的exchange。

Bingo!directTest交換器成功創建。接下來,我們去編寫DirectRabbitSender的代碼

@Component
public class DirectRabbitSender{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private final String EXCHANGE_NAME = "directTest";
    private final String ROUTING_KEY = "direct_routing_key";

    public void send(Object message) {
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, ROUTING_KEY, message);
    }

}

我們可以看到代碼中,通過rabbitTemplate發送消息到了交換器為directTest,routingkey為direct_routing_key的地方。但這時候我們沒有任何隊列了,自然接不到消息。現在我們去編寫第一個消費者DirectConsumerOne來接受消息。

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "Direct_test_queue_1", durable = "true"),
        exchange = @Exchange(value = "directTest"),
        key = "direct_routing_key"
))
public class DirectConsumerOne {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列Direct_test_queue_1接到消息" + message);
    }

}

通過代碼可以看到,我們通過@QueueBinding把Direct_test_queue_1隊列綁定到了directTest和direct_routing_key上。Direct_test_queue_1並沒有在rabbitmq創建,這並沒有關係。一般來說,@RabbitListener會自動去創建隊列。啟動程序,我們去看一下rabbitmq里隊列是不是創建了。

Bingo!再次驗證成功。我們去看看綁定關係是不是正確。這時候Direct_test_queue_1應該綁定到了名為directTest的交換器,而綁定的routingkey為direct_routing_key

biubiubiu!綁定關係完全正確。到了這裏,我們進行最後一步,寫了單元測試去發送消息,查看控制台中消費者是否成功收到消息。RabbitmqApplicationTests的代碼如下:

@SpringBootTest
class RabbitmqApplicationTests {

    @Autowired
    private DirectRabbitSender directRabbitSender;

    @Test
    void contextLoads() {
    }

    @Test
    public void directSendTest(){
        directRabbitSender.send("direct-sender");
        directRabbitSender.send("direct-sender_test");
    }

}

啟動測試類,然後去查看控制台。

沒錯,這就是我們想要達到的效果!基本可以宣布Direct模式驗證成功。服務端生成exchange,客戶端去生成隊列綁定的方式在direct模式下完全可行。為了保險起見,再驗證一下生成多個消費者綁定到同一個隊列是否可行。

DirectConsumerTwo代碼如下:

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "Direct_test_queue_2", durable = "true"),
        exchange = @Exchange(value = "directTest"),
        key = "direct_routing_key"
))
public class DirectConsumerTwo {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列Direct_test_queue_2接到消息" + message);
    }

}

DirectConsumerThree代碼如下:

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "Direct_test_queue_3", durable = "true"),
        exchange = @Exchange(value = "directTest"),
        key = "direct_routing_key"
))
public class DirectConsumerThree {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列Direct_test_queue_3接到消息" + message);
    }

}

啟動測試類,我們去看兩個地方:

  1. rabbitmq是否創建了客戶端綁定的三個隊列Direct_test_queue_1、Direct_test_queue_2、Direct_test_queue_3
  2. 消費者應該各自收到2條消息(Test中發送了兩條,參看上面 RabbitmqApplicationTests 的代碼)。那3個隊列,控制台中應該打印了6條消息。

hohohoho!創建成功,並且綁定關係我看了也全都正確。我們去看控制台

6條!沒有任何毛病,至此,可以宣布Direct模式下,完全支持我們最初的想法:服務端生成exchange,客戶端去生成隊列綁定的方式在direct模式下完全可行。

fanout模式驗證

接下來我們驗證一下fanout的方式,基本操作流程和Direct模式一致。代碼的結構也差不多:

  1. 針對生產者,需要RabbitmqConfig,直接在Direct模式下的rabbitmqConfig里直接添加Fanout的交換器配置
  2. 針對生產者,需要FanoutRabbitSender,用來實現Fanout模式的消息發送
  3. 針對消費者,需要FanoutConsumerOne,來測試第一個隊列Fanout_test_queue_1生成和消息接收
  4. 針對消費者,需要FanoutConsumerTwo,來測試第二個隊列Fanout_test_queue_2生成和消息接收
  5. 針對消費者,需要FanoutConsumerThree,來測試第三個隊列Fanout_test_queue_3生成和消息接收
  6. 測試類RabbitmqApplicationTests也直接復用Direact模式下測試的類

我就不多BB,直接上代碼了。

RabbitmqConfig代碼如下

@Configuration
public class RabbitmqConfig {

    @Bean
    DirectExchange directExchange(){
        return new DirectExchange("directTest", true, false);
    }

    @Bean
    FanoutExchange fanoutExchange(){
        return new FanoutExchange("fanoutTest", true, false);
    }

}

FanoutRabbitSender的代碼如下,此處和direct模式的區別是Fanout中沒有routingkey,所以代碼里也沒定義routingkey:

@Component
public class FanoutRabbitSender{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private final String EXCHANGE_NAME = "fanoutTest";

    public void send(Object message) {
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, null, message);
    }

}

我們到這裏先啟動程序試試,看看fanoutTest的交換器在沒有綁定隊列的情況下是否生成了。

棒棒棒!和我們想的一樣,那接下來去寫完所有的消費者,這裏和Direct模式最重要的區別是@Exchange中必須要指定type為fanout。direct模式的代碼里沒指定是因為@Exchange的type默認值就是direct。我直接上代碼了:

/**
 * 監聽器主動去聲明queue=fanout_test_queue_1,並綁定到fanoutTest交換器
 */
@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "fanout_test_queue_1", durable = "true"),
        exchange = @Exchange(value = "fanoutTest", type = ExchangeTypes.FANOUT)
))
public class FanoutConsumerOne {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列fanout_test_queue_1接到消息" + message);
    }

}

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "fanout_test_queue_2", durable = "true"),
        exchange = @Exchange(value = "fanoutTest", type = ExchangeTypes.FANOUT)
))
public class FanoutConsumerTwo {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列fanout_test_queue_2接到消息" + message);
    }

}

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "fanout_test_queue_3", durable = "true"),
        exchange = @Exchange(value = "fanoutTest", type = ExchangeTypes.FANOUT)
))
public class FanoutConsumerThree {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列fanout_test_queue_3接到消息" + message);
    }

}

接着去測試類RabbitmqApplicationTests中加上fanout的發送測試,然後註釋掉direct的單元測試,以便一會造成干擾

@SpringBootTest
class RabbitmqApplicationTests {

    @Autowired
    private DirectRabbitSender directRabbitSender;

    @Autowired
    private FanoutRabbitSender fanoutRabbitSender;

    @Test
    void contextLoads() {
    }

//    @Test
//    public void directSendTest(){
//        directRabbitSender.send("direct-sender");
//        directRabbitSender.send("direct-sender_test");
//    }

    @Test
    public void fanoutSendTest(){
        fanoutRabbitSender.send("fanout-sender_1");
        fanoutRabbitSender.send("fanout-sender_2");
    }

}

代碼都完成了,現在我們啟動測試類,看看控制台是否正常收到了消息

看圖看圖,fanout模式下也完全認證成功!!!那我們可以宣布,文章開頭的猜想完全可以實現。

總結

服務端只聲明exchange,客戶端來創建queue和綁定exchange的方式完全可行。並且在Direct和Fanout模式下都可行。

那我們可以推測在Header模式的交換器和Topic模式的交換器下應該也大差不差。具體各位可自行驗證,基本流程和上面direct和fanout的流程差不多。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

MongoDB設計方法及技巧

MongoDB是一種流行的數據庫,可以在不受任何錶格schema模式的約束下工作。數據以類似JSON的格式存儲,並且可以包含不同類型的數據結構。例如,在同一集合collection 中,我們可以擁有以下兩個文檔document:

{
    id: '4',
    name: 'Mark',
    age: '21',
    addresses : [
        { street: '123 Church St', city: 'Miami', cc: 'USA' },
        { street: '123 Mary Av', city: 'Los Angeles', cc: 'USA' }
    ]
}

{
    id: '15',
    name: 'Robin',
    department: 'New Business',
    example: 'robin@example.com'
}

為了能夠充分利用MongoDB的優勢,您必須了解並遵循一些基本的數據庫設計原則。在講解設計方法之前,我們必須首先了解MongoDB存儲數據的結構。

一、 數據如何存儲在MongoDB中

與傳統的RDBMS關係型數據庫不同,MongoDB並沒有表Table,行row和列column的概念。它將數據存儲在集合collections,文檔documents和字段fields中。下圖說明了與RDBMS類比的結構之間的關係:

二、數據庫設計技巧和竅門

2.1.規範化存儲與非規範化存儲

因為MongoDB使用文檔來存儲數據,所以理解“規範化存儲“”和“非規範化存儲”的概念非常重要。

規範化存儲:-規範化意味着將數據存儲到多個集合collections中,並在它們之間設計關聯關係。數據保存之後,更新數據比較容易。但是在讀取數據的時候,規範化存儲的缺點就顯現出來。如果要從多個集合collections查找數據,則必須執行多個查詢,從而使讀取數據的速度變慢。 (比如:將網頁標題、作者、內容分別存儲到不同的collections中)

非規範化存儲:-這種方式將若干對象數據,以嵌套的方式存儲到單個文檔中。它在讀取數據的時候表現更好,但在寫入時會變慢。這種存儲數據的方式還將佔用更多空間。 (比如:將網頁標題、作者、內容分別存儲到同一個collection中)

所以在兩種存儲數據方式之間進行選擇之前,先評估一下你的應用數據庫的使用方式。

  • 如果您有一個不需要頻繁更新的數據,更新的即時一致性不是很重要,但是在讀取時需要良好的性能,那麼非規範化可能是明智的選擇。(比如:我們博客的博文,作者一旦保存之後,幾乎就不在進行頻繁的修改,但是面臨着讀者頻繁的讀取閱讀操作)

  • 如果數據庫中的文檔數據需要不斷的更新,並且您希望在寫入時具有良好的性能,那麼您可能需要考慮規範化存儲。(比如:需要頻繁修改數據的業務類系統)

2.2. 一對多關係

與RDBMS相比,在MongoDB中對“一對多”關係建模需要進行更細粒度的設計。許多初學者陷入將文檔數組嵌入父文檔中的陷阱。正如我們在上文中介紹的,知道何時進行規範化存儲或非規範化存儲是非常重要的。因此設計者需要考慮關係的基數是“一個對少數幾個”還是“一個對多個”?每種關係將具有不同的建模方法。

例如:下面“一個對少數幾個”的建模示例。最好的建模方法是在父文檔(persopn)中嵌入幾個(address):

> db.person.findOne()
{
  name: 'Mark Kornfield',
  ssn: '1223-234-75554',
  addresses : [
     { street: '123 Church St', city: 'Miami', cc: 'USA' },
     { street: '123 Mary Av', city: 'Los Angeles', cc: 'USA' }
  ]
}

在“一個對多個”示例中,我們將考慮設計兩個集合,即產品products集合和零件parts集合。每個零件都有一個“ ObjectID”,該“ ObjectID”將出現在產品集合的引用中。這樣的設計可以讓讀寫性能更高效。

> db.parts.findOne()
{
    _id : ObjectID('AAAA'),
    partno : '1224-dsdf-2215',
    name : 'bearing',
    price: 2.63

> db.products.findOne()
{
    name : 'car',
    manufacturer : 'Ford',
    catalog_number: 2234,
    parts : [     // array of references to Part documents
        ObjectID('AAAA'),    // reference to the bearing above
        ObjectID('F17C'),    // reference to a different Part
        ObjectID('D2AA'),
        // etc
]

2.3.設計模式可視化

儘管MongoDB是schemaless“無模式的”,但仍然存在將集合collections可視化為圖表的方法。能夠查看設計圖,將對您理解和設計MongoDB的方式上產生重大影響。

DbSchema是可以很好地完成可視化設計工作的一個工具。如下圖所示,它將通過讀取集合和文檔來推導架構。此外,您只需單擊就可以修改圖中的對象。在DbSchema中,您還可以為MongoDB創建外鍵,當然僅在本地創建,只用於設計目的。

2.4.智能索引

為了保持數據庫的良好性能,有必要建立智能索引,這將簡化寫入和讀取操作。知道MongoDB的索引優勢和局限性非常重要,MongoDB保留用於排序操作的內存限製為32MB。如果你不使用索引,則排序時數據庫將被迫將所有排序文檔hold在內存裏面,如果達到32M的限制,則數據庫將返回錯誤或空集。

結論

對MongoDB的透徹理解與對數據庫想要實現的目標的清晰了解是良好數據庫設計的秘訣。

歡迎關注我的博客,裏面有很多精品合集

  • 本文轉載註明出處(必須帶連接,不能只轉文字):字母哥博客。

覺得對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創作動力! 。另外,筆者最近一段時間輸出了如下的精品內容,期待您的關注。

  • 《手摸手教你學Spring Boot2.0》
  • 《Spring Security-JWT-OAuth2一本通》
  • 《實戰前後端分離RBAC權限管理系統》
  • 《實戰SpringCloud微服務從青銅到王者》
  • 《VUE深入淺出系列》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

我們是如何做go語言系統測試覆蓋率收集的?

工程效能領域,測試覆蓋率度量總是繞不開的話題,我們也不例外。在七牛雲,我們主要使用go語言構建雲服務,在考慮系統測試覆蓋率時,最早也是通過圍繞原生go test -c -cover的能力來構建。這個方案,筆者還曾在 MTSC2018大會上有過專項分享。其實我們當時已經做了不少自動化工作,能夠針對很多類型的代碼庫,自動插樁服務,自動生成TestMain()等方法,但隨着接入項目越來越多,以及後面使用場景的不斷複雜化,我們發現這套還是有其先天局限,會讓後面越來越難受:

  • 程序必須關閉才能收集覆蓋率。如果將這套系統僅定位在收集覆蓋率數據上,這個痛點倒也能忍受。但是如果想進一步做精準測試等方向,就很受局限。
  • 因為不想污染被測代碼庫,我們採取了自動化的方式,在編譯階段給每個服務生成類似main_test.go文件。但這種方式,其最難受的地方在於flag的處理,要知道go test命令本身會調用flag.Parse方法,所以這裏需要自動化的修改源碼,保證被測程序的flag定義,要先於go test調用flag.Parse之前。但是,隨着程序自己使用flag姿勢的複雜化,我們發現越來越難有通用方案來處理這些flag,有點難受。
  • 受限於go test -c命令的先天缺陷,它會給被測程序注入一些測試專屬的flag,比如-test.coverprofile, -test.timeout等等。這個是最難受的,因為它會破壞被測程序的啟動姿勢。我們知道系統測試面對是完整被測集群,如果你需要專門維護一套測試集群來做覆蓋率收集時,就會顯得非常浪費。好鋼就應該用在刀刃上,在七牛雲,我們倡導極客文化,追求用工程師思維解決重複問題。而作為業務效率部門,我們自己更應該走在前列。

也是因為以上的種種考量,我們內部一直在優化這一套系統,到今天這一版,我們已從架構和實現原理上完成了顛覆,能夠做到無損插樁,運行時分析覆蓋率,當屬非常優雅。

Goc – A Comprehensive Coverage Testing System for The Go Programming Language

一圖勝千言:

使用goc run .的姿勢直接運行被測程序,就能在運行時,通過goc profile命令方便的得到覆蓋率結果。是不是很神奇?是不是很優雅?

這個系統就是goc, 設計上希望完全兼容go命令行工具核心命令(go buld/install/run)。使用體驗上,也希望向go命令行工具靠攏:

以下是goc 1.0版本支持的功能:

系統測試覆蓋率收集方案

有了goc,我們再來看如何收集go語言系統測試覆蓋率。整體比較簡單,大體只需要三步:

  • 首先通過goc server命令部署一個服務註冊中心,它將會作為樞紐服務跟所有的被測服務通信。

  • 使用goc build --center="<server>" 命令編譯被測程序。goc不會破壞被測程序的啟動方式,所以你可以直接將編譯出的二進制發布到集成測試環境。

  • 環境部署好之後,就可以做執行任意的系統測試。而在測試期間,可以在任何時間,通過goc profile --center="<server>"拿到當前被測集群的覆蓋率結果。
    是不是很優雅?

goc 核心原理及未來

goc在設計上,拋棄老的go test -c -cover模式,而是直接與go tool cover工具交互,避免因go test命令引入的一系列弊端。goc同樣沒有選擇自己做插樁,也是考慮go語言的兼容性,以及性能問題,畢竟go tool cover工具,原生採用結構體來定義counter收集器,每個文件都有單獨的結構體,性能相對比較可靠。goc旨在做go語言領域綜合性的覆蓋率工具以及精準測試系統,其還有很長的路要走:

  • 基於PR的單測/集測/系統覆蓋率增量分析
  • 精準測試方向,有一定的產品化設計體驗,方便研發與測試日常使用
  • 擁抱各種CICD系統

當前goc已經開源了,歡迎感興趣的同學,前往代碼倉庫查看詳情並Star支持。當然,我們更歡迎有志之士,能夠參与貢獻,和我們一起構建這個有意思的系統。

最後,父親節快樂!

Contact me ?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化