5 登录
5.1 分析
用户输入信息点击登录按钮后,向后台发送ajax请求,调用 LoginServlet ,实现以下几个主要功能:
1.获取用户输入的信息
2.调用service层查询该用户是否存在
3.如果该用户存在且已激活则跳转至主页 index.html
4.否则输出错误信息
5.2 前台代码
5.2.1 登录页面login.html
<script> $(function () { // 登录按钮绑定单击事件 $("#btn_sub").click(function () { // 发送ajax请求 $.post("loginServlet",$("#loginForm").serialize(),function (data) { // data:{flag:false,errorMsg:''} if (data.flag){ location.href = "index.html"; }else { $("#errorMsg").html(data.errorMsg); } }); }); }); </script>
5.3 后台代码
5.3.1 LoginServlet
根据5.1分析部分的描述,servlet的代码如下:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 先校验验证码 String check = request.getParameter("check"); HttpSession session = request.getSession(); String checkcode_server = (String)session.getAttribute("CHECKCODE_SERVER"); session.removeAttribute("CHECKCODE_SERVER"); // 保证验证码只能使用一次 if (checkcode_server == null || !checkcode_server.equalsIgnoreCase(check)){ ResultInfo info = new ResultInfo(); info.setFlag(false); info.setErrorMsg("验证码错误"); // 将info序列化为json ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(info); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json); return; } // 获取用户名密码 Map<String, String[]> map = request.getParameterMap(); User user = new User(); try { BeanUtils.populate(user, map); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } UserService service = new UserServiceImpl(); User u = service.login(user); ResultInfo info = new ResultInfo(); if (u == null){ info.setFlag(false); info.setErrorMsg("用户名或密码错误"); } if (u != null && !"Y".equals(u.getStatus())){ info.setFlag(false); info.setErrorMsg("您尚未激活,请登录邮箱激活"); } if (u != null && "Y".equals(u.getStatus())){ info.setFlag(true); session.setAttribute("user", u); } ObjectMapper mapper = new ObjectMapper(); response.setContentType("application/json;charset=utf-8"); mapper.writeValue(response.getOutputStream(), info); }
5.3.2 service层 Userservice
servlet要调用该函数完成用户的查询,用户只输入了用户名和密码,所以就根据这两个值进行查询
@Override public User login(User user) { return userDao.findByUsernameAndPassword(user.getUsername(), user.getPassword()); }
5.3.3 dao层 UserDao
注意:如果用户输入了错误的用户名或密码导致查询数据库失败的话,template.queryForObject 方法会报错,所以这里应该将异常catch一下,使得查询失败时返回一个空 User 对象
@Override public User findByUsernameAndPassword(String username, String password) { User user = null; try { String sql = "select * from tab_user where username = ? and password = ?"; // 第二个参数是指定返回结果要封装成的类型 user = template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), username, password); } catch (Exception e) { } return user; }
5.3.4 登录成功后右上角显示用户姓名
index.html中,页面的头部和尾部的部分是由include.js动态加载的
<script type="text/javascript" src="js/include.js"></script>
观察include.js
$(function () { $.get("header.html",function (data) { $("#header").html(data); }); $.get("footer.html",function (data) { $("#footer").html(data); }); });
可以看出是页面加载完成后再动态加载头部和尾部的信息的(分别封装在header.html和footer.html)
所以在header.html中添加发送异步请求查询当前登录用户的代码
<script> $(function () { $.get("findUserServlet",[],function (data) { // {uid:1,name:"李四"} var msg = "欢迎回来," + data.name; $("#span_username").html(msg); }); }); </script>
相应的servlet,从session中获取user对象,以json格式写回前端
@WebServlet("/findUserServlet") public class FindUserServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Object user = request.getSession().getAttribute("user"); //将user写回客户端 ObjectMapper mapper = new ObjectMapper(); response.setContentType("application/json;charset=utf-8"); mapper.writeValue(response.getOutputStream(),user); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } }
6 退出登录
6.1 分析
session中有user对象说明用户登陆了,所以退出登录的实现可以如下:
1.访问servlet,将session销毁
2.跳转到登录页面
6.2 代码
6.2.1 为header.html中退出按钮添加事件
<a href="javascript:location.href = 'exitServlet';">退出</a>
6.2.2 退出操作服务端 ExitServlet
注意response重定向要加虚拟目录
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 销毁session request.getSession().invalidate(); // 跳转 response.sendRedirect(request.getContextPath() + "/login.html"); }
7 优化服务端servlet代码
7.1 目的
减少Servlet的数量,现在是一个功能一个Servlet,将其优化为一个模块一个Servlet,相当于在数据库中一张表对应一个Servlet,在Servlet中提供不同的方法,完成用户的请求。
由于UserServlet继承了BaseServlet,BaseServlet又继承了HttpServlet所以当浏览器访问UserServlet时会自动调用BaseServlet的service方法,实现分发。
Idea控制台中文乱码解决:-Dfile.encoding=gb2312
7.2 分发类BaseServlet
首先获取方法名,然后根据方法名采用反射机制加载UserServlet中相应的方法,并传入response和request来调用,但注意servlet中的方法是由protected修饰的,所以在使用反射机制加载方法时应当忽略访问修饰符。并在执行方法之前进行暴力反射。
Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); method.setAccessible(true); method.invoke(this,req,resp);
但更好的方法是将UserServlet中的方法修饰符改为public
package cn.itcast.travel.web.servlet; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; // 不需要被浏览器输入访问到 public class BaseServlet extends HttpServlet { // 访问UserServlet是自动执行该方法 // 完成方法分发 @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String uri = req.getRequestURI();// /travel/user/add // 获取方法名称 String methodName = uri.substring(uri.lastIndexOf("/") + 1); try { // this这里代表的是调用 BaseServlet 的 UserServlet Method method = this.getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); method.invoke(this,req,resp); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } /** * 将传入的对象序列化为json并写会客户端 * @param obj */ public void writeValue(Object obj, HttpServletResponse response) throws IOException { ObjectMapper mapper = new ObjectMapper(); response.setContentType("application/json;charset=utf-8"); mapper.writeValue(response.getOutputStream(), obj); } /** * 将传入的对象序列化为json返回 * @param obj * @return */ public String writeValueAsString(Object obj) throws JsonProcessingException { ObjectMapper mapper = new ObjectMapper(); return mapper.writeValueAsString(obj); } }
7.3 提供用户服务的UserServlet
只需创建几个方法
并将之前写过的相应Servlet的逻辑复制到方法里面即可,具体代码如下(就是将前面的servlet做一个整合):
package cn.itcast.travel.web.servlet; import cn.itcast.travel.domain.ResultInfo; import cn.itcast.travel.domain.User; import cn.itcast.travel.service.UserService; import cn.itcast.travel.service.impl.UserServiceImpl; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.beanutils.BeanUtils; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.Map; @WebServlet("/user/*") public class UserServlet extends BaseServlet { private UserService service = new UserServiceImpl(); /** * 注册功能 * @param request * @param response * @throws ServletException * @throws IOException */ public void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("regist"); // 先校验验证码 String check = request.getParameter("check"); HttpSession session = request.getSession(); String checkcode_server = (String)session.getAttribute("CHECKCODE_SERVER"); session.removeAttribute("CHECKCODE_SERVER"); // 保证验证码只能使用一次 if (checkcode_server == null || !checkcode_server.equalsIgnoreCase(check)){ ResultInfo info = new ResultInfo(); info.setFlag(false); info.setErrorMsg("验证码错误"); // 将info序列化为json ObjectMapper mapper = new ObjectMapper(); String json = writeValueAsString(info); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json); return; } // 获取数据 Map<String, String[]> map = request.getParameterMap(); // 封装 User user = new User(); try { BeanUtils.populate(user,map); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } // 调用service层 boolean flag = service.regist(user); // 响应结果 ResultInfo info = new ResultInfo(); if (flag){ info.setFlag(true); }else { info.setFlag(false); info.setErrorMsg("注册失败"); } // 将info序列化为json ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(info); // 将json写回客户端 response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json); } /** * 登录功能 * @param request * @param response * @throws ServletException * @throws IOException */ public void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("login"); // 先校验验证码 String check = request.getParameter("check"); HttpSession session = request.getSession(); String checkcode_server = (String)session.getAttribute("CHECKCODE_SERVER"); session.removeAttribute("CHECKCODE_SERVER"); // 保证验证码只能使用一次 if (checkcode_server == null || !checkcode_server.equalsIgnoreCase(check)){ ResultInfo info = new ResultInfo(); info.setFlag(false); info.setErrorMsg("验证码错误"); // 将info序列化为json ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(info); response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json); return; } // 获取用户名密码 Map<String, String[]> map = request.getParameterMap(); User user = new User(); try { BeanUtils.populate(user, map); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } User u = service.login(user); ResultInfo info = new ResultInfo(); if (u == null){ info.setFlag(false); info.setErrorMsg("用户名或密码错误"); } if (u != null && !"Y".equals(u.getStatus())){ info.setFlag(false); info.setErrorMsg("您尚未激活,请登录邮箱激活"); } if (u != null && "Y".equals(u.getStatus())){ info.setFlag(true); session.setAttribute("user", u); } writeValue(info, response); } /** * 查询单个用户 * @param request * @param response * @throws ServletException * @throws IOException */ public void findOne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("findOne"); Object user = request.getSession().getAttribute("user"); //将user写回客户端 writeValue(user, response); } /** * 退出功能 * @param request * @param response * @throws ServletException * @throws IOException */ public void exit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("exit"); // 销毁session request.getSession().invalidate(); // 跳转 response.sendRedirect(request.getContextPath() + "/login.html"); } /** * 激活功能 * @param request * @param response * @throws ServletException * @throws IOException */ public void active(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("active"); // 获取激活码 String code = request.getParameter("code"); if (code != null){ // 激活 boolean flag = service.active(code); // 判断标记 String msg; if (flag){ msg = "激活成功,请<a href = 'http://localhost/travel/login.html'>登录</a>"; }else { msg = "激活失败,请联系管理员"; } response.setContentType("text/html;charset=utf-8"); response.getWriter().write(msg); } } }
7.4 修改html页面中之前写的servlet的路径
7.4.1 登录页面login.html
$.post("user/login",$("#loginForm").serialize(),function (data) { // data:{flag:false,errorMsg:''} if (data.flag){ location.href = "index.html"; }else { $("#errorMsg").html(data.errorMsg); } });
7.4.2 头部页面header.html
$.get("user/findOne",[],function (data) { // {uid:1,name:"李四"} var msg = "欢迎回来," + data.name; $("#span_username").html(msg); });
7.4.3 注册页面regist.html
$.post("user/regist",$(this).serialize(),function (data) { // 处理服务器响应的数据 if (data.flag){ // 注册成功 location.href = "register_ok.html"; }else { $("#errorMsg").html(data.errorMsg); } });
7.4.4 service层UserServiceImpl
修改邮件的激活路径
String content = "<a href = 'http://localhost/travel/user/active?code="+ user.getCode() +"'>点击激活</a>";
8 分类数据展示
网页顶部导航栏
8.1 分析
用户点击主页导航栏上的按钮,如:国内游,之后向后台发送ajax请求,调用相应的 CategoryServlet 类中的 findAll 方法查询所有的数据以json格式返回,前台循环遍历结果数组并展示。
servlet调用service层,service层调用dao层,都是最简单的查询所有sql语句。
8.2 后台代码
8.2.1 CategoryServlet
package cn.itcast.travel.web.servlet; import cn.itcast.travel.domain.Category; import cn.itcast.travel.service.CategoryService; import cn.itcast.travel.service.impl.CategoryServiceImpl; import com.fasterxml.jackson.databind.ObjectMapper; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.List; @WebServlet("/category/*") public class CategoryServlet extends BaseServlet { private CategoryService service = new CategoryServiceImpl(); /** * 查询所有 * @param request * @param response * @throws ServletException * @throws IOException */ public void findAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Category> cs = service.findAll(); writeValue(cs, response); } }
8.2.2 service层CategoryService
接口
package cn.itcast.travel.service; import cn.itcast.travel.domain.Category; import java.util.List; public interface CategoryService { public List<Category> findAll(); }
实现类
package cn.itcast.travel.service.impl; import cn.itcast.travel.dao.CategoryDao; import cn.itcast.travel.dao.impl.CategoryDaoImpl; import cn.itcast.travel.domain.Category; import cn.itcast.travel.service.CategoryService; import java.util.List; public class CategoryServiceImpl implements CategoryService { private CategoryDao categoryDao = new CategoryDaoImpl(); @Override public List<Category> findAll() { return categoryDao.findAll(); } }
8.2.3 CategoryDao
接口
package cn.itcast.travel.dao; import cn.itcast.travel.domain.Category; import java.util.List; public interface CategoryDao { /** * 查询所有分类 * @return */ public List<Category> findAll(); }
实现类
package cn.itcast.travel.dao.impl; import cn.itcast.travel.dao.CategoryDao; import cn.itcast.travel.domain.Category; import cn.itcast.travel.util.JDBCUtils; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import java.util.List; public class CategoryDaoImpl implements CategoryDao { private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource()); @Override public List<Category> findAll() { String sql = "select * from tab_category"; return template.query(sql, new BeanPropertyRowMapper<>(Category.class)); } }
8.3 前台代码
8.3.1 header.html
页面加载后,发送ajax请求,请求路径:category/findAll
// 查询分类列表 $.get("category/findAll",[],function (data) { // [{cid:1,cname="国内游"},{cid:2,cname="国外游"}] var lis = '<li class="nav-active"><a href="index.html">首页</a></li>'; // 遍历数组,字符串拼接 for (var i = 0; i < data.length; i++) { var li = '<li><a href="route_list.html">' + data[i].cname + '</a></li>'; lis += li; } lis += '<li><a href="favoriterank.html">收藏排行榜</a></li>'; // 将lis设置的ul中 $("#category").html(lis); });
8.4 对分类数据进行缓存优化
分类的数据在每一次页面加载后都会重新请求数据库来加载,对数据库的压力比较大,而且分类的数据不会经常产生变化,所以可以使用redis来缓存这个数据。
在CategoryService中可以添加redis缓存操作,先从redis中查询数据,如果没有相应的数据则再调用dao层从mysql中取出数据存入缓存中并返回集合
操作redis数据库的jedis工具类:
package cn.itcast.travel.util; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.io.IOException; import java.io.InputStream; import java.util.Properties; /** * Jedis工具类 */ public final class JedisUtil { private static JedisPool jedisPool; static { //读取配置文件 InputStream is = JedisPool.class.getClassLoader().getResourceAsStream("jedis.properties"); //创建Properties对象 Properties pro = new Properties(); //关联文件 try { pro.load(is); } catch (IOException e) { e.printStackTrace(); } //获取数据,设置到JedisPoolConfig中 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(Integer.parseInt(pro.getProperty("maxTotal"))); config.setMaxIdle(Integer.parseInt(pro.getProperty("maxIdle"))); //初始化JedisPool jedisPool = new JedisPool(config, pro.getProperty("host"), Integer.parseInt(pro.getProperty("port"))); } /** * 获取连接方法 */ public static Jedis getJedis() { return jedisPool.getResource(); } /** * 关闭Jedis */ public static void close(Jedis jedis) { if (jedis != null) { jedis.close(); } } }
期望数据中存储的顺序就是将来展示的顺序,使用redis的sortedset
还需要注意,如果使用redis中的sortedset,则取出来的是Set,由于findAll()方法返回的需要是List,所以需要手动做一个转换。如果除了取出sortedset中存储的数据之外,还要取出每一条数据对应的score,则需要zrangeWithScores方法,返回的Set中存储了Tuple类型,Tuple有两个成员变量,一个element对应存进去的数据,一个score对应sortedset的score
@Override public List<Category> findAll() { // 查redis Jedis jedis = JedisUtil.getJedis(); // 查询sortedset中的分数(cid)和值(cname) Set<Tuple> categorys = jedis.zrangeWithScores("category", 0, -1); List<Category> cs = null; // 没有命中查mysql if (categorys == null || categorys.size() == 0){ System.out.println("数据库查询"); cs = categoryDao.findAll(); // 存入redis for (int i = 0; i < cs.size(); i ++){ // 以id作为排序的分数 jedis.zadd("category",cs.get(i).getCid(),cs.get(i).getCname()); } }else { // 返回的需要是list System.out.println("redis查询"); cs = new ArrayList<Category>(); for (Tuple tuple : categorys) { Category category = new Category(); category.setCname(tuple.getElement()); category.setCid((int)tuple.getScore()); cs.add(category); } } return cs; }