- 浏览: 265865 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
DragonKiiiiiiiing:
支持楼主,中国互联网太缺这种无私奉献的人了。您的这本书我已拜读 ...
JAVA NIO 全书 译稿 -
sp42:
非常感谢!热部署帮助很大!
Pure JS (2): 热部署 (利用 JDK 7 NIO 监控文件变化) -
sp42:
其实在我的架构中,我更倾向于 JSP 作为前端模板系统~还是写 ...
Pure JS (5.3):pure.render 的实现(构造window对象,实现服务器端 JQuery Template) -
sp42:
非常不错,楼主做的正是鄙人想做的做的,而且比鄙人的来的成熟、健 ...
OMToolkit介绍(5): 总结 -
cfanllm:
楼主辛苦了,,,谢谢分享啊
JAVA NIO 全书 译稿
OMTookit介绍(1) 简单示例:OMSimpleBlog
还是之前介绍过的开源项目OMToolkit(http://code.google.com/p/oh-my-toolkit/),在正式介绍OMToolkit的实现方式之前,先以一个简单的例子(OMSimpleBlog)说明OMToolkit的功能。我们以一个空项目开始(包含了必要的目录结构和文件,和空的Database类),请先下载附件中的OMSimpleBlog_Empty.rar,解压后导入到eclipse中。
1.Entity建模
首先是Entity的建模。OMSimpleBlog的数据存储结构比较简单,只有Database、User、Article三个用于持久化的Entity。建模方式如下:
Database建模
package com.omc.entity; import com.omc.core.*; import com.omc.core.field.*; @SuppressWarnings("unused") public class Database extends Entity { private Children<User> userList; private References<Article> articleList; }
Database类包含了一个用户列表和一个文章列表。
Children与References的区别在于,Children表示严格的父子关系:
1. 当一个Entity被删除时,其所有的Children也将被删除;
2. 当一个新建的Entity对象被添加到parent的Children中时,该对象在事务提交时将被持久化。
3. 当一个Entity对象从parent的Children中移除时,该对象在事务提交时将被删除。
而References仅表示普通的引用关系,与对象的持久化无关。
实际上,所有被持久化的Entity对象都应该是某个其他Entity对象的Children(或Child)之一(Database对象除外)。
User建模
package com.omc.entity; import com.omc.core.*; import com.omc.core.field.*; @SuppressWarnings("unused") public class User extends Entity { private StringField name; private StringField password; private StringField blogName; private Children<Article> articleList; }
User类包含了名称、密码、博客名称和文章列表。
因为这里的博客属性比较简单,只有blogName,所以合并到了User中(否则可以将Blog作为Child<Blog>属性);同时,没有对文章进行分类,所以直接包含了一个文章列表(否则需要包含的是Children<Category>)。
Article建模
package com.omc.entity; import com.omc.core.*; import com.omc.core.field.*; @SuppressWarnings("unused") public class Article extends Entity { private DateField published; private StringField title; private TextField summary; private TextField content; private Reference<User> user; }
Article类包含了发布时间、标题、摘要、全文内容和一个对User的引用。
这里需要特别说明的,是TextField这个类型。实际上是继承自Child<Text>,即内部封装了实体Text,这么做的目的是为了利用Child的延迟加载机制,因为我们并非总是需要显示文章的摘要和全文内容,而这两个字段通常又比较长,读取比较耗时。
同时,OMToolkit针对TextField类,在通过URL赋值、视图渲染时进行了特殊处理,使得它的使用与一般的StringField的使用没有很大区别,甚至还增加了getHtml()方法,使得我们在视图可以使用类似${content.html}的写法显示转为html的全文(不过目前这个方法还不完善,只能进行一些简单的转换)。
这些用于建模的字段(StringField、DateField、Textfield、Children、References等),都继承自OMField,它们都会被持久化存储。而基本类型(String、int、long等)的属性不会被持久化,通常被用作显示相关的属性,这点后面将会提到。
好了,到此为止,就完成了所有需要持久化的Entity的建模,目前的代码应该与附件中的OMSimpleBlog_Step1.rar类似。接下来,我们将实现用户的注册与登录等操作。
2.用户注册与登录
母版页面
在views/Master文件夹中增加三个母版页面:
views/Home/master.html
<html> <head> <meta http-equiv = "content-type" content = "text/html; charset=GBK" /> <title> Blog : #abstract title </title> <link rel="stylesheet" type="text/css" href="/resources/style.css" /> </head> <body> <div align="center"> <img src="/resources/banner.jpg" /> </div> <div align="center" id="box"> #abstract content </div> </body> <script defer type="text/javascript" src="/resources/script.js"></script> </html>
这个页面是“母版的母版”,包含了基本的html结构,并引用了resources文件夹中的图片、css、js等。其中标注“#abstract”的部分,表示可以被覆盖的部分。我们将在接下来的页面中看到如何进行继承和覆盖:
views/Master/home.html
#extends /Master/master #override title 博客首页 #override content <div id="header"> #abstract header </div> <h1>Oh, My Simple Blog!</h1> #abstract content
这个页面继承了master.html,以“博客首页”覆盖了“#abstract title”,并用一些html片段覆盖了“#content”,不过也引入了新的“#abstract header”和“#abstract content”,又将在继承它的页面中被覆盖。
首页、登录、注册页面
接下来,在views/Home文件夹中增加三个页面:
views/Home/index.html
#extends /Master/home #override header <a href="/User/get/id/${userId}/pageSize/10">我的博客</a> <a href="/User/edit/id/${userId}/pageSize/10">博客设置</a> <a href="/User/logout">注销登录</a> #override content
这是网站的首页,不过也因为现在还没有做文章显示的部分,所以先用空行覆盖“#abstract content”。
views/Home/login.html
#extends /Master/home #override header <div class="error">${error}</div> <form action="/User/login" method="post"> <span class="pRight"> 用户名:<input name="name" id="用户名" class="input" /> </span> <span class="pRight"> 密码:<input type="password" name="password" id="密码" class="input" /> </span> <span class="pRight"> 记住我:<input type="checkbox" name="remember" /> </span> <span class="pRight"> <input type="submit" value="登录" /> </span> <a href="/Home/register">注册新用户</a> <a href="/Admin">管理员登录</a> </form> #override content
这是用户登录页面,包含了一个提交登录信息的表单。
views/Home/register.html
#extends /Master/home #override header <div class="error">${error}</div> <form action="/User/save" method="post"> <span class="pRight"> 用户名:<input name="name" id="用户名" maxlength="20" class="input" /> </span> <span class="pRight"> 密码:<input type="password" name="password" id="密码" maxlength="20" class="input" /> </span> <span class="pRight"> 博客名称:<input name="blogName" id="博客名称" maxlength="20" class="input" /> </span> <span class="pRight"> <input type="submit" value="注册" /> </span> <a href="/Home/login">用户登录</a> <a href="/Admin">管理员登录</a> </form> #override content
这是用户注册页面,包含了一个提交注册信息的表单。
现在可以运行网站了(eclipse中的Run as -> Java Appliaction)。启动网站后,在浏览器中输入http://localhost,可以看到index.html,输入http://localhost/Home/login和http://localhost/Home/register,也可以看到相应的页面。(如果80端口已经被占用,可以到Cfg.cfg中修改port参数)
效果如图:
实现Home和User中的方法
接下来,我们开始实现后台处理过程。首先在com.omc.entity中新增Home类:
package com.omc.entity; import com.omc.core.*; import com.omc.core.Annotations.*; @Ignore public class Home extends Entity { @Role("user") public String index() throws Exception { return toView(); } }
@Ignre表示这个Entity是不会持久化的。@Role("user")表示只有登录后才能访问index页面,否则将重定向到登陆页面。
现在需要修改一下Cfg.cfg,将
loginEntity=Home loginAction=index
这两行,修改为
loginEntity=User loginAction=checkLogin
接下来,实现User类中的处理方法。
首先,在User类中增加以下属性:
private Session session; private List<Cookie> cookies; private String error = ""; private boolean remember;
这些属性都不会被实例化,只是“视图属性”。
session和cookies这两个属性将被自动赋值,用于操作session和cookie。error属性用于保存错误信息并显示在页面上。remember属性用于接收 login.html 的“remember(记住我)”参数。
接着,在User中增加 checkLogin() 方法和 checkUser()方法,用于检查用户的登录状态:
public String checkLogin() throws Exception { name.set(Cookie.get(cookies, "name")); password.set(Cookie.get(cookies, "password")); return toView("/Home/" + (checkUser() ? "index" : "login")); } private boolean checkUser() throws Exception { for (Reference<Entity> entity : doList()) { User user = (User) entity.get(); if (name.equals(user.name) && password.equals(user.password)) { session.set("role", "user"); session.set("userId", user.getId()); return true; } } return false; }
这些代码将先检查cookie中保存的用户名和密码(简单起见,这里的密码没有加密),然后尝试自动登录,登录失败则导航至 login 页面。现在,再次启动服务器,输入http://localhost,将会被导航至登录页面,而不是index页面。
接着,实现“注册”方法,在User中加入下列代码:
@UpdateOptions(toUpdate = { "name[20]", "password[20]", "blogName[20]" }) public String save() throws Exception { if (exists()) { error = cfg("exists") + '\n'; return toView("/Home/register"); } doSave(); session.set("role", "user"); session.set("userId", getId()); return toView("/Home/index"); } private boolean exists() throws Exception { for (Reference<Entity> entity : doList()) { User user = (User) entity.get(); if (name.equals(user.name)) { return true; } } return false; }
这里首先要说明的是@UpdateOptions注解,这个注解有以下作用:
1. 说明这个方法是中更新的对象将在事务提交时被持久化;对于创建的对象,如果被添加到parent的Children中,也会被持久化。
2. 在标有这个注解的方法中获取对象时,将同时申请该对象的更新锁,这使得其他线程对该对象的访问需要等待。
3. 将会对输入URL进行检查,如这里的写法,表示输入URL中包含的Field只能有name、password和blogName,长度最大为20。
4. @UpdateOptions还有属性 String[] allowEmpty() default {},表示允许为空的输入。
5. 不符合输入标准的URL将被重定向至登录页面。
总之,这个注解通知OMToolkit准备进行持久化(修改DB),同时也限制了输入,以确保安全。
然后,让我们看看方法中的实现逻辑:
1. 检查用户是否已经存在,如果存在则从配置文件中提取错误信息,记录到error变量中,并返回注册页面。
为了便于配置错误信息,需要在Cfg文件夹下创建文件User.cfg来记录错误信息:
exists=该用户名已存在!
2. doSave()方法是继承而来的,表示将自己添加到parent的*List列表中,这里是Database对象的userList列表中。
由于不会对parent进行持久化,所以parent通常是需要在URL中指定,否则将视为database。
3. 在session中保存role和userId,表示用于已登录,然后显示index页面。
现在可以在此启动服务器,在登录页面中,点击“注册新用户”链接,然后输入用户名、密码、博客名称,点击“注册”按钮,没有问题的话,就会返回index页面了。
“注册”之后,当然就需要“登录”了。在User类中增加登录相关的代码:
public String login() throws Exception { if (!checkUser()) { error = cfg("error") + '\n'; return toView("/Home/login"); } if (remember) { cookies.add(new Cookie("name", name.get())); cookies.add(new Cookie("password", password.get())); } return toView("/Home/Index"); }
这段代码表示:先检查用户名、密码是否正确,错误则回到登录页面并显示错误,然后检查“记住我”单选框是否被选中,如果选中则在cookie中保存用户名和密码。同时,还需要在User.cfg中添加一行错误信息:
error=用户名或密码错误!
现在启动服务器,用刚刚注册过的用户名登录吧!
最后,是“注销登录”,在User类中加入:
public String logout() throws Exception { session.set("userId", null); session.set("role", null); cookies.add(new Cookie("name", "")); cookies.add(new Cookie("password", "")); return toView("/Home/login"); }
没什么特别的逻辑,清空session和cookie,回到登录页面。
“我的博客”与“博客设置”
到目前为止,首页上的“我的博客”与“博客设置”链接都还无法使用。不过由于文章的部分还没有实现,现在能够显示和设置的内容也十分有限。
首先,增加以下页面:
views/master/user.html
#extends /Master/master #override title ${blogName} #override content <div id="header"> <a href="/">回到首页</a> <a href="/User/get/id/${id}/pageSize/10">文章列表</a> </div> <h1>${blogName}</h1> ${name} #abstract content
views/User/get.html
#extends /Master/user #override content
views/User/edit.html
#extends /Master/user #override content <form action="/User/update" method="post"> <input type="hidden" name="id" value="${id}" /> <input type="hidden" name="pageIndex" value="${pageIndex}" /> <input type="hidden" name="pageSize" value="${pageSize}" /> 博客名称:<input name="blogName" id="博客名称" maxlength="20" value="${blogName}" /> <input type="submit" value="保存" /> </form>
在User中增加edit()方法:
@Role("user") public String edit() throws Exception { return toView(); }
get()方法可以先不加,表示只需要简单地调用toView()就行了。不过后面要显示分页后的文章时,就需要增加get()方法了。
好了,到目前为止,代码应该与附件中的OMSimpleBlog_Step2.rar相似了,运行看看,是否一切正常?遇到问题的话,请看看log.txt中记录的信息,并反馈给我。
3.文章发布、编辑和删除
光是注册和登录,肯定是不够的。一个blog,至少也要能显示文章吧?接下来我们就实现文章的发布、编辑和删除。
Databse与User的修改
在此之前,让我们将Database类和User类稍作修改。在Database类中加入下列代码:
public void saveArticle(Article article) { articleList.add(article); } public void deleteArticle(long id) { articleList.remove(id); }
在User类中增加get()方法,并修改edit()方法:
private List<Reference<Article>> pagedArticles; public String get() throws Exception { pagedArticles = PageUtil.run(articleList, getRequest()); return toView(); } @Role("user") public String edit() throws Exception { return get(); }
因为我们需要在显示用户的blog的同时显示分页后的文章,所以借助了分页辅助类PageUtil。这个辅助类将按照 id 降序排列文章,由于id的生成是升序的,实际上最新的文章将显示在最前面。
保存文章的处理过程
先在Article中加入下列代码:
private Session session; private interface Getable { public String get() throws Exception; } @Role("user") @UpdateOptions(toUpdate = { "title[100]", "summary[500]", "content[50000]" }) public String save() throws Exception { user.set((User) getParent()); return checkOwner(new Getable() { public String get() throws Exception { published.set(new Date()); doSave(); getDatabase().saveArticle(Article.this); return userEditAction(); } }); } private String checkOwner(Getable afterCheck) throws Exception { Object userId = session.get("userId"); if (userId == null || (Long) userId != user.getId()) { getTransaction().rollback(); return toView("/Home/login"); } return afterCheck.get(); } private Database getDatabase() throws Exception { return ((Database) getDatabse()); } private String userEditAction() throws Exception { return action("/User/edit/pageSize/10/id/" + user.getId()); }
好的,增加了不少代码。不用着急,让我们一点一点地分析:
首先,Getable这个接口,被用于checkOwner()方法,表示确认当前用户的合法性(防止非法修改别人的博客)之后,真正执行的操作及返回的页面。
为什么这么做?因为几乎所有对文章的操作都要检查用户的合法性,我们不希望重复这些代码,所以将这个逻辑提取为一个方法。后面将会看到这么做的好处。当然也许有的人不喜欢这么做,也没关系,这里不是重点,基本上属于个人喜好。
然后,是getTransaction().rollback(),这表示事务回滚,取消修改。因为已经确认用户的身份是不合法的了,但对象又已经被更新,所以需要在自动提交之前进行回滚。
最后,让我们看看save()方法的逻辑:先将user属性设为指定的parent(这个parent是通过Web请求中的参数指定的),然后检查用户合法性,接着设置发布日期,通过doSave()将自己保存到parent的articleList中,同时也将自己保存到Database的references中,最后转到User.edit()的调用(action()与toView()不同,将会重新模拟一次请求)。
接着,是一些页面的修改和增加。
显示和发布文章的相关页面
views/Home文件夹下的三个页面文件的最后,都需要增加一行:
#include /Article/list/action/register/pageIndex/${pageIndex}/pageSize/10
views/User/get.html,增加:
#if empty 没有文章! #end #if !empty #loop pagedArticles <table width="60%" class="block"> <tr> <td> <p class="listTitle"> <a href="/Article/get/id/${id}">${title}</a> <span class="small">${published}</span> </p> <p> ${summary} </p> </td> </tr> </table> #end #end <p> #if !empty <a href="/User/get/id/${id}/pageIndex/1/pageSize/${pageSize}">第一页</a> #if hasLastPage <a href="/User/get/id/${id}/pageIndex/${lastPage}/pageSize/${pageSize}">上一页</a> #end #if hasNextPage <a href="/User/get/id/${id}/pageIndex/${nextPage}/pageSize/${pageSize}">上一页</a> #end <a href="/User/get/id/${id}/pageIndex/${pageCount}/pageSize/${pageSize}">最后一页</a> 当前第${pageIndex}页,共${pageCount}页 #end </p>
views/User/edit.html,增加:
#if empty 没有文章! #end #if !empty #loop pagedArticles <table width="60%" class="block"> <tr> <td> <p class="pTop"> <a href="/Article/get/id/${id}/" target="_blank">${title}</a> <span class="small">${published}</span> <a href="/Article/edit/id/${id}">编辑</a> <a href="/Article/delete/id/${id}/parent/${user.id}/pageIndex/${pageIndex}/pageSize/${pageSize}" name="文章">删除</a> </p> <p> ${summary} </p> </td> </tr> </table> #end #end <p> #if !empty <a href="/User/edit/id/${id}/pageIndex/1/pageSize/${pageSize}">第一页</a> #if hasLastPage <a href="/User/edit/id/${id}/pageIndex/${lastPage}/pageSize/${pageSize}">上一页</a> #end #if hasNextPage <a href="/User/edit/id/${id}/pageIndex/${nextPage}/pageSize/${pageSize}">上一页</a> #end <a href="/User/edit/id/${id}/pageIndex/${pageCount}/pageSize/${pageSize}">最后一页</a> 当前第${pageIndex}页,共${pageCount}页 #end </p> <a href="/User/publish/id/${id}">发布新文章</a>
新增views/User/publish.html:
#extends /Master/user #override content <form action="/Article/save" method="post"> <input type="hidden" name="parent" value="${id}" /> <input type="hidden" name="userId" value="${id}" /> <input type="hidden" name="author" value="${name}" /> <table> <tr> <td>标题: <input name="title" id="标题" maxlength="100" size="90" /></td> </tr> <tr> <td> 摘要:<br /> <textarea name="summary" id="摘要" maxlength="500" rows="3" cols="100"></textarea> </td> </tr> <tr> <td> 正文:<br /> <textarea name="content" id="正文" maxlength="50000" rows="15" cols="100"></textarea> </td> </tr> </table> <input type="submit" value="保存" /> <input type="button" value="取消" onclick="history.back()" /> </form>
新增views/Master/article.html:
#extends /Master/master #override title ${user.blogName} #override content <div id="header"> <a href="/">回到首页</a> <a href="/User/get/id/${user.id}/pageSize/10">文章列表</a> </div> <h1>${user.blogName}</h1> #abstract content
新增view/Article/list.html:
<table width="60%"> #if empty <tr> <td colspan="2"> 没有文章! </td> </tr> #end #if !empty #loop <tr> <td> <div class="block"> <p> <a href="/Article/get/id/${id}"> ${title} </a> </p> <p> ${summary} </p> <p class="small"> <a href="/User/get/id/${user.id}/pageSize/10">${user.blogName}</a> ${user.name} ${published} </p> </div> </td> </tr> #end #end </table> <p> #if !empty <a href="/Home/${action}/pageIndex/1">第一页</a> #if hasLastPage <a href="/Home/${action}/pageIndex/${lastPage}">上一页</a> #end #if hasNextPage <a href="/Home/${action}/pageIndex/${nextPage}">下一页</a> #end <a href="/Home/${action}/pageIndex/${pageCount}">最后一页</a> 当前第${pageIndex}页,共${pageCount}页 #end </p>
新增view/Article/get.html:
#extends /Master/article #override content #override content <table width="80%" class="block"> <tr> <td> <h1 align="center">${title}</h1> </td> </tr> <tr> <td align="center"> <span class="small">${user.name} ${published}</span> </td> </tr> <tr> <td align="left"> <div id="content"> ${content.html} </div> </td> </tr> </table>
现在可以启动服务器,测试一下文章发布功能了。效果如图:
接下来,是文章的编辑与删除。
文章编辑与删除
Article中增加:
@Role("user") public String edit() throws Exception { return checkOwner(new Getable() { public String get() throws Exception { return toView(); } }); } @Role("user") @UpdateOptions(toUpdate = { "title[100]", "summary[500]", "content[50000]" }) public String update() throws Exception { return checkOwner(new Getable() { public String get() throws Exception { return userEditAction(); } }); } @Role("user") @UpdateOptions public String delete() throws Exception { return checkOwner(new Getable() { public String get() throws Exception { doDelete(); return userEditAction(); } }); } @Override public void doDelete() throws Exception { user.get().deleteArticle(getId()); getDatabase().deleteArticle(getId()); }
edit()方法:检查权限和用户合法性,然后显示编辑页面。
update()方法:只需加上@UpdateOptions,就会西东保存更新。
delete()方法:调用doDelete(),返回编辑用户(博客)页面。
doDelete()方法:将自己从user和database的articleList中移除。
增加页面views/Article/edit.html:
#extends /Master/article #override content <form action="/Article/update" method="post"> <input type="hidden" name="id" value="${id}" /> <input type="hidden" name="parent" value="${user.id}" /> <input type="hidden" name="userId" value="${user.id}" /> <input type="hidden" name="author" value="${author}" /> <table> <tr> <td>标题: <input name="title" id="标题" maxlength="100" size="90" value="${title}" /></td> </tr> <tr> <td> 摘要:<br /> <textarea name="summary" id="摘要" maxlength="500" rows="3" cols="100" >${summary}</textarea> </td> </tr> <tr> <td> 正文:<br /> <textarea name="content" id="正文" maxlength="50000" rows="15" cols="100" >${content}</textarea> </td> </tr> </table> <input type="submit" value="保存" /> <input type="button" value="取消" onclick="history.back()" /> </form>
现在可以测试一下文章编辑和删除功能了。
到目前为止,文章的发布、编辑、删除也完成了,现在的代码应该与附件中的OMSimpleBlog_Step3.rar相似了。
最后,增加后台数据管理功能。
4.后台数据管理
管理页面
增加views/Admin/login.html:
#extends /Master/master #override title 数据库管理员登录 #override content <form action="/Admin/admin" method="post"> <table> <tr> <td colspan="2" align="center"> <h1>数据库管理员登录</h1> </td> </tr> <tr> <td colspan="2" align="center" class="error"> ${error} </td> </tr> <tr> <td>用户名:</td> <td><input name="name" id="用户名" /></td> </tr> <tr> <td>密码:</td> <td><input type="password" name="password" id="密码" /></td> </tr> <tr> <td colspan="2" align="center"> <input type="submit" value="登录" /> </td> </tr> </table> </form>
增加views/Admin/welcome.html:
#extends /Master/admin #override title 管理员,您好! #override content <table> <tr> <td align="center"> <h1>管理员,您好!</h1> </td> </tr> <tr> <td align="center"> <h2>您可以执行如下操作:</h2> </td> </tr> <tr> <td> <h2><a href="/User/list/pageSize/10">管理用户</a>:查看或删除用户。</h2> </td> </tr> <tr> <td> <h2><a href="/Article/adminList/pageSize/10">管理文章</a>:查看或删除文章。</h2> </td> </tr> <tr> <td> <h2><a href="/Admin/logout">注销登录</a>:注销并回到登陆页面。</h2> </td> </tr> </table>
现在可以通过URL http://localhost/Admin/login 和 http://localhost/Admin/welcome 查看这两个页面。
在Admin类中增加:
package com.omc.entity; import com.omc.core.*; import com.omc.core.Annotations.*; import com.omc.server.*; @Ignore @SuppressWarnings("unused") public class Admin extends Entity { private Session session; private String name; private String password; private String error = ""; public String index() throws Exception { return toView(isRole("admin") ? "welcome" : "login"); } public String admin() throws Exception { if (!name.equals(cfg("name")) || !password.equals(cfg("password"))) { error = cfg("error") + '\n'; return toView("login"); } session.set("role", "admin"); return toView("welcome"); } @Role("admin") public String welcome() throws Exception { return toView(); } public String logout() throws Exception { session.set("role", null); return toView("login"); } }
index():利用Entity内置的isRole(String role)方法,根据权限跳转至不同页面。
admin():检查用户名和密码,设置session,跳转至欢迎页面。
welcome():增加权限控制。
logout():注销登录,删除session,回到登录页面。
需要新增配置文件 cfg/Admin.cfg:
name=admin password=admin1234 error=用户名或密码错误!
现在可以测试管理员的登录与注销了。
用户管理
增加页面views/User/list.html:
#extends /Master/admin #override title 用户管理 #override content <h1>用户管理</h1> #if empty 没有用户! #end #if !empty <table width="40%"> <tr> <th>用户名</th> <th>博客名</th> <th>操作</th> </tr> #loop <tr> <td align="center">${name}</td> <td align="center">${blogName}</td> <td align="center"> <a href="/User/delete/id/${id}/pageIndex/${pageIndex}/pageSize/${pageSize}" name="用户"> 删除</a> </td> </tr> #end </table> #end #if !empty <a href="/User/list/pageIndex/1/pageSize/${pageSize}">第一页</a> #if hasLastPage <a href="/User/list/pageIndex/${lastPage}/pageSize/${pageSize}">上一页</a> #end #if hasNextPage <a href="/User/list/pageIndex/${nextPage}/pageSize/${pageSize}">下一页</a> #end <a href="/User/list/pageIndex/${pageCount}/pageSize/${pageSize}">最后一页</a> 当前第${pageIndex}页,共${pageCount}页 #end
在User类中增加:
@Role("admin") public String list() throws Exception { return super.list(); } @Role("admin") @UpdateOptions public String delete() throws Exception { return super.delete(); }
现在可以查看和删除用户了。
这里需要说明的是,在删除User时,会级联调用Children的doDelete()方法,将相应的文章也删除。
文章管理
增加页面views/Article/adminList.html:
#extends /Master/admin #override title 文章管理 #override content <h1>文章管理</h1> #if empty 没有文章! #end #if !empty <table width="60%"> <tr> <th>标题</th> <th>作者</th> <th>发布时间</th> <th>操作</th> </tr> #loop <tr> <td align="center">${title}</td> <td align="center">${user.name}</td> <td align="center"><span class="small">${published}</span></td> <td align="center"> <a href="/Article/adminDelete/id/${id}/pageIndex/${pageIndex}/pageSize/${pageSize}" name="文章"> 删除</a> </td> </tr> #end </table> #end #if !empty <a href="/Article/adminList/pageIndex/1/pageSize/${pageSize}">第一页</a> #if hasLastPage <a href="/Article/adminList/pageIndex/${lastPage}/pageSize/${pageSize}">上一页</a> #end #if hasNextPage <a href="/Article/adminList/pageIndex/${nextPage}/pageSize/${pageSize}">下一页</a> #end <a href="/Article/adminList/pageIndex/${pageCount}/pageSize/${pageSize}">最后一页</a> 当前第${pageIndex}页,共${pageCount}页 #end
在Article类中增加:
@Role("admin") public String adminList() throws Exception { return super.list(); } @Role("admin") @UpdateOptions public String adminDelete() throws Exception { doDelete(); return action("adminList"); }
现在可以查看和删除文章了。
现在的代码,应该与OMSimpleBlog_Complete.rar相似。
最后,让我们把做好的Blog打包发布吧。
打包发布
目前还不能使用Ant功能一键发布,只能手动打包。不过也并不麻烦。
首先,新建文件夹OMSimpleBlog_Release,将工程中的非代码文件复制发到文件夹中。使用eclipse的Export->Jar File功能导出jar,只需勾选src目录,将导出的jar也放在OMSimpleBlog_Release中,编写简单的run.bat文件(linux的话,应该是run.sh):
java -jar server.jar
双击run.bat,启动服务器,看看效果吧!
到目前为止,所有的开发都已经完成了,现在的结果应该与附件中的OMSimpleBlog_Release.rar相似。当然,也可能遗漏了什么。总之,遇到问题的话,尽量反馈给我吧(注意看看log.txt中的错误信息)。谢谢!
- OMSimpleBlog_Empty.rar (95.7 KB)
- 下载次数: 3
- OMSimpleBlog_Step1.rar (97.3 KB)
- 下载次数: 2
- OMSimpleBlog_Step2.rar (102.9 KB)
- 下载次数: 2
- OMSimpleBlog_Step3.rar (115.9 KB)
- 下载次数: 2
- OMSimpleBlog_Complete.rar (120.1 KB)
- 下载次数: 3
- OMSimpleBlog_Release.rar (115.3 KB)
- 下载次数: 3
发表评论
-
OMToolkit介绍(5): 总结
2011-03-22 19:13 1751OMToolkit介绍(5): 总结 ( ... -
OMToolkit介绍(4) :Object-Oriented Database 实现
2011-03-22 07:26 1181OMToolkit介绍(4) :Object-Oriented ... -
OMToolkit介绍(3) :Web Framework 实现
2011-03-20 21:17 1602OMToolkit介绍(3) :Web Framework 实 ... -
OMToolkit介绍(2) :Web Server 实现
2011-03-19 04:18 1636OMToolkit介绍(2) :Web Server 实现 ... -
OMToolkit: Web Server,Web Framework 及 Object-Orinted Database 的简单实现
2011-03-15 02:01 1623概述 最近在看一些NIO和concurrent的资 ...
相关推荐
简单的 Linux SSH 防暴力破解脚本示例:
一个简单的BP神经网络示例:一个使用梯度下降算法的简单神经网络示例
示例:XX学院.zip
第 6 章:生长建模、生存分析和 N=1 时间序列分析视图示例 第 7 章:具有横截面数据视图示例的混合建模 第 8 章:采用纵向数据视图示例的混合建模 第 9 章:具有复杂调查数据视图示例的多层建模 第10章:多层次...
- 语法示例:`UPDATE table_name SET column1 = value1, column2 = value2,... WHERE condition;` - 示例:`UPDATE employees SET salary = 50000 WHERE name = 'Jane Smith';` #### 三、数据定义语言(Data ...
### Linux简易介绍 #### 第一章 Linux操作系统概述 ##### 一、Linux命令格式 Linux操作系统提供了丰富的命令行功能,这使得用户能够通过简单的文本输入来完成各种复杂的操作。以下是Linux命令格式的一些基本介绍:...
微信小程序-功能示例:二维码生成器.zip 小程序模板代码,可以直接从源码里粘贴复制过来,虽然这样做不利于自己独立编写代码。
示例: 使用 Python 列出逻辑网络API 类提供了访问逻辑网络集合的方法 logicalnetworks.该方法收集所有系统中的逻辑网络.下面的例子列出所
本文将详细介绍华为ETS2222AT指令集的相关知识点,包括CDMA模块AT指令的基本使用方法、测试示例以及部分通用指令与呼叫控制指令的具体功能。通过本篇文章的学习,读者能够更加深入地了解华为ETS2222AT指令集的应用...
ChatGPT写行业分析报告示例:新能源行业研究报告
134-演示文稿-示例:Linux的ext2文件系统.pdf
- 示例:`comm file1 file2`。 7. **csplit**:根据指定模式分割文件。 - 示例:`csplit file '/pattern/'`。 8. **cut**:从文件中切出字段。 - 示例:`cut -d ':' -f 1 /etc/passwd`,这里从 /etc/passwd ...
- 示例:`ln file1 lnk1` - **touch -t YYMMDDHHMM file1**: 修改一个文件或目录的时间戳。 - 示例:`touch -t 0712250000 file1` #### 6. 文件搜索 - **find / -name "file1"**: 从 `/` 开始进入根文件系统搜索...
- 示例: `diff file1 file2` 显示文件1和文件2之间的不同之处。 10. **find** - 在目录树中搜索文件。 - 示例: `find / -name "filename"` 在根目录下查找名为“filename”的文件。 11. **git** - 分布式版本...
Python代码示例:将一个文件夹中未标记的文件复制到另一个文件夹
下面将详细介绍动词连用形的变化规则及其实用例。 ##### 1. 变化规则 **① 五段动词** - **a. 连用形1**:动词词尾变成其所在行的い段字。 - 示例:食べる → 食べい → 食べる - **b. 连用形2**:在后接「て、...
- 示例:`Form1.AcceptButton = button1;` 10. **Modal属性**: - 用途:设置窗体是否为模式窗体。 - 示例:`Form1.Modal = true;` 11. **ActiveControl属性**: - 用途:获取或设置容器控件中的活动控件。 -...