# 黑马旅游网demo **Repository Path**: yxn-coder/itheima-travel-demo ## Basic Information - **Project Name**: 黑马旅游网demo - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 4 - **Created**: 2021-09-12 - **Last Updated**: 2024-07-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 黑马旅游网项目遇到的问题及解决 算是经历了整整四天吧,前两天听课跟着视频敲。后两天自己手动完成剩余的其他若干功能,一路debug过来,收获许多,在此记录。希望自己永远保持热忱,加油。 ## 一、项目配置问题 ### 1、【maven项目目录结构】 可能一开始创建出来的项目文件目录形式不对,IDEA对目录结构有明显的要求,可以通过以下方法修改,当然其他情况也可以使用; ![image-20200326232605666](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200326232605666.png) ### 2、【修改目录属性】 ![mvn](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/mvn.png) 后来发现可以直接右键点击目录,选择make directory as 效果是一样的。 ![image-20200327125444781](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200327125444781.png) ### 3、【设置web源目录】 ![websource](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/websource.png) ### 4、【maven低版本和servlet3.0冲突】 视频中使用的是Servlet3.0之后的版本,利用注解配置,我原来之前一直也是注解配置,想试着使用xml配置练练手。但是当我从xml到注解转换的过程中,遇到了一些问题,主要是Servlet版本和maven仓库版本冲突?具体我也不太清清除,过于真实,整个过程迷迷糊糊的,bug频出,也试过很多方法,真的不知道是哪个方法起了作用。 以下内容仅记录自己的纠错过程,首先我先去视频中的代码文件查看一下有偏差的地方:直接锁定是Servlet的版本问题,servlet3.0之后才可以使用注解,而我使用的是2.5。 接着,我参考了这个博客:[Maven创建webapp骨架无法使用@WebServlet来实现注解配置解决方案](https://blog.csdn.net/qq_41753944/article/details/103899701),修改了maven仓库中的jar包的web.xml内容,可能是我操作的问题,并没有见效。 接着又在某个论坛上看到一个方法,可以重新指定xml的版本。【后面测试了几次,貌似和这个关系不大】 ![image-20200327124757985](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200327124757985.png) ![image-20200327124807705](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200327124807705.png) 接着在pom.xml中添加servlet3.0之后的依赖,需要注意的是,要指定scope为provide,不然的话可能会产生冲突。【一定要注意找到填写正确坐标,有一次我把artifactId里写成servlet-api死活下载不来】 ```xml javax.servlet javax.servlet-api 3.1.0 provided ``` 就像这样的冲突错误(下面两种都是因为scope没有指定privided的原因,因为添加上去就成功了) `com.travel.web.filter.CharacterFilter cannot be cast to javax.servlet.Filter`。 `javax.servlet.ServletException: java.lang.LinkageError`。 中途会遇到缺少javaEE啥啥啥的,annotation包依赖缺少的错误,按照提示添加即可。 以上就是我从web.xml到注解配置的全过程,有点坎坷,但是今后遇到这样的问题,兴许会多一些思路吧。 ### 5、【控制台输出乱码解决】 ![image-20200327221444893](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200327221444893.png) `settings`->`Build,Execution,Deployment`->`Build Tools`->`Maven`->`Runner`。 设置`VM-Option`参数,指定虚拟机字符集:`-Dfile.encoding=gb2312`,如果不行可以设置称其他的。 ## 二、前台代码 ### 1、【发送异步请求】 ```js //校验通过,ajax发送请求,提交表单数据 $("#registerForm").serialize() $.post("registerUserServlet",$(this).serialize(),function (data) { if(data.flag){ //注册成功,跳转成功页面 location.href= "register_ok.html"; }else{ //如果错误,需要重新对验证码servlet请求一次,不然会导致会话中的验证码消失,图片虽然存在,但码已经没有了 document.getElementById("check_img").src= "checkCode?"+new Date().getTime(); //注册失败,给errormsg添加提示信息 $("#error_msg").html(data.errorMsg); } }) ``` ### 2、【校验手机号格式】 ```js var reg_telephone = /^1(3|4|5|7|8)\d{9}$/; ``` ### 3、【校验邮箱格式】 ```js var reg_email = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/; ``` ### 4、【失去焦点事件】 注意此时将函数名作为Function对象传入,没有()。 ```js $("#username").blur(checkUsername); ``` ### 5、【前后端交互】 涉及到【前后端交互】,将信息封装为对象,是值得我学习的地方。 ```java public class ResultInfo implements Serializable { private boolean flag;//后端返回结果正常为true,发生异常返回false private Object data;//后端返回结果数据对象 private String errorMsg;//发生异常的错误消息 } ``` ### 6、【html的onclick()事件】 参考:[https://blog.csdn.net/ywl570717586/article/details/53130863](https://blog.csdn.net/ywl570717586/article/details/53130863),该博客分割线以下内容。 ### 7、【jackson】 ```java import com.fasterxml.jackson.databind.ObjectMapper; //将info对象序列化为json,返回客户端 ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(info); //将json数据写回客户端 //设置content-type response.setContentType("application/json;charset=utf-8"); response.getWriter().write(json); ``` ### 8、【checkbox】 没有指定value属性得时候,传递过去的值为on!!! ### 9、【获取url中拼接的参数】 ```js //根据传递过来的参数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; } ``` ## 三、工具类的使用 利用【MailUtils】完成邮箱发送,不过得在邮箱设置里面申请开启服务。 利用【uuid工具类】完成随机激活码的生成。 利用【JedisUtils】完成redis客户端的获取,从而操作redis数据库。 利用【JDBCUtils】封装druid连接池,返回数据源对象。 ## 四、路径分发思想 参考HttpServlet的service方法对请求的方式进行路径分发,对应不同的方法,完成不同的类似Servlet完成的功能,真的受益匪浅,回过头来思考原本需要定义那么多那么多的Servlet,现在完全封装到一个UserServlet中,妙啊。 【分发Servlet】 ```java try { Method method = this.getClass().getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); //调用方法 method.invoke(this,req,resp); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } ``` 报错:`java.lang.NoSuchMethodException` 原因:调用方法是Protected修饰的。 解决: 1. 忽略访问权限修饰符+暴力破解。 ```java try { //忽略访问权限修饰符 Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); //暴力破解 method.setAccessible(true); //调用方法 method.invoke(this,req,resp); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } ``` 2. 直接将调用的方法权限修改为public即可。(看到这操作,忍不住笑了) ## 五、数据库问题 ### 1、【连接问题】 `com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure` 报错原因:可能是因为MySQL服务没有开启,打开services.msc,开启MySQL就ok了。 类似的问题还有redis服务端未开启:`redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out`。 ### 2、【mysql语句】 报错:`Every derived table must have its own alias` ```sql SELECT COUNT(*) FROM ( select * from department) AS aa; -- 语法如此,需要给子查询的表加上别名。 ``` ### 3、【缓存优化】 有些资源每次加载页面都会重新请求数据库数据来加载,对数据库的压力比较大,且这些数据不会经常发生变化,可以进行缓存优化。 1. 在service层中,首先判断数据是否存在于redis缓存中,如果有的话直接从缓存中获取。 2. 如果缓存中没有,例如第一次请求时缓存中还不存在,这就需要去数据库中查询,并将查询得到的数据添加进缓存。 [https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/105167978](https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/105167978) ## 六、个人对项目一些细微不足的优化 ### 1、【Alibaba Java Coding Guidelines】 这个插件下载之后,才发现自己原来写的注释那么没有原则,哈哈,这个东西对于有强迫症的人来说,简直魔鬼无疑。 ### 2、【抽取了验证码校验功能】 ```java private boolean checkCode(HttpServletRequest request,HttpServletResponse response) throws IOException { //验证码校验 String check = request.getParameter("check"); HttpSession session = request.getSession(); String checkCodeServer = (String) session.getAttribute("checkCode"); //保证验证码只能使用一次 session.removeAttribute("checkCode"); //验证码不相等 if(checkCodeServer == null||!checkCodeServer.equalsIgnoreCase(check)){ //对用户输入验证码进行判断 if("".equals(check)){ info.setErrorMsg("验证码不能为空"); }else { info.setErrorMsg("验证码错误"); } info.setFlag(false); response.setContentType(JSON_CONTENT_TYPE); String s = mapper.writeValueAsString(info); response.getWriter().write(s); return false; } return true; } ``` ### 3、【关于list=null和list.size()==0】 这也是我在回头看代码的时候思考的一个问题,曾经在一些微信公众号上见过类似的科普。 有时候dao层可能会产生list集合为空的情况,比如没有查询到list,这时候如果返回null,在service层就需要做相应的非null判断,有时候可能会忘记。我最初的想法是初始化一个空的ArrayList,`List list = new ArrayList();` ```java @Override public List findAll() { //List list = Collections.emptyList(); List list = new ArrayList<>(); try{ String sql = "select * from tab_category"; list = template.query(sql,new BeanPropertyRowMapper<>(Category.class)); }catch (Exception e){ } return list; } ``` 在查找资料的过程中,发现Collections集合类有专门产生空集合的方法,例如`List list = Collections.emptyList();`,查看他的源码可以发现,实际上它创建一个静态内部类的对象`private static class EmptyList`。更特别的是,产生的list并没有我们熟悉的add,remove等方法,对他进行这些操作会直接抛出`UnsupportedOperationException`异常。 [https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/105168546](https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/105168546) ## 七、实现未完成的功能 ### 1、【加载完成时,让大图成为第一张】 ![优化](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/%E4%BC%98%E5%8C%96.png) ### 2、【增加验证码为空的信息,并且刷新验证码】 ![验证码优化](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/%E9%AA%8C%E8%AF%81%E7%A0%81%E4%BC%98%E5%8C%96.png) ### 3、【增加我的收藏及分页功能】 ```js 我的收藏 //点击我的收藏 judgeUser = function (){ //未登录 if(user == null){ alert("您尚未登录,请登录!") location.href = "http://localhost/travel/login.html"; }else{ //已登录a // alert(user.uid); var uid = user.uid; // http://localhost/travel/route/pageFavorite?uid=7 location.href = "http://localhost/travel/myfavorite.html?uid=7"; } } ``` ![image-20200329214614651](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200329214614651.png) ![image-20200329215218489](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200329215218489.png) ### 4、【增加了自动登录功能】 利用cookie技术,在客户端存储账号密码,实现自动登录。 ### 5、【增加了热门推荐功能】 增加热门推荐,并连接路线具体信息。 ![image-20200330115015955](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200330115015955.png) ### 6、【增加了首页三大种类旅游路线的显示功能】 ![index](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/index.png) 但是上面对应的样式是真的不知道哪里改,经过debug,发现点击状态下会激活active样式是没错的,bug处在左边三栏和右边字体差了一格,希望知道怎么修复的小伙伴教教我!不然也太难受了。 > 注:在评论区Stevenlinc同学的提醒下,成功修改bug:修改index.html,将a链接放入span中即可。 > > ```html > > > ``` 在这部分基础上,又优化了部分显示: ```html ``` css样式改变: ```css ``` 这部分利用枚举类+反射调用方法消除if……else结构过多的冗杂: ```java /** * @author Summerday * * 旅游主题枚举类 */ public enum CategoryEnum { /** * 人气旅游 */ COUNT("count","findCount"), /** * 最新旅游 */ NEWEST("rdate","findDate"), /** * 主题旅游 */ THEME("isThemeTour","findTheme"), /** * 默认为人气旅游 */ DEFAULT("","findCount"); private String category; private String methodName; //省略getter、setter、constructor } ``` ```java public void findCategory(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String categoryStr = request.getParameter("category"); List list = new ArrayList<>(); //页面显示的数量 int size = 4; //遍历枚举实例 for(CategoryEnum c:CategoryEnum.values()){ //匹配传入参数category if(c.getCategory().equals(categoryStr)){ //获取对应的方法名 String methodName = c.getMethodName(); try { //根据方法名获取方法对象 Method method = routeService.getClass().getMethod(methodName, int.class); //利用实例对象调用方法返回list list = (List) method.invoke(routeService, size); break; }catch (Exception e){ e.printStackTrace(); } } } writeValue(list,response); } ``` ### 7、【增加了收藏排行榜展示分页以及查询功能】 ![image-20200330162047428](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200330162047428.png) ![image-20200330162104173](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200330162104173.png) 这个部分在sql语句部分消耗了许多时间,表关系如下: ![image-20200330164901970](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200330164901970.png) 我的想法是,先根据tab_favorite中的rid进行分组,然后计算每个rid的数量,就是用户收藏的个数,按照降序排列,生成新的子查询表。然后在tab_route中寻找rid与子查询表rid相同的路线,并进行where子查询,模糊匹配路线名,以及金额大小,最后就可以获得: 由收藏次数降序排序的route,并且是完全符合搜寻条件的。 ```java @Override public List findRouteByRangePage(int start, int pageSize, String rname, int first, int last) { //String sql = "select * from tab_route where cid = ? limit ? , ?"; String sql = "SELECT * FROM (SELECT * FROM (SELECT rid,COUNT(rid) AS COUNT FROM tab_favorite " + "GROUP BY rid ORDER BY COUNT(rid) DESC)AS aa)AS bb,tab_route t WHERE t.rid = bb.rid "; StringBuilder sb = new StringBuilder(); //条件们 List params = new ArrayList(); //判断参数是否有值 if(rname!=null&&rname.length()>0){ sb.append("and t.rname like ?"); params.add("%"+rname+"%"); } if(first!=0){ sb.append("and t.price > ? "); //添加?对应的值 params.add(first); } if(last!=0){ sb.append("and t.price < ? "); //添加?对应的值 params.add(last); } //分页 sb.append("limit ? , ? "); sql += sb.toString(); params.add(start); params.add(pageSize); return template.query(sql,new BeanPropertyRowMapper<>(Route.class),params.toArray()); } ``` 面前能够完成需求,只是比较繁琐,不知有没有更好的方案,欢迎交流。 ### 8、【一点小bug】 1. js页面字符串比较大小需要注意:`console.log("5">"123");`结果是true,因为字符串比较回从前向后依次比较,如果希望数值比较,可以利用`parseInt(first) > parseInt(last)`。 2. 在查询搜索的时候,由于没有在本文框中输入信息,因此数据返回的时候,页码部分的调用的ajax请求函数的参数将为`""`,而不是`null`,调用`favoriteRank(null, rname, first, last)`,会造成`javascript:favoriteRank(2,,,)`函数调用失败。我想了个很蠢办法:在调用之前对参数进行判断,如果为`""`,就认为赋null。 ```js if (rname === "" && first === "" && last === "") { favoriteRank(null, null, null, null) } else if (rname === "" && first === "") { favoriteRank(null, null, null, last) } else if (rname === "") { favoriteRank(null, null, first, last) } else { favoriteRank(null, rname, first, last) } ``` 这样子就可以解决了: ![image-20200330170556377](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200330170556377.png) ## 八、个人反思 1. 总体来说在看视频学习的时候能够跟上思路,也许跟项目复杂度不高有些许关系。 2. 在一些小知识掌握的不够扎实,导致许多细节的地方四处寻找博客,时间消耗较多。 3. 还有一个明显的感受就是,听的时候都会,自己做的时候就有点犹豫,生怕哪里搞错。 4. 自己也对项目本身不足之处进行优化,例如验证码刷新失效、某些页面跳转、代码部分重构等,这个过程还是挺锻炼我的排错纠错能力,debug渐渐熟练,原本的一些问题就越发容易解决。 5. 最重要的一点:在敲代码前一定要确定自己的思路,有了思路,写起来真的很清晰!看老师从前台分析到后台,从servlet到service,再到dao,每一层的任务都划分得清清楚楚,真的值得我学习再学习。 6. 最近在培养这种列提纲的意识,真的吃逻辑,但列出来之后就感觉自己还是能行的,无非就是消耗的时间多一点。 7. 其实之前看过一些spring,看到一些依赖注入的问题,其实理解没有那么深刻的,又回过头来练练web项目,理解又更加深刻一些。 8. 一起奋战的兄弟们,加油! --- ## 九、后续补充 有小伙伴在拿到项目之后,找到了小bug,如下: ![image-20200412181822002](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200412181822002.png) 然后出现了:xxx is not defined ![image-20200412181908414](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/image-20200412181908414.png) 因为在函数参数里面,在拼接的时候,**需要用单引号将参数括起来**。 ![TIM图片20200412182040](img/%E9%BB%91%E9%A9%AC%E6%97%85%E6%B8%B8%E7%BD%91%E9%A1%B9%E7%9B%AE%E9%81%87%E5%88%B0%E7%9A%84%E9%97%AE%E9%A2%98%E5%8F%8A%E8%A7%A3%E5%86%B3/TIM%E5%9B%BE%E7%89%8720200412182040.png) 还可以优化一下: ```js var first_param = rname ===""?null:rname; var second_param = first ===""?null:first; var thrid_param = last ===""?null:last; favoriteRank(null, first_param, second_param, thrid_param) ``` 这样就可以解决了。 ## 十、源码下载 之前码云链接失效,可能是因为我误把仓库设置成私有的了,之后我重新上传至码云仓库,需要代码的小伙伴可以进行下载。如果觉得本文对你有帮助,请动动小手点个赞,哈哈。 > [https://gitee.com/tqbx/itheima-travel-demo](https://gitee.com/tqbx/itheima-travel-demo)