過濾器 和 攔截器 6個區別,別再傻傻分不清了_台中搬家

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

本文收錄在個人博客:www.chengxy-nds.top,技術資料共享,同進步

周末有個小夥伴加我微信,向我請教了一個問題:老哥,過濾器 (Filter) 和 攔截器 (Interceptor) 有啥區別啊? 聽到題目我的第一感覺就是:簡單

畢竟這兩種工具開發中用到的頻率都相當高,應用起來也是比較簡單的,可當我準備回復他的時候,竟然不知道從哪說起,支支吾吾了半天,場面炒雞尷尬有木有,工作這麼久一個基礎問題答成這樣,丟了大人了。

平時覺得簡單的知識點,但通常都不會太關注細節,一旦被別人問起來,反倒說不出個所以然來。

歸根結底,還是對這些知識了解的不夠,一直停留在會用的階段,以至於現在一看就會一說就廢!這是典型基礎不紮實的表現,哎·~,其實我也就是個虛胖!

知恥而後勇,下邊結合實踐,更直觀的來感受一下兩者到底有什麼不同?

準備環境

我們在項目中同時配置 攔截器過濾器

1、過濾器 (Filter)

過濾器的配置比較簡單,直接實現Filter 接口即可,也可以通過@WebFilter註解實現對特定URL攔截,看到Filter 接口中定義了三個方法。

  • init() :該方法在容器啟動初始化過濾器時被調用,它在 Filter 的整個生命周期只會被調用一次。注意:這個方法必須執行成功,否則過濾器會不起作用。

  • doFilter() :容器中的每一次請求都會調用該方法, FilterChain 用來調用下一個過濾器 Filter

  • destroy(): 當容器銷毀 過濾器實例時調用該方法,一般在方法中銷毀或關閉資源,在過濾器 Filter 的整個生命周期也只會被調用一次

@Component
public class MyFilter implements Filter {
    
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        System.out.println("Filter 前置");
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 處理中");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {

        System.out.println("Filter 後置");
    }
}

2、攔截器 (Interceptor)

攔截器它是鏈式調用,一個應用中可以同時存在多個攔截器Interceptor, 一個請求也可以觸發多個攔截器 ,而每個攔截器的調用會依據它的聲明順序依次執行。

首先編寫一個簡單的攔截器處理類,請求的攔截是通過HandlerInterceptor 來實現,看到HandlerInterceptor 接口中也定義了三個方法。

  • preHandle() :這個方法將在請求處理之前進行調用。注意:如果該方法的返回值為false ,將視為當前請求結束,不僅自身的攔截器會失效,還會導致其他的攔截器也不再執行。

  • postHandle():只有在 preHandle() 方法返回值為true 時才會執行。會在Controller 中的方法調用之後,DispatcherServlet 返回渲染視圖之前被調用。 有意思的是postHandle() 方法被調用的順序跟 preHandle() 是相反的,先聲明的攔截器 preHandle() 方法先執行,而postHandle()方法反而會後執行。

  • afterCompletion():只有在 preHandle() 方法返回值為true 時才會執行。在整個請求結束之後, DispatcherServlet 渲染了對應的視圖之後執行。

@Component
public class MyInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        System.out.println("Interceptor 前置");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        System.out.println("Interceptor 處理中");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("Interceptor 後置");
    }
}

將自定義好的攔截器處理類進行註冊,並通過addPathPatternsexcludePathPatterns等屬性設置需要攔截或需要排除的 URL

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**");
    }
}

我們不一樣

過濾器 和 攔截器 均體現了AOP的編程思想,都可以實現諸如日誌記錄、登錄鑒權等功能,但二者的不同點也是比較多的,接下來一一說明。

1、實現原理不同

過濾器和攔截器 底層實現方式大不相同,過濾器 是基於函數回調的,攔截器 則是基於Java的反射機制(動態代理)實現的。

這裏重點說下過濾器!

在我們自定義的過濾器中都會實現一個 doFilter()方法,這個方法有一個FilterChain 參數,而實際上它是一個回調接口。ApplicationFilterChain是它的實現類, 這個實現類內部也有一個 doFilter() 方法就是回調方法。

public interface FilterChain {
    void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}

ApplicationFilterChain裏面能拿到我們自定義的xxxFilter類,在其內部回調方法doFilter()里調用各個自定義xxxFilter過濾器,並執行 doFilter() 方法。

public final class ApplicationFilterChain implements FilterChain {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) {
            ...//省略
            internalDoFilter(request,response);
    }
 
    private void internalDoFilter(ServletRequest request, ServletResponse response){
    if (pos < n) {
            //獲取第pos個filter    
            ApplicationFilterConfig filterConfig = filters[pos++];        
            Filter filter = filterConfig.getFilter();
            ...
            filter.doFilter(request, response, this);
        }
    }
 
}

而每個xxxFilter 會先執行自身的 doFilter() 過濾邏輯,最後在執行結束前會執行filterChain.doFilter(servletRequest, servletResponse),也就是回調ApplicationFilterChaindoFilter() 方法,以此循環執行實現函數回調。

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        filterChain.doFilter(servletRequest, servletResponse);
    }

2、使用範圍不同

我們看到過濾器 實現的是 javax.servlet.Filter 接口,而這個接口是在Servlet規範中定義的,也就是說過濾器Filter 的使用要依賴於Tomcat等容器,導致它只能在web程序中使用。

而攔截器(Interceptor) 它是一個Spring組件,並由Spring容器管理,並不依賴Tomcat等容器,是可以單獨使用的。不僅能應用在web程序中,也可以用於ApplicationSwing等程序中。

3、觸發時機不同

過濾器攔截器的觸發時機也不同,我們看下邊這張圖。

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

台中搬家公司推薦超過30年經驗,首選台中大展搬家

過濾器Filter是在請求進入容器后,但在進入servlet之前進行預處理,請求結束是在servlet處理完以後。

攔截器 Interceptor 是在請求進入servlet后,在進入Controller之前進行預處理的,Controller 中渲染了對應的視圖之後請求結束。

4、攔截的請求範圍不同

在上邊我們已經同時配置了過濾器和攔截器,再建一個Controller接收請求測試一下。

@Controller
@RequestMapping()
public class Test {

    @RequestMapping("/test1")
    @ResponseBody
    public String test1(String a) {
        System.out.println("我是controller");
        return null;
    }
}

項目啟動過程中發現,過濾器的init()方法,隨着容器的啟動進行了初始化。

此時瀏覽器發送請求,F12 看到居然有兩個請求,一個是我們自定義的 Controller 請求,另一個是訪問靜態圖標資源的請求。

看到控制台的打印日誌如下:

執行順序 :Filter 處理中 -> Interceptor 前置 -> 我是controller -> Interceptor 處理中 -> Interceptor 處理后

Filter 處理中
Interceptor 前置
Interceptor 處理中
Interceptor 後置
Filter 處理中

過濾器Filter執行了兩次,攔截器Interceptor只執行了一次。這是因為過濾器幾乎可以對所有進入容器的請求起作用,而攔截器只會對Controller中請求或訪問static目錄下的資源請求起作用。

5、注入Bean情況不同

在實際的業務場景中,應用到過濾器或攔截器,為處理業務邏輯難免會引入一些service服務。

下邊我們分別在過濾器和攔截器中都注入service,看看有什麼不同?

@Component
public class TestServiceImpl implements TestService {

    @Override
    public void a() {
        System.out.println("我是方法A");
    }
}

過濾器中注入service,發起請求測試一下 ,日誌正常打印出“我是方法A”

@Autowired
    private TestService testService;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        System.out.println("Filter 處理中");
        testService.a();
        filterChain.doFilter(servletRequest, servletResponse);
    }
Filter 處理中
我是方法A
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor 後置

在攔截器中注入service,發起請求測試一下 ,竟然TM的報錯了,debug跟一下發現注入的service怎麼是Null啊?

這是因為加載順序導致的問題,攔截器加載的時間點在springcontext之前,而Bean又是由spring進行管理。

攔截器:老子今天要進洞房;
Spring:兄弟別鬧,你媳婦我還沒生出來呢!

解決方案也很簡單,我們在註冊攔截器之前,先將Interceptor 手動進行注入。注意:在registry.addInterceptor()註冊的是getMyInterceptor() 實例。

@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Bean
    public MyInterceptor getMyInterceptor(){
        System.out.println("注入了MyInterceptor");
        return new MyInterceptor();
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
    }
}

6、控制執行順序不同

實際開發過程中,會出現多個過濾器或攔截器同時存在的情況,不過,有時我們希望某個過濾器或攔截器能優先執行,就涉及到它們的執行順序。

過濾器用@Order註解控制執行順序,通過@Order控制過濾器的級別,值越小級別越高越先執行。

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class MyFilter2 implements Filter {

攔截器默認的執行順序,就是它的註冊順序,也可以通過Order手動設置控制,值越小越先執行。

 @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**").order(2);
        registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**").order(1);
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**").order(3);
    }

看到輸出結果發現,先聲明的攔截器 preHandle() 方法先執行,而postHandle()方法反而會後執行。

postHandle() 方法被調用的順序跟 preHandle() 居然是相反的!如果實際開發中嚴格要求執行順序,那就需要特別注意這一點。

Interceptor1 前置
Interceptor2 前置
Interceptor 前置
我是controller
Interceptor 處理中
Interceptor2 處理中
Interceptor1 處理中
Interceptor 後置
Interceptor2 處理后
Interceptor1 處理后

那為什麼會這樣呢? 得到答案就只能看源碼了,我們要知道controller 中所有的請求都要經過核心組件DispatcherServlet路由,都會執行它的 doDispatch() 方法,而攔截器postHandle()preHandle()方法便是在其中調用的。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
        try {
         ...........
            try {
           
                // 獲取可以執行當前Handler的適配器
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (logger.isDebugEnabled()) {
                        logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                    }
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 注意: 執行Interceptor中PreHandle()方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // 注意:執行Handle【包括我們的業務邏輯,當拋出異常時會被Try、catch到】
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);

                // 注意:執行Interceptor中PostHandle 方法【拋出異常時無法執行】
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
        }
        ...........
    }

看看兩個方法applyPreHandle()applyPostHandle()具體是如何被調用的,就明白為什麼postHandle()preHandle() 執行順序是相反的了。

boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if(!interceptor.preHandle(request, response, this.handler)) {
                    this.triggerAfterCompletion(request, response, (Exception)null);
                    return false;
                }
            }
        }

        return true;
    }
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
        HandlerInterceptor[] interceptors = this.getInterceptors();
        if(!ObjectUtils.isEmpty(interceptors)) {
            for(int i = interceptors.length - 1; i >= 0; --i) {
                HandlerInterceptor interceptor = interceptors[i];
                interceptor.postHandle(request, response, this.handler, mv);
            }
        }
    }

發現兩個方法中在調用攔截器數組 HandlerInterceptor[] 時,循環的順序竟然是相反的。。。,導致postHandle()preHandle() 方法執行的順序相反。

總結

我相信大部分人都能熟練使用濾器和攔截器,但兩者的差別還是需要多了解下,不然開發中使用不當,時不時就會出現奇奇怪怪的問題,以上內容比較簡單,新手學習老鳥複習,有遺漏的地方還望大家积極補充,如有理解錯誤之處,還望不吝賜教。

原創不易,燃燒秀髮輸出內容

整理了幾百本各類技術电子書, 送給小夥伴們, 我的同名公眾號自行領取。和一些小夥伴們建了一個技術交流群,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就加入我們吧!

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

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

2歲女童泳池溺水 送醫急救中_台中搬家公司

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

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

〔記者王宣晴/新北報導〕新北市淡水淡金路一段的大台北潛水運動中心今晚發生2歲女童溺水案件,警消獲報趕抵時,溺水女童已恢復呼吸心跳,目前送醫急救中。

據查證,葉姓女童的外公是大台北潛水運動中心的負責人,今晚7時,葉姓女童獨自在運動中心內玩耍,卻不慎掉入泳池中,被發現時女童已面部朝下,浮在水面上,女童外公見狀嚇得趕緊報案急救。

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

新北市消防局獲報趕抵,發現女童還有呼吸心跳,連忙將她送往淡水馬偕醫院急救,但目前仍未脫離險境,警方也已介入調查,釐清女童溺水原因。

2歲女童溺水案發現場。(民眾提供)

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

OkHttp,一次無奈的使用_網頁設計公司

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

一次使用OKHTTP的心痛歷程

最近由於一些不得已的原因,接觸到了OKHttp,說起來也挺Dan疼的,之前同事將生產附件上傳地址配置成了測試地址,還好數量不多,沒有造成太大的影響,況且的是這位同事又離職了,當時只能在心中
默默的問候了他N遍,當然問候完了之後,也不得不繼續數據同步的工作。

OKHTTP官方地址:okHttp

本文源地址:一次使用OKHTTP的心痛歷程

介紹

由於OkHttp官網的介紹十分詳細,這裏只能貼上一段翻譯過後的introduce:

HTTP是現代應用網絡的一種方式。這就是我們交換數據和媒體的方式。高效地使用HTTP可以讓你的東西更快地加載並節省帶寬。

OkHttp是一個高效的Http客戶端,在默認的情況下:

  • HTTP / 2支持允許對同一主機的所有請求共享一個套接字。
  • 連接池可減少請求延遲(如果HTTP / 2不可用)。
  • 透明的GZIP縮小了下載大小。
  • 響應緩存可以完全避免網絡重複請求。

不過在我使用下來,OkHttp比 apache-http 好用太多,層次結構較直觀。

使用場景

本次場景是將上傳到測試環境的文件信息,下載到本地,然後再上傳到生產環境。

解決過程如下:

  • 將錯誤數據從數據庫表中粘貼到本地新建的一個Excel文件中。(畢竟直接連接數據庫風險更大)

  • 讀取Excel內的信息,獲取文件地址。

  • 請求文件地址,獲取到流文件信息。

  • 拿到流文件信息,拼接上傳數據,上傳到新的生產環境中。

  • 上傳完成后,獲取到生產環境文件地址。

  • 獲取到生產文件地址的同時,生成更新的SQL語句。

  • 到數據庫中執行SQL語句。

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

    網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

使用過程

本次使用沒有搭建新的工程,直接再 src/test/java 目錄下新建一個Java類。

引入OKHttp的依賴:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.3.1</version>
</dependency>

在引入了 okhttp 的jar包后,基本上就可以開始隨心所欲的進行自己任意喪心病狂的Http請求了。

比如,它直接同步和異步的請求:

同步GET

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/helloworld.txt")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      Headers responseHeaders = response.headers();
      for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
      }

      System.out.println(response.body().string());
    }
  }

異步GET

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        try (ResponseBody responseBody = response.body()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

          Headers responseHeaders = response.headers();
          for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
          }

          System.out.println(responseBody.string());
        }
      }
    });
  }

Header信息

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println("Server: " + response.header("Server"));
      System.out.println("Date: " + response.header("Date"));
      System.out.println("Vary: " + response.headers("Vary"));
    }
  }

POST請求流信息

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

POST請求File信息

  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

Post表單提交

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody formBody = new FormBody.Builder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

POST多個Body請求

  /**
   * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
   * these examples, please request your own client ID! https://api.imgur.com/oauth2
   */
  private static final String IMGUR_CLIENT_ID = "...";
  private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

因為使用過程中大多數都是按照官網的例子來進行,所以這次使用的代碼是類似於官方提供的例子,當然也是不太好意思貼出來,哈哈。

總結

OkHttp算得上是相見恨晚,之前一遍一遍寫 apache-http 的時候就覺得 apche 有點冗餘,就是想有一個輕量級的,比較好上手,容易懂的http-client,不過現在接觸到了 okhttp,還是得感謝那位配錯地址的兄弟。

以上更多請求例子可以訪問:OKhttp-Request-example

參考資料:

OkHttp.io

OKhttp-Request-example

OKHttp-Github

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

7.5-15萬不買日系車 上班族首選車型顏值高有格調!_網頁設計公司

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

內飾運動感沒有那麼強烈,整體比較中規中矩,不過,配色上沒有那麼沉悶。福克斯有1。0T 1。5T 1。6L三種動力車型選擇,三種車型配置相差並不算太大,都標配了ESp,剎車輔助,牽引力控制等的配置,每一款動力的車型低配和次低配舒適性的配置稍少了一些,但對於日常使用而言,影響並不大。

如今路上的車輛越來越多,塞車,找不到停車位,已經是習以為常的時。因為這樣,現在不少人會選擇“體型”較小的車型,這樣在路上行駛或者在塞車擠位的時候都比較輕鬆。對於一般的上班族而言,下面這些車型就適合。

上汽大眾-pOLO

指導價:7.59-14.69萬

pOLO的外觀大家也再熟悉不過了,大眾臉配上小“體型”,有一種別樣的規矩感。內飾造型同樣是大眾風,但鋼琴烤漆高亮度面板和鍍鉻飾條,稍微的提升了些檔次感,布局也以實用為主。

pOLO有1.4L,1.6L以及1.4T車型選擇,對於一般用車,1.4T車型的選擇性較低。1.4L車型除了頂配之外都沒有配備ESp,剎車輔助,牽引力控制,這方便比較不就到,舒適配置表現中下,畢竟是8萬的大眾車。1.6L車型低配和次低配車型安全配置較低,舒適性配置比1.4L車型多了幾個,例如,電動天窗,定速巡航等,頂配和次頂配車型有配備ESp,剎車輔助,牽引力控制,舒適性配置也比較齊全。另外,1.4L車型的自動風尚型可以選裝ESp,剎車輔助,牽引力控制。

長安福特-福克斯

指導價:9.98-16.58萬

馬丁臉有較強烈的視覺感,整體的設計運動型較強,對於喜歡有點激情的上班族來說,很適合。內飾運動感沒有那麼強烈,整體比較中規中矩,不過,配色上沒有那麼沉悶。

福克斯有1.0T 1.5T 1.6L三種動力車型選擇,

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

三種車型配置相差並不算太大,都標配了ESp,剎車輔助,牽引力控制等的配置,每一款動力的車型低配和次低配舒適性的配置稍少了一些,但對於日常使用而言,影響並不大。中配車型配置比較齊全,日常使用很足夠,例如,電動天窗,多功能方向盤,后駐車雷達等都有配備。而頂配車型還配備了併線輔助,車道偏離系統,但價格較貴,日常使用,選擇中低配車型就足夠了。

東風標緻-標緻308S

指導價:11.27-17.97萬

不一樣的風情,這是308S給人的第一眼感覺,濃濃的法系味道。內飾顯得比較簡約,但做工依然保持了法系的精緻,看起很有質感,物理按鍵被替代成了觸碰式的,這對操作的實用性有一定的減少。

308S有1.2T 1.6L 1.6T三種動力車型車選擇,1.2T和1.6L的頂配車型配置較為齊全一些,但有些奇怪的是,全系都有配備ESp,剎車輔助,上坡輔助,就是沒有配備牽引力控制,頂配有配備全景天窗但是全系都沒有電動天窗,這算是法系的獨特的風格?中低配車型雖然配置較少,但對於日常使用也還是足夠的,只要你對配置要求不高的話。1.6T車型整體配置算得上齊全,有配備牽引力控制,但還是沒有電動天窗,其他舒適性配置算得上豐富,不過價格就貴了不少,不太推薦。

總結:斯文派的pOLO,激情派的福克斯,時尚派的標誌308S,三種不同風格之選。在價格上,pOLO算上優惠,最低配裸車價可以去到6萬多,而福克斯和308S價格稍微貴一些,但配置上會好很多。總體而言,福克斯各方面比較均衡,價格不高也不低,配置也一樣,對於現在人選車的“理念”而言,比較適合。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

低配高配相差甚遠 怎樣花最少的錢享受到高配置?_網頁設計公司

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

而高配車雖有各種高科技配置,但也帶來了高額的購車成本。所以,如果選了低配車而又不滿意,還有什麼好辦法可以補救下。總結:對於低配車加裝配置,可能在成本上比原高配車要佔優,但要注意的是,無論後期加裝的配置有多先進,總體上跟原高配車的體驗還是會有所不同。

買車除了關心汽車的三大件,配置高低也是必不可少的一項指標。但問題是,真到了選擇高配車和低配車的時候,你會怎麼選?現在的低配車配置寒酸,見不得人,但購買門檻低。而高配車雖有各種高科技配置,但也帶來了高額的購車成本。所以,

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

如果選了低配車而又不滿意,還有什麼好辦法可以補救下?

總結:對於低配車加裝配置,可能在成本上比原高配車要佔優,但要注意的是,無論後期加裝的配置有多先進,總體上跟原高配車的體驗還是會有所不同。因此,在看來,後期加裝配置也要適可而止,要跟自己實際需求掛鈎,切勿盲目貪心的強行增加配置,特別是一些高級配置,更是要慎之又慎,不然,花點錢倒是小事,搞壞了車子才是悲劇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

最便宜的中型車 13萬國貨或韓系你選誰?_網頁設計公司

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

動力對比B70搭載兩款發動機。2。0L自然吸氣發動機、1。8T渦輪增壓發動機,最大功率108千瓦、137千瓦,最大扭矩184牛米、235牛米,傳動系統方面,配備的是6擋手動和6擋手自一體,在參數上,B70的整套動力總成,動力還是比較充裕的,無論是買手動還是手自一體車型,穩定性都比較高。

可能在一些朋友眼裡,B級車一直都是20萬出頭,但是你終於不用去湊20萬去買一款B級車了,只要15萬左右的預算就可以買到一款B級車了,而且配置高,顏值高!最近編者的朋友也是握着15W左右的預算想買B級車,就看中了名圖和奔騰B70,問編者怎麼選,下面,就讓我們一起來看看這兩輛車之間如何選擇。

北京現代-名圖

指導價:12.98-17.68萬

一汽奔騰-奔騰B70

指導價:9.98-14.98萬

外觀對比

B70整體延續了家族化的設計風格,以往成熟穩重的造型被一套時尚動感的設計所取代,車身的線條比較圓滑,尾部方面設計簡潔大方,B70採用了雙邊共兩出的排氣管,並配鍍鉻裝飾條,整體視覺感官非常精緻。

名圖在外觀上,看起來並沒有B70那麼運動時尚,而是另外一種商務端莊的范,視覺效果並沒有太大的衝擊,車側與車尾造型之間比較簡潔,流暢,保險杠下緣黑色的塑料處理加上單邊雙出的排氣管,也為該車在穩重之餘增添一絲活力。

內飾對比

B70整體還是走運動時尚路線,中控台的“開口”設計就有些另類,顯得過於浮夸了,但整體的設計還是比較簡潔的,深黑色的內飾配上紅線縫製,更加突顯運動的氛圍。中控台也使用了軟質材料,整體的做工用料還是不錯的。在空間方面,B70還是要比名圖寬敞不少,無論是前排還是後排的的腿部空間的余量還是很大的。在長途乘坐感受上,會比名圖要舒服。

名圖的內飾,比B70的內飾氛圍要高級很多,平直式的中控布局,

綠能、環保無空污,成為 電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

還有大面積的木紋裝飾板點綴,所營造的效果更加沉穩,在儲物空間方面,名圖做的相當到位,手機、錢包。水瓶都能找到放的儲物空間,非常好用。在空間方面,頭部空間比較局促,但是在腿部空間表現相當不錯,翹個二郎腿,都沒什麼問題,符合B級車的水準。

動力對比

B70搭載兩款發動機;2.0L自然吸氣發動機、1.8T渦輪增壓發動機,最大功率108千瓦、137千瓦,最大扭矩184牛米、235牛米,傳動系統方面,配備的是6擋手動和6擋手自一體,在參數上,B70的整套動力總成,動力還是比較充裕的,無論是買手動還是手自一體車型,穩定性都比較高。底盤對路面細微的震動傳遞清晰,整個底盤的調校比較硬朗、偏運動。

名圖搭載了1.8L.2.0L的自然吸氣發動機,1.6T渦輪增壓發動機,最大功率105千瓦、114千瓦、128.7千瓦;最大扭矩176牛米、192牛米、265牛米,在傳動系統方面配備了6擋手動、6擋手自一體、7擋雙離合,可供消費者的選擇很多,就看自己的需求了,在動力上,名圖並不會比B70差,名圖在懸挂和底盤調教上更偏重舒適。對路面的顛簸、起伏路面過濾得更為徹底,無論是前後排的乘坐舒適性都非常不錯。

編者點評:

奔騰B70雖然是自主品牌,但並不會比合資品牌差,而且整體的做工、動力都有很大的提升,性價比也特別高,而名圖呢,可圈可點,整體性也還是不錯,但是看起來商務氣息特別濃,缺乏時尚感,和B70相比,它根本就沒有性價比可談,所以我更推薦奔騰B70,B70更加的親民,喜歡與否就看自己的選擇。汽車圈裡還有這樣的一句話“買韓系車,還不如買自主品牌”。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

除了邁騰帕薩特雅閣 20萬的B級車我們還能選什麼?

99萬上汽大眾斯柯達 速派售價:16。98-27。68萬東風悅達起亞 K5售價:15。98-24。88萬總結:今天所介紹的這幾款中級車都各有特色,但卻是實力出眾的值得購買的車型。最新的C6作為雪鐵龍再次征戰中型車市場的車型,有着優雅大氣的外觀造型與寬敞的空間,配以較低的售價,整體還是很值得購買的。

目前SUV市場是很火,但作為傳統領域的中級車市場仍然保持以往的穩定;這是廠商努力穩住的細分市場;而這個級別當中有着眾多的車型可供選擇;但預算只有20萬的話要想購置一輛品質出眾的中級車還是有難度的,不過看完今天的推薦將會解除你的困惑。

廣汽豐田 凱美瑞

售價:18.48-32.98萬

東風雪鐵龍 C6

售價:18.99-27.99萬

上汽大眾斯柯達 速派

售價:16.98-27.68萬

東風悅達起亞 K5

售價:15.98-24.88萬

總結:今天所介紹的這幾款中級車都各有特色,但卻是實力出眾的值得購買的車型。

最新的C6作為雪鐵龍再次征戰中型車市場的車型,有着優雅大氣的外觀造型與寬敞的空間,配以較低的售價,整體還是很值得購買的。

現款速派已經上市了一段時間,整體外觀造型年輕了不少,出自MQB平台的產品,整體的做工精良,相比邁騰價格更低,除了品牌力差一些之外,其實是一款出眾的車型。

K5當年可謂是一時風頭,只是太過於犀利的外觀造型使得它比較適合年輕消費者,但偏低的起步價使得擁有一輛的中級車的門檻降低了很多。

凱美瑞作為這個級別長年的領跑者,在性價比、可靠性和實際體驗上都沒有明顯的短板,可以說誰選都不會錯的大眾情人,同時遇上網約車新規的出台,就如為凱美瑞定製一般,在跑滴滴方面有着特別的優勢。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

ASP.NET Core Blazor Webassembly 之 路由

web最精妙的設計就是通過url把多個頁面串聯起來,並且可以互相跳轉。我們開發系統的時候總是需要使用路由來實現頁面間的跳轉。傳統的web開發主要是使用a標籤或者是服務端redirect來跳轉。那今天來看看Blazor是如何進行路由的。

使用@page指定組件的路由path

我們可以在Blazor里給每個組件指定一個path,當路由匹配的時候會显示這個組件。

@page "/page/a"

 <h2>
     PAGE A
 </h2>

@code {
 
}

訪問/page/a 看到Page A頁面被渲染出來了。

注意:如果是在瀏覽器里敲入url按回車切換頁面,會發生一次http請求,然後重新渲染blazor應用。

使用a標籤進行頁面跳轉

a標籤作為超鏈接是我們web開發最常用的跳轉方式,blazor同樣支持。
新建Page B

@page "/page/b"

 <h2>
     PAGE B
 </h2>

@code {
 
}

在Page A頁面添加一個a標籤進行跳轉:

@page "/page/a"

 <h2>
     PAGE A
 </h2>
<p>
    <a href="/page/b">Page B</a>
</p>

@code {
 
}

運行一下試試:

注意:使用a連接在頁面間進行跳轉不會發生http請求到後台,頁面是直接在前端渲染出來的。

通過路由傳參

通過http的url進行頁面間傳參是我們web開發的常規操作。下面我們演示下如何從Page A傳遞一個參數到Page B。我們預設Page A裏面有個UserName需要傳遞到Page B,並且显示出來。

通過path傳參

通過url傳參一般有兩種方式,一種是直接把參數組合在path里,比如“/page/b/小明”這樣。

修改Page A:

@page "/page/a"

 <h2>
     PAGE A
 </h2>
<p>
    <a href="/page/b/@userName">Page B</a>
</p>

@code {
    private string userName = "小明";
}

通過把userName組合到path上傳遞給Page B。

修改Page B:

@page "/page/b/{userName}"

 <h2>
     PAGE B
 </h2>
<p>
    userName: @userName
</p>

@code {
    [Parameter]
    public string userName { get; set; }
}

Page B 使用一個“/page/b/{userName}” pattern來匹配userName,並且userName需要標記[Parameter]並且設置為public。

通過QueryString傳參

除了把參數直接拼接在path里,我們還習慣通過QueryString方式傳遞,比如“/page/b?username=小明”。

修改Page A:

@page "/page/a"

<h2>
    PAGE A
</h2>
<p>
    <a href="/page/b?username=@userName">Page B</a>
</p>

@code {
    private string userName = "小明";
}

首先安裝一個工具庫:

Install-Package Microsoft.AspNetCore.WebUtilities -Version 2.2.0

修改Page B:

@page "/page/b"

<h2>
    PAGE B
</h2>
<p>
    userName: @UserName
</p>

@using Microsoft.AspNetCore.WebUtilities;

@inject NavigationManager NavigationManager;

@code {
    [Parameter]
    public string UserName { get; set; }


    protected override void OnInitialized()
    {
        var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
        QueryHelpers.ParseQuery(uri.Query).TryGetValue("username", out Microsoft.Extensions.Primitives.StringValues userName);
        Console.WriteLine(NavigationManager.Uri);
        UserName = userName.ToString();
        Console.WriteLine(UserName);

        base.OnInitialized();
    }
}

頁面獲取QueryString的傳參比較麻煩,Blazor並沒有進行封裝。所以我們需要通過QueryHelpers.ParseQuery方法手工把QueryString格式化成字典形式,然後獲取對應的參數。QueryHelpers類存在Microsoft.AspNetCore.WebUtilities這個庫里,需要通過nuget安裝。

NavLink

NavLink是個導航組件,它其實就是封裝了a標籤。當選中的時候,也就是當前的url跟它的href一致的時候,會自動在class上加上active類,所以可以用來控制選中的樣式。默認的3個導航菜單就是用的NavLink。

比如導航到counter的NavLink:

   <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
    </NavLink>

最後翻譯成html:

<a href="counter" class="nav-link active">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
</a>

NavigationManager

有的時候我們可能需要在代碼里進行導航,如果是JavaScript我們會用window.location來切換頁面,Blazor為我們提供了相應的封裝:NavigationManager。使用NavigationManager可以通過代碼直接進行頁面間的跳轉。我們在Page A頁面放個按鈕然後通過按鈕的點擊事件進行跳轉:

@page "/page/a"

<h2>
    PAGE A
</h2>
<p>
   <button @onclick="GoToB">
       go to B
   </button>
</p>

@inject NavigationManager NavigationManager
@code {

    private void GoToB()
    {
        NavigationManager.NavigateTo("/page/b?username=小貓");
    }

}

修改Page A的代碼,注入NavigationManager對象,通過NavigationManager.NavigateTo方法進行跳轉。

擴展Back方法

Blazor封裝的NavigationManager咋一看以為跟WPF的NavigationService一樣,我想當然的覺得裏面肯定有個Back方法可以進行後退。但是查了一番發現還真的沒有,這就比較尷尬了,沒辦法只能使用JavaScript來實現了。

為了方便我們給NavigationManager直接寫個擴展方法吧。
首先修改Program把IServiceCollection暴露出來:

    public class Program
    {
        public static IServiceCollection Services;

        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
            Services = builder.Services;

            await builder.Build().RunAsync();
        }
    }

擴展類:

  public static class Ext
    {
        public static void Back(this NavigationManager navigation)
        {
            var jsruntime = Program.Services.BuildServiceProvider().GetService<IJSRuntime>();
            jsruntime.InvokeVoidAsync("history.back");
        }
    }

這個擴展方法很簡單,從DI容器里獲取IJSRuntime的實例對象,通過它去調用JavaScript的history.back方法。

修改Page B:

@page "/page/b"

<h2>
    PAGE B
</h2>
<p>
    userName: @UserName
</p>
<p>
    <button @onclick="GoBack">
        Go back
    </button>
</p>

@using Microsoft.AspNetCore.WebUtilities;

@inject NavigationManager NavigationManager;

@code {
    [Parameter]
    public string UserName { get; set; }


    protected override void OnInitialized()
    {
        var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
        QueryHelpers.ParseQuery(uri.Query).TryGetValue("username", out Microsoft.Extensions.Primitives.StringValues userName);
        Console.WriteLine(NavigationManager.Uri);
        UserName = userName.ToString();
        Console.WriteLine(UserName);

        base.OnInitialized();
    }

    private void GoBack()
    {
        NavigationManager.Back();
    }
}

在Page B頁面上添加一個按鈕,點擊調用NavigationManager.Back方法就能回到上一頁。

總結

到此Blazor路由的內容學習的差不多了,整體上沒有什麼特別的,就是NavigationManager只有前進方法沒有後退是比較讓我震驚的。

相關內容:

ASP.NET Core Blazor Webassembly 之 數據綁定
ASP.NET Core Blazor Webassembly 之 組件
ASP.NET Core Blazor 初探之 Blazor WebAssembly
ASP.NET Core Blazor 初探之 Blazor Server

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

想不到7.5萬起車型開起來比14萬的車還更爽!

38-11。28萬本田飛度有着“知乎神車”的稱號,原因10萬以內沒能找到一輛這樣低油耗、加速快以及高保值率的合資車。它使用的是1。5L地球夢發動機,最大功率96千瓦,配合5MT手動變速箱,可以實現9s以內的百公里加速時間,另外還有CVT變速箱可以選擇。

前言

“才六七萬錢的車,能開就行了,還要什麼操控樂趣?”,相信這句話是不少人都聽到過的,認為低價位的汽車都是比較無聊的,但是有沒有想過有些六七萬起售的汽車能給到你超越價格的樂趣,甚至要比貴了一倍價錢的車型還要好玩?

小型車,其實是一個被忽略的市場,在國產緊湊型轎車以及小型SUV的轟炸下,合資小型車的市場份額被逐漸蠶食。以前那種合資小型車百花齊放的景象已經看不到了,前些年年輕人都追求的是這種有着絕佳的操控樂趣小車。

它們都有着小巧的車身,在車流中可以非常靈活地穿梭;有着精準的操控,坐在車內有着一種人車合一的感覺;有着極低的重心,即使在山道也能有着較佳的車身姿態。有着類似卡丁車的操控樂趣,而且還有着較高的改裝潛力。

上汽大眾-pOLO

官方指導價:7.59-14.69萬

作為德系的代表,大眾pOLO有着極高的造詣,小巧的車身配合精準的操控,這輛小型車有着超高的駕駛樂趣,較高速度過彎都能有着一個極佳的姿態。所以在不少車隊甚至是大眾官方都是將其作為賽車車型的首選,價格低操控好而且改裝潛力大。

不過對於家庭用戶來說,後排空間就相對局促。動力方面有着1.4L以及1.6L自然吸氣發動機可以選擇,變速箱則是5MT以及6AT變速箱可以選擇,不過更推薦的是5MT,傳動效率高動力佳,而且檔位清晰,離合容易控制,上手難度低。

廣汽本田-飛度

官方指導價:7.38-11.28萬

本田飛度有着“知乎神車”的稱號,原因10萬以內沒能找到一輛這樣低油耗、加速快以及高保值率的合資車。它使用的是1.5L地球夢發動機,最大功率96千瓦,配合5MT手動變速箱,可以實現9s以內的百公里加速時間,另外還有CVT變速箱可以選擇。

它還有着極其優秀的車廂空間,完全不亞於某些合資緊湊型轎車,加上低油耗的特點非常適合家庭使用。動力強以及手感極好的手動變速箱,讓它有着超高的加速性能,不過過彎的時候側傾稍微有點大,但幸好可以通過改裝改善。

長安鈴木-雨燕

官方指導價:5.98-8.28萬

鈴木雨燕是之前小型車大熱時的產物,到如今那麼多年都僅僅是對外觀進行小小的修改,所以有着當初那種最佳的駕駛樂趣,不過也是因為這麼多年都沒有進行性能上的改進而為人所詬病。發動機方面有着1.3L以及1.5L兩種排量可以選擇,1.5L自然吸氣發動機最大功率76千瓦,相對弱勢。

但配合5MT手動變速箱操控極佳,加上優秀的底盤表現以及改裝潛力使得它是第三代飛度之前最熱的改裝車,如今在不少賽車場都可以看到它的蹤影。並且對於某些改裝發燒友來說,可以無損改裝進口速翼特的M16A發動機也是重大的優勢,動力輸出更加,高轉有着極其優秀的表現和魅力。

編輯總結:

以上三款汽車都可以說是這個價位中操控最好的代表,甚至比起12萬左右的合資緊湊型轎車還要優秀。價格基本可以在不到7萬可以買到,尤其是大眾pOLO,終端起售價甚至可以在6.5萬左右,非常適合追求駕駛樂趣的年輕人。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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

「持續集成實踐系列 」Jenkins 2.x 構建CI自動化流水線常見技巧

在上一篇文章中,我們介紹了Jenkins 2.x實現流水線的兩種語法,以及在實際工作中該如何選擇腳本式語法或聲明式語法。原文可查閱:「持續集成實踐系列」Jenkins 2.x 搭建CI需要掌握的硬核要點(一)

在使用傳統的Jenkins Web界面和項目時,比如自由風格類型的任務,我們對處理流程的控制能力是有限的。所採用的典型形式是任務鏈:任務完成后觸發其他的任務。或者我們可能會包括構建后處理,不管任務成功完成與否,總是去做一些類似發送通知的事情。

除了這些基本的功能外,還可以添加條件性構建步驟插件,通過基於單個或者多個條件的構建步驟來定義更加複雜的流程。但即便如此,相比於我們編寫程序時可以直接控制執行流程的方法,條件性構建步驟插件對流程的控制能力依然有限。

在本篇中,我們將聊一下,關於Jenkins流水線DSL語言所提供的用於控制流水線執行流程基本結構和一些常見技巧。

1. Pipeline流水線指令常見結構

正如在系列第一篇文章中介紹到的,Jenkins DSL採用的是Groovy腳本語言。這也意味着如果當你掌握了Groovy語言,可以按照需求在流水線中使用Groovy語言的結構和習慣用法,針對這一類使用者,通常會更傾向於用腳本式語法來實現流水線。但不管採用的是哪種語法,從流水線組成的角度來講,都是由一些不同指令+步驟構建結構化代碼塊。

對於腳本式流水線,基本結構如下:

node('worker'){
    stage('階段'){
        // DSL
    }
}

構建腳本式流水線常用的結構或者說代碼塊節點主要由nodestage兩個組成。

而,聲明式流水線基本結構構成環節相對要多一些,整理了一張圖如下:

需要劃一個重點:可以簡單理解node是用於腳本式流水線,而agent則是用於聲明式流水線。

Jenkins Pipeline支持的指令(常見):

指令名 說明 作用域
agent 指定流水線或特定階段在哪裡運行。 stage 或pipeline
environment 設置環境變量 stage或pipeline
tools 自動下載並安裝指定的工具,並將其加入到PATH變量中 stage或pipeline
input 暫停pipeline,提示輸入內容 stage
options 用來指定一些預定義選項 stage 或 pipeline
parallel 并行執行多個step stage
parameters 允許執行pipeline前傳入一些參數 pipeline
triggers 定義執行pipeline的觸發器 pipeline
when 定義階段執行的條件 stage
build 觸發其他的job steps

options Jenkins Pipeline常見配置參數:

參數名 說明 例子
buildDiscarder 保留最近歷史構建記錄的數量 buildDiscarder(logRotator(numToKeepStr: ’10’)
timestamps 添加時間戳到控制台輸出 timestamps()
disableConcurrentBuilds 阻止Jenkins併發執行同一個流水線 disableConcurrentBuilds()
retry pipeline發生失敗后重試次數 retry(4)
timeout pipeline運行超時時間 timeout(time:1, unit: ‘HOURS’)

示例:

pipeline{
    agent any
    options{
        buildDiscarder(logRotator(numToKeepStr: '10')
        timestamps()
        retry(3)
        timeout(time:1, unit: 'HOURS')
    }
    stages{
        stage('demo'){
            steps{
                sh 'echo hello'
            }
        }
    }
}

更多pipeline指令,可參見官方介紹:

https://www.jenkins.io/doc/book/pipeline/syntax/#

下述僅挑幾個常用的,用於流水線流程控制選項的指令項,介紹一些常用技巧。

2. 超時(Timeout)

這個timeout步驟允許限制等待某個行為發生時腳本所花費的時間。其語法相當簡單。示例如下:

timeout(time:60,unit:'SECONDS'){
    //該代碼塊中的過程被設置為超時
}

默認的時間單位是min。如果發生超時,該步驟就會拋出一個異常。如果異常沒有被處理,將導致整個流水線過程被中止。

通常推薦的做法是,在使用timeout對任何造成流水線暫停的步驟(如一個input步驟)進行封裝,這樣做的結果是,即使出現差錯導致在限定的時間內沒有得到期望的輸入,流水線也會繼續執行。

示例如下:

node{
    def response
    stage('input'){
        timeout(time:10,unit:'SECONDS'){
            response = input message :'Please Input User'
            parameters:[string(defaultValue:'mikezhou',description:'Enter UserId:',name:'userid')]
        }
        echo "Username = " + response
    }
}

在這種情況下,Jenkins將會給用戶10s做出反應,如果時間到了,Jenkins會拋出一個異常來中止流水線。

如果實際在設計流水線時,當超時發生時,並不想中止流水線向下執行,可以引入try...catch代碼塊來封裝timeout。

如下代碼塊所示:

node{
    def response
    stage('input')
{
      try {
        timeout(time:10,unit:'SECONDS'){
            response = input message :'Please Input User'
            parameters:[string(defaultValue:'mikezhou',description:'Enter UserId:',name:'userid')]
         }
       }
       catch(err){
            response = 'user1'
      }
    }
}

需要注意的是,在處理異常的時候,可以在捕獲異常處設置為期望的默認值。

3. 重試(retry)

這個retry閉包將代碼封底裝為一個步驟,當代碼中有異常發生時,該步驟可以重試n次。其語法如下:

retry(n){
  //代碼過程
}

如果達到重試的限制並且發生了一個異常,那麼整個過程將會被中止(除非異常被處理,如使用try...catch代碼塊)

retry(2){
    try {
       def result=build job: "test_job"
       echo result
      }
    catch(err){
        if(!err.getMessage().contains("UNSTABLE"))
        throw err
    }
}

4. 等待直到(waitUntil)

引入waitUntil步驟,會導致整個過程一直等待某件事發生,通常這裏的“某件事”指的是可以返回true的閉包。

如果代碼過程永不返回true的話,這個步驟將會無期限地等待下去而不會結束。所以一般常見的做法,會結合timeout步驟來封裝waitUntil步驟。

例如,使用waitUntil代碼塊來等待一個標記文件出現:

timeout(time:15,unit:'SECONDS'){
    waitUntil{
        def ret = sh returnStatus:true,script:'test -e /home/jenkins2/marker.txt'
        return (ret==0)
    }
}

再舉一個例子,假如我們要等待一個Docker容器運行起來,以便我們可以在流水線中通過REST API調用獲取一些數據。在這種情況下,如果這個URL還不可用,就會得到一個異常。為了保證異常被拋出的時候進程不會立即退出,我們可以使用try...catch代碼塊來捕獲異常並且返回false。

timeout(time:150,unit:'SECONDS'){
    waitUntil{
        try{
            sh "docker exec ${containerid} curl --silent http://127.0.0.1:8080/api/v1/registry >/test/output/url.txt"
            return true
        }
        catch(err)
            return false
    }
}

5.Stash暫存:實現跨節點文件共享

在Jenkins的DSL中,stashunstash函數允許在流水線的節點間和階段間保存或獲取文件。

基本用法格式:

stash name:"<name>" [includes:"<pattern>" excludes:"<pattern>"]
unstash "<name>"

我們通過名稱或模式來指定一個被包括或被排除的文件的集合。給這些文件的暫存處命名,以便後面通過這個名稱使用這些文件。

提到stash,很多讀者可能會把Jenkins stashGit stash功能弄混,需要說明一下,Jenkins stashGit stash功能是不同的。Git stash函數是為了暫存一個工作目錄的內容,緩存那些還沒有提交到本地代碼倉庫的代碼。而Jenkins stash函數是為了暫存文件,以便在節點間共享。

例如,master節點和node節點,實現跨主機共享文件:

pipeline{
    agent none
    stages{
        stage('stash'){
            agent { label "master" }
            steps{
                writeFile file: "test.txt", text: "$BUILD_NUMBER"
                stash name: "test", includes: "test.txt"
            }
        }
        stage('unstash'){
            agent { label "node" }
            steps{
                script{
                    unstash("test")
                    def content = readFile("test.txt")
                    echo "${content}"
                }
            }
        }
    }
}

如果你覺得文章還不錯,請大家點贊分享下。你的肯定是我最大的鼓勵和支持。

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

【其他文章推薦】

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

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