`
裴小星
  • 浏览: 260918 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
8ccf5db2-0d60-335f-a337-3c30d2feabdb
Java NIO翻译
浏览量:27563
F3e939f0-dc16-3d6e-8c0b-3315c810fb91
PureJS开发过程详解
浏览量:71863
07a6d496-dc19-3c71-92cf-92edb5203cef
MongoDB Java ...
浏览量:61968
社区版块
存档分类
最新评论

OMTookit介绍(1) 简单示例:OMSimpleBlog

阅读更多

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对象除外)。

 

  这里之所以没有把Article作为Children,是因为Article应该是从属于User的,即删除一个用户时,其文章也应该被删除。
  另外,*List这种命名方式被用于Entity内置的doAdd()、doDelete()、doList()等方法,如删除一个Entity时,会先获取它的parent,再将自己从parent的*List列表中remove()掉。如果不采用这种方式,就需要自己实现这些方法了。

 

  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/loginhttp://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/loginhttp://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中的错误信息)。谢谢!

10
8
分享到:
评论
1 楼 ericslegend 2011-03-19  
很不错啊~

相关推荐

Global site tag (gtag.js) - Google Analytics