javaweb开发实战——在线课程申请与审批系统
前期准备
学生选课,老师审批,要求数据库存储
需求分析
1.在线课程申请模块功能:
1.1学生登录功能
已有账号学生可以在线登录到申请系统中。
用户账户数据可预先存储到数据库中。
1.2在线申请功能
学生登录后,在可选课程列表中,选择要申请的课程。
每次申请只能选择一门课程,不可重复申请。
课程详细数据可预先存储到数据库中。
1.3已申请课程,审批进度查询功能
审批过程中的申请可以进行进度查询。
审批状态有:申请已提交、审批中、审批成功、申请驳回。
审批结束后,也就是审批状态为成功或者驳回,学生可以点击确认,完成本次申请。
1.4已申请课程审批记录查询功能
学生可查询本人已申请的全部记录,包括审批通过和驳回的记录。
2.在线审批模块功能:
2.1课程申请审批功能
已有教师角色账号的教师可登录审批功能。
如果已提交的申请不符合申请条件,可以驳回。
2.2已审批申请查询功能
教师可查询已审批的申请,查询结果可模糊查询。
3.额外功能:
系统后台:系统用户管理。
适应通用性,系统应采用MVC设计模式。
知识储备
JDBC:
是一种用于执行SQL语句的Java API,可以为多种关系型数据库提供统一访问,它是由一组用Java语言编写的类和接口组成的。
TOMCAT:
实现了 jsp/servlet 规范,是一个轻量级服务器,开源免费
MVC模型:
M(model,模型):通常用于封装数据模型(实体类)。
V(view,视图):通常用于展示数据。动态展示用 JSP 页面,静态数据展示用 HTML。JSP 擅长显示界面,不擅长处理程序逻辑。在 Web 开发中多用于展示动态界面。
C(controller,控制器):通常用于处理请求和响应。一般指的是 Servlet。Servlet 擅长处理业务逻辑,不擅长输出显示界面。在 Web 开发中多用于控制程序逻辑(流程)。
设计模式
编程实现
创建数据库
我这里是用Navicat Premium 16创建的webhome数据库,根据需求,简单创建了course、enroll、users三个表。表里的字段有啥要根据需求来,比如users是用户表,那用户名、密码必须得有;考虑到学生和老师都用自己的真实姓名,那就加上一个real_name;为了区分不同的用户,需要有role值来区分。当然这些数据都是varchar 255 ,整个int其实也行,就是转换起来麻烦多,不是很友好。
users表如下所示。
包含管理员、学生、老师三个角色,通过role值来区分。
这里的大长串u_id是随机生成的,我后面jsp中使用java自带的uuid工具类的randomUUID方法来生成。
course表包含了instructor,这个是课程主讲教师,而manager相当于教务处老师,主管课程教学的。但是后面我并没有将这两个属性的关系搞清楚,所以这个manager属性在这里是没用的。
enroll表是course和users表的关系集合,表示学生与课程申请审批关系。在这里有提交申请(submitted)、申请通过(approved)和申请驳回(refused)三种状态。
我在bean包里做了类型转换,目的是在界面上输出中文。
javabean封装数据
其实就是创建三个实体类:Course、Enrollment、User。这里我导入了lombok包,用注释@Data代表所有数据的getter和setter方法。@All@No大长串分别是全参构造方法和无参构造方法。简化了代码书写。
这些类的属性名可以跟数据库里的字段名不一样,但是得一一对应,自己清楚哪个属性对应哪个字段。
JDBC连接数据库
为了方便数据库操作,这里设计了Global类来专门驱动连接数据库,这个类有两个方法,分别是getConnnection和closeConnnection,用来连接和关闭数据库连接。
这其中String url = “jdbc:mysql://localhost:3306/webHome?characterEncoding=utf-8&serverTimezone=GMT%2B8”;代码中的webHome是我的数据库名称,你可以替换成自己的数据库名称。两个“root”是我的mysql用户名和密码,需要替换成自己的。
Dao接口与DaoImpl具体方法实现
以courseDao接口为例,这里列出了六个方法getAll和query是返回查询列表,justQuery是返回单一查询目标,add、update、del方法分别是增改删功能。
以courseDaoImpl类为例,courseDaoImpl类继承courseDao接口,在这里将需要写mysql语句来操作mysql数据库数据。
getAll方法:是通过调用query方法实现的,query方法的参数是sql语句"select * from course",这是因为要返回所有列表。
query方法:先建立Course类。在方法内部,创建了一个空的 ArrayList 对象 list,用于存储查询结果。使用 Global.getConnection() 获取数据库连接,并通过 createStatement() 方法创建一个 Statement 对象 statement,用于执行 SQL 语句。通过 executeQuery() 方法执行给定的 SQL 查询,将结果存储在 ResultSet 对象 resultSet 中。在循环中,通过调用 next() 方法,逐行遍历查询结果。对于每一行,根据 ResultSet 中的列索引,使用 getString() 方法获取具体的列值,并创建一个 Course 对象。将每个 Course 对象添加到先前创建的 list 中。循环结束后,关闭 resultSet 和 statement 对象,释放资源。调用 Global.closeConnection() 关闭数据库连接。如果在执行过程中遇到异常,使用 e.printStackTrace() 方法打印异常信息。最后返回存储结果的 list 对象。
add方法:它接收一个 Course 对象作为参数,代表要添加的课程信息。在方法内部,创建一个布尔类型的变量 flag,用于表示添加操作是否成功。创建一个 SQL 插入语句,将课程信息插入到名为 course 的表中。语句中的占位符 ? 表示待插入的值。使用 Global.getConnection() 获取数据库连接,并通过 prepareStatement() 方法创建一个 PreparedStatement 象 preparedStatement,用于执行带有参数的 SQL 语句。使用 setString() 方法为 preparedStatement 对象设置参数值。通过调用 course 对象的相应方法,获取要插入的课程属性值,例如课程ID、课程名称、讲师、主管和时间表,并将其传递给对应的索引位置。通过调用 execute() 方法执行 SQL 语句。将执行结果赋给 flag 变量,表示添加操作是否成功。关闭 preparedStatement 和数据库连接,释放资源。如果在执行过程中遇到异常,使用 e.printStackTrace() 方法打印异常信息。最后,返回 flag 变量,表示添加操作的结果。
update方法:它接收一个 Course 对象作为参数,代表要更新的课程信息。在方法内部,创建一个布尔类型的变量 flag,用于表示更新操作是否成功。创建一个 SQL 更新语句,通过 update 关键字指定要更新的表名为 course,并使用占位符 ? 表示待更新的值。使用 Global.getConnection() 获取数据库连接,并通过 prepareStatement() 方法创建一PreparedStatement 对象 preparedStatement,用于执行带有参数的 SQL 语句。使用 setString() 方法为preparedStatement 对象设置参数值。通过调用 course 对象的相应方法,获取要更新的课程属性值,例如课程名称、讲师、主管和时间表,并将其传递给对应的索引位置。通过调用 execute() 方法执行 SQL 语句。将执行结果赋给 flag 变量,表示更新操作是否成功。关闭 preparedStatement 和数据库连接,释放资源。如果在执行过程中遇到异常,使用 e.printStackTrace() 方法打印异常信息。最后,返回 flag 变量,表示更新操作的结果。
delete方法:它接收一个 Course 对象作为参数,代表要删除的课程。在方法内部,创建一个布尔类型的变量 flag,用于表示删除操作是否成功。创建一个 SQL 删除语句,通过 delete from 关键字指定要删除的表名为 course,并使用占位符 ? 表示待删除的条件。使用 Global.getConnection() 获取数据库连接,并通过 prepareStatement() 方法创建一个PreparedStatement 对象 preparedStatement,用于执行带有参数的 SQL 语句。使用 setString() 方法为 preparedStatement 对象设置参数值。通过调用 course 对象的 getUuid() 方法,获取要删除课程的唯一标识,并将其传递给占位符的位置。通过调用 execute() 方法执行 SQL 语句。将执行结果赋给 flag 变量,表示删除操作是否成功。关闭 preparedStatement 和数据库连接,释放资源。如果在执行过程中遇到异常,使用 e.printStackTrace() 方法打印异常信息。最后,返回 flag 变量,表示删除操作的结果。
Service接口和ServiceImpl具体方法实现
Dao接口的方法很通用,如果需要具体实现业务逻辑,还需要编写Service接口。
以CourseService接口为例,这里有queryAll,queryUnSelected,querySelected,queryByID,queryLikeField,add,edit,del共9种方法(其中queryByID参数不同)。为了丰富查询功能,我编写了查询所有列表、查询已选课的列表、查询未选课的列表、根据ID查询、模糊查询;而增删改方法类似。
以CourseServiceImpl方法的模糊查询queryLikeField为例,这里的参数是两个String,前一个是数据库的字段名,后一个是模糊查询值。
之后的add、edit、del方法也都是调用Dao接口方法。
在这里是采用单例设计模式。
以查询未选课的列表方法queryUnSelected为例,编写sql语句。
以UserService接口为例,相比于CourseService接口,多了一个verifyLogin方法。在UserServiceImpl类中编写verifyLogin函数体。首先是编写sql语句,通过query方法查到数据库中符合条件的User,并用列表储存。这里做了一个判断,如果列表为空,就创建一个新User,而这个新User是没有赋值的,后期判断UserID为null,就会返回用户名或密码错误;如果列表不为空,就返回查询列表第一个(get(0)即得到第一个)。
Servlet编写
AddCourseServlet、EditCourseServlet使用了doPost方法,把数据放到request body中传递参数。
delCourseServlet类采用doGet方法,获取课程ID并删除。
在用户业务逻辑控制器中,LoginServlet采用doPost方法,提交数据到服务器。再进入verifyLogin方法判断数据库中是否有该用户数据,如果有就重定向到main.jsp中。
JSP编写
login.jsp如下所示,采用img上传svg图片,form表单展示用户名和密码,并用input存储用户输入的值。
正确登录后我们进入main.jsp中。main.jsp中使用多个container容器装载课程、审批、用户界面,并按照权限展示。权限为2代表学生,系统只显示课程列表和申请记录两个容器;权限为1代表老师,能看到课程列表、课程审批、审批记录三个容器(其实申请记录和审批记录是一个容器,按需求来说这是不应该的。并且如果要展示审批流,这是有待改进的地方);权限为0代表管理员,能看到用户展示容器;以上三者均可以看到退出登录按钮。(用容器替代不同jsp,简化了一些,但真正的审批系统吞吐量大,不可以采用这种方式的)
以学生身份登录,用户名“zhangsan”,密码“zs”,看到课程列表和申请记录两个容器。
以老师身份登录,用户名“lisi”,密码“ls”,看到课程列表、课程审批和审批记录三个容器。
以管理员身份登录,用户名“admin”,密码“admin”,看到用户展示一个容器。
以用户信息容器为例,主要是table表格的运用。
TypeScript和CSS
以userlist.ts为例,del1、edit1、add1函数用来响应main.jsp的onclick操作。
以style1.css为例,这个是规定界面颜色和格式。
最后总结
我觉得这次做的不错的地方就是Global类将数据库连接和关闭整合,减少了代码冗余。并且在里面创建了很多静态变量,方便直接调用。然后就是service调用dao,终于有点层层调用的严密感了。针对本次系统编码实现,最主要的是与数据库的增删改查;但是前端的东西又不能放弃,否则就不是web界面了。但是前后端的配合需要下很大功夫,总体来说做成这样已经超出我的逾期了。慢慢加油吧!