JavaWeb综合旅游网项目

本示例为JavaWeb前后端结合综合示例,供各位平时参考练习使用,功能不断完善中,之后可直接部署在服务器,文末会给出源码链接。

使用Maven来构建项目,使用时直接导入pom.xml,运行命令:tomcat7:run

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>cn.itcast</groupId>
  <artifactId>travel</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <!--servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>


        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.26</version>
            <scope>compile</scope>
        </dependency>
        <!--druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
        <!--jdbcTemplate-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.1.2.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.1.2.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.1.2.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.1.2.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.1</version>
            <scope>compile</scope>
        </dependency>
        <!--beanUtils-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.2</version>
            <scope>compile</scope>
        </dependency>
        <!--jackson-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.3.3</version>
        </dependency>


        <!--javaMail-->
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>javax.mail-api</artifactId>
            <version>1.5.6</version>
        </dependency>
        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>1.5.3</version>
        </dependency>
        <!--jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.7.0</version>
        </dependency>

    </dependencies>


    <build>
        <!--maven插件-->
        <plugins>
            <!--jdk编译插件-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>utf-8</encoding>
                </configuration>
            </plugin>
            <!--tomcat插件-->
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <!-- tomcat7的插件, 不同tomcat版本这个也不一样 -->
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <!-- 通过maven tomcat7:run运行项目时,访问项目的端口号 -->
                    <port>80</port>
                    <!-- 项目访问路径  本例:localhost:9090,  如果配置的aa, 则访问路径为localhost:9090/aa-->
                    <path>/travel</path>
                </configuration>
            </plugin>

        </plugins>
    </build>
</project> 

1 技术栈

1.1 web层

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

1.2 Service层

  • Javamail:java发送邮件工具
  • Redis:nosql内存数据库
  • Jedis:java的redis客户端

1.3 Dao层

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

整体设计模式遵循MVC三层模型

2 数据库

数据库travel.sql文件

链接:https://pan.baidu.com/s/1XJtpYZZ_svFQYAHhcf0IWQ 
提取码:suwy

-- 创建数据库
CREATE DATABASE travel;
-- 使用数据库
USE travel;
--创建表

设计逻辑如下: 

3 页面注册功能

3.1 页面效果

 

3.2 功能分析

主页面先使用js完成表单校验,校验完成之后向后台 registUserServlet 发送ajax异步请求完成表单的提交,如果注册成功则跳至成功页面。

registUserServlet 将提交的表单的数据封装成User对象,并调用 service 层的 UserService 传入该对象尝试注册,根据注册结果写回提示信息。

UserService 调用 dao 层的 UserDao 查询信提交的用户是否已经注册过了,如果已经注册过则直接返回false,否则调用 dao 层保存新用户数据,完成注册。

UserDao 就是两个简单的sql指令:通过用户名查询用户、保存新用户。

3.3 代码

根据以上分析可以快速完成代码如下:

3.3.1 register.html

register.html页面添加表单校验

<script>
   /*
      表单校验:
         1.用户名:单词字符,长度8到20位
         2.密码:单词字符,长度8到20位
         3.email:邮件格式
         4.姓名:非空
         5.手机号:手机号格式
         6.出生日期:非空
         7.验证码:非空
    */

   // 校验用户名
   function checkUsername() {
      var username = $("#username").val();
      // 正则
      var reg_username = /^\w{8,20}$/;
      var flag = reg_username.test(username);
      if (flag){
         $("#username").css("border","");
      }else {
         $("#username").css("border","1px solid red");
      }
      return flag;
   }

   // 校验密码
   function checkPassword() {
      var password = $("#password").val();
      // 正则
      var reg_password = /^\w{8,20}$/;
      var flag = reg_password.test(password);
      if (flag){
         $("#password").css("border","");
      }else {
         $("#password").css("border","1px solid red");
      }
      return flag;
   }

   // 校验邮箱
   function checkEmail(){
      var email = $("#email").val();
      // 正则
      var reg_email = /^\w+@\w+\.\w+$/;
      var flag = reg_email.test(email);
      if (flag){
         $("#email").css("border","");
      }else {
         $("#email").css("border","1px solid red");
      }
      return flag;
   }

   // 校验姓名
   function checkName(){
      var name = $("#name").val();

      var reg_name = /^\w+$/;
      var flag = reg_name.test(name);

      if (flag){
         $("#name").css("border","");
      }else {
         $("#name").css("border","1px solid red");
      }
      return flag;
   }

   // 校验手机号
   function checkTelephone(){
      var telephone = $("#telephone").val();

      var reg_telephone = /^1[3456789]\d{9}$/;
      var flag = reg_telephone.test(telephone);

      if (flag){
         $("#telephone").css("border","");
      }else {
         $("#telephone").css("border","1px solid red");
      }
      return flag;
   }

   // 校验生日
   function checkBirthday(){
      var birthday = $("#birthday").val();
      var flag = (birthday != []);

      if (flag){
         $("#birthday").css("border","");
      }else {
         $("#birthday").css("border","1px solid red");
      }
      return flag;
   }

   // 校验验证码
   function checkCheck(){
      var check = $("#check").val();
      var flag = (check != []);

      if (flag){
         $("#check").css("border","");
      }else {
         $("#check").css("border","1px solid red");
      }
      return flag;
   }


   $(function () {
      //当表单提交时,调用所有的校验方法
      $("#registerForm").submit(function(){
         return  checkUsername() && checkPassword() && checkEmail() &&
               checkName() && checkTelephone() && checkBirthday() &&
               checkCheck();
      });

      //当某一个组件失去焦点时,调用对应的校验方法
      $("#username").blur(checkUsername);
      $("#password").blur(checkPassword);
      $("#email").blur(checkEmail);
      $("#name").blur(checkName);
      $("#telephone").blur(checkTelephone);
      $("#birthday").blur(checkBirthday);
      $("#check").blur(checkCheck);
   });
</script> 

异步(ajax)提交表单

在此使用异步提交表单是为了获取服务器响应的数据。由于前台使用的是html作为视图层,不能够直接从servlet相关的域对象获取值,只能通过ajax获取响应数据。

jquery的serialize()方法可以将表单的数据序列化为 username=zhangsan$password=123 格式的字符串形式

//当表单提交时,调用所有的校验方法
$("#registerForm").submit(function(){
   if (checkUsername() && checkPassword() && checkEmail()){
      $.post("registUserServlet",$(this).serialize(),function (data) {
         // 处理服务器响应的数据
      });
   }
   // 防止页面跳转
   return  false;
});

3.3.2 RegistUserServlet

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 获取数据
    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层
    UserService service = new UserServiceImpl();
    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);
}

3.3.3 UserService以及UserServiceImpl

package cn.itcast.travel.service;

import cn.itcast.travel.domain.User;

public interface UserService {
    /**
     * 注册用户方法
     * @param user
     * @return
     */
    boolean regist(User user);
}
package cn.itcast.travel.service.impl;

import cn.itcast.travel.dao.UserDao;
import cn.itcast.travel.dao.impl.UserDaoImpl;
import cn.itcast.travel.domain.User;
import cn.itcast.travel.service.UserService;

public class UserServiceImpl implements UserService {

    private UserDao userDao = new UserDaoImpl();

    /**
     * 注册用户方法
     * @param user
     * @return
     */
    @Override
    public boolean regist(User user) {
        // 根据用户名查询
        User u = userDao.findByUsername(user.getUsername());
        if (u != null){
            // 用戶名存在,注册失败
            return false;
        }
        // 保存用户信息
        userDao.save(user);
        return true;
    }
}

3.3.4 UserDao以及UserDaoImpl

package cn.itcast.travel.dao;

import cn.itcast.travel.domain.User;

public interface UserDao {
    /**
     * 根据用户名查询用户信息
     * @param username
     * @return
     */
    public User findByUsername(String username);

    /**
     * 保存用户
     * @param user
     */
    public void save(User user);
}
package cn.itcast.travel.dao.impl;

import cn.itcast.travel.dao.UserDao;
import cn.itcast.travel.domain.User;
import cn.itcast.travel.util.JDBCUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

public class UserDaoImpl implements UserDao {

    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    @Override
    public User findByUsername(String username) {
        User user = null;
        try {
            String sql = "select * from tab_user where username = ?";
            // 第二个参数是指定返回结果要封装成的类型
            user = template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), username);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return user;
    }

    @Override
    public void save(User user) {
        String sql = "insert into tab_user(username,password,name,birthday,sex,telephone,email)" +
                "values(?,?,?,?,?,?,?)";
        template.update(sql, user.getUsername(),
                user.getPassword(),
                user.getName(),
                user.getBirthday(),
                user.getSex(),
                user.getTelephone(),
                user.getEmail());
    }
}

注意:在 findByUsername 方法中,如果根据传入的username没有查询到的话,会报错而不是返回null,所以需要抓一下异常,使出现异常时返回null

注意:JDBCUtils中的代码

InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties");

最后是"druid.properties"而不是"/druid.properties"

3.3.5 添加验证码校验

响应数据对象 ResultInfo ,将相应数据封装为一个对象,便于操作

package cn.itcast.travel.domain;

import java.io.Serializable;
import java.util.Objects;

/**
 * 用于封装后端返回前端数据对象
 */
public class ResultInfo implements Serializable {
    private boolean flag;//后端返回结果正常为true,发生异常返回false
    private Object data;//后端返回结果数据对象
    private String errorMsg;//发生异常的错误消息

    //无参构造方法
    public ResultInfo() {
    }
    public ResultInfo(boolean flag) {
        this.flag = flag;
    }
    /**
     * 有参构造方法
     * @param flag
     * @param errorMsg
     */
    public ResultInfo(boolean flag, String errorMsg) {
        this.flag = flag;
        this.errorMsg = errorMsg;
    }
    /**
     * 有参构造方法
     * @param flag
     * @param data
     * @param errorMsg
     */
    public ResultInfo(boolean flag, Object data, String errorMsg) {
        this.flag = flag;
        this.data = data;
        this.errorMsg = errorMsg;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

RegistUserServlet.javadoPost() 方法的获取数据之前的部分添加验证码校验功能

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

3.3.6 register.html 页面添加处理服务器响应的数据的逻辑

$("#registerForm").submit(function(){
   if (checkUsername() && checkPassword() && checkEmail()){
      $.post("registUserServlet",$(this).serialize(),function (data) {
         // 处理服务器响应的数据
         if (data.flag){
            // 注册成功
            location.href = "register_ok.html";
         }else {
            $("#errorMsg").html(data.errorMsg);
         }
      });
   }
   // 防止页面跳转
   return  false;
});

4 邮件激活

为了保证用户填写的邮箱是正确的。将来可以推广一些宣传信息,到用户邮箱中。

4.1 发送邮件

  1. 申请邮箱(这里直接用了qq邮箱,个人感觉操作起来比较方便)
  2. 开启授权码
  3. 在MailUtils中设置自己的邮箱账号和密码(授权码)

邮件工具类:MailUtils,调用其中 sendMail 方法可以完成邮件发送。

需传入目标邮箱、邮件内容以及邮件标题

package cn.itcast.travel.util;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

/**
 * 发邮件工具类
 */
public final class MailUtils {
    private static final String USER = "[email protected]"; // 发件人称号,同邮箱地址
    private static final String PASSWORD = "pzkiehtlulvyijbj"; // 如果是qq邮箱可以使户端授权码,或者登录密码

    /**
     *
     * @param to 收件人邮箱
     * @param text 邮件正文
     * @param title 标题
     */
    /* 发送验证信息的邮件 */
    public static boolean sendMail(String to, String text, String title){
        try {
            final Properties props = new Properties();
            props.put("mail.smtp.auth", "true");
            props.put("mail.smtp.host", "smtp.qq.com");

            // 发件人的账号
            props.put("mail.user", USER);
            //发件人的密码
            props.put("mail.password", PASSWORD);

            // 构建授权信息,用于进行SMTP进行身份验证
            Authenticator authenticator = new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    // 用户名、密码
                    String userName = props.getProperty("mail.user");
                    String password = props.getProperty("mail.password");
                    return new PasswordAuthentication(userName, password);
                }
            };
            // 使用环境属性和授权信息,创建邮件会话
            Session mailSession = Session.getInstance(props, authenticator);
            // 创建邮件消息
            MimeMessage message = new MimeMessage(mailSession);
            // 设置发件人
            String username = props.getProperty("mail.user");
            InternetAddress form = new InternetAddress(username);
            message.setFrom(form);

            // 设置收件人
            InternetAddress toAddress = new InternetAddress(to);
            message.setRecipient(Message.RecipientType.TO, toAddress);

            // 设置邮件标题
            message.setSubject(title);

            // 设置邮件的内容体
            message.setContent(text, "text/html;charset=UTF-8");
            // 发送邮件
            Transport.send(message);
            return true;
        }catch (Exception e){
            e.printStackTrace();
        }
        return false;
    }

    public static void main(String[] args) throws Exception { // 做测试用
        MailUtils.sendMail("[email protected]","你好,这是一封测试邮件,无需回复。","测试邮件");
        System.out.println("发送成功");
    }
    
}

4.2 用户点击邮件激活

用户激活其实就是修改数据库用户表中的 status 为‘Y’

User 类如下,有一个表示激活状态的变量 status

具体实现逻辑如下:

用户点击邮箱收到的链接时调用后台 Servlet 实现以下功能:

  1. 获取url中的激活码
  2. 如果获取不到则直接返回注册失败
  3. 否则根据激活码字段调用 service 层尝试激活用户
  4. 根据激活结果返回相应的信息。

service层实现尝试激活用户的功能,首先调用 dao 层查询激活码对应的用户是否存在,如果存在则再调用 dao 层的激活方法进行激活。

Dao层负责修改user的state为Y。

4.2.1 借助工具类来生成唯一的字符串作为激活码

package cn.itcast.travel.util;

import java.util.UUID;

/**
 * 产生UUID随机字符串工具类
 */
public final class UuidUtil {
   private UuidUtil(){}
   public static String getUuid(){
      return UUID.randomUUID().toString().replace("-","");
   }
   /**
    * 测试
    */
   public static void main(String[] args) {
      System.out.println(UuidUtil.getUuid());
      System.out.println(UuidUtil.getUuid());
      System.out.println(UuidUtil.getUuid());
      System.out.println(UuidUtil.getUuid());
   }
}

4.2.2 在 UserServiceImpl.java 中添加发送邮件代码

激活邮件包含一个超链接

"<a href = 'http://localhost/travel/activeUserServlet?code="+ user.getCode() +"'>点击激活</a>"

当用户点击注册时将自动发送邮件 

public boolean regist(User user) {
    // 根据用户名查询
    User u = userDao.findByUsername(user.getUsername());
    if (u != null){
        // 用戶名存在,注册失败
        return false;
    }
    // 保存用户信息
    // 设置激活码
    user.setCode(UuidUtil.getUuid());
    // 设置激活状态
    user.setStatus("N");
    userDao.save(user);

    // 激活邮件发送
    String content = "<a href = 'http://localhost/travel/activeUserServlet?code="+ user.getCode() +"'>点击激活</a>";
    MailUtils.sendMail(user.getEmail(), content, "激活邮件");

    return true;
}

4.2.3 激活的 ActiveUserServlet

这里将通过激活码查询对象并判断对象是否为null的操作放到了service层里,更合理

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 获取激活码
    String code = request.getParameter("code");
    if (code != null){
        // 激活
        UserService service = new UserServiceImpl();
        boolean flag = service.active(code);
        // 判断标记
        String msg;
        if (flag){
            msg = "激活成功,请<a href = 'login.html'>登录</a>";
        }else {
            msg = "激活失败,请联系管理员";
        }
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write(msg);
    }
}

4.2.4 service层实现激活:active

@Override
public boolean active(String code) {
    // 根据激活码查询用户
    User user = userDao.findByCode(code);
    if (user != null){
        userDao.updateStatus(user);
        return true;
    }else
        return false;
}

4.2.5 dao层实现根据激活码查询、更新用户状态

根据激活码查询

@Override
public User findByCode(String code) {
    User user = null;
    try {
        String sql = "select * from tab_user where code = ?";
        user = template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), code);
    }catch (Exception e){
        e.printStackTrace();
    }
    return user;
}

更新用户状态

@Override
public void updateStatus(User user) {
    String sql = "update tab_user set status = 'Y' where id = ?";
    template.update(sql, user.getUid());
}

修改保存Dao代码,加上存储status和code 的代码逻辑

@Override
public void save(User user) {
    String sql = "insert into tab_user(username,password,name,birthday,sex,telephone,email,status,code)" +
            "values(?,?,?,?,?,?,?,?,?)";
    template.update(sql, user.getUsername(),
            user.getPassword(),
            user.getName(),
            user.getBirthday(),
            user.getSex(),
            user.getTelephone(),
            user.getEmail(),
            user.getStatus(),
            user.getCode());
}

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.htmlfooter.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继承了BaseServletBaseServlet又继承了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;
}

9 旅游线路的分页展示

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

查询不同分类的旅游线路sql

Select * from tab_route where cid = ?;

所以点击分类栏时,应该在页面访问路径里面传一个cid

9.1 类别的id的传递

js中 location.search 属性,是一个可读可写的字符串,可设置或返回当前 URL 的查询部分(问号 ? 之后的部分)

假设当前的URL就是 http://www.runoob.com/submit.htm?email=someone@ example.com

<script>
document.write(location.search);
</script>

输出

[email protected]

9.1.1 header.html

传递cid

var li = '<li><a href="route_list.html?cid='+ data[i].cid +'">' + data[i].cname + '</a></li>';

9.1.2 route_list.html

获取cid

<script>
    $(function () {
        var search = location.search;
        var cid = search.split("=")[1];
    });
</script>

9.2 通过id查询不同类别的旅游线路数据

分页展示

9.2.1 分析

由于分页展示需要总记录数、总页数、当前页码、每页显示条数以及每页显示的数据集合,所以将这些数据封装成一个类 PageBean 来便于传递参数。

客户端向后台发送ajax请求,只用携带当前页码(currentPage)、每页显示条数(pageSize)以及分页id(cid)即可计算出其余信息,在我之前的博客中已经详细论述过,这里不做赘述。

9.2.2 服务器端

PageBean对象:

package cn.itcast.travel.domain;

import java.util.List;

// 分页对象
public class PageBean<T> {

    private int totalCount;     // 总记录数
    private int totalPage;      // 总页数
    private int currentPage;    // 当前页码
    private int pageSize;       // 每页显示的条目数

    private List<T> list;       // 每页要显示的数据

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        this.totalCount = totalCount;
    }

    public int getTotalPage() {
        return totalPage;
    }

    public void setTotalPage(int totalPage) {
        this.totalPage = totalPage;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public void setCurrentPage(int currentPage) {
        this.currentPage = currentPage;
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }
}

RouteServlet:

处理客户端传来的三个参数:cid、当前页面和每页显示的条目数,并调用service层返回一个pb对象

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;

@WebServlet("/route/*")
public class RouteServlet extends BaseServlet {

    private RouteService routeService = new RouteServiceImpl();

    /**
     * 分页查询
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    public void pageQuery(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String currentPageStr = request.getParameter("currentPage");
        String pageSizeStr = request.getParameter("PageSize");
        String cidStr = request.getParameter("cid");

        int cid = 0;
        if (cidStr != null && cidStr.length() > 0  && !"null".equals(cidStr)){
            cid = Integer.parseInt(cidStr);
        }

        int currentPage = 0;
        if (currentPageStr != null && currentPageStr.length() > 0){
            currentPage = Integer.parseInt(currentPageStr);
        }else {
            currentPage = 1;    // 默认是第一页
        }

        int pageSize = 0;
        if (pageSizeStr != null && pageSizeStr.length() > 0){
            pageSize = Integer.parseInt(pageSizeStr);
        }else {
            pageSize = 5;   // 默认每页显示5条记录
        }
        // 调用service查询PageBean对象
        PageBean<Route> pb = routeService.pageQuery(cid, currentPage, pageSize);

        // 序列化为json
        writeValue(pb, response);
    }
}

servlet调用了service层的 pageQuery 方法来请求一个 PageBean对象,实现如下:

RouteService:

接口

package cn.itcast.travel.service;

import cn.itcast.travel.domain.PageBean;
import cn.itcast.travel.domain.Route;

// 旅游线路service
public interface RouteService {

    /**
     * 根据类别进行分类查询
     * @param cid
     * @param currentPage
     * @param pageSize
     * @return
     */
    public PageBean<Route> pageQuery(int cid, int currentPage, int pageSize);
}

实现类,封装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;

public class RouteServiceImpl implements RouteService {

    private RouteDao routeDao = new RouteDaoImpl();

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

RouteDao:

接口

package cn.itcast.travel.dao;

import cn.itcast.travel.domain.Route;

import java.util.List;

public interface RouteDao {

    /**
     * 根据cid查询总记录数
     */
    public int findTotalCount(int cid);

    /**
     * 根据cid,start,pageSize查询当前页的数据集合
     */
    public List<Route> findByPage(int cid, int start, int pageSize);

}

实现类

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;

public class RouteDaoImpl implements RouteDao {

    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    @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.class), cid, start, pageSize);
    }
}

9.2.3 客户端

route_list.html 中由于在异步请求数据的过程中还要异步请求数据,第一次是页面加载完成之后请求PageBean的数据,第二次是分页页码请求超链接的cid和当前页面数据,所以不能直接在

$(function () {}

里面写方法体了,要单独抽取出一个方法

function load(cid, currentPage) {
    $.get("route/pageQuery", {cid:cid,currentPage:currentPage}, function (pb) {
        
        };
}

load方法中展示分页工具条和分页页码的逻辑如下:

// 分页工具条
$("#totalPage").html(pb.totalPage);
$("#totalCount").html(pb.totalCount);

// 展示分页页码
var lis = "";

var firstPage = '<li onclick="javascript:load(' + cid + ',' + 1 + ')"><a href="javascript:void(0)">首页</a></li>';
var preNum = pb.currentPage - 1;
if (preNum <= 0)
    preNum = 1;
var prePage = '<li onclick="javascript:load(' + cid + ',' + preNum + ')" class="threeword"><a href="javascript:void(0)">上一页</a></li>';

lis += firstPage;
lis += prePage;

for (var i = 1; i <= pb.totalPage; i ++){
    var li;
    if ( i == pb.currentPage){
        li = '<li class="curPage" onclick="javascript:load(' + cid + ',' + i + ')"><a href="javascript:void(0)">' + i + '</a></li>';
    }else {
        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:;">下一页</a></li>';
var lastPage = '<li onclick="javascript:load(' + cid + ',' + pb.totalPage + ')" class="threeword"><a href="javascript:void(0;">末页</a></li>';

lis += nextPage;
lis += lastPage;

$("#pageNum").html(lis);

展示列表数据的逻辑和展示分类页码类似:

var route_lis = "";
            for (var i = 0; i < pb.list.length; i++) {
                // {rid:1,rname:"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>&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个,控制逻辑如下图:

修改分页页码的逻辑:

/*
    1.一共展示10个页码,能够达到前5后4的效果
    2.如果前边不够5个,后边补齐10个
    3.如果后边不足4个,前边补齐10个
*/
var begin;
var end;

if (pb.totalPage < 10){
    begin = 1;
    end = pb.totalPage;
}else {
    // 总页数超过10页
    begin = pb.currentPage - 5 ;
    end = pb.currentPage + 4 ;

    if (begin < 1){
        begin = 1;
        end = begin + 9;
    }

    if (end > pb.totalPage){
        end = pb.totalPage;
        begin = end - 9;
    }
}

for (var i = begin; i < end; i++) {
    var li;
    if ( i == pb.currentPage){
        li = '<li class="curPage" onclick="javascript:load(' + cid + ',' + i + ')"><a href="javascript:void(0)">' + i + '</a></li>';
    }else {
        li = '<li onclick="javascript:load(' + cid + ',' + i + ')"><a href="javascript:void(0)">' + i + '</a></li>';
    }
    lis += li;
}

定位到页面顶部:

window.scrollTo(0,0);

10 旅游线路名称查询

10.1 查询参数的传递

 

10.1.1 工具类 getParameter.js

用于获取路径中key对应的值

//根据传递过来的参数name获取对应的值
function getParameter(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)","i");
    var r = location.search.substr(1).match(reg);
    if (r!=null) return (r[2]); return null;
}

10.1.2 header.html

将输入的要搜索的数据放到网页链接后面

$("#search-button").click(function () {
    // 待搜索的线路名称
    var rname = $("#search-input").val();
    var cid = getParameter("cid");
    location.href = "http://localhost/travel/route_list.html?cid=" + cid + "&rname=" + rname;
});

10.1.3 route_list.html

获取cid和rname,注意如果要获取的是汉字,则需要进行一个解码

var cid = getParameter("cid");
var rname = getParameter("rname");
if (rname){
    rname = window.decodeURIComponent(rname);
}

10.2 修改后台代码

10.2.1 RouteServlet

RouteServlet 原先并没有接收rname参数,需要添加

注意:如果用的是tomcat7而不是8则需要处理一下中文乱码的问题

String rname = request.getParameter("rname");
rname = new String(rname.getBytes("iso-8859-1"),"utf-8");
// 调用service查询PageBean对象
PageBean<Route> pb = routeService.pageQuery(cid, currentPage, pageSize, rname);

10.2.2 修改service层

RouteServiceImpl.java

int totalCount = routeDao.findTotalCount(cid, rname);
List<Route> list = routeDao.findByPage(cid, start, pageSize, rname);

10.2.3 dao层

sql语句要根据是否有cid的值和是否有rname的值动态生成

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.ArrayList;
import java.util.List;

public class RouteDaoImpl implements RouteDao {

    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    @Override
    public int findTotalCount(int cid, String rname) {
        // String sql = "select count(*) from tab_route where cid = ?";
        String sql = " select count(*) from tab_route where 1 = 1 ";
        StringBuilder sb = new StringBuilder(sql);

        List params = new ArrayList();  // 问号对应的值
        if (cid != 0){
            sb.append(" and cid = ? ");
            params.add(cid);
        }

        if (rname != null && rname.length() > 0 && !"null".equals(rname)){
            sb.append(" and rname like ? ");
            params.add("%" + rname + "%");
        }

        sql = sb.toString();

        return template.queryForObject(sql, Integer.class, params.toArray());
    }

    @Override
    public List<Route> findByPage(int cid, int start, int pageSize, String rname) {
        String sql = " select * from tab_route where 1 = 1 ";
        StringBuilder sb = new StringBuilder(sql);

        List params = new ArrayList();  // 问号对应的值
        if (cid != 0){
            sb.append(" and cid = ? ");
            params.add(cid);
        }

        if (rname != null && rname.length() > 0 && !"null".equals(rname)){
            sb.append(" and rname like ? ");
            params.add("%" + rname + "%");
        }
        sb.append(" limit ? , ? ");
        params.add(start);
        params.add(pageSize);
        
        sql = sb.toString();

        return template.query(sql, new BeanPropertyRowMapper<>(Route.class), params.toArray());
    }
}

10.3 修改前台代码

当页面加载完成后,调用load()发送ajax请求,加载数据,此时应该过了一个请求参数rname

$(function () {
    var cid = getParameter("cid");
    var rname = getParameter("rname");
    if (rname){
        rname = window.decodeURIComponent(rname);
    }
    // 当页面加载完成后,调用load()发送ajax请求,加载数据
    load(cid, null, rname);
});

function load(cid, currentPage, rname) 部分代码(主要是调用load()时传入参数的改变):

// 展示分页页码
var lis = "";

var firstPage = '<li onclick="javascript:load(' + cid + ',1,\'' + rname + '\')"><a href="javascript:void(0)">首页</a></li>';
var preNum = pb.currentPage - 1;
if (preNum <= 0)
    preNum = 1;
var prePage = '<li onclick="javascript:load(' + cid + ',' + preNum + ',\'' + rname + '\')" class="threeword"><a href="javascript:void(0)">上一页</a></li>';

lis += firstPage;
lis += prePage;
for (var i = begin; i < end; i++) {
    var li;
    if ( i == pb.currentPage){
        li = '<li class="curPage" onclick="javascript:load(' + cid + ',' + i + ',\'' + rname + '\')"><a href="javascript:void(0)">' + i + '</a></li>';
    }else {
        li = '<li onclick="javascript:load(' + cid + ',' + i + ',\'' + rname + '\')"><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 + ',\'' + rname + '\')" class="threeword"><a href="javascript:;">下一页</a></li>';
var lastPage = '<li onclick="javascript:load(' + cid + ',' + pb.totalPage + ',\'' + rname + '\')" class="threeword"><a href="javascript:void(0;">末页</a></li>';

11 旅游线路的详细展示

11.1 分析

需要查询三张表:route_img表、route表、seller表

用户点击查看详情后传入路线的id:rid,后台Servlet调用service层,从三张表中分别查出相应的信息,封装到Route对象中返回

11.2 后台代码

11.2.1 RouteServlet 中添加 findOne 方法

public void findOne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String rid = request.getParameter("rid");
    Route route = routeService.findOne(rid);
    writeValue(route, response);
}

11.2.2 RouteService 中添加 findOne 方法

实现从三个表中查询不同的数据,最终封装到Route类对象中并返回

public Route findOne(String rid) {
    // 查route表
    Route route = routeDao.findOne(Integer.parseInt(rid));

    // 查询route_img表,获得小图片集合信息
    List<RouteImg> routeImgList = routeImgDao.findByRid(route.getRid());
    route.setRouteImgList(routeImgList);

    // 查seller表,获得商家对象
    Seller seller = sellerDao.findById(route.getSid());
    route.setSeller(seller);

    return route;
}

11.2.3 RouteDaoImpl 中添加 findOne 方法

public Route findOne(int rid) {
    String sql = "select * from tab_route where rid = ?";
    return template.queryForObject(sql, new BeanPropertyRowMapper<Route>(Route.class), rid);
}

11.2.4 RouteImgDaoImpl

public List<RouteImg> findByRid(int rid) {
    String sql = "select * from tab_route_img where rid = ? ";
    return template.query(sql, new BeanPropertyRowMapper<RouteImg>(RouteImg.class), rid);
}

11.2.5 SellerDaoImpl

public Seller findById(int id) {
    String sql = "select * from tab_seller where sid = ? ";
    return template.queryForObject(sql, new BeanPropertyRowMapper<Seller>(Seller.class), id);
}

11.3 前台代码

11.3.1 route_list.html

查看详情a标签的href要添加一个字段?rid=

<a href="route_detail.html?rid=' + route.rid + '">查看详情</a>

11.3.2 Route_detail.html

  1. 获取rid
  2. 发送ajax请求,获取route对象
  3. 解析对象的数据
<script src="js/getParameter.js"></script>
$(document).ready(function() {
    //自动播放
    goImg();
    // var timer = setInterval("auto_play()", 5000);
});
function goImg() {
    //焦点图效果
    //点击图片切换图片
    $('.little_img').on('mousemove', function() {
        $('.little_img').removeClass('cur_img');
        var big_pic = $(this).data('bigpic');
        $('.big_img').attr('src', big_pic);
        $(this).addClass('cur_img');
    });
    //上下切换
    var picindex = 0;
    var nextindex = 4;
    $('.down_img').on('click',function(){
        var num = $('.little_img').length;
        if((nextindex + 1) <= num){
            $('.little_img:eq('+picindex+')').hide();
            $('.little_img:eq('+nextindex+')').show();
            picindex = picindex + 1;
            nextindex = nextindex + 1;
        }
    });
    $('.up_img').on('click',function(){
        var num = $('.little_img').length;
        if(picindex > 0){
            $('.little_img:eq('+(nextindex-1)+')').hide();
            $('.little_img:eq('+(picindex-1)+')').show();
            picindex = picindex - 1;
            nextindex = nextindex - 1;
        }
    });
}

$(function () {
    var rid = getParameter("rid");

    $.get("route/findOne", {rid: rid}, function (route) {
        $("#rname").html(route.rname);
        $("#routeIntroduce").html(route.routeIntroduce);
        $("#price").html("¥" + route.price);
        $("#sname").html(route.seller.sname);
        $("#consphone").html(route.seller.consphone);
        $("#address").html(route.seller.address);
        // 图片的展示
        var ddstr = '<a class="up_img up_img_disable"></a>';

        // 遍历routeImgList
        for (var i = 0; i < route.routeImgList.length; i++) {
            var astr;
            if (i >= 4){
                astr = '<a title="" class="little_img" data-bigpic="' + route.routeImgList[i].bigPic + '" style="display:none;">\n' +
                    '            <img src="' + route.routeImgList[i].smallPic + '">\n' +
                    '       </a>';
            }else {
                astr = '<a title="" class="little_img" data-bigpic="' + route.routeImgList[i].bigPic + '" >\n' +
                    '            <img src="' + route.routeImgList[i].smallPic + '">\n' +
                    '       </a>';
            }
            ddstr += astr;
        }
        ddstr += '<a class="down_img down_img_disable" style="margin-bottom: 0;"></a>';
        $("#dd").html(ddstr);
        // 图片展示和切换
        goImg();
    });
});

12 旅游线路收藏

12.1 分析

12.1.1 判断当前登录用户是否收藏过该线路

当页面加载完成后,发送ajax请求,获取用户是否收藏的标记

根据标记,展示不同的按钮样式

12.2 后台代码

12.2.1 dao

public class FavoriteDaoImpl implements FavoriteDao {

    private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());

    @Override
    public Favorite findByRidAndUid(int rid, int uid) {
        Favorite favorite = null;
        try {
            String sql = "select * from tab_favorite where rid = ? and uid = ?";
            favorite = template.queryForObject(sql, new BeanPropertyRowMapper<>(Favorite.class), rid, uid);
        }catch (Exception e){
            e.printStackTrace();
        }
        return favorite;
    }
}

12.2.2 service

public class FavoriteServiceImpl implements FavoriteService {

    private FavoriteDao favoriteDao = new FavoriteDaoImpl();

    @Override
    public boolean isFavorite(String rid, int uid) {
        Favorite favorite = favoriteDao.findByRidAndUid(Integer.parseInt(rid), uid);
        return favorite != null;
    }
}

12.2.3 servlet

在 RouteServlet 中添加方法判断当前登陆的用户是否收藏过该线路

public void isFavorite(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String rid = request.getParameter("rid");
    User user = (User)request.getSession().getAttribute("user");
    int uid;
    if (user == null){
        // 未登录
        uid = 0;
    }else {
        uid = user.getUid();
    }

    // 调用FavoriteService查询是否收藏
    boolean flag = favoriteService.isFavorite(rid, uid);
    writeValue(flag, response);
}

12.3 前端代码

12.3.1 Route_detail.html

$(function () {
    // 发送请求,判断用户是否收藏过该线路
    var rid = getParameter("rid");
    $.get("route/isFavorite", {rid: rid}, function (flag) {
        alert(flag);
        if (flag == true){
            // 用户收藏了
            $("#favorite").addClass("already");
        }else {
            $("#favorite").removeClass("already");
        }
    });
});

12.4 收藏次数

12.4.1 RouteServiceImpl

在该类的 findOne 方法中添加对收藏次数的封装

int count = favoriteDao.findCountByRid(route.getRid());
route.setCount(count);

12.4.2 FavoriteDaoImpl

dao层添加相应的方法

public int findCountByRid(int rid) {
    String sql = "select count(*) from tab_favorite where rid = ?";
    return template.queryForObject(sql, Integer.class, rid);
}

12.4.3 Route_detail.html

在数据展示部分添加收藏次数的展示

$("#favoriteCount").html("已收藏" + route.count + "次");

12.5 点击按钮 实现 / 取消 收藏

12.5.1 分析

12.5.2 RouteServlet

增加一个添加收藏的方法

public void addFavorite(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String rid = request.getParameter("rid");
    User user = (User) request.getSession().getAttribute("user");
    int uid;
    if (user == null) {
        // 未登录
        return;
    } else {
        uid = user.getUid();
    }

    if (!favoriteService.isFavorite(rid, uid))
        favoriteService.add(rid, uid);
    else
        favoriteService.remove(rid, uid);
}

12.5.3 FavoriteService

@Override
public void add(String rid, int uid) {
    favoriteDao.add(Integer.parseInt(rid),uid);
}

@Override
public void remove(String rid, int uid) {
    favoriteDao.remove(Integer.parseInt(rid), uid);
}

12.5.4 FavoriteDao

@Override
public void add(int rid, int uid) {
    String sql = "insert into tab_favorite values(?,?,?)";
    template.update(sql,rid,new Date(),uid);
}

@Override
public void remove(int rid, int uid) {
    String sql = "delete from tab_favorite where rid = ? and uid = ?";
    template.update(sql, rid, uid);
}

12.5.5 前台 Route_detail.html

给<a>标签添加onclick方法如果要调用js代码,后面方法名一定要加括号!

onclick="addFavorite()"

// 点击收藏按钮
function addFavorite() {
    var rid = getParameter("rid");
    $.get("user/findOne",{},function (user) {
        if(user){
            $.get("route/addFavorite",{rid:rid},function () {
                // 刷新页面
                location.reload();
            });
        }else {
            alert("尚未登录,请先登录");
            location.href = "http://localhost/travel/login.html";
        }
    });
}

 

 

源码下载链接:https://download.csdn.net/download/HNU_Csee_wjw/12560408

猜你喜欢

转载自blog.csdn.net/HNU_Csee_wjw/article/details/107003003