一、需求分析
本项目实现的功能是一个网上二手车交易平台,主要围绕二手车的交易进行开发。
在该系统,分为两个角色,一个是普通用户角色,即会员,另一个角色是管理员用户,两个角色分别有不同的权限。具体如下:
普通用户(会员):
- 注册、登录
- 个人信息的查询
- 个人信息及密码修改
- 二手车的添加、修改、发布、删除
- 查询并维护自己的出售信息并支持留言、同时支持对其他人的留言进行回复
- 查看平台所有二手车信息并进行留言
- 支持禁止他人对自己发布的车辆留言
管理员用户:
- 登录
- 个人信息及密码的修改
- 车辆管理:能查看平台所有车辆,并进行留言操作、屏蔽操作
- 会员管理:支持修改、删除会员的信息
- 查询所有会员信息(支持模糊查询)
- 可取消会员资格(使之可正常登录但无法浏览网站信息)
二、使用的技术
本系统是基于 MVC 模式搭建的,使用的技术包括
Servlet
JSP
JDBC
EL 表达式
JSTL
Filter 过滤器
等
三、系统的结构层次
本系统的代码结构逻辑清晰,具体的实现分为Web层(表示层)、业务层、数据访问层,利用MVC架构将JSP、Servlet、JavaBean分层操作并联系起来,形成一种弱耦合关系,在后期对系统的维护上少了很多麻烦,例如要增加一个功能,只需在Servlet进行逻辑的控制,并调用Service业务服务层的代码,业务层就去调用数据访问层的具体逻辑实现,完成对数据库项的增加操作,实现数据的持久化,在Servlet控制层里,只需完成2件事,一件是调用业务逻辑层的实现方法,另一件事是将相关属性放在request中,并转发给其他JSP页面去做显示或输入处理。代码逻辑条理十分清晰,具体的代码结构如下:
不同层次的实现放在不同的 package 中,将代码模块化,方便维护。
其中:
com.bean:主要放实体类
com.controller:主要放Servlet
com.dao:主要放接口
com.daoImpl:主要放接口的实现类
com.filter:主要放filter过滤器
com.service:主要是业务逻辑的实现
com.utils:主要放工具类(对数据库的连接和资源释放)
MVC 模式的逻辑图如下:
其中,JSP页面即MVC模式中的V(view),Servlet为MVC中的C(Controller),然后Service、Dao、MySql组成MVC中的M(model),每个层次每个模块的功能如下:
1. JSP:
(1)呈现数据:从request中获取Servlet放入的属性
(2)接收用户的输入
(3)编写前端页面显示代码给出对于的提示,参与用户交互
2. Servlet:
(1)获取请求信息:获取请求参数
(2)验证请求参数的合法性:验证失败需要返回页面,并给出提示
(3)把请求参数封装为一个JavaBean
(4)调用业务逻辑方法获取返回的结果
(5)把返回的结果放入request中
(6)响应页面:转发、重定向
3. Service:
(1)实现业务逻辑
(2)返回结果
4. Dao:
(1)执行增删改查操作(CRUD)
(2)返回结果
5. MySql:
(1)存储数据,对数据进行持久化操作
四、系统部分功能和逻辑分析
1. MySQL 数据库
该系统包含两个数据表,一个是 user 表,一个是 car 表。它们分别包含的属性如下
(1)user表
id:用户 id 号(主键)
name:用户登录名称
pwd:用户登录密码
limit:该用户的受限情况(int)
isadmin:该用户是否是管理员(int)
(2)car表
userid:用户 id 号(主键,同时是 user 表的 userid 属性的外键)
carid:二手车 id 号
brand:车辆品牌
model:车辆型号
price:车辆价格
color:车辆颜色
dateposted:发布日期(timestamp)
isrelease:发布状态(int)
canmessage:是否支持用户留言(int)
carcomment:留言板(text)
isfake:是否是虚假消息(该属性的操作属于管理员权限)(int)
2. Servlet 控制器部分
其中控制器,即 MVC 模式中的 C(Controller),我通过使用通配符,对Servlet进行配置,然后在doGet()方法利用反射机制分辨从JSP传过来的请求,并映射到相应的Servlet中。通过使用这一方法,可以将同一类型的Servlet放在一个.java文件中,不需要每个servlet写一个java文件进行配置。
其中
CarServlet 配置为:@WebServlet(".car")
UserServlet配置为:@WebServlet(".do")
通过.do或.car将车辆管理和用户管理这两个业务分离开,这样处理业务就变得非常方便,这一做法,可以让在编码过程中思路逻辑变得更加的清晰。而且采取上面所说的结构,在编码过程中遇到报错能更快的定位到bug,并进行更正。
3. JSP 视图部分
系统采取的JSP显示界面也进行了分类管理(结构如下图),使编程思路更加清晰。
这里关键的一点是登录页面,即首页,要放在WebContent下面,不能放在WEB-INF下面,因为WEB-INF属于保护目录,不能直接被访问,本项目的登录页面 login.jsp 放在 WebContent 下,并在 web.xml 配置为首页,如下图所示:
如果需要访问 WEB-INF 下的 jsp 文件,需要通过servlet转发,才能实现访问操作。这同时也能保证系统的安全性,保证用户权限得到有效的管理。
同时为保证系统代码结构各个层次各司其职,在JSP页面没有多余的Java代码,业务逻辑执行操作都在其他层次完成。
本系统在界面的显示上,虽然没有加入太多的样式使界面更加高级,但基本保证了界面的可读性和用户交互性。
4. Filter过滤器
Filter是一个实现了Filter接口的Java类,与Servlet相似,它由Servlet容器进行调用和执行,Filter需要在web.xml文件中进行注册和设置它所能拦截的资源。在注册之后,该Filter就可以对Servlet容器发送给Servlet程序的请求Servlet程序回送给Servlet容器的响应进行拦截,可以决定是否将请求继续传递给Servlet程序,以及对请求和响应信息是否进行修改。
该系统利用了Filter过滤器对Servlet容器调用Servlet的过程进行拦截,进行编码的转换,从而在Servlet进行响应处理的前后保证了编码的统一,统一为utf-8格式,如下图:
同时需要在web.xml进行配置,如下图:
5. EL表达式 和JSTL
在JSP页面获取request中的数据时并没有出现JAVA代码,而是采用EL表达式获取值,实现0 Java代码。让JSP页面的功能更加纯粹。利用点访问符访问对象的属性。
本系统采用JSTL与EL表达式结合使用,其中JSTL需要导入相应的jar包,并在JSP页面导入标签库
<%@ taglib uri=“http://java.sun.com/jsp/jstl/core” prefix=“c”%>
本系统使用到的主要有 c:if、c:foreach 标签。
6. 弱耦合逻辑结构关联方式
其中的层层调用,除了类对象调用方法实现层层调用外,还有就是通过转发将数据放在request域内,在jsp页面可以通过requestScope去取到数据,在Servlet也可以接收jsp页面传过来的数据,通过request.getParameter( )可以获取。
7. 接口设计
该系统还有一个比较好的一点就是在对业务逻辑进行处理时,利用接口,然后再在接口的实现类进行操作。面向接口编程可以增加复用性和通用性。
其中
Dao和DaoImpl分别是通用接口和通用接口的实现类,
CarDao和CarDaoImpl是二手车车辆管理的接口和实现类
UserDao和UserDaoImpl是用户管理(包括管理员及普通用户)的接口和实现类
为了使接口更具通用性,这里采用了“泛型”,代码如下图:
8. 数据库操作和JDBC
使用JDBC可以直接访问数据库,JDBC是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,使用这个类库可以以一种标准的方法、方便地访问数据库资源。JDBC为访问不同的数据库提供了一种统一的途径,方便开发(本系统采用的是MySql数据库)。
JDBC API 是一系列的接口,使应用程序能够进行数据库连接,执行SQL语句,并且得到返回结果。
基本的流程是:
(1)在Driver Manager中注册
(2)获取连接(Connection)
(3)调用相应的方法做增删改查
(4)获取处理结果数据
下面对这几个基本过程进行解析:
<1>注册
调用Class类的静态方法forName(),向其传递要加载的JDBC驱动的类名,代码如下:
String driverClass="com.mysql.cj.jdbc.Driver";
Class.forName(driverClass);
<2>建立连接
调用DriverManager类的getConnection()方法建立到数据库的连接。JDBC URL标识一个被注册的驱动程序,从而建立到数据库的连接。通俗来说,就是JDBC URL说明要连接数据库管理系统中的哪个数据库。代码如下:
String jdbcUrl="jdbc:mysql://localhost:3306/SecondCarTrade?serverTimezone=Hongkong";
Connection connection=DriverManager.getConnection(jdbcUrl, user, password);
<3>调用方法实现数据库永久化操作
调用数据库有3种方式,分别为:
Statement
PreparedStatement
CallableStatement
该系统使用的是PreparedStatement。
因为考虑到如果使用Statement的话,获取sql语句是通过字符串拼接的方式,所以就必须得考虑到它的拼写规则,并且得时刻注意不能和关键字和特殊字符进行冲突,这对编码来说不方便,且容易出错。
而PreparedStatement优势就比较凸显,使用PreparedStatement可以进行预编译,提高性能,同时还可以防止SQL注入,对sql语句也不需要进行字符串拼接,直接写sql语句就可以调用执行方法操作数据库。对于语句中的属性值,可以通过“?”作为占位符,可以传入参数。
以添加用户来举例,参数传递过来user对象,在方法内部获取user对象的所需的相应属性,通过问号占位符填入sql语句,然后调用update方法操作数据库,代码结构如下:
<4>获取返回数据
将对数据库操作的结果通过List或者ResultSet的方式返回,以“获取所有用户所有车辆”方法举例,代码如下:
五、代码解析和功能演示
下面对具体的逻辑结构和具体实现代码进行解析,JSP页面的显示代码就不一一演示了,具体查看源代码去看就行了,注释都写得很清晰。
首先从登录页面 login.jsp 启动,登录时会根据用户类型和账号密码进行身份验证:
//登录
public void LoginUserServlet(HttpServletRequest request, HttpServletResponse response) throws Exception {
//得到jsp页面传过来的参数
//(因为在同个request域中,所以可以用request获取其数据)
String name=request.getParameter("name");
String pwd=request.getParameter("pwd");
int userType=Integer.parseInt(request.getParameter("userType"));// 会员0 /管理员1
System.out.println("userType="+userType);
/*这里id不能直接request.getParameter获取,因为在用户登录的时候没有填id,所以jsp页面没有传"id"信息过来,这里获取不到的*/
int id=service.LoginFindId(name, pwd);//根据name和pwd查找id
UserDao ud =new UserDaoImpl();//创建接口的实现类,ud对象可以操作UserDaoImpl类内容
System.out.println("id="+id);
//service.LimitUser(id)执行成功,返回true,则该用户变为受限
//service.isLimit(id)返回true,则该用户变为受限
//userType==0 会员
if(service.login(name, pwd) && (!service.isLimit(id)) && (userType==0) && !service.isAdmin(id)) {
//如果登录通过,并且不受限
//request.setAttribute("xiaoxi", "welcom "+name);//向request域中放置信息
System.out.println("(会员)登录成功");
String message="个人会员 登录成功";
//注:个人用户登录成功,将他的id放在request域中,在jsp页面可以获取,然后通过jsp可以再把该id信息转到其他servlet中(比如下面的SearchUserSelfById() )
request.setAttribute("id", id);//设置id到request域,在每个个人用户登录后分别显示他个人的信息(通过这个id去获取)
request.setAttribute("message", message);
//转发到个人用户主页UserHomePage.jsp
request.getRequestDispatcher("/WEB-INF/user/UserHomePage.jsp").forward(request, response);//转发到成功页面
}
//userType==1 管理员
else if(service.login(name, pwd) && (!service.isLimit(id)) && (userType==1) && service.isAdmin(id)) {
//如果登录通过,并且不受限
//request.setAttribute("xiaoxi", "welcom "+name);//向request域中放置信息
System.out.println("(管理员)登录成功");
String message="管理员登录成功";
request.setAttribute("message", message);
request.getRequestDispatcher("/WEB-INF/admin/AdminHomePage.jsp").forward(request, response);//转发到成功页面
}
//个人用户(受限用户)
else if(service.login(name, pwd) && service.isLimit(id) && (userType==0) && !service.isAdmin(id)){
//登录成功,但受限
System.out.println("登录成功,但您的会员资格受限,无法访问汽车信息");
String message="登录成功,但您的会员资格受限,无法访问汽车信息";
request.setAttribute("ERRMESSAGE", message);
request.getRequestDispatcher("/WEB-INF/admin/LimitedUser.jsp").forward(request, response);//转发到成功页面
}
//共用
else {
//登录不通过,重定向到失败界面(respnse 响应)
System.out.println("登录失败,请确认账号密码以及身份是否正确");
String message="登录失败,请确认账号密码以及身份是否正确";
request.setAttribute("message", message);
//response.sendRedirect("/WEB-INF/view/Fail.jsp"); 重定向不在一个request里面
request.getRequestDispatcher("/WEB-INF/view/LoginFail.jsp").forward(request, response);//转发到失败页面
}
}
登录成功会跳转到个人用户主页,如下图:
下面演示”显示个人信息“:
/*根据个人id查询个人信息(个人用户)*/
public void SearchUserSelfById(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id=Integer.parseInt(request.getParameter("id"));//从jsp页面传过来,用getParameter
request.setAttribute("id", id);//把id放在request域中,然后在ShowUserInfoSelf可以获取到,显示用
User user=service.getUser(id);
/*转发提交到request,到相应jsp页面显示*/
request.setAttribute("user", user); /*将取出来的user对象保存在request域里面*/
String message="个人信息查询成功";
request.setAttribute("message", message);
request.getRequestDispatcher("/WEB-INF/user/ShowUserInfoSelf.jsp").forward(request, response);
}
我们可以对个人信息进行更新:
//个人用户修改信息触发界面(跳转到修改的界面)
public void editUserSelfInfo(HttpServletRequest request, HttpServletResponse response) throws Exception {
int id=Integer.parseInt(request.getParameter("id"));
User user=service.getUser(id);
/*转发提交到request,到相应jsp页面显示*/
request.setAttribute("user", user); /*将取出来的user对象保存在request域里面*/
request.getRequestDispatcher("/WEB-INF/update/UpdateUserInfoSelf.jsp").forward(request, response);//转发到jsp页面显示
}
修改完成后提交,完成数据库持久化操作,如下:
//个人用户信息修改成功后回到这里
public void UpdateUserInfoSelfServlet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/*获取从 Update.jsp 页面输入的信息(用户修改后的信息)*/
System.out.println("进入 UpdateUserInfoSelfServlet ");
int id=Integer.parseInt(request.getParameter("id"));//从jsp页面传过来,用getParameter
String name=request.getParameter("name");
String pwd=request.getParameter("pwd");
String sex=request.getParameter("sex");
System.out.println("name="+name+" pwd="+pwd+" sex="+sex);
User user=new User(id,name,pwd,sex,0,0);//User类必须有个带参构造方法(因为这是在个人信息修改的方法里,所以直接把limit和isAdmin默认0)
service.UpdateUserServlet(user);//Userservice的同名方法
System.out.println("update successfully");
String message="个人信息修改成功";
request.setAttribute("message", message);
//修改成功回到显示个人信息的界面
request.setAttribute("user",user);//传回ShowUserInfoSelf.jsp,不然那边无法显示更新后的信息
request.getRequestDispatcher("/WEB-INF/user/ShowUserInfoSelf.jsp").forward(request, response);
}
下面进行”二手车管理首页“进行二手车的相关操作:
//二手车辆管理首页
public void CarsManageHomePageServlet(HttpServletRequest request, HttpServletResponse response) throws Exception {
List<Car> cars=new ArrayList<Car>();
int userid=Integer.parseInt(request.getParameter("userid"));//这里是userid,不是id
User user = serviceOfUser.getUser(userid);//根据jsp传过来的用户id,定位到用户
cars=serviceOfCar.getCarsListOfUserid(userid);//根据userid获取到
//String message="-------二手车管理首页---------";
request.setAttribute("user", user);//放在request,到CarsManageHomePage.jsp可以取出来用
request.setAttribute("cars", cars);
request.setAttribute("userid", userid);//单独传userid过去,因为CarsManageHomePageServlet不仅连接user,又连接对car的一系列操作
//request.setAttribute("message", message);
request.getRequestDispatcher("/WEB-INF/car/CarsManageHomePage.jsp").forward(request, response);
}
来到了二手车管理首页
下面一一进行演示
添加二手车:
//添加车辆 触发界面(编辑界面)
public void AddCarPageServlet(HttpServletRequest request, HttpServletResponse response) throws Exception {
/*实现*/
int userid=Integer.parseInt(request.getParameter("userid"));//通过 " ?userid " 传过来了
System.out.println("AddCarServlet()---userid="+userid);
User user = serviceOfUser.getUser(userid);//根据jsp传过来的用户id,定位到用户
/*转发*/
String message="添加车辆页面";
request.setAttribute("user", user);//user放在这个request里
/*将消息message转到一个jsp页面success.jsp,在页面打印一个“成功”消息*/
request.setAttribute("message", message);
request.getRequestDispatcher("/WEB-INF/add/AddCarPage.jsp").forward(request, response);
}
在AddCarPage.jsp页面进行车辆信息的添加后,来到AddCarServlet
//添加车辆
//获取添加界面的信息
public void AddCarServlet(HttpServletRequest request, HttpServletResponse response) throws Exception {
/*实现*/
int userid=Integer.parseInt(request