(轉)mblog解讀(一)

(二期)十一、開源博客項目mblog解讀(一)

【課程11】圖片上傳模塊.xmind54.6KBhtml

【課程11】消息發...通知.xmind55.2KBreact

【課程11】異常處理分析.xmind95.4KBweb

【課程11預習】多...解讀.xmind0.4MBspring

【課程11】第三方...模塊.xmind0.2MB數據庫

【課程11】多人博...解讀.xmind0.3MBjson

 

項目簡介

mblog (mtons blog)開源免費的Java多人博客系統。設計模式

技術選型

項目結構

 

項目模塊切分

咱們先來想一下,咱們能不能一個項目就用一個模塊。這樣開起來很方便,簡單明瞭,那麼作起來呢,接下來咱們分析一下。api

假設咱們有這麼一個項目,整個項目構建一個war包,而每一層放到各自的Package裏面。以下:緩存

Itoo-Exam安全

com.tgb.itoo.exam.dao —–負責與數據庫的交互,封裝了hibernate的交互類

com.tgb.itoo.exam.service—-負責處理業務邏輯,放Service接口及其實現類

com.tgb.itoo.exam.web——-負責與客戶端的交互,主要放action/controller,jsp等等

com.tgb.itoo.exam.util——–工具類

那麼隨着咱們項目的擴大,Maven項目也會愈來愈大,那麼會遇到下面的幾個問題:

一、首先build整個項目的時間愈來愈長,儘管你一直在web層工做,但你不得不build整個項目。

 

二、模塊化體現不出來,若是咱們只負責維護某個模塊,由於咱們全部的模塊都在一個war包中,那麼咱們能夠隨意修改其餘模塊(權限的控制),致使版本管理混亂,衝突。同時由於模塊太多,太大,很差維護。不少人都在同時修改這個war包,將致使工做沒法順利進行。

 

三、pom.xml原本是能夠繼承、複用的,可是若是咱們新建一個項目,只能依賴於這個war包,那麼會把這個war包的相關的前臺的東西依賴過來,致使項目管理混亂。

 

這樣的管理是混亂的,沒有遵照一個設計模式原則:「高內聚,低耦合」。相反在代碼內部,全部的東西都耦合在了一塊兒。所以咱們須要劃分模塊。

 

另外,隨着技術的飛速發展和各種用戶對軟件的要求愈來愈高,軟件自己變得愈來愈複雜,設計人員開始採用各類方式進行開發,因而就有了咱們的分層架構、分層模塊來提升代碼的清晰和重用。從而實現了系統內部的高內聚、低耦合。

 

模塊化的好處

一、方便重用,當咱們再開發一條teacher線的時候,咱們只須要引用itoo-base,itoo-exam-api,這些包都是複用的,稱爲咱們平臺複用的基礎類庫,供全部的項目使用。這是模塊化最重要的一個目的。

二、靈活性。好比咱們這些公共的jar包,itoo-base,itoo-tool,itoo-exam-api等這些jar包,咱們不須要再當源碼,只須要deploy到nexus,其餘人從nexus下載便可。代碼的可維護性、可擴展性好,而且保證了項目獨立性與完整性。

 

使用模塊化配置,複用性強,防止pom變得過於龐大,方便構建;針對項目的管理更方便,每個模塊都是獨立的,抽象出一個父類來管理第三方jar的版本,開發人員只須要開發本身的線,其餘的都不用管,靈活;基於此種基礎咱們還能夠作分佈式。

 

聚合:

Maven聚合:當咱們的模塊很是多的時候,咱們想要一次構建多個項目,而不是到多個模塊的目錄下分別執行命令。Maven的聚合特性就是爲該需求服務的。

Maven構建: Maven首先解析聚合模塊pom、分析要構建的模塊、並計算出一個反應堆構建順序,而後根據這個順序依次構建各個模塊。反應堆是全部模塊組成的一個構建結構。 

繼承

Maven繼承也是爲了防止重複,讓項目的jar包版本一致,在項目管理上起了很大的做用。

好比說相同的jar包咱們每一個人都須要依賴一遍,而且每一個人引用的版本號不一樣,勢必形成項目混亂,運行出問題。

一、子模塊省略grouopId和version,都會從父模塊依賴下來。

二、子模塊元素pom.xml。

 

總之,聚合是爲了方便快速構建項目,繼承是爲了消除重複配置,在簡化pom的同時還能促進各個模塊配置的一致性。

異常處理機制
關鍵類-HandlerExceptionResolver

Spring MVC經過HandlerExceptionResolver處理程序的異常,包括Handler的映射、數據綁定以及目標方法的執行。HandlerExceptionResolver時一個接口,該接口的實現類都有處理異常的功能。HandlerExceptionResolver是該接口應用普遍的一個實現類,而且DispatcherServlet默認裝配了HandlerExceptionResolver 的Bean。

 

SpringMVC 提供的異常處理主要有兩種方式:

  • 一種是直接實現本身的HandlerExceptionResolver
  • 一種是使用註解

經過註解的方式實現處理異常主要有如下兩種方式:

  • 1 @ControllerAdvice+@ExceptionHandler:配置對全局異常進行處理
  • 2 @Controller + @ExceptionHandler:配置對當前所在Controller的異常進行處理

在SpringMVC中,處理異常類其實是HandlerExceptionResolver子類。HandlerExceptionResolver處理全部controller類在執行過程當中拋出的未被處理的異常。

本文演示如何使用以上多種處理異常的方式,最後演示以不一樣的方式將異常結果返回給調用者

 

  • 返回 modelAndView
  • 返回一個頁面的地址
  • 返回 JSON
  • 返回 http 錯誤碼
系統默認實現的HandlerExceptionResolver

如下是系統默認加載到spring mvc容器中的HandlerExceptionResolver

  • ExceptionHandlerExceptionResolver: 根據@ExceptionHandler註解的方法處理對應的異常。其實上文的經過註解的方式處理異常,實際就是在這個類中實現
  • ResponseStatusExceptionResolver: 根據@ResponseStatus註解的方法處理異常
  • DefaultHandlerExceptionResolver: 將異常轉化爲特定的HTTP的狀態碼
  • HandlerExceptionResolverComposite:此類經過列表包含以上3個HandlerExceptionResolver,當捕獲異常時,會循環調用以上3個HandlerExceptionResolver進行處理

ExceptionHandlerExceptionResolver處理過程總結一下:

  • 根據用戶調用Controller中相應的方法獲得HandlerMethod,以後構造ExceptionHandlerMethodResolver,
  • 構造ExceptionHandlerMethodResolver有2種選擇,
  • 1.經過HandlerMethod拿到Controller,找出Controller中帶有@ExceptionHandler註解的方法(局部)
  • 2.找到@ControllerAdvice註解配置的類中的@ExceptionHandler註解的方法(全局)。
  • 這2種方式構造的ExceptionHandlerMethodResolver中都有1個key爲Throwable,value爲Method的緩存。以後經過發生的異常找出對應的Method,而後調用這個方法進行處理。
  • 這裏異常還有個優先級的問題,好比發生的是NullPointerException,可是聲明的異常有Throwable和Exception,這時候ExceptionHandlerMethodResolver找Method的時候會根據異常的最近繼承關係找到繼承深度最淺的那個異常,即Exception。
發佈與通知
常見場景

在實際開發過程當中,經常遇到這種場景: 

作完某一件事情之後,須要廣播一些消息或者通知,告訴其餘的模塊進行一些事件處理,通常來講,能夠一個一個發送請求去通知,可是這種方式比較消耗業務時間。那種更好解決方法呢,那就是事件監聽,事件監聽也是設計模式中 發佈-訂閱模式、觀察者模式的一種實現。

觀察者模式:在對象之間定義了一對多的依賴,這樣一來,當一個對象改變狀態,依賴它的對象會收到通知並自動更新。

Spring的事件爲Bean和Bean之間的消息傳遞提供支持。當一個對象處理完某種任務後,通知另外的對象進行某些處理,經常使用的場景有進行某些操做後發送通知,消息,操做記錄,發送短信、郵件等狀況。 

Spring的事件遵循的流程

自定義事件,繼承ApplicationEvent(org.springframework.context.ApplicationEvent)

定義監聽事件,實現ApplicationListener(org.springframework.context.ApplicationListener)

使用容器觸發事件。

發佈事件,使用applicationContext發佈事件。

 

ApplicationEvent中,在自定義事件的構造方法中除了第一個source參數,其餘參數均可以去自定義,

能夠根據項目實際狀況進行監聽傳參,這裏就只定義個簡單的String字符串的透傳。

邏輯整理

這裏使用一種簡單的單機實現Spring的事件模型ApplicationEvent。

第一步、分別自定義訂閱和通知事件,繼承ApplicationEvent

第二步、分別定義事件監聽器,實現ApplicationListener

第三步、使用容器發佈事件(訂閱事件、通知事件)

項目運用
  • 定義通知事件NotifyEvent。
  • FollowController.sendNotify()-->發送關注通知。
  • NotifyEventHandler,關注事件監聽處理類,保存通知到數據庫
spring中的運用

 

知識拓展--@EventListener

有條件的事件處理

爲了使註釋@EventListener的功能更強大,Spring 4.2支持用SpEL表達式表達事件類型的方式

定義事件

public class TestEvent extends ApplicationEvent {
 
  
    public boolean isImport;
 
  
    public TestEvent(Object source, boolean isImport) {
        super(source);
        this.isImport = isImport;
    }
 
  
    public boolean isImport() {
        return isImport;
    }
 
  
    public void setImport(boolean anImport) {
        isImport = anImport;
    }
 
  
    @Override
    public String toString() {
        return "TestEvent{" +
                "isImport=" + isImport +
                '}';
    }
}

定義監聽

@Component
public class EventHandler {
 
  
    @EventListener(condition="#testEvent.isImport")
    public void TestEventTest(TestEvent testEvent) {
        System.out.println("==============TestEvent==============" + testEvent.toString());
    }
}
知識拓展--guava的EventBus

定義事件

public class GuavaEvent {
 
  
    private final int message;
 
  
    public GuavaEvent(int message) {
        this.message = message;
        System.out.println("event message:"+message);
    }
 
  
    public int getMessage() {
        return message;
    }
 
  
}

定義事件監聽

public class GuavaEventListener {
    public int lastMessage = 0;
 
  
    @Subscribe
    public void listen(GuavaEvent event) {
 
  
        lastMessage = event.getMessage();
        System.out.println("guava--------Message:"+lastMessage);
    }
 
  
    public int getLastMessage() { 
        return lastMessage;
    }
 
  
}

發佈事件

//guava test
EventBus eventBus = new EventBus();
GuavaEventListener listener = new GuavaEventListener();
eventBus.register(listener);
 
  
eventBus.post(new GuavaEvent(200));
eventBus.post(new GuavaEvent(300));
 
  
System.out.println("LastMessage:"+listener.getLastMessage());
事件監聽者[Listeners]

監聽特定事件(如,CustomerChangeEvent)

  • 傳統實現:定義相應的事件監聽者類,如CustomerChangeEventListener;
  • EventBus實現:以CustomerChangeEvent爲惟一參數建立方法,並用Subscribe註解標記。

把事件監聽者註冊到事件生產者:

  • 傳統實現:調用事件生產者的registerCustomerChangeEventListener方法;這些方法不多定義在公共接口中,所以開發者必須知道全部事件生產者的類型,才能正確地註冊監聽者;
  • EventBus實現:在EventBus實例上調用EventBus.register(Object)方法;請保證事件生產者和監聽者共享相同的EventBus實例。

按事件超類監聽(如,EventObject甚至Object):

  • 傳統實現:很困難,須要開發者本身去實現匹配邏輯;
  • EventBus實現:EventBus自動把事件分發給事件超類的監聽者,而且容許監聽者聲明監聽接口類型和泛型的通配符類型(wildcard,如 ? super XXX)。

檢測沒有監聽者的事件:

  • 傳統實現:在每一個事件分發方法中添加邏輯代碼(也可能適用AOP);
  • EventBus實現:監聽DeadEvent;EventBus會把全部發布後沒有監聽者處理的事件包裝爲DeadEvent(對調試很便利)。
事件生產者[Producers]

管理和追蹤監聽者:

  • 傳統實現:用列表管理監聽者,還要考慮線程同步;或者使用工具類,如EventListenerList;
  • EventBus實現:EventBus內部已經實現了監聽者管理。

向監聽者分發事件:

  • 傳統實現:開發者本身寫代碼,包括事件類型匹配、異常處理、異步分發;
  • EventBus實現:把事件傳遞給 EventBus.post(Object)方法。異步分發能夠直接用EventBus的子類AsyncEventBus。
圖片上傳模塊

@PostContruct

是spring框架的註解,在方法上加該註解會在項目啓動的時候執行該方法,也能夠理解爲在spring容器初始化的時候執行該方法。

 

從這個文件開始看:

  • UploadController
  • 圖片上傳的入口,經過upload方法上傳圖片,並返回上傳結果
  • FileRepo
  • 圖片上傳接口,定義圖片上傳應該擁有的全部相關方法
  • AbstractFileRepo
  • 實現FileRepo接口,把實現類基礎公用部分的方法實現。
  • FileRepoImpl
  • 繼承抽象類AbstractFileRepo,重寫getRoot()方法,爲絕對路徑保存圖片
DateFormatUtils.format(new Date(), YYYYMMDDHHMMSS);
//輸出值格式例如:/2018/0527/27160051
圖片壓縮關鍵類-- Thumbnails
  • 指定大小進行縮放
  • 按照比例進行縮放
  • 不按照比例,指定大小進行縮放
  • 旋轉
  • 水印
  • 裁剪
  • 轉化圖像格式
  • 輸出到OutputStream
  • 輸出到BufferedImage
<dependency>
   <groupId>net.coobird</groupId>
   <artifactId>thumbnailator</artifactId>
   <version>0.4.8</version>
</dependency>

使用例子:

Oauth2.0協議運用

傳統受權方式缺點:

(1)網站爲了後續的服務,會保存用戶的密碼,這樣很不安全。

(2)Google不得不部署密碼登陸,而咱們知道,單純的密碼登陸並不安全。

(3)網站擁有了獲取用戶儲存在Google全部資料的權力,用戶無法限制網站得到受權的範圍和有效期。

(4)用戶只有修改密碼,才能收回賦予網站的權力。可是這樣作,會使得其餘全部得到用戶受權的第三方應用程序所有失效。

(5)只要有一個第三方應用程序被破解,就會致使用戶密碼泄漏,以及全部被密碼保護的數據泄漏。

OAuth就是爲了解決上面這些問題而誕生的。

協議原理

 

(A)用戶打開客戶端之後,客戶端要求用戶給予受權。

(B)用戶贊成給予客戶端受權。

(C)客戶端使用上一步得到的受權,向認證服務器申請令牌。

(D)認證服務器對客戶端進行認證之後,確認無誤,贊成發放令牌。

(E)客戶端使用令牌,向資源服務器申請獲取資源。

(F)資源服務器確認令牌無誤,贊成向客戶端開放資源。

 

OAuth的校驗流程爲何這麼複雜,直接受權以後redirect回accessToken不就結了嗎?爲何還要返回auth_code以後請求accessToken?

首先,redirect是不安全的,隨時能夠暫停回調而拿到accessToken,拿到了accessToken也就意味着拿到了受權,可是auth_code是和client相對應的,那麼即便拿到了auth_code還須要再次申請accessToken,申請accessToken時須要校驗Client和state。同時協議設計的原則就是隻有Client能拿到accessToken而用戶是拿不到的。

Oauth2.0安全使用建議

資源提供方:

  • 對client_id和回調地址作嚴格校驗
  • 獲取access token的code僅能使用一次,且與受權用戶關聯
  • 儘可能避免直接讀取當前用戶session進行綁定
  • 有效使用client_secret參數

資源使用方:

  • 使用Authorization Code方式進行受權
  • 受權過程使用state隨機哈希,並在服務端進行判斷
  • 儘可能使用HTTPS保證受權過程的安全性
  • 最後,對oauth2.0有詳細的瞭解,嚴格按照流程進行開發。
QQ受權登陸

QQ登陸OAuth2.0整體處理流程以下:

Step1:申請接入,獲取appid和apikey

Step2:開發應用,並設置協做者賬號進行測試聯調;

Step3放置QQ登陸按鈕

Step4:經過用戶登陸驗證和受權,獲取Access Token

Step5:經過Access Token獲取用戶的OpenID

Step6調用OpenAPI,來請求訪問或修改用戶受權的資源。

 

QQ互聯官網開發攻略:http://wiki.connect.qq.com/%E5%BC%80%E5%8F%91%E6%94%BB%E7%95%A5_server-side

 

項目接入:

  • QQ登陸按鈕連接
  • http://localhost:8080/oauth/callback/call_qq
  • 獲取Authorization Code
  • https://graph.qq.com/oauth2.0/authorize?response_type=code&redirect_uri=&state=0vnuc37nwskcs9cr3yo1wvaq&client_id=
  • 經過Authorization Code獲取Access Token
  • https://graph.qq.com/oauth2.0/token?code=&client_id=&client_secret=&grant_type=&authorization_code&redirect_uri=
  • 經過accessToken獲取openid
  • https://graph.qq.com/oauth2.0/me?access_token=
  • 經過accessToken和openid獲取用戶信息
  • https://graph.qq.com/user/get_user_info?access_token=&oauth_consumer_key=&openid=&format=json
  • 判斷是否已經註冊,爲註冊跳轉到/bind_oauth方法進行帳號註冊與綁定。而後使用shiro登陸。
相關文章
相關標籤/搜索