基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(六)

系列文章

  1. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用 abp cli 搭建項目
  2. 基於 abp vNext 和 .NET Core 開發博客項目 – 給項目瘦身,讓它跑起來
  3. 基於 abp vNext 和 .NET Core 開發博客項目 – 完善與美化,Swagger登場
  4. 基於 abp vNext 和 .NET Core 開發博客項目 – 數據訪問和代碼優先
  5. 基於 abp vNext 和 .NET Core 開發博客項目 – 自定義倉儲之增刪改查
  6. 基於 abp vNext 和 .NET Core 開發博客項目 – 統一規範API,包裝返回模型
  7. 基於 abp vNext 和 .NET Core 開發博客項目 – 再說Swagger,分組、描述、小綠鎖
  8. 基於 abp vNext 和 .NET Core 開發博客項目 – 接入GitHub,用JWT保護你的API
  9. 基於 abp vNext 和 .NET Core 開發博客項目 – 異常處理和日誌記錄
  10. 基於 abp vNext 和 .NET Core 開發博客項目 – 使用Redis緩存數據
  11. 基於 abp vNext 和 .NET Core 開發博客項目 – 集成Hangfire實現定時任務處理
  12. 基於 abp vNext 和 .NET Core 開發博客項目 – 用AutoMapper搞定對象映射
  13. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(一)
  14. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(二)
  15. 基於 abp vNext 和 .NET Core 開發博客項目 – 定時任務最佳實戰(三)
  16. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(一)
  17. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(二)
  18. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(三)
  19. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(四)
  20. 基於 abp vNext 和 .NET Core 開發博客項目 – 博客接口實戰篇(五)
  21. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(一)
  22. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(二)
  23. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(三)
  24. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(四)
  25. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(五)
  26. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(六)
  27. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(七)
  28. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(八)
  29. 基於 abp vNext 和 .NET Core 開發博客項目 – Blazor 實戰系列(九)
  30. 基於 abp vNext 和 .NET Core 開發博客項目 – 終結篇之發布項目

上一篇完成了博客文章詳情頁面的數據展示和基於JWT方式的簡單身份驗證,本篇繼續推進,完成後台分類管理的所有增刪改查等功能。

分類管理

在 Admin 文件夾下新建Razor組件,Categories.razor,設置路由,@page "/admin/categories"。將具體的展示內容放在組件AdminLayout中。

@page "/admin/categories"

<AdminLayout>
      <Loading />
</AdminLayout>

在這裏我會將所有分類展示出來,新增、更新、刪除都會放在一個頁面上去完成。

先將列表查出來,添加API的返回參數,private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;,然後再初始化中去獲取數據。

//QueryCategoryForAdminDto.cs
namespace Meowv.Blog.BlazorApp.Response.Blog
{
    public class QueryCategoryForAdminDto : QueryCategoryDto
    {
        /// <summary>
        /// 主鍵
        /// </summary>
        public int Id { get; set; }
    }
}
/// <summary>
/// API返回的分類列表數據
/// </summary>
private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;

/// <summary>
/// 初始化
/// </summary>
/// <returns></returns>
protected override async Task OnInitializedAsync()
{
    var token = await Common.GetStorageAsync("token");
    Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");

    categories = await FetchData();
}

/// <summary>
/// 獲取數據
/// </summary>
/// <returns></returns>
private async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> FetchData()
{
    return await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>>("/blog/admin/categories");
}

初始化的時候,需要將我們存在localStorage中的token讀取出來,因為我們後台的API都需要添加 Authorization Header 請求頭才能成功返回數據。

在Blazor添加請求頭也是比較方便的,直接Http.DefaultRequestHeaders.Add(...)即可,要注意的是 token值前面需要加 Bearer ,跟了一個空格不可以省略。

獲取數據單獨提成了一個方法FetchData(),因為會頻繁用到,現在在頁面上將數據綁定進行展示。

@if (categories == null)
{
    <Loading />
}
else
{
    <div class="post-wrap categories">
        <h2 class="post-title">-&nbsp;Categories&nbsp;-</h2>
        @if (categories.Success && categories.Result.Any())
        {
            <div class="categories-card">
                @foreach (var item in categories.Result)
                {
                    <div class="card-item">
                        <div class="categories">
                            <NavLink title="刪除" @onclick="@(async () => await DeleteAsync(item.Id))"></NavLink>
                            <NavLink title="編輯" @onclick="@(() => ShowBox(item))"></NavLink>
                            <NavLink target="_blank" href="@($"/category/{item.DisplayName}")">
                                <h3>@item.CategoryName</h3>
                                <small>(@item.Count)</small>
                            </NavLink>
                        </div>
                    </div>
                }
                <div class="card-item">
                    <div class="categories">
                        <NavLink><h3 @onclick="@(() => ShowBox())">~~~ 新增分類 ~~~</h3></NavLink>
                    </div>
                </div>
            </div>
        }
        else
        {
            <ErrorTip />
        }
    </div>
}

同樣的當categories還沒成功獲取到數據的時候,我們直接在展示 <Loading />組件。然後就是循環列表數據在foreach中進行綁定數據。

在每條數據最前面,加了刪除和編輯兩個按鈕,刪除的時候調用DeleteAsync方法,將當前分類的Id傳給他即可。新增和編輯的時候調用ShowBox方法,他接受一個參數,當前循環到的分類對象item,即QueryCategoryForAdminDto

同時這裏考慮到復用性,我寫了一個彈窗組件,Box.Razor,放在Shared文件夾下面,可以先看一下標題為彈窗組件的內容再回來繼續往下看。

刪除分類

接下來看看刪除方法。

/// <summary>
/// 刪除分類
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
private async Task DeleteAsync(int id)
{
    // 彈窗確認
    bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n真的要幹掉這個該死的分類嗎");

    if (confirmed)
    {
        var response = await Http.DeleteAsync($"/blog/category?id={id}");

        var result = await response.Content.ReadFromJsonAsync<ServiceResult>();

        if (result.Success)
        {
            categories = await FetchData();
        }
    }
}

刪除之前搞個原生的confirm進行提示,避免手殘誤刪。因為API那邊使用的是HttpDelete,所有我們調用API時候要用Http.DeleteAsync,返回的是HttpResponseMessage對象,需要我們手動處理接收返回數據,將其轉換為ServiceResult對象,如果判斷刪除成功后重新調用FetchData()刷新分類數據。

新增/更新分類

新增和更新數據選擇使用彈窗的方式來進行(彈窗組件在下方),首先是需要一個參數判斷彈窗是否打開,因為是將新增和更新放在一起,所以如何判斷是新增還是更新呢?這裏使用Id來進行判斷,當編輯的時候肯定會有Id參數。新增的時候是沒有參數傳遞的。

當我們打開彈窗后裏面需要展示兩個input框,用來供輸入要保存的數據,同樣是添加兩個變量。

添加所需的這幾個參數。

/// <summary>
/// 默認隱藏Box
/// </summary>
private bool Open { get; set; } = false;

/// <summary>
/// 新增或者更新時候的分類字段值
/// </summary>
private string categoryName, displayName;

/// <summary>
/// 更新分類的Id值
/// </summary>
private int id;

現在可以將Box組件添加到頁面上。

<div class="post-wrap categories">
	...
</div>

<Box OnClickCallback="@SubmitAsync" Open="@Open">
    <div class="box-item">
        <b>DisplayName:</b><input type="text" @bind="@displayName" @bind:event="oninput" />
    </div>
    <div class="box-item">
        <b>CategoryName:</b><input type="text" @bind="@categoryName" @bind:event="oninput" />
    </div>
</Box>

確定按鈕回調事件執行SubmitAsync()方法,打開狀態參數為上面添加的Open,按鈕文字ButtonText為默認值不填。

添加了兩個input,將兩個分類字段分別綁定上去,使用@bind@bind:event。前者等價於設置其value值,後者等價於一個change事件當值改變後會重新賦給綁定的字段參數。

現在可以來看看點擊了新增或者編輯按鈕的方法ShowBox(...),接收一個參數QueryCategoryForAdminDto讓其默認值為null。

/// <summary>
/// 显示box,綁定字段
/// </summary>
/// <param name="dto"></param>
private void ShowBox(QueryCategoryForAdminDto dto = null)
{
    Open = true;
    id = 0;

    // 新增
    if (dto == null)
    {
        displayName = null;
        categoryName = null;
    }
    else // 更新
    {
        id = dto.Id;
        displayName = dto.DisplayName;
        categoryName = dto.CategoryName;
    }
}

執行ShowBox()方法,將彈窗打開,設置Open = true;和初始化id的值id = 0;

通過參數是否null進行判斷是新增還是更新,這樣打開彈窗就搞定了,剩下的就交給彈窗來處理了。

因為新增和更新API需要還對應的輸入參數EditCategoryInput,去添加它不要忘了。

那麼現在就只差按鈕回調事件SubmitAsync()了,主要是給輸入參數進行賦值調用API,執行新增或者更新即可。

/// <summary>
/// 確認按鈕點擊事件
/// </summary>
/// <returns></returns>
private async Task SubmitAsync()
{
    var input = new EditCategoryInput()
    {
        DisplayName = displayName.Trim(),
        CategoryName = categoryName.Trim()
    };

    if (string.IsNullOrEmpty(input.DisplayName) || string.IsNullOrEmpty(input.CategoryName))
    {
        return;
    }

    var responseMessage = new HttpResponseMessage();

    if (id > 0)
        responseMessage = await Http.PutAsJsonAsync($"/blog/category?id={id}", input);
    else
        responseMessage = await Http.PostAsJsonAsync("/blog/category", input);

    var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();
    if (result.Success)
    {
        categories = await FetchData();
        Open = false;
    }
}

當參數為空時,直接return什麼都不執行。通過當前Id判斷是新增還是更新操作,調用不同的方法PutAsJsonAsyncPostAsJsonAsync去請求API,同樣返回到是HttpResponseMessage對象,最後如果操作成功,重新請求一個數據,刷新分類列表,將彈窗關閉掉。

分類管理頁面的全部代碼如下:

點擊查看代碼

@page "/admin/categories"

<AdminLayout>
    @if (categories == null)
    {
        <Loading />
    }
    else
    {
        <div class="post-wrap categories">
            <h2 class="post-title">-&nbsp;Categories&nbsp;-</h2>
            @if (categories.Success && categories.Result.Any())
            {
                <div class="categories-card">
                    @foreach (var item in categories.Result)
                    {
                        <div class="card-item">
                            <div class="categories">
                                <NavLink title="刪除" @onclick="@(async () => await DeleteAsync(item.Id))"></NavLink>
                                <NavLink title="編輯" @onclick="@(() => ShowBox(item))"></NavLink>
                                <NavLink target="_blank" href="@($"/category/{item.DisplayName}")">
                                    <h3>@item.CategoryName</h3>
                                    <small>(@item.Count)</small>
                                </NavLink>
                            </div>
                        </div>
                    }
                    <div class="card-item">
                        <div class="categories">
                            <NavLink><h3 @onclick="@(() => ShowBox())">~~~ 新增分類 ~~~</h3></NavLink>
                        </div>
                    </div>
                </div>
            }
            else
            {
                <ErrorTip />
            }
        </div>

        <Box OnClickCallback="@SubmitAsync" Open="@Open">
            <div class="box-item">
                <b>DisplayName:</b><input type="text" @bind="@displayName" @bind:event="oninput" />
            </div>
            <div class="box-item">
                <b>CategoryName:</b><input type="text" @bind="@categoryName" @bind:event="oninput" />
            </div>
        </Box>
    }
</AdminLayout>

@code {
    /// <summary>
    /// 默認隱藏Box
    /// </summary>
    private bool Open { get; set; } = false;

    /// <summary>
    /// 新增或者更新時候的分類字段值
    /// </summary>
    private string categoryName, displayName;

    /// <summary>
    /// 更新分類的Id值
    /// </summary>
    private int id;

    /// <summary>
    /// API返回的分類列表數據
    /// </summary>
    private ServiceResult<IEnumerable<QueryCategoryForAdminDto>> categories;

    /// <summary>
    /// 初始化
    /// </summary>
    /// <returns></returns>
    protected override async Task OnInitializedAsync()
    {
        var token = await Common.GetStorageAsync("token");
        Http.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");

        categories = await FetchData();
    }

    /// <summary>
    /// 獲取數據
    /// </summary>
    /// <returns></returns>
    private async Task<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>> FetchData()
    {
        return await Http.GetFromJsonAsync<ServiceResult<IEnumerable<QueryCategoryForAdminDto>>>("/blog/admin/categories");
    }

    /// <summary>
    /// 刪除分類
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    private async Task DeleteAsync(int id)
    {
        Open = false;

        // 彈窗確認
        bool confirmed = await Common.InvokeAsync<bool>("confirm", "\n真的要幹掉這個該死的分類嗎");

        if (confirmed)
        {
            var response = await Http.DeleteAsync($"/blog/category?id={id}");

            var result = await response.Content.ReadFromJsonAsync<ServiceResult>();

            if (result.Success)
            {
                categories = await FetchData();
            }
        }
    }

    /// <summary>
    /// 显示box,綁定字段
    /// </summary>
    /// <param name="dto"></param>
    private void ShowBox(QueryCategoryForAdminDto dto = null)
    {
        Open = true;
        id = 0;

        // 新增
        if (dto == null)
        {
            displayName = null;
            categoryName = null;
        }
        else // 更新
        {
            id = dto.Id;
            displayName = dto.DisplayName;
            categoryName = dto.CategoryName;
        }
    }

    /// <summary>
    /// 確認按鈕點擊事件
    /// </summary>
    /// <returns></returns>
    private async Task SubmitAsync()
    {
        var input = new EditCategoryInput()
        {
            DisplayName = displayName.Trim(),
            CategoryName = categoryName.Trim()
        };

        if (string.IsNullOrEmpty(input.DisplayName) || string.IsNullOrEmpty(input.CategoryName))
        {
            return;
        }

        var responseMessage = new HttpResponseMessage();

        if (id > 0)
            responseMessage = await Http.PutAsJsonAsync($"/blog/category?id={id}", input);
        else
            responseMessage = await Http.PostAsJsonAsync("/blog/category", input);

        var result = await responseMessage.Content.ReadFromJsonAsync<ServiceResult>();
        if (result.Success)
        {
            categories = await FetchData();
            Open = false;
        }
    }
}

彈窗組件

考慮到新增和更新數據的時候需要彈窗,這裏就簡單演示一下寫一個小組件。

在 Shared 文件夾下新建一個Box.razor

在開始之前分析一下彈窗組件所需的元素,彈窗肯定有一個確認和取消按鈕,右上角需要有一個關閉按鈕,關閉按鈕和取消按鈕一個意思。他還需要一個打開或者關閉的狀態,判斷是否打開彈窗,還有就是彈窗內需要自定義展示內容。

確定按鈕的文字可以自定義,所以差不多就需要3個參數,組件內容RenderFragment ChildContent,是否打開彈窗bool Open默認隱藏,按鈕文字string ButtonText默認值給”確定”。然後最重要的是確定按鈕需要一個回調事件,EventCallback<MouseEventArgs> OnClickCallback 用於執行不同的事件。

/// <summary>
/// 組件內容
/// </summary>
[Parameter]
public RenderFragment ChildContent { get; set; }

/// <summary>
/// 是否隱藏
/// </summary>
[Parameter]
public bool Open { get; set; } = true;

/// <summary>
/// 按鈕文字
/// </summary>
[Parameter]
public string ButtonText { get; set; } = "確定";

/// <summary>
/// 確認按鈕點擊事件回調
/// </summary>
[Parameter]
public EventCallback<MouseEventArgs> OnClickCallback { get; set; }

/// <summary>
/// 關閉Box
/// </summary>
private void Close() => Open = false;

右上角關閉和取消按鈕直接在內部進行處理,執行Close()方法,將參數Open值設置為false即可。

對應的html如下。

@if (Open)
{
    <div class="shadow"></div>
    <div class="box">
        <div class="close" @onclick="Close"></div>
        <div class="box-content">
            @ChildContent
            <div class="box-item box-item-btn">
                <button class="box-btn" @onclick="OnClickCallback">@ButtonText</button>
                <button class="box-btn btn-primary" @onclick="Close">取消</button>
            </div>
        </div>
    </div>
}

關於樣式

下面是彈窗組件所需的樣式代碼,大家需要的自取,也可以直接去GitHub實時獲取最新的樣式文件。

.box {
    width: 600px;
    height: 300px;
    border-radius: 5px;
    background-color: #fff;
    position: fixed;
    top: 50%;
    left: 50%;
    margin-top: -150px;
    margin-left: -300px;
    z-index: 997;
}
.close {
    position: absolute;
    right: 3px;
    top: 2px;
    cursor: pointer;
}
.shadow {
    width: 100%;
    height: 100%;
    position: fixed;
    left: 0;
    top: 0;
    z-index: 996;
    background-color: #000;
    opacity: 0.3;
}
.box-content {
    width: 90%;
    margin: 20px auto;
}
.box-item {
    margin-top: 10px;
    height: 30px;
}
.box-item b {
    width: 130px;
    display: inline-block;
}
.box-item input[type=text] {
    padding-left: 5px;
    width: 300px;
    height: 30px;
}
.box-item label {
    width: 100px;
    white-space: nowrap;
}
.box-item input[type=radio] {
    width: auto;
    height: auto;
    visibility: initial;
    display: initial;
    margin-right: 2px;
}
.box-item button {
    height: 30px;
    width: 100px;
}
.box-item-btn {
    position: absolute;
    right: 20px;
    bottom: 20px;
}
.box-btn {
    display: inline-block;
    height: 30px;
    line-height: 30px;
    padding: 0 18px;
    background-color: #5A9600;
    color: #fff;
    white-space: nowrap;
    text-align: center;
    font-size: 14px;
    border: none;
    border-radius: 2px;
    cursor: pointer;
}
button:focus {
    outline: 0;
}
.box-btn:hover {
    opacity: .8;
    filter: alpha(opacity=80);
    color: #fff;
}
.btn-primary {
    border: 1px solid #C9C9C9;
    background-color: #fff;
    color: #555;
}
.btn-primary:hover {
    border-color: #5A9600;
    color: #333;
}
.post-box {
    width: 98%;
    margin: 27px auto 0;
}
.post-box-item {
    width: 100%;
    height: 30px;
    margin-bottom: 5px;
}
.post-box-item input {
    width: 49.5%;
    height: 30px;
    padding-left: 5px;
    border: 1px solid #ddd;
}
.post-box-item input:nth-child(1) {
    float: left;
    margin-right: 1px;
}
.post-box-item input:nth-child(2) {
    float: right;
    margin-left: 1px;
}
.post-box .box-item b {
    width: auto;
}
.post-box .box-item input[type=text] {
    width: 90%;
}

好了,分類模塊的功能都完成了,標籤和友情鏈接的管理界面還會遠嗎?這兩個模塊的做法和分類是一樣的,有興趣的可以自己動手完成,今天到這吧,未完待續…

開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

上周熱點回顧(6.8-6.14)

熱點隨筆:

· 真慘!連各大編程語言都擺起地攤了! (軒轅之風)
· .NET開發者省份分佈排名 (張善友)
· C#9.0 終於來了,您還學的動嗎? 帶上VS一起解讀吧!(應該是全網第一篇) (一線碼農)
· WinUI 3 試玩報告 (dino.c)
· [C#.NET 拾遺補漏]04:你必須知道的反射 (LiamWang)
· 120行代碼打造.netcore生產力工具-小而美的後台異步組件 (福祿網絡技術團隊)
· .Net Core實戰之基於角色的訪問控制的設計 (陳珙)
· 面試了 6 輪 Google 中國 之後,還是掛了 (程序猿石頭)
· 手動造輪子——基於.NetCore的RPC框架DotNetCoreRpc (yi念之間)
· .Net Core微服務入門全紀錄(一)——項目搭建 (xhznl)
· 搭上末班車去了京東,終於可以做東哥兄弟… (龍躍十二)
· 8000字長文讓你徹底了解 Java 8 的 Lambda、函數式接口、Stream 用法和原理 (風的姿態)

熱點新聞:

· 中國最慘創業者:3年前我被投資人趕出公司,3年後讓我賠3800萬!
· 馬化騰每天刷Leetcode?代碼你打算寫到幾歲?
· Linux之父親手拒補丁、怒懟亞馬遜程序員 網友:我的快樂又回來了
· 一場屬於少數人的復蘇
· 打破Oracle垄斷!支付寶OceanBase獨立:年內將發布重大版本升級
· 京東真正上岸了
· 關閉微信朋友圈個性化廣告有多難?13 個步驟點擊 16 次,只能關半年
· 27歲程序員轉職賞金獵人:一個漏洞10萬美元,比工資香多了
· .NET 5.0 Preview 5 發布
· 好評率84%,微軟工作室翻身大作《盜賊之海》登陸Steam!
· 阿里雲年度戰略首次公開!中台做厚,硬件擴張,還要再招5000人
· 分析了上千張照片發現:R語言程序員最快樂,Java開發者最年輕

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

重學 Java 設計模式:實戰享元模式「基於Redis秒殺,提供活動與庫存信息查詢場景」

作者:小傅哥
博客:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

程序員‍‍的上下文是什麼?

很多時候一大部分編程開發的人員都只是關注於功能的實現,只要自己把這部分需求寫完就可以了,有點像被動的交作業。這樣的問題一方面是由於很多新人還不了解程序員的職業發展,還有一部分是對於編程開發只是工作並非興趣。但在程序員的發展來看,如果不能很好的處理上文(產品),下文(測試),在這樣不能很好的了解業務和產品發展,也不能編寫出很有體繫結構的代碼,日久天長,1到3年、3到5年,就很難跨越一個個技術成長的分水嶺。

擁有接受和學習新知識的能力

你是否有感受過小時候在什麼都還不會的時候接受知識的能力很強,但隨着我們開始長大后,慢慢學習能力、處事方式、性格品行,往往會固定。一方面是形成了各自的性格特徵,一方面是圈子已經固定。但也正因為這樣的故步,而很少願意聽取別人的意見,就像即使看到了一整片內容,在視覺盲區下也會過掉到80%,就在眼前也看不見,也因此導致了能力不再有較大的提升。

編程能力怎樣會成長的最快

工作內容往往有些像在工廠擰螺絲,大部分內容是重複的,也可以想象過去的一年你有過多少創新和學習了新的技能。那麼這時候一般為了多學些內容會買一些技術書籍,但!技術類書籍和其他書籍不同,只要不去用看了也就只是輕描淡寫,很難接納和理解。就像設計模式,雖然可能看了幾遍,但是在實際編碼中仍然很少會用,大部分原因還是沒有認認真真的跟着實操。事必躬親才是學習編程的最好是方式。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-11-01 使用一坨代碼實現業務需求
itstack-demo-design-11-02 通過設計模式優化代碼結構,減少內存使用和查詢耗時

三、享元模式介紹

享元模式,主要在於共享通用對象,減少內存的使用,提升系統的訪問效率。而這部分共享對象通常比較耗費內存或者需要查詢大量接口或者使用數據庫資源,因此統一抽離作為共享對象使用。

另外享元模式可以分為在服務端和客戶端,一般互聯網H5和Web場景下大部分數據都需要服務端進行處理,比如數據庫連接池的使用、多線程線程池的使用,除了這些功能外,還有些需要服務端進行包裝后的處理下發給客戶端,因為服務端需要做享元處理。但在一些遊戲場景下,很多都是客戶端需要進行渲染地圖效果,比如;樹木、花草、魚蟲,通過設置不同元素描述使用享元公用對象,減少內存的佔用,讓客戶端的遊戲更加流暢。

在享元模型的實現中需要使用到享元工廠來進行管理這部分獨立的對象和共享的對象,避免出現線程安全的問題。

四、案例場景模擬

在這個案例中我們模擬在商品秒殺場景下使用享元模式查詢優化

你是否經歷過一個商品下單的項目從最初的日均十幾單到一個月後每個時段秒殺量破十萬的項目。一般在最初如果沒有經驗的情況下可能會使用數據庫行級鎖的方式下保證商品庫存的扣減操作,但是隨着業務的快速發展秒殺的用戶越來越多,這個時候數據庫已經扛不住了,一般都會使用redis的分佈式鎖來控制商品庫存。

同時在查詢的時候也不需要每一次對不同的活動查詢都從庫中獲取,因為這裏除了庫存以外其他的活動商品信息都是固定不變的,以此這裏一般大家會緩存到內存中。

這裏我們模擬使用享元模式工廠結構,提供活動商品的查詢。活動商品相當於不變的信息,而庫存部分屬於變化的信息。

五、用一坨坨代碼實現

邏輯很簡單,就怕你寫亂。一片片的固定內容和變化內容的查詢組合,CV的哪裡都是!

其實這部分邏輯的查詢在一般情況很多程序員都是先查詢固定信息,在使用過濾的或者添加if判斷的方式補充變化的信息,也就是庫存。這樣寫最開始並不會看出來有什麼問題,但隨着方法邏輯的增加,後面就越來越多重複的代碼。

1. 工程結構

itstack-demo-design-11-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── ActivityController.java
  • 以上工程結構比較簡單,之後一個控制類用於查詢活動信息。

2. 代碼實現

/**
 * 博客:https://bugstack.cn - 沉澱、分享、成長,讓自己和他人都能有所收穫!
 * 公眾號:bugstack蟲洞棧
 * Create by 小傅哥(fustack) @2020
 */
public class ActivityController {

    public Activity queryActivityInfo(Long id) {
        // 模擬從實際業務應用從接口中獲取活動信息
        Activity activity = new Activity();
        activity.setId(10001L);
        activity.setName("圖書嗨樂");
        activity.setDesc("圖書優惠券分享激勵分享活動第二期");
        activity.setStartTime(new Date());
        activity.setStopTime(new Date());
        activity.setStock(new Stock(1000,1));
        return activity;
    }

}
  • 這裏模擬的是從接口中查詢活動信息,基本也就是從數據庫中獲取所有的商品信息和庫存。有點像最開始寫的商品銷售系統,數據庫就可以抗住購物量。
  • 當後續因為業務的發展需要擴展代碼將庫存部分交給redis處理,那麼久需要從redis中獲取活動的庫存,而不是從庫中,否則將造成數據不統一的問題。

六、享元模式重構代碼

接下來使用享元模式來進行代碼優化,也算是一次很小的重構。

享元模式一般情況下使用此結構在平時的開發中並不太多,除了一些線程池、數據庫連接池外,再就是遊戲場景下的場景渲染。另外這個設計的模式思想是減少內存的使用提升效率,與我們之前使用的原型模式通過克隆對象的方式生成複雜對象,減少rpc的調用,都是此類思想。

1. 工程結構

itstack-demo-design-11-02
└── src
    ├── main
    │   └── java
    │       └── org.itstack.demo.design
    │           ├── util
    │           │	└── RedisUtils.java	
    │           ├── Activity.java
    │           ├── ActivityController.java
    │           ├── ActivityFactory.java
    │           └── Stock.java
    └── test
        └── java
            └── org.itstack.demo.test
                └── ApiTest.java

享元模式模型結構

  • 以上是我們模擬查詢活動場景的類圖結構,左側構建的是享元工廠,提供固定活動數據的查詢,右側是Redis存放的庫存數據。
  • 最終交給活動控制類來處理查詢操作,並提供活動的所有信息和庫存。因為庫存是變化的,所以我們模擬的RedisUtils中設置了定時任務使用庫存。

2. 代碼實現

2.1 活動信息

public class Activity {

    private Long id;        // 活動ID
    private String name;    // 活動名稱
    private String desc;    // 活動描述
    private Date startTime; // 開始時間
    private Date stopTime;  // 結束時間
    private Stock stock;    // 活動庫存
    
    // ...get/set
}
  • 這裏的對象類比較簡單,只是一個活動的基礎信息;id、名稱、描述、時間和庫存。

2.2 庫存信息

public class Stock {

    private int total; // 庫存總量
    private int used;  // 庫存已用
    
    // ...get/set
}
  • 這裡是庫存數據我們單獨提供了一個類進行保存數據。

2.3 享元工廠

public class ActivityFactory {

    static Map<Long, Activity> activityMap = new HashMap<Long, Activity>();

    public static Activity getActivity(Long id) {
        Activity activity = activityMap.get(id);
        if (null == activity) {
            // 模擬從實際業務應用從接口中獲取活動信息
            activity = new Activity();
            activity.setId(10001L);
            activity.setName("圖書嗨樂");
            activity.setDesc("圖書優惠券分享激勵分享活動第二期");
            activity.setStartTime(new Date());
            activity.setStopTime(new Date());
            activityMap.put(id, activity);
        }
        return activity;
    }

}
  • 這裏提供的是一個享元工廠,通過map結構存放已經從庫表或者接口中查詢到的數據,存放到內存中,用於下次可以直接獲取。
  • 這樣的結構一般在我們的編程開發中還是比較常見的,當然也有些時候為了分佈式的獲取,會把數據存放到redis中,可以按需選擇。

2.4 模擬Redis類

public class RedisUtils {

    private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

    private AtomicInteger stock = new AtomicInteger(0);

    public RedisUtils() {
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            // 模擬庫存消耗
            stock.addAndGet(1);
        }, 0, 100000, TimeUnit.MICROSECONDS);

    }

    public int getStockUsed() {
        return stock.get();
    }

}
  • 這裏處理模擬redis的操作工具類外,還提供了一個定時任務用於模擬庫存的使用,這樣方面我們在測試的時候可以觀察到庫存的變化。

2.4 活動控制類

public class ActivityController {

    private RedisUtils redisUtils = new RedisUtils();

    public Activity queryActivityInfo(Long id) {
        Activity activity = ActivityFactory.getActivity(id);
        // 模擬從Redis中獲取庫存變化信息
        Stock stock = new Stock(1000, redisUtils.getStockUsed());
        activity.setStock(stock);
        return activity;
    }

}
  • 在活動控制類中使用了享元工廠獲取活動信息,查詢后將庫存信息在補充上。因為庫存信息是變化的,而活動信息是固定不變的。
  • 最終通過統一的控制類就可以把完整包裝后的活動信息返回給調用方。

3. 測試驗證

3.1 編寫測試類

public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    private ActivityController activityController = new ActivityController();

    @Test
    public void test_queryActivityInfo() throws InterruptedException {
        for (int idx = 0; idx < 10; idx++) {
            Long req = 10001L;
            Activity activity = activityController.queryActivityInfo(req);
            logger.info("測試結果:{} {}", req, JSON.toJSONString(activity));
            Thread.sleep(1200);
        }
    }

}
  • 這裏我們通過活動查詢控制類,在for循環的操作下查詢了十次活動信息,同時為了保證庫存定時任務的變化,加了睡眠操作,實際的開發中不會有這樣的睡眠。

3.2 測試結果

22:35:20.285 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":1},"stopTime":1592130919931}
22:35:21.634 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":18},"stopTime":1592130919931}
22:35:22.838 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":30},"stopTime":1592130919931}
22:35:24.042 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":42},"stopTime":1592130919931}
22:35:25.246 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":54},"stopTime":1592130919931}
22:35:26.452 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":66},"stopTime":1592130919931}
22:35:27.655 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":78},"stopTime":1592130919931}
22:35:28.859 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":90},"stopTime":1592130919931}
22:35:30.063 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":102},"stopTime":1592130919931}
22:35:31.268 [main] INFO  org.i..t.ApiTest - 測試結果:10001 {"desc":"圖書優惠券分享激勵分享活動第二期","id":10001,"name":"圖書嗨樂","startTime":1592130919931,"stock":{"total":1000,"used":114},"stopTime":1592130919931}

Process finished with exit code 0
  • 可以仔細看下stock部分的庫存是一直在變化的,其他部分是活動信息,是固定的,所以我們使用享元模式來將這樣的結構進行拆分。

七、總結

  • 關於享元模式的設計可以着重學習享元工廠的設計,在一些有大量重複對象可復用的場景下,使用此場景在服務端減少接口的調用,在客戶端減少內存的佔用。是這個設計模式的主要應用方式。
  • 另外通過map結構的使用方式也可以看到,使用一個固定id來存放和獲取對象,是非常關鍵的點。而且不只是在享元模式中使用,一些其他工廠模式、適配器模式、組合模式中都可以通過map結構存放服務供外部獲取,減少ifelse的判斷使用。
  • 當然除了這種設計的減少內存的使用優點外,也有它帶來的缺點,在一些複雜的業務處理場景,很不容易區分出內部和外部狀態,就像我們活動信息部分與庫存變化部分。如果不能很好的拆分,就會把享元工廠設計的非常混亂,難以維護。

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙集群升級場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰橋接模式(多支付渠道「微信、支付寶」與多支付模式「刷臉、指紋」場景)
  • 6. 重學 Java 設計模式:實戰組合模式(營銷差異化人群發券,決策樹引擎搭建場景)

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

使用Apache Spark和Apache Hudi構建分析數據湖

1. 引入

大多數現代數據湖都是基於某種分佈式文件系統(DFS),如HDFS或基於雲的存儲,如AWS S3構建的。遵循的基本原則之一是文件的“一次寫入多次讀取”訪問模型。這對於處理海量數據非常有用,如數百GB到TB的數據。

但是在構建分析數據湖時,更新數據並不罕見。根據不同場景,這些更新頻率可能是每小時一次,甚至可能是每天或每周一次。另外可能還需要在最新視圖、包含所有更新的歷史視圖甚至僅是最新增量視圖上運行分析。

通常這會導致使用用於流和批處理的多個系統,前者處理增量數據,而後者處理歷史數據。

處理存儲在HDFS上的數據時,維護增量更新的常見工作流程是這裏所述的Ingest-Reconcile-Compact-Purge策略。

Apache Hudi之類的框架在這裏便可發揮作用。它在後台為我們管理此工作流程,從而使我們的核心應用程序代碼更加簡潔,Hudi支持對最新數據視圖的查詢以及查詢在某個時間點的增量更改。

這篇文章將介紹Hudi的核心概念以及如何在Copy-On-Write模式下進行操作。

本篇文章項目源代碼放在github。

2. 大綱

  • 先決條件和框架版本
  • Hudi核心概念
  • 初始設置和依賴項
  • 使用CoW表

2.1 先決條件和框架版本

如果你事先了解如何使用scala編寫spark作業以及讀取和寫入parquet文件,那麼本篇文章理解起來將非常容易。

框架版本如下

  • JDK: openjdk 1.8.0_242
  • Scala: 2.12.8
  • Spark: 2.4.4
  • Hudi Spark bundle: 0.5.2-incubating

注意:在撰寫本文時,AWS EMR與Hudi v0.5.0-incubating集成在一起,該軟件包具有一個bug會導致upsert操作卡死或花費很長時間才能完成,可查看相關issue了解更多,該問題已在當前版本的Hudi(0.5.2-incubating及之後版本)中修復。如果計劃在AWS EMR上運行代碼,則可能要考慮用最新版本覆蓋默認的集成版本。

2.2 Hudi核心概念

先從一些需要理解的核心概念開始。

1. 表類型

Hudi支持兩種表類型

  • 寫時複製(CoW):寫入CoW表時,將運行Ingest-Reconcile-Compact-Purge周期。每次寫操作后,CoW表中的數據始終是最新記錄,對於需要儘快讀取最新數據的場景,可首選此模式。數據僅以列文件格式(parquet)存儲在CoW表中,由於每個寫操作都涉及壓縮和覆蓋,因此此模式產生的文件最少。

  • 讀時合併(MoR):MoR表專註於快速寫操作。寫入這些表將創建增量文件,隨後將其壓縮以生成讀取時的最新數據,壓縮操作可以同步或異步完成,數據以列文件格式(parquet)和基於行的文件格式(avro)組合存儲。

這是Hudi文檔中提到的兩種表格格式之間的權衡取捨。

Trade-off CoW MoR
數據延遲 Higher Lower
更新開銷 (I/O) Higher (重寫整個parquet文件) Lower (追加到delta log文件)
Parquet文件大小 Smaller (高update(I/0) 開銷) Larger (低更新開銷)
Write Amplification Higher Lower (由compaction策略決定)

2. 查詢類型

Hudi支持兩種主要類型的查詢:“快照查詢”和“增量查詢”。除兩種主要查詢類型外,MoR表還支持“讀優化查詢”。

  • 快照查詢:對於CoW表,快照查詢返回數據的最新視圖,而對於MoR表,則返回接近實時的視圖。 對於MoR表,快照查詢將即時合併基本文件和增量文件,因此可能會有一些讀取延遲。使用CoW,由於寫入負責合併,因此讀取很快,只需要讀取基本文件。

  • 增量查詢:增量查詢使您可以通過指定“開始”時間或在特定時間點通過指定“開始”和“結束”時間來查看特定提交時間之後的數據。

  • 讀優化查詢:對於MoR表,讀取優化查詢返回一個視圖,該視圖僅包含基本文件中的數據,而不合併增量文件。

3. 以Hudi格式寫入時的關鍵屬性

  • hoodie.datasource.write.table.type,定義表的類型-默認值為COPY_ON_WRITE。對於MoR表,將此值設置為MERGE_ON_READ。

  • hoodie.table.name,這是必填字段,每個表都應具有唯一的名稱。

  • hoodie.datasource.write.recordkey.field,將此視為表的主鍵。此屬性的值是DataFrame中列的名稱,該列是主鍵。

  • hoodie.datasource.write.precombine.field,更新數據時,如果存在兩個具有相同主鍵的記錄,則此列中的值將決定更新哪個記錄。選擇諸如時間戳記的列將確保選擇具有最新時間戳記的記錄。

  • hoodie.datasource.write.operation,定義寫操作的類型。值可以為upsert,insert,bulk_insert和delete,默認值為upsert。

2.3 初始設置和依賴項

1. 依賴說明

為了在Spark作業中使用Hudi,需要使用spark-sql,hudi-spark-bundle和spark-avro依賴項,此外還需要將Spark配置為使用KryoSerializer。

pom.xml大致內容如下

<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <encoding>UTF-8</encoding>
    <scala.version>2.12.8</scala.version>
    <scala.compat.version>2.12</scala.compat.version>
    <spec2.version>4.2.0</spec2.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
        <version>${scala.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql_${scala.compat.version}</artifactId>
        <version>2.4.4</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hudi</groupId>
        <artifactId>hudi-spark-bundle_${scala.compat.version}</artifactId>
        <version>0.5.2-incubating</version>
    </dependency>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-avro_${scala.compat.version}</artifactId>
        <version>2.4.4</version>
    </dependency>
</dependencies>

2. 設置Schema

我們使用下面的Album類來表示表的schema。

case class Album(albumId: Long, title: String, tracks: Array[String], updateDate: Long)   

3. 生成測試數據

創建一些用於upsert操作的數據。

  • INITIAL_ALBUM_DATA有兩個記錄,鍵為801。
  • UPSERT_ALBUM_DATA包含一個更新的記錄和兩個新的記錄。
def dateToLong(dateString: String): Long = LocalDate.parse(dateString, formatter).toEpochDay

private val INITIAL_ALBUM_DATA = Seq(
    Album(800, "6 String Theory", Array("Lay it down", "Am I Wrong", "68"), dateToLong("2019-12-01")),
    Album(801, "Hail to the Thief", Array("2+2=5", "Backdrifts"), dateToLong("2019-12-01")),
    Album(801, "Hail to the Thief", Array("2+2=5", "Backdrifts", "Go to sleep"), dateToLong("2019-12-03"))
  )

  private val UPSERT_ALBUM_DATA = Seq(
    Album(800, "6 String Theory - Special", Array("Jumpin' the blues", "Bluesnote", "Birth of blues"), dateToLong("2020-01-03")),
    Album(802, "Best Of Jazz Blues", Array("Jumpin' the blues", "Bluesnote", "Birth of blues"), dateToLong("2020-01-04")),
    Album(803, "Birth of Cool", Array("Move", "Jeru", "Moon Dreams"), dateToLong("2020-02-03"))
  )

4. 初始化SparkContext

最後初始化Spark上下文。這裏要注意的重要一點是KryoSerializer的使用。

val spark: SparkSession = SparkSession.builder()
    .appName("hudi-datalake")
    .master("local[*]")
    .config("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
    .config("spark.sql.hive.convertMetastoreParquet", "false") // Uses Hive SerDe, this is mandatory for MoR tables
    .getOrCreate()

2.4 使用CoW表

本節將處理CoW表的記錄,如讀取和刪除記錄。

1. basePath(基本路徑)和Upsert方法

定義一個basePath,upsert方法會將表數據寫入該路徑,該方法將以org.apache.hudi格式寫入Dataframe,請確保上面討論的所有Hudi屬性均已設置。

val basePath = "/tmp/store"

private def upsert(albumDf: DataFrame, tableName: String, key: String, combineKey: String) = {
    albumDf.write
      .format("hudi")
      .option(DataSourceWriteOptions.TABLE_TYPE_OPT_KEY, DataSourceWriteOptions.COW_TABLE_TYPE_OPT_VAL)
      .option(DataSourceWriteOptions.RECORDKEY_FIELD_OPT_KEY, key)
      .option(DataSourceWriteOptions.PRECOMBINE_FIELD_OPT_KEY, combineKey)
      .option(HoodieWriteConfig.TABLE_NAME, tableName)
      .option(DataSourceWriteOptions.OPERATION_OPT_KEY, DataSourceWriteOptions.UPSERT_OPERATION_OPT_VAL)
      // Ignore this property for now, the default is too high when experimenting on your local machine
      // Set this to a lower value to improve performance.
      // I'll probably cover Hudi tuning in a separate post.
      .option("hoodie.upsert.shuffle.parallelism", "2")
      .mode(SaveMode.Append)
      .save(s"$basePath/$tableName/")
  }

2. 初始化upsert

插入INITIAL_ALBUM_DATA,我們應該創建2條記錄,對於801,該記錄的日期為2019-12-03。

val tableName = "Album"
upsert(INITIAL_ALBUM_DATA.toDF(), tableName, "albumId", "updateDate")
spark.read.format("hudi").load(s"$basePath/$tableName/*").show()

讀取CoW表就像使用格式(“hudl”)的常規spark.read一樣簡單。

// Output
+-------------------+--------------------+------------------+----------------------+--------------------+-------+-----------------+--------------------+----------+
|_hoodie_commit_time|_hoodie_commit_seqno|_hoodie_record_key|_hoodie_partition_path|   _hoodie_file_name|albumId|            title|              tracks|updateDate|
+-------------------+--------------------+------------------+----------------------+--------------------+-------+-----------------+--------------------+----------+
|     20200412182343|  20200412182343_0_1|               801|               default|65841d0a-0083-447...|    801|Hail to the Thief|[2+2=5, Backdrift...|     18233|
|     20200412182343|  20200412182343_0_2|               800|               default|65841d0a-0083-447...|    800|  6 String Theory|[Lay it down, Am ...|     18231|
+-------------------+--------------------+------------------+----------------------+--------------------+-------+-----------------+--------------------+----------+

另一種確定的方法是查看Workload profile的日誌輸出,內容大致如下

Workload profile :WorkloadProfile {globalStat=WorkloadStat {numInserts=2, numUpdates=0}, partitionStat={default=WorkloadStat {numInserts=2, numUpdates=0}}}

3. 更新記錄

upsert(UPSERT_ALBUM_DATA.toDF(), tableName, "albumId", "updateDate")

查看Workload profile的日誌輸出,並驗證它是否符合預期

Workload profile :WorkloadProfile {globalStat=WorkloadStat {numInserts=2, numUpdates=1}, partitionStat={default=WorkloadStat {numInserts=2, numUpdates=1}}}

查詢輸出如下

spark.read.format("hudi").load(s"$basePath/$tableName/*").show()

//Output
+-------------------+--------------------+------------------+----------------------+--------------------+-------+--------------------+--------------------+----------+
|_hoodie_commit_time|_hoodie_commit_seqno|_hoodie_record_key|_hoodie_partition_path|   _hoodie_file_name|albumId|               title|              tracks|updateDate|
+-------------------+--------------------+------------------+----------------------+--------------------+-------+--------------------+--------------------+----------+
|     20200412183510|  20200412183510_0_1|               801|               default|65841d0a-0083-447...|    801|   Hail to the Thief|[2+2=5, Backdrift...|     18233|
|     20200412184040|  20200412184040_0_1|               800|               default|65841d0a-0083-447...|    800|6 String Theory -...|[Jumpin' the blue...|     18264|
|     20200412184040|  20200412184040_0_2|               802|               default|65841d0a-0083-447...|    802|  Best Of Jazz Blues|[Jumpin' the blue...|     18265|
|     20200412184040|  20200412184040_0_3|               803|               default|65841d0a-0083-447...|    803|       Birth of Cool|[Move, Jeru, Moon...|     18295|
+-------------------+--------------------+------------------+----------------------+--------------------+-------+--------------------+--------------------+----------+

4. 查詢記錄

我們在上面查看數據的方式稱為“快照查詢”,這是默認設置,另外還支持“增量查詢”。

4.1 增量查詢

要執行增量查詢,我們需要在讀取時將hoodie.datasource.query.type屬性設置為incremental,並指定hoodie.datasource.read.begin.instanttime屬性。 這將在指定的即時時間之後讀取所有記錄,對於本示例,我們將instantTime指定為20200412183510

spark.read
    .format("hudi")
    .option(DataSourceReadOptions.QUERY_TYPE_OPT_KEY, DataSourceReadOptions.QUERY_TYPE_INCREMENTAL_OPT_VAL)
    .option(DataSourceReadOptions.BEGIN_INSTANTTIME_OPT_KEY, "20200412183510")
    .load(s"$basePath/$tableName")
    .show()

這將在提交時間20200412183510之後返回所有記錄。

+-------------------+--------------------+------------------+----------------------+--------------------+-------+--------------------+--------------------+----------+
|_hoodie_commit_time|_hoodie_commit_seqno|_hoodie_record_key|_hoodie_partition_path|   _hoodie_file_name|albumId|               title|              tracks|updateDate|
+-------------------+--------------------+------------------+----------------------+--------------------+-------+--------------------+--------------------+----------+
|     20200412184040|  20200412184040_0_1|               800|               default|65841d0a-0083-447...|    800|6 String Theory -...|[Jumpin' the blue...|     18264|
|     20200412184040|  20200412184040_0_2|               802|               default|65841d0a-0083-447...|    802|  Best Of Jazz Blues|[Jumpin' the blue...|     18265|
|     20200412184040|  20200412184040_0_3|               803|               default|65841d0a-0083-447...|    803|       Birth of Cool|[Move, Jeru, Moon...|     18295|
+-------------------+--------------------+------------------+----------------------+--------------------+-------+--------------------+--------------------+----------+

5. 刪除記錄

我們要查看的最後一個操作是刪除,刪除類似於upsert,需要一個待刪除記錄的DataFrame,如下面的示例代碼所示,不需要整行,只需要主鍵即可。

val deleteKeys = Seq(
    Album(803, "", null, 0l),
    Album(802, "", null, 0l)
)

import spark.implicits._

val df = deleteKeys.toDF()

df.write.format("hudi")
    .option(DataSourceWriteOptions.TABLE_TYPE_OPT_KEY, DataSourceWriteOptions.COW_TABLE_TYPE_OPT_VAL)
    .option(DataSourceWriteOptions.RECORDKEY_FIELD_OPT_KEY, "albumId")
    .option(HoodieWriteConfig.TABLE_NAME, tableName)
    // Set the option "hoodie.datasource.write.operation" to "delete"
    .option(DataSourceWriteOptions.OPERATION_OPT_KEY, DataSourceWriteOptions.DELETE_OPERATION_OPT_VAL)
    .mode(SaveMode.Append) // Only Append Mode is supported for Delete.
    .save(s"$basePath/$tableName/")

spark.read.format("hudi").load(s"$basePath/$tableName/*").show()

這是本部分介紹的全部內容。後面我們將探討在MERGE-ON-READ表進行操作。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

【神回復】思域高爾夫怎麼選?20萬內哪款合資SUV更值?

新款捷達的性價比還是挺高的,在市場征戰多年擁有良好的口碑,性價比較高的版本為1。5L手動舒適型與1。5L自動舒適型,兩個配置車型的區別在於變速器不同,在配置上都擁有电子車身穩定系統,后駐車雷達,定速巡航,電動天窗,行車電腦显示屏等,滿足日常家用需求完全沒問題,同時在市場上也有三萬左右的優惠。

如果是自己一個人開的話,那麼在空間方面就不用考慮太多了,高爾夫的話採用的1.4T發動機搭配雙離合的組合,雖然在動力輸出上以及雙離合變速器的平順性上不如思域,但是在轉向的精度上比思域高,換裝多連桿后獨立懸架后底盤更加有韌性,隔音濾振相比思域都有更好的表現。

思域則是擁有非常強的動力輸出,跑高速的話後勁相比高爾夫會更強,同時乘坐空間以及配置要優於高爾夫,搭載的CVT變速器也更加平順,不過就是在轉向,底盤濾振以及隔音上面不如高爾夫。

總的來說,高爾夫的操控性能更佳,思域的動力更強,如果是經常跑高速的話更推薦動力更強的思域。

20萬左右性價比高的SUV可以看看雪佛蘭的探界者或者是斯柯達的柯迪亞克,其中雪佛蘭探界者擁有更低的售價同時加上有可觀的優惠,外觀充滿粗獷之感同時在動力,操控上都有非常不錯的表現,不足在於內飾做工用料一般並且啟停系統無法關閉。

柯迪亞克在華的銷量雖然不溫不火,但是它的產品力同樣十分強大,擁有硬朗的外觀以及非常可觀的乘坐空間,還有七座車型可選,次低配車型的配置已經十分豐富,再加上大眾的成熟動力總成以及良好的底盤濾振和隔音,開起來還是有一定高級感的。

新款捷達的性價比還是挺高的,在市場征戰多年擁有良好的口碑,性價比較高的版本為1.5L手動舒適型與1.5L自動舒適型,兩個配置車型的區別在於變速器不同,在配置上都擁有电子車身穩定系統,后駐車雷達,定速巡航,電動天窗,行車電腦显示屏等,滿足日常家用需求完全沒問題,同時在市場上也有三萬左右的優惠。

假如對空間沒有很大需求的話更加推薦選擇性價比更高的威馳,不僅外觀內飾相比於老氣的經典軒逸更加好看,同時在安全配置上也更加豐富,而經典軒逸的動力相比威馳來說有更強一點的輸出,各種舒適性配置更加齊全,但是售價更高,假如預算充足並且也能接受經典軒逸的外觀內飾的話買來代代步可以可以的,但是預算不是很充足只能買到經典軒逸低配的話建議選擇威馳。

假如是純家用車型的話更加推薦XR-V,雖然其定位為小型SUV,但是在空間上擁有越級的表現,採用的自吸發動機搭配CVT的組合能夠帶來比較平順的體驗,而且在配置方面也能滿足一般的家用需求,性價比頗高,不足之處在於底盤濾振和隔音較差。

馬自達對於運動、操控有着非同一般的偏執,在其SUV車型上也毫不妥協,採用創馳藍天技術+魂動跨界車的風格設計,帶來年輕動感的外觀以及不俗的操控體驗,單憑這兩點就能讓CX-4成為許多年輕消費者的心頭好,不過馬自達在空間方面的造詣一直平平淡淡,適合對操控有追求並且對空間無要求的消費者。

以上就是本期網友問答欄目的全部內容,假如你也想上牆的話,點擊下方留言留下你的問題並且點個贊,就有機會在下期欄目看見你的身影!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

廣汽三菱全新SUV高調亮相,詳細解析Eclipse Cross

尾燈的視覺辨識度更高,加上LED的燈帶與前大燈相互對應,總體看來頗有種概念車的feel,增添幾分獨有的尊貴感。實力大玩家,強悍產品實力讓Eclipse Cross備受稱讚除了外表俊俏的外觀設計,Eclipae Cross擁有卓越的動力性能,硬實力毋庸置疑。

作為中國規模最大、參展品牌最多的中國車展之一,北京車展會吸引到全世界最主流的汽車廠商和供應商,展示最重磅的產品和最前沿的科技,被譽為中國汽車行業的風向標。而在2018年的北京車展上,廣汽三菱全新戰略車型Eclipse Cross的亮相引起車迷的熱烈關注。

Eclipse曾經是三菱旗下的經典入門級跑車,在消費者心中具有很重要的地位。這一次,三菱用Eclipse Cross這個名字來為這款全新SUV命名,可謂意義深長。作為三菱汽車的全球戰略車型,Eclipse Cross已經在北美市場、日本還有台灣等地區先行上市,一經上市便贏得消費者的熱烈反響。根據廣汽三菱的企業發展規劃,Eclipse Cross將在今年投放市場,屆時將助力企業銷量與品牌實力的雙重提升。

俊俏小鮮肉,高顏值是Eclipse Cross的鮮明特徵

犹如一個俊俏的小鮮肉,第一眼見到Eclipse Cross定會被它的高顏值所吸引。這裏所指的高顏值,不是一般群眾所熱捧的那種炫酷誇張高大上的未來感,它本身並沒有承襲三菱其他SUV固有的霸氣外露的形象,而是很聰明地把精緻的設計與運動元素融洽地結合在一起。

承襲三菱Dynamic shield設計語言,Eclipse Cross的前臉的特徵十分明顯。同色的保險杠與大面積鍍鉻裝飾條搭配相得益彰,使整個車頭都極具衝擊力。遠近光一體的LED大燈是Eclipse Cross的另一亮點,造型設計與歐藍德師出同門,只是Eclipse Cross的大燈輪廓會更加犀利,犹如刀鋒一般,十分符合年輕化的設計風格。

車身側面的Cross元素顯露無疑,Eclipse Cross擁有動感型SUV特有的俯衝式腰線及外輪廓造型,車身多處有着尋人耐味的線條稜角造型;從前門底部延伸至後輪眉的曲線條更是給這充滿運動活力的車身賦予了更多力量感。輪轂造型極具設計感,與整車運動風格十分契合。細節來看,輪拱以及側裙處的黑色運動套件非常醒目,在Coupe風格中也顯露了一絲小野性,具有運動范。

尾燈作為整車最大的點睛之筆,儼然成為了Eclipse Cross最具代表性的標籤。橫貫式的尾燈把整塊尾窗玻璃一分為二,極具個性設計。當尾燈亮起,尤其是在黑暗的環境下觀察,不難發現這種設計的絕妙之處。尾燈的視覺辨識度更高,加上LED的燈帶與前大燈相互對應,總體看來頗有種概念車的feel,增添幾分獨有的尊貴感。

實力大玩家,強悍產品實力讓Eclipse Cross備受稱讚

除了外表俊俏的外觀設計,Eclipae Cross擁有卓越的動力性能,硬實力毋庸置疑。Eclipse Cross配備了一套S-AWC超級全輪控制系統,該系統源自三菱多年的世界拉力錦標賽的比賽經驗,是經過多年的技術積累研發出來的电子式動力分配系統。在車輛行駛過程中,S-AWC能夠根據不同的行駛路況,精確控制牽引力、制動力、前後、左右輪間扭矩輸出,從而大幅提升車輛在過彎等情況下的穩定性,抑制側滑、轉向不足、轉向過度等情況的出現,更好地發揮車輛極限,大幅提高車輛的行駛穩定性。

空間上,Eclipse Cross的空間舒適性和靈活性同樣值得稱讚。後排座椅支持前後調節,最大200mm的空間位移給足後排消費者最大的乘坐空間。與此同時,後排座椅還支持4/6放倒,能大幅增加空間的裝載面積。據悉,Eclipse Cross 還將搭載廣汽三菱全新的車聯網技術,具備很高的實用性。

“三菱官方稱用Eclipse這個名字是因為這款SUV的車尾採用溜背式設計,整體看起來非常動感。” 美國權威媒體Car And Driver這樣評價。

除了權威媒體的肯定,Eclipse Cross也獲得國際權威機構的實力認可。憑藉著潮流時尚的外觀和強悍的產品實力,日前,Eclipse Cross榮獲美國️芝加哥優良設計獎(GOOD DESIGN Awards)。該獎項是全球最為知名、歷史悠久的國際設計大獎之一,與德國紅點設計大獎(Red Dot)、日本優秀設計獎(GOOD DESIGN Awards)並列為設計界的三大獎。

總結:

Eclipse Cross從設計上開始有了巨大的改變,以Dynamic Shield家族設計語言為基調,車身整體造型不再平庸,變得更具個性、更加年輕。高顏值的設定,相信Eclipse Cross將會成為繼歐藍德之後又一爆款車型。

以Eclipse Cross為代表的全新車型亮相,標志著廣汽三菱產品矩陣的進一步完善。除了Eclipse Cross,在此次北京車展上,廣汽三菱還帶來了首款純電動概念車E MORE、歐藍德榮耀冠軍版等多款重磅車型,吸睛無數。伴隨着產品陣營的不斷優化,廣汽三菱勢必躋身主流視野,圓滿完成2020年30萬的年銷量目標。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

搭載同級最強的寶馬1.6T發動機,這款智能社交SUV厲害了!

智能音樂:內容庫提供了1700萬首QQ正版音樂,滿足了年輕人的娛樂需求。智能導航:系統提供免費的智能導航,遇到停車或加油等需求,系統會主動推薦附近最合適的加油站和停車場。智能社交:用戶可以通過“AI in car”發起好友集結或加入好友集結,還可以連接行車記錄儀、智能相機等智能硬件,記錄沿途美景趣事,形成路書分享到朋友圈。

是個“喜新厭舊”的人,所以在逛車展的時候,會比較關注新車型的發布,此次北京車展上新車也不少,其中有一款新發布的SUV就非常引人關注-東風風行T5,第一次見到真車,有眼前一亮的感覺,外觀真的很亮眼。

據官方透露,風行T5是東風風行3.0時代的首款產品,除了全新的設計語言,還搭載了和騰訊合作開發的FutureLink3.0車聯網系統,社交是這款新車非常強調的一點,明顯能感覺得這款車就是為年輕人量身訂做的。不過,現在早已不是SUV的紅利期了,在SUV已經接近了泛濫的情況下,這款車型要憑藉什麼來脫穎而出呢?

正如前面所說的,目前市面上SUV已經犹如黃河泛濫,一發不可收拾,作為一款新車,如果長相普通的話,很快就會淹沒在車海里,就外觀而言,東風風行T5的氣場足夠強大,有着明顯的辨識度。

風行T5的整體設計非常霸氣,超大進氣格柵向兩側延展,和劍羽式的LED大燈融為一體,拉寬了橫向的視覺,內部的細節也很有設計感,黑色的中網搭配線段式的鍍鉻飾條,同時一條粗壯的鍍鉻飾條將格柵一分為二,兩側的霧燈區域造型和線條較為誇張,並配有銀色的下護板,車頭看起來氣勢很足,視覺衝擊力強 。

車身側面的腰線比較筆直硬朗,D柱採用了時下比較流行的懸浮式車頂,並通過收窄的設計形成一種聚焦的效果。車尾部分的線條極為豐富,造型富有層次感,一體貫穿式的尾燈設計別具一格,和下方的鍍鉻飾條將“FORTHING”的標識環繞其中,嵌入式的雙邊雙出矩形尾排設計看起來更為兇悍,突出了運動的定位。

風行T5的內飾也是比較喜歡的一點,採用環抱式的布局,營造了一種以駕駛者為中心的感覺,大面積軟質材料的包裹,配合縫線的設計很好地提升檔次感,撞色的設計散發出濃濃的運動氣息。真皮的平底多功能方向盤的樣式也比較新穎,搭配部分銀色裝飾提升了時尚感,中控台的鋼琴式按鍵也很精緻考究。

而最吸引眼球12英寸超大懸浮式中央觸控大屏與12.3英寸全液晶儀錶,中控大屏比榮威RX5的10.4英寸還要大,無論是科技感還是檔次感都非常出色。可惜車展現場沒能看到大屏和液晶儀錶盤點亮的效果,但從效果圖來看還是很贊的。

智能社交是風行T5非常強調的一點,去年中國移動社交用戶規模高達5.9億,像這樣的年輕一代很多都是“離開網絡就會死星人”,對車載互聯功能有着很強烈的願望。風行T5搭載了聯合騰訊開發的“FutureLink3.0”車聯網系統,通過鏈接騰訊“AI in Car”雲端生態,構建了一個智能汽車交互平台,形成了一套完整的智能生態系統,大致有3大功能模塊:智能音樂、智能導航、智能社交。

智能音樂:內容庫提供了1700萬首QQ正版音樂,滿足了年輕人的娛樂需求;

智能導航:系統提供免費的智能導航,遇到停車或加油等需求,系統會主動推薦附近最合適的加油站和停車場;

智能社交:用戶可以通過“AI in car”發起好友集結或加入好友集結,還可以連接行車記錄儀、智能相機等智能硬件,記錄沿途美景趣事,形成路書分享到朋友圈。

車內的眾多操作都可以通過騰訊叮噹語音助理進行,即使是口語化的表達也能精準識別,充分解放了駕駛者的雙手,便利性大大提升。此外,基於騰訊大數據,系統會根據用戶的使用習慣,作出個性化的精準推薦,比如導航可以預判用戶出行的目的地,QQ音樂可以根據用戶的喜好推薦歌單,說不定,最懂你的就是你的車。

風行T5雖然定位為緊湊型SUV,但車長為4530mm,軸距達到了2720mm,空間表現自然不會差。以身高4375px的體驗者來看,後排有接近3拳的腿部空間,並不輸一般的中型SUV。同時後排中央座椅帶有頭枕,座椅很厚實,填充物比較柔軟,坐墊也比較長,對大腿承托效果很明顯,後排也還有空調出風口,底部還設計了12V電源和USB接口,這個配置對於後排乘客來說還是很好用的,細節方面做得很好。

動力也是風行T5的一大亮點,搭載了由寶馬技術授權的1.6TD渦輪增壓發動機,擁有缸內直噴、雙通道渦管增壓、連續可變氣門升程、可變流量機油泵等多項技術,最大功率為150kW(204ps),在1700轉時即可爆發出280N.m的最大扭矩,傳動匹配7速雙離合變速箱,官方綜合油耗為7.5L/100km,經濟性方面也有不錯的表現。

從數據來看,風行T5的動力不僅在中國品牌陣營中屬於強悍,即便是算上合資陣營,同級中也基本無人能出其右,壓榨動力頗為厲害的本田高功率版1.5T渦輪增壓發動機的最大功率也不超過200ps,風行T5的動力確實令人期待。

據悉,風行T5擁有ADAS智能駕駛輔助系統,採用了與特斯拉相同的Mobileye圖像識別技術,可實現ACC自適應巡航、AEB主動剎車、LDW車道偏移提醒、IHC智能遠近光等6大智能模塊功能。此外,還有自動感應智能電動尾門、半自動泊車系統、藍牙鑰匙、手機無線充電等諸多智能科技加持,不過具體的配置還不得而知。

緊湊型SUV可以說是兵家必爭之地,這個市場已經不是一般的擁擠了,競爭的激烈程度已不用多說,像哈弗H6、傳祺GS4、博越和榮威RX5這些長期霸佔銷量排行榜前列的熱門車型,可能開始擔心起風行T5這個後起之秀。不過在看來,風行T5的后發優勢不容小覷,其在動力、智能化和空間方面有着不小的優勢,外觀內飾也是年輕人喜歡的風格。不過,在此次車展上,風行T5並沒有公布價格,現在唯一的問題就是風行T5的售價,如果價格依然像以往非常親民的話,覺得風行T5會有非常不錯的市場表現。

總 結

在這屆北京車展發布的眾多新車之中,作為東風風行的緊湊型SUV,東風風行T5絕對配得上“重磅”二字,外觀和內飾屬於一眼就讓人喜歡的類型,設計手法越來成熟,高級感方面做得也足夠出色,還有同級最強悍的動力和年輕人最喜歡的智能互聯,加上中國品牌一貫以來配置和質價比方面的優勢,風行T5值得期待,希望不用等太久吧。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

這台被稱為“雷霆”的小跑車,性能炸裂連超跑也顧忌三分

而內飾也不含糊,彩色液晶儀錶板、在M5上首次使用、帶M1、M2快速設定按鈕的三幅式運動型方向盤、包裹性更好的真皮桶椅、紅色點火按鈕等等。可以說,M2 雷霆版相比M2普通版,無論從机械上還是設備上都有了很大的改進,也難怪在國內這款車被稱為“M2 雷霆版”。

今年3月份,作為寶馬M最強最新的旗艦和燈塔車型,全新BMW M5以打破上海國際賽車場量產四門房車最快圈速紀錄的形象,呈現在世人眼前。而這,也揭開了2018年寶馬M在中國新篇章的開端。在北京車展上,寶馬M2雷霆版的全新亮相,再次為寶馬M點燃了在中國市場的下一個火種。

說到寶馬M,相信大家都不會陌生。而相對於其DNA而言,寶馬M一直堅持着紮根於賽道,源於賽道的精神,為我們提供了一款又一款令人振奮的、具有濃烈個性的產品。事實上,全新寶馬M2雷霆版的推出也展現了寶馬M對中國市場的理解。在採訪BMW M部門總裁范梅爾先生時得知,作為一個以賽道為基本的品牌,由於中國的賽車歷史和傳統並不深遠,很多人對賽車還是保持着一知半解的狀態。

不過由於寶馬M堅持在中國進行大力度的推廣,並舉辦如BMW M精英駕駛訓練營、M嘉年華等活動,讓更多的朋友對寶馬M開始有了興趣。很多購買帶M package和M performance package車型的朋友開始去體驗寶馬M所帶來的樂趣,並最終進入了寶馬M的大家庭。而對於那些對駕駛非常有要求,也希望提升自身駕駛技術和車輛性能的M 狂熱粉絲,寶馬M也提供從經典的M車型、Competition、CS、CSL等各個層級的車型,來提供給消費者選擇。

就以這次推出的M2雷霆版為例,它是基於原廠的M2所打造的一款“准”入門級M車型。在中國,M2是現在M家族之中最好賣的車型之一,也是操控最好、最純粹的M車。因此不少熱衷於性能的朋友,也希望能夠有一款更“辣”的M2。相比標準版的M2,M2雷霆版的性能會更高一個層次。

首先最大改變的是發動機部分。M2雷霆版採用了與M3、M4同款的S55B30雙渦輪增壓直六發動機,配上7速雙離合變速箱。雖然馬力被調低至410匹,但S55的反應和動力響應會更快更直接,也更能激化M2的底盤潛力。而扭矩維持M3、M4上S55的550牛米,相信更輕的M2在加速性能上絕對有機會幹掉大哥。也難怪有人說,這才是真正的M2“完全版”。

除了發動機以外,為了對應增加的動力輸出,M2雷霆版的引擎機艙加入了M3、M4上見到的U型CFRp穩定桿,以增加車頭的整體剛性和抗扭性。另外,鋁製輕量化前後懸挂可以提供更輕的簧下重量提升操控反應;帶電控閥門的雙邊雙出型排氣讓S55的性能更好發揮之餘,也有更雄渾的聲浪。Brembo代工的前四后二剎車和打孔剎車碟設計讓M2雷霆版有更強的、能對應賽道極限需求的剎車性能;全新設計的19寸輕量化合金輪圈除了顏值更高,也能提升操控動力反應。

而外觀方面,M2雷霆版上也有不少專屬設計。譬如通風性能更好的熏黑的雙腎型進氣格柵、主動式LED頭燈、輕微修改的前包圍、M4的雙色后視鏡等等等等。而內飾也不含糊,彩色液晶儀錶板、在M5上首次使用、帶M1、M2快速設定按鈕的三幅式運動型方向盤、包裹性更好的真皮桶椅、紅色點火按鈕等等。可以說,M2 雷霆版相比M2普通版,無論從机械上還是設備上都有了很大的改進,也難怪在國內這款車被稱為“M2 雷霆版”。

不過,即使這是一台“Competition”競技版的M車型,在駕駛性能上足以做到不妥協。不過寶馬M2雷霆版並不代表這是一台在街道上很難開的車。相反,M2 雷霆版依然很照顧到舒適性的需求,這也是寶馬M在誕生以來的一種堅持。按照范梅爾先生的說法,將一台快車做得舒適,會比將一台舒適的車做得快更簡單,這也是寶馬M一直以來兼顧舒適性和不妥協的操控性的一條獨特的方程式。

從M2雷霆版在北京車展的全球首發可以看出,中國已經成為寶馬M相當重要的市場。可以說,其受歡迎程度的與日俱增,其實和寶馬M敢於創新的工匠精神是分不開的。譬如寶馬M5首次在高性能M房車上使用了M X-drive四驅系統。而在能夠完美解決重量的問題之後,M也將會出現電動車型。范梅爾先生稱,即使是自動駕駛,對M來說也是非常重要的技術,這和M本身秉承的賽道精神並不矛盾。假如能夠以自動駕駛通勤,再在賽車場上親自去駕駛M系列車型體驗高性能帶來的樂趣,這將是非常美妙的。

2017年,M在中國銷量勁增37%,2018年1月至2月,M在中國的銷量較去年同比漲幅達到90%。同時,2017年M運動套件車型銷售超過9萬台,占整體BMW 銷量的15%。這種市場效應足以證明BMW M已經開始在消費者心中紮根,成為眾多BMW愛好者的共同追求。而除了M系列的車型以外,寶馬M也一直在尋求降低門檻的手段。就以寶馬M2為例,雖然已經是作為M家族最入門的車款,但相信對於很多熱愛寶馬M的年輕人來說門檻也可能會有點高。而M240i作為一款高性能2系,能夠銜接從普通寶馬到寶馬M之間的空間,這對於很多熱愛寶馬M的朋友來說,相信也是一個很好的消息。

隨着不斷壯大的粉絲群體,BMW M也在努力致力於打造中國的高性能汽車運動文化,促進賽車運動發展。2018年,備受M粉絲推崇的M嘉年華將繼續在中國舉辦。中國成為該活動德國之外的唯一舉辦國家。

2018年,BMW M將於2018年在國內推出7款新車。為了讓更多的朋友接觸到M的樂趣,也將會以消費者需求為先,推出更多的M運動套件車型。對於國內消費者來說,這無疑是最樂於看到的事情。現在我們已經知道有寶馬M5、寶馬M2雷霆版和寶馬M4 CS,再加上M8概念車。那接下來將會有什麼M車型,相信非常值得期待。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

能讓奔馳寶馬甚至是蘭博基尼、法拉利都服的,大概只有TA了

不說廢話了,今晚秋名山上見,輸了的向思域學習。報告,在街邊發現一款神似路虎的“五菱宏光S“款SUV,不知道售價如何的,趕緊問一問,錯過就買不到了。你說你這年輕人哈,年紀輕輕就開上五菱宏光S,也不知道低調點,你讓我三十歲開寶馬的怎麼好意思。

有種車,不是任何人都有能力駕馭;有種車,開豪車之人唯恐避之不及;有種車,雖然相貌平平卻震懾天下眾車。也許機智的你已猜到,沒錯,它就是號稱“神車“的五菱宏光(還有S)。

提起五菱宏光,相信車迷們都知道”決戰秋名山,輸者留下車標“,也許是秋名山急彎太多,豪車固然馬力大,但車重,開起來還不好掌控出彎入彎的時機,特別是最後1公里的5連髮夾彎,不少豪車都栽在此地。那麼宏光是怎麼成為一代“神車”的呢?

五菱宏光(S)名震天下后,它的代表性文字徽章更是受到眾車的膜拜,車尾部不貼個五菱宏光徽章都不好意思開出街頭。

蘭博基尼Lp700都貼上了“五菱宏光S”,這個操作很6,掌聲響起來。不過還是想問一句,這款五菱宏光S哪裡有賣,賣多少錢,我也想買一輛。

寶馬車也來湊熱鬧了,你TM這是湊熱鬧,還是來炫富的,老子反手就是一巴掌。不說廢話了,今晚秋名山上見,輸了的向思域學習。

報告,在街邊發現一款神似路虎的“五菱宏光S“款SUV,不知道售價如何的,趕緊問一問,錯過就買不到了。

你說你這年輕人哈,年紀輕輕就開上五菱宏光S,也不知道低調點,你讓我三十歲開寶馬的怎麼好意思。

小子,你就不能正經點嗎?好好的五菱宏光S你幹嘛把它改成跑車了,想去交警局喝茶嗎?

自從家裡有了五菱宏光S,我就愛上了野馬。

獨步江湖的五菱宏光S,闖蕩江湖多年未逢敵手。一言不合就是開干,拳打瑪莎拉蒂,腳踢保時捷,勇撞小牛,這些根本都不是事。

然而根據中華人民共和國交通法和機動車登記當中的相關條列显示,其實這些貼車標的行為已經涉及到違法行為。還是覺得各位車主要好自為之,且行且珍惜,被警察蜀黍請去喝茶就不太好了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

捷豹品牌的魅力源自何處?他或許可以給我們答案

因為空間變大,使得車輛三分之二的空間都是給車廂的。Q:捷豹設計了很多經典的轎車和跑車,這幾年從E-pACE到I-pACE開始在SUV市場做一些發力,從設計層面上來說捷豹SUV有哪些核心的設計理念,以及如何在競爭激烈的SUV市場讓人留下令人深刻的印象。

相比較德系三駕馬車來說,捷豹的車型在產品線覆蓋上雖然沒有那麼完整,但是在運動性能以及車輛設計上卻具有非凡的魅力,因此捷豹在中國市場的銷量也是節節攀升,這一次北京車展上捷豹展示了自家的I-pACE以及E-pACE等多款新車型,我們在這一次北京車展上也有幸採訪到了捷豹的捷豹品牌量產車及SV車型設計總監 Wayne Burgess先生,下面一起看看這位年輕的設計總監能夠帶給我們哪些信息吧!

專訪領導: Wayne Burgess 捷豹品牌量產車及SV車型設計總監

Q:我想請問一下,您覺得E-pACE這款車型最大的設計亮點是在哪裡?

Wayne Burgess:我們非常重視這款車設計,因為車型是捷豹品牌,而且我們想把它當成消費者了解我們品牌入口的車型,它有很多特色,如燈的設計,例如輪轂的尺寸比較大大,非常有捷豹的特色。可以看一下它整體的設計特點有很多源自捷豹經典車型的元素,比如前臉是借鑒了F-TYpE的設計,還有後背線條的設計更像是我們雙門的轎跑。實際上這款E-pACE的車雖然是SUV,但我們希望讓消費者想起F-TYpE跑車的特點。

Q:我想問一個關於產品受眾的問題,在捷豹的規劃中E-pACE的受眾應該是哪些?

Wayne Burgess:我們希望E-pACE車型可以延續並進一步擴大捷豹車型對於年輕消費者的吸引力,這裏面也包括年輕的家庭和女性。

Q:這個問題我想了很久,E-pACE這款全新車型在設計開發的時候,想到它的用戶畫像是怎麼樣的,具備哪些的特點?

Wayne Burgess:我們是整體的看年輕一代的消費者或車主對車有哪些需求,比如我們非常關注車的空間,還有車載的互聯性、USB插口以及放水杯的位置和空間等,這些都是我們考慮的重點。還有就是如果是年輕的家庭,他們可能會帶一兩個小孩,包括後備箱也要放一些嬰兒車或者是其他的設備,對後備箱的空間需求也會比較大。在內飾方面我們希望強調捷豹品牌車型獨有的一些特點,例如運動感,又比如整個門的設計和門把手的觸感,以及用了金屬格,就是希望給消費者該有的豪華的感覺。

Q:設計這款I-pACE車型的時候,我想知道有沒有考慮過他的風阻係數是不是被設計的特別低?

Wayne Burgess:的確是。整個空氣動力學和風阻都是我們考慮的重點之一。拿I-pACE來說,這是一台純電動的車,包括車身的設計,前面的引擎蓋、尾翼都是非常符合空氣動力學的設計,提供優異的風阻表現, 能進一步優化電動車的續航里程。除了I-pACE我還負責特殊車輛部門車型的設計,這些車型更多借鑒了賽車的元素,對空氣動力學有更多的考慮。

Q:捷豹為什麼想打造I-pACE這樣一款車型?

Wayne Burgess:主要是有兩個原因:第一個是出於整個戰略性原因,現在整個大環境和政策都在向著降低排放的方向發展,所以打造純電SUV很有必要;第二個是出於設計的目的,電池是布置在前後軸的中間,考慮了車型的空間優化和電池組擺放,純電SUV也是很有意義的。

對於捷豹路虎的品牌我們知道路虎的SUV更多一些,我們希望在捷豹品牌引入第一個純電動車,這樣也是一個創新,對消費者來說也是一個驚喜。

因為電動車型是不需要傳統燃油發動機的,所以整個車內空間變得優化,擋風玻璃下就是座艙,整個給我們的感覺更像跑車。因為空間變大,使得車輛三分之二的空間都是給車廂的。

Q:捷豹設計了很多經典的轎車和跑車,這幾年從E-pACE到I-pACE開始在SUV市場做一些發力,從設計層面上來說捷豹SUV有哪些核心的設計理念,以及如何在競爭激烈的SUV市場讓人留下令人深刻的印象?

Wayne Burgess:我覺得對於捷豹的品牌來說不管是轎車、跑車還是SUV,我們最關注的是外形的美觀和空間的設計,包括車輪和車身的比例、軸距以及車廂和車身的比例,我們需要保證穩定性和駕駛性能。我覺得和其他品牌相比,除了法拉力等品牌的超跑,捷豹是花了最大的功夫和時間研究推出最完美比例的設計和美學的品牌。

Q:剛才提到捷豹是一個非常注重美學設計的,我個人也非常欣賞這種設計的風格。問一下E-pACE這款車型身上有哪些設計元素是您個人最喜歡的?

Wayne Burgess:我喜歡的主要有三點,第一個是車身後三分之一的設計,可以看到很多F-TYpE的影子。還有後面的線條優化,從後面看車標旁邊線的時候它其實和F-TYpE很相近,但我們還是做了一些改良,讓它感覺更酷一些。還有車尾的線條非常像雙門轎跑的感覺,還有擋風玻璃和捷豹元素的設計,讓人看了會有會心一笑的感覺。

Q:之前咱們聊到捷豹SUV的設計,它有很多突出運動性的地方,包括線條的勾勒。但作為一款SUV,在設計和工程層面是如何平衡車內空間以及車外美觀線條的?

Wayne Burgess:包括整體的設計還有對幾何學的利用,舉個例子來說比如車頂天窗和側窗的線條,我們看到非常像雙門轎跑的感覺,整體就是利用設計和美學以及空間性能的考慮,保證了頂窗和側窗以及窗艙設計的運動感。

Q:想再聊一下今天早上說到的F-TYpE跑車2.0T車型,它具有哪些產品的亮點?

Wayne Burgess:19款最大的亮點就是我們引入了2.0T,之前都是3.0,現在2.0T是19款F-TYpE最大的消息,這樣消費者能夠以更能接受的價格買到一個超級跑車。

Q:所以它的競爭力會有很大幅度的提升。

Wayne Burgess:沒錯。

Q:我對英國汽車其實蠻有感情的,對捷豹也相對比較了解,從設計層面來說它有很多經典的設計,像很多年前的A-TYpE包括現在有很多新車的設計,您個人最喜歡哪款車?

Wayne Burgess:XJ13,當時做了一個雪茄型的車身。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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