月份: 2020 年 9 月

  • Jmeter(十二) – 從入門到精通 – JMeter邏輯控制器 – 終篇(詳解教程)

    Jmeter(十二) – 從入門到精通 – JMeter邏輯控制器 – 終篇(詳解教程)

    1.簡介

    Jmeter官網對邏輯控制器的解釋是:“Logic Controllers determine the order in which Samplers are processed.”。

    意思是說,邏輯控制器可以控制採樣器(samplers)的執行順序。由此可知,控制器需要和採樣器一起使用,否則控制器就沒有什麼意義了。放在控制器下面的所有的採樣器都會當做一個整體,執行時也會一起被執行。

    JMeter邏輯控制器可以對元件的執行邏輯進行控制,除僅一次控制器外,其他可以嵌套別的種類的邏輯控制器。

    2.邏輯控制器分類

    JMeter中的Logic Controller分為兩類:
    (1)控制測試計劃執行過程中節點的邏輯執行順序,如:Loop Controller、If Controller等;
    (2)對測試計劃中的腳本進行分組、方便JMeter統計執行結果以及進行腳本的運行時控制等,如:Throughput Controller、Transaction Controller。

    3.預覽邏輯控制器 

    首先我們來看一下JMeter的邏輯控制器,路徑:線程組(用戶)->添加->邏輯控制器(Logic Controller);我們可以清楚地看到JMeter5中共有17個邏輯控制器,如下圖所示:

    如果上圖您看得不是很清楚的話,宏哥總結了一個思維導圖,關於JMeter5的邏輯控制器類型,如下圖所示: 

     通過以上的了解,我們對邏輯控制器有了一個大致的了解和認識。下面宏哥就給小夥伴或則童鞋們分享講解一些通常在工作中會用到的邏輯控制器。 

    4.常用邏輯控制器詳解

      這一小節,宏哥就由上而下地詳細地講解一下常用的邏輯控制器。

    4.1Runtime Controller

    運行控制器用來控制其子元件的執行時長。市場單位是秒。

     1、我們先來看看這個Runtime Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 運行控制器,如下圖所示:

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空;

    Runtime:默認為1,去掉1則默認為0,此時不執行其節點下的元件。 與線程組中的調度器的持續時間 效果一致。不填 或 0,不會執行樣例

    4.1.1Runtime控制器控制其下取樣器執行2s

    1、創建測試計劃,設置 Runtime 控制器的運行時間 為 2,線程組設置默認不變,如下圖所示:

    Runtime 控制器設置

    線程組設置

    2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

    4.1.2使用線程組中的調度器控制樣例運行3s

    1、創建測試計劃,設置 Runtime 控制器的運行時間 為 2,線程組設置運行時間3,如下圖所示:

    線程組設置

    Runtime 控制器設置

    2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

    線程組設置3,Runtime控制器設置2,但是運行時間是2s。所以從上邊的運行時間得出結論:如果線程組中設置了持續時間,Runtime 控制器也設置了 運行時間,那麼會優先於線程組中的設置。

    4.2Simple Controller

    Simple Controller用來指定了一個執行單元,它不改變元件的執行順序。在它下邊還可以嵌套其他控制器。簡單控制器可以編輯只有名稱和註釋。就像他的名字一樣,簡單,可以理解為一個文件夾,就是分組用的,沒有其他特殊功能,但相比不添加簡單控制器,區別在於簡單控制器可以被模塊控制器所引用。其作用就是分組,比如QQ好友列表,可分為家人、同學、等。一般是請求較多,需要分組時採用。

     1、我們先來看看這個Simple Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 簡單控制器,如下圖所示:

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空。

    4.2.1簡單實例

    1、創建測試計劃,線程組設置循環10,如下圖所示:

    2、配置好以後,運行JMeter,然後查看結果樹,如下圖所示:

    4.3Throughput Controller

    用來控制其下元件的執行次數,並無控制吞吐量的功能,想要控制吞吐量可以使用Constant Throughput Timer,後邊會講解到。吞吐量控制器有兩種模式:Total Executions:設置運行次數與Percent Executions:設置運行比例(1~100之間)。

    1、我們先來看看這個Throughput Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 吞吐量控制器,如下圖所示: 

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空;

    Total Executions:執行百分比(1-100);

    percent Executions:執行數量;

    Throughput:根據上邊選擇的方式填寫,百分比為0~100;

    Per User:線程數,當選Total Executions時,是線程數;當選percent Executions時,是線程數*循環次數。

    4.3.1不勾選Per User

    1、線程組中設置 線程數量 2,循環次數 10,吞吐量控制器 設置 Total Executions,吞吐量設置為 2,其下添加一個取樣器,如下圖所示:

    2、配置好以後,運行JMeter,然後查看結果樹(執行了2次),如下圖所示:

    3、現在將 吞吐量控制器 設置為百分比的控制方式,吞吐量設置為:50%,如下圖所示:

    4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了10次,計算方式:10=吞吐量50% * 循環次數10 * 線程數 2),如下圖所示:

    4.3.2勾選Per User

    1、線程組中設置 線程數量 2,循環次數 10,吞吐量控制器 設置 Total Executions,吞吐量設置為 2,其下添加一個取樣器,勾選Per User,如下圖所示:

    線程組設置

    吞吐量控制器

    2、配置好以後,點擊“保存”,運行JMeter,然後查看結果樹(總共執行了4次,其中吞吐量設置為2,執行2次,線程設置為2,執行2次,總共4次),函數 __threadNum 只是簡單地返回當前線程的編號,如下圖所示:

    3、現在將 吞吐量控制器 設置為百分比的控制方式,吞吐量設置為:50,如下圖所示:

    4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了10次,計算方式:10=吞吐量50% * 循環次數10 * 線程數 2),如下圖所示:

    綜上所述:

    勾選Per User:

    1.線程數*循環次數>=線程數*吞吐量時,Total Executions模式的執行次數=線程數*吞吐量。

    2.線程數*循環次數<線程數*吞吐量時,Total Executions模式的執行次數=當線程數*循環次數。

    不勾選Per User:

    1.線程數*循環次數<=吞吐量時,Total Executions模式的執行次數=線程數*循環次數。

    2.線程數*循環次數>吞吐量時,Total Executions模式的執行次數=吞吐量。

    l Percent Executions:設置運行比例(1~100之間),單位為%

    不管Per User是否勾選,按Percent Executions模式的執行次數都不受Per User影響,Percent Executions模式的執行次數=線程數*循環次數*吞吐量%。(循環次數=線程組循環次數*循環控制器循環次數)

    l Per User:勾選該項的話則按虛擬用戶數(線程數)來計算執行次數,不勾選則按所有虛擬用戶數來計算執行次數

    測試計劃

    序號 線程數 循環次數 模式 Throughput Per User 執行次數
    1 2 10 Percent 50 Y 10
    2 2 10 Percent 50 N 10
    3 2 10 Total 7 Y 14
    4 2 10 Total 7 N 7
    5 2 2 Total 7 Y 4
    6 2 2 Total 7 N 4

    下面說明一下這6個場景:
    (1)序號1和2場景,Per User 對總執行次數沒有影響。
    (2)序號3場景,Per User勾選,每個虛擬用戶(線程)執行7次,共執行14次。
    (3)序號4場景,Per User不勾選,則所有虛擬用戶執行7次。
    (4)序號5場景,Per User勾選,每個虛擬用戶(線程)執行7次,共執行14次,由於Thread Group計劃循環次數是4(2線程*2循環)次,所以最多只能執行4次。
    (5)序號6場景,Per User不勾選,所有虛擬用戶執行7次,由於Thread Group計劃循環次數是4(2線程*2循環)次,所以最多只能執行4次。

    4.4Module Controller

    模塊控制器可以快速的切換腳本,不用來回的新建,方便腳本調試。 

    可以理解為引用、調用的意思,執行內容為Module To Run種所選的內容,引用範圍為當前測試計劃內的測試片段、邏輯控制器<模塊控制器除外>
    被引用的邏輯控制器、測試片段可以為禁用狀態,被引用后仍然會被執行。
    可以將模塊控制器與包括控制器一起學習比較,模塊控制器是從內部文件中引用,引用上相對比較靈活,可以只引用部分測試片段或模塊內容,包括控制器是從外部文件引用,只能引用整個測試片段的內容。
    注意:被應用的模塊位置不可隨意變更,變更後會執行時出現提示引用失敗
    找到目標元素:快速查找與跳轉的作用,點擊後會立即跳轉到所選的邏輯控制器的內容詳情

    1、我們先來看看這個Module Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 >  模塊控制器,如下圖所示: 

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空;

    Forever:勾選上這一項表示一直循環下去。

    4.4.1實例

    1、創建測試計劃,添加兩個測試片段,並且在每個測試片段下添加一個取樣器,然後,添加線程組,再添加模塊控制器,最後添加查看結果樹,如下圖所示:

    2、配置模塊控制器,選擇第一個測試片段,如下圖所示:

    3、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第1個測試片段的取樣器),如下圖所示:

    4、配置模塊控制器,選擇第二個測試片段,如下圖所示: 

    5、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第2個測試片段的取樣器),如下圖所示:

    4.5Switch Controller

    Switch Controller:開關控制器,通過其下樣例順序數值或名稱 控制執行某一個樣例。

     1、我們先來看看這個if Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 如果 (if) 控制器,如下圖所示:

    2、關鍵參數說明如下:

    Name:名稱,可以隨意設置,甚至為空;

    Comments:註釋,可隨意設置,可以為空;

    Switch Value:指定請求的索引或者名稱,索引從0開始,如果沒有賦值,或者索引超過請求個數的話就執行第0個請求。可以是数字,也可以是字符,為字符時匹配取樣器名稱,如果匹配不上就會默認並找取樣器名稱為default的取樣器,如果沒有則不運行。

    4.5.1數值

    數值:表示將執行其下第 數值+1個取樣器,例如:填1,將執行第2個取樣器;填0或者不填,將執行第1個取樣器;數值超出其下取樣器數目時,執行第1個取樣器。

    1、創建一個測試計劃,設置線程組和Switch控制器,如下圖所示:

    線程組

    Switch控制器

    2、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第3<數值+1>個取樣器),如下圖所示:

    3、修改Switch控制器的數值為0或者不填,如下圖所示:

    4、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了第1<數值為0或者不填,執行第1個取樣器>個取樣器),如下圖所示:

    4.5.2字符

    1、創建一個測試計劃,設置線程組和Switch控制器(直接使用取樣器名字),如下圖所示:

    線程組

    Switch控制器

    2、配置好以後,點擊“保存”運行JMeter,然後查看結果樹(執行了使用名字的取樣器),如下圖所示:

    5.小結

    好了,今天關於邏輯控制器的上篇就講解到這裏,這一篇主要介紹了 Runtime Controller 、 Simple Controller 、Throughput ControllerModule Controller 和  Switch Controller

     

    您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波  推薦  不要忘記哦!!!

    別忘了點 推薦 留下您來過的痕迹

     

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 基於領域驅動設計(DDD)超輕量級快速開發架構(二)動態linq查詢的實現方式

    基於領域驅動設計(DDD)超輕量級快速開發架構(二)動態linq查詢的實現方式

    -之動態查詢,查詢邏輯封裝復用

    基於領域驅動設計(DDD)超輕量級快速開發架構詳細介紹請看

    https://www.cnblogs.com/neozhu/p/13174234.html

    需求

    1. 配合EasyUI datagird filter實現多字段(任意字段)的篩選
    2. 根據業務需求篩選特定的狀態或條件,如:查看結案的訂單,最近30天的訂單,查看屬於我的訂單.等等,這些邏輯是固定也是可以被重用,但又不想每次寫相同的條件,那麼下面我會給我的解決方案.

    需求1隻是一個偷懶的實現方式,因為datagrid自帶這個功能,但又不想根據具體的需求來畫查詢條件,如果需求必須要再datagrid上面做一塊查詢條件的輸入那目前只能在前端自己手工添加,在組織後傳入後台,暫時不在這裏討論

    需求2可能不太好解釋,看完代碼就自然理解為什麼要這麼做了,這麼做的好處有哪些

    具體實現的方式

     

     默認情況下 datagrid 有幾列就可以對這幾列進行篩選,對於日期型的字段會採用between,選擇2個時間之間進行篩選,数字類型會提供大於小於等符號選擇,可以自行嘗試,其原理是datagrid 會根據datagrid 頭部輸入的值生成一個Json字符串發送後台請求數據

    JSON:格式

    filterRules: [
    {field:field,op:op,value:value},
    {field:field,op:op,value:value},
    ] 
    • 通常的做法是一個一個判斷加條件
      1 var filters = JsonConvert.DeserializeObject<IEnumerable<filterRule>>(filterRules); 
      2 foreach (var rule in filters)
      3         {
      4           if (rule.field == "Id" && !string.IsNullOrEmpty(rule.value) && rule.value.IsInt())
      5           {
      6             var val = Convert.ToInt32(rule.value);
      7             switch (rule.op)
      8             {
      9               case "equal":
     10                 this.And(x => x.Id == val);
     11                 break;
     12               case "notequal":
     13                 this.And(x => x.Id != val);
     14                 break;
     15               case "less":
     16                 this.And(x => x.Id < val);
     17                 break;
     18               case "lessorequal":
     19                 this.And(x => x.Id <= val);
     20                 break;
     21               case "greater":
     22                 this.And(x => x.Id > val);
     23                 break;
     24               case "greaterorequal":
     25                 this.And(x => x.Id >= val);
     26                 break;
     27               default:
     28                 this.And(x => x.Id == val);
     29                 break;
     30             }
     31           }
     32           if (rule.field == "Name" && !string.IsNullOrEmpty(rule.value))
     33           {
     34             this.And(x => x.Name.Contains(rule.value));
     35           }
     36           if (rule.field == "Code" && !string.IsNullOrEmpty(rule.value))
     37           {
     38             this.And(x => x.Code.Contains(rule.value));
     39           }
     40 
     41           if (rule.field == "Address" && !string.IsNullOrEmpty(rule.value))
     42           {
     43             this.And(x => x.Address.Contains(rule.value));
     44           }
     45 
     46           if (rule.field == "Contect" && !string.IsNullOrEmpty(rule.value))
     47           {
     48             this.And(x => x.Contect.Contains(rule.value));
     49           }
     50 
     51           if (rule.field == "PhoneNumber" && !string.IsNullOrEmpty(rule.value))
     52           {
     53             this.And(x => x.PhoneNumber.Contains(rule.value));
     54           }
     55 
     56           if (rule.field == "RegisterDate" && !string.IsNullOrEmpty(rule.value))
     57           {
     58             if (rule.op == "between")
     59             {
     60               var datearray = rule.value.Split(new char[] { '-' });
     61               var start = Convert.ToDateTime(datearray[0]);
     62               var end = Convert.ToDateTime(datearray[1]);
     63 
     64               this.And(x => SqlFunctions.DateDiff("d", start, x.RegisterDate) >= 0);
     65               this.And(x => SqlFunctions.DateDiff("d", end, x.RegisterDate) <= 0);
     66             }
     67           }
     68           if (rule.field == "CreatedDate" && !string.IsNullOrEmpty(rule.value))
     69           {
     70             if (rule.op == "between")
     71             {
     72               var datearray = rule.value.Split(new char[] { '-' });
     73               var start = Convert.ToDateTime(datearray[0]);
     74               var end = Convert.ToDateTime(datearray[1]);
     75 
     76               this.And(x => SqlFunctions.DateDiff("d", start, x.CreatedDate) >= 0);
     77               this.And(x => SqlFunctions.DateDiff("d", end, x.CreatedDate) <= 0);
     78             }
     79           }
     80 
     81 
     82           if (rule.field == "CreatedBy" && !string.IsNullOrEmpty(rule.value))
     83           {
     84             this.And(x => x.CreatedBy.Contains(rule.value));
     85           }
     86 
     87          if (rule.field == "LastModifiedDate" && !string.IsNullOrEmpty(rule.value))
     88           {
     89             if (rule.op == "between")
     90             {
     91               var datearray = rule.value.Split(new char[] { '-' });
     92               var start = Convert.ToDateTime(datearray[0]);
     93               var end = Convert.ToDateTime(datearray[1]);
     94 
     95               this.And(x => SqlFunctions.DateDiff("d", start, x.LastModifiedDate) >= 0);
     96               this.And(x => SqlFunctions.DateDiff("d", end, x.LastModifiedDate) <= 0);
     97             }
     98           }
     99 
    100           if (rule.field == "LastModifiedBy" && !string.IsNullOrEmpty(rule.value))
    101           {
    102             this.And(x => x.LastModifiedBy.Contains(rule.value));
    103           }
    104 
    105         }

    View Code

    • 新的做法是動態根據field,op,value生成一個linq 表達式,不用再做繁瑣的判斷,這塊代碼也可以被其它項目使用,非常好用
    namespace SmartAdmin
    {
     
      public static class PredicateBuilder
      {
    
        public static Expression<Func<T, bool>> FromFilter<T>(string filtergroup) {
          Expression<Func<T, bool>> any = x => true;
          if (!string.IsNullOrEmpty(filtergroup))
          {
            var filters = JsonSerializer.Deserialize<filter[]>(filtergroup);
    
              foreach (var filter in filters)
              {
                if (Enum.TryParse(filter.op, out OperationExpression op) && !string.IsNullOrEmpty(filter.value))
                {
                  var expression = GetCriteriaWhere<T>(filter.field, op, filter.value);
                  any = any.And(expression);
                }
              }
          }
    
          return any;
        }
    
        #region -- Public methods --
        public static Expression<Func<T, bool>> GetCriteriaWhere<T>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
        {
          var name = GetOperand<T>(e);
          return GetCriteriaWhere<T>(name, selectedOperator, fieldValue);
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(Expression<Func<T, object>> e, OperationExpression selectedOperator, object fieldValue)
        {
          var name = GetOperand<T>(e);
          return GetCriteriaWhere<T, T2>(name, selectedOperator, fieldValue);
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T>(string fieldName, OperationExpression selectedOperator, object fieldValue)
        {
          var props = TypeDescriptor.GetProperties(typeof(T));
          var prop = GetProperty(props, fieldName, true);
          var parameter = Expression.Parameter(typeof(T));
          var expressionParameter = GetMemberExpression<T>(parameter, fieldName);
          if (prop != null && fieldValue != null)
          {
           
            BinaryExpression body = null;
            switch (selectedOperator)
            {
              case OperationExpression.equal:
                body = Expression.Equal(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType)?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.notequal:
                body = Expression.NotEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.less:
                body = Expression.LessThan(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.lessorequal:
                body = Expression.LessThanOrEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.greater:
                body = Expression.GreaterThan(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.greaterorequal:
                body = Expression.GreaterThanOrEqual(expressionParameter, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(body, parameter);
              case OperationExpression.contains:
                var contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                var bodyLike = Expression.Call(expressionParameter, contains, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodyLike, parameter);
              case OperationExpression.endwith:
                var endswith = typeof(string).GetMethod("EndsWith",new[] { typeof(string) });
                var bodyendwith = Expression.Call(expressionParameter, endswith, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodyendwith, parameter);
              case OperationExpression.beginwith:
                var startswith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
                var bodystartswith = Expression.Call(expressionParameter, startswith, Expression.Constant(Convert.ChangeType(fieldValue, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType), prop.PropertyType));
                return Expression.Lambda<Func<T, bool>>(bodystartswith, parameter);
              case OperationExpression.includes:
                return Includes<T>(fieldValue, parameter, expressionParameter, prop.PropertyType);
              case OperationExpression.between:
                return Between<T>(fieldValue, parameter, expressionParameter, prop.PropertyType);
              default:
                throw new Exception("Not implement Operation");
            }
          }
          else
          {
            Expression<Func<T, bool>> filter = x => true;
            return filter;
          }
        }
    
        public static Expression<Func<T, bool>> GetCriteriaWhere<T, T2>(string fieldName, OperationExpression selectedOperator, object fieldValue)
        {
    
    
          var props = TypeDescriptor.GetProperties(typeof(T));
          var prop = GetProperty(props, fieldName, true);
    
          var parameter = Expression.Parameter(typeof(T));
          var expressionParameter = GetMemberExpression<T>(parameter, fieldName);
    
          if (prop != null && fieldValue != null)
          {
            switch (selectedOperator)
            {
              case OperationExpression.any:
                return Any<T, T2>(fieldValue, parameter, expressionParameter);
    
              default:
                throw new Exception("Not implement Operation");
            }
          }
          else
          {
            Expression<Func<T, bool>> filter = x => true;
            return filter;
          }
        }
    
    
    
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> or)
        {
          if (expr == null)
          {
            return or;
          }
    
          return Expression.Lambda<Func<T, bool>>(Expression.OrElse(new SwapVisitor(expr.Parameters[0], or.Parameters[0]).Visit(expr.Body), or.Body), or.Parameters);
        }
    
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> and)
        {
          if (expr == null)
          {
            return and;
          }
    
          return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(new SwapVisitor(expr.Parameters[0], and.Parameters[0]).Visit(expr.Body), and.Body), and.Parameters);
        }
    
        #endregion
        #region -- Private methods --
    
        private static string GetOperand<T>(Expression<Func<T, object>> exp)
        {
          if (!( exp.Body is MemberExpression body ))
          {
            var ubody = (UnaryExpression)exp.Body;
            body = ubody.Operand as MemberExpression;
          }
    
          var operand = body.ToString();
    
          return operand.Substring(2);
    
        }
    
        private static MemberExpression GetMemberExpression<T>(ParameterExpression parameter, string propName)
        {
          if (string.IsNullOrEmpty(propName))
          {
            return null;
          }
    
          var propertiesName = propName.Split('.');
          if (propertiesName.Count() == 2)
          {
            return Expression.Property(Expression.Property(parameter, propertiesName[0]), propertiesName[1]);
          }
    
          return Expression.Property(parameter, propName);
        }
    
        private static Expression<Func<T, bool>> Includes<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression ,Type type)
        {
          var safetype= Nullable.GetUnderlyingType(type) ?? type;
    
          switch (safetype.Name.ToLower())
          {
            case  "string":
              var strlist = (IEnumerable<string>)fieldValue;
              if (strlist == null || strlist.Count() == 0)
              {
                return x => true;
              }
              var strmethod = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
              var strcallexp = Expression.Call(Expression.Constant(strlist.ToList()), strmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(strcallexp, parameterExpression);
            case "int32":
              var intlist = (IEnumerable<int>)fieldValue;
              if (intlist == null || intlist.Count() == 0)
              {
                return x => true;
              }
              var intmethod = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
              var intcallexp = Expression.Call(Expression.Constant(intlist.ToList()), intmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(intcallexp, parameterExpression);
            case "float":
              var floatlist = (IEnumerable<float>)fieldValue;
              if (floatlist == null || floatlist.Count() == 0)
              {
                return x => true;
              }
              var floatmethod = typeof(List<int>).GetMethod("Contains", new Type[] { typeof(int) });
              var floatcallexp = Expression.Call(Expression.Constant(floatlist.ToList()), floatmethod, memberExpression);
              return Expression.Lambda<Func<T, bool>>(floatcallexp, parameterExpression);
            default:
              return x => true;
          }
          
        }
        private static Expression<Func<T, bool>> Between<T>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression, Type type)
        {
          
          var safetype = Nullable.GetUnderlyingType(type) ?? type;
          switch (safetype.Name.ToLower())
          {
            case "datetime":
              var datearray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var start = Convert.ToDateTime(datearray[0] + " 00:00:00", CultureInfo.CurrentCulture);
              var end = Convert.ToDateTime(datearray[1] + " 23:59:59", CultureInfo.CurrentCulture);
              var greater = Expression.GreaterThan(memberExpression, Expression.Constant(start, type));
              var less = Expression.LessThan(memberExpression, Expression.Constant(end, type));
              return Expression.Lambda<Func<T, bool>>(greater, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(less, parameterExpression));
            case "int":
            case "int32":
              var intarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var min = Convert.ToInt32(intarray[0] , CultureInfo.CurrentCulture);
              var max = Convert.ToInt32(intarray[1], CultureInfo.CurrentCulture);
              var maxthen = Expression.GreaterThan(memberExpression, Expression.Constant(min, type));
              var minthen = Expression.LessThan(memberExpression, Expression.Constant(max, type));
              return Expression.Lambda<Func<T, bool>>(maxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(minthen, parameterExpression));
            case "decimal":
              var decarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var dmin = Convert.ToDecimal(decarray[0], CultureInfo.CurrentCulture);
              var dmax = Convert.ToDecimal(decarray[1], CultureInfo.CurrentCulture);
              var dmaxthen = Expression.GreaterThan(memberExpression, Expression.Constant(dmin, type));
              var dminthen = Expression.LessThan(memberExpression, Expression.Constant(dmax, type));
              return Expression.Lambda<Func<T, bool>>(dmaxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(dminthen, parameterExpression));
            case "float":
              var farray = ((string)fieldValue).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var fmin = Convert.ToDecimal(farray[0], CultureInfo.CurrentCulture);
              var fmax = Convert.ToDecimal(farray[1], CultureInfo.CurrentCulture);
              var fmaxthen = Expression.GreaterThan(memberExpression, Expression.Constant(fmin, type));
              var fminthen = Expression.LessThan(memberExpression, Expression.Constant(fmax, type));
              return Expression.Lambda<Func<T, bool>>(fmaxthen, parameterExpression)
                .And(Expression.Lambda<Func<T, bool>>(fminthen, parameterExpression));
            case "string":
              var strarray = ( (string)fieldValue ).Split(new char[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
              var smin = strarray[0];
              var smax = strarray[1];
            
              var strmethod = typeof(string).GetMethod("Contains");
              var mm = Expression.Call(memberExpression, strmethod, Expression.Constant(smin, type));
              var nn = Expression.Call(memberExpression, strmethod, Expression.Constant(smax, type));
    
    
              return Expression.Lambda<Func<T, bool>>(mm, parameterExpression)
                .Or(Expression.Lambda<Func<T, bool>>(nn, parameterExpression));
            default:
              return x => true;
          }
    
        }
    
    
    
        private static Expression<Func<T, bool>> Any<T, T2>(object fieldValue, ParameterExpression parameterExpression, MemberExpression memberExpression)
        {
          var lambda = (Expression<Func<T2, bool>>)fieldValue;
          var anyMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
          .First(m => m.Name == "Any" && m.GetParameters().Count() == 2).MakeGenericMethod(typeof(T2));
    
          var body = Expression.Call(anyMethod, memberExpression, lambda);
    
          return Expression.Lambda<Func<T, bool>>(body, parameterExpression);
        }
    
        private static PropertyDescriptor GetProperty(PropertyDescriptorCollection props, string fieldName, bool ignoreCase)
        {
          if (!fieldName.Contains('.'))
          {
            return props.Find(fieldName, ignoreCase);
          }
    
          var fieldNameProperty = fieldName.Split('.');
          return props.Find(fieldNameProperty[0], ignoreCase).GetChildProperties().Find(fieldNameProperty[1], ignoreCase);
    
        }
        #endregion
      }
    
      internal class SwapVisitor : ExpressionVisitor
      {
        private readonly Expression from, to;
        public SwapVisitor(Expression from, Expression to)
        {
          this.from = from;
          this.to = to;
        }
        public override Expression Visit(Expression node) => node == from ? to : base.Visit(node);
      }
      public enum OperationExpression
      {
        equal,
        notequal,
        less,
        lessorequal,
        greater,
        greaterorequal,
        contains,
        beginwith,
        endwith,
        includes,
        between,
        any
      }
    }

    View Code

     1 public async Task<JsonResult> GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
     2     {
     3       try
     4       {
     5         var filters = PredicateBuilder.FromFilter<Company>(filterRules);
     6         var total = await this.companyService
     7                              .Query(filters)
     8                              .AsNoTracking()
     9                              .CountAsync()
    10                               ;
    11         var pagerows = (await this.companyService
    12                              .Query(filters)
    13                               .AsNoTracking()
    14                            .OrderBy(n => n.OrderBy(sort, order))
    15                            .Skip(page - 1).Take(rows)
    16                            .SelectAsync())
    17                            .Select(n => new
    18                            {
    19                              Id = n.Id,
    20                              Name = n.Name,
    21                              Code = n.Code,
    22                              Address = n.Address,
    23                              Contect = n.Contect,
    24                              PhoneNumber = n.PhoneNumber,
    25                              RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
    26                            }).ToList();
    27         var pagelist = new { total = total, rows = pagerows };
    28         return Json(pagelist);
    29       }
    30       catch(Exception e) {
    31         throw e;
    32         }
    33 
    34     }

    配合使用的代碼

    • 對於固定查詢邏輯的封裝和復用,當然除了復用還可以明顯的提高代碼的可讀性.
    public class OrderSalesQuery : QueryObject<Order>
    {
        public decimal Amount { get; set; }
        public string Country { get; set; }
        public DateTime FromDate { get; set; }
        public DateTime ToDate { get; set; }
    
        public override Expression<Func<Order, bool>> Query()
        {
            return (x => 
                x.OrderDetails.Sum(y => y.UnitPrice) > Amount &&
                x.OrderDate >= FromDate &&
                x.OrderDate <= ToDate &&
                x.ShipCountry == Country);
        }
    }

    查看訂單的銷售情況,條件 金額,國家,日期

    var orderRepository = new Repository<Order>(this);
        
    var orders = orderRepository
        .Query(new OrderSalesQuery(){ 
            Amount = 100, 
            Country = "USA",
            FromDate = DateTime.Parse("01/01/1996"), 
            ToDate = DateTime.Parse("12/31/1996" )
        })
        .Select();

    調用查詢方法

    public class CustomerLogisticsQuery : QueryObject<Customer>
    {
        public CustomerLogisticsQuery FromCountry(string country)
        {
            Add(x => x.Country == country);
            return this;
        }
    
        public CustomerLogisticsQuery LivesInCity(string city)
        {   
            Add(x => x.City == city);
            return this;
        }
    }

    客戶查詢 根據國家和城市查詢

    public class CustomerSalesQuery : QueryObject<Customer>
    {
        public CustomerSalesQuery WithPurchasesMoreThan(decimal amount)
        {
            Add(x => x.Orders
                .SelectMany(y => y.OrderDetails)
                .Sum(z => z.UnitPrice * z.Quantity) > amount);
    
            return this;
        }
    
        public CustomerSalesQuery WithQuantitiesMoreThan(decimal quantity)
        {
            Add(x => x.Orders
                .SelectMany(y => y.OrderDetails)
                .Sum(z => z.Quantity) > quantity);
    
            return this;
        }
    }

    客戶的銷售情況,金額和數量

    var customerRepository = new Repository<Customer>(this);
    
    var query1 = new CustomerLogisticsQuery()
        .LivesInCity("London");
    
    var query2 = new CustomerSalesQuery()
        .WithPurchasesMoreThan(100)
        .WithQuantitiesMoreThan(10);
    
    customerRepository
        .Query(query1.And(query2))
        .Select()
        .Dump();

    復用上面的定義的查詢方法

    以上這些都是改項目提供的方法,非常的好用

     

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

    【其他文章推薦】

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

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

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

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

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

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

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

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

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

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

    7,Docker容器目錄掛載

      7.1 簡介

    容器目錄掛載:

    我們可以在創建容器的時候,將宿主機的目錄與容器內的目錄進行映射,這樣我們就可以實現宿主機和容器目錄的雙向數據自動同步;

      7.2 作用

    前面學過cp命令來實現數據傳遞,這種方式比較麻煩;

    我們通過容器目錄掛載,能夠輕鬆實現代碼上傳,配置修改,日誌同步等需求;

      7.3 實現

    語法:

    docker run -it -v  /宿主機目錄:/容器目錄 鏡像名

    多目錄掛載

    docker run -it -v /宿主機目錄:/容器目錄 -v /宿主機目錄2:/容器目錄2  鏡像名

    注意:

    如果你同步的是多級目錄,可能會出現權限不足的提示;

    這是因為Centos7中的安全模塊selinux把權限禁掉了,我們需要添加  –privileged=true 來解決掛載的目錄沒有權限的問題;

      7.4 掛載目錄只讀

    docker run -it -v  /宿主機目錄:/容器目錄:ro 鏡像名

     

    8,Docker遷移與備份

      8.1 概述

    我們開發的時候,經常自定義鏡像,然後commit提交成鏡像到本地倉庫,但是我們發布到客戶服務器的時候,可以用前面講得搞到hub官方,或者阿里雲,但是有些機密性的項目,是禁止公網存儲的,所以我們只能通過docker鏡像備份和遷移實現;

      8.2 實現

    備份鏡像:

    docker save -o 備份鏡像的名稱  源鏡像名稱:tag版本

     docker save -o mytomcat7.1.tar java1234/tomcat7:7.1

     

    恢復鏡像:

    docker load -i 鏡像文件

    docker load -i mytomcat7.1.tar

     

    9,DockerFile詳解

      9.1 DockerFile簡介

    Dockerfile是由一系列命令和參數構成的腳本,這些命令應用於操作系統(centos或者Ubuntu)基礎鏡像並最終創建的一個新鏡像;

    我們前面講過的用手工的方式,修改配置文件,或者添加,刪除文件目錄的方式,來構建一種新鏡像;這種手工方式麻煩,容易出錯,而且不能復用;

    我們這裏講Dockerfile,用腳本方式來構建自動化,可復用的,高效率的創建鏡像方式,是企業級開發的首選方式;

     

    再軟件系統開發生命周期中,採用Dockerfile來構建鏡像;

    1、對於開發人員:可以為開發團隊提供一個完全一致的開發環境;

    2、對於測試人員:可以直接拿開發時所構建的鏡像或者通過Dockerfile文件構建一個新的鏡像開始工作;

    3、對於運維人員:在部署時,可以實現應用的無縫移植。

      9.2 DockerFile常用指令

    FROM image_name:tag 定義了使用哪個基礎鏡像啟動構建流程
    MAINTAINER user_info 聲明鏡像維護者信息
    LABEL key value 鏡像描述元信息(可以寫多條)
    ENV key value 設置環境變量(可以寫多條)
    RUN command 構建鏡像時需要運行的命令(可以寫多條)
    WORKDIR path_dir 設置終端默認登錄進來的工作目錄
    EXPOSE port 當前容器對外暴露出的端口
    ADD source_dir/file dest_dir/file 將宿主機的文件複製到容器內,如果是一個壓縮文件,將會在複製后自動解壓
    COPY source_dir/file dest_dir/file 和ADD相似,但是如果有壓縮文件是不能解壓
    VOLUME 創建一個可以從本地主機或其他容器掛載的掛載點,一般用來存放數據庫和需要保持的數據等
    CMD 指定容器啟動時要運行的命令,假如有多個CMD,最後一個生效
    ENTRYPOINT 指定容器啟動時要運行的命令
    ONBUILD 當構建一個被繼承的Dockerfile時運行的命令,父鏡像在被子鏡像繼承後父鏡像的onbuild被觸發。可以把ONBUID理解為一個觸發器。

     

    10,Docker私有倉庫

      10.1 簡介

    Docker私有倉庫主要是企業內部用來存放鏡像的倉庫,相對官方倉庫以及阿里雲倉庫,具有更高的保密安全級別;

      10.2 私有倉庫搭建

    第一步:拉取私有倉庫鏡像 (私有倉庫程序本身就是一個鏡像)

    docker pull registry

    第二步:啟動私有倉庫容器

    docker run -di –name=myRegistry -p 5000:5000 registry

    第三步:測試

    http://192.168.1.112:5000/v2/_catalog

    看到這個 說明啟動OK。因為倉庫里還沒有鏡像,所以就是空的;

    第四步:etc/docker 修改daemon.json,讓docker信任私有倉庫地址

    “insecure-registries”: [“192.168.1.112:5000”]

     

    第五步:修改配置后重啟docker;

     systemctl restart docker

      10.3 私有倉庫測試

    第一步:標記此鏡像為私有倉庫的鏡像

    docker tag tomcat:7 192.168.1.112:5000/mytomcat7

    第二步:上傳鏡像到私有倉庫

    docker push 192.168.1.112:5000/mytomcat7

    此時私有倉庫里已經有了這個鏡像;

    第三步:刪除192.168.1.112:5000/mytomcat7本地倉庫鏡像

    docker rmi -f 192.168.1.112:5000/mytomcat7

    第四步:從私有倉庫拉取192.168.1.112:5000/mytomcat7鏡像,並運行;

    docker run -it -p 8080:8080 192.168.1.112:5000/mytomcat7

    第五步:瀏覽器運行 http://192.168.1.112:8080測試

     

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

    作者: java1234_小鋒

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

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

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

     

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

    【其他文章推薦】

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

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

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

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

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

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

  • lin-cms-dotnetcore功能模塊的設計

    lin-cms-dotnetcore功能模塊的設計

    lin-cms-dotnetcore功能模塊的設計

    先來回答以下問題。可拉到最下面查看預覽圖。

    1.什麼是cms?

    Content Management System,內容管理系統。

    2.dotnetcore是什麼?

    .NET Core,是由Microsoft開發,目前在.NET Foundation(一個非營利的開源組織)下進行管理,採用寬鬆的MIT協議,可構建各種軟件,包括Web應用程序、移動應用程序、桌面應用程序、雲服務、微服務、API、遊戲和物聯網應用程序。

    3.lin-cms 是什麼?

    Lin-CMS 是林間有風團隊經過大量項目實踐所提煉出的一套內容管理系統框架。Lin-CMS 可以有效的幫助開發者提高 CMS 的開發效率,

    Lin的定位在於實現一套 CMS的解決方案,管理系統的基礎框架,提供了不同的後端,不同的前端實現,後端也支持不同的數據庫,是一套前後端完整的解決方案

    目前官方團隊維護 lin-cms-vue,lin-cms-spring-boot,lin-cms-koa,lin-cms-flask 社區維護了 lin-cms-tp5,lin-cms-react,lin-cms-dotnetcore,即已支持vue,react二種前端框架,java,nodejs,python,php,c#等五種後端語言。

    lin-cms-vue(官方)

    • https://github.com/TaleLin/lin-cms-vue
    • Vue+ElementUI構建的CMS開發框架,
    • 林間有風團隊經過大量項目實踐所提煉出的一套內容管理系統框架
    • 內置了 CMS 中最為常見的需求:用戶管理、權限管理、日誌系統等

    lin-cms-koa(官方)

    • python
    • https://github.com/TaleLin/lin-cms-koa
    • 使用Node.JS KOA構建的CMS開發框架

    lin-cms-flask(官方)

    • node.js
    • https://github.com/TaleLin/lin-cms-flask
    • A simple and practical CMS implememted by flask

    lin-cms-spring-boot(官方)

    • java
    • https://github.com/TaleLin/lin-cms-spring-boot
    • 基於SpringBoot的CMS/DMS/管理系統開發框架

    lin-cms-tp5(社區)

    • php 被官方fork。
    • https://github.com/TaleLin/lin-cms-tp5
    • A simple and practical CMS implememted by ThinkPHP 5.1

    lin-cms-react(社區)

    • https://github.com/Bongkai/lin-cms-react
    • React+Antd構建的CMS開發框架

    lin-cms-dotnetcore(社區)

    • C#
    • A simple and practical CMS implemented by .NET Core 3.1 一個簡單實用、基於.NET Core
    • https://github.com/luoyunchong/lin-cms-dotnetcore
    • .NET Core 3.1實現的CMS;前後端分離、Docker部署、OAtuh2授權登錄、自動化部署DevOps、GitHub Action同步至Gitee

    4.lin-cms-dotnetcore有哪些特點?

    基於.NET Core3.1實現的LIN-CMS-VUE後端API,並增加了博客模塊。目前實現簡約的權限管理系統、基礎字典項管理、隨筆專欄,評論點贊、關注用戶、技術頻道(標籤分類)、消息通知,標籤等仿掘金模塊。

    功能模塊的設計

    基礎權限模塊

    • 用戶信息:郵件、用戶名(唯一)、昵稱、頭像、分組、是否激活、手機號、是否是Admin、個性簽名
      • [x] 註冊/登錄
      • [x] 上傳頭像
      • [x] 修改個人密碼
      • [x] 用戶基本信息修改
      • [x] 用戶增刪改,配置分組
    • 綁定第三方賬號
      • [x] GitHub登錄
      • [x] QQ 登錄
      • [ ] Gitee登錄
    • 分組信息:是否靜態分組(無法刪除,無法修改分組編碼)、名稱可以修改
      • [x] 分組增刪改
      • [x] 分組配置權限
    • 文件管理
      • [x] 本地文件上傳
      • [x] 七牛雲存儲
      • [x] 文件去重,秒傳
    • 系統日誌:請求方法、路徑、http返回碼、時間、用戶昵稱、用戶id、訪問哪個權限、 日誌信息
      • [x] 記錄系統請求的日誌
      • [ ] 異常日誌
    • 設置管理:name(鍵),value(值),provider_name(提供名),provider_key(提供者值)
      • [x] 設置新增修改刪除
      • [x] 所有設置

    比如存某用戶選擇的是markdown還是富文本。

    name="Article.Editor",
    value="markdown" 或 "富文本",
    provider_name為"User",
    provider_key為用戶Id
    

    或存儲七牛雲的某一個配置

    name="Qiniu.AK",
    value="asfadsfadf23rft66S4XM2GIK7FxfqefauYkcAyNGDAc" ,
    provider_name為"Qiniu"或自己定義的字符串
    provider_key為空
    

    cms 管理員維護模塊

    • [x] 標籤管理:名稱、圖片,是否啟用/禁用,排序、文章數量、用戶關注數量。
      • [x] 標籤增刪改
      • [x] 標籤列表,禁用
      • [x] 校正文章數量
    • [x] 技術頻道:封面圖、名稱、是否啟用/禁用、排序、編碼、備註描述、下屬標籤.一個技術頻道對應多個標籤
      • [x] 技術頻道增刪改
      • [x] 列表、禁用
    • [x] 隨筆管理:
      • [x] 審核隨筆/拉黑
      • [x] 管理員刪除隨筆
    • [x] 評論管理
      • [x] 後台審核通過/拉黑
      • [x] 管理員刪除評論
    • [x] 字典類別管理:編碼,名稱,排序
      • [x] 增刪改查
    • [x] 字典管理::編碼,名稱,排序,類別:如隨筆類型(原創、轉載、翻譯)
      • [x] 增刪改查

    cms 用戶端模塊

    • 技術頻道
      • [x] 首頁展示技術頻道
      • [x] 選擇技術頻道后,可再根據標籤查詢文章
    • 分類專欄管理:發布隨筆時可選擇單個分類。
      • [x] 分類增刪改(隨筆數量、圖片、名稱、排序)
      • [x] 分類列表,僅查看、編輯自己創建的分類專欄
    • 標籤:統計每個標籤下多少個文章、多少人關注
      • [x] 標籤列表
      • [x] 無限加載
      • [x] 最新/最熱 根據標籤名稱模糊查詢
      • [x] 已關注的標籤
      • [x] 熱門標籤
    • 隨筆
      • [x] 支持markdown,增刪改(僅自己的隨筆),修正分類專欄中的隨筆數量
      • [x] 支持富文本編輯隨筆
      • [x] 列表無限加載,按標籤查詢隨筆
      • [x] 點贊隨筆
      • 隨筆詳情頁
        • [x] 支持目錄導航(滾動時,固定至頂部位置),展示字數統計、預計閱讀時長;
        • [x] 作者介紹:頭像,昵稱,簽名,隨筆數;
        • [x] 展示文章類型:原創、轉載、翻譯
        • [ ] 相關文章
        • [ ] 推薦文章
    • 評論
      • [ ] 用戶關閉評論時,無法對隨筆進行評論
      • [ ] 評論隨筆(內容支持超鏈接、emoji)
      • [x] 刪除自己的評論
      • [x] 點贊評論
      • [x] 回複評論
    • 關注
      • [x] 關注/取消關注用戶
      • [x] 關注/取消關註標簽
      • [x] 我關注的用戶發隨筆
    • 個人主頁
      • 隨筆
        • [x] 用戶專欄分類展示
        • [x] 最新發布的隨筆
      • 關注
        • [x] 關注的用戶
        • [x] 粉絲
        • [x] 關注的標籤
    • 設置
      • 個人主頁設置
        • [x] 個人資料更新
      • 安全設置
        • [x] 密碼修改:快速登錄的賬號,初次設置時可留空
      • 博客設置
        • [x] 編輯器設置,(可切換markdown/富文本)
        • [x] 代碼風格配置(tango、native、monokai、github、solarized-light、vs)
    • 消息
      • [x] 評論:點贊評論、評論隨筆、回複評論
      • [x] 喜歡和贊:點贊隨筆、點贊評論
      • [x] 關注,誰誰關注了你

    腦圖分享

    http://naotu.baidu.com/file/6532431a2e1f0c37c93c5ffd1dd5b49c?token=87690a9bc64fbae1

    分組

    分為三種

    id  name        info
    1	Admin	    系統管理員
    2	CmsAdmin	內容管理員
    3	User	    普通用戶
    

    審計日誌

    大多數表存在如下8個字段,用於記錄行的變化狀態,is_deleted為軟刪除,執行刪除操作時,將其狀態置為true,默認實體類繼承 FullAduitEntity 即可擁有以下8個字段。該設計參考ABP中的實現。FullAduitEntity為泛型,默認id為long類型,FullAduitEntity<Guid>,即可改變主鍵類型,默認LinUser表主鍵long,保持create_user_id,delete_user_id,update_user_id都與LinUser的主鍵相同

    
    id	                bigint
    create_user_id  	bigint
    create_time	        datetime
    is_deleted	        bit
    delete_user_id  	bigint
    delete_time	        datetime
    update_user_id	    bigint
    update_time	        datetime
    
    
    

    相關技術

    • 數據庫相關:ORM:FreeSql+DataBase:MySQL5.6
    • ASP.NET Core3.1+WebAPI+RESTful
    • 簡化對象映射:AutoMapper
    • 身份認證框架:IdentityServer4
    • Json Web令牌:JWT
    • 文檔API:Swagger(Swashbuckle.AspNetCore)
    • 序列化:Newtonsoft.Json
    • 測試框架:Xunit
    • 日誌 Serilog
    • 依賴注入服務AutoFac
    • 通用擴展方法 Z.ExtensionMethods
    • 雲存儲:七牛雲 MQiniu.Core
    • 分佈式事務、EventBus:DotNeteCore.CAP
    • GitHub第三方授權登錄AspNet.Security.OAuth.GitHub
    • QQ第三方授權登錄AspNet.Security.OAuth.QQ
    • Docker
    • Azure DevOps
    • 健康檢查AspNetCore.HealthChecks.UI.Client
    • GitHub Action同步至Gitee

    分層結構(Layers)

    • framework
      • IGeekfan.CAP.MySql:為CAP實現了配合FreeSql的事務一致性擴展
    • identityserver4
      • LinCms.IdentityServer4:使用id4授權登錄
    • src
      • LinCms.Web:接口API(ASP.NET Core)
      • LinCms.Application:應用服務
      • LinCms.Application.Contracts:DTO,數據傳輸對象,應用服務接口
      • LinCms.Infrastructure:基礎設施,數據庫持久性的操作
      • LinCms.Core:該應用的核心,實體類,通用操作類,AOP擴展,分頁對象,基礎依賴對象接口,時間擴展方法,當前用戶信息,異常類,值對象
      • LinCms.Plugins 使用單項目實現某個業務的擴展,不需要主要項目結構,可暫時忽略。
    • test
      • LinCms.Test:對倉儲,應用服務或工具類進行測試

    功能特性

    • [x] Azure Devops CI/CD構建
    • [x] GitHub Action實現 GitHub Gitee代碼同步
    • [x] .Net Core結合AspNetCoreRateLimit實現限流
    • [x] 方法級別權限控制
    • 社交賬號管理:支持多種第三社交賬號登錄,不干涉原用戶數據,實現第三方賬號管理
    • 多語言
    • [x] 全局敏感詞處理
    • 日誌記錄,方便線上排查錯誤
    • [ ] 支持多種數據庫,並測試,
      • [x] Mysql
      • [ ] Postgresql
      • [ ] Sql Server
      • [ ] SQlite

    產品設計-評論模塊的設計

    下面我們來設計一個評論模塊,需要注意的是,一個評論模塊也有不同的方式。從展示形式,排序規則,按鈕功能設計,操作等方式都有詳細的分析設計,大家可以參考who shi pm 中的文章http://www.woshipm.com/pd/3548139.html,這裏主要講解下展式方式中的主題式的應用。

    1.主題式

    相信很多人都刷過抖音,他的評論主題式的強化版。

    特點為前三個是熱門評論(喜歡最多的評論),將評論分為二級,第一級採用時間倒序,第二級按照時間正序,有助於理解上下文關係。

    可以總結為如下功能點

    用戶操作:

    • [x] 評論隨筆(內容支持超鏈接、emoji)
    • [x] 點贊評論/取消點贊
    • [x] 回複評論
    • [x] 刪除自己的評論

    運營操作:

    • [x] 審核通過/拉黑評論
    • [x] 刪除任何評論
    • [x] 拉黑后的显示邏輯。(保留當前區塊、显示內容為:該評論因違規被拉黑)
    • 刪除:(如果是二級評論,直接軟刪除,如果是一級評論,軟刪除子評論和當前評論-需要提前提醒用戶)

    交互設計

    • 評論的字數長度(500)、emoji。
    • 點贊交互-動畫、消息通知/推送
    • 評論區域元素,需要有明確可點擊的區域,會跳轉到哪個地方。

    優化

    • 精選或者叫置頂評論
    • 該文章是否開放評論功能。
    • 熱門評論(點贊最多的評論)
    • 標註哪些評論是作者,標準哪些評論被用戶點贊。
    • @邏輯,emoji,舉報等

    2 平鋪式

    特點是不區分子父節點關係,比如現在博客園(大多數的主題),微信朋友圈,github。
    不過博客園,github,回復時,可選擇引用回復,方便用戶理解上下文關係。

    3.蓋樓式

    這種方式有點這種感覺 》>-,即.net core中間件請求方式。當回復越來越多時,显示的效果就越誇張。一層套一層的显示上下文的關係。下圖是網易新聞的評論效果。

    排行榜見解

    排行榜從心理學上分析,主要從四個方面影響着您:尋找權威 、參与比較 、關注主流 、自我確認。

    如何設計一個簡單的排行榜呢。。

    在一個博客隨筆中,我們設計一個3天、七天(周榜)、30天(月榜)、全部的榜單。以瀏覽量(權重1)、點贊量(20)、評論量(30)。權重可自己定義。

    1.默認取最新的隨筆

    前台傳create_time時,使用如下sql

    select * from `blog_article` order by create_time desc;
    

    2.傳排序方式為最近n天的熱榜時。

    參數:THREE_DAYS_HOTTEST(三天)、WEEKLY_HOTTEST(七天)、MONTHLY_HOTTEST(一個月)、HOTTEST(全部)

    mysql 查詢當前日期時間前三天數據

    select date_sub(now() ,interval 3 day);
    

    根據權重查詢

    select * from `blog_article` a 
    where a.`create_time`>(select date_sub(now() ,interval 3 day))
    order by (a.`view_hits` + a.`likes_quantity` * 20 + a.`comment_quantity` * 30) DESC, a.`create_time` DESC
    
    

    更多參考

    • 評論區如何設計?
    • 萬字長文深度分析:產品排行榜的設計和玩法
    • 想知道誰是你的最佳用戶?基於Redis實現排行榜周期榜與最近N期榜

    lin-cms 開源地址分享

    • 後端接口 https://github.com/luoyunchong/lin-cms-dotnetcore
    • 管理後台UI https://github.com/luoyunchong/lin-cms-vue
    • 前端UIhttps://github.com/luoyunchong/lin-cms-vvlog

    Demo

    • 用戶端 lin-cms-vvlog https://vvlog.baimocore.cn

      • 普通用戶:710277267@qq.com
      • 密碼:123qwe
    • 管理員 lin-cms-vue https://cms.baimocore.cn/

      • 管理員: admin
      • 密碼:123qwe

    預覽圖

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

    【其他文章推薦】

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

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

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

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

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

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

  • 邊緣計算告訴你們公司空調怎麼開最省錢

    邊緣計算告訴你們公司空調怎麼開最省錢

    據統計,現代城市人的生活與工作同樓宇息息相關,超過80%的時間都是在城市樓宇中度過,樓宇智能毋庸置疑是影響深遠的關鍵研究課題。

    近年來,隨着邊緣計算技術的崛起,邊緣智能相關的場景應用拓展也成為科技公司爭相展現技術創新和商業價值的路徑,各種邊緣AI的解決方案亦應運而生,如華為雲智能邊緣平台IEF,一站式端雲協同多模態AI開發平台HiLens。據統計,現代城市人的生活與工作同樓宇息息相關,超過80%的時間都是在城市樓宇中度過,樓宇智能毋庸置疑是影響深遠的關鍵研究課題。本文將圍繞樓宇智能其中最重要的課題之一中央空調能效預測與管理來展開,目前,該課題面臨最大的瓶頸是:現有的大多數能效預測與管理方法僅限於雲端單任務,無法支撐中央空調能效模型在邊緣隱含的大量複雜場景上的能力。

    眾所周知,暖通空調系統(包括供暖,通風和空調)主導着商業建築的用電量。對暖通空調系統的現有研究表明,準確量化冷水機組的能效比(數值越大越節能)非常重要,近期提出的數據驅動的能效比預測可以被應用到雲上。但是,由於不同園區擁有不同型號的空調或不同種類的傳感器,導致不同邊緣各個項目在特徵、模型等方面區別很大,在小樣本情況下很難用一個通用模型適應所有的項目。

    近年來,華為雲邊緣雲創新lab與來自香港理工大學、IBM研究院、華中科技大學、同濟大學、深圳大學等知名校企研究團隊密切合作並持續開展技術研究,以邊緣樓宇智能領域場景為依託,希望逐步解決現實中隱含大量複雜場景的邊緣智能問題。有興趣的讀者歡迎關注2018到2020間年發表的多任務學習、多任務調度和多任務應用等歷史工作:

    通用算法:多任務遷移與邊緣調度

    基於元數據的多任務遷移關係發現

    Zheng, Z., Wang Y., Dai Q., Zheng H., Wang, D. “Metadata-driven task relation discovery for multi-task learning.” In Proceedings of IJCAI (CCF-A), 2019.

    在這篇論文中有一個多任務的實際應用案例,不同邊緣智能項目採用不同設備使得邊緣側模型不同,從而可以應用於多任務設定。這篇論文的亮點是引入元數據,元數據是數據集的描述信息,在複雜系統中用於日常系統運作,蘊含專家信息。基於元數據提取任務屬性,本論文設計了元數據任務屬性與樣本任務屬性層次結合的多任務通用AI算法(圖1)。相關論文專家評審也認為該技術在應用實踐中显示了實用價值,對機器學習項目真正落地具備重要意義,將成為當今大型組織感興趣的技術。

    圖1 顏色代表不同聚類簇,数字代表不同設備型號。基於樣本屬性的方法容易導致負遷移(同一簇中混淆不同型號設備模型,左圖),而基於元數據的方法可以避免負遷移(右圖)。

    多任務遷移學習的邊緣任務分配系統與實現

    Zheng, Z., Chen, Q., Hu, C., Wang, D., & Liu, F. “On-edge Multi-task Transfer Learning: Model and Practice with Data-driven Task Allocation.” In Proceedings of IEEE TPDS (CCF-A), 2019.

    Chen, Q., Zheng, Z., Hu, C., Wang, D., & Liu, F. “Data-driven task allocation for multi-task transfer learning on the edge. ” In Proceedings of IEEE ICDCS (CCF-B), 2019.

    多任務遷移學習是解決邊緣上樣本不足的典型做法。而目前邊緣上的任務分配調度工作通常假設不同的多個任務是同等重要的,導致資源分配在任務層面不夠高效。為了提升系統性能與服務質量,我們發現不同任務對決策的重要性是一個亟需衡量的重要指標。我們證明了基於重要性的任務分配是NP-complete的背包問題變種,並且在多變的邊緣場景下該複雜問題的解需要被頻繁地重新計算。因此我們提出一個用於解決該邊緣計算問題的AI驅動算法,並且在實際多變的邊緣場景中進行算法測試(圖2),與SOTA算法相比該算法能減少3倍以上的處理時間和近50%的能源消耗。

    圖2 根據邊緣場景動態進行任務分配調度

    邊緣應用:樓宇智能

    基於多任務的冷機負荷控制

    Zheng, Z., Chen, Q., Fan, C., Guan, N., Vishwanath, A., Wang, D., & Liu, F. “Data Driven Chiller Sequencing for Reducing HVAC Electricity Consumption in Commercial Buildings.” In Proceedings of ACM e-Energy, 2018. Best Paper Award.

    Zheng, Z., Chen, Q., Fan, C., Guan, N., Vishwanath, A., Wang, D., & Liu, F. “An Edge Based Data-Driven Chiller Sequencing Framework for HVAC Electricity Consumption Reduction in Commercial Buildings.” IEEE Transactions on Sustainable Computing, 2019.

    多任務可以應用於樓宇節能中。冷機是樓宇中的耗能大戶。冷機能效預測與管理,預測冷機負荷決策的能效比並優化冷機負荷決策,一直是樓宇智能最重要的研究問題之一。本研究觀測到,在冷機決策能效預測中,不同邊緣項目的設備型號和工況不同會導致最終需求的模型不同。這種情況下僅採用雲端單一模型的做法容易導致精度下降和決策失誤。本工作研發了一種邊雲協同的多任務冷機負荷決策框架(圖3),在利用現有端邊節點且不部署額外硬件的情況下,較當前工業界方法節能30%以上。

    圖3 邊雲協同的冷機負荷決策框架

    基於多任務的空調舒適度預測

    Zheng, Z., Dai Y., Wang D., “DUET: Towards a Portable Thermal Comfort Model.” In Proceedings of ACM BuildSys (Core rank A), 2019.

    Yang, L., Zheng, Z., Sun, J., Wang, D., & Li, X. A domain-assisted data driven model for thermal comfort prediction in buildings. In Proceedings of ACM e-Energy. 2018.

    空調舒適度預測是樓宇智能歷史長河中重要的研究課題之一。目前的舒適度預估方法通常要求額外的傳感器或者用戶反饋等人工干預,這使得規模化本身成為難題。基於機器學習方法的空調舒適度預測已被證明可以減少額外的人工干預。但在不同邊緣場景下,樓宇製冷類型、安裝傳感器類別等因素會使得雲上單一通用模型出現嚴重錯誤。本研究提出了一種多任務的方法進行空調舒適度的預測,在精度上較機理模型和單任務模型分別提升39%和31%。

    邊緣自適應任務定義

    基於以上項目,讀者可以了解到基於多任務的邊緣智能算法、系統與應用。值得注意的是,在使用多任務之前,首先需要回答任務如何定義和劃分的問題,如確定在一個應用內不同項目所需機器學習模型的數量以及各個模型的應用範圍。該方法目前通常只能由數據科學家和領域專家人工進行干預,自動化程度低,難以規模化複製。因此,邊緣自動定義機器學習任務是一個懸而未決但又重要的難題。

    為了在邊緣各種場景自適應地定義機器學習預測任務,華為雲邊緣雲創新Lab近日發表了研究論文《MELODY: Adaptive Task Definition of COP Prediction with Metadata for HVAC Control and Electricity Saving》。該研究提出了一種包含任務定義的多任務預測框架(MELODY),其中任務定義能夠自適應地定義並學習複數能效比預測任務。

    MELODY是第一個根據各種邊緣場景自適應定義能效比預測任務的方法。本研究工作為尋求自動有效的邊緣機器學習方法的研究人員和應用開發人員提供了一種有吸引力的機制,特別適用於對於元數據多樣化但數據樣本不足的複雜系統。MELODY的關鍵思想是使用元數據動態劃分多個任務,論文提出了元數據的數學定義以及提取元數據的2種來源和方法。

    該團隊在實際應用中評估該方案的性能:基於2個大型工業園區中的8座建築物中9台冷機進行4個月實驗。實驗結果表明,MELODY解決方案優於最新的能效比預測方法,並且能夠為兩個園區每月節省252 MWh的電量,較當前建築中冷水機的運行方式節省了35%以上的能源。

    MELODY論文已獲ACM e-Energy 2020接收:

    Zimu Zheng,Daqi Xie,Jie Pu,Feng Wang. MELODY: Adaptive Task Definition of COP Prediction with Metadata for HVAC Control and Electricity Saving. ACM e-Energy 2020. Australia.

    ACM e-Energy 屬於ACM EIG-Energy Interest Group、計算機與能源交叉的旗艦會議。

    1、論文接受率23.2%,歷年接受率在20%左右;

    2、與CCF-A的Ubicomp; CCF-B的ECAI、TKDD H5-index相同;

    3、55位評審程序委員會成員中包括Andrew A. Chien、Klara Nahrstedt、Prashant Shenoy等8位ACM/IEEE Fellow(約15%);

    4、評審程序委員會成員來自IBM研究院、伊利諾伊大學香檳分校、劍橋大學、華盛頓大學、普渡大學、馬薩諸塞大學阿默斯特分校、西蒙菲沙大學、南洋理工大學、清華大學、香港理工大學等國際知名校企;

    5、與CCF-A的STOC、ISCA、PLDI;CCF-B的IWQos、SIG Metric、COLT、HPDC、ICS、LCTES、SPAA等同屬ACM Federated Computing Research Conference (FCRC)系列的13個會議中,ACM FCRC頂會系列由Google、微軟、IBM、華為、arm、Xilinx等國際知名企業贊助。

    能效比預測

    基於冷機的暖通空調系統通常用於商業建築中,消耗的電力占建築物總用電量的40%至70%,這種消耗量主要由暖通空調系統的消耗量決定。商業建築物支付的電費(其中大部分歸於暖通空調系統)通常位於組織運營支出的前三名。這種趨勢給設施管理者帶來了巨大的壓力,他們需要通過減少與暖通空調系統相關的電力消耗來提高建築的能源利用效率。

    暖通空調的主要消耗來自冷機(見圖4)。典型的冷機負荷控制的有效性在很大程度上取決於冷機運行時的性能,即在不同的冷負荷條件下的能效比。能效比是衡量冷機能效的指標,指的是在單位輸入功率消耗下的輸出冷量。能效比通常大於1,值越大,意味着效率越高。在實踐中,設施管理人員通常在冷機部署到建築物期間,在首次測試和調試冷水機組時衡量能效比的初始信息,並用該初始信息來執行冷機負荷控制。初始信息測試時通常將冷量負荷視為唯一參數。然而,這些初始信息無法捕獲實際參數的影響,並且已被近期研究證明是不精確的。

    圖4 冷機示意圖

    本研究以能效比預測問題作為個例研究。能效比高度依賴於多種因素,例如工況、冷量需求、設備老化、天氣等。為了在冷水機組中捕獲這些因素,現有工作已經提出採用數據驅動方法。能效比預測問題可以看做是在訓練階段學習一種被稱為模型的“公式”,該公式在推理階段能夠輸出具有給定特徵的能效比。

    自適應任務定義

    現有方法通常假定預測任務的配置,比如同一應用下的預測模型的數量和預測模型的應用範圍,是由數據科學家或領域專家定義和固定的。下文比較三種被廣泛接受的設定:單任務設定、多任務設定和專家輔助的多任務設定。

    單任務設定

    一個最典型的、被廣泛接受的預測任務配置方法是基於固定的單任務設定:這意味着將所有數據集作為一個整體合併在一起,並訓練單個預測模型。研究人員可以使用任何機器學習算法(例如SVM、神經網絡、Boosting等)來學習這種模型,並在任何場景下的推理階段都應用訓練出的這單個模型。

    單任務設定假設對於同一應用下不同項目內不同的數據集,單個模型應足以描述所選特徵和能效比之間的關係。但是,這種假設可能並不總是成立。

    比方說有兩個園區採用了兩種類型的冷水機:園區H使用了特靈CVHG1100冷水機和園區J使用了開利W3C100冷水機,那麼應根據冷水機的型號調整在特徵和能效比之間的熱力學模型。邊緣用戶往往也期待看到應用到兩個邊緣項目的模型有所不同:即使兩種冷水機輸入相同的水溫等特徵值,輸出的能效比也應不同。但如果將兩個數據集合併在一起並訓練同一個能效比模型,通常很難在沒有人工干預的情況下確保這一點。

    論文作者過去的研究還表明,除了不同邊緣項目採用冷機型號不同可能導致模型不同外,可能導致模型不同的例子還包括:不同項目採用的工況和參數配置不同、不同項目採用的傳感器種類不同、不同季節採用特徵不同等(篇幅原因不再贅述,感興趣的讀者可以參考文章開頭提及的研究團隊歷史工作)。不同邊緣場景訓練出的模型應用範圍可能是迥乎不同的。所以對於不同的場景都採用單任務設定並非總是最佳選擇,這可能在實踐中引發重大錯誤,尤其是某些邊緣智能項目中訓練樣本的大小不足以在大量特徵中自動將場景彼此區分的情況。

    多任務設定

    但目前預測任務的配置,例如所需模型的數量以及模型的應用範圍,仍然是一個開放性問題。為了深入研究此問題,該團隊進一步驗證多任務設定而非單任務設定,也即觀察多個模型在多個測試集上的性能。在一個實際建築物中,使用了從冷機1到冷機5的訓練數據集訓練了5個模型(以下稱為M1 – M5)。然後在另外5個測試數據集(T1 – T5)的不同場景中測試了5個模型的性能。實驗及其結果分別如圖5-1、5-2所示。

    圖5-1 複數冷機訓練模型在不同冷機測試集下的實驗示意圖

    圖5-2 複數冷機訓練模型在不同冷機測試集下的預測準確率和樣本採集時間對比結果

    觀察結果显示,

    1)精度

    儘管是基於不同的數據集進行訓練,但是冷機1的模型在冷機2和冷機3的測試集上效果很好,而在冷機4和冷機5的測試集上卻導致嚴重錯誤。對於冷機2到冷機5的模型可以看到類似的觀察結果。這是因為冷機1到冷機3來自同一種冷機型號,而冷機4和冷機5是另一種型號。

    2)樣本採集時間

    如果按冷機來劃分任務,每個冷機任務至少需要81天的樣本。但如果按照型號劃分為2個任務,每個型號任務僅需30天的樣本。這是因為每個型號任務包含多台冷機採集的數據。

    根據上述精度和樣本採集時間的結果,與其考慮5個冷機從而定義5個冷機任務,在這個數據集下不如考慮2個型號(冷機1-3和冷機4-5)從而定義2個型號任務,在上述例子中可以降低63%左右的樣本採集時間,同時提升近10%的精度。

    專家輔助的多任務設定

    實際上,不僅冷機型號,隨時間變化的環境(例如,天氣條件)和工況(例如,供水溫度)也可以導致能效比模型的變化。藉助領域專家的知識,可以在構建的環境中定義固定的任務,並將這些固定的任務應用於不同的建築中。

    例如,基於建築環境研究中的領域專業知識,該團隊最近一項工作在三座建築物中根據工況給出了固定的50個任務,用於多任務冷機能效比預測;該團隊最近另外一項工作根據季節和製冷類型在160座建築物中給出了固定的4個任務,以進行多任務熱舒適性預測。

    但是,所需模型的數量及其應用範圍可以根據不同的邊緣項目場景而變化,而領域專家的配置很難跟隨不同邊緣項目動態擴展。例如,在一個建築物的少量數據集中,最好有3個任務,即訓練3種不同的模型進行能效比預測。但是在另一個包含1000座建築物的大型數據集中,最好有75個任務。在邊緣場景手動定義要預測的機器學習任務通常會導致成本過高或準確性降低,尤其是當任務隨項目和時間而動態變化時。因此,有必要針對不同場景自適應地定義任務。

    MELODY

    本研究工作旨在解決自適應任務定義問題,也即不同場景下自動化定義不同的任務,例如,在不同場景中確定需要使用的模型數量以及模型的應用範圍等。該團隊遇到三個主要挑戰,並提出了使用自適應任務定義方法的多任務預測框架(MELODY)。

    挑戰1:當前項目的目標未知,而且通常更糟糕的是,可能的任務候選集也未知。

    MELODY通過提出任務挖掘解決了第一個挑戰。它基於諸如任務森林等新穎結構和算法來自適應地定義任務,參見圖6。這使得MELODY可規模化到眾多建築和環境的能效比預測。

    圖6 任務森林的例子:數據表示模型訓練樣本,屬性表示模型應用範圍;節點表示子任務,包括數據、屬性和模型(若有);森林的每個根節點,也即每棵樹的頂點,表示各個子任務合併成的一個任務。對任務森林初始化和維護等具體實現和算法複雜度等證明,有興趣的讀者可以閱讀論文附錄。

    挑戰2:標誌能效比模型應用範圍的屬性未知,同時此類屬性的來源也在研究中。

    MELODY通過使用元數據作為任務屬性的來源來解決第二個挑戰。

    元數據由領域專家定義,用於建築管理系統的日常控制。例如,傳感器的名稱和建築物的類型是元數據。在MELODY框架中,該團隊提出了從數據庫的兩個來源中提取兩種元數據的方法。

    元數據包含潛在領域信息,藉助這些信息,能夠自適應地提取具有領域知識的任務,併為自動和強大的任務定義打開了方便之門,如圖7所示。

    圖7 基於元數據提取的任務定義(具體實現請參見論文)

    挑戰3:任務組合數量隨屬性數量指數增長;因此,冷機樣本不足以為所有組合訓練模型。

    MELODY通過利用多任務遷移學習克服了第三個挑戰。在多任務優化中,學習任務可以使用來自其他不同任務的知識,從而減少數據量的需求。

    多任務評估

    本研究工作通過將其應用於實際數據來評估方案的性能,在2個大型工業園區中的8座建築物中9台冷機進行4個月時間的實驗。園區情況可參見圖8。

    圖8 2個大型工業園區中的8座建築物及其冷機信息

    表1 任務定義輸出結果

    表1显示了通過任務定義算法挖掘出的任務的總體信息,在Park J和Park H中發現了兩組不同的任務集合。觀察显示不同項目模型的數量和使用模型時的場景都不同。藉助五分鐘的間隔數據,可以在Park J中挖掘出33個任務,這些任務模型的應用範圍主要根據冷機額定功率和平均濕度的來判斷。藉助一小時的時間間隔數據,Park H中僅有2個任務,應用範圍需要通過額定功率和額定製冷量來判斷。可以發現每個任務中的樣本量很小。 對於總共35個任務,有13個任務的樣本數少於100,其餘22個任務的樣本數少於1000。

    研究比較了幾種應用於冷機能效預測的典型方法:

    (1)工業界當前方法:初始配置文件(IP)利用安裝時測量的初始配置文件來估算未來的能效比,是目前工業界正在使用的方法。

    (2)學術界常用方法:單任務學習(STL)通過將來自每個數據集的所有任務的數據匯總在一起來學習一個模型;

    (3)近期研究工作:關於數據源的獨立多任務學習(IMTL),它獨立於數據源學習每個任務。例如,針對9個冷機固定9個任務,而無需在任務之間共享任何樣本或知識;

    (4)近期研究工作:具有領域知識的多任務學習(MTL),它學習具有由領域知識定義的任務聚類。例如,固定的50個任務,其中10個負載比和5個冷機。

    表2 各方法錯誤率提升

    表2結果显示,MELODY的任務定義可以比STL(單任務方法)有所提升。 但是,不正確的任務定義(即IMTL和MTL)對比單任務方法未能有所提升。這主要是因為與在不同數據集中(如MELODY)使用自適應任務的方法相比,IMTL和MTL在劃分任務後會生成較小的數據集,這導致部分任務內缺乏訓練樣本。當任務數量隨着屬性數目和時間推移而增加時,效果變得更差,因為任務遷移關係變得越來越複雜。在這種情況下,任務之間共享知識變得更具挑戰性,並容易導致一種被稱為負遷移的影響,也即從不相關的源域到目標域共享知識而導致的錯誤。可以看到,MELODY能解決相關問題,從而使得結果優於最新的能效比預測方法,將能效比預測誤差率降低了18.18-61.70%,最終能夠在兩個園區上每月節省252 MWh的電量,與當前建築中冷水機的運行方式相比節省了36.75%以上的能源。

    本文作者:鄭博士,華為雲邊緣雲創新Lab高級研究工程師,畢業於香港理工大學,主要研究方向是邊緣智能及AIoT。發表國際相關領域頂級會議及期刊 (TPDS、 IJCAI、 ICDCS、CIKM、TOSN、TIST等) 論文十餘篇,多次獲得最佳會議論文獎項,多次獲得關鍵技術突破、高價值專利和新服務孵化等華為傑出貢獻獎項。

    華為雲邊緣雲創新Lab:願景是探索端邊雲協同關鍵技術,構建無所不在的、極致體驗的智能邊緣雲。聯合工業夥伴和學術機構,共同致力於研究邊緣雲創新技術、孵化邊緣雲創新應用、構建邊緣雲繁榮生態。研究方向包括大規模智能邊緣雲平台、邊雲協同AI、端邊雲協同渲染與視頻加速。目前已孵化上線華為邊緣計算平台IEF,並貢獻首個基於Kubernetes的雲原生邊緣計算平台KubeEdge,獲尖峰開源技術創新獎、最佳智能邊緣計算技術創新平台等多項獎項;孵化的業內首個邊雲協同增量學習工作流即將上線華為雲HiLens服務、IEF服務;學術上近2年已發表7篇邊雲協同AI、雲原生邊緣計算相關頂會論文,獲多項最佳論文和優秀論文獎項。

     

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

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

    【其他文章推薦】

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

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

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

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

    ※超省錢租車方案

    FB行銷專家,教你從零開始的技巧

  • Jmeter系列(26)- 詳解 JSON 提取器

    Jmeter系列(26)- 詳解 JSON 提取器

    如果你想從頭學習Jmeter,可以看看這個系列的文章哦

    https://www.cnblogs.com/poloyy/category/1746599.html

     

    為什麼要用 JSON 提取器

    • JSON 是目前大多數接口響應內容的數據格式
    • 在接口測試中,不同接口之間可能會有數據依賴,在 Jmeter 中可以通過後置處理器來提取接口的響應內容
    • JSON 提取器是其中一個可以用來提取響應內容的元件

     

    JSON 提取器的應用場景

    1. 提取某個特定的值
    2. 提取多個值
    3. 按條件取值
    4. 提取值組成的列表

     

    JSON 提取器

    我們通過實際栗子去講述理論知識點

     

    JSON 提取器界面介紹

     

    字段含義

     

    字段 結果
    Apply to 應用範圍,選默認的 main sample only 就行了
    Names of created variables
    • 接收提取值的變量名
    • 多個變量用 ; 分隔
    • 必傳
    JSON Path expression
    • json path 表達式,用來提取某個值
    • 多個表達式用 ; 分隔
    • 必傳
    Match No.(0 for Random)
    • 取第幾個值,多個值用 ; 分隔
    • 0:隨機,默認
    • -1:所有
    • 1:第一個值
    • 非必傳
    Compute concatenation var(suffix_ALL)
    • 如果匹配到多個值,則將它們都連接起來,不同值之間用 , 分隔
    • 變量會自動命名為 <variable name>_ALL 
    Default Values
    • 缺省值,匹配不到值的時候取該值,可寫error
    • 多個值用 ; 分隔
    • 非必傳

     

    入門栗子 

    栗子的前提

    這個栗子,我都會以這個地址的接口來完成 JSON 提取器的實戰慄子,大家可以註冊個賬號玩一玩哦

    http://api.yesapi.cn/docs.php?keyword=%E4%BC%9A%E5%91%98&channel=api

     

    測試計劃樹結構

    下面多個栗子都以這個測試計劃為基礎哦

     

    提取某個特定的值的栗子

    登錄接口響應

    登錄是執行其他接口的前置接口,所以要獲取用戶登錄后的 token、uuid

     

    提取 token

    相對路徑的方式

     

    提取 uuid

    絕對路徑的方式

     

    其他接口調用 token、uuid

     

    知識點

    • 提取某個特定值的方式有兩種:絕對路徑、相對路徑
    • 提其他接口可以通過 ${var} 這種格式,來獲取提取到的值

     

    綜合栗子

    • 上面講的是使用 JSON 提取器時的一個流程
    • 在實際項目中,接口的響應內容肯定是非常複雜的,而我們需要提取的值也是多樣化的,需要通過各種實戰慄子來講述清晰

     

    JSON 字符串

    這也是某個接口返回的響應內容,後面的栗子也是以這個 JSON 字符串為基礎來提取各種值

    感興趣也可以自己玩一玩:http://api.yesapi.cn/docs-api-App.User.GetList.html

    {
        "ret": 200,
        "msg": "V2.5.1 YesApi App.User.GetList",
        "data": {
            "total": 3,
            "err_msg": "",
            "err_code": 0,
            "users": [
                {
                    "role": "user",
                    "status_desc": "正常",
                    "reg_time": "2020-06-22 15:19:51",
                    "role_desc": "普通會員",
                    "ext_info": {
                        "yesapi_nickname": "",
                        "yesapi_points": 0
                    },
                    "uuid": "6D5EDCB459F0917A98106E07D5438C58",
                    "username": "fangjieyaossb",
                    "status": 0
                },
                {
                    "role": "user",
                    "status_desc": "正常",
                    "reg_time": "2020-06-22 14:27:17",
                    "role_desc": "普通會員",
                    "ext_info": {
                        "yesapi_nickname": "",
                        "yesapi_points": 0
                    },
                    "uuid": "0164DC0680F84DCE40D3DD4A36640ECA",
                    "username": "fangjieyaossa",
                    "status": 0
                },
                {
                    "role": "admin",
                    "status_desc": "正常",
                    "reg_time": "2020-03-23 22:48:32",
                    "role_desc": "管理員",
                    "ext_info": {
                        "yesapi_nickname": "",
                        "yesapi_points": 0
                    },
                    "uuid": "079BF6BB82AFCFC7084F96AECAF0519F",
                    "username": "fangjieyaoss",
                    "status": 0
                }
            ]
        }
    }

     

    提取單個值

    Jsonpath 結果
    $.data.total 2
    $..total 2
    $..users[0].role user
    $..uuid 079BF6BB82AFCFC7084F96AECAF0519F
    $.data.users[0].ext_info.yesapi_points 0

     

    重點

    • 如果匹配到多個值(像 $..uuid ),也只能提取到一個值
    • 如果想提取匹配到的所有 uuid,可以設置為 -1,結果如下圖

    還會告訴你匹配了多少個值 ${uuid_matchNr} ,記住,調用變量時,不再是 ${uuid} 而是 ${uuid_1} 、 ${uuid_2} 

     

    利用切片提取單個值

    和 Python  切片一樣的原理

    Jsonpath 結果
    $..users[2] 第三個 users
    $..users[-2] 倒數第二個users
    $..users[0,1] 前面兩個users
    $..users[:2] 第一、二個users
    $..users[1:2] 第二個users
    $..users[-2:] 倒數兩個users
    $..users[1:] 第二個開始的所有users

     

    提取多個值

    • 四種寫法類似,選一種方法自己熟記即可
    • 重點:提取多個值,提取器的 Match No. 必須填 -1

     

    $.data.users[*].role

    提取所有 role 字段值

    [*] 表示取數組的所有元素

     

    $..users..role_desc

    提取所有 role_desc 字段值

     

    $..reg_time

    提取所有 reg_time 字段值

     

    $..[*].username

    提取所有 username 字段值

     

    按條件提取值

    有時候只需要提取某個特定條件下的參數值

     

    語法格式

    [?(expression)]

     

    栗子

    Jsonpath 結果
    $..users[?(@.uuid)] 提取 users 裡面包含 uuid 字段的記錄
    $..users[?(@.reg_time > ‘2020-06-01’)] 提取 reg_time 字段大於 2020-06-01 的記錄
    $..users[?(@.role_desc =~ /.*會員.*?/i)] 提取 role_desc 字段包含會員的記錄
    $..users[?(@.status == 0)] 提取 status 字段等於 0 的記錄

     

    @

    代表當前節點,像上面的四個栗子,@代表 users 這個列表字段

     

    =~

    • 後面跟正則表達式,如果想提取包含指定字符的值,可以使用此正則: /.*指定字符串.*?/i 
    •  i  代表大小寫不敏感

     

    提取數據指定字段的值的栗子

    提取 users 第一條記錄的 uuid、username 字段的值

    $..users[0].['uuid','username']

     

    測試結果

    new_1={"uuid":"6D5EDCB459F0917A98106E07D5438C58","username":"luojunjiessb"}

     

    勾選 Compute concatenation var 的栗子

    JSON 提取器

     

    測試結果

    uuid_1=6D5EDCB459F0917A98106E07D5438C58
    uuid_2=0164DC0680F84DCE40D3DD4A36640ECA
    uuid_3=079BF6BB82AFCFC7084F96AECAF0519F
    uuid_ALL=6D5EDCB459F0917A98106E07D5438C58,0164DC0680F84DCE40D3DD4A36640ECA,079BF6BB82AFCFC7084F96AECAF0519F
    uuid_matchNr=3

     

    一個 JSON 提取器有多個 Jsonpath 的栗子

    JSON 提取器

     

    測試結果

    uuid1_1=6D5EDCB459F0917A98106E07D5438C58
    uuid1_2=0164DC0680F84DCE40D3DD4A36640ECA
    uuid1_3=079BF6BB82AFCFC7084F96AECAF0519F
    uuid1_matchNr=3
    uuid2_1=6D5EDCB459F0917A98106E07D5438C58
    uuid2_2=0164DC0680F84DCE40D3DD4A36640ECA
    uuid2_3=079BF6BB82AFCFC7084F96AECAF0519F
    uuid2_matchNr=3

     

    知識點

    • 如果有多個 Jsonpath 的時候,每個字段都必填值,且字段值的數量要一致(像上圖,每個字段都填了兩個值)
    • 勾不勾 Compute concatenation var 都行
    • 字段值數量不一致則無法提取值

     

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

  • Flutter學習筆記(36)–常用內置動畫,Flutter學習筆記(36)–常用內置動畫

    Flutter學習筆記(36)–常用內置動畫,Flutter學習筆記(36)–常用內置動畫

    如需轉載,請註明出處:Flutter學習筆記(36)–常用內置動畫

    Flutter給我們提供了很多而且很好用的內置動畫,這些動畫僅僅需要簡單的幾行代碼就可以實現一些不錯的效果,Flutter的動畫分為補間動畫和基於物理的動畫,基於物理的動畫我們先不說。

    補間動畫很簡單,Android裏面也有補間動畫,就是給UI設置初始的狀態和結束狀態,經過我們定義的一段時間,系統去幫助我們實現開始到結束的過渡變化,這就是補間動畫。

    今天我們要看的Flutter的內置動畫就是補間動畫,根據Flutter提供的動畫組件,我們去設置初始、結尾的狀態,並且定義一下這個變化過程所需要的時間,再經過系統的處理(其實就是setState())來達到動畫的效果。

    接下來我們會寫一下常用的內置動畫組件,並且提供一下動畫效果的gif,方便大家更直觀的去理解。

    • AnimatedContainer

    看到Container我們就會知道這是一個帶有動畫屬性的容器組件,這個組件可以定義大小、顏色等屬性,那麼我們是不是就可以給這個組件設置初始和結束的大小及顏色的屬性值,然後通過系統來幫助我們來補足中間過程的動畫呢?

    答案是可以的,下面看一下demo和動畫效果:

    class _MyHomePageState extends State<MyHomePage> {
      double _width = 100.0;
      double _height = 100.0;
      Color _color = Colors.red;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: AnimatedContainer(
            width: _width,
            height: _height,
            duration: Duration(seconds: 2),
            color: _color,
            curve: Curves.bounceInOut,
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                _width = 300.0;
                _height = 300.0;
                _color = Colors.green;
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    demo很簡單,就是先定義好組件初始的大小和顏色,點擊按鈕,在按鈕事件裏面去更改大小和顏色的屬性值。這裏唯一需要特別說一下就是curve這個屬性。

    curve指的是動畫曲線?我開始的時候不理解這個動畫曲線是什麼意思,後來看了一組圖之後,豁然開朗。demo裏面curve我們用的是Curves.bounceInOut。如下:

     

    它其實就是一個非線性的動畫的變化形式(變化過程)也可以理解為就是一種函數,也不知道這麼說大家能不能理解。

    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4}

     

    這裡是每一種curve曲線的表現形式,大家可以看看,也可以在demo裏面多嘗試,或者可以看另一篇博客,有動畫曲線Curves 效果。

    • AnimatedCrossFade

    Flutter中文網:一個widget,在兩個孩子之間交叉淡入,並同時調整他們的尺寸。

    個人說明:CrossFade,故名思意,淡入淡出,AnimatedCrossFade組件包含兩個子widget,一個firstChild一個secondChild,這兩個組件根據狀態(我們自己定義的一個標識)改變狀態,

    一個淡入,一個淡出,同時改變大小或顏色等屬性。

    class _MyHomePageState extends State<MyHomePage> {
      bool _showFirst = true;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: AnimatedCrossFade(
            firstChild: Container(
              width: 100,
              height: 100,
              color: Colors.red,
              alignment: Alignment.center,
              child: Text('firstChild'),
            ),
            secondChild: Container(
              width: 200,
              height: 200,
              color: Colors.green,
              alignment: Alignment.center,
              child: Text('secondChild'),
            ),
            duration: Duration(seconds: 3),
            crossFadeState:
                _showFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond,
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                _showFirst = false;
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    • Hero

    Hero常用於頁面跳轉的過長動畫,比如電商App有一個商品列表,列表的每個item都有一張縮略圖,點擊會跳轉到詳情頁面,在Flutter中將圖片從一個路由飛到另一個路由稱為hero動畫,儘管相同的動作有時也稱為 共享元素轉換

    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Hero(
            tag: 'heroTag',
            child: ClipOval(
              child: Image.asset(
                'images/banner.png',
                width: 60,
                height: 60,
                fit: BoxFit.cover,
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                Navigator.push(context, MaterialPageRoute(builder: (_) {
                  return new HeroPage();
                }));
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    詳情頁面:

    import 'package:flutter/material.dart';
    
    class HeroPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'HeroPage',
          home: Scaffold(
            appBar: AppBar(
              title: Text('HeroPage'),
            ),
            body: Center(
              child: GestureDetector(
                child: Hero(
                  tag: 'heroTag',
                  child: ClipOval(
                    child: Image.asset(
                      'images/banner.png',
                      width: 300,
                      height: 300,
                      fit: BoxFit.cover,
                    ),
                  ),
                ),
                onTap: () {
                  Navigator.pop(context);
                },
              ),
            ),
          ),
        );
      }
    }

     

    注:特彆強調一下,為了將兩個頁面的元素關聯起來,hero有個tag標識,前後兩個頁面的tag標識必須一樣,不然的話元素是關聯不起來的,也就意味着不會產生hero動畫。

    1.同級tag不允許相同。

    2.前後頁面想要有hero動畫,tag必須相同。

    3.前後關聯起來的hero組件,其各自內部的child組件不是必須一樣的,就是說前面的hero的子組件可以是image,後面的hero的子組件可以是image以外的其他組件。

    • AnimatedBuilder

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 3), vsync: this);
        _animation =
            new Tween(begin: 0.0, end: 200.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: AnimatedBuilder(
              animation: _animation,
              builder: (BuildContext context, Widget child) {
                return Center(
                  child: Container(
                    color: Colors.red,
                    width: _animation.value,
                    height: _animation.value,
                    child: child,
                  ),
                );
              },
            ));
      }
    }

    AnimationController:動畫控制器(定義動畫過程時長)。

    Animation:動畫變化區間值(也可以說是開始和結束的關鍵幀值),demo里定義的值為初始0,結束200。

    _animation.value:關鍵幀值是0和200,_animation.value的值為0–200之間連續變化的值(0-1-2-3-…-198-199-200)。

    • DecoratedBoxTransition

    Decortated可以給容器添加各種外觀裝飾,比如增加圓角、陰影等裝飾。DecoratedBox的動畫版本,可以給它的Decoration不同屬性使用動畫

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<Decoration> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 3), vsync: this);
        _animation = DecorationTween(
                begin: BoxDecoration(
                    borderRadius: BorderRadius.all(Radius.circular(0.0)),
                    color: Colors.red),
                end: BoxDecoration(
                    borderRadius: BorderRadius.all(Radius.circular(30.0)),
                    color: Colors.green))
            .animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: DecoratedBoxTransition(
              decoration: _animation,
              child: Container(
                width: 100,
                height: 100,
              ),
            ),
          ),
        );
      }
    }

    • FadeTransition

    透明度變化動畫,因為透明度也是在0-1之間變化的,所以animation就還繼續用double類型的就可以了。

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: FadeTransition(
              opacity: _animation,
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
              ),
            ),
          ),
        );
      }
    }

    • RotationTransition

    旋轉動畫,對widget使用旋轉動畫 1~360°(Tween(begin: 0.0, end: 1.0))這裏的0-1指的是0°-360°

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: RotationTransition(
              turns: _animation,
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
                child: Center(child: Text('data')),
              ),
            ),
          ),
        );
      }
    }

    • ScaleTransition

    縮放動畫,Tween(begin: 1.0, end: 0.2)指的是原大小的倍數,demo里是由原大小縮小到原來的0.2倍。

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.2).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: ScaleTransition(
              scale: _animation,
              child: Container(
                width: 200,
                height: 200,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
                child: Center(child: Text('data')),
              ),
            ),
          ),
        );
      }
    }

    • SizeTransition

    僅一個方向進行縮放

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.2).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: SizeTransition(
              axis: Axis.horizontal,
              sizeFactor: _animation,
              child: Center(
                child: Container(
                  width: 200,
                  height: 200,
                  decoration: BoxDecoration(
                    color: Colors.red,
                  ),
                  child: Center(child: Text('data')),
                ),
              ),
            ),
          ),
        );
      }
    }

     

    以上!有任何疑問歡迎留言!

     

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • Spring系列.事務管理原理簡析

    Spring的事務管理功能能讓我們非常簡單地進行事務管理。只需要進行簡單的兩步配置即可:

    step1:開啟事務管理功能

    @Configuration
    //@EnableTransactionManagement註解有以下幾個屬性
    //proxyTargetClass屬相:指定事務的AOP是通過JDK動態代理實現,還是CGLIB動態代理實現。true的話是CGLIB,false的話是JDK動態代理
    //                     需要注意的是這個屬性只有在AdviceMode設置成AdviceMode.PROXY的情況下才會生效,加入使用ASPECTJ這AOP框架的話,這個屬性就失效了。
    //                     另外,這個屬性的設定可能會影響其他需要動態代理的類。比如說將這個屬性設置成true,@Async註解的方法也會使用CGLIB生成代理類。
    //                     但是總的來說,這個屬性的設置不會造成什麼負面影響,畢竟JDK動態代理和CGLIB動態代理都能實現我們的需求
    
    //mode屬性:Spring提供的AOP功能有兩種實現方式,一種是Spring自帶的AOP功能,主要靠JDK代理和CGLIB代理實現,另外一種是通過第三方框架ASPECTJ實現。這個選項
    //        就是設定Spring用哪種方式提供AOP功能。AdviceMode.PROXY表示用Spring自帶的AOP功能,AdviceMode.ASPECTJ表示使用AdviceMode提供AOP功能。
    //        需要注意的是Spring自帶的AOP功能不支持本地調用的代理功能,也就是說同一個類中的方法互相調用不會“觸發”代理方法。如果想讓自調用觸發代理,可以考慮使用ASPECTJ。
    
    //order屬性:表示當一個連接點(方法)被切多次時(也就是說有多個Advice和連接點關聯),這些連接點的執行順序。
    @EnableTransactionManagement
    public class TxConfig {
    }
    

    step2:在需要事務管理的方法上添加@Transactional註解

    @Override
    @Transactional
    public int saveSysUser(SysUser user) {
        int i = sysUserMapper.insert(user);
        return i;
    }
    

    整個使用流程就這麼簡單。這篇博客就來簡單分析下Spring是怎麼實現事務管理的。

    對事務管理進行AOP的過程

    Spring的很多功能都是通過AOP功能實現的,事務管理也是。我們之前的文章分析過Spring基礎AOP實現的原理。這邊再簡單提下Spring實現AOP的
    原理:

    Spring基礎的AOP功能的開關是@EnableAspectJAutoProxy,這個註解註冊了一個Bean——AnnotationAwareAspectJAutoProxyCreator,這個Bean才是實現AOP功能的關鍵。
    這個Bean實現了InstantiationAwareBeanPostProcessor接口(這個接口是BeanPostProcessor的子接口)。熟悉Spring的讀者知道,實現BeanPostProcessor接口的Bean
    會在其他Bean初始化之前初始,然後在其他Bean初始化的時候,BeanPostProcessor的實現會對這些Bean進行“加工處理”。

    這邊AnnotationAwareAspectJAutoProxyCreator就承擔了加工處理類的角色。這個Bean在其他Bean初始化前後會判斷這個Bean中的方法是不是有對應的Advice,如果有的話就會
    通過動態代理的方式生成動態代理類將通知織入進去。

    我們發現開啟事務管理的方式和開啟AOP功能的方式很像,也是通過Enable註解開啟。所以很自然就猜想事務管理是不是也是通過BeanPostProcessor的方式實現的。帶着這個猜想去看下@EnableTransactionManagement註解。

    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(TransactionManagementConfigurationSelector.class)
    public @interface EnableTransactionManagement {
    
    	boolean proxyTargetClass() default false;
    	AdviceMode mode() default AdviceMode.PROXY;
    	int order() default Ordered.LOWEST_PRECEDENCE;
    }
    
    

    看到上面的代碼,我們很自然的會去看TransactionManagementConfigurationSelector的代碼。Spring有兩種方式提供AOP功能,一種是自帶的動態代理的功能,一種是
    通過ASPECTJ的方式提供。這邊主要討論Spring自帶的AOP功能。

    protected String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            //用代理的方式實現事務管理的AOP功能
            case PROXY:
                return new String[] {AutoProxyRegistrar.class.getName(),
                        ProxyTransactionManagementConfiguration.class.getName()};
            case ASPECTJ:
                return new String[] {determineTransactionAspectClass()};
            default:
                return null;
        }
    }
    

    上面的代碼中,我們主要關注PROXY這個case中的方法。這個case中註冊了兩個類:AutoProxyRegistrar和ProxyTransactionManagementConfiguration。

    首先我們來看AutoProxyRegistrar這個類,層層點進入,我們發現這個類最終就是註冊了InfrastructureAdvisorAutoProxyCreator這個類。仔細看InfrastructureAdvisorAutoProxyCreator
    這個類實現的接口的話,你會發現這個類也是BeanPostProcesser系列的類。看到這裏,我的直覺是事務管理的AOP過程和Spring基礎的AOP功能原理可能是一樣的。

    再仔細看InfrastructureAdvisorAutoProxyCreator對BeanPostProcesser系列接口的實現,你會發現都是繼承的AbstractAutoProxyCreator。看到這個驗證了我之前的想法。

    下面是Spring對事務管理進行AOP的過程,你會發現和基礎的AOP功能是一套代碼。

    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }
    
    
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    
        // Create proxy if we have advice.
        // 代碼1
        // 這邊是獲取Advice和Advisor的具體代碼
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            //生成代理類
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
    
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    
    

    再來看看ProxyTransactionManagementConfiguration做了些啥?點進源代碼你會發現這個類的功能很簡單,就是註冊了下面幾個事務管理相關的基礎Bean。

    • BeanFactoryTransactionAttributeSourceAdvisor;
    • TransactionAttributeSource;
    • TransactionInterceptor。

    事務管理的生效過程

    上面的章節中講了Spring是怎麼生成事務相關的AOP代理類的。這邊來講下Spring的事務管理是怎麼生效的——怎麼開啟事務,怎麼回滾事務,怎麼提交事務,Spring中的事務傳播
    機制是怎麼生效的。

    這塊的代碼主要是在TransactionAspectSupport的invokeWithinTransaction方法中(不要問我是怎麼找到這段代碼的…)。下面講下這個方法中的幾個關鍵點。

    
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
    			final InvocationCallback invocation) throws Throwable {
    
        // If the transaction attribute is null, the method is non-transactional.
        TransactionAttributeSource tas = getTransactionAttributeSource();
        //獲取TransactionAttribute,這個類主要是@Transactional註解的配置信息
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        //確認事務管理器
        final TransactionManager tm = determineTransactionManager(txAttr);
    
        if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
            ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
                if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
                    throw new TransactionUsageException(
                            "Unsupported annotated transaction on suspending function detected: " + method +
                            ". Use TransactionalOperator.transactional extensions instead.");
                }
                ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
                if (adapter == null) {
                    throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
                            method.getReturnType());
                }
                return new ReactiveTransactionSupport(adapter);
            });
            return txSupport.invokeWithinTransaction(
                    method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
        }
    
        PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
    
        if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
            // Standard transaction demarcation with getTransaction and commit/rollback calls.
            TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
    
            Object retVal;
            try {
                // This is an around advice: Invoke the next interceptor in the chain.
                // This will normally result in a target object being invoked.
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                // target invocation exception
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                cleanupTransactionInfo(txInfo);
            }
    
            if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
                // Set rollback-only in case of Vavr failure matching our rollback rules...
                TransactionStatus status = txInfo.getTransactionStatus();
                if (status != null && txAttr != null) {
                    retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                }
            }
    
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }else {
            final ThrowableHolder throwableHolder = new ThrowableHolder();
    
            // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
            try {
                Object result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
                    TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
                    try {
                        Object retVal = invocation.proceedWithInvocation();
                        if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
                            // Set rollback-only in case of Vavr failure matching our rollback rules...
                            retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                        }
                        return retVal;
                    }
                    catch (Throwable ex) {
                        if (txAttr.rollbackOn(ex)) {
                            // A RuntimeException: will lead to a rollback.
                            if (ex instanceof RuntimeException) {
                                throw (RuntimeException) ex;
                            }
                            else {
                                throw new ThrowableHolderException(ex);
                            }
                        }
                        else {
                            // A normal return value: will lead to a commit.
                            throwableHolder.throwable = ex;
                            return null;
                        }
                    }
                    finally {
                        cleanupTransactionInfo(txInfo);
                    }
                });
    
                // Check result state: It might indicate a Throwable to rethrow.
                if (throwableHolder.throwable != null) {
                    throw throwableHolder.throwable;
                }
                return result;
            }
            catch (ThrowableHolderException ex) {
                throw ex.getCause();
            }
            catch (TransactionSystemException ex2) {
                if (throwableHolder.throwable != null) {
                    logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                    ex2.initApplicationException(throwableHolder.throwable);
                }
                throw ex2;
            }
            catch (Throwable ex2) {
                if (throwableHolder.throwable != null) {
                    logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                }
                throw ex2;
            }
        }
    }
    
    

    事務操作的主要代碼都在這個方法中,要詳細將這個方法能寫很多內容。這邊就不詳細展開了,大家感興趣的可以仔細研究下這個方法。

    重要類總結

    • InfrastructureAdvisorAutoProxyCreator:事務管理AOP註冊
    • BeanFactoryTransactionAttributeSourceAdvisor:Spring事務管理基礎Bean
    • TransactionAttributeSource:Spring事務管理基礎Bean
    • TransactionInterceptor:Spring事務管理基礎Bean
    • TransactionAspectSupport的invokeWithinTransaction方法:事務處理的主要方法

    相關註解

    如果你仔細看過Spring的相關源代碼,會發現Spring的Enable系列的註解都是上面的“套路”,熟悉了@EnableTransactionManagement註解生效的原理,其他註解都是類似的生效規則。比如

    • @EnableAsync
    • @EnableScheduling

    希望大家能做到觸類旁通。

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

    FB行銷專家,教你從零開始的技巧

  • Java多線程之volatile詳解

    Java多線程之volatile詳解

    本文目錄

    • 從多線程交替打印A和B開始
    • Java 內存模型中的可見性、原子性和有序性
    • Volatile原理
      • volatile的特性
      • volatile happens-before規則
      • volatile 內存語義
      • volatile 內存語義的實現
    • CPU對於Volatile的支持
      • 緩存一致性協議
    • 工作內存(本地內存)並不存在
    • 總結
    • 參考資料

    從多線程交替打印A和B開始

    面試中經常會有一道多線程交替打印A和B的問題,可以通過使用Lock和一個共享變量來完成這一操作,代碼如下,其中使用num來決定當前線程是否打印

    public class ABTread {
    
        private static int num=0;
        private static Lock lock=new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread A=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        lock.lock();
                        if (num==0){
                            System.out.println("A");
                            num=1;
                        }
                        lock.unlock();
                    }
                }
            },"A");
            Thread B=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        lock.lock();
                        if (num==1){
                            System.out.println("B");
                            num=0;
                        }
                        lock.unlock();
                    }
                }
            },"B");
            A.start();
            B.start();
        }
    }
    

    這一過程使用了一個可重入鎖,在以前可重入鎖的獲取流程中有分析到,當鎖被一個線程持有時,後繼的線程想要再獲取鎖就需要進入同步隊列還有可能會被阻塞。
    現在假設當A線程獲取了鎖,B線程再來獲取鎖且B線程獲取失敗則會調用LockSupport.park()導致線程B阻塞,線程A釋放鎖時再還行線程B
    是否會經常存在阻塞線程和還行線程的操作呢,阻塞和喚醒的操作是比較費時間的。是否存在一個線程剛釋放鎖之後這一個線程又再一次獲取鎖,由於共享變量的存在,
    則獲取鎖的線程一直在做着毫無意義的事情。

    可以使用volatile關鍵字來修飾共享變量來解決,代碼如下:

    public class ABTread {
    
        private static volatile  int num=0;
        public static void main(String[] args) throws InterruptedException {
    
            Thread A=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        if (num==0){        //讀取num過程記作1
                            System.out.println("A");
                            num=1;          //寫入num記位2
                        }
                    }
                }
            },"A");
            Thread B=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        if (num==1){        //讀取num過程記作3
                            System.out.println("B");
                            num=0;          ////寫入num記位4
                        }
                    }
                }
            },"B");
            A.start();
            B.start();
        }
    }
    

    Lock可以通過阻止同時訪問來完成對共享變量的同時訪問和修改,必要的時候阻塞其他嘗試獲取鎖的線程,那麼volatile關鍵字又是如何工作,
    在這個例子中,是否效果會優於Lock呢。

    Java 內存模型中的可見性、原子性和有序性

    • 可見性:指線程之間的可見性,一個線程對於狀態的修改對另一個線程是可見的,也就是說一個線程修改的結果對於其他線程是實時可見的。
      可見性是一個複雜的屬性,因為可見性中的錯誤總是會違背我們的直覺(JMM決定),通常情況下,我們無法保證執行讀操作的線程能實時的看到其他線程的寫入的值。
      為了保證線程的可見性必須使用同步機制。退一步說,最少應該保證當一個線程修改某個狀態時,而這個修改時程序員希望能被其他線程實時可見的,
      那麼應該保證這個狀態實時可見,而不需要保證所有狀態的可見。在 Javavolatilesynchronizedfinal 實現可見性。

    • 原子性:如果一個操作是不可以再被分割的,那麼我們說這個操作是一個原子操作,即具有原子性。但是例如i++實際上是i=i+1這個操作是可分割的,他不是一個原子操作。
      非原子操作在多線程的情況下會存在線程安全性問題,需要是我們使用同步技術將其變為一個原子操作。javaconcurrent包下提供了一些原子類,
      我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicIntegerAtomicLongAtomicReference等。在 Javasynchronized 和在 lockunlock 中操作保證原子性

    • 有序性:一系列操作是按照規定的順序發生的。如果在本線程之內觀察,所有的操作都是有序的,如果在其他線程觀察,所有的操作都是無序的;前半句指“線程內表現為串行語義”後半句指“指令重排序”和“工作內存和主存同步延遲”
      Java 語言提供了 volatilesynchronized 兩個關鍵字來保證線程之間操作的有序性。volatile 是因為其本身包含“禁止指令重排序”的語義,
      synchronized 是由“一個變量在同一個時刻只允許一條線程對其進行 lock 操作”這條規則獲得的,此規則決定了持有同一個對象鎖的兩個同步塊只能串行執行。

    Volatile原理

    volatile定義:Java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致的更新,線程應該通過獲取排他鎖單獨獲取這個變量;
    java提供了volatile關鍵字在某些情況下比鎖更好用。

    • Java語言提供了volatile了關鍵字來提供一種稍弱的同步機制,他能保證操作的可見性和有序性。當把變量聲明為volatile類型后,
      編譯器與運行時都會注意到這個變量是一個共享變量,並且這個變量的操作禁止與其他的變量的操作重排序。

    • 訪問volatile變量時不會執行加鎖操作。因此也不會存在阻塞競爭的線程,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制。

    volatile的特性

    volatile具有以下特性:

    • 可見性:對於一個volatile的讀總能看到最後一次對於這個volatile變量的寫
    • 原子性:對任意單個volatile變量的讀/寫具有原子性,但對於類似於i++這種複合操作不具有原子性。
    • 有序性:

    volatile happens-before規則

    根據JMM要求,共享變量存儲在共享內存當中,工作內存存儲一個共享變量的副本,
    線程對於共享變量的修改其實是對於工作內存中變量的修改,如下圖所示:

    從多線程交替打印A和B開始章節中使用volatile關鍵字的實現為例來研究volatile關鍵字實現了什麼:
    假設線程A在執行num=1之後B線程讀取num指,則存在以下happens-before關係

    1)  1 happens-before 2,3 happens-before 4
    2)  根據volatile規則有:2 happens-before 3
    3)  根據heppens-before傳遞規則有: 1 happens-before 4
    

    至此線程的執行順序是符合我們的期望的,那麼volatile是如何保證一個線程對於共享變量的修改對於其他線程可見的呢?

    volatile 內存語義

    根據JMM要求,對於一個變量的獨寫存在8個原子操作。對於一個共享變量的獨寫過程如下圖所示:

    對於一個沒有進行同步的共享變量,對其的使用過程分為readloaduseassign以及不確定的storewrite過程。
    整個過程的語言描述如下:

    - 第一步:從共享內存中讀取變量放入工作內存中(`read`、`load`)
    - 第二步:當執行引擎需要使用這個共享變量時從本地內存中加載至**CPU**中(`use`)
    - 第三步:值被更改后使用(`assign`)寫回工作內存。
    - 第四步:若之後執行引擎還需要這個值,那麼就會直接從工作內存中讀取這個值,不會再去共享內存讀取,除非工作內存中的值出於某些原因丟失。
    - 第五步:在不確定的某個時間使用`store`、`write`將工作內存中的值回寫至共享內存。
    

    由於沒有使用鎖操作,兩個線程可能同時讀取或者向共享內存中寫入同一個變量。或者在一個線程使用這個變量的過程中另一個線程讀取或者寫入變量。
    上圖中1和6兩個操作可能會同時執行,或者在線程1使用num過程中6過程執行,那麼就會有很嚴重的線程安全問題,
    一個線程可能會讀取到一個並不是我們期望的值。

    那麼如果希望一個線程的修改對後續線程的讀立刻可見,那麼只需要將修改后存儲在本地內存中的值回寫到共享內存
    並且在另一個線程讀的時候從共享內存重新讀取而不是從本地內存中直接讀取即可;事實上
    當寫一個volatile變量時,JMM會把該線程對應的本地內存中共享變量值刷新會共享內存;
    而當讀取一個volatile變量時,JMM會從主存中讀取共享變量
    ,這也就是volatile的寫-讀內存語義。

    volatile的寫-讀內存語義:

    • volatile寫的內存語義:當寫一個volatile變量時,JMM會把該線程對應的本地內存中共享變量值刷新會共享內存
    • volatile讀的內存語義:當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效,線程接下來將從主內存中讀取共享變量。

    如果將這兩個步驟綜合起來,那麼線程3讀取一個volatile變量后,寫線程1在寫這個volatile變量之前所有可見的共享變量的值都將樂客變得對線程3可見。

    volatile變量的讀寫過程如下圖:

    需要注意的是:在各個線程的工作內存中是存在volatile變量的值不一致的情況的,只是每次使用都會從共享內存讀取並刷新,執行引擎看不到不一致的情況,
    所以認為volatile變量在本地內存中不存在不一致問題。

    volatile 內存語義的實現

    在前文Java內存模型中有提到重排序。為了實現volatile的內存語義,JMM會限制重排序的行為,具體限制如下錶:

    是否可以重排序 第二個操作 第二個操作 第二個操作
    第一個操作 普通讀/寫 volatile volatile
    普通讀/寫 NO
    volatile NO NO NO
    volatile NO NO

    說明:

    - 若第一個操作時普通變量的讀寫,第二個操作時volatile變量的寫操作,則編譯器不能重排序這兩個操作
    - 若第一個操作是volatile變量的讀操作,不論第二個變量是什麼操作不餓能重排序這兩個操作
    - 若第一個操作時volatile變量的寫操作,除非第二個操作是普通變量的獨寫,否則不能重排序這兩個操作
    

    為了實現volatile變量的內存語義,編譯器生成字節碼文件時會在指令序列中插入內存屏障來禁止特定類型的處理器排序。
    為了實現volatile變量的內存語義,插入了以下內存屏障,並且在實際執行過程中,只要不改變volatile的內存語義,
    編譯器可以根據實際情況省略部分不必要的內存屏障

    - 在每個volatile寫操作前面插入StoreStore屏障
    - 在每個volatile寫操作後面插入StoreLoad屏障
    - 在每個volatile讀操作後面插入LoadLoad屏障
    - 在每個volatile讀操作後面插入LoadStore屏障
    

    插入內存屏障后volatile寫操作過程如下圖:

    插入內存屏障后volatile讀操作過程如下圖:

    至此在共享內存和工作內存中的volatile的寫-讀的工作過程全部完成

    但是現在的CPU中存在一個緩存,CPU讀取或者修改數據的時候是從緩存中獲取並修改數據,那麼如何保證CPU緩存中的數據與共享內存中的一致,並且修改后寫回共享內存呢?

    CPU對於Volatile的支持

    緩存行:cpu緩存存儲數據的基本單位,cpu不能使數據失效,但是可以使緩存行失效。

    對於CPU來說,CPU直接操作的內存時高速緩存,而每一個CPU都有自己L1、L2以及共享的L3級緩存,如下圖:

    那麼當CPU修改自身緩存中的被volatile修飾的共享變量時,如何保證對其他CPU的可見性。

    緩存一致性協議

    在多處理器的情況下,每個處理器總是嗅探總線上傳播的數據來檢查自己的緩存是否過期,當處理器發現自己對應的緩存對應的地址被修改,
    就會將當前處理器的緩存行設置為無效狀態,當處理器對這個數據進行操作的時候,會重新從系統中把數據督導處理器的緩存里。這個協議被稱之為緩存一致性協議。

    緩存一致性協議的實現又MEIMESIMOSI等等。

    MESI協議緩存狀態

    狀態 描述
    M(modified)修改 該緩存指被緩存在該CPU的緩存中並且是被修改過的,即與主存中的數據不一致,該緩存行中的數據需要在未來的某個時間點寫回主存,當寫回註冊年之後,該緩存行的狀態會變成E(獨享)
    E(exclusive)獨享 該緩存行只被緩存在該CPU的緩存中,他是未被修改過的,與主存中數據一致,該狀態可以在任何時候,當其他的CPU讀取該內存時編程共享狀態,同樣的,當CPU修改該緩存行中的內容時,該狀態可以變為M(修改)
    S(share)共享 該狀態意味着該緩存行可能被多個CPU緩存,並且各個緩存中的數據與主存中的數據一致,當有一個CPU修改自身對應的緩存的數據,其它CPU中該數據對應的緩存行被作廢
    I(Invalid)無效 該緩存行無效

    MESI協議可以防止緩存不一致的情況,但是當一個CPU修改了緩存中的數據,但是沒有寫入主存,也會存在問題,那麼如何保證CPU修改共享被volatile修飾的共享變量后立刻寫回主存呢。

    在有volatile修飾的共享變量進行寫操作的時候會多出一條帶有lock前綴的彙編代碼,而這個lock操作會做兩件事:

    1. 將當前處理器的緩存行的數據協會到系統內存。lock信號確保聲言該信號期間CPU可以獨佔共享內存。在之前通過鎖總線的方式,現在採用鎖緩存的方式。
    2. 這個寫回操作會使其他處理器的緩存中緩存了該地址的緩存行無效。在下一次這些CPU需要使用這些地址的值時,強制要求去共享內存中讀取。

    如果對聲明了volatile的共享變量進行寫,JVM會向CPU發送一條lock指令,使得將這個變量所在的緩存行緩存的數據寫回到內存中。而其他CPU通過嗅探總線上傳播的數據,
    使得自身緩存行失效,下一次使用時會從主存中獲取對應的變量。

    工作內存(本地內存)並不存在

    根據JAVA內存模型描述,各個線程使用自身的工作內存來保存共享變量,那麼是不是每個CPU緩存的數據就是從工作內存中獲取的。這樣的話,在CPU緩存寫回主存時,
    協會的是自己的工作內存地址,而各個線程的工作內存地址並不一樣。CPU嗅探總線時就嗅探不到自身的緩存中緩存有對應的共享變量,從而導致錯誤?

    事實上,工作內存並不真實存在,只是JMM為了便於理解抽象出來的概念,它涵蓋了緩存,寫緩衝區、寄存器及其他的硬件編譯器優化。所以緩存是直接和共享內存交互的。
    每個CPU緩存的共享數據的地址是一致的。

    總結

    • volatile提供了一種輕量級同步機制來完成同步,它可以保操作的可見性、有序性以及對於單個volatile變量的讀/寫具有原子性,對於符合操作等非原子操作不具有原子性。

    • volatile通過添加內存屏障及緩存一致性協議來完成對可見性的保證。

    最後Lock#lock()是如何保證可見性的呢??

    Lock#lock()使用了AQSstate來標識鎖狀態,而statevolatile標記的,由於對於volatile的獨寫操作時添加了內存屏障的,所以在修改鎖狀態之前,
    一定會將之前的修改寫回共享內存。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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

  • mysql定時備份任務

    mysql定時備份任務

    簡介

    在生產環境上,為了避免數據的丟失,通常情況下都會定時的對數據庫進行備份。而Linux的crontab指令則可以幫助我們實現對數據庫定時進行備份。首先我們來簡單了解crontab指令,如果你會了請跳到下一個內容mysql備份
    本文章的mysql數據庫是安裝在docker容器當中,以此為例進行講解。沒有安裝到docker容器當中也可以參照參照。

    contab定時任務

    使用crontab -e來編寫我們的定時任務。

    0 5 * * 1 [command]
    

    前面的5個数字分別代表分、時、日、月、周,後面的 command為你的執行命令。
    假如你需要在每天晚上8點整執行定時任務,那麼可以這麼寫

    0 8 * * * [command]
    

    擴展:
    crontab -l 可以查看自己的定時任務
    crontab -r 刪除當前用戶的所有定時任務

    mysql備份

    快速上手

    這裏我的mysql數據庫是docker容器。假如你需要在每天晚上8點整執行定時任務,那麼可以這麼寫。
    首先執行命令crontab -e

    0 8 * * * docker exec mysql_container mysqldump -uroot -proot_password database_name > /var/backups/mysql/$(date +%Y%m%d_%H%M%S).sql
    

    mysql_container 為你的數據庫容器名
    mysqldump 是mysql數據庫導出數據的指令
    -u 填寫root賬號
    -p 填寫root密碼
    database_name 需要備份的數據庫名
    /var/backups/mysql/$(date +%Y%m%d_%H%M%S).sql 備份文件,後面是文件名的格式

    如果你沒什麼要求,單純的只是想要備份,那麼上面那個命令就可以幫你進行定時備份。

    小坑: mysql備份的時候我使用了docker exec -it mysqldump ... 這樣的命令去做bash腳本,因為-i參數是有互動的意思,導致在crontab中執行定時任務的時候,沒有輸出數據到sql文件當中。所以使用crontab定時的對docker容器進行備份命令的時候不要添加-i參數。

    crontab優化

    我不建議直接在crontab -e裏面寫要執行的命令,任務多了就把這個文件寫的亂七八招了。
    建議把數據庫備份的命令寫成一個bash腳本。在crontab這裏調用就好了
    如:建立一個/var/backups/mysql/mysqldump.sh文件,內容如下

    docker exec mysql_container mysqldump -uroot -pmypassword database_name > /var/backups/mysql/$(date +%Y%m%d_%H%M%S).sql
    

    然後把文件改為當前用戶可執行的:

    chmod 711 /var/backups/mysql/mysqldump.sh
    

    執行crontab -e 命令修改成如下:

    0 20 * * * /var/backups/mysql/mysqldump.sh
    

    那麼這樣就比較規範了。

    mysql備份優化

    因為sql文件比較大,所以一般情況下都會對sql文件進行壓縮,不然的話磁盤佔用就太大了。
    假設你做了上面這一步 crontab優化,我們可以把mysqldump.sh腳本改成下面這樣:

    export mysqldump_date=$(date +%Y%m%d_%H%M%S) && \
    docker exec mysql_container mysqldump -uroot -pmypassword database_name> /var/backups/mysql/$mysqldump_date.sql && \
    gzip /var/backups/mysql/$mysqldump_date.sql
    find /var/backups/mysql/ -name "*.sql" -mtime +15 -exec rm -f {} \;
    

    export 在系統中自定義了個變量mysqldump_date,給備份和壓縮命令使用
    gzip 為壓縮命令,默認壓縮了之後會把源文件刪除,壓縮成.gz文件
    find ... 這行命令的意思為,查詢 /var/backups/mysql/目錄下,創建時間15天之前(-mtime +15),文件名後綴為.sql的所有文件 執行刪除命令-exec rm -f {} \;。總的意思就是:mysql的備份文件只保留15天之內的。15天之前的都刪除掉。

    數據恢復

    若一不小心你執行drop database,穩住,淡定。我們首先要創建數據庫被刪除的數據庫。

    >mysql create database database_name;
    

    然後恢復最近備份的數據。恢復備份的命令:

    docker exec -i mysql_container mysql -uroot -proot_password database_name < /var/backups/mysql/20200619_120012.sql
    

    雖然恢復了備份文件的數據,但是備份時間點之後的數據我們卻沒有恢復回來。
    如:晚上8點進行定時備份,但是卻在晚上9點drop database,那麼晚上8點到晚上9點這一個小時之內的數據卻沒有備份到。這時候就要使用binlog日誌了。

    binlog日誌

    binlog 是mysql的一個歸檔日誌,記錄的數據修改的邏輯,如:給 ID = 3 的這一行的 money 字段 + 1。
    首先登錄mysql后查詢當前有多少個binlog文件:

    > mysql show binary logs;
    +---------------+-----------+-----------+
    | Log_name      | File_size | Encrypted |
    +---------------+-----------+-----------+
    | binlog.000001 |       729 | No        |
    | binlog.000002 |      1749 | No        |
    | binlog.000003 |      1087 | No        |
    +---------------+-----------+-----------+
    

    查看當前正在寫入的binlog

    mysql> show master status\G;
    

    生成新的binlog文件,mysql的後續操作都會寫入到新的binlog文件當中,一般在恢複數據都時候都會先執行這個命令。

    mysql> flush logs
    

    查看binlog日誌

    mysql> show binlog events in 'binlog.000003';
    

    小知識點:初始化mysql容器時,添加參數--binlog-rows-query-log-events=ON。或者到容器當中修改/etc/mysql/my.cnf文件,添加參數binlog_rows_query_log_events=ON,然後重啟mysql容器。這樣可以把原始的SQL添加到binlog文件當中。

    恢複數據

    拿回上面例子的這段話。

    晚上8點進行定時備份,但是卻在晚上9點drop database,那麼晚上8點到晚上9點這一個小時之內的數據卻沒有備份到。。

    首先進入到mysql容器后,切換到/var/lib/mysql目錄下,查看binlog文件的創建日期

    cd /var/lib/mysql
    ls -l
    ...
    -rw-r----- 1 mysql mysql      729 Jun 19 15:54  binlog.000001
    -rw-r----- 1 mysql mysql     1749 Jun 19 18:45  binlog.000002
    -rw-r----- 1 mysql mysql     1087 Jun 19 20:58  binlog.000003
    ...
    

    從文件日期可以看出:當天時間為2020-06-21,binlog.000002文件的最後更新時間是 18:45 分,那麼晚上8點的備份肯定包含了binlog.000002的數據;
    binlog.000003的最後更新日期為 20:58 分,那麼我們需要恢復的數據 = 晚上8點的全量備份 + binlog.000003的 20:00 – 執行drop database命令時間前的數據。

    恢復命令格式:

    mysqlbinlog [options] file | mysql -uroot -proot_password database_name
    

    mysqlbinlog常用參數:

    –start-datetime 開始時間,格式 2020-06-19 18:00:00
    –stop-datetime 結束時間,格式同上
    –start-positon 開始位置,(需要查看binlog文件)
    –stop-position 結束位置,同上

    恢復備份數據和binlog數據前建議先登錄mysql后執行flush logs生成新的binlog日誌,這樣可以專註需要恢複數據的binlog文件。
    首先我們需要查看binlog日誌,在哪個位置進行了drop database操作:

    mysql> show binlog events in 'binlog.000003';
    +---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------+
    | Log_name      | Pos | Event_type     | Server_id | End_log_pos | Info                                                                                                                                        |
    +---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------+
    | binlog.000003 |   4 | Format_desc    |         1 |         125 | Server ver: 8.0.20, Binlog ver: 4                                                                                                           |
    | binlog.000003 | 125 | Previous_gtids |         1 |         156 |                                                                                                                                             |
    | binlog.000003 | 156 | Anonymous_Gtid |         1 |         235 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                                                                        |
    | binlog.000003 | 235 | Query          |         1 |         318 | BEGIN                                                                                                                                       |
    | binlog.000003 | 318 | Rows_query     |         1 |         479 | # INSERT INTO `product_category` SET `name` = '床上用品' , `create_time` = 1592707634 , `update_time` = 1592707634 , `lock_version` = 0      |
    | binlog.000003 | 479 | Table_map      |         1 |         559 | table_id: 139 (hotel_server.product_category)                                                                                               |
    | binlog.000003 | 559 | Write_rows     |         1 |         629 | table_id: 139 flags: STMT_END_F                                                                                                             |
    | binlog.000003 | 629 | Xid            |         1 |         660 | COMMIT /* xid=2021 */                                                                                                                       |
    | binlog.000004 | 660 | Anonymous_Gtid |         1 |         739 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                                                                        |
    | binlog.000004 | 739 | Query          |         1 |         822 | drop database hotel_server /* xid=26 */                                                                                                     |
    +---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------+
    

    根據上面的日誌,我們可以看到,在End_log_pos = 822 的位置執行了drop database操作,那麼使用binlog恢復的範圍就在2020-06-19 20:00:00 – 660 的位置。為什麼是660?因為drop database的上一個事務的提交是660的位置,命令如下:

    mysqlbinlog --start-datetime=2020-06-19 20:00:00 --stop-position=660 /var/lib/mysql/binlog.000003 | mysql -uroot -proot_password datbase_name
    

    如果你的範圍包括了822的位置,那麼就會幫你執行drop database命令了。不信你試試?
    執行完上面的命令,你的數據就會恢復到drop database前啦!開不開心,激不激動!

    總結

    因為mysql定時備份是在生產環境上必須的任務。是很常用的。所以我就迫不及待的寫博客。當然也很感謝我同事的幫助。這篇文章已經寫了三天了,因為我也是在不斷地試錯,不斷的更新文章。避免把錯誤的知識點寫出來。如果幫到你了,關注我一波唄!謝謝。

    個人博客網址: https://colablog.cn/

    如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您

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

    【其他文章推薦】

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

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

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

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

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

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