一、项目介绍
线上论坛
- 相关技术:SpringBoot+SpringMvc+Mybatis+Mysql+Swagger
- 项目简介:本项目是一个功能丰富的线上论坛,用户可编辑、发布、删除帖子,并评论、点赞。帖子按版块分类,方便查找。同时,用户可以修改和展示个人信息,还能发送私信与其他用户交流。
- 项目描述:
- 采用前后端分离架构,通过JSON格式传输数据,基于SpringBoot和Mybatis框架构建。
- 设计统一的数据返回格式,全局处理错误信息,提升用户体验。
- 利用@ControllerAdvice和@ExceptionHandler实现全局异常处理,确保系统稳定性。
- 使用HandlerInterceptor拦截器实现用户登录校验,并采用MD5算法对密码进行加密存储。
- 集成Swagger自动生成API测试接口,方便开发者进行测试和调试。
- 利用Mybatis Generator自动生成常用的增删改查方法,提高开发效率。
二、项目实现效果
部分页面展示:
帖子页面(首页显示所有帖子,切换版块则显示对应帖子):
用户个人信息展示页面(包含用户发布的帖子):
个人信息修改页面:
帖子详情页面(下方为评论):
用户之间私信:
部分功能说明:
页面上方 :帖子搜索功能并未实现,月亮按钮为可切换夜间模式,铃铛按钮为用户私信功能。
个人中心:其中头像修改功能并未实现。
三、项目具体实现
1.软件生命周期
一个软件的生命周期可以划分为
- 可行性研究
- 需求分析
- 概要设计
- 详细设计
- 编码实现
- 测试
- 使用及维护
- 退役
2.项目需求分析
- 用户 注册(设置用户名,昵称,密码,且需要同意隐私条款才可注册)、登录、退出登录 的功能。
- 个人中心可修改个人信息(包括 昵称,邮箱地址,电话号码,个人简介)和修改密码(需确认原密码)。
- 用户信息页:显示用户信息(昵称,发帖数,邮箱,注册时间,个人简介)及该用户发布的帖子(各个帖子显示其 标题 ,作者 ,发布时间,阅读量,点赞数,评论数),帖子按发布时间倒序排列,用户访问其他用户主页时可发送私信。
- 帖子列表页:首页显示所有帖子(各个帖子显示其 标题 ,作者 ,发布时间,阅读量,点赞数,评论数),切换版块则显示对应版块帖子,并且统计该版块下所有帖子数量,帖子按发布时间倒序排列。
- 查看全文:显示帖子信息(标题,正文,作者,发布时间,阅读量,点赞数,评论数),并且可点赞,评论帖子(下方评论按时间倒序排列),以及给作者发私信,若查看本人帖子,则还有修改,删除帖子功能。
- 添加,修改,删除帖子功能(除了标题,正文外,还要选择帖子版块)。
- 私信功能:显示信息状态(未读,已读,已回复),且无法给自己发送私信。
- 评论,点赞帖子功能。
- 页面可切换为白天,夜间显示。
- 用户权限限制:访问所有页面均要登录(未登录无法访问,强制跳转至登录页面)。
3.设计
设计数据库存储 用户,帖子,帖子评论,用户私信,帖子版块 信息。
其中用户信息表结构:
4.编码实现
4.1项目构建及相关配置
基于SpringBoot和Mybatis框架构建项目,及MySQL,MyBatis等相关配置...
4.2创建实体类(依据数据库中表结构)
例如:
@Data
public class User {
private Long id;
private String username;
@JsonIgnore //不参与Json序列化
private String password;
private String nickname;
private String phoneNum;
private String email;
private Byte gender;
@JsonIgnore //不参与Json序列化
private String salt;
@JsonInclude(JsonInclude.Include.ALWAYS) // 不论任何情况都参与JSON序列化
private String avatarUrl;
private Integer articleCount;
private Byte isAdmin;
private String remark;
private Byte state;
@JsonIgnore //不参与Json序列化
private Byte deleteState;
private Date createTime;
private Date updateTime;
}
4.3数据库持久层(涉及到增删改查)
例如:
@Mapper
public interface ArticleMapper {
int insert(Article row);
int insertSelective(Article row);
Article selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(Article row);
int updateByPrimaryKeyWithBLOBs(Article row);
int updateByPrimaryKey(Article row);
/**
* 显示首页列表
* @return
*/
List<Article> selectAll ();
/**
* 显示版块列表
* @param boardId
* @return
*/
List<Article> selectAllByBoardId(Long boardId);
/**
* 帖子详情
* @param id
* @return
*/
Article selectDetailById(Long id);
/**
* 根据用户id查询对应用户帖子列表
* @param userId
* @return
*/
List<Article> selectByUserId (Long userId);
}
4.4统一前后端数据交互对象
/**
* Created with IntelliJ IDEA.
* Description:统一的前后端交互对象
* User: 林
* Date: 2024-02-29
* Time: 19:39
*/
public class AppResult<T> {
@JsonInclude(JsonInclude.Include.ALWAYS) // 不论任何情况都参与JSON序列化
private long code;
@JsonInclude(JsonInclude.Include.ALWAYS) // 不论任何情况都参与JSON序列化
private String message;
@JsonInclude(JsonInclude.Include.ALWAYS) // 不论任何情况都参与JSON序列化
private T data;//泛型
public AppResult(long code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public AppResult(long code, String message) {
this(code,message,null);
}
public long getCode() {
return code;
}
public void setCode(long code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
//提供一些常用的静态方法
/**
* 成功
*/
//不需要返回数据
public static AppResult success(){
return new AppResult(ResultCode.SUCCESS.getCode(),ResultCode.SUCCESS.getMessage());
}
//不需要返回数据,自定义描述信息
public static AppResult success(String message){
return new AppResult(ResultCode.SUCCESS.getCode(),message);
}
//有数据
public static <T> AppResult<T> success(T data){
return new AppResult(ResultCode.SUCCESS.getCode(),ResultCode.SUCCESS.getMessage(),data);
}
//有数据,自定义描述信息
public static <T> AppResult<T> success(String message,T data){
return new AppResult(ResultCode.SUCCESS.getCode(),message,data);
}
/**
* 失败
*/
public static AppResult failed(){
return new AppResult(ResultCode.FAILED.getCode(),ResultCode.FAILED.getMessage());
}
//自定义描述信息
public static AppResult failed(String message){
return new AppResult(ResultCode.FAILED.getCode(),message);
}
//直接接收一个状态码对象,因为失败原因有多个
public static AppResult failed(ResultCode resultCode){
return new AppResult(resultCode.getCode(),resultCode.getMessage());
}
}
4.5全局统一异常处理
使用@ControllerAdvice配和@ExceptionHandler实现全局异常处理:
/**
* Created with IntelliJ IDEA.
* Description:全局统一异常处理
* User: 林
* Date: 2024-03-01
* Time: 15:57
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
//处理自定义的异常
@ResponseBody
@ExceptionHandler(ApplicationException.class)
public AppResult applicationExceptionHandler (ApplicationException e) {
// 打印异常信息
e.printStackTrace(); // 上生产之前要删除,生产环境使用日志记录的
// 打印日志
log.error(e.getMessage());
if (e.getErrorResult() != null) {
return e.getErrorResult();
}
// 非空校验
if (e.getMessage() == null || e.getMessage().equals("")) {
return AppResult.failed(ResultCode.ERROR_SERVICES);
}
// 返回具体的异常信息
return AppResult.failed(e.getMessage());
}
//处理非自定义的异常
@ResponseBody
@ExceptionHandler(Exception.class)
public AppResult exceptionHandler (Exception e) {
// 打印异常信息
e.printStackTrace();
// 打印日志
log.error(e.getMessage());
// 非空校验
if (e.getMessage() == null || e.getMessage().equals("")) {
return AppResult.failed(ResultCode.ERROR_SERVICES);
}
// 返回异常信息
return AppResult.failed(e.getMessage());
}
}
4.6注册、登录、退出登录
利用MD5对密码进行加密存储:
public class MD5Util {
/**
* 对字符串进行MD5加密
* @param str 明文
* @return 密文
*/
public static String md5 (String str) {
return DigestUtils.md5Hex(str);
}
/**
* 对用户密码进行加密
* @param str 密码明文
* @param salt 扰动字符,盐值
* @return 密文
*/
public static String md5Salt (String str, String salt) {
return md5(md5(str) + salt);//将原密码加密,拼上盐值,再一起加密成密文
}
}
4.7修改个人信息
先查询到之前的用户信息,再修改(修改密码则要还检验原密码)。
4.8用户主页信息
查询操作:根据用户id查询用户信息和用户所发布的帖子(按发布时间倒序排列)。
4.9帖子列表
查询操作:首页查询所有帖子,切换版块则查询对应版块帖子(按发布时间倒序排列)。
4.10查看全文
查询加修改操作:查询帖子,更新阅读量+1。
4.11添加、修改、删除帖子
增加,修改,删除操作:这些操作声明为事务
/**
* 创建帖子
* @param article
*/
@Transactional//事务
void create (Article article);
4.12私信功能
发送,回复私信:增加操作。
信息查看和信息状态更新(涉及到查询,修改操作):用户点开私信列表(查询所有收到的私信),有新的消息,则该消息此时状态为未读。点开该消息(查询该消息详情),则状态为已读(修改操作,修改消息状态)。回复该消息,则该消息状态为已回复(修改操作,修改消息状态)。
public interface IMessageService {
/**
* 创建一个私信,用于发送给其他用户
* @param message
*/
void create (Message message);
/**
* 根据用户id统计未读的信息
* @param receiveUserId
* @return
*/
Integer selectUnreadCount(Long receiveUserId);
/**
* 据id查询用户接收的私信列表,包括发送者信息和私信内容
* @param receiveUserId
* @return
*/
List<Message> selectByReceiveUserId (Long receiveUserId);
/**
* 根据id更新私信状态
* @param id
* @param state
*/
void updateStateById(Long id, Byte state);
/**
* 根据id查找私信
* @param id
* @return
*/
Message selectById(Long id);
/**
* 回复私信
* @param repliedId
* @param message
*/
@Transactional
void reply (Long repliedId, Message message);
}
4.13评论,点赞帖子
评论帖子:增加操作...
点赞帖子:修改操作,+1.
4.14白天,夜间效果
前端实现...
4.15用户权限限制
使用HandlerInterceptor拦截器实现用户登录校验:
/**
* Created with IntelliJ IDEA.
* Description:登录拦截器
* User: 林
* Date: 2024-03-05
* Time: 22:13
*/
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Value("${lin-forum.login.url}")
private String defaultURL;
//对拦截的内容做前置处理
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取session
HttpSession session=request.getSession(false);
//判断session
if(session!=null && session.getAttribute(USER_SESSION)!=null){
//用户已经登录
return true;
}
//还未登录,重定向到登录页面
//校验url地址是否以/开头
if(!defaultURL.startsWith("/")){
defaultURL="/"+defaultURL;
}
response.sendRedirect(defaultURL);
return false;
}
}
四、项目代码(gitee地址)
forum · new林/项目 - 码云 - 开源中国 (gitee.com)
(服务器过期了,没部署...)