文章目录
- 🌈Ajax检验注册名
- 🌈Ajax添加购物车
- 🌈上传与更新家居图片
- 🌈作业布置
- 🍍会员登陆后不能访问后台管理
- 🍍解决图片冗余问题
- 🍍分页导航完善
🌈Ajax检验注册名
需求分析
- 注册会员时, 如果名字已经注册过, 当光标离开输入框, 提示会员名已经存在, 否则提示不存在
- 要求使用ajax完成
程序框架图
- MemberServlet - 返回json格式的字符串 - 方式一
protected void isExistByName(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1.获取用户名 String username = req.getParameter("username"); //2.调用service boolean existsByUsername = memberService.isExistsByUsername(username); //3.思路 //(1)如果返回json格式[不要乱写, 要根据前端的需求来写] //(2)因为前后端都是我们自己写的, 格式我们自己定义 //(3){"isExist": true}; //(4)先用最简单的方法拼接 => 一会改进[扩展] String resultJson = "{\"isExist\": " + existsByUsername + "}"; //4.返回 resp.getWriter().print(resultJson); }
返回json格式的字符串 - 方式二
protected void isExistByName(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1.获取用户名 String username = req.getParameter("username"); //2.调用service boolean existsByUsername = memberService.isExistsByUsername(username); //3.思路 //(1)如果返回json格式[不要乱写, 要根据前端的需求来写] //(2)因为前后端都是我们自己写的, 格式我们自己定义 //(3){"isExist": true}; //(4)先用最简单的方法拼接 => 一会改进[扩展] //String resultJson = "{\"isExist\": " + existsByUsername + "}";字符串就不需要再转 //(5)将要返回的数据封装成map => json格式 Map<Object, Object> map = new HashMap<>(); map.put("isExist", existsByUsername); //map.put("email", "978964140@qq.com"); //map.put("phone", "13031748275"); //4.返回json格式的数据 Gson gson = new Gson(); String resultJson = gson.toJson(map); resp.getWriter().print(resultJson); }
- 前端
$("#username").mouseleave(function () {//鼠标离开事件[无需点击, 即可触发] var usernameValue = $(this).val(); $.getJSON( //这里尽量准确, 一把确定[复制粘贴] "memberServlet", "action=isExistByName&username=" + usernameValue, function (data) { alert(data.isExist); console.log("data= ", data);//显示json格式的数据: 1.要用逗号; 2.要用console.log() } /*========================================================================================*/ "memberServlet?action=isExistByName&username=" + usernameValue, function (data) { alert(data.isExist); console.log("data= ", data);//显示json格式的数据: 1.要用逗号; 2.要用console.log() } /*========================================================================================*/ "memberServlet", { action: "isExistByName", username: usernameValue }, function (data) { alert(data.isExist); console.log("data= ", data);//显示json格式的数据: 1.要用逗号; 2.要用console.log() } /*========================================================================================*/ "memberServlet", { "action": "isExistByName", "username": usernameValue }, function (data) { alert(data.isExist); //前端人员只能通过console.log()来查看你的数据, 然后才知道怎么获取你的数据 console.log("data= ", data);//显示json格式的数据: 1.要用逗号; 2.要用console.log() if (data.isExist) { $("span[class='errorMsg']").text("用户名 " + usernameValue + " 不可用"); } else { $("span[class='errorMsg']").text("用户名 " + usernameValue + " 可用"); } ) }
- Ajax检验验证码
- MemberServlet
protected void verifyCaptcha(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取用户提交的验证码 String captcha = req.getParameter("captcha"); //从session中获取 生成的验证码 HttpSession session = req.getSession(); String token = (String) session.getAttribute(KAPTCHA_SESSION_KEY); //立即删除session中的验证码, 防止该验证码被重复使用 session.removeAttribute(KAPTCHA_SESSION_KEY); //如果token不为空, 并且和用户提交的验证码保持一致, 就继续 if (token != null) { Map<Object, Object> map = new HashMap<>(); boolean verifyCaptcha = token.equalsIgnoreCase(captcha); map.put("verifyCaptcha", verifyCaptcha); //返回json格式的数据 Gson gson = new Gson(); String resultJson = gson.toJson(map); resp.getWriter().print(resultJson); } }
- 前端
$("#code").blur(function () {//光标焦点离开事件[点击后离开, 才可以触发] var captchaValue = this.value; $.getJSON( "memberServlet?action=verifyCaptcha&captcha="+captchaValue, function (data) { console.log("data= ", data); if (data.verifyCaptcha) { $("span.errorMsg2").text("验证码正确"); } else { $("span.errorMsg2").text("验证码错误"); } } ); })
在验证码标签旁补充一个span标签
<span class="errorMsg2" style="float: right; font-weight: bold; font-size: 15pt; margin-left: 10px; color: lightgray;"></span>
🌈Ajax添加购物车
- CartServlet添加addItemByAjax方法
//添加一个添加家居到购物车的方法 [Ajax]
protected void addItemByAjax(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
int id = DataUtils.parseInt(request.getParameter("id"), 1);//家居id
//根据id获取对应的家居信息
Furn furn = furnService.queryFurnById(id);
//先把正常的逻辑走完, 再处理异常的情况
//如果某家居的库存为0, 就不要添加到购物车, 直接请求转发到首页面
//if (furn.getInventory() <= 0) {
// request.getRequestDispatcher("/index.jsp").forward(request, response);
// return;
//}
HttpSession session = request.getSession();
Cart cart = (Cart) session.getAttribute("cart");
//得到购物车 有可能是空的,也有可能是上次的
if (cart == null) {
cart = new Cart();
session.setAttribute("cart", cart);
}
//构建一条家居明细: id,家居名,数量, 单价, 总价
//count类型为Integer, 不赋值默认值为null
CartItem cartItem = new CartItem(id, furn.getName(), 1, furn.getPrice(), furn.getPrice());
//将家居明细加入到购物车中. 如果家居id相同,数量+1;如果是一条新的商品,那么就新增
cart.addItem(cartItem, furn.getInventory());
System.out.println("cart= " + cart);
//规定格式 {"cartTotalCount": 3}
//方式一:
//String resultJson = "{\"cartTotalCount\": " + cart.getTotalCount() + "}";
//response.getWriter().print(resultJson);
//方式二: 创建map,可扩展性强
Map<Object, Object> map = new HashMap<>();
map.put("cartTotalCount", cart.getTotalCount());
//转成json
Gson gson = new Gson();
String resultJson = gson.toJson(map);
//返回
response.getWriter().print(resultJson);
//String referer = request.getHeader("referer");
//response.sendRedirect(referer);
}
- 前端
//给所有选定的button都赋上点击事件
$("button.add-to-cart").click(function () {
var id = $(this).attr("furnId");
//location.href = "cartServlet?action=addItem&id=" + id;
//这里我们使用jquery发出ajax请求, 得到数据进行局部刷新, 解决刷新这个页面效率低的问题
$.getJSON(
"cartServlet?action=addItemByAjax&id=" + id, function (data) {
console.log("data=", data);
//刷新局部 <span class="header-action-num"></span>
$("span.header-action-num").text(data.cartTotalCount);
}
)
});
- 解决Ajax请求转发失败
测试, 会发现针对ajax的重定向和请求转发会失败, 也就是AuthFilter.java的权限拦截不生效, 也就是点击Add to Cart, 后台服务没有响应
使用ajax向后台发送请求跳转页面无效的原因
- 主要是服务器得到的是ajax发送过来的request, 也就是说这个请求不是浏览器请求的, 而是ajax请求的. 所以servlet根据request进行请求转发或重定向都不能影响浏览器的跳转
- 解决方案: 如果想要实现跳转, 可以返回url给ajax, 在浏览器执行window.location(url);
工具类添加方法 - 判断请求是不是一个ajax请求
/** * 判断请求是不是一个ajax请求 * @param request * @return */ public static boolean isAjaxRequest(HttpServletRequest request) { //X-Requested-With: XMLHttpRequest return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); }
修改AuthFilter.java
if (member == null) {//说明用户没有登录 if (!WebUtils.isAjaxRequest(request)) {//如果不是ajax请求 //转发到登陆页面, 转发不走过滤器 servletRequest.getRequestDispatcher("/views/member/login.jsp") .forward(servletRequest, servletResponse); } else {//如果是ajax请求 //返回ajax请求, 按照json格式返回 {"url": url} //String url = "views/member/login.jsp"; //String resultJson = "{\"url\": \"" + url + "\"}"; //1.构建map Map<Object, Object> map = new HashMap<>(); map.put("url", "views/member/login.jsp"); //2.转成json字符串 String resultJson = new Gson().toJson(map); //3.返回 servletResponse.getWriter().print(resultJson); } 重定向-拦截-重定向-拦截-重定向-拦截 //((HttpServletResponse) servletResponse) // .0sendRedirect(request.getContextPath() + "/views/member/login.jsp"); return;//直接返回 }
修改getJson
//这里我们使用jquery发出ajax请求, 得到数据进行局部刷新, 解决刷新这个页面效率低的问题 $.getJSON( "cartServlet?action=addItemByAjax&id=" + id, function (data) { console.log("data=", data); if (data.url == undefined) { //刷新局部 <span class="header-action-num"></span> $("span.header-action-num").text(data.cartTotalCount); } else { location.href = data.url; } } )
🌈上传与更新家居图片
引入文件上传下载的包: commons-io-1.4.jar, commons-fileupload-1.2.1.jar
FurnDAOImpl的查询语句加上图片字段 image_path as imagePath
需求分析
- 后台修改家居, 可以点击图片, 选择新的图片
- 这里会用到文件上传功能
思路分析-程序框架图
- furn_update.jsp
<style type="text/css"> #pic { position: relative; } input[type="file"] { position: absolute; left: 0; top: 0; height: 180px; opacity: 0; cursor: pointer; } </style>
<script type="text/javascript"> function prev(event) { //获取展示图片的区域 var img = document.getElementById("preView"); //获取文件对象 var file = event.files[0]; //获取文件阅读器: Js的一个类, 直接使用即可 var reader = new FileReader(); reader.readAsDataURL(file); reader.onload = function () { //给img的src设置图片url img.setAttribute("src", this.result) } } </script>
去掉a标签
<div id="pic"> <img class="img-responsive ml-3" src="${requestScope.furn.imagePath}" alt="" id="preView"> <input type="file" name="imagePath" id="" value="${requestScope.furn.imagePath}" onchange="prev(this)"/> </div>
- 分析空指针异常
将form表单改成文件表单
<form action="manage/furnServlet" method="post" enctype="multipart/form-data"></form>
点击修改家居
报错
将web.xml中500的错误提示配置注销掉, 将异常信息暴露出来
再次点击修改家居信息, 报错信息显示出来, BasicServlet空指针异常
所以有时候报错信息显示出来很重要
分析: 如果表单是enctype=“multipart/form-data”, 那么req.getParameter(“action”) 的方法得不到action值, 所以BasicServlet会报错
具体原因: req.getParameter(“action”)取不到form-data里的数据
- 解决空指针异常
解决方案: 将参数action, id, pageNo以url拼接的方式传参, BasicServlet便不会出错
注意: post请求可以人为主动在地址中拼接参数,拼接的参数可以直接像get那样接收
<form action="manage/furnServlet?action=update&id=${requestScope.furn.id}&pageNo=${param.pageNo}" method="post" enctype="multipart/form-data">
- FurnServlet update方法
处理普通字段if (fileItem.isFormField()) {//文本表单字段 将提交的家居信息, 封装成Furn对象 switch (fileItem.getFieldName()) { case "name": furn.setName(fileItem.getString("utf-8")); break; case "business": furn.setBusiness(fileItem.getString("utf-8")); break; case "price": furn.setPrice(new BigDecimal(fileItem.getString())); break; case "saleNum": furn.setSaleNum(Integer.parseInt(fileItem.getString())); break; case "inventory": furn.setInventory(Integer.parseInt(fileItem.getString())); break; } }
处理文件字段
将文件上传路径保存成一个常量public class WebUtils { public static final String FURN_IMG_DIRECTORY = "assets/images/product-image/"; }
//文件表单字段 => 获取上传的文件的名字 String name = fileItem.getName(); //如果用户没有选择新的图片, name = "" if (!"".equals(name)) { //1.把上传到到服务器 temp目录下的文件保存到指定的目录 String filePath = "/" + WebUtils.FURN_IMG_DIRECTORY; //2.获取完整的目录 String fileRealPath = req.getServletContext().getRealPath(filePath); System.out.println("fileRealPath= " + fileRealPath); //3.创建这个上传的目录 File fileRealPathDirectory = new File(fileRealPath); if (!fileRealPathDirectory.exists()) { fileRealPathDirectory.mkdirs(); } //4.将文件拷贝到fileRealPathDirectory目录下 //对上传的文件名进行处理, 前面增加一个前缀, 保证是唯一的即可. 防止文件名重复造成覆盖 //构建了一个上传的文件的完整路径[目录+文件名] name = UUID.randomUUID().toString() + "_" + System.currentTimeMillis() + "_" + name; String fileFullPath = fileRealPathDirectory + "\\" + name; //保存 fileItem.write(new File(fileFullPath)); //关闭流 fileItem.getOutputStream().close(); //更新家居图的图片 furn.setImagePath(WebUtils.FURN_IMG_DIRECTORY + name); }
全部代码
protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //将提交修改的家居信息,封装成Furn对象 //如果你的表单是enctype="multipart/form-data", req.getParameter("id") 得不到id int id = DataUtils.parseInt(req.getParameter("id"), 0); //获取到对应furn对象[从db中获取] Furn furn = furnService.queryFurnById(id); //todo 如果furn为null, 则return //1.判断是不是文件表单 if (ServletFileUpload.isMultipartContent(req)) { //2.创建DiskFileItemFactory对象, 用于构建一个解析上传数据的工具对象 DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory(); //3.构建一个解析上传数据的工具对象 ServletFileUpload servletFileUpload = new ServletFileUpload(diskFileItemFactory); //解决中文乱码问题 servletFileUpload.setHeaderEncoding("utf-8"); //4.servletFileUpload对象可以把表单提交的数据[文本/文件], 封装到FileItem文件项中 try { List<FileItem> list = servletFileUpload.parseRequest(req); for (FileItem fileItem : list) { //判断是不是一个文件 => 文本表单字段 if (fileItem.isFormField()) { 将提交的家居信息, 封装成Furn对象 switch (fileItem.getFieldName()) { case "name"://家居名 furn.setName(fileItem.getString("utf-8")); break; case "business"://制造商 furn.setBusiness(fileItem.getString("utf-8")); break; case "price"://价格 furn.setPrice(new BigDecimal(fileItem.getString())); break; case "saleNum"://销量 furn.setSaleNum(Integer.parseInt(fileItem.getString())); break; case "inventory"://库存 furn.setInventory(Integer.parseInt(fileItem.getString())); break; } } else { //文件表单字段 => 获取上传的文件的名字 String name = fileItem.getName(); //如果用户没有选择新的图片, name = "" if (!"".equals(name)) { //1.把上传到到服务器 temp目录下的文件保存到指定的目录 String filePath = "/" + WebUtils.FURN_IMG_DIRECTORY; //2.获取完整的目录 String fileRealPath = req.getServletContext().getRealPath(filePath); System.out.println("fileRealPath= " + fileRealPath); //3.创建这个上传的目录 File fileRealPathDirectory = new File(fileRealPath); if (!fileRealPathDirectory.exists()) { fileRealPathDirectory.mkdirs(); } //4.将文件拷贝到fileRealPathDirectory目录下 //对上传的文件名进行处理, 前面增加一个前缀, 保证是唯一的即可. 防止文件名重复造成覆盖 //构建了一个上传的文件的完整路径[目录+文件名] name = UUID.randomUUID().toString() + "_" + System.currentTimeMillis() + "_" + name; String fileFullPath = fileRealPathDirectory + "\\" + name; //保存 fileItem.write(new File(fileFullPath)); //关闭流 fileItem.getOutputStream().close(); //更新家居图的图片 furn.setImagePath(WebUtils.FURN_IMG_DIRECTORY + name); } } } else { System.out.println("不是文件表单..."); } //更新furn对象->DB furnService.updateFurn(furn); System.out.println("更新成功..."); //请求转发到 update_ok.jsp req.getRequestDispatcher("/views/manage/update_ok.jsp") .forward(req, resp); } catch (Exception e) { throw new RuntimeException(e); } } }
将checkout.jsp复制成update_ok.jsp
<a class="active" href="manage/furnServlet?action=page&pageNo=${param.pageNo}"> <h4>家居修改成功, 点击返回家居管理页面</h4> </a>
🌈作业布置
🍍会员登陆后不能访问后台管理
需求分析
- 管理员admin登陆后, 可访问所有页面
- 会员登陆后, 不能访问后台管理相关页面, 其他页面可以访问
- 假定管理员名字就是admin, 其它会员名就是普通会员
AuthFilter - 代码
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("请求/cartServlet 被拦截...");
HttpServletRequest request = (HttpServletRequest) servletRequest;
//得到请求的url
String url = request.getServletPath();
System.out.println("url= " + url);
//判断是否要验证
if (!excludedUrls.contains(url)) {
//获取到登陆的member对象
Member member = (Member) request.getSession().getAttribute("member");
if (member == null) {//说明用户没有登录
if (!WebUtils.isAjaxRequest(request)) {//如果不是ajax请求
//转发到登陆页面, 转发不走过滤器
servletRequest.getRequestDispatcher("/views/member/login.jsp")
.forward(servletRequest, servletResponse);
} else {//如果是ajax请求
//返回ajax请求, 按照json格式返回 {"url": url}
//1.构建map
Map<Object, Object> map = new HashMap<>();
map.put("url", "views/member/login.jsp");
//2.转成json字符串
String resultJson = new Gson().toJson(map);
//3.返回
servletResponse.getWriter().print(resultJson);
}
return;//直接返回
}
//如果member不为空
if ("admin".equals(member.getUsername())) {//管理员登陆
//全部放行
} else {//普通用户登录, 部分页面不能放行
//如果该用户不是admin, 但是它访问了后台, 就转到管理员登录页面
//if ("/manage/furnServlet".equals(url) || url.contains("/views/manage/")) {
//.* 匹配任意个字符
if ("/manage/furnServlet".equals(url) || url.matches("^/views/manage/.*")) {
request.getRequestDispatcher("/views/manage/manage_login.jsp")
.forward(servletRequest, servletResponse);
}
}
}
//如果请求的是登录页面, 那么就放行
filterChain.doFilter(servletRequest, servletResponse);
System.out.println("请求/cartServlet验证通过, 放行");
}
🍍解决图片冗余问题
需求分析
- 家居图片都放在一个文件夹, 会越来越多
- 请尝试在assets/images/product-image/目录下, 自动创建年月日目录, 比如20230612. 以天为单位来存放上传图片
- 当上传新家居的图片, 原来的图片就没有用了, 应当删除原来的家居图片
工具类添加方法 - 返回当前日期
public static String getYearMonthDay() { //第三代日期类 LocalDateTime now = LocalDateTime.now(); int year = now.getYear(); int month = now.getMonthValue(); int day = now.getDayOfMonth(); String date = year + "/" + month + "/" + day + "/"; return date; }
🍍分页导航完善
需求分析
- 如果总页数<=5, 就全部显示
- 如果总页数>5, 按照如下规则显示(这个规则由程序员/业务来决定)
2.1 如果当前页是前3页, 就显示1-5
2.2 如果当前页是后3页, 就显示最后5页
2.3 如果当前页是中间页, 就显示 当前页前2页, 当前页, 当前页后2页
代码实现
<c:choose>
<%--如果总页数<=5, 就全部显示--%>
<c:when test="${requestScope.page.pageTotal <= 5}">
<c:set scope="page" var="begin" value="1"></c:set>
<c:set scope="page" var="end" value="${requestScope.page.pageTotal}"></c:set>
</c:when>
<%--如果总页数>5, 按照如下规则显示(这个规则由程序员/业务来决定)--%>
<c:when test="${requestScope.page.pageTotal > 5}">
<c:choose>
<%--如果当前页是前3页, 就显示1-5--%>
<c:when test="${requestScope.page.pageNo <= 3}">
<c:set scope="page" var="begin" value="1"></c:set>
<c:set scope="page" var="end" value="5"></c:set>
</c:when>
<%--如果当前页是后3页, 就显示最后5页--%>
<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal - 3}">
<c:set scope="page" var="begin" value="${requestScope.page.pageTotal - 4}"></c:set>
<c:set scope="page" var="end" value="${requestScope.page.pageTotal}"></c:set>
</c:when>
<%--如果当前页是中间页, 就显示 当前页前2页, 当前页, 当前页后2页--%>
<c:otherwise>
<c:set scope="page" var="begin" value="${requestScope.page.pageNo - 2}"></c:set>
<c:set scope="page" var="end" value="${requestScope.page.pageNo + 2}"></c:set>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
🐀🐂🐅🐇🐉🐍🐎🐏