Java部分
封装一个数据结构,这里我选择了双向循环链表,也叫双链表。先简单介绍一下,双向循环链表的每个数据节点都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表的任意一个节点开始,都可以很方便地访问它的前驱节点和后继节点。
上学期刚学过C语言的数据结构,链表这一块用的还是比较多的,看一两个视频复习一下,用Java写起来也是比较快的,我一开始也没看到这个作业,一直在写JavaWeb的部分,最后花了大半天的时间写完。
1、编写接口
通过查看源码和数据结构的书籍,就大概可以知道实现一个数据结构需要编写什么方法,这里写了一个接口,会更加清楚明了一些,一个数据结构基本的方法都有实现。接口没有注释,实现类有较为详细的注释。
import java.util.Comparator;
import java.util.Iterator;public interface List<E> {void add(E element);void add(int index,E element);void remove(E element);E remove(int index);E get(int index);E set(int index ,E element);void clear();boolean contains(E element);int indexOf(E element);boolean isEmpty();int size();void sort(Comparator<E> c);List<E> subList(int fromIndex,int toIndex);Iterator<E> iterator();}
2、方法实现
在实现方法之前肯定要定义一个节点和初始化一些东西
//链表的节点 使用private修饰
private class Node{private E data;//双向循环链表 前驱 和后继private Node pre;private Node next;//三个构造器public Node(){this(null,null,null);}public Node(E data){this.data=data;pre=null;next=null;}public Node(E data, Node pre, Node next) {this.data = data;this.pre = pre;this.next = next;}@Overridepublic String toString() {return data.toString();}}
//双向循环链表有头节点和尾节点
private Node head;//头节点
private Node tail;//尾节点
private int size;//链表的长度//初始化
public LinkedList(){head=null;tail=null;size=0;
}
接下来就是一个个去实现链表的方法。
在涉及到index 的方法时,都要先判断是否在范围之内,否则就要抛出异常
//判断index是否合法if(index<0||index>size){//抛出异常throw new IndexOutOfBoundsException("index out of range");}
sort方法里也有一个异常
//排序@Overridepublic void sort(Comparator<E> c) {if(c==null){throw new IllegalArgumentException("can not be null");}
方法具体的实现我就不细说了,这里展示一下测试的结果
从测试结果可以看出该链表基本功能
JavaWeb
开发简述
这次的大作业我差不多20天时间都没闲着,前面一段时间主要在学,真正写了一个星期的样子。主要使用HTML + CSS + JS + JQuery + Ajax + Servlet + JSP,运用MVC思想来分层设计,每个功能的实现一开始都比较困难,需要调试很久。写到后面就发现代码都是重复的,感觉这样写太麻烦了,Dao层,Service层和Servlet层,同时有些功能也实现不了。我就去学了JQuery和Ajax,还有一个Jquery的插件 Jquery Validate,这三个用的也比较多。通过这三者我利用JS实现了比较多的功能。JQuery是为了用Ajax学了一点,有些时候用的还是原生JS。JavaWeb我是跟着B站狂神学的,这个讲的有点乱我就自己去看博客看别人是怎么写的,从别人那里学会了很多解决问题的小技巧。
同时这次大作业最大的困难就是时间不够,我是在Ubuntu系统上开发的,之前集训的时候我的windows崩了,只好重装系统,随之而来的就是各种软件和环境变量的配置。于是我选择了Linux,安装软件方便也不用配置环境变量。但是调教和学习这个系统同样花了我很多的时间,因为有些软件安装起来比windows更麻烦一点。最折磨的就是ubuntu经常会出各自问题,Idea经常写到一半闪退导致代码丢失一部分,我至少1/3的时间在解决系统的问题。 就像今天本来是写这个文档的,系统就崩了,我搞了一天,终于能够启动了,在晚上11:59分把作业发到邮箱去了。
那么多崩溃日志就离谱(本来我桌面上是没东西的,崩溃之后文件都跑到桌面上了,老毛病了),也不是Ubuntu的问题吧,可能我没有调教好,写完大作业就去解决。
功能简单描述
1.前后台
-
前台主要做展示交互,首页和每个话题都有对应的页面。
-
后台可以对完整进行管理设置,对学习小组,话题,评论,用户进行管理。根据管理对象不同,能够进行的操作也不同,比如用户可以增删改查,而评论我们一般只能查询和删除(也不能改别人的评论吧)。
-
后台管理都设置了对应的查询关键字,可以方便的查询
2.权限管理
权限有两种:普通用户和管理员
-
普通用户登录后可以在前台浏览和评论,可以加入相应的学习小组
-
管理员可以登录后台,可以对用户、学习小组、评论等进行管理
管理员点击登陆之后直接登陆到后台,普通用户登陆到前台
3.用户注册和登录
-
注册:填写相关信息即可注册成功,管理员后台也可以添加用户。
-
登录:输入正确的账号密码即可登录成功,没有登陆是无法前往前台的,因为这个学习小组类似实验室的官网一部分,需要学号登陆。
4.学习小组
学习小组下有许多话题讨论
不是小组成员也可以看话题讨论,但是只有是小组成员才能去创建话题
5.话题讨论
-
小组成员创建话题,有标题和标题描述
-
用户可以在话题下评论,也可以在评论下评论。
这么看着可能功能不多,但是我写的细节很多,功能之间都是串联起来的,例如删除一个小组,那么我不止删除这个小组,我也把这个小组下的话题和评论都删除了。 接下来详细介绍各个页面以及功能。
项目结构
技术栈: HTML + CSS + JS + JQuery + Ajax + Servlet + JSP
数据库结构
这个数据库还是比较符合三大范式的,这也导致我前台传入数据到后台查询返回需要多写许多步骤,例如comment表内只有创建用户的ID没有用户名,显示创建评论的用户名时需要多写几个步骤。
页面详细介绍
登陆注册页面
这个页面是我跟着b站上的学的,在写这个大作业之前 我是把b站上黑马前端的网课都看的差不多了,html+css+js,觉得美化页面还是挺有趣的。我自己写的前端也是花了很长时间去优化各种细节:按钮的hover,盒子的阴影等等
这个页面我是完全跟着视频敲的,我自己也写不出来这么厉害的页面,主要是CSS动画没这么多,视频只有不到一个小时,我自己敲花了一天,遇到看不懂的地方就要去查相应的用法。
首先是这个页面的HTML部分, 一开始引入了normalize.css,这个是从cdnjs上引入的( 一个好用的js在线库,比较快),就是重置CSS 用的,个人感觉跟初始化CSS代码作用类似。normalize有一些优点:保留有用的默认值,规范化各种元素的样式。更正错误和常见的浏览器不一致,通过细微的修改提高可用性等等。这个我没有下载,可能会加载一小会。
<!-- 引入normalize css --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css">
然后这里有个图标
<!-- 引用 favicon图标 -->
<link rel="shortcut icon" href="images/bitbug_favicon(2).ico">
这个图标也是我特意去网上查怎么修改的。
主体部分就是一个注册的表单,一个登陆的表单,一个用于切换的浮层。最后背景就是从unsplash嫖来的壁纸,多个背景会自动切换。
这个页面主要就是动画比较流畅自然。
第一次加载可能需要一点点时间
登陆注册页面功能
- 根据权限的不同,自动登录到前台或者后台
- 可以注册用户,学号是唯一的,这里本来想实现发送邮件的功能,可惜我在测试的时候邮件经常发送失败,就删去了这个功能
- 因为时间原因这里的注册没有检查学号的重复和确认密码,我在后台的创建用户利用Ajax和Jquery的一个插件实现了表单提交检验包括学号的重复问题
前台页面
这个页面是我自己纯手写的,有三个CSS文件,没有用到框架。
整体感觉比较大气美观的(把两个Test小组删掉就更好看了)
最上面的nav一栏,主要显示了用户名,首页和退出的按钮,两个Servlet搞定。考虑到大作业说是在实验室官网的背景下,那么官网肯定有很多功能,这样nav就不会只有这一点内容了。
轮播图
接下来是一个网页的轮播图,这是我用JS纯手写的,这个的目的就是放一些热点话题或者新建了个小组用来宣传等等。
写了大概有160行代码,功能还是非常完善的,自动播放,手动切换,并且比较流畅,篇幅原因我就不放代码出来了。这张图片点击可以直接跳转到对应的话题。
小组分类模块
一开始进入首页没有选择分类那么,会加载所有小组的话题
选择了一个小组可以加载对应的话题
话题讨论模块
首先是创建话题,没有加入该小组的话是不能创建该小组下的话题的
同样没有选择小组也是不能创建话题的
确认是该小组的成员之后,点击就会出现一个文本域
刚创建好话题自然没有评论,就会显示暂无评论
侧边栏
右侧有一个固定的侧边栏
三个功能:
- 小组分类:直接跳转到小组分类进行选择
- 话题讨论:跳转到第一条话题讨论
- 加入小组:加入该小组
如果已经加入该小组弹出提示:
如何实现
上述功能基本都是用Ajax实现的
<%if(request.getSession().getAttribute("groupId")==null){
%>
<script>document.querySelector('.makeTopic').addEventListener('click',function (){alert('请选择小组');});var member=document.querySelector('.addMember');member.addEventListener('click',function (){alert('请选择小组');});</script>
<% }else{ %>
<script>var path1 = $("#path").val();var makeTopic=$(".makeTopic");var chosenGroup=document.querySelector('#groupId<%=request.getSession().getAttribute("groupId")%>');//改变选中小组的颜色chosenGroup.style.color='#0066ff';//创建话题 的提示function checkMember(obj){console.log('ajax------- checkMember ')$.ajax({type:"GET",url:path1+"/jsp/member.do",data:{method:"check"},dataType:"json",success:function(data){if(data.checkResult == "true"){document.getElementById('inputbox').style.display='block';}else if(data.checkResult == "false"){alert('请先加入该小组');}else if(data.checkResult == "noInfo"){alert('该小组可能不存在')}},error:function(data){alert('对不起,无法创建')}});}$(function(){makeTopic.on("click",function(){var obj = $(this);checkMember(obj);});});//加入小组的提示function addMember(obj){console.log('ajax------- addMember ')$.ajax({type:"GET",url:path1+"/jsp/member.do",data:{method:"add"},dataType:"json",success:function(data){if(data.addResult == "success"){alert('加入成功');}else if(data.addResult == "fail"){alert('Sorry,加入失败')}else if(data.addResult == "noInfo"){alert('该小组可能不存在')}else {alert('已加入该小组!')}},error:function(data){alert('对不起,加入失败')}});}$(function(){$(".addMember").on("click",function(){var obj = $(this);addMember(obj);});});
</script>
<% } %>
下面就是Servlet Service Dao 这三层
这里将一下我怎么写的这三层,之后篇幅原因就不讲了
首先Servlet
我doGet里面通过前端传入的method参数,去调用对应的函数
@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String method = request.getParameter("method");System.out.println("method:!!!!" + method);//调用什么方法if (method != null && method.equals("add")) {this.add(request, response);}else if(method!=null&&method.equals("query")){this.query(request,response);}else if(method!=null&&method.equals("number")){this.number(request,response);}else if(method!=null&&method.equals("deleteUser")){this.delete(request,response);}else if(method!=null&&method.equals("modify")){//通过用户id得到用户this.getUserById(request, response,"usermodify.jsp");}else if(method!=null&&method.equals("modifyFunc")){//this.modify(request,response);}}
例如这个add函数
//增加用户private void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("Servlet ===>添加用户中");//前端获取参数String email = req.getParameter("email");String userName = req.getParameter("userName");String userPassword = req.getParameter("userPassword");String rl=req.getParameter("roleSel");Integer role=0;if (rl == null|| rl=="") {role=0;}else {role = Integer.parseInt(rl);}String studentNumber= req.getParameter("studentNumber");String fromPage=req.getParameter("fromPage");System.out.println(email+userName+userPassword+role+studentNumber);//把这些值方进一个用户中User user = new User();user.setEmail(email);user.setUserName(userName);user.setPassword(userPassword);user.setRole(role);user.setStudentNumber(studentNumber);UserServiceImpl userService = new UserServiceImpl();Boolean flag = userService.add(user);if (fromPage.equals("index")){resp.sendRedirect("login.jsp");}else{resp.sendRedirect("backstage.jsp");}}
这样做能够实现代码的复用
然后Service层没什么好讲的,Dao层下面有个BaseDao
有JDBCUtils的功能,也封装了增删改查操作
//编写查询公共方法public static ResultSet execute(Connection connection,PreparedStatement preparedStatement, ResultSet resultSet ,String sql, Object[] params) throws SQLException {//预编译的sql,在后面直接执行就可以了preparedStatement = connection.prepareStatement(sql);for (int i = 0; i < params.length; i++) {//setObject,占位符从1开始,但是我们的数组是从0开始!preparedStatement.setObject(i+1,params[i]);}resultSet = preparedStatement.executeQuery();return resultSet;}//编写增删改公共方法public static int execute(Connection connection,PreparedStatement preparedStatement,String sql,Object[] params) throws SQLException {preparedStatement = connection.prepareStatement(sql);for (int i = 0; i < params.length; i++) {//setObject,占位符从1开始,但是我们的数组是从0开始!preparedStatement.setObject(i+1,params[i]);}int updateRows = preparedStatement.executeUpdate();return updateRows;}
难点:
以上功能的难点就是前端美化打磨的挺长时间,编写了大量的Servlet方法 Service和Dao许多小细节也是卡了挺久,现在做完之后是感觉对这些非常熟练,没有什么难点了,当时写的话还是花了不少时间的。想要实现这些功能Ajax必不可少,不然点击一个按钮整个页面就要刷新一次,Ajax实现了异步处理,局部刷新,配合上Jquery使用起来一开始比较困难,后来就熟练了
话题讨论页面
最上面的nav与首页一样
nav的下方就是话题的描述,发起人和创建评论按钮
创建评论
这里有一个富文本编辑器 KindEditor插件,本来想自己写的,后来发现实现这个比较难,而且需要花很长时间,于是我就找了一个插件,也是琢磨了好久才成功使用。
这个编辑器可以上传图片链接,修改文字格式等等,上传文件与图片实现不了,因为我感觉上传文件这个功能对我现在个人来说比较麻烦,会出现许多问题,虽然我学了也写了demo。
上传图片链接
多级评论模块
在创建评论之后,用户可以在评论下方回复这个评论,如上图所示,有一个发布评论的文本框。
这里我好想理解错了多级评论的意思,我这里好像不能对二级评论进行回复,没看清要求大意了。但是原理是一样的利用comment表的pid(上级评论id)然后递归实现。这里我来不及实现了,最后一天了(系统还崩了),开学前我会慢慢完善的。
这里的文本框也是有些细节的:
他的长度会根据内容来改变,要知道textarea是没有这个功能的,利用js才能实现
所以说我这个大作业细节还是比较多的,因为一些大网站(CSDN 知乎)也有这些功能,我尽量去模仿。
这样前台差不多就展示完毕了,接下来是后台的展示。
后台主页面
这个后台页面也是受狂神视频里的那个项目启发来写的,我上面的代码结构也是狂神说JavaWeb的风格,应该是比较规范的。
用户管理
先看最重要的用户管理
用户查询
点击查询可以进行模糊查询
之后的每个管理模块都有这个功能就不去一个一个去展示了。
用户添加
从红字可以看到,即时的表单校验,不用去刷新页面。这是通过JQuery的一个插件 Validate 。有一个检查学号重复的功能(系统通过学号登陆)是通过Ajax配合Validate 的remote实现的
$().ready(function() {
// 在键盘按下并释放及提交后验证提交表单$("#addUserFrom").validate({rules: {userName: {required: true,minlength: 3},studentNumber: {required: true,remote:{url: path+"/jsp/user.do",//后台处理程序type: "GET", //数据发送方式data: { //要传递的数据studentNumber: function() {return $("#studentNumber").val();},method:"number"}}},userPassword: {required: true,minlength: 6},confirm_password: {required: true,minlength: 6,equalTo: "#userPassword"},email: {email: true},roleSel:{required:true}},messages: {userName: {required: "请输入用户名",minlength: "用户名最少由三个字符组成"},studentNumber: {required: "请输入学号",remote: "该学号已创建帐号!"},password: {required: "请输入密码",minlength: "密码长度不能小于 6 个字符"},confirm_password: {required: "请输入密码",minlength: "密码长度不能小于 6 个字母",equalTo: "两次密码输入不一致"},email: "请输入一个正确的邮箱",roleSel:{required:"请选择权限"},},submitHandler: function(form) {form.submit();}});
});
这个JQuery插件,非常的好用,也能配合上Ajax实现远程校验,如何使用我就不说了,看代码也能差不多看懂。
修改用户
跟添加用户差不多,只不过调用的servlet等等不同
删除用户
通过JS Ajax 实现的删除
点击弹出确认框,确认之后就会直接删除,并且页面不用进行刷新
function deleteUser(obj){$.ajax({type:"GET",url:path+"/jsp/user.do",data:{method:"deleteUser",userid:obj.attr("userid")},dataType:"json",success:function(data) {if (data.delResult == "true") {//删除成功:移除删除行obj.parents("tr").remove();} else if (data.delResult == "false") {//删除失败alert('删除失败')}},error:function(data){alert('删除失败')}});
}$(function(){$(".deleteUser").on("click",function(){var obj = $(this);if(confirm('是否确认删除?')){deleteUser(obj);}});$(".modifyUser").on("click",function(){var obj = $(this);window.location.href=path+"/jsp/user.do?method=modify&uid="+ obj.attr("userid");});});
其他管理模块
由于其他管理模块功能类似,我就不细说了直接贴图。功能类似,但是代码还是要一遍一遍的写,我写的时候都快不想写了。
值得讲的一个地方就是 删除模块,小组 >> 话题>>评论
所以删除小组的时候 会把小组对应的话题删掉,也会把话题对应的评论删掉 由于三大范式 评论表中没有 小组的ID 所以这个实现比较麻烦
同理 删除话题也会删除话题对应的评论
private void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("Servlet ===>删除小组中");String groupId= req.getParameter("groupid");System.out.println(groupId);Integer id=Integer.parseInt(groupId);GroupService groupService=new GroupServiceImpl();TopicService topicService=new TopicServiceImpl();CommentService commentService=new CommentServiceImpl();Boolean flag=groupService.delete(id);//获取小组下的话题列表List<Topic> topicList=topicService.getTopicList(id,"");System.out.println(topicList);//删除话题for(Topic topic:topicList){commentService.delete(0,topic.getId());}//删除小组下的话题Boolean flag2=topicService.delete(0,id);HashMap<String, String> resultMap = new HashMap<String, String>();if(flag==true){resultMap.put("delResult","true");}else{resultMap.put("delResult","false");}resp.setContentType("application/json");PrintWriter outPrintWriter = resp.getWriter();outPrintWriter.write(JSONArray.toJSONString(resultMap));outPrintWriter.flush();outPrintWriter.close();}
小组管理:
小组按照要求前台可以创建,但是我觉得这个小组是比较固定的,而且也是比较重要的,就像我们这个集训的赛道一样,所以我前台没有放创建小组的窗口,只有管理员在后台才能增删改查
话题管理
话题和评论只能删除和查询 ,按理来说这两个也只能删除和查询
总结
这次大作业也是花了很长时间,在集训的时候我因为提前组好了队伍出去社会实践了(考勤分估计没了),花了十几天的时间,只参加了前两天和最后几天的集训,少了很多时间学习。虽然之前自学过Java mysql 前端等等 但不怎么使用也差不多遗忘了。所以这20天我一遍学习一遍写项目,还是很充实的一个暑假,之后我也会慢慢完善我的第一个项目。