day13-功能实现12
家居网购项目实现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向后台发送请求跳转页面无效的原因:
- 主要是服务器得到的是ajax发送过来的request,这个请求不是浏览器发送的请求,而是ajax请求的。因此servlet对request进行请求转发或者重定向都不能影响浏览器的跳转
- 这时就出现了请求转发和重定向失效的问题
- 解决方案:如果想要实现跳转,可以返回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需求分析/图解

- 后台修改家居,可以点击图片,选择新的图片
- 这里会使用到文件上传功能
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值是空的,服务端没有办法获取对应的文件名,保存到数据库中的只是路径

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

测试:

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

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

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!