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="取消" οnclick="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="取消" οnclick="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中的错误信息)。谢谢!