文章目录
- 🐀Java后端经典三层架构
- 🐇MVC模型
- 🐇开发环境搭建
- 🐇会员注册
- 🌳前端验证用户注册信息
- 🌳思路分析
- 🍉创建表
- 🍉创建实体类
- 🍉DAO
- 🍌MemberDAOImpl
- 🍉Service
- 🍌MemberServiceImpl
- 🌳接通web层
- 🐇会员登陆
- 🌳登陆错误_信息回显
- 🐇servlet合并
- 🍎反射+模板设计模式+动态代理
- 🌳显示家居
- 🌳添加家居
- 🍉解决重复添加
- 🍉后端数据校验说明
- 🍉BeanUtils自动封装Bean
- 🌳删除家居
- 🌳修改家具
- 🍃后台分页
- 🍒新建Page类
- 🍒DAO
- 🍒Service
- 🍒web层获取page对象
- 🍒前端页面
- 🍅后台分页导航
- 🍅修改后返回原页面
- 🍅删除后返回原页面
- 🍅添加后返回原页面
- 🍃首页分页
- 🍅首页搜索
- 🍅两个奇怪的问题
- 🌳会员显示登录名
- 🍅注销登录
- 🍅验证码
- 🌳购物车
🐀Java后端经典三层架构
分层 | 对应包 | 说明 |
---|---|---|
web层 | com.zzw.furns.web/servlet/controller/handler | 接受用户请求, 调用service |
service层 | com.zzw.furns.service | Service接口包 |
com.zzw.furns.service.impl | Service接口实现类 | |
dao持久层 | com.zzw.furns.dao | Dao接口包 |
com.zzw.furns.dao.impl | Dao接口实现类 | |
实体bean对象 | com.zzw.furns.pojo/entity/domain/bean | JavaBean类 |
工具类 | 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)
解读
- model 最早期就是javabean, 就是早期的jsp+servlet+javabean
- 后面业务复杂度越来越高, model逐渐分层化/组件化(service+dao)
- 后面又出现了持久化技术(service+dao+持久化技术(hibernate / mybatis / mybatis-plus))
- MVC依然是原来的mvc, 只是变得更加强大
🐇开发环境搭建
详情请参考👉
- 新建Java项目, 导入web框架
- 导入jar包
- 项目的结构
- 拷贝到web路径下
- 配置Tomcat
Rebuild project, 让项目识别到这些资源, 然后再启动Tomcat
- 对于复杂的前端页面, 要学会打开当前页面的结构, 提高工作效率
🐇会员注册
🌳前端验证用户注册信息
<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.java | Service接口包 |
MemberServiceImpl.java | Service接口实现类 | |
dao持久层 | MemberDAO.java | Dao接口包 |
MemberDAOImpl | Dao接口实现类 | |
实体bean对象 | Member.java | JavaBean类 |
工具类 | JdbcUtilsByDruid.java | 工具类 |
🍉创建表
🍉创建实体类
包括无参构造器和set方法. 如果添加有参构造器, 记得书写无参构造器
- 从满汉楼项目引入BasicDAO.java, JdbcUtilsByDruid.java, Druid.properties
- 修改Druid配置文件要连接的数据库名, 确保用户名密码正确. url后面是做批处理用的
- 修改JdbcUtilsByDruid的路径
配置快捷键
- 测试
🍉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层
- 新建LoginServlet
login.html请求
login_ok.html
快捷键
效果
🌳登陆错误_信息回显
将login.html重命名为login.jsp, 修改base标签
LoginServlet
login.jsp
添加span标签
🐇servlet合并
方法一: 增加隐藏域
合并到MemberServlet
🍎反射+模板设计模式+动态代理
🌳显示家居
需求分析
- 给后台管理提供独立登陆页面 manage_login.jsp(已提供)
- 管理员(admin表)登陆成功后, 显示管理菜单页面
- 管理员点击家具管理, 显示所有家居信息
程序框架图
- 页面准备
- 新建admin表 👉 参考member表
新建furn表
- 新建Admin实体类
新建Furn实体类
- 书写AdminDAO, AdminDAOImpl, 并测试; 书写AdminService, AdminServiceImpl, 并测试 👉 参考Member
书写FurnDAO, FurnDAOImpl 👉 并测试
- 书写FurnService, FurnServiceImpl 👉 并测试
- 接通web层
配置web.xml, 书写AdminServlet
配置web.xml, 书写FurnServlet
将doGet()方法移到BasicServlet中
- 前端页面
manage_login.jsp 登录验证, 请求AdminServlet
manage_menu.jsp 请求FurnServlet
furn_manage.jsp 显示家居信息
🌳添加家居
思路分析
- 请求添加家居, 请求FurnServlet的add方法, 将前端提交的数据封装到Furn对象
- 调用FurnService.add(Furn furn)方法
- 跳转到显示家居的页面
- FurnDAO
- FurnService
- web层
FurnServlet
解决中文乱码问题
- 前端: 添加furn_add.jsp
🍉解决重复添加
请求转发, 当用户刷新页面时, 会重新发出第一次的请求, 造成数据重复提交
解决方案: 使用重定向
🍉后端数据校验说明
后端方案一
后端方案二
前端方案三
🍉BeanUtils自动封装Bean
引入: commons-logging-1.1.1.jar, commons-beanutils-1.8.0.jar
- 使用BeanUtils自动封装javabean
debug小技巧👉
- 报错
原因: 由于前端没有传imagePath的字段, 所有后端在构建的furn对象的时候, imagePath传了个null,
解决方案👇
- 将 把数据自动封装成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; } }
调用
🌳删除家居
需求分析
- 管理员进入到家居管理页面
- 点击删除家居链接, 弹出确认窗口, 确认-删除, 取消-放弃
程序框架图
- FurnDAO
- FurnService
- web层
FurnServlet
- furn_add.jsp页面
jQuery操作父元素, 兄弟元素, 子元素, 请移步👉
js弹框请移步👉
🌳修改家具
思路分析
- 管理员进入家居管理页面furn_manage.jsp
- 点击修改家居链接, 回显该家居信息 [furn_update.jsp]
- 填写新的信息, 点击修改家居按钮
- 修改成功后, 显示刷新后的家居列表
程序框架图
- FurnDAO
- FurnService
- web层
FurnServlet
- 前端
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. 新建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 -->
🍅首页搜索
需求分析
- 顾客进入首页页面
- 点击搜索按钮, 可以输入家居名
- 正确显示分页导航条, 并且要求在分页时, 保留上次搜索条件
程序框架图
- DAO
模糊查询👉
- service
- web层 CustomerFurnServlet
page方法就被抛弃了
- 前端 index.jsp
🍅两个奇怪的问题
- 点击家居管理, 发出两个请求
抓包
原因
请求首页面即进入到indx.jsp, index.jsp又请求转发到CustomerFurnServlet
问题解决- 首页分页出现问题
原因
🌳会员显示登录名
需求分析
- 会员登陆成功, login_ok.jsp显示登录信息
- 如果登陆成功后返回首页面, 显示订单管理和安全退出
- 如果用户没有登陆过, 首页就显示登录和注册超链接
程序框架图
实现
重命名时, 会联动修改
将login_ok.jsp中的index.html改成index.jsp. 注意, 不要改成views/customer/index.jsp
🍅注销登录
思路分析
- 用户登录成功后
- login_ok.jsp, 点击安全退出, 注销登录
- 返回首页, 也可点击安全退出, 注销登录
程序框架图
实现
🍅验证码
程序框架图
- web层
KaptchaServlet -> 引入kaptcha-2.3.2.jar包, 在web.xml中配置KaptchaServlet
MemberServlet
- 前端页面
login.jsp
验证码不能为空
点击图片更换验证码
将register_ok.html, register_fail.html改造成jsp页面
login.jsp页面总是默认停留在会员登录的div内, 修改
注册回显信息
🌳购物车
程序框架图
实现
- 创建CartServlet
🐀🐂🐅🐇🐉🐍🐎🐏