- 浏览: 266893 次
- 性别:
- 来自: 北京
-
文章分类
最新评论
-
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 1770OMToolkit介绍(5): 总结 ( ... -
OMToolkit介绍(4) :Object-Oriented Database 实现
2011-03-22 07:26 1184OMToolkit介绍(4) :Object-Oriented ... -
OMToolkit介绍(3) :Web Framework 实现
2011-03-20 21:17 1630OMToolkit介绍(3) :Web Framework 实 ... -
OMToolkit介绍(2) :Web Server 实现
2011-03-19 04:18 1649OMToolkit介绍(2) :Web Server 实现 ... -
OMToolkit: Web Server,Web Framework 及 Object-Orinted Database 的简单实现
2011-03-15 02:01 1655概述 最近在看一些NIO和concurrent的资 ...
相关推荐
qt 一个基于Qt Creator(qt,C++)实现中国象棋人机对战.
热带雨林自驾游自然奇观探索
冰川湖自驾游冰雪交融景象
C51 单片机数码管使用 Keil项目C语言源码
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
前端分析-2023071100789s12
Laz_制作了一些窗体和对话框样式.7z
1、文件内容:ocaml-docs-4.05.0-6.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/ocaml-docs-4.05.0-6.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊
学习笔记-沁恒第六讲-米醋
工业机器人技术讲解【36页】
内容概要:本文档详细介绍了在 CentOS 7 上利用 Docker 容器化环境来部署和配置 Elasticsearch 数据库的过程。首先概述了 Elasticsearch 的特点及其主要应用场景如全文检索、日志和数据分析等,并强调了其分布式架构带来的高性能与可扩展性。之后针对具体的安装流程进行了讲解,涉及创建所需的工作目录,准备docker-compose.yml文件以及通过docker-compose工具自动化完成镜像下载和服务启动的一系列命令;同时对可能出现的问题提供了应对策略并附带解决了分词功能出现的问题。 适合人群:从事IT运维工作的技术人员或对NoSQL数据库感兴趣的开发者。 使用场景及目标:该教程旨在帮助读者掌握如何在一个Linux系统中使用现代化的应用交付方式搭建企业级搜索引擎解决方案,特别适用于希望深入了解Elastic Stack生态体系的个人研究与团队项目实践中。 阅读建议:建议按照文中给出的具体步骤进行实验验证,尤其是要注意调整相关参数配置适配自身环境。对于初次接触此话题的朋友来说,应该提前熟悉一下Linux操作系统的基础命令行知识和Docker的相关基础知识
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
网络小说的类型创新、情节设计与角色塑造
毕业设计_基于springboot+vue开发的学生考勤管理系统【源码+sql+可运行】【50311】.zip 全部代码均可运行,亲测可用,尽我所能,为你服务; 1.代码压缩包内容 代码:springboo后端代码+vue前端页面代码 脚本:数据库SQL脚本 效果图:运行结果请看资源详情效果图 2.环境准备: - JDK1.8+ - maven3.6+ - nodejs14+ - mysql5.6+ - redis 3.技术栈 - 后台:springboot+mybatisPlus+Shiro - 前台:vue+iview+Vuex+Axios - 开发工具: idea、navicate 4.功能列表 - 系统设置:用户管理、角色管理、资源管理、系统日志 - 业务管理:班级信息、学生信息、课程信息、考勤记录、假期信息、公告信息 3.运行步骤: 步骤一:修改数据库连接信息(ip、port修改) 步骤二:找到启动类xxxApplication启动 4.若不会,可私信博主!!!
在智慧城市建设的大潮中,智慧园区作为其中的璀璨明珠,正以其独特的魅力引领着产业园区的新一轮变革。想象一下,一个集绿色、高端、智能、创新于一体的未来园区,它不仅融合了科技研发、商业居住、办公文创等多种功能,更通过深度应用信息技术,实现了从传统到智慧的华丽转身。 智慧园区通过“四化”建设——即园区运营精细化、园区体验智能化、园区服务专业化和园区设施信息化,彻底颠覆了传统园区的管理模式。在这里,基础设施的数据收集与分析让管理变得更加主动和高效,从温湿度监控到烟雾报警,从消防水箱液位监测到消防栓防盗水装置,每一处细节都彰显着智能的力量。而远程抄表、空调和变配电的智能化管控,更是在节能降耗的同时,极大地提升了园区的运维效率。更令人兴奋的是,通过智慧监控、人流统计和自动访客系统等高科技手段,园区的安全防范能力得到了质的飞跃,让每一位入驻企业和个人都能享受到“拎包入住”般的便捷与安心。 更令人瞩目的是,智慧园区还构建了集信息服务、企业服务、物业服务于一体的综合服务体系。无论是通过园区门户进行信息查询、投诉反馈,还是享受便捷的电商服务、法律咨询和融资支持,亦或是利用云ERP和云OA系统提升企业的管理水平和运营效率,智慧园区都以其全面、专业、高效的服务,为企业的发展插上了腾飞的翅膀。而这一切的背后,是大数据、云计算、人工智能等前沿技术的深度融合与应用,它们如同智慧的大脑,让园区的管理和服务变得更加聪明、更加贴心。走进智慧园区,就像踏入了一个充满无限可能的未来世界,这里不仅有科技的魅力,更有生活的温度,让人不禁对未来充满了无限的憧憬与期待。
1.版本:matlab2014/2019a/2024a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。
内容概要:本文介绍了使用 Matlab 实现基于 BO(贝叶斯优化)的 Transformer 结合 GRU 门控循环单元时间序列预测的具体项目案例。文章首先介绍了时间序列预测的重要性及其现有方法存在的限制,随后深入阐述了该项目的目标、挑战与特色。重点描述了项目中采用的技术手段——结合 Transformer 和 GRU 模型的优点,通过贝叶斯优化进行超参数调整。文中给出了模型的具体实现步骤、代码示例以及完整的项目流程。同时强调了数据预处理、特征提取、窗口化分割、超参数搜索等关键技术点,并讨论了系统的设计部署细节、可视化界面制作等内容。 适合人群:具有一定机器学习基础,尤其是熟悉时间序列预测与深度学习的科研工作者或从业者。 使用场景及目标:适用于金融、医疗、能源等多个行业的高精度时间序列预测。该模型可通过捕捉长时间跨度下的复杂模式,提供更为精准的趋势预判,辅助相关机构作出合理的前瞻规划。 其他说明:此项目还涵盖了从数据采集到模型发布的全流程讲解,以及GUI图形用户界面的设计实现,有助于用户友好性提升和技术应用落地。此外,文档包含了详尽的操作指南和丰富的附录资料,包括完整的程序清单、性能评价指标等,便于读者动手实践。
漫画与青少年教育关系
励志图书的成功案例分享、人生智慧提炼与自我提升策略