1. 前言
旅游网项目,今日内容主要为优化Servlet、分类数据查询和分页查询。
2. 优化Servlet
2.1 目的
为了减少Servlet的数量,现在是一个功能一个Servlet,将其优化为相同功能的Servlet为一个模块。相当于在数据库中一张表对应一个Servlet,在Servlet中提供不同的方法,完成用户的请求。
2.2 BaseServlet
编写
主要进行方法的分发,利用反射机制,获取请求路径和方法名称,再获取方法对象然后执行。
package cn.itcast.travel.web.servlet;
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 {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("baseServlet的service方法被执行");
// 完成方法分发
// 1. 获取请求路径
String uri = req.getRequestURI(); // uri = /travel/user/add
System.out.println("请求uri:"+uri);
// 2. 获取方法名称
String methodName = uri.substring(uri.lastIndexOf('/') + 1);// 为何+1?
System.out.println("方法名称"+methodName);
// 3. 获取方法对象Method
System.out.println(this); // this表示当前调用service的对象,谁调用我我表示谁
try {
// 获取方法
Method method = this.getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
// 4.执行方法
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
2.3 UserServlet
改写
继承自BaseServlet
,所有用户相关的方法都抽取在内。只需访问user/对应方法
即可完成相应功能。
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 {
// 声明UserService业务对象
private UserService service = new UserServiceImpl();
/**
* @Description: 用户注册功能
* @Author: zero
* @param request
* @param response
* @return void
* @Date 2019/7/23 21:35
*/
public void regist(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 获取用户输入的验证码
String check = request.getParameter("check");
// 从session中获取生成的验证码
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; // todo:为何return?
}
// 验证通过
// 1. 获取前台表单数据
Map<String, String[]> map = request.getParameterMap();
// 2. 封装为user对象
User user = new User();
try {
BeanUtils.populate(user,map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 3. 调用service完成注册
service = new UserServiceImpl();
boolean flag = service.regist(user);
ResultInfo info = new ResultInfo();
// 4. 响应结果
if (flag){
// 注册成功
info.setFlag(true);
}else{
// 注册失败
info.setFlag(false);
info.setErrorMsg("注册失败!");
}
// 将info对象序列化为json
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(info);
// 将json数据回写客户端
// 设置content-type
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
}
/**
* @Description: 邮箱激活功能
* @Author: zero
* @param request
* @param response
* @return void
* @Date 2019/7/23 21:36
*/
public void active(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1. 获取激活码
String code = request.getParameter("code");
if (code != null){
// 2. 调用service完成激活
service = new UserServiceImpl();
boolean flag = service.active(code);
// 3. 判断标记
String msg = null;
if (flag){
// 激活成功
msg = "激活成功,请<a href='login.html'>登录</a>";
}else{
// 激活失败
msg = "激活失败,请联系管理员!";
}
// 回写数据
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(msg);
}
}
/**
* @Description: 用户登录功能
* @Author: zero
* @param request
* @param response
* @return void
* @Date 2019/7/23 21:37
*/
public void login(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1. 获取用户名和密码
Map<String, String[]> map = request.getParameterMap();
// 2. 封装User对象
User user = new User();
try {
BeanUtils.populate(user,map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 3. 调用service查询
service = new UserServiceImpl();
User u = service.login(user);
ResultInfo info = new ResultInfo();
// 4. 判断用户对象是否为null
if (u == null){
// 用户名或密码错误
info.setFlag(false);
info.setErrorMsg("用户名或密码错误");
}
// 5. 判断用户账户是否激活
if (u != null && !"Y".equals(u.getStatus())){
// 用户未激活
info.setFlag(false);
info.setErrorMsg("您的账户尚未激活,请激活");
}
// 6. 判断登录成功
if (u != null && "Y".equals(u.getStatus())){
// 登录成功
info.setFlag(true);
// 将登陆用户信息存入session中
request.getSession().setAttribute("user",u);
}
// 响应数据
ObjectMapper mapper = new ObjectMapper();
response.setContentType("application/json;charset=utf-8");
mapper.writeValue(response.getOutputStream(),info);
}
/**
* @Description: 查询单个用户,前台显示登录用户功能
* @Author: zero
* @param request
* @param response
* @return void
* @Date 2019/7/23 21:38
*/
public void findOne(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 从session中获取登录用户
Object user = request.getSession().getAttribute("user");
// 将user回写客户端
ObjectMapper mapper = new ObjectMapper();
response.setContentType("application/json;charset=utf-8");
mapper.writeValue(response.getOutputStream(),user);
}
/**
* @Description: 用户退出功能
* @Author: zero
* @param request
* @param response
* @return void
* @Date 2019/7/23 21:38
*/
public void exit(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1. 销毁session
request.getSession().invalidate();
// 2. 跳转到登录页面
response.sendRedirect(request.getContextPath()+"/login.html");
}
}
2.4 页面路径改写
将所有用户相关的请求路径改为user/对应方法
。例登录路径:user/login
激活功能URL:String content = "<a href='http://localhost/travel/user/active?code="+user.getCode()+"'>点击激活账户</a>";
3. 分类数据展示
3.1 分析
3.2 后台代码实现
3.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;
/**
* @Author: zero
* @Description: Date:Create: in 2019/7/23 22:51
* Modified By:
*/
@WebServlet("/category/*")
public class CategoryServlet extends BaseServlet {
private CategoryService service = new CategoryServiceImpl();
/**
* @Description:查询所有
* @Author: zero
* @param request
* @param response
* @return void
* @Date 2019/7/23 22:52
*/
public void findAll(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 调用servvice查询所有
List<Category> categories = service.findAll();
// 2. 序列化为json返回
writeValue(categories,response);
}
}
3.2.2 CategoryService
代码
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;
/**
* @Author: zero
* @Description: Date:Create: in 2019/7/23 22:49
* Modified By:
*/
public class CategoryServiceImpl implements CategoryService {
private CategoryDao categoryDao = new CategoryDaoImpl();
@Override
public List<Category> findAll() {
return categoryDao.findAll();
}
}
3.2.3 CategoryDao
代码
package cn.itcast.travel.dao;
import cn.itcast.travel.domain.Category;
import java.util.List;
/**
* @Author: zero
* @Description: Date:Create: in 2019/7/23 22:42
* Modified By:
*/
public interface CategoryDao {
/**
* @Description: 查询所有
* @Author: zero
* @param
* @return List集合
* @Date 2019/7/23 22:44
*/
List<Category> findAll();
}
3.2.4 优化部分代码
将序列化json
方法封装在BaseServlet
,减少代码冗余
/**
* @Description: 直接将传入的对象序列化为json,并且写回客户端
* @Author: zero
* @param obj
* @param response
* @return void
* @Date 2019/7/23 23:16
*/
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);
}
/**
* @Description: 将传入的对象序列化为json后返回
* @Author: zero
* @param obj
* @return String
* @Date 2019/7/23 23:18
*/
public String writeValueAsString(Object obj) throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(obj);
}
3.3 前台代码实现
// 查询分类数据
$.get("category/findAll",{},function (data) {
// [{cid:1,cname:xxx},{cid:2,cname:xxx},]
var lis = '<li class="nav-active"><a href="index.html">首页</a></li>';
// 遍历数组,拼接字符串(<li>)
for (var i = 0; i < data.length; i++) {
var li = '<li><a href="route_list.html">'+data[i].cname+'</a></li>';
lis += li;
}
// 拼接收藏排行榜的li,<li><a href="favoriterank.html"> 收藏排行榜</a></li>
lis+='<li><a href="favoriterank.html">收藏排行榜</a></li>';
// 将lis字符串,设置到ul的html内容中
$("#category").html(lis);
})
3.4 对分类数据进行缓存优化
分类的数据在每一次页面加载后都会重新请求数据库来加载,对数据库的压力比较大,且分类的数据不会经常产生变化,故在此可以使用redis来缓存这个数据。
3.5 优化代码实现
/**
* @Description: 使用redis缓存,优化代码,减少对数据库的访问
* @Author: zero
* @param
* @return List
* @Date 2019/7/24 20:52
*/
@Override
public List<Category> findAll() {
// 初始化集合对象
List<Category> cs = null;
// 1. 从redis中查询数据
// 1.1 获取redis客户端连接对象
Jedis jedis = JedisUtil.getJedis();
// 1.2 使用sortedset排序查询
Set<String> categorys = jedis.zrange("category", 0, -1);
// 2. 判断集合是否为null
if (categorys == null || categorys.size() == 0){
// 2.1 为空,则表示第一次访问
System.out.println("redis中无数据,查询数据库...");
// 2.2 查询数据库,获取数据
cs = categoryDao.findAll();
// 2.3 将集合数据存储到redis中的category的key
for (int i = 0; i < cs.size(); i++) {
jedis.zadd("category",cs.get(i).getCid(),cs.get(i).getCname());
}
}else{
// 3. 不为空,将查询到的set集合数据存入list再返回
// 因返回的是list集合,故将查询到的set集合数据转换为list数据
System.out.println("redis中有数据,查询缓存...");
cs = new ArrayList<Category>();
for (String name : categorys) {
Category category = new Category();
category.setCname(name);
cs.add(category);
}
}
return cs;
}
4. 旅游线路的分页展示
4.1 类别id的传递
- 从
redis
中查询数据是,需要将分数也查询出来;并且在返回list集合里,将cid
的值设置为对应分数
- 前台页面传递对应
cid
,修改header.html
,加上data[i].cid
即可。
var li = '<li><a href="route_list.html?cid='+data[i].cid+'">'+data[i].cname+'</a></li>';
- 获取
cid
$(function(){
var search = location.search;
// alert(search);
// 切割字符串,拿到第二个值
var cid = search.split("=")[1];
});
4.2 根据id查询不同类别的旅游线路
4.2.1 分析
4.2.2 后台代码实现
4.2.2.1 PageBean
对象
后台返回给前台的对象
public class PageBean<T> {
private int totalCount; // 总记录数
private int totalPage; // 总页数
private int currentPage; // 当前页码
private int pageSize; // 每页显示的条数
private List<T> list; // 每页显示的数据集合
// 省略getset方法
}
4.2.2.2 RouteServlet
控制器代码
负责接收前台传递的参数并处理,调用service
查询到PageBean
对象后序列化为json
再返回给前端。
package cn.itcast.travel.web.servlet;
import cn.itcast.travel.domain.PageBean;
import cn.itcast.travel.domain.Route;
import cn.itcast.travel.service.RouteService;
import cn.itcast.travel.service.impl.RouteServiceImpl;
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;
/**
* @Author: zero
* @Description: Date:Create: in 2019/7/24 21:52
* Modified By:
*/
@WebServlet("/route/*")
public class RouteServlet extends BaseServlet {
private RouteService routeService = new RouteServiceImpl();
public void pageQuery(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1. 接受参数
String currentPageStr = request.getParameter("currentPage");
String pageSizeStr = request.getParameter("pageSize");
String cidStr = request.getParameter("cid");
// 2. 处理参数
int cid = 0; // 类别id
if (cidStr != null && cidStr.length() > 0){
cid = Integer.parseInt(cidStr);
}
int currentPage = 0; // 当前页码,如果不传递,则默认为第一页
if (currentPageStr != null && currentPageStr.length() > 0){
currentPage = Integer.parseInt(currentPageStr);
}else {
currentPage = 1;
}
int pageSize = 0; // 每页显示条数,默认为5
if (pageSizeStr != null && pageSizeStr.length() > 0){
pageSize = Integer.parseInt(pageSizeStr);
}else {
pageSize = 5;
}
// 3. 调用service查询PageBean对象
PageBean<Route> pb = routeService.pageQuery(cid, currentPage, pageSize);
// 4. 将pageBean对象序列化为json,返回
writeValue(pb,response);
}
}
4.2.2.3 RouteServiceImpl
代码
根据当前页码,类别信息等,从数据库中查询出当前类别对应数据集合,封装PageBean
对象后返回
package cn.itcast.travel.service.impl;
import cn.itcast.travel.dao.RouteDao;
import cn.itcast.travel.dao.impl.RouteDaoImpl;
import cn.itcast.travel.domain.PageBean;
import cn.itcast.travel.domain.Route;
import cn.itcast.travel.service.RouteService;
import java.util.List;
/**
* @Author: zero
* @Description: Date:Create: in 2019/7/24 22:04
* Modified By:
*/
public class RouteServiceImpl implements RouteService {
private RouteDao routeDao = new RouteDaoImpl();
/**
* @Description: 查询当前页数据集合并封装为PageBean对象后返回
* @Author: zero
* @param cid
* @param currentPage
* @param pageSize
* @return 封装好的PageBean对象
* @Date 2019/7/24 22:50
*/
@Override
public PageBean<Route> pageQuery(int cid, int currentPage, int pageSize) {
// 封装PageBean
PageBean<Route> pb = new PageBean<>();
// 设置当前页码
pb.setCurrentPage(currentPage);
// 设置每页显示的条数
pb.setPageSize(pageSize);
// 设置总记录数
int totalCount = routeDao.findTotalCount(cid);
pb.setTotalCount(totalCount);
// 设置当前页显示的数据集合
int start = (currentPage - 1) * pageSize; // 开始的记录数
List<Route> list = routeDao.findByPage(cid, start, pageSize);
pb.setList(list);
// 设置总页数 = 总记录数 / 每页显示条数
int totalPage = totalCount % pageSize == 0 ? totalCount / pageSize : (totalCount / pageSize) + 1;
pb.setTotalPage(totalPage);
return pb;
}
}
4.2.2.4 RouteDaoImpl
,DAO实现类
查询当前类别总记录数和类别当前页的数据集合
package cn.itcast.travel.dao.impl;
import cn.itcast.travel.dao.RouteDao;
import cn.itcast.travel.domain.Route;
import cn.itcast.travel.util.JDBCUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
/**
* @Author: zero
* @Description: Date:Create: in 2019/7/24 22:09
* Modified By:
*/
public class RouteDaoImpl implements RouteDao {
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
/**
* @Description: 根据cid查询总记录数
* @Author: zero
* @param cid
* @return 总记录数
* @Date 2019/7/24 22:08
*/
@Override
public int findTotalCount(int cid) {
return template.queryForObject("select count(*) from tab_route where cid = ?",Integer.class,cid);
}
/**
* @Description: 根据cid,start,pageSize查询当前页的数据集合
* @Author: zero
* @param cid
* @param start
* @param pageSize
* @return 查询到的当前页数据集合
* @Date 2019/7/24 22:08
*/
@Override
public List<Route> findByPage(int cid, int start, int pageSize) {
String sql = "select * from tab_route where cid = ? limit ? , ?";
return template.query(sql,new BeanPropertyRowMapper<Route>(Route.class),cid,start,pageSize);
}
}
4.2.3 前台代码实现
<script>
$(function () {
var search = location.search;
// alert(search);
// 切割字符串,拿到第二个值
var cid = search.split("=")[1];
// 当页码加载完毕后,调用load方法,
load(cid);
})
function load(cid, currentPage) {
// 发送ajax请求,请求route/pageQuery,传递cid
$.get("route/pageQuery",{cid:cid,currentPage:currentPage},function (pb) {
// 解析PageBean数据,展示到页面上
// 1. 分页工具条数据展示
// 1.1 展示总页码和总记录数
$("#totalPage").html(pb.totalPage);
$("#totalCount").html(pb.totalCount);
var lis = "";
var fristPage = '<li onclick="javascript:load('+cid+')"><a href="javascript:void(0)">首页</a></li>';
// 计算上一页的页码
var beforeNum = pb.currentPage - 1;
if (beforeNum <= 0){
beforeNum = 1;
}
var beforePage = '<li onclick="javascript:load('+cid+','+beforeNum+')" class="threeword"><a href="javascript:void(0)">上一页</a></li>';
lis += fristPage;
lis += beforePage;
// 1.2 展示分页页码
/*
仿百度分页样式:前五后四
1. 一共展示10个页码,能够达到前5后4的效果
2. 如果前边不足5个,后边补齐10个
3. 如果后边不足4个,前边补齐10个
*/
// 定义开始位置begin和结束位置end
var begin; // 开始位置
var end; // 结束位置
// 1. 显示10个页码
if(pb.totalPage < 10){
// 总页码不足10页
begin = 1;
end = pb.totalPage;
}else{
// 总页码超过10页
// 实现前5后4效果
begin = pb.currentPage - 5;
end = pb.currentPage + 4;
// 进行数据修正
// 2. 如果前边不够5个,后边补齐10个
if (begin < 1){
begin = 1;
end = begin + 9;
}
// 3. 如果后边不足4个,前边补齐10个
if (end > pb.totalPage){
end = pb.totalPage;
begin = end - 9;
}
}
for (var i = begin; i <= end; i++) {
var li;
// 判断当前页码是否等于i
if (pb.currentPage == i){
li = '<li class="curPage" onclick="javascript:load('+cid+','+i+')"><a href="javascript:void(0)">'+i+'</a></li>';
}else{
// 创建页码的li
li = '<li onclick="javascript:load('+cid+','+i+')"><a href="javascript:void(0)">'+i+'</a></li>';
}
// 拼接进字符串
lis += li;
}
// for (var i = 1; i <= pb.totalPage ; i++) {
// var li;
// // 判断当前页码是否等于i
// if (pb.currentPage == i){
// li = '<li class="curPage" onclick="javascript:load('+cid+','+i+')"><a href="javascript:void(0)">'+i+'</a></li>';
// }else{
// // 创建页码的li
// li = '<li onclick="javascript:load('+cid+','+i+')"><a href="javascript:void(0)">'+i+'</a></li>';
// }
// // 拼接进字符串
// lis += li;
// }
// 计算下一页页码
var nextNum = pb.currentPage + 1;
if(nextNum >= pb.totalPage){
nextNum = pb.totalPage;
}
var nextPage = '<li onclick="javascript:load('+cid+','+nextNum+')" class="threeword"><a href="javascript:void(0);">下一页</a></li>';
var lastPage = '<li onclick="javascript:load('+cid+','+pb.totalPage+')" class="threeword"><a href="javascript:;">末页</a></li>';
lis += nextPage;
lis += lastPage;
// 将lis内容设置到ul中
$("#pageNum").html(lis);
// 2. 列表数据展示
var route_lis = "";
for (var i = 0; i < pb.list.length; i++) {
// 获取数据 {rid": 1,"rname": "xxx","price": 999.0,xxxx}
var route = pb.list[i];
var li = '<li>\n' +
' <div class="img"><img src="'+route.rimage+'" style="width: 299px"></div>\n' +
' <div class="text1">\n' +
' <p>'+route.rname+'</p>\n' +
' <br/>\n' +
' <p>'+route.routeIntroduce+'</p>\n' +
' </div>\n' +
' <div class="price">\n' +
' <p class="price_num">\n' +
' <span>¥</span>\n' +
' <span>'+route.price+'</span>\n' +
' <span>起</span>\n' +
' </p>\n' +
' <p><a href="route_detail.html">查看详情</a></p>\n' +
' </div>\n' +
' </li>';
route_lis += li;
}
$("#route").html(route_lis);
// 每次点击翻页后,定位到页面顶部
window.scrollTo(0,0);
});
}
</script>