介绍
使用的技术
需求分析
数据模型
应用架构
数据层
控制器
动作处理人员
视图
过滤器
结果
基于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。










