当前位置:首页 » 《资源分享》 » 正文

javaweb项目(基于三层架构+jsp+servlet+jQuery)web资源库_努力是为了站在万人中央,成为别人的光

14 人参与  2022年01月08日 13:42  分类 : 《资源分享》  评论

点击全文阅读


文章目录

  • 项目需求分析
  • 概要设计
  • 三层架构搭建项目结构
    • 三层架构简介
    • 后端注册功能实现
      • eclipse配置
      • 创建web项目
      • 用户表设计与创建数据库和表
      • 创建用户实体类
      • 控制层(controller)实现
      • 业务逻辑层(service)实现
      • 数据访问层(dao)实现
      • 使用jdbc工具类和导入mysql驱动包
    • 前端注册功能实现
      • Bootstrap框架下载与使用
      • 前端页面结构
      • 前端页面表单验证
    • 注册功能综合测试

项目需求分析

(1)用户注册与登录模块:未注册用户可以通过注册用户信息,登录到资源交流平台,达到相互交流学习的目的。已有账号密码的学生或老师可以直接登录课程资源库系统,进行资源浏览。

  • 学生用户成功登陆Web资源库后可以上传学生资源,收藏和下载系统内所有资源,发布学生帖子和评论所有帖子,关注其他用户。
  • 教师用户成功登陆Web资源库后可以上传教师资源,收藏和下载系统内所有资源,发布教师帖子和评论所有帖子,关注其他用户。
  • 管理员用户具有用户信息管理、学生信息批量导入、资源管理、交流区管理的权限。

(2)资源浏览与下载模块:用户可以直接在web资源的首页查看到所有发布的文件和图片,对于共享的资源文件可以直接下载保存。
(3)资源上传模块:用户可以直接上传资源文件,上传文件时需要录入资源的名字,类别,等级和描述。
(4)学习推荐模块:随着Web资源库的内容增加,用户需要从大量资源文件中获取自己想要的资源。学习推荐模块,是对用户实施的一个推荐功能。用户在推荐的目录文件中也可以选择关注自己感兴趣的用户,一起参与讨论。
(5)用户交流区模块:在论坛中,学生用户可以直接发贴提问,回复和评论所有帖子,教师用户可以发帖回答问题,回复和评论所有帖子,帮助学生解决学习上的疑惑。
(6)后台管理模块:管理员进行Web资源库的后台管理。管理员可以在此模块管理用户账号,进行增删查改的操作,并且批量导入学生信息。同时也要讲系统内的资源构件分门别类,删除出错的构件,将优质资源标“优”,将优质的资源摘录到Web资源库首页并且置顶,将交流区的优质回复摘录到Web资源库首页置顶的用户评论区,对交流区内违规操作的用户进行禁言处理。

概要设计

经过需求分析后,按照系统实现的功能进行模块划分,将相关功能的实现划分到同一模块中。
(1)按照管理系统的要求将模块划分为六大部分,用户注册与登录模块,资源浏览与下载模块,资源上传模块和推荐学习模块,用户交流区模块,后台管理模块。
在这里插入图片描述

(2)将六个模块细化

用户注册与登录-注册注册成为资源库的一个用户,可以共享web资源内容
用户注册与登录-登录学生用户、教师用户和管理员用户登录账号,分别拥有不同的权限
后台管理-用户管理对用户信息进行管理,批量导入学生,增删查改
后台管理-资源管理对资源信息进行管理,删除出错的构件,将优质资源标“优”
后台管理-论坛管理对发布的帖子进行管理,对交流区内违规操作的用户进行禁言处理
资源浏览与下载-资源中心包含所有用户已经上传的资源信息,可以让用户浏览
资源浏览与下载-资源下载将感兴趣的内容进行资源下载保存到本地
资源上传-上传教师可以上传教师资源文件,学生可以上传个人文件
推荐学习-资源置顶推荐系统将推荐的资源置顶,让用户方便搜索到想要的内容
用户交流区-论坛帖子汇总了所有学生和教师的帖子,方便大家在论坛内展开讨论
用户交流区-我要发帖学生和教师可以发布个人的帖子,评论他人的帖子,关注其他用户

三层架构搭建项目结构

三层架构简介

首先来说,三层架构与MVC的目标一致:都是为了解耦和、提高代码复用。MVC是一种设计模式,而三层架构是一种软件架构。

三层架构分为:控制层(controller层即servlet)、业务逻辑层(service层)、数据访问层(dao层) ,再加上实体类(Model)和表现层(web层即jsp)

实体类(Model)

在Java中,往往将其称为Entity实体类。数据库中用于存放数据,而我们通常选择会用一个专门的类来抽象出数据表的结构,类的属性就一对一的对应这表的属性。

Model实体类需要被dao层,service层和web层引用。

数据访问层(dao)

主要是存放对数据类的访问,即对数据库的添加、删除、修改、更新等基本操作

dao就是根据业务需求,构造SQL语句,构造参数,调用帮助类,获取结果,dao层被service层调用

业务逻辑层(service)

service层好比是桥梁,将web表示层与dao数据访问层之间联系起来。所要负责的,就是处理涉及业务逻辑相关的问题,比如在调用访问数据库之前,先处理数据、判断数据。

后端注册功能实现

eclipse配置

双击eclipse,选择自己的工作空间
在这里插入图片描述

设置工作空间的字符编码

在这里插入图片描述
在这里插入图片描述

设置JSP的字符编码

在这里插入图片描述

配置tomcat服务器

在这里插入图片描述
在这里插入图片描述

创建web项目

按Ctrl+N输入dynamic web project

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

用户表设计与创建数据库和表

用户表设计

学生,教师都有个人信息(姓名,性别,年龄,生日),但教师不包含班级字段,但它们都有账号信息以及身份标识。管理员只有账号信息,身份标识。

注册时一般只需提供账号,密码,身份标识,如果是学生还需要提供班级,而个人信息是通过登录后通过个人信息修改的。

为了简单起见,可以把学生,教师,管理员放在一张表中,共有的字段设置为不可为空(账号,密码,身份标识),不共有的设置可以为空,表如下

user表

字段名称类型约束描述
user_idvarchar(20)NOT NULL PRIMARY KEY用户ID
user_passwordvarchar(20)NOT NULL用户密码
user_namevarchar(20)NULL用户姓名(默认为空)
user_sexvarchar(10)NULL用户性别(默认为空)
user_birthdaydate()NULL用户生日(默认为空)
user_classvarchar(20)NULL班级
user_levelvarchar(20)NOT NULL用户标识

使用mysql命令行创建数据库和表

CREATE DATABASE web_resource;
CREATE TABLE `user` (
  `user_id` varchar(20) NOT NULL,
  `user_password` varchar(20) NOT NULL,
   `user_name` varchar(20)  NULL,
  `user_sex` varchar(10) DEFAULT NULL,
  `user_birthday` date DEFAULT NULL,
  `user_class` varchar(20) DEFAULT NULL,
  `user_level` varchar(20) NOT NULL,
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

创建用户实体类

鼠标左键java文件夹按Ctrl+N输入class

在这里插入图片描述

输入存放实体类的包名和类名后finish

在这里插入图片描述

user.java代码如下

public class User {
    private String userId;
    private String userPassword;
    private String userName;
    private String userSex;
    private String userBirthday;
    private String userClass;
    private String userLevel;
	public User() {
		super();
	}
	public User(String userId, String userPassword, String userName, String userSex, String userBirthday,
			String userClass, String userLevel) {
		super();
		this.userId = userId;
		this.userPassword = userPassword;
		this.userName = userName;
		this.userSex = userSex;
		this.userBirthday = userBirthday;
		this.userClass = userClass;
		this.userLevel = userLevel;
	}
	public String getUserId() {
		return userId;
	}
	public void setUserId(String userId) {
		this.userId = userId;
	}
	public String getUserPassword() {
		return userPassword;
	}
	public void setUserPassword(String userPassword) {
		this.userPassword = userPassword;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getUserSex() {
		return userSex;
	}
	public void setUserSex(String userSex) {
		this.userSex = userSex;
	}
	public String getUserBirthday() {
		return userBirthday;
	}
	public void setUserBirthday(String userBirthday) {
		this.userBirthday = userBirthday;
	}
	public String getUserClass() {
		return userClass;
	}
	public void setUserClass(String userClass) {
		this.userClass = userClass;
	}
	public String getUserLevel() {
		return userLevel;
	}
	public void setUserLevel(String userLevel) {
		this.userLevel = userLevel;
	}
	@Override
	public String toString() {
		return "User [userId=" + userId + ", userPassword=" + userPassword + ", userName=" + userName + ", userSex="
				+ userSex + ", userBirthday=" + userBirthday + ", userClass=" + userClass + ", userLevel=" + userLevel
				+ "]";
	}
}

控制层(controller)实现

鼠标左键java文件夹按Ctrl+N输入servlet

输入存放servlet的包名(com.zhuo.controller),由于注册是和用户相关的,还可以建一个子目录user,这样可以和后面各种类型的servlet进行区分,然后输入类名后next

在这里插入图片描述

修改URL映射,这样浏览器可以通过地址栏访问注册的servlet

在这里插入图片描述

RegisterServlet.java代码如下

public class RegisterServlet extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		/* 设置字符编码,解决中文乱码问题 */
		request.setCharacterEncoding("utf-8");
		response.setContentType("text/html;charset=utf-8");
		/* 接收客户端信息 */
		String userId = request.getParameter("userId");
		String userPassword = request.getParameter("userPassword");
		String userName = request.getParameter("userName");
		String userLevel = request.getParameter("userLevel");
		String userClass = request.getParameter("userClass");
		/* 创建用户实体,并把请求参数设置为用户实体成员变量值 */
		User user = new User();
		user.setUserId(userId);
		user.setUserPassword(userPassword);
		user.setUserName(userName);
		user.setUserLevel(userLevel);
		user.setUserClass(userClass);
		// 创建用户service层接口的实现类对象
		UserServiceImpl userServiceImpl = new UserServiceImpl();
		/* 调用业务逻辑方法注册用户 */
		int i = 0;
		try {
			i = userServiceImpl.register(user);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		/* 若i>0,则重定向到login.jsp */
		if (i > 0) {
			response.sendRedirect("views/before/login.jsp");
		}
	}
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		doGet(req, resp);
	}
}

RegisterServlet的主要作用就是接受客户提交的表单(通过注册页面提交),然后把参数封装到实体类中,之后就可以以实体类作用参数调用service层业务逻辑处理方法,通过返回值判断是否注册成功

业务逻辑层(service)实现

service层分为接口和接口的实现类,为了要定义接口呢?直接用类不行吗?

我们知道接口是空实现,具体实现由子类继承。那么这样的好处就是当我们扩展业务功能时,只需在接口添加即可,具体的实现交给子类实现。由于接口只包含方法签名,而不包含复杂的逻辑代码,很容易就可以看出有什么业务功能

定义接口

鼠标左键java文件夹按Ctrl+N输入interface

输入存放service层的接口包名和接口名后finish

在这里插入图片描述

在UserService.java中添加一个注册的抽象方法

	// 注册用户
	public int register(User user) throws SQLException;

定义接口的实现类

鼠标左键java文件夹按Ctrl+N输入class

输入存放service层的接口实现类的包名和类名并继承UserService接口后finish

在这里插入图片描述

在UserServiceImpl实现类中重写接口的register方法

      
      /*
	  * 创建用户dao层接口的实现类,并赋给接口对象变量。
	  *  实现上转型,也就面向接口编程,而不关心它的 实现类是谁
	  */
		UserDao userDao = new UserDaoImpl();
		@Override
		public int register(User user) throws SQLException {
			//把用户插入到数据库中
			int i = userDao.insertUser(user);
			return i;
		}

实现类中重写的register方法,主要功能是封装了底层操作(即对数据库的增删改查),只提现业务功能(即注册)

数据访问层(dao)实现

dao层和service层类似,就不再重复阐述了

定义接口

鼠标左键java文件夹按Ctrl+N输入interface

输入存放dao层接口的包名(com.zhuo.dao)和类名后finish

在UserDao.java中添加一个插入用户到数据库抽象方法

	// 把用户插入到数据库中
	public int insertUser(User user) throws SQLException;

定义接口的实现类

输入存放dao层的接口实现类的包名和类名并继承UserDao接口后finish

在这里插入图片描述

在UserDaoImpl实现类中重写接口的insertUser方法

	   //存放查询数据库返回的结果集
		ResultSet resultSet;
		@Override
		public int insertUser(User user) throws SQLException {
			//sql插入语句
			String sql = "insert into user (user_id, user_password, user_name, "
					+ "user_sex, user_birthday,  user_class, user_level) "
					+ "values(?,?,?,?,?,?,?);";
			// 调用jdbc工具类执行sql语句,如果操作成功,i就是操作成功的条数(即i > 0)
			int i = JDBCUtil.executeUpdate(sql, user.getUserId(), 
					user.getUserPassword(), user.getUserName(),
					user.getUserSex(), user.getUserBirthday(), 
					user.getUserClass(), user.getUserLevel());
			return i;
		}

实现类UserDaoImpl重写了接口的insertUser方法,主要功能是调用jdbc工具执行sql插入语句添加用户

使用jdbc工具类和导入mysql驱动包

创建jdbc工具类

鼠标左键java文件夹按Ctrl+N输入class

输入存放工具类的包名和类名后finish

在这里插入图片描述

JDBCUtil.java代码如下

public class JDBCUtil {
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/web_resource?useUnicode=true&characterEncoding=utf-8";
    private static final String USER = "root";
    private static final String PASSWORD = "12345";
    private static Connection ct;
    private static PreparedStatement ps;
    private static ResultSet rs;
    static {
        // 1.加载驱动,只需要加载一次,所以放到静态代码块中
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    /**
     * 描述:封装一个方法可以获得连接,目的可以在其他地方之接调用
     */
    public static Connection getConnection() {
        try {

            ct = DriverManager.getConnection(URL, USER, PASSWORD);
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return ct;
    }
    /**
     * 描述:封装一个方法可以完成查询操作
     *
     * @param sql 要查询的sql语句
     * @param obj 占位符的具体内容
     * @return ResultSet 将查询到的结果返回
     */
    public static ResultSet executeQuery(String sql, Object... obj) {
        // 1.得到连接
        ct = getConnection();
        // 2.创键发送对象
        try {
            ps = ct.prepareStatement(sql);
            // 处理占位符问题
            if (obj != null) {

                for (int i = 0; i < obj.length; i++) {
                    ps.setObject(i + 1, obj[i]);
                }
            }
            rs = ps.executeQuery();
        } catch (SQLException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return rs;
    }
    /**
     * 描述:封装一个方法可以完成DDL,DML操作
     *
     * @param sql 要操作的sql语句
     * @param obj 占位符
     * @return
     */
    public static int executeUpdate(String sql, Object... obj) {
        // 1.得到连接
        ct = getConnection();
        // 2.创键发送对象
        try {
            ps = ct.prepareStatement(sql);
            // 处理占位符问题
            if (obj != null) {
                for (int i = 0; i < obj.length; i++) {
                    ps.setObject(i + 1, obj[i]);
                }
            }
            int in = ps.executeUpdate();
            close(ct, ps, null);
            return in;
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return 0;
    }
    /**
     * 描述:封装一个关闭资源的方法
     *
     * @param ct 连接对象
     * @param ps 发送sql语句对象
     * @param rs 返回值对象
     */
    public static void close(Connection ct, PreparedStatement ps, ResultSet rs) {

        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (ct != null) {
            try {
                ct.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    // 给外部一个访问ct,和ps的方法
    public static Connection getCt() {
        return ct;
    }
    public static PreparedStatement getPs() {
        return ps;
    }
}

JDBCUtil工具类只需把常量PASSWORD修改为自己的mysql密码即可

导入mysql驱动包

要想连接数据库,需要加载mysql驱动包

jar包下载地址:https://repo1.maven.org/maven2/mysql/mysql-connector-java/

选择对应自己的mysql版本,下载好之后复制到WEB-INF的lib文件夹即可

在这里插入图片描述

至此后台主要的功能都实现了,等到编写前台代码时会加些前台表单验证的servlet(验证码,检查用户是否存在)。

到目前为止的项目结构如下

在这里插入图片描述

前端注册功能实现

前端页面包含结构(html,jsp),样式(css即表现),行为(javascript)。

Bootstrap框架下载与使用

我们主要的部分是结构(表单)和行为(验证表单),而样式不是重点。可以使用一些开源的css框架或者使用自己喜欢的样式,这些在网上都可以找到,我使用的Bootstrap框架

Bootstrap框架下载

Bootstrap有压缩版的,也有没压缩版的。如果有兴趣可以下载没压缩版的阅读源码
Bootstrap下载

Bootstrap框架使用

前端代码一般是放在webapp目录下,可以在webapp目录下创建一个views文件夹,存放前端代码

左击webapp按Ctrl+N输入folder后next

在这里插入图片描述

输入文件夹名views后finish

在这里插入图片描述

前端分为前台和后台,还可以在views文件下继续细分为前台(before文件夹),后台(after文件夹),这样可以保持项目结构简洁清晰

由于bootstrap框架,前台,后台都会用的到,所以可以在views文件夹创建一个css子文件夹存放bootstrap,然后在html文件中的head标签内添加引用即可,我的引用如下

<link
	href="${pageContext.request.contextPath}/views/css/bootstrap.min.css"
	rel="stylesheet">

在EL表达式通过page域获取请求域,然后通过请求域获取项目名即contextPath,这样就可以引用到这个文件

前端页面结构

注册页面主要的结构就是表单,里面包含一些组件(文本框,密码框,选择框,提交按钮)

左击before文件夹按Ctrl输入jsp,创建register.jsp,添加如下代码

<body class="gray-bg">
	<div class="middle-box text-center loginscreen   animated fadeInDown">
		<div>
			<h3>注册Resource library+</h3>
			<form class="m-t" role="form"
				action="${pageContext.request.contextPath}/register"
				onsubmit="return checkRegisterForm(this);" method="post">
				<div class="form-group">
					<p>
						<input type="text" class="form-control" name="userId"
							onfocus="focusItem(this)" 
							onblur="checkBlurRegisterItem(this)"
							placeholder="账号" required><span></span>
					</p>
				</div>
				<div class="form-group">
					<p>
						<input type="password" class="form-control" 
						name="userPassword" onfocus="focusItem(this)" 
						onblur="checkBlurRegisterItem(this)" placeholder="密码" 
						required><span></span>
					</p>
				</div>
				<div class="form-group">
					<p>
						<input type="password" class="form-control"
							onfocus="focusItem(this)" 
							onblur="checkBlurRegisterItem(this)"
							name="userPasswordConfirm" placeholder="确认密码" 
							required> <span></span>
					</p>
				</div>
				<div class="form-group">
					<p>
						<input type="text" class="form-control" name="userName"
							onfocus="focusItem(this)" 
							onblur="checkBlurRegisterItem(this)"
							placeholder="姓名" required><span></span>
					</p>
				</div>
				<div class="form-group">
					<select class="form-control" id="user_level" name="userLevel">
						<option value="学生">学生</option>
						<option value="教师">教师</option>
					</select>
				</div>
				<br>
				<div class="form-group" id="student_class_form">
					<p>
						<input type="text" class="form-control" id="student_class"
							name="userClass" onfocus="focusItem(this)"
							onblur="checkBlurRegisterItem(this)" 
							placeholder="班级" required>
						<span></span>
					</p>
				</div>
				<div class="form-group">
					<p>
						<input class="form-control" type="text" name="veryCode" 
						value="" onfocus="focusItem(this)" 
						onblur="checkBlurRegisterItem(this)" placeholder="验证码" />
						<img height="25" src="${pageContext.request.contextPath}/getcode" 
						alt="看不清,换一张" onclick="change(this)"><span></span>
					</p>
				</div>
				<button type="submit" class="btn btn-primary block full-width m-b">
				注册
				</button>
				<p class="text-muted text-center">
					<small>已经有一个账户?</small>
				</p>
				<a class="btn btn-sm btn-white btn-block" href="login.jsp">登录</a>
			</form>
		</div>
	</div>
</body>

html代码结构就不详细说明了,不懂得可以查文档,这里主要讲一下重要的内容

我们给表单,组件,图片绑定的事件处理onfocus(获取焦点事触发),onblur(失去焦点时触发),onsubmit(提交表单时触发),onclick(点击时触发)

后面在js会给出处理函数,就不详细说明了

在验证码的文本框input标签后添加一个img标签来显示验证码图片,src属性指定的是生成验证的servlet,下面来说验证码的实现

验证码的实现

验证码是通过在img标签中的src属性指定为servlet的url,这样servlet就会把验证码通过输出流响应给客户端,下面给出生成验证码的servlet

创建servlet的方式在前面我已经讲过了,这里就不再重复了

在controller层的user包下创建CodeServlet.java,添加如下代码

	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// 调用工具类生成的验证码和验证码图片
		Map<String, Object> codeMap = CodeUtil.generateCodeAndPic();
		/* 将四位数字的验证码保存到Session中
		 * 这样当客户提交时,会从session中获取生成的验证码并与用户
		 * 输入的验证码进行匹配 */
		HttpSession session = req.getSession();
		session.setAttribute("code", codeMap.get("code").toString());
		// 禁止图像缓存。
		resp.setHeader("Pragma", "no-cache");
		resp.setHeader("Cache-Control", "no-cache");
		resp.setDateHeader("Expires", -1);
		resp.setContentType("image/jpeg");
		// 将图像输出到Servlet输出流中。
		ServletOutputStream sos;
		try {
			sos = resp.getOutputStream();
			ImageIO.write((RenderedImage) codeMap.get("codePic"), "jpeg", sos);
			sos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

通过上面代码我们知道,真正生成验证码的是工具类CodeUtil。而servlet主要功能调用工具类的方法生成验证码并放在session中,起到封装的作用。然后通过输出流响应给客户端

在utils包下创建工具类CodeUtil,代码如下

public class CodeUtil {
    private static int width = 100;// 定义图片的width
    private static int height = 20;// 定义图片的height
    private static int codeCount = 4;// 定义图片上显示验证码的个数
    private static int xx = 15;
    private static int fontHeight = 18;
    private static  int codeY = 16;
    private static char[] codeSequence = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
            'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
    
    /**
     * 生成一个map集合
     * code为生成的验证码
     * codePic为生成的验证码BufferedImage对象
     * @return
     */
    public static Map<String,Object> generateCodeAndPic() {
        // 定义图像buffer
        BufferedImage buffImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // Graphics2D gd = buffImg.createGraphics();
        // Graphics2D gd = (Graphics2D) buffImg.getGraphics();
        Graphics gd = buffImg.getGraphics();
        // 创建一个随机数生成器类
        Random random = new Random();
        // 将图像填充为白色
        gd.setColor(Color.WHITE);
        gd.fillRect(0, 0, width, height);
        // 创建字体,字体的大小应该根据图片的高度来定。
        Font font = new Font("Fixedsys", Font.BOLD, fontHeight);
        // 设置字体。
        gd.setFont(font);
        // 画边框。
        gd.setColor(Color.BLACK);
        gd.drawRect(0, 0, width - 1, height - 1);
        // 随机产生40条干扰线,使图象中的认证码不易被其它程序探测到。
        gd.setColor(Color.BLACK);
        for (int i = 0; i < 30; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            gd.drawLine(x, y, x + xl, y + yl);
        }
        // randomCode用于保存随机产生的验证码,以便用户登录后进行验证。
        StringBuffer randomCode = new StringBuffer();
        int red = 0, green = 0, blue = 0;
        // 随机产生codeCount数字的验证码。
        for (int i = 0; i < codeCount; i++) {
            // 得到随机产生的验证码数字。
            String code = String.valueOf(codeSequence[random.nextInt(36)]);
            // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同。
            red = random.nextInt(255);
            green = random.nextInt(255);
            blue = random.nextInt(255);
            // 用随机产生的颜色将验证码绘制到图像中。
            gd.setColor(new Color(red, green, blue));
            gd.drawString(code, (i + 1) * xx, codeY);
            // 将产生的四个随机数组合在一起。
            randomCode.append(code);
        }
        Map<String,Object> map  =new HashMap<String,Object>();
        //存放验证码
        map.put("code", randomCode);
        //存放生成的验证码BufferedImage对象
        map.put("codePic", buffImg);
        return map;
    }
}

完成到这里我们就可以测试验证码的功能了,启动项目,在浏览器地址栏输入http://localhost:8080/web_resource/views/before/register.jsp

刷新页面,也就是在请求一次servlet,这样验证码就跟着改变,后面会在js实现点击图片更改验证码

效果如下

在这里插入图片描述

前端页面表单验证

浏览器对所有get请求都有缓存功能,而请求js是get请求,所以可以把全部的js代码单独放在一个js文件中,这样浏览器只需请求一次js,效率就提高了

在views文件夹下创建js子文件夹存放js代码

下面给出的所有js代码都放在一个js文件中,我的是function.js。在页面结构的body标签之前引用即可,我的引用如下

<script src="${pageContext.request.contextPath}/views/js/function.js"></script>
</body>

使用js点击图片改变验证码

可以通过js实现点击图片改变验证码,这很简单。在img标签中想更改图片,其实就改变src的属性值。

但是我们请求的是一个servlet,所以url不能改,能改的只有查询串(即请求参数)。

请求参数要各不相同,使用Date类获取本地当前系统时间就可以实现,js代码如下

// 改变验证码图片
function change(img) {
	img.src = getWebRootPath() + "/getcode?" + new Date().getTime();
}

这里使用的获取web根路径的函数,js代码如下

// 获取根路径
function getWebRootPath() {
	var a = window.document.location.href;//
	var b = window.document.location.pathname;
	var pos = a.indexOf(b);
	var path = a.substring(0, pos);
	a = a.substring(a.indexOf("/") + 2, a.length);
	a = a.substring(a.indexOf("/") + 1, a.length);
	var pathName = a.substring(0, a.indexOf("/"));
	return path + "/" + pathName;
}

在注册页面结构中已经在img标签中使用了onclick点击事件函数,调用的正是change函数。所有可以直接测试了

效果如下

在这里插入图片描述

使用jQuery动态改变班级字段

jQuery是js库,它可以简化我们对DOM的操作

有两个版本的 jQuery 可供下载:

  • Production version - 用于实际的网站中,已被精简和压缩。
  • Development version - 用于测试和开发(未压缩,是可读的代码)

这两个版本都可以从 jQuery.com 下载。

在views文件夹的js子文件夹中添加jQuery,然后在head标签中进行引用即可,我的引用如下

<script
	src="${pageContext.request.contextPath}/views/js/jquery-2.1.1.js"></script>

当在选择框选择学生时有班级文本框,但选择教师时是没有班级文本框的,下面通过jQuery实现这种效果,代码如下

/* 通过id获取选择框对象并绑定事件change函数,参数为匿名函数
* 匿名函数当change执行时便会执行,这样当选择框值改变时便会执行匿名函数*/
$("#user_level").change(function() {
			if ($('#user_level').val() == "学生") {
				// 通过id获取存放班级文本框容器div并调用可显函数
				$('#student_class_form').show();
			} else {
				// 调用隐藏函数
				$('#student_class_form').hide();
				// 获取班级文本框并设置值可以为空
				$('#student_class').attr("required", false);
			}
		});

测试效果

在这里插入图片描述

使用jQuery和Ajax异步验证表单

jQuery前面已经介绍过了,这里主要讲一下Ajax技术

简短地说,AJAX 是与服务器交换数据的,在不重载整个网页的情况下,AJAX 通过后台加载数据,并在网页上进行显示。

用户注册时用的账号可能已经是注册过,由于账号是唯一的(即主键),这时如果不在客户端先进行检测的就提交表单到服务器就会报错

同理,用户在表单输入的验证码也需要在客户端先进行检测

要想在不把表单数据发送到服务器的情况下验证用户名是否存在,验证码是否输入正确,就需要使用Ajax

它可以请求服务器(servlet)获取数据,然后通过后台判断数据并进行相应处理,而不是加载整个网页(刷新,或者跳转页面)

下面验证账号是否存在的js核心代码,如下

// 验证账号是否存在的servlet路径
var url = getWebRootPath() + "/check_user_id?id=" + encodeURI($(obj).val()) + "&" + new Date().getTime();
/*.get() 方法通过 HTTP GET 请求从服务器上请求数据。
* 可选的匿名函数参数是请求成功后所执行的函数名。*/
$.get(url, function(data) {
// 服务器返回false则用户名已存在
if (data == "false") {
	// 在页面显示提示信息
	msgBox.html('账号不能使用!');
	msgBox.addClass('error');
	// 标记改为false,阻止表单提交
	flagId = false;
} else {
		flagId = true;
		}
});

这里只是单独给出来说明验证账号的原理,实际运行需要添加到事件函数中才能运行,后面会给出完整代码

同样的验证用户输入的验证码是否正确也是通过Ajax请求servlet,接受服务端响应的信息来判断对不对,这里代码就不再给出

在注册页面结构中,已经给表单组件绑定了onblur(失去焦点时执行)事件,且事件处理函数都是一样的checkBlurRegisterItem(this)

这样就可以在这里集中验证表单是否为空,密码和确认密码是否一样,账号是否已存在,验证码是否正确了,完整代码如下

//标记位,只有有一个为false,则阻止表单提交
var flagId = false;
var flagPassword = false;
var flagPasswordConfirm = false;
var flagName = false;
var flagClass = true;
var flagCode = false;
function checkBlurRegisterItem(obj) {
	// 获取表单组件的下一个元素(即span),用来提示信息
	var msgBox = $(obj).next('span');
	/* 通过表单组件的name属性来判断是那个组件*/
	switch ($(obj).attr('name')) {
		/* 检测文本框是否为空,若不为空则验证账号是否存在*/
		case "userId":
		    // 获取账号文本框值,判断是否为空
			if (obj.value == "") {
				// 设置span元素的值
				msgBox.html('账号不能为空');
				// 给span元素添加类属性的值,后面会使用内联样式修改显示
				msgBox.addClass('error');
				// 标记改为false,阻止表单提交
				flagId = false;
			} else {
				// 验证账号是否存在的servlet路径
				var url = getWebRootPath() + "/check_user_id?id=" + encodeURI($(obj).val()) + "&" + new Date().getTime();
				/*.get() 方法通过 HTTP GET 请求从服务器上请求数据。
				* 可选的匿名函数参数是请求成功后所执行的函数名。*/
				$.get(url, function(data) {
					// 服务器返回false则用户名已存在
					if (data == "false") {
						// 在页面显示提示信息
						msgBox.html('账号不能使用!');
						msgBox.addClass('error');
						// 标记改为false,阻止表单提交
						flagId = false;
					} else {
						flagId = true;
					}
				});
			}
			break;
		/* 判断密码框是否为空*/
		case "userPassword":
			if (obj.value == "") {
				msgBox.html('密码不能为空');
				msgBox.addClass('error');
				flagPassword = false;
			} else {
				flagPassword = true;
			}
			break;
		/* 判断密码和确认密码是否相同*/
		case "userPasswordConfirm":
			if (obj.value == "") {
				msgBox.html('确认密码不能为空');
				msgBox.addClass('error');
				flagPasswordConfirm = false;
			} else if ($(obj).val() != $('input[name="userPassword"]').val()) {
				msgBox.html('两次输入的密码不一致');
				msgBox.addClass('error');
				flagPasswordConfirm = false;
			} else {
				flagPasswordConfirm = true;
			}
			break;
		/* 判断姓名是否为空*/
		case "userName":
			if (obj.value == "") {
				msgBox.html('姓名不能为空');
				msgBox.addClass('error');
				flagName = false;
			} else {
				flagName = true;
			}
			break;
		// 判断班级是否为空
		case "userClass":
			if (obj.value == "") {
				msgBox.html('班级不能为空');
				msgBox.addClass('error');
			}
			break;
		/* 判断验证码是否为空,若不为空则判断是否正确*/
		case "veryCode":
			var numshow = $(obj).next().next();
			if (obj.value == "") {
				numshow.html('验证码不能为空');
				numshow.addClass('error');
				flagCode = false;
			} else {
				var url = getWebRootPath() + "/check_user_code?num=" + encodeURI($(obj).val()) + "&" + new Date().getTime();
				$.get(url, function(data) {
					if (data == "false") {
						numshow.html('验证码输入有误');
						numshow.addClass('error');
						flagCode = false;
					} else {
						flagCode = true;
					}
				});
			}
			break;
	}
}

验证账号是否存在以及验证是否正确都是请求servlet来判断,下面分别给出

检测验证码是否正确的servlet

在controller层的user包下创建CheckUserCode.java,代码如下

	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// 接收客户端信息(即验证码)
		String num = request.getParameter("num");
		/* 从session获取生成的验证码 */
		HttpSession session = request.getSession();
		String sysCode = (String) session.getAttribute("code");
		// 获取输出流
		PrintWriter out = response.getWriter();
		/* 判断用户输入的验证和生成的验证码是否相同
		 * 若相同则响应true,否则响应false */
		if (sysCode.equals(num)) {
			out.print("true");
		} else {
			out.print("false");
		}
		out.close();
	}

完成到这步,就可以进行小测试,检查验证码效果,由于编写了servlet需要重启项目

测试效果

在这里插入图片描述

提示不太醒目,我们可以在注册页面中添加内联样式来修改一下。只需在head标签添加以下代码即可

<style>
.form-group p .error {
	display: inline-block;
	border: 1px solid #ff855a;
	background-color: #ffe8e0;
	height: 25px;
	line-height: 25px;
	padding: 0px 20px;
	margin-left: 20px;
}
</style>

检测账号是否存在的servlet

在controller层的user包中创建CheckUserId.java,代码如下

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		/* 设置字符编码,解决中文乱码问题 */
		request.setCharacterEncoding("UTF-8");
		response.setContentType("text/html;charset=utf-8");
		// 接收客户端信息(即账号)
		String id = request.getParameter("id");
		//创建用户service层接口的实现类
		UserServiceImpl userServiceImpl = new UserServiceImpl();
		/* 调用业务逻辑方法检查用户是否重复,若查到则返回count>0 */
		int count = 0;
		try {
			count = userServiceImpl.checkIdRepeated(id);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		// 获取输出流对象
		PrintWriter out = response.getWriter();
		/* 向客户发送查找成功或失败的标记 */
		if(count > 0 ){
			out.print("false");
		}else{
			out.print("true");
		}
		out.close();
	}

代码的功能注释已经详细说明,这里我们在说明一下之前搭建的三层架构:

显然这里检查账号是否存在的servlet是处于控制层,而就是说检查账号不是它实现的,它只是起到控制作用,比如它调用了service层的业务逻辑方法checkIdRepeated

下面我们只需要在service层的UserService接口中添加检查账号是否存在的业务功能,然后在UserServiceImpl实现类中重写即可

在接口中添加的业务逻辑方法如下

	// 检查账号是否存在
	public int checkIdRepeated(String userId) throws SQLException;

在实现类重写接口方法

	@Override
	public int checkIdRepeated(String userId) throws SQLException {
		// 通过账号在数据库进行检索
		int i = userDao.selectById(userId);
		return i;
	}

dao层的实现和service层是一样的,这里我就直接给出代码

在dao层的UserDao接口中添加检索账号的抽象方法

    // 通过账号在数据库进行检索
	public int selectById(String userId) throws SQLException;

在dao层的UserDaoImpl实现类中重写接口方法

	@Override
	public int selectById(String userId) throws SQLException {
		// sql检索语句
		String sql = "select * from user where user_id=?";
		// 调用jdbc工具类执行sql语句,如果操作成功,i就是操作成功的条数(即i > 0)
		resultSet = JDBCUtil.executeQuery(sql, userId);
		int i = 0;
		if (resultSet.next()) {
			i = 1;
		}
		return i;
	}

至此,检查账号是否存在的代码已经全部编写完成,测试之前需要重启项目,同时需要在数据库中添加一个账号

在这里插入图片描述

测试效果

在这里插入图片描述

在前面我们给表单组件绑定了失去焦点时执行的函数checkRegisterForm(this),我们知道如果表单组件输入为空等等,则会显示一个提示框

但是如果用户再次点击表单组件时,提示信息不会消失,所以我们还得给所有表单绑定一个获取焦点时执行的事件函数focusItem(this),然后再js编写处理逻辑即可,代码如下

function focusItem(obj) {
  /*  由于验证input组件还有img元素,它的下下元素才是span
	所以要单独处理*/
	if ($(obj).attr('name') == 'veryCode') {
		// 把span元素的值啥为空,并把类属性删掉
		$(obj).next().next().html('').removeClass('error');
	} else {
		// 把span元素的值啥为空,并把类属性删掉
		$(obj).next('span').html('').removeClass('error');
	}
}

这里就不再演示效果了,等到验证注册功能时会看到这个效果的

我们编写了表单组件的失去焦点和获取焦点的js事件函数,但是当点击提交时表单还需要验证一次,这里只需给form表单组件绑定一个事件onsubmit,处理函数为checkRegisterForm(this),代码如下

function checkRegisterForm(form) {
	// 获取所有input组件,返回一个数组包含input标签的数组(即对象)
	var elements = form.getElementsByTagName('input');
	
	/* 遍历input标签*/
	for (var i = 0; i < elements.length; i++) {
		// 判断input对象是否为null
		if (elements[i] != null) {
			// 检查input是否有onblur属性
			if (elements[i].getAttribute("onblur")) {
				checkBlurRegisterItem(elements[i]);
			}
		}
	}
    /*判断所有组件的标记,若为false,则说明有的组件为空或者输入有误
	* 直接 return false 阻止表单提交*/
	if (flagId && flagPassword && flagPasswordConfirm &&
		flagName && flagClass && flagCode) {
		return true;
	} else {
		return false;
	}
}

通过获取所有input标签并返回一个包含input对象的数组,然后遍历判断input标签是否包含onblur属性,若有则调用失去焦点时执行的函数checkBlurRegisterItem

注册功能综合测试

ok,大功告成,到现在注册功能所有代码均编写完成,现在就可以来测试注册功能了

测试效果

在这里插入图片描述

在这里插入图片描述

教师注册效果就不再演示了,下一篇会更新在此三层架构完善登录功能


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/32916.html

验证码  用户  表单  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1