本示例为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.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 邮件激活
为了保证用户填写的邮箱是正确的。将来可以推广一些宣传信息,到用户邮箱中。
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 = "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 实现以下功能:
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()); }