miracle just wanna be better

Servlet数据库管理项目

2019-08-13
miracle

工作原理图

限制ip

Servlet层

 <servlet>
    <servlet-name>SysInitServletName</servlet-name>
    <servlet-class>cn.tedu.servlet.SysInitServlet</servlet-class>
    <init-param>
      <param-name>ipRange</param-name>
      <param-value>10.8.38.1-10.8.38.100</param-value>
    </init-param>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
  </servlet>
  • 此xml有load-on-starup,此servlet在服务器加载的时候就加载
  • 中,变量只在此servlet生效,具体查看Servlet基础篇
 <context-param>
    <param-name>globalIpRange</param-name>
    <param-value>10.8.38.1-10.8.38.100</param-value>
  </context-param>
  <context-param>
    <param-name>globalEncoding</param-name>
    <param-value>UTF-8</param-value>
  </context-param>

此xml功能同上,但此中,是全局变量,对整个项目生效

  <servlet>
    <servlet-name>IPLimitedServletName</servlet-name>
    <servlet-class>cn.tedu.servlet.IPLimitedServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>IPLimitedServletName</servlet-name>
    <url-pattern>/ip</url-pattern>
  </servlet-mapping>
  <servlet>

此servlet是对ip进行限制

<welcome-file-list>
    <welcome-file>ip</welcome-file>
</welcome-file-list>

当有人访问时,IPLimitedServlet生命周期开始,SysInitServlet在加载时生命周期就开始

SysInitServlet.java

public class SysInitServlet extends HttpServlet {

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		super.destroy();
	}

	@Override
	public void init() throws ServletException {
		//读取全局数据
		String ipRange=this.getServletContext().getInitParameter("globalIpRange");
		String encoding=this.getServletContext().getInitParameter("globalEncoding");
		CommonValue.ipRange=ipRange;
		CommonValue.encoding=encoding;
		System.out.println(CommonValue.ipRange);
		System.out.println(CommonValue.encoding);
		//读取局部数据
		/*String ipRange=this.getInitParameter("ipRange");
		String encoding=this.get
		InitParameter("encoding");
		CommonValue.ipRange=ipRange;
		CommonValue.encoding=encoding;
		System.out.println(CommonValue.ipRange);
		System.out.println(CommonValue.encoding);*/
	}

	@Override
	public void init(ServletConfig config) throws ServletException {
		System.out.println("init(ServletConfig)");
				//读取全局数据
				String ipRange=config.getServletContext().getInitParameter("globalIpRange");
				String encoding=config.getServletContext().getInitParameter("globalEncoding");
				CommonValue.ipRange=ipRange;
				CommonValue.encoding=encoding;
				System.out.println(CommonValue.ipRange);
				System.out.println(CommonValue.encoding);
				//读取局部数据
				/*String ipRange=this.getInitParameter("ipRange");
				String encoding=this.getInitParameter("encoding");
				CommonValue.ipRange=ipRange;
				CommonValue.encoding=encoding;
				System.out.println(CommonValue.ipRange);
				System.out.println(CommonValue.encoding);*/
	}
}
public class CommonValue {	
	public static String ipRange="";
	public static String encoding="";

}

上面有两种方式获得ipRange,一种是读取全局数据,另一种是读取局部数据,读取的方式不一样,两种方式对应上面两种xml的写法,当有init(ServletConfig config)时调用该方法,无参数方法不调用.把得到的IPRange和encoding存给CommonValue.java.

以上的代码都是在服务器加载时就工作

业务逻辑层

然后写限制ip的servlet具体业务逻辑

@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String ipRange=CommonValue.ipRange;
		String[] ips=ipRange.split("-");
		int ip1=Integer.parseInt(ips[0].substring(ips[0].lastIndexOf(".")+1));
		int ip2=Integer.parseInt(ips[1].substring(ips[1].lastIndexOf(".")+1));;
		System.out.println(ip1+" "+ip2);
		//获取客户端访问的真实ip
		String realIp=req.getRemoteAddr();
		int clientIp=Integer.parseInt(realIp.substring(realIp.lastIndexOf(".")+1));
		System.out.println("realIp="+realIp);
		if(clientIp>ip1&&clientIp<ip2){
			resp.sendRedirect("login.html");//如果符合就重定向到登录界面
		}else{
			PrintWriter out=resp.getWriter();
			out.append("u r not qulify to read,ur ip="+realIp);
			out.close();
		}
	}

登录

servlet层

先写xml,web.xml在服务器启动时就加载到内存,当请求login时,找到UserLoginServlet.class并实例化对象

<servlet>
    <servlet-name>UserLoginServletName</servlet-name>
    <servlet-class>cn.tedu.servlet.UserLoginServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>UserLoginServletName</servlet-name>
    <url-pattern>/login</url-pattern>
  </servlet-mapping>

UserLoginServlet.java

@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//获取前端提交的数据
		String uname=req.getParameter("userName");
		String upwd=req.getParameter("userPassword");
		User user=new User();
		user.setName(uname);
		user.setPassword(upwd);
		//调用业务
		UserService userService=new UserServiceImpl();
		boolean flag=userService.login(user);
		//根据业务的返回结果做响应
		if(flag){
			resp.sendRedirect("success.jsp");
		}else{
			resp.sendRedirect("login.jsp");
		}
	}

业务逻辑层

接下来调用业务层 UserService.java(业务接口)

public interface UserService {
	//登录的业务接口方法
	public boolean login(User user);

	public boolean register(User user);

}

具体业务逻辑

public class UserServiceImpl implements UserService {
	private UserDao userDao=new UserDaoImpl();
	@Override
	public boolean login(User user) {
		boolean flag=false;
		int id=userDao.login(user);//调用数据库
		if(id>0){
			flag=true;
		}
		return flag;
	}
}

数据访问层

通过逻辑代码调用数据库的数据 数据库访问层接口 userDao.java

public interface UserDao {
	//登陆的数据库方法
	public int login(User user);
}

登录操作实现 useDaoImpl.java

public class UserDaoImpl implements UserDao {

	@Override
	public int login(User user) {
		int id=0;
		try {
			String sql = "select id from t_user where username=? and userpassword=?";
			Object[] params = new Object[] { user.getName(), user.getPassword() };
			List<User> users = CommonDao.executeQuery(User.class, sql, params);
			if (users != null && users.size() == 1) {
				id = users.get(0).getId();
			} 
		} catch (Exception e) {
			e.printStackTrace();
		}
		return id;
	}

响应

最后响应到浏览器上,如果登录成功则重定向到usershowall.jsp,否则重定向到当前页面

总结

数据的轮回如下

浏览器–>Servlet–>业务逻辑层–>数据访问层(数据)–>业务逻辑层–>Servlet–>浏览器
最终响应在浏览器上

注册

servlet层

同登录,在创建Servlet类时可以直接创建Servlet对象,会自动在web.xml生成相应的配置

  <servlet>
    <servlet-name>UserRegisterServlet</servlet-name>
    <servlet-class>cn.tedu.servlet.UserRegisterServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>UserRegisterServlet</servlet-name>
    <url-pattern>/register</url-pattern>
  </servlet-mapping>

web.xml在服务器启动时就加载到内存,在请求register时,找到register同名的Servlet,找到类通过反射实例化对象

UserRegisterServlet.java

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取注册的数据
		String uname=request.getParameter("userName");
		String upwd=request.getParameter("userPassword");
		String uage=request.getParameter("age");
		String uaddress=request.getParameter("address");
		User user=new User();
		user.setName(uname);
		user.setPassword(upwd);
		user.setAddress(uaddress);
		user.setAge(Integer.parseInt(uage));
		//2.注册的业务
		UserService userService=new UserServiceImpl();
		boolean flag=userService.register(user);
		//3.根据业务的返回结果做响应
		if(flag){
			
		}else{
			
		}
	}

业务逻辑层

上面的servlet得到表单的数据,然后调用业务逻辑

UserService.java(业务接口)同上

public interface UserService {
	//登录的业务接口方法
	public boolean login(User user);

	public boolean register(User user);

}

具体的注册逻辑接口的实现 UserServiceImpl.java

@Override
	public boolean register(User user) {
		boolean flag=false;
		int rowAffect=userDao.register(user);
		if(rowAffect==1){
			flag=true;
		}
		return false;
	}

###数据访问层

业务逻辑调用数据库数据 数据库访问层接口 userDao.java

public interface UserDao {
	//注册的数据库方法
	public int register(User user);
}

具体的实现
userDaoImple.java

@Override
	public int register(User user) {
		int rowAffect=0;
		try {
			String sql = "insert into t_user (username,userpassword,age,address) values(?,?,?,?)";
			rowAffect = CommonDao.executeUpdate(sql,
					new Object[] { user.getName(), user.getPassword(), user.getAge(), user.getAddress() });
		} catch (Exception e) {
			// TODO: handle exception
		}
		return rowAffect;
	}

响应

最后响应到浏览器上,如果登录成功则重定向到login.jsp,否则重定向到当前页面

总结

数据的轮回如下

浏览器–>Servlet–>业务逻辑层–>数据访问层(数据)–>–>业务逻辑层–>Servlet–>浏览器
最终响应在浏览器上

数据库操作(PropertyUtil)

数据库(CommonDao)的操作在jdbc讲过,为了更方便的修改不同的数据库,此项目用PropertyUtil方式 下面是通用的连接数据库操作 CommonDao.java

public class CommonDao {
	private static PropertyUtil pu=new PropertyUtil("mysql.properties");
	private static String driverClass=pu.getProperty("jdbc_driverClass");
	private static String url=pu.getProperty("jdbc_url");
	private static String username=pu.getProperty("jdbc_username");
	private static String userpassword=pu.getProperty("jdbc_userpassword");
	/**
	 * 获取连接的公共方法
	 * @return
	 * @throws Exception
	 */
	public static Connection getConnection()throws Exception{
		Connection con=null;
		Class.forName(driverClass);
		con=DriverManager.getConnection(
				url,
				username,
				userpassword);
		return con;
	}
}

定义mysql属性文件
mysql.properties

jdbc_driverClass=com.mysql.jdbc.Driver
jdbc_url=jdbc:mysql://localhost:3306/testdb
jdbc_username=root
jdbc_userpassword=root

propertyUtil得到这个属性文件,然后通过文件中的属性名来得到具体的值,这样通过修改属性文件中的属性值就可以连接不同的数据库

显示所有数据

servlet层

先创建UserShowAllServlet,当浏览器访问此servlet时,寻找此url-pattern,找到servlet-name,在servlet标签中找到对应的类,通过反射实例化对象(步骤同上)

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取数据(无)
		//2.调用查询所有用户信息的业务方法
		List<User> users=new UserServiceImpl().findAllUsers();
		//3.根据业务的返回结果绑定给request对象,把带有数据的request对象转发给usershowall.jsp
		//绑定数据到request对象中
		request.setAttribute("allusers", users);
		//把带有新数据的request对象转发给下一个目的地usershowall.jsp
		RequestDispatcher rd=request.getRequestDispatcher("usershowall.jsp");
		rd.forward(request, response);
		
		
	}

浏览器访问UserShowAllServlet时,发送request请求,通过service调用doGet方法
doGet方法里,把需要的所有user对象数据绑定到request对象中,然后转发到usershowall.jsp,key值为allusers,这个key下面会用到

业务逻辑层

具体的业务逻辑

UserService.java(接口)

public List<User> findAllUsers();

接下来实现这个接口中的方法

UserServiceImpl.java

public List<User> findAllUsers() {
		return userDao.findAllUsers();
	}

数据访问层

业务逻辑中调用了userDao中的方法

userDao.java(接口)

public List<User> findAllUsers();

接下来实现这个接口中的方法

public List<User> findAllUsers() {
		List<User> users=null;
		try {
			String sql = "select id,username name,userpassword password,age,address from t_user";
			users = CommonDao.executeQuery(User.class, sql);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return users;
	}

响应浏览器usershowall.jsp

得到这些数据后要把这些所有数据显示在浏览器上面,用表格的形式显示在浏览器上,有多少数据就要创建多少行数据
我们用jstl在jsp页面里实现这些标签的生成

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

在jsp文件开头加入此语句

<c:forEach var="user" items="${requestScope.allusers}" varStatus="vs">
 		<tr>
 			<td>${vs.index}</td>
 			<td>${vs.count}</td>
 			<td>${user.id}</td>
 			<td>${user.name}</td>
 			<td>${user.password}</td>
 			<td>${user.age}</td>
 			<td>${user.address}</td>
 			<td><a href="UserDeleteServlet?uid=${user.id}">删除</a></td>
 			<td><a href="UserFindById?uid=${user.id}">修改</a></td>
 		</tr>
 	</c:forEach>	

上面语句相当于for循环,var=”user”定义变量user,循环遍历的值是${allusers},这个值是通过上面request对象绑定得到的,然后把得到的属性值添加在表格中
最后的删除操作是一个超链接,超链接访问的是一个servlet,通过?隔开后面带有一个数据,得知调用的是doGet()方法,后面写删除操作↓↓↓

总结

数据的轮回如下

浏览器–>Servlet–>业务逻辑层–>数据访问层(数据)–>业务逻辑层–>Servlet–>浏览器
最终响应在浏览器上

删除数据

修改数据的方式是在显示所有数据的基础上,点击数据后面的修改来操作的,通过上面usershowall.jsp得知是通过超链接访问servlet方式来删除

servlet层

上面超链接调用的是此servlet的doGet()方法

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取要删除的id
		String id=request.getParameter("uid");
		//2.调用删除用户业务
		boolean flag=new UserServiceImpl().deleteUser(Integer.parseInt(id));
		//3.根据业务的返回结果做跳转响应
		if(flag)
		response.sendRedirect("UserShowAllServlet");
	}

进入doGet()方法,先得到附带的uid的值,然后调用删除用户业务逻辑

业务逻辑层

UserService.java(接口)

public boolean deleteUser(int id);

实现上面方法

UserServiceImpl.java

public boolean deleteUser(int id) {
		boolean flag=false;
		int rowAffect=userDao.deleteUser(id);
		if(rowAffect==1){
			flag=true;
		}
		return flag;
	}

数据访问层

业务逻辑层调用了数据访问层方法

UserDao.java(接口)

public int deleteUser(int id);

实现上面方法

public int deleteUser(int id) {
		int rowAffect=0;
		try {
			String sql="delete from t_user where id=?";
			rowAffect=CommonDao.executeUpdate(sql, id);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return rowAffect;
		
	}

响应浏览器UserShowAllServlet

跳转响应到UserShowAllServlet,上面已经做了分析,重新显示所有最新数据,相应的数据就被删除了

修改数据

修改数据分为两步

  1. 通过id查找用户信息
  2. 修改查找到的用户信息

Servlet层

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding(CommonValue.encoding);
		//1.获取用户的id
		String id=request.getParameter("uid");
		//2.根据id查询用户信息
		User user=new UserServiceImpl().findUserById(Integer.parseInt(id));
		//3.把查询到的数据转发给update.jsp
		//绑定数据给request
		request.setAttribute("user", user);
		//转发request到新的目标url
		request.getRequestDispatcher("update.jsp").forward(request, response);
	}

进入UserFindById,带入一个uid值,调用的也是doGet()方法,然后调用具体查询用户业务逻辑

业务逻辑层

根据id查找用户信息
UserService.java()接口

public User findUserById(int id);

实现上面方法 UserServiceImpl.java

public User findUserById(int id) {
		User user=userDao.findUserById(id);
		return user;
	}

数据访问层

进入数据访问层,根据用户id查询用户信息的数据库方法

UserDao.java(接口)

public User findUserById(int id);

实现上面方法
UserDaoImpl.java

public User findUserById(int id) {
		User user=null;
		try {
			String sql="select id,username name,userpassword password,age,address from t_user where id=?";
			List<User> users=CommonDao.executeQuery(User.class, sql,id);
			if(users!=null&&users.size()==1){
				user=users.get(0);
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return user;
	}

响应浏览器

查到相应的数据后,重定向响应到浏览器update.jsp上,显示出一个修改界面让用户进行修改

update.jsp

<`form action="UserUpdateServlet" method="post">
<`input type="hidden" name="userId" value="${requestScope.user.id}"/>
<`table border="1px" align="center">
 		<tr>
 			<td>用户名</td>
 			<td><`input type="text" name="userName" value="${requestScope.user.name}"/></td>
 		</tr>
 		<tr>
 			<td>&nbsp;&nbsp;</td>
 			<td><`input type="password" name="userPassword" value="${requestScope.user.password}"/></td>
 		</tr>
 		<tr>
 			<td>年龄</td>
 			<td><`input type="text" name="age" value="${requestScope.user.age}"/></td>
 		</tr>
 		<tr>
 			<td>地址</td>
 			<td><`input type="text" name="address" value="${requestScope.user.address}"/></td>
 		</tr>
 		<tr>

重定向时为request对象绑定了key为user的user对象数据,并把user对象的属性值显示在jsp页面上,此时就可以重新为这些属性赋值,因为不能设置id的值,所以将id设置为隐藏按钮,提交后进入UserUpdateServlet

servlet层

再次进入servlet层,进入UserUpdateServlet,调用的是post方法

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding(CommonValue.encoding);
		String uid=request.getParameter("userId");
		String uname=request.getParameter("userName");
		String upwd=request.getParameter("userPassword");
		String uage=request.getParameter("age");
		String uaddress=request.getParameter("address");
		User user=new User();
		user.setId(Integer.parseInt(uid));
		user.setAddress(uaddress);
		user.setName(uname);
		user.setPassword(upwd);
		user.setAge(Integer.parseInt(uage));
		boolean flag=new UserServiceImpl().updateUser(user);
		if(flag)
		response.sendRedirect("UserShowAllServlet");
	}

此方法得到新赋的值,然后调用业务逻辑更新方法

业务逻辑层

更新用户信息接口方法 UserService.java(接口)

public boolean updateUser(User user);

实现上面方法

public boolean updateUser(User user) {
		boolean flag=false;
		int rowAffect=userDao.updateUser(user);
		if(rowAffect==1){
			flag=true;
		}
		return flag;
	}

数据访问层

业务逻辑层调用了数据库方法
更新用户信息的数据库方法
UserDao.java

public int updateUser(User user);

实现上面方法

public int updateUser(User user) {
		int rowAffect=0;
		try {
			String sql="update t_user set username=?,userpassword=?,age=?,address=? where id=?";
			rowAffect=CommonDao.executeUpdate(sql, user.getName(),user.getPassword(),user.getAge(),user.getAddress(),user.getId());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return rowAffect;
	}

响应浏览器

更新完成后,重定向到UserShowAllServlet里,又重新加载usershowall.jsp,显示当前最新的数据

分页+模糊查询

如果用户登录后,数据量非常庞大的情况下,不能在页面显示所有数据信息,于是有了分页

servlet层

 <servlet>
    <description></description>
    <display-name>FindUserByPageServlet</display-name>
    <servlet-name>FindUserByPageServlet</servlet-name>
    <servlet-class>cn.tedu.servlet.FindUserByPageServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>FindUserByPageServlet</servlet-name>
    <url-pattern>/FindUserByPageServlet</url-pattern>
  </servlet-mapping>

通过上面web.xml找到FindUserByPageServlet通过反射实例化对象

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取数据(当前页码,模糊条件用户名和地址)
		//获取currentPage
		int currentPage=1;
		String currentPage_String=request.getParameter("currentPage");
		if(currentPage_String!=null){
			currentPage=Integer.parseInt(currentPage_String);
		}
		//获取模糊关键字
		String keyword1=request.getParameter("keyword1");
		String keyword2=request.getParameter("keyword2");
		String kw1=(keyword1==null)? "":keyword1;
		String kw2=(keyword2==null)? "":keyword2;
		String[] keywords=new String[]{kw1,kw2};
		//获取每一页显示记录的条数PageSize(page.properties)
		int pageSize=Integer.parseInt(new PropertyUtil("page.properties").getProperty("pageSize"));
		
		
		//2.调用分页的业务
		UserService userService=new UserServiceImpl();
		Page page=userService.findUserByPage(currentPage,pageSize,keywords);
		
		//3.把查询到的分页信息绑定给request,转发给usershowbypage.jsp
		request.setAttribute("page", page);
		request.getRequestDispatcher("usershowbypage.jsp").forward(request, response);
		
	}

默认进入时显示第一页,通过request对象得到浏览器输入的keyword1和keyword2和当前页码数,通过page.properties得到每页数据条数,去调用业务逻辑

业务逻辑层

分页+模糊查询的接口方法
UserService.java(接口)

public Page findUserByPage(int currentPage, int pageSize, String[] keywords);

实现上面方法

UserServiceImpl.java

public Page findUserByPage(int currentPage, int pageSize, String[] keywords) {
		Page page=new Page();
		page.setCurrentPage(currentPage);
		page.setPageSize(pageSize);
		page.setKeywords(keywords);
		//查询数据库获取带有模糊条件的总记录数
		int totalCount=userDao.getCount(keywords);
		page.setTotalCount(totalCount);
		//计算总页数
		int totalPage=(totalCount%pageSize==0)? (totalCount/pageSize):(totalCount/pageSize)+1;
		page.setTotalPage(totalPage);
		//计算前一页
		if(currentPage==1){
			page.setPreviousPage(1);
		}else{
			page.setPreviousPage(currentPage-1);
		}
		//计算下一页
		if(currentPage==totalPage){
			page.setNextPage(totalPage);
		}else{
			page.setNextPage(currentPage+1);
		}
		//从数据库获取当前页的数据
		List<User> users=userDao.getUsersByPage(currentPage,pageSize,keywords);
		page.setData(users);
		return page;
	}

该方法计算总页数,上一页,下一页,当前页user数据并存入Page对象

数据访问层

分页+模糊的数据库方法
userDao.java(接口)

//分页+模糊
	public int getCount(String[] keywords);
	public List<User> getUsersByPage(int currentPage, int pageSize, String[] keywords);

实现上面方法

userDaoImpl.java

public int getCount(String[] keywords) {
		int count=0;
		try {
			String sql="select count(id) geshu from t_user "
					+ "where username like ? and address like ?";
			Object[] params=new Object[]{"%"+keywords[0]+"%","%"+keywords[1]+"%"};
			List<CountVO> counts=CommonDao.executeQuery(CountVO.class, sql, params);
			if(counts!=null&&counts.size()==1){
				count=counts.get(0).getGeshu();
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return count;
	}

	@Override
	public List<User> getUsersByPage(int currentPage, int pageSize, String[] keywords) {
		List<User> users=null;
		try {
			String sql="select id,username name,userpassword password,age,address from t_user where username like ? and address like ? limit ?,?";
			Object[] params=new Object[]{"%"+keywords[0]+"%","%"+keywords[1]+"%",(currentPage-1)*pageSize,pageSize};
			users=CommonDao.executeQuery(User.class, sql, params);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return users;
	}

查找总页数时,还记得jdbc基础那一张讲的反射方式吗,在求聚合函数时,我们User对象里没有相应的属性和方法,于是我们新创建一个ov类
CountOV.java

public class CountVO {
	private int geshu;
	public int getGeshu() {
		return geshu;
	}
	public void setGeshu(int geshu) {
		this.geshu = geshu;
	}
}

属性就叫个数,然后通过sql语句count(id) geshu 别名来把值存给CountOV,然后取出该值
再通过查找当前页分页查询的方式得到数据,存进到page对象中,把page对象返回到浏览器

响应浏览器

在该servlet中,request对象绑定了page对象所有数据,于是我们就得到了currentPage当前页,previousPage上一页,nextPage下一页,pageData当前页数据这些属性,把这些属性返回到usershowbypage.jsp上
usershowbypage.jsp

<`form action="FindUserByPageServlet" method="post">
 		用户名:<`input type="text" name="keyword1" value="${page.keywords[0]}"/>
 		地址:<`input type="text" name="keyword2" value="${page.keywords[1]}"/>
 		<`input type="submit" value="模糊查询"/>
 	</form>
 	<div style="font-size:30px;font-wight:bold;">显示用户信息</div>
 	<`table border="1" align="center">
 		<tr>
 			<th>序号1</th>
 			<th>序号2</th>
 			<th>id</th>
 			<th>用户名</th>
 			<th>密码</th>
 			<th>年龄</th>
 			<th>地址</th>
 			<th>删除</th>
 			<th>修改</th>
 		</tr>
 	<c:forEach var="user" items="${requestScope.page.data}" varStatus="vs">
 		<tr>
 			<td>${vs.index}</td>
 			<td>${vs.count}</td>
 			<td>${user.id}</td>
 			<td>${user.name}</td>
 			<td>${user.password}</td>
 			<td>${user.age}</td>
 			<td>${user.address}</td>
 			<td><a href="UserDeleteServlet?uid=${user.id}">删除</a></td>
 			<td><a href="UserFindById?uid=${user.id}">修改</a></td>
 		</tr>
 	</c:forEach>	
 	</table>
 	<!-- 分页条开始 -->
 	<c:if test="${requestScope.page.totalPage>1}">
 	[${page.currentPage}/${page.totalPage}]
 		<!-- 当前页为第一页的情况 -->
 		<c:if test="${requestScope.page.currentPage==1}">
 			<a href="FindUserByPageServlet?currentPage=2&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">下一页</a>
 			<a href="FindUserByPageServlet?currentPage=${page.totalPage}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">尾页</a>
 		</c:if>
 		<!-- 既不是第一页也不是最后一页 -->
 		<c:if test="${requestScope.page.currentPage>1 and requestScope.page.currentPage<requestScope.page.totalPage}">
 			<a href="FindUserByPageServlet?currentPage=1&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">首页</a>
 			<a href="FindUserByPageServlet?currentPage=${page.previousPage}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">上一页</a>
 			<a href="FindUserByPageServlet?currentPage=${page.nextPage}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">下一页</a>
 			<a href="FindUserByPageServlet?currentPage=${page.totalPage}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">尾页</a>
 		</c:if>
 		<!-- 当前页为最后一页 -->
 		<c:if test="${requestScope.page.currentPage==requestScope.page.totalPage}">
 			<a href="FindUserByPageServlet?currentPage=1&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">首页</a>
 			<a href="FindUserByPageServlet?currentPage=${page.previousPage}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">上一页</a>
 		</c:if>
 	</c:if>
 	<!-- 分页条结束 -->

该jsp增加了两个表单,1:关键字1,2:关键字2,增加了页码导航栏
把当前页的数据同showalluser方式一样,显示在当前页面
分页条:

  • 如果总页数大于1,就显示分页条
  • 显示[当前页数/总页数]
  • 当前是第一页,就增加下一页和尾页
  • 当前是中间页,就增加首页,上一页,下一页,尾页
  • 当前是尾页,就增加首页和上一页 功能:
  • 点击上一页,超链接指向到
FindUserByPageServlet?currentPage=${page.previousPage}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}  

通过doGet方式,把三个参数传入到FindUserByPageServlet中,然后重复上面的过程

  • 点击下一页,超链接指向到
FindUserByPageServlet?currentPage=${page.nextPage}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]} 

通过doGet方式,把三个参数传入到FindUserByPageServlet中,然后重复上面的过程

  • 点击首页,超链接指向到
FindUserByPageServlet?currentPage=1&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]} 

通过doGet方式,把三个参数传入到FindUserByPageServlet中,然后重复上面的过程

  • 点击尾页,超链接指向到
FindUserByPageServlet?currentPage=${page.totalPage}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]} 

通过doGet方式,把三个参数传入到FindUserByPageServlet中,然后重复上面的过程

跳转页面

<!-- 跳转页 -->
 	<`form action="FindUserByPageServlet" method="post">
 	<`input type="text" value="${page.currentPage}" style="width:30px" name="currentPage"/>
 	<`input type="submit" value="跳转"/>
 	</form>

创建一个表单,text文本框name值是currentPage,输入页数后,跳转到FindUserByPageServlet,重复上面过程

下拉框

<!-- 下拉列表跳转 -->
 	<form action="FindUserByPageServlet" method="post" id="form">
 	<`select onchange="submitForm()" name="currentPage">
 		<c:forEach var="num" begin="1" end="${page.totalPage}" varStatus="vs">
 			<option value="${vs.count}">${vs.count}</option>
 		</c:forEach>
 		<option value="${page.currentPage}" selected="selected">当前页:${page.currentPage}</option>
 	</select>
 	</form>
 	<!-- js函数处理onchange事件 -->
 	<script type="text/javascript">
 	function submitForm(){
 		var form=document.getElementById("form");
 		form.submit();
 	}
 	</script>

用jstl循环总页数,从1开始遍历,用vs.count作为value值和显示的值,到totalPage结束
select的name为currentPage,得到值后传给FindUserByPageServlet
给select增加了onchange事件,当选择的页面发生变化时,自动重定向页面,onchange用javascript语句实现

分页条

<!-- 分页条,一共显示7个码 -->
 	<c:if test="${page.totalPage>=7 and page.currentPage<=5}">
 		<c:forEach var="num" begin="1" end="7" varStatus="vs">
 			<a href="FindUserByPageServlet?currentPage=${vs.count}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">${vs.count}</a>
 		</c:forEach>
 	</c:if>	
 	<!-- 当当前页大于5且后面还能再加两个,向前显示4个,向后显示2个 -->
	<c:if test="${page.totalPage>=7 and page.currentPage>5 and page.currentPage+2<=page.totalPage}">
 		<c:forEach var="num" begin="${page.currentPage-4}" end="${page.currentPage+2}" varStatus="vs">
 			<a href="FindUserByPageServlet?currentPage=${num}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">${num}</a>
 		</c:forEach>
 	</c:if>	
 	<!-- 当当前页大于5且后面不能再加两个,向前显示少于7页的页数,向后显示到总页数 -->
 	<c:if test="${page.totalPage>=7 and page.currentPage>5 and page.currentPage+2>page.totalPage}">
 		<c:forEach var="num" begin="${page.currentPage-6+page.totalPage-page.currentPage}" end="${page.totalPage}" varStatus="vs">
 			<a href="FindUserByPageServlet?currentPage=${num}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">${num}</a>
 		</c:forEach>
 	</c:if>	
 	<c:if test="${page.totalPage<7}">
 		<c:forEach var="num" begin="1" end="${page.totalPage}" varStatus="vs">
 			<a href="FindUserByPageServlet?currentPage=${vs.count}&keyword1=${page.keywords[0]}&keyword2=${page.keywords[1]}">${vs.count}</a>
 		</c:forEach>
 	</c:if>

每页显示7个数字,以第5个数字作为当前页,同时作为参考位置,向后增加两个数字

  • 总页数<7,从1增加到totalPage
  • 总页数>7 and currentPage<=5,从1增加到7
  • 总页数>7 and currentPage>5 and currentPage+2>totalPage, 此时说明后面不能增加两个,直能到最大页数,为了保证还是7个数字,向前补,currentPage-6到currentPage时,正好保证7个数字,然后减去currentPage后面的数,就是7个,即page.currentPage-6+page.totalPage-page.currentPage.
  • 总页数>7 and currentPage>5 and currentPage+2<=totalPage 此时就从currentPage-4到currentPage+3

登出(退出账号)

<a href="UserLogoutServlet">登出</a>

点击该超链接访问该servlet

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session=request.getSession();
		//session对象存在,session对象中的userName的数据被移除
		session.removeAttribute("userName");
		//另一种写法(不推荐)
		//session.setAttribute("userName", null);
		//session对象不存在了
		session.invalidate();
		response.sendRedirect("login.jsp");
		
	}

通过HttpSession session=request.getSession();得到当前浏览器和服务器的session对象
把session对象销毁,则登入状态消失

删除的判断

在前面分页显示的时候,用循环的方式增加表单数据,增加一个判断,显示数据时,不把登录用户的删除按钮显示出来

<c:if test="${userName!=user.name}">
 	<a href="UserDeleteServlet?uid=${user.id}">删除</a>
 </c:if>

UserLoginServlet.java增加了如下代码

//获取前端提交的数据
String uname=req.getParameter("userName");
session.setAttribute("userName", uname);//给session对象存uname值

然后重定向到分页显示页面

验证码功能

在登录页面增加一个验证码功能

<tr>
 			<td>验证码</td>
 			<td>
 			<`input type="text" name="validateCode" size=5/>
 			<img src="CodeServlet" onclick="this.src='CodeServlet?'+Math.random();" title="点击更换" style="cursor:pointer;" >
 			<span style="color:red;">${requestScope.messagecode}</span>
 			</td>
 			</tr>

增加一个text文本框,增加一个img图片,sec指向的是Codeservlet这个Servlet,用js随机生成一个数字,为img增加一个onclick单击事件,每点击一次图片,就会向CodeServlet发送一次请求
随机数的作用是形成新的刷新请求,解决服务器缓存的问题,一直取同一张图片 增加了一个span标签,里面放的是messagecode

CodeServlet.java

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//此do方法生成一张图片
		Random r=new Random();
		//0.创建空白图片
		BufferedImage image=new BufferedImage(100,30,BufferedImage.TYPE_INT_RGB);
		//1.获取图片的画笔
		Graphics g=image.getGraphics();
		//2.设置画笔的颜色
		g.setColor(new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255)));
		//3.绘制矩形背景
		g.fillRect(0, 0, 100, 30);
		//4.调用一个自定义的方法,获取长度为5的字母和数字的字符串
		String number=this.getNumber(5);
		//5.随机生成的验证码存储在session中
		HttpSession session=request.getSession();
		session.setAttribute("code", number);
		//6.重新设置画笔,准备画验证码
		g.setColor(new Color(0,0,0));
		g.setFont(new Font(null,Font.BOLD,24));
		//7.绘制验证码
		g.drawString(number, 5, 25);
		//8.绘制8条干扰线
		for(int i=0;i<8;i++){
			g.setColor(new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255)));
			g.drawLine(r.nextInt(100),r.nextInt(30),r.nextInt(100),r.nextInt(30));
		}
		//9.设置响应的内容
		response.setContentType("image/jpeg");
		OutputStream os=response.getOutputStream();
		//10.把image图片写出到响应中(写道网络输出流上)
		ImageIO.write(image, "jpeg", os);
		os.flush();
		os.close();
	}
	private String getNumber(int size){
		String str="ABCDEFGHIGKLMNOPQRSTUVWXYZ1234567890";
		String number="";
		Random r= new Random();
		for(int i=0;i<size;i++){
			number+=str.charAt(r.nextInt(str.length()));
		}
		return number;
	}

UserLoginServlet.java

//获取用户数据的验证码
		String validateCode=req.getParameter("validateCode");
		//获取前端提交的数据
		String uname=req.getParameter("userName");
		String upwd=req.getParameter("userPassword");
		System.out.println(uname+" "+upwd);
		User user=new User();
		user.setName(uname);
		user.setPassword(upwd);
		//从session中获取系统生成的验证码
		HttpSession session=req.getSession();
		String code=session.getAttribute("code").toString();
//从session中获取系统生成的验证码
		HttpSession session=req.getSession();
		String code=session.getAttribute("code").toString();
		session.setAttribute("userName", uname);
		if(validateCode.equalsIgnoreCase(code)){
			//调用业务
			UserService userService=new UserServiceImpl();
			
			boolean flag=userService.login(user);
			//根据业务的返回结果做响应
			if(flag){
				//重定向到分页+模糊
				resp.sendRedirect("FindUserByPageServlet");
			}else{
				resp.sendRedirect("login.jsp");
			}
		}else{
			req.setAttribute("messagecode", "验证码错误");
			req.getRequestDispatcher("login.jsp").forward(req, resp);
		}

Servlet从session中取出CodeServlet生成的验证码,从jsp中取出用户输入的验证码,进行对比,如果输入错误,把messagecode通过request对象发送给jsp,在页面显示出来

注册检测用户名

在register.jsp增加如下代码

<td>用户名</td>
 		<td>
 			<`input type="text" name="userName" onblur="doCheckName();"/>
 			<span id="uname" style="color:red;"></span>
 		</td>

给用户名输入框增加一个事件 onblur(鼠标移开事件焦点)
当输完用户名鼠标点击其他地方就触发,事件的名字是doCheckName(),这是个js写的事件
增加了一个span标签,暂时是空的
用ajax异步想服务器发送请求,局部刷新

原生js的ajax方法

register.js

//创建ajax对象
var xmlHttpRequest;
function createXMLHttpRequest(){
	if(window.XMLHttpRequest){
		//非ie浏览器
		xmlHttpRequest=new XMLHttpRequest();
	}else{
		//ie浏览器
		xmlHttpRequest=new ActiveXObject("Microsoft.XMLHTTP");
	}
}
//ajax异步请求访问数据库,检测用户名是否存在
function doCheckName(){
	createXMLHttpRequest();//创建ajax对象
	//获取文本框内容
	var uname=document.forms[0].userName.value;
	//调用open方法
	xmlHttpRequest.open("get","CheckNameServlet?uname="+uname,true);
	//如果是post提交,必须先写下面内容
	//xmlHttpRequest.setRequestHeader("content-type","applicatoin/x-www-form-urlencoded");
	//给xmlHttpRequest对象注册事件onreadystatechange
	xmlHttpRequest.onreadystatechange=function(){
		handStateChange();
	};
	//发送请求,此时才请求servlet
	xmlHttpRequest.send(null);
}
function handStateChange(){
	if(xmlHttpRequest.readyState==4){
		if(xmlHttpRequest.status==200){
			//获取服务端相应的文本信息
			var responseText=xmlHttpRequest.responseText;
			//把得到的文本做dom编程,修改html页面中的局部内容
			var span_ele=document.getElementById("uname");
			//清除span_ele元素的第一个节点
			if(span_ele.hasChildNodes()){
				span_ele.removeChild(span_ele.childNodes[0]);
			}
			//根据响应回来的文本创建文本节点对象
			var txt_node=document.createTextNode(responseText);
			span_ele.appendChild(txt_node);
		}
	}
}
  1. 先创建ajax对象XMLHTTPRequest
  2. 调用open方法
    • 参数1:提交的方式
    • 参数2:提交到服务器url
    • 参数3:true异步,false同步
  3. 给XMLHttpRequest对象注册onreadystatechange事件 判断readyState的值是4,和status的是值是200,然后获取服务端传递回来的xml或json或文本,然后dom编程更改html页面或jsp页面的局部内容
  4. 调用send方法发送请求

调用send方法才访问servlet,最后通过js操作html标签来显示相应的文字

第2步请求了CheckNameServlet

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding(CommonValue.encoding);
		//获取用户名信息
		String uname=request.getParameter("uname");
		//调用查询用户名是否存在的业务方法
		boolean flag=new UserServiceImpl().findUserByName(uname);
		//根据业务的返回结果做相应的信息响应
		String responseText="";
		if(flag){
			responseText="用户名被占用";
		}else{
			responseText="用户名可用";
		}
		//把信息响应给客户端js
		response.setContentType("text/html;charset=utf-8");
		PrintWriter out=response.getWriter();
		out.print(responseText);
		out.flush();
		out.close();
		
	}

业务逻辑层

该servlet调用了业务方法,为了节省时间,我们不在叙述接口的问题,直接写实现方法

UserServiceImpl.java

public boolean findUserByName(String uname) {
		boolean flag=false;
		int id=userDao.findUserByName(uname);
		if(id>0){
			flag=true;
		}
		return flag;
	}

数据访问层

该业务方法调用了数据库方法

public int findUserByName(String uname) {
		int id=0;
		try {
			String sql="select id from t_user where username=?";
			List<User> users=CommonDao.executeQuery(User.class, sql, uname);
			if(users!=null&&users.size()==1){
				id=users.get(0).getId();
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return id;
	}

业务层最后将文字通过response对象用流发给ajax的js代码,js代码反馈到浏览器上

jquery的ajax方法

在jsp导入jquery.js和register.js

function doCheckName(){
	//获取文本框数据
	var uname=$("form input[name=userName]").val();
	alert(uname);
	//发送ajax异步请求
	$.ajax({
		url:"CheckNameServlet",
		type:"post",
		data:{"uname":uname},
		dataType:"json",
		success:function(data){
			alert(data.status);
			alert(data.message);
			//把得到的文本做dom编程,修改html页面中的局部内容
			var span_ele=document.getElementById("uname");
			if(span_ele.hasChildNodes()){
				span_ele.removeChild(span_ele.childNodes[0]);
				//根据响应回来的文本创建文本节点对象
				var txt_node=document.createTextNode(data.message);
				span_ele.appendChild(txt_node);
			}
			
		},
		error:function(){
			alert("请求失败");
		}
	});
}

改进提示信息

上面的方法

//根据业务的返回结果做相应的信息响应
		String responseText="";
		if(flag){
			responseText="用户名被占用";
		}else{
			responseText="用户名可用";
		}

通过字符串的方式返回用户名是否被占用,此判断代码全部在servlet 中,如果该判断数据量庞大,就很不清晰,所以创建一个单独的ov类Result.java,用来存储这些信息
Result.java

public class Result implements Serializable{
	/**
	 * 0:失败
	 * 1:成功
	 */
	private int status;
	/**
	 * 存储提示消息
	 */
	private String message;
	/**
	 * 存储数据
	 */
	private Object data;
	public int getStatus() {
		return status;
	}
	public void setStatus(int status) {
		this.status = status;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
	public Object getData() {
		return data;
	}
	public void setData(Object data) {
		this.data = data;
	}
	
}

相应的代码变成

String responseText="";
		Result result=new Result();
		if(flag){
			//responseText="{\"msg\":\"用户名被占用\"}";
			result.setStatus(0);
			result.setMessage("用户名被占用");
		}else{
			//responseText="{\"msg\":\"用户名可用\"}";
			result.setStatus(1);
			result.setMessage("用户名可以用");
		}

转成json字符串发送

responseText=JSON.toJSONString(result);

记住用户名

了解cookie的特点后(保存在浏览器),可以通过cookie方式来记住用户名

 			<tr>
 			<td>选项</td>
 			<td>
 			<`input type="checkbox" name="remembername" value="true"/>记住用户名
 			<`input type="checkbox" name="autologin" value="true"/>十天自动登录
 			
 			</td>
 			</tr>

在login.jsp中增加两个checkbox,当选中时,value的值是true
在UserLoginServlet中,写如下代码

//获取记住用户名
String rememberName=req.getParameter("remembername");
//记住用户名功能代码
	if("true".equals(rememberName)){
		System.out.println("选中记住用户名项");
		//开始记住用户名
		Cookie cookie=new Cookie("remname",URLEncoder.encode(uname,"utf-8"));
		cookie.setPath(req.getContextPath()+"/");//把cookie存进根节点
		cookie.setMaxAge(3600*5);//设置cookie存活时间是5个小时
		resp.addCookie(cookie);
}

验证账号密码正确后,得到rememberName的值是true,新建一个cookie,key值是remname,把uname用户名转码成utf-8格式,防止汉字不能识别,然后把cookie加入到response对象中,响应给浏览器
因为在jsp页面中显示出来,所以我们只能用js代码来获取cookie的值,自动添加到text框中

	window.onload=function(){
		//用js代码从cookie中读取cookie的信息
		var uname="${cookie.remname.value}";
		//alert(uname);
		document.forms[0].userName.value=decodeURI(uname);

自动登录

依然用cookie来存储账号密码信息

	<tr>
 			<td>选项</td>
 			<td>
 			<`input type="checkbox" name="remembername" value="true"/>记住用户名
 			<`input type="checkbox" name="autologin" value="true"/>十天自动登录
 			
 			</td>
 			</tr>
//获取10天自动登录是否打勾
		String autologin=req.getParameter("autologin");

打勾后,给servlet的值为true

if("true".equals(autologin)){
	//创建cookie,key是autologin value是用户名#密码
	Cookie cookie=new Cookie("autologin",URLEncoder.encode(uname,"utf-8")+"#"+Base64.getEncoder().encodeToString(upwd.getBytes()));
	cookie.setPath(req.getContextPath()+"/");//把cookie存进根节点
	cookie.setMaxAge(3600*24*10);//设置cookie存活时间是10天
	resp.addCookie(cookie);
}

新建一个Cookie,把autologin作为key存入,把账号(转码后)#密码(base64转码后) 作为value存入
在jsp页面接收

	//处理自动登录
	//取出autologin对应的值,用户名#密码
	var autologin_value="${cookie.autologin.value}";
	if(autologin_value!=""){
		var infos=autologin_value.split("#");
		//把用户名解码后放在用户名文本框中
		document.forms[0].userName.value=decodeURI(infos[0]);
		//把密码用base64解码后放在密码框中
		document.forms[0].userPassword.value=window.atob(infos[1]);	
		document.forms[0].submit();

得到value后记得转码,把账号密码自动加入到文本框中后自动提交给服务器
注意:
需要解决验证码的问题,怎么绕过验证码呢

//获取从客户端发送过来的所有的cookie
		boolean f=false;
		Cookie[] cookies=req.getCookies();
		if(cookies!=null){
			for(Cookie c:cookies){
				if("autologin".equals(c.getName())){
					//说明autologin的cookie是存在的,自动登录
					f=true;
				}
			}
		}
		
		if(f || validateCode.equalsIgnoreCase(code)){
//调用业务
			UserService userService=new UserServiceImpl();
			
			boolean flag=userService.login(user);
			//根据业务的返回结果做响应
			if(flag){
				//记住用户名功能代码
				if("true".equals(rememberName)){
					System.out.println("选中记住用户名项");
					//开始记住用户名
					Cookie cookie=new Cookie("remname",URLEncoder.encode(uname,"utf-8"));
					cookie.setPath(req.getContextPath()+"/");//把cookie存进根节点
					cookie.setMaxAge(3600*5);//设置cookie存活时间是5个小时
					resp.addCookie(cookie);
				}
				if("true".equals(autologin)){
					//创建cookie,key是autologin value是用户名#密码
					Cookie cookie=new Cookie("autologin",URLEncoder.encode(uname,"utf-8")+"#"+Base64.getEncoder().encodeToString(upwd.getBytes()));
					cookie.setPath(req.getContextPath()+"/");//把cookie存进根节点
					cookie.setMaxAge(3600*24*10);//设置cookie存活时间是10天
					resp.addCookie(cookie);
				}
		
		}

servlet获取cookie,查找cookie中是否有自动登录,如果有,就设置boolean变量为true,如果验证码正确或设置了自动登录,都可以进入判断里,安全怎么保证呢,从上面发现第一次只有账号密码验证码正确的情况下才创建记住密码的cookie,所以已经可以正确登录后才记住了密码

登出(去除自动登录)

上面的登出只是去除了session的信息,后面加的自动登录的cookie没有被移出,完善一下代码如下

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//登出后删除自动登录的cookie
		Cookie[] c=request.getCookies();
		if(c!=null){
		for(Cookie cookie:c){
			if("autologin".equals(cookie.getName())||"remname".equals(cookie.getName())){
				cookie.setMaxAge(0);//清除指定的cookie
				cookie.setPath(request.getContextPath()+"/");
				response.addCookie(cookie);
			}
		}
	}
		//另一种删除cookie(不推荐),给autologin设置了一个错误的value(账号密码信息),		//登录失败后自动跳到登录页面
		/*Cookie cookie=new Cookie("autologin","aa");
		cookie.setMaxAge(0);//销毁cookie
		cookie.setPath(request.getContextPath()+"/");
		response.addCookie(cookie);
		response.sendRedirect("login.jsp");*/
		
		//删除session信息
		HttpSession session=request.getSession();
		//session对象存在,session对象中的userName的数据被移除
		session.removeAttribute("userName");
		//另一种写法(不推荐)
		//session.setAttribute("userName", null);
		//session对象不存在了
		session.invalidate();
		response.sendRedirect("login.jsp");	
	}

过滤器

设置编码

因为servlet工作前一定会经过过滤器,所以可以把编码设置在过滤器,完成对编码格式的转换

  <filter>
    <display-name>EncodingFilter</display-name>
    <filter-name>EncodingFilter</filter-name>
    <filter-class>cn.tedu.filter.EncodingFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>EncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

/*代表匹配所有的servlet,jsp
反射实例化EncodingFilter对象

EncodingFilter.java

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request=(HttpServletRequest)servletRequest;
		HttpServletResponse reponse=(HttpServletResponse)servletResponse;
		request.setCharacterEncoding("utf-8");
		reponse.setCharacterEncoding("utf-8");
		chain.doFilter(servletRequest, servletResponse);
	}

设置是否经过登录验证

<filter>
    <display-name>CheckLoginFilter</display-name>
    <filter-name>CheckLoginFilter</filter-name>
    <filter-class>cn.tedu.filter.CheckLoginFilter</filter-class>
    <init-param>
      <param-name>checkSessionKey</param-name>
      <param-value>userName</param-value>
    </init-param>
    <init-param>
      <param-name>redirectURL</param-name>
      <param-value>/login.jsp</param-value>
    </init-param>
    <init-param>
      <param-name>notCheckURL</param-name>
      <param-value>/login.jsp;/register.jsp;/login;/register;/register.js;/CodeServlet;/CheckNameServlet</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CheckLoginFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

上面设置一些局部变量

  • checkSessionKey userName 这个变量相当于人为设置了一个过滤器的开关,当有userName这个值时,过滤器打开,否则过滤器关闭
  • redirectURL /login.jsp 设置重定向的url,登录的url
  • notCheckURL /login.jsp;/register.jsp; /login;/register;/register.js; 设置免过滤的资源,不需要经过过滤

成员变量

//如果没有登录,访问其他资源就跳转到登录页面
	private String redirectURL;
	//session中是否存储用户,如果session中存储了用户名,说明已经登录了
	private String sessionKey=null;
	//所有资源都拦截/过滤,但下面集合中的url是可以通过的
	private List<String> notCheckURLList=new ArrayList<String>();

具体功能

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
		//1.强制类型转换
		HttpServletRequest request=(HttpServletRequest)servletRequest;
		HttpServletResponse response=(HttpServletResponse)servletResponse;
		//2.获取当前浏览器对应的session对象
		HttpSession session=request.getSession();
		//3.如果sessionKey=null,表示直接访问下一个过滤器
		//或者直接请求资源,相当于空走了一个过滤器
		if(this.sessionKey==null||this.sessionKey.equals("")){
			chain.doFilter(servletRequest, servletResponse);
			return;
		}
		//4.检查是否登陆过
		//只要是免检列表之外的和session对象中的以userName为key中没有存用户名
		if(!checkRequestURLNotFilterList(request) && session.getAttribute(sessionKey)==null){
			//能进此处,相当于要调转到login.jsp
			//http://localhost:8080/twp/login.jsp
			response.sendRedirect(request.getContextPath()+this.redirectURL);
			return;
		}
		//5.上面两个if的条件都没有成立,需要访问下一个过滤器
		//可能是登陆过的,也有可能是免检列表之内的
		chain.doFilter(servletRequest, servletResponse);
	}
	private boolean checkRequestURLNotFilterList(HttpServletRequest request ){
		String uri=request.getServletPath()+(request.getPathInfo()==null? "":request.getPathInfo());
		System.out.println(uri);
		return this.notCheckURLList.contains(uri);
	}

	/**
	 * @see Filter#init(FilterConfig)
	 */
	public void init(FilterConfig fConfig) throws ServletException {
		//在此init方法中获取初始化的参数
		//login.jsp
		this.redirectURL=fConfig.getInitParameter("redirectURL");
		//userName
		this.sessionKey=fConfig.getInitParameter("checkSessionKey");
		String notCheckURL=fConfig.getInitParameter("notCheckURL");
		if(notCheckURL!=null){
			//标准写法
			//String[] urls=notCheckURLList.split(";");
			//另一种写法
			StringTokenizer st=new StringTokenizer(notCheckURL,";");
			this.notCheckURLList.clear();
			while(st.hasMoreElements()){
				this.notCheckURLList.add(st.nextToken());
			}
		}
		System.out.println(redirectURL);
		System.out.println(sessionKey);
		System.out.println(this.notCheckURLList);
	}

只有成功登陆过才会有session对象,session属性里存有userName这个key,在session生命周期内就可以直接进入成功登录后的页面,或者注册登录页面及相关资源可以直接免过滤器进入

if(!checkRequestURLNotFilterList(request) && session.getAttribute(sessionKey)==null)

这个判断很巧妙,当是免检登录页面时为false,直接不判断后面就跳出if,向下走,进入请求的jsp,如果不是免检登录页面时为true,此时就需要判断是否有key为userName的session,如果没有,就进入if,如果有,就跳出if,继续访问请求的页面.

监听器

显示在线人数

<div>在线人数:${applicationScope.onLine}</div>
public class OnLineListener implements HttpSessionListener {
	private int count=0;
    /**
     * Default constructor. 
     */
    public OnLineListener() {
       System.out.println("统计在线人数的监听器生命周期开始");
    }

	/**
	 * 只有session创建后才会自动调用此方法
	 * 每执行一次此方法,就要给一个变量累加1
     * @see HttpSessionListener#sessionCreated(HttpSessionEvent)
     */
    public void sessionCreated(HttpSessionEvent se)  { 
        count++;
        //把count的值传送给页面显示,跨浏览器显示,所以要借助application/servletContext
        HttpSession session=se.getSession();
        ServletContext application=session.getServletContext();
        application.setAttribute("onLine", count);
    }

	/**
	 * 只有session销毁才会自动调用此方法
	 * 每执行一次此方法,就要给变量累减1
     * @see HttpSessionListener#sessionDestroyed(HttpSessionEvent)
     */
    public void sessionDestroyed(HttpSessionEvent se)  { 
    	count--;
    	 //把count的值传送给页面显示,跨浏览器显示,所以要借助application/servletContext
        HttpSession session=se.getSession();
        ServletContext application=session.getServletContext();
        application.setAttribute("onLine", count);
        
    }
	
}

只要有人访问此jsp,就+1,如果有人退出,就-1

显示登录人数

<div>在线人数:${applicationScope.onLineUser}</div>

public class OnLineUserListener implements HttpSessionAttributeListener { private int count=0; /** * Default constructor. */ public OnLineUserListener() { System.out.println(“统计登录人数的监听器的生命周期开始”); // TODO Auto-generated constructor stub }

/**
 * 执行session.setAttribute方法时触发此方法
 * @see HttpSessionAttributeListener#attributeAdded(HttpSessionBindingEvent)
 */
public void attributeAdded(HttpSessionBindingEvent event)  { 
	String name=event.getName();
	if("userName".equals(name)){
		count++;
		//把count数据显示在页面
		HttpSession session=event.getSession();
		ServletContext application=session.getServletContext();
		application.setAttribute("onLineUser", count);
	}
     
}

/**
 * 执行session.removeAttribute方法时触发此方法
 * @see HttpSessionAttributeListener#attributeRemoved(HttpSessionBindingEvent)
 */
public void attributeRemoved(HttpSessionBindingEvent event)  { 
	String name=event.getName();
	if("userName".equals(name)){
		count--;
		//把count数据显示在页面
		HttpSession session=event.getSession();
		ServletContext application=session.getServletContext();
		application.setAttribute("onLineUser", count);
	}
}

/**
 * 执行session.replaceAttribute方法时触发此方法
 * @see HttpSessionAttributeListener#attributeReplaced(HttpSessionBindingEvent)
 */
public void attributeReplaced(HttpSessionBindingEvent event)  { 
     // TODO Auto-generated method stub
}

}

有人登录后session会增加一个属性userName,如果有setAttribute方法执行,则+1,有removeAttribute就-1.

导出excel表格

在分页查询页增加一个超链接

 <a href="ExportExcelServlet">全部导出</a><br>

此超链接指向ExportExcelServlet

servlet层

ExportExcelServlet.java

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//1.获取数据
		//2.调用查询所有数据的业务
		UserService userService=new UserServiceImpl();
		byte[] excelData=userService.exportUser();
		//3.相应数据给excel表格
		//把excel的字节数据响应给客户端并下载
		response.setContentType("application/x-msdownload");
		response.setHeader("Content-Disposition", "attachment;filename=alluser.xls");
		response.setContentLength(excelData.length);
		OutputStream os=response.getOutputStream();
		os.write(excelData);
		os.flush();
		os.close();
	}

servlet依然只完成3部分功能,获取数据,调用业务,返回数据

业务逻辑层

@Override
	public byte[] exportUser() {
		byte[] data=null;
		//获取所有的用户数据
		List<User> users=userDao.findAllUsers();
		if(users!=null && users.size()>0){
			data=ExportUtil.write2Excel(users);
		}
		return data;
	}

此工具类把users集合遍历填到excel中,并转成流 ExportUtil.java

/**
 * 导出的工具类
 * @author PC
 *
 */
public class ExportUtil {

	public static byte[] write2Excel(List<User> users) {
		byte[] data=null;
		//创建一个内存字节流,因为wb.write(OutputStream)
		ByteArrayOutputStream out=null;
		try{
			out=new ByteArrayOutputStream();
			//创建工作簿的对象(老版本.xls)
			Workbook wb=new HSSFWorkbook();
			//创建sheet对象
			Sheet sheet=wb.createSheet("allUsers");
			//创建行对象
			Row row=sheet.createRow(0);
			//创建一个字符串数组,用于存储列头信息
			String[] columns=new String[]{"id","用户名","密码","年龄","地址","头像"};
			//给第一行添加列头信息
			for(int i=0;i<6;i++){
				Cell cell=row.createCell(i);
				//设置样式
				cell.setCellType(HSSFCell.CELL_TYPE_STRING);
				sheet.setColumnWidth(i, 6000);
				CellStyle style=wb.createCellStyle();
				Font font=wb.createFont();
				font.setBold(true);
				font.setColor(HSSFColor.RED.index);
				style.setFont(font);
				cell.setCellStyle(style);
				//给单元格添加文字数据
				cell.setCellValue(columns[i]);
			}
			//循环数据,创建excel表格的行
			for(int j=0;j<users.size();j++){
				User user=users.get(j);
				Row r=sheet.createRow(j+1);
				//给当前行的若干添加数据
				r.createCell(0).setCellValue(user.getId());;
				r.createCell(1).setCellValue(user.getName());;
				r.createCell(2).setCellValue(user.getPassword());;
				r.createCell(3).setCellValue(user.getAge());;
				r.createCell(4).setCellValue(user.getAddress());;
				r.createCell(5).setCellValue(user.getHeadimage());;
			}
			wb.write(out);
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(out!=null){
				try {
					out.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		//把内存流中的数据转换成字节数组
		data=out.toByteArray();
		return data;
	}
	

}

响应浏览器

在浏览器点击后,直接下载

显示用户的头像

在usershowbypage.jsp页面添加一列,给user实体类增加了新属性headimage,这个属性存储用户头像文件的名字
在webapp新建一个文件夹images,通过以下方式,把用户的头像显示出来,并增加一个超链接,通过点击超链接来下载用户头像图片

<td>
 			<img src="images/${user.headimage}" width="50px" height="50px"/>
			<a href="DownloadServlet?fileName=${user.headimage}">下载</a> 				
 			</td>

Downloadservlet.java

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		//获取要下载的文件名
				String fileName=request.getParameter("fileName");
				//检测文件在服务器中是否存在
				String realPath=this.getServletContext().getRealPath("/images");
				//拼装一个完整的路径
				String allPath=realPath+File.separator+fileName;
				System.out.println("allPath="+allPath);
				File file=new File(allPath);
				if(file.exists()){
					//文件存在可以下载
					//要想下载文件,必须给response对象设置一个消息头
					response.setHeader("Content-Disposition", "attachment;filename="+new String(fileName.getBytes("utf-8"),"ISO8859-1"));
					//开始下载文件
					OutputStream os=response.getOutputStream();
					//先从服务器本地把文件读入到内存中
					InputStream is=new FileInputStream(file);
					byte[] buf=new byte[1024];
					int len=-1;
					while((len=is.read(buf))!=-1){
						os.write(buf,0,len);
					}
					os.flush();
					os.close();
					is.close();
				}
	}

上传用户头像(可不传)

添加一个input上传文件

<tr>
	<`form action="register" method="POST" enctype="multipart/form-data">

 			<td>头像</td>
 			<td><`input type="file" name="userheadfile"></td>
 		</tr>

提交后,进入UserRegisterServlet中,因为我们用了第三方jar包,所以要在action添加enctype,也不能用原来的方式进行传值,所以以下为修改后
UserRegisterServlet.java

//1.获取注册的数据
		User user=new UploadUtil().uploadFile(request, response);
		//2.注册的业务
		UserService userService=new UserServiceImpl();
		boolean flag=userService.register(user);
		//3.根据业务的返回结果做响应
		if(flag){
			response.sendRedirect("login.jsp");
		}else{
			//删除上传的文件
			String realPath=request.getServletContext().getRealPath("/images");
			File file = new File(realPath,user.getHeadimage());
			if(file.exists()){
				file.delete();
			}
			response.sendRedirect("register.jsp");
		}

获取注册的数据用到了工具类uploadUtil,在这里判断了不选择文件时(文件长度为0),文件名设为default.png
uploadUtil.java

public class UploadUtil {

	private List<String> fileTypes = new ArrayList<String>();

	public User uploadFile(HttpServletRequest request, HttpServletResponse response) {
		User user = new User();

		try {
			String fileName = "";
			//手动添加可接受的文件类型
			fileTypes.add("image/png");
			fileTypes.add("image/jpg");
			fileTypes.add("image/jpeg");
			fileTypes.add("image/bmp");
			response.setContentType("text/html;charset=utf-8");
			PrintWriter out = response.getWriter();
			// Check that we have a file upload request
			// 检查我们有一个文件上传请求
			boolean isMultipart = ServletFileUpload.isMultipartContent(request);
			System.out.println(isMultipart);
			if (isMultipart) {
				// 说明request中有一个文件
				// Create a factory for disk-based file items
				// 创建一个磁盘文件条目工厂,工厂生产对象
				DiskFileItemFactory factory = new DiskFileItemFactory();
				// Create a new file upload handler
				ServletFileUpload upload = new ServletFileUpload(factory);
				// Parse the request
				// 把request中的数据,转换成list集合,元素类型为fileItem
				List<FileItem> items = upload.parseRequest(request);
				// Process the uploaded items
				// 处理上传的条目
				Iterator<FileItem> iter = items.iterator();
				while (iter.hasNext()) {
					FileItem item = iter.next();

					if (item.isFormField()) {
						// 是非文件域,除了文件之外的input
						String name = item.getFieldName();// <input name>
						String value = item.getString();// <input value>
						value = new String(value.getBytes("ISO8859-1"), "utf-8");
						System.out.println(name + " " + value);
						if ("userName".equals(name)) {
							user.setName(value);
						}
						if ("userPassword".equals(name)) {
							user.setPassword(value);
						}
						if ("age".equals(name)) {
							user.setAge(Integer.parseInt(value));
						}
						if ("address".equals(name)) {
							user.setAddress(value);
						}
					} else {
						// 是文件域<input type="file"processUploadFile(item)
						String fieldName = item.getFieldName();
						fileName = item.getName();// 获取真实文件名
						System.out.println(fileName);
						String contentType = item.getContentType();// 获取文件内容类型
						boolean isInMemory = item.isInMemory();// 是否在内存中
						long sizeInBytes = item.getSize();// 获取文件的长度
						System.out.println(
								fieldName + " " + fileName + " " + contentType + " " + isInMemory + " " + sizeInBytes);
						// 处理获取真实的文件名,因为浏览器的差异,导致文件名前带有路径,用此功能去掉路径
						if (fileName != null && fileName != "") {
							fileName = FilenameUtils.getName(fileName);
							fileName = user.getName() + "_headimage." + fileName.split("\\.")[1];
							user.setHeadimage(fileName);
						}
						if (sizeInBytes != 0) {
							if (fileTypes.contains(contentType)) {
								if (sizeInBytes < 4194304) {
									// 准备上传文件在服务器的存储路径
									String realPath = request.getServletContext().getRealPath("/images");
									System.out.println(realPath);
									File file = new File(realPath);
									if (!file.exists()) {
										file.mkdir();
									}
									// 把文件上传到upload文件夹
									File uploadFile = new File(file, fileName);
									item.write(uploadFile);// 把文件写到具体路径
								} else {
									out.println("<div sty='color:red;'>文件太大</div>");
									out.flush();
									out.close();
								}
							} else {
								out.println("<div sty='color:red;'>文件类型不匹配</div>");
								out.flush();
								out.close();
							}
						} else {
							fileName = "default.png";
						}

					}

				}
			}

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return user;
	}
}

用户名重复后,不可提交

在js中增加以下代码就可实现,当返回被占用时,设置属性为disabled就可以了,否则移除

var submit_ele=document.getElementById("submit");
			if(responseText=='{"message":"用户名被占用","status":0}'){
				submit_ele.setAttribute("disabled","disabled");
			}else{
				submit_ele.removeAttribute("disabled");
			}

设置必须填写

在intput中添加required=”required”属性,就不能为空


上一篇 Servlet

Comments

Content