分類
發燒車訊

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】 可複製集搭建

可複製集 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

 

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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

分類
發燒車訊

「半雞半鴨」:考古發現最古老禽類化石

摘錄自2020年3月28日大紀元報導

考古學家發現一隻禽類生物的頭骨和腿骨化石,經檢驗發現它生活在距今約6,680萬~6,670萬年前,是至今全球發現的最古老的鳥類化石。

這份近期發表在《自然》(Nature)期刊上的研究公布了這一發現。研究者之一劍橋大學的古生物學家菲爾德(Daniel Field)說:「這是我們至今發現的最早存在的鳥類的證據。」

恐龍大約在距今6,600萬年前滅絕,因此這隻生物生活在僅比那個時間點早一點的時期。在此之前,科學家找到的最早的鳥類化石大約生活在距今6,650萬年前。新發現的化石比這個更早一些。

研究人員估計這隻鳥的體重為400克左右,只有現在的水鳥——鳧的一半大。「我們認為它的臉看起來有點像現代的雞,但是頭骨的後面看起來又更像現在的鴨子。」菲爾德說。一起發現的還有它的腿骨化石,看起來它有著兩條細長的腿,說明它是生活在岸邊的一種禽類。

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

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

分類
發燒車訊

幫居民生計找出路 坦尚尼亞社區林業 改善盜伐有成

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

分類
發燒車訊

地球暖化冰河融解 北極新發現5座島嶼

摘錄自2019年10月23日民視新聞報導

俄羅斯研究人員23日表示,他們在北極偏遠地區發現了5座新島,目前還沒有命名研究人員是在法蘭士約瑟夫地群島,發現這5座新島嶼。島嶼原本為冰河覆蓋,因為冰河融化的關係,才被人發現。從無人機空拍畫面可以看到島嶼上有北極熊和海象等動物居住。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

分類
發燒車訊

一種失去飛翔能力的鳥 使科學家重拾對環境的希望

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

分類
發燒車訊

防堵武漢肺炎 美國家公園仍營業 遊客湧入憂成防疫破口

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

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

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

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