项目编号:BS-PT-077
前言:
对于当前的个人博客系统,比较封闭并且有自己的局限性,一旦流量大了以后就会受到不明来历的恶意流量攻击。无论你买什么服务器都没有用。对于个人来说,基本都是重启服务器是最佳的解决办法。所以,安全问题对于独立博客来说是一个很大的挑战。并且作为一个个人博客,网站中只有拥有者才能进行文章的发布等操作,这使得其他用户不能发布自己的文章。
校园文章发布系统吸收了个人博客部分特点。允许所有用户发布文章,摒弃掉博客网站只能拥有者发文章这一弊端,用户还能建立属于自己个人的分类与标签,使得用户个性化设置达到最大,并且每个用户既是文章发布者,也是文章的阅读者,能对别人发布的文章进行查看评论等操作,并且本系统主要面向对象为在校大学生,所以在网站安全方面,后期可以将项目运行在局域网内,使得只有使用校园网才能对我们的系统进行访问。
一,项目简介
本系统设计了两种用户角色:普通用户和系统管理员。普通用户可以新建属于个人的分类与标签,可以发布个人的公开或者私有文章,并且还能游览系统内的其他公开文章,对文章进行点赞、评论等操作。系统管理员主要是对用户产生的数据进行统计与维护;能对系统中的用户进行统计和操作,对用户的信息进行查看、删除等;能对用户发布的文章进行审核,对于一些非法的文章不予通过,并提醒用户所发的文章违规,还能对文章进行删除和维护等操作;能对用户的评论信息进行管理,查看用户们所发布的评论,还可以将违规评论直接删除;能对用户创建的分类信息进行查看、修改、删除等操作;能对用户创建的标签信息进行查看、修改、删除等。该系统的功能模块图如图3-1所示。
图3-1系统功能模块图
普通用户可以使用网站的基础功能,如查看平台首页、进入用户个人中心、进入用户管理、对个人的文章管理、搜索系统的所有文章等,用户功能用例图如图3-2所示。
图3-2 用户功能用例图
管理员通过系统自带的帐号登录,管理员账号除了可以使用普通用户的所有功能外,还可以进入后台管理页面,对用户和文章进行管理,对文章进行评论管理,对文章进行分类和标签管理。管理员功能用例图如图3-3所示。
图3-3 管理员用例图
本系统有文章搜索模块、文章管理模块、平台首页模块、用户管理模块、个人中心模块、管理员模块6个功能模块,其主要功能分析如下:
1.文章搜索模块功能
表3-1 文章搜索模块功能描述
功能描述 |
|
按文章标题搜索 |
输入文章的部分标题,查看对应的文章 |
按分类搜索 |
通过文章的大分类得到一个类型的文章 |
按标签搜索 |
通过文章所包含的标签,得到对应的文章 |
按文章信息搜索 |
通过关键字搜索包含该关键字的文章 |
2.文章管理模块
表3-2 文章管理模块功能描述
功能名称 |
功能描述 |
文章回收站 |
用户可以将文章删除放入回收站 |
用户发布文章 |
用户可以发布自己的个人文章 |
用户修改文章 |
用户可以修改自己发布的文章的内容 |
3.平台首页模块
表3-3 平台首页模块功能描述
功能名称 |
功能描述 |
用户注册 |
用户通过QQ邮箱注册系统的账号 |
用户登录 |
用户通过个人账号登录系统 |
游览平台文章 |
对系统中的文章进行查阅游览 |
查看文章详细信息 |
查看某一篇文章的详细内容 |
用户评论文章 |
在文章详细内容界面可对文章发布个人的评论 |
文章点赞 |
在文章详细内容界面可对文章点赞 |
4.用户管理模块
表3-4 用户管理模块功能描述
功能名称 |
功能描述 |
查看归档信息 |
用户可按照文章发布时间查看发布的所有文章 |
管理分类信息 |
用户对个人文章的分类做增删改查 |
管理标签信息 |
用户对个人的文章标签做增删改查 |
个人通知 |
当别人对用户的文章进行评论或者别人回复用户的文章后,都会在个人通知中显示 |
5.个人中心模块
表3-5 个人中心模块功能描述
功能名称 |
功能描述 |
查看个人信息 |
用户可查看个人的详细信息 |
修改个人信息 |
对自己的信息进行修改 |
修改密码 |
修改个人的账号密码 |
6.管理员模块
表3-6 管理员模块功能描述
功能名称 |
功能描述 |
登录注册 |
管理员可注册和登录后台系统 |
用户管理 |
可查看、删除用户的信息 |
文章管理 |
审核用户发布的文章,可对文章进行增删查 |
评论管理 |
查看用户的评论,对非法的评论进行删除 |
分类管理 |
对用户新建的分类进行管理 |
标签管理 |
对用户新建的标签进行管理 |
二,环境介绍
语言环境:Java: jdk1.8
数据库:Mysql: mysql5.7
应用服务器:Tomcat: tomcat8.5.31
开发工具:IDEA或eclipse
三,系统展示
5.1 平台首页模块
5.1.1 注册与登录
前端的登录界面由login.vue实现,注册页面由register.vue实现。用户注册时,用户需要先填写邮箱,接着发送验证码,通过/user/getSendCode接口将邮箱号传入后端,后端接收到请求后,首先判断当前邮箱是否已经在系统中,若存在系统则返回信息提示用户直接登录,否则调用UserService类中的getSendCode方法,先生成一个六位的随机验证码,将验证码存入Redis中,再通过qq邮箱接口,发送对应邮件到用户的邮箱中,返回成功信息,提示用户验证码发送成功,用户输入正确的邮箱和合法的密码后,点击注册按钮,此时调用/user/regist接口,将用户的信息和验证码一起传给后端,后端首先从Redis中获取当前用户的正确验证码,与传来的验证码进行匹配,如果匹配成功则将用户的信息写入数据库,返回成功信息给用户,提示用户注册成功,若验证码不正确,则直接返回验证码输入有误的信息给前端,前端提示用户验证码输入错误。界面实现效果如图5-1所示。
图5-1 用户注册界面
用户注册完成后,则可以进行账号登录,输入邮箱和密码之后点击登录,前端通过/user/loginPwd接口向后端发送请求,后端从数据库中查询是否存在这个用户,并且查询该用户的密码,与当前用户输入的是否一致,若密码也一致则返回用户的信息给前端,前端通过传回的数据是否为用户个人信息为判断的标准,如果返回了个人信息,则提示用户登录成功,将页面跳转到系统的主页,并且将用户的个人信息存储到游览器的sessionStorage中,以便用户只是刷新游览器时,保证用户的登录状态,若返回flase则提示用户账号或者密码输出错误。界面实现效果如图5-2所示。
图5-2 用户登录界面
5.1.2 游览平台文章
在用户登录后,前端通过this.$router.push('/homeIndex')进行路由跳转,进入主页,在主页界面进行挂载之前,前端通过/article/initGetArticleList接口,向后端发送请求,对主页的文章列表数据进行初始化,后端将查询到的数据返回给前端,前端接收到数据后对文章列表进行渲染。界面实现效果图如图5-3所示。接着前端还会通过/home/getTjCarouselArticle接口,向后端请求首页轮播图的数据,因为轮播图数据是通过文章的点赞量来确定,并且通过定时器每天凌晨五点对数据进行更新,因此会将数据放入Redis缓存,以此减轻数据库的压力,所以每次请求时都会从Redis缓存中获取数据,将结果返回给前端,前端对数据进行渲染。界面实现效果如图5-4所示。
图5-4 首页轮播图界面
5.1.3 查看文章详细信息
当用户点击某一篇文章时,前端进行路由跳转,并且会将该篇文章的id作为路由参数一起放入请求的路由中,在页面组件挂载前,会先准备文章详细信息界面所需要的数据,通过/home/getArticleById接口获取当前文章的详细信息,前端接收后端返回的数据后,对文章的详细内容进行渲染,加载文章的详细信息和目录信息,如图5-5所示;通过/home/getFiveArticleByUserid接口获取当前文章的作者的最新发布的五篇文章的跳转链接,后端实现为通过用户的id加以文章发布的日期做降序排列,最终取前五条数据得到需要的数据,前端接收后端的数据后,对数据进行渲染,界面实现效果图如图5-6所示;通过/comment/getArticleCommentCount接口分页获取当前文章的评论信息,后端接收请求后,通过文章id查询属于该文章的评论的信息,将得到的数据返回给前端,前端将得到的数据进行渲染,界面实现效果图如图5-7所示。
图5-5 文章详细信息界面
图5-6 用户其他文章推荐界面
图5-7 文章评论信息界面
5.1.4 用户评论
用户评论功能分为两类,一类是用户对文章进行评论,另一类是用户对其他用户的评论进行回复。当用户对文章进行评论时,点击提交按钮,如图5-8所示,前端会先判断当前用户是否登录,若未登录,则提示用户登录,并通过this.$router.push('/login')语句跳转到登录界面,再判断当前用户评论的内容是否为空,若为空则提示用户评论不能为空,不为空则通过/comment/addComment发送请求到后端,后端在数据库中新增一条数据后,返回成功信息给前端,前端提示用户评论成功。如果是用户回复其他用户的评论,则先点击目标评论后面的回复按钮,此时会出现回复框,如图5-9所示,用户输入需要回复的内容后,点击提交按钮,前端也是通过/comment/addComment接口发送请求到后端,后端插入一条数据后,返回成功信息给前端,前端提示用户回复成功。
图5-8 用户评论文章界面
图5-9 用户回复评论界面
5.2 文章管理模块
5.2.1 文章回收站
用户可以在个人中心查看自己所发布的文章的列表,在这里用户可以点击删除按钮,将文章放入回收站,如图5-10所示,点击删除按钮后,界面会提示用户是否确认删除,用户点击确认,前端会通过/article/delArticleList接口向后端发送删除选中文件的请求,后端接收到请求后,将对应文章移入回收站中,返回成功信息给前端,前端提示用户删除成功。接着用户可以在回收站中找到被删除的文章,如图5-11所示,用户可以恢复被删除的文章,也可以彻底删除文章,若点击恢复按钮,前端会通过/article/delArticleList接口,发送对应的参数到后端,后端通过其中的参数去进行判断,是将文章移入回收站还是恢复该文章,恢复成功后,返回成功信息给前端,前端提示用户恢复文章成功;若用户点击的是删除按钮,则通过/article/delArticleList接口,给后端发送对应的参数,后端解析参数后会将选定的文章数据进行彻底删除,最后返回给前端成功信息,前端进行渲染,提示用户删除成功。
图5-10 用户操作个人文章界面
图5-11 用户查看文章回收站界面
5.2.2 用户发布文章
用户点击发布文章按钮后,进入文章编写界面,在这里用户可以编写文章内容,还可以在文章中插入图片,插入图片时通过/file/uploadImg接口直接将图片保存到服务器中,再将该图片的访问路径返回给前端,前端会通过markdown保存图片的请求路径,并显示图片的预览,如图5-12所示,当用户编写好文章内容后,则可以进入文章发布界面,如图5-13所示,在这里用户需要将文章的必要信息完善,在选择分类时,前端已经通过/classify/getMyClassify接口,获取了该用户现有的所有分类,接着监视分类输入框,展示用户可能想要选择的分类,也可以直接在这里创建一个新的分类,如图5-14所示,在选择标签时,系统不仅仅会显示用户个人的标签,还会推荐本平台文章数前五的五个标签供用户选择,平台前五标签通过/labels/getTJLabels接口向后端发送请求,将标签信息封装在数组中,返回给前端,前端就能进行展示,用户在这里可以选择多个标签,也会根据用户在搜索框中输入的关键字,通过/labels/getMyLabels接口进行模糊查询,显示用户可能想要选择的标签,如图5-15所示,最终用户点击发表按钮,前端通过/article/addArticle接口将文章信息发送给后端,后端接收数据后,将文章信息插入数据库,将分类表和标签表中文章数量加一,再文章分类表、文章标签表中添加文章和分类、标签之间的关联数据,返回成功信息给前端,前端接收后提示用户文章发布成功,并通过this.$router.push({path:'/userCenter/articleList'})跳转到用户中心的文章列表。
图5-12 用户文章内容上传图片界面
四,核心代码展示
package com.qst.controller; import com.qst.pojo.ResultInfo; import com.qst.pojo.schoolarticle.Article; import com.qst.service.ArticleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/article") public class ArticleController { @Autowired ArticleService articleService; //添加普通文章 @RequestMapping("/addArticle") public ResultInfo addArticle(Article article){ System.out.println(article); try { return articleService.addArticle(article); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常啦,请稍后再试"); } } //添加文章草稿 @RequestMapping("/addArticleDraft") public ResultInfo addArticleDraft(Article article){ try { return articleService.addArticleDraft(article); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常啦,请稍后再试"); } } //展示文章列表、分页、多条件查询 @RequestMapping("/showArticleList") public ResultInfo showArticleList( Article article,Integer current, Integer size){ try { return articleService.showArticleList(article,current,size); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常啦,查询失败,请稍后再试"); } }//展示首页文章列表 @RequestMapping("/initGetArticleList") public ResultInfo initGetArticleList( Article article,Integer current, Integer size){ try { return articleService.initGetArticleList(article,current,size); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常啦,查询失败,请稍后再试"); } } //将文章放入回收站或者将回收站的文章恢复或将文章彻底删除 @RequestMapping("/delArticleList") public ResultInfo delArticleList(Article article,Integer[] idList){ try { return articleService.delArticleList(article,idList); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统暂时出现异常,请稍后再试"); } } //搜索功能 @RequestMapping("/search") public ResultInfo search(String keywords){ try { return articleService.search(keywords); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统暂时出现异常,请稍后再试"); } } }
package com.qst.controller; import com.qst.pojo.ResultInfo; import com.qst.pojo.schoolarticle.Comments; import com.qst.pojo.schoolarticle.Likes; import com.qst.service.CommentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/comment") public class CommentController { @Autowired CommentService commentService; //获取文章所拥有的评论数量 @RequestMapping("/getArticleCommentCount") public ResultInfo getArticleCommentCount(Integer articleid){ try { return commentService.getArticleCommentCount(articleid); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,获取文章评论数失败"); } } //添加一条评论 @RequestMapping("/addComment") public ResultInfo addComment(Comments comments){ System.out.println(comments); try { return commentService.addComment(comments); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,插入评论失败"); } } //查询当前页的评论 @RequestMapping("/showComment") public ResultInfo showComment(Comments comments,Integer current){ try { return commentService.showComment(comments,current); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,查询评论失败"); } } //查询评论的回复评论 @RequestMapping("/showCommentReply") public ResultInfo showCommentReply(Comments comments,Integer current,Integer size){ try { return commentService.showCommentReply(comments,current,size); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,查询评论的回复失败"); } } //查询当前用户的评论 @RequestMapping("/getCommentListByUserid") public ResultInfo getCommentListByUserid(Comments comments,Integer current,Integer size,String keywords){ try { return commentService.getCommentListByUserid(comments,current,size,keywords); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,查询评论列表失败"); } } //查询当前用户发布的评论 @RequestMapping("/getUserComment") public ResultInfo getUserComment(Comments comments,Integer current,Integer size,String keywords){ try { return commentService.getUserComment(comments,current,size,keywords); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,查询评论列表失败"); } } //查询当前用户对该片文章的信息点赞了多少 @RequestMapping("/getLikesByUseridArticleid") public ResultInfo getLikesByUseridArticleid(Likes likes){ try { return commentService.getLikesByUseridArticleid(likes); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,查询用户点赞信息失败"); } } //用户对评论点赞或取消点赞 @RequestMapping("/setCommentLikes") public ResultInfo setCommentLikes(Likes likes,Integer status){ try { return commentService.setCommentLikes(likes,status); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,用户点赞操作失败"); } } //用户通过评论 @RequestMapping("/setCommentStatus") public ResultInfo setCommentStatus(Integer[] idList){ try { return commentService.setCommentStatus(idList); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,通过评论失败"); } } //用户删除评论 @RequestMapping("/delComment") public ResultInfo delComment(Integer[] idList){ try { return commentService.delComment(idList); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,删除评论失败"); } } //更改用户收到的回复的状态 @RequestMapping("/setReceivedRelpyStatus") public ResultInfo setReceivedRelpyStatus(Comments comments){ try { return commentService.setReceivedRelpyStatus(comments); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,修改回复状态失败"); } } }
package com.qst.controller; import com.qst.pojo.ResultInfo; import com.qst.pojo.schoolarticle.Article; import com.qst.service.HomeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/home") public class HomeController { @Autowired HomeService homeService; //查询前台首页用户的文章数、分类数、标签数 @RequestMapping("/getUserArticleLabelClassifyCount") public ResultInfo getUserArticleLabelClassifyCount(Integer userid){ try { return homeService.getUserArticleLabelClassifyCount(userid); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,获取用户文章数量失败"); } } //查询首页轮播图的推荐文章列表 @RequestMapping("/getTjCarouselArticle") public ResultInfo getTjCarouselArticle(){ try { return homeService.getTjCarouselArticle(); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,获取推荐文章列表失败"); } } //通过文章id,获取文章的详细信息 @RequestMapping("/getArticleById") public ResultInfo getArticleById(Integer articleid){ if (articleid==null) return new ResultInfo(false,"请选择正确的文章"); try { return homeService.getArticleById(articleid); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常,获取文章详细信息失败"); } } //通过用户id获取该用户的5篇最新文章 @RequestMapping("/getFiveArticleByUserid") public ResultInfo getFiveArticleByUserid(Integer userid,Integer articleid){ if(userid==null) return new ResultInfo(false,"用户名为空,所以无法查询用户的最新文章"); try { return homeService.getFiveArticleByUserid(articleid,userid); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常啦,请稍后再试"); } } //判断用户是否为当前文章点赞 @RequestMapping("/isLikeArticle") public ResultInfo isLikeArticle(Article article){ System.out.println(article); try { return homeService.isLikeArticle(article); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常啦,请稍后再试"); } } //给当前文章点赞的状态进行修改 @RequestMapping("/setArticleLike") public ResultInfo setArticleLike(Article article,Integer flag,Integer articleuserid){ try { return homeService.setArticleLike(article,flag,articleuserid); } catch (Exception e) { e.printStackTrace(); return new ResultInfo(false,"系统异常啦,请稍后再试"); } } }
五,项目总结
本系统采用前后端分离设计,前端与后端相互独立,前端使用Vue+ Element-UI开发,后端基于SSM框架开发。前端项目名称byVue,如图4-1所示。后端项目名称final-project,如图4-2所示。
图4-1 前端项目结构图
前端目录说明如下:
node_modules: 它用于存储项目的各种依赖项,例如Axios。没有moudles文件,项目就不能运行;
public:用于存储静态文件;
public/index.html: 它是一个模板文件,用于生成项目的条目文件。webpack打包的JS和CSS也会自动注入到页面中。当我们的浏览器访问项目时,它将默认打开生成的index.html;
src:我们存放各种vue文件的地方;
src/assets:用于存放各种静态文件,如图片等;
src/compnents:用于存放我们的公共组件,如 header、footer等;
src/views:用于存放我们写好的各种页面,如login、main等;
src/APP.VUE: 主vue模块 引入其他模块,app.vue是项目的主组件,所有页面都是在app.vue下切换的;
src/main.js: 入口文件的主要功能是初始化Vue实例。同时,可以在这个文件中引用一些组件库,或者全局挂起一些变量;
src/router.js: 路由文件可以理解为我们访问的每个页面的地址路径。同时,路由保护可以直接写入其中;
src/store.js:主要用于项目里边的一些状态的保存,state中保存状态,mutations中写用于修改state中的状态;
package.json: 模块基本信息项目开发所需要模块,版本,项目名称;
package-lock.json: 在NPM安装过程中会生成一个文件,记录当前状态下实际安装的每个NPM包的具体源代码和版本号;
vue.config.js: 保存vue配置的文件,可以用于设置代理,打包配置等。