论坛系统(Spring+Struts+Hibernate)

论坛系统(基于SSH实现)

学web后台开发已经有一段时间了,是时候做一点小总结。前段时间学习了一个小项目——论坛系统,是基于Spring+Struts1+Hibernate框架开发的,虽然Struts1框架已逐渐比较少人用了,但作为初学者我觉得还是有必要了解一下。在实现完这个小系统后,我想把它改成基于Struts2实现的,毕竟Struts2比Struts1优秀得多。但了解到目前很多企业公司都采用了Spring+SpringMVC+Mybatis的组合框架,所以后来我把这个系统改成了基于Spring+SpringMVC+Mybatis实现的,SSM比SSH好就不用我多说了。下面先介绍基于SSH实现的,想看基于SSM实现的可直接看下一篇文章。

整个工程目录以及数据库表如下图:

          

代码下载

运行效果:

界面虽然丑了点,但我们这里只专注于后台开发,界面就交给前端吧!微笑下面UI的代码就不贴出来了,自己下载看吧。

1、系统功能模块

本系统包括5个模块:用户模块、版面类型模块、版面模块、帖子模块和回帖模块。

  • 用户模块:包括用户的注册、登录、注销、以及查看用户资料等功能。发表帖子、发表回帖、设置版主等都需要用到用户信息。注册时将记录用户的注册IP、注册时间等,登录时将记录用户的登录IP、登录时间等。
  • 版面类别模块:本系统为二级结构,即先创建版面类别,然后才创建版面。本模块包括创建版面类别,列出所有的类别、首页等。首页将列出所有的版面类别、各类别下对应的版面、帖子总数、回帖总数、最后发表的帖子或者回复、版主等。
  • 版面模块:包括创建版面、设置版主,列出所有本版的帖子等。帖子列表包括帖子标题、作者、发表时间、回帖数、人气、最后回复人、最后回复时间等。

  • 帖子模块 :包括发表帖子、浏览帖子等。发表帖子时记录发表帖子的IP等;浏览帖子时分页列出所有的回帖,并更新帖子的人气(浏览次数)等。

  • 回帖模块:包括发表回帖等。

2、数据库设计

实体类使用Hibernate的注解的方式配置,数据库表结构由实体类决定,DDL语句由Hibernate自动生成。本系统包括5个实体类:Person(用户)、Category(版面类型)、Board(版面)、Thread(帖子)、Reply(回帖)。所有实体类都继承自BaseBean,BaseBean中定义了主键id、乐观锁键version、删除标志位deleted、创建日期dateCreated等基本字段。各实体类之间存在着一对一、一对多、多对多等关系,实体类关系图如下图:

2.1、BaseBean基类代码

BaseBean基类代码以及Hibernate配置如下(getter、setter方法略,下同):
注意:实体类中的父类要使用@MappedSuperclass
package bean;

@MappedSuperclass						//实体类父类
public class BaseBean {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer id;

	@Version							//版本列,hibernate自动维护该列
	private Integer version;

	private boolean deleted;

	@Temporal(value = TemporalType.TIMESTAMP)
	private Date dateCreated;
}

2.2、Person类代码

Person类为用户信息,属性boardsAdministrated代表该Person管理的版面,也就是版主。一个版面可以有多个版主,一个版主也可以管理多个版面,所以Person与Board是多对多的关系。
package bean;

@Entity
@Table
public class Person extends BaseBean {

	private String account;

	private String password;

	private String sex;

	private String name;

	private String birthday;

	private String email;

	private String ipCreated;

	@Temporal(value = TemporalType.TIMESTAMP)
	private Date dateLastActived;

	private String ipLastActived;

	@ManyToMany(mappedBy = "administrators")
	private Set<Board> boardsAdministrated = new HashSet<Board>();

}

2.3、Category类代码

Category类为版面类型
package bean;

@Entity
@Table
@org.hibernate.annotations.Entity
public class Category extends BaseBean {

	private String name;

	@OneToMany(mappedBy = "category")
	private List<Board> boards = new ArrayList<Board>();
}

2.4、Board类代码

Board类代表版面,Board有两个一对多的属性lastThread与lastReply,以及一个多对多的属性administrators。lastThread和lastReply分别保存最后发表的帖子及回帖。
package bean;

@Entity
@Table
public class Board extends BaseBean {

	@ManyToOne
	@JoinColumn(name = "category_id")
	private Category category;

	private String name;

	private String description;

	private int threadCount;

	private int replyCount;

	@ManyToOne
	@JoinColumn(name = "last_reply_id")
	private Reply lastReply;

	@ManyToOne
	@JoinColumn(name = "last_thread_id")
	private Thread lastThread;

	@ManyToMany(cascade = CascadeType.ALL)
	@JoinTable(name = "board_administrator", joinColumns = { @JoinColumn(name = "board_id") }, inverseJoinColumns = { @JoinColumn(name = "person_id") })
	private Set<Person> administrators = new HashSet<Person>();
}

2.5、Thread类代码

Thread类代表帖子,其中内容比较大,因此配置为延迟加载。
package bean;

@Entity
@Table
@org.hibernate.annotations.Entity
public class Thread extends BaseBean {

	@ManyToOne
	@JoinColumn(name = "board_id")
	private Board board;

	private String title;

	@Basic(fetch = FetchType.LAZY)
	@Column(columnDefinition="longtext")
	private String content;

	@ManyToOne
	@JoinColumn(name = "author_id")
	private Person author;

	private String ipCreated;

	private int hit;

	@ManyToOne
	@JoinColumn(name = "author_last_replied_id")
	private Person authorLastReplied;

	@Temporal(TemporalType.TIMESTAMP)
	private Date dateLastReplied;

	private boolean readonly;

	private boolean topped;

	private int replyCount;
}

2.6、Reply类代码

Reply代表回帖
package bean;

@Entity
@Table
@org.hibernate.annotations.Entity
public class Reply extends BaseBean {

	@ManyToOne
	@JoinColumn(name = "thread_id")
	private Thread thread;

	private String title;

	@Basic(fetch = FetchType.LAZY)
	private String content;

	@ManyToOne
	@JoinColumn(name = "author_id")
	private Person author;

	private int floor;

	private String ipCreated;
}

2.7、数据库与Hibernate配置

在配置前先在MySQL中创建一个名为forum的数据库,Hibernate配置在Spring的配置文件applicationContext.xml中,由Spring管理,采用hbm2ddl策略为update,启动时会自动生成对应的表。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">


	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close">	<!-- 定义数据源 -->
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url"
			value="jdbc:mysql://localhost:3306/forum?characterEncoding=UTF-8"></property>
		<property name="username" value="root"></property>
		<property name="password" value="5845201314"></property>
	</bean>

	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
		destroy-method="destroy">					<!-- SessionFactory实现类 -->
		<property name="dataSource" ref="dataSource"></property>
		<property name="annotatedClasses">		<!-- 配置实体类 -->
			<list>
				<value>bean.Board</value>
				<value>bean.Category</value>
				<value>bean.Person</value>
				<value>bean.Reply</value>
				<value>bean.Thread</value>
			</list>
		</property>

		<property name="hibernateProperties">		<!-- Hibernate属性 -->
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
				<prop key="hibernate.show_sql">true</prop>
				<prop key="hibernate.hbm2ddl.auto">update</prop>
				<prop key="hibernate.current_session_context_class">thread</prop>
			</props>
		</property>
	</bean>

	<bean id="hibernateTransactionmanager"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">	<!-- Hibernate事务管理器 -->
		<property name="sessionFactory" ref="sessionFactory"></property>
	</bean>

	<bean id="hibernateTransactionAttributeSource"
		class="org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource">	<!-- 事务管理规则 -->
		<property name="properties">	<!-- 具有事务管理的方法名 -->
			<props>
				<prop key="*">PROPAGATION_REQUIRED</prop>	<!-- 所有方法都加事务 -->
			</props>
		</property>
	</bean>
	
	......
</beans>

3、DAO层设计

DAO层负责与数据库打交道,它把Java Bean分拆成SQL语句并执行。个人觉得在ORM框架的基础上再封装一个DAO会更加方便。

3.1、IDAO代码

由于Hibernate能操作所有的实体类,因此只定义一个DAO层即可。遵循接口编程原则,IDAO接口定义了最基本的DAO操作,同时也引入泛型,方便实现不同的实体类。
package dao;

public interface IDao<T> {

	public T find(Class<T> clazz, int id);

	public void create(T baseBean);

	public void save(T baseBean);

	public void delete(T baseBean);
	
	public List<T> list(String hql);

	public int getTotalCount(String hql, Object... params);

	public List<T> list(String hql, int firstResult, int maxSize,
			Object... params);

	public Query createQuery(String hql);
}

3.2、DaoImpl代码

DaoImpl继承了Spring提供的HibernateDaoSupport类,该类主要提供两个方法:
public final HibernateTemplate getHibernateTemplate();
public final void setSessionFactory(SessionFactory sessionFactory);
其中setSessionFactory方法接收来自Spring的依赖注入,接收了配置在Spring的SessionFactory实例,getHibernateTemplate方法用来利用该实例生成Session,再生成HibernateTemplate来完成数据库的访问。
package dao;

public class DaoImpl<T> extends HibernateDaoSupport implements IDao<T> {

	@SuppressWarnings("unchecked")
	public T find(Class<T> clazz, int id) {
		return (T) getHibernateTemplate().get(clazz, id);
	}

	public void create(T baseBean) {
		getHibernateTemplate().persist(baseBean);
	}

	public Query createQuery(String hql) {
		return getSession().createQuery(hql);
	}

	public void delete(T baseBean) {
		getHibernateTemplate().delete(baseBean);
	}

	@SuppressWarnings("unchecked")
	public List<T> list(String hql) {
		return getHibernateTemplate().find(hql);
	}

	public int getTotalCount(String hql, Object... params) {
		Query query = createQuery(hql);
		for (int i = 0; params != null && i < params.length; i++)
			query.setParameter(i + 1, params[i]);
		Object obj = createQuery(hql).uniqueResult();
		return ((Long) obj).intValue();
	}

	@SuppressWarnings("unchecked")
	public List<T> list(String hql, int firstResult, int maxResults,Object... params) {
		Query query = createQuery(hql);
		for (int i = 0; params != null && i < params.length; i++)
			query.setParameter(i + 1, params[i]);
		List<T> list = createQuery(hql).setFirstResult(firstResult).setMaxResults(maxResults).list();
		return list;
	}

	public void save(T baseBean) {
		getHibernateTemplate().save(baseBean);
	}
}

4、Service层设计

业务代码集中在Service层,Action中调用Service完成业务逻辑,Service调用DAO完成数据存取。

4.1、IService接口

IService接口是所有Service的基类,定义了最基本的Service层方法。
package Service;

public interface IService<T> {

	public T find(Class<T> clazz, int id);

	public void create(T baseBean);

	public void save(T baseBean);

	public void delete(T baseBean);

	public List<T> list(String hql);

	public int getTotalCount(String hql, Object... params);

	public List<T> list(String hql, int firstResult, int maxSize,
			Object... params);
}

4.2、ServiceImpl实现

ServiceImpl实现了IService接口的方法,由于各个实体类的create()方法有不同的业务逻辑,因此create()方法定义为abstract的,有各自的Service单独实现。
由于含有抽象方法,类必须是抽象类!IDAO对象将由Spring通过IoC注射进来。
package Service;

public abstract  class ServiceImpl<T extends BaseBean> implements IService<T> {

	protected IDao<T> dao;

	public IDao<T> getDao() {
		return dao;
	}

	public void setDao(IDao<T> dao) {
		this.dao = dao;
	}

	public T find(Class<T> clazz, int id) {
		return dao.find(clazz, id);
	}

	public abstract void create(T baseBean);

	public void delete(T baseBean) {
		baseBean.setDeleted(true);
		dao.save(baseBean);
	}

	public int getTotalCount(String hql, Object... params) {
		return dao.getTotalCount(hql, params);
	}

	public void save(T baseBean) {
		dao.save(baseBean);
	}

	public List<T> list(String hql) {
		return dao.list(hql);
	}

	public List<T> list(String hql, int firstResult, int maxSize,
			Object... params) {
		return dao.list(hql, firstResult, maxSize, params);
	}

}

5、Action层设计

为了便于统一风格,统一控制,所有Action都继承自ForumAction基类,所有的ActionForm都继承自ForumForm基类。

5.1、ForumForm基类

ForumForm类继承了Struts的ActionForm,封装了action、title属性,action用于区分不同的执行方式,title代表页面的标题。
package form;

public class ForumForm extends ActionForm {

	private String action;
	private String title;

	public String getAction() {
		return action;
	}
	public void setAction(String action) {
		this.action = action;
	}
	public String getTitle() {
		return title;
	}
	public void setTitle(String title) {
		this.title = title;
	}
}

5.2、ForumAction基类

ForumAction类继承了DispatchAction分发器,使用Action作为分发器的参数。各模块的Action需要继承该类,并实现各自的list()方法。
package action;

public abstract class ForumAction extends DispatchAction {

	protected Log log = LogFactory.getLog(getClass());
	@Override
	public ActionForward execute(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) throws Exception {

		ForumForm forumForm = (ForumForm) form;
		
		forumForm.setTitle("轻量级 Java EE 论坛程序");
		System.out.println("execute  action:"+forumForm.getAction());
		if (forumForm.getAction() == null|| forumForm.getAction().trim().length() == 0) {
			return this.list(mapping, form, request, response);
		}
		return super.execute(mapping, form, request, response);
	}

	public abstract ActionForward list(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response)throws Exception;

}

6、用户模块

6.1、IPersonService接口

IPersonService接口继承IService接口,还需要定义自己单独的方法。
package Service;

public interface IPersonService<T extends Person> extends IService<T> {

	/** 根据帐号查找用户 */
	public T findPersonByAccount(String account);
	/** 根据帐号、密码查找用户 */
	public T getPerson(String account, String password);
}

6.2、PersonServiceImpl实现

PersonServiceImpl实现IPersonService接口,还需要实现自己的方法,创建用户时,密码需要经过加密后才保存到数据库,MD5工具类可下载源代码查看。
package Service;

public class PersonServiceImpl<T extends Person> extends ServiceImpl<T> implements IPersonService<T> {

	@SuppressWarnings("unchecked")
	@Override
	public T findPersonByAccount(String account) {
		List<T> person = this.getDao().createQuery(" select p from Person p "+ " where lower(p.account) = lower(:account) and deleted = false ")
		.setParameter("account", account.trim()).list();
		if (person.size() > 0)
			return person.get(0);
		return null;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T getPerson(String account, String password) {
		List<T> list = this.getDao().createQuery(" select p from Person p where p.account = :account "+ " and p.password = :password and p.deleted = false ")
				.setParameter("account", account).setParameter("password",MD5Util.calc(password)).list();
		if (list.size() > 0)
			return list.get(0);
		return null;
	}

	@Override
	public void create(T person) {
		if (findPersonByAccount(person.getAccount()) != null) 
			throw new RuntimeException("帐号 " + person.getAccount() + " 已经存在。");

		person.setPassword(MD5Util.calc(person.getPassword()));
		this.getDao().create(person);
	}
}

6.3、PersonForm代码

PersonForm还拥有person和password属性,省略set和get方法
package form;

public class PersonForm extends ForumForm {

	private Person person = new Person();
	private String password;

	public ActionErrors validate(ActionMapping mapping,HttpServletRequest request) {
		return null;
	}
	public void reset(ActionMapping mapping, HttpServletRequest request) {
	}

}

6.4、PersonAction代码——用户

PersonAction完成用户模块的注册、登录、注销、查看用户资料等。 先看注册相关代码。personService对象将由Spring注射进来,注册时会把用户的id以及account保存到Session中,方便下次登录时不需要进入登录界面。
package action;

public class PersonAction extends ForumAction {

	private IPersonService<Person> personService;

	//默认方法 返回到注册页面
	public ActionForward list(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		return this.initAdd(mapping, form, request, response);
	}

	public ActionForward initAdd(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		PersonForm personForm = (PersonForm) form;
		personForm.setTitle("用户注册");
		return mapping.findForward("add");
	}

	public ActionForward add(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		PersonForm personForm = (PersonForm) form;
		personForm.setTitle("用户注册");
		Person person = personForm.getPerson();
		person.setIpCreated(request.getRemoteAddr());
		person.setIpLastActived(request.getRemoteAddr());
		person.setDateCreated(new Date());
		person.setDateLastActived(new Date());

		if (person.getAccount() == null|| person.getAccount().trim().length() == 0) {
			request.setAttribute("message", "请输入帐号");
			return this.initAdd(mapping, form, request, response);
		}

		if (person.getPassword() == null|| person.getPassword().trim().length() == 0|| !person.getPassword().equals(personForm.getPassword())) {
			request.setAttribute("message", "密码不一致");
			return this.initAdd(mapping, form, request, response);
		}
		try {
			personService.create(person);
			PersonUtil.setPersonInf(request, response, person);
			request.setAttribute("message", "注册成功");
			return new ActionForward("success", "/jsp/person/success.jsp",false);
		} catch (Exception e) {
			request.setAttribute("message", "注册失败,原因:" + e.getMessage());
			return this.initAdd(mapping, form, request, response);
		}
	}
	public IPersonService<Person> getPersonService() {
		return personService;
	}

	public void setPersonService(IPersonService<Person> personService) {
		this.personService = personService;
	}
}

6.5、PersonAction配置

在struts-config.xml中需要添加PersonAction的配置代码,并且必须配置了controller后,Spring才能管理Action。之后几个模块的配置类似如下,就不再介绍了,拦截器与异常捕捉代码,可下载查看。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.3//EN" "http://struts.apache.org/dtds/struts-config_1_3.dtd">

<struts-config>
  <form-beans >
  	<form-bean name="personForm" type="form.PersonForm"></form-bean>
  </form-beans>
  
  <global-exceptions>
  	<exception key="login" type="java.lang.Exception" handler="exception.ForumExceptionHandler"></exception>
  </global-exceptions>
  
  <global-forwards />
  
  <action-mappings>
  	<action attribute="personForm" 
  		path="/person"
  		name="personForm"
  		input="/jsp/person/listPerson.jsp"
  		scope="request"
  		parameter="action"
  		type="action.PersonAction">				<!-- DispatchAction是通过请求参数来选择方法 -->
  		<forward name="add" path="/jsp/person/addPerson.jsp"></forward>
  		<forward name="list" path="/jsp/person/listPerson.jsp"></forward>
  	</action>
  </action-mappings>
  
  <controller processorClass="org.springframework.web.struts.DelegatingRequestProcessor"></controller>	<!-- 把Struts的Action交给Spring代理 -->
  
  <message-resources parameter="com.yourcompany.struts.ApplicationResources" />
</struts-config>

配置applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

	//结合前面代码
	<bean id="dao" class="dao.DaoImpl">
		<property name="sessionFactory" ref="sessionFactory"></property>
	</bean>

	<bean id="personService"
		class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">	<!-- 事务工厂代理类 -->
		<property name="transactionManager" ref="hibernateTransactionmanager"></property>
		<property name="target">	<!-- 被管理的对象,匿名Bean -->
			<bean class="Service.PersonServiceImpl">
				<property name="dao" ref="dao"></property>
			</bean>
		</property>
		<property name="transactionAttributeSource" ref="hibernateTransactionAttributeSource"></property>
	</bean>

	<bean id="loginInterceptor"
		class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">	<!-- 方法前拦截器 -->
		<property name="advice">
			<bean class="interceptor.LoginInterceptor" />
		</property>
		<property name="mappedName" value="*"></property>
	</bean>

	<bean name="/person" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="interceptorNames">
			<list>
				<value>loginInterceptor</value>
			</list>
		</property>
		<!-- 被拦截的对象 -->
		<property name="target">
			<bean class="action.PersonAction">
				<property name="personService" ref="personService"></property>
			</bean>
		</property>
	</bean>

</beans>
注册界面的效果如下:

6.6、PersonAction代码——实现用户登录

package action;

public class PersonAction extends ForumAction {

	private IPersonService<Person> personService;
	//结合前面相关代码

	public ActionForward initLogin(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		PersonForm personForm = (PersonForm) form;
		personForm.setTitle("用户登录");
		return new ActionForward("login", "/jsp/person/login.jsp", false);
	}

	public ActionForward login(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response)throws Exception {

		PersonForm personForm = (PersonForm) form;
		personForm.setTitle("用户登录");
		Person person = personService.getPerson(personForm.getPerson().getAccount(), personForm.getPerson().getPassword());
		if (person == null)
			throw new AccountException("用户名密码错误");
		PersonUtil.setPersonInf(request, response, person);
		person.setIpLastActived(request.getRemoteAddr());
		person.setDateLastActived(new Date());
		personService.save(person);
		request.setAttribute("message", "欢迎回来");
		return new ActionForward("success", "/jsp/person/success.jsp", false);
	}

	public ActionForward logout(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response)throws Exception {

		PersonForm personForm = (PersonForm) form;
		personForm.setTitle("用户注销");
		request.getSession(true).setAttribute(PersonUtil.PERSON_INFO, null);
		return new ActionForward("login", "/jsp/person/login.jsp", false);
	}
}
登录效果如下

6.7、PersonAction代码——查看用户资料

package action;

public class PersonAction extends ForumAction {

	private IPersonService<Person> personService;

	//结合前面代码
	public ActionForward view(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response)throws Exception {

		PersonForm personForm = (PersonForm) form;
		personForm.setTitle("查看用户资料");
		Person person = personService.find(Person.class, personForm.getPerson().getId());
		request.setAttribute("person", person);
		return new ActionForward("view", "/jsp/person/viewPerson.jsp", false);
	}

}
运行效果:

7、版面类型模块

7.1、ICategoryService接口

ICategoryService不需要特别的方法,因此没有定义任何操作
package Service;
public interface ICategoryService<T extends Category> extends IService<T> {

}

7.2、CategoryServiceImpl实现

CategotyServiceImpl只需要实现ServiceImpl的create()即可。
package Service;

public class CategoryServiceImpl<T extends Category> extends ServiceImpl<T> implements ICategoryService<T> {
	
	@Override
	public void create(T category) {
		if (dao.createQuery(" from Category c where c.name = :name and c.deleted = false ").setParameter("name", category.getName()).list().size() > 0) 
			throw new RuntimeException("类别 " + category.getName() + " 已经存在。");
		dao.create(category);
	}
}

7.3、CategoryForm代码

package form;

public class CategoryForm extends ForumForm {

	private Category category = new Category();
	public Category getCategory() {
		return category;
	}
	public void setCategory(Category category) {
		this.category = category;
	}
	public ActionErrors validate(ActionMapping mapping,HttpServletRequest request) {
		return null;
	}
	public void reset(ActionMapping mapping, HttpServletRequest request) {
	}
}

7.4、CategoryAction代码——实现浏览类别

浏览类别时会显示类别的版面、版面的帖子数、回帖数、最后发表的帖子等。
package action;

public class CategoryAction extends ForumAction {

	private ICategoryService<Category> categoryService;

	public ActionForward list(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		CategoryForm categoryForm = (CategoryForm) form;
		List<Category> categoryList = categoryService.list(" from Category where deleted = false ", 0, Integer.MAX_VALUE,null);
		request.setAttribute("categoryList", categoryList);
		return new ActionForward("list", "/jsp/category/listCategory.jsp",false);
	}

	public ICategoryService<Category> getCategoryService() {
		return categoryService;
	}

	public void setCategoryService(ICategoryService<Category> categoryService) {
		this.categoryService = categoryService;
	}

}
实现效果如图:

7.5、CategoryAction代码——实现添加类别

添加类别只是纯粹地将类别保存进数据库
package action;

public class CategoryAction extends ForumAction {

	private ICategoryService<Category> categoryService;

	public ActionForward initAdd(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		CategoryForm categoryForm = (CategoryForm) form;
		categoryForm.setTitle("添加类别");
		return new ActionForward("add", "/jsp/category/addCategory.jsp", false);
	}

	public ActionForward add(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		CategoryForm categoryForm = (CategoryForm) form;
		categoryForm.setTitle("添加类别");
		Category category = categoryForm.getCategory();
		category.setDateCreated(new Date());
		categoryService.create(category);
		request.setAttribute("category", category);
		return new ActionForward("add", "/jsp/category/success.jsp", false);
	}
}
运行效果:

8、版面模块

版面模块包括添加版面、浏览版面帖子列表、设置版主等。

8.1、IBoradService接口

设置版主利用Hibernate的Cascade自动实现,因此不需要定义特别的操作。
package Service;

public interface IBoardService<T extends Board> extends IService<T> {
}

8.2、BoardServiceImpl实现

package Service;

public class BoardServiceImpl<T extends Board> extends ServiceImpl<T> implements IBoardService<T> {
	@Override
	public void create(T board) {
		if (dao.createQuery(" from Board b where b.deleted = false and b.name = :name ").setParameter("name", board.getName().trim()).list().size() > 0)
			throw new RuntimeException("版面 " + board.getName() + " 已经存在。");
		dao.create(board);
	}
}

8.3、BoardForm代码

package form;

public class BoardForm extends ForumForm {

	private Category category = new Category();
	private Board board = new Board();
	private int[] adminId;

	public ActionErrors validate(ActionMapping mapping,HttpServletRequest request) {
		return null;
	}
	public void reset(ActionMapping mapping, HttpServletRequest request) {
	}
	//省略getter和setter方法
}

8.4、BoardAction代码——实现浏览版面

浏览版面会以分页的形式列出所有的帖子,分页使用Pagination类实现,放在uitl工具包里。
package action;

public class BoardAction extends ForumAction {

	private ICategoryService<Category> categoryService;
	private IBoardService<Board> boardService;
	private IThreadService<Thread> threadService;
	private IPersonService<Person> personService;

	@Override
	@SuppressWarnings("all")
	public ActionForward list(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {

		BoardForm boardForm = (BoardForm) form;
		Board board = boardService.find(Board.class, boardForm.getBoard().getId());
		boardForm.setBoard(board);
		int totalCount = threadService.getTotalCount(" select count(t) from Thread t "+ " where t.deleted = false and t.board.id = "+ board.getId(), null);
		Pagination pagination = new Pagination(request, response);
		pagination.setRecordCount(totalCount);
		List<Thread> threadList = threadService.list(" select t from Thread t "+ " where t.deleted = false and t.board.id = " + board.getId()
				+ " order by t.dateLastReplied desc ", pagination.getFirstResult(), pagination.getPageSize(), null);
		request.setAttribute("board", board);
		request.setAttribute("pagination", pagination);
		request.setAttribute("threadList", threadList);
		boardForm.setTitle("帖子列表 - 版面:" + board.getName());
		return new ActionForward("list", "/jsp/thread/listThread.jsp", false);
	}
	//省略setter和getter方法
}
实现效果:

8.5、BoardAction代码——实现版面添加

版面添加时需要选择类别,因此需要将所有耳朵类别显示出来。
package action;

public class BoardAction extends ForumAction {

	private ICategoryService<Category> categoryService;
	private IBoardService<Board> boardService;
	private IThreadService<Thread> threadService;
	private IPersonService<Person> personService;

	public ActionForward initAdd(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		BoardForm boardForm = (BoardForm) form;
		boardForm.setTitle("添加版面");
		List<Category> categoryList = categoryService.list(" from Category c where c.deleted = false ");
		request.setAttribute("categoryList", categoryList);
		return new ActionForward("add", "/jsp/board/addBoard.jsp", false);
	}

	public ActionForward add(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		BoardForm boardForm = (BoardForm) form;
		boardForm.setTitle("添加版面");
		Category category = categoryService.find(Category.class, boardForm.getCategory().getId());
		Board board = boardForm.getBoard();
		board.setCategory(category);
		board.setDateCreated(new Date());
		boardService.create(board);
		request.setAttribute("category", category);
		return new ActionForward("success", "/jsp/board/success.jsp", false);
	}
}
实现效果:

8.6、BoardAction代码——设置版主

设置版主要显示版面的信息、待选的版主列表,并且现在的版主要处于选中状态。
package action;

public class BoardAction extends ForumAction {

	private ICategoryService<Category> categoryService;
	private IBoardService<Board> boardService;
	private IThreadService<Thread> threadService;
	private IPersonService<Person> personService;

	public ActionForward initSetAdmin(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		BoardForm boardForm = (BoardForm) form;
		Board board = boardService.find(Board.class, boardForm.getBoard().getId());
		List<Person> personList = personService.list(" from Person p where p.deleted = false ");
		int[] adminId = new int[board.getAdministrators().size()];
		int i = 0;
		for (Iterator<Person> it = board.getAdministrators().iterator(); it.hasNext(); i++) {
			Person p = it.next();
			adminId[i] = p.getId();
		}
		boardForm.setAdminId(adminId);
		request.setAttribute("board", board);
		request.setAttribute("personList", personList);
		boardForm.setTitle("设置管理员 - 版面:" + board.getName());
		return new ActionForward("success", "/jsp/board/setAdmin.jsp", false);
	}

	public ActionForward setAdmin(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		BoardForm boardForm = (BoardForm) form;
		Board board = boardService.find(Board.class, boardForm.getBoard().getId());
		board.getAdministrators().clear();
		int[] adminId = boardForm.getAdminId();
		for (int i = 0; adminId != null && i < adminId.length; i++) {
			Person person = personService.find(Person.class, adminId[i]);
			board.getAdministrators().add(person);
		}
		boardService.save(board);
		boardForm.setTitle("设置管理员 - 版面:" + board.getName());
		return new ActionForward("success", "/jsp/board/success.jsp", false);
	}
}
实现效果:

9、帖子模块

帖子模块包括浏览帖子与发表帖子,浏览帖子会以分页的形式列出所有回帖,发表帖子时允许编辑HTML代码。

9.1、IThreadService接口

浏览帖子时需要统计帖子的点击率
package Service;

public interface IThreadService<T extends Thread> extends IService<T> {
	public void updateHit(Integer threadId);
}

9.2、ThreadServiceImpl实现

发表帖子时需要更新版面的信息,包括帖子数、最后发表的帖子、作者、时间等
package Service;

public class ThreadServiceImpl<T extends Thread> extends ServiceImpl<T> implements IThreadService<T> {

	@Override
	@SuppressWarnings("all")
	public void create(T thread) {
		dao.create(thread);
		int totalCount = dao.getTotalCount(" select count(t) from Thread t "+ " where t.deleted = false and t.board.id = "+ thread.getBoard().getId(), null);
		dao.createQuery(" update Board b "+ " set b.lastThread.id = :lastThreadId, b.lastReply.id = null, threadCount = :threadCount "+ " where b.id = :boardId ").setParameter(
						"lastThreadId", thread.getId()).setParameter("boardId",thread.getBoard().getId()).setParameter("threadCount",totalCount).executeUpdate();
	}

	public void updateHit(Integer threadId) {
		dao.createQuery(" update Thread t set t.hit = t.hit + 1 where t.id = :id ").setParameter("id", threadId).executeUpdate();
	}
}

9.3、ThreadForm代码

package form;

public class ThreadForm extends ForumForm {

	private Thread thread = new Thread();
	private Board board = new Board();

	public ActionErrors validate(ActionMapping mapping,HttpServletRequest request) {
		return null;
	}

	public void reset(ActionMapping mapping, HttpServletRequest request) {
	}
	//省略setter和getter方法
}

9.4、ThreadAction代码——实现浏览帖子

浏览帖子时以分页的形式显示所有的回帖,并更新人气
package action;

public class ThreadAction extends ForumAction {

	private IThreadService<Thread> threadService;
	private IPersonService<Person> personService;
	private IBoardService<Board> boardService;
	private IReplyService<Reply> replyService;

	@Override
	@SuppressWarnings("all")
	public ActionForward list(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		return this.view(mapping, form, request, response);
	}

	@SuppressWarnings("all")
	public ActionForward view(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		ThreadForm threadForm = (ThreadForm) form;
		Thread thread = threadService.find(Thread.class, (int) threadForm.getThread().getId());
		int totalCount = replyService.getTotalCount(" select count(r) from Reply r "+ " where r.deleted = false and r.thread.id = "
						+ threadForm.getThread().getId(), null);
		Pagination pagination = new Pagination(request, response);
		pagination.setRecordCount(totalCount);
		List<Reply> replyList = replyService.list(" from Reply r where r.deleted = false and r.thread.id = "+ threadForm.getThread().getId()
						+ " order by r.id asc ", pagination.getFirstResult(),pagination.getPageSize(), null);
		threadService.updateHit(thread.getId());
		request.setAttribute("category", thread.getBoard().getCategory());
		request.setAttribute("board", thread.getBoard());
		request.setAttribute("thread", thread);
		request.setAttribute("pagination", pagination);
		request.setAttribute("replyList", replyList);
		threadForm.setTitle("浏览帖子 - 标题:" + thread.getTitle());
		return new ActionForward("list", "/jsp/thread/viewThread.jsp", false);
	}
	//省略setter和getter方法
}
实现效果:

9.5、ThreadAction代码——实现发表帖子

发表帖子时需要用到HTML编辑器,本系统采用tinyMCE,tinyMCE是js实现的编辑器,功能十分强大,而且使用十分简单,只需要在JSP中引入即可。本系统把textArea变成HTML编辑器。
package action;

public class ThreadAction extends ForumAction {

	private IThreadService<Thread> threadService;
	private IPersonService<Person> personService;
	private IBoardService<Board> boardService;
	private IReplyService<Reply> replyService;

	public ActionForward initAdd(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {
		ThreadForm threadForm = (ThreadForm) form;
		Board board = boardService.find(Board.class, threadForm.getBoard().getId());
		threadForm.setBoard(board);
		request.setAttribute("board", board);
		threadForm.setTitle("发表帖子 - 版面:" + board.getName());
		return new ActionForward("add", "/jsp/thread/addThread.jsp", false);
	}

	public ActionForward add(ActionMapping mapping, ActionForm form,
			HttpServletRequest request, HttpServletResponse response) {
		ThreadForm threadForm = (ThreadForm) form;
		Board board = boardService.find(Board.class, threadForm.getBoard().getId());
		PersonInfo personInfo = PersonUtil.getPersonInfo(request, response);
		Person person = personService.find(Person.class, personInfo.getId());
		Thread thread = threadForm.getThread();
		thread.setBoard(board);
		thread.setAuthor(person);
		thread.setAuthorLastReplied(null);
		thread.setDateCreated(new Date());
		thread.setDateLastReplied(new Date());
		thread.setDeleted(false);
		thread.setIpCreated(request.getRemoteAddr());
		thread.setReadonly(false);
		thread.setReplyCount(0);
		thread.setTopped(false);
		threadService.create(thread);
		threadForm.setTitle("发表帖子 - 版面:" + board.getName());
		return this.list(mapping, form, request, response);
	}
}
运行效果:

10、回帖模块

回帖模块只有回帖一个功能。

10.1、IReplyService接口

package Service;

public interface IReplyService<T extends Reply> extends IService<T> {
}

10.2、ReplyServiceImpl实现

发表回帖时,需要更新版面最后发表的回帖、作者、时间,并更新帖子最后回复的回帖、作者、时间,以及回帖总数。
package Service;

public class ReplyServiceImpl<T extends Reply> extends ServiceImpl<T> implements IReplyService<T> {

	@Override
	public void create(T reply) {
		dao.create(reply);
		// 更新帖子最后回复、最后回复日期、作者、回帖数
		int count = dao.getTotalCount(" select count(r) from Reply r "+ " where r.deleted = false and r.thread.id = "+ reply.getThread().getId(), null);
		dao.createQuery(" update Thread t "+ " set t.authorLastReplied.id = :authorLastReplied, "+ " t.dateLastReplied = :dateLastReplied, "
				+ " t.replyCount = :replyCount "+ " where t.id = :threadId ").setParameter("authorLastReplied", reply.getAuthor().getId()).setParameter(
				"dateLastReplied", reply.getDateCreated()).setParameter("replyCount", count).setParameter("threadId",reply.getThread().getId()).executeUpdate();
		// 更新版面的最后发表数、最后发表时间
		int replyCount = dao.getTotalCount(" select count(r) from Reply r "+ " where r.deleted = false " + " and r.thread.board.id = "+ reply.getThread().getBoard().getId(), null);

		dao.createQuery(" update Board b " + " set b.lastThread.id = null, "+ " b.lastReply.id = :lastReplyId, "
				+ " b.replyCount = :replyCount "+ " where b.id = :boardId ").setParameter(
				"lastReplyId", reply.getId()).setParameter("boardId",reply.getThread().getBoard().getId()).setParameter("replyCount", replyCount).executeUpdate();
		int floor = dao.getTotalCount(" select count(r) from Reply r "+ " where r.thread.id = " + reply.getThread().getId(), null);
		// 回帖处于第几楼
		reply.setFloor(floor);
		dao.save(reply);
	}
}

10.3、ReplyForm代码

package form;

public class ReplyForm extends ForumForm {

	private Thread thread = new Thread();
	private Reply reply = new Reply();

	public ActionErrors validate(ActionMapping mapping,HttpServletRequest request) {
		return null;
	}

	public void reset(ActionMapping mapping, HttpServletRequest request) {
	}
	//省略setter和getter方法
}

10.4、ReplyAction代码——实现发表回帖

package action;

public class ReplyAction extends ForumAction {

	private IPersonService<Person> personService;
	private IThreadService<Thread> threadService;
	private IReplyService<Reply> replyService;

	public ActionForward list(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		ReplyForm replyForm = (ReplyForm) form;
		return null;
	}

	public ActionForward initAdd(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		ReplyForm replyForm = (ReplyForm) form;
		Thread thread = threadService.find(Thread.class, (int) replyForm.getThread().getId());
		request.setAttribute("category", thread.getBoard().getCategory());
		request.setAttribute("board", thread.getBoard());
		request.setAttribute("thread", thread);
		replyForm.setTitle("回复帖子 - 标题:" + thread.getTitle());
		return new ActionForward("add", "/jsp/reply/addReply.jsp", false);
	}

	public ActionForward add(ActionMapping mapping, ActionForm form,HttpServletRequest request, HttpServletResponse response) {
		ReplyForm replyForm = (ReplyForm) form;
		Thread thread = threadService.find(Thread.class, (int) replyForm.getThread().getId());
		PersonInfo personInfo = PersonUtil.getPersonInfo(request, response);
		Person person = personService.find(Person.class, personInfo.getId());
		Reply reply = replyForm.getReply();
		reply.setThread(thread);
		reply.setDateCreated(new Date());
		reply.setDeleted(false);
		reply.setAuthor(person);
		replyService.create(reply);
		request.setAttribute("category", thread.getBoard().getCategory());
		request.setAttribute("board", thread.getBoard());
		request.setAttribute("thread", thread);
		request.setAttribute("reply", reply);
		replyForm.setTitle("回复帖子 - 标题:" + thread.getTitle());
		return new ActionForward("success", "/jsp/reply/success.jsp", false);
	}
	//省略setter和getter方法
}
实现效果:




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值