由于新冠病毒的原因,无法上学。百无聊赖下自己开始看视频学习,跟着视频做完了这个项目来检验学习成果,顺便写篇博客来记录一下。
话不多说,开始正题。
百度网盘
百度网盘链接:https://pan.baidu.com/s/1EfP8ZsQYSPTz80EGwh-1jw
提取码:ovf4
1. HTML、CSS、JS、JQuery、Ajax
2. Servlet 前端控制器
3. Filter 过滤器
4. BeanUtils 数据的封装工具类
5. JackSon Json数据序列化工具
1. JavaMail java处理电子邮件的接口
2. Redis key-value存储系统
3. Jedis Redis的Java连接开发工具
1. Druid连接池 Alibaba提供的数据库连接池
2. JDBCUtils JDBC的工具类
3. JdbcTemplate Spring MVC内置的对JDBC的封装
4. MySql数据库 SQL关系型数据库
pom配置文件可能会有部分jar包无法加载或者冲突等问题,我们可以到Maven的中央仓库复制需要的jar包到pom文件中,IDE就可以自动下载jar包。或者找到Maven本地仓库将版本改为我们已经下载好的版本。
关于Maven的搭建和相关问题的处理,可见Maven环境搭建
-- 创建数据库
CREATE DATABASE travel;
-- 使用数据库
USE travel;
--创建表
复制给定的SQL即可
当input输入框失去焦点时,使用JS的正则表达式对输入的内容进行判断看其是否符合规则。
不符合规则:将边框变成红色
符合规则:不做任何改变
当所有需要判断的内容都符合规则时,提交按钮才会提交数据。
部分代码如下:
// email校验
function checkEmail(){
const s = $("#email").val();
//定义正则 [email protected]
const check = /^\w+@\w+\.\w+$/;
const flag = check.test(s);
if(flag){
$("#email").css("border","");
}else {
$("#email").css("border","1px solid red");
}
return flag;
}
//校验所有内容以及发送Ajax请求
$(function () {
$("#registerForm").submit(function () {
if(checkPassword() && checkUsername() && checkEmail() && checkName() && checkBirthday() && checkNumber()){
//serialize()函数将表单中的数据自动封装转化为键值对的形式
$.post("user/register",$(this).serialize(),function (data) {
if(data.flag){
location.href="register_ok.html";
}else {
alert(data.errorMsg);
}
});
}
return false;
});
//失去焦点时校验用户名
$("#username").blur(checkUsername);
//失去焦点时校验密码
$("#password").blur(checkPassword);
//失去焦点时校验邮箱
$("#email").blur(checkEmail);
//姓名
$("#name").blur(checkName);
//手机号
$("#telephone").blur(checkNumber);
//出生日期
$("#birthday").blur(checkBirthday);
});
为了减轻服务器的压力,查询数据库之前,先校验验证码。
每随机生成一个验证码,就会存入session中。
校验验证码时,提取session中的验证码与input中提取的验证码相比较。
验证码正确:点击提交按钮时会将所有数据发送到数据库中进行操作。
验证码错误:弹出提示框,提示验证码错误。这时候点击提交按钮时无效的。
1、先根据Ucode码查询数据库是否有数据
如果有,注册失败。
如果没有,将表单的数据存入数据库。
2、发送激活邮件。
用户点击激活邮件连接,这时改变数据库中的激活状态码。此时用户才算成功注册。
service层代码如下:
/**
* 注册用户
* @param user 从表单中获取并封装的user对象
* @return 成功为true,失败为false
*/
@Override
public boolean register(User user) {
if(dao.findUserByUsername(user.getUsername())==null){
String code = UuidUtil.getUuid();
user.setCode(code);
user.setStatus("N");
dao.save(user);
String url ="http://localhost:8080/travel/activeUserServlet?code="+user.getCode()+"";
//激活邮件发送
String content ="【无无@wuliu旅游网】
点击激活
网页链接:"+url+"
无需回复,祝您一切安好!";
return MailUtils.sendMail(user.getEmail(),content,"激活邮件");
}else{
return false;
}
}
同上
根据用户名密码查询tab_user表。
如果能够返回一个user对象,再判断该user对象是否激活。
如果已经激活,则登录成功,跳转到主页。
如果没有激活,则给出提示。
servlet代码如下:
public void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
if(!checkCode(request)){
info.setFlag(false);
info.setErrorMsg("验证码错误!");
writeValueToOutputStream(info,response);
}
Map map = request.getParameterMap();
User user = new User();
try {
BeanUtils.populate(user,map);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
UserService service = new UserServiceImpl();
User u = service.login(user);
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);
//将user存入session
request.getSession().setAttribute("user",u);
}
writeValueToOutputStream(info,response);
}
前端发送Ajax请求只需要判断回传的json数据中flag是否为true。
如果为true,则跳转到主页面。
如果为false,则在提示框中打印errorMsg数据即可。
前端代码如下
$(function () {
$("#but_login").click(function(){
$.post("user/login",$("#loginForm").serialize(),function (data) {
//data : {flag:false,errorMsg:''}
if(data.flag){
//登录成功
location.href="index.html";
}else{
//登录失败
$("#errorMsg").html(data.errorMsg);
}
})
});
})
在登录成功时,我们会将user对象存入session,并且在主页上显示 " 欢迎回来,××× " 的字样。
当用户点击退出时,我们只需要删除session信息即可
```
public void exit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.getSession().invalidate();
response.sendRedirect(request.getContextPath()+"/login.html");
}
```
为了避免servlet太多,我们将servlet划分为一个个模块使用反射的方式执行该servlet。具体实施如下:
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//完成方法的分发
String uri = req.getRequestURI();
String methodName = uri.substring(uri.lastIndexOf("/")+1);
//通过反射执行userServlet中的方法
try {
//获取方法
Method method = this.getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class);
//执行方法
method.invoke(this,req,resp);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
从数据库中查询这8种分类数据,并展示在页面上。
1、页面加载完毕后发送Ajax请求。
2、先查询Redis数据库中是否有一个key 为 category的数据。
如果有数据,直接将数据返回转化为Json格式返回给前端。
如果没有数据,查询MySql数据库,将数据返回给前端,再将数据存入Redis数据库中。
service层代码如下
@Override
public List<Category> findAll() {
//获取Redis对象
Jedis jedis = JedisUtil.getJedis();
//使用下列方法查询,可以查询出id
Set<Tuple> set = jedis.zrangeByScoreWithScores("category",0,-1);
List<Category> list = new ArrayList<>();
//Redis中没有数据
if(set==null || set.size() == 0){
//查询数据库
list = dao.findAll();
for (Category category : list) {
jedis.zadd("category",category.getCid(),category.getCname());
}
}
//Redis中有数据
else{
for (Tuple s : set) {
Category c = new Category();
c.setCid((int) s.getScore());
c.setCname(s.getElement());
list.add(c);
}
}
//重写list的比较器方法,将结果按照ID的大小排序
list.sort(new Comparator<Category>() {
@Override
public int compare(Category o1, Category o2) {
return o1.getCid()-o2.getCid();
}
});
return list;
}
遍历后端回传的数据,拼接字符串填充为动态的数据。
拼接字符串时一定要注意单双引号的问题。
前端代码如下:
$.get("category/findAll",{},function (data) {
//[{cid:1,cname:国内游},{},{}]
let lis = '首页 ';
//遍历数组,拼接字符串()
for (let i = 0; i < data.length; i++) {
const cid = data[i].cid;
const cname = data[i].cname;
const li = '+ cid+'&rname=">'+cname+'';
lis += li;
}
//拼接收藏排行榜的li,收藏排行榜
lis+= '收藏排行榜 ';
//将lis字符串,设置到ul的html内容中
$("#category").html(lis);
//获取输入框的值
});
分析:
1、查询每页旅游线路所需要的条件:
- CurrentPage 当前页码(如果不传,默认为1)
- PageSize 每页显示条数
- Cid 当前旅游分类的id
2、根据Cid查询需要返回的数据:
- TotalCount 该旅游分类的旅游路线总条数
- TotalPage 总页码数(TotalPage =TotalCount%PageSize==0 ? TotalCount/PageSize : TotalCount/PageSize+1)
- Start 每页开始的数目 (Start = (CurrentPage-1)* PageSize)
- PageList 需要显示的路线的集合
3、将数据封装为PageBean对象并转化为Json格式返回给前端页面
Dao层代码如下:
1、查询当前分类下的旅游线路总数
@Override
public int findTotalCount(int cid) {
String sql = "select count(*) from tab_route where cid=? ";
return template.queryForObject(sql, Integer.class,cid);
}
2、查询当前分类下的旅游线路集合
@Override
public List<Route> findByPage(int cid, int start, int pageSize) {
String sql = "select * from tab_route where cid = ? limit ? , ?";
return template.query(sql,new BeanPropertyRowMapper<Route>(Route.class),cid,start,pageSize);
}
3、计算各个数值,封装PageBean对象
@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;
}
分析
页面加载完成之后向“ route/pageQuery ”发送Ajax请求,获取PageBean对象。
1、遍历PageBean对象的List集合拼接字符串。
2、遍历PageBean对象的TotalPage ,拼接字符串展示在分页栏。
3、分页栏优化
- 当页面数小于10页时,展示所有页码。
- 当页面数大于10页时,只展示8个页码。
- 当页码大于4时,页码按照前4后3的模式排列。
- 当前页码前后不足时,补齐8个页码。
1、旅游线路列表展示
//2.列表数据展示
var route_lis = "";
for (var i = 0; i < pb.list.length; i++) {
//获取{rid:1,rname:"xxx"}
var route = pb.list[i];
var li = '\n' +
' +route.rimage+'" style="width: 299px;">\n' +
' \n' +
' '
+route.rname+'\n' +
'
\n' +
' '
+route.routeIntroduce+'\n' +
' \n' +
' \n' +
' ';
route_lis += li;
}
2、旅游路线页码栏代码
//解析PageBean对象
//页码展示
const totalCount = pb.totalCount;
const totalPage = pb.totalPage;
$("#totalPage").html(totalPage);
$("#totalCount").html(totalCount);
const currentPage = pb.currentPage;
//分页栏展示
let bePage = currentPage - 1;
if(bePage < 0){
bePage = 1;
}
let laPage = currentPage + 1;
if(laPage > totalPage){
laPage = totalPage;
}
let lis = '';
const fPage = '+cid+','+1+',\''+rname+'\');">首页 \n' +
'+cid+','+bePage+',\''+rname+'\');" class="threeword">上一页 ';
lis += fPage;
let begin ;
let end ;
if(totalPage<8){
begin = 1;
end = totalPage;
}else{
begin = currentPage - 4;
end = currentPage + 3;
if(begin <= 0){
begin = 1;
end = begin + 7;
}
if(end > totalPage){
end = totalPage;
begin = end - 7;
}
}
for (let i = begin; i <= end ; i++) {
let li;
if(currentPage === i){
li = '+cid+','+i+');">' + i + ' ';
}else {
li = '+cid+','+i+');">' + i + ' ';
}
lis += li;
}
const lPage ='+cid+','+laPage+')" class="threeword">下一页 \n' +
'+cid+','+totalPage+');" class="threeword">末页 ';
lis += lPage;
$("#pageNum").html(lis);
1、提取名称关键字
$("#search-button").click(function () {
//线路名称
var rname = $("#search_input").val();
var cid = getParameter("cid");
// 跳转路径 http://localhost/travel/route_list.html?cid=5,拼接上rname=xxx
location.href="http://localhost/travel/route_list.html?cid="+cid+"&rname="+rname;
});
var cid = getParameter("cid");
//获取rname的参数值
var rname = getParameter("rname");
//判断rname如果不为null或者""
if(rname){
//url解码
rname = window.decodeURIComponent(rname);
}
2、根据条件查询数据库
Dao层查询小技巧
Sql可以先写 “select * from tab_××× where ”
Dao层Sql的编写可以用StringBuilder字符缓冲区进行Sql的拼接
Dao层代码如下
//查询满足条件的数据条数
@Override
public int findTotalCount(int cid,String rname) {
String sql = "select count(*) from tab_route where 1=1 ";
List params = new ArrayList<>();
StringBuilder sb = new StringBuilder(sql);
if(cid != 0){
sb.append(" and cid=?");
params.add(cid);
}
if(rname != null && rname.length()>0){
sb.append(" and rname like ?");
params.add("%"+rname+"%");
}
sql = sb.toString();
return template.queryForObject(sql, Integer.class,params.toArray());
}
//查询满足条件的Route对象并封装为List集合
@Override
public List<Route> findByPage(int cid, int start, int pageSize,String rname) {
String sql = null;
List<Route> list = null;
boolean idEmpty = cid==0;
boolean nameEmpty = rname.equals(" ");
if(!idEmpty && nameEmpty){
sql = "select * from tab_route where cid=? limit ? , ? ";
list = template.query(sql,new BeanPropertyRowMapper<>(Route.class),cid,start,pageSize);
return list;
}
if(idEmpty && !nameEmpty){
sql = "select * from tab_route where rname like ? limit ? , ? ";
rname = "%"+rname+"%";
list = template.query(sql,new BeanPropertyRowMapper<>(Route.class),rname,start,pageSize);
return list;
}
if(idEmpty && nameEmpty){
sql = "select * from tab_route limit ?,?";
list = template.query(sql,new BeanPropertyRowMapper<>(Route.class),start,pageSize);
return list;
}
else{
sql = "SELECT * FROM tab_route WHERE cid=? AND rname LIKE ? LIMIT ?,?";
rname = "%"+rname+"%";
list = template.query(sql,new BeanPropertyRowMapper<>(Route.class),cid,rname,start,pageSize);
return list;
}
}
3、将数据库查询的数据展示在页面上
前端代码类似以上代码
1、点击查看详情按钮时传递当前路线的id
2、根据id查询数据库并封装为Route对象返回
3、根据Route对象的内容填充页面数据
HTML页面填充Route详情代码如下
$(function () {
const rid = getParameter("rid");
$.post("route/routeInfo",{rid:rid},function (route) {
$("#rname").html(route.rname);
$("#price").html("¥"+route.price);
$("#routeIntroduce").html(route.routeIntroduce);
$("#sname").html(route.seller.sname);
$("#consphone").html(route.seller.consphone);
$("#address").html(route.seller.address);
$("#dt").html('+route.rimage+'">');
const count = 100+route.count;
$("#fCount").html("已收藏"+count+"次");
let dds ='';
let last = '';
dds += last;
for (let i = 0; i <route.routeImgList.length ; i++) {
const rImgList = route.routeImgList[i];
let dd = '';
if(i<=4){
dd = '+ rImgList.bigPic + '">\n' +
' + rImgList.smallPic + '">\n' +
' ';
}else{
dd = '+ rImgList.bigPic + '" style="display:none;" >\n' +
' + rImgList.smallPic + '">\n' +
' ';
}
dds += dd;
}
let next = '';
dds += next;
$("#dd").html(dds);
show();
fButton();
});
});
1、点击收藏按钮,发送异步请求
根据 Uid和Rid查询 tab_favorite 表
-如果有数据,那么表示该线路已被该用户收藏
将收藏按钮置灰,移除Onclick()事件,并将按钮变成不可点击状态。
-如果没有数据,表示该线路没有被该用户收藏
将收藏按钮变成红色,并且加上Onclick()事件。点击按钮时发送异步请求更新数据库。
2、将数据库回传的数据写回到前端页面
代码同上
直接添加的功能,大致思路同 旅游线路分页展示 只是每页显示的数量变成了12.其他大同小异 ,这里不做赘述。