Flutter實戰】文本組件及五大案例

老孟導讀:大家好,這是【Flutter實戰】系列文章的第二篇,這一篇講解文本組件,文本組件包括文本展示組件(Text和RichText)和文本輸入組件(TextField),基礎用法和五個案例助你快速掌握。
第一篇鏈接:【Flutter實戰】移動技術發展史

Text

Text是显示文本的組件,最常用的組件,沒有之一。基本用法如下:

Text('老孟')

注意:Text組件一定要包裹在Scaffold組件下,否則效果如下:

文本的樣式在style中設置,類型為TextStyleTextStyle中包含很多文本樣式屬性,下面介紹一些常用的。

設置文本大小和顏色:

Text('老孟',style: TextStyle(color: Colors.red,fontSize: 20),),

上面黑色的字體為沒有設置的效果,作為對比。

設置字體粗細:

Text('老孟',style: TextStyle(fontWeight: FontWeight.bold))

字體粗細共有9個級別,為w100w900FontWeight.boldw700

設置斜體:

Text('老孟',style: TextStyle(fontStyle: FontStyle.italic,))

設置自定義的字體:

  1. 首先下載字體庫(比如中華字體庫)
  2. 將字體文件拷貝的項目中,一般目錄是:assets/fonts/,assets和fonts都需要手動創建,此目錄不是必須的,而是約定俗成,資源一般都放在assets目錄下。
  3. 配置pubspec.yaml
fonts:
  - family: maobi 
    fonts:
      - asset: assets/fonts/maobi.ttf

maobi:是自己對當前字體的命名,有意義即可。

asset:字體文件的目錄。

使用:

Text('老孟', style: TextStyle(fontFamily: 'maobi',)),

設置對齊方式:

Container(
  height: 100,
  width: 200,
  color: Colors.blue.withOpacity(.4),
  child: Text('老孟', textAlign: TextAlign.center),
),

textAlign只是控制水平方向的對齊方式,值說明如下:

  • left:左對齊
  • right:右對齊
  • center:居中
  • justify:兩端對齊,此屬性中文存在bug(Flutter版本:1.17.3)也可以在官方issue中關注此問題
  • start:前端對齊,和TextDirection屬性有關,如果設置TextDirection.ltr,則左對齊,設置TextDirection.rtl則右對齊。
  • end:末端對齊,和TextDirection屬性有關,如果設置TextDirection.ltr,則右對齊,設置TextDirection.rtl則左對齊。

設置文本自動換行:

Container(
  height: 100,
  width: 200,
  color: Colors.blue.withOpacity(.4),
  child: Text('老孟,專註分享Flutter技術和應用實戰',softWrap: true,),
)

文本超出範圍時的處理:

Container(
  height: 100,
  width: 200,
  color: Colors.blue.withOpacity(.4),
  child: Text('老孟,專註分享Flutter技術和應用實戰',overflow: TextOverflow.ellipsis,),
)

溢出的處理方式:

  • clip:直接裁剪。
  • fade:越來越透明。
  • ellipsis:省略號結尾。
  • visible:依然显示,此時將會溢出父組件。

設置全局字體樣式:

MaterialApptheme中設置如下

MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
   ...
    textTheme: TextTheme(
        bodyText2: TextStyle(color: Colors.red,fontSize: 24),
    )
  ),
  home: Scaffold(
    body: TextDemo(),
  ),
)

Text組件默認為紅色,

Text('老孟'),

Text('老孟',style: TextStyle(color: Colors.blue,fontSize: 20),),

RichText

RichText的屬性和Text基本一樣,使用如下:

RichText(
  text: TextSpan(
      style: DefaultTextStyle.of(context).style,
      children: <InlineSpan>[
        TextSpan(text: '老孟', style: TextStyle(color: Colors.red)),
        TextSpan(text: ','),
        TextSpan(text: '專註分享Flutter技術和應用實戰'),
      ]),
)

TextField

TextField是文本輸入組件,即輸入框,常用組件之一。基本用法:

TextField()

不需要任何參數,一個最簡單的文本輸入組件就出來了,效果如下:

decoration是TextField組件的裝飾(外觀)參數,類型是InputDecoration。

icon显示在輸入框的前面,用法如下:

TextField(
  decoration: InputDecoration(
    icon: Icon(Icons.person),
  ),
)

當輸入框是空而且沒有焦點時,labelText显示在輸入框上邊,當獲取焦點或者不為空時labelText往上移動一點,labelStyle參數表示文本樣式,具體參考TextStyle, 用法如下:

TextField(
  decoration: InputDecoration(
    labelText: '姓名:',
    labelStyle: TextStyle(color:Colors.red)
  ),
)

hasFloatingPlaceholder參數控制當輸入框獲取焦點或者不為空時是否還显示labelText,默認為true,显示。

helperText显示在輸入框的左下部,用於提示用戶,helperStyle參數表示文本樣式,具體參考TextStyle用法如下:

TextField(
  decoration: InputDecoration(
    helperText: '用戶名長度為6-10個字母',
    helperStyle: TextStyle(color: Colors.blue),
    helperMaxLines: 1
  ),
)

hintText是當輸入框為空時的提示,不為空時不在显示,用法如下:

TextField(
  decoration: InputDecoration(
    hintText: '請輸入用戶名',
    hintStyle: TextStyle(color: Colors.grey),
    hintMaxLines: 1
  ),
)

errorText显示在輸入框的左下部,默認字體為紅色,用法如下:

TextField(
  decoration: InputDecoration(
    errorText: '用戶名輸入錯誤',
    errorStyle: TextStyle(fontSize: 12),
    errorMaxLines: 1,
    errorBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.red)),
  ),
)

prefix系列的組件是輸入框前面的部分,用法如下:

TextField(
  decoration: InputDecoration(
    prefixIcon: Icon(Icons.person)
  ),
)

注意prefix和icon的區別,icon是在輸入框邊框的外部,而prefix在裏面。

suffix和prefix相反,suffix在輸入框的尾部,用法如下:

TextField(
  decoration: InputDecoration(
      suffixIcon: Icon(Icons.person)
  ),
)

counter組件統計輸入框文字的個數,counter僅僅是展示效果,不具備自動統計字數的功能, 自動統計字數代碼如下:

var _textFieldValue = '';
TextField(
  onChanged: (value){
    setState(() {
      _textFieldValue = value;
    });
  },
  decoration: InputDecoration(
    counterText: '${_textFieldValue.length}/32'
  ),
)

filled為true時,輸入框將會被fillColor填充,仿QQ登錄輸入框代碼如下:

Container(
  height: 60,
  width: 250,
  child: TextField(
    decoration: InputDecoration(
      fillColor: Color(0x30cccccc),
      filled: true,
      enabledBorder: OutlineInputBorder(
          borderSide: BorderSide(color: Color(0x00FF0000)),
          borderRadius: BorderRadius.all(Radius.circular(100))),
      hintText: 'QQ號/手機號/郵箱',
      focusedBorder: OutlineInputBorder(
          borderSide: BorderSide(color: Color(0x00000000)),
          borderRadius: BorderRadius.all(Radius.circular(100))),
    ),
  ),
)

controller是輸入框文本編輯的控制器,可以獲取TextField的內容、設置TextField的內容,下面將輸入的英文變為大寫:

TextEditingController _controller;

@override
void initState() {
  super.initState();
  _controller = TextEditingController()
    ..addListener(() {
      //獲取輸入框的內容,變為大寫
      _controller.text = _controller.text.toUpperCase();

    });
}

@override
Widget build(BuildContext context) {
  return TextField(
    controller: _controller,
  );
}

@override
dispose() {
  super.dispose();
  _controller.dispose();
}

有時輸入框後面帶有“清除”功能,需要controller來實現。如果需要2個TextField的內容進行同步,只需要給2個TextField設置同一個controller即可實現。

keyboardType參數控制軟鍵盤的類型,說明如下:

  • text:通用鍵盤。
  • multiline:當TextField為多行時(maxLines設置大於1),右下角的為“換行” 按鍵。
  • number:数字鍵盤。
  • phone:手機鍵盤,比数字鍵盤多”*”和 “#”。
  • datetime:在ios上和text一樣,在android上出現数字鍵盤、”:”和 “-“。
  • emailAddress:郵箱鍵盤,有”@” 和 “.”按鍵。
  • url:url鍵盤,有”/” 和 “.”按鍵。
  • visiblePassword:既有字幕又有数字的鍵盤。

textInputAction參數控制軟鍵盤右下角的按鍵,說明如下:

  • none:android上显示返回鍵,ios不支持。
  • unspecified:讓操作系統自己決定哪個合適,一般情況下,android显示“完成”或者“返回”。
  • done:android显示代表“完成”的按鈕,ios显示“Done”(中文:完成)。
  • go:android顯示錶達用戶去向目的地的圖標,比如向右的箭頭,ios显示“Go”(中文:前往)。
  • search:android顯示錶達搜索的按鈕,ios显示”Search”(中文:搜索)。
  • send:android顯示錶達發送意思的按鈕,比如“紙飛機”按鈕,ios显示”Send”(中文:發送)。
  • next:android顯示錶達“前進”的按鈕,比如“向右的箭頭”,ios显示”Next”(中文:下一項)。
  • previous:android顯示錶達“後退”的按鈕,比如“向左的箭頭”,ios不支持。
  • continueAction:android 不支持,ios僅在ios9.0+显示”Continue”(中文:繼續)。
  • join:Android和ios显示”Join”(中文:加入)。
  • route:android 不支持,ios显示”Route”(中文:路線)。
  • emergencyCall:android 不支持,ios显示”Emergency Call”(中文:緊急電話)。
  • newline:android顯示錶達“換行”的按鈕,ios显示”換行“。

大家可能發現了,Android上显示的按鈕大部分是不確定的,比如next有的显示向右的箭頭,有的显示前進,這是因為各大廠商對Android ROM定製引發的。

textCapitalization參數是配置鍵盤是大寫還是小寫,僅支持鍵盤模式為text,其他模式下忽略此配置,說明如下:

  • words:每一個單詞的首字母大寫。
  • sentences:每一句話的首字母大寫。
  • characters:每個字母都大寫
  • none:都小寫

這裏僅僅是控制軟鍵盤是大寫模式還是小寫模式,你也可以切換大小寫,系統並不會改變輸入框內的內容。

textAlignVertical表示垂直方向的對齊方式,textDirection表示文本方向,用法如下:

TextField(
  textAlignVertical: TextAlignVertical.center,
  textDirection: TextDirection.rtl,
)

toolbarOptions表示長按時彈出的菜單,有copycutpasteselectAll,用法如下:

TextField(
  toolbarOptions: ToolbarOptions(
    copy: true,
    cut: true,
    paste: true,
    selectAll: true
  ),
)

cursor表示光標,用法如下:

TextField(
  showCursor: true,
  cursorWidth: 3,
  cursorRadius: Radius.circular(10),
  cursorColor: Colors.red,
)

效果如下:

將輸入框設置為密碼框,只需obscureText屬性設置true即可,用法如下:

TextField(
  obscureText: true,
)

通過inputFormatters可以限制用戶輸入的內容,比如只想讓用戶輸入字符,設置如下:

TextField(
  inputFormatters: [
    WhitelistingTextInputFormatter(RegExp("[a-zA-Z]")),
  ],
)

這時用戶是無法輸入数字的。

onChanged是當內容發生變化時回調,onSubmitted是點擊回車或者點擊軟鍵盤上的完成回調,onTap點擊輸入框時回調,用法如下:

TextField(
  onChanged: (value){
    print('onChanged:$value');
  },
  onEditingComplete: (){
    print('onEditingComplete');
  },
  
  onTap: (){
    print('onTap');
  },
)

輸入框右下角經常需要字數統計,除了使用上面介紹的方法外,還可以使用buildCounter,建議使用此方法,用法如下:

TextField(
  maxLength: 100,
  buildCounter: (
    BuildContext context, {
    int currentLength,
    int maxLength,
    bool isFocused,
  }) {
    return Text(
      '$currentLength/$maxLength',
    );
  },
)

動態獲取焦點

FocusScope.of(context).requestFocus(_focusNode);

_focusNode為TextField的focusNode:

_focusNode = FocusNode();

TextField(
	focusNode: _focusNode,
	...
)

動態失去焦點

_focusNode.unfocus();

過渡顏色的文字

Builder(
  builder: (BuildContext context) {
    RenderBox box = context.findRenderObject();
    final Shader linearGradient = LinearGradient(
      colors: <Color>[Colors.purple, Colors.blue],
    ).createShader(
        Rect.fromLTWH(0.0, 0.0, box?.size?.width, box?.size?.height));

    return Text(
      '老孟,專註分享Flutter技術和應用實戰',
      style: new TextStyle(
          fontSize: 18.0,
          fontWeight: FontWeight.bold,
          foreground: Paint()..shader = linearGradient),
    );
  },
)

Builder組件是為了計算當前Text組件大小,生成LinearGradient。

帶前後置標籤的文本

RichText(
  text: TextSpan(
      style: DefaultTextStyle.of(context).style,
      children: <InlineSpan>[
        WidgetSpan(
            child: Container(
              margin: EdgeInsets.only(right: 10),
              padding: EdgeInsets.symmetric(horizontal: 10),
              decoration: BoxDecoration(
                  shape: BoxShape.rectangle,
                  borderRadius: BorderRadius.all(Radius.circular(20)),
                  color: Colors.blue),
              child: Text(
                '判斷題',
                style: TextStyle(color: Colors.white),
              ),
            )),
        TextSpan(text: '泡沫滅火器可用於帶電滅火'),

      ]),
)

“服務協議”

通常在登錄頁面的底部會出現登錄即代表同意並閱讀 《服務協議》,其中《服務協議》為藍色且可點擊:

Text.rich(
  TextSpan(
      text: '登錄即代表同意並閱讀',
      style: TextStyle(fontSize: 11, color: Color(0xFF999999)),
      children: [
        TextSpan(
          text: '《服務協議》',
          style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
          recognizer: TapGestureRecognizer()
            ..onTap = () {
              print('onTap');
            },
        ),
      ]),
)

登錄密碼輸入框

TextField(
  decoration: InputDecoration(
    fillColor: Color(0x30cccccc),
    filled: true,
    enabledBorder: OutlineInputBorder(
        borderSide: BorderSide(color: Color(0x00FF0000)),
        borderRadius: BorderRadius.all(Radius.circular(100))),
    hintText: '輸入密碼',
    focusedBorder: OutlineInputBorder(
        borderSide: BorderSide(color: Color(0x00000000)),
        borderRadius: BorderRadius.all(Radius.circular(100))),

  ),
  textAlign: TextAlign.center,
  obscureText: true,
  onChanged: (value) {

  },
)

評論回復

Text.rich(
  TextSpan(
      text: '回復',
      style: TextStyle(fontSize: 11, color: Color(0xFF999999)),
      children: [
        TextSpan(
          text: '@老孟:',
          style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
          recognizer: TapGestureRecognizer()
            ..onTap = () {
              print('onTap');
            },
        ),
        TextSpan(
          text: '你好,想知道Flutter發展前景如何?',
        ),
      ]),
)

交流

老孟Flutter博客地址(330個控件用法):http://laomengit.com

歡迎加入Flutter交流群(微信:laomengit)、關注公眾號【老孟Flutter】:

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

基於 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 開發博客項目 – 終結篇之發布項目

上一篇完成了博客的主題切換,菜單和二維碼的显示與隱藏功能,本篇繼續完成分頁查詢文章列表的數據展示。

添加頁面

現在點擊頁面上的鏈接,都會提示錯誤消息,因為沒有找到對應的路由地址。先在Pages下創建五個文件夾:Posts、Categories、Tags、Apps、FriendLinks。

然後在對應的文件夾下添加Razor組件。

  • Posts文件夾:文章列表頁面Posts.razor、根據分類查詢文章列表頁面Posts.Category.razor、根據標籤查詢文章列表頁面Posts.Tag.razor、文章詳情頁Post.razor
  • Categories文件夾:分類列表頁面Categories.razor
  • Tags文件夾:標籤列表頁面Tags.razor
  • Apps文件夾:Apps.razor準備將友情鏈接入口放在裏面
  • FriendLinks文件夾:友情鏈接列表頁面FriendLinks.razor

先分別創建上面這些Razor組件,差不多除了後台CURD的頁面就這些了,現在來逐個突破。

不管三七二十一,先把所有頁面的路由給確定了,指定頁面路由使用 @page 指令,官方文檔說不支持可選參數,但是可以支持多個路由規則。

默認先什麼都不显示,可以將之前的加載中圈圈寫成一個組件,供每個頁面使用。

在Shared文件夾添加組件Loading.razor

<!--Loading.razor-->
<div class="loader"></div>
//Posts.razor
@page "/posts/"
@page "/posts/page/{page:int}"
@page "/posts/{page:int}"

<Loading />

@code {
    /// <summary>
    /// 當前頁碼
    /// </summary>
    [Parameter]
    public int? page { get; set; }
}

這裏我加了三條,可以匹配沒有page參數,帶page參數的,/posts/page/{page:int}這個大家可以不用加,我是用來兼容目前線上的博客路由的。總的來說可以匹配到:/posts/posts/1/posts/page/1這樣的路由。

//Posts.Category.razor
@page "/category/{name}"

<Loading />

@code {
    /// <summary>
    /// 分類名稱參數
    /// </summary>
    [Parameter]
    public string name { get; set; }
}

根據分類名稱查詢文章列表頁面,name當作分類名稱參數,可以匹配到類似於:/category/aaa/category/bbb這樣的路由。

//Posts.Tag.razor
@page "/tag/{name}"

<Loading />

@code {
    /// <summary>
    /// 標籤名稱參數
    /// </summary>
    [Parameter]
    public string name { get; set; }
}

這個根據標籤名稱查詢文章列表頁面和上面差不多一樣,可以匹配到:/tag/aaa/tag/bbb這樣的路由。

//Post.razor
@page "/post/{year:int}/{month:int}/{day:int}/{name}"

<Loading />

@code {
    [Parameter]
    public int year { get; set; }

    [Parameter]
    public int month { get; set; }

    [Parameter]
    public int day { get; set; }

    [Parameter]
    public string name { get; set; }
}

文章詳情頁面的路由有點點複雜,以/post/開頭,加上年月日和當前文章的語義化名稱組成。分別添加了四個參數年月日和名稱,用來接收URL的規則,使用int來設置路由的約束,最終可以匹配到路由:/post/2020/06/09/aaa/post/2020/06/9/bbb這樣的。

//Categories.razor
@page "/categories"

<Loading />

//Tags.razor
@page "/tags"

<Loading />

//FriendLinks.razor
@page "/friendlinks"

<Loading />

分類、標籤、友情鏈接都是固定的路由,像上面這樣就不多說了,然後還剩一個Apps.razor

//Apps.razor
@page "/apps"

<div class="container">
    <div class="post-wrap">
        <h2 class="post-title">-&nbsp;Apps&nbsp;-</h2>
        <ul>
            <li>
                <a target="_blank" href="https://support.qq.com/products/75616"><h3>吐個槽_留言板</h3></a>
            </li>
            <li>
                <NavLink href="/friendlinks"><h3>友情鏈接</h3></NavLink>
            </li>
        </ul>
    </div>
</div>

在裏面添加了一個友情鏈接的入口,和一個 騰訊兔小巢 的鏈接,歡迎大家吐槽留言噢。

現在可以運行一下看看,點擊所有的鏈接都不會提示錯誤,只要路由匹配正確就會出現加載中的圈圈了。

文章列表

在做文章列表的數據綁定的時候遇到了大坑,有前端開發經驗的都知道,JavaScript弱類型語言中接收json數據隨便玩,但是在Blazor中我試了下動態接受傳遞過來的JSON數據,一直報錯壓根運行不起來。所以在請求api接收數據的時候需要指定接收對象,那就好辦了我就直接引用API中的.Application.Contracts就行了啊,但是緊接着坑又來了,目標框架對不上,引用之後也運行不起來,這裏應該是之前沒有設計好。

於是,我就想了一個折中的辦法吧,將API中的返回對象可以用到的DTO先手動拷貝一份到Blazor項目中,後續可以考慮將公共的返回模型做成Nuget包,方便使用。

那麼,最終就是在Blazor中添加一個Response文件夾,用來放接收對象,裏面的內容看圖:

有點傻,先這樣解決,後面在做進一步的優化吧。

將我們複製進來的東東,在_Imports.razor中添加引用。

//_Imports.razor
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Meowv.Blog.BlazorApp.Shared
@using Response.Base
@using Response.Blog

@inject HttpClient Http
@inject Commons.Common Common

@inject HttpClient Http:注入HttpClient,用它來請求API數據。

現在有了接收對象,接下來就好辦了,來實現分頁查詢文章列表吧。

先添加三個私有變量,限制條數,就是一次加載文章的數量,總頁碼用來計算分頁,還有就是API的返回數據的接收類型參數。

/// <summary>
/// 限制條數
/// </summary>
private int Limit = 15;

/// <summary>
/// 總頁碼
/// </summary>
private int TotalPage;

/// <summary>
/// 文章列表數據
/// </summary>
private ServiceResult<PagedList<QueryPostDto>> posts;

然後當頁面初始化的時候,去加載數據,渲染頁面,因為page參數可能存在為空的情況,所以要考慮進去,當為空的時候給他一個默認值1。

/// <summary>
/// 初始化
/// </summary>
protected override async Task OnInitializedAsync()
{
    // 設置默認值
    page = page.HasValue ? page : 1;

    await RenderPage(page);
}

/// <summary>
/// 點擊頁碼重新渲染數據
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
private async Task RenderPage(int? page)
{
    // 獲取數據
    posts = await Http.GetFromJsonAsync<ServiceResult<PagedList<QueryPostDto>>>($"/blog/posts?page={page}&limit={Limit}");

    // 計算總頁碼
    TotalPage = (int)Math.Ceiling((posts.Result.Total / (double)Limit));
}

在初始化方法中設置默認值,調用RenderPage(...)獲取到API返回來的數據,並根據返回數據計算出頁碼,這樣就可以綁定數據了。

@if (posts == null)
{
    <Loading />
}
else
{
    <div class="post-wrap archive">
        @if (posts.Success && posts.Result.Item.Any())
        {
            @foreach (var item in posts.Result.Item)
            {
                <h3>@item.Year</h3>
                @foreach (var post in item.Posts)
                {
                    <article class="archive-item">
                        <NavLink href="@("/post" + post.Url)">@post.Title</NavLink>
                        <span class="archive-item-date">@post.CreationTime</span>
                    </article>
                }
            }
            <nav class="pagination">
                @for (int i = 1; i <= TotalPage; i++)
                {
                    var _page = i;

                    if (page == _page)
                    {
                        <span class="page-number current">@_page</span>
                    }
                    else
                    {
                        <a class="page-number" @onclick="@(() => RenderPage(_page))" href="/posts/@_page">@_page</a>
                    }
                }
            </nav>
        }
        else
        {
            <ErrorTip />
        }
    </div>
}

在加載數據的時候肯定是需要一個等待時間的,因為不可抗拒的原因數據還沒加載出來的時候,可以讓它先轉一會圈圈,當posts不為空的時候,再去綁定數據。

在綁定數據,for循環頁碼的時候我又遇到了一個坑,這裏不能直接去使用變量i,必須新建一個變量去接受它,不然我傳遞給RenderPage(...)的參數就會是錯的,始終會取到最後一次循環的i值。

當判斷數據出錯或者沒有數據的時候,在把錯誤提示<ErrorTip />扔出來显示。

做到這裏,可以去運行看看了,肯定會報錯,因為還有一個重要的東西沒有改,就是我們接口的BaseAddress,在Program.cs中,默認是當前Blazor項目的運行地址。

我們需要先將API項目運行起來,拿到地址配置在Program.cs中,因為現在還是本地開發,有多種辦法可以解決,可以將.HttpApi.Hosting設為啟動項目直接運行起來,也可以使用命令直接dotnet run

我這裏為了方便,直接發布在IIS中,後續只要電腦打開就可以訪問了,你甚至選擇其它任何你能想到的方式。

關於如何發布這裏先不做展開,有機會的話寫一篇將.net core開發的項目發布到 Windows、Linux、Docker 的教程吧。

所以我的Program.cs中配置如下:

//Program.cs
using Meowv.Blog.BlazorApp.Commons;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Net.Http;
using System.Threading.Tasks;

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

            var baseAddress = "https://localhost";

            if (builder.HostEnvironment.IsProduction())
                baseAddress = "https://api.meowv.com";

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

            builder.Services.AddSingleton(typeof(Common));

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

baseAddress默認為本地開發地址,使用builder.HostEnvironment.IsProduction()判斷是否為線上正式生產環境,改變baseAddress地址。

現在可以看到已經可以正常獲取數據,並且翻頁也是OK的,然後又出現了一個新的BUG。

解決BUG

細心的可以發現,當我點擊頭部組件的Postsa 標籤菜單時候,頁面沒有發生變化,只是路由改變了。

思來想去,我決定使用NavigationManager這個URI和導航狀態幫助程序來解決,當點擊頭部的Postsa 標籤菜單直接刷新頁面得了。

Common.cs中使用構造函數注入NavigationManager,然後添加一個跳轉指定URL的方法。

/// <summary>
/// 跳轉指定URL
/// </summary>
/// <param name="uri"></param>
/// <param name="forceLoad">true,繞過路由刷新頁面</param>
/// <returns></returns>
public async Task RenderPage(string url, bool forceLoad = true)
{
    _navigationManager.NavigateTo(url, forceLoad);

    await Task.CompletedTask;
}

forceLoad = true的時候,將會繞過路由直接強制刷新頁面,如果forceLoad = false,則不會刷新頁面。

緊接着在Header.razor中修改代碼,添加點擊事件。

@*<NavLink class="menu-item" href="posts">Posts</NavLink>*@

<NavLink class="menu-item" href="posts" @onclick="@(async () => await Common.RenderPage("posts"))">Posts</NavLink>

總算是搞定,完成了分頁查詢文章列表的數據綁定,今天就到這裏吧,未完待續…

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

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

【翻譯】.NET 5 Preview5發布

今天,發布了.NET 5.0 Preview5。主要對它進行了一小部分新功能和性能的改進。.NET 5.0 Preview 4包含了一些計劃和.NET 5.0要交付的內容。
現在,大多數的功能都已經包含在裏面,但是有許多功能還未到最終狀態。預計這個版本在Preview 7中完善。

可以下載適用於Windows,macOS和Linux的.NET 5.0 Preview 5:

  • Windows and macOS installers
  • Binaries
  • Docker images
  • Snap installer

ASP.NET Core和EF Core也在今天發布了

我們需要使用Visual Studio 2019 16.7才能使用.NET 5.0。 安裝最新版本的C#擴展,以將.NET 5.0與Visual Studio Code結合使用。 Mac的Visual Studio尚不支持.NET 5.0。

發布說明:

  • .NET 5.0 release notes
  • .NET 5.0 known issues
  • GitHub release
  • GitHub tracking issue

RyuJIT改進

對RyuJIT JIT編譯器進行了以下改進

  • 新的、更快的、可移植的tailcall helper實現。
  • ARM64硬件內部物理的實現進程
    • 實現ASIMD Extract Insert ExtractVector64 ExtractVector128
    • 實現 ASIMD widening, narrowing, saturating intrinsics
    • 添加VectorTableList和TableVectorExtension內部函數
    • 添加對ARM64內部函數的CreateScalarUnsafe()支持
    • ARM64對Vector64.Create()和Vector128.Create()的內在支持
    • 使用arm64 intrinsic優化BitOperations.PopCount ()
  • 在影響正則表達式編譯的情況下改進了JIT的速度
  • 使用新的硬件特性BSF/BSR改進英特爾架構性能
  • 實現Vector{Size}.AllBitsSet

Native exports

曾要求為導出本機二進制文件啟用導出功能,這些功能已在很長一段時間內調用.NET代碼。 這是一個很好的方案,現在通過.NET 5.0啟用它。 該功能的構建塊是託管對UnmanagedCallersOnlyAttribute的API支持。

這個功能是創建更高級別體驗的基礎。 團隊的Aaron Robinson一直在從事.NET Native Exports項目,該項目為將.NET組件作為本機庫發布提供了更完整的體驗。 目前正在尋求有關此功能的反饋,以幫助我們確定該方法是否應包含在產品中。

.NET本地導出項目使我們能夠:

  • 公開自定義的 native exports。
  • 不需要像COM這樣的高級互操作技術。
  • 跨平台工作

現有的項目可以實現類似的方案,例如:

  • Unmanaged Exports
  • DllExport

[重大變化]刪除.NET 5.0中內置的WinRT支持

注意:這個變化將出現在預覽6中。這是一個早期的通知。

Windows Runtime(WinRT)是Windows中公開使用的新API的技術和ABI。 您可以通過.NET代碼調用這些API,類似於使用C ++的方法。 在.NET Core 3.0中添加了對WinRT interop的支持,這是對Windows桌面客戶端框架(Windows Forms和WPF)添加支持的一部分。

最近,一直在與Windows團隊緊密合作,以更改和改進WinRT interop與.NET的協作方式。 我們已用Windows團隊在.NET 5.0中提供的C#/WinRT工具鏈替換了內置的WinRT支持。 WinRT互操作中的此更改是一項重大更改,使用WinRT的.NET Core 3.x應用將需要重新編譯。 我們將在即將來臨的預覽中提供更多信息。

在.NET 5中的支持WinRT API中明確指出了這些好處:

  • WinRT互操作可以獨立於.NET runtime進行開發和改進。
  • 使WinRT互操作與為其他操作系統(如iOS和Android)提供的互操作系統對稱。
  • 可以利用許多其他。net特性(AOT, c#特性, IL linking)。
  • 簡化.NET運行時代碼庫(刪除6萬行代碼)。

將System.DirectoryServices.Protocols擴展到Linux和macOS

添加對System.DirectoryServices.Protocols的跨平台支持。 在Preview 5中,添加了對Linux的支持,並在Preview 6中添加了對macOS的支持。對Windows支持已經存在。

System.DirectoryServices.Protocols是比System.DirectoryServices更低級別的API,並且啟用更多方案。 System.DirectoryServices包含僅適用Windows的概念/實現,因此製作跨平台並不是一個很容易的事情。
這兩個API都支持對目錄服務服務器(如LDAP或Active Directory)進行控制和交互。

Alpine 3.12

本周,增加了對Alpine 3.12,.NET Core 3.1和.NET 5的支持。 Alpine Linux的維護者於5月29日宣布發布Alpine 3.12。 與過去相比,以更快,更可預測的方式增加對新Linux發行版的支持。

會發現他們已經開始使用一種新的發布模型來發布新發行版的問題。 這就是我們對Alpine 3.12所做的。 將來,我們計劃更早地發布這些問題。 例如,需要跟蹤的下一個發行版可能是Ubuntu 20.10。 尚未決定,但可能會在7月或8月發布該發行版的類似問題,以準備10月發行的新Ubuntu版本。

最後

感謝大家對.NET 5.0預覽版的反饋以及您的早期反饋。 正如文章的簡介中所建議的那樣,現在才發布大約一半。 現在包括了大多數功能,但是我們希望在接下來的幾個預覽中會進行許多更改,以完成體驗並消除仍然存在的粗糙邊緣。

來源:https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-5/

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

日本廣島法院宣判 擴大認定核爆「黑雨」受害者

摘錄自2020年7月29日中央社報導

美國於二戰末期對日本投下原子彈迄今已近75年。日本廣島地方法院今天(29日)做出罕見判決,擴大認定核彈倖存者。法新社報導,廣島地方法院裁決,84名原告都應獲得給予核彈攻擊受害者的醫療福利。

在1945年8月6日廣島核爆後,若干地區降下放射性物質「黑雨」。日本政府在二戰後為事發時位於這些地區的民眾提供免費醫療照護,上述原告事發時不在被指定的地區,但淋到「黑雨」,他們主張自己遭受的健康衝擊與政府認定地區內受害人相似。

日本放送協會(NHK)報導,廣島地方法院審判長高島義行在庭上表示:「醫療文件顯示,這些居民經歷的疾病據信與核彈有關,這符合認定被爆者的法律要件。」對於多年來主張自己也受苦於核彈攻擊的人士而言,這場官司也具象徵性意義。

污染治理
公害污染
國際新聞
原子彈
核爆

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

垃圾也「三倍」? 研究:各國減塑政策來不及 2040年入海塑膠垃圾將增三倍

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

蝴蝶效應!中日韓梅雨季重災情 歸因於北極異常高溫

摘錄自2020年8月4日自由時報報導

中日韓3國今年受梅雨季暴雨影響,接連發生重大災情,南韓媒體引述專家說法稱,今年梅雨季的強降雨災情與全球暖化下的北極異常高溫有關。

根據《韓聯社》報導,今年梅雨季比起往年為長,中國華中地區、長江流域爆發劇烈洪災,日本九州暴雨重災,南韓強降雨亦造成死亡災情,其根本原因就是全球持續暖化。

《韓聯社》引述專家所言指出,在全球暖化下,北極氣溫大幅升高導致積雪及冰層融化,暴露出的黃褐色地表加劇從陽光吸取熱能,讓當地空氣溫度飆升。在暖空氣影響下,原本從東向西流動的冷空氣轉而朝南北方向作用,進而移動至中日韓等國。

南韓氣象廳則認為,今年梅雨季偏長因素雖不能僅歸因在全球暖化,但確實是氣候變化造成。

氣候變遷
土地利用
國際新聞
中國
日本
韓國
北極
極端高溫
全球暖化
災害

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

超市減塑有成 英格蘭一次性購物袋收費後 2019年用量下降59%

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

全美中國可疑種子包裹化驗出爐 恐破壞在地生態

摘錄自2020年8月4日中央社報導

美國農業部(USDA)動植物健康檢查局副局長艾爾西(Osama El-Lissy)表示,截至7月29日止,已鑑定出芥菜、高麗菜、牽牛花、薄荷、鼠尾草、迷迭香、薰衣草等植物種子,另有木棉花、玫瑰種子。目前鑑定出的品種雖屬無害,但植物專家警告,外來種子有可能損害農作物。

美國哥倫比亞廣播公司新聞網(CBS News)報導,全美50州都有民眾通報收到可疑種子包裹。美國農業部已提醒民眾收到後千萬不要栽種,應立刻向當地的農政單位通報。德州農業廳長米勒(Sid Miller)呼籲民眾保持警覺,「那可能是細菌,也可能是病毒或某個外來種植物」。

維吉尼亞州的農業官員警告:「外來物種會造成環境浩劫,取代或破壞原生植物及昆蟲,嚴重損害農作物。降低外來物種入侵並大量繁殖的風險並減少相關管控成本,最有效的方法就是採取措施、預防入侵物種的引入。」

愛荷華州農業暨土地管理廳的種子檢驗官員普魯伊斯納(Robin Pruisner)表示,有通報指出種子上可能含有殺蟲或除黴劑,對農作物危害甚大。

生物多樣性
國際新聞
美國
種子
外來物種
入侵植物
外來種植物

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

網絡KPI異常檢測之時序分解算法

時間序列數據伴隨着我們的生活和工作。從牙牙學語時的“1, 2, 3, 4, 5, ……”到房價的走勢變化,從金融領域的刷卡記錄到運維領域的核心網性能指標。時間序列中的規律能加深我們對事物和場景的認識,時間序列中的異常能提醒我們某些部分可能出現問題。那麼如何去發現時間序列中的規律、找出其中的異常點呢?接下來,我們將揭開這些問題的面紗。

什麼是異常

直觀上講,異常就是現實與心理預期產生較大差距的特殊情形。如2020年春節的新型肺炎(COVID-19,coronavirus disease 2019),可以看到2月12日有一個明顯的確診病例的升高,這就是一個異常點,如下圖:

從統計上講,嚴重偏離預期的點,常見的可以通過3-sigma準則來判定。

從數學上講,它就是一個分段函數:

那麼我們有哪些方法來發現異常呢?異常分析的方法有很多,在本文中,我們主要講解時間序列分解的算法。接下來,我們先從時間序列的定義開始講起。

什麼是時間序列

前面章節,我們列舉了生活和工作中的一些時間序列的例子,但是並沒有給出定義。在本節中,我們將首先給出時間序列的定義,然後給出時間序列的分類方法,最後再給大家展示常見的時間序列。

1.時間序列的定義

時間序列是不同時間點的一系列變量所組成的有序序列。例如北京市2013年4月每日的平均氣溫就構成了一個時間序列,為了方便,我們一般認為序列中相鄰元素具有相同的時間間隔。

時間序列可以分為確定的和隨機的。例如,一個1990年出生的人,從1990年到1999年年齡可以表述為{0,1,2,…,9},這個序列並沒有任何隨機因素。這是一個確定性的時間序列。現實生活中我們所面對的序列更多的是摻雜了隨機因素的時間序列,例如氣溫、銷售量等等,這些是帶有隨機性的例子。我們說的時間序列一般是指帶有隨機性的。

那麼對於隨機性的時間序列,又如何進行分類呢?

2.時間序列的分類

從研究對象上分,時間序列分為一元時間序列和多元時間序列,如新冠肺炎例子中,只看確診病例的變化,它是一元時間序列。如果把確診病例和疑似病例聯合起來看,它是一個多元時間序列。

從時間參數上分,時間序列分為離散時間的時間序列和連續時間的時間序列。例如氣溫變化曲線,通常是按照天、小時進行預測、計算的,這個採集的時間是離散的,因此,它是一個離散時間的時間序列。再如花粉在水中呈現不規則的運動,它無時無刻不在運動,它是一個連續時間的時間序列,這就是大家眾所周知的布朗運動。在我們的工作中,我們一般遇到的都是離散時間的時間序列。

從統計特徵上分,時間序列分為平穩時間序列和非平穩時間序列。平穩序列從直觀上講,均值和標準差不隨着時間發生變化,而非平穩序列均值或者標準差一般會隨着時間發生變化。下面兩個圖分別給出平穩序列和非平穩序列的例子。

3.常見的時間序列

在本節,我們將給大家列舉一些常見的時間序列,讓大家對常見的時間序列有一個直觀的概念。

時間序列的分解

前面給大家講了異常和時間序列的概念,本章將給大家講解時間序列分解技術。

1.目的

時間序列分解是探索時序變化規律的一種方法,主要探索周期性和趨勢性。基於時序分解的結果,我們可以進行後續的時間預測和異常檢測。

2.主要組成部分

在時間序列分析中,我們經常要關注趨勢和周期。因此,一般地,我們將時序分成三個部分:趨勢部分、周期部分和殘差部分。結合下圖CO2含量的例子(見下圖)對這三個主要部分進行解釋:

1)趨勢部分:展示了CO2含量逐年增加;

2)周期部分:反應了一年中CO2含量是周期波動的;

3)殘差部分:趨勢和周期部分不能解釋的部分。

3.時序分解模型

時間序列分解基於分解模型的假設。通常,我們會考慮以下兩種模型:

加法模型適用於以下場景:

  1. 當周期性不隨着趨勢發生變化時,首選加法模型,如下圖(a);
  2. 當目標存在負值時,應選擇加法模型;

乘法模型適用於以下場景:

  1. 周期隨着隨時發生變化時,首選乘法模型,如下圖(b);
  2. 經濟數據,首選乘法模型(增長率、可解釋)。

另外,當我們不清楚選擇哪個模型時,可以兩個模型都使用,選擇誤差最小的那一個。

由於乘法模型與加法模型可以相互轉化,我們後面僅以加法模型來進行介紹。

4.時序分解算法

基於周期、趨勢分解的時序分解算法主要有經典時序分解算法、Holt-Winters算法和STL算法。經典時序分解算法起源於20世紀20年代,方法較簡單。Holt-Winters算法於1960年由Holt的學生 Peter Winters 提出,能夠適應隨着時間變化的季節項。STL(Seasonal and Trend decomposition using Loess)分解法,由Cleveland 等於1990年提出,比較通用,且較為穩健。三者之間的關係,如下圖所示:

 

4.1經典時序分解算法

經典時序分解算法是最簡單的一種分解算法,它是很多其他分解算法的基礎。該算法基於“季節部分不隨着時間發生變化”這一假設,且需要知道序列的周期。另外,該算法基於滑動平均技術。

其中,m=2k+1. 也就是說,時刻t的趨勢項的估計值可以通過前後k個時刻內的平均值得到。階數 m 越大,趨勢越光滑。由上面的公式可以看出,m一般取奇數,這保證了對稱性。但是在很多場景下,周期是偶數,例如一年有4個季度,則周期為4,是偶數。此時,需要做先做一個4階滑動平均(4-MA),再對所得結果做一個2階滑動平均(2-MA),整個過程記為 。這樣處理后的結果是對稱的,即加權的滑動平均,數學表達如下:

下面我們將講解經典時序分解算法的計算步驟。

經典時序分解算法雖然簡單、應用廣泛,但是也存在一些問題:

1) 無法估計序列最前面幾個和最後面幾個的趨勢和周期部分,例如若m=4,則無法估計前2個和后2個觀測的趨勢和周期的部分;

2) 嚴重依賴“季節性部分每個周期都是相同的”這一假設;

3) 過度光滑趨勢部分。

4.2Holt-Winters算法

在上一節中,我們介紹了經典時序分解算法,但是它嚴重依賴“季節性部分每個周期都是相同的”這一假設。為了能夠適應季節部分隨時間發生變化,Holt-Winters算法被提出。Holt-Winters算法是基於簡單指數光滑技術。首先,我們先介紹簡單指數光滑技術。

簡單指數光滑的思想主要是以下兩點:

  1. 對未來的預測:用當前的水平對下一時刻的點進行預測;
  2. 當前水平的估計:使用當前時刻的觀測值和預測值(基於歷史觀測數據的預測值,即上一時刻的水平)的加權平均作為當前水平的估計。

簡單指數光滑的模型比較簡單,如下:

Holt-Winters算法是簡單指數光滑在趨勢(可理解為水平的變化率)和季節性上的推廣,主要包括水平(前文中的趨勢項)、趨勢項和季節項三個部分。

4.3 STL算法

STL(Seasonal and Trend decomposition using Loess)是一個非常通用的、穩健性強的時序分解方法,其中Loess是一種估算非線性關係的方法。STL分解法由 Cleveland et al. (1990) 提出。

STL算法中最主要的是局部光滑技術 (locally weighted scatterplot smoothing, LOWESS or LOESS),有時也稱為局部多項式回歸擬合。它是對兩維散點圖進行平滑的常用方法,它結合了傳統線性回歸的簡潔性和非線性回歸的靈活性。當要估計某個響應變量值時,先從其預測點附近取一個數據子集(如下圖實點 是要預測的點,選取周圍的需點來進行擬合),然後對該子集進行線性回歸或二次回歸,回歸時採用加權最小二乘法(如下圖,採用的是高斯核進行加權),即越靠近估計點的值其權重越大,最後利用得到的局部回歸模型來估計響應變量的值。用這種方法進行逐點運算得到整條擬合曲線。

STL算法的主要環節包含內循環、外循環和季節項后平滑三個部分:

  • 內循環:
  • 外循環:

外循環主要作用則是引入了一個穩健性權重項,以控制數據中異常值產生的影響,這一項將會考慮到下一階段內循環的臨近權重中去。

  • 季節項后平滑:

趨勢分量和季節分量都是在內循環中得到的。循環完后,季節項將出現一定程度的毛刺現象,因為在內循環中平滑時是在每一個截口中進行的,因此,在按照時間序列重排后,就無法保證相鄰時段的平滑了,為此,還需要進行季節項的后平滑,后平滑基於局部二次擬合,並且不再需要在loess中進行穩健性迭代。

異常判斷的準則

對於異常的判斷,我們常用的有 n-sigma 準則和boxplot準則(箱線圖準則)。那這些準備是如何計算的,有哪些區別和聯繫呢?

1.n-sigma 準則

n-sigma準則有計算簡單、效率高且有很強的理論支撐,但是需要近似正態的假設,且均值和標準差的計算用到了全部的數據,因此,受異常點的影響較大。

2.boxplot 準則

為了降低異常點的影響,boxplot準則被提出。boxplot(箱線圖)是一種用作显示一組數據分散情況的統計圖,經常用於異常檢測。BoxPlot的核心在於計算一組數據的中位數、兩個四分位數、上限和下限,基於這些統計值畫出箱線圖。

根據上面的統計值就可以畫出下面的圖,超過上限的點或這個低於下限的點都可以認為是異常點。

從上面的計算上可以看出,boxplot對異常點是穩健的。

基於時序分解的異常檢測算法

在前面的章節,我們了解了時序分解的算法,也學習了異常判斷的準則,那麼如何基於時序分解進行異常檢測呢?在本章,我們將首先給出異常檢測算法的原理,再給出基於時序分解的異常檢測算法步驟。

1.異常檢測算法原理

回顧一下異常的定義,它是一個分段函數:

我們可以看到預測值(擬合值)和閾值是不知道的。對於預測值,我們可以通過找規律來猜這個預測值是多少,本章我們可以通過時序分解找周期和趨勢的規律,進而得到預測值。對於閾值,我們可以看到閾值是針對真實值和預測值的差值設置的,目的是把異常值找到,因此我們只要找到正常值的殘差和異常值的殘差的邊界即可。而我們n-sigma準則和boxplot準則就可以根據殘差把邊界找出來,即閾值。這個思考和實現的過程示意圖如下:

2.基於時序分解的異常檢測算法

Demo代碼下載地址 ,本文主要是想記錄基於時間序列的異常檢測方法,希望能夠幫到你。

 

點擊關注,第一時間了解華為雲新鮮技術~

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

Flink 如何分流數據

  • 場景
  • 分流方式
  • 如何分流
    • 使用Filter分流
    • 使用Split分流
    • 使用Side Output分流

場景

獲取流數據的時候,通常需要根據所需把流拆分出其他多個流,根據不同的流再去作相應的處理。

舉個例子:創建一個商品實時流,商品有季節標籤,需要對不同標籤的商品做統計處理,這個時候就需要把商品數據流根據季節標籤分流。

分流方式

  • 使用Filter分流
  • 使用Split分流
  • 使用Side Output分流

如何分流

先模擬一個實時的數據流

import lombok.Data;
@Data
public class Product {
    public Integer id;
    public String seasonType;
}

自定義Source

import common.Product;
import org.apache.flink.streaming.api.functions.source.SourceFunction;

import java.util.ArrayList;
import java.util.Random;

public class ProductStremingSource implements SourceFunction<Product> {
    private boolean isRunning = true;

    @Override
    public void run(SourceContext<Product> ctx) throws Exception {
        while (isRunning){
            // 每一秒鐘產生一條數據
            Product product = generateProduct();
            ctx.collect(product);
            Thread.sleep(1000);
        }
    }

    private Product generateProduct(){
        int i = new Random().nextInt(100);
        ArrayList<String> list = new ArrayList();
        list.add("spring");
        list.add("summer");
        list.add("autumn");
        list.add("winter");
        Product product = new Product();
        product.setSeasonType(list.get(new Random().nextInt(4)));
        product.setId(i);
        return product;
    }
    @Override
    public void cancel() {

    }
}

輸出:

使用Filter分流

使用 filter 算子根據數據的字段進行過濾。

import common.Product;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import source.ProductStremingSource;

public class OutputStremingDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<Product> source = env.addSource(new ProductStremingSource());

        // 使用Filter分流
        SingleOutputStreamOperator<Product> spring = source.filter(product -> "spring".equals(product.getSeasonType()));
        SingleOutputStreamOperator<Product> summer = source.filter(product -> "summer".equals(product.getSeasonType()));
        SingleOutputStreamOperator<Product> autumn  = source.filter(product -> "autumn".equals(product.getSeasonType()));
        SingleOutputStreamOperator<Product> winter  = source.filter(product -> "winter".equals(product.getSeasonType()));
        source.print();
        winter.printToErr();

        env.execute("output");
    }
}

結果輸出(紅色為季節標籤是winter的分流輸出):

使用Split分流

重寫OutputSelector內部類的select()方法,根據數據所需要分流的類型反正不同的標籤下,返回SplitStream,通過SplitStream的select()方法去選擇相應的數據流。

只分流一次是沒有問題的,但是不能使用它來做連續的分流。

SplitStream已經標記過時了

public class OutputStremingDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<Product> source = env.addSource(new ProductStremingSource());

        // 使用Split分流
        SplitStream<Product> dataSelect = source.split(new OutputSelector<Product>() {
            @Override
            public Iterable<String> select(Product product) {
                List<String> seasonTypes = new ArrayList<>();
                String seasonType = product.getSeasonType();
                switch (seasonType){
                    case "spring":
                        seasonTypes.add(seasonType);
                        break;
                    case "summer":
                        seasonTypes.add(seasonType);
                        break;
                    case "autumn":
                        seasonTypes.add(seasonType);
                        break;
                    case "winter":
                        seasonTypes.add(seasonType);
                        break;
                    default:
                        break;
                }
                return seasonTypes;
            }
        });
        DataStream<Product> spring = dataSelect.select("machine");
        DataStream<Product> summer = dataSelect.select("docker");
        DataStream<Product> autumn = dataSelect.select("application");
        DataStream<Product> winter = dataSelect.select("middleware");
        source.print();
        winter.printToErr();

        env.execute("output");
    }
}

使用Side Output分流

推薦使用這種方式

首先需要定義一個OutputTag用於標識不同流

可以使用下面的幾種函數處理流發送到分流中:

  • ProcessFunction
  • KeyedProcessFunction
  • CoProcessFunction
  • KeyedCoProcessFunction
  • ProcessWindowFunction
  • ProcessAllWindowFunction

之後再用getSideOutput(OutputTag)選擇流。

public class OutputStremingDemo {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<Product> source = env.addSource(new ProductStremingSource());

        // 使用Side Output分流
        final OutputTag<Product> spring = new OutputTag<Product>("spring");
        final OutputTag<Product> summer = new OutputTag<Product>("summer");
        final OutputTag<Product> autumn = new OutputTag<Product>("autumn");
        final OutputTag<Product> winter = new OutputTag<Product>("winter");
        SingleOutputStreamOperator<Product> sideOutputData = source.process(new ProcessFunction<Product, Product>() {
            @Override
            public void processElement(Product product, Context ctx, Collector<Product> out) throws Exception {
                String seasonType = product.getSeasonType();
                switch (seasonType){
                    case "spring":
                        ctx.output(spring,product);
                        break;
                    case "summer":
                        ctx.output(summer,product);
                        break;
                    case "autumn":
                        ctx.output(autumn,product);
                        break;
                    case "winter":
                        ctx.output(winter,product);
                        break;
                    default:
                        out.collect(product);
                }
            }
        });

        DataStream<Product> springStream = sideOutputData.getSideOutput(spring);
        DataStream<Product> summerStream = sideOutputData.getSideOutput(summer);
        DataStream<Product> autumnStream = sideOutputData.getSideOutput(autumn);
        DataStream<Product> winterStream = sideOutputData.getSideOutput(winter);

        // 輸出標籤為:winter 的數據流
        winterStream.print();

        env.execute("output");
    }
}

結果輸出:

更多文章:www.ipooli.com

掃碼關注公眾號《ipoo》

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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