Maven项目之黑马旅游网
视频讲解
本项目是根据黑马程序员的教学视频完成。完成一个黑马旅游网相关功能的实现。
这里就记录一些自己在完成项目时遇到的问题以及解决方法
代码所在仓库地址:https://gitee.com/qingyu1011/springboot_study/tree/master/Maven/Travel03
项目初始化
数据库准备
相关sql语句在上述仓库中的resources文件下,创建后的结果为:
创建Maven webapp项目
视频讲解
这里我们选择使用骨架的方式
之后Finish即可
通过骨架创建好webapp项目后,我们删除pom.xml中的一些内容,只保留剩余代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?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>top.qing</groupId> <artifactId>Travel03</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging>
<name>Travel03 Maven Webapp</name> <url>http://www.example.com</url>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties>
</project>
|
这个时候的目录结构如下:
这个时候需要我们手动加入一些文件目录(如java、test)。步骤如下:
技术选型
Web层
- Servlet:前端控制器
- html:视图
- Filter:过滤器
- BeanUtils:数据封装
- Jackson:json序列化工具
Service层
- Javamail:java发送邮件工具
- Redis:nosql内存数据库
- Jedis:java的redis客户端
Dao层
- Mysql:数据库
- Druid:数据库连接池
- JdbcTemplate:jdbc的工具
代码实现
注册功能
视频讲解
分析:
表单校验
表单检验:提升用户体验,并减轻服务器压力
利用正则表达式,在注册时要进行表单校验:
- 用户名:单词字符,长度8到20位
- 密码:单词字符,长度8到20位
- email:邮件格式
- 姓名:非空
- 手机号:手机号格式
- 出生日期:非空
- 验证码:非空
视频讲解
(代码和异步(ajax)提交表单一起放在下面)
异步(ajax)提交表单
在此使用异步提交表单是为了获取服务器响应的数据。因为我们前台使用的是html作为视图层,不能够直接从servlet相关的域对象获取值,只能通过ajax获取响应数据。
视频讲解
表单检验和异步提交代码如下:
在regist.html的<head>
标签下放入代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
| <script src="js/jquery-3.3.1.js"></script> <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 = /^[\s\S]*.*[^\s][\s\S]*$/;
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[3|4|5|7|8][0-9]{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 reg_birthday = /^[\s\S]*.*[^\s][\s\S]*$/;
var flag = reg_birthday.test(birthday); if(flag){ $("#birthday").css("border",""); }else{ $("#birthday").css("border","1px solid red"); } return flag; }
function checkCheckCode(){ var check = $("#check").val(); var reg_check = /^[\s\S]*.*[^\s][\s\S]*$/;
var flag = reg_check.test(check); if(flag){ $("#check").css("border",""); }else{ $("#check").css("border","1px solid red"); } return flag; }
$(function () { $("#registerForm").submit(function(){ if(checkUsername() && checkPassword() && checkEmail() && checkName() && checkTelephone() && checkBirthday() && checkCheckCode()){ $.post("registUserServlet",$(this).serialize(),function(data){ if(data.flag){ location.href="register_ok.html"; }else{ $("#errorMsg").html(data.errorMsg); } }); } return false; }); $("#username").blur(checkUsername); $("#password").blur(checkPassword); $("#email").blur(checkEmail); $("#name").blur(checkName); $("#telephone").blur(checkTelephone); $("#birthday").blur(checkBirthday); $("#check").blur(checkCheckCode); });
</script>
|
注册功能后端代码
视频讲解
这一块具体就看仓库代码吧。不过写的时候要细心,不然很容易报空指针异常。
邮件激活功能
为什么要进行邮件激活?为了保证用户填写的邮箱是正确的。将来可以推广一些宣传信息,到用户邮箱中。
关于邮件激活动能就不添加了,但是还是可以了解一下。
视频讲解
登录功能
分析:
视频讲解
前端代码
后端代码
- 编写LoginUserServlet
- 补充UserService接口以及UserServiceImpl
- 补充UserDao接口以及UserDaoImpl
退出功能
什么叫做登录了?session中有user对象。
实现步骤:
- 访问servlet,将session销毁
- 跳转到登录页面
BaseServlet抽取
减少Servlet的数量,现在是一个功能一个Servlet,将其优化为一个模块一个Servlet,相当于在数据库中一张表对应一个Servlet,在Servlet中提供不同的方法,完成用户的请求。
(视频讲解)
例子:创建BaseServlet如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| package top.qing.web.servlet;
import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class BaseServlet extends HttpServlet {
@Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String uri = req.getRequestURI();
String methodName = uri.substring(uri.lastIndexOf('/') + 1);
try { 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(); }
} }
|
抽取UserServlet代码见仓库
分类数据展示
效果:
分析:
(视频讲解)
后端代码
- CategoryServlet
- CategoryService
- CategoryDao
前端代码
缓存优化:redis
分析发现,分类的数据在每一次页面加载后都会重新请求数据库来加载,对数据库的压力比较大,而且分类的数据不会经常产生变化,所有可以使用redis来缓存这个数据。
优化,以后就不需要每次都发送findAll请求了
旅游线路的分页展示
点击了不同的分类后,将来看到的旅游线路不一样的。通过分析数据库表结构,发现,旅游线路表和分类表时一个多对一的关系
根据id查询不同类别的旅游线路数据(分页展示)
分页展示旅游线路数据
分析:
(视频讲解)
客户端(前端)代码编写
- 客户端发送ajax请求,请求PageBean数据
- 携带三个参数:currentPage,pageSize,cid
服务器端(后端)代码编写
- 创建PageBean对象
- RouteServlet
- RouteService
- RouteDao
创建PageBean对象代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package top.qing.domain;
import lombok.Data;
import java.util.List;
@Data public class PageBean<T> { private Integer totalCount; private Integer totalPage; private Integer currentPage; private Integer pageSize;
private List<T> list; }
|
旅游线路名称查询
前端代码
后端代码
- 修改RouteServlet
- 修改RouteService
- 修改RouteDao
(都是补充rname参数,这个rname就是从前端传来的用户查询的关键字)
注意一下RouteDaoImpl的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| package top.qing.dao.impl;
import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import top.qing.dao.RouteDao; import top.qing.domain.Route; import top.qing.util.JDBCUtils;
import java.util.ArrayList; import java.util.List;
public class RouteDaoImpl implements RouteDao { private JdbcTemplate jdbcTemplate = new JdbcTemplate(JDBCUtils.getDataSource());
@Override public int findTotalCount(int cid,String rname) {
String sql = "select count(rid) from tab_route where 1=1"; StringBuilder stringBuilder = new StringBuilder(sql); List params = new ArrayList<>(); if (cid!=0){ stringBuilder.append(" and cid = ?"); params.add(cid); } if (rname!=null&&rname.length()>0){ stringBuilder.append(" and rname like ?"); params.add("%"+rname+"%"); } sql = stringBuilder.toString(); return jdbcTemplate.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 stringBuilder = new StringBuilder(sql); List params = new ArrayList<>(); if (cid!=0){ stringBuilder.append(" and cid = ?"); params.add(cid); } if (rname!=null&&rname.length()>0){ stringBuilder.append(" and rname like ?"); params.add("%"+rname+"%"); } stringBuilder.append(" limit ?.,?"); params.add(start); params.add(pageSize); sql = stringBuilder.toString(); List<Route> routes = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Route>(Route.class), params.toArray()); return routes; } }
|
查看旅游详情
分析:
视频讲解
前端代码
Route_detail.html中加载后:
- 获取rid
- 发送ajax请求,获取route对象
- 解析对象的数据
后端代码
- 补充RouteServlet:增加findOne()方法,用于查询一个旅游线路的详细信息(即一个Route对象)
- 补充RouteService
- 补充RouteDao
旅游线路收藏展示
分析:
当页面加载完成后,发送ajax请求,获取用户是否收藏的标记。根据标记,展示不同的按钮样式
视频讲解
前端代码
后端代码
点击按钮收藏路线功能
分析:
视频讲解
前端代码
route_detail.html
后端代码
- 补充RouteServlet:
addFavorite()
方法,用于更新tab_favorite表的数据,从逻辑上完成收藏功能
- 补充FavoriteService
- 补充FavoriteDao
遇到的问题
pom.xml之lombok
最开始我选择的lombok版本为1.18.12版的,在tomcat运行后会报错:
Unable to process Jar entry [module-info.class] from Jar.....for annotations
解决方案:将lombok版本降至1.16.18版本
参考文章
空指针异常
是由于写代码粗心导致的。比如说在UserServiceImpl实现类中的regist方法,本来应该保存user(参数由前端数据提供并封装的),但我却错误的写成了user_find(从数据库查询的用于判断用户名是否存在)。
BaseServlet中的一些方法封装
响应数据writeValue方法:
1 2 3 4 5 6 7 8
| ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(resultInfo);
response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json);
|