Java EE - 02 做一个作业管理系统(jsp+servlet+layui)

JAVAEE-02 做一个作业管理系统(jsp+servlet+layui)

简介
github地址: 传送门
技术实现:servlet + jsp + layui + mysql , 之前学了下layui,所以顺便就用上了,还是蛮好用的哈哈。
主要功能:
   老师:登录,注册,添加自己的新学生、作业(只能面向自己的学生,也可以是部分学生),查看自己的学生、自己布置的作业以及学生作业的完成情况,删除学生、作业,更改学生信息,更改作业信息,登出
   学生:登录,查看作业、提交作业,登出

一、数据库设计

数据库一共有五张表,设计均满足第三范式要求:

  1. student(sid)   
  2. teacher(tid)  
  3. homework(hid)   
  4. student_homework(sid, hid)
  5. teach(sid, tid)

数据库连接类采用了单例模式,在第三部分代码设计会讲到。

以下是数据库关系模式图
在这里插入图片描述

二、业务流程

学生流程图

在这里插入图片描述

老师的流程图

在这里插入图片描述

三、代码设计

  1. 采用了前端cookie保持登录状态,当再次打开登录页面时,会进行登录状态的判断,若已登录且未退出,则会自动进行登录,登录成功与登出成功的时候则会改变cookie中的登录状态。
  2. 添加了拦截器设置,用来检验登录状态,未登录时不能访问只有登录后才能访问的页面,除了index.jsp(欢迎页面,在这个页面里我直接跳转到了登录页面)、login.jsp、register.jsp registerSuccess.jsp、/studentLogin、/teacherLogin(需要登录验证的几个)页面外,属于老师的设置了拦截器TeacherLoginFliter,属于学生的设置了StudentLoginFilter。
    代码比较多,我在这贴一些主要的代码就行,剩下的我就不一一赘述,大家可以在我的的github去看  仓库地址
    下面是StudentLoginFilter,TeacherLoginFilter也是类似的,在这就不贴了
    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
    public class StudentLoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    servletRequest.setCharacterEncoding("utf-8"); //防止中文乱码
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse) servletResponse;
    Cookie[] cookies = request.getCookies();
    String loginStatus = "";
    for (Cookie c : cookies) {
    if ("loginStatus".equals(c.getName())) {
    loginStatus = c.getValue();
    if ("student_true".equals(loginStatus)) {
    filterChain.doFilter(servletRequest, servletResponse);
    return;
    }
    break;
    }
    }
    response.sendRedirect("login.jsp");
    }

    @Override
    public void destroy() {

    }
    }

web.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<filter>
<filter-name>StudentLoginFilter</filter-name>
<filter-class>com.kle.code.filter.StudentLoginFilter</filter-class>
</filter>

<!-- 配置多个映射,哪些请求执行过滤器 -->
<filter-mapping>
<filter-name>StudentLoginFilter</filter-name>
<url-pattern>/student/*</url-pattern>
</filter-mapping>

<filter>
<filter-name>TeacherLoginFilter</filter-name>
<filter-class>com.kle.code.filter.TeacherLoginFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>TeacherLoginFilter</filter-name>
<url-pattern>/teacher/*</url-pattern>
</filter-mapping>

</web-app>

login.jsp进行登录判断

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<%
request.setCharacterEncoding("utf-8"); //防止中文乱码
Cookie[] cookies = request.getCookies();
boolean studentLogged = false;
boolean teacherLogged = false;
String username = "";
String password = "";
if(cookies != null){
for (Cookie c : cookies) {
if ("loginStatus".equals(c.getName())) {
String loginStatus = c.getValue();
if ("student_true".equals(loginStatus)) {
response.sendRedirect("/student/studentHome");
} else if ("teacher_true".equals(loginStatus)) {
response.sendRedirect("/teacher/teacherHome");
}
break;
}
}
for (Cookie c : cookies) {
if ("loginStatus".equals(c.getName())) {
String loginStatus = c.getValue();
if ("student_false".equals(loginStatus)) {
studentLogged = true;
} else if ("teacher_false".equals(loginStatus)) {
teacherLogged = true;
}
break;
}
}
if(teacherLogged || studentLogged){
//输入框显示账号密码
for(Cookie c : cookies){
if("username".equals(c.getName())){
username = URLDecoder.decode(c.getValue(), "utf-8");
}
if("password".equals(c.getName())){
password = URLDecoder.decode(c.getValue(), "utf-8");
}
}
}
}
%>
//刚开始显示的是学生登录页面
<% if(!username.equals("") && !password.equals("") && studentLogged){ %>
<script>
document.getElementById("s_username").value = <%=username%>;
document.getElementById("s_password").value = <%=password%>;
</script>
<%}%>
</body>
<script src="static/layui/layui.all.js"></script>
<script>
layui.use('element', function(){
var element = layui.element;
//学生、老师登录页面切换监听
element.on('tab(demo)', function(data){
//切换到学生页面
if(data.index === 0){
<% if(!username.equals("") && !password.equals("") && studentLogged){ %>
document.getElementById("s_username").value = <%=username%>;
document.getElementById("s_password").value = <%=password%>;
<%}%>
}else {
//切换到老师页面
<% if(!username.equals("") && !password.equals("") && teacherLogged){ %>
document.getElementById("t_username").value = <%=username%>;
document.getElementById("t_password").value = <%=password%>;
<%}%>
}
<%}%>
});
});
</script>

数据库连接类

采用单例模式实现数据库连接资源获取类,在这我是用的是静态内部类的方式,利用了jvm的加载机制,当然也可以用DCL的方式实现,这两种都是比较推荐的实现方式。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class DbUtil {

private String url = "jdbc:mysql://127.0.0.1:3306/class";
private String allUrl = url + "?user=kle&password=yqyforever";

public Connection getConnection(){
Connection conn = null;
try {
String driverName = "com.mysql.cj.jdbc.Driver";
Class.forName(driverName);
conn = DriverManager.getConnection(allUrl);
}catch (Exception e) {
e.printStackTrace();
}
return conn;
}


/**
* 关闭数据库连接
* @param rs
* @param conn
* @param stat
*/
public void close(Connection conn,Statement stat, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
rs = null;
}
}
if(stat != null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
stat = null;
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
conn = null;
}
}
}

/**
* 关闭数据库连接
* @param conn
* @param stat
*/
public void close(Connection conn,Statement stat){
if(stat != null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
stat = null;
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
conn = null;
}
}
}
//静态内部类-单例模式 获取数据库资源连接类

private static class SingleHolder{
private static DbUtil INSTANCE = new DbUtil();
}

public static DbUtil getInstance(){
return SingleHolder.INSTANCE;
}

}

DbUtil的使用

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
public List<Homework> getHomeworkOfTeacher(String tid) {
String sqlString = "SELECT * FROM homework WHERE tid=" + tid;
List<Homework> list = new ArrayList<>();
//获取连接
Connection connection = DbUtil.getInstance().getConnection();
ResultSet resultSet = null;
try{
Statement statement = connection.createStatement();
resultSet = statement.executeQuery(sqlString);
while (resultSet.next()){
Homework h = new Homework();
h.setHid(resultSet.getInt("hid"));
h.setTitle(resultSet.getString("title"));
h.setContent(resultSet.getString("content"));
h.setCreateTime(resultSet.getTimestamp("create_time"));
h.setUpdateTime(resultSet.getTimestamp("update_time"));
list.add(h);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//关闭连接
DbUtil.getInstance().close(resultSet);
}
return list;
}

四、页面展示

注册、登录
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
老师:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
学生:
在这里插入图片描述
在这里插入图片描述

五、总结心得

1 . jar包的添加
不使用构建工具,这里以我使用的阿里的fastjson为例:
①File->Project Structure->Modules->Dependencies,添加jar包
在这里插入图片描述
②Artifacts, 右键点击WEB-INF,创建lib文件夹,这儿有个注意的点:创建完成后在IDEA里是看不到这个文件夹的,只有在out文件夹下才能找到,所以没有找到的画不用太担心,属于正常现象
在这里插入图片描述

③在右边项目里找到要添加的模块,右键点击Put into /WEB-INF/lib
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200312230431469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxNDQ2ODUy,size_16,color_FFFFFF,t_70)
④最后点击ok就大功告成了

2 . WEB-INF属于安全目录,其下的文件是不能直接通过地址栏访问的

以下是几种访问WEB-INF下文件的方式  

方法一:本来WEB-INF中的jsp就是无法通过地址栏访问的,所以安全。
如果说你要访问这个文件夹中的jsp文件需要在项目的web.xml文件中去配置servlet格式差不多的配置就ok了
方法二
<jsp:forward page = “/WEB-INF/jsp/test/test.jsp” />
方法三
request.getRequestDispatcher(“/WEB-INF/a.jsp”).forward(request, response);

3 . 关于数据库的单例模式
我采用的是静态内部类,利用了jvm的类加载机制,当然推荐的还有DCL实现,当然还有其他的实现方式,不过大都有点缺陷,不是特别推荐使用。
使用单例模式的原因:
数据库链接用单例模式的原因:
1,单例只保留一个对象,可以减少系统资源开销。
2,提高创建速度,每次都获取已经存在的对象因此提高创建速度全局共享对象。
3,单例在系统中只存在一个对象实例,因此任何地方使用此对象都是一个对象避免多实例创建使用时产生的逻辑错误。

这儿给大家推荐一篇博客,讲了单例模式的各种实现方式以及优缺点,大家可以看一下,讲的还是挺拔不错的,比较简洁
单例模式实现博客链接
静态内部类前面写过了,这儿说一下DCL,写的时候一定要在INSTANCE前加上volatile,避免jvm编译器的指令重排会导致并发问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

4 .关于jsp接收到servlet返回数据的中文乱码

对请求以及响应进行编码设置即可

1
2
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");

5 .Servlet向前端返回json等数据

1
2
3
4
5
6
String jsonString = "[]"; 
//获取response的Writer对象,用来写入返回数据
PrintWriter out = resp.getWriter();
out.write(jsonString);
out.flush();
out.close();

6 .之前在我的电脑发现了一个现象,我的电脑似乎不用导入mysql-connector的jar包就可以直接连接mysql,当时就感觉比较奇怪,最后花了一番功夫找到了原因,我在我的C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext目录下找到了这个jar包,原来我并不是没有用这个包,只是我的jar包放在了jre的ext扩展目录,当使用jdk时,他也会被加载。
在这里插入图片描述
这儿在网上找了一张java类加载器的加载顺序图。
在这里插入图片描述
7 .使用Maven后遇到了一个错误
在这里插入图片描述
这是因为idea将项目改为maven项目后,发现jdk默认1.5版本,网上有两种办法解决:
①按下面两张图修改
在这里插入图片描述
在这里插入图片描述
②在pom.xml中添加以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

第一种亲测可行,第二种博主尚未尝试。。。

7 .最后还有一个问题,就是IDEA控制台一直中文乱码,尝试了各种方法都不行,暂且先记下,等以后找到解决方法再说。
在这里插入图片描述
————————————————
补充,上面的控制台中文乱码找到了一种解决方法:
到Tomcat根目录下“conf”子目录下的“logging.properties”文件中查看日志记录的配置,找到该文件,可发现很多的“encoding = UTF-8”配置项,将所有的“encoding = UTF-8”都修改为“encoding = GBK”,使其识别支持中文,保存文件修改后,再次启动Tomcat,中文乱码就不见了。
在这里插入图片描述

———————————————————————————————

六、代码重构

一、改为Maven项目
由于之前的JavaEE 02项目(也就是学生作业管理系统)已经被我变成Maven了,不太好截图,就随便建了一个项目重新操作了下,顺便截了图
①右键点击项目名称,点击Add Framework
在这里插入图片描述
②选择Maven,点击OK就可以了在这里插入图片描述
③此外,使用Maven还遇到了一个问题,在上面的总结心得中的第7条列了出来并找到了解决方法。

④在pom.xml中导入jar包配置联网下载完毕后即可使用jar包
我只是用了mysql-connector和fastjson两个jar包,配置如下文件等待自动下载完毕后就可以使用了。
在这里插入图片描述

二、关于数据库优化
前面的代码里有提到,具体使用了一个静态内部类的单例模式通过getInstance方法去获得获取数据库资源的类实例,然后封装了一个getConnection方法和两个关闭连接的方法。具体代码在第三部分代码设计中的数据库连接类中,代码我就不重复帖了,回过头看一下就可以喽。

最后:github地址: 传送门