黑马旅游网项目后端思路 以及常见错误的总结 需源码请私信

个人建议 这个项目 还是蛮不错的 有很多注意思考的地方
所用技术 以及环境介绍

环境搭建 配置

技术选层

  1. Web层

    	Servlet 前端控制器
    	Html 视图   数据展示  效果展示
    	Filter 过滤器
    	Beanutils  数据封装
    	Jackson  json序列化工具 
    
  2. Service层

    	Javamail 邮件发送邮件
    	Redis  nosql内存数据库
    	Jedis  java的redis的客户端
    
  3. Dao层 数据访问层

    	Mysql 数据库
    	Druid数据库连接池
    	JdbcTempalte  jdbc的工具
    

服务器使用的是Tomcat 数据库mysql

导入对应的pom.xml时候 对应加载Maven

过滤器 防止出现乱码

        //将父接口转为子接口
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) rep;
        //获取请求方法
        String method = request.getMethod();
        //解决post请求中文数据乱码问题
        if(method.equalsIgnoreCase("post")){
            request.setCharacterEncoding("utf-8");
        }
        //处理响应乱码
        response.setContentType("text/html;charset=utf-8");
        filterChain.doFilter(request,response);

项目结构
在这里插入图片描述

注册功能 表单展示

在这里插入图片描述
Register.html
使用js完成表单检验
使用ajax完成表单提交
注册完成跳转到成功页面

regisUserServlet
获取数据
封装User对象
调用service完成注册
根据service的返回 提示信息
1. 将提示信息转为json
2. 设置响应头contertType

对应的注册提交表单后 后端处理 核心代码 清楚session会话域 防止验证码因为刷新失效

@WebServlet("/registerUserServlet")
public class RegisUserServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 验证码
        String check = req.getParameter("check");
        // 从session中获取
        HttpSession session = req.getSession();
        String checkcode_server = (String) session.getAttribute("CHECKCODE_SERVER");
        // 清楚Session会话域      保证验证码只能使用一次
        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);
            resp.setContentType("application/json;charset=utf-8");
            resp.getWriter().write(json);
            return ;
        }

        Map<String, String[]> map = req.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数据写回客户端
        // 设置content-type
        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().write(json);
    }

注册完成之后会进行一个邮箱的注册激活认证 需要邮箱的验证

工具类的展示


/**
 * 发邮件工具类
 */

public final class MailUtils {
    private static final String USER = ""; // 发件人称号,同邮箱地址
    private static final String PASSWORD = ""; // 如果是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;
    }

再用户激活的时候
进入UserDaoimpl中进行修改 加入存储 status 和 code的代码逻辑

后台代码实现
编写 RegisUserServlet
编写UserService以及UserSericelmpl
编写UserDao 以及UserDaoImpl

登录功能 表单展示

在这里插入图片描述

login页面中的表单提交

		$(function () {
			// 给登录按钮绑定单击事件
			$("#btn_sub").click(function(){
				// 发送ajax请求 提交表单数据
				$.post("user/login",$("#loginForm").serialize(),function (data){
					 // data : {flag:false ,errorMsg:''}
					if(data.flag){
						// 登录成功
						location.href="index.html"
					}else{
						// 登录失败
						$("#errorMsg").html(data.errorMsg);
					}
				});
			});
		});

index页面中用户姓名的提示信息功能 ‘

	再登录完成之后 需要将用户名 存入到session中   对后面的欢迎: 用户 使用
        if(u !=null && "Y".equals(u.getStatus())){
            // 登录成功
            info.setFlag(true);
            // 登录成功之后 将页面中中获得名字存入到session  为之后登录之后名字进行显示
            req.getSession().setAttribute("user",u);
        }
	对应的findUserServlet

        // 从session中获取登录用户
        Object user = req.getSession().getAttribute("user");
        // 将user写回客户端

        ObjectMapper mapper = new ObjectMapper();
        resp.setContentType("application/json;charset=utf-8");
        mapper.writeValue(resp.getOutputStream(),user);

登录的条件就是 session 中有user对象
实现步骤
访问servlet 将session销毁
跳转到登录页面

登录成功判断的三种对应的情况’
// 登录对应的三种登录可能型
// 1 可能不存在对饮固定用户名
// 2 存在账号和密码 正确 但是没有正确的激活状态
// 3 账号和密码 都存正确

优化Servelt 进行方法的抽取
减少Servlet 的数量 现在是一个Servlet 将其中优化为 一个模块 一个Servlet 相当于在数据库中一张表对应一个Servlet 在Servlet中提供不同的方法 完成用户的请求
中间继承方法 BaseServlet
完成方法的分发 拿到最后的路径方法名 获取方法对象 通过反射方式来执行这个方法

    // 继承HttpServlet 实现其中对应的service 方法
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        // 完成方法的分发  拿到路径最后的方法名  获取方法对象  通过反射方式来执行这个方法
        // 获取请求路径
        String uri=req.getRequestURI();  // user/add
        // 获取方法名称
        String methodName = uri.substring(uri.lastIndexOf('/')+1);

        // 获取方法对象Method
        try {
            // 忽略访问权限修饰符  获取方法
            Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
            // 执行方法
            // 暴力反射
            method.setAccessible(true);
            method.invoke(this,req,resp);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    //  方法的抽取   直接提高对应方法的使用   减少代码的冗余
    //  直接将传入的对象序列化为json  并且写回客户端
    public void writeValue(Object obj, HttpServletResponse resp) throws IOException {
        // 序列化 json返回
        ObjectMapper mapper = new ObjectMapper();
        resp.setContentType("application/json;charset=utf-8");
        mapper.writeValue(resp.getOutputStream(),obj);
    }

    // 将传入的对象序列化为json 返回
    public String writeValueAsString(Object obj) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(obj);

设置字符参数 控制台乱码问题解决: 会出现乱码
File—Build —Maven — Runner –VM Options --Dfile.encodeing=gb2312

分类数据展示功能

存在问题:
	每次刷新都会请求数据库加载 分类数据  分类数据不会经常产生变化,
	对数据库的压力大   采用缓存  减少对数据库的访问次数
	查询效率高 故使用redis进行缓存

Index.html 包含 header.html

发送ajax请求访问服务器 加载真正的分类数据
遍历数组

CateaggeoryServlet

FindAll(){
调用Servlet查询list
将List集合序列化为json返回

}
CateaggeoryServlet

    private CategoryService service = new CategoryServiceImpl();
    // 查询所有分类数据展示
    public void findAll(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 调用serice 查询所有
        List<Category> cs = service.findAll();

        // 序列化json返回
        ObjectMapper mapper = new ObjectMapper();
        resp.setContentType("application/json;charset=utf-8");
        mapper.writeValue(resp.getOutputStream(),cs);

对应的FindAll

    // 控制对应的 cid rname 以及对应的页码显示
    @Override
    public List<Category> findAll() {
        //  从redis中查询
        //  获取jedis客户端
        Jedis jedis = JedisUtil.getJedis();
        //  查询sortedset中的分数(cid)和值(cname)
        Set<Tuple> categorys = jedis.zrangeWithScores("category", 0, -1);

        List<Category> cs = null;
        //  判断查询的集合是否为空
        if (categorys == null || categorys.size() == 0) {
            //  如果为空,需要从数据库查询,在将数据存入redis
            //  从数据库查询
            cs = categoryDao.findAll();
            //  将集合数据存储到redis中的 category的key
            for (int i = 0; i < cs.size(); i++) {
                jedis.zadd("category", cs.get(i).getCid(), cs.get(i).getCname());
            }
        } else {
            // 如果不为空,将set的数据存入list    查询的是set 但是返回需要List集合 所以进行集合之间的转换
            cs = new ArrayList<Category>();
            for (Tuple tuple : categorys) {
                Category category = new Category();
                category.setCname(tuple.getElement());
                category.setCid((int)tuple.getScore());
                cs.add(category);
            }
        }

        return cs;

对分类的数据进行缓存优化
分类的数据在每一次页面加载后都会重新请求数据库加载 对数据库的压力比较大 而且分类的数据不经常产生变化 所以可以使用redis来缓存这个数据

用redis进行缓存的优化
在CategoryService
从redis中查询是否为null

第一次访问查询数据库 将数据库存入redis中

不是第一次访问
返回集合

CateoryDao
均执行dindAll() 方法
在这里插入图片描述

redis使用的时候 一定不要忘记打开redis

reids  打开的 win+R  cd 进入对应的redis文件夹

    使用  redis-server.exe redis.windows.conf
reids  打开的 win+R  cd 进入对应的redis文件夹

    使用  redis-server.exe redis.windows.conf

    如果不可以则使用一下命令

    redis-client.exe

    shutdown

    exit

    然后重新用以上的命令

旅游线路的分页展示
点击发现不同的分类后 将来看到的旅游线路不一样的 通过分析数据库表结构 发 旅游线路表和分类表一个 多对一的关系

通过以下三个类功能进行实现
RouteSerlvet
RouteService
RouteDao

Select * from tab_route where cid = ? ;
Redis中查询score(cid)

  • <a Href =”source_list.html?cid = ‘+data[i].cid+’”>’+data[i].cname+’
  • 类别id的传递

    Redis中查询score(cid)
    页面传递cid
    Header.html传递cid
    

    分页展示旅游线路数据

    同步方式 可以将数据放入 request域中 在jsp中使用foreach标签和el 表达式 进行遍历数据 生成数据

    Html只能通过异步的方式 完成前台页面的展示

    客户端页面
    客户端页面发送的ajax请求PageBean的数据
    携带参数
    currentPage (当前页码)
    pageSize(每页显示的条数)
    cid(分页cid)

    在RouteServlet进行响应和处理 此处的判断条件需要判空处理 防止出现空指针

        public void pageQuery(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 接受 三个参数
            String currentPageStr = request.getParameter("currentPage");
            String pageSizeStr = request.getParameter("pageSize");
            String cidStr = request.getParameter("cid");
    
            //接受rname 线路名称
    
            //  再获取对应的rname时候需要做出对应的 条件限制  否在再不输入rname的时候 会造成对应的空指针异常
            //  尤其是再分页查询的时候  再点击第二页的时候 会出现 不能正常显示到数据的现象
            String rname = request.getParameter("rname");
    
            if(rname!= null && rname.length()>0 && !"null".equals(rname)){
                rname = new String(rname.getBytes("iso-8859-1"),"utf-8");
            }
    
            int cid = 0;//类别id
            // 处理参数                防止出现空指针异常
            if(cidStr != null && cidStr.length() > 0 && !"null".equals(cidStr)){
                cid = Integer.parseInt(cidStr);
            }
    
            //当前页码,如果不传递,则默认为第一页
            int currentPage = 0;
            if(currentPageStr != null && currentPageStr.length() > 0  ){
                currentPage = Integer.parseInt(currentPageStr);
            }else{
                currentPage = 1;
            }
    
            //每页显示条数,如果不传递,默认每页显示5条记录
            int pageSize = 0;
            if(pageSizeStr != null && pageSizeStr.length() > 0  ){
                pageSize = Integer.parseInt(pageSizeStr);
            }else{
                pageSize = 5;
            }
    
            // 调用service查询PageBean对象
            PageBean<Route> pb = routeService.pageQuery(cid, currentPage, pageSize,rname);
    
            //  将pageBean对象序列化为json,返回
            writeValue(pb,response);
    

    服务器端 PageBean 将被序列化为json 返回到客户端

    // 分页对象
    // 将参数封装到PageBean中
    public class PageBean<T> {
    
        private int totalCount;  // 总记录数
        private int totalPage;   // 总页数
        private int currentPage; // 当前页码
        private int pageSize;    // 每页显示的条数
        private List<T> list;//每页显示的数据集合
    

    对应的PgeQuery

        public void pageQuery(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 接受 三个参数
            String currentPageStr = request.getParameter("currentPage");
            String pageSizeStr = request.getParameter("pageSize");
            String cidStr = request.getParameter("cid");
    
            //接受rname 线路名称
    
            //  再获取对应的rname时候需要做出对应的 条件限制  否在再不输入rname的时候 会造成对应的空指针异常
            //  尤其是再分页查询的时候  再点击第二页的时候 会出现 不能正常显示到数据的现象
            String rname = request.getParameter("rname");
    
            if(rname!= null && rname.length()>0 && !"null".equals(rname)){
                rname = new String(rname.getBytes("iso-8859-1"),"utf-8");
            }
    
    
    
            int cid = 0;//类别id
            // 处理参数                防止出现空指针异常
            if(cidStr != null && cidStr.length() > 0 && !"null".equals(cidStr)){
                cid = Integer.parseInt(cidStr);
            }
    
            //当前页码,如果不传递,则默认为第一页
            int currentPage = 0;
            if(currentPageStr != null && currentPageStr.length() > 0  ){
                currentPage = Integer.parseInt(currentPageStr);
            }else{
                currentPage = 1;
            }
    
            //每页显示条数,如果不传递,默认每页显示5条记录
            int pageSize = 0;
            if(pageSizeStr != null && pageSizeStr.length() > 0  ){
                pageSize = Integer.parseInt(pageSizeStr);
            }else{
                pageSize = 5;
            }
    
            // 调用service查询PageBean对象
            PageBean<Route> pb = routeService.pageQuery(cid, currentPage, pageSize,rname);
    
            //  将pageBean对象序列化为json,返回
            writeValue(pb,response);
        }
    

    旅游线路名称查询 模糊查询

    在这里插入图片描述

    查询参数的传递 -------------------- 点击搜索按钮 触发搜索事件

    在header.html中
    给按钮设置id 绑定响应事件

     $(function () {
            // 查询分类数据
            // 设置确定数据访问的路径
            $.post("category/findAll",{},function (data) {
                var lis = ' <li class="nav-active"><a href="index.html">首页</a></li>';
                // 遍历数组  拼接字符串
                for(var i =0; i<data.length; i++){
                    // 后面根据 cid 进行访问查询对应的 数据库内容
                    var li = '<li><a href="route_list.html?cid='+data[i].cid+'">'+data[i].cname+'</a><li>';
                    lis += li;
                }
    
                //拼接收藏排行榜
                lis+'<li><a href="favoriterank.html">收藏排行榜</a></li>';
    
    
                // 将lis字符串 设置到ul的html内容中
                $("#category").html(lis);
            });
    
            //  给搜索按钮绑定单击事件  获取搜索输入框的内容
            $("#search-button").click(function () {
                // 需要线路名称  用作模糊匹配
                var rname=$("#search_input").val();
                // 跳转路径  进行拼接
                var cid=getParameter("cid");
                    location.href="http://localhost/travel/route_list.html?cid="+cid+"&rname="+rname;
            });
        });
    

    修改后台代码
    先进入CategoryServlet 调用service.findAll()
    对应进入categoryDao 调用categoryDao.findAll();

            // 调用serice 查询所有
            List<Category> cs = service.findAll();
    

    对应的findAll

        private CategoryDao categoryDao = new CategoryDaoImpl();
    
        // 控制对应的 cid rname 以及对应的页码显示
        @Override
        public List<Category> findAll() {
            //  从redis中查询
            //  获取jedis客户端
            Jedis jedis = JedisUtil.getJedis();
            //  查询sortedset中的分数(cid)和值(cname)
            Set<Tuple> categorys = jedis.zrangeWithScores("category", 0, -1);
    
            List<Category> cs = null;
            //  判断查询的集合是否为空
            if (categorys == null || categorys.size() == 0) {
                //  如果为空,需要从数据库查询,在将数据存入redis
                //  从数据库查询
                cs = categoryDao.findAll();
                //  将集合数据存储到redis中的 category的key
                for (int i = 0; i < cs.size(); i++) {
                    jedis.zadd("category", cs.get(i).getCid(), cs.get(i).getCname());
                }
            } else {
                // 如果不为空,将set的数据存入list    查询的是set 但是返回需要List集合 所以进行集合之间的转换
                cs = new ArrayList<Category>();
                for (Tuple tuple : categorys) {
                    Category category = new Category();
                    category.setCname(tuple.getElement());
                    category.setCid((int)tuple.getScore());
                    cs.add(category);
                }
            }
    
            return cs;
        }
    

    Dao数据库种的查询

        // 查询所有对应的数据
        public List<Category> findAll() {
    
            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;
        }
    

    对结果集合进行返回 将对应的数据填充到前端位置

    查看详情
    Route_detail.html? rid =1
    当页码加载时 发送ajax 请求 查询Route对象

    查看详情 所对应的  超链接    再超连接上面绑定ird
    当页码加载时发送ajax 请求  查询route对象
    			RouteServlet
    			findOne(){
    					接收rid
    					调用service查询
    					转换json返回
    		}
    RouteService
    	Findone( int  rid){
    			1 根据id查询route对象RouteDao
    			2根据rid路线id查询Tab_route_img 将集合设置到route对象
    			3  根据sid卖家id查询 tab_seller 查询卖家信息  将其设置到route对象 
    	}
    

    对应的findOne方法

        public void findOne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                // 接收参数id
                String rid = request.getParameter("rid");
                // 调用service 查询route对象
                Route route = routeService.finOne(rid);
                // 转为json写回客户端
                writeValue(route,response);
        }
    

    对应的 rid条件 进行查询 routeService

            Route route  = routeDao.finOne(Integer.parseInt(rid));
    
            // 根据route的id查询图片集合信息
            List<RouteImg> routeImgList = routeImgDao.finByRid(route.getRid());
    
            // 将集合 设置到route对象
            route.setRouteImgList(routeImgList);
            Seller seller = sellerDao.findByid(route.getSid());
            route.setSeller(seller);
    

    访问以下三个Dao
    RouteDao
    RouteImplDao
    SellerDao

    调用三个Dao

    前台代码
    	Route_detail.html中加载后
    				获取rid
    				发送ajax请求 获取route对象
    				解析对象的数据 
    

    旅游线路收藏功能

    分析

    判断当前登录用户是否收藏过该线路
    当页面加载完成后 发送ajax请求 获取用户是否收藏的标记
    传递线路id rid
    根据标记 展示不用的按钮样式 请求对应的RouteServlet
    获取rid线路id
    获取当前登录的用户对象 session
    如果user对象为null 设置条uid =0

    	调用FavoriteService 查询 传递rid  uid
    	写回客户端flag标记
    

    FavoriteService
    isFavorite(rid , uid)
    FavoriteDao
    findByUi dan Rid(uid rid)

    Route_detai.html

    收藏次数的动态展示

    点击收藏线路的按钮

    前台判断用户是否登录
    如果登录可以点击按钮
    没有登录 给出提示信息
    发送AJAX请求
    RouteServlet
    获取线路的rid
    获取用户的uid
    调用service添加

    FavoriteServlet add方法

            // 获取线路id
                String rid = request.getParameter("rid");
            //  获取当前登录的用户 user
            User user = (User) request.getSession().getAttribute("user");
            int uid;
            if(user == null){
               return ;
            }else{
                uid= user.getUid();
            }
    
            //  调用service 添加
            favoriteService.add(rid,uid);
        }
    

    FavoriteDao add方法

        public void add(int rid, int uid) {
                String sql = "insert into tab_favorite  value(?,?,?)";
                template.update(sql,rid,new Date(),uid);
        }
    

    个人总结 整体项目
    我个人犯错误和难点集合
    配置文件版本
    对应的空指针报错异常处理
    细节上的字符输入错误 大小写出错
    分页的代码处理
    queryForObject有且只能查询一条数据 如果数据库中没有这条数据或者数据库中这条数据又相同的异常也会抛出
    主要因为是 数据查询时 出现 不能有正确的对应信息
    抛出异常 使用try catch
    BaseServlet 进行抽取 种的暴力注解
    先是servlet层 调用 service层进行实现类 调用dao层 进行查表 调用执行sql语句 进行返回查询结果

    欢迎遇到问题进行留言一起讨论

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值