標籤: 網頁設計

  • 基於領域驅動設計(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

    預覽圖

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

    【其他文章推薦】

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

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

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

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

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

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

  • 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 都行
    • 字段值數量不一致則無法提取值

     

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

  • 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維修中心

  • Java 多線程基礎(十)interrupt()和線程終止方式

    Java 多線程基礎(十)interrupt()和線程終止方式

    一、interrupt() 介紹

    interrupt() 定義在 Thread 類中,作用是中斷本線程

    本線程中斷自己是被允許的;其它線程調用本線程的 interrupt() 方法時,會通過 checkAccess() 檢查權限。這有可能拋出 SecurityException 異常。
    如果本線程是處於阻塞狀態:調用線程的 wait() , wait(long) 或 wait(long, int) 會讓它進入等待(阻塞)狀態,或者調用線程的 join(),join(long),join(long, int),sleep(long),sleep(long, int) 也會讓它進入阻塞狀態。若線程在阻塞狀態時,調用了它的 interrupt() 方法,那麼它的“中斷狀態”會被清除並且會收到一個 InterruptedException 異常。例如,線程通過 wait() 進入阻塞狀態,此時通過 interrupt() 中斷該線程;調用 interrupt() 會立即將線程的中斷標記設為 true,但是由於線程處於阻塞狀態,所以該“中斷標記”會立即被清除為 “false”,同時,會產生一個 InterruptedException 的異常
    如果線程被阻塞在一個 Selector 選擇器中,那麼通過 interrupt() 中斷它時;線程的中斷標記會被設置為 true,並且它會立即從選擇操作中返回。
    如果不屬於前面所說的情況,那麼通過 interrupt() 中斷線程時,它的中斷標記會被設置為 true。
    中斷一個“已終止的線程”不會產生任何操作。

    二、線程終止方式

    Thread中的 stop() 和 suspend() 方法,由於固有的不安全性,已經建議不再使用!
    下面,我先分別討論線程在“阻塞狀態”和“運行狀態”的終止方式,然後再總結出一個通用的方式。

    (一)、終止處於“阻塞狀態”的線程.

    通常,我們通過“中斷”方式終止處於“阻塞狀態”的線程
    當線程由於被調用了 sleep(),,wait(),join() 等方法而進入阻塞狀態;若此時調用線程的 interrupt() 將線程的中斷標記設為 true。由於處於阻塞狀態,中斷標記會被清除,同時產生一個InterruptedException 異常。將 InterruptedException 放在適當的位置就能終止線程,形式如下:

    public void run() {
        try {
            while (true) {
                // 執行業務
            }
        } catch (InterruptedException ie) {  
            // 由於產生InterruptedException異常,退出while(true)循環,線程終止!
        }
    }

    說明:

    在while(true)中不斷的執行業務代碼,當線程處於阻塞狀態時,調用線程的 interrupt() 產生 InterruptedException 中斷。中斷的捕獲在 while(true) 之外,這樣就退出了 while(true) 循環!

    注意:

    對 InterruptedException 的捕獲務一般放在 while(true) 循環體的外面,這樣,在產生異常時就退出了 while(true) 循環。否則,InterruptedException 在 while(true) 循環體之內,就需要額外的添加退出處理。形式如下: 

    public void run() {
        while (true) {
            try {
                // 執行任務...
            } catch (InterruptedException ie) {  
                // InterruptedException在while(true)循環體內。
                // 當線程產生了InterruptedException異常時,while(true)仍能繼續運行!需要手動退出
                break;
            }
        }
    }

    說明:

    上面的 InterruptedException 異常的捕獲在 whle(true) 之內。當產生 InterruptedException 異常時,被 catch 處理之外,仍然在 while(true) 循環體內;要退出 while(true) 循環體,需要額外的執行退出while(true) 的操作。

    (二)、終止處於“運行狀態”的線程

    通常,我們通過“標記”方式終止處於“運行狀態”的線程。其中,包括“中斷標記”和“額外添加標記”。

    1、通過“中斷標記”終止線程

    public void run() {
        while (!isInterrupted()) {
            // 執行任務...
        }
    }

    說明:

    isInterrupted() 是判斷線程的中斷標記是不是為 true。當線程處於運行狀態,並且我們需要終止它時;可以調用線程的 interrupt() 方法,使用線程的中斷標記為 true,即 isInterrupted() 會返回true。此時,就會退出while循環。
    注意:interrupt() 並不會終止處於“運行狀態”的線程!它會將線程的中斷標記設為 true。

    2、通過“額外添加標記”終止線程

    private volatile boolean flag= true;
    protected void stopTask() {
        flag = false;
    }
    public void run() {
        while (flag) {
            // 執行任務...
        }
    }

    說明:

    線程中有一個 flag 標記,它的默認值是 true;並且我們提供 stopTask() 來設置 flag 標記。當我們需要終止該線程時,調用該線程的 stopTask() 方法就可以讓線程退出 while 循環。
    注意:將 flag 定義為 volatile 類型,是為了保證 flag 的可見性。即其它線程通過 stopTask() 修改了 flag 之後,本線程能看到修改后的 flag 的值。

    (三)、通過方式

    綜合線程處於“阻塞狀態”和“運行狀態”的終止方式,比較通用的終止線程的形式如下:

    public void run() {
        try {
            // 1. isInterrupted()保證,只要中斷標記為true就終止線程。
            while (!isInterrupted()) {
                // 執行任務...
            }
        } catch (InterruptedException ie) {  
            // 2. InterruptedException異常保證,當InterruptedException異常產生時,線程被終止。
        }
    }
    1、isInterrupted()保證,只要中斷標記為 true 就終止線程。
    2、InterruptedException 異常保證,當 InterruptedException 異常產生時,線程被終止。

    三、示例

    public class InterruptTest {
        public static void main(String[] args) {
            try {
                Thread t1 = new MyThread("t1"); // 新建線程t1
                System.out.println(t1.getName() + "[" + t1.getState() + "] is new.");
                
                t1.start();// 啟動線程t1
                System.out.println(t1.getName() + "[" + t1.getState() + "] is started.");
                
                Thread.sleep(300);// 休眠300毫秒,然後主線程給t1發“中斷”指令,查看t1狀態
                t1.interrupt();
                System.out.println(t1.getName() + "[" + t1.getState() + "] is interrupted.");
                
                Thread.sleep(300);// 休眠300毫秒,然後查看t1狀態
                System.out.println(t1.getName() + "[" + t1.getState() + "] is interrupted now.");
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            try {
                int i = 0;
                while(!isInterrupted()) {
                    Thread.sleep(100);// 休眠100毫秒
                    ++i;
                    System.out.println(Thread.currentThread().getName() + "[" + this.getState() + "] loop " + i);
                }
            }catch(InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "[" + this.getState() + "] catch InterruptedException");
            }
        }
    }
    // 運行結果
    t1 [ NEW ] is new.
    t1 [ RUNNABLE ] is started.
    t1 [ RUNNABLE ] loop 1
    t1 [ RUNNABLE ] loop 2
    t1 [ RUNNABLE ] loop 3
    t1 [ RUNNABLE ] catch InterruptedException
    t1 [ TERMINATED ] is interrupted.
    t1 [ TERMINATED ] is interrupted now.

    說明:

    ①、主線程 main 中通過 new MyThread(“t1”) 創建線程 t1,之後通過 t1.start() 啟動線程 t1。
    ②、t1 啟動之後,會不斷的檢查它的中斷標記,如果中斷標記為“false”;則休眠 100ms。
    ③、t1 休眠之後,會切換到主線程main;主線程再次運行時,會執行t1.interrupt()中斷線程t1。t1收到中斷指令之後,會將t1的中斷標記設置“false”,而且會拋出 InterruptedException 異常。在 t1 的 run() 方法中,是在循環體 while 之外捕獲的異常;因此循環被終止。

    我們對上面的結果進行小小的修改,將run()方法中捕獲InterruptedException異常的代碼塊移到while循環體內。

    public class InterruptTest {
        public static void main(String[] args) {
            try {
                Thread t1 = new MyThread("t1"); // 新建線程t1
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is new.");
                
                t1.start();// 啟動線程t1
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is started.");
                
                Thread.sleep(300);// 休眠300毫秒,然後主線程給t1發“中斷”指令,查看t1狀態
                t1.interrupt();
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted.");
                
                Thread.sleep(300);// 休眠300毫秒,然後查看t1狀態
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted now.");
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            
        }
    }
    class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            int i = 0;
            while(!isInterrupted()) {
                try {
                    Thread.sleep(100); // 休眠100ms
                } catch (InterruptedException ie) {  
                    System.out.println(Thread.currentThread().getName() +" [ "+this.getState()+" ] catch InterruptedException.");  
                }
                i++;
                System.out.println(Thread.currentThread().getName()+" [ "+this.getState()+" ] loop " + i);  
            }
        }
    }
    // 運行結果
    t1 [ NEW ] is new.
    t1 [ RUNNABLE ] is started.
    t1 [ RUNNABLE ] loop 1
    t1 [ RUNNABLE ] loop 2
    t1 [ TIMED_WAITING ] is interrupted.
    t1 [ RUNNABLE ] catch InterruptedException.
    t1 [ RUNNABLE ] loop 3
    t1 [ RUNNABLE ] loop 4
    t1 [ RUNNABLE ] loop 5
    t1 [ RUNNABLE ] loop 6
    t1 [ RUNNABLE ] is interrupted now.
    t1 [ RUNNABLE ] loop 7
    ...... // 無限循環

    說明:

    程序進入了死循環了。

    這是因為,t1在“等待(阻塞)狀態”時,被 interrupt() 中斷;此時,會清除中斷標記(即 isInterrupted() 會返回 false),而且會拋出 InterruptedException 異常(該異常在while循環體內被捕獲)。因此,t1理所當然的會進入死循環了。
    解決該問題,需要我們在捕獲異常時,額外的進行退出 while 循環的處理。例如,在 MyThread 的 catch(InterruptedException) 中添加 break 或 return 就能解決該問題。

    下面是通過“額外添加標記”的方式終止“狀態狀態”的線程的示例:

    public class InterruptTest {
        public static void main(String[] args) {
            try {
                MyThread t1 = new MyThread("t1"); // 新建線程t1
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is new.");
                
                t1.start();// 啟動線程t1
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is started.");
                
                Thread.sleep(300);// 休眠300毫秒,然後主線程給t1發“中斷”指令,查看t1狀態
                t1.stopTask();
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted.");
                
                Thread.sleep(300);// 休眠300毫秒,然後查看t1狀態
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted now.");
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            
        }
    }
    class MyThread extends Thread{
        private volatile boolean flag = true;
        public void stopTask() {
            flag = false;
        }
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            synchronized (this) {
                int i = 0;
                while(flag) {
                    try {
                        Thread.sleep(100); // 休眠100ms
                    } catch (InterruptedException ie) {  
                        System.out.println(Thread.currentThread().getName() +" [ "+this.getState()+" ] catch InterruptedException.");  
                        break;
                    }
                    i++;
                    System.out.println(Thread.currentThread().getName()+" [ "+this.getState()+" ] loop " + i);  
                }
            }
            
        }
    }
    // 運行結果
    t1 [ NEW ] is new.
    t1 [ RUNNABLE ] is started.
    t1 [ RUNNABLE ] loop 1
    t1 [ RUNNABLE ] loop 2
    t1 [ RUNNABLE ] loop 3
    t1 [ RUNNABLE ] is interrupted.
    t1 [ TERMINATED ] is interrupted now.

    四、interrupted() 和 isInterrupted()的區別

    interrupted() 和 isInterrupted()都能夠用於檢測對象的“中斷標記”。
    區別是,interrupted() 除了返回中斷標記之外,它還會清除中斷標記(即將中斷標記設為 false);而 isInterrupted() 僅僅返回中斷標記

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • JVM 總結

    JVM 總結

    JVM GC 總結。

    周志明大大的《深入理解Java虛擬機》出第三版了,早早的買了這本書,卻一直沒有花時間看。近來抽空溫習了一下,感覺又有了新的收穫。這裏簡單總結下。

    GC的由來

    由於的動態性,操作系統將堆交由給了開發者自己管理,手動申請,手動釋放。對於C++,則是將這個權限繼續交給了開發者,而對於Java,則是將這個過程自動化了。為什麼要釋放內存呢?最簡單的原因就是操作系統一共給你了4G的內存空間,你需要的時候,就去借用。有借有還,再借不難,只借不還,最後4G內存空間被用完了,你就無法再申請新的內存了。內存泄漏,就是只借不還。

    JVM在操作系統與開發者之間又封裝了一層,間接的接管了內存的劃分。同時也將堆統一管理起來,使得開發者只管借用內存,由JVM負責回收,了解JVM的回收機制,明白它的原理,能讓開發者在不同的場景下,定製不同的回收規則,提高回收效率。

    關於GC的思考

    如果讓我設計一個能自動回收垃圾的虛擬機,我會怎麼設計呢?

    • 什麼時候開始回收?
    • 怎麼判斷這部分內存可以回收?
    • 怎麼回收這部分的垃圾?

    這3個問題,也是JVM開發者一直在思考的問題。之前簡單了解過JVM,就知道JVM會有Stop The World的問題,這對於用戶體驗來說非常不好,其根本原因便是因為在回收垃圾的時候,用戶線程可能會修改這部分內存,如果不暫停用戶線程,則可能會導致嚴重的問題,而如何減少Stop The World的時候,甚至讓其消失,是各個垃圾回收器一直追求的目標。

    哪些內存可以回收?

    對於一個對象來說,當不存在任何一個引用能夠訪問到這個對象的時候,則說明這個對象可以進行回收。因為沒有任何引用指向這個對象,那麼這個對象就不能被讀或寫。

    • 引用計數法

      前面說判斷一個對象可以被回收的標準就是是否還有引用指向這個對象,所以最容易想到的便是引用計數法,通過判斷一個對象的引用數量即可,可是這樣無法判斷兩個循環引用的對象。

    • 可達性分析

      可達性分析指的是從目前程序中正在使用的所有引用的對象出發,循環遍歷所有能找到的對象。

      作為出發的點的這些對象,被稱為GC Roots

      GC Roots主要包括以下幾種:

      • 在虛擬機棧(比如棧幀中的本地遍歷表)中引用的對象
      • 靜態屬性引用的對象
      • 常量池引用對象(比如
        String Table
      • 本地方法棧引用的對象
      • Java虛擬機內部的引用對應的對象
      • 所有被同步鎖持有的對象

      總體來說,就是當前程序中正在被使用的引用所指向的對象會被作為GC Roots

    GC Roots出發,依次查找,就能標記出當前存活的對象。但是標記這個過程,細節上依然存在問題:

    • STW : 標記是通過引用查找對象的,如果在標記過程中,用戶修改了引用的對象,那麼會導致不可預估的後果,因此一般標記過程中,是會STW

    • 跨代標記 : 現在的垃圾回收器,大多數都是分代,或者分區域回收的,也就是說,可能進行垃圾回收的時候,不是標記所有的垃圾,而是標記一部分,比如老年代或者新生代。此時就存在一個問題,跨代引用。比如一個新生代的對象,僅僅被一個老年代對象引用的話,對於Yong GC來說,是不會掃描老年代對象的,這個時候就會造成誤判。解決這個誤判的方法便是記憶集(Remembered Set),記憶集通過AOP技術生成寫屏障來維護。

      前面說了從GC Roots開始掃面,那分代收集的,怎麼知道哪些對象是新生代的,哪些對象是老年代的呢?因為GC Roots是包含了所有引用的。後面想想,其實對象的分代信息是存放在對象頭裡面的。在掃描GC Roots的時候,只保留新生代的對象即可。這樣基本能保證掃描到的是新生代對象,然後老年代對新生代引用交給記憶集實現就行(自己的猜測,沒有證據)

      JVM書中說道通過AOP生成的寫屏障會使得只要有更新操作,無論更新的是不是老年代對新生代對象的引用,都會使卡表變髒,不過這樣的代價相對來說是能接受的。

    • GC Roots 需要掃描的引用過多 :隨着現在Java應用越做越大,Java堆也越來越大,GC Roots的掃描是需要STW的,如果每次GC都逐個掃描,會非常的浪費時間。解決這個問題的辦法就是OopMap,使用OopMap記錄應用程序所存放的引用,每次需要GC的時候掃描這個OopMap即可生成對應的GC RootsOopMap通過安全點和安全區域來維護,只有在安全點或安全區域的時候,才更新OopMap和進行垃圾回收。

    • 併發標記過程可能丟失存活的對象 :從CMSG1,都將從GC Roots出發標記存活對象的過程修改成併發的,這樣會需要解決的問題就是標記過程中如果用戶修改了對象的引用,可能會導致本應該存活的對象”丟失“(可以通過三色標記分析),相應的解決方案便是破壞存活對象消失的必要條件,分別是增量更新(Incremental Upate)和原始快照(Snapshot At The Begin,SATB),增量更新破壞的是第一個條件,每插入一個引用,就都記錄下來,而原始快照破壞的是第二個條件,每刪除一個,都將其記錄下來。

      增量更新和併發快照也是通過前面所說的AOP技術生成寫屏障來維護

    通過以上分析以及解決方案,基本明白了怎麼標記那些內存可以回收,接下來需要分析的就是什麼時候開始回收

    什麼時候開始內存回收?

    對於內存回收來說,開始也需要有一定的講究,理論上來說,隨時隨地都可以開始內存回收,但是如果回收時使用的內存過多,會導致GC時間過程,進而STW時間也會很長,如果回收過於頻繁,又會導致吞吐量下降,畢竟每次掃描GC Roots都回STW的。

    同時,前面還說過,對於用戶線程來說,需要將用戶線程運行到安全點,更新對應的OopMap,才能開始垃圾回收。

    因此,對應何時GC,有以下幾點分析:

    • 對於新生代來說,一般新生代滿了(Eden + Survivor1)就會開始進行(Yong/Minor GC

    • 對於老年代來說,一般是老年代滿了了會開始Full/Major GC

      注意:這裏的滿了,需要根據具體的回收器不同,來衡量真正的滿,對於沒有併發過程的GC,老年代滿一般指的是真正到達100%,已經無法分配內存了,對於有併發過程的GC,則需要預留出來空間給用戶線程在併發過程中同時申請內存,如果預留內存過小,則會使用非併發垃圾回收器進行Full GC

      CMS: -XX:CMSInitiatingOccupancyFraction 設置,默認92% (JDK 8),表示當老年代垃圾佔用到92%就開始老年代回收, JDK 9后便無法使用CMS

      G1: -XX:G1ReservePercent設置,默認為10,表示當整個Java堆使用到達90%,就開始回收。同時配合的參數還有-XX:InitiatingHeapOccupancyPercent=n,默認值為45,表示使用率到達45%就啟動標記周期。這裏的GCMixed GC

      一般來說,只有CMS才有Major GC,其他老年代GC都會回收整個Java堆,也稱為Full GC

    • 統計得到的Minor GC晉陞到老年代的平均大小大於老年代剩餘的空間。(JDK 6 之後已經刪除了擔保規則)

    • GC併發失敗(concurrent mode failure): 情況如前面說的,併發標記過程中,又出現了新生代晉陞的情況,但是此時老年代剩下的內存不足夠放下晉陞的對象的時候,會生導致Full GC

      這裏的Full GC和情況1中說到達預留空間的GC不一樣,情況1是正常進行的GC,而這個併發失敗卻是GC過程中出現了異常,一般需要切換到非併發GC,此時性能會大大下降

    • 方法區區域被使用完畢:JDK 8之後將方法區從Perm Gen替換成了元空間,一般來說元空間大小理論上等於本地內存大小,不過元空間有一個默認初始值,到達默認初始值后,會通過Full GC擴大

      注意:G1只有Yong GCMixed GC。沒有Full GC的概念,也就是說如果需要回收方法區的話,只能退化為Serial GC進行Full GC

      CMS可以通過-XX:+CMSClassUnloadingEnabled設置併發回收方法區

    • 最大連續空間裝不下大對象:對於CMS,基於標記-清除算法來說,即使空間足夠,但是由於內存碎片,裝不下分配的大對象時,會進行一次Full GC,對於G1來說,當分配巨型對象的時候,如果在老年代無法找到連續的Humongous的時候,會進行Full GC

    • 用戶執行System.gc(),可以通過-XX:+DisableExplicitGC屏蔽

    怎麼回收這些內存

    最後一步便是怎麼回收這些內存。怎麼回收,書中介紹不多,總體來說有以下三種:

    • 標記-清除(
      Mark-Sweep):最原始的方法,實現簡單,不用移動對象,很容易做到不用
      Stop The Word,但是缺點也很致命,容易產生內存碎片。標記清除的速度一般,
      Mark階段與活對象的數量成正比,
      Sweep階段與整堆大小成正比。目前只有
      CMS使用這種回收方案
    • 標記-複製(
      Mark-Copying):基於標記-清除修改的垃圾回收算法,需要移動對象。 前期標記,然後複製活下來的對象到另一個區域,再總體回收整塊區域。標記複製算法對於新生代這種專門放朝生夕死的對象效率非常高,因為存活下來的對象少,所以
      Mark階段和
      Copying階段花費的時間都會比較少,幾乎所有的分代
      GC新生代都是使用的這種算法
    • 標記-整理(
      Mark-Compact): 基於標記-清除算法修改的垃圾回收器,需要移動對象。前期標記,然後將所有對象移動到一起,再對剩餘的區域進行回收,速度最慢,但是不會產生內存碎片。

    對於新生代使用標記-複製算法,是毋庸置疑的。但是對於老年代,使用標記清除還是標記整理,需要有一定的考量。因為使用標記-清除,不用移動對象,速度會相對來說比較快,但是由於存在內存碎片,無法使用指針碰撞的方式分配內存,而不得不使用“分區空閑分配鏈表”來解決內存分配的問題,這樣會對在內存分配帶來一定的效率影響,而標記-整理算法需要移動對象,特別是對於老年代這種大對象來說,移動這些對象將是一種極為負重的操作,但是標記-整理不會產生內存碎片。

    因此,基於以上考慮,對於CMS這種側重響應速度,致力於減少STW時間的回收器來說,選擇了標記-清除算法,但是由於內存分配是一個非常頻繁的操作,使用”分區空閑分配鏈表”會降低整個垃圾回收器的吞吐量,因此,對於Parllel Scavenge這種注重回收吞吐的垃圾回收器來說,選擇了標記-整理算法。當然,對於G1則是吞吐和響應速度都比較注重,權衡之下,選擇了標記-整理(全局)算法。

    GC的概念,到這裏基本總結完畢,但是,如果僅僅是理論,只是讓我們記着一些概念性的東西,接下來,我會結合CMSG1GC日誌以及《深入理解JVM》第四章的內容,聊一聊如何分析以及查看GC過程,簡單介紹如果進行GC調優。

    個人公眾號:

    不定期更新一些經典Java書籍總結。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 美國迪士尼致力環保 將停用一次用塑膠吸管

    摘錄自2018年7月27日蘋果日報美國報導

    美國娛樂巨頭華特迪士尼公司( Walt Disney Company)昨天(26日)宣示,明年中期以前,迪士尼樂園等將停止使用一次性塑膠吸管。鑑於塑料垃圾造成的海洋污染日益嚴重,為保護地球環境,歐美正在推廣相同的措施。

    迪士尼指出,此舉將可每年減少1.75億根以上的吸管、1.3億根攪拌棒,強調本次嘗試是迪士尼履行環保責任的一環。

    共同社則報導,據與迪士尼方面簽訂許可協議的東方樂園公司稱,由於位於千葉縣浦安市的東京迪士尼度假區,運營母體不同,因此不受此次華特迪士尼公司決定的影響。然而該公司也指,「正在研究減少塑料廢棄物」。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 核外洩「鈾污染」超標1300倍 美國西屋工廠地板破8公分

    摘錄自2018年7月26日東森新聞報導

    美國南卡羅萊納州西屋公司(Westinghouse)驚傳核外洩,放射性鈾已經污染廠區下方的土壤,程度是一般土壤環境中鈾含量的約1300倍。據《美聯社》報導,核燃料生產廠的鋼筋水泥地板破洞約8公分,導致放射性鈾外洩。美國疾病控管及防治中心的資料顯示,若水中的鈾含量超標,可能造成飲用者腎臟受損。

    美國聯邦核能規範委員會(Nuclear Regulatory Commission,NRC)已經證實此事。該廠位於南卡首府、最大城哥倫比亞市的南邊,放射性的鈾是用來製造核燃料棒。

    西屋公司二年前曾因工廠防治空污裝置中的鈾累積過多,而關閉部分廠區。

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 保護復活島生態文化 智利限遊客停留最多30日

    摘錄自2018年7月30日自由時報報導

    智利國會今年3月立法,強化旅客登復活島規範,並限制外來人口數量,限令於8月1日正式生效。

    法新社報導,根據新法令,外籍人士或非當地原住民的智利人,在島上停留的期限從原本的90天降至30天,僅原住民拉帕努伊人(Rapa Nui)的直系血親,才可在島上長期居住。此外,公務員、政府相關組織人士,以及在當地從事獨立經濟活動者和家人,也獲准在島上居住。觀光客則須在抵達時,出示飯店預約證明或當地人的邀請函,日後也將訂定島上最高人數限制。

    去年的人口普查資料顯示,島上居民已增至7750人,比數十年前多出一倍以上。

    此外,人潮除了破壞文化樣貌和生態平衡,也使當地的基礎服務瀕臨極限。當地政府的環境顧問古特雷茲(Ana Maria Gutierrez)舉例說明,復活島居民10年前每年約產生1.5噸廢棄物,但如今一天就高達2.5噸,因為居民的環保意識相當薄弱。

    市長艾德蒙茲一方面認同新法是「好的開始」,但另一方面卻直指新法「規模太小」,不足以保護島上文化、遺產和特色,甚至指稱許多拉帕努伊人企盼的是,全面禁止新住民到來。

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

    【其他文章推薦】

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

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

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

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

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

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