技术选型

Web层

  • Servlet: 前端控制器
  • Html: 视图
  • Filter: 过滤器
  • BeanUtils:数据封装
  • Jackson:Json序列化工具

    Service层

  • Javamail: java发送邮件工具

  • Redis: nosql内存数据库
  • Jedis: javaRedis客户端

    Dao层

  • Mysql:数据库

  • Druid:数据库连接池
  • JdbcTemplate:jdbc工具

创建数据库

功能实现

用户相关功能

注册

  • 功能分析

截屏2021-01-20 11.25.33.png

  • 代码实现
    • 前台效果
      • 表单校验
      • ajax提交
        • 使用异步提交是为了获取服务器响应的数据,因为我们使用的html,不能直接从servlet相关域获取数据,所以需要异步获取数据

截屏2021-01-20 12.39.43.png

  • 后台实现
    • 编写RegistUserServlet
    • 编写UserService以及UserServiceImpl
    • 编写UserDao以及UserDaoImpl

      邮箱激活

  • 修改用户表的status为Y

截屏2021-01-20 15.21.34.png

  • 代码实现

    • ActiveUserServlet
    • UserService
    • UserDao: findByCode updateStatus

      登录

      截屏2021-01-20 18.51.50.png

      index显示用户姓名

      1. $(function () {
      2. $.get("/travel/findUserServlet",{},function (data) {
      3. //响应数据{uid:1,name:''}
      4. var msg = "欢迎回来, "+data.name;
      5. $("#span_username").html(msg);
      6. });
      7. })

      退出

  • 访问servlet,销毁session

  • 跳转到登录页面

    1. //1-销毁session
    2. request.getSession().invalidate();
    3. //2-跳转页面
    4. response.sendRedirect(request.getContextPath()+"/login.html");

    优化Servlet(反射 🌟

  • 减少servlet的数量,目前是一个功能一个Servlet,将其优化为一个模块一个Servlet,相当于在数据库中一张表对应一个Servlet,完成用户需求截屏2021-01-20 21.01.34.png

  • 因为各种功能Servlet,例如(UserServlet,CategoryServlet)继承BaseServlet,所以在BaseServlet获取this时,谁(UserSetvlet\CategoryServlet)调用了BaseServlet,那么这个this就是该对象的class全类名,再通过反射获取该对象当前使用的成员方法,而该成员方法名就通过 request.getRequestURI() 来获取路径然后获取最后一段的成员方法名,再通过invoke方法执行该成员方法来实现方法的分发 ```java public class BaseServlet extends HttpServlet { @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

    1. //完成方法分发
    2. //1-获取请求路径
    3. String uri = req.getRequestURI(); // -> /travel/user/add
    4. System.out.println("请求uri:"+uri);
    5. //2-获取方法名称
    6. String methodName = uri.substring(uri.lastIndexOf('/') + 1);
    7. System.out.println("方法名称:"+methodName);
    8. //3-获取方法对象Method
    9. //谁调用了BaseServlet,this就是该对象的全类名
    10. System.out.println(this);//cn.itcast.travel.web.servlet.UserServlet@1887040
    11. try {
    12. Method method = this.getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
    13. //4-执行方法
    14. method.invoke(this,req,resp);
    15. } catch (NoSuchMethodException e) {
    16. e.printStackTrace();
    17. } catch (IllegalAccessException e) {
    18. e.printStackTrace();
    19. } catch (InvocationTargetException e) {
    20. e.printStackTrace();
    21. }

    } }

<a name="gDlo6"></a>
### 分类信息展示
<a name="qq8il"></a>
#### 获取分类数据
![截屏2021-01-21 13.41.49.png](https://cdn.nlark.com/yuque/0/2021/png/1238354/1611207757311-39ed2a25-05f3-486e-8e20-29c12664d099.png#align=left&display=inline&height=524&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2021-01-21%2013.41.49.png&originHeight=524&originWidth=1610&size=160422&status=done&style=none&width=1610)
```javascript
//查询分类数据
        $.get("/travel/category/findAll",{},function (data) {
            // [{cid:1,cname:"xx"},{},{}...]
            //首页和收藏排行榜是固定的
            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;
            }
            //收藏排行榜
            lis+='<li><a href="favoriterank.html">收藏排行榜</a></li>';

            //将lis字符串设置到ul的html内容中
            $("#category").html(lis);

缓存优化分类数据

  • 分析发现分类数据在每一次页面加载后都会重新请求数据库来加载,对数据库的压力比较大,而且分类的数据不会经常产生变化,所以可以使用redis来缓存这个数据截屏2021-01-21 14.06.12.png
  • 期望数据库存储的顺序就是展示的顺序

    //1-从redis中查询
          //1-1获取jedis
          Jedis jedis = JedisUtil.getJedis();
          //1-2使用sortedset排序
          //Set<String> categorys = jedis.zrange("category", 0, -1);
          //1-3查询sortedset中的score(cid)和值(cname)
          Set<Tuple> categorys = jedis.zrangeWithScores("category", 0, -1);
          //2-判断查询集合是否为空
          List<Category> cs=null;
          if(categorys==null || categorys.size()==0)
          {
              System.out.println("从数据库查询");
              //3-如果为空,第一次访问,需要从数据库查询,再将数据存入redis
              //3-1从数据库查询
              cs = categoryDao.findAll();
              //3-2将集合数据存储到redis中名为 category的key
              for (int i = 0; i < cs.size(); i++) {
                  jedis.zadd("category",cs.get(i).getCid(),cs.get(i).getCname());
              }
          }else {
              System.out.println("从redis查询");
              //4-如果不为空,将set数据存入list,返回
              cs = new ArrayList<Category>();
              for (Tuple tuple : categorys) {
                  //将set数据存入list
                  Category category = new Category();
                  category.setCname(tuple.getElement());
                  category.setCid((int)tuple.getScore());
                  cs.add(category);
              }
    
          }
          return cs;
    
  • 上方代码中在Redis中查询了sortedset的socre,使用的是jedis的zrangWithScore()方法

    旅游线路分页展示

  • 点击不同分类,看到的旅游线路不一样,通过分析数据库表结构,旅游线路表和分类表是多对一的关系

  • select * from tab_route where cid=? 根据点击的分类id获取不同分类数据
  • 使用htmlDOM的location对象获取url路径携带的参数(?cid=x),然后通过分隔字符串获取cid var id = search.split("=")[1];
  • 分析

截屏2021-01-21 15.01.23.png

  • 创建pageBean

    /**
    * 分页对象
    */
    public class PageBean<T> {
      private int totalCount; //总记录数
      private int totalPage; //总页数
      private int currentPage; // 当前页码
      private int pageSize; //每页显示条数
    
      private List<T> list; //每页显示的数据集合
    
      get..set..
    
  • 代码编写

DAO层代码

@Override
    public int findTotalCount(int cid) {
        String sql = "select count(*) from tab_route where cid = ?";
        return template.queryForObject(sql,Integer.class,cid);
    }

    @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);
    }

Service层

 @Override
    public PageBean<Route> pageQuery(int cid, int currentPage, int pageSize) {
        //封装pageBean
        PageBean<Route> pb = new PageBean<Route>();
        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;
    }

前台代码编写

 $(function () {
            var search = location.search;//?cid=x
            //alert(search);
            var cid = search.split("=")[1];//截取cid

            //当页面加载完成后调用load方法,发送ajax请求加载数据
            load(cid);
        });

        function load(cid,currentPage) {
            //发送ajax请求,请求route/pageQuery,传递cid
            $.get("/travel/route/pageQuery",{cid:cid,currentPage:currentPage},function (pb) {
                //解析pageBean数据,展示到页面
                //1-分页条数据展示
                //1.1-展示总页码和总记录书
                $("#totalPage").html(pb.totalPage)
                $("#totalCount").html(pb.totalCount)

                var lis="";
                var firstPage = '<li onclick="javascript:load(' + cid + ')" ><a href="javascript:void">首页</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">上一页</a></li>';
                lis+=firstPage;
                lis+=beforePage;
                //1.2-分页页码
                for (let 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">'+i+'</a></li>';
                    }else {
                        //创建一个页码的li
                        li = '<li onclick="javascript:load(' + cid + ',' + i + ')"><a href="javascript:void">' + i + '</a></li>';
                    }
                    //拼接字符串
                    lis +=li;
                }
                //计算下一页页码
                var nextNum = pb.currentPage +1;
                if(nextNum >= pb.totalPage+1){
                    nextNum=pb.totalPage;
                }
                var lastPage='<li onclick="javascript:load(' + cid + ',' + pb.totalPage + ')" class="threeword"><a href="javascript:void">末页</a></li>';
                var nextPage='<li onclick="javascript:load(' + cid + ',' + nextNum + ')" class="threeword"><a href="javascript:void">下一页</a></li>';
                lis+=nextPage;
                lis+=lastPage;
                //将lis设置到ul中
                $("#pageNum").html(lis);

                //2-list集合数据
                var route_lis ="";
                for (let i = 0; i <pb.list.length ; i++) {
                    //获取{rid:1,rname:"xxx"...}
                    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>&yen;</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);
            });
        }
  • 页码优化,一共展示10个页码,前5后4

    • 前边不足5个,则后边补充
    • 后边不足4个,则前边补充

      //1.2-分页页码
                 //定义开始位置和结束位置
                 var begin;//开始
                 var end;//结束
                 //1要显示10个页码
                 if(pb.totalPage< 10){
                     //总页码小于10的情况
                     begin = 1;
                     end=pb.totalPage;
                 }else{
                     //总页码超过10页
                     begin = pb.currentPage -5;
                     end = pb.currentPage +4;
      
                     //如果前边不够5个,后边补齐
                     if(begin <1){
                         begin =1;
                         end=begin+9;
                     }
                     //后边不够4个,前边补齐
                     if(end >pb.totalPage){
                         end = pb.totalPage;
                         begin = end - 9;
                     }
                 }
                 for (let i = begin; i <=end ; i++) {
                     //将循环判断修改为begin和end
      
  • 最后将页面定位到顶部

    //定位到页面顶部
    window.scrollTo(0,0);