黑马旅游网项目bug记录

发表于:2020-5-15 10:40

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:天乔巴夏丶    来源:博客园

  算是经历了整整四天吧,前两天听课跟着视频敲。后两天自己手动完成剩余的其他若干功能,一路debug过来,收获许多,在此记录。希望自己永远保持热忱,加油。
  有小伙伴反应码云链接失效的问题,呃。。如果有需要的小伙伴可以评论区联系我,可以发邮箱给你们,有问题可以交流,一起进步哦。
  一、项目配置问题
  1、maven项目目录结构
  可能一开始创建出来的项目文件目录形式不对,IDEA对目录结构有明显的要求,可以通过以下方法修改,当然其他情况也可以使用;
  
  2、修改目录属性
  
  后来发现可以直接右键点击目录,选择make directory as 效果是一样的。
  
  3、设置web源目录
  
  4、maven低版本和servlet3.0冲突
  视频中使用的是Servlet3.0之后的版本,利用注解配置,我原来之前一直也是注解配置,想试着使用xml配置练练手。但是当我从xml到注解转换的过程中,遇到了一些问题,主要是Servlet版本和maven仓库版本冲突?具体我也不太清清除,过于真实,整个过程迷迷糊糊的,bug频出,也试过很多方法,真的不知道是哪个方法起了作用。
  以下内容仅记录自己的纠错过程,首先我先去视频中的代码文件查看一下有偏差的地方:直接锁定是Servlet的版本问题,servlet3.0之后才可以使用注解,而我使用的是2.5。
  接着,我参考了这个博客:Maven创建webapp骨架无法使用@WebServlet来实现注解配置解决方案,修改了maven仓库中的jar包的web.xml内容,可能是我操作的问题,并没有见效。
  接着又在某个论坛上看到一个方法,可以重新指定xml的版本。【后面测试了几次,貌似和这个关系不大】
  
  接着在pom.xml中添加servlet3.0之后的依赖,需要注意的是,要指定scope为provide,不然的话可能会产生冲突。一定要注意找到填写正确坐标,有一次我把artifactId里写成servlet-api死活下载不来
   <!--Servlet-->
  <dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
  </dependency>
    就像这样的冲突错误(下面两种都是因为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、控制台输出乱码解决
  
  settings->Build,Execution,Deployment->Build Tools->Maven->Runner。
  设置VM-Option参数,指定虚拟机字符集:-Dfile.encoding=gb2312,如果不行可以设置称其他的。
  二、前台代码
  1、发送异步请求
   //校验通过,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、校验手机号格式
 var reg_telephone = /^1(3|4|5|7|8)\d{9}$/;
   3、校验邮箱格式
 var reg_email = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
   4、失去焦点事件
  注意此时将函数名作为Function对象传入,没有()。
 $("#username").blur(checkUsername);
   5、前后端交互
  将信息封装为对象,是值得我学习的地方。
  public class ResultInfo implements Serializable {
  private boolean flag;//后端返回结果正常为true,发生异常返回false
  private Object data;//后端返回结果数据对象
  private String errorMsg;//发生异常的错误消息
  }
   6、jackson
  利用mapper对象操作json数据
   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中拼接的参数
   //根据传递过来的参数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
   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修饰的。
  解决:
  忽略访问权限修饰符+暴力破解。
   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();
  }
   直接将调用的方法权限修改为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
 SELECT COUNT(*) FROM ( select * from department) AS aa; -- 语法如此,需要给子查询的表加上别名。
   3、缓存优化
  有些资源每次加载页面都会重新请求数据库数据来加载,对数据库的压力比较大,且这些数据不会经常发生变化,可以进行缓存优化。
  在service层中,首先判断数据是否存在于redis缓存中,如果有的话直接从缓存中获取。
  如果缓存中没有,例如第一次请求时缓存中还不存在,这就需要去数据库中查询,并将查询得到的数据添加进缓存。
  https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/105167978
  五、个人对项目一些细微不足的优化
  1、Alibaba Java Coding Guidelines
  这个插件下载之后,才发现自己原来写的注释那么没有原则,哈哈,这个东西对于有强迫症的人来说,简直魔鬼无疑。
  2、抽取了验证码校验功能
   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、关于listnull和list.size()0
  这也是我在回头看代码的时候思考的一个问题,曾经在一些微信公众号上见过类似的科普。
  有时候dao层可能会产生list集合为空的情况,比如没有查询到list,这时候如果返回null,在service层就需要做相应的非null判断,有时候可能会忘记。我最初的想法是初始化一个空的ArrayList,List<Category> list = new ArrayList()
   ![优化](E:\1JavaBlog\maven\pic\优化.png)![优化](E:\1JavaBlog\maven\pic\优化.png)    @Override
  public List<Category> findAll() {
  //List<Category> list = Collections.emptyList();
  List<Category> 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<Category> list = Collections.emptyList();,查看他的源码可以发现,实际上它创建一个静态内部类的对象private static class EmptyList<E>。更特别的是,产生的list并没有我们熟悉的add,remove等方法,对他进行这些操作会直接抛出UnsupportedOperationException异常。
  https://blog.csdn.net/Sky_QiaoBa_Sum/article/details/105168546
  六、实现未完成的功能
  1、加载完成时,让大图成为第一张
  
  2、增加验证码为空的信息,并且刷新验证码
  避免图文不符。
  
  3、增加我的收藏及分页功能
    <a href="javascript:void(judgeUser())" id="myFavorite" class="collection">我的收藏</a>
  //点击我的收藏
  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";
  }
  }

  
  4、增加了自动登录功能
  利用cookie技术,在客户端存储账号密码,实现自动登录。
  5、增加了热门推荐功能
  增加热门推荐,并连接路线具体信息。
  
  6、增加了首页三大种类旅游路线的显示功能
  
  但是上面对应的样式是真的不知道哪里改,经过debug,发现点击状态下会激活active样式是没错的,bug处在左边三栏和右边字体差了一格,希望知道怎么修复的小伙伴教教我!不然也太难受了。
  7、增加了收藏排行榜展示分页以及查询功能
  
  这个部分在sql语句部分消耗了许多时间,表关系如下:
  
  我的想法是,先根据tab_favorite中的rid进行分组,然后计算每个rid的数量,就是用户收藏的个数,按照降序排列,生成新的子查询表。然后在tab_route中寻找rid与子查询表rid相同的路线,并进行where子查询,模糊匹配路线名,以及金额大小,最后就可以获得:
  由收藏次数降序排序的route,并且是完全符合搜寻条件的。
   @Override
  public List<Route> 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
  js页面字符串比较大小需要注意:console.log("5">"123");结果是true,因为字符串比较回从前向后依次比较,如果希望数值比较,可以利用parseInt(first) > parseInt(last)。
  在查询搜索的时候,由于没有在本文框中输入信息,因此数据返回的时候,页码部分的调用的ajax请求函数的参数将为"",而不是null,调用favoriteRank(null, rname, first, last),会造成javascript:favoriteRank(2,,,)函数调用失败。我想了个很蠢办法:在调用之前对参数进行判断,如果为"",就认为赋null。
   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)
  }
   这样子就可以解决了:
  
  七、个人反思
  总体来说在看视频学习的时候能够跟上思路,也许跟项目复杂度不高有些许关系。
  在一些小知识掌握的不够扎实,导致许多细节的地方四处寻找博客,时间消耗较多。
  还有一个明显的感受就是,听的时候都会,自己做的时候就有点犹豫,生怕哪里搞错。
  自己也对项目本身不足之处进行优化,例如验证码刷新失效、某些页面跳转、代码部分重构等,这个过程还是挺锻炼我的排错纠错能力,debug渐渐熟练,原本的一些问题就越发容易解决。
  最重要的一点:在敲代码前一定要确定自己的思路,有了思路,写起来真的很清晰!看老师从前台分析到后台,从servlet到service,再到dao,每一层的任务都划分得清清楚楚,真的值得我学习再学习。
  最近在培养这种列提纲的意识,真的吃逻辑,但列出来之后就感觉自己还是能行的,无非就是消耗的时间多一点。
  其实之前看过一些spring,看到一些依赖注入的问题,其实理解没有那么深刻的,又回过头来练练web项目,理解又更加深刻一些。
  一起奋战的兄弟们,加油!
  八、后续补充
  有小伙伴在拿到项目之后,找到了小bug,如下:
  
  然后出现了:xxx is not defined
  
  因为在函数参数里面,在拼接的时候,需要用单引号将参数括起来。
  
   还可以优化一下:
   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)
    这样就可以解决了。

      本文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号