(旧版) 家居购项目 1.分页导航 2.购物车 3.订单生成 4.权限验证 5.事务管理 6.上传图片

news2024/11/17 13:21:05

文章目录

  • 🐀Java后端经典三层架构
    • 🐇MVC模型
    • 🐇开发环境搭建
    • 🐇会员注册
      • 🌳前端验证用户注册信息
      • 🌳思路分析
        • 🍉创建表
        • 🍉创建实体类
        • 🍉DAO
          • 🍌MemberDAOImpl
        • 🍉Service
          • 🍌MemberServiceImpl
        • 🌳接通web层
    • 🐇会员登陆
      • 🌳登陆错误_信息回显
    • 🐇servlet合并
      • 🍎反射+模板设计模式+动态代理
      • 🌳显示家居
      • 🌳添加家居
          • 🍉解决重复添加
          • 🍉后端数据校验说明
          • 🍉BeanUtils自动封装Bean
      • 🌳删除家居
      • 🌳修改家具
      • 🍃后台分页
        • 🍒新建Page类
        • 🍒DAO
        • 🍒Service
        • 🍒web层获取page对象
        • 🍒前端页面
          • 🍅后台分页导航
          • 🍅修改后返回原页面
          • 🍅删除后返回原页面
          • 🍅添加后返回原页面
      • 🍃首页分页
          • 🍅首页搜索
          • 🍅两个奇怪的问题
      • 🌳会员显示登录名
          • 🍅注销登录
          • 🍅验证码
      • 🌳购物车
          • 🍆显示购物车
          • 🍆修改购物车
          • 🍆删除购物车
      • 🌳生产订单
        • 🍉创建表
        • 🍉实体类
        • 🍉DAO
        • 🍉service
        • 🍉servlet
        • 🍉前端
      • 🌳显示订单[订单管理]
      • 🌈过滤器权限验证
      • 🌈事务管理
          • 1. 数据不一致问题
          • 2. 程序框架图
          • 🌈Transaction过滤器
      • 🌈统一错误页面
      • 🌈Ajax检验注册名
      • 🌈Ajax添加购物车
      • 🌈上传与更新家具图片
      • 🌈作业布置
        • 🍍会员登陆后不能访问后台管理
        • 🍍解决图片冗余问题
        • 🍍分页导航完善

🐀Java后端经典三层架构

在这里插入图片描述

分层对应包说明
web层com.zzw.furns.web/servlet/controller/handler接受用户请求, 调用service
service层com.zzw.furns.serviceService接口包
com.zzw.furns.service.implService接口实现类
dao持久层com.zzw.furns.daoDao接口包
com.zzw.furns.dao.implDao接口实现类
实体bean对象com.zzw.furns.pojo/entity/domain/beanJavaBean类
工具类com.zzw.furns.utils工具类
测试包com.zzw.furns.test完成对dao/service测试

🐇MVC模型

MVC全称: Model模型, View试图, Controller控制器
MVC最早出现在JavaEE三层中的Web层, 它可以有效地指导WEB层代码如何有效地分离, 单独工作

  • View试图: 只负责数据和界面的显示, 不接受任何与显示数据无关的代码, 便于程序员和美工的分工与合作(Vue/Jsp/Thymeleaf/Html)
  • Controller控制器: 只负责接收请求, 调用业务层的代码处理请求, 然后派发给页面, 是一个"调度者"的角色
  • Model模型: 将与业务逻辑相关的数据封装为具体的JavaBean类, 其中不掺杂任何与数据处理相关的代码(JavaBean/Domain/Pojo)

在这里插入图片描述
在这里插入图片描述

解读

  1. model 最早期就是javabean, 就是早期的jsp+servlet+javabean
  2. 后面业务复杂度越来越高, model逐渐分层化/组件化(service+dao)
  3. 后面又出现了持久化技术(service+dao+持久化技术(hibernate / mybatis / mybatis-plus))
  4. MVC依然是原来的mvc, 只是变得更加强大

🐇开发环境搭建

详情请参考👉

  1. 新建Java项目, 导入web框架在这里插入图片描述
  2. 导入jar包
    在这里插入图片描述
  3. 项目的结构
    在这里插入图片描述
    在这里插入图片描述
  4. 拷贝到web路径下
    在这里插入图片描述
    在这里插入图片描述
  5. 配置Tomcat
    Rebuild project, 让项目识别到这些资源, 然后再启动Tomcat
    在这里插入图片描述
  6. 对于复杂的前端页面, 要学会打开当前页面的结构, 提高工作效率
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

🐇会员注册

🌳前端验证用户注册信息

script引文件是src属性

    <script type="text/javascript" src="../../script/jquery-3.6.0.min.js"></script>
    <script type="text/javascript">
        $(function () {//页面加载完毕后执行 function
            $("#sub-btn").click(function () {
                //采用过关斩将法
                //正则表达式验证用户名
                var usernameValue = $("#username").val();
                var usernamePattern = /^\w{6,10}$/;
                if (!usernamePattern.test(usernameValue)) {
                    $("span[class='errorMsg']").text("用户名格式不对, 需要6-10个字符(大小写字母,数字,下划线)");
                    return false;
                }
                //验证密码
                var passwordValue = $("#password").val();
                var passwordPattern = /^\w{6,10}$/;
                if (!passwordPattern.test(passwordValue)) {
                    $("span.errorMsg").text("密码格式不对, 需要6-10个字符(大小写字母,数字,下划线)");
                    return false;
                }
                //两次密码要相同
                var rePwdValue = $("#repwd").val();
                if (passwordValue != rePwdValue) {
                    $("span.errorMsg").text("两次密码不相同");
                    return false;
                }
                //这里仍然采用过关斩将法
                //验证邮件
                var emailVal = $("#email").val();
                //在java中, 正则表达式的转义是\\; 在js中, 正则表达式转义是\
                var emailPattern = /^[\w-]+@([a-zA-Z]+\.)+[a-zA-Z]+$/;
                if (!emailPattern.test(emailVal)) {
                    $("span.errorMsg").text("电子邮件的格式不正确, 请重新输入");
                    return false;
                }
                //这里暂时不提交=>显示验证通过
                $("span.errorMsg").text("验证通过");
                return false;
            });
        })
    </script>

🌳思路分析

创建表->javabean->DAO->service
在这里插入图片描述

分层对应包说明
web层RegisterServlet.java接受浏览器发送数据; 调用相关的service;根据执行结果,返回页面数据
service层MemberService.javaService接口包
MemberServiceImpl.javaService接口实现类
dao持久层MemberDAO.javaDao接口包
MemberDAOImplDao接口实现类
实体bean对象Member.javaJavaBean类
工具类JdbcUtilsByDruid.java工具类

🍉创建表

在这里插入图片描述

🍉创建实体类

满汉楼项目
包括无参构造器和set方法. 如果添加有参构造器, 记得书写无参构造器

  1. 从满汉楼项目引入BasicDAO.java, JdbcUtilsByDruid.java, Druid.properties
  2. 修改Druid配置文件要连接的数据库名, 确保用户名密码正确. ?后面是做批处理用的
    在这里插入图片描述
  3. 修改JdbcUtilsByDruid的路径
    在这里插入图片描述
    配置快捷键
    在这里插入图片描述
    在这里插入图片描述
  4. 测试
    在这里插入图片描述

🍉DAO

在这里插入图片描述

🍌MemberDAOImpl
public class MemberDAOImpl extends BasicDAO<Member> implements MemberDAO {
   /**
    * 通过用户名返回对应的Member
    * @param username 用户名
    * @return 对应的Member, 如果没有该Member返回null
    */
   @Override
   public Member queryMemberByUsername(String username) {
       //现在sqlyog测试, 然后再拿到程序中, 这样可以提高我们的开发效率, 减少不必要的bug
       String sql = "SELECT id, username, `password`, email FROM member WHERE username = ?";
       Member member = querySingle(sql, Member.class, username);
       return member;
   }

   /**
    * 保存一个会员
    * @param member 传入一个Member对象
    * @return 如果返回-1, 就是失败; 返回其它的数字, 就是受影响的行数
    */
   @Override
   public int saveMember(Member member) {
       //连同单引号一并换成 ? , 它会自动加上单引号
       String sql = "INSERT INTO member(id, username, `password`, email) " +
               "VALUES(NULL, ?, MD5(?), ?)";
       int updateRows = update(sql, member.getUsername(), member.getPassword(), member.getEmail());
       return updateRows;
   }
}

测试
在这里插入图片描述

🍉Service

在这里插入图片描述

🍌MemberServiceImpl
public class MemberServiceImpl implements MemberService {
   //定义MemberDAO属性
   private MemberDAO memberDAO = new MemberDAOImpl();

   /**
    * 判断用户名是否存在
    *
    * @param username 用户名
    * @return 如果存在返回true, 否则返回false
    */
   @Override
   public boolean isExistsByUsername(String username) {
       //小技巧: 如果看某个方法:
       // (1)ctrl+b 定位到memberDAO的编译类型中的方法
       // (2)如果使用ctrl+alt+b 会定位到实现类的方法
       //如果有多个类实现了该方法, 会让你选择
       return memberDAO.queryMemberByUsername(username) == null ? false : true;
   }

   @Override
   public boolean registerMember(Member member) {
       return memberDAO.saveMember(member) == 1 ? true : false;
   }
}

在这里插入图片描述
测试
在这里插入图片描述

🌳接通web层

在这里插入图片描述
将所有路径修改成相对路径
在这里插入图片描述
在这里插入图片描述
配置RegisterServlet, 请求RegisterServlet
在这里插入图片描述

🐇会员登陆

MemberDAO
在这里插入图片描述
在这里插入图片描述
MemberDAOImpl
在这里插入图片描述
测试(不要忘了测试)
在这里插入图片描述
快捷键
在这里插入图片描述
在这里插入图片描述

MemberService
在这里插入图片描述
在这里插入图片描述
测试(不要忘了测试)
在这里插入图片描述

web层

  1. 新建LoginServlet
    在这里插入图片描述
    login.html请求
    在这里插入图片描述
    login_ok.html
    在这里插入图片描述
    快捷键
    在这里插入图片描述
    效果
    在这里插入图片描述
    在这里插入图片描述

🌳登陆错误_信息回显

将login.html重命名为login.jsp, 修改base标签

LoginServlet
在这里插入图片描述
login.jsp
在这里插入图片描述
添加span标签
在这里插入图片描述

🐇servlet合并

方法一: 增加隐藏域
在这里插入图片描述
合并到MemberServlet
在这里插入图片描述

🍎反射+模板设计模式+动态代理

在这里插入图片描述
在这里插入图片描述

🌳显示家居

需求分析

  1. 给后台管理提供独立登陆页面 manage_login.jsp(已提供)
  2. 管理员(admin表)登陆成功后, 显示管理菜单页面
  3. 管理员点击家具管理, 显示所有家居信息

程序框架图
在这里插入图片描述

  1. 页面准备在这里插入图片描述在这里插入图片描述
  2. 新建admin表 👉 参考member表
    新建furn表
    在这里插入图片描述
  3. 新建Admin实体类
    新建Furn实体类
    在这里插入图片描述
  4. 书写AdminDAO, AdminDAOImpl, 并测试; 书写AdminService, AdminServiceImpl, 并测试 👉 参考Member
    书写FurnDAO, FurnDAOImpl 👉 并测试
    在这里插入图片描述
  5. 书写FurnService, FurnServiceImpl 👉 并测试
    在这里插入图片描述
  6. 接通web层
    配置web.xml, 书写AdminServlet
    在这里插入图片描述
    配置web.xml, 书写FurnServlet
    在这里插入图片描述
    在这里插入图片描述
    将doGet()方法移到BasicServlet中
    在这里插入图片描述
  7. 前端页面
    manage_login.jsp 登录验证, 请求AdminServlet
    在这里插入图片描述
    在这里插入图片描述
    manage_menu.jsp 请求FurnServlet
    在这里插入图片描述
    furn_manage.jsp 显示家居信息
    在这里插入图片描述

🌳添加家居

思路分析

  1. 请求添加家居, 请求FurnServlet的add方法, 将前端提交的数据封装到Furn对象
  2. 调用FurnService.add(Furn furn)方法
  3. 跳转到显示家居的页面
  1. FurnDAO
    在这里插入图片描述
  2. FurnService
    在这里插入图片描述
  3. web层
    FurnServlet
    在这里插入图片描述
    解决中文乱码问题
    在这里插入图片描述
  4. 前端: 添加furn_add.jsp
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
🍉解决重复添加

请求转发, 当用户刷新页面时, 会重新发出第一次的请求, 造成数据重复提交
在这里插入图片描述
解决方案: 使用重定向
在这里插入图片描述
在这里插入图片描述

🍉后端数据校验说明

后端方案一
在这里插入图片描述后端方案二
在这里插入图片描述
前端方案三
在这里插入图片描述

🍉BeanUtils自动封装Bean

引入: commons-logging-1.1.1.jar, commons-beanutils-1.8.0.jar

  1. 使用BeanUtils自动封装javabean
    在这里插入图片描述debug小技巧👉
    在这里插入图片描述
  2. 报错
    原因: 由于前端没有传imagePath的字段, 所有后端在构建的furn对象的时候, imagePath传了个null,
    解决方案👇
    在这里插入图片描述
  3. 将 把数据自动封装成JavaBean的功能封装到工具类
public class DataUtils {
   //将方法, 封装到静态方法, 方便使用
   public static <T> T copyParamToBean(Map value, T bean) {
       try {
           BeanUtils.populate(bean, value);
       } catch (Exception e) {
           throw new RuntimeException(e);
       }
       return bean;
   }
}

调用
在这里插入图片描述

🌳删除家居

需求分析

  1. 管理员进入到家居管理页面
  2. 点击删除家居链接, 弹出确认窗口, 确认-删除, 取消-放弃

程序框架图
在这里插入图片描述

  1. FurnDAO
    在这里插入图片描述
  2. FurnService
    >
  3. web层
    FurnServlet
    在这里插入图片描述
  4. furn_add.jsp页面
    jQuery操作父元素, 兄弟元素, 子元素, 请移步👉
    js弹框请移步👉
    在这里插入图片描述
    在这里插入图片描述

🌳修改家具

思路分析

  1. 管理员进入家居管理页面furn_manage.jsp
  2. 点击修改家居链接, 回显该家居信息 [furn_update.jsp]
  3. 填写新的信息, 点击修改家居按钮
  4. 修改成功后, 显示刷新后的家居列表

程序框架图
在这里插入图片描述

  1. FurnDAO
    在这里插入图片描述
    在这里插入图片描述
  2. FurnService
    在这里插入图片描述
    在这里插入图片描述
  3. web层
    FurnServlet
    在这里插入图片描述
    在这里插入图片描述
  4. 前端
    furn_manage.jsp 点击修改,发出请求
    在这里插入图片描述
    furn_update.jsp 修改数据,点击提交
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

🍃后台分页

shortcuts: ctrl+alt+u👉在局部打开类图
程序框架图

🍒新建Page类

在这里插入图片描述

🍒DAO

思路
在这里插入图片描述
实现
在这里插入图片描述
在这里插入图片描述

🍒Service

在这里插入图片描述
在这里插入图片描述

🍒web层获取page对象

在这里插入图片描述

🍒前端页面

取缔list方法
在这里插入图片描述
在这里插入图片描述
furn_manage.jsp
在这里插入图片描述

🍅后台分页导航

程序框架图

<!--  Pagination Area Start -->
<div class="pro-pagination-style text-center mb-md-30px mb-lm-30px mt-6" data-aos="fade-up">
   <ul>
       <%--如果当前页 > 1, 就显示首页和上一页--%>
       <li><a style="pointer-events:${requestScope.page.pageNo == 1 ? "none" : "auto"};"
              href="manage/furnServlet?action=page&pageNo=1&pageSize=${requestScope.page.pageSize}">首页</a>
       </li>
       <li><a style="pointer-events: ${requestScope.page.pageNo == 1 ? "none" : "auto"}"
              href="manage/furnServlet?action=page&pageNo=${requestScope.page.pageNo - 1}&pageSize=${requestScope.page.pageSize}">上一页</a>
       </li>

       <%--显示所有的分页数 先确定开始的页数 begin 1; 再确定结束的页数 end=>pageTotal--%>
       <%--最多显示10, 这里涉及算法--%>
       <c:set scope="page" var="begin" value="1"></c:set>
       <c:set scope="page" var="end" value="${requestScope.page.pageTotal}"></c:set>
       <%--循环显示--%>
       <c:forEach begin="${pageScope.begin}" end="${pageScope.end}" var="i"><%--总的页数--%>
           <%--如果i是当前页, 就使用class="active"来修饰--%>
           <li><a class="${i eq requestScope.page.pageNo ? "active" : ""}"
                  href="manage/furnServlet?action=page&pageNo=${i}&pageSize=${requestScope.page.pageSize}">${i}</a>
           </li>
       </c:forEach>

       <%--如果当前页 < 总的页数, 就显示末页和下一页--%>
       <li>
           <a style="pointer-events:${requestScope.page.pageNo == requestScope.page.pageTotal ? "none" : "auto"};"
              href="manage/furnServlet?action=page&pageNo=${requestScope.page.pageNo + 1}&pageSize=${requestScope.page.pageSize}">下一页</a>
       </li>
       <li>
           <a style="pointer-events:${requestScope.page.pageNo == requestScope.page.pageTotal ? "none" : "auto"};"
              href="manage/furnServlet?action=page&pageNo=${requestScope.page.pageTotal}&pageSize=${requestScope.page.pageSize}">末页</a>
       </li>

       <li><a>共${requestScope.page.pageTotal}</a></li>
       <li><a>共${requestScope.page.totalRow}记录</a></li>
   </ul>
</div>
<!--  Pagination Area End -->
🍅修改后返回原页面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🍅删除后返回原页面

在这里插入图片描述
在这里插入图片描述

🍅添加后返回原页面

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🍃首页分页

需求分析

  1. 顾客进入首页页面
  2. 分页显示家居
  3. 正确显示分页导航条, 即功能完善, 可以使用

程序框架图
在这里插入图片描述

实现>1. 新建CustomerFurnServlet
在这里插入图片描述
2. 前端页面
在这里插入图片描述
直接请求CustomerFurnServlet, 获取网站首页要显示的分页数据
类似我们网站的入口页面👉jsp请求转发标签
在这里插入图片描述
index.jsp
在这里插入图片描述
3. 显示数据

<c:forEach items="${requestScope.page.items}" var="furn">
   <div class="col-lg-3 col-md-6 col-sm-6 col-xs-6 mb-6" data-aos="fade-up"
        data-aos-delay="200">
       <!-- Single Product -->
       <div class="product">
           <div class="thumb">
               <a href="shop-left-sidebar.html" class="image">
                   <img src="${furn.imagePath}" alt="Product"/>
                   <img class="hover-image" src="assets/images/product-image/5.jpg"
                        alt="Product"/>
               </a>
               <span class="badges">
                   <span class="sale">-10%</span>
                   <span class="new">New</span>
               </span>
               <div class="actions">
                   <a href="#" class="action wishlist" data-link-action="quickview"
                      title="Quick view" data-bs-toggle="modal"
                      data-bs-target="#exampleModal"><i
                           class="icon-size-fullscreen"></i></a>
               </div>
               <button title="Add To Cart" class=" add-to-cart">Add
                   To Cart
               </button>
           </div>
           <div class="content">
               <h5 class="title">
                   <a href="shop-left-sidebar.html">Simple ${furn.name} </a></h5>
               <span class="price">
                   <span class="new">家居: ${furn.name}</span>
               </span>
               <span class="price">
                   <span class="new">厂商: ${furn.business}</span>
               </span>
               <span class="price">
                   <span class="new">价格: ${furn.price}</span>
               </span>
               <span class="price">
                   <span class="new">销量: ${furn.saleNum}</span>
               </span>
               <span class="price">
                   <span class="new">库存: ${furn.inventory}</span>
               </span>
           </div>
       </div>
   </div>
</c:forEach>

分页导航

<!--  Pagination Area Start -->
<div class="pro-pagination-style text-center mb-md-30px mb-lm-30px mt-6" data-aos="fade-up">
   <ul>
       <li><a style="pointer-events: ${requestScope.page.pageNo > 1 ? "auto" : "none"}"
              href="customerFurnServlet?action=page&pageNo=1&pageSize=${requestScope.page.pageSize}">首页</a>
       </li>
       <li><a style="pointer-events: ${requestScope.page.pageNo > 1 ? "auto" : "none"}"
              href="customerFurnServlet?action=page&pageNo=${requestScope.page.pageNo - 1}&pageSize=${requestScope.page.pageSize}">上一页</a>
       </li>

       <c:set scope="page" var="begin" value="1"></c:set>
       <c:set scope="page" var="end" value="${requestScope.page.pageTotal}"></c:set>
       <c:forEach begin="${begin}" end="${end}" var="i">
           <li><a class="${i == requestScope.page.pageNo ? "active" : ""}"
                  href="customerFurnServlet?action=page&pageNo=${i}&pageSize=${requestScope.page.pageSize}">${i}</a>
           </li>
       </c:forEach>

       <li><a style="pointer-events: ${requestScope.page.pageNo < requestScope.page.pageTotal ? "auto" : "none"}"
              href="customerFurnServlet?action=page&pageNo=${requestScope.page.pageNo + 1}&pageSize=${requestScope.page.pageSize}">下一页</a>
       </li>
       <li><a style="pointer-events: ${requestScope.page.pageNo < requestScope.page.pageTotal ? "auto" : "none"}"
              href="customerFurnServlet?action=page&pageNo=${requestScope.page.pageTotal}&pageSize=${requestScope.page.pageSize}">末页</a>
       </li>
       <li><a>共${requestScope.page.pageTotal}</a></li>
       <li><a>共${requestScope.page.totalRow}记录</a></li>
   </ul>
</div>
<!--  Pagination Area End -->
🍅首页搜索

需求分析

  1. 顾客进入首页页面
  2. 点击搜索按钮, 可以输入家居名
  3. 正确显示分页导航条, 并且要求在分页时, 保留上次搜索条件

程序框架图
在这里插入图片描述

  1. DAO
    模糊查询👉
    在这里插入图片描述
    在这里插入图片描述
  2. service
    在这里插入图片描述
    在这里插入图片描述
  3. web层 CustomerFurnServlet
    page方法就被抛弃了
    在这里插入图片描述
  4. 前端 index.jsp

    在这里插入图片描述
🍅两个奇怪的问题
  1. 点击家居管理, 发出两个请求
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    抓包
    在这里插入图片描述
    原因
    在这里插入图片描述
    请求首页面即进入到indx.jsp, index.jsp又请求转发到CustomerFurnServlet
    在这里插入图片描述
    问题解决
  2. 首页分页出现问题
    在这里插入图片描述
    在这里插入图片描述
    原因
    在这里插入图片描述

🌳会员显示登录名

需求分析

  1. 会员登陆成功, login_ok.jsp显示登录信息
  2. 如果登陆成功后返回首页面, 显示订单管理和安全退出
  3. 如果用户没有登陆过, 首页就显示登录和注册超链接

程序框架图
在这里插入图片描述

实现
重命名时, 会联动修改在这里插入图片描述在这里插入图片描述
将login_ok.jsp中的index.html改成index.jsp. 注意, 不要改成views/customer/index.jsp在这里插入图片描述在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🍅注销登录

思路分析

  1. 用户登录成功后
  2. login_ok.jsp, 点击安全退出, 注销登录
  3. 返回首页, 也可点击安全退出, 注销登录

程序框架图
在这里插入图片描述
实现
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

🍅验证码

程序框架图
在这里插入图片描述

  1. web层
    KaptchaServlet -> 引入kaptcha-2.3.2.jar包, 在web.xml中配置KaptchaServlet
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    MemberServlet
    在这里插入图片描述
    在这里插入图片描述
  2. 前端页面
    login.jsp
    在这里插入图片描述
    验证码不能为空
    在这里插入图片描述点击图片更换验证码
    在这里插入图片描述
    将register_ok.html, register_fail.html改造成jsp页面
    login.jsp页面总是默认停留在会员登录的div内, 修改
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述注册回显信息
    在这里插入图片描述在这里插入图片描述

🌳购物车

程序框架图
在这里插入图片描述

cartItem模型
在这里插入图片描述

Cart数据模型
在这里插入图片描述
在这里插入图片描述
测试
在这里插入图片描述

实现

  1. 创建CartServlet
    在这里插入图片描述
  2. 首页获取id请求后台
    在这里插入图片描述
    在这里插入图片描述
  3. 首页购买的商品总数量在这里插入图片描述
    在这里插入图片描述
🍆显示购物车

需求分析

  1. 查看购物车, 可以显示如下信息
  2. 选中了哪些家居, 名称, 数量, 金额
  3. 统计购物车共多少商品, 总价多少

程序框架图
在这里插入图片描述

  1. 走通购物车
    cart.jsp
    在这里插入图片描述
    index.jsp跳转
    排错
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    定位
    在这里插入图片描述
    在这里插入图片描述
    页面源代码
    在这里插入图片描述
  2. 显示家居项
<tbody>
<%--找到显示购物车项,  进行循环的items--%>
<c:if test="${not empty sessionScope.cart.items}">
   <%--
       1.sessionScope.cart.items => 取出的是HashMap<Integer, CartItem>
       2.所以通过foreach标签取出的每一个对象, 即entry是 HashMap<Integer, CartItem>的 k-v
       3.var其实就是 entry
       4.所以要取出cartItem对象, 是通过 entry.value取出
 --%>
   <c:forEach items="${sessionScope.cart.items}" var="entry">
       <tr>
           <td class="product-thumbnail">
               <a href="#"><img class="img-responsive ml-3"
                                src="assets/images/product-image/1.jpg"
                                alt=""/></a>
           </td>
           <td hidden="hidden" class="product-name"><a href="#">${entry.key}</a></td>
               <%--隐藏域--%>
           <td class="product-name"><a href="#">${entry.value.name}</a></td>
           <td class="product-price-cart"><span class="amount">$${entry.value.price}</span>
           </td>
           <td class="product-quantity">
               <div class="cart-plus-minus" onclick="change1(this)">
                   <input class="cart-plus-minus-box" type="text" name="qtyButton"
                          value="${entry.value.count}"/>
               </div>
           </td>
           <td class="product-subtotal">$${entry.value.totalPrice}</td>
           <td class="product-remove">
               <a href="cartServlet?action=del&key=${entry.key}"><i
                       class="icon-close"></i></a>
           </td>
       </tr>
   </c:forEach>
</c:if>
</tbody>
  1. 计算总价
    在这里插入图片描述
    在这里插入图片描述
🍆修改购物车

需求分析

  1. 进入购物车, 可以修改购买数量
  2. 更新该商品项的金额
  3. 跟新购物车商品数量和总金额

程序框架图
在这里插入图片描述

  1. Cart增加方法
    在这里插入图片描述
  2. CartServlet
    在这里插入图片描述
  3. 前端
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    cart.jsp
    在这里插入图片描述
    在这里插入图片描述
🍆删除购物车

需求分析

  1. 进入购物车, 可以删除某商品
  2. 可以清空购物车
  3. 要求给出适当的确认信息

程序框架图
在这里插入图片描述

  1. 删除购物车
    在这里插入图片描述
    在这里插入图片描述
  2. 清空购物车
    在这里插入图片描述
    在这里插入图片描述

🌳生产订单

需求分析

  1. 进入购物车, 点击购物车结账
  2. 生成订单和订单项
  3. 如果会员没有登陆, 先进入登陆页面, 完成登陆后再结账

程序框架图

🍉创建表

order表

-- 创建家居网购需要的数据库和表
-- 删除数据库
DROP DATABASE IF EXISTS home_furnishing;

-- 删除表
DROP TABLE `order`;

-- 创建数据库
CREATE DATABASE home_furnishing;

-- 切换
USE home_furnishing;

-- 创建订单表
-- 每个字段应当使用 not null 来约束
-- 字段类型的设计, 应当和相关联表的字段类型相对应
-- 是否需要使用外键? 
-- 1.需要[可以从db层保证数据的一致性(早期hibernate框架要求必须使用外键)]
-- 2.不需要[外键对效率有影响, 应当从程序的业务层保证数据的一致性(推荐)]
CREATE TABLE `order` (
	id VARCHAR(60) PRIMARY KEY, -- 订单编号
	create_time DATETIME NOT NULL,-- 年月日 时分秒
	price DECIMAL(10,2) NOT NULL,-- 订单价格
	`status` TINYINT NOT NULL, -- 订单状态(1未发货 2已发货 3已结账)
	member_id INT NOT NULL -- 谁的订单
)CHARSET utf8 ENGINE INNODB;

order_item表

-- 创建家居网购需要的数据库和表
-- 删除数据库
DROP DATABASE IF EXISTS home_furnishing;

-- 删除表
DROP TABLE order_item;

-- 创建数据库
CREATE DATABASE home_furnishing;

-- 切换
USE home_furnishing;

-- 创建订单明细表
CREATE TABLE order_item (
	id INT PRIMARY KEY AUTO_INCREMENT, -- 订单明细id
	`name` VARCHAR(32) NOT NULL,-- 家居名
	`count` INT UNSIGNED NOT NULL,-- 数量
	price DECIMAL(10, 2) NOT NULL,-- 价格
	total_price DECIMAL(10, 2) NOT NULL,-- 订单项的总价格
	order_id VARCHAR(60) NOT NULL -- 订单编号
)CHARSET utf8 ENGINE INNODB;

🍉实体类

订单表

public class Order {

  private String id;
  private Date createTime;
  private BigDecimal price;
  private Integer status;
  private Integer memberId;
  
  public Order() {
  }
  //有参构造器
  //getter方法, setter方法
}

订单明细表

public class OrderItem {
  private Integer id;
  private String name;
  private Integer count;
  private BigDecimal price;
  private BigDecimal totalPrice;
  private String orderId;

  public OrderItem() {
  }
  //有参构造器
  //getter方法, setter方法
}

🍉DAO

OrderDAO
在这里插入图片描述
OrderItemDAO

在这里插入图片描述

🍉service

在这里插入图片描述

public class OrderServiceImpl implements OrderService {
    private OrderDAO orderDAO = new OrderDAOImpl();
    private OrderItemDAO orderItemDAO = new OrderItemDAOImpl();
    private FurnDAO furnDAO = new FurnDAOImpl();
    //在这里可以感受到javaee分层的好处. 在service层, 通过组合多个dao的方法,
    // 完成某个业务 慢慢体会好处
    
    @Override
    public String saveOrder(Cart cart, int memberId) {
        //将cart购物车的数据以order和orderItem的形式保存到DB中

        //因为生成订单会操作多张表, 因此会涉及到多表事务的问题, ThreadLocal+Mysql事务机制+过滤器

        //1.通过cart对象, 构建一个对应的order对象
        //  先生成一个UUID, 表示当前的订单号, UUID是唯一的
        String orderId = UUID.randomUUID().toString();//订单id
        Order order = new Order(orderId, new Date(), cart.getCartTotalPrice(), 0, memberId);
        //保存order到数据表
        orderDAO.saveOrder(order);//订单生成成功
        //通过cart对象, 遍历CartItem, 构建OrderItem对象, 并保存到对应的order_item表
        Map<Integer, CartItem> cartItems = cart.getItems();
        String orderItemId = "";
        for (CartItem cartItem : cartItems.values()) {
            //通过cartItem对象构建了orderItem对象
            OrderItem orderItem = new OrderItem(null, cartItem.getName(), cartItem.getCount(),
                    cartItem.getPrice(), cartItem.getTotalPrice(), orderId);
            //保存
            orderItemDAO.saveOrderItem(orderItem);

            //更新furn表  saleNum销量 - inventory库存
            //(1) 获取furn对象
            Furn furn = furnDAO.queryFurnById(cartItem.getId());
            //(2) 更新furn对象的 saleNum销量 - inventory库存
            furn.setInventory(furn.getInventory() - cartItem.getCount());
            furn.setSaleNum(furn.getSaleNum() + cartItem.getCount());
            //(3) 更新到数据表
            furnDAO.updateFurn(furn);
        }
        //清空购物车
        cart.clear();
        return orderId;
    }
}

在这里插入图片描述

🍉servlet

public class OrderServlet extends BasicServlet {
    //定义属性
    private OrderService orderService = new OrderServiceImpl();

    /**
     * 生成订单
     * @param request
     * @param response
     * @throws ServletException
     * @throws IOException
     */
    protected void saveOrder(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        //获取购物车
        Cart cart = (Cart) session.getAttribute("cart");

        //如果cart为空, 说明会员没有购买任何家居, 转发到首页
        if (cart == null) {
            request.getRequestDispatcher("/index.jsp").forward(request, response);
            return;
        }

        //获取到登陆的member对象
        Member member = (Member) session.getAttribute("member");
        if (member == null) {//说明用户没有登录, 转发到登陆页面
            //重定向到登陆页面
            request.getRequestDispatcher("/views/member/login.jsp")
                    .forward(request, response);
            return;//直接返回
        }

        //可以生成订单
        String orderId = orderService.saveOrder(cart, member.getId());//订单, 订单明细已生成
        session.setAttribute("orderId", orderId);//订单id
        //使用重定向放入到checkout.jsp
        response.sendRedirect(request.getContextPath() + "/views/order/checkout.jsp");
    }
}

在这里插入图片描述

  • 防止生成空订单
    hashMap.clear之后, 是置空还是size置为0
    在这里插入图片描述
    在这里插入图片描述

🍉前端

在这里插入图片描述

checkout.html修改为checkout.jsp
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

🌳显示订单[订单管理]

  • 添加购物车按钮动态处理

需求分析

  1. 如果某家居库存为0, 前台的"Add to Cart" 按钮显示为"暂时缺货"
  2. 后台也加上校验. 只有在 库存>0时, 才能添加到购物车

在这里插入图片描述

思路分析

  1. 首页添加家居到购物车时, 加以限制
  2. 购物车里, 更新家居数量时,加以限制

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 管理订单

需求分析

  1. 完成订单管理-查看
  2. 具体流程参考显示家居
  3. 静态页面order.html 和 order_detail.html 已提供

程序框架图
在这里插入图片描述

  1. DAO
    在这里插入图片描述
  2. service
    在这里插入图片描述
  3. web层
    在这里插入图片描述
  4. 前端
    index.jsp, cart.jsp, login_ok.jsp, checkout.jsp, order.jsp均可跳转到订单管理
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 管理订单项

程序框架图
在这里插入图片描述

  1. DAO
    在这里插入图片描述
    在这里插入图片描述
  2. service
    在这里插入图片描述
    在这里插入图片描述
  3. web层
    在Order实体类中新增count属性, 在生成订单时, 将totalCount赋给count
    在这里插入图片描述
  4. 前端
    order.jsp跳转到OrderItemServlet
    在这里插入图片描述
    order_detail.jsp
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    order表增加count列 - ALTER TABLE order ADD COUNT INT UNSIGNED NOT NULL AFTER price;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

🌈过滤器权限验证

需求分析

  1. 加入过滤器权限验证
  2. 如果没有登陆, 查看购物车和添加到购物车, 就会自动转到会员登陆页面
  1. 配置拦截url
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
        version="4.0">
   <!--过滤器一般我们配置在上面-->
   <filter>
       <filter-name>AuthFilter</filter-name>
       <filter-class>com.zzw.furns.filter.AuthFilter</filter-class>
       <init-param>
       	   <!--这里配置了后, 还需要在过滤器中处理-->
           <param-name>excludedUrls</param-name>
           <param-value>/views/manage/manage_login.jsp,/views/member/login.jsp</param-value>
       </init-param>
   </filter>
   <filter-mapping>
       <filter-name>AuthFilter</filter-name>
       <!--这里配置要验证的url
           1.在filter-mapping中的url-pattern配置 要拦截/验证的url
           2.对于我们不去拦截的url, 就不配置
           3.对于要拦截的目录中的某些要放行的资源, 再通过配置指定
       -->
       <url-pattern>/views/cart/*</url-pattern>
       <url-pattern>/views/manage/*</url-pattern>
       <url-pattern>/views/member/*</url-pattern>
       <url-pattern>/views/order/*</url-pattern>
       <url-pattern>/cartServlet</url-pattern>
       <url-pattern>/manage/furnServlet</url-pattern>
       <url-pattern>/orderServlet</url-pattern>
       <url-pattern>/orderItemServlet</url-pattern>
   </filter-mapping>
</web-app>

2.过滤器逻辑判断

/**
* 这是用于权限验证的过滤器, 对指定的url进行验证
* 如果登陆过, 就放行; 如果没有登陆, 就回到登陆页面
* @author 赵志伟
* @version 1.0
*/
@SuppressWarnings({"all"})
public class AuthFilter implements Filter {

   private List<String> excludedUrls;

   @Override
   public void init(FilterConfig filterConfig) throws ServletException {
       //获取到配置的excludedUrls
       String strExcludedUrls = filterConfig.getInitParameter("excludedUrls");
       String[] split = strExcludedUrls.split(",");
       //将 splitUrl 转成 list
       excludedUrls = Arrays.asList(split);
       System.out.println("excludedUrls= " + excludedUrls);
   }

   @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) {//说明用户没有登录
               //转发到登陆页面, 转发不走过滤器
               servletRequest.getRequestDispatcher("/views/member/login.jsp")
                       .forward(servletRequest, servletResponse);

               重定向-拦截-重定向-拦截-重定向-拦截
               //((HttpServletResponse) servletResponse)
               //        .sendRedirect(request.getContextPath() + "/views/member/login.jsp");
               return;//直接返回
           }

       }


       //验证通过, 放行
       filterChain.doFilter(servletRequest, servletResponse);
       System.out.println("请求/cartServlet验证通过, 放行");
   }

   @Override
   public void destroy() {

   }
}
  1. 处理管理员登陆
    >
    在这里插入图片描述

🌈事务管理

1. 数据不一致问题
  1. 将FurnDAOImpl.java的updateFurn方法的sql故意写错
    [furnDAO.updateFurn(furn);由ctrl+alt+b定位到updateFurn的实现方法]
  2. 在OrderServiceImpl的saveOrder()方法内捕获一下异常, 目的是保证程序能够继续执行
  3. 查看数据库里的数据会有什么结果. 会出现数据不一致的问题.
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
我在首页购买了一个小台灯, 数据库中生成了对应的订单和订单项, 但家居表里该小台灯的销量和库存没有变化, 纹丝不动. 相当于客户下单了, 但没有给人家发货.
在这里插入图片描述

在这里插入图片描述

2. 程序框架图

思路分析

  1. 使用 Filter + ThreadLocal 来进行事务管理
  2. 说明: 在一次http请求中, servlet-service-dao 的调用过程, 始终是一个线程, 这是使用ThreadLocal的前提
  3. 使用ThreadLocal来确保所有dao操作都在同一个Connection内

程序框架图
在这里插入图片描述

  1. 修改JdbcUtilsByDruid工具类
public class JdbcUtilsByDruid {
   private static DataSource dataSource;
   //定义属性ThreadLocal, 这里存放一个Connection
   private static ThreadLocal<Connection> threadlocalConn = new ThreadLocal<>();

   /**
    * 从ThreadLocal获取connection, 从而保证在一个线程中
    * 获取的是同一个Connection
    */
   public static Connection getConnection() {
       Connection connection = threadlocalConn.get();
       if (connection == null) {//说明当前的threadlocal没有这个连接
           try {
               //就从数据库连接池中取出连接放入threadlocal
               connection = dataSource.getConnection();
               //将连接设置为手动提交, 既不要让它自动提交
               connection.setAutoCommit(false);
               threadlocalConn.set(connection);
           } catch (SQLException e) {
               throw new RuntimeException(e);
           }
       }
       return connection;
   }

   /**
    * 提交事务
    */
   public static void commit() {
       Connection connection = threadlocalConn.get();
       if (connection != null) {
           try {
               connection.commit();
           } catch (SQLException e) {
               throw new RuntimeException(e);
           } finally {
               try {
                   connection.close();
               } catch (SQLException e) {
                   throw new RuntimeException(e);
               }
           }
           //1.当提交后, 需要把connection从threadlocalConn中清除掉
           //2.不然会造成threadlocalConn长时间持有该连接, 会影响效率
           //3.也因为Tomcat底层使用的是线程池技术
           threadlocalConn.remove();
       }
   }

   /**
    * 说明: 所谓回滚是 回滚/撤销 和connection管理的操作 删除/修改/添加
    */
   public static void rollback() {
       Connection connection = threadlocalConn.get();
       if (connection != null) {
           try {
               connection.rollback();
           } catch (SQLException e) {
               throw new RuntimeException(e);
           } finally {
               try {
                   connection.close();
               } catch (SQLException e) {
                   throw new RuntimeException(e);
               }
           }
           threadlocalConn.remove();
       }
   }
  1. 修改BasicDao
    删掉各个方法finally代码块里的close方法. 只有在事务结束后才实施关闭连接的操作. 一是提交事务后关闭连接; 二是增删改出错后, 回滚关闭连接.
   public List<T> queryMany(String sql, Class<T> clazz, Object... objects) {
       Connection connection = null;
       try {
           connection = JdbcUtilsByDruid.getConnection();
           List<T> tList =
                   queryRunner.query(connection, sql, new BeanListHandler<>(clazz), objects);
           return tList;
       } catch (SQLException e) {
           throw new RuntimeException(e);//编译异常->运行异常抛出
       }
   }

   //查询单行, 返回的是一个对象
   public T querySingle(String sql, Class<T> clazz, Object... objects) {
       Connection connection = null;
       try {
           connection = JdbcUtilsByDruid.getConnection();
           T object
                   = queryRunner.query(connection, sql, new BeanHandler<>(clazz), objects);
           return object;
       } catch (Exception e) {
           throw new RuntimeException(e);
       }
   }

   //查询某一字段
   public Object queryScalar(String sql, Object... objects) {
       Connection connection = null;
       try {
           connection = JdbcUtilsByDruid.getConnection();
           Object query = queryRunner.query(connection, sql, new ScalarHandler(), objects);
           return query;
       } catch (Exception e) {
           throw new RuntimeException(e);
       }
   }

   public int update(String sql, Object... objects) {
       Connection connection = null;
       try {
           //这里是从数据库连接池获取connection
           //注意:每次从连接池中取出connection, 不能保证是同一个
           //1.我们目前已经是从和当前线程关联的ThreadLocal获取的connection
           //2.所以可以保证是同一个连接[在同一个线程中/在同一个请求中 => 因为一个请求对应一个线程]
           connection = JdbcUtilsByDruid.getConnection();
           return queryRunner.update(connection, sql, objects);
       } catch (Exception e) {
           throw new RuntimeException(e);
       }
   }
  1. 控制层进行事务管理
    前提OrderServiceImpl里报错的代码取消try-catch, 在OrderServlet控制层捕获
       //1.如果我们只是希望对orderService.saveOrder()方法进行事务控制
       //2.那么我们可以不使用过滤器,直接在这个位置进行提交和回滚即可
       //可以生成订单
       String orderId = null;//订单, 订单明细已生成
       try {
           orderId = orderService.saveOrder(cart, member.getId());
           JdbcUtilsByDruid.commit();//提交
       } catch (Exception e) {
           JdbcUtilsByDruid.rollback();
           e.printStackTrace();
       }
🌈Transaction过滤器

程序框架图
在这里插入图片描述

体会: 异常机制是可以参与业务逻辑的
在这里插入图片描述
在这里插入图片描述

  1. 在web.xml中配置
   <filter>
       <filter-name>TransactionFilter</filter-name>
       <filter-class>com.zzw.furns.filter.TransactionFilter</filter-class>
   </filter>
   <filter-mapping>
       <filter-name>TransactionFilter</filter-name>
       <!--这里我们对请求都进行事务管理 -->
       <url-pattern>/*</url-pattern>
   </filter-mapping>
  1. 在OrderService控制层里取消捕获异常, 将代码重新改回下述模样
    String orderId = orderService.saveOrder(cart, member.getId());
    同时BasicServlet模板里也取消异常捕获, 或者将异常抛出, 代码如下
       try {
           Method declaredMethod =
                   this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
           System.out.println("this = " + this);//com.zzw.furns.web.MemberServlet@38f54ed7
           declaredMethod.invoke(this, req, resp);
           System.out.println("this.getClass() = " + this.getClass());
       } catch (Exception e) {
           //将发生的异常, 继续throw
           throw new RuntimeException(e);
       }
  1. 在代码执行完毕后, 会运行到Transaction过滤器的后置代码, 在这里进行异常捕获, 如果发生异常, 则回滚.
   @Override
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
       try {
           //放行
           filterChain.doFilter(servletRequest, servletResponse);
           JdbcUtilsByDruid.commit();//统一提交
       } catch (Exception e) {//出现了异常
           JdbcUtilsByDruid.rollback();//回滚
           e.printStackTrace();
       }
   }

🌈统一错误页面

需求分析

  1. 如果在访问/操作网站时, 出现了内部错误, 统一显示 500.jsp
  2. 如果访问/操作的页面/servlet不存在时, 统一显示 404.jsp

思路分析

  1. 发生错误/异常时, 将错误/异常抛给tomcat
  2. 在web.xml中配置不同错误显示的页面即可
  1. 引入404.html, 500.html, 修改成jsp文件
    页面首行<%@ page contentType="text/html;charset=UTF-8" language="java" %>
    base标签<base href="<%=request.getContextPath()%>/">
    将跳转链接改成index.jsp
    <a class="active" href="index.jsp"> <h4 style="color: darkblue">您访问的页面不存在 返回首页</h4> </a>
  2. web.xml配置
<!--错误提示的配置一般写在web.xml的下面-->

<!--500 错误提示页面-->
<error-page>
   <error-code>500</error-code>
   <location>/views/error/500.jsp</location>
</error-page>
<!--404 错误提示页面-->
<error-page>
   <error-code>404</error-code>
   <location>/views/error/404.jsp</location>
</error-page>
  1. 修改事务过滤器, 将异常抛给tomcat
   @Override
   public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
       try {
           //放行
           filterChain.doFilter(servletRequest, servletResponse);
           JdbcUtilsByDruid.commit();//统一提交
       } catch (Exception e) {//出现了异常
           //只有在try{}中出现了异常, 才会进行catch{}
           //才会进行回滚
           JdbcUtilsByDruid.rollback();//回滚
           //抛出异常, 给tomcat. tomcat会根据error-page来显示对应页面
           throw new RuntimeException(e);
           //e.printStackTrace();

       }
   }

🌈Ajax检验注册名

需求分析

  1. 注册会员时, 如果名字已经注册过, 当光标离开输入框, 提示会员名已经存在, 否则提示不存在
  2. 要求使用ajax完成

程序框架图
在这里插入图片描述

  1. 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);
}
  1. 前端
$("#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检验验证码
  1. 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);
       }
   }
  1. 前端
    $("#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添加购物车

  1. 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);
    }
  1. 前端
            //给所有选定的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);
                    }
                )
            });
  1. 解决Ajax请求转发失败
    测试, 会发现针对ajax的重定向和请求转发会失败, 也就是AuthFilter.java的权限拦截不生效, 也就是点击Add to Cart, 后台服务没有响应

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

  1. 主要是服务器得到的是ajax发送过来的request, 也就是说这个请求不是浏览器请求的, 而是ajax请求的. 所以servlet根据request进行请求转发或重定向都不能影响浏览器的跳转
  2. 解决方案: 如果想要实现跳转, 可以返回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}
       //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

需求分析

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

思路分析-程序框架图
在这里插入图片描述

  1. 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>

去掉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}"
          οnchange="prev(this)"/>
</div>
  1. 分析空指针异常
    将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里的数据
    在这里插入图片描述
  2. 解决空指针异常
    解决方案: 将参数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">
    在这里插入图片描述
  3. 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);
                   }
               }
           }

           //跟新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>

🌈作业布置

🍍会员登陆后不能访问后台管理

需求分析

  1. 管理员admin登陆后, 可访问所有页面
  2. 会员登陆后, 不能访问后台管理相关页面, 其他页面可以访问
  3. 假定管理员名字就是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验证通过, 放行");
    }

🍍解决图片冗余问题

需求分析

  1. 家居图片都放在一个文件夹, 会越来越多
  2. 请尝试在assets/images/product-image/目录下, 自动创建年月日目录, 比如20230612. 以天为单位来存放上传图片
  3. 当上传新家居的图片, 原来的图片就没有用了, 应当删除原来的家居图片

工具类添加方法 - 返回当前日期

   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;
   }

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

🍍分页导航完善

需求分析

  1. 如果总页数<=5, 就全部显示
  2. 如果总页数>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>

🐀🐂🐅🐇🐉🐍🐎🐏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/687659.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kali磁盘空间不足,软连接解决apt下载问题

知识点&#xff1a; ①在Linux系统下(其他操作系统也有类似的规定)&#xff0c;磁盘的分区大致可以分为三类&#xff0c;分别为主分区、扩展分区和逻辑分区等等。 ②主分区可以有1-4个&#xff0c;扩展分区可以有0-1个&#xff0c;逻辑分区编号从5开始。 ③主分区可以直接进行格…

数据库信息速递 DataStax与谷歌合作将向NoSQL AstraDB引入向量搜索技术

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

机器学习5:基于线性回归理解减少“损失”的方法

在上节《机器学习4&#xff1a;基本术语》中&#xff0c;笔者介绍了“损失&#xff08;Loss&#xff09;”的定义&#xff0c;在训练模型时&#xff0c;减少损失&#xff08;Reducing Loss&#xff09;是极为关键的&#xff0c;只有“损失”足够小的机器学习系统才有实用价值。…

【数据库】mysql主从复制与读写分离

文章目录 一、读写分离1. 什么是读写分离2. 为什么要读写分离3. 什么时候要读写分离4. 主从复制与读写分离5. Mysql 主从复制原理&#xff08;主从复制的类型&#xff09;6. Mysql 主从复制的工作过程7. Mysql 读写分离原理 二、主从复制的配置操作1. 环境配置2. 搭建 MySQL主从…

从第一性原理揭秘爱因斯坦相对论

摘要&#xff1a; 本文首先将探讨狭义相对论的基本原理及其起源。接着&#xff0c;我们将深入分析狭义相对论的世界观给我们的认知带来了哪些本质的改变。最后&#xff0c;我们将探讨狭义相对论为何无法解决引力的矛盾性问题&#xff0c;以及广义相对论是如何有效解决此类问题的…

最大匹配问题---男女匹配问题(算法)

扯淡&#xff1a; 今天期末复习的时候发现一个算法很有意思&#xff0c;就是男女最大分配对象问题&#xff0c;几对男女最多能凑够几对对象。 根据社会主义核心价值观&#xff0c;我们最好整一夫一妻制&#xff0c;分配一人一对象&#xff0c;我辈义不容辞。 题目分析&#…

需求分析六步法

需求收集可能看起来不言自明&#xff0c;但它很少得到应有的充分关注。就像运动前伸展或睡前刷牙一样&#xff0c;这是一项经常被忽视的简单任务。 但是&#xff0c;忽视这些看似简单的事情的后果可能会导致伤害、蛀牙&#xff0c;或者在项目管理的情况下&#xff0c;导致项目…

GeoServer发布图层遇到的几个雷点及解决方案

目录 前言 一、图层编码导致图层预览无服务的异常问题 1、问题描述 2、问题分析 3、问题解决 二、图层空间参考投影设置的问题 1、问题描述 2、问题分析 3、问题解决 三、Qgis导出的SLD在GeoServer中发布预览报错的问题 1、问题描述 2、问题分析 3、问题解决 四、GeoS…

java 临床症状识别系统Myeclipse开发mysql数据库web结构jsp编程计算机网页项目

一、源码特点 JSP 临床症状识别系统 是一套完善的系统源码&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;以及相应配套的设计文档&#xff0c;系统主要采用B/S 模式开发。 研究的基本内容是基于Web的临床症状识别…

一文详解如何用GPU来运行Python代码/基于Python自制一个文件解压缩小工具

前几天捣鼓了一下Ubuntu&#xff0c;正是想用一下我旧电脑上的N卡&#xff0c;可以用GPU来跑代码&#xff0c;体验一下多核的快乐&#xff0c;感兴趣的小伙伴快跟随小编一起了解一下吧 简介 前几天捣鼓了一下Ubuntu&#xff0c;正是想用一下我旧电脑上的N卡&#xff0c;可以用…

Nova代码解析

1. 引言 前序博客有&#xff1a; Nova: Recursive Zero-Knowledge Arguments from Folding Schemes学习笔记基于cycle of curves的Nova证明系统&#xff08;1&#xff09;基于cycle of curves的Nova证明系统&#xff08;2&#xff09; 微软团队2021年论文 《Nova: Recursive…

警惕2本期刊被剔除!2023年6月EI目录已更新!(附全目录下载)

2023年6月EI期刊目录更新 爱思唯尔官网近日更新了EI期刊目录&#xff0c;此次更新是2023年6月1日&#xff0c;与上次更新&#xff08;2023年2月&#xff09;相比&#xff0c;有3本期刊名称在Serials&#xff08;连续出版&#xff09;列表中搜索不到&#xff0c;其中&#xff0…

【前端|HTML系列第2篇】HTML零基础入门之标签元素

大家好&#xff0c;欢迎来到前端入门系列的第二篇博客。在这个系列中&#xff0c;我们将一起学习前端开发的基础知识&#xff0c;从零开始构建网页和Web应用程序。本篇博客将为大家介绍HTML&#xff08;超文本标记语言&#xff09;常用标签元素&#xff0c;帮助零基础小白快速入…

Upload靶场通关笔记(更新中)

文章目录 一、Pass-011.抓包上传2.获取上传路径3.工具验证 二、Pass-02三、Pass-031.使用httpd.conf自定义后缀2.提取上传文件名3.工具测试4.注意点四、Pass-041.上传.htaccess2.上传图片3.工具测试 五、Pass-05六、Pass-061.空格.号绕过2.工具测试 七、Pass-07八、Pass-081.特…

电气工程师日常工作常遇到的41种接线方法(一)

01 电动机接线 一般常用三相交流电动机接线架上都引出6个接线柱&#xff0c;当电动机铭牌上标为Y形接法时&#xff0c;D6、D4、D5相连接&#xff0c;D1-D3接电源&#xff1b;为△形接法时&#xff0c;D6与D1连接&#xff0c;D4与D2连接&#xff0c;D5与D3连接&#xff0c;然后…

报表软件测评|如何选择一款简单易用的报表开发工具

报表业务的稳定性天生较差&#xff0c;随着业务的推进&#xff0c;会不断产生新的查询统计需求&#xff0c;报表也必须相应地不断增加、修改。这导致报表的数量似乎没有止境。通常情况下&#xff0c;企业信息系统的主要功能模块在上线后相对稳定&#xff0c;不再变化&#xff0…

文心一言 VS 讯飞星火 VS chatgpt (48)-- 算法导论6.1 6题

值为<23&#xff0c;17&#xff0c;14&#xff0c;6&#xff0c;13&#xff0c;10&#xff0c;1&#xff0c;5&#xff0c;7&#xff0c;12>的数组是一个最大堆吗? 文心一言&#xff1a; 是的&#xff0c;这个数组是一个最大堆。 最大堆是一种特殊的二叉树&#xff0…

第十六届CISCN复现MISC——国粹

国粹 不是我说&#xff0c;我当时比赛的时候&#xff0c;在那里叭叭叭的数的老用心了结果他是一道非常不常规的图片密码题&#xff0c;又是一种我没见过的题型 看了一些大佬的解题&#xff0c;知道他是一个坐标类型的图片拼凑 发现很多都提到了opencv&#xff0c;又是一个知识…

Hadoop生态之Mapreduce

今天给大家带来的是Hadoop生态中的Mapreduce&#xff0c;看到这里诸佬们可能就有疑惑了呢&#xff0c;啥是Mapreduce&#xff1f;小小的脑袋大大的疑惑。 在上篇博客中博主使用了王者来举例子&#xff0c;如果把Hadoop当作王者的话&#xff0c;HDFS是后台存储点券数据的系统的…

元宇宙:在虚拟世界里遨游星辰大海

元宇宙是我们探索内心世界需求的一种可能路径&#xff0c;寄托着我们对智慧社会生活的一种向往。针对当前元宇宙热潮产生的一系列问题&#xff0c;我们需要以积极审慎的态度&#xff0c;稳步推动元宇宙产业健康有序发展。 元宇宙总在前进的路上发展 其实&#xff0c;早在几十年…