談談我對 Flutter 未來發展 和 “嵌套地獄” 的淺顯看法

Flutter 未來發展

提到 Flutter 就不得不提到 Fuchsia 系統,這是一個尚未正式發布的操作的系統,引用 Android 和 Chrome 的高級副總裁 Hiroshi Lockheimer 在一檔播客節目中對 Fuchsia 的介紹是:

不僅僅是手機和個人電腦,在物聯網的世界里,越來越多的設備需要操作系統、新的軟件運行環境等支持。我認為,在具有不同優勢和專業化的諸多操作系統中還存在很大的發展空間。Fuchsia 就是其中之一,所以,請繼續保持關注。

是的,Fuchsia 系統是為物聯網研發的操作系統,物聯網簡稱 IoT,現在全世界都在押注 IoT,包括華為、小米等國內公司。

那 Flutter 和 Fuchsia 又有什麼關係呢?

Flutter 是 Fuchsia 官方指定的唯一UI開發框架。

現在有很多物聯網操作系統 ,Fuchsia 就一定可以脫穎而出嗎?

不一定,未來的事情誰說的准呢,但在我看來 Fuchsia 是最有可能發展起來的物聯網操作系統,因為一個操作系統的發展除了本身優秀以外,最大的阻礙其實是生態,而 Fuchsia 在生態方面具有天然的優勢, 國外的一篇報道曾說:

Google 希望將 Android App 無縫移植到 Fuchsia 上,而且一直在做相關工作。

試想一下,一旦 Google 將 Android App 無縫移植到 Fuchsia 上,其他物聯網操作系統如何與之抗衡。

這裏引用 Google 公眾號底部的一句話送給大家:

預測未來不如創造未來

在跨平台技術上 Flutter 還有很多競爭對手,比如 HTML5、React Native、Weex、快應用、小程序等,我曾在跨平台技術發展簡介 中詳細說明了各個跨平台技術的發展歷史及優缺點。

Flutter 的出現會終結其他跨平台技術?我想不會的, React Native 發展了這麼多年也沒有完全乾掉 HTML5,應為 HTML5 有其獨特的應用場景,比如 營銷活動場景、新聞或者博客詳情頁面等,這些場景非常適合 HTML5。因此 Flutter 也不可能終結其他跨平台技術,總結一句話就是:

未來很長一段時間,將會是跨平台技術共存的時代,但 Flutter 適用場景更為廣闊。

Flutter 嵌套地獄

現在網絡上對 Flutter 吐槽最多大概就是 Flutter “嵌套地獄”寫法了,為什麼會出現這種現象?個人認為最大的原因就是目前大部分開源的 Flutter 項目都是這種嵌套寫法(包括我自己以前也是如此),導致後來的初學者認為這麼寫沒有問題,當項目越來越複雜時,這種嵌套寫法給項目的維護帶來了巨大的挑戰。下面說說如何避免這種嵌套寫法?

比如實現如下效果:

嵌套地獄 的寫法:

@override
Widget build(BuildContext context) {
  return Column(
    children: <Widget>[
      Container(
        height: 45,
        child: Row(
          children: <Widget>[
            SizedBox(
              width: 30,
            ),
            Icon(
              Icons.notifications,
              color: Colors.blue,
            ),
            SizedBox(
              width: 30,
            ),
            Expanded(
              child: Text('消息中心'),
            ),
            Container(
              padding: EdgeInsets.symmetric(horizontal: 10),
              decoration: BoxDecoration(
                  shape: BoxShape.rectangle,
                  borderRadius: BorderRadius.all(Radius.circular(50)),
                  color: Colors.red),
              child: Text(
                '2',
                style: TextStyle(color: Colors.white),
              ),
            ),
            SizedBox(
              width: 15,
            ),
          ],
        ),
      ),
      Divider(),
      //類似上面的布局寫6個
    ],
  );
}

上面還僅僅是第一項的布局,下面還有7個,一個30多行代碼,7個就是200多行的布局代碼,這還僅僅是布局代碼,如果加上邏輯,都不敢想象啊。

或許有一點封裝思想開發者會將每一個 Item封裝為一個方法,寫法如下:

_buildItem(IconData iconData, Color iconColor, String title, Widget widget) {
  return Container(
    height: 45,
    child: Row(
      children: <Widget>[
        SizedBox(
          width: 30,
        ),
        Icon(
          iconData,
          color: iconColor,
        ),
        SizedBox(
          width: 30,
        ),
        Expanded(
          child: Text('$title'),
        ),
        widget,
        SizedBox(
          width: 15,
        ),
      ],
    ),
  );
}

@override
Widget build(BuildContext context) {
  return Column(
    children: <Widget>[
      _buildItem(...),
      Divider(),
      _buildItem(...),
      Divider(),
      _buildItem(...),
      Divider(),
      _buildItem(...),
      Divider(),
      _buildItem(...),
      Divider(),
      _buildItem(...),
      Divider(),
    ],
  );
}

這樣看起來好多了,基本解決了嵌套地獄問題,但這樣寫還存在一個非常大的問題-性能問題,一旦其中一個数字發生變化,整個頁面都要重建,Flutter 開發中非常重要的一個原則就是 盡可能少的重建組件,因此將上面封裝到方法中組件變為一個 Widget。

class SettingDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        _SettingItem(
          iconData: Icons.notifications,
          iconColor: Colors.blue,
          title: '消息中心',
          suffix: _NotificationsText(
            text: '2',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.thumb_up,
          iconColor: Colors.green,
          title: '我贊過的',
          suffix: _Suffix(
            text: '121篇',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.grade,
          iconColor: Colors.yellow,
          title: '收藏集',
          suffix: _Suffix(
            text: '2個',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.shopping_basket,
          iconColor: Colors.yellow,
          title: '已購小冊',
          suffix: _Suffix(
            text: '100個',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.account_balance_wallet,
          iconColor: Colors.blue,
          title: '我的錢包',
          suffix: _Suffix(
            text: '10萬',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.location_on,
          iconColor: Colors.grey,
          title: '閱讀過的文章',
          suffix: _Suffix(
            text: '1034篇',
          ),
        ),
        Divider(),
        _SettingItem(
          iconData: Icons.local_offer,
          iconColor: Colors.grey,
          title: '標籤管理',
          suffix: _Suffix(
            text: '27個',
          ),
        ),
      ],
    );
  }
}

class _SettingItem extends StatelessWidget {
  const _SettingItem(
      {Key key, this.iconData, this.iconColor, this.title, this.suffix})
      : super(key: key);

  final IconData iconData;
  final Color iconColor;
  final String title;
  final Widget suffix;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 45,
      child: Row(
        children: <Widget>[
          SizedBox(
            width: 30,
          ),
          Icon(iconData,color: iconColor,),
          SizedBox(
            width: 30,
          ),
          Expanded(
            child: Text('$title'),
          ),
          suffix,
          SizedBox(
            width: 15,
          ),
        ],
      ),
    );
  }
}

class _NotificationsText extends StatelessWidget {
  final String text;

  const _NotificationsText({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(horizontal: 10),
      decoration: BoxDecoration(
          shape: BoxShape.rectangle,
          borderRadius: BorderRadius.all(Radius.circular(50)),
          color: Colors.red),
      child: Text(
        '$text',
        style: TextStyle(color: Colors.white),
      ),
    );
  }
}

class _Suffix extends StatelessWidget {
  final String text;

  const _Suffix({Key key, this.text}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      '$text',
      style: TextStyle(color: Colors.grey.withOpacity(.5)),
    );
  }
}

封裝為一個個單獨的小組件,將有變化的組件盡量單獨封裝,這樣就不會重建整個控件樹,增強了可讀性和可維護性,而且對性能有很大的提升。

最後總結一句:

雖然 Flutter 一切皆是組件,但並不代表一切都要寫在組件中。

當然這僅僅是我個人的看法,如果您有更好的方法歡迎一起討論,從我做起,規範寫法,為 Flutter 發展貢獻做出一點微不足道的貢獻。

交流

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

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

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

【其他文章推薦】

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

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

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

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

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

2. 背包,隊列和棧

  許多基礎數據類型都和對象的集合有關。數據類型的值就是一組對象的集合,所有操作都是關於添加,刪除或是訪問集合中的對象。背包(Bag),隊列(Quene)和棧(Stack) 它們的不同之處在於刪除或者訪問對象的順序不同。

  

  1. API

  

  Stack 和 Quene 都含有一個能夠刪除集合中特定元素的方法。

  實現上面API需要高級語言的特性:泛型,裝箱拆箱,可迭代(實現 IEnumerable 接口)。

  

  1. 背包

  背包是一種不支持從中刪除元素的集合類型——它的目的就是幫助用例收集元素并迭代遍歷所有元素。用例也可以使用棧或者隊列,但使用 Bag 可以說明元素的處理順序不重要。

  

  2.先進先出隊列

  隊列是基於先進先出(FIFO)策略的集合類型。

 

  3. 下壓棧

  下壓棧(簡稱棧)是一種基於後進先出(LIFO)策略的集合類型。

  應用例子:計算輸入字符串  (1+((2+3)*(4*5)))表達式的值。

  使用雙棧解決:

    1. 將操作數壓入操作數棧;

    2. 將運算符壓入運算符棧;

    3. 忽略做括號;

    4. 在遇到右括號時,彈出一個運算符,彈出所需數量的操作數,並將運算符和操作數的運算結果壓入操作數棧。

 

  2.用數組實現

  實現下壓棧:

    //想要數據類型可迭代,需要實現IEnumerable
    public class ResizingStack<Item> : IEnumerable<Item>
    {
        private Item[] a = new Item[1];
        private int N = 0;
        public bool IsEmpty{ get {
                return N == 0;
            } }
        public int Size { get {
                return N;
            } }
        public int Count { get; set; }

        /// <summary>
        /// 使數組處於半滿
        /// </summary>
        /// <param name="max"></param>
        private void Resize(int max)
        {
            Count = 0;
            Item[] temp = new Item[max];
            for(var i = 0;i<N;i++)
            {
                temp[i] = a[i];
                Count++;
            }
            a = temp;
        }

        public void push(Item item)
        {
            if (N == a.Length)
                Resize(a.Length * 2);
            a[N++] = item;
        }

        public Item Pop()
        {
            Item item = a[--N];
            a[N] = default(Item); //避免對象遊離
            if (N > 0 && N == a.Length / 4)
                Resize(a.Length/2);
            return item;
        }

        IEnumerator<Item> IEnumerable<Item>.GetEnumerator()
        {
            return new ResizingStackEnumerator<Item>(a);
        }

        public IEnumerator GetEnumerator()
        {
            return new ResizingStackEnumerator<Item>(a);
        }

    }
    class ResizingStackEnumerator<Item> : IEnumerator<Item>
    {
        private Item[] a;
        private int N = 0;
        public ResizingStackEnumerator(Item[] _a)
        {
            a = _a;
            N = a.Length-1;
        }

        public object Current => a[N--];

        Item IEnumerator<Item>.Current => a[N--];

        public void Dispose()
        {
            throw new NotImplementedException();
        }

        public bool MoveNext()
        {
            return N > 0;
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }

  

  3.鏈表

  鏈表是在集合類的抽象數據類型實現中表示數據的另一種基礎數據結構。

  定義:鏈表是一種遞歸的數據結構,它或者指向空,或者指向另一個節點的引用,該節點含有一個泛型元素和一個指向另一個鏈表的引用。

    class Node<Item>
    {
        public Item item { get; set; }
        public Node<Item> Next { get; set; }
    }

  1.構造鏈表

  鏈表表示的是一列元素。

  根據遞歸的定義,只需要一個 Node 類型的變量就能表示一條鏈表,只要保證它的 Next 值是 null 或指向另一個 Node 對象,該對象的 Next 指向另一條鏈表。

  

 

  2.在表頭插入結點

  在鏈表列表中插入新節點的最簡單位置是開始。要在首結點為 first 的給定鏈表開頭插入字符串 not ,先將 first 保存在 oldfirst 中,然後將一個新結點賦予 first ,並將 first 的 item 設為 not, Next  設置為 oldfirst 。

  

  在鏈表開頭插入一個結點所需的時間和鏈表長度無關。

 

  3.從表頭刪除結點

  只需將 first 指向 first.next 即可。first 原來指向的對象變成了一個孤兒,垃圾回收機制會將其回收。

 

  同樣,該操作所需的時間和鏈表長度無關。

 

  4.在表尾插入結點

  當鏈表不止有一個結點時,需要一個指向鏈表最後結點的鏈接 oldlast,創建新的結點,last 指向新的最後結點。然後 oldlast.next  指向 last。

  當鏈表只有一個結點時,首結點又是尾結點。只需將 last 指向新的結點,然後 first.next 指向 last。

 

  5.其他位置的插入和刪除操作

  上述操作可以很容易的實現,但是下面的操作比較複雜:

    1. 刪除指定的結點

    2. 在指定結點前插入一個新結點

  這些操作需要我們遍歷鏈表,它所需的時間和鏈表的長度成正比。想要實現任意插入和刪除結點需要使用雙向鏈表,其中每個結點都含有兩個鏈接,分別指向上一個和下一個結點。

 

  6. 遍歷

  簡單實現:

    public class Bag<Item>
    {
        private Node<Item> first;
        public void Add(Item item)
        {
            Node<Item> oldFirst = first;
            first = new Node<Item>() { 
                item = item,
                Next = oldFirst
            };

        }
    }
            Bag<int> bags = new Bag<int>();
            for (var i = 0; i < 10; i++)
            {
                bags.Add(i);
            }

            for (var x = bags.first; x != null; x = x.Next)
            {
                Console.WriteLine(x.item);
            }

  

  實現 IEnumerable 接口 實現遍歷:

    public class Bag<Item>: IEnumerable<Item>
    {
        public Node<Item> first;
        public void Add(Item item)
        {
            Node<Item> oldFirst = first;
            first = new Node<Item>() { 
                item = item,
                Next = oldFirst
            };

        }

        public IEnumerator<Item> GetEnumerator()
        {
            return new LineEnumerator<Item>(first);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new LineEnumerator<Item>(first);
        }
    }

    public class LineEnumerator<Item> : IEnumerator<Item>
    {
        public Node<Item> first;
        public LineEnumerator(Node<Item> _first)
        {
            first = _first;
        }
        public Item Current { get {
                var oldfirst = first;
                first = first.Next;
                return oldfirst.item;
            } }

        object IEnumerator.Current => first;

        public void Dispose()
        {
            return;
        }

        public bool MoveNext()
        {
            if (first != null)
                return true;
            return false;
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }
        public static void LineTest()
        {
            Bag<int> bags = new Bag<int>();
            for (var i = 0; i < 10; i++)
            {
                bags.Add(i);
            }

            foreach(var bag in bags)
            {
                Console.WriteLine(bag);
            }
        }

 

  4. 用鏈表實現背包

  見上述代碼。

 

  5. 用鏈表實現棧

  Stack API 中 Pop() 刪除一個元素,按照前面的從表頭刪除結點實現,Push() 添加一個元素,按照前面在表頭插入結點。 

    public class Stack<Item> : IEnumerable<Item>
    {
        public Node<Item> first;
        private int N;


        public bool IsEmpty()
        {
            return first == null; //或 N == 0
        }

        public int Size()
        {
            return N;
        }

        public void Push(Item item)
        {
            Node<Item> oldfirst = first;
            first = new Node<Item>() { 
                item = item,
                Next = oldfirst
            };
            N++;
        }

        public Item Pop()
        {
            Item item = first.item;
            first = first.Next;
            N--;
            return item;
        }

        public IEnumerator<Item> GetEnumerator()
        {
            return new StackLineIEnumerator<Item>(first);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new StackLineIEnumerator<Item>(first);
        }
    }

    public class StackLineIEnumerator<Item> : IEnumerator<Item>
    {
        private Node<Item> first;
        public StackLineIEnumerator(Node<Item> _first)
        {
            first = _first;
        }
        public Item Current { get {
                var oldfirst = first;
                first = first.Next;
                return oldfirst.item;
            } }

        object IEnumerator.Current => throw new NotImplementedException();

        public void Dispose()
        {
            return;
        }

        public bool MoveNext()
        {
            return first != null;
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }

  鏈表的使用達到了最優設計目標:

    1. 可以處理任意類型的數據;

    2. 所需的空間總是和集合的大小成正比;

    3. 操作所需的時間總是和集合的大小無關;

  

   6. 用鏈表實現隊列

  需要兩個實例變量,first 指向隊列的開頭,last 指向隊列的表尾。添加一個元素 Enquene() ,將結點添加到表尾(鏈表為空時,first 和 last 都指向新結點)。刪除一個元素 Dequene() ,刪除表頭的結點(刪除后,當隊列為空時,將 last 更新為 null)。

    public class Quene<Item> : IEnumerable<Item>
    {
        public Node<Item> first;
        public Node<Item> last;
        private int N;

        public bool IsEmpty()
        {
            return first == null;
        }

        public int Size()
        {
            return N;
        }

        public void Enquene(Item item)
        {
            var oldlast = last;
            last = new Node<Item>() { 
                item = item,
                Next = null
            };

            if (IsEmpty())
                first = last;
            else
                oldlast.Next = last;
            N++;
        }

        public Item Dequene()
        {
            if (IsEmpty())
                throw new Exception();
            Item item = first.item;
            first = first.Next;
            if (IsEmpty())
                last = null;
            N--;
            return item;
        }

        public IEnumerator<Item> GetEnumerator()
        {
            return new QueneLineEnumerator<Item>(first);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new QueneLineEnumerator<Item>(first);
        }
    }
    public class QueneLineEnumerator<Item> : IEnumerator<Item>
    {
        private Node<Item> first;
        public QueneLineEnumerator(Node<Item> _first)
        {
            first = _first;
        }
        public Item Current { get {
                var oldfirst = first;
                first = first.Next;
                return oldfirst.item;
            } }

        object IEnumerator.Current => throw new NotImplementedException();

        public void Dispose()
        {
            return;
        }

        public bool MoveNext()
        {
            return first != null ;
        }

        public void Reset()
        {
            throw new NotImplementedException();
        }
    }

   

  7. 總結

  在結構化存儲數據集時,鏈表是數組的一種重要的替代方式。

  數組和鏈表這兩種數據類型為研究算法和更高級的數據結構打下了基礎。

  基礎數據結構:

數據結構 優點 缺點
數組 通過索引可以直接訪問任意元素 在初始化時就需要知道元素的數量
鏈表 使用的空間大小和元素數量成正比 需要同引用訪問任意元素

  

  在研究一個新的應用領域時,可以按照以下步驟識別目標,定義問題和使用數據抽象解決問題:

  1. 定義 API

  2. 根據特定的應用場景開發用例代碼

  3. 描述一種數據結構(即一組值的表示),並在 API 的實現中根據它定義類的實例變量。

  4. 描述算法,即實現 API,並根據它應用於用例

  5. 分析算法的性能

 

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

MongoDB副本集replica set (二)–副本集環境搭建

(一)主機信息

操作系統版本:centos7 64-bit

數據庫版本   :MongoDB 4.2 社區版

ip hostname
192.168.10.41 mongoserver1
192.168.10.42 mongoserver2
192.168.10.43 mongoserver3

(二)副本集搭建過程

首先需要在3台服務器上安裝MongoDB軟件,安裝過程見:https://www.cnblogs.com/lijiaman/p/12983589.html。安裝完成之後,即可進行後續的配置,具體操作如下:

(1)在一台機器上創建keyfile

openssl rand -base64 756 > /mongo/mongo-keyfile
chmod 400 /mongo/mongo-keyfile

(2)拷貝feyfile到所有節點

scp /mongo/mongo-keyfile root@192.168.10.42:/mongo/
scp /mongo/mongo-keyfile root@192.168.10.43:/mongo/

(3)以啟用身份驗證的方式開啟所有節點
這裏將所有參數設置到配置文件裏面,方便管理,配置文件如下:

[root@mongodbserver1 mongo]# cat /etc/mongod.conf
# mongod.conf

# for documentation of all options, see:
# http://docs.mongodb.org/manual/reference/configuration-options/

# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /mongo/mongod.log

# Where and how to store data.
storage:
dbPath: /mongo/data
journal:
enabled: true
# engine:
# mmapv1:
# wiredTiger:

# how the process runs
processManagement:
fork: true # fork and run in background
pidFilePath: /mongo/mongod.pid # location of pidfile

# network interfaces
net:
port: 27017
bindIp: 0.0.0.0 # Listen to local interface only, comment to listen on all interfaces.

security:
authorization: enabled                 # 啟用身份驗證
keyFile: /mongo/mongo-keyfile         # 配置keyfile文件
 
replication:
replSetName: rstest                    # 設置副本集名稱

然後啟動所有節點,以節點1為例:

[root@mongodbserver1 mongo]# mongod -f /etc/mongod.conf

(4)初始化副本集
在其中一個節點執行以下腳本初始化副本集,只需在一個節點上執行即可。

rs.initiate(
{
_id : "rstest",
members: [
{ _id : 0, host : "192.168.10.41:27017" },
{ _id : 1, host : "192.168.10.42:27017" },
{ _id : 2, host : "192.168.10.43:27017" }
]
}
)

參數含義:
_id          :副本集的名稱
members :副本集的成員信息

在初始化時,會觸發投票選舉一個主節點,可以使用rs.status()來確定主節點成員

rstest:SECONDARY> rs.status()
...
"members" : [
{
"_id" : 0,
"name" : "192.168.10.41:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 280,
"optime" : {
"ts" : Timestamp(1592897767, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2020-06-23T07:36:07Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1592897607, 1),
"electionDate" : ISODate("2020-06-23T07:33:27Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
...

(5)創建管理員用戶
第一個用戶必須要有創建其它用戶的權限,例如需要有userAdminAnyDatabase權限,並且需要創建在admin數據庫中。
因為是在副本集上創建用戶,故要在主節點上執行。如創建root用戶

use admin;

db.createUser(
{
user:"root",
pwd:"123456",
roles:[{role:"userAdminAnyDatabase",db:"admin"}]
}
)

(6)以管理員身份登錄數據庫
通過以下方式以管理員身份登錄到數據庫

mongo -u root -p 123456 --authenticationDatabase admin

(7)創建一個集群管理員賬戶
clusterAdmin角色被授予副本集操作的權限,如配置副本集。在admin數據庫中創建一個集群管理員並授予clusterAdmin角色。

use admin

db.createUser(
{
"user" : "replica",
"pwd" : "replica",
roles: [ { "role" : "clusterAdmin", "db" : "admin" } ]
}
)

(8)要啟用身份驗證,需要重啟數據庫
重啟完成后,就需要以用戶密碼方式登錄數據庫了,假如不使用用戶名密碼,可以登錄數據庫,但是無法訪問數據

[root@mongodbserver2 mongo]# mongo
MongoDB shell version v4.2.7
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("d49b410b-a7af-4550-a455-faa82885517b") }
MongoDB server version: 4.2.7
rstest:PRIMARY> show dbs
rstest:PRIMARY> 
rstest:PRIMARY> db
test

只有使用了用戶名密碼,才能查到數據:

[root@mongodbserver2 mongo]# mongo -u root -p 123456 --authenticationDatabase admin 
MongoDB shell version v4.2.7
connecting to: mongodb://127.0.0.1:27017/?authSource=admin&compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("a1f0da48-1266-4766-a9e4-32b97a46c3ec") }
MongoDB server version: 4.2.7
rstest:PRIMARY> 
rstest:PRIMARY> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB

【完】

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

OpenCV開發筆記(六十五):紅胖子8分鐘帶你深入了解ORB特徵點(圖文並茂+淺顯易懂+程序源碼)

若該文為原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/106926496
各位讀者,知識無窮而人力有窮,要麼改需求,要麼找專業人士,要麼自己研究
紅胖子(紅模仿)的博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中…(點擊傳送門)

OpenCV開發專欄(點擊傳送門)

上一篇:《OpenCV開發筆記(六十四):紅胖子8分鐘帶你深入了解SURF特徵點(圖文並茂+淺顯易懂+程序源碼)》
下一篇:持續補充中…

 

前言

  紅胖子,來也!
  識別除了傳統的模板匹配之外就是體征點了,前面介紹了Suft特徵點,還有一個傳統的就會ORB特徵點了。
  其實識別的特徵點多種多樣,既可以自己寫也可以使用opencv為我們提供的,一般來說根據特徵點的特性和效率,選擇適合我們場景的特徵就可以了。
  本篇,介紹ORB特徵提取。

 

Demo

  
  
  
  

 

ORB特徵點

概述

  ORB是ORiented Brief的簡稱,是briedf算法的改進版,於2011年在《ORB:an fficient alternative to SIFT or SURF》中提出。
ORB算法分為兩部分,分別是特徵點提取和特徵點描述:

  • 特徵提取:由FAST(Features from Accelerated Segment Test)算法發展來的;
  • 特徵點描述:根據BRIEF(Binary Robust IndependentElementary Features)特徵描述算法改進的。

  ORB特徵是將FAST特徵點的檢測方法與BRIEF特徵描述子結合起來,並在它們原來的基礎上做了改進與優化。據說,ORB算法的速度是sift的100倍,是surf的10倍。

Brief描述子

  該特徵描述子是在特徵點附近隨機選取若干點對,將這些點對的灰度值的大小,組合成一個二進制串,組合成一個二進制傳,並將這個二進制串作為該特徵點的特徵描述子。
  Brief的速度快,但是使用灰度值作為描述字計算的源頭,毫無疑問會有一些顯而易見的問題:

  • 旋轉后灰度變了導致無法識別,因其不具備旋轉不變形;
  • 由於是計算灰度,噪聲灰度化則無法去噪,所以對噪聲敏感;
  • 尺度不同影響灰度計算,所以也不具備尺度不變形;
    ORB是試圖使其具備旋轉不變性和降低噪聲敏感度而提出的。

特徵檢測步驟

步驟一:使用brief算子的方式初步提取。

  該步能夠提取大量的特徵點,但是有很大一部分的特徵點的質量不高。從圖像中選取一點P,以P為圓心畫一個半徑為N像素半徑的圓。圓周上如果有連續n個像素點的灰度值比P點的灰度值大或者小,則認為P為特徵點。
  

步驟二:機器學習的方法篩選最優特徵點。

  通俗來說就是使用ID3算法訓練一個決策樹,將特徵點圓周上的16個像素輸入決策樹中,以此來篩選出最優的FAST特徵點。

步驟三:非極大值抑制去除局部較密集特徵點。

  使用非極大值抑制算法去除臨近位置多個特徵點的問題。為每一個特徵點計算出其響應大小。計算方式是特徵點P和其周圍16個特徵點偏差的絕對值和。在比較臨近的特徵點中,保留響應值較大的特徵點,刪除其餘的特徵點。

步驟四:使用金字塔來實現多尺度不變形。

步驟五:使用圖像的矩判斷特徵點的旋轉不變性

  ORB算法提出使用矩(moment)法來確定FAST特徵點的方向。也就是說通過矩來計算特徵點以r為半徑範圍內的質心,特徵點坐標到質心形成一個向量作為該特徵點的方向。

ORB類的使用

cv::Ptr<cv::ORB> _pOrb = cv::ORB::create();
std::vector<cv::KeyPoint> keyPoints1;
//特徵點檢測
_pOrb->detect(srcMat, keyPoints1);

ORB相關函數原型

static Ptr<ORB> create(int nfeatures=500,
                       float scaleFactor=1.2f,
                       int nlevels=8,
                       int edgeThreshold=31,
                       int firstLevel=0,
                       int WTA_K=2,
                       int scoreType=ORB::HARRIS_SCORE,
                       int patchSize=31,
                       int fastThreshold=20);
  • 參數一:int類型的nfeatures,用於ORB的,保留最大的關鍵點數,默認值500;
  • 參數二:float類型的scaleFactor,比例因子,大於1時為金字塔抽取比。的等於2表示經典的金字塔,每一個下一層的像素比上一層少4倍,但是比例係數太大了將顯著降低特徵匹配分數。另一方面,太接近1個比例因子這意味着要覆蓋一定的範圍,你需要更多的金字塔級別,所以速度會受影響的,默認值1.2f;
  • 參數三:int類型的nlevels,nlevels金字塔級別的數目。最小級別的線性大小等於輸入圖像線性大小/功率(縮放因子,nlevels-第一級),默認值為8;
  • 參數四:int類型的edgeThreshold,edgeThreshold這是未檢測到功能的邊框大小。它應該大致匹配patchSize參數。;
  • 參數五:int類型的firstLevel,要將源圖像放置到的金字塔級別。以前的圖層已填充使用放大的源圖像;
  • 參數六:int類型的WTA_K,生成定向簡短描述符的每個元素的點數。這個默認值2是指取一個隨機點對並比較它們的亮度,所以我們得到0/1的響應。其他可能的值是3和4。例如,3表示我們取3隨機點(當然,這些點坐標是隨機的,但是它們是由預定義的種子,因此簡短描述符的每個元素都是從像素確定地計算出來的矩形),找到最大亮度點和獲勝者的輸出索引(0、1或2)。如此輸出將佔用2位,因此需要一個特殊的漢明距離變量,表示為NORM_HAMMING2(每箱2位)。當WTA_K=4時,我們取4個隨機點計算每個點bin(也將佔用可能值為0、1、2或3的2位)。;
  • 參數七:int類型的scoreType,HARRIS_SCORES表示使用HARRIS算法對特徵進行排序(分數寫入KeyPoint::score,用於保留最佳nfeatures功能);FAST_SCORE是產生稍微不穩定關鍵點的參數的替代值,但計算起來要快一點;
  • 參數八:int類型的patchSize,定向簡短描述符使用的修補程序的大小。當然,在較小的金字塔層特徵覆蓋的感知圖像區域將更大;
  • 參數九:int類型的fastThreshold,快速閾值;
void xfeatures2d::SURT::detect( InputArray image,
                                std::vector<KeyPoint>& keypoints,
                                InputArray mask=noArray() );
  • 參數一:InputArray類型的image,輸入cv::Mat;
  • 參數二:std::Vector類型的keypoints,檢測到的關鍵點;
  • 參數三:InputArray類型的mask,默認為空,指定在何處查找關鍵點的掩碼(可選)。它必須是8位整數感興趣區域中具有非零值的矩陣。;
void xfeatures2d::SURT::compute( InputArray image,
                                 std::vector<KeyPoint>& keypoints,
                                 OutputArray descriptors );
  • 參數一:InputArray類型的image,輸入cv::Mat;
  • 參數二:std::Vector類型的keypoints,描述符不能為其已刪除計算的。有時可以添加新的關鍵點,例如:SIFT duplicates keypoint有幾個主要的方向(每個方向);
  • 參數三:OutputArray類型的descriptors,計算描述符;
// 該函數結合了detect和compute,參照detect和compute函數參數
void xfeatures2d::SURT::detectAndCompute( InputArray image,
                                          InputArray mask,
                                          std::vector<KeyPoint>& keypoints,
                                          OutputArray descriptors,
                                          bool useProvidedKeypoints=false );

繪製關鍵點函數原型

void drawKeypoints( InputArray image,
                    const std::vector<KeyPoint>& keypoints,
                    InputOutputArray outImage,
                    const Scalar& color=Scalar::all(-1),
                    int flags=DrawMatchesFlags::DEFAULT );
  • 參數一:InputArray類型的image,;
  • 參數二:std::Vector類型的keypoints,原圖的關鍵點;
  • 參數三:InputOutputArray類型的outImage,其內容取決於定義在輸出圖像。請參閱參數五的標誌flag);
  • 參數四:cv::Scalar類型的color,繪製關鍵點的顏色,默認為Scalar::all(-1)隨機顏色,每個點都是這個顏色,那麼隨機時,每個點都是隨機的;
  • 參數五:int類型的flags,默認為DEFAULT,具體參照DrawMatchesFlags枚舉如下:

 

相關博客

  本源碼中包含了“透視變換”,請參照博文《OpenCV開發筆記(五十一):紅胖子8分鐘帶你深入了解透視變換(圖文並茂+淺顯易懂+程序源碼)》

 

特徵點總結

  根據前面連續三篇的特徵點,我們其實可以猜到了所有的匹配都是這樣提取特徵點,然後使用一些算法來匹配,至於使用什麼特徵點提取就是需要開發者根據實際的經驗去選取,單一的特徵點/多種特徵點提取混合/自己寫特徵點等等多種方式去提取特徵點,為後一步的特徵點匹配做準備,特徵點通用的就到此篇,後續會根據實際開發項目中使用的到隨時以新的篇章博文去補充。
  《OpenCV開發筆記(六十三):紅胖子8分鐘帶你深入了解SIFT特徵點(圖文並茂+淺顯易懂+程序源碼)》
  《OpenCV開發筆記(六十四):紅胖子8分鐘帶你深入了解SURF特徵點(圖文並茂+淺顯易懂+程序源碼》
  《OpenCV開發筆記(六十五):紅胖子8分鐘帶你深入了解ORB特徵點(圖文並茂+淺顯易懂+程序源碼)》

 

Demo源碼

void OpenCVManager::testOrbFeatureDetector()
{
    QString fileName1 = "13.jpg";
    int width = 400;
    int height = 300;

    cv::Mat srcMat = cv::imread(fileName1.toStdString());
    cv::resize(srcMat, srcMat, cv::Size(width, height));

    cv::String windowName = _windowTitle.toStdString();
    cvui::init(windowName);

    cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3),
                                srcMat.type());
    cv::Ptr<cv::ORB> _pObr = cv::ORB::create();

    int k1x = 0;
    int k1y = 0;
    int k2x = 100;
    int k2y = 0;
    int k3x = 100;
    int k3y = 100;
    int k4x = 0;
    int k4y = 100;
    while(true)
    {
        windowMat = cv::Scalar(0, 0, 0);

        cv::Mat mat;

        // 原圖先copy到左邊
        mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                        cv::Range(srcMat.cols * 0, srcMat.cols * 1));
        cv::addWeighted(mat, 0.0f, srcMat, 1.0f, 0.0f, mat);

        {
            std::vector<cv::KeyPoint> keyPoints1;
            std::vector<cv::KeyPoint> keyPoints2;

            cvui::printf(windowMat, 0 + width * 1, 10 + height * 0, "k1x");
            cvui::trackbar(windowMat, 0 + width * 1, 20 + height * 0, 165, &k1x, 0, 100);
            cvui::printf(windowMat, 0 + width * 1, 70 + height * 0, "k1y");
            cvui::trackbar(windowMat, 0 + width * 1, 80 + height * 0, 165, &k1y, 0, 100);

            cvui::printf(windowMat, width / 2 + width * 1, 10 + height * 0, "k2x");
            cvui::trackbar(windowMat, width / 2 + width * 1, 20 + height * 0, 165, &k2x, 0, 100);
            cvui::printf(windowMat, width / 2 + width * 1, 70 + height * 0, "k2y");
            cvui::trackbar(windowMat, width / 2 + width * 1, 80 + height * 0, 165, &k2y, 0, 100);

            cvui::printf(windowMat, 0 + width * 1, 10 + height * 0 + height / 2, "k3x");
            cvui::trackbar(windowMat, 0 + width * 1, 20 + height * 0 + height / 2, 165, &k3x, 0, 100);
            cvui::printf(windowMat, 0 + width * 1, 70 + height * 0 + height / 2, "k3y");
            cvui::trackbar(windowMat, 0 + width * 1, 80 + height * 0 + height / 2, 165, &k3y, 0, 100);

            cvui::printf(windowMat, width / 2 + width * 1, 10 + height * 0 + height / 2, "k4x");
            cvui::trackbar(windowMat, width / 2 + width * 1, 20 + height * 0 + height / 2, 165, &k4x, 0, 100);
            cvui::printf(windowMat, width / 2 + width * 1, 70 + height * 0 + height / 2, "k4y");
            cvui::trackbar(windowMat, width / 2 + width * 1, 80 + height * 0 + height / 2, 165, &k4y, 0, 100);

            std::vector<cv::Point2f> srcPoints;
            std::vector<cv::Point2f> dstPoints;

            srcPoints.push_back(cv::Point2f(0.0f, 0.0f));
            srcPoints.push_back(cv::Point2f(srcMat.cols - 1, 0.0f));
            srcPoints.push_back(cv::Point2f(srcMat.cols - 1, srcMat.rows - 1));
            srcPoints.push_back(cv::Point2f(0.0f, srcMat.rows - 1));

            dstPoints.push_back(cv::Point2f(srcMat.cols * k1x / 100.0f, srcMat.rows * k1y / 100.0f));
            dstPoints.push_back(cv::Point2f(srcMat.cols * k2x / 100.0f, srcMat.rows * k2y / 100.0f));
            dstPoints.push_back(cv::Point2f(srcMat.cols * k3x / 100.0f, srcMat.rows * k3y / 100.0f));
            dstPoints.push_back(cv::Point2f(srcMat.cols * k4x / 100.0f, srcMat.rows * k4y / 100.0f));

            cv::Mat M = cv::getPerspectiveTransform(srcPoints, dstPoints);
            cv::Mat srcMat2;
            cv::warpPerspective(srcMat,
                                srcMat2,
                                M,
                                cv::Size(srcMat.cols, srcMat.rows),
                                cv::INTER_LINEAR,
                                cv::BORDER_CONSTANT,
                                cv::Scalar::all(0));

            mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
                            cv::Range(srcMat.cols * 1, srcMat.cols * 2));
            cv::addWeighted(mat, 0.0f, srcMat2, 1.0f, 0.0f, mat);

            //特徵點檢測
            _pObr->detect(srcMat, keyPoints1);
            //繪製特徵點(關鍵點)
            cv::Mat resultShowMat;
            cv::drawKeypoints(srcMat,
                             keyPoints1,
                             resultShowMat,
                             cv::Scalar(0, 0, 255),
                             cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
            mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                            cv::Range(srcMat.cols * 0, srcMat.cols * 1));
            cv::addWeighted(mat, 0.0f, resultShowMat, 1.0f, 0.0f, mat);

            //特徵點檢測
            _pObr->detect(srcMat2, keyPoints2);
            //繪製特徵點(關鍵點)
            cv::Mat resultShowMat2;
            cv::drawKeypoints(srcMat2,
                             keyPoints2,
                             resultShowMat2,
                             cv::Scalar(0, 0, 255),
                             cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
            mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
                           cv::Range(srcMat.cols * 1, srcMat.cols * 2));
            cv::addWeighted(mat, 0.0f, resultShowMat2, 1.0f, 0.0f, mat);

            cv::imshow(windowName, windowMat);
        }
        // 更新
        cvui::update();
        // 显示
        // esc鍵退出
        if(cv::waitKey(25) == 27)
        {
            break;
        }
    }
}

 

工程模板:對應版本號v1.59.0

  對應版本號v1.59.0

 

上一篇:《OpenCV開發筆記(六十四):紅胖子8分鐘帶你深入了解SURF特徵點(圖文並茂+淺顯易懂+程序源碼)》
下一篇:持續補充中…

 

原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/106926496

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

FreeSql.Generator命令行代碼生成器是如何實現的

目錄

  • FreeSql介紹
  • FreeSql.Generator
  • RazorEngine.NetCore
  • 源碼解析
  • FreeSql.Tools

FreeSql

FreeSql 是功能強大的對象關係映射技術(O/RM),支持 .NETCore 2.1+ 或 .NETFramework 4.0+ 或 Xamarin。

有一個強大的ORM,也方便我們開發一個代碼生成器。

一般情況下,我們開發數據庫相關的應用,主要分為三種code first、db first、model first

我只用過前二種,

  • code first,代碼優先,數據庫都是根據實體類生成,所有的關係,可以是邏輯關聯,也可以是物理關聯。
  • DB First: 數據庫優先,直接設計表結構,用設計工具生成表,設計主鍵,外鍵、索引,關聯關係等。

當我們使用DB First時,設計好的數據庫,我們怎麼生成一些實體類、通用的代碼、控制器、服務層、Dto呢。今天我來給大家介紹一下FreeSql項目中的一些工具。當然,不使用此ORM的小夥伴也能使用此工具,因為他是通用。

FreeSql.Generator 命令行方式

通過幾行命令,就可實現生成項目中通用的代碼結構,不需要複製一段代碼后修改,加快開發速度,減少重複勞動,少用一根頭髮。

由於每個人的項目結構,代碼位置各不相同,對於ORM來說,不同的業務邏輯各不相同,所以該項目沒有相應的模板,相信使用過Razor的同學一定能實現自己的模板。

1-2年前,我和一個學長也寫過代碼生成器,這裏分享一下當時做項目時的一些模板,https://github.com/i542873057/SJNScaffolding/tree/master/SJNScaffolding.RazorPage/Templates,該項目是基於 . NET Core+Razor Page,由於已離職,所以沒有繼續維護,這些模板都和ABP相關,當時提取了一些通用的功能,單表操作,可以直接生成前後端功能,只需要在word中按統一的格式寫好數據字典的文檔,直接複製到系統,即可根據空格,定義類型等方式解析字段。

回到FreeSql.Generator 命令行

  • 對於此工具的使用可參考 https://github.com/dotnetcore/FreeSql/wiki/DbFirst
  • 源碼位置 https://github.com/dotnetcore/FreeSql/tree/master/Extensions/FreeSql.Generator
  • 前提是本地安裝了.net core 3.1 的sdk.

怎麼使用呢。

  1. 安裝 dotnet-tool 生成實體類
dotnet tool install -g FreeSql.Generator
  1. 新建目錄,在地址欄輸入 cmd 快速打開命令窗口,輸入命令:
FreeSql.Generator --help

我們可以看到

C:\Users\igeekfan\Desktop\code>FreeSql.Generator --help
        ____                   ____         __
       / __/  ____ ___  ___   / __/ ___ _  / /
      / _/   / __// -_)/ -_) _\ \  / _ `/ / /
     /_/    /_/   \__/ \__/ /___/  \_, / /_/
                                    /_/


  # Github # https://github.com/2881099/FreeSql v1.5.0

    使用 FreeSql 快速生成數據庫的實體類

    更新工具:dotnet tool update -g FreeSql.Generator


  # 快速開始 #

  > FreeSql.Generator -Razor 1 -NameOptions 0,0,0,0 -NameSpace MyProject -DB "MySql,Data Source=127.0.0.1;..."

     -Razor 1                  * 選擇模板:實體類+特性

     -Razor 2                  * 選擇模板:實體類+特性+導航屬性

     -Razor "d:\diy.cshtml"    * 自定義模板文件

     -NameOptions              * 總共4個布爾值,分別對應:
                               # 首字母大寫
                               # 首字母大寫,其他小寫
                               # 全部小寫
                               # 下劃線轉駝峰

     -NameSpace                * 命名空間

     -DB "MySql,Data Source=127.0.0.1;Port=3306;User ID=root;Password=root;Initial Catalog=數據庫;Charset=utf8;SslMode=none;Max pool size=2"

     -DB "SqlServer,Data Source=.;Integrated Security=True;Initial Catalog=數據庫;Pooling=true;Max Pool Size=2"

     -DB "PostgreSQL,Host=192.168.164.10;Port=5432;Username=postgres;Password=123456;Database=數據庫;Pooling=true;Maximum Pool Size=2"

     -DB "Oracle,user id=user1;password=123456;data source=//127.0.0.1:1521/XE;Pooling=true;Max Pool Size=2"

     -DB "Sqlite,Data Source=document.db"

     -DB "Dameng,server=127.0.0.1;port=5236;user id=2user;password=123456789;database=2user;poolsize=2"
                               Dameng 是國產達夢數據庫

     -Filter                   Table+View+StoreProcedure
                               默認生成:表+視圖+存儲過程
                               如果不想生成視圖和存儲過程 -Filter View+StoreProcedure

     -Match                    正則表達式,只生成匹配的表,如:dbo\.TB_.+

     -FileName                 文件名,默認:{name}.cs

     -Output                   保存路徑,默認為當前 shell 所在目錄
                               推薦在實體類目錄創建 gen.bat,雙擊它重新所有實體類
  • 更新命令行
dotnet tool update -g FreeSql.Generator
  1. 這裏lin-cms-dotnetcore這個項目來測試。

  • 數據庫表名是下劃線,字段也是下劃線方式。
  • -Razor 指定 第一個模板
  • -NameOptions 0,0,0,1 最後一個1,代表 下劃線轉駝峰,滿足C#命名規則
  • -NameSpace 指定了命名空間 LinCms.Core.Entities
  • -DB 就是數據庫的相關配置
  • mysql 本地地址 127.0.0.1 3306端口 用戶名 root 密碼123456 數據庫 lin-cms
  • -Match book 這樣就能只生成book,支持正則表達式,如 -Math lin_user 就會生成以lin_user開頭的表。如dbo.TB_.+,會生成以TB開頭的表。即只生成匹配的表
  1. 執行此命令。
FreeSql.Generator -Razor 1  -NameOptions 0,0,0,1 -NameSpace LinCms.Core.Entities -DB "MySql,Data Source=127.0.0.1;Port=3306;User ID=root;Password=123456;Initial Catalog=lincms;Charset=utf8;SslMode=none;Max pool size=2"

這時候代碼已經生成了

其中一個代碼 生成如下。這些類是partial ,熟悉C#的同學,應該知道,類的定義使用此關鍵字,我們能在不同的地方為該類擴展。以防止重新同步數據庫的結構時,丟失改動的字段。

namespace LinCms.Core.Entities {

	[JsonObject(MemberSerialization.OptIn), Table(Name = "book")]
	public partial class Book {

		/// <summary>
		/// 主鍵Id
		/// </summary>
		[JsonProperty, Column(Name = "id", IsPrimary = true, IsIdentity = true)]
		public long Id { get; set; }

		[JsonProperty, Column(Name = "author", DbType = "varchar(20)")]
		public string Author { get; set; } = string.Empty;

		[JsonProperty, Column(Name = "image", DbType = "varchar(50)")]
		public string Image { get; set; } = string.Empty;

        //更多xxx
	}

}

  • 最終效果圖如下

此時會生成二個文件
__重新生成.bat,下次重新點擊他就能重新生成實體類了。

FreeSql.Generator -Razor "__razor.cshtml.txt" -NameOptions 1,1,0,1 -NameSpace MyProject -DB "MySql,Data Source=127.0.0.1;Port=3306;User ID=root;Password=123456;Initial Catalog=lincms;Charset=utf8;SslMode=none;Max pool size=2" -FileName "{name}.cs"

上面的命令-Razor 指定了這個txt文件 __razor.cshtml.txt

我們可以定義自己的模板,以生成符合自已業務的的代碼,從而實現快速開發。

我們可以看下模板中的文件內容,他就是asp.net下的mvc 結構下的razor後端模板渲染,把這個.txt後綴去掉,就很明了了。對於asp.net mvc的razor,我們可以將控制器下方法的值替換掉cshtml中的值。這個過程是有一個類庫在幫我們實現的,叫RazorEngine,不過那個是.net framework下的實踐。.NET Framework 下的RazorEngine代碼生成介紹

@using FreeSql.DatabaseModel;@{
var gen = Model as RazorModel;

Func<string, string> GetAttributeString = attr => {
	if (string.IsNullOrEmpty(attr)) return null;
	return string.Concat(", ", attr.Trim('[', ']'));
};
Func<DbColumnInfo, string> GetDefaultValue = col => {
    if (col.CsType == typeof(string)) return " = string.Empty;";
    return "";
};
}
//xxx
namespace @gen.NameSpace {

@if (string.IsNullOrEmpty(gen.table.Comment) == false) {
	@:/// <summary>
	@:/// @gen.table.Comment.Replace("\r\n", "\n").Replace("\n", "\r\n		/// ")
	@:/// </summary>
}
	[JsonObject(MemberSerialization.OptIn)@GetAttributeString(gen.GetTableAttribute())]
	public partial class @gen.GetCsName(gen.FullTableName) {

	@foreach (var col in gen.columns) {

		if (string.IsNullOrEmpty(col.Coment) == false) {
		@:/// <summary>
		@:/// @col.Coment.Replace("\r\n", "\n").Replace("\n", "\r\n		/// ")
		@:/// </summary>
		}
		@:@("[JsonProperty" + GetAttributeString(gen.GetColumnAttribute(col)) + "]")
		@:public @gen.GetCsType(col) @gen.GetCsName(col.Name) { get; set; }@GetDefaultValue(col)
@:
	}
	}
@gen.GetMySqlEnumSetDefine()
}

RazorEngine.NetCore

到了.NET Core時代,我看了下FreeSql.Generator用的這個類庫RazorEngine.NetCore,實現動態操作cshtml,生成需要的文本。

Razor Engine是基於微軟Razor解析的模板引擎,它允許你使用Razor語法構建動態模板,你只需要使用Engine的靜態方法,Engine.Razor.RunCompile等。

創建一個控制台應用,然後安裝包。

Install-Package RazorEngine.NetCore
using RazorEngine;
using RazorEngine.Templating; // For extension methods.


string template = "Hello @Model.Name, welcome to RazorEngine!";
var result = Engine.Razor.RunCompile(template, "templateKey", null, new { Name = "World" });

Console.WriteLine(result);
  • 輸出如下內容
Hello World, welcome to RazorEngine!

此處使用的RunCompile方法是擴展方法,您需要引用RazorEngine.Templating命名空間。

The “templateKey” 保持唯一值,比如使用guid值。字符串,並且你可以根據此字符串key重新運行緩存的模板。

如果再次根據此key,可使用原本的模板。

var result = Engine.Razor.Run("templateKey", null, new { Name = "Max" });
  • 會輸出如下內容
Hello Max, welcome to RazorEngine!

上面中的RunCompile第三個參數,傳null,因為我們第四個參數使用的是匿名類,

根目錄創建一個HelloWord.cshtml,要選擇屬性,->如果較新則複製 內容,

Hello @Model.Name, welcome to RazorEngine!

控制台如下代碼。

string templateFilePath = "HelloWorld.cshtml";
var templateFile = File.ReadAllText(templateFilePath);
string templateFileResult = Engine.Razor.RunCompile(templateFile, Guid.NewGuid().ToString(), null, new
{
    Name = "World"
});

Console.WriteLine(templateFileResult);
  • 控制台輸出
Hello World, welcome to RazorEngine!
  • 使用強類型 CopyRightUserInfo.cs生成一個版權所有
using System;
namespace OvOv.Razor
{
    public class CopyRightUserInfo
    {
        public string UserName { get; set; }
        public string EmailAddress { get; set; }
        public DateTime CreateTime { get; set; }
        public string FileRemark { get; set; }
    }

}

根目錄創建一個CopyRightTemplate.cshtml,要選擇屬性,->如果較新則複製 內容,

@{
    var gen = Model as OvOv.Razor.CopyRightUserInfo;
}
//=============================================================
// 創建人:            @gen.UserName
// 創建時間:          @gen.CreateTime
// 郵箱:             @gen.EmailAddress
//==============================================================

控制台如下代碼。

string copyRightTemplatePath = "CopyRightTemplate.cshtml";
var copyRightTemplate = File.ReadAllText(copyRightTemplatePath);
string copyRightResult = Engine.Razor.RunCompile(copyRightTemplate, Guid.NewGuid().ToString(), typeof(CopyRightUserInfo), new CopyRightUserInfo
{
    CreateTime = DateTime.Now,
    EmailAddress = "710277267@qq.com",
    UserName = "IGeekFan"
});
Console.WriteLine(copyRightResult);

Console.ReadKey();
  • 控制台輸出
//=============================================================
// 創建人:            IGeekFan
// 創建時間:          2020/6/23 18:14:08
// 郵箱:             710277267@qq.com
//==============================================================

全放到控制台下,輸出如下結果。代碼生成器最重要的一點解決了,我們就能實現自己的代碼生成器,先構建自己的模板,實現輸入(命令行,WPF,WEB端及更多),輸出(生成文件)。

  • 以上源碼已放到示例代碼中 https://github.com/luoyunchong/dotnetcore-examples/blob/master/aspnetcore-freesql/OvOv.Razor/Program.cs

源碼解析

首先這是一個控制台應用,Main(string[] args)可接收多個參數。

  1. 處理無參數,–help
  2. 處理args數組,解析出所有的參數,如果沒有設置,則為默認值。(處理一些參數異常問題)最重要的是根據-Razor,選定對應的模板。
ArgsRazor=""//根據-Razor  1 不是2 還是模板的路徑,取出的模板文本值。
var razorId = Guid.NewGuid().ToString("N");
RazorEngine.Engine.Razor.Compile(ArgsRazor, razorId);
  1. 根據數據庫連接串,取出參數過濾后的表,視圖,存儲過程
  2. 循環數據庫的表等,
  • model為模板中需要的數據
  • razorId與上文的razorId相同,
  • sw為生成后的文本保存的值。
var sw = new StringWriter();
var model = new RazorModel(fsql, ArgsNameSpace, ArgsNameOptions, tables, table);
RazorEngine.Engine.Razor.Run(razorId, sw, null, model);
  1. 將sw字符串保存生成類.cs文件(根據參數配置生成文件名)
  2. 另外生成一個__重新生成.bat,__razor.cshtml.txt,方便後續用戶重新生成實體類。

FreeSql.Tools

這是 FreeSql 衍生出來的輔助工具包,內含生成器等功能;作者:mypeng1985
因為這個不兼容mac,linux,所以作者建議使用dotnet-tool 命令行工具生成實體類,從而支持MAC/Linux系統。對於不是使用FreeSql的開發者,也能使用此工具,你只需要修改對應的模板即可。

使用方式:不多介紹。

  • https://github.com/2881099/Freesql.tools
  • 分為WPF ,WinForm + DSkin 版本(套網頁)
  • 看了下代碼,底層生成代碼邏輯也是用的RazorEngine .NET Framework 下的RazorEngine代碼生成介紹

FreeSql官方群 4336577

預覽圖

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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