本示例为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 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三层模型
数据库travel.sql文件
链接:https://pan.baidu.com/s/1XJtpYZZ_svFQYAHhcf0IWQ 提取码:suwy
-- 创建数据库
CREATE DATABASE travel;
-- 使用数据库
USE travel;
--创建表
设计逻辑如下:
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.java 中 doPost() 方法的获取数据之前的部分添加验证码校验功能
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.1 发送邮件
- 申请邮箱(这里直接用了qq邮箱,个人感觉操作起来比较方便)
- 开启授权码
- 在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 = "1229173945@qq.com"; // 发件人称号,同邮箱地址
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("nju_wjw@163.com","你好,这是一封测试邮件,无需回复。","测试邮件");
System.out.println("发送成功");
}
}
4.2 用户点击邮件激活
用户激活其实就是修改数据库用户表中的 status 为‘Y’
User 类如下,有一个表示激活状态的变量 status:
具体实现逻辑如下:
用户点击邮箱收到的链接时调用后台 Servlet 实现以下功能:
- 获取url中的激活码
- 如果获取不到则直接返回注册失败
- 否则根据激活码字段调用 service 层尝试激活用户
- 根据激活结果返回相应的信息。
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.1 分析
用户输入信息点击登录按钮后,向后台发送ajax请求,调用 LoginServlet,实现以下几个主要功能:
- 获取用户输入的信息
- 调用service层查询该用户是否存在
- 如果该用户存在且已激活则跳转至主页 index.html
- 否则输出错误信息
5.2 前台代码
5.2.1 登录页面login.html
<script>
$(function () {
// 登录按钮绑定单击事件
$("#btn_sub").click(function () {
// 发送ajax请求
$.post("loginServlet",$("#loginForm").serialize(),function (data) {
// data:{flag:false,errorMsg:''}
if (data.flag){
location.href = "index.html";
}else {
$("#errorMsg").html(data.errorMsg);
}
});
});
});
</script>
5.3 后台代码
5.3.1 LoginServlet
根据5.1分析部分的描述,servlet的代码如下:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 先校验验证码
String check = request.getParameter("check");
HttpSession session = request.getSession();
String checkcode_server = (String)session.getAttribute("CHECKCODE_SERVER");
session.removeAttribute("CHECKCODE_SERVER"); // 保证验证码只能使用一次
if (checkcode_server == null || !checkcode_server.equalsIgnoreCase(check)){
ResultInfo info = new ResultInfo();
info.setFlag(false);
info.setErrorMsg("验证码错误");
// 将info序列化为json
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(info);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
return;
}
// 获取用户名密码
Map<String, String[]> map = request.getParameterMap();
User user = new User();
try {
BeanUtils.populate(user, map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
UserService service = new UserServiceImpl();
User u = service.login(user);
ResultInfo info = new ResultInfo();
if (u == null){
info.setFlag(false);
info.setErrorMsg("用户名或密码错误");
}
if (u != null && !"Y".equals(u.getStatus())){
info.setFlag(false);
info.setErrorMsg("您尚未激活,请登录邮箱激活");
}
if (u != null && "Y".equals(u.getStatus())){
info.setFlag(true);
session.setAttribute("user", u);
}
ObjectMapper mapper = new ObjectMapper();
response.setContentType("application/json;charset=utf-8");
mapper.writeValue(response.getOutputStream(), info);
}
5.3.2 service层 Userservice
servlet要调用该函数完成用户的查询,用户只输入了用户名和密码,所以就根据这两个值进行查询
@Override
public User login(User user) {
return userDao.findByUsernameAndPassword(user.getUsername(), user.getPassword());
}
5.3.3 dao层 UserDao
注意:如果用户输入了错误的用户名或密码导致查询数据库失败的话,template.queryForObject 方法会报错,所以这里应该将异常catch一下,使得查询失败时返回一个空 User 对象
@Override
public User findByUsernameAndPassword(String username, String password) {
User user = null;
try {
String sql = "select * from tab_user where username = ? and password = ?";
// 第二个参数是指定返回结果要封装成的类型
user = template.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), username, password);
} catch (Exception e) {
}
return user;
}
5.3.4 登录成功后右上角显示用户姓名
index.html中,页面的头部和尾部的部分是由include.js动态加载的
<script type="text/javascript" src="js/include.js"></script>
观察include.js
$(function () {
$.get("header.html",function (data) {
$("#header").html(data);
});
$.get("footer.html",function (data) {
$("#footer").html(data);
});
});
可以看出是页面加载完成后再动态加载头部和尾部的信息的(分别封装在header.html和footer.html)
所以在header.html中添加发送异步请求查询当前登录用户的代码
<script>
$(function () {
$.get("findUserServlet",[],function (data) {
// {uid:1,name:"李四"}
var msg = "欢迎回来," + data.name;
$("#span_username").html(msg);
});
});
</script>
相应的servlet,从session中获取user对象,以json格式写回前端
@WebServlet("/findUserServlet")
public class FindUserServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Object user = request.getSession().getAttribute("user");
//将user写回客户端
ObjectMapper mapper = new ObjectMapper();
response.setContentType("application/json;charset=utf-8");
mapper.writeValue(response.getOutputStream(),user);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
}
6.1 分析
session中有user对象说明用户登陆了,所以退出登录的实现可以如下:
- 访问servlet,将session销毁
- 跳转到登录页面
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.1 目的
减少Servlet的数量,现在是一个功能一个Servlet,将其优化为一个模块一个Servlet,相当于在数据库中一张表对应一个Servlet,在Servlet中提供不同的方法,完成用户的请求。
由于UserServlet继承了BaseServlet,BaseServlet又继承了HttpServlet所以当浏览器访问UserServlet时会自动调用BaseServlet的service方法,实现分发。
Idea控制台中文乱码解决:-Dfile.encoding=gb2312
7.2 分发类BaseServlet
首先获取方法名,然后根据方法名采用反射机制加载UserServlet中相应的方法,并传入response和request来调用,但注意servlet中的方法是由protected修饰的,所以在使用反射机制加载方法时应当忽略访问修饰符。并在执行方法之前进行暴力反射。
Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
method.setAccessible(true);
method.invoke(this,req,resp);
但更好的方法是将UserServlet中的方法修饰符改为public
package cn.itcast.travel.web.servlet;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 不需要被浏览器输入访问到
public class BaseServlet extends HttpServlet {
// 访问UserServlet是自动执行该方法
// 完成方法分发
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String uri = req.getRequestURI();// /travel/user/add
// 获取方法名称
String methodName = uri.substring(uri.lastIndexOf("/") + 1);
try {
// this这里代表的是调用 BaseServlet 的 UserServlet
Method method = this.getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
/**
* 将传入的对象序列化为json并写会客户端
* @param obj
*/
public void writeValue(Object obj, HttpServletResponse response) throws IOException {
ObjectMapper mapper = new ObjectMapper();
response.setContentType("application/json;charset=utf-8");
mapper.writeValue(response.getOutputStream(), obj);
}
/**
* 将传入的对象序列化为json返回
* @param obj
* @return
*/
public String writeValueAsString(Object obj) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
}
}
7.3 提供用户服务的UserServlet
只需创建几个方法
并将之前写过的相应Servlet的逻辑复制到方法里面即可,具体代码如下(就是将前面的servlet做一个整合):
package cn.itcast.travel.web.servlet;
import cn.itcast.travel.domain.ResultInfo;
import cn.itcast.travel.domain.User;
import cn.itcast.travel.service.UserService;
import cn.itcast.travel.service.impl.UserServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.beanutils.BeanUtils;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
@WebServlet("/user/*")
public class UserServlet extends BaseServlet {
private UserService service = new UserServiceImpl();
/**
* 注册功能
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("regist");
// 先校验验证码
String check = request.getParameter("check");
HttpSession session = request.getSession();
String checkcode_server = (String)session.getAttribute("CHECKCODE_SERVER");
session.removeAttribute("CHECKCODE_SERVER"); // 保证验证码只能使用一次
if (checkcode_server == null || !checkcode_server.equalsIgnoreCase(check)){
ResultInfo info = new ResultInfo();
info.setFlag(false);
info.setErrorMsg("验证码错误");
// 将info序列化为json
ObjectMapper mapper = new ObjectMapper();
String json = writeValueAsString(info);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
return;
}
// 获取数据
Map<String, String[]> map = request.getParameterMap();
// 封装
User user = new User();
try {
BeanUtils.populate(user,map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 调用service层
boolean flag = service.regist(user);
// 响应结果
ResultInfo info = new ResultInfo();
if (flag){
info.setFlag(true);
}else {
info.setFlag(false);
info.setErrorMsg("注册失败");
}
// 将info序列化为json
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(info);
// 将json写回客户端
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
}
/**
* 登录功能
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("login");
// 先校验验证码
String check = request.getParameter("check");
HttpSession session = request.getSession();
String checkcode_server = (String)session.getAttribute("CHECKCODE_SERVER");
session.removeAttribute("CHECKCODE_SERVER"); // 保证验证码只能使用一次
if (checkcode_server == null || !checkcode_server.equalsIgnoreCase(check)){
ResultInfo info = new ResultInfo();
info.setFlag(false);
info.setErrorMsg("验证码错误");
// 将info序列化为json
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(info);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
return;
}
// 获取用户名密码
Map<String, String[]> map = request.getParameterMap();
User user = new User();
try {
BeanUtils.populate(user, map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
User u = service.login(user);
ResultInfo info = new ResultInfo();
if (u == null){
info.setFlag(false);
info.setErrorMsg("用户名或密码错误");
}
if (u != null && !"Y".equals(u.getStatus())){
info.setFlag(false);
info.setErrorMsg("您尚未激活,请登录邮箱激活");
}
if (u != null && "Y".equals(u.getStatus())){
info.setFlag(true);
session.setAttribute("user", u);
}
writeValue(info, response);
}
/**
* 查询单个用户
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void findOne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("findOne");
Object user = request.getSession().getAttribute("user");
//将user写回客户端
writeValue(user, response);
}
/**
* 退出功能
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void exit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("exit");
// 销毁session
request.getSession().invalidate();
// 跳转
response.sendRedirect(request.getContextPath() + "/login.html");
}
/**
* 激活功能
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void active(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("active");
// 获取激活码
String code = request.getParameter("code");
if (code != null){
// 激活
boolean flag = service.active(code);
// 判断标记
String msg;
if (flag){
msg = "激活成功,请<a href = 'http://localhost/travel/login.html'>登录</a>";
}else {
msg = "激活失败,请联系管理员";
}
response.setContentType("text/html;charset=utf-8");
response.getWriter().write(msg);
}
}
}
7.4 修改html页面中之前写的servlet的路径
7.4.1 登录页面login.html
$.post("user/login",$("#loginForm").serialize(),function (data) {
// data:{flag:false,errorMsg:''}
if (data.flag){
location.href = "index.html";
}else {
$("#errorMsg").html(data.errorMsg);
}
});
7.4.2 头部页面header.html
$.get("user/findOne",[],function (data) {
// {uid:1,name:"李四"}
var msg = "欢迎回来," + data.name;
$("#span_username").html(msg);
});
7.4.3 注册页面regist.html
$.post("user/regist",$(this).serialize(),function (data) {
// 处理服务器响应的数据
if (data.flag){
// 注册成功
location.href = "register_ok.html";
}else {
$("#errorMsg").html(data.errorMsg);
}
});
7.4.4 service层UserServiceImpl
修改邮件的激活路径
String content = "<a href = 'http://localhost/travel/user/active?code="+ user.getCode() +"'>点击激活</a>";
网页顶部导航栏
8.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;
}
点击了不同的分类后,将来看到的旅游线路不一样的。通过分析数据库表结构,旅游线路表和分类表是一个多对一的关系
查询不同分类的旅游线路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=someone@example.com
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>¥</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.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.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
- 获取rid
- 发送ajax请求,获取route对象
- 解析对象的数据
<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.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
给标签添加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
如有需要可以关注我的个人公众号【N小王日记】,里面有我个人联系方式。