您的位置:首页 > 其它

OA的学习--第七天的内容--论坛模块

2015-04-02 19:21 288 查看
    OA第七天的学习,是从第64集开始到81集,共18集.终于快结束了,真真是不容易啊.废话不多说,还是早弄完早了吧.第七天的主要内容就是完成论坛模块,其中比较重要的几个点就是,查询和发布帖子/主题/版块,以及排序等的问题,但是这些和基本的增删改查还是比较接近的,所以除了排序基本不介绍了.然后就是用FCKeditor网页编辑器发帖,还有就是分页了,分页有两种,一种是最简单的分页,还有一种是带条件的分页,并且最后按照惯例,都进行了封装,达到代码复用的效果.

排序

    排序的要求是这样子的,首先有3种帖子,一种置顶,一种精华,一种普通.置顶贴一定是放在最前面,但是多个置顶贴的先后顺序是按更新时间来排序的;精华帖是不影响顺序的,和普通帖一样,按更新时间来.
/**
* 查询主题列表
*
*/
@Deprecated
public List<Topic> findByForum(Forum forum) {
//排序:所有置顶贴在最上面,并按最后更新时间排序,让新状态的在最上面;但是精华帖不影响顺序
//1.查询2次,先查置顶再查精华
//2.将置顶帖作为字段,来解决
//3.如何让他任务0和1是一样的,而2是不一样的,
return getSession().createQuery(//
//TODO:怎么排序
"FROM Topic t WHERE t.forum=? ORDER BY (CASE t.type WHEN 2 THEN 2 ELSE 0 END) DESC,t.lastUpdateTime DESC")//
.setParameter(0,forum)
.list();
}
    核心就是写这么一句查询语句:FROM Topic t WHERE t.forum=? ORDER BY (CASE t.type WHEN 2 THEN 2 ELSE 0 END) DESC,t.lastUpdateTime DESC").其中整体是按更新时间倒序排序,所以最新的在最上面;然后还有根据 (CASE t.type WHEN 2 THEN
2 ELSE 0 END) DESC 排序排序,其中t.type中,普通贴的type为0,精华为1,而置顶为2.所以只有置顶贴为2,而普通帖和精华帖为0,然后按照2和0倒序排序,则置顶贴放在最前面.

使用FCKeditor发帖

    首先看下效果,就是这样的一个工具.可以改变输入文字的样式,插入图片.若是没有这个我们可以自己写HTML代码加上样式.



    使用网页编辑器FCKeditor发帖,就是要准备FCKeditor相关包,放到项目中.做法就是,导入js文件,然后准备一个地方放FCKeditor,最后就是写js代码显示FCKeditor.FCKeditor的创建有两种,一种直接create创建,一种replaceTextarea.



    只是现在用的FCKeditor,工具栏有很多不需要的,以及表情太少了.如何自定义自己的FCKeditor工具栏.在FCKeditor的配置文件fckconfig.js中,添加自定义的一种工具栏.然后设置ToolbarSet为bbs就可以了.



    但是直接修改fckconfig.js文件不是很好,可以再写一个自定义的配置文件,然后先加载默认的,然后加载自定义的,自定义的就可以覆盖默认的.所以在fckconfig.js中加上自定义配置文件myconfig.js.



    然后在myconfig.js,添加ToolBarSet,以及对于图片表情,给一个路径,以及图片名.



简单分页

    简单分页的效果就是在这样,基本上加上jquery.pagination.js分页插件,然后再写些真分页的查询语句就可以了,对于这里还做了转到页码,以及显示最多显示前后共10个页码.这里的问题是,pageSize没有地方设置,所以写死了.



    先分析分页需要做的事,以及写好大概的代码,并且列出需要的参数.其中分页的记录是recordList,而控制显示最多显示前后共10个页码,由于需要计算,所以直接让传递算好的值beginPageIndex和endPageIndex。最后转到的下拉列表中的值,根据总页数从1开始遍历生成,而gotoPage的功能实现需要写js代码控制。


   

    然后封装一个PageBean实体,封装分页需要的所有数据.并将这些数据进行分类,一种是前台指定,接收前台的值就可以;一种是查询数据库,得出确切的值;还有就是根据上面的两种数据,可以计算得到的数据.而且在构造函数中,通过接收前4个属性,来自动计算后3个属性.
/**
* 分页功能中一页的信息
* @author liu
*
*/
public class PageBean {
//指定或是页面参数
private int	currentPage;	  // 当前页;
private int	pageSize;         // 每页显示多少条

//查询数据库
private int	recordCount;      // 总记录数
private List recordList; 	  // 本页的数据列表;

//计算
private int	pageCount;        // 总页数;
private int	beginPageIndex;   // 页码列表的开始索引
private int endPageIndex;     // 页码列表的结束索引

public PageBean() {};

/**
* 只接收前4个必要的shuxing,会自动计算其他3个属性的值
* @param currentPage
* @param pageSize
* @param recordCount
* @param recordList
*/
public PageBean(int currentPage, int pageSize, int recordCount,
List recordList) {
this.currentPage = currentPage;
this.pageSize = pageSize;
this.recordCount = recordCount;
this.recordList = recordList;

// 计算总页码 (若有余数,加一页)
pageCount = (recordCount + pageSize -1)/pageSize;

// 计算beginPageIndex 和end PageIndex
// 总页数不多于10页,则全部显示
if (pageCount <= 10) {
beginPageIndex = 1;
endPageIndex = pageCount;
}
// 总页数多于10页,则显示当前页附近的共10个页面
else {
// 当前页附近的共10个页码(前4个 + 当前页 + 后5个)
beginPageIndex = currentPage - 4;
endPageIndex = currentPage + 5;
// 当前面的页码不足4个时,则显示最前面10个页码,1-10
if (beginPageIndex < 1) {
beginPageIndex = 1;
endPageIndex = 10;
}
// 当后面的页码不足5个时,则显示最后10个页码,倒数第10个-倒数第1个
if (endPageIndex > pageCount) {
endPageIndex = pageCount;
beginPageIndex = pageCount - 10 + 1;
}
}
}
…
}
    最后形成的结构就是这样,对于JSP,Action和Service分析各做了什么?

    对于jsp就是在页面上显示这些数据,而Action,需要接收jsp传递的参数,然后调用service的方法获取到pageBean,最后回显到jsp中,(实现时为了显示方便,所以pageBean放到了对象栈中).而service提供getPageBean的方法,先是查询出符合条件的记录,然后与查询总记录数,最后调用pageBean的构造函数,传递4个属性,计算3个属性,最后返回的pageBean中7个属性就会填充好.



    首先可以看下JSP的代码,最开始的时候,是每个页面都写一份这样的代码,后来由于有很多分页功能的页面,所以优化,提取和封装了这样一段jsp片段pageView.jspf.

    那么,在JSP页面中需要写什么,才能引入这个分页片段.需要include分页文件,然后要设置一个form用于提交,因为转到的封装需要用到这个.
<!--分页信息-->
<%@ include file="/WEB-INF/jsp/public/pageView.jspf" %>
<s:form action="topic_show?id=%{id}"></s:form>
    然后再看下pageView.jspf片段文件的内容,首先需要struts的标签和编码utf-8.然后就可是开始建立自定义的分页控件了,里面的样式由pageCommon.css提供.
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>

<div id=PageSelectorBar>
<div id=PageSelectorMemo>
页次:${currentPage}/${pageCount }页  
每页显示:${pageSize }条  
总记录数:${recordCount }条
</div>
<div id=PageSelectorSelectorArea>

<a href="javascript: gotoPage(1)" title="首页" style="cursor: hand;">
<img src="${pageContext.request.contextPath}/style/blue/images/pageSelector/firstPage.png"/>
</a>

<s:iterator begin="%{beginPageIndex}" end="%{endPageIndex}" var="num">
<s:if test="#num == currentPage"> <%-- 当前页 --%>
<span class="PageSelectorNum PageSelectorSelected">${num}</span>
</s:if>
<s:else> <%-- 非当前页 --%>
<span class="PageSelectorNum" style="cursor: hand;" onClick="gotoPage(${num});">${num}</span>
</s:else>

</s:iterator>

<a href="javascript: gotoPage(${pageCount})" title="尾页" style="cursor: hand;">
<img src="${pageContext.request.contextPath}/style/blue/images/pageSelector/lastPage.png"/>
</a>

转到:
<select onchange="gotoPage(this.value)" id="_pn">
<s:iterator begin="1" end="%{pageCount}" var="num">
<option value="${num}">${num}</option>
</s:iterator>
</select>
<script type="text/javascript">
$("#_pn").val("${currentPage}");
</script>
</div>
</div>


<script type="text/javascript">
function gotoPage( pageNum ){
//方法1:路径写死,不能复用
// window.location.href = "forum_show.action?id=${id}&pageNum=" + pageNum;
//方法2:在form中中添加隐藏域,记录pageNum值,然后提交
$(document.forms[0]).append("<input type='hidden' name='pageNum' value='" + pageNum +"'>");
document.forms[0].submit();
}
</script>


    其中,当前页码的样式有PageSelectorSelected,选中样式.首页和尾页是<<和>>图片显示,并且所有都是调用gotoPage,转到页面的js方法.

    而gotoPage的js跳转代码,之前用window.location.href的方式,写死路径,不能复用;所以改成在页面的form中加隐藏域,并提交.所以js页面一定要有为分页写一个form,而且若页面中有多个form,则该form必须放在最上面.

    接着Action的代码,由于多个页面都有分页功能,所以优化,所以将pageNum和pageSize放在BaseAction中了,但是实际这应该是页面接收过来的.

    然后分页最开始是在replyService中写了一个getPageBeanByTopic方法.后来由于带参数的分页中也有这么一段,于是优化写了一个getPageBean的传hql语句的方法(在下面的带参数分页中会介绍).
public abstract class BaseAction<T> extends ActionSupport implements ModelDriven<T> {
…
protected int pageNum = 1; // 当前页
protected int pageSize = 10; //每页显示多少条记录
…
}
public class TopicAction extends BaseAction<Topic> {
/**
* 显示单个主题(主贴+回帖列表)
* @return
* @throws Exception
*/
public String show() throws Exception {
//准备Topic
Topic topic = topicService.getById(model.getId());
ActionContext.getContext().put("topic",topic);

//准备分页信息v1
PageBean pageBean = replyService.getPageBeanByTopic(pageNum,pageSize,topic);
ActionContext.getContext().getValueStack().push(pageBean);

//准备分页信息v2
//		String hql = "FROM Reply r WHERE r.topic = ? ORDER BY r.postTime ASC";
//		List<Object> parameters = new ArrayList<Object>();
//		parameters.add(topic);
//
//		PageBean pageBean = replyService.getPageBean(pageNum,pageSize,hql,parameters);
// 		ActionContext.getContext().getValueStack().push(pageBean);
return "show";
}
}
    Service层的代码
public class ReplyServiceImpl extends DaoSupportImpl<Reply> implements ReplyService {
/**
* 查询分页信息
* @param pageNum
* @param pageSize
* @param topic
* @return
*/
public PageBean getPageBeanByTopic(int pageNum, int pageSize, Topic topic) {

//查询所有回复记录
List list = getSession().createQuery(//
"FROM Reply r WHERE r.topic = ? ORDER BY r.postTime ASC")//
.setParameter(0,topic)//
.setFirstResult((pageNum -1) * pageSize)//
.setMaxResults(pageSize)
.list();
//查询总记录数
Long count = (Long) getSession().createQuery(//
"SELECT COUNT(*) FROM Reply r WHERE r.topic = ? ")//
.setParameter(0,topic)//
.uniqueResult();

return new PageBean(pageNum,pageSize,count.intValue(),list);
}
}
    这样简单分页就做好了.然后看看带条件的分页

带条件分页

    先看下带条件分页的效果.就是这样,在简单的分页插件上面,有3个下拉框和一个提交按钮.其中第一个下拉框,可以选择主题(帖子)的类型,是全部显示,还是只显示精华.第二个是控制排序的条件,其中默认排序的话,置顶贴在最前面;还有只按最后更新时间,以及只按回复数量,还有只按主题发表时间;最后一个下拉框就是选择降序还是升序.最后只有点击提交才有效果.



    首页,做法是和上面其实差不多,都要引入分页插件,但是这里还要多写些下拉框和按钮,然后对于后台,之前只要papeSize和当前页,现在还需要接收前台的参数,最后根据参数拼凑sql语句,查询出结果,返回.

    先看下JSP的代码,主要就是用s:select标签的list属性,写好各个下拉框中的value和text,还有提交按钮.以及引入pageView.jspf文件.而提交会提交到forum_show,并且会传递版块的id.
<s:form action="forum_show?id=%{id}">
<div id="MainArea">
<div id="PageHead"></div>
<center>
...
<div class="ForumPageTableBorder">
...
<!--其他操作-->
<div id="TableTail">
<div id="TableTail_inside">
<table border="0" cellspacing="0" cellpadding="0" height="100%" align="left">
<tr valign=bottom>
<td></td>
<td>
<s:select name="viewType" list="#{0:'全部主题', 1:'全部精华贴'}"/>
<s:select name="orderBy" onchange="onSortByChange(this.value)"
list="#{0:'默认排序(所有置顶帖在前面,并按最后更新时间降序排列)', 1:'只按最后更新时间排序', 2:'只按主题发表时间排序', 3:'只按回复数量排序'}"/>
<s:select name="asc" list="#{false:'降序', true:'升序'}"/>
<input type="IMAGE" src="${pageContext.request.contextPath}/style/blue/images/button/submit.PNG" align="ABSMIDDLE"/>
</td>
</tr>
</table>
</div>
</div>
</div>
</center>
</div>
</s:form>

<!--分页信息-->
<%@ include file="/WEB-INF/jsp/public/pageView.jspf" %>
    然后 Action的代码,没有建立字典表,自己定义3个属性,接收前台下拉框中的值.然后直接写hql语句,来查询.最后调用service的getPageBean方法,查询出结果,并放到对象栈中回显到前台
/**
* 版块
* @author liu
*
*/
@Controller
@Scope("prototype")
public class ForumAction extends BaseAction<Forum> {

/**
* 0:查看全部主题 ;
* 1:只查看精华帖
*/
private int viewType = 0;

/**
* 0.默认排序(所有置顶帖在前面,并按最后更新时间降序排列,
* 1:'只按最后更新时间排序',
* 2:'只按主题发表时间排序',
* 3:'只按回复数量排序'}"
*/
private int orderBy = 0;
/**
* true:表示升序
* false:表示降序
*/
private boolean asc = false;

/**
* 显示单个版块(主题列表)
* @return
* @throws Exception
*/
public String show() throws Exception {
//准备数据,forum
Forum forum = forumService.getById(model.getId());
ActionContext.getContext().put("forum",forum);

//准备分页信息v3 拼接
String hql = " FROM Topic t WHERE t.forum = ? ";

List<Object> parameters = new ArrayList<Object>();
parameters.add(forum);

if(viewType == 1) { //1.表示只看精华帖
hql = hql + " AND t.type = ? ";
parameters.add(Topic.TYPE_BEST);
}

if (orderBy == 1) {
// 1:'只按最后更新时间排序',
hql = hql + " ORDER BY t.lastUpdateTime " + (asc ? "ASC" : "DESC");
}else if(orderBy == 2){
// 2:'只按主题发表时间排序',
hql = hql + " ORDER BY t.postTime " + (asc ? "ASC" : "DESC");
}else if(orderBy == 3){
// 3:'只按回复数量排序'}"
hql = hql + " ORDER BY t.replyCount " + (asc ? "ASC" : "DESC");
}else {
// 0.默认排序(所有置顶帖在前面,并按最后更新时间降序排列,
hql = hql + " ORDER BY (CASE t.type WHEN 2 THEN 2 ELSE 0 END) DESC,t.lastUpdateTime DESC";
}

PageBean pageBean = replyService.getPageBean(pageNum,pageSize,hql,parameters);
ActionContext.getContext().getValueStack().push(pageBean);

return "show";
}
…
…//getter,setter
}

    最后,就是Service的代码.为了提取优化,所以将getPageBeanByTopic和getPageBeanByForum提取为getPageBean放在了DaoSupportImpl中。先是查询出所有的Topic主题(帖子)记录,然后填充条件,查询,查询出需要的分页后的记录,以及查询出总记录数,最后就是PageBean的构造方法了.

@Service
@Transactional
@SuppressWarnings("unchecked")
public class ReplyServiceImpl extends DaoSupportImpl<Reply> implements ReplyService {
....
}


@Transactional  //注解可以继承
@SuppressWarnings("unchecked")
public class DaoSupportImpl<T> implements DaoSupport<T> {
/**
* 公共的查询分页信息方法
*/
@Deprecated
public PageBean getPageBean(int pageNum, int pageSize, String hql,
List<Object> parameters) {
System.out.println("---------->DAOSupportImpl.getPageBean()");
//查询所有记录
Query listQuery = getSession().createQuery(hql); //创建查询对象
if (parameters != null) { //设置参数
for (int i = 0; i < parameters.size(); i ++ ) {
listQuery.setParameter(i,parameters.get(i));
}
}

listQuery.setFirstResult((pageNum -1) * pageSize);
listQuery.setMaxResults(pageSize);
List list = listQuery.list();

//查询总记录数
Query countQuery = getSession().createQuery("SELECT COUNT(*)" + hql);
if (parameters != null) { //设置参数
for (int i = 0; i < parameters.size(); i ++ ) {
countQuery.setParameter(i,parameters.get(i));
}
}
Long count = (Long) countQuery.uniqueResult(); //执行查询

return new PageBean(pageNum,pageSize,count.intValue(),list);
}
}
    这样,带参数的分页也完成了,难度主要就是要接收参数,然后感觉参数对应的值,拼凑hql语句,来查询.但是可以看出hql语句都写在Action中了,这样是很不好的.所以需要进一步的优化.

封装分页

    建立一个QueryHelper类,里面封装hql语句.将hql分为3部分,先是查什么,fromClause,以及怎么查,whereClause,以及查完如何排序orderByClause.

    在QueryHelper中,封装FROM子句,WHERE和ORDERBY子句.对于构造方法,提供别名参数,对于where子句,需要使用addCondition,添加条件,里面可以放各种参数.并且为了优化,设置了重载,只要append传入true,则可以一直将多个addCondition写成一条语句.还有就是添加orderBy属性的.中间的都还比较简单,以及最后的preparePageBean,调用service查询出分页数据,然后放到对象栈中.
public class QueryHelper {
private String fromClause; // FROM子句
private String whereClause = ""; // WHERE子句
private String orderByClause = ""; // ORDERBY子句

private List<Object> parameters = new ArrayList<Object>();
/**
* 生成FROM子句
* @param clazz
* @param alias
*/
public QueryHelper(Class clazz,String alias) {
fromClause = "FROM " + clazz.getSimpleName() + " " + alias;
}

/**
* 拼接WHERE子句
* @param condition
* @param param
*/
public QueryHelper addCondition(String condition,Object... params) {
if(whereClause.length() == 0) {
whereClause = " WHERE " + condition;
}else {
whereClause = whereClause + " AND " + condition;
}

if (params != null) {
for (Object p : params) {
parameters.add(p);
}
}

return this;
}

/**
* 如果第一个参数为true,则拼接WHERE语句
* @param append
* @param condition
* @param params
*/
public QueryHelper addCondition(boolean append,String condition,Object... params) {
if (append) {
addCondition(condition,params);
}

return this;
}

/**
* 拼接OrderBy子句
* @param propertyName
* 			参与排序的属性名
* @param asc
* 			true,表示升序;false表示降序
*/
public QueryHelper addOrderProperty(String propertyName,boolean asc) {
if(orderByClause.length() == 0) {
orderByClause = " ORDER BY " + propertyName + (asc ? " ASC":" DESC");
}else {
orderByClause += ", " + propertyName + (asc ? " ASC":" DESC");
}
return this;
}

/**
* 如果第一个参数为true,则拼接ORDER BY语句
* @param append
* @param propertyName
* @param asc
*/
public QueryHelper addOrderProperty(boolean append,String propertyName,boolean asc) {
if (append) {
addOrderProperty(propertyName,asc);
}
return this;
}

/**
* 获取生成的用于查询数据列表的HQL语句
* @return
*/
public String getListQueryHql() {
return fromClause + whereClause + orderByClause;
}

/**
* 获取生成的用于查询总记录数的HQL语句
* @return
*/
public String getCountQueryHql() {
return "SELECT COUNT(*) " + fromClause + whereClause ;
}

/**
* 获取HQL中的参数值列表
* @return
*/
public List<Object> getParameters() {
return parameters;
}

/**
* 查询分页信息,并放到值栈栈顶
* @param service
* @param pageNum
* @param pageSize
*/
public void preparePageBean(DaoSupport<?> service,int pageNum,int pageSize) {
PageBean pageBean = service.getPageBean(pageNum, pageSize, this);
ActionContext.getContext().getValueStack().push(pageBean);
}
}
    这样Action中就用QueryHelper就能完成查询分页的功能.不要看这代码这么长,实际只有一句,就是不停的添加各种情况.若viewType为1,就是只显示精华帖,如果viewType为0,显示全部,那就不用条件了.如果orderby为0,1,2,3各种情况,其中默认除了要置顶贴在最前面,还有按最后更新时间降序排列.最后preparePageBean()查询数据,放到对象栈中.
new QueryHelper(Topic.class, "t")
//过滤条件
.addCondition("t.forum=?", forum)
.addCondition(viewType == 1, "t.type=?", Topic.TYPE_BEST)
//排序条件
.addOrderProperty(orderBy == 1, "t.lastUpdateTime", asc) // 1:'只按最后更新时间排序',
.addOrderProperty(orderBy == 2, "t.postTime", asc) // 2:'只按主题发表时间排序',
.addOrderProperty(orderBy == 3, "t.replyCount", asc) // 3:'只按回复数量排序'}"
.addOrderProperty(orderBy == 0, "(CASE t.type WHEN 2 THEN 2 ELSE 0 END)", false) // 0.默认排序(所有置顶帖在前面,并按最后更新时间降序排列,
.addOrderProperty(orderBy == 0, "t.lastUpdateTime", false)
.preparePageBean(replyService, pageNum, pageSize);
    而简单的分页,也可以简化成.
//准备分页信息v4 QueryHelper 最终版
new QueryHelper(Reply.class, "r")
.addCondition("r.topic = ?", topic)
.addOrderProperty("r.postTime", true)
.preparePageBean(replyService, pageNum, pageSize);
    经过这一系列的优化,现在写一个分页,已经非常简单了.只要引入pageView.jspf,然后在Action中写这么一句就可以了.

    到目前为止,汤阳光的OA视频,全部完成了,总结也全部完成了.至于说的用思维导图和一份大总结,我先暂时失忆了.

    写这份总结,之前对我来说,让我深入了解各种知识不少,到后期就感觉不想写了,但是好在坚持写完了,后期主要觉得收获有点小,而且这样写感觉有些太细了,有些没有重点,不过好在都完成了,一切都不是事了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: