在做项目之前我也在网上看过其他人的一些作品,但他们的论坛系统真的比较简陋,就是简单的楼层1、2、3、4。。。往下排,不可以在楼层下面回复,页面也很简单,与现在主流的贴吧论坛差距很大。既然没人来,大佬又不屑做,那我就来搞一个。
众所周知论坛应该分为三个层级,大板块——帖子——楼层,而楼层又可以详细分为普通楼层和回复楼层,这样想的话思路就比较清晰了,一个简单的包含关系而已,重点是数据库该如何表示,不多说,上图:
对于本论坛的功能需求分析,再根据上述数据库的E-R模型图,如下为本数据库的各个表以及其字段:
1.用户表:用于存储所有用户及管理员信息。
用户表(tb_users)
主键:uid、email;
外键:无;
解释:uid(用户id),username(用户昵称),sex(性别),email(电子邮箱),power(权限),password(密码),registertime(注册时间),underwrite(个性签名),birthdata(出生日期),experence(经验),headphoto(头像);
用户表中,采用双主键分别是uid和email,为的是防止用户登入信息重复造成数据库混乱登入功能BUG。
2.板块表:用于存储板块信息。
板块表(tb_boards)
主键:bid;
外键:user_id—uid(tb_users);
解释:bid(板块id),user_id(用户id),boardname(板块名称),boardintroduction(板块介绍),boardclassfication(板块分类),boardimage(图示);
与tb_users绑定外键,方便展示板块时一起带回板块创建者信息。
3.帖子表:用于存储板块信息。如表3-3所示。
帖子表(tb_posts)
主键:pid;
外键:user_id—uid(tb_users)、board_id—bid(tb_board);
解释:pid(帖子id),user_id(用户id),board_id(板块id),posttitle(帖子标题),posttime(帖子发表时间),postheat(帖子热度),posttype(帖子分类);
4.楼层表:用于帖子中的楼层信息。
楼层表(tb_floors)
主键:fid;
外键:user_id—uid(tb_users)、post_id(tb_posts);
解释:fid(楼层id),user_id(用户id),post_id(帖子id),floornum(楼层层数),floortime(楼层回复时间),floorcontent(回复内容);
5.回复楼层表:用于存储楼层中的回复信息。
回复楼层表(tb_refloors)
主键:refid;
外键:user_id—uid(tb_users)、reuser_id—uid(tb_users)、floor_id—fid(tb_floor);
解释:refid(回复楼层id),user_id(用户id),reuser_id(回复人id),floor_id(楼层id),refloorcontent(回复内容),refloortime(回复时间),messagestate(回复状态);
本想跳过这步,但是很多同学可能对框架还不是太熟悉,自己搭的不一定与我的项目耦合,那就重零开始搭建框架。首先说一下我这边的配置:Tomcat 8.0;MyEclipse 2014;JDK 1.7;MySQL 5.7;
不一样也不要紧,配置路径那边自己调。
新建项目GameBBS,我这里直接把目录结构截图了吧,你们看着建:
com.ve.config:存放Web、Spring的配置文件;
com.ve.controller:存放控制层的处理逻辑;
com.ve.mapper:存放对应的数据库会话;
com.ve.po:存放数据模型;
com.ve.service:存放服务类处理逻辑,方便controller调用;
com.ve.utils:存放一些用到的工具类,比如说随机生成验证码;
值得一提的是我的框架配置用的是全注解配置,并不是传统的xml,对这个感兴趣的同学可以去这个大佬博客看一下:使用全注解配置Spring MVC
这三个是框架必须的配置文件
package com.ve.config;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.Configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.ve.service.ServiceHook;
/**
* 相当于spring,需要扫描service层,mapper层,可以配置相应的东西(数据源,会话工厂,事务管理等等)
*
* **/
@org.springframework.context.annotation.Configuration
//相当于spring 扫描service层
@ComponentScan(basePackageClasses={ServiceHook.class})
//启动事务管理
@EnableTransactionManagement
public class RootConfig {
//配置数据源
@Bean
public DataSource buildDataSource() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/db_bbs");
ds.setUsername("root");
ds.setPassword("123");
return ds;
}
//配置log4日志
@Bean
public Configuration buildMyBatisConfig() {
LogFactory.useLog4J2Logging();
Configuration configuration = new Configuration();
return configuration;
}
//配置会话工厂
@Bean(name="sqlSessionFactory")
public SqlSessionFactoryBean buildSessionFactory(
DataSource datasource, Configuration config) {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(datasource);
factory.setConfiguration(config);
return factory;
}
//利用会话工厂扫描mapper包(扫描mapper层)
@Bean
public MapperScannerConfigurer buildMybatisMapperSacnConfig() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setSqlSessionFactoryBeanName("sqlSessionFactory");
msc.setBasePackage("com.ve.mapper");
return msc;
}
//开启事务
@Bean
public DataSourceTransactionManager transactionManager(
BasicDataSource datasource) {
return new DataSourceTransactionManager(datasource);
}
}
package com.ve.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import com.ve.controller.ControllerHook;
/**
* webconfig 类似于 springmvc 需要启动webmvc , 扫描想应的包(controller) *
**/
@Configuration
// 启动Mvc
@EnableWebMvc
// 扫描handler层
@ComponentScan(basePackageClasses = { ControllerHook.class })
public class WebConfig extends WebMvcConfigurerAdapter {
// 用视图解析器,配置前后缀
@Bean
public ViewResolver resolver() {
InternalResourceViewResolver vr = new InternalResourceViewResolver();
vr.setPrefix("/WEB-INF/views/");
vr.setSuffix(".jsp");
return vr;
}
// 前端网页静态资源加载
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// TODO Auto-generated method stub
super.addResourceHandlers(registry);
registry.addResourceHandler("/framework/**").addResourceLocations(
"/framework/");
registry.addResourceHandler("/resources/**").addResourceLocations(
"/resources/");
}
}
package com.ve.config;
import javax.servlet.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
@Configuration
public class WebInit extends
AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return new Class>[] { RootConfig.class };
}
@Override
protected Class>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class>[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[] { "/" };
}
@Override
protected Filter[] getServletFilters() {
// TODO 自动生成的方法存根
return new Filter[] { new CharacterEncodingFilter("UTF-8") };
}
}
数据库配置什么的在RootConfig中根据自己需要改,其他一般没什么要变动的。
这个我觉得没什么好讲的,就是简单的get,set方法,我就以user为例吧,其他的你们根据数据库表来建。
package com.ve.po;
public class UserModel {
private int uid;
private String username;
private int sex; /* 0=男,1=女 */
private String email;
private int power; /* 0=板块管理,1=大会员,2=普通会员 */
private String password;
private String registertime;
private String underwrite;
private String birthdate; /*年龄由出生日期计算得出,不需要用户填写,用户填写的出生日期转换为整形储存,取出时分段计算取值*/
private int experience; /*发帖,回帖增加经验值,用户等级有经验值分层判定,这里只记录总经验值,等级显示由页面计算显示*/
private String headphoto;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getPower() {
return power;
}
public void setPower(int power) {
this.power = power;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRegistertime() {
return registertime;
}
public void setRegistertime(String registertime) {
this.registertime = registertime;
}
public String getUnderwrite() {
return underwrite;
}
public void setUnderwrite(String underwrite) {
this.underwrite = underwrite;
}
public String getBirthdate() {
return birthdate;
}
public void setBirthdate(String birthdate) {
this.birthdate = birthdate;
}
public int getExperience() {
return experience;
}
public void setExperience(int experience) {
this.experience = experience;
}
public String getHeadphoto() {
return headphoto;
}
public void setHeadphoto(String headphoto) {
this.headphoto = headphoto;
}
}
mapper主要是负责po与数据库的交互,这里主要是存放SQL语句和调用方法。
package com.ve.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.ve.po.PostModel;
public interface PostMapper {
@Select("select * from tb_posts where board_id = #{bid} order by pid desc limit #{startPos},#{pageSize}")
/* post和user的组合查询 */
@Results({ @Result(column = "user_id", property = "user", one = @One(select = "com.ve.mapper.AccountMapper.selectbyuid")) })
public List postlist(@Param("bid") int bid,
@Param("startPos") int startPos, @Param("pageSize") int pageSize);
@Insert("insert into tb_posts (user_id,board_id,posttitle,posttype) values(#{user_id},#{board_id},#{posttitle},#{posttype})")
public int newpost(PostModel pmodel);
@Select("select @@identity")
// 查询最新生成记录的id
public int returnid();
@Select("SELECT COUNT(*) FROM tb_posts where board_id = #{bid}")
public int getpostCount(int bid);/* 某板块帖子总数 */
@Select("select * from tb_posts where posttitle like #{search}")
@Results({ @Result(column = "user_id", property = "user", one = @One(select = "com.ve.mapper.AccountMapper.selectbyuid")) })
public List searchpost(String search);/* 帖子标题的模糊查询 */
}
组合查询语句一对一,一对多,我只会用一对一,一对多不知道该怎么具体操作,在页面拿数据的时候拿不出来,希望知道的大佬可以点拨一下。
package com.ve.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.ve.po.ReFloorModel;
public interface ReFloorMapper {
@Select("select * from tb_refloors where floor_id in(select fid from tb_floors where post_id =#{pid})")
@Results({
@Result(column = "user_id", property = "user", one = @One(select = "com.ve.mapper.AccountMapper.selectbyuid")),
@Result(column = "reuser_id", property = "reuser", one = @One(select = "com.ve.mapper.AccountMapper.selectbyuid")) })
public List refloorlist(int pid); /* 组合查询,定位帖子里所有的回复 */
@Insert("insert into tb_refloors (user_id,reuser_id,floor_id,refloorcontent) values (#{user_id},#{reuser_id},#{floor_id},#{refloorcontent})")
public int reply(ReFloorModel rfmodel);
}
就是回复楼层,我觉得应该在查询普通楼层的时候将回复楼层数据一起带出来的,可惜我不知道怎么用语句写出这种关系,所以只能用这种笨方法一股脑的先把所有fid相关的回复楼层全找出来,在页面中再排序。
package com.ve.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import com.ve.po.FloorModel;
public interface FloorMapper {
@Select("select * from tb_floors where post_id = #{pid}")
@Results({ @Result(column = "user_id", property = "user", one = @One(select = "com.ve.mapper.AccountMapper.selectbyuid")) })
public List floorlist(int pid); /* 列出楼层 */
@Insert("insert into tb_floors (user_id,post_id,floornum,floorcontent) values (#{user_id},#{post_id},#{floornum},#{floorcontent})")
public int postreply(FloorModel fmodel); /* 楼层回复写入数据库 */
}
源代码已上传,感兴趣的同学可以看一下https://download.csdn.net/download/qq_39249005/10828545