介紹
使用的技術
需求分析
數據模型
應用架構
數據層
控制器
動作處理人員
視圖
過濾器
結果
基於Java的Web應用程序,用於TODO活動。通過此Web應用程序,可以通過現代的Web瀏覽器創建,讀取和更新其Todos。該應用程序還實現了AAA,這意味著每個用戶都有自己的帳戶,待辦事項列表等都是私人的。
該文檔不適合新手。您必須擁有以下技術的良好知識,以了解本文檔和相關應用程序:
爪哇
Servlet,JSP
Apache Tomcat
mysql
html
Apache Netbeans IDE
Firefox
這完全是一個後端項目。因此,不使用前端技術,例如CSS,JavaScript。該項目的目的是有效地學習和展示不同的Java Servlet API的不同部分。
我們將從需求分析開始開發Web應用程序。然後,我們將繼續進行數據庫設計。數據是任何Web應用程序的核心。幾乎所有用例都涉及數據。一旦準備好了Web應用程序的數據模型,我們將繼續設計應用程序的體系結構。在此階段,我們將看到我們的應用程序對不同的HTTP動作的行為。因為,應用程序用戶執行的所有操作都是通過HTTP進行的。我們將考慮所有可能的用戶行動並清楚地定義它們。接下來,我們將繼續設計界面和類。
對於我們的應用程序,我們首先定義一個對我們來說是什麼。待辦事項是必須完成的任務。我們創建了此類任務的列表,以幫助我們過上普遍的生活。當我們將任務完成一個接一個地完成任務時,我們一直在跟踪列表。我們的待辦事項對我們的屬性低於屬性:
最初,任務將具有“待辦事項”狀態。當我們開始研究它時,我們將其更改為“正在進行”。完成任務後,我們將其狀態標記為“完成”。
我們希望我們的應用程序還支持多個用戶。每個用戶都有自己的私人列表。因此,用戶看不到他人的待辦事項列表。用戶應由其用戶名確定,這是他們對我們的有效電子郵件地址。在我們的應用程序上為用戶提供帳戶。因此,一個帳戶的屬性低於屬性:
我們需要一個“管理員”帳戶才能管理帳戶。管理員帳戶應使用用戶名“管理員”。管理員用戶可以:
最後兩個項目值得觀察。通常,據信具有“管理員”權利的用戶可以訪問每個人的信息。我們不想要這樣。另外,我們已經定義了我們的“管理”帳戶只是管理帳戶。這不是用於管理用戶列表。 “管理員”用戶帳戶不經常使用。它僅用於特殊目的。對於我們的應用程序,我們希望一個用戶帳戶還可以處理“管理員”帳戶。因此,只有在需要時才使用“管理員”憑據登錄時,它將是同一個人。因為它是使用“管理員”帳戶僅用於管理所有帳戶的現有用戶帳戶,所以我們不希望管理帳戶單獨的任務列表。這沒有任何目的。
我們希望待辦事項列表始終持續。這意味著,一旦用戶成功地創建了一個待辦事項,就永遠無法刪除它。同樣,我們也不想刪除用戶帳戶。總之,我們不想支持應用程序中的“刪除”操作。因此,我們只支持CRU中的Crud。
因為,我們希望我們的應用程序維護私人待辦事項列表,我們希望該應用程序提供登錄和註銷機制。這稱為“身份驗證”。包括“管理員”在內的每個用戶都應首先對自己進行身份驗證。成功身份驗證後,用戶將被重定向到其工作區。因為我們正在討論兩種類型的用戶(一種admin,另一種是正常),所以我們的應用程序中將有兩種類型的工作區。管理員用戶應僅使用用戶帳戶管理工作區。普通用戶應僅使用TODO列表管理工作區。兩者都是獨家。普通用戶看不到管理員的工作區。和管理員用戶看不到普通用戶的工作空間。這稱為“授權”。
除上述要求外,我們還希望我們的應用程序存儲用戶登錄時間戳和註銷時間戳的詳細信息。通過此,我們正在跟踪用戶在應用程序上的活動。這不是完全來自AAA的“會計”,但對於我們的應用程序,這目的是將其稱為“會計”。
根據到目前為止我們已收集的要求,我們了解我們必須為應用程序實體存儲數據:
少數帳戶的示例數據:
| 帳戶ID | 使用者名稱 | 名 | 姓 | 密碼 | 創建在 | 地位 |
|---|---|---|---|---|---|---|
| 1 | 行政 | 行政人員 | 用戶 | 密碼 | 2020-05-06 17:34:04 | 啟用 |
| 2 | [email protected] | 約翰 | 約翰遜 | 單詞 | 2020-05-07 12:34:04 | 禁用 |
| 3 | [email protected] | 埃里克 | 愛立信 | Twoword | 2020-05-08 13:34:04 | 啟用 |
| 4 | [email protected] | 安娜 | 瑪麗 | 三個字 | 2020-05-09 11:34:04 | 啟用 |
我們看到整個表中都重複了帳戶狀態。因此,作為數據庫標準化的一部分,最好將重複的數據放在單獨的表中。背後有一些充分的理由。假設我們有100位用戶。我們希望分別用1和2替換啟用和禁用的單詞。我們必須修改表所有行的狀態列。想像一下,對數千行進行這種修改會多麼麻煩!值得慶幸的是,救援的數據庫標準化!
歸一化後,我們將有兩個表 - councor_statuses和帳戶:
councor_statuses
| ID | 地位 |
|---|---|
| 1 | 啟用 |
| 2 | 禁用 |
帳戶
| 帳戶ID | 使用者名稱 | 名 | 姓 | 密碼 | 狀態ID |
|---|---|---|---|---|---|
| 1 | 行政 | 行政人員 | 用戶 | 密碼 | 1 |
| 2 | [email protected] | 約翰 | 約翰遜 | 單詞 | 2 |
| 3 | [email protected] | 埃里克 | 愛立信 | Twoword | 1 |
| 4 | [email protected] | 安娜 | 瑪麗 | 三個字 | 2 |
同樣,我們將有三個表用於任務 - task_statuses,task_priorities和Task:
task_statuses
| ID | 地位 |
|---|---|
| 1 | 托多 |
| 2 | 進行中 |
| 3 | 完畢 |
task_priorities
| ID | 優先事項 |
|---|---|
| 1 | 重要和緊急 |
| 2 | 重要但不緊急 |
| 3 | 並不重要,但緊急 |
| 4 | 並不重要,也不緊急 |
任務
| 任務ID | 帳戶ID | 細節 | 創建在 | 最後期限 | 最後更新 | 狀態ID | 優先ID |
|---|---|---|---|---|---|---|---|
| 1 | 2 | 購買鉛筆。 | 2019-05-06 17:40:03 | 2019-05-07 17:40:03 | 2 | 1 | |
| 2 | 3 | 買書。 | 2019-05-07 7:40:03 | 2019-05-07 17:40:03 | 2019-05-07 23:40:03 | 2 | 1 |
最後,我們還有另一個需要存儲帳戶會話數據的要求。我們將如下表所示存儲它:
counduct_sessions
| 會話ID | 帳戶ID | 會話創建 | 會話結束 |
|---|---|---|---|
| ASD1GH | 1 | 2019-05-06 17:40:03 | 2019-05-06 18:00:03 |
通常,在企業應用程序中,ID並未作為整數存儲。因為只要使用整數就可以更輕鬆地查詢其他信息!在現實世界應用中,ID不是數字,而是字母數字,具有100個字符。因此,使某人無法猜測另一個ID!
我們將數據庫稱為MySQL中的“ todo”。這是基於上述信息構建的數據模型:

我們將按照著名且廣泛使用的MVC 2 Desgin模式來開發此應用。下圖顯示了我們將如何為應用程序實施MVC: 
我們的申請將是基於操作的。當用戶將HTTP請求發送到我們的應用程序時,我們將其轉換為應用程序上的相應操作。我們支持的操作是創建,閱讀和更新(CRU)。我們的應用程序本質上是數據驅動的。它有助於數據庫上的操作。它可以幫助用戶在身份驗證和授權機制的幫助下安全可安全地存儲他們的數據。它充當數據庫的基於HTML和HTTP的接口。
當用戶向我們的應用程序提出HTTP請求時,我們以HTML的形式發送了請求的數據。 HTML支持鏈接和表格,以幫助用戶與Web應用程序進行交互。鏈接用於檢索/獲取(HTTP GET)信息,而表格用於將數據(HTTP POST)發佈到Web應用程序。
因此,這就是我們將如何將HTTP請求轉化為操作:
| HTML元素 | HTTP方法 | 申請行動 |
|---|---|---|
| 超連結 | HTTP獲取 | 閱讀詳細信息 |
| 形式 | HTTP帖子 | 創建或更新 |
HTTP GET將數據作為查詢參數發送到URL。同時,HTTP Post在HTTP請求主體中發送數據。 HTTP Post不會通過URL揭示數據,而HTTP獲得了。因此,HTTP GET不適合發送登錄憑據。沒有人願意看到他們的用戶名和密碼附加到HTTP請求URL上!我們將在登錄時使用HTTP Post發送用戶的憑據。
因此,我們已經決定如何使用HTTP,HTML和數據庫。 HTTP的另一個概念對於我們了解基於HTTP的應用程序的體系結構至關重要。那是URL-統一的資源定位器。這是示例服務器上託管的名為“ WebApp”的Web應用程序的示例URL:
http://www.example.com/webapp/details?id=12
在上面的示例URL中,“ http”是協議, www.example.com是域名或服務器名稱,“ webapp”是部署在服務器上的應用程序上下文,“詳細信息”是我們發送http請求的應用程序。 'id'是我們傳遞給具有“ 12”的“詳細信息”的查詢參數。查詢參數為我們提供了一種機制,可以將參數傳遞到Web應用程序,並從Web應用程序中響應中接收相關內容。例如,想像一個在Web服務器上運行的天氣應用程序。我們可以將位置作為查詢參數的選擇發送到Web應用程序,而不是向我們提供所有位置的天氣報告列表。然後,該應用程序將以響應我們選擇位置的天氣詳細信息發送。
與我們的PC上運行的應用程序不同,Web應用程序在Web服務器上運行。在Web服務器上運行的Java應用程序稱為Servlet。 servlet效仿Web應用程序。它們在容器中運行。 Apache Tomcat是這樣一個容器的熱門示例。容器軟件將RAW HTTP請求和響應轉換為Java對象,並為Servlet提供。一個靜態網站為每個HTTP請求提供相同的內容。但是,servlet可以生成每個HTTP請求不同的動態內容。
我們正在構建的TODO Web應用程序將包含多個部分 - servlet,過濾器,JSP文件,數據庫類,Pojos等。我們將在容器(tomcat)上的一個應用程序上下文(或應用程序環境)下將它們全部放在一起(例如捆綁它們)。我們將此申請上下文稱為“ todo”。因此,如果我們在端口8080上的PC上運行Tomcat,則可以通過URL訪問我們的應用程序上下文“ todo”:
http://localhost:8080/todo/
我們的數據層由JDBC數據源上的DAO模式和工廠模式的實現組成。我們選擇JDBC DataSource而不是DriverManager,因為我們希望從連接池的好處中獲得好處。
我們只有一個用作控制器的servlet,稱為“主”。用戶的HTTP請求對我們來說是一個動作。因此,我們的控制器servlet的目的是為提出的HTTP請求選擇適當的操作。控制器servlet選擇一個動作處理程序,然後將其交給用戶提出的請求。我們不會在控制器中編寫操作執行步驟。我們保持清潔和苗條。其目的是“選擇”動作處理程序。不要單獨“執行”動作。操作處理程序執行請求的操作後,控制器將接收“下一步”,以作為動作處理程序的響應執行。 Controller的工作是簡單地選擇執行請求響應的資源。總之,我們使控制器遠離所有業務邏輯。
我們將將我們的控制器servlet映射到URI模式/app/* 。因此,我們的控制器servlet將處理遵循模式/app/每個URI。
用戶要求的操作是我們應用程序的業務邏輯。動作處理程序是我們MVC實施中的模型。每個動作都必須實現操作接口:
public interface Action {
/*
An action is supposed to execute and return results. ActionResponse represents the response.
*/
public abstract ActionResponse execute ( HttpServletRequest request , HttpServletResponse response )
throws Exception ;
}一項措施應該執行並返回結果。我們為這種結果創建一個特殊的類 - ActionResponse。動作處理程序可以選擇“向前”或“重定向”。
public class ActionResponse {
private String method ;
private String viewPath ;
public ActionResponse () {
this . method = "" ;
this . viewPath = "" ;
}
public void setMethod ( String method ) {
this . method = method ;
}
public String getMethod () {
return this . method ;
}
public void setViewPath ( String viewPath ) {
this . viewPath = viewPath ;
}
public String getViewPath () {
return this . viewPath ;
}
@ Override
public String toString () {
return this . getClass (). getName ()+ "[" + this . method + ":" + this . viewPath + "]" ;
}
}我們實施工廠設計模式。我們創建一個工廠類-ActionFactory-為我們提供所需的動作處理程序類:
public class ActionFactory {
private static Map < String , Action > actions = new HashMap < String , Action >() {
{
put ( new String ( "POST/login" ), new LoginAction ());
put ( new String ( "GET/login" ), new LoginAction ());
put ( new String ( "GET/logout" ), new LogoutAction ());
put ( new String ( "GET/admin/accounts/dashboard" ), new AdminAccountsDashboardAction ());
put ( new String ( "GET/admin/accounts/new" ), new AdminNewAccountFormAction ());
put ( new String ( "POST/admin/accounts/create" ), new AdminCreateAccountAction ());
put ( new String ( "GET/admin/accounts/details" ), new AdminReadAccountDetailsAction ());
put ( new String ( "POST/admin/accounts/update" ), new AdminUpdateAccountAction ());
put ( new String ( "GET/tasks/dashboard" ), new UserTasksDashboardAction ());
put ( new String ( "GET/tasks/new" ), new UserNewTaskFormAction ());
put ( new String ( "GET/tasks/details" ), new UserReadTaskDetailsAction ());
put ( new String ( "POST/tasks/create" ), new UserCreateTaskAction ());
put ( new String ( "POST/tasks/update" ), new UserUpdateTaskAction ());
put ( new String ( "GET/users/profile" ), new UserReadProfileAction ());
put ( new String ( "POST/users/update" ), new UserUpdateProfileAction ());
}
;
};
public static Action getAction ( HttpServletRequest request ) {
Action action = actions . get ( request . getMethod () + request . getPathInfo ());
if ( action == null ) {
return new UnknownAction ();
} else {
return action ;
}
}
}我們的控制器servlet的目的是:
protected void processRequest ( HttpServletRequest request , HttpServletResponse response )
throws ServletException , IOException {
Action action = ActionFactory . getAction ( request );
try {
ActionResponse actionResponse = action . execute ( request , response );
if ( actionResponse . getMethod (). equalsIgnoreCase ( "forward" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":forward:" + actionResponse );
this . getServletContext (). getRequestDispatcher ( actionResponse . getViewPath ()). forward ( request , response );
} else if ( actionResponse . getMethod (). equalsIgnoreCase ( "redirect" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":redirect:" + actionResponse );
if ( actionResponse . getViewPath (). equals ( request . getContextPath ())) {
response . setHeader ( "Cache-Control" , "no-cache, no-store, must-revalidate" );
response . setHeader ( "Pragma" , "no-cache" );
response . setDateHeader ( "Expires" , 0 );
}
response . sendRedirect ( actionResponse . getViewPath ());
} else if ( actionResponse . getMethod (). equalsIgnoreCase ( "error" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":error:" + actionResponse );
response . sendError ( 401 );
} else {
System . out . println ( this . getClass (). getCanonicalName () + ":" + actionResponse );
response . sendRedirect ( request . getContextPath ());
}
} catch ( Exception e ) {
e . printStackTrace ();
}
} 下表顯示了我們的應用程序響應及其相關的操作處理程序的HTTP請求列表:
| 用戶的預期操作 | HTTP請求URI | 動作處理程序 |
|---|---|---|
| 提交空登錄憑據 | GET /app/login | 登入 |
| 提交登錄憑據 | POST /app/login | 登入 |
| 獲取帳戶儀表板 | GET /app/admin/accounts/dashboard | AdminAccountsDashboardAction |
| 獲取新帳戶表 | GET /app/admin/accounts/new | AdminNewAcCountformaction |
| 提交新的帳戶詳細信息 | POST /app/admin/accounts/create | AdminCreateAccountaction |
| 獲取帳戶的詳細信息 | GET /app/admin/accounts/details?id=xx | AdminReadAccountDetailsaction |
| 更新帳戶的詳細信息 | POST /app/admin/accounts/update | AdminupDateAccountaction |
| 獲取任務儀表板 | GET /app/tasks/dashboard | USERTASKSDASHBOARDACTION |
| 獲取新的任務表格 | GET /app/tasks/new | USERNEWTASKFORMACTION |
| 提交新的任務詳細信息 | POST /app/tasks/create | UserCreatetAskAction |
| 獲取任務的詳細信息 | GET /app/tasks/details?id=xx | UserReadTaskDetailsaction |
| 更新任務的詳細信息 | POST /app/tasks/update | UserUpDateTaskAction |
| 獲取我的個人資料的詳細信息 | GET /app/users/profile | UserReadProfileAction |
| 更新我的個人資料詳細信息 | POST /app/users/update | 用戶UpdateProfileAction |
| 註銷 | GET /app/logout | 註銷 |
操作處理程序的工作是執行業務邏輯,並選擇適當的視圖組件作為對用戶提出的請求的響應。下表顯示了所有動作處理程序及其視圖組件:
| 動作處理程序 | 查看組件 |
|---|---|
| 登入 | /WEB-INF/pages/admin/accounts/dashboard.jsp/WEB-INF/pages/tasks/dashboard.jsp |
| AdminAccountsDashboardAction | /WEB-INF/pages/admin/accounts/dashboard.jsp |
| AdminNewAcCountformaction | /WEB-INF/pages/admin/accounts/newAccount.jsp |
| AdminCreateAccountaction | /WEB-INF/pages/admin/accounts/createAccountResult.jsp |
| AdminReadAccountDetailsaction | /WEB-INF/pages/admin/accounts/accountDetails.jsp |
| AdminupDateAccountaction | /WEB-INF/pages/admin/accounts/updateAccountResult.jsp |
| USERTASKSDASHBOARDACTION | /WEB-INF/pages/tasks/dashboard.jsp |
| USERNEWTASKFORMACTION | /WEB-INF/pages/tasks/newTask.jsp |
| UserCreatetAskAction | /WEB-INF/pages/tasks/createTaskResult.jsp |
| UserReadTaskDetailsaction | /WEB-INF/pages/tasks/taskDetails.jsp |
| UserUpDateTaskAction | /WEB-INF/pages/tasks/updateTaskResult.jsp |
| UserReadProfileAction | /WEB-INF/pages/users/viewProfile.jsp |
| 用戶UpdateProfileAction | /WEB-INF/pages/users/updateProfileResult.jsp |
| 未知 | /WEB-INF/pages/users/unknownAction.jsp |
視圖組件構建將發送給用戶所需的HTML響應。查看組件讀取操作處理程序設置的消息,並將其顯示給用戶。
我們使用過濾器攔截傳入的HTTP請求。所有過濾器將在請求傳遞給控制器servlet之前使用。任何傳入的HTTP請求將首先通過身份驗證過濾器來處理。通過此過濾器,我們檢查用戶是否已登錄。如果未登錄,我們將用戶重定向到登錄頁面。成功通過身份驗證過濾器後,HTTP請求將被另外兩個過濾器攔截。在這些過濾器中,我們檢查URI路徑,用戶是“管理員”或普通用戶。如果普通用戶試圖訪問“ admin” URI路徑,我們將阻止此類訪問。如果“管理員”用戶正在嘗試訪問與任務相關的URI路徑,我們將阻止此類訪問。
只有“管理員”可以以/app/admin/*開頭訪問URI,並且只有普通用戶才能以/app/tasks/*開始訪問URIS。可以訪問其他URIS /app/login , /app/logout , /app/users/* 。
我們不會攔截我們發送的答复。
因此,這就是我們的應用程序的樣子。為了簡單起見,我一直保持UI平原。沒有CSS或JavaScript。










