终于有机会写这篇文章了,写博客这东西还真不能拖啊,当时很有激情,很清楚自己要写一些什么,重点强调些什么,拖到现在,都不知道该怎么写了。
项目介绍
首先这个项目是作为这学期的Java
Web大作业的,我们小组共三个人,我主要负责前台功能及界面,我们的选题是“在线新闻发布系统”。前台需要实现,新闻浏览,新闻筛选(按类型或关键字),登录后用户可以新闻增删改,新闻评论,问题反馈,信息密码修改等;后台实现新闻、用户、评论、反馈的增删改,还有新闻审核功能,我们有个特色是控制首页推送的新闻,也就是说首页并不是展示最近添加的新闻,而是我们所控制的,就像一些大网站是靠某些数据计算判断这些内容是否推送到首页一样,控制首页的轮播图,很有趣!当然我也只是介绍我的这部分,至于其他部分由我们小组其他成员完成,我不会去介绍这部分。
展示
展示的主要有某一类的新闻,具体内容,评论区,个人主页,个人资料,反馈页。。。
展示
环境/工具
构建工具:Maven3.6.3
数据库:MySQL5.5
基础环境:SSM,Spring5.0.6、Mybatis3.5.3
前台设计:layui
富文本编辑器:CKEditor
图标来源:阿里巴巴矢量图标库
工具:IDEA、Tomcat9.0、Navicat Premium15、Chrome、Edge
项目结构
项目结构
从图中已经可以看出整个项目是怎样的了,还是比较清晰的。
关键部分
因为项目之前我也见拆成许多部分了(链接会放在文末),所以我只会提一些关键的或是很有趣的部分。
1 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
本项目很多数据展示都是靠JSTL与EL实现的,c标签下的if、forEach、choose、when等也是经常使用,个人感受还是挺方便的,虽然并不符合一些开发标准。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <script src ="${pageContext.request.contextPath}/Static/js/lib/jquery-3.5.1.min.js" > </script > <script type ="text/javascript" > var cPage = 0 ; var uid = '${sessionScope.userS.userId}' ; $(function ( ) { $("#index" ).trigger ("click" ); $("#search" ).click (function ( ) { var key = $("#key" ).val (); main ("${pageContext.request.contextPath}/queryNewsByKey?key=" + key + "&cPage=" + cPage); }); $('#key' ).bind ('keypress' , function (event ) { if (event.keyCode === 13 ) { $("#search" ).click (); } }); }); function byCid (cid ) { main ("${pageContext.request.contextPath}/queryNewsByCId?cid=" + cid + "&cPage=" + cPage); } function toEdit ( ) { var myForm = document .createElement ("form" ); myForm.method = "post" ; myForm.target = "_blank" ; myForm.action = "${pageContext.request.contextPath}/toEdit" ; var myInput = document .createElement ("input" ); myInput.setAttribute ("name" , "uid" ); myInput.setAttribute ("value" , uid); myForm.appendChild (myInput); document .body .appendChild (myForm); myForm.submit (); document .body .removeChild (myForm); } function main (href ) { $("#main" ).html ('' ); $("#main" ).load (href); } </script >
这里用的还是挺有意思的,将需要的内容放入名为“main”的div中,实现头部尾部不变,只有中间部分变化。还有那个toEdit()方法写文章按钮,将a标签get变为form的post实现,增强了安全性,同时可以将对应的Controller方法改为只接受post请求,这样就保障了未登录用户的写文章和登录用户编辑他人文章。
除了正常拼接成html元素外,往往需要增加到页面或从页面删除
比如下面的评论删除实现,cid是comment_id是评论的唯一标识符,将评论的id设置为“div_cid”,因为每条评论cid不同,所以存在不同id的div,这时我们删除评论,关键代码就是$("#div_" + cid).remove();
,这样的操作方便且简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <script type ="text/javascript" > function delComment (cid ) { if (confirm ("确定要删除吗?" )) { $.ajax ({ url : "${pageContext.request.contextPath}/deleteComment" , type : "post" , data : {cid : cid}, dataType : "json" , success : function (data ) { if (data.result === 'success' ) { $("#div_" + cid).remove (); } else { alert ("评论删除失败" ); } } }); return true ; } else { return false ; } } </script >
评论删除也类同,JSON数据处理成html能识别的s,然后$("#newComment").prepend(s);
,这要注意是使用.prepend在元素前,还是append在元素后。
DAO层
后端并没有用到什么特牛逼的技术,让我用我也不会啊。所以后端很朴素简单,主要过程是,写Mapper中的sql语句,这些都是原子操作,无非就是些增删改,下面是分页查询某类新闻
1 2 3 @Select("select * from news where category_id=#{cid} and state=1 and news_id<=(select news_id from news where category_id=#{cid} and state=1 order by news_id desc limit ${cPage},1)order by news_id desc limit 10") @ResultMap("News") List<News> QueryNewsByCId (@Param("cid") long cid, @Param("cPage") int cPage) ;
Service层
前面说了DAO是原子操作,Service层则是将DAO层的操作组合形成我们真正需要的操作。举个例子:DAO已经实现了,查询用户是否存在和用户创建操作,现在我需要创建一个新用户,这时我需要的是先查询这个用户是否存在,若存在则提示已存在,若不存在则创建。就是这样的道理,Service是DAO的组合,形成有逻辑的操作。下面是删除某条新闻,连带着删除这条新闻的评论和图片。
1 2 3 4 @Override public int DeleteNews (long nid) { return this .frontMapper.DeleteNews(nid) + this .frontMapper.DeleteCommentN(nid) + this .frontMapper.DeletePic(nid); }
Controller层
简单的说就是Servlet,只不过有了框架的加持使我们编程更加容易且方便,Controller就是除了ModelAndView离用户最近的了,这时要处理用户的Request,并Response,调用关系就是,通过前端交互交由Controller处理,Controller调用Service,Service调用Dao,Dao完成数据库操作。下面是利用layui上传用户头像示例,图片命名方式是用户id_icon.xxx,这样可保证用户头像唯一性,同时设置了上传限制为1M。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <script > layui.use ('upload' , function ( ) { var $ = layui.jquery , upload = layui.upload ; var uploadInst = upload.render ({ elem : '#test1' , url : '/NEWS_war_exploded/uploadUserIcon/' ,accept :'images' ,size :1024 , before : function (obj ) { obj.preview (function (index, file, result ) { $('#demo1' ).attr ('src' , result); }); } , done : function (res ) { if (res.code > 0 ) { return layer.msg ('上传失败' ); } var demoText = $('#demoText' ); demoText.html ('<span style="color: #4cae4c;">上传成功</span>' ); var fileupload = $(".image" ); fileupload.attr ("value" ,res.data .src ); console .log (fileupload.attr ("value" )); $("#userIcon" ).val (res.data .src ); } , error : function ( ) { var demoText = $('#demoText' ); demoText.html ('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>' ); demoText.find ('.demo-reload' ).on ('click' , function ( ) { uploadInst.upload (); }); } }); }); </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @ResponseBody @RequestMapping("uploadUserIcon") public Map upload (MultipartFile file, HttpSession session) { Users users= (Users) session.getAttribute("userS" ); String prefix="" ; String myFileName=users.getUserId()+"_icon" ; OutputStream out = null ; InputStream fileInput=null ; try { if (file!=null ){ String originalName = file.getOriginalFilename(); prefix=originalName.substring(originalName.lastIndexOf("." )+1 ); String filepath = session.getServletContext().getRealPath("/Static/img" ); String fileName = filepath +"\\" +myFileName+"." + prefix; File files=new File (fileName); System.out.println(filepath); if (!files.getParentFile().exists()){ files.getParentFile().mkdirs(); } file.transferTo(files); Map<String,Object> map2=new HashMap <>(); Map<String,Object> map=new HashMap <>(); map.put("code" ,0 ); map.put("msg" ,"" ); map.put("data" ,map2); map2.put("src" ,"/Static/img/" +myFileName+"." + prefix); return map; } }catch (Exception e){ }finally { try { if (out!=null ){ out.close(); } if (fileInput!=null ){ fileInput.close(); } } catch (IOException e) { } } Map<String,Object> map=new HashMap <>(); map.put("code" ,1 ); map.put("msg" ,"" ); return map; }
总结
说实话开发过程遇到很多问题,小组内还出现过矛盾,不过一切都过去了,结局还是很不错的,所有失败和教训都是最宝贵的财富,它会帮助我走好接下的每一步。
问题与展望
项目到最终提交的时候还不是我满意的样子,我知道的就存在一些问题,比如:权限安全方面并没有做好,应该设置一个拦截器,拦截未登录用户所有增删改操作,在我们这个系统中是支持游客访问的,不过只能查看新闻,其他什么也不能操作,不过最终这个也是没有完全实现,是个遗憾;还有就是对于数据库方面有很大的遗憾,因为对于Mybatis和MySQL理解并不到位,导致数据库设计的很糟糕,对之后的造成了很大影响;还有前端设计实在是搞不来,基本上都是东拼西凑,最后导致一些js矛盾,或是极其复杂,可读性极差。这些都是已经存在的问题。
说一说展望吧,前台功能太少了,记得当时我们提交的选题报告中的功能还有新闻收藏、点赞、转发,用户等级系统,用户关注等。最后都没有实现,当时可能没有什么想法,不知道怎么做,但到现在,怎么做我已经有那个框架了,如果有机会真的可以打这些功能加上去。
合作开发重要性
这学期还有一门课“软件工程”,虽然这门课有些水,但还是有些收获的。
合作开发,需求分析太重要了,一定要统一的开发环境,什么MySQL版本统一了,JDK统一,Spring统一等等太重要了,不然的话就会遇到许许多多的问题。
代码规范也是非常重要的,命名、类、接口等这是系统能不能走的远的评判标准。如果设计得当那么接下来的开发会很顺利,包括之后的维护升级等;如果设计不当那么接下来就像从头开始一样麻烦。
结束
这篇文章干货不多,将整个项目介绍完也不太现实,源码全摆上去也没太大意义。所以这里主要说的是这次项目的感受与总结。
相关链接
maven与npm使用镜像
Java Web
大作业(在线新闻发布系统)数据库设计
SSM整合
富文本编辑器CKEditor配置与使用
AJAX与JSON初次使用