草庐IT

day13-功能实现12

liyuelian 2023-04-16 原文

家居网购项目实现012

以下皆为部分代码,详见 https://github.com/liyuelian/furniture_mall.git

29.功能27-Ajax检验注册名

29.1需求分析/图解

用户注册时,后端通过验证,提示用户当前输入的用户名是否可用。

29.2思路分析

29.3代码实现

dao层和service层的方法在之前已经实现过了,这里不必再写

29.3.1web层

MemberServlet添加方法isExistUserName,该方法返回json格式的数据给前端

/**
 * 校验某个用户名是否已经存在数据库中
 *
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void isExistUserName(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //1.获取用户名
    String username = req.getParameter("username");
    //2.调用service
    boolean isExistUsername = memberService.isExistsUsername(username);
    //3.返回json格式[按照前端的需求]
    //{"isExist":false}
    //先使用最简单的拼接,一会使用可拓展的方式
    //String resultJson = "{\"isExist\":" + isExistUsername + "}";
    
    //=>将要返回的数据返回map=>json
    //使用map可以方便拓展
    HashMap<String, Object> resultMap = new HashMap<>();
    resultMap.put("isExist", isExistUsername);
    String resultJson = new Gson().toJson(resultMap);

    //4.返回json
    resp.getWriter().write(resultJson);
}

29.3.2前端

login.jsp使用Ajax局部请求刷新

//给注册模块的用户名输入框绑定一个失去焦点事件
$("#username").blur(function () {
    //获取输入的用户名
    var username = this.value;
    //发出ajax请求(使用jquery的$.getJSON())
    //jQuery.getJSON(url,data,success(data,status,xhr))
    $.getJSON(
        "memberServlet",
        //使用json格式发送数据
        {
            "action": "isExistUserName",
            "username": username,
        },
        function (data) {
            if (data.isExist) {
                $("span.errorMsg").text("用户名已经存在,不能使用");
            } else {
                $("span.errorMsg").text("用户名可用");
            }
        })
})

29.4完成测试

30.功能28-Ajax添加购物车

30.1需求分析/图解

当前每次添加家居到购物车方式,每次都需要sendRedirect(),会刷新整个页面,数据传输开销大

实际上添加家居到购物车,整个页面只需要刷新购物车的数量

因此使用ajax进行优化,只要刷新购物车的数量即可

30.2思路分析

30.3代码实现

30.3.1web层

CartServlet:

/**
 * 添加家居数据到购物车-Ajax方式
 *
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void addItemByAjax(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    //得到添加的家居ID
    int id = DataUtils.parseInt(req.getParameter("id"), 0);
    //获取到id对应的Furn对象
    Furn furn = furnService.queryFurnById(id);
    if (furn == null || furn.getStock() == 0) {//如果没有对应的家居信息或者该家居库存为0
        return;//结束业务
    }
    //根据furn构建CartItem
    CartItem item =
            new CartItem(furn.getId(), furn.getName(), furn.getPrice(), 1, furn.getPrice());
    //从session获取cart对象
    Cart cart = (Cart) req.getSession().getAttribute("cart");
    if (null == cart) {//如果当前的session没有cart对象
        //创建一个cart对象
        cart = new Cart();
        //将其放入到session中
        req.getSession().setAttribute("cart", cart);
    }
    //将cartItem加入到cart对象
    cart.addItem(item);

    //添加完毕后,将当前购物车的商品数量以json形式的数据返回
    //前端得到json后进行局部刷新即可
    //1.规定json格式{"cartTotalcount,3"}
    Map<String, Object> resultMap = new HashMap<>();
    //2.创建map
    resultMap.put("cartTotalcount", cart.getTotalCount());
    //3.转为json
    String resultJson = new Gson().toJson(resultMap);
    resp.getWriter().write(resultJson);
}

30.3.2前端

customer/index.jsp

//给add to cart绑定事件
$("button.add-to-cart").click(function () {
    //获取到点击的furn-id
    var furnId = $(this).attr("furnId");
    //发出一个请求-添加家居=>后面改成ajax
    //location.href = "cartServlet?action=addItem&id=" + furnId;

    //改为ajax请求,得到数据进行局部刷新,解决刷新这个页面的效率低的问题
    //jQuery.getJSON(url,data,success(data,status,xhr))
    $.getJSON(
        "cartServlet",
        {
            "action": "addItemByAjax",
            "id": furnId
        },
        function (data) {
            //刷新局部 <span class="header-action-num">
            $("span.header-action-num").text(data.cartTotalCount)
        }
    )
})

30.3.3解决ajax请求转发失效的问题

测试上面的代码,会发现针对ajax的重定向和请求转发失效了,AuthFilter.java的权限拦截没有用了,即我们点击add to cart,后台服务没有响应,怎么办?

使用ajax向后台发送请求跳转页面无效的原因:

  1. 主要是服务器得到的是ajax发送过来的request,这个请求不是浏览器发送的请求,而是ajax请求的。因此servlet对request进行请求转发或者重定向都不能影响浏览器的跳转
  2. 这时就出现了请求转发和重定向失效的问题
  3. 解决方案:如果想要实现跳转,可以返回url,在浏览器执行window.location(url)

utils包WebUtils:

package com.li.furns.utils;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 李
 * @version 1.0
 */
public class WebUtils {
    /**
     * 判断一个请求是否是ajax请求
     *
     * @param request
     * @return
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
    }
}

AuthFilter:

package com.li.furns.filter;

import com.google.gson.Gson;
import com.li.furns.entity.Member;
import com.li.furns.utils.WebUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * 这是用于权限验证的过滤器,对指定的url进行验证
 * 如果登录过,就放行;如果没有登录,就返回登录页面
 *
 * @author 李
 * @version 1.0
 */
public class AuthFilter implements Filter {
    //后面我们把要排除的url放入到excludedUrls中
    private List<String> excludedUrls;

    public void init(FilterConfig config) throws ServletException {
        //获取到配置的excludedUrls
        String strExcludedUrls = config.getInitParameter("excludedUrls");
        //进行分割
        String[] splitUrl = strExcludedUrls.split(",");
        //将splitUrl转成List,赋给excludedUrls
        excludedUrls = Arrays.asList(splitUrl);
        System.out.println("excludedUrls=>" + excludedUrls);
    }

    public void destroy() {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        //权限验证
        HttpServletRequest req = (HttpServletRequest) request;
        //得到请求的url
        String url = req.getServletPath();

        //判断是否要验证
        if (!excludedUrls.contains(url)) {//如果url不在配置的规则中,就进行校验
            //得到session中的member对象
            Member member = (Member) req.getSession().getAttribute("member");
            if (member == null) {//说明用户没有登录过
                //先判断该请求是否为Ajax请求
                if (!WebUtils.isAjaxRequest(req)) {//不是ajax请求
                    //转发到登录页面
                    //不要使用重定向,因为重定向的url符合过滤器规则时也会被拦截,
                    //如果设置不合理就会出现 请求无线循环重定向的 情况
                    req.getRequestDispatcher("/views/member/login.jsp").forward(request, response);
                } else {//如果是ajax请求
                    //以json格式返回一个url
                    HashMap<String, Object> resultMap = new HashMap<>();
                    resultMap.put("url", "views/member/login.jsp");
                    String resultJson = new Gson().toJson(resultMap);
                    response.getWriter().write(resultJson);
                }
                return;//返回
            }
        }
        //否则就放行
        chain.doFilter(request, response);
    }
}

修改前端接口customer/index.jsp

//给add to cart绑定事件
$("button.add-to-cart").click(function () {
    //获取到点击的furn-id
    var furnId = $(this).attr("furnId");
    //发出一个请求-添加家居=>后面改成ajax
    //location.href = "cartServlet?action=addItem&id=" + furnId;

    //改为ajax请求,得到数据进行局部刷新,解决刷新这个页面的效率低的问题
    //jQuery.getJSON(url,data,success(data,status,xhr))
    $.getJSON(
        "cartServlet",
        {
            "action": "addItemByAjax",
            "id": furnId
        },
        function (data) {
            if (data.url == undefined) {
                //说明没有返回url,过滤器没有让跳转到登录页面,即说明已经登录过了
                $("span.header-action-num").text(data.cartTotalCount);
            } else {
                //否则说明当前服务器返回了url,要求定位
                location.href = data.url;
            }
        }
    )
})

30.4完成测试

没有登录的情况下,点击add to cart,页面成功跳转到login.jsp

登录后,点击添加购物车,成功添加

31.功能29-上传/更新家居图片

31.1需求分析/图解

  1. 后台修改家居,可以点击图片,选择新的图片
  2. 这里会使用到文件上传功能

31.2思路分析

31.3代码实现

31.3.1前端页面

修改furn_manage.jsp:添加事件,修改上传表单

<style type="text/css">
    #pic{
        position: relative;
    }
    input[type="file"] {
        position: absolute;
        left: 0;
        top: 0;
        height: 150px;
        opacity: 0;
        cursor: pointer;
    }
</style>
<script type="text/javascript">
    function prev(event) {
        //获取展示图片的区域
        var img = document.getElementById("prevView");
        //获取文件对象
        let file = event.files[0];
        //获取文件阅读器
        let reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = function () {
            //给 img 的 src 设置图片 url
            img.setAttribute("src", this.result);
        }
    }
</script>
....
....
<%--修改上传表单--%>
<form action="manage/furnServlet" method="post" enctype="multipart/form-data">
    .....
    ....
    ....
    <div id="pic">
        <img id="prevView"
             class="img-responsive ml-3"
             src="${requestScope.furn.imgPath}"
             alt=""/>
        <input type="file" name="imgPath" id="" value="${requestScope.furn.imgPath}"
               onchange="prev(this)"/>
    </div>
....
....

</form>
空指针异常

在修改后端代码前,我们先来试着运行一下,得到的结果是空指针异常

分析空指针异常

因为前端的表单使用了enctype="multipart/form-data",这意味着如果后端以request.getParameter("attrName")方式获取表单属性的方法已经失效了,因为此时表单项不再以字符串的形式传递。

后端得不到表单的action属性,无法在BasicServlet中完成反射,因此出现了空指针异常。

解决方法

因为BasicServlet是通过参数action获得将要反射的方法,因此直接在表单提交中写上action的值。

 <form action="manage/furnServlet?id=${requestScope.furn.id}&action=update&pageNo=${param.pageNo}" method="post" enctype="multipart/form-data">

31.3.2web层

引入相关jar包

在utils包WebUtils中定义一个文件上传的路径:

//定义一个文件上传的路径
public static String FURN_IMG_DIRECTORY = "assets/images/product-image";

FurnService中修改update方法:

/**
 * 处理修改家居的请求
 *
 * @param req
 * @param resp
 * @throws ServletException
 * @throws IOException
 */
protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //如果表单为 enctype="multipart/form-data"
    //那么使用request.getParameter("attrName")的方式将不能直接从表单中得到attribute
    int id = DataUtils.parseInt(req.getParameter("id"), 0);
    //获取到对应的furn对象[从数据库中获取]
    Furn furn = furnService.queryFurnById(id);
    if (furn == null) {
        return;
    }

    //1.先判断是不是文件表单(enctype="multipart/form-data")
    if (ServletFileUpload.isMultipartContent(req)) {
        //2.创建DiskFileItemFactory对象,用于构建一个解析上传数据的工具对象
        DiskFileItemFactory diskFileItemFactory =
                new DiskFileItemFactory();
        //3.创建一个解析上传数据的工具对象
        /**
         *      前端表单提交的就是input元素
         *      <input type="file" name="pic" id="" value="" onchange="prev(this)"/>
         *     家居名: <input type="text" name="name"><br/>
         *     <input type="submit" value="上传"/>
         */
        ServletFileUpload servletFileUpload =
                new ServletFileUpload(diskFileItemFactory);
        //解决接收到的文件名是中文乱码问题
        servletFileUpload.setHeaderEncoding("utf-8");
        //4.关键地方
        // servletFileUpload对象可以把表单提交的数据text或文件
        // 将其封装到 FileItem文件项中
        try {
            List<FileItem> list =
                    servletFileUpload.parseRequest(req);
            //遍历并分别处理
            for (FileItem fileItem : list) {
                //判断是不是一个文件
                /**
                 * isFormField()方法用于
                 * 判断FileItem类对象封装的数据是一个普通文本表单字段,还是一个文件表单字段,
                 * 如果是普通表单字段则返回true,否则返回false
                 */
                if (fileItem.isFormField()) {//为true,说明普通表单项
                    if ("name".equals(fileItem.getFieldName())) {//家居名
                        furn.setName(fileItem.getString("utf-8"));
                    } else if ("maker".equals(fileItem.getFieldName())) {//制造商
                        furn.setMaker(fileItem.getString("utf-8"));
                    } else if ("price".equals(fileItem.getFieldName())) {//价格
                        furn.setPrice(new BigDecimal(fileItem.getString()));
                    } else if ("sales".equals(fileItem.getFieldName())) {//销量
                        furn.setSales(new Integer(fileItem.getString()));
                    } else if ("stock".equals(fileItem.getFieldName())) {//库存
                        furn.setStock(new Integer(fileItem.getString()));
                    }
                } else {//说明是一个文件表单项
                    //获取上传的文件的名字
                    String name = fileItem.getName();

                    //把上传到服务器temp目录下的文件保存到指定目录
                    //1.指定一个目录,比如我们网站的工作目录下
                    String filePath = "/" + WebUtils.FURN_IMG_DIRECTORY;
                    //但是一般来说,工作目录是不确定的,所以我们动态获取
                    //2.获取完整目录/路径
                    String fileRealPath =
                            req.getServletContext().getRealPath(filePath);
                    //下面的目录是和根据你web项目运行环境改变而改变的(动态的)
                    //fileRealPath=
                    // D:\IDEA-workspace\furniture_mall\out\artifacts\
                    // furniture_mall_war_exploded\assets\images\product-image

                    //3.创建上传的文件的目录
                    //  写一个工具类,可以返回一个日期,如2024/11/11,
                    //  这样可以将不同日期上传的文件放到不同目录下,
                    //  防止一个文件夹存放的文件过多造成访问速度变慢
                    File fileRealPathDirectory =
                            new File(fileRealPath);

                    if (!fileRealPathDirectory.exists()) {//如果文件目录不存在
                        fileRealPathDirectory.mkdirs();//创建
                    }
                    //4.上传到服务器temp目录下的文件拷贝到上述创建的目录下
                    //构建文件上传的完整路径:目录+文件名
                    //防止出现文件覆盖问题,把获取到的用户上传文件名加一个前缀,保证文件名唯一即可
                    //如果担心在高并发的情况下会出现UUID相同,可以在UUID后再加上一个系统当前毫秒数
                    name = UUID.randomUUID().toString() + "_" + name;
                    String fileFullPath = fileRealPathDirectory + "/" + name;
                    fileItem.write(new File(fileFullPath));//保存

                    fileItem.getOutputStream().close();//关闭流

                    //更新家居的文件图片路径
                    furn.setImgPath(WebUtils.FURN_IMG_DIRECTORY + "/" + name);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //更新furn对象
    furnService.updateFurn(furn);
    //可以请求转发到更新成功的页面
    req.getRequestDispatcher("/views/manage/update_ok.jsp").forward(req, resp);
}

添加update_ok.jsp显示成功页面:略。

31.4完成测试

登录管理员账号,点击修改家居

修改家居

提交后显示修改成功

点击返回管理页面,成功返回修改家居的对应页面

数据库显示修改成功

解决一个bug

如下,如果在修改的时候没有上传新的图片,点击修改后,图片就会显示不出来。

原因是如果没有上传新图片,那么在上传表单中图片项的value值是空的,服务端没有办法获取对应的文件名,保存到数据库中的只是路径

解决方案:在获取文件时判断值

测试:

不修改任何数据的情况下点击修改家居

显示修改成功,返回家居显示页,可以看到仍然显示成功

有关day13-功能实现12的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  3. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. ruby - 安装libv8(3.11.8.13)出错,Bundler无法继续 - 2

    运行bundleinstall后出现此错误:Gem::Package::FormatError:nometadatafoundin/Users/jeanosorio/.rvm/gems/ruby-1.9.3-p286/cache/libv8-3.11.8.13-x86_64-darwin-12.gemAnerroroccurredwhileinstallinglibv8(3.11.8.13),andBundlercannotcontinue.Makesurethat`geminstalllibv8-v'3.11.8.13'`succeedsbeforebundling.我试试gemin

  6. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  7. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  8. ruby - Arrays Sets 和 SortedSets 在 Ruby 中是如何实现的 - 2

    通常,数组被实现为内存块,集合被实现为HashMap,有序集合被实现为跳跃列表。在Ruby中也是如此吗?我正在尝试从性能和内存占用方面评估Ruby中不同容器的使用情况 最佳答案 数组是Ruby核心库的一部分。每个Ruby实现都有自己的数组实现。Ruby语言规范只规定了Ruby数组的行为,并没有规定任何特定的实现策略。它甚至没有指定任何会强制或至少建议特定实现策略的性能约束。然而,大多数Rubyist对数组的性能特征有一些期望,这会迫使不符合它们的实现变得默默无闻,因为实际上没有人会使用它:插入、前置或追加以及删除元素的最坏情况步骤复

  9. ruby - "public/protected/private"方法是如何实现的,我该如何模拟它? - 2

    在ruby中,你可以这样做:classThingpublicdeff1puts"f1"endprivatedeff2puts"f2"endpublicdeff3puts"f3"endprivatedeff4puts"f4"endend现在f1和f3是公共(public)的,f2和f4是私有(private)的。内部发生了什么,允许您调用一个类方法,然后更改方法定义?我怎样才能实现相同的功能(表面上是创建我自己的java之类的注释)例如...classThingfundeff1puts"hey"endnotfundeff2puts"hey"endendfun和notfun将更改以下函数定

  10. ruby-on-rails - rails 功能测试 - 2

    在Rails自动生成的功能测试(test/functional/products_controller_test.rb)中,我看到以下代码:classProductsControllerTest我的问题是:方法调用products()在哪里/如何定义?products(:one)到底是什么意思?看代码,大概意思是“创建一个产品”,但是它是如何工作的呢?注意我是Ruby/Rails的新手,如果这些是微不足道的问题,我深表歉意。 最佳答案 如果您查看test/fixtures文件夹,您会看到一个products.yml文件。这是在您创建

随机推荐