標籤: 如何寫文案

  • 程序員的命名素養

    程序員的命名素養

    引言

    今天來聊聊命名相關內容。

    在日常工作中,項目、類、方法、表等等等等,都需要我們起名來標識區分。好的名字讓人賞心悅目,不好的名字讓人看的想吐。

    最近工作有幸寫了node、前端、php、sql、scala,也見識了公司各位前輩們的命名功底。其中不乏abc命名、拼音命名、蹩腳英文命名,更有不少從別的地方粘過來連名都不改的操作。

    命名沒有對錯,只是規範一點,可以提高可讀性、可維護性。

    命名原則

    拼寫正確

    拼寫正確是可讀的基礎。

    play shiftplay shit自己體會一下

    清新明了,見名知意

    根據要表達的內容命名,一針見血。

    getNameById 根據id獲取名稱
    ClassLoader 類加載器
    MYSQL_USERNAME mysql用戶名 
    

    如上幾個例子,我們一眼就知道要表達什麼,可讀性高。

    使用英文字母命名

    在編程中,英文還是較為主流的,最好使用單詞來命名,再不濟也是用拼音來命名。

    不論是拼音或單詞,清晰表意是首要。

    保持一致

    在一個項目中,應該使用統一的規範來命名。

    無規矩不成方圓。

    合理使用動詞名詞

    類名、變量名通常應使用名詞。如ClassLoaderuserId

    對於方法名、函數名,應包含動詞。如handleClickgenerateUniqueId

    命名方法

    常見的命名方法有駝峰命名法、匈牙利命名法、帕斯卡命名法、中/下劃線命名法

    駝峰命名法Camel-Case

    駝峰命名法,又叫小駝峰命名法,如名稱所表達的意思,指混合使用大小寫字母老表示名字。

    userIdgetCompanyNameById

    應用很廣泛。

    匈牙利命名法

    基本原則是:變量名=屬性+類型+對象描述。通過名稱可以直觀的了解他的所屬、類型等信息。

    是早期的命名方式,早期IDE沒有很智能的時候,這種命名是很有必要的。

    iNum,表示int類型的num

    現在依舊很少有人用了。

    帕斯卡命名法

    又叫大駝峰命名法,就是把駝峰命名的首字母大寫了。

    ClassLoader

    中/下劃線命名法

    單詞全部小寫,單詞和單詞間用中劃線或下劃線分割。

    user_idpython-flask-demo

    下劃線命名在數據庫中較為常見。

    常量命名法

    這個不是官方的方法,但是常量一般是由固定規範的。

    格式:所有單詞的所有字母都是大寫,單詞之間用下戶線連接。

    APOLLO_NAMESPAC

    總結

    好的命名習慣是每個程序員必備的基本素養。

    寫代碼時,好的命名會讓思路更加清洗,代碼寫的更加絲滑。

    代碼就是程序員的形象,從命名的細節開始,讓自己更帥一些。

    個人公眾號:碼農峰,定時推送行業資訊,持續發布原創技術文章,歡迎大家關注。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 重識Java8函數式編程

    重識Java8函數式編程

    前言

    最近真的是太忙忙忙忙忙了,很久沒有更新文章了。最近工作中看到了幾段關於函數式編程的代碼,但是有點費解,於是就準備總結一下函數式編程。很多東西很簡單,但是如果不總結,可能會被它的各種變體所困擾。接觸Lambda表達式已經很久了,但是也一直是處於照葫蘆畫瓢的階段,所以想自己去編寫相關代碼,也有些捉襟見肘。

    1. Lambda表達式的不同形式

    // 基本形式
    參數 -> 主體
    

    1.1 形式一

    Runnable noArguments = () -> System.out.println("Hello World");
    

    該形式的Lambda表達式不包含參數,使用空括號()表示沒有參數。它實現了Runnable接口,該接口也只有一個run方法,沒有桉樹,且返回類型為void。

    1.2 形式二

    ActionListener oneArgument = event -> System.out.println("button clicked");
    

    該形式的Lambda表達式包含且只包含一個參數,可省略參數的符號。

    1.3 形式三

    Runnable multiStatement = () -> {
    	System.out.print("Hello"); 
        System.out.println(" World"); 
    };
    

    Lambda表達式的主體不僅可以使一個表達式,而且也可以是一段代碼塊,使用大括號{}將代碼塊括起來。該代碼塊和普通方法遵循的規則別無二致,可以用返回或拋出異常來退出。只有以行代碼的Lambda表達式也可以使用大括號,用以明確Lambda表達式從何處開始,到哪裡結束。

    1.4 形式四

    BinaryOperator<Long> add = (x, y) -> x + y;
    

    Lambda表達式也可以表示包含多個參數的方法,上面的Lambda表達式並不是將兩個数字相加,而是創建了一個函數,用來計算兩個数字相加的結果。變量add的類型時BinaryOperator ,它不是兩個数字的和,而是將兩個数字相加的那行代碼。

    1.5 形式五

    BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
    

    到目前為止,所有Lambda表達式中的參數類型都是由編譯器推斷得出的。但有時最好也可以显示聲明參數類型,此時就需要使用小括號將參數括起來,多個參數的情況也是如此。

    2. 引用值,而不是變量

    如果你曾使用過匿名內部類,也許遇到過這樣的情況:需要引用它所在方法里的變量。這是,需要將變量聲明為final。

    final String name = getUserName(); 
    button.addActionListener(new ActionListener() {
    	public void actionPerformed(ActionEvent event) { 
            System.out.println("hi " + name); 
        } 
    });
    

    將變量聲明為 final,意味着不能為其重複賦 值。同時也意味着在使用 final 變量時,實際上是在使用賦給該變量的一個特定的值。

    Java 8 雖然放鬆了這一限制,可以引用非 final 變量,但是該變量在既成事實上必須是 final(意思就是你不能再次對該變量賦值)。雖然無需將變量聲明為 final,但在 Lambda 表達式中,也無法用作非終態變量。如 果堅持用作非終態變量,編譯器就會報錯。 既成事實上的 final 是指只能給該變量賦值一次。換句話說,Lambda 表達式引用的是值, 而不是變量。

    例如:

    String name = getUserName(); 
    button.addActionListener(event -> System.out.println("hi " + name));
    

    3. 函數接口

    在 Java 里,所有方法參數都有固定的類型。假設將数字 3 作為參數傳給一個方法,則參數 的類型是 int。那麼,Lambda 表達式的類型又是什麼呢?

    使用只有一個方法的接口來表示某特定方法並反覆使用,是很早就有的習慣。使用 Swing 編寫過用戶界面的人對這種方式都不陌生,這裏無需再標新立異,Lambda 表達式也使用同樣的技巧,並將這種接口稱為函數接口。

    接口中單一方法的命名並不重要,只要方法簽名和 Lambda 表達式的類型匹配即可。可在函數接口中為參數起一個有意義的名字,增加代碼易讀性,便於更透徹 地理解參數的用途。

    3.1 Java中重要的函數接口

    接口 參數 返回類型 示例
    Predicate T boolean 判斷是否
    Consumer T void 輸出一個值
    Function<T,R> T T 獲得對象的名字
    Supplier None T 工廠方法
    UnaryOperator T T 邏輯非(!)
    BinaryOperator (T, T) T 求兩個數的乘積(*)

    3.2 函數接口定義

    定義函數接口需要使用到註解@FunctionalInterface

    例如:

    @FunctionalInterface
    public interface MyFuncInterface {
    	void print();
    }
    

    使用:

    public class MyFunctionalInterfaceTest {
        public static void main(String[] args) {
            doPrint(() -> System.out.println("java"));
        }
    
        public static void doPrint(MyFuncInterface my) {
            System.out.println("請問你喜歡什麼編程語言?");
            my.print();
        }
    }
    

    說明:

    這隻是一個很簡單的例子,有人覺得為什麼要搞這麼複雜,去定義一個接口?這個問題還是讀者在平時的工作中去感悟吧,總之,先學會怎麼用它。不至於看了別人寫的代碼都看不懂。

    至於我個人的理解,可以簡單聊聊。以前寫過JavaScript,裏面有一種語法就是將自定義函數B作為參數傳遞到另外一個函數A裏面,在函數A裏面會執行你自定義的函數B邏輯,我當時就非常喜歡這種特性,因為每個人關於函數B的實現可能不一樣,亦或者場景不一樣也會導致函數B的實現不一樣。我覺得Java8的這個函數式編程就是對這一特性的補充。

    4. 流

    流的常用操作有很多,例如collect(toList())mapfiltermaxmin等,下面介紹一下flatMapreduce

    4.1 flatMap

    flatMap 方法可用 Stream 替換值,然後將多個 Stream 連接成一個 Stream。

    List<Integer> together = Stream.of(asList(1, 2), asList(3, 4)) 				 
        .flatMap(numbers -> numbers.stream())
        .collect(toList()); 
    assertEquals(asList(1, 2, 3, 4), together);
    

    調用 stream 方法,將每個列錶轉換成 Stream 對象,其餘部分由 flatMap 方法處理。 flatMap 方法的相關函數接口和 map 方法的一樣,都是 Function 接口,只是方法的返回值 限定為 Stream 類型罷了。

    4.2 reduce

    reduce 操作可以實現從一組值中生成一個值。對於 count、min 和 max 方 法,因為常用而被納入標準庫中。事實上,這些方法都是 reduce 操作。

    如何通過 reduce 操作對 Stream 中的数字求和。以 0 作起點——一個空Stream 的求和結果,每一步都將 Stream 中的元素累加至 accumulator,遍歷至 Stream 中的 最後一個元素時,accumulator 的值就是所有元素的和。

    int count = Stream.of(1, 2, 3)
        .reduce(0, (acc, element) -> acc + element); 
    assertEquals(6, count);
    

    Lambda 表達式的返回值是最新的 acc,是上一輪 acc 的值和當前元素相加的結果。reducer 的類型是前面已介紹過的 BinaryOperator。

    5. Optional

    reduce 方法的一個重點尚未提及:reduce 方法有兩種形式,一種如前面出現的需要有一 個初始值,另一種變式則不需要有初始值。沒有初始值的情況下,reduce 的第一步使用 Stream 中的前兩個元素。有時,reduce 操作不存在有意義的初始值,這樣做就是有意義的,此時,reduce 方法返回一個 Optional 對象。

    Optional 是為核心類庫新設計的一個數據類型,用來替換 null 值。人們對原有的 null 值有很多抱怨。人們常常使用 null 值表示值不存在,Optional 對象能更好地表達這個概念。使用 null 代 表值不存在的最大問題在於 NullPointerException。一旦引用一個存儲 null 值的變量,程 序會立即崩潰。使用 Optional 對象有兩個目的:首先,Optional 對象鼓勵程序員適時檢查變量是否為空,以避免代碼缺陷;其次,它將一個類的 API 中可能為空的值文檔化,這比閱讀實現代碼要簡單很多。

    下面我們舉例說明 Optional 對象的 API,從而切身體會一下它的使用方法。使用工廠方法 of,可以從某個值創建出一個 Optional 對象。Optional 對象相當於值的容器,而該值可以 通過 get 方法提取。

    Optional<String> a = Optional.of("a"); 
    assertEquals("a", a.get());
    

    Optional 對象也可能為空,因此還有一個對應的工廠方法 empty,另外一個工廠方法 ofNullable 則可將一個空值轉換成 Optional 對象。下面的代碼同時展示 了第三個方法 isPresent 的用法(該方法表示一個 Optional 對象里是否有值)。

    Optional emptyOptional = Optional.empty(); 
    Optional alsoEmpty = Optional.ofNullable(null); assertFalse(emptyOptional.isPresent());
    

    使用 Optional 對象的方式之一是在調用 get() 方法前,先使用 isPresent 檢查 Optional 對象是否有值。使用 orElse 方法則更簡潔,當 Optional 對象為空時,該方法提供了一個 備選值。如果計算備選值在計算上太過繁瑣,即可使用 orElseGet 方法。該方法接受一個 Supplier 對象,只有在 Optional 對象真正為空時才會調用。

    assertEquals("b", emptyOptional.orElse("b")); 
    assertEquals("c", emptyOptional.orElseGet(() -> "c"));
    

    最後

    實踐是檢驗真理的唯一標準,多寫代碼,多思考,你的代碼才會越來越好。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 記一次uboot升級過程的兩個坑

    記一次uboot升級過程的兩個坑

    背景

    之前做過一次uboot的升級,當時留下了一些記錄,本文摘錄其中比較有意思的兩個問題。

    啟動失敗問題

    問題簡述

    uboot代碼中用到了一個庫,考慮到庫本身跟uboot版本沒什麼關係,就直接把舊的庫文件拷貝過來使用。結果編譯鏈接是沒問題,啟動卻會卡住。

    消失的打印

    為了明確卡住的位置,就去修改了庫的源碼,添加一些打印(此時還是在舊版本uboot下編譯的),結果發現卡住的位置或隨着添加打印的變化而變化,且有些打印語句,添加后未打印出來。

    我決定先從這些神秘消失的打印入手。

    分析下uboot中的printf實現,最底層就是寫寄存器,是一個同步的函數,也沒什麼可疑的地方。

    為了確認打印不出來的時候,到底有沒有調用到printf,我決定給printf增加一個計數器,在gd結構體中,增加一個printf_count字段,初始化為0,每次打印時執行printf_count++並打印出值。

    設計這個試驗,本意是確認未打印出來時是否確實也調用到了printf,但卻有了別的發現,實驗結果中printf_count值會異常變化,不是按打印順序遞增,而是會突變成很大的異常值。

    printf_countgd結構體的成員,那就是gd的問題了。進一步將uboot全局結構體gd的地址打印出來。確認了原因是gd結構體的指針變化了。

    這也可以解釋部分打印消失的現象,原因是我們在gd中有另一個字段,用於控制打印等級。當gd被改動了,printf就可能解析出錯,誤以為打印等級為0而提前返回。

    gd的實現

    那麼好端端的,gd為什麼會被改了呢?這就要先看看gd到底是怎麼實現的了。

    uboot中維護了一個全局的結構體gd。在代碼中加入

    DECLARE_GLOBAL_DATA_PTR;
    

    即可使用gd指針訪問這個全局結構體,許多地方都會藉助gd來保存傳遞信息。

    進一步看看這個宏的定義

    舊版本uboot:
    #define DECLARE_GLOBAL_DATA_PTR        register volatile gd_t *gd asm ("r8")
    
    新版本uboot:
    #define DECLARE_GLOBAL_DATA_PTR        register volatile gd_t *gd asm ("r9")
    

    居然不一樣,一個是將gd的值放到r8寄存器,一個是放在r9寄存器。

    那麼就可以猜測到,庫是在舊版本uboot中編譯出來的,可能使用了r9,那麼放到新版本uboot中去,就會破壞r9寄存器中保存的gd值,導致一系列依賴gd的代碼不能正常工作。

    驗證改動

    為了求證,將庫反彙編出來,發現確實避開了r8寄存器,但使用了r9寄存器。

    說明uboot在指定gd寄存器的同時,還有某種方法讓其他代碼不使用這個寄存器。

    那是不是把舊uboot中的這個r8改成r9,重新編譯庫就可以了呢?試一下,還是不行。

    那麼禁止其他代碼使用r8寄存器肯定就是通過別的方式實現的了。簡單粗暴地在舊版本uboot下搜索r8,去掉.c .h等類型后,很容易發現了

    ./arch/arm/cpu/armv7/config.mk:24:PLATFORM_RELFLAGS += -fno-common -ffixed-r8 -msoft-floa
    

    -ffixed-r8修改為-ffixed-r9,重新編譯出庫,這回就可以正常工作了,打印正常,啟動正常。反彙編出來也可以看到,新編譯出來的庫用了r8沒有用r9

    當然更好的改法,是直接在新版本的uboot中編譯,這是最可靠的。

    追本溯源

    話說回來,為什麼兩個版本的uboot,會使用不同的寄存器呢?難道有什麼坑?

    這就得去翻一下git記錄了。

    commit fe1378a961e508b31b1f29a2bb08ba1dac063155
    Author: Jeroen Hofstee <jeroen@myspectrum.nl>
    Date:   Sat Sep 21 14:04:41 2013 +0200
    
        ARM: use r9 for gd
        
        To be more EABI compliant and as a preparation for building
        with clang, use the platform-specific r9 register for gd
        instead of r8.
        
        note: The FIQ is not updated since it is not used in u-boot,
        and under discussion for the time being.
        
        The following checkpatch warning is ignored:
        WARNING: Use of volatile is usually wrong: see
        Documentation/volatile-considered-harmful.txt
        
        Signed-off-by: Jeroen Hofstee <jeroen@myspectrum.nl>
        cc: Albert ARIBAUD <albert.u.boot@aribaud.net>
    

    git記錄中,也可以確認完整地將r8切換到r9,都需要做哪些修改

    diff --git a/arch/arm/config.mk b/arch/arm/config.mk
    index 16c2e3d1e0..d0cf43ff41 100644
    --- a/arch/arm/config.mk
    +++ b/arch/arm/config.mk
    @@ -17,7 +17,7 @@ endif
     
     LDFLAGS_FINAL += --gc-sections
     PLATFORM_RELFLAGS += -ffunction-sections -fdata-sections \
    -                     -fno-common -ffixed-r8 -msoft-float
    +                     -fno-common -ffixed-r9 -msoft-float
     
     # Support generic board on ARM
     __HAVE_ARCH_GENERIC_BOARD := y
    diff --git a/arch/arm/cpu/armv7/lowlevel_init.S b/arch/arm/cpu/armv7/lowlevel_init.S
    index 82b2b86520..69e3053a42 100644
    --- a/arch/arm/cpu/armv7/lowlevel_init.S
    +++ b/arch/arm/cpu/armv7/lowlevel_init.S
    @@ -22,11 +22,11 @@ ENTRY(lowlevel_init)
            ldr     sp, =CONFIG_SYS_INIT_SP_ADDR
            bic     sp, sp, #7 /* 8-byte alignment for ABI compliance */
     #ifdef CONFIG_SPL_BUILD
    -       ldr     r8, =gdata
    +       ldr     r9, =gdata
     #else
            sub     sp, #GD_SIZE
            bic     sp, sp, #7
    -       mov     r8, sp
    +       mov     r9, sp
     #endif
            /*
             * Save the old lr(passed in ip) and the current lr to stack
    diff --git a/arch/arm/include/asm/global_data.h b/arch/arm/include/asm/global_data.h
    index 79a9597419..e126436093 100644
    --- a/arch/arm/include/asm/global_data.h
    +++ b/arch/arm/include/asm/global_data.h
    @@ -47,6 +47,6 @@ struct arch_global_data {
     
     #include <asm-generic/global_data.h>
     
    -#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
    +#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9")
     
     #endif /* __ASM_GBL_DATA_H */
    diff --git a/arch/arm/lib/crt0.S b/arch/arm/lib/crt0.S
    index 960d12e732..ac54b9359a 100644
    --- a/arch/arm/lib/crt0.S
    +++ b/arch/arm/lib/crt0.S
    @@ -69,7 +69,7 @@ ENTRY(_main)
            bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
            sub     sp, #GD_SIZE    /* allocate one GD above SP */
            bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
    -       mov     r8, sp          /* GD is above SP */
    +       mov     r9, sp          /* GD is above SP */
            mov     r0, #0
            bl      board_init_f
     
    @@ -81,15 +81,15 @@ ENTRY(_main)
      * 'here' but relocated.
      */
     
    -       ldr     sp, [r8, #GD_START_ADDR_SP]     /* sp = gd->start_addr_sp */
    +       ldr     sp, [r9, #GD_START_ADDR_SP]     /* sp = gd->start_addr_sp */
            bic     sp, sp, #7      /* 8-byte alignment for ABI compliance */
    -       ldr     r8, [r8, #GD_BD]                /* r8 = gd->bd */
    -       sub     r8, r8, #GD_SIZE                /* new GD is below bd */
    +       ldr     r9, [r9, #GD_BD]                /* r9 = gd->bd */
    +       sub     r9, r9, #GD_SIZE                /* new GD is below bd */
     
            adr     lr, here
    -       ldr     r0, [r8, #GD_RELOC_OFF]         /* r0 = gd->reloc_off */
    +       ldr     r0, [r9, #GD_RELOC_OFF]         /* r0 = gd->reloc_off */
            add     lr, lr, r0
    -       ldr     r0, [r8, #GD_RELOCADDR]         /* r0 = gd->relocaddr */
    +       ldr     r0, [r9, #GD_RELOCADDR]         /* r0 = gd->relocaddr */
            b       relocate_code
     here:
     
    @@ -111,8 +111,8 @@ clbss_l:cmp r0, r1                  /* while not at end of BSS */
            bl red_led_on
     
            /* call board_init_r(gd_t *id, ulong dest_addr) */
    -       mov     r0, r8                  /* gd_t */
    -       ldr     r1, [r8, #GD_RELOCADDR] /* dest_addr */
    +       mov     r0, r9                  /* gd_t */
    +       ldr     r1, [r9, #GD_RELOCADDR] /* dest_addr */
            /* call board_init_r */
            ldr     pc, =board_init_r       /* this is auto-relocated! */
    

    啟動慢問題

    問題簡述

    填了幾個坑之後,新的uboot可以啟動到內核了,但發現啟動速度非常慢,內核啟動速度慢了接近10倍!明明是同一個內核,為什麼差異這麼大。

    排查寄存器

    初步排查了下設備樹配置,以及uboot跳轉內核前的一些關鍵寄存器,確實在兩個版本的uboot中有所不同,但具體去看這些不同,發現都不會影響速度,將一些驅動對齊之後寄存器差異基本就消失了。

    差異的分界

    那再細看,kernel的速度有差異,uboot呢?在哪個時間點之後,速度開始產生差異?

    嘗試在兩個版本的uboot中插入一些操作,對比時間戳,發現兩個uboot在某個節點之後的速度確實有區別。

    進一步排查,原來是在打開cache操作之後,舊uboot的速度就會比新uboot快。嘗試將舊ubootcache關掉,則二者基本一致。嘗試將舊uboot操作cache的代碼,移植到新uboot,未發生改變。

    此時可確認新uboot的開cache有問題。但覺得這個跟kernel啟動慢沒關係。因為uboot進入kernel之前都會關cache,由kernel自己去重新打開。

    也就是不管是用哪份uboot,也不管uboot中是否開了cache,對kernel階段都應該沒有影響才對。

    於是記錄下來uboot的這個問題,待後續修復。先繼續找kernel啟動慢的原因。(注:現在看來當時的做法是有問題的,這裏的異常這麼明顯,應該設法追蹤下去找出原因才對)

    鎖定uboot

    uboot的嫌疑非常大,但還不能完全確認,因為uboot之前還有一級spl。是否會是spl的問題呢?

    嘗試改用新spl+舊uboot,啟動速度正常。而新spl+新uboot的啟動速度則很慢,其他因素都不變,說明問題確實出在uboot階段。

    多做or少做

    當時到這一步就卡住了,直接比較兩份uboot的代碼不太現實,差異太大了。

    後來我就給自己提了個問題,到底新uboot是多做了某件事情,還是少做了某件事情?

    換個說法,目前已知

    spl --> 舊uboot --> kernel(速度快)
    spl --> 新uboot --> kernel(速度快)
    

    但到底是以下的情況A還是情況B呢?

    A: spl(速度慢) --> 舊uboot(做了某個會提升速度的操作) --> kernel(速度快)
       spl(速度慢) --> 新uboot(少做了某個會提升速度的操作) --> kernel(速度慢)
    
    B: spl(速度快) --> 舊uboot(沒做特殊操作) --> kernel(速度快)
       spl(速度快) --> 新uboot(多做了某個會限制速度的操作) --> kernel(速度慢)
    

    為了驗證,我決定讓spl直接啟動內核,看看內核到底是快是慢。

    支持過程碰到了一些小問題

    1.spl沒有能力加載這麼大的kernel

    解決:此時不需要kernel能完全啟動,只需要能加載啟動一段,足以體現出啟動速度是否正常即可,於是裁剪出一個非常小kernel來輔助實驗。

    2.kernel需要dtb

    解決:內核有一個CONFIG_BUILD_ARM_APPENDED_DTB_IMAGE選項。選上重新編譯。編譯后再用ddkerneldtb拼接到一起,作為新的kernel。這樣,spl就只需要加載一個文件並跳轉過去即可。

    試驗結果,spl啟動的kernel和使用新uboot啟動的kernel速度一致,均比舊uboot啟動的kernel慢。

    說明,舊uboot中做了某個關鍵操作,而新uboot沒做。

    找出關鍵操作

    那接下來的任務就是,找出舊uboot中的這個關鍵操作了。

    怎麼找呢?有了上一步的成果,我們可以使用以下方法來排查

    1. spl加載kernel和舊uboot

    2. spl跳轉到舊uboot,此時kernel其實已經在dram中準備好了,隨時可以啟動

    3. 在舊uboot的啟動流程各個階段,嘗試直接跳轉到kernel,觀察啟動速度

    4. 如果在舊ubootA點跳轉kernel啟動慢,B點跳轉啟動快,則說明關鍵操作位於AB點之間。

    方法有了,很快就鎖定到start.S,進一步在start.S中揪出了這段代碼

    #if defined(CONFIG_ARM_A7)
    @set SMP bit
        mrc     p15, 0, r0, c1, c0, 1
        orr        r0, r0, #(1<<6)
        mcr        p15, 0, r0, c1, c0, 1
    #endif
    

    ubootstart.S中沒有這段代碼,嘗試在新ubootstart.S中添加此操作,速度立馬恢復正常了。

    再全局搜索下,原來這個新版本uboot中,套路是在board_init中進行此項設置的,而這個平台從舊版本移植過來,就沒有設置 SMP bit, 補上即可。

    SMP bit是什麼

    SMP 是指對稱多處理器,看起來這個 bit 會影響多核的 cache一致性,此處沒有再深入研究。

    但可以知道,對於單處理器的情況,也需要設置這個bit才能正常使用cache

    貼下arm的圖和描述:

    [6]	SMP	
    
    Signals if the Cortex-A9 processor is taking part in coherency or not.
    
    In uniprocessor configurations, if this bit is set, then Inner Cacheable Shared is treated as Cacheable. The reset value is zero.
    

    搜下kernel的代碼,發現也是有地方調用了的。不過這個芯片是單核的,根本就沒配置CONFIG_SMP

    #ifdef CONFIG_SMP
    	ALT_SMP(mrc	p15, 0, r0, c1, c0, 1)
    	ALT_UP(mov	r0, #(1 << 6))		@ fake it for UP
    	tst	r0, #(1 << 6)			@ SMP/nAMP mode enabled?
    	orreq	r0, r0, #(1 << 6)		@ Enable SMP/nAMP mode
    	orreq	r0, r0, r10			@ Enable CPU-specific SMP bits
    	mcreq	p15, 0, r0, c1, c0, 1
    #endif
    

    總結

    整理出來一方面是記錄這兩個bug,另一方面也是想記錄下當時的一些操作。

    畢竟同樣的bug可能以後都不會碰到了,但解bug的方法和思路卻是可以積累復用的。

    blog: https://www.cnblogs.com/zqb-all/p/13172546.html
    公眾號:https://sourl.cn/shT3kz

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

    【其他文章推薦】

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

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

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

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

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

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

  • 手把手教你學Numpy,搞定數據處理——收官篇

    手把手教你學Numpy,搞定數據處理——收官篇

    本文始發於個人公眾號:TechFlow,原創不易,求個關注

    今天是Numpy專題第6篇文章,我們一起來看看Numpy庫當中剩餘的部分。

    數組的持久化

    在我們做機器學習模型的研究或者是學習的時候,在完成了訓練之後,有時候會希望能夠將相應的參數保存下來。否則的話,如果是在Notebook當中,當Notebook關閉的時候,這些值就丟失了。一般的解決方案是將我們需要的值或者是數組“持久化”,通常的做法是存儲在磁盤上。

    Python當中讀寫文件稍稍有些麻煩,我們還需要創建文件句柄,然後一行行寫入,寫入完成之後需要關閉句柄。即使是用with語句,也依然不夠簡便。針對這個問題,numpy當中自帶了寫入文件的api,我們直接調用即可。

    通過numpy當中save的文件是二進制格式的,所以我們是無法讀取其中內容的,即使強行打開也會是亂碼。

    以二進制的形式存儲數據避免了數據類型轉化的過程,尤其是numpy底層的數據是以C++實現的,如果使用Python的文件接口的話,勢必要先轉化成Python的格式,這會帶來大量開銷。既然可以存儲,自然也可以讀取,我們可以調用numpy的load函數將numpy文件讀取進來。

    要注意我們保存的時候沒有添加文件後綴,numpy會自動為我們添加後綴,但是讀取的時候必須要指定文件的全名,否則會numpy無法找到,會引發報錯。

    不僅如此,numpy還支持我們同時保存多個數組進入一個文件當中。

    我們使用savez來完成,在這個api當中我們傳入了a=arr,b=arr,其實是以類似字典的形式傳入的。在文件當中,numpy會將變量名和數組的值映射起來。這樣我們在讀入的時候,就可以通過變量名訪問到對應的值了。

    如果要存儲的數據非常大的話,我們還可以對數據進行壓縮,我們只需要更換savez成savez_compressed即可。

    線性代數

    Numpy除了科學計算之外,另外一大強大的功能就是支持矩陣運算,這也是它廣為流行並且在機器學習當中大受歡迎的原因之一。我們在之前的線性代數的文章當中曾經提到過Numpy這方面的一些應用,我們今天再在這篇文章當中匯總一些常用的線性代數的接口。

    點乘

    說起來矩陣點乘應該是最常用的線代api了,比如在神經網絡當中,如果拋開激活函數的話,一層神經元對於當前數據的影響,其實等價於特徵矩陣點乘了一個係數矩陣。再比如在邏輯回歸當中,我們計算樣本的加權和的時候,也是通過矩陣點乘來實現的。

    在Andrew的深度學習課上,他曾經做過這樣的實現,對於兩個巨大的矩陣進行矩陣相乘的運算。一次是通過Python的循環來實現,一次是通過Numpy的dot函數實現,兩者的時間開銷相差了足足上百倍。這當中的效率差距和Python語言的特性以及併發能力有關,所以在機器學習領域當中,我們總是將樣本向量化或者矩陣化,通過點乘來計算加權求和,或者是係數相乘。

    在Numpy當中我們採用dot函數來計算兩個矩陣的點積,既可以寫成a.dot(b),也可以寫成np.dot(a, b)。一般來說我更加喜歡前者,因為寫起來更加方便清晰。如果你喜歡後者也問題不大,這個只是個人喜好。

    注意不要寫成*,這個符號代表兩個矩陣元素兩兩相乘,而不是進行點積運算。它等價於np當中的multiply函數。

    轉置與逆矩陣

    轉置我們曾經在之前的文章當中提到過,可以通過.T或者是np.transpose來完成。

    Numpy中還提供了求解逆矩陣的操作,這個函數在numpy的linalg路徑下,這個路徑下實現了許多常用的線性代數函數。根據線性代數當中的知識,只有滿秩的方陣才有逆矩陣。我們可以通過numpy.linalg.det先來計算行列式來判斷,否則如果直接調用的話,對於沒有逆矩陣的矩陣會報錯。

    在這個例子當中,由於矩陣b的行列式為0,說明它並不是滿秩的,所以我們求它的逆矩陣會報錯。

    除了這些函數之外,linalg當中還封裝了其他一些常用的函數。比如進行qr分解的qr函數,進行奇異值分解的svd函數,求解線性方程組的solve函數等。相比之下,這些函數的使用頻率相對不高,所以就不展開一一介紹了,我們可以用到的時候再去詳細研究。

    隨機

    Numpy當中另外一個常用的領域就是隨機數,我們經常使用Numpy來生成各種各樣的隨機數。這一塊在Numpy當中其實也有很多的api以及很複雜的用法,同樣,我們不過多深入,挑其中比較重要也是經常使用的和大家分享一下。

    隨機數的所有函數都在numpy.random這個路徑下,我們為了簡化,就不寫完整的路徑了,大家記住就好。

    randn

    這個函數我們經常在代碼當中看到,尤其是我們造數據的時候。它代表的是根據輸入的shape生成一批均值為0,標準差為1的正態分佈的隨機數。

    要注意的是,我們傳入的shape不是一個元組,而是每一維的大小,這一點和其他地方的用法不太一樣,需要注意一下。除了正態分佈的randn之外,還有均勻分佈的uniform和Gamma分佈的gamma,卡方分佈的chisquare。

    normal

    normal其實也是生成正態分佈的樣本值,但不同的是,它支持我們指定樣本的均值和標準差。如果我們想要生成多個樣本,還可以在size參數當中傳入指定的shape。

    randint

    顧名思義,這個函數是用來生成隨機整數的。它接受傳入隨機數的上下界,最少也要傳入一個上界(默認下界是0)。

    如果想要生成多個int,我們可以在size參數傳入一個shape,它會返回一個對應大小的數組,這一點和uniform用法一樣。

    shuffle

    shuffle的功能是對一個數組進行亂序,返回亂序之後的結果。一般用在機器學習當中,如果存在樣本聚集的情況,我們一般會使用shuffle進行亂序,避免模型受到樣本分佈的影響。

    shuffle是一個inplace的方法,它會在原本值上進行改動,而不會返回一個新值。

    choice

    這也是一個非常常用的api,它可以在數據當中抽取指定條數據。

    但是它只支持一維的數組,一般用在批量訓練的時候,我們通過choice採樣出樣本的下標,再通過數組索引去找到這些樣本的值。比如這樣:

    總結

    今天我們一起研究了Numpy中數據持久化、線性代數、隨機數相關api的使用方法,由於篇幅的限制,我們只是選擇了其中比較常用,或者是比較重要的用法,還存在一些較為冷門的api和用法,大家感興趣的可以自行研究一下,一般來說文章當中提到的用法已經足夠了。

    今天這篇是Numpy專題的最後一篇了,如果你堅持看完本專題所有的文章,那麼相信你對於Numpy包一定有了一個深入的理解和認識了,給自己鼓鼓掌吧。之後周四會開啟Pandas專題,敬請期待哦。

    如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

    本文使用 mdnice 排版

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

    【其他文章推薦】

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

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

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

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

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

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

  • JAVA設計模式 2【創建型】原型模式的理解與使用、理解淺克隆和深克隆

    JAVA設計模式 2【創建型】原型模式的理解與使用、理解淺克隆和深克隆

    在本節中,我們將學習和使用原型模式;這一節學習的原型模式也是創建型 模式的其中之一。再次複習一下:創建型 模式就是描述如何去更好的創建一個對象。

    我們都知道,在JAVA 語言中。使用new 關鍵字創建一個新對象。將新的對象放到堆內存 裏面。當然,這個內存肯定是有大小限制的,況且,JAVA 不同於C語言等。 有內存管理機制,就是我們常說的垃圾回收器GC,才可以保證內存不被溢出。

    說這些其實就是為了表示:為啥要用單例模式,能節省內存的時候,能用一個對象解決重複的事情,絕對不會創建多個。

    概述

    原型模式描述的如何快速創建重複的對象,並且減少new 關鍵字的使用。

    • 抽象原型類
    • 具體原型類
    • 訪問類

    容我來一個一個解釋:

    抽象原型類 也就是我們具體要實現的某個類,這個類在JAVA 裏面是有具體的接口的,其實是一個空接口,Cloneable

     * @author  unascribed
     * @see     java.lang.CloneNotSupportedException
     * @see     java.lang.Object#clone()
     * @since   JDK1.0
     */
    public interface Cloneable {
    }
    

    我們會發現,這個類沒有任何的方法,怎麼來實現它,不要慌。先接着走。

    具體原型類 也就是我們具體要克隆 的對象。比如我們重複的要創建100個學生Student 對象,那麼具體的學生對象就是具體原型類

    public class Student implements Cloneable {
    
        private int id;
    
        private String name;
    
        private int sex;
    }
    

    訪問類 我就不必多說了

    淺克隆和深克隆

    原型模式其實也分淺克隆和深克隆。如何理解這兩個概念呢?

    淺克隆

    protected native Object clone() throws CloneNotSupportedException;
    

    淺克隆,只需要具體原型類 實現Cloneable 接口,並且重寫父類Object類的clone() 方法,即可實現對象的淺克隆。

    Student student1 = new Student(1, "李四");
    Student student2 = student1.clone();
    
    System.out.println(student1);
    System.out.println(student2);
    
    System.out.println(student1 == student2);
    ---------------------
    學號:1,姓名:李四
    學號:1,姓名:李四
    false
    
    • 通過執行clone() 方法即可創建一個相同的,具有同樣屬性的對象。
    • 並且是新的對象,內存地址有所不同。

    我們來看看,對於引用類型的變量,淺克隆是否可以進行克隆;

    Teacher teacher = new Teacher(1, "張老師");
    
    Student student1 = new Student(1, "李四", teacher);
    Student student2 = student1.clone();
    
    System.out.println(student1);
    System.out.println(student2);
    
    System.out.println(student1 == student2);
    ------------
    學號:1,姓名:李四,老師=Teacher@1b6d3586
    學號:1,姓名:李四,老師=Teacher@1b6d3586
    false
    

    我們發現,引用類型並沒有被克隆,也就是說:

    特點

    • 淺克隆對於基本類型,可以進行完全的克隆,並且克隆的對象是一個新的對象
    • 但是對象裏面的引用,是無法被克隆的。

    深克隆(序列化)

    何謂序列化?

    我們創建的都是保存在內存裏面的,只要被虛擬機GC進行回收,那麼這個對象的任何屬性都是消失,我們能不能找一個方法,將內存中這種對象的屬性以及對象的狀態通過某種東西保存下來,比如保存到數據庫,下次從數據庫將這個對象還原到內存裏面。 這就是序列化。

    • 序列化 內存對象->序列字符
    • 反序列化 序列字符->內存對象

    請參考: https://baike.baidu.com/item/序列化/2890184

    JAVA 序列化

     * @see java.io.Externalizable
     * @since   JDK1.1
     */
    public interface Serializable {
    }
    

    JAVA 提供了一個空接口,其實這個接口和上面的Cloneable 一樣,都是一個空接口,其實這個空接口就是作為一種標識 你的對象實現了這個接口,JAVA 認為你的這個就可以被序列化 ,就是這麼簡單。

    Teacher teacher = new Teacher(1, "張老師");
    
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ObjectOutputStream stream = new ObjectOutputStream(outputStream);
    
    stream.writeObject(teacher);
    System.out.println(Arrays.toString(outputStream.toByteArray()));
    ----------
    [-84, -19, 0, 5, 115, 114, 0, 7, 84, 101, 97,。。。。。。
    

    通過將對象序列化、其實也就是將內存中的對象轉化為二進制 字節數組

    反序列化

    Teacher teacher = new Teacher(1, "張老師");
    System.out.println(teacher);
    
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ObjectOutputStream stream = new ObjectOutputStream(outputStream);
    
    stream.writeObject(teacher);
    System.out.println(Arrays.toString(outputStream.toByteArray()));
    
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(outputStream.toByteArray());
    ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);
    
    Teacher teacher1 = (Teacher) inputStream.readObject();
    System.out.println(teacher1);
    ---------------
    id=1,name=張老師
    [-84, -19, 0, 5, 115, xxxxx,-127, -27, -72, -120]
    id=1,name=張老師
    

    通過序列化和反序列化,即可對象的深克隆

    小結

    這一節,在講述 原型模式的同時,將原有實現原型模式的clone() 淺克隆,延伸到深克隆這一概念。其實JAVA 的原型模式,實現起來較為簡單。但還是要按需要實現,Object 類提供的 clone 淺克隆 是沒辦法克隆對象的引用類型的。需要克隆引用類型,還是需要序列化 深克隆

    參考

    http://c.biancheng.net/view/1343.html
    https://www.liaoxuefeng.com/wiki/1252599548343744/1298366845681698

    代碼示例

    https://gitee.com/mrc1999/Dev-Examples

    歡迎關注

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

    【其他文章推薦】

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

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

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

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

    ※超省錢租車方案

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

  • Java多線程之內存模型

    Java多線程之內存模型

    目錄

    • 多線程需要解決的問題
      • 線程之間的通信
      • 線程之間的同步
    • Java內存模型
      • 內存間的交互操作
      • 指令屏障
      • happens-before規則
    • 指令重排序
      • 從源程序到字節指令的重排序
      • as-if-serial語義
      • 程序順序規則
    • 順序一致性模型
      • 順序一致性模型特性
      • 順序一致性模型特性
      • 當程序未正確同步會發生什麼
    • 參考資料

    多線程需要解決的問題

    在多線程編程中,線程之間如何通信和同步是一個必須解決的問題:

    線程之間的通信:

    線程之間有兩種通信的方式:消息傳遞和共享內存

    • 共享內存:線程之間共享程序的公共狀態,通過讀——寫修改公共狀態進行隱式通信。如上面代碼中的numLock可以被理解為公共狀態
    • 消息傳遞:線程之間沒有公共狀態,必須通過發送消息來進行显示通信
      在java中,線程是通過共享內存來完成線程之間的通信

    線程之間的同步:

    同步指程序中永固空值不同線程間的操作發生的相對順序的機制

    • 共享內存:同步是显示進行的,程序員需要指定某個方法或者某段代碼需要在線程之間互斥執行。如上面代碼中的Lock加鎖和解鎖之間的代碼塊,或者被synchronized包圍的代碼塊
    • 消息傳遞:同步是隱式執行的,因為消息的發送必然發生在消息的接收之前,例如使用Objetc#notify(),喚醒的線程接收信號一定在發送喚醒信號的發送之後。

    Java內存模型

    在java中,所有的實例域,靜態域、數組都被存儲在堆空間當中,堆內存在線程之間共享。

    所有的局部變量,方法定義參數和異常處理器參數不會被線程共享,在每個線程棧中獨享,他們不會存在可見性和線程安全問題。

    從Java線程模型(JMM)的角度來看,線程之間的共享變量存儲在主內存當中,每個線程擁有一個私有的本地內存(工作內存)本地內存存儲了該線程讀——寫共享的變量的副本。
    JMM只是一個抽象的概念,在現實中並不存在,其中所有的存儲區域都在堆內存當中。JMM的模型圖如下圖所示:

    而java線程對於共享變量的操作都是對於本地內存(工作內存)中的副本的操作,並沒有對共享內存中原始的共享變量進行操作;

    以線程1和線程2為例,假設線程1修改了共享變量,那麼他們之間需要通信就需要兩個步驟:

    1. 線程1本地內存中修改過的共享變量的副本同步到共享內存中去
    2. 線程2從共享內存中讀取被線程1更新過的共享變量
      這樣才能完成線程1的修改對線程2的可見。

    內存間的交互操作

    為了完成這一線程之間的通信,JMM為內存間的交互操作定義了8個原子操作,如下錶:

    操作 作用域 說明
    lock(鎖定) 共享內存中的變量 把一個變量標識為一條線程獨佔的狀態
    unlock(解鎖) 共享內存中的變量 把一個處於鎖定的變量釋放出來,釋放后其他線程可以進行訪問
    read(讀取) 共享內存中的變量 把一個變量的值從共享內存傳輸到線程的工作內存。供隨後的load操作使用
    load(載入) 工作內存 把read操作從共享內存中得到的變量值放入工作內存的變量副本當中
    use(使用) 工作內存 把工作內存中的一個變量值傳遞給執行引擎
    assign(賦值) 工作內存 把一個從執行引擎接受到的值賦值給工作內存的變量
    store(存儲) 作用於工作內存 把一個工作內存中的變量傳遞給共享內存,供後續的write使用
    write(寫入) 共享內存中的變量 把store操作從工作內存中得到的變量的值放入主內存

    JMM規定JVM四線時必須保證上述8個原子操作是不可再分割的,同時必須滿足以下的規則:

    1. 不允許readloadstorewrite操作之一單獨出現,即不允許只從共享內存讀取但工作內存不接受,或者工作捏村發起回寫但是共享內存不接收
    2. 不允許一個線程捨棄assign操作,即當一個線程修改了變量后必須寫回工作內存和共享內存
    3. 不允許一個線程將未修改的變量值寫回共享內存
    4. 變量只能從共享內存中誕生,不允許線程直接使用未初始化的變量
    5. 一個變量同一時刻只能由一個線程對其執行lock操作,但是一個變量可以被同一個線程重複執行多次lock,但是需要相同次數的unlock
    6. 如果對一個變量執行lock操作,那麼會清空工作內存中此變量的值,在執行引擎使用這個變量之前需要重新執行load和assign
    7. 不允許unlock一個沒有被鎖定的變量,也不允許unlock一個其他線程lock的變量
    8. 對一個變量unlock之前必須把此變量同步回主存當中。

    longdouble的特殊操作
    在一些32位的處理器上,如果要求對64位的longdouble的寫具有原子性,會有較大的開銷,為了照固這種情況,
    java語言規範鼓勵但不要求虛擬機對64位的longdouble型變量的寫操作具有原子性,當JVM在這種處理器上運行時,
    可能會把64位的long和double拆分成兩次32位的寫

    指令屏障

    為了保證內存的可見性,JMM的編譯器會禁止特定類型的編譯器重新排序;對於處理器的重新排序,
    JMM會要求編譯器在生成指令序列時插入特定類型的的內存屏障指令,通過內存屏障指令巾紙特定類型的處理器重新排序

    JMM規定了四種內存屏障,具體如下:

    屏障類型 指令示例 說明
    LoadLoad Barriers Load1;LoadLoad;Load2 確保Load1的數據先於Load2以及所有後續裝在指令的裝載
    StoreStore Barries Store1;StoreStore;Store2 確保Store1數據對於其他處理器可見(刷新到內存)先於Store2及後續存儲指令的存儲
    LoadStore Barriers Load1;LoadStore;Store2 確保Load1的裝載先於Store2及後續所有的存儲指令
    StoreLoad Barrier Store1;StoreLoad;Load2 確保Store1的存儲指令先於Load1以及後續所所有的加載指令

    StoreLoad是一個“萬能”的內存屏障,他同時具有其他三個內存屏障的效果,現代的處理器大都支持該屏障(其他的內存屏障不一定支持),
    但是執行這個內存屏障的開銷很昂貴,因為需要將處理器緩衝區所有的數據刷回內存中。

    happens-before規則

    在JSR-133種內存模型種引入了happens-before規則來闡述操作之間的內存可見性。在JVM種如果一個操作的結果過需要對另一個操作可見,
    那麼兩個操作之間必然要存在happens-bsfore關係:

    • 程序順序規則:一個線程中的個每個操作,happens-before於該線程的後續所有操作
    • 監視器鎖規則:對於一個鎖的解鎖,happens-before於隨後對於這個鎖的加鎖
    • volatitle變量規則:對於一個volatile的寫,happens-before於認意後續對這個volatile域的讀
    • 線程啟動原則:對線程的start()操作先行發生於線程內的任何操作
    • 線程終止原則:線程中的所有操作先行發生於檢測到線程終止,可以通過Thread.join()、Thread.isAlive()的返回值檢測線程是否已經終止
    • 線程終端原則:對線程的interrupt()的調用先行發生於線程的代碼中檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測是否發生中斷
    • 對象終結原則:一個對象的初始化完成(構造方法執行結束)先行發生於它的finalize()方法的開始。
    • 傳遞性:如果A happens-before B B happends-beforeC,那麼A happends-before C

    指令重排序

    從源程序到字節指令的重排序

    眾所周知,JVM執行的是字節碼,Java源代碼需要先編譯成字節碼程序才能在Java虛擬機中運行,但是考慮下面的程序;

    int a = 1;
    int b = 1;
    

    在這段代碼中,ab沒有任何的相互依賴關係,因此完全可以先對b初始化賦值,再對a變量初始化賦值;

    事實上,為了提高性能,編譯器和處理器通常會對指令做重新排序。重排序分為3種:

    1. 編譯器優化的重排序。編譯器在不改變單線程的程序語義的前提下,可以安排字語句的執行順序。編譯器的對象是語句,不是字節碼,
      但是反應的結果就是編譯后的字節碼和寫的語句順序不一致。
    2. 執行級并行的重排序。現代處理器採用了并行技術,來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
    3. 內存系統的重排序,由於處理器使用了緩存和讀/寫緩衝區,這使得加載和存儲操作看上去可能是在亂序執行。

    數據依賴性:如果兩個操作訪問同一個變量,且兩個操作有一個是寫操作,則這兩個操作存在數據依賴性,改變這兩個操作的執行順序,就會改變執行結果。

    儘管指令重排序會提高代碼的執行效率,但是卻為多線程編程帶來了問題,多線程操作共享變量需要一定程度上遵循代碼的編寫順序,
    也需要將修改的共享數據存儲到共享內存中,不按照代碼順序執行可能會導致多線程程序出現內存可見性的問題,那又如何實現呢?

    as-if-serial語義

    as-if-serial語義:不論程序怎樣進行重排序,(單線程)程序的執行結果不能被改變。編譯器、runtime和處理器都必須支持as-if-serial語義。

    程序順序規則

    假設存在以下happens-before程序規則:

        1) A happens-before B
        2) B happens-before C
        3) A happens-before C
    

    儘管這裏存在A happens-before B這一關係,但是JMM並不要求A一定要在B之前執行,僅僅要求A的執行結果對B可見。
    即JMM僅要求前一個操作的結果對於后一個操作可見,並且前一個操作按照順序排在後一個操作之前。
    但是若前一個操作放在後一個操作之後執行並不影響執行結果,則JMM認為這並不違法,JMM允許這種重排序。

    順序一致性模型

    在一個線程中寫一個變量,在另一個線程中同時讀取這個變量,讀和寫沒有通過排序來同步來排序,就會引發數據競爭。

    數據競爭的核心原因是程序未正確同步。如果一個多線程程序是正確同步的,這個程序將是一個沒有數據競爭的程序。

    順序一致性模型只是一個參考模型。

    順序一致性模型特性

    • 一個線程中所有的操作必須按照程序的順序來執行。
    • 不管線程是否同步,所有的線程都只能看到一個單一的執行順序。

    在順序一致性模型中每個曹祖都必須原子執行且立刻對所有線程可見。

    當程序未正確同步會發生什麼

    當線程未正確同步時,JMM只提供最小的安全性,當讀取到一個值時,這個值要麼是之前寫入的值,要麼是默認值。
    JMM保證線程的操作不會無中生有。為了保證這一特點,JMM在分配對象時,首先會對內存空間清0,然後才在上面分配對象。

    未同步的程序在JMM種執行時,整體上是無序的,執行結果也無法預知。位同步程序子兩個模型中執行特點有如下幾個差異:

    • 順序一致性模型保證單線程內的操作會按照程序的順序執行,而JMM不保證單線程內的操作會按照程序的順序執行
    • 順序一致性模型保證所有線程只能看到一致的操作執行順序,而JMM不保證所有線程能看到一致的操作執行順序
    • JMM不保證對64位的longdouble型變量具有寫操作的原子性,而順序一致性模型保證對所有的內存的讀/寫操作都具有原子性

    參考資料

    java併發編程的藝術-方騰飛,魏鵬,程曉明著

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

  • 【Mongodb】 可複製集搭建

    【Mongodb】 可複製集搭建

    可複製集 replica set

    概念圖

    可複製集需要至少3個以上的mongodb節點,其中有一個主節點promary,其餘的為副本節點secondary

    可複製集有三個角色:

    • 主要成員(Primary):主要接收所有寫操作。就是主節點。
    • 副本成員(Secondary):從主節點通過複製操作以維護相同的數據集,即備份數據,不可寫操作,但可以讀操作(但需要配置)。是默認的一種從節點類型。
    • 仲裁者(Arbiter):不保留任何數據的副本,只具有投票選舉作用。當然也可以將仲裁服務器維護為副本集的一部分,即副本成員同時也可以是仲裁者。也是一種從節點類型。

    關於仲裁者:
    如果主節點+副本節點是偶數推薦添加仲裁者,如果主節點+ 副本節點是奇數可以不添加仲裁者。仲裁者將永遠是仲裁者,而主要人員可能會退出並成為次要人員,而次要人員可能成為選舉期間的主要人員。

    為什麼要用可複製集?它有什麼重要性?

    1. 避免數據丟失,保障數據安全,提高系統安全性;
      (最少3節點,最大50節點)
    2. 自動化災備機制,主節點宕機后通過選舉產生新主機;提高系統健壯性;
      (7個選舉節點上限)
    3. 讀寫分離,負載均衡,提高系統性能;

    搭建

    準備三個mongodb節點

    正準備三個mongodb節點,我們先搭建一個主節點,2個副本節點的模式
    修改配置mongo.conf

    • 第一個
    systemLog:
      #MongoDB發送所有日誌輸出的目標指定為文件 
      destination: file
      #mongod或mongos應向其發送所有診斷日誌記錄信息的日誌文件的路徑 
      path: "/home/amber/mongodb/mongodb-001/log/mongod.log" 
      #當mongos或mongod實例重新啟動時,mongos或mongod會將新條目附加到現有日誌文件的末尾。 
      logAppend: true
    storage: 
      #mongod實例存儲其數據的目錄。storage.dbPath設置僅適用於mongod。 
      dbPath: "/home/amber/mongodb/mongodb-001/data/db" 
      journal:
        #啟用或禁用持久性日誌以確保數據文件保持有效和可恢復。 
        enabled: true
    processManagement:
      #啟用在後台運行mongos或mongod進程的守護進程模式。 
      fork: true 
      #指定用於保存mongos或mongod進程的進程ID的文件位置,其中mongos或mongod將寫入其PID 
      pidFilePath: "/home/amber/mongodb/mongodb-001/log/mongod.pid" 
    net:
      #服務實例綁定所有IP,有副作用,副本集初始化的時候,節點名字會自動設置為本地域名,而不是ip 
      #bindIpAll: true 
      #服務實例綁定的IP 
      bindIp: 0.0.0.0
      #bindIp 
      #綁定的端口 
      port: 27017
    replication: 
      #副本集的名稱 
      replSetName: myrs
    
    • 第二個第三個配置
      把上述文件中的mongodb-001換成mongodb-002``mongodb-003
      端口分別換成27018 27019

    然後分別在mongodb-00X的根目錄下執行啟動命令

    ./bin/mongod -f ./conf/mongod.conf
    

    檢查進程

    ps -ef|grep mongod
    

    設置主節點

    進入27017的那個mongod的客戶端,並且執行

    rs.initiate({
          _id: "myrs", //  需要和replSetName的名稱一致
          version: 1,
          members: [{ _id: 0, host : "192.168.xx.xx:27017" }]});
    

    或者

    rs.initiate({}) 
    

    執行結果

    提示:
    1)“ok”的值為1,說明創建成功。
    2)命令行提示符發生變化,變成了一個從節點角色,此時默認不能讀寫。稍等片刻,回車,變成主節  點。
    

    配置副本節點

    再27017的mongod客戶端,也就是主節點上執行192.168.xx.xx:27018是副本節點的ip和端口

    rs.add("192.168.xx.xx:27018")
    rs.add("192.168.xx.xx:27019")
    

    使用

    rs.status()
    

    就可以看到members會有三個節點了

    測試

    再主節點插入一條數據

    use article;
    db.comment.insert({name: "amber"})
    

    再從節點查看,結果

    這是因為需要再從節點再次進行

    rs.slaveok() // 確認當前節點是副本節點
    db.comment.find(); // 查看當前數據
    

    可以看到已經有數據了,這樣一主二從的可複製成功了

    如果關閉主節點,在從節點執行rs.status();
    可以看到原來的主節點的health變成了0

    27018變成了新的主節點

    主節點的選舉原則

    MongoDB在副本集中,主節點選舉的觸發條件:
    1) 主節點故障
    2) 主節點網絡不可達(默認心跳信息為10秒)
    3) 人工干預(rs.stepDown(600))

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • 【Spring註解驅動開發】使用InitializingBean和DisposableBean來管理bean的生命周期,你真的了解嗎?

    寫在前面

    在《【Spring註解驅動開發】如何使用@Bean註解指定初始化和銷毀的方法?看這一篇就夠了!!》一文中,我們講述了如何使用@Bean註解來指定bean初始化和銷毀的方法。具體的用法就是在@Bean註解中使用init-method屬性和destroy-method屬性來指定初始化方法和銷毀方法。除此之外,Spring中是否還提供了其他的方式來對bean實例進行初始化和銷毀呢?

    項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

    InitializingBean接口

    1.InitializingBean接口概述

    Spring中提供了一個InitializingBean接口,InitializingBean接口為bean提供了屬性初始化后的處理方法,它只包括afterPropertiesSet方法,凡是繼承該接口的類,在bean的屬性初始化后都會執行該方法。InitializingBean接口的源碼如下所示。

    package org.springframework.beans.factory;
    public interface InitializingBean {
    	void afterPropertiesSet() throws Exception;
    }
    

    根據InitializingBean接口中提供的afterPropertiesSet()方法的名字可以推斷出:afterPropertiesSet()方法是在屬性賦好值之後調用的。那到底是不是這樣呢?我們來分析下afterPropertiesSet()方法的調用時機。

    2.何時調用InitializingBean接口?

    我們定位到Spring中的org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory類下的invokeInitMethods()方法中,來查看Spring加載bean的方法。

    題外話:不要問我為什麼會是這個invokeInitMethods()方法,如果你和我一樣對Spring的源碼非常熟悉的話,你也會知道是這個invokeInitMethods()方法,哈哈哈哈!所以,小夥伴們不要只顧着使用Spring,還是要多看看Spring的源碼啊!Spring框架中使用了大量優秀的設計模型,其代碼的編寫規範和嚴謹程度也是業界開源框架中數一數二的,非常值得閱讀。

    我們來到AbstractAutowireCapableBeanFactory類下的invokeInitMethods()方法,如下所示。

    protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
        throws Throwable {
    	//判斷該bean是否實現了實現了InitializingBean接口,如果實現了InitializingBean接口,則調用bean的afterPropertiesSet方法
        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (logger.isTraceEnabled()) {
                logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
            }
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
                        //調用afterPropertiesSet()方法
                        ((InitializingBean) bean).afterPropertiesSet();
                        return null;
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
                //調用afterPropertiesSet()方法
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }
    
        if (mbd != null && bean.getClass() != NullBean.class) {
            String initMethodName = mbd.getInitMethodName();
            if (StringUtils.hasLength(initMethodName) &&
                !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                !mbd.isExternallyManagedInitMethod(initMethodName)) {
                //通過反射的方式調用init-method
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }
    

    分析上述代碼后,我們可以初步得出如下信息:

    • Spring為bean提供了兩種初始化bean的方式,實現InitializingBean接口,實現afterPropertiesSet方法,或者在配置文件和@Bean註解中通過init-method指定,兩種方式可以同時使用。
    • 實現InitializingBean接口是直接調用afterPropertiesSet()方法,比通過反射調用init-method指定的方法效率相對來說要高點。但是init-method方式消除了對Spring的依賴。
    • 如果調用afterPropertiesSet方法時出錯,則不調用init-method指定的方法。

    也就是說Spring為bean提供了兩種初始化的方式,第一種實現InitializingBean接口,實現afterPropertiesSet方法,第二種配置文件或@Bean註解中通過init-method指定,兩種方式可以同時使用,同時使用先調用afterPropertiesSet方法,后執行init-method指定的方法。

    DisposableBean接口

    1.DisposableBean接口概述

    實現org.springframework.beans.factory.DisposableBean接口的bean在銷毀前,Spring將會調用DisposableBean接口的destroy()方法。我們先來看下DisposableBean接口的源碼,如下所示。

    package org.springframework.beans.factory;
    public interface DisposableBean {
    	void destroy() throws Exception;
    }
    

    可以看到,在DisposableBean接口中只定義了一個destroy()方法。

    在Bean生命周期結束前調用destory()方法做一些收尾工作,亦可以使用destory-method。前者與Spring耦合高,使用類型強轉.方法名(),效率高。後者耦合低,使用反射,效率相對低

    2.DisposableBean接口注意事項

    多例bean的生命周期不歸Spring容器來管理,這裏的DisposableBean中的方法是由Spring容器來調用的,所以如果一個多例實現了DisposableBean是沒有啥意義的,因為相應的方法根本不會被調用,當然在XML配置文件中指定了destroy方法,也是沒有意義的。所以,在多實例bean情況下,Spring不會自動調用bean的銷毀方法。

    單實例bean案例

    創建一個Animal的類實現InitializingBean和DisposableBean接口,代碼如下:

    package io.mykit.spring.plugins.register.bean;
    
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.stereotype.Component;
    /**
     * @author binghe
     * @version 1.0.0
     * @description 測試InitializingBean接口和DisposableBean接口
     */
    public class Animal implements InitializingBean, DisposableBean {
        public Animal(){
            System.out.println("執行了Animal類的無參數構造方法");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("執行了Animal類的初始化方法。。。。。");
    
        }
        @Override
        public void destroy() throws Exception {
            System.out.println("執行了Animal類的銷毀方法。。。。。");
    
        }
    }
    

    接下來,我們新建一個AnimalConfig類,並將Animal通過@Bean註解的方式註冊到Spring容器中,如下所示。

    package io.mykit.spring.plugins.register.config;
    
    import io.mykit.spring.plugins.register.bean.Animal;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    /**
     * @author binghe
     * @version 1.0.0
     * @description AnimalConfig
     */
    @Configuration
    @ComponentScan("io.mykit.spring.plugins.register.bean")
    public class AnimalConfig {
        @Bean
        public Animal animal(){
            return new Animal();
        }
    }
    

    接下來,我們在BeanLifeCircleTest類中新增testBeanLifeCircle02()方法來進行測試,如下所示。

    @Test
    public void testBeanLifeCircle02(){
        //創建IOC容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
        System.out.println("IOC容器創建完成...");
        //關閉IOC容器
        context.close();
    }
    

    運行BeanLifeCircleTest類中的testBeanLifeCircle02()方法,輸出的結果信息如下所示。

    執行了Animal類的無參數構造方法
    執行了Animal類的初始化方法。。。。。
    IOC容器創建完成...
    執行了Animal類的銷毀方法。。。。。
    

    從輸出的結果信息可以看出:單實例bean下,IOC容器創建完成后,會自動調用bean的初始化方法;而在容器銷毀前,會自動調用bean的銷毀方法。

    多實例bean案例

    多實例bean的案例代碼基本與單實例bean的案例代碼相同,只不過在AnimalConfig類中,我們在animal()方法上添加了@Scope(“prototype”)註解,如下所示。

    package io.mykit.spring.plugins.register.config;
    import io.mykit.spring.plugins.register.bean.Animal;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Scope;
    /**
     * @author binghe
     * @version 1.0.0
     * @description AnimalConfig
     */
    @Configuration
    @ComponentScan("io.mykit.spring.plugins.register.bean")
    public class AnimalConfig {
        @Bean
        @Scope("prototype")
        public Animal animal(){
            return new Animal();
        }
    }
    

    接下來,我們在BeanLifeCircleTest類中新增testBeanLifeCircle03()方法來進行測試,如下所示。

    @Test
    public void testBeanLifeCircle03(){
        //創建IOC容器
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AnimalConfig.class);
        System.out.println("IOC容器創建完成...");
        System.out.println("-------");
        //調用時創建對象
        Object bean = ctx.getBean("animal");
        System.out.println("-------");
        //調用時創建對象
        Object bean1 = ctx.getBean("animal");
        System.out.println("-------");
        //關閉IOC容器
        ctx.close();
    }
    

    運行BeanLifeCircleTest類中的testBeanLifeCircle03()方法,輸出的結果信息如下所示。

    IOC容器創建完成...
    -------
    執行了Animal類的無參數構造方法
    執行了Animal類的初始化方法。。。。。
    -------
    執行了Animal類的無參數構造方法
    執行了Animal類的初始化方法。。。。。
    -------
    

    從輸出的結果信息中可以看出:在多實例bean情況下,Spring不會自動調用bean的銷毀方法。

    好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

    項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

    寫在最後

    如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

    【其他文章推薦】

    ※超省錢租車方案

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

    ※回頭車貨運收費標準

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

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

  • 基於領域驅動設計(DDD)超輕量級快速開發架構,ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net

    基於領域驅動設計(DDD)超輕量級快速開發架構,ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net

     

    smartadmin.core.urf 這個項目是基於asp.net core 3.1(最新)基礎上參照領域驅動設計(DDD)的理念,並參考目前最為了流行的abp架構開發的一套輕量級的快速開發web application 技術架構,專註業務核心需求,減少重複代碼,開始構建和發布,讓初級程序員也能開發出專業並且漂亮的Web應用程序

    域驅動設計(DDD)是一種通過將實現與不斷髮展的模型相連接來滿足複雜需求的軟件開發方法。域驅動設計的前提如下:

    • 將項目的主要重點放在核心領域和領域邏輯上;
    • 將複雜的設計基於領域模型;
    • 啟動技術專家和領域專家之間的創造性合作,以迭代方式完善解決特定領域問題的概念模型。

    最終的核心思想還是SOLID,只是實現的方式有所不同,ABP可能目前對DDD設計理念最好的實現方式。但對於小項目我還是更喜歡 URF.Core https://github.com/urfnet/URF.Core 這個超輕量級的實現。

    同時這個項目也就是我2年前的一個開源項目 ASP.NET MVC 5 SmartCode Scaffolding for Visual Studio.Net 的升級版,支持.net core.目前沒有把所有功能都遷移到.net core,其中最重要的就是代碼生成這塊。再接下來的時間里主要就是完善代碼生成的插件。當然也要看是否受歡迎,如果反應一般,我可能不會繼續更新。

    Demo 網站

     演示站點 
    賬號:demo 密碼:123456

    GitHub 源代碼 https://github.com/neozhu/smartadmin.core.urf

    喜歡請給個 Star 每一顆Star都是鼓勵我繼續更新的動力 謝謝
    如果你用於自己公司及盈利性的項目,希望給與金錢上的贊助,並且保留原作者的版權

    分層

    smartadmin.core.urf遵行DDD設計模式來實現應用程序的四層模型

    • 表示層(Presentation Layer):用戶操作展示界面,使用SmartAdmin – Responsive WebApp模板+Jquery EasyUI
    • 應用層(Application Layer):在表示層與域層之間,實現具體應用程序邏輯,業務用例,Project:StartAdmin.Service.csproj
    • 域層(Domain Layer):包括業務對象(Entity)和核心(域)業務規則,應用程序的核心,使用EntityFrmework Core Code-first + Repository實現
    • 基礎結構層(Infrastructure Layer):提供通用技術功能,這些功能主要有第三方庫來支持,比如日誌:Nlog,服務發現:Swagger UI,事件總線(EventBus):dotnetcore/CAP,認證與授權:Microsoft.AspNetCore.Identity,後面會具體介紹

    內容

     

    域層(Domain Layer)

    • 實體(Entity,BaseEntity) 通常實體就是映射到關係數據庫中的表,這裏說名一下最佳做法和慣例:
    1. 在域層定義:本項目就是(SmartAdmin.Entity.csproj)
    2. 繼承一個基類 Entity,添加必要審計類比如:創建時間,最後修改時間等
    3. 必須要有一個主鍵最好是GRUID(不推薦複合主鍵),但本項目使用遞增的int類型
    4. 字段不要過多的冗餘,可以通過定義關聯關係
    5. 字段屬性和方法盡量使用virtual關鍵字。有些ORM和動態代理工具需要

     

    • 存儲庫(Repositories) 封裝基本數據操作方法(CRUD),本項目應用 URF.Core實現
    • 域服務
    • 技術指標
    • 應用層

      • 應用服務:用於實現應用程序的用例。它們用於將域邏輯公開給表示層,從表示層(可選)使用DTO(數據傳輸對象)作為參數調用應用程序服務。它使用域對象執行某些特定的業務邏輯,並(可選)將DTO返回到表示層。因此,表示層與域層完全隔離。對應本項目:(SmartAdmin.Service.csproj)
      • 數據傳輸對象(DTO):用於在應用程序層和表示層或其他類型的客戶端之間傳輸數據,通常,使用DTO作為參數從表示層(可選)調用應用程序服務。它使用域對象執行某些特定的業務邏輯,並(可選)將DTO返回到表示層。因此,表示層與域層完全隔離.對應本項目:(SmartAdmin.Dto.csproj)
      • Unit of work:管理和控制應用程序中操作數據庫連接和事務 ,本項目使用 URF.Core實現
    • 基礎服務層

      • UI樣式定義:根據用戶喜好選擇多種頁面显示模式
      • 租戶管理:使用EntityFrmework Core提供的Global Filter實現簡單多租戶應用
      • 賬號管理: 對登錄系統賬號維護,註冊,註銷,鎖定,解鎖,重置密碼,導入、導出等功能
      • 角色管理:使用Microsoft身份庫管理角色,用戶及其權限管理
      • 導航菜單:系統主導航欄配置
      • 角色授權:配置角色显示的菜單
      • 鍵值對配置:常用的數據字典維護,如何正確使用和想法後面會介紹
      • 導入&導出配置:使用Excel導入導出做一個可配置的功能
      • 系統日誌:asp.net core 自帶的日誌+Nlog把所有日誌保存到數據庫方便查詢和分析
      • 消息訂閱:集中訂閱CAP分佈式事件總線的消息
      • WebApi: Swagger UI Api服務發現和在線調試工具
      • CAP: CAP看板查看發布和訂閱的消息

    快速上手開發

    • 開發環境
      • Visual Studio .Net 2019
      • .Net Core 3.1
      • Sql Server(LocalDb)
    • 附加數據庫

      使用SQL Server Management Studio 附加.\src\SmartAdmin.Data\db\smartadmindb.mdf 數據庫(如果是localdb,那麼不需要修改數據庫連接配置)

    • 打開解決方案

    第一個簡單的需求開始 
    新增 Company 企業信息 完成CRUD 導入導出功能

    • 新建實體對象(Entity)

    在SmartAdmin.Entity.csproj項目的Models目錄下新增一個Company.cs類

     1 //記住:定義實體對象最佳做法,繼承基類,使用virtual關鍵字,盡可能的定義每個屬性,名稱,類型,長度,校驗規則,索引,默認值等
     2 namespace SmartAdmin.Data.Models
     3 {
     4     public partial class Company : URF.Core.EF.Trackable.Entity
     5     {
     6         [Display(Name = "企業名稱", Description = "歸屬企業名稱")]
     7         [MaxLength(50)]
     8         [Required]
     9         //[Index(IsUnique = true)]
    10         public virtual string Name { get; set; }
    11         [Display(Name = "組織代碼", Description = "組織代碼")]
    12         [MaxLength(12)]
    13         //[Index(IsUnique = true)]
    14         [Required]
    15         public virtual string Code { get; set; }
    16         [Display(Name = "地址", Description = "地址")]
    17         [MaxLength(128)]
    18         [DefaultValue("-")]
    19         public virtual string Address { get; set; }
    20         [Display(Name = "聯繫人", Description = "聯繫人")]
    21         [MaxLength(12)]
    22         public virtual string Contect { get; set; }
    23         [Display(Name = "聯繫電話", Description = "聯繫電話")]
    24         [MaxLength(20)]
    25         public virtual string PhoneNumber { get; set; }
    26         [Display(Name = "註冊日期", Description = "註冊日期")]
    27         [DefaultValue("now")]
    28         public virtual  DateTime RegisterDate { get; set; }
    29     }
    30 }
    31 //在 SmartAdmin.Data.csproj 項目 SmartDbContext.cs 添加
    32 public virtual DbSet<Company> Companies { get; set; }

    View Code

    • 添加服務對象 Service

    在項目 SmartAdmin.Service.csproj 中添加ICompanyService.cs,CompanyService.cs 就是用來實現業務需求 用例的地方

      1 //ICompany.cs
      2 //根據實際業務用例來創建方法,默認的CRUD,增刪改查不需要再定義
      3 namespace SmartAdmin.Service
      4 {
      5   // Example: extending IService<TEntity> and/or ITrackableRepository<TEntity>, scope: ICustomerService
      6   public interface ICompanyService : IService<Company>
      7   {
      8     // Example: adding synchronous Single method, scope: ICustomerService
      9     Company Single(Expression<Func<Company, bool>> predicate);
     10     Task ImportDataTableAsync(DataTable datatable);
     11     Task<Stream> ExportExcelAsync(string filterRules = "", string sort = "Id", string order = "asc");
     12   }
     13 }
     14 // 具體實現接口的方法
     15 namespace SmartAdmin.Service
     16 {
     17   public class CompanyService : Service<Company>, ICompanyService
     18   {
     19     private readonly IDataTableImportMappingService mappingservice;
     20     private readonly ILogger<CompanyService> logger;
     21     public CompanyService(
     22       IDataTableImportMappingService mappingservice,
     23       ILogger<CompanyService> logger,
     24       ITrackableRepository<Company> repository) : base(repository)
     25     {
     26       this.mappingservice = mappingservice;
     27       this.logger = logger;
     28     }
     29 
     30     public async Task<Stream> ExportExcelAsync(string filterRules = "", string sort = "Id", string order = "asc")
     31     {
     32       var filters = PredicateBuilder.FromFilter<Company>(filterRules);
     33       var expcolopts = await this.mappingservice.Queryable()
     34              .Where(x => x.EntitySetName == "Company")
     35              .Select(x => new ExpColumnOpts()
     36              {
     37                EntitySetName = x.EntitySetName,
     38                FieldName = x.FieldName,
     39                IgnoredColumn = x.IgnoredColumn,
     40                SourceFieldName = x.SourceFieldName
     41              }).ToArrayAsync();
     42 
     43       var works = (await this.Query(filters).OrderBy(n => n.OrderBy(sort, order)).SelectAsync()).ToList();
     44       var datarows = works.Select(n => new
     45       {
     46         Id = n.Id,
     47         Name = n.Name,
     48         Code = n.Code,
     49         Address = n.Address,
     50         Contect = n.Contect,
     51         PhoneNumber = n.PhoneNumber,
     52         RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
     53       }).ToList();
     54       return await NPOIHelper.ExportExcelAsync("Company", datarows, expcolopts);
     55     }
     56 
     57     public async Task ImportDataTableAsync(DataTable datatable)
     58     {
     59       var mapping = await this.mappingservice.Queryable()
     60                         .Where(x => x.EntitySetName == "Company" &&
     61                            (x.IsEnabled == true || (x.IsEnabled == false && x.DefaultValue != null))
     62                            ).ToListAsync();
     63       if (mapping.Count == 0)
     64       {
     65         throw new  NullReferenceException("沒有找到Work對象的Excel導入配置信息,請執行[系統管理/Excel導入配置]");
     66       }
     67       foreach (DataRow row in datatable.Rows)
     68       {
     69 
     70         var requiredfield = mapping.Where(x => x.IsRequired == true && x.IsEnabled == true && x.DefaultValue == null).FirstOrDefault()?.SourceFieldName;
     71         if (requiredfield != null || !row.IsNull(requiredfield))
     72         {
     73           var item = new Company();
     74           foreach (var field in mapping)
     75           {
     76             var defval = field.DefaultValue;
     77             var contain = datatable.Columns.Contains(field.SourceFieldName ?? "");
     78             if (contain && !row.IsNull(field.SourceFieldName))
     79             {
     80               var worktype = item.GetType();
     81               var propertyInfo = worktype.GetProperty(field.FieldName);
     82               var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
     83               var safeValue = (row[field.SourceFieldName] == null) ? null : Convert.ChangeType(row[field.SourceFieldName], safetype);
     84               propertyInfo.SetValue(item, safeValue, null);
     85             }
     86             else if (!string.IsNullOrEmpty(defval))
     87             {
     88               var worktype = item.GetType();
     89               var propertyInfo = worktype.GetProperty(field.FieldName);
     90               if (string.Equals(defval, "now", StringComparison.OrdinalIgnoreCase) && (propertyInfo.PropertyType == typeof(DateTime) || propertyInfo.PropertyType == typeof(Nullable<DateTime>)))
     91               {
     92                 var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
     93                 var safeValue = Convert.ChangeType(DateTime.Now, safetype);
     94                 propertyInfo.SetValue(item, safeValue, null);
     95               }
     96               else if (string.Equals(defval, "guid", StringComparison.OrdinalIgnoreCase))
     97               {
     98                 propertyInfo.SetValue(item, Guid.NewGuid().ToString(), null);
     99               }
    100               else if (string.Equals(defval, "user", StringComparison.OrdinalIgnoreCase))
    101               {
    102                 propertyInfo.SetValue(item, "", null);
    103               }
    104               else
    105               {
    106                 var safetype = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
    107                 var safeValue = Convert.ChangeType(defval, safetype);
    108                 propertyInfo.SetValue(item, safeValue, null);
    109               }
    110             }
    111           }
    112           this.Insert(item);
    113         }
    114       }
    115     }
    116 
    117     // Example, adding synchronous Single method
    118     public Company Single(Expression<Func<Company, bool>> predicate)
    119     {
    120       
    121       return this.Repository.Queryable().Single(predicate);
    122 
    123     }
    124   }
    125 }

    View Code

    • 添加Controller

    MVC Controller

      1 namespace SmartAdmin.WebUI.Controllers
      2 {
      3   public class CompaniesController : Controller
      4   {
      5     private  readonly ICompanyService companyService;
      6     private readonly IUnitOfWork unitOfWork;
      7     private readonly ILogger<CompaniesController> _logger;
      8     private readonly IWebHostEnvironment _webHostEnvironment;
      9     public CompaniesController(ICompanyService companyService,
     10           IUnitOfWork unitOfWork,
     11           IWebHostEnvironment webHostEnvironment,
     12           ILogger<CompaniesController> logger)
     13     {
     14       this.companyService = companyService;
     15       this.unitOfWork = unitOfWork;
     16       this._logger = logger;
     17       this._webHostEnvironment = webHostEnvironment;
     18     }
     19 
     20     // GET: Companies
     21     public IActionResult Index()=> View();
     22     //datagrid 數據源
     23     public async Task<JsonResult> GetData(int page = 1, int rows = 10, string sort = "Id", string order = "asc", string filterRules = "")
     24     {
     25       try
     26       {
     27         var filters = PredicateBuilder.FromFilter<Company>(filterRules);
     28         var total = await this.companyService
     29                              .Query(filters)
     30                              .AsNoTracking()
     31                              .CountAsync()
     32                               ;
     33         var pagerows = (await this.companyService
     34                              .Query(filters)
     35                               .AsNoTracking()
     36                            .OrderBy(n => n.OrderBy(sort, order))
     37                            .Skip(page - 1).Take(rows)
     38                            .SelectAsync())
     39                            .Select(n => new
     40                            {
     41                              Id = n.Id,
     42                              Name = n.Name,
     43                              Code = n.Code,
     44                              Address = n.Address,
     45                              Contect = n.Contect,
     46                              PhoneNumber = n.PhoneNumber,
     47                              RegisterDate = n.RegisterDate.ToString("yyyy-MM-dd HH:mm:ss")
     48                            }).ToList();
     49         var pagelist = new { total = total, rows = pagerows };
     50         return Json(pagelist);
     51       }
     52       catch(Exception e) {
     53         throw e;
     54         }
     55 
     56     }
     57     //編輯 
     58     [HttpPost]
     59     [ValidateAntiForgeryToken]
     60     public async Task<JsonResult> Edit(Company company)
     61     {
     62       if (ModelState.IsValid)
     63       {
     64         try
     65         {
     66           this.companyService.Update(company);
     67 
     68           var result = await this.unitOfWork.SaveChangesAsync();
     69           return Json(new { success = true, result = result });
     70         }
     71          catch (Exception e)
     72         {
     73           return Json(new { success = false, err = e.GetBaseException().Message });
     74         }
     75       }
     76       else
     77       {
     78         var modelStateErrors = string.Join(",", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
     79         return Json(new { success = false, err = modelStateErrors });
     80         //DisplayErrorMessage(modelStateErrors);
     81       }
     82       //return View(work);
     83     }
     84     //新建
     85     [HttpPost]
     86     [ValidateAntiForgeryToken]
     87    
     88     public async Task<JsonResult> Create([Bind("Name,Code,Address,Contect,PhoneNumber,RegisterDate")] Company company)
     89     {
     90       if (ModelState.IsValid)
     91       {
     92         try
     93         {
     94           this.companyService.Insert(company);
     95        await this.unitOfWork.SaveChangesAsync();
     96           return Json(new { success = true});
     97         }
     98         catch (Exception e)
     99         {
    100           return Json(new { success = false, err = e.GetBaseException().Message });
    101         }
    102 
    103         //DisplaySuccessMessage("Has update a Work record");
    104         //return RedirectToAction("Index");
    105       }
    106       else
    107        {
    108         var modelStateErrors = string.Join(",", this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors.Select(n => n.ErrorMessage)));
    109         return Json(new { success = false, err = modelStateErrors });
    110         //DisplayErrorMessage(modelStateErrors);
    111       }
    112       //return View(work);
    113     }
    114     //刪除當前記錄
    115     //GET: Companies/Delete/:id
    116     [HttpGet]
    117     public async Task<JsonResult> Delete(int id)
    118     {
    119       try
    120       {
    121         await this.companyService.DeleteAsync(id);
    122         await this.unitOfWork.SaveChangesAsync();
    123         return Json(new { success = true });
    124       }
    125      
    126       catch (Exception e)
    127       {
    128         return Json(new { success = false, err = e.GetBaseException().Message });
    129       }
    130     }
    131     //刪除選中的記錄
    132     [HttpPost]
    133     public async Task<JsonResult> DeleteChecked(int[] id)
    134     {
    135       try
    136       {
    137         foreach (var key in id)
    138         {
    139           await this.companyService.DeleteAsync(key);
    140         }
    141         await this.unitOfWork.SaveChangesAsync();
    142         return Json(new { success = true });
    143       }
    144       catch (Exception e)
    145       {
    146         return Json(new { success = false, err = e.GetBaseException().Message });
    147       }
    148     }
    149     //保存datagrid編輯的數據
    150     [HttpPost]
    151     public async Task<JsonResult> AcceptChanges(Company[] companies)
    152     {
    153       if (ModelState.IsValid)
    154       {
    155         try
    156         {
    157           foreach (var item in companies)
    158           {
    159             this.companyService.ApplyChanges(item);
    160           }
    161           var result = await this.unitOfWork.SaveChangesAsync();
    162           return Json(new { success = true, result });
    163         }
    164         catch (Exception e)
    165         {
    166           return Json(new { success = false, err = e.GetBaseException().Message });
    167         }
    168       }
    169       else
    170       {
    171         var modelStateErrors = string.Join(",", ModelState.Keys.SelectMany(key => ModelState[key].Errors.Select(n => n.ErrorMessage)));
    172         return Json(new { success = false, err = modelStateErrors });
    173       }
    174 
    175     }
    176     //導出Excel
    177     [HttpPost]
    178     public async Task<IActionResult> ExportExcel(string filterRules = "", string sort = "Id", string order = "asc")
    179     {
    180       var fileName = "compnay" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
    181       var stream = await this.companyService.ExportExcelAsync(filterRules, sort, order);
    182       return File(stream, "application/vnd.ms-excel", fileName);
    183     }
    184     //導入excel
    185     [HttpPost]
    186     public async Task<IActionResult> ImportExcel()
    187     {
    188       try
    189       {
    190         var watch = new Stopwatch();
    191         watch.Start();
    192         var total = 0;
    193         if (Request.Form.Files.Count > 0)
    194         {
    195           for (var i = 0; i < this.Request.Form.Files.Count; i++)
    196           {
    197             var model = Request.Form["model"].FirstOrDefault() ?? "company";
    198             var folder = Request.Form["folder"].FirstOrDefault() ?? "company";
    199             var autosave = Convert.ToBoolean(Request.Form["autosave"].FirstOrDefault());
    200             var properties = (Request.Form["properties"].FirstOrDefault()?.Split(','));
    201             var file = Request.Form.Files[i];
    202             var filename = file.FileName;
    203             var contenttype = file.ContentType;
    204             var size = file.Length;
    205             var ext = Path.GetExtension(filename);
    206             var path = Path.Combine(this._webHostEnvironment.ContentRootPath, "UploadFiles", folder);
    207             if (!Directory.Exists(path))
    208             {
    209               Directory.CreateDirectory(path);
    210             }
    211             var datatable = await NPOIHelper.GetDataTableFromExcelAsync(file.OpenReadStream(), ext);
    212             await this.companyService.ImportDataTableAsync(datatable);
    213             await this.unitOfWork.SaveChangesAsync();
    214             total = datatable.Rows.Count;
    215             if (autosave)
    216             {
    217               var filepath = Path.Combine(path, filename);
    218               file.OpenReadStream().Position = 0;
    219 
    220               using (var stream = System.IO.File.Create(filepath))
    221               {
    222                 await file.CopyToAsync(stream);
    223               }
    224             }
    225 
    226           }
    227         }
    228         watch.Stop();
    229         return Json(new { success = true, total = total, elapsedTime = watch.ElapsedMilliseconds });
    230       }
    231       catch (Exception e) {
    232         this._logger.LogError(e, "Excel導入失敗");
    233         return this.Json(new { success = false,  err = e.GetBaseException().Message });
    234       }
    235         }
    236     //下載模板
    237     public async Task<IActionResult> Download(string file) {
    238       
    239       this.Response.Cookies.Append("fileDownload", "true");
    240       var path = Path.Combine(this._webHostEnvironment.ContentRootPath, file);
    241       var downloadFile = new FileInfo(path);
    242       if (downloadFile.Exists)
    243       {
    244        var fileName = downloadFile.Name;
    245        var mimeType = MimeTypeConvert.FromExtension(downloadFile.Extension);
    246        var fileContent = new byte[Convert.ToInt32(downloadFile.Length)];
    247         using (var fs = downloadFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
    248         {
    249           await fs.ReadAsync(fileContent, 0, Convert.ToInt32(downloadFile.Length));
    250         }
    251         return this.File(fileContent, mimeType, fileName);
    252       }
    253       else
    254       {
    255         throw new FileNotFoundException($"文件 {file} 不存在!");
    256       }
    257     }
    258 
    259     }
    260 }

    View Code

    • 新建 View

    MVC Views\Companies\Index

      1 @model SmartAdmin.Data.Models.Company
      2 @{
      3   ViewData["Title"] = "企業信息";
      4   ViewData["PageName"] = "Companies_Index";
      5   ViewData["Heading"] = "<i class='fal fa-window text-primary'></i> 企業信息";
      6   ViewData["Category1"] = "組織架構";
      7   ViewData["PageDescription"] = "";
      8 }
      9 <div class="row">
     10   <div class="col-lg-12 col-xl-12">
     11     <div id="panel-1" class="panel">
     12       <div class="panel-hdr">
     13         <h2>
     14           公司信息
     15         </h2>
     16         <div class="panel-toolbar">
     17           <button class="btn btn-panel bg-transparent fs-xl w-auto h-auto rounded-0" data-action="panel-collapse" data-toggle="tooltip" data-offset="0,10" data-original-title="Collapse"><i class="fal fa-window-minimize"></i></button>
     18           <button class="btn btn-panel bg-transparent fs-xl w-auto h-auto rounded-0" data-action="panel-fullscreen" data-toggle="tooltip" data-offset="0,10" data-original-title="Fullscreen"><i class="fal fa-expand"></i></button>
     19         </div>
     20 
     21       </div>
     22       <div class="panel-container show">
     23         <div class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0  text-muted bg-subtlelight-fade ">
     24           <div class="row no-gutters align-items-center">
     25             <div class="col">
     26               <!-- 開啟授權控制請參考 @@if (Html.IsAuthorize("Create") -->
     27               <div class="btn-group btn-group-sm">
     28                 <button onclick="append()" class="btn btn-default">
     29                   <span class="fal fa-plus mr-1"></span> 新增
     30                 </button>
     31               </div>
     32               <div class="btn-group btn-group-sm">
     33                 <button name="deletebutton" disabled onclick="removeit()" class="btn btn-default">
     34                   <span class="fal fa-times mr-1"></span> 刪除
     35                 </button>
     36               </div>
     37               <div class="btn-group btn-group-sm">
     38                 <button name="savebutton" disabled onclick="acceptChanges()" class="btn btn-default">
     39                   <span class="fal fa-save mr-1"></span> 保存
     40                 </button>
     41               </div>
     42               <div class="btn-group btn-group-sm">
     43                 <button name="cancelbutton" disabled onclick="rejectChanges()" class="btn btn-default">
     44                   <span class="fal fa-ban mr-1"></span> 取消
     45                 </button>
     46               </div>
     47               <div class="btn-group btn-group-sm">
     48                 <button onclick="reload()" class="btn btn-default"> <span class="fal fa-search mr-1"></span> 查詢 </button>
     49                 <button type="button" class="btn btn-default dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
     50                   <span class="sr-only">Toggle Dropdown</span>
     51                 </button>
     52                 <div class="dropdown-menu dropdown-menu-animated">
     53                   <a class="dropdown-item js-waves-on" href="javascript:void()"> 我的記錄 </a>
     54                   <div class="dropdown-divider"></div>
     55                   <a class="dropdown-item js-waves-on" href="javascript:void()"> 自定義查詢 </a>
     56                 </div>
     57               </div>
     58               <div class="btn-group btn-group-sm hidden-xs">
     59                 <button type="button" onclick="importExcel.upload()" class="btn btn-default"><span class="fal fa-cloud-upload mr-1"></span> 導入 </button>
     60                 <button type="button" class="btn btn-default  dropdown-toggle dropdown-toggle-split waves-effect waves-themed" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
     61                   <span class="sr-only">Toggle Dropdown</span>
     62                 </button>
     63                 <div class="dropdown-menu dropdown-menu-animated">
     64                   <a class="dropdown-item js-waves-on" href="javascript:importExcel.downloadtemplate()">
     65                     <span class="fal fa-download"></span> 下載模板
     66                   </a>
     67                 </div>
     68               </div>
     69               <div class="btn-group btn-group-sm hidden-xs">
     70                 <button onclick="exportexcel()" class="btn btn-default">
     71                   <span class="fal fa-file-export mr-1"></span>  導出
     72                 </button>
     73               </div>
     74 
     75             </div>
     76 
     77           </div>
     78 
     79         </div>
     80         <div class="panel-content">
     81           <div class="table-responsive">
     82             <table id="companies_datagrid">
     83             </table>
     84           </div>
     85         </div>
     86       </div>
     87     </div>
     88   </div>
     89 </div>
     90 <!-- 彈出窗體form表單 -->
     91 <div id="companydetailwindow" class="easyui-window"
     92      title="明細數據"
     93      data-options="modal:true,
     94                     closed:true,
     95                     minimizable:false,
     96                     collapsible:false,
     97                     maximized:false,
     98                     iconCls:'fal fa-window',
     99                     onBeforeClose:function(){
    100                       var that = $(this);
    101                       if(companyhasmodified()){
    102                         $.messager.confirm('確認','你確定要放棄保存修改的記錄?',function(r){
    103                         if (r){
    104                           var opts = that.panel('options');
    105                           var onBeforeClose = opts.onBeforeClose;
    106                           opts.onBeforeClose = function(){};
    107                           that.panel('close');
    108                           opts.onBeforeClose = onBeforeClose;
    109                           hook = false;
    110                         }
    111                         });
    112                         return false;
    113                       }
    114                     },
    115                     onOpen:function(){
    116                        $(this).window('vcenter');
    117                        $(this).window('hcenter');
    118                     },
    119                     onRestore:function(){
    120                     },
    121                     onMaximize:function(){
    122                     }
    123                     " style="width:820px;height:420px;display:none">
    124   <!-- toolbar -->
    125   <div class="panel-content py-2 rounded-bottom border-faded border-left-0 border-right-0  text-muted bg-subtlelight-fade sticky-top">
    126     <div class="d-flex flex-row-reverse pr-4">
    127       <div class="btn-group btn-group-sm mr-1">
    128         <button name="saveitembutton" onclick="savecompanyitem()" class="btn btn-default">
    129           <i class="fal fa-save"></i> 保存
    130         </button>
    131       </div>
    132       <div class="btn-group btn-group-sm mr-1" id="deleteitem-btn-group">
    133         <button onclick="deletecompanyitem()" class="btn btn-danger">
    134           <i class="fal fa-trash-alt"></i> 刪除
    135         </button>
    136       </div>
    137     </div>
    138   </div>
    139   <div class="panel-container show">
    140     <div class="container">
    141       <div class="panel-content">
    142         <form id="company_form"
    143               class="easyui-form form-horizontal p-1"
    144               method="post"
    145               data-options="novalidate:true,
    146                             onChange: function(target){
    147                               hook = true;
    148                               $('button[name*=\'saveitembutton\']').prop('disabled', false);
    149                              },
    150                              onLoadSuccess:function(data){
    151                                hook = false;
    152                                $('button[name*=\'saveitembutton\']').prop('disabled', true);
    153                              }">
    154           @Html.AntiForgeryToken()
    155           <!--Primary Key-->
    156           @Html.HiddenFor(model => model.Id)
    157           <fieldset class="form-group">
    158             <!-- begin row -->
    159             <!--名稱-->
    160             <div class="row h-100 justify-content-center align-items-center">
    161               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.Name)</label>
    162               <div class="col-md-4 mb-1 pl-1">
    163                 <input id="@Html.IdFor(model => model.Name)"
    164                        name="@Html.NameFor(model => model.Name)"
    165                        value="@Html.ValueFor(model => model.Name)"
    166                        tabindex="0" required
    167                        class="easyui-textbox"
    168                        style="width:100%"
    169                        type="text"
    170                        data-options="prompt:'@Html.DescriptionFor(model => model.Name)',
    171                                  required:true,
    172                                  validType: 'length[0,50]'
    173                                  " />
    174               </div>
    175               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.Code)</label>
    176               <div class="col-md-4 mb-1 pl-1">
    177                 <input id="@Html.IdFor(model => model.Code)"
    178                        name="@Html.NameFor(model => model.Code)"
    179                        value="@Html.ValueFor(model => model.Code)"
    180                        tabindex="1" required
    181                        class="easyui-textbox"
    182                        style="width:100%"
    183                        type="text"
    184                        data-options="prompt:'@Html.DescriptionFor(model => model.Code)',
    185                                  required:true,
    186                                  validType: 'length[0,12]'
    187                                  " />
    188               </div>
    189               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.Address)</label>
    190               <div class="col-md-4 mb-1 pl-1">
    191                 <input id="@Html.IdFor(model => model.Address)"
    192                        name="@Html.NameFor(model => model.Address)"
    193                        value="@Html.ValueFor(model => model.Address)"
    194                        tabindex="2"
    195                        class="easyui-textbox"
    196                        style="width:100%"
    197                        type="text"
    198                        data-options="prompt:'@Html.DescriptionFor(model => model.Address)',
    199                                  required:false,
    200                                  validType: 'length[0,50]'
    201                                  " />
    202               </div>
    203               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.Contect)</label>
    204               <div class="col-md-4 mb-1 pl-1">
    205                 <input id="@Html.IdFor(model => model.Contect)"
    206                        name="@Html.NameFor(model => model.Contect)"
    207                        value="@Html.ValueFor(model => model.Contect)"
    208                        tabindex="3"
    209                        class="easyui-textbox"
    210                        style="width:100%"
    211                        type="text"
    212                        data-options="prompt:'@Html.DescriptionFor(model => model.Contect)',
    213                                  required:false,
    214                                  validType: 'length[0,12]'
    215                                  " />
    216               </div>
    217               <label class="col-md-2 pr-1 form-label text-right">@Html.DisplayNameFor(model => model.PhoneNumber)</label>
    218               <div class="col-md-4 mb-1 pl-1">
    219                 <input id="@Html.IdFor(model => model.PhoneNumber)"
    220                        name="@Html.NameFor(model => model.PhoneNumber)"
    221                        value="@Html.ValueFor(model => model.PhoneNumber)"
    222                        tabindex="4"
    223                        class="easyui-textbox"
    224                        style="width:100%"
    225                        type="text"
    226                        data-options="prompt:'@Html.DescriptionFor(model => model.PhoneNumber)',
    227                                  required:false,
    228                                  validType: 'length[0,20]'
    229                                  " />
    230               </div>
    231               <label class="col-md-2 pr-1 form-label text-right text-danger">@Html.DisplayNameFor(model => model.RegisterDate)</label>
    232               <div class="col-md-4 mb-1 pl-1">
    233                 <input id="@Html.IdFor(model => model.RegisterDate)"
    234                        name="@Html.NameFor(model => model.RegisterDate)"
    235                        value="@Html.ValueFor(model => model.RegisterDate)"
    236                        tabindex="5" required
    237                        class="easyui-datebox"
    238                        style="width:100%"
    239                        type="text"
    240                        data-options="prompt:'@Html.DescriptionFor(model => model.RegisterDate)',
    241                                  required:true,
    242                                  formatter:dateformatter" />
    243               </div>
    244             </div>
    245           </fieldset>
    246         </form>
    247       </div>
    248     </div>
    249   </div>
    250 </div>
    251 
    252  
    253 @await Component.InvokeAsync("ImportExcel", new ImportExcelOptions { entity="Company",
    254   folder="Companies",
    255   url="/Companies/ImportExcel",
    256   tpl="/Companies/Download"
    257 
    258 
    259 })
    260 
    261 @section HeadBlock {
    262   <link href="~/css/notifications/toastr/toastr.css" rel="stylesheet" asp-append-version="true" />
    263   <link href="~/css/formplugins/bootstrap-daterangepicker/bootstrap-daterangepicker.css" rel="stylesheet" asp-append-version="true" />
    264   <link href="~/js/easyui/themes/insdep/easyui.css" rel="stylesheet" asp-append-version="true" />
    265 }
    266 @section ScriptsBlock {
    267   <script src="~/js/dependency/moment/moment.js" asp-append-version="true"></script>
    268   <script src="~/js/notifications/toastr/toastr.js"></script>
    269   <script src="~/js/formplugins/bootstrap-daterangepicker/bootstrap-daterangepicker.js" asp-append-version="true"></script>
    270   <script src="~/js/easyui/jquery.easyui.min.js" asp-append-version="true"></script>
    271   <script src="~/js/easyui/plugins/datagrid-filter.js" asp-append-version="true"></script>
    272   <script src="~/js/easyui/plugins/columns-ext.js" asp-append-version="true"></script>
    273   <script src="~/js/easyui/plugins/columns-reset.js" asp-append-version="true"></script>
    274   <script src="~/js/easyui/locale/easyui-lang-zh_CN.js" asp-append-version="true"></script>
    275   <script src="~/js/easyui/jquery.easyui.component.js" asp-append-version="true"></script>
    276   <script src="~/js/plugin/filesaver/FileSaver.js" asp-append-version="true"></script>
    277   <script src="~/js/plugin/jquery.serializejson/jquery.serializejson.js" asp-append-version="true"></script>
    278   <script src="~/js/jquery.custom.extend.js" asp-append-version="true"></script>
    279   <script src="~/js/jquery.extend.formatter.js" asp-append-version="true"></script>
    280   <script>
    281         var $dg = $('#companies_datagrid');
    282         var EDITINLINE = true;
    283         var company = null;
    284     var editIndex = undefined;
    285     //下載Excel導入模板
    286 
    287     //執行導出下載Excel
    288     function exportexcel() {
    289       const filterRules = JSON.stringify($dg.datagrid('options').filterRules);
    290       console.log(filterRules);
    291       $.messager.progress({ title: '請等待',msg:'正在執行導出...' });
    292       let formData = new FormData();
    293       formData.append('filterRules', filterRules);
    294       formData.append('sort', 'Id');
    295       formData.append('order', 'asc');
    296       $.postDownload('/Companies/ExportExcel', formData).then(res => {
    297         $.messager.progress('close');
    298         toastr.success('導出成功!');
    299       }).catch(err => {
    300         //console.log(err);
    301         $.messager.progress('close');
    302         $.messager.alert('導出失敗', err.statusText, 'error');
    303       });
    304 
    305     }
    306             //彈出明細信息
    307     function showdetailswindow(id, index) {
    308       const company = $dg.datagrid('getRows')[index];
    309       opencompanydetailwindow(company, 'Modified');
    310     }
    311         function reload() {
    312                $dg.datagrid('uncheckAll');
    313                $dg.datagrid('reload');
    314         }
    315             //新增記錄
    316        function append() {
    317                 company = {
    318                     Address: '-',
    319                     RegisterDate: moment().format('YYYY-MM-DD HH:mm:ss'),
    320                 };
    321                 if (!EDITINLINE) {
    322                     //彈出新增窗口
    323                     opencompanydetailwindow(company, 'Added');
    324                 } else {
    325                     if (endEditing()) {
    326                         //對必填字段進行默認值初始化
    327                        $dg.datagrid('insertRow',
    328                             {
    329                                 index: 0,
    330                                 row: company
    331                             });
    332                         editIndex = 0;
    333                        $dg.datagrid('selectRow', editIndex)
    334                             .datagrid('beginEdit', editIndex);
    335                         hook = true;
    336                     }
    337                 }
    338         }
    339             //刪除編輯的行
    340         function removeit() {
    341                 if (this.$dg.datagrid('getChecked').length <= 0 && EDITINLINE) {
    342                     if (editIndex !== undefined) {
    343                         const delindex = editIndex;
    344                        $dg.datagrid('cancelEdit', delindex)
    345                             .datagrid('deleteRow', delindex);
    346                         hook = true;
    347                     } else {
    348                         const rows =$dg.datagrid('getChecked');
    349                         rows.slice().reverse().forEach(row => {
    350                             const rowindex =$dg.datagrid('getRowIndex', row);
    351                            $dg.datagrid('deleteRow', rowindex);
    352                             hook = true;
    353                         });
    354                     }
    355                 } else {
    356                     deletechecked();
    357                 }
    358         }
    359             //刪除該行
    360         function deleteRow(id) {
    361             $.messager.confirm('確認', '你確定要刪除該記錄?', result => {
    362                 if (result) {
    363                     dodeletechecked([id]);
    364                 }
    365             })
    366         }
    367             //刪除選中的行
    368          function deletechecked() {
    369                 const id =$dg.datagrid('getChecked').filter(item => item.Id != null && item.Id > 0).map(item => {
    370                     return item.Id;
    371                 });
    372                 if (id.length > 0) {
    373                     $.messager.confirm('確認', `你確定要刪除這 <span class='badge badge-icon position-relative'>${id.length} </span> 行記錄?`, result => {
    374                         if (result) {
    375                             dodeletechecked(id);
    376                         }
    377                     });
    378                 } else {
    379                     $.messager.alert('提示', '請先選擇要刪除的記錄!', 'question');
    380                 }
    381         }
    382             //執行刪除
    383         function dodeletechecked(id) {
    384             $.post('/Companies/DeleteChecked', { id: id })
    385                 .done(response => {
    386                     if (response.success) {
    387                         toastr.error(`成功刪除[${id.length}]行記錄`);
    388                         reload();
    389                     } else {
    390                         $.messager.alert('錯誤', response.err, 'error');
    391                     }
    392                 })
    393                 .fail((jqXHR, textStatus, errorThrown) => {
    394                     $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
    395                 });
    396         }
    397             //開啟編輯狀態
    398     function onClickCell(index, field) {
    399 
    400       company = $dg.datagrid('getRows')[index];
    401       const _actions = ['action', 'ck'];
    402       if (!EDITINLINE || $.inArray(field, _actions) >= 0) {
    403         return;
    404       }
    405 
    406       if (editIndex !== index) {
    407         if (endEditing()) {
    408           $dg.datagrid('selectRow', index)
    409             .datagrid('beginEdit', index);
    410           hook = true;
    411           editIndex = index;
    412           const ed = $dg.datagrid('getEditor', { index: index, field: field });
    413           if (ed) {
    414             ($(ed.target).data('textbox') ? $(ed.target).textbox('textbox') : $(ed.target)).focus();
    415           }
    416         } else {
    417           $dg.datagrid('selectRow', editIndex);
    418         }
    419       }
    420     }
    421             //關閉編輯狀態
    422     function endEditing() {
    423 
    424             if (editIndex === undefined) {
    425                 return true;
    426             }
    427             if (this.$dg.datagrid('validateRow', editIndex)) {
    428                 $dg.datagrid('endEdit', editIndex);
    429                 return true;
    430             } else {
    431                 const invalidinput = $('input.validatebox-invalid', $dg.datagrid('getPanel'));
    432                 const fieldnames = invalidinput.map((index, item) => {
    433                     return $(item).attr('placeholder') || $(item).attr('id');
    434                 });
    435                 $.messager.alert('提示', `${Array.from(fieldnames)} 輸入有誤.`, 'error');
    436                 return false;
    437             }
    438         }
    439             //提交保存後台數據庫
    440         function acceptChanges() {
    441             if (endEditing()) {
    442                 if ($dg.datagrid('getChanges').length > 0) {
    443                     const inserted = $dg.datagrid('getChanges', 'inserted').map(item => {
    444                         item.TrackingState = 1;
    445                         return item;
    446                     });
    447                     const updated = $dg.datagrid('getChanges', 'updated').map(item => {
    448                         item.TrackingState = 2
    449                         return item;
    450                     });
    451                     const deleted = $dg.datagrid('getChanges', 'deleted').map(item => {
    452                         item.TrackingState = 3
    453                         return item;
    454                     });
    455                     //過濾已刪除的重複項
    456                     const changed = inserted.concat(updated.filter(item => {
    457                         return !deleted.includes(item);
    458                     })).concat(deleted);
    459                     //$.messager.progress({ title: '請等待', msg: '正在保存數據...', interval: 200 });
    460                     $.post('/Companies/AcceptChanges', { companies: changed })
    461                         .done(response => {
    462                             //$.messager.progress('close');
    463                             //console.log(response);
    464                             if (response.success) {
    465                                 toastr.success('保存成功');
    466                                 $dg.datagrid('acceptChanges');
    467                                 reload();
    468                                 hook = false;
    469                             } else {
    470                                 $.messager.alert('錯誤', response.err, 'error');
    471                             }
    472                         })
    473                         .fail((jqXHR, textStatus, errorThrown) => {
    474                             //$.messager.progress('close');
    475                             $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
    476                         });
    477                 }
    478             }
    479         }
    480         function rejectChanges() {
    481             $dg.datagrid('rejectChanges');
    482             editIndex = undefined;
    483             hook = false;
    484         }
    485     $(document).ready(function () {
    486       //定義datagrid結構
    487       $dg.datagrid({
    488         rownumbers: true,
    489         checkOnSelect: false,
    490         selectOnCheck: false,
    491         idField: 'Id',
    492         sortName: 'Id',
    493         sortOrder: 'desc',
    494         remoteFilter: true,
    495         singleSelect: true,
    496         method: 'get',
    497         onClickCell: onClickCell,
    498         clientPaging: false,
    499         pagination: true,
    500         striped: true,
    501         filterRules: [],
    502         onHeaderContextMenu: function (e, field) {
    503           e.preventDefault();
    504           $(this).datagrid('columnMenu').menu('show', {
    505             left: e.pageX,
    506             top: e.pageY
    507           });
    508         },
    509         onBeforeLoad: function () {
    510           const that = $(this);
    511           document.addEventListener('panel.onfullscreen', () => {
    512             setTimeout(() => {
    513               that.datagrid('resize');
    514             }, 200)
    515           })
    516         },
    517         onLoadSuccess: function (data) {
    518           editIndex = undefined;
    519           $("button[name*='deletebutton']").prop('disabled', true);
    520           $("button[name*='savebutton']").prop('disabled', true);
    521           $("button[name*='cancelbutton']").prop('disabled', true);
    522         },
    523         onCheck: function () {
    524           $("button[name*='deletebutton']").prop('disabled', false);
    525         },
    526         onUncheck: function () {
    527           const checked = $(this).datagrid('getChecked').length > 0;
    528           $("button[name*='deletebutton']").prop('disabled', !checked);
    529         },
    530         onSelect: function (index, row) {
    531           company = row;
    532         },
    533         onBeginEdit: function (index, row) {
    534           //const editors = $(this).datagrid('getEditors', index);
    535 
    536         },
    537         onEndEdit: function (index, row) {
    538           editIndex = undefined;
    539         },
    540         onBeforeEdit: function (index, row) {
    541           editIndex = index;
    542           row.editing = true;
    543           $("button[name*='deletebutton']").prop('disabled', false);
    544           $("button[name*='cancelbutton']").prop('disabled', false);
    545           $("button[name*='savebutton']").prop('disabled', false);
    546           $(this).datagrid('refreshRow', index);
    547         },
    548         onAfterEdit: function (index, row) {
    549           row.editing = false;
    550           editIndex = undefined;
    551           $(this).datagrid('refreshRow', index);
    552         },
    553         onCancelEdit: function (index, row) {
    554           row.editing = false;
    555           editIndex = undefined;
    556           $("button[name*='deletebutton']").prop('disabled', true);
    557           $("button[name*='savebutton']").prop('disabled', true);
    558           $("button[name*='cancelbutton']").prop('disabled', true);
    559           $(this).datagrid('refreshRow', index);
    560         },
    561         frozenColumns: [[
    562           /*開啟CheckBox選擇功能*/
    563           { field: 'ck', checkbox: true },
    564           {
    565             field: 'action',
    566             title: '操作',
    567             width: 85,
    568             sortable: false,
    569             resizable: true,
    570             formatter: function showdetailsformatter(value, row, index) {
    571               if (!row.editing) {
    572                 return `<div class="btn-group">\
    573                                                          <button onclick="showdetailswindow('${row.Id}',  ${index})" class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" title="查看明細" ><i class="fal fa-edit"></i> </button>\
    574                                                          <button onclick="deleteRow('${row.Id}',${index})" class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" title="刪除記錄" ><i class="fal fa-times"></i> </button>\
    575                                                     </div>`;
    576               } else {
    577                 return `<button class="btn btn-primary btn-sm btn-icon waves-effect waves-themed" disabled title="查看明細"  ><i class="fal fa-edit"></i> </button>`;
    578               }
    579             }
    580           }
    581         ]],
    582         columns: [[
    583 
    584           {    /*名稱*/
    585             field: 'Name',
    586             title: '<span class="required">@Html.DisplayNameFor(model => model.Name)</span>',
    587             width: 200,
    588             hidden: false,
    589             editor: {
    590               type: 'textbox',
    591               options: { prompt: '@Html.DescriptionFor(model => model.Name)', required: true, validType: 'length[0,50]' }
    592             },
    593             sortable: true,
    594             resizable: true
    595           },
    596           {    /*組織代碼*/
    597             field: 'Code',
    598             title: '<span class="required">@Html.DisplayNameFor(model => model.Code)</span>',
    599             width: 120,
    600             hidden: false,
    601             editor: {
    602               type: 'textbox',
    603               options: { prompt: '@Html.DescriptionFor(model => model.Code)', required: true, validType: 'length[0,12]' }
    604             },
    605             sortable: true,
    606             resizable: true
    607           },
    608           {    /*地址*/
    609             field: 'Address',
    610             title: '@Html.DisplayNameFor(model => model.Address)',
    611             width: 200,
    612             hidden: false,
    613             editor: {
    614               type: 'textbox',
    615               options: { prompt: '@Html.DescriptionFor(model => model.Address)', required: false, validType: 'length[0,50]' }
    616             },
    617             sortable: true,
    618             resizable: true
    619           },
    620           {    /*聯繫人*/
    621             field: 'Contect',
    622             title: '@Html.DisplayNameFor(model => model.Contect)',
    623             width: 120,
    624             hidden: false,
    625             editor: {
    626               type: 'textbox',
    627               options: { prompt: '@Html.DescriptionFor(model => model.Contect)', required: false, validType: 'length[0,12]' }
    628             },
    629             sortable: true,
    630             resizable: true
    631           },
    632           {    /*聯繫電話*/
    633             field: 'PhoneNumber',
    634             title: '@Html.DisplayNameFor(model => model.PhoneNumber)',
    635             width: 120,
    636             hidden: false,
    637             editor: {
    638               type: 'textbox',
    639               options: { prompt: '@Html.DescriptionFor(model => model.PhoneNumber)', required: false, validType: 'length[0,20]' }
    640             },
    641             sortable: true,
    642             resizable: true
    643           },
    644           {   /*註冊日期*/
    645             field: 'RegisterDate',
    646             title: '<span class="required">@Html.DisplayNameFor(model => model.RegisterDate)</span>',
    647             width: 140,
    648             align: 'right',
    649             hidden: false,
    650             editor: {
    651               type: 'datebox',
    652               options: { prompt: '@Html.DescriptionFor(model => model.RegisterDate)', required: true }
    653             },
    654             formatter: dateformatter,
    655             sortable: true,
    656             resizable: true
    657           },
    658         ]]
    659       }).datagrid('columnMoving')
    660         .datagrid('resetColumns')
    661         .datagrid('enableFilter', [
    662           {   /*Id*/
    663             field: 'Id',
    664             type: 'numberbox',
    665             op: ['equal', 'notequal', 'less', 'lessorequal', 'greater', 'greaterorequal']
    666           },
    667           {     /*註冊日期*/
    668             field: 'RegisterDate',
    669             type: 'dateRange',
    670             options: {
    671               onChange: value => {
    672                 $dg.datagrid('addFilterRule', {
    673                   field: 'RegisterDate',
    674                   op: 'between',
    675                   value: value
    676                 });
    677 
    678                 $dg.datagrid('doFilter');
    679               }
    680             }
    681           },
    682         ])
    683         .datagrid('load', '/Companies/GetData');
    684     }
    685     );
    686 
    687   </script>
    688   <script type="text/javascript">
    689     //判斷新增編輯狀態
    690     var MODELSTATE = 'Added';
    691     var companyid = null;
    692     function opencompanydetailwindow(data, state) {
    693       MODELSTATE = state;
    694       initcompanydetailview();
    695       companyid = (data.Id || 0);
    696       $("#companydetailwindow").window("open");
    697       $('#company_form').form('reset');
    698       $('#company_form').form('load', data);
    699     }
    700     //刪除當前記錄
    701     function deletecompanyitem() {
    702       $.messager.confirm('確認', '你確定要刪除該記錄?', result => {
    703         if (result) {
    704           const url = `/Companies/Delete/${companyid}`;
    705           $.get(url).done(res => {
    706             if (res.success) {
    707               toastr.success("刪除成功");
    708               $("#companydetailwindow").window("close");
    709               reload();
    710             } else {
    711               $.messager.alert("錯誤", res.err, "error");
    712             }
    713           });
    714         }
    715       });
    716     }
    717     //async 保存數據
    718     async function savecompanyitem() {
    719       const $companyform = $('#company_form');
    720       if ($companyform.form('enableValidation').form('validate')) {
    721         let company = $companyform.serializeJSON();
    722         let url = '/Companies/Edit';
    723         //判斷是新增或是修改方法
    724         if (MODELSTATE === 'Added') {
    725           url = '/Companies/Create';
    726         }
    727         var token = $('input[name="__RequestVerificationToken"]', $companyform).val();
    728         //$.messager.progress({ title: '請等待', msg: '正在保存數據...', interval: 200 });
    729         $.ajax({
    730           type: "POST",
    731           url: url,
    732           data: {
    733             __RequestVerificationToken: token,
    734             company: company
    735           },
    736           dataType: 'json',
    737           contentType: 'application/x-www-form-urlencoded; charset=utf-8'
    738         })
    739           .done(response => {
    740             //$.messager.progress('close');
    741             if (response.success) {
    742               hook = false;
    743               $companyform.form('disableValidation');
    744               $dg.datagrid('reload');
    745               $('#companydetailwindow').window("close");
    746               toastr.success("保存成功");
    747             } else {
    748               $.messager.alert("錯誤", response.err, "error");
    749             }
    750           })
    751           .fail((jqXHR, textStatus, errorThrown) => {
    752             //$.messager.progress('close');
    753             $.messager.alert('異常', `${jqXHR.status}: ${jqXHR.statusText} `, 'error');
    754           });
    755       }
    756     }
    757     //關閉窗口
    758     function closecompanydetailwindow() {
    759       $('#companydetailwindow').window('close');
    760     }
    761 
    762     //判斷是否有沒有保存的記錄
    763     function companyhasmodified() {
    764       return hook;
    765     }
    766 
    767 
    768     function initcompanydetailview() {
    769       //判斷是否显示功能按鈕
    770       if (MODELSTATE === 'Added') {
    771         $('#deleteitem-btn-group').hide();
    772       } else {
    773         $('#deleteitem-btn-group').show();
    774       }
    775 
    776       //回車光標移動到下個輸入控件
    777       //日期類型 註冊日期
    778       $('#RegisterDate').datebox('textbox').bind('keydown', function (e) {
    779         if (e.keyCode == 13) {
    780           $(e.target).emulateTab();
    781         }
    782       });
    783     }
    784   </script>
    785 }

    View Code

     

    上面View層的代碼非常的複雜,但都是固定格式,可以用scaffold快速生成

    • 配置依賴注入(DI),註冊服務

    打開 startup.cs 在 public void ConfigureServices(IServiceCollection services) 註冊服務 services.AddScoped<IRepositoryX, RepositoryX>(); 
    services.AddScoped<ICustomerService, CustomerService>();

    • 更新數據庫

    EF Core Code-First 同步更新數據庫 
    在 Visual Studio.Net 
    Package Manager Controle 運行 
    PM>:add-migration create_Company 
    PM>:update-database 
    PM>:更新完成

    • Debug 運行項目 

    高級應用

    CAP 分佈式事務的解決方案及應用場景 
    nuget 安裝組件 
    PM> Install-Package DotNetCore.CAP 
    PM> Install-Package DotNetCore.CAP.RabbitMQ 
    PM> Install-Package DotNetCore.CAP.SqlServer \

    • 配置Startup.cs
     1 public void ConfigureServices(IServiceCollection services)
     2     {
     3       services.AddCap(x =>
     4       {
     5         x.UseEntityFramework<SmartDbContext>();
     6         x.UseRabbitMQ("127.0.0.1");
     7         x.UseDashboard();
     8         x.FailedRetryCount = 5;
     9         x.FailedThresholdCallback = failed =>
    10         {
    11           var logger = failed.ServiceProvider.GetService<ILogger<Startup>>();
    12           logger.LogError($@"A message of type {failed.MessageType} failed after executing {x.FailedRetryCount} several times, 
    13                         requiring manual troubleshooting. Message name: {failed.Message.GetName()}");
    14         };
    15       });
    16     }

    View Code

     

    • 發布消息
    • 訂閱消息

    roadmap

    • 完善主要的開發文檔
    • 支持My SQL數據庫
    • 還會繼續重構和完善代碼
    • 開發Scaffold MVC模板,生成定製化的Controller 和 View 減少開發人員重複工作
    • 完善授權訪問策略(policy-based authorization)
    • 開發Visual Sutdio.net代碼生成插件(類似國內做比較好的52abp)

    我的聯繫方式,qq群,贊助二維碼

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

    【其他文章推薦】

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

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

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

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

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

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

  • 京都龜岡市將禁止提供塑膠袋 業者反彈起步困難

    摘錄自2020年3月17日Yahoo!新聞報導

    日本京都的龜岡市領先全國通過「塑膠袋禁止條例」,今後在當地買東西結帳,就算願意付錢也不會提供塑膠袋,擅自提供的店家還會被罰款。這項規定原本預定8月上路,但因為市民團體反應還需要適應的時間,因此目前還沒有訂下確切的實行時間。

    要改變基本生活習慣並不容易。就怕居民反對,龜岡市從去年到今年前後辦了50場說明會。但有民眾認為,要是沒和鄰近的市鎮合作,很難辦到完全禁用。根據說明會後的統計,贊成這項規定的市民高達7成5。事實上從塑膠袋要收費開始,就能看到明顯改變,帶購物袋來採買的民眾從原本的20%,大幅增加為80%。

    這一頭成功安撫民眾,另一頭還得得到超市等業者的支持。有超市業者表示,就算現在訂購紙袋,到進貨為止也要2個月。日本加盟協會負責人也表示:「像加熱的關東煮、焗烤、杯麵,得加熱湯讓客人帶走就無法賣了,毫無疑問會對業績造成影響,這可是生死存亡的問題,說真心話希望能多點緩衝時間。」

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

    【其他文章推薦】

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

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

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

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

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

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