灌水论坛系统
旨在通过一个相对完整且功能丰富的Web应用实例,全面地实践和巩固Web开发所需的各项核心技术和工程方法,从而提升其综合应用能力和解决实际开发问题的能力。它不仅仅是完成一个软件,更是一个学习、实践和提升的过程
灵感:想要设计一个类似于 CSDN 的博客论坛,可以在里面分享自己的知识、感受,大家也可以在里面交流
灌水论坛系统是一个基于Java Web技术的现代化网络论坛平台,旨在为用户提供一个分享知识、交流经验、发布内容的在线社区。该系统以轻量级论坛为设计目标,注重用户体验和系统性能。
服务器端技术:Spring、Spring Boot、Spring MVC、MyBatis
浏览器端技术:HTML、CSS、JavaScript、jQuery、Bootstrap
数据库:MySQL
项目构建工具:Maven
版本控制工具:Git + GITEE
系统采用经典的三层架构模式,实现了前后端的逻辑分离:
客户端层:HTML + CSS + JavaScript + Tabler UI
|
| HTTP/HTTPS
v
应用服务层:Spring Boot + Spring MVC + Swagger
|
| MyBatis
v
数据存储层:MySQL数据库系统采用MySQL数据库,设计了规范化的数据表结构:
公共字段:无特殊要求的情况下,每张表必须有长整型的自增主键,删除状态、创建时间、更新时间,如下所示:
字段 | 类型 | 非空(Y/N) | 主键(Y/N) | 默认值 | 备注 |
|---|---|---|---|---|---|
id | bigint | Y | Y | 编号,主键自增 | |
state | tinyint | Y | N | 0 | 状态,0正常,1禁用 |
deletState | tinyint | Y | N | 0 | 是否删除,0否,1是 |
creatTime | dateTime | Y | N | 创建时间,精确到 s | |
updateTime | dateTime | Y | N | 更新时间,精确到 s |
(1)用户表 (t_user):
字段 | 类型 | 非空(Y/N) | 主键(Y/N) | 默认值 | 备注 |
|---|---|---|---|---|---|
id | bigint | Y | Y | 编号, 主键自增 | |
username | varchar(20) | Y | N | 用户名, 唯一 | |
password | varchar(32) | Y | N | 加密后密码 | |
nickname | varchar(50) | Y | N | 昵称 | |
phoneNum | varchar(20) | N | N | NULL | 手机号 |
varchar(50) | N | N | NULL | 电子邮箱 | |
gender | tinyint | Y | N | 2 | 性别: 0女, 1男, 2保密 |
salt | varchar(32) | Y | N | 密码加密 | |
avatarUrl | varchar(255) | N | N | NULL | 用户头像路径 |
articleCount | int | Y | N | 0 | 发帖数量 |
isAdmin | tinyint | Y | N | 0 | 是否管理员: 0否, 1是 |
remark | varchar(1000) | N | N | NULL | 备注, 自我介绍 |
state | tinyint | Y | N | 0 | 状态: 0正常, 1禁言 |
deleteState | tinyint | Y | N | 0 | 是否删除: 0否, 1是 |
createTime | datetime | Y | N | 创建时间, 精确到秒 | |
updateTime | datetime | Y | N | 更新时间, 精确到秒 |
注意:这里存图片我们用的是路径,而不是直接存图片本身,这样大大减少服务器占用空间(用图床存图片,然后从图床提出图片即可)
(2)版块表 (t_board):
字段 字段 | 类型 | 非空(Y/N) | 主键(Y/N) | 默认值 | 备注 备注 |
|---|---|---|---|---|---|
id | bigint | Y | Y | 编号, 主键自增 | |
name | varchar(50) | Y | N | 版块名 | |
articleCount | int | Y | N | 0 | 帖子数量 |
sort | int | Y | N | 0 | 排序优先级, 升序 |
state | tinyint | Y | N | 0 | 状态: 0正常, 1禁用 |
deleteState | tinyint | Y | N | 0 | 是否删除: 0否, 1是 |
createTime | datetime | Y | N | 创建时间, 精确到秒 | |
updateTime | datetime | Y | N | 更新时间, 精确到秒 |
(3)帖子表 (t_article):
字段 | 类型 | 非空(Y/N) | 主键(Y/N) | 默认值 | 备注 |
|---|---|---|---|---|---|
id | bigint | Y | Y | 编号,主键自增 | |
boardId | bigint | Y | N | 编号, 主键自增 (应为版块编号) | |
userId | bigint | Y | N | 发帖人, 关联用户编号 | |
title | varchar(100) | Y | N | 帖子标题 | |
content | text | Y | N | 帖子正文 | |
visitCount | int | Y | N | 0 | 访问量 |
replyCount | int | Y | N | 0 | 回复数 |
likeCount | int | Y | N | 0 | 点赞数 |
state | tinyint | Y | N | 0 | 状态: 0正常, 1禁用 |
deleteState | tinyint | Y | N | 0 | 是否删除: 0否, 1是 |
createTime | datetime | Y | N | 创建时间, 精确到秒 | |
updateTime | datetime | Y | N | 更新时间, 精确到秒 |
(4)帖子回复表 (t_article_reply):
字段 | 类型 | 非空(Y/N) | 主键(Y/N) | 默认值 | 备注 |
|---|---|---|---|---|---|
id | bigint | Y | Y | 编号, 主键自增 | |
articleId | bigint | Y | N | 关联帖子编号 | |
postUserId | bigint | Y | N | 楼主用户, 关联用户编号 | |
replyId | bigint(20) | N | N | NULL | 关联回复编号, 支持楼中楼 |
replyUserId | bigint(20) | N | N | NULL | 楼主下的回复用户编号, 支持楼中楼 |
content | varchar(500) | Y | N | 回贴内容 | |
likeCount | int | Y | N | 回贴内容 (应为点赞数) | |
state | tinyint | Y | N | 0 | 状态: 0正常, 1禁用 |
deleteState | tinyint | Y | N | 0 | 是否删除: 0否, 1是 |
createTime | datetime | Y | N | 创建时间, 精确到秒 | |
updateTime | datetime | Y | N | 更新时间, 精确到秒 |
(5)站内信表 (t_message):
字段 | 类型 | 非空(Y/N) | 主键(Y/N) | 默认值 | 备注 |
|---|---|---|---|---|---|
id | bigint | Y | Y | 编号, 主键自增 | |
postUserId | bigint | Y | N | 发送者, 关联用户编号 | |
receiveUserId | bigint | Y | N | 接收者, 关联用户编号 | |
content | varchar(255) | Y | N | 内容 | |
state | tinyint | Y | N | 0 | 状态: 0正常, 1禁用 |
deleteState | tinyint | Y | N | 0 | 是否删除: 0否, 1是 |
createTime | datetime | Y | N | 创建时间, 精确到秒 | |
updateTime | datetime | Y | N | 更新时间, 精确到秒 |
用户模块负责用户账号生命周期管理,核心功能包括:
内容模块负责论坛核心内容的发布和管理,主要功能包括:
版块模块负责论坛分类管理,主要功能包括:
消息模块提供用户间的私信功能,主要实现包括:
搜索模块提供内容检索功能,实现在帖子和用户中快速查找:
系统采用基于Session的认证机制,实现流程如下:
系统使用MD5+Salt的方式存储密码,具体实现:
系统采用物理分页方式,在SQL层面实现数据分页,减轻服务器内存压力:
前端采用Tabler UI框架,实现现代化、响应式的界面设计:
系统包含多个核心页面,各自负责不同功能:
系统在设计时考虑了后续功能扩展需求:
这些数据结构是项目中核心实体的 Java 对象表示,主要通过 Lombok 的 @Data 注解自动生成 getter, setter, toString 等方法,并使用 Swagger 注解提供 API 文档信息。
描述:存储用户的基本信息、认证信息和状态。
public class User {
private Long id;
@ApiModelProperty("用户名")
private String username;
@JsonIgnore // 不参与JSON序列化
private String password;
@ApiModelProperty("昵称")
private String nickname;
// ... 其他字段 ...
@ApiModelProperty("头像地址")
@JsonInclude(JsonInclude.Include.ALWAYS) // 强制参与JSON序列化
private String avatarUrl;
// ... 其他字段 ...
@ApiModelProperty("注册日期")
private Date createTime;
private Date updateTime;
}@Data
@ApiModel("用户信息")
public class User {
private Long id; // 用户ID
@ApiModelProperty("用户名")
private String username; // 登录用户名
@JsonIgnore // JSON序列化时忽略密码
private String password; // 加密后的密码
@ApiModelProperty("昵称")
private String nickname; // 显示昵称
@ApiModelProperty("电话号码")
private String phoneNum; // 电话号码
@ApiModelProperty("邮箱")
private String email; // 电子邮箱
@ApiModelProperty("性别")
private Byte gender; // 性别(0女,1男,2保密)
@JsonIgnore
private String salt; // 密码加密盐值
@ApiModelProperty("头像地址")
private String avatarUrl; // 头像URL
@ApiModelProperty("发帖数量")
private Integer articleCount; // 发帖数
@ApiModelProperty("是否管理员")
private Byte isAdmin; // 管理员标识
@ApiModelProperty("个人简介")
private String remark; // 个人简介
private Byte state; // 用户状态
private Date createTime; // 注册时间
private Date updateTime; // 更新时间
}@Data
public class ArticleReply {
private Long id; // 回复ID
private Long articleId; // 关联帖子ID
private Long postUserId; // 回复者ID
private Long replyId; // 被回复的回复ID(楼中楼)
private Long replyUserId; // 被回复者ID
private String content; // 回复内容
private Integer likeCount; // 点赞数
private Byte state; // 回复状态
private Date createTime; // 回复时间
private Date updateTime; // 更新时间
private User user; // 关联的用户信息
}(1)密码加密实现
public class MD5Util {
// 使用MD5+盐值加密
public static String md5Salt(String password, String salt) {
String passwordSalt = password + salt;
return DigestUtils.md5DigestAsHex(passwordSalt.getBytes());
}
}(2)用户登录实现
@Service
public class UserServiceImpl implements IUserService {
@Resource
private UserMapper userMapper;
@Override
public User login(String username, String password) {
// 1. 根据用户名查询用户
User user = userMapper.selectByUsername(username);
if (user == null) {
return null;
}
// 2. 验证密码
String encryptPassword = MD5Util.md5Salt(password, user.getSalt());
if (!encryptPassword.equals(user.getPassword())) {
return null;
}
// 3. 清除敏感信息
user.setPassword(null);
user.setSalt(null);
return user;
}
}(1)发帖实现
@Service
public class ArticleServiceImpl implements IArticleService {
@Resource
private ArticleMapper articleMapper;
@Resource
private BoardMapper boardMapper;
@Override
@Transactional
public void createArticle(Article article) {
// 1. 设置初始值
article.setVisitCount(0);
article.setReplyCount(0);
article.setLikeCount(0);
article.setState((byte)0);
article.setCreateTime(new Date());
article.setUpdateTime(new Date());
// 2. 保存帖子
articleMapper.insert(article);
// 3. 更新版块帖子数
boardMapper.incrementArticleCount(article.getBoardId());
}
}(2)帖子列表查询
@Service
public class ArticleServiceImpl implements IArticleService {
@Override
public PageInfo<Article> getArticleList(Integer pageNum, Integer pageSize, Long boardId) {
// 1. 设置分页
PageHelper.startPage(pageNum, pageSize);
// 2. 查询条件
ArticleExample example = new ArticleExample();
if (boardId != null) {
example.createCriteria()
.andBoardIdEqualTo(boardId)
.andStateEqualTo((byte)0);
}
example.setOrderByClause("create_time desc");
// 3. 执行查询
List<Article> articles = articleMapper.selectByExample(example);
// 4. 填充用户信息
for (Article article : articles) {
User user = userMapper.selectByPrimaryKey(article.getUserId());
article.setUser(user);
}
return new PageInfo<>(articles);
}
}楼中楼回复实现
@Service
public class ArticleReplyServiceImpl implements IArticleReplyService {
@Override
@Transactional
public void reply(ArticleReply reply) {
// 1. 设置初始值
reply.setLikeCount(0);
reply.setState((byte)0);
reply.setCreateTime(new Date());
reply.setUpdateTime(new Date());
// 2. 保存回复
replyMapper.insert(reply);
// 3. 更新帖子回复数
Article article = articleMapper.selectByPrimaryKey(reply.getArticleId());
article.setReplyCount(article.getReplyCount() + 1);
articleMapper.updateByPrimaryKey(article);
}
}(1)分页查询实现
使用 PageHelper 实现物理分页:
// 配置分页插件
@Configuration
public class MybatisConfig {
@Bean
public PageInterceptor pageInterceptor() {
return new PageInterceptor();
}
}
// 使用分页
public PageInfo<Article> getArticleList(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Article> list = articleMapper.selectByExample(new ArticleExample());
return new PageInfo<>(list);
}(2)统一响应处理
@Data
public class AppResult<T> {
private Integer code; // 状态码
private String message; // 提示信息
private T data; // 响应数据
public static <T> AppResult<T> success(T data) {
AppResult<T> result = new AppResult<>();
result.setCode(0);
result.setMessage("success");
result.setData(data);
return result;
}
public static AppResult failed(ResultCode resultCode) {
AppResult result = new AppResult();
result.setCode(resultCode.getCode());
result.setMessage(resultCode.getMessage());
return result;
}
}(3)安全控制实现
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 1. 获取session
HttpSession session = request.getSession(false);
if (session == null) {
response.sendRedirect("/sign-in.html");
return false;
}
// 2. 验证用户登录状态
User user = (User) session.getAttribute(AppConfig.USER_SESSION);
if (user == null) {
response.sendRedirect("/sign-in.html");
return false;
}
return true;
}
}(4)文件上传实现
@RestController
@RequestMapping("/upload")
public class UploadController {
@PostMapping("/image")
public AppResult<String> uploadImage(@RequestParam("file") MultipartFile file) {
// 1. 验证文件类型
String contentType = file.getContentType();
if (!contentType.startsWith("image/")) {
return AppResult.failed(ResultCode.FAILED_UPLOAD_TYPE);
}
// 2. 生成文件名
String fileName = UUID.randomUUID().toString() +
file.getOriginalFilename().substring(
file.getOriginalFilename().lastIndexOf("."));
// 3. 保存文件
try {
String filePath = uploadPath + fileName;
file.transferTo(new File(filePath));
return AppResult.success("/uploads/" + fileName);
} catch (IOException e) {
return AppResult.failed(ResultCode.FAILED_UPLOAD);
}
}
}(1)总体流程图


(2)注册顺序图

(3)登录顺序图


对系统的核心功能模块,包括用户管理、帖子管理、版块管理、回复管理、站内信以及搜索功能,进行了全面的测试。
(1)测试目的:验证用户能否成功注册新账户,并使用已注册账户正常登录和注销
(2)测试过程:
(3)测试结果:
① 用户注册功能:对于合法数据,用户均能成功注册;对于已存在的用户名或不符合格式要求的数据,系统能给出正确的错误提示。密码加密存储机制(MD5+Salt)按预期工作
错误提示如下:

成功注册 就直接跳转到登录页面
② 用户登录功能:使用正确的凭据可以成功登录,错误的凭据会导致登录失败并给出提示。会话在用户登录期间保持有效,注销功能可以正确清除会话。
登录失败如下:

(1)测试目的:验证用户发帖、编辑帖子、删除帖子、查看帖子列表及详情的功能
(2)测试过程:
(3)测试结果:

(1)测试目的:验证用户对帖子进行回复、查看回复、删除回复功能
(2)测试过程:
(3)测试结果:

(1)测试目的:验证版块列表的展示及用户能否根据版块筛选帖子。
(2)测试过程:
(3)测试结果:

(1)测试目的:验证用户之间发送和接收私信的功能。
(2)测试过程:
(3)测试结果:

(1)测试目的:验证用户能否根据关键词搜索帖子和用户
(2)测试过程:
(3)测试结果:

(1)测试目的:验证管理员和普通用户身份的区别
(2)测试过程:
(3)测试结果:

对系统的关键操作进行了初步的性能评估,主要关注响应时间和并发处理能力。
页面加载速度

结论:当前灌水论坛系统是一个完整的Web论坛应用,采用主流的Java技术栈,结合现代化的设计理念,实现了用户注册、登录、发帖、回复、私信等核心功能。系统架构清晰,代码组织规范,具有良好的可扩展性和维护性。
待改进之处与未来展望:
至于代码的话:可以查看我的仓库