標籤: 租車

  • 中聚電池1.9億入股雲南電動車公司50%股權

    中聚電池宣佈收購一家在雲南昆明產銷電動汽車公司的五成權益。中聚電池以1.9億元的總代價收購GIANT INDUSTRY全部股權,將按每股0.5元發行3.8億股新股份支付。代價股份佔經擴大後已發行股本2.19%。

    GIANT INDUSTRY擁有雲南美的客車製造50%的間接權益。雲南美主要從事製造、銷售、組裝及維修客車、電動汽車及總成,以及零件和部件。收購事項將為集團提供即時平台,以從事電動汽車製造。

    另外,中聚電池還宣佈,建議待該公司股東於股東特別大會上批准及經百慕達公司註冊處處長批准,將該公司英文名稱由「SinopOLy Battery Limited」更改為「FDG Electric Vehicles Limited」,而中文名稱則改為“五龍電動車(集團)”。

    董事會相信,建議更改名稱將為該公司提供新企業定位,符合該公司加強重點於電動汽車相關業務的新品牌,今後業務將包括但不限於設計、生產及銷售電動汽車;以及鋰離子電池業務的研發、生產及銷售和電動汽車租賃業務等。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

  • 中國正考慮降低電動汽車進口關稅

    據中新網報道,中國政府正考慮進行電動汽車稅收改革。中國政府官員日前會見特斯拉(Tesla)首席執行官馬斯克(Musk Elon),均表示中國將支持電動汽車產業。

    科技部部長萬鋼在北京會見馬斯克時表示,中國政府正在考慮電動汽車在稅收方面的改革,比如在進口關稅方面會有別於傳統汽車的進口,但具體細則現在還在制定之中。

    工業和信息化部部長苗圩會見馬斯克時則表示,中國政府高度重視新能源汽車產業的發展,希望特斯拉公司發揮自身優勢,不斷創新,加強與中國企業的合作。

    他同時指出,中國政府正在制定政策,幫助像特斯拉一樣的企業進入中國,促進電動汽車產業在中國的發展。

    馬斯克亦表示,特斯拉公司非常重視中國市場,願加強與中國企業的合作。特斯拉已經在北京與上海建設充電站,電力來源為光伏與電網的結合,可實現24小時不間斷充電。」特斯拉未來還計畫建立超級充電網路。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • Model S需求來到高原區 面臨BMW電動車威脅

    Model S需求來到高原區 面臨BMW電動車威脅

    巴克萊銀行6日發表分析報告指出,以特斯拉目前當紅的電動轎車「Model S」在美國、挪威的需求都逐漸來到高原區的情況來看,該公司未來恐怕會面臨愈來愈多風險,尤其是將面臨來自德國知名車商BMW即將出款的電動車的威脅。

    本週稍晚,BMW計劃在加州聖莫尼卡(Santa Monica)的法說會上發表插電式混合動力跑車「i8」。除了i8之外,BMW已推出了另一款電動轎車「i3」。根據報告,BMW的i系列車款不但顛覆了動力工程學的傳統概念,對車體設計、配送系統以及品牌形象也有全新理念。此外,BMW在整個製造過程中對續航力也特別重視。

    BMW的i8售價為136,000美元,而i3售價則為41,350美元。相較之下,特斯拉Model S系列車款售價則在70,000-93,000美元之間。i8、i3近期內可能會侵蝕部分Model S客戶層,而BMW中期要推的i5也可能對特斯拉大眾豪華車款「Gen III」構成威脅。

    巴克萊認為,BMW已將自身定位為超越特斯拉等同業的科技領航員。特斯拉的目標顧客可能剛好夾在BMW這兩款i系列車款中間。i3的車體較小,每次充電後的續航里程不如Model S,但其主打的客戶層卻與特斯拉類似;而i8則是較為高階、更為昂貴的跑車款式。

    本站,BMW公司針對旗下i系列車款,還設計了專屬的太陽能充電車棚。由BMW集團所屬BMW Designworks USA所設計、安置於美國加州Malibu的太陽能充電車棚(Solar Carport)目前已展開運作,安裝於車棚頂上的太陽能面板可為BMW i車款供電。

    圖:i3電動汽車

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

    ※超省錢租車方案

  • TOYOTA將與FirstElement合作建立加氣站

    TOYOTA將與FirstElement合作建立加氣站

    據AUTONET報導,TOYOTA將與能源公司FirstElement攜手合作加氫站網絡的架設,預計2015-2016年能夠建成20多處的加氣站,並能在2024年實現擴建至100座的計畫。

    TOYOTA於2013年底東京車展發表準量產版的FCV Concept,此後又藉由年初的2014 CES消費電子展預告2015年將率先投入北美市場販售,因此對於加氫站等後勤網絡的建設排上日程。

    其實在過去的20年來,TOYOTA就已持續針對氫燃料電池進行研發,自2002年以來便投入一系列的測試車輛於北美進行實測,隨著技術的突破,使得氫燃料電池技術成本大幅下修,終於達到原廠量產的預期。

    儘管FCV(氫燃料電池車)最終量產版本的相關數據原廠尚未公開,但參考FCV Concept所呈現的數據就相當令人滿意。

    動力來源為分別藏在車身底下的氫燃料儲存槽及電池,具備等同於136hp馬力的100kW輸出功率,需花費10秒才能完成100km/h的加速衝刺,滿電的情況下續航力約可達到482km,而且僅需3-5分鐘的時間便能將儲存槽加滿,省去了傳統電池充電耗時的問題。

    重點是氫燃料電池是以氫或含氫物質結合氧所形成的一種燃料電池,在化學反應之後僅會產生電流及水且完全沒有污染。

    動力來源為分別藏在車身底下的氫燃料儲存槽及電池(圖片來源:AUTONET)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

  • 特斯拉Q1營收同比增27% 中國市場未來備受關注

    美國電動汽車廠商特斯拉今(8)日發布Q1財報,不按美國通用會計準則計算(Non-GAAP),特斯拉第一季度營收達7.13億美元,同比增長27%;凈利潤1700萬美元,同比增長10%

    在與財報一同發布的季度股東信中,特斯拉CEO伊隆馬斯克(Elon Musk)指出中國市場的重要性,並將其視為未來幾年間的最大市場之一。上個月,特斯拉向中國車主交付了首批Model S轎車,並借勢掀起一場“特斯拉熱”。

    股東信中還談到上海政府部門對於特斯拉的支持。一項舉措是,上海車主購買Model S可以免費上牌,節約了7萬元以上的牌照費用;另一項則是在超級充電站方面大開綠燈。這也正好是特斯拉目前在中國面臨的兩大痛點:上牌難和充電難。

    進口純電動車獲免費牌照

    上海承諾今年撥出3000張免費牌照,供進口純電動車使用,這是此類政策首次向國外品牌開放。北京也計劃效仿上海,今年拿出500張牌照,專門提供給購買國外品牌純電動車的消費者。

    充電問題依然棘手

    馬斯克訪華期間接受媒體採訪時曾表示,希望建設更多采用太陽能的超級充電站。但截至目前,只有上海的一座超級充電站已經投入使用,而北京的兩個站點建設完畢,尚未對外開放。

    近期,特斯拉在美國新澤西州開設了第100家超級充電站。今年,該公司還計劃在全球開設超過200家超級充電站,美國占大多數,其余則位於加拿大、歐洲和中國。

    電池供給不足限制產能

    目前,特斯拉位於美國加州佛蒙特(Fremont)的工廠每周可生產約700輛Model S,比2013年底提升了15%。特斯拉計劃在今年底將這一數字提升至1000輛。與動輒以萬為量單位的通用、福特等傳統汽車廠商相比,特斯拉的量很低,一方面是由於只有一座工廠,另一方面則是受到了電池供給不足的限制。

    Model S採用的鎳鈷鋁電池由松下代工。特斯拉正在推進一個名為“巨型工廠”(Gigafactory)的計劃,並與松下及其他廠商進行談判,希望徹底解決電池能不足的問題。

    然而,根據股東信披露的信息,“巨型工廠”目前仍處於選址階段,直到2017年才能生產出首批電池,2020年才能達到最大產能,即每年生產50GWh的電池包和35GWh的電池單元。特斯拉認為,到那時“將不會面臨任何供貨限制”。

    此外,特斯拉的SUV車型Model X將於今年第四季度推出原型車。這是特斯拉規劃的“三步走”計劃的第二步。根據此前披露的檔案,特斯拉將在2017年左右推出第三代車型——一款廉價的家用轎車,售價預計在3萬至3.5萬美元之間。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

  • Linux Systemd 詳細介紹: Unit、Unit File、Systemctl、Target

    Linux Systemd 詳細介紹: Unit、Unit File、Systemctl、Target

    Systemd

    簡介

    CentOS 7 使用 Systemd 替換了SysV

    Ubuntu 從 15.04 開始使用 Systemd

    Systemd 是 Linux 系統工具,用來啟動守護進程,已成為大多數發行版的標準配置

    特點

    優點:

    1. 按需啟動進程,減少系統資源消耗

    2. 并行啟動進程,提高系統啟動速度

      在 SysV-init 時代,將每個服務項目編號,依次執行啟動腳本。Ubuntu 的 Upstart 解決了沒有直接依賴的啟動之間的并行啟動。而 Systemd 通過 Socket 緩存、DBus 緩存和建立臨時掛載點等方法進一步解決了啟動進程之間的依賴,做到了所有系統服務併發啟動。對於用戶自定義的服務,Systemd 允許配置其啟動依賴項目,從而確保服務按必要的順序運行。

      SystemV Upstart 參考上一篇博文:Linux 初始化系統 SystemV Upstart

    3. 使用 CGroup 監視和管理進程的生命周期

      CGroup 提供了類似文件系統的接口,當進程創建子進程時,子進程會繼承父進程的 CGroup。因此無論服務如何啟動新的子進程,所有的這些相關進程都會屬於同一個 CGroup

      在 Systemd 之前的主流應用管理服務都是使用 進程樹 來跟蹤應用的繼承關係的,而進程的父子關係很容易通過 兩次 fork 的方法脫離。

      而 Systemd 則提供通過 CGroup 跟蹤進程關係,引補了這個缺漏。通過 CGroup 不僅能夠實現服務之間訪問隔離,限制特定應用程序對系統資源的訪問配額,還能更精確地管理服務的生命周期

    4. 統一管理服務日誌

    5. 支持快照和系統恢復

    缺點:

    1. 過於複雜,與操作系統的其他部分強耦合,違反”keep simple, keep stupid”的Unix 哲學

    架構圖

    Unit(單元|服務)

    Systemd 可以管理所有系統資源:

    1. 將系統資源劃分為12類
    2. 將每個系統資源稱為一個 Unit。Unit 是 Systemd 管理系統資源的基本單位
    3. 使用一個 Unit File 作為 Unit 的單元文件,Systemd 通過單元文件控制 Unit 的啟動

    例如,MySQL服務被 Systemd 視為一個 Unit,使用一個 mysql.service 作為啟動配置文件

    Unit File(單元文件|配置文件)

    單元文件中包含該單元的描述、屬性、啟動命令等

    類型

    Systemd 將系統資源劃分為12類,對應12種類型的單元文件

    系統資源類型 單元文件擴展名 單元文件描述
    Service .service 封裝守護進程的啟動、停止、重啟和重載操作,是最常見的一種 Unit 文件
    Target .target 定義 target 信息及依賴關係,一般僅包含 Unit 段
    Device .device 對於 /dev 目錄下的硬件設備,主要用於定義設備之間的依賴關係
    Mount .mount 定義文件系統的掛載點,可以替代過去的 /etc/fstab 配置文件
    Automount .automount 用於控制自動掛載文件系統,相當於 SysV-init 的 autofs 服務
    Path .path 用於監控指定目錄或文件的變化,並觸發其它 Unit 運行
    Scope .scope 這種 Unit 文件不是用戶創建的,而是 Systemd 運行時產生的,描述一些系統服務的分組信息
    Slice .slice 用於表示一個 CGroup 的樹
    Snapshot .snapshot 用於表示一個由 systemctl snapshot 命令創建的 Systemd Units 運行狀態快照,可以切回某個快照
    Socket .socket 監控來自於系統或網絡的數據消息
    Swap .swap 定義一個用戶做虛擬內存的交換分區
    Timer .timer 用於配置在特定時間觸發的任務,替代了 Crontab 的功能

    對於操作單元文件的命令,如果缺省擴展名,則默認.service擴展名

    而操作 target 的命令,例如 isolate,則默認.target擴展名

    語法

    單元文件的語法來源於 XDG桌面入口配置文件.desktop文件

    Unit 文件可以分為三個配置區段:

    • Unit 段:所有 Unit 文件通用,用來定義 Unit 的元數據,以及配置與其他 Unit 的關係
    • Install 段:所有 Unit 文件通用,用來定義如何啟動,以及是否開機啟動
    • Service 段:服務(Service)類型的 Unit 文件(後綴為 .service)特有的,用於定義服務的具體管理和執行動作

    單元文件中的區段名和字段名大小寫敏感

    每個區段內都是一些等號連接的鍵值對(鍵值對的等號兩側不能有空格)

    Unit 段

    主要字段如下:

    • Description:當前服務的簡單描述

    • Documentation:文檔地址,可以是一個或多個文檔的 URL 路徑

      【依賴關係】

    • Requires:與其它 Unit 的強依賴關係,如果其中任意一個 Unit 啟動失敗或異常退出,當前 Unit 也會被退出

    • Wants:與其它 Unit 的弱依賴關係,如果其中任意一個 Unit 啟動失敗或異常退出,不影響當前 Unit 繼續執行

      只涉及依賴關係,默認情況下 兩個 Unit 同時啟動

      【啟動順序】

    • After:該字段指定的 Unit 全部啟動完成以後,才會啟動當前 Unit

    • Before:該字段指定的 Unit 必須在當前 Unit 啟動完成之後再啟動

      只涉及啟動順序,不影響啟動結果和運行情況

    • Binds To:與 Requires 相似,該字段指定的 Unit 如果退出,會導致當前 Unit 停止運行

    • Part Of:一個 Bind To 作用的子集,僅在列出的 Unit 失敗或重啟時,終止或重啟當前 Unit,而不會隨列出Unit 的啟動而啟動

    http://manpages.ubuntu.com/manpages/bionic/en/man5/systemd.unit.5.html

    Install 段

    主要字段如下:

    • WantedBy:它的值是一個或多個 target,執行enable命令時,符號鏈接會放入/etc/systemd/system目錄下以 target 名 + .wants後綴構成的子目錄中
    • RequiredBy:它的值是一個或多個 target,執行enable命令時,符號鏈接會放入/etc/systemd/system目錄下以 target 名 + .required後綴構成的子目錄中
    • Alias:當前 Unit 可用於啟動的別名
    • Also:當前 Unit 被 enable/disable 時,會被同時操作的其他 Unit

    http://manpages.ubuntu.com/manpages/bionic/en/man5/systemd.unit.5.html

    Service 段

    主要字段如下:

    【啟動類型】

    • Type:定義啟動時的進程行為。它有以下幾種值。
      • Type=simple:默認值,ExecStart字段啟動的進程為主進程
        • 服務進程不會 fork,如果該服務要啟動其他服務,不要使用此類型啟動,除非該服務是 socket 激活型
      • Type=forkingExecStart字段將以fork()方式從父進程創建子進程啟動,創建後父進程會立即退出,子進程成為主進程。
        • 通常需要指定PIDFile字段,以便 Systemd 能夠跟蹤服務的主進程
        • 對於常規的守護進程(daemon),除非你確定此啟動方式無法滿足需求,使用此類型啟動即可
      • Type=oneshot:只執行一次,Systemd 會等當前服務退出,再繼續往下執行
        • 適用於只執行一項任務、隨後立即退出的服務
        • 通常需要指定RemainAfterExit=yes字段,使得 Systemd 在服務進程退出之後仍然認為服務處於激活狀態
      • Type=dbus:當前服務通過 D-Bus 信號啟動。當指定的 BusName 出現在 DBus 系統總線上時,Systemd認為服務就緒
      • Type=notify:當前服務啟動完畢會發出通知信號,通知 Systemd,然後 Systemd 再啟動其他服務
      • Type=idle:Systemd 會等到其他任務都執行完,才會啟動該服務。
        • 一種使用場合是:讓該服務的輸出,不與其他服務的輸出相混合

    【啟動行為】

    • ExecStart:啟動當前服務的命令

      ExecStart=/bin/echo execstart1
      ExecStart=
      ExecStart=/bin/echo execstart2
      

      順序執行設定的命令,把字段置空,表示清除之前的值

    • ExecStartPre:啟動當前服務之前執行的命令

    • ExecStartPost:啟動當前服務之後執行的命令

    • ExecReload:重啟當前服務時執行的命令

    • ExecStop:停止當前服務時執行的命令

    • ExecStopPost:停止當前服務之後執行的命令

    • RemainAfterExit:當前服務的所有進程都退出的時候,Systemd 仍認為該服務是激活狀態

      • 這個配置主要是提供給一些並非常駐內存,而是啟動註冊后立即退出,然後等待消息按需啟動的特殊類型服務使用的
    • TimeoutSec:定義 Systemd 停止當前服務之前等待的秒數

      注:所有的啟動設置之前,都可以加上一個連詞號(-),表示”抑制錯誤”,即發生錯誤的時候,不影響其他命令的執行。比如,EnvironmentFile=-/etc/sysconfig/sshd(注意等號後面的那個連詞號),就表示即使/etc/sysconfig/sshd文件不存在,也不會拋出錯誤。

    【重啟行為】

    • RestartSec:Systemd 重啟當前服務間隔的秒數
    • KillMode:定義 Systemd 如何停止服務,可能的值包括:
      • control-group(默認值):當前控制組裡面的所有子進程,都會被殺掉
      • process:只殺主進程(sshd 服務,推薦值)
      • mixed:主進程將收到 SIGTERM 信號,子進程收到 SIGKILL 信號
      • none:沒有進程會被殺掉,只是執行服務的 stop 命令。
    • Restart:定義何種情況 Systemd 會自動重啟當前服務,可能的值包括:
      • no(默認值):退出后不會重啟
      • on-success:只有正常退出時(退出狀態碼為0),才會重啟
      • on-failure:非正常退出時(退出狀態碼非0),包括被信號終止和超時,才會重啟(守護進程,推薦值)
      • on-abnormal:只有被信號終止和超時,才會重啟(對於允許發生錯誤退出的服務,推薦值)
      • on-abort:只有在收到沒有捕捉到的信號終止時,才會重啟
      • on-watchdog:超時退出,才會重啟
      • always:不管是什麼退出原因,總是重啟

    【上下文】

    • PIDFile:指向當前服務 PID file 的絕對路徑。

    • User:指定運行服務的用戶

    • Group:指定運行服務的用戶組

    • EnvironmentFile:指定當前服務的環境參數文件。該文件內部的key=value鍵值對,可以用$key的形式,在當前配置文件中獲取

      啟動sshd,執行的命令是/usr/sbin/sshd -D $OPTIONS,其中的變量$OPTIONS就來自EnvironmentFile字段指定的環境參數文件。

    http://manpages.ubuntu.com/manpages/bionic/en/man5/systemd.service.5.html

    佔位符

    在 Unit 文件中,有時會需要使用到一些與運行環境有關的信息,例如節點 ID、運行服務的用戶等。這些信息可以使用佔位符來表示,然後在實際運行中動態地替換為實際的值。

    詳細了解見 https://cloud.tencent.com/developer/article/1516125

    模板

    在現實中,往往有一些應用需要被複制多份運行,就會用到模板文件

    模板文件的寫法與普通單元文件基本相同,只是模板文件名是以 @ 符號結尾。例如:apache@.service

    通過模板文件啟動服務實例時,需要在其文件名的 @ 字符後面附加一個用於區分服務實例的參数字符串,通常這個參數是用於監控的端口號或控制台 TTY 編譯號

    systemctl start apache@8080.service
    

    Systemd 在運行服務時,首先尋找跟單元名完全匹配的單元文件,如果沒有找到,才會嘗試選擇匹配模板

    例如上面的命令,System 首先會在約定的目錄下尋找名為 apache@8080.service 的單元文件,如果沒有找到,而文件名中包含 @ 字符,它就會嘗試去掉後綴參數匹配模板文件。對於 apache@8080.service,Systemd 會找到 apache@.service 模板文件,並通過這個模板文件將服務實例化。

    詳細了解見 https://cloud.tencent.com/developer/article/1516125

    狀態

    systemctl list-unit-files 將會列出文件的 state,包括 static, enabled, disabled, masked, indirect

    • masked

      service軟鏈接到/dev/null

      該單元文件被禁止建立啟動鏈接

    • static

      該單元文件沒有[Install]部分(無法執行),只能作為其他配置文件的依賴

    • enabled

      已建立啟動鏈接

    • disabled

      沒建立啟動鏈接

    https://askubuntu.com/a/731674

    示例

    1. 關掉觸摸板配置文件

      Unit]
      Description=Switch-off Touchpad
      
      [Service]
      Type=oneshot
      ExecStart=/usr/bin/touchpad-off start
      ExecStop=/usr/bin/touchpad-off stop
      RemainAfterExit=yes
      
      [Install]
      WantedBy=multi-user.target
      
      • oneshot 表明這個服務只要運行一次就夠了,不需要長期運行
      • RemainAfterExit字段設為yes,表示進程退出以後,服務仍然保持執行。這樣的話,一旦使用systemctl stop命令停止服務,ExecStop指定的命令就會執行,從而重新開啟觸摸板

    Systemd 內建命令

    systemd-analyze

    Analyze and debug system manager, If no command is passed, Systemd-analyze time is implied

    https://www.freedesktop.org/software/systemd/man/systemd-analyze.html

    systemd-analyze time

    查看初始化耗時

    systemd-analyze blame

    打印所有運行單元,按它們初始化的時間排序。此信息可用於優化啟動時間。注意,輸出可能具有誤導性,因為一個服務的初始化可能非常緩慢,因為它等待另一個服務的初始化完成

    systemd-run

    將一個指定的服務變成後台服務

    未測試

    參考 https://www.freedesktop.org/software/systemd/man/systemd-run.html

    systemctl 系統服務管理命令

    systemctl是 Systemd 的主命令,用於管理系統

    與 service 命令的區別

    1. systemctl 融合了 service 和 chkconfig 的功能
    2. 在 Ubuntu18.04 中沒有自帶 chkconfig 命令;service 命令實際上重定向到 systemctl 命令
    動作 SysV Init 指令 Systemd 指令
    啟動某服務 service httpd start systemctl start httpd
    停止某服務 service httpd stop systemctl stop httpd
    重啟某服務 service httpd restart systemctl restart httpd
    檢查服務狀態 service httpd status systemctl status httpd
    刪除某服務 chkconfig –del httpd 停掉應用,刪除其配置文件
    使服務開機自啟動 chkconfig –level 5 httpd on systemctl enable httpd
    使服務開機不自啟動 chkconfig –level 5 httpd off systemctl disable httpd
    查詢服務是否開機自啟 chkconfig –list | grep httpd systemctl is-enabled httpd
    加入自定義服務 chkconfig –add test systemctl load test
    显示所有已啟動的服務 chkconfig –list systemctl list-unit-files | grep enabled

    參數

    --all

    显示加載到內存的所有單元

    --type

    -t --type=

    显示指定類型(12種類型)的單元

    --state

    --state=

    显示指定狀態的單元或單元文件

    • 單元狀態

      輸入 systemctl list-units --stateTab鍵,显示所有可用的值

    • 單元文件狀態

      另外還可以用 enabled static disabled 等systemctl list-unit-files 显示的狀態

    --failed

    --state=failed

    显示加載失敗的單元

     systemctl --failed
    
    --version

    打印 Systemd 版本

    lfp@legion:/lib/systemd/system$ systemctl --version
    Systemd 237
    +PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid
    

    單元命令

    我的理解

    • systemd 對單元的管理,不涉及單元文件自身屬性和內容
    list-units

    相當於systemctl

    列出當前已加載的單元(內存)

    默認情況下僅显示處於激活狀態(正在運行)的單元

    UNIT 單元名

    LOAD 加載狀態

    ACTIVE SUB 執行狀態(大狀態 子狀態)

    DESCRIPTION 描述

    start

    啟動單元

    systemctl start mysql.service
    
    stop

    停止單元

    systemctl stop mysql.service
    
    kill

    殺掉單元進程

    systemctl kill mysql.service
    
    reload

    不終止單元,重新加載 針對該單元的 運行配置文件,而不是 針對 systemd的 該單元的啟動配置文件

    例如啟動 MySQL 服務,reload 可以在不停止服務的情況下重載 MySQL 的配置文件 my.cnf

    restart

    重啟單元

    該單元在重啟之前擁有的資源不會被完全清空,比如文件描述符存儲設施

    systemctl reload mysql.service
    
    status

    status [unit | PID]

    显示單元或進程所屬單元的運行信息

    systemctl status mysql.service
    

    Loaded行:配置文件的位置,是否設為開機啟動

    Active行:表示正在運行

    Main PID行:主進程ID

    CGroup塊:應用的所有子進程

    日誌塊:應用的日誌

    is-active

    判斷指定的單元是否處於激活狀態

    # 默認會打印當前單元的狀態,可以通過 --quiet 參數取消打印
    lfp@legion:~$ systemctl is-active mysql
    active
    
    is-failed

    判斷指定的單元是否處於啟動失敗狀態

    lfp@legion:~$ systemctl is-failed mysql
    active
    
    list-dependencies

    查看單元之間的依賴關係

    systemctl list-dependencies graphical.target 
    systemctl list-dependencies mysql.service
    
    show

    show --property= Unit <==> show -p Unit

    显示單元所有底層參數

    lfp@legion:~$ systemctl show -p MainPID mysql
    MainPID=1061
    
    set-property

    在單元啟動的時候設置運行時的某個屬性,立即生效,並保存在磁盤中作為啟動配置

    如果添加了--runtime則重啟后失效

    並非所有的屬性都可以設置,只是 systemd.resource-control 包含的屬性

    isolate

    切換到某個 target(系統狀態),立即停止該 target 未包含的單元進程。也可以理解為切換 runlevel

    如果沒有指定擴展名,則默認.target

    只有當.target單元文件中的AllowIsolate=yes時,才能使用 isolate 切換;也可以用IgnoreOnIsolate=yes字段來拒絕使用 isolate 切換

    systemctl isolate multi-user.target
    
    cat

    显示單元配置文件的備份文件,包括插入式配置drop-ins,可以完整的看到單元服務的配置。注意這裏打印的依據是磁盤的上內容,如果用戶修改了配置文件(磁盤已修改)但是未執行daemon-reload命令(內存中未更新),那麼該命令显示的配置和實際執行情況有出入

    lfp@legion:~$ systemctl cat mysql.service 
    # /lib/systemd/system/mysql.service
    # MySQL Systemd service file
    
    [Unit]
    Description=MySQL Community Server
    ...
    
    [Install]
    WantedBy=multi-user.target
    
    [Service]
    Type=forking
    ...
    # 這段就显示的是 插入式配置 drop-in 的內容
    # /etc/systemd/system/mysql.service.d/mysql.conf
    # MySQL Systemd service file
    
    [Unit]
    # Description=MySQL Community Server conf
    
    [Service]
    # ExecStartPost=/home/lfp/bin/espeak.sh
    

    單元文件命令

    我的理解

    • systemd 對單元文件自身屬性和內容的管理
    list-unit-files

    列出所有已安裝的單元文件和它們的啟用狀態

    list-units的區別是

    • list-units 僅显示當前已加載到內存中的單元
    • list-unit-files 會讀取單元文件內容,列出所有單元,包括存在於硬盤未加載進內存的單元

    實際測試結果:

    systemctl list-unit-files 显示“348 Unit files listed”

    systemctl list-units –all 显示“405 loaded units listed”

    systemctl list-units 显示 “232 loaded units listed”

    enable

    使某個單元開機自啟動

    這會根據單元文件內容中的[Install]指定的 target 組,創建一個軟鏈接

    lfp@legion:/etc/systemd/system$ vim v2rayL.service
    # 文件內容 [Install] 段
    ......
    [Install]
    WantedBy=multi-user.target
    ......
    
    lfp@legion:/etc/systemd/system$ systemctl is-enabled v2rayL.service 
    disabled
    lfp@legion:/etc/systemd/system$ systemctl enable v2rayL.service 
    # 根據 [Install] 段指定的組,添加軟鏈接
    Created symlink /etc/systemd/system/multi-user.target.wants/v2rayL.service → /etc/systemd/system/v2rayL.service.
    lfp@legion:/etc/systemd/system$ systemctl is-enabled v2rayL.service 
    enabled
    
    disable

    取消某個單元開機自啟動設置,刪除軟鏈接

    這會刪除所有指向該單元文件的軟鏈接,不僅僅是 enable 操作創建的

    reenable

    disable 和 enable 的結合,根據單元文件內容中的 [Install] 段,重置軟鏈接

    is-enabled

    檢查某個單元是否是開機自啟動的(建立的啟動鏈接)

    lfp@legion:~$ systemctl is-enabled mysql
    enabled
    
    get-default

    獲取默認啟動 target,default-target 是指向該 target 的軟鏈接

    set-default

    設置默認啟動 target,同時修改 default-target 指向設定的 target

    systemctl set-default multi-user.target
    

    生命周期管理命令

    daemon-reload

    重新加載所有的單元文件和依賴關係

    對單元文件有修改的時候,需要執行該命令重新加載文件內容

    系統管理命令

    reboot
    systemctl reboot
    

    重啟系統(異步操作)

    it will return after the reboot operation is enqueued, without waiting for it to complete

    poweroff

    關閉系統,切斷電源(異步操作)

    halt

    僅CPU停止工作,其他硬件仍處於開機狀態(異步操作)

    suspend

    暫停系統(異步操作)

    將觸發執行suspend.target

    hibernate

    讓系統進入冬眠狀態(異步操作)

    將觸發執行hibernate.target

    目錄、文件

    /run/systemd/system/

    單元(服務)運行時生成的配置文件所在目錄
    /etc/systemd/system/

    系統或用戶自定義的配置文件,初始化過程中Systemd只執行/etc/systemd/system目錄裏面的配置文件

    /lib/systemd/system/

    軟件安裝時添加的配置文件,類似於 /etc/init.d/

    對於支持 Systemd 的程序,安裝的時候,會自動的在 /lib/systemd/system 目錄添加一個配置文件

    其他目錄都是軟鏈接

    /etc/systemd/system/default.target

    Systemd 執行的第一個單元文件,符號鏈接到默認啟動 target 對應的 .target 單元文件

    優先級

    SysV 的啟動腳本放在/etc/init.d目錄下

    Systemd 的單元文件放在/etc/systemd/system/lib/systemd/system目錄下

    當一個程序在3個目錄下都存在啟動方式時,優先級是/etc/systemd/system --> /lib/systemd/system --> /etc/init.d

    lfp@legion:/etc/init.d$ ll
    -rwxr-xr-x   1 root root  5650 5月  19 22:09 mysql*
    
    lfp@legion:/etc/systemd/system$ ll
    -rw-r--r--  1 root root  511 5月  20 01:42  mysql.service
    
    lfp@legion:/lib/systemd/system$ ll
    -rw-r--r--  1 root root   499 5月  20 01:20  mysql.service
    
    • /etc/systemd/system 裏面的同名service會覆蓋/lib/systemd/system 裏面的

      注意查看文件信息,該同名文件不能是指向 /lib/systemd/system 的軟鏈接

      軟鏈接不會覆蓋而會同步

    • 如果某個程序不存在Systemd 單元文件,那麼會執行/etc/init.d裏面的啟動腳本

    根據啟動過程, /etc/systemd/system/multi-user.target.wants/ 目錄下是很多指向 /lib/systemd/system/目錄的軟鏈接,所以兩個目錄下的單元文件會互相同步。

    如果/etc/systemd/system//etc/systemd/system/multi-user.target.wants/ 同時存在單元文件,測試發現,不管是手動啟動還是開機自啟動,使用的都是 /etc/systemd/system/ 目錄下的service單元文件

    測試
    執行/etc/init.d目錄下的腳本

    mysql 修改 mysql.servicemysql.service.bak 然後通過service mysql restart啟動/etc/init.d/mysql腳本

    下面是啟動后的一些信息

    注:在恢復mysql.service之前,需要先通過service mysql stop 利用/etc/init.d/mysql腳本中的stop結束上面的進程,否則一旦恢復,service mysql stop 執行的操作就不是 /etc/init.d/mysql腳本中的stop,無法結束上面的進程,出現命令無法正常執行的情況

    結束上面的進程,恢復mysql.service,重新啟動

    /etc/systemd/system 覆蓋測試

    未修改前,查看MySQL的狀態

    lfp@legion:~$ service mysql status
    ● mysql.service - MySQL Community Server
    	# 可以發現這裏的 mysql.service 是在 /lib/systemd/system 下面
       Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: en
       Active: active (running) since Sat 2020-04-25 18:34:30 CST; 5h 33min ago
     Main PID: 988 (mysqld)
        Tasks: 28 (limit: 4915)
       CGroup: /system.slice/mysql.service
               └─988 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
    
    4月 25 18:34:30 legion Systemd[1]: Starting MySQL Community Server...
    4月 25 18:34:30 legion Systemd[1]: Started MySQL Community Server.
    

    將 /lib/systemd/system 下面的文件複製到 /etc/systemd/system/ 下面

    sudo cp /lib/systemd/system/mysql.service /etc/systemd/system/
    

    修改 mysql.service

    sudo vim /etc/systemd/system/mysql.service
    

    重啟 mysql.service ,系統提示需要重新加載

    lfp@legion:~$ systemctl restart mysql.service 
    Warning: The Unit file, source configuration file or drop-ins of mysql.service changed on disk. 
    Run 'systemctl daemon-reload' to reload units.
    lfp@legion:~$ systemctl daemon-reload	#  重新加載
    lfp@legion:~$ systemctl restart mysql.service # 重啟
    lfp@legion:~$ systemctl status mysql.service 
    ● mysql.service - MySQL Community Server hahahaha	# 發現這裡是修改之後的,覆蓋了 /lib/systemd/lib 中的
    #                                 這裏也可以看到加載路徑
       Loaded: loaded (/etc/systemd/system/mysql.service; enabled; vendor preset: en
       Active: active (running) since Sun 【2020-04-26】 00:47:02 CST; 5s ago
      Process: 21590 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/m
      Process: 21581 ExecStartPre=/usr/share/mysql/mysql-Systemd-start pre (code=exi
     Main PID: 21592 (mysqld)
        Tasks: 27 (limit: 4915)
       CGroup: /system.slice/mysql.service
               └─21592 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pi
    
    4月 26 00:47:02 legion Systemd[1]: Starting MySQL Community Server hahahaha...
    4月 26 00:47:02 legion Systemd[1
    

    Target

    兩個含義

    1. 系統的某個狀態稱為一個 target(類似於”狀態點”)

    2. 達到某個系統狀態,所需的一個或多個資源(Unit)稱為一個 target(一個 Unit 組)

      1. target是一個抽象的系統資源,不像MySQL有實體

      2. 如果一個target只包含一個Unit,那麼該 target,沒有對應的目錄,指的就是這個 Unit

        例如 hibernate.target 只包含 systemd-hibernate.service一個Unit

        如果一個target包含多個Unit,那麼該target,有對應的 xxx.target.wants 目錄,指的是目錄裏面所有的Unit

        例如 multi-user.target 包含位於/etc/systemd/system/multi-user.target.wants目錄下的多個 Unit

    target也是一個 Target 類型的系統資源,有對應的單元文件 xxx.target

    Systemd 使用 target 來劃分和管理資源(Unit),啟動(激活)某個 xxx.target 單元文件,通過執行該 target 包含的 Unit,使系統達到某種狀態

    對於狀態點的理解:

    例如,執行systemd suspend命令讓系統暫停,會觸發啟動suspend.target,然後執行裏面的systemd-suspend.service Unit,使系統達到一個暫停的狀態

    傳統的init啟動模式裏面,有 RunLevel 的概念,跟 Target 的作用很類似。不同的是,RunLevel 是互斥的,不可能多個 RunLevel 同時啟動,但是多個 Target 可以同時啟動

    啟動 target

    runlevel是 SysV init 初始化系統中的概念,在Systemd初始化系統中使用的是 Target,他們之間的映射關係是

    Runlevel Target 說明
    0 poweroff.target 關閉系統
    1 rescue.target 維護模式
    2,3,4 multi-user.target 多用戶,無圖形系統(命令行界面)
    5 graphical.target 多用戶,圖形化系統(圖形用戶界面)
    6 reboot.target 重啟系統

    啟動過程

    1. 讀入 /boot 目錄下的內核文件

    2. 內核文件加載完之後,開始執行第一個程序/sbin/init 初始化進程,由 Systemd 初始化系統引導,完成相關的初始化工作

    3. Systemd 執行default.target ,獲知設定的啟動 target

      實際上 default.target 是指向設定的啟動 target 的軟鏈接

    4. Systemd 執行啟動 target 對應的單元文件。根據單元文件中定義的依賴關係,傳遞控制權,依次執行其他 target 單元文件,同時啟動每個 target 包含的單元

      對於圖形化界面,默認 target 是 graphical,Systemd 執行位於/lib/systemd/system/ 目錄下的 graphical.target 單元文件,根據 target 單元文件中定義的依賴關係,依次啟動其他 target 單元文件以及各個 target 包含的位於/etc/systemd/system/目錄下的單元

      例如: graphical.target 的依賴關係是

      [Unit]
      Description=Graphical Interface
      Documentation=man:systemd.special(7)
      Requires=multi-user.target #
      Wants=display-manager.service #
      Conflicts=rescue.service rescue.target
      After=multi-user.target rescue.service rescue.target display-manager.service #
      AllowIsolate=yes
      

      因此,依次啟動 multi-user.target –> basic.target –> sysinit.target –> local-fs.target –>local-fs-pre.target –> …

      同時啟動每個 target 包含的位於/etc/systemd/system/目錄下的Unit

      SysV對應的 rc5.d –> /etc/init.d 目錄下的指定的腳本就不會在開機的時候執行了

    查看默認 target

    systemctl get-default

    lfp@legion:~$ runlevel
    N 5
    lfp@legion:~$ systemctl get-default
    graphical.target
    

    修改默認 target

    systemctl set-default [xxx.target]

    # Ubuntu18.04
    # 圖形用戶界面 切換 命令行界面
    sudo systemctl set-default multi-user.target
    # 命令行界面 切換 圖形用戶界面
     systemctl set-default graphical.target
     reboot
     # 命令行界面 想進入 圖形用戶界面(僅進入一次,重啟系統后仍然會進入命令行模式)
     sudo systemctl start lightdm
    

    https://ask.csdn.net/questions/695344

    https://askubuntu.com/a/788465

    其他操作

    修改配置文件

    1. 直接修改/lib/systemd/system目錄下的單元文件

      如果軟件包更新,修改會被丟棄

    2. /lib/systemd/system中的單元文件複製到/etc/systemd/system/

      如果軟件包更新,不會同步更新

    3. /etc/systemd/system/ 中添加配置(推薦)

    添加配置

    步驟:

    1. /etc/systemd/system/ 目錄下新建<單元名>.d目錄
    2. <單元名>.d目錄下,新建<單元名>.conf文件
    3. <單元名>.conf文件中修改配置

    測試:

    1. 創建目錄及文件

      # /mysql.service.d
      lfp@legion:/etc/systemd/system/mysql.service.d$ ls
      mysql.conf
      
    2. 修改配置

      # MySQL Systemd service config file
      # 不需要所有的組,僅添加需要修改的組及選項
      
      [Unit]
      Description=MySQL Community Server config test
      
      [Service]
      ExecStartPost=/home/lfp/bin/espeak.sh
      
    3. 重啟測試

      lfp@legion:/etc/systemd/system/mysql.service.d$ systemctl daemon-reload
      lfp@legion:/etc/systemd/system/mysql.service.d$ systemctl restart mysql.service
      lfp@legion:/etc/systemd/system/mysql.service.d$ systemctl status mysql.service
      lfp@legion:/etc/systemd/system/mysql.service.d$ systemctl status mysql.service
      #                                                                            描述已經被修改
      ● mysql.service - MySQL Community Server config test
         Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
      # 加入了配置文件
        Drop-In: /etc/systemd/system/mysql.service.d
                 └─mysql.conf
         Active: active (running) since Thu 2020-05-21 20:18:02 CST; 12min ago
      # 新增的動作執行成功
        Process: 4703 ExecStartPost=/home/lfp/bin/espeak.sh (code=exited, status=0/SUCCESS)
        Process: 4672 ExecStart=/usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid (code=exited, status=0/SUCCESS)
        Process: 4663 ExecStartPre=/usr/share/mysql/mysql-Systemd-start pre (code=exited, status=0/SUCCESS)
       Main PID: 4674 (mysqld)
          Tasks: 27 (limit: 4915)
         CGroup: /system.slice/mysql.service
                 └─4674 /usr/sbin/mysqld --daemonize --pid-file=/run/mysqld/mysqld.pid
      
      5月 21 20:18:02 legion espeak.sh[4703]: ALSA lib pcm_route.c:867:(find_matching_chmap) Found no matching channel map
      

    添加開機啟動服務

    1. 添加啟動配置文件
    2. 通過 rc.local文件

    /lib/systemd/rc.local.service

    # 如果存在,就自動添加到 multi-user.target
    # This Unit gets pulled automatically into multi-user.target by
    # Systemd-rc-local-generator if /etc/rc.local is executable.
    [Unit]
    Description=/etc/rc.local Compatibility
    Documentation=man:systemd-rc-local-generator(8)
    ConditionFileIsExecutable=/etc/rc.local
    After=network.target
    
    [Service]
    Type=forking
    ExecStart=/etc/rc.local start
    TimeoutSec=0
    RemainAfterExit=yes
    GuessMainPID=no
    
    

    創建 rc.local 文件,賦予可執行權限,即可添加啟動命令

    sudo touch /etc/rc.local
    chmod 755 /etc/rc.local
    
    lfp@legion:/etc$ vim rc.local
    #!/bin/sh -e
    echo "test rc.local" > /usr/local/rclocal.log
    echo "rc.local `date +%Y-%m-%d-%H:%M:%S`" >/home/lfp/log/rclocal.log
    exit 0
    

    system 工具集

    hostnamectl 主機名管理命令

    hostnamectl
    hostnamectl status

    Show current system hostname and related information

    lfp@legion:/lib/systemd/system$ hostnamectl status
       Static hostname: legion
             Icon name: computer-laptop
               Chassis: laptop
            Machine ID: b28xxxxxxxx2ecafa29e
               Boot ID: 21xxxxxxxxxxxx1d3a47504d
      Operating System: Ubuntu 18.04.4 LTS
                Kernel: Linux 5.3.0-51-generic
          Architecture: x86-64
    

    journalctl 日誌管理命令

    Systemd 統一管理所有 Unit 的啟動日誌。帶來的好處就是,可以只用journalctl一個命令,查看所有日誌(內核日誌和應用日誌)。

    配置文件

    /etc/systemd/journald.conf

    日誌保存目錄

    /var/log/journal/

    默認日誌最大限製為所在文件系統容量的 10%,可通過 /etc/systemd/journald.conf 中的 SystemMaxUse 字段來指定

    該目錄是 systemd 軟件包的一部分。若被刪除,systemd 不會自動創建它,直到下次升級軟件包時重建該目錄。如果該目錄缺失,systemd 會將日誌記錄寫入 /run/systemd/journal。這意味着,系統重啟後日志將丟失。

    journalctl -u [服務名]

    查看指定單元的日誌

    journalctl -b
    • journalctl -b -0 显示本次啟動的信息
    • journalctl -b -1 显示上次啟動的信息
    • journalctl -b -2 显示上上次啟動的信息

    參考

    http://manpages.ubuntu.com/manpages/bionic/en/man1/systemctl.1.html

    http://manpages.ubuntu.com/manpages/bionic/en/man5/systemd.unit.5.html

    https://www.cnblogs.com/yingsong/p/6012180.html

    https://www.cnblogs.com/sparkdev/p/8472711.html

    http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html

    http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html

    https://linoxide.com/linux-how-to/systemd-boot-process/

    https://cloud.tencent.com/developer/article/1516125

    https://www.cnblogs.com/sparkdev/p/8472711.html

    https://www.ibm.com/developerworks/cn/linux/1407_liuming_init3/index.html?ca=drs-

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 2. 背包,隊列和棧

    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特徵點(圖文並茂+淺顯易懂+程序源碼)

    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.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

    預覽圖

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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