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;
}
@Override
public 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方法里也有一个异常
//排序
@Override
public 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参数,去调用对应的函数
@Override
protected 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天我一遍学习一遍写项目,还是很充实的一个暑假,之后我也会慢慢完善我的第一个项目。