邊工作邊渡假 日本後疫情時代 政府鼓勵人民到國家公園「Workation」

文:宋瑞文

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

因疫公園無人修剪 蜜蜂獲得喘息機會

摘錄自2020年5月26日公視報導

因為疫情的關係,公園綠地的花草都缺乏人力修剪,結果植物長得更好更茂盛,讓蜜蜂有了更多的食物來源,今年的蜂蜜收成也可望提升。

倫敦公園裡的花草,因缺乏人力修剪,反而長得更為茂盛茁壯,正好為蜜蜂提供採集食物的絕佳機會。事實上,隨著都市開發,野生花草大量消失,全球目前有35%的授粉昆蟲正瀕臨滅絕,都市裡寸土寸金的公園綠地,就成了牠們主要的食物補給站。

研究指出,每四週修剪一次,將可使花朵和花蜜的生長保持在最佳狀態。 這次疫情因為居家令的關係,導致公園綠地疏於修剪花草叢生,竟意外讓蜜蜂獲得喘息機會,人類也等於間接受益。

生活環境
國際新聞
英國
疫情
武漢肺炎
蜜蜂
動物與大環境變遷

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

澳洲野火煙霧恐導致400多人死亡 4000多人住院

摘錄自2020年5月26日中央社報導

澳洲環境衛生專家今(26日)告訴政府公共調查機構,澳洲最近野火季致命野火產生的煙霧,與近幾個月445人的死亡及逾4000人的住院治療有關。

澳洲皇家委員會(Royal Commission)是政府特設的公共調查機構,負責尋找改善因應自然災害的方法,必須在下次野火季來臨前,於8月31日報告結果。

澳洲塔斯馬尼亞大學(Universityof Tasmania)孟席斯醫學研究所(Menzies Institute for Medical Research)副教授強斯敦(Fay Johnston)指出,研究所的模型發現,有445人的死亡、3340人的住院及1373人的急診就醫可歸咎於野火。

強斯敦表示,80%的澳洲人(大約2000萬人)受到大火產生的煙霧影響,逾1000萬公頃的土地遭到焚燒。

生態保育
生物多樣性
國際新聞
澳洲
森林野火
森林

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

左打病毒右殺蝗蟲 印度和巴基斯坦面臨雙重危機

摘錄自2020年5月26日聯合報報導

正當全球各國疲於對抗新冠病毒之際,印度和巴基斯坦還多了一個頭痛問題:蝗災。成群蝗蟲侵襲印度20多個行政區,災區超過5萬公項,以拉賈斯坦、中央和古吉拉特三個邦最嚴重。

在巴基斯坦,這波蝗災是近20多年來最嚴重,巴國於2月宣布全國緊急狀態,俾路支省、信德省和旁遮普省有38%面積淪為蝗蟲繁殖的溫床。

向來敵對的印度和巴基斯坦,為了解決蝗害破天荒合作,自4月以來已開過九次視訊會議,亦有阿富汗和伊朗的專家加入。

生活環境
永續發展
土地利用
國際新聞
印度
巴基斯坦
蝗蟲
糧食

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

老鼠斷糧生死鬥:美國疫情封鎖引爆的「鼠患警報」

摘錄自2020年5月26日聯合報報導

美國疾病管制中心(CDC)上星期對全美發出了「鼠患警報」,指在連續多月的防疫封鎖下,全國各地——特別是都會區的城中地段——紛紛出現了鼠患通報數量異常增加的情況。因為都會鼠群倚賴的餐飲業垃圾與商業區廚餘系統,已因防疫歇業而「斷糧多時」,因此除了鼠群大舉遷移、大膽深入民宅區外,部分鼠群更因生存壓力而出現互食、噬崽、以及鼠群格鬥的「暴力傾向」。

長期以來,以紐約為首的大型城市,都一直苦於城市鼠患的問題。但在2020年初,武漢肺炎疫情的封鎖之下,全國各地的衛生單位、防治機關,都紛紛接到了「大量新增」的居民鼠患通報。相關狀態,自3月中旬陸續爆發,但到了5月下旬,美國疾管中心才終於發出了「鼠患警報」,提醒美國民眾應該注意居家衛生與糧食囤積的安全,以避免養鼠為患、或遭遇那批從都心防疫區裡竄出求生的「侵略性鼠群」。

CDC表示,在近期的鼠患通報中,各級單位不僅發現了「鼠群侵門踏戶」不畏人類的機率大增;治鼠專家們也紛紛觀察到,都會區鼠群出現了「鼠吃鼠」、「鼠群相殺」與「嗜食幼鼠」的極端狀況。種種跡象皆顯示,被人類防疫行為嚴重影響的都會區老鼠,正因生存壓力而在劇烈地轉變集體行為。

生活環境
國際新聞
美國
老鼠
動物與大環境變遷

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

網絡編程-Netty-Reactor模型

目錄

  • # 摘要
  • 高性能服務器
  • Reactor模式
    • Reactor單線程模型設計
    • Reactor多線程模型設計
    • 主從Reactor多線程模型設計
    • Netty Reactor模型設計
  • 參考
  • 你的鼓勵也是我創作的動力
  • Posted by 微博@Yangsc_o
  • 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

# 摘要

在前兩篇《快速理解Linux網絡I_O》、《java的I_O模型-BIO&NIO&AIO》兩邊中介紹了Linux下的I/O模型和java中的I/O模型,今天我們介紹Reactor模型,並探究Netty的實現

高性能服務器

在互聯網時代,我們使用的軟件基本上全是C/S架構,C/S架構的軟件一個明顯的好處就是:只要有網絡,你可以在任何地方干同一件事。C/S架構可以抽象為如下模型:

  • C就是Client(客戶端),上面的B是Browser(瀏覽器)
  • S就是Server(服務器):服務器管理某種資源,並且通過操作這種資源來為它的客戶端提供某種服務

那服務器如何能快速的處理用戶的請求呢?在我看來高性能服務器至少要滿足如下幾個需求:

  • 效率高:既然是高性能,那處理客戶端請求的效率當然要很高了
  • 高可用:不能隨便就掛掉了
  • 編程簡單:基於此服務器進行業務開發需要足夠簡單
  • 可擴展:可方便的擴展功能
  • 可伸縮:可簡單的通過部署的方式進行容量的伸縮,也就是服務需要無狀態

而滿足如上需求的一個基礎就是高性能的IO!

Reactor模式

什麼是Reactor模式?

兩種I/O多路復用模式:Reactor和Proactor,兩個與事件分離器有關的模式是Reactor和Proactor。Reactor模式採用同步IO,而Proactor採用異步IO。

在Reactor中,事件分離器負責等待文件描述符或socket為讀寫操作準備就緒,然後將就緒事件傳遞給對應的處理器,最後由處理器負責完成實際的讀寫工作。

在Proactor模式中,處理器–或者兼任處理器的事件分離器,只負責發起異步讀寫操作。IO操作本身由操作系統來完成。傳遞給操作系統的參數需要包括用戶定義的數據緩衝區地址和數據大小,操作系統才能從中得到寫出操作所需數據,或寫入從socket讀到的數據。事件分離器捕獲IO操作完成事件,然後將事件傳遞給對應處理器。

說人話的方式理解:

  • reactor:能收了你跟俺說一聲。
  • proactor: 你給我收十個字節,收好了跟俺說一聲。

Doug Lea是這樣類比的

  • Reactor通過調度適當的處理程序來響應IO事件;
  • 處理程序執行非阻塞操作
  • 通過將處理程序綁定到事件來管理;

Reactor單線程模型設計

單線程版本Java NIO的支持:

  • Channels:與支持非阻塞讀取的文件,套接字等的連接

  • Buffers:類似於數組的對象,可由Channels直接讀取或寫入

  • Selectors:通知一組通道中哪一個有IO事件

  • SelectionKeys:維護IO事件狀態和綁定

  • Reactor 代碼如下

public class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocketChannel;

    public Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        key.attach(new Acceptor());
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
                    dispatch(selectionKey);
                }
                selectionKeys.clear();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    new Handler(selector, channel);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Thread(
                new Reactor(1234)
        ).start();
    }

}
  • Handler代碼如下:
public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }


    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                System.out.println("接收到來自客戶端(" + socketChannel.socket().getInetAddress().getHostAddress()
                        + ")的消息:" + new String(inputBuffer.array()));
                seletionKey.attach(new Sender());
                seletionKey.interestOps(SelectionKey.OP_WRITE);
                seletionKey.selector().wakeup();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

}

這個模型和上面的NIO流程很類似,只是將消息相關處理獨立到了Handler中去了!雖然說到NIO一個線程就可以支持所有的IO處理。但是瓶頸也是顯而易見的!如果這個客戶端多次進行請求,如果在Handler中的處理速度較慢,那麼後續的客戶端請求都會被積壓,導致響應變慢!所以引入了Reactor多線程模型!

Reactor多線程模型設計

Reactor多線程模型就是將Handler中的IO操作和非IO操作分開,操作IO的線程稱為IO線程,非IO操作的線程稱為工作線程!這樣的話,客戶端的請求會直接被丟到線程池中,客戶端發送請求就不會堵塞!

Reactor保持不變,僅需要改動Handler代碼:

public class Handler implements Runnable{
    private final static int DEFAULT_SIZE = 1024;
    private final SocketChannel socketChannel;
    private final SelectionKey seletionKey;
    private static final int READING = 0;
    private static final int SENDING = 1;
    private int state = READING;

    ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
    ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);

    private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;
    private Selector selector;


    public Handler(Selector selector, SocketChannel channel) throws IOException {
        this.selector = selector;
        this.socketChannel = channel;
        socketChannel.configureBlocking(false);
        this.seletionKey = socketChannel.register(selector, 0);
        seletionKey.attach(this);
        seletionKey.interestOps(SelectionKey.OP_READ);
        selector.wakeup();
    }

    @Override
    public void run() {
        if (state == READING) {
            read();
        } else if (state == SENDING) {
            write();
        }
    }

    private void write() {
        try {
            socketChannel.write(outputBuffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (outIsComplete()) {
            seletionKey.cancel();
        }
    }

    private void read() {
        try {
            socketChannel.read(inputBuffer);
            if (inputIsComplete()) {
                process();
                executorService.execute(new Processer());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public boolean inputIsComplete() {
        return true;
    }
    public boolean outIsComplete() {
        return true;
    }


    public void process() {
        // do something...
    }

    class Sender implements Runnable {
        @Override
        public void run() {
            try {
                socketChannel.write(outputBuffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (outIsComplete()) {
                seletionKey.cancel();
            }
        }
    }

    synchronized void processAndHandOff() {
        process();
        // or rebind attachment
        state = SENDING;
        seletionKey.interestOps(SelectionKey.OP_WRITE);
        selector.wakeup();
    }

    class Processer implements Runnable {
        @Override
        public void run() {
            processAndHandOff();
        }
    }

}

主從Reactor多線程模型設計

主從Reactor多線程模型是將Reactor分成兩部分,mainReactor負責監聽server socket,accept新連接,並將建立的socket分派給subReactor。subReactor負責多路分離已連接的socket,讀寫網絡數據,對業務處理功能,其扔給worker線程池完成。通常,subReactor個數上可與CPU個數等同:

Handler保持不變,僅需要改動Reactor代碼:

public class Reactor {
    // also create threads
    Selector[] selectors;
    AtomicInteger next = new AtomicInteger(0);
    final ServerSocketChannel serverSocketChannel;

    private static ExecutorService sunReactors = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private static final int PROCESSING = 3;

    public Reactor(int port) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        selectors = new Selector[4];
        for (int i = 0; i < selectors.length; i++) {
            Selector selector = selectors[i];
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.configureBlocking(false);
            SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            key.attach(new Acceptor());
            new Thread(()->{
                while (!Thread.interrupted()) {
                    try {
                        selector.select();
                        Set<SelectionKey> selectionKeys = selector.selectedKeys();
                        for (SelectionKey selectionKey : selectionKeys) {
                            dispatch(selectionKey);
                        }
                        selectionKeys.clear();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }



    private void dispatch(SelectionKey selectionKey) {
        Runnable run = (Runnable) selectionKey.attachment();
        if (run != null) {
            run.run();
        }
    }

    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel channel = serverSocketChannel.accept();
                if (channel != null) {
                    sunReactors.execute(new Handler(selectors[next.getAndIncrement() % selectors.length], channel));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new Reactor(1234);
    }

}

以上是三種不同的設計思路,接下來看一下Netty這個一個高性能NIO框架,其是如何實現Reactor模型的!

Netty Reactor模型設計

  • 看一個最簡單的Netty服務端代碼
public final class EchoServer {
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
    public static void main(String[] args) throws Exception {
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(serverHandler);
                 }
             });
            ChannelFuture f = b.bind(PORT).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  • Netty Server Handler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.write(msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }
}

我們從Netty服務器代碼來看,與Reactor模型進行對應!

  • EventLoopGroup就相當於是Reactor,bossGroup對應主Reactor,workerGroup對應從Reactor
  • TimeServerHandler就是Handler
  • child開頭的方法配置的是客戶端channel,非child開頭的方法配置的是服務端channel

當然Netty的線程模型並不是固定的,它支持Reactor單線程模型、Reactor多線程模型、主從模型,上面的例子是一個主從模型的,下面進行詳細的分析,如圖所示:

服務啟動時,創建了兩個EventLoopGroup,它們實際上是兩個Reactor線程池,一個用於接收TCP連接、一個用於處理I/O相關的讀寫操作、或者執行系統task、定時task等;

  • Netty用於接收客戶端請求連接池職責如下:
    • 接收客戶端請求並初始化channel參數;
    • 講鏈路變更事件通知給ChannelPipiline;
  • Netty用於處理I/O連接池職責如下:
    • 異步讀取通信對端的數據報,發送讀事件到ChannelPipiline;
    • 異步發送消息對端的數據報,調用ChannelPipiline的消息發送接口;
    • 執行系統調用task;
    • 執行系統定時任務task,例如鏈路空閑狀態檢測定時任務;

參考

Scalable IO in Java

高性能Server—Reactor模型

NIO技術概覽

《Netty 權威指南》第二版 — 李林峰

你的鼓勵也是我創作的動力

打賞地址

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價

SpringSceurity(4)—短信驗證碼功能實現

SpringSceurity(4)—短信驗證碼功能實現

有關SpringSceurity系列之前有寫文章

1、SpringSecurity(1)—認證+授權代碼實現

2、SpringSecurity(2)—記住我功能實現

3、SpringSceurity(3)—圖形驗證碼功能實現

一、思考

1、設計思路

在獲取短信驗證碼功能和圖形驗證碼還是有很多相似的地方,所以這裡在設計獲取短信驗證的時候,將之前開發好的的圖形驗證碼進一步整合、抽象與重構。

在獲取驗證碼的時候,它們最大的不同在於: 圖形驗證碼是通過接口返回獲取給前端。而短信驗證碼而言是通過第三方API向我們手機推送

但是它們在登陸的時候就有很大的不同了,對於圖形驗證碼而言驗證通過之前就走 UsernamePasswordAuthenticationFilter 過濾器了開始校驗用戶名密碼了。

但對於短信登陸而言,確實也需要先現在短信驗證碼是否通過,但是一旦通過他是不走 UsernamePasswordAuthenticationFilter,而是通過其它方式查詢用戶信息來校驗

認證已經通過了。

這篇博客只寫獲取獲取短信驗證碼的功能,不寫通過短信驗證碼登陸的邏輯。

2、重構設計

這裏才是最重要的,如何去設計和整合短信驗證碼和圖形驗證碼的代碼,是我們最應該思考的。如何將相似部分抽離出來,然後去實現不相同的部分。

整理后發現不同點主要在於

 1、獲取驗證碼。因為對於圖形驗證碼需要有個畫布,而短信驗證碼並不需要,所以它們可以實現同一個接口,來完成不同的邏輯。
 2、發送驗證碼。對於圖形驗證碼來講只要把驗證碼返給前端就可以,而短信驗證碼而言是通過第三方API將驗證碼發到我們的手機上。
    所以這裏也可以通過實現統一接口來具體實現不同的方法。

相同部分我可以通過抽象類來完成實現,不同部分可以通過具體的實現類來實現。

AbstractValidateCodeProcessorService 抽象類是用來實現兩種驗證碼可以抽離的部分。ImageCodeProcessorServiceImpl

SmsCodeProcessorServiceImpl方法是來實現兩種驗證碼不同的發送方式。

在簡單看下時序圖可能會更加明白點。

一個接口只有一個方法(processor)就是處理驗證碼,它其實需要做三件事。

 1、獲取驗證碼。2、將驗證碼存入session。3、將驗證碼信息通過短信或者圖形驗證碼發送出去。

首先講生成獲取驗證碼,這裡有一個公共接口和兩個實現類

對於保存驗證碼信息而言,可以在直接在 AbstractValidateCodeProcessorService抽象類來完成,都不需要去實現。

對發送驗證碼信息而言,只需要實現AbstractValidateCodeProcessorService抽象類的send發送驗證碼接口即可。

整個大致接口設計就是這樣,具體的可以通過代碼來展示。

二、代碼實現

1、驗證碼屬性

短信驗證碼和圖形驗證后包含屬性有 codeexpireTime,短信驗證碼只有這兩個屬性,而圖形驗證碼還多一個BufferedImage實例對象屬性,所以將共同屬性進行抽取

,抽取為ValidateCode類,代碼如下:

ValidateCode實體

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ValidateCode {

    private String code;

    private LocalDateTime expireTime;

    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expireTime);
    }

}

對於圖形驗證碼而言,除了需要code和過期時間還需要圖片的畫布,所以繼承ValidateCode之後再寫自己屬性

ImageCode實體

@Data
public class ImageCode extends ValidateCode {

    private BufferedImage image;

    public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
        super(code, expireTime);
        this.image = image;
    }

    public ImageCode(BufferedImage image, String code, int expireIn) {
        super(code, LocalDateTime.now().plusSeconds(expireIn));
        this.image = image;
    }
}

對於短信驗證碼而言,暫時不需要添加自己的屬性字段了。

SmsCode實體

public class SmsCode extends ValidateCode {

    public SmsCode(String code, LocalDateTime expireTime) {
        super(code, expireTime);
    }

    public SmsCode(String code, int expireIn) {
        super(code, LocalDateTime.now().plusSeconds(expireIn));
    }
}

2、ValidateCodeProcessor接口

ValidateCodeProcessor接口主要是完成 驗證碼的生成、保存與發送的完整流程,接口的主要設計如下所示:

ValidateCodeProcessorService接口

public interface ValidateCodeProcessorService {

    /**
     * 因為現在有兩種驗證碼,所以存放到seesion的key不能一樣,所以前綴+具體type
     */
    String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
    /**
     * 通過也是 type+CODE_PROCESSOR獲取對於的bean
     */
    String CODE_PROCESSOR = "CodeProcessorService";

    /**
     * 這個接口要做三件事
     * 1、獲取驗證碼。
     * 2、將驗證碼存入session
     * 3、將驗證碼信息通過短信或者圖形驗證碼發送出去。
     * (將spring-boot-security-study-03接口裡的那三步進行里封裝)
     *
     */
    void processor(ServletWebRequest request) throws Exception;

由於圖片驗證碼和短信驗證碼的 生成和保存、發送等流程是固定的。所以這裏寫一個抽象類來實現ValidateCodeProcessor接口,來實現相似部分。

AbstractValidateCodeProcessorService抽象類

@Component
public abstract class AbstractValidateCodeProcessorService<C> implements ValidateCodeProcessorService {

    private static final String SEPARATOR = "/code/";

    /**
     * 操作session的工具集
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    /**
     * 這是Spring的一個特性,就是在項目啟動的時候會自動收集系統中 {@link ValidateCodeGeneratorService} 接口的實現類對象
     *
     * key為bean name
     */
    @Autowired
    private Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap;

    @Override
    public void processor(ServletWebRequest request) throws Exception {
        //第一件事
        C validateCode = generate(request);
        //第二件事
        save(request, validateCode);
        //第三件事
        send(request, validateCode);
    }

    /**
     * 生成驗證碼
     *
     */
    private C generate(ServletWebRequest request) {
        String type = getProcessorType(request);
        //這裏 image+CodeGenerator = imgCodeGenerator 對應的就是ImageCodeGeneratorServiceService
        ValidateCodeGeneratorService validateCodeGenerator = validateCodeGeneratorMap.get(type.concat(ValidateCodeGeneratorService.CODE_GENERATOR));
        return (C) validateCodeGenerator.generate(request);
    }

    /**
     * 保存驗證碼到session中
     */
    private void save(ServletWebRequest request, C validateCode) {
        //這裏也是封裝了一下
        sessionStrategy.setAttribute(request, SESSION_KEY_PREFIX.concat(getProcessorType(request).toUpperCase()), validateCode);
    }

    /**
     * 發送驗證碼 (只有發送驗證碼是需要自己去實現的。)
     */
    protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;

    /**
     * 獲取請求URL中具體請求的驗證碼類型
     *
     */
    private String getProcessorType(ServletWebRequest request) {
        // 獲取URI分割后的第二個片段 (/code/image 通過/code/ 切割后就只剩下 image
        return StringUtils.substringAfter(request.getRequest().getRequestURI(), SEPARATOR);
    }
}

簡單說明

1、這裏用到了Spring一個特性就是Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap 可以把ValidateCodeGeneratorService所以的實現類都放
到這個map中,key為bean的名稱。

2、抽象類中實現了 ValidateCodeProcessor接口的processor方法,它主要是完成了驗證碼的創建、保存和發送的功能。

3、generate 方法根據傳入的不同泛型而生成了特定的驗證碼。

4、save 方法是將生成的驗證碼實例對象存入到session中,兩種驗證碼的存儲方式一致,只是有個key不一致,所以代碼也是通用的。

5、send 方法一個抽象方法,分別由ImageCodeProcessorService和SmsCodeProcessorService來具體實現,也是根據泛型來判斷具體調用哪一個具體的實現類的send方法。

3、驗證碼的生成接口

上面說過驗證的生成應該也是通過實現類

ValidateCodeGeneratorService

public interface ValidateCodeGeneratorService {

    /**
     * 這個常量也是用來 type+CodeGeneratorService獲取對於bean對象
     */
    String CODE_GENERATOR = "CodeGeneratorService";

    /**
     * 生成驗證碼
     * 具體是圖片驗證碼 還是短信驗證碼就需要對應的實現類
     */
    ValidateCode generate(ServletWebRequest request);
}

圖形驗證碼具體實現類

mageCodeGeneratorServiceImpl

@Data
@Component("imageCodeGeneratorService")
public class ImageCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {

    private static final String IMAGE_WIDTH_NAME = "width";
    private static final String IMAGE_HEIGHT_NAME = "height";
    private static final Integer MAX_COLOR_VALUE = 255;

    @Autowired
    private ValidateCodeProperties validateCodeProperties;

    @Override
    public ImageCode generate(ServletWebRequest request) {
        int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, validateCodeProperties.getImage().getWidth());
        int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME,validateCodeProperties.getImage().getHeight());
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();

        Random random = new Random();

        // 生成畫布
        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        // 生成数字驗證碼
        StringBuilder sRand = new StringBuilder();
        for (int i = 0; i < validateCodeProperties.getImage().getLength(); i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand.append(rand);
            g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            g.drawString(rand, 13 * i + 6, 16);
        }

        g.dispose();

        return new ImageCode(image, sRand.toString(), validateCodeProperties.getImage().getExpireIn());
    }

    /**
     * 生成隨機背景條紋
     *
     * @param fc 前景色
     * @param bc 背景色
     * @return RGB顏色
     */
    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > MAX_COLOR_VALUE) {
            fc = MAX_COLOR_VALUE;
        }
        if (bc > MAX_COLOR_VALUE) {
            bc = MAX_COLOR_VALUE;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }
}

短信驗證碼具體實現類

SmsCodeGeneratorServiceImpl

@Data
@Component("smsCodeGeneratorService")
public class SmsCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {

    @Autowired
    private ValidateCodeProperties validateCodeProperties;


    @Override
    public SmsCode generate(ServletWebRequest request) {
        //生成隨機數
        String code = RandomStringUtils.randomNumeric(validateCodeProperties.getSms().getLength());
        return new SmsCode(code, validateCodeProperties.getSms().getExpireIn());
    }
}

4、驗證碼的發送邏輯類

獲取的實現類寫好了,我們在寫發送具體的發送實現類,發送類的實現類是實現AbstractValidateCodeProcessorService抽象類的。

圖片發送實現類

ImageCodeProcessorServiceImpl

@Component("imageCodeProcessorService")
public class ImageCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<ImageCode> {

    private static final String FORMAT_NAME = "JPEG";

    /**
     * 發送圖形驗證碼,將其寫到相應中
     *
     * @param request   ServletWebRequest實例對象
     * @param imageCode 驗證碼
     */
    @Override
    protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception {
        ImageIO.write(imageCode.getImage(), FORMAT_NAME, request.getResponse().getOutputStream());
    }
}

短信發送具體實現類。這裏只是後台輸出就好了,實際中只要接入對於的SDK就可以了。

SmsCodeProcessorServiceImpl

@Component("smsCodeProcessorService")
public class SmsCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<SmsCode> {
    private static final String SMS_CODE_PARAM_NAME = "mobile";

    @Override
    protected void send(ServletWebRequest request, SmsCode smsCode) throws Exception {
        //這裡有一個參數也是前端需要傳來的 就是用戶的手機號
        String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), SMS_CODE_PARAM_NAME);
        // 這裏僅僅寫個打印,具體邏輯一般都是調用第三方接口發送短信
        System.out.println("向手機號為:" + mobile + "的用戶發送驗證碼:" + smsCode.getCode());
    }

整個大致就是這樣,我們再來測試一下。

三、測試

1、ValidateCodeController接口

獲取驗證碼接口

@RestController
@Slf4j
public class ValidateCodeController {


    @Autowired
    private  Map<String, ValidateCodeProcessorService> validateCodeProcessorMap;


    @RequestMapping("/code/{type}")
    public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {
        if(!StringUtils.equalsAny(type, "image", "sms")){
            log.info("type類型錯誤 type={}",type);
            return;
        };

        //根據type獲取具體的實現類
        ValidateCodeProcessorService validateCodeProcessorService = validateCodeProcessorMap.get(type.concat(ValidateCodeProcessorService.CODE_PROCESSOR));
        validateCodeProcessorService.processor(new ServletWebRequest(request, response));

    }

}

2、獲得圖形驗證碼

獲取成功

3、獲取短信驗證碼

獲取短信驗證碼需要多傳一個參數就是mobile 手機號碼

因為這裏發送短信沒有接第三方SDK,而是直接在控制台輸出

參考

1、Spring Security技術棧開發企業級認證與授權(JoJo)

這一套架構設計真的非常的好,代碼可讀性和可用性都非常高,以後如果要接第三個驗證碼只要實現發送和獲取的接口來自定義實a現就好了。很受啟發!

別人罵我胖,我會生氣,因為我心裏承認了我胖。別人說我矮,我就會覺得好笑,因為我心裏知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。
攻我盾者,乃我內心之矛(20)

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

架構C02:商業模式與架構設計

商業模式與架構設計:A段架構與B段架構

《思考軟件創新設計:A段架構師思考技術》 A段架構師必須具備鮮活的創新思維,睿智的策略思考,犀利的洞察力和靈活的戰術才能把握稍縱即逝的商機                                                       

 

 

 

 

 

 

 

 

 

 

 

  

  段架構師 B段架構師
關注點 產品策略規劃 實踐策略,執行能力,技術變遷
協作對象 協助產品經理 協助研發經理生產經理
思維的差異 獲利思維,知彼才能在複雜商業環境生存 成本思維,知己才能在成本和收益做出合適選擇

 

                目前我們所接觸的大多是B段技術的架構,更關注“知己”,我們做研發改進,敏捷管理,技術重構,就是為了更好的平衡技術的成本和業務的收益  

 A段架構與商業模式:以不變應萬變

 

架構師需要考慮的商業要素

決策前(A段設計)—->決策點—>決策后(B段設計)  

商業思維三要素:商業模式,架構模式,創新產品

 

軟件是現實世界的映射和抽象

現實世界是複雜多變的,所以由需求就是複雜多變的,軟件也是複雜多變的, 所以現實中組織要發展就要面對變化的適合的變化,反應到軟件上也會隨需求的變化而變化,所以軟件本質上是一個演化的系統,是一個複雜的系統  

商業與技術的關係

商業維度,現實世界是複雜多變的組織需要不停的適應市場的變化, 從產品維度需要不停的創新滿足客戶和市場的需求, 而從技術和架構的維度來看,架構則希望更少的信息熵,用更少的技術元素來表述更多的業務結構,這也正是為什麼我們追求模型,模式,結構與算法  

 商業和產品做加法,架構設計做減法 

在複雜的現實中,用簡單的抽象來支撐商業的變化,用靈活的設計支持業務的創新   《深奧的簡潔》是一本科普讀物,裏面講述了碎行,自我組織,自我類似等等自然界好些美妙的規律 https://zh.wikipedia.org/wiki/%E5%88%86%E5%BD%A2#%E7%A4%BA%E4%BE%8B    

大樹的隱喻描述商業,架構,研發技術生產管理

大樹的上層是枝恭弘=叶 恭弘,要吸收陽光雨露,要開花結果,是對外界展示的活躍和生機的一面,這裏用來表述商業模式和創新產品 這些都是要變化的部分,而且收外部影響較明顯   再次是樹榦是中層A段架構,中層要求穩既要約束和輔助枝恭弘=叶 恭弘發展和繁榮又要保護下層樹根承受壓力   下層部分的話就是B段架構,生產,技術,管理,這些是看不見但是很重要的元素,是整個樹木生命繁榮的根本    

從複雜中抽象出簡單,用簡單和較少信息熵,應對複雜多變的商業和產品

簡單的有序的產品和架構設計,通過一定的約束組合可以形成一個富有活力的系統,底層元素的簡單又保證了它可以包容現實中的複雜變化,應對紛繁複雜的現實情況,支持商業的變革和產品的創新    

B段架構技術和業務的矛盾:用成本收益作為衡量標準

 

變的是需求和技術,不變的是成本與收益評估,是要創造價值的目標

  “你這個功能啥時候能上? ” “這個有難度目前不行,需要做重構,技術細節blablabla…” “提這麼多需求沒幾個有用的,根本不懂技術實現,你要覺的能行為啥你不上”   產品和技術的矛盾點: 1. 資源的搶佔 2. 成本的評估 3.內外部目標的差異 4.內部目標設定不合理  

解決問題:業務知識+成本核算

技術要了解業務背景,業務收益,要解決的問題是什麼?只有這樣才能解決問題,做出架構設計,做出模型設計,解決業務問題,幫助客戶解決現實場景的問題  

優秀的架構要融和技術與業務的平衡和成本收益的評估

  1. 清晰服務業務短期目標,明確技術定位,輔助實現當前階段業務訴求 2. 協調技術資源投入和分配 3. 進行成本與收益的評估,確定做哪些,不做那些,先做那些,怎麼做收益更大 4. 預留長期技術規劃和儲備  

我們是解決昨日之債務,還是準備迎接今日之挑戰?

衡量的標準就是做這件事的收益?   產品和業務做哪些收益更大:產品的願景和價值觀 本年度看做哪些收益更大(OKR) 本季度本月做哪些收益最大(月度發版路標規劃) 當天本周做哪些收益最大(周計劃)  

舊系統的改造 OR 新技術的引進?

技術儲備和技術棧規劃方面: 中小型創業型公司,非技術驅動的公司 關注中長期發展的技術與趨勢,不要太超前,不必做小白鼠   舊系統改造方面: 假如不能明顯的產生業務價值,單純的把報表生成把半小時優化到5分鐘,不如做一些其他更有業務價值的任務 假如沒有其他高附件值任務可以去做,假如報表生成佔用研發時間減少了質量保證時間,影響了交付質量也可以去做  

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

關於Graph Convolutional Network的初步理解

  為給之後關於圖卷積網絡的科研做知識積累,這裏寫一篇關於GCN基本理解的博客。GCN的本質是一個圖網絡中,特徵信息的交互+與傳播。這裏的圖指的不是圖片,而是數據結構中的圖,圖卷積網絡的應用非常廣泛 ,經常被舉到的典型例子就是一個空間中熱量的傳遞和交互,這裏不多作贅述。

一、圖卷積網絡與普通卷積網絡的應用範圍

  圖卷積網絡為什麼叫圖卷積網絡呢?圖卷積網絡,其實就是就是GCN,但GCN為什麼是圖神經網絡呢?小編也很疑惑。

  好了!開玩笑的話先打住,進入正題。首先複習一下卷積神經網絡的工作原理,以檢測圖片的過程為例,卷積神經網絡提取圖片特徵的過程,其實就是對每個像素周邊像素加權求和的過程,初始每個像素對應的卷積核的權重隨機,在通過反向傳遞、逐層梯度下降的優化之後才會得到合理的權重,根據這個權重得到的feature map便是特徵提取的結果。對於圖像等像素排列規整的結構來說,使用普通的卷積神經網絡提取特徵沒有任何問題,但對於某些形如交通網、電網等“不整齊”的結構,普通的CNN就沒有用武之地了,引用知乎大佬學術性的話講,就是“CNN在Non Euclidean Structure的數據上無法保持平移不變性”,翻譯成人話就是對於圖結構的數據,其每個點鄰接的數量各不相同,會給CNN提取特徵帶來很大的困難;要提取圖結構的空間特徵進行後續的機器學習,就需要使用圖卷積網絡。簡而言之,GCN是CNN的升級版,CNN做不到的,GCN可以做,GCN做的到的,CNN做不到。

二、圖卷積網絡提取空間特徵的方式

  提取拓撲圖空間特徵有兩種方法:空間領域與譜領域。這裏我只對譜領域的提取方法作總結概述(空間領域的沒學),通過定義圖上的傅里恭弘=叶 恭弘變換,圖的卷積方式得到表示方式,與深度學習結合得到最終的圖卷積網絡。在進行傅里恭弘=叶 恭弘和卷積的推導前先複習一下線代?一張圖的拉普拉斯矩陣一般為其度矩陣D減去其鄰接矩陣A,其他常見定義也有D-1LD與D-1L。

1.圖的特徵分解

  對圖的拉普拉斯矩陣進行譜分解,說的通俗易懂一點就是對角化。使用拉普拉斯矩陣進行運算的優勢在這裏體現:拉普拉斯矩陣滿足譜分解所需線性無關的條件。圖的拉普拉斯矩陣分解形式為UPU-1,其中U=[u1,u2,…,un],為列向量是單位特徵向量的矩陣;P為含有n個特徵值的對角矩陣。

2.含特徵向量與特徵值矩陣的傅里恭弘=叶 恭弘變換定義

  在瀏覽一些大佬的博客與知乎時我常常感到詫異:進行完矩陣分解后怎麼突然講到傅里恭弘=叶 恭弘變化了?理清思路后發現相關矩陣傅里恭弘=叶 恭弘變換的定義是最後卷積公式推導的基礎,由於兩函數的卷積是其函數傅立恭弘=叶 恭弘變換乘積的逆變換,即:

  為了能將針對圖的傅里恭弘=叶 恭弘變換類比代入上述公式,我們需要推廣傅里恭弘=叶 恭弘變換,把特徵函數 eiωt 變為拉普拉斯矩陣的特徵向量。

  由傅里恭弘=叶 恭弘變換的一般形式:

  類比特徵方程定義:

LV=λV

  L、V、λ分別代表拉普拉斯矩陣、特徵向量/函數、特徵值。將特徵向量與前面定義的u矩陣對應,得到最終圖的傅里恭弘=叶 恭弘變換定義為:

 

  其中f(i)對應圖的各個頂點,ux*(i)表示第x個特徵向量的第 i 個分量。那麼特徵值λx下F的圖傅里恭弘=叶 恭弘變換就是與λx對應的特徵向量ux進行內積運算。矩陣形式為:

  即f^=UT f。同時由傅里恭弘=叶 恭弘逆變換基本公式:

  得到傅里恭弘=叶 恭弘逆變換的矩陣形式:

  

  即f=Uf^

3.圖卷積推導  

  在定義完圖上的傅里恭弘=叶 恭弘變換之後,總算要開始讓夢想照進現實將卷積運算推廣到圖上了。由卷積定理:

     將對應圖上各點的f與卷積核h的傅里恭弘=叶 恭弘定義代入其中,卷積核在圖上的傅里恭弘=叶 恭弘變換被定義為:

  按卷積定理將兩者傅里恭弘=叶 恭弘變換形式相乘得到:

  最後乘U求得傅立恭弘=叶 恭弘變換乘積的逆變換,最終得到卷積:

  以上,GCN粗略的推導過程就整理完畢了。

 

  參考網站: https://www.zhihu.com/question/54504471?sort=created

        https://www.cnblogs.com/h2zZhou/p/8405717.html

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

70多萬能買到的SUV公路之王 是寶馬X5 並且是35i

能合法上牌嗎。全國牌可以上嗎。答:該款車所以手續都是合法齊全的,絕對可以合法上牌,並且全國各地都可以上牌,而廣州之外的地區經銷商在全國各地都有合作的夥伴會幫助上牌。問:該款車上牌的費用是多少錢。答:全國上牌都是6000元。

說到SUV公路之王,相信很多車迷朋友第一個想到的就是寶馬X5了。那為什麼是寶馬X5呢?同級別的SUV還不少車型,例如保時捷-卡宴、奧迪Q7、奔馳GLE等,而寶馬X5能從中脫穎而出,被譽為“SUV公路之王”,首先寶馬品牌本身流淌着出色的操控性血統就已經起到決定性的作用,再加上動力十足的3.0T直列六缸發動機和xDrive全時四驅系統的搭配,您說寶馬X5的公路表現能不“稱王”嗎!

然而作為SUV公路之王,其售價自然就不低了,中規進口寶馬X5 xDrive 35i指導價是83.80萬元起售,按落地價算起來已接近百萬元的售價了,高昂的價格令不少預算不足的客戶只能退求其次選擇75.80萬元起售的xDrive 28i,但是xDrive 28i搭載的只是245匹馬力的四缸發動機,動力的不足和直列六缸的缺失實在是令人糾結不已。

為了滿足車友們的需求,令更多的車友擁有這款SUV公路之王,再次出馬,聯合廣州當地知名豪車經銷商推出平行進口中東版寶馬X5 35i,特惠售價為74.80萬元。該款中東版寶馬X5 35i配置對應的是售價為95.80萬元的中規版35i豪華型,按目前廣州當地中規版寶馬X5優惠8個點來計算,中規版寶馬X5 35i豪華型優惠後車價是88.13萬元,就算是中規版35i最低配典雅型83.80萬元優惠8個點后也要77.09萬元,對比之下這次推出的中東版寶馬X5 35i是十分優惠的。

另外很重要的一點是該款中東版寶馬X5是原廠帶有中文菜單和導航的,而更重要的是該款中東版寶馬X5是現車銷售的,在現在年底購車高峰期的節點上,有現車可能比起優惠來說更為實在,因為據了解到的情況中規版寶馬X5現車是很少了,訂車也趕不上明年春節前提車了,所以這次推出的中東版寶馬X5在明年春節前完成提車手續絕對沒有問題,可以說是現車與優惠並存,有興趣的朋友可以致電購車熱線諮詢:400-855-5813

下面該款中東版寶馬X5 35i的具體情況以問答的方式來進行說明

問:這款平行進口2016款中東版寶馬X5 35i有什麼顏色?

答:該款車有黑色、棕色、白色三種外觀,內飾均為棕色,其中黑色和棕色外觀現車較多,白色現車較少。

問:這款平行進口2016款中東版寶馬X5 35i只能在廣州提車嗎?

答:該款車不需要一定在廣州提車,因為是平行進口車沒有區域限制,是可以售全國的,如果跟經銷商協商預先付了全款,長江以南地區是可以免費發車的,其他地區按運輸強度收取運費發車。

問:這款平行進口2016款中東版寶馬X5 35i手續齊全嗎?能合法上牌嗎?全國牌可以上嗎?

答:該款車所以手續都是合法齊全的,絕對可以合法上牌,並且全國各地都可以上牌,而廣州之外的地區經銷商在全國各地都有合作的夥伴會幫助上牌。

問:該款車上牌的費用是多少錢?

答:全國上牌都是6000元。

問:該款車的保險費用是多少錢?

答:保險費用是27000元左右,需要在經銷商處購買,根據選擇險種的不同,保險費用會相應變動。

問:該款車的購置稅是多少錢?算上所以費用落地價是多少錢?

答:按74.80萬元車價算,購置稅約是63932元左右,算上所有費用落地價約是84.49萬元左右。

問:該款車是否享有與中規進口寶馬X5一樣的保修?能在4S店保養嗎?

答:因為是平行進口車的關係,該款車是不能享有中規進口車在4S店的保修,不過該車可以由售車經銷商提供保修,是店保兩年不限公里數,並且售車經銷商在廣州已有經營德國名車維修保養十多年的實力,不論是保修還是保養都無需擔心。另外車主如果不想在售車經銷商處保修保養,是可以付費到全國各地的寶馬4S店進行維修和保養。

問:如果在購車平台上下單?

答:點擊“閱讀原文”直接進入下單界面,訂金為1000元,可支付寶或微信支付,訂金直接抵扣車款,則實際銷售合同是74.70萬元;下單后不購車訂金隨時可退,成功下單後會有工作人員與您聯繫安排購車事宜,請保持電話暢通。

注:有任何疑問可致電購車熱線諮詢:400-855-5813本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

※推薦台中搬家公司優質服務,可到府估價