大家好呀!我是小笙~ 该家居网购的技术栈是javaweb开发,我觉得这个是对之前学习的知识的一个总结和输出,是个很好的自我学习反馈的过程.
家居网购项目
家居网购-程序框架图
使用分层模式: 分层的目的是为了解耦,降低代码的耦合度,有利项目的维护和升级
三层架构MVC
MVC 全称∶ Model 模型、View 视图、 Controller 控制器
MVC 的理念是将软件代码拆分成为组件,单独开发,组合使用(目的还是为了解耦合), 也有很多落地的框架比如 SpringMVC
- MVC 最早出现在 JavaEE 三层中的 Web 层,它可以有效的指导 WEB 层的代码如何有效 分离,单独工作
- 后面业务复杂度越来越高, model 逐渐分层/组件化 (service + dao)
项目准备
需要引入的jar包
程序框架图
页面展示
首页展示
登录注册页面
家居后台管理页面
购物车页面
订单生成页面
功能实现
注册功能
1.设计用户表
# 创建数据库
DROP DATABASE IF EXISTS home_shopping;
CREATE DATABASE home_shopping;
USE home_shopping;
# 创建用户表
CREATE TABLE member(
`id` INT PRIMARY KEY AUTO_INCREMENT, # id
`username` VARCHAR(10) NOT NULL UNIQUE, # 用户名
`password` VARCHAR(32) NOT NULL, # 密码
`email` VARCHAR(64), # 邮箱
`user_state` TINYINT NOT NULL DEFAULT 0 # 用户模式 0 会员 1 管理员 -1 未登录
)CHARSET utf8 ENGINE INNODB;
INSERT INTO member(`username`,`password`,`email`,`user_state`) VALUES('Al_tair',MD5('luo1234567'),'lns@qq.com',1);
INSERT INTO member(`username`,`password`,`email`,`user_state`) VALUES('admins',MD5('1234567'),'1079936146@qq.com',0);
SELECT * FROM member;
2.Dao层对数据库的CRUD
public interface MemberDao {
/**
* 添加会员用户信息
*/
public boolean addUserInfo(Member member);
/**
* 查询是否有该用户名的会员用户
*/
public boolean queryUserInfo(String username);
/**
* 查询有该用户名和密码的会员用户信息
*/
public Member queryUserInfo(String username,String password);
}
// 备注:实现代码略,记得每次写完实现代码使用Junit测试
3.Service层对业务的操作
public interface MemberService {
/**
* 注册用户
*/
public boolean registerUser(Member member);
/**
* 是否有存在该用户
*/
public boolean isExistUser(String username);
/**
* 用户登录
*/
public Member login(String username,String password);
}
// 备注:实现代码略,记得每次写完实现代码使用Junit测试
4.前端页面效果
<!-- 截图部分重点 -->
<div class="login-register-form">
<!-- 错误提醒 -->
<span class="errorMsg"
style="float: right; font-weight: bold; font-size: 20pt; margin-left: 10px;">
</span>
<form action="memberServlet" method="post">
<input type="hidden" name="action" value="register">
<input type="text" id="username" name="username" placeholder="Username"/>
<input type="password" id="password" name="password" placeholder="输入密码"/>
<input type="password" id="repwd" name="repassword" placeholder="确认密码"/>
<input name="email" id="email" placeholder="电子邮件" type="email"/>
<input type="text" id="code" name="code" style="width: 50%" placeholder="验证码"/>
<img class="codeImgRegister" alt="" src="">
<div class="button-box">
<button type="submit" id="register-btn"><span>会员注册</span></button>
</div>
</form>
</div>
5.前端页面校验(JQuery库)
-
验证用户名:必须字母,数字下划线组成,并且长度为 6 到 10 位
/^[a-zA-Z0-9_]{6,10}$/
-
验证密码:必须由字母,数字下划线组成,并且长度为 6 到 10 位
-
邮箱格式验证:常规验证即可 (正则表达式 )
/^[A-Za-z0-9]+([_\.][A-Za-z0-9]+)*@([A-Za-z0-9\-]+\.)+[A-Za-z]{2,6}$/
-
验证码:校验
导入包
表单重复提交情况:
- 提交完表单。服务器使用请求转发进行页面跳转。用户刷新(F5),会发起最后一 次的请求, 造成表单重复提交问题。解决:用重定向
- 用户正常提交,由于网络延迟等原因,未收到服务器的响应,这时,用户着急多 点了几次提交操作,也会造成表单重复提交。解决: 验证码
- 用户正常提交,服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提 交。也会造成表单重复提交,解决:验证码
<!-- 点击校验码,更新校验码 --> $(".codeImgRegister").click(function(){ this.src="<%=request.getContextPath() + "/"%>kaptchaServlet?d=" + new Date(); })
-
校验优化:Ajax异步请求
<script type="text/javascript"> // Ajax异步请求:这里是登录用户的校验,同理注册也是可以的 $("#LoginUsername").blur(function(){ $.getJSON( "memberServlet", { action:"IsExistUser", username:$("#LoginUsername").val(), }, function (data,state,XHttp) { if(!data.IsExistUser){ $("span.errorMsg").text("用户名不存在!"); }else{ $("span.errorMsg").text(""); } } ) }) </script>
6.后端校验
if(username == null || password == null || email == null || repassword == null){
response.sendRedirect(getServletContext().getContextPath() + "/views/member/register_fail.jsp");
return;
}
// 用户名格式校对
String pattern = "^[a-zA-Z0-9_]{6,10}$";
if(Pattern.matches(pattern,username)){
System.out.println("用户名格式正确");
}else{
System.out.println("用户名格式不正确");
response.sendRedirect(getServletContext().getContextPath() + "/views/member/register_fail.jsp");
return;
}
// 密码格式校对
pattern = "^[a-zA-Z0-9_]{6,10}$";
if(Pattern.matches(pattern,password)){
System.out.println("密码格式正确");
}else{
System.out.println("密码格式不正确");
response.sendRedirect(getServletContext().getContextPath() + "/views/member/register_fail.jsp");
return;
}
// 输入两次密码是否相同
if(!password.equals(repassword)){
System.out.println("输入两次密码不相同!");
response.sendRedirect(getServletContext().getContextPath() + "/views/member/register_fail.jsp");
return;
}
// 邮箱格式校对
pattern = "\\w+@\\w+\\.[a-z]+(\\.[a-z]+)?";
if(Pattern.matches(pattern,email)){
System.out.println("邮箱格式正确");
}else{
System.out.println("邮箱格式不正确");
response.sendRedirect(getServletContext().getContextPath() + "/views/member/register_fail.jsp");
return;
}
// 获取验证码
HttpSession session = request.getSession();
Object realCode = session.getAttribute(KAPTCHA_SESSION_KEY);
// 立即删除session中该属性
session.removeAttribute(KAPTCHA_SESSION_KEY);
if(realCode == null || code == null || !code.equalsIgnoreCase((String)realCode)){
System.out.println("验证码不正确");
response.sendRedirect(getServletContext().getContextPath() + "/views/member/register_fail.jsp");
return;
}
7.注册页面后端需求分析
-
会员注册信息,前端验证通过后,提交给服务器
-
如果用户名在数据库中已经存在,后台给出提示信息,并返回重新注册
-
如果用户名没有在数据库中,完成注册,并返回注册成功的页面
-
-
注册密码为 md5 加密
登录功能
1.前端页面效果
<div class="login-register-form">
<!-- 显示错误信息 -->
<span style="font-size: 18pt;font-weight: bold;float: right;color: red">
<span class="errorMsg"
style="float: right; font-weight: bold; font-size: 20pt; margin-left: 10px;">
</span>
</span>
<form action="memberServlet" method="post">
<input type="hidden" name="action" value="login">
<input type="text" id="LoginUsername" name="username" value="${requestScope.get("username")}" placeholder="Username"/>
<input type="password" name="password" placeholder="Password"/>
<input type="text" id="code2" name="code" style="width: 50%" placeholder="验证码"/>
<img class="codeImgLogin" alt="" src="kaptchaServlet">
<div class="button-box">
<div class="login-toggle-btn">
<input type="checkbox"/>
<a class="flote-none" href="javascript:void(0)">Remember me</a>
<a href="#">Forgot Password?</a>
</div>
<button type="submit" id="login_btn"><span>Login</span></button>
</div>
</form>
</div>
2.后端需求分析
-
分别校验输入的用户名和密码是否符合格式
-
通过用户名和密码去数据库查找数据
-
如果找到数据,跳转到登录成功的页面
-
如果没有找到数据或者用户名密码格式不对,都会返回登录页面并且显示
-
3.优化Servlet,将处理请求和分发请求分开
-
问题提出:其实不管是会员注册还是会员登录都是对会员用户的数据操作,但是却分出了两个Servlet,接下来我们尝试解决web层的优化
-
方案一:通过隐藏域的值来判断是login还是register
<input type="hidden" name="action" value="register">
String action = request.getParameter("action"); if("register".equals(action)){ RegisterServlet(request,response); }else if("login".equals(action)){ LoginServlet(request,response); }
问题存在:虽然分开了处理请求和分发请求,但是本质还是在一个Servlet操作,同时随着业务的拓展,会存在代码复用的问题
-
方案二:反射 + 模板设计模式 + 动态绑定
-
就是将接收http请求和分发请求交给父类BasicServlet,将处理请求交给子类
public abstract class BasicServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 注意action值要和方法名一致 String action = request.getParameter("action"); try { Method declaredMethod = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class); declaredMethod.invoke(request,response); } catch (Exception e) { e.printStackTrace(); } } }
-
-
后台家居管理
1.设计家居表
CREATE TABLE furn(
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, # id
`name` VARCHAR(32) NOT NULL DEFAULT ' ', # 家居名
`price` DECIMAL(11,2) NOT NULL, # 价格
`vendor` VARCHAR(32) NOT NULL, # 制造商
`sales` INT UNSIGNED NOT NULL, # 销量
`inventory` INT UNSIGNED NOT NULL, # 库存
`img_path` VARCHAR(256) NOT NULL # 存放图片的路径
)CHARSET utf8 ENGINE INNODB
# 增
INSERT INTO furns(`name`,`price`,`vendor`,`sales`,`inventory`,`img_path` ) VALUES ('Simple 北欧小桌',2030.00,'瑞典',668,20,'http://localhost:9999/homeShopping/shop-left-sidebar.html');
# 改
UPDATE furn SET `name`='花盆',`price`=200,`vendor`='中国',`sales` =20,
`inventory`=12,`img_path`='assets/images/product-image/2.jpg' WHERE `id` = 2;
# 删
DELETE FROM furn WHERE `id` = 1;
# 查
SELECT *FROM furns;
实体类
private Integer id;
private String name;
private BigDecimal price;
private String vendor;
private Integer sales;
private Integer inventory;
private String imgPath;
2.设计Page分页数据模型
public class Page<T> {
public final static Integer PAGE_SIZE = 3;
// 表示当前页号
private Integer pageNo;
// 表示每页显示最大记录数
private Integer pageSize = PAGE_SIZE;
// 表示当前页要显示的数据
private List<T> items;
// 表示共有多少页
private Integer totalPage;
// 表示共有多少记录
private Integer totalRow;
// 分页导航url
private String url;
}
3.Dao对数据库的操作
public interface FurnDao {
/**
* 查询所有家居信息
*/
public List<Furn> queryAllFurns();
/**
* 通过id查询家居数据
*/
public Furn queryFurnById(Integer id);
/**
* 添加家居
*/
public boolean addFurns(Furn furn);
/**
* 删除家居
*/
public boolean deleteFurnById(int id);
/**
* 修改家居
*/
public boolean updateFurn(Furn furn);
/**
* 表示记录数量
*/
public int rowNum();
/**
* 分页数据获取
*/
public List<Furn> pageItems(int begin,int rowNum);
/**
* 表示记录数量(通过家具名过滤)
*/
public int rowNumByName(String name);
/**
* 分页数据获取(通过家具名过滤)
* @param begin 开始的记录位置 从0开始取
* @param rowNum 取记录数
*/
public List<Furn> pageItemsByName(int begin,int rowNum,String name);
}
4.Service层对业务的操作
public interface FurnService {
/**
* 通过id查询家居数据
*/
public Furn queryFurnById(Integer id);
/**
* 显示家居
*/
public List<Furn> showFurns();
/**
* 添加家居
*/
public boolean addFurns(Furn furn);
/**
* 删除家居
*/
public boolean deleteFurnById(int id);
/**
* 修改家居
*/
public boolean updateFurn(Furn furn);
/**
* 家居显示分页管理
* @param pageNo 页号
* @param pageSize 一个页面最大记录数
*/
public Page<Furn> pageManage (int pageNo,int pageSize);
/**
* 家居显示分页管理(通过家具名过滤)
* @param pageNo 页号
* @param pageSize 一个页面最大记录数
*/
public Page<Furn> pageManageByName (int pageNo,int pageSize,String name);
}
分页算法
@Override
public Page<Furn> pageManage(int pageNo,int pageSize) {
Page<Furn> page = new Page<>();
page.setPageNo(pageNo);
page.setPageSize(pageSize);
int totalRow = furns.rowNum();
page.setTotalRow(totalRow);
int totalPage = (int)Math.ceil(totalRow * 1.0 / pageSize);
page.setTotalPage(totalPage);
int begin = pageSize*(pageNo-1);
page.setItems(furns.pageItems(begin, (totalRow-begin)/pageSize>0?pageSize:totalRow%pageSize));
page.setUrl("url");
return page;
}
5.后台家居管理页面
<!-- 家居显示 -->
<c:forEach items="${requestScope.get('page').items}" var="furn">
<tr>
<td class="product-thumbnail">
<a href="#"><img class="img-responsive ml-3" src="${furn.imgPath}"
alt=""/></a>
</td>
<td class="product-name"><a href="#">${furn.name}</a></td>
<td class="product-name"><a href="#">${furn.vendor}</a></td>
<td class="product-price-cart"><span class="amount">${furn.price}</span></td>
<td class="product-quantity">
${furn.sales}
</td>
<td class="product-quantity">
${furn.inventory}
</td>
<td class="product-remove">
<a class="updateFurn" href="views/furn/furn_update.jsp?id=${furn.id}&name=${furn.name}&price=${furn.price}&vendor=${furn.vendor}&sales=${furn.sales}&inventory=${furn.inventory}&imgPath=${furn.imgPath}
&pageNo=${requestScope.get("page").pageNo}&furnName=${requestScope.name}">
<i class="icon-pencil"></i>
</a>
<a class="deleteFurn" href="manage/furnServlet?action=deleteFurn&id=${furn.id}&pageNo=${requestScope.get("page").pageNo}&furnName=${requestScope.name}"><i class="icon-close"></i></a>
</td>
</tr>
</c:forEach>
分页显示代码
<ul>
<li><a href="${requestScope.page.url}">首页</a></li>
<c:if test="${requestScope.get('page').pageNo > 1}">
<li><a href="${requestScope.page.url}&pageNo=${requestScope.get('page').pageNo-1}">上页</a></li>
</c:if>
<%--设置显示分页号数--%>
<%--页面总数大于5的时候,显示5个页码--%>
<c:if test="${requestScope.get('page').totalPage > 5}">
<c:set var="begin" value="${requestScope.get('page').pageNo-2}"/>
<c:set var="end" value="${requestScope.get('page').pageNo+2}"/>
</c:if>
<%--页面位置在前面2个--%>
<c:if test="${requestScope.get('page').pageNo <= 3}">
<c:set var="begin" value="1"/>
<c:set var="end" value="5"/>
</c:if>
<%--页面位置在后面2个--%>
<c:if test="${(requestScope.get('page').totalPage - requestScope.get('page').pageNo) <= 3}">
<c:set var="begin" value="${requestScope.get('page').totalPage-4}"/>
<c:set var="end" value="${requestScope.get('page').totalPage}"/>
</c:if>
<%--页面总数小于5的时候,默认全部显示--%>
<c:if test="${requestScope.get('page').totalPage <= 5}">
<c:set var="begin" value="1"/>
<c:set var="end" value="${requestScope.get('page').totalPage}"/>
</c:if>
<%--循环遍历显示页号数--%>
<c:forEach begin="${begin}" end="${end}" var="pageNum">
<c:if test="${pageNum == requestScope.get('page').pageNo}">
<li><a class="active" href="${requestScope.page.url}&pageNo=${pageNum}">${pageNum}</a></li>
</c:if>
<c:if test="${pageNum != requestScope.get('page').pageNo}">
<li><a href="${requestScope.page.url}&pageNo=${pageNum}">${pageNum}</a></li>
</c:if>
</c:forEach>
<c:if test="${requestScope.get('page').pageNo < requestScope.get('page').totalPage}">
<li><a href="${requestScope.page.url}&pageNo=${requestScope.get('page').pageNo+1}">下页</a></li>
</c:if>
<li><a href="${requestScope.page.url}&pageNo=${requestScope.get('page').totalPage}">末页</a></li>
<li><a>共${requestScope.get('page').totalPage}页</a></li>
<li><a>共${requestScope.get('page').totalRow}记录</a></li>
</ul>
6.控制层对家居的增删改查
BeanUtils自动封装Bean
- BeanUtils 工具类,它可以一次性的把所有请求的参数注入到 JavaBean 中
- BeanUtils 工具类,经常用于把 Map 中的值注入到 JavaBean 中,或者是对象属性值的拷贝操作
- 需要导入需要的 jar 包: commons-beanutils-1.8.0.jar commons-logging-1.1.1.jar
// 使用工具包前
String name = request.getParameter("name");
String price = request.getParameter("price");
String vendor = request.getParameter("vendor");
String sales = request.getParameter("sales");
String inventory = request.getParameter("inventory");
String imgPath = request.getParameter("imgPath");
try {
Furn furn = new Furn(name, new BigDecimal(price), vendor,
Integer.valueOf(sales), Integer.valueOf(inventory), img);
furns.addFurns(furn);
}catch (Exception e){
request.setAttribute("errorMsg","添加家居数据格式错误!");
request.getRequestDispatcher("/views/furn/furn_add.jsp").forward(request,response);
return;
}
// 使用工具包之后
//1.会使用工具类 DataUtils 来完成自动封装 JavaBean
//2.注意表单提交的数据对应的 name 需要和 JavaBean 的属性名保持一致
Furn furn = new Furn();
try {
// 本质:通过反射来为Javabean对象set方法添加属性值
BeanUtils.populate(furn,request.getParameterMap());
furns.addFurns(furn);
}catch (Exception e){
request.setAttribute("errorMsg","添加家居数据格式错误!");
request.getRequestDispatcher("/views/furn/furn_add.jsp").forward(request,response);
return;
}
搜索框
<div class="header_account_list">
<a href="javascript:void(0)" class="header-action-btn search-btn"><i class="icon-magnifier"></i></a>
<div class="dropdown_search">
<form class="action-form" action="customerFurnServlet">
<input type="hidden" name="action" value="customerFurnByName">
<input class="form-control" name="name" placeholder="输入家居名搜索" type="text">
<button class="submit" type="submit"><i class="icon-magnifier"></i></button>
</form>
</div>
</div>
通过page对象的url来携带搜索的家具名,起到点击分页栏,仍旧处在搜索结果的条件下
protected void customerFurnByName(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 页号
String pageNo = request.getParameter("pageNo");
// 搜索框内容:家居名
String name = request.getParameter("name");
// session记录登录情况以及身份
HttpSession session = request.getSession();
Object username = session.getAttribute("username");
Object userState = session.getAttribute("userState");
if(username == null){
username = "登录|注册";
userState = -1;
}
if(name == null){
name = "";
}
if(pageNo == null){
pageNo = "1";
}
// 分页(根据家具名来分页搜索)
Page<Furn> pages = furns.pageManageByName(Integer.parseInt(pageNo), 4,name);
StringBuffer url = new StringBuffer("customerFurnServlet?action=customerFurnByName");
if(!"".equals(name)){
url.append("&name=").append(name);
}else{
url.append("&name=").append("");
}
pages.setUrl(url.toString());
request.setAttribute("page",pages);
request.setAttribute("username",username);
request.setAttribute("userState",userState);
request.getRequestDispatcher("/views/customer/index.jsp").forward(request,response);
}
添加家居
<tr>
<td class="product-thumbnail">
<input type="hidden" name="imgPath" value="assets/images/product-image/1.jpg">
<a href="#"><img class="img-responsive ml-3" src="assets/images/product-image/1.jpg" alt=""/></a>
</td>
<td class="product-name"><input name="name" style="width: 60%" type="text" value=""/></td>
<td class="product-name"><input name="vendor" style="width: 90%" type="text" value=""/></td>
<td class="product-price-cart"><input name="price" style="width: 90%" type="text" value=""/></td>
<td class="product-quantity">
<input name="sales" style="width: 90%" type="text" value=""/>
</td>
<td class="product-quantity">
<input name="inventory" style="width: 90%" type="text" value=""/>
</td>
<td>
<input type="submit" style="width: 90%;background-color: silver;border: silver;border-radius: 20%;" value="添加家居"/>
</td>
</tr>
protected void addFurn(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pageNo = request.getParameter("pageNo");
Furn furn = new Furn();
try {
BeanUtils.populate(furn,request.getParameterMap());
}catch (Exception e){
request.setAttribute("errorMsg","添加家居数据格式错误!");
request.getRequestDispatcher("/views/furn/furn_add.jsp").forward(request,response);
return;
}
furns.addFurns(furn);
response.sendRedirect(getServletContext().getContextPath() + "/manage/furnServlet?action=showPageDisplayByName&pageNo=" + pageNo);
}
删除家居
前端判断是否删除
<script type="text/javascript">
$(function(){
$("a.deleteFurn").click(function(){
var furnName = $(this).parent().parent().find("td:eq(1)").text();
if(confirm("确定删除家居【"+ furnName + "】?")){
return true;
}
return false;
})
})
</script>
protected void deleteFurn(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pageNo = request.getParameter("pageNo");
String id = request.getParameter("id");
String name = URLEncoder.encode(request.getParameter("furnName"), "utf-8");
boolean deleteFlag = false;
try {
// 通过id删除家居
deleteFlag = furns.deleteFurnById(Integer.parseInt(id));
}catch (Exception e){
throw new RuntimeException();
}
System.out.println(deleteFlag?"删除成功":"删除失败");
response.sendRedirect(getServletContext().getContextPath() + "/manage/furnServlet?action=showPageDisplayByName&name=" + name + "&pageNo=" + pageNo);
}
修改家居
<tr>
<td class="product-thumbnail">
<div id="pic">
<img id="prevView" name="imgPath" class="img-responsive ml-3" src=<%=request.getParameter("imgPath")%> alt=""/>
<input id="upload" type="file" name="pic" onchange="prev(this)"/>
</div>
</td>
<td class="product-name"><input name="name" style="width: 60%" type="text" value=<%=request.getParameter("name")%>></td>
<td class="product-name"><input name="vendor" style="width: 90%" type="text" value=<%=request.getParameter("vendor")%>></td>
<td class="product-price-cart"><input name="price" style="width: 90%" type="text" value=<%=request.getParameter("price")%>></td>
<td class="product-quantity">
<input name="sales" style="width: 90%" type="text" value=<%=request.getParameter("sales")%>>
</td>
<td class="product-quantity">
<input name="inventory" style="width: 90%" type="text" value=<%=request.getParameter("inventory")%>>
</td>
<td>
<input type="submit" style="width: 90%;background-color: silver;border: silver;border-radius: 20%;" value="修改家居"/>
</td>
</tr>
<!-- furn_manage.jsp -->
<a class="updateFurn" href="views/furn/furn_update.jsp?id=${furn.id}&name=${furn.name}&price=${furn.price} &vendor=${furn.vendor}&sales=${furn.sales}&inventory=${furn.inventory}&imgPath=${furn.imgPath}">
<i class="icon-pencil"></i>
</a>
<!-- furn_update.jsp -->
<tr>
<td class="product-thumbnail">
<input type="hidden" name="imgPath" value="assets/images/product-image/default.jpg">
<a href="#"><img class="img-responsive ml-3" src=<%=request.getParameter("imgPath")%>
alt=""/></a>
</td>
<td class="product-name"><input name="name" style="width: 60%" type="text" value=<%=request.getParameter("name")%>></td>
<td class="product-name"><input name="vendor" style="width: 90%" type="text" value=<%=request.getParameter("vendor")%>></td>
<td class="product-price-cart"><input name="price" style="width: 90%" type="text" value=<%=request.getParameter("price")%>></td>
<td class="product-quantity">
<input name="sales" style="width: 90%" type="text" value=<%=request.getParameter("sales")%>>
</td>
<td class="product-quantity">
<input name="inventory" style="width: 90%" type="text" value=<%=request.getParameter("inventory")%>>
</td>
<td>
<input type="submit" style="width: 90%;background-color: silver;border: silver;border-radius: 20%;" value="修改家居"/>
</td>
</tr>
购物车功能
1.设计购物车数据模型
// 数据库种每一件商品的信息
public class CarItem {
private Integer id;
private String name;
private BigDecimal singlePrice;
private Integer count;
private BigDecimal singleTotalPrice;
private String imgPath;
}
/**
* 含有多个CarItem,用来管理购物车内多个商品
*/
public class Cart {
/**
* 存储 id 对应的商品
*/
private Map<Integer,CarItem> items = new HashMap<>();
/**
* 用来有序存放加入购物车的顺序
*/
private List<CarItem> itemsList = new ArrayList<>();
public List<CarItem> getItemsList() {
return itemsList;
}
/**
* 添加商品到购物车
*/
public void addItem(CarItem carItem){
Integer id = carItem.getId();
CarItem originalCarItem = items.get(id);
if(originalCarItem == null){ // 购物车内没有相同的该商品
items.put(id,carItem);
itemsList.add(carItem);
}else{
originalCarItem.setCount(carItem.getCount() + originalCarItem.getCount());
originalCarItem.setSingleTotalPrice(carItem.getSingleTotalPrice().add(originalCarItem.getSingleTotalPrice()));
}
}
/**
* 返回商品的总金额
*/
public BigDecimal getCartItemsPrice(){
BigDecimal totalPrice = new BigDecimal(0.0);
Set<Integer> itemsId = items.keySet();
for (Integer id : itemsId) {
totalPrice = totalPrice.add((items.get(id)).getSingleTotalPrice());
}
return totalPrice;
}
/**
* 获取商品的总数
*/
public Integer getCartItemsCount(){
Integer totalCount = 0;
if(items == null){
return totalCount;
}
Set<Integer> itemsId = items.keySet();
for (Integer id : itemsId) {
totalCount += items.get(id).getCount();
}
return totalCount;
}
/**
* 返回指定商品列表
*/
public List<CarItem> listItem(Integer begin,Integer end){
if(items == null){
return null;
}
List<CarItem> carItems = new ArrayList<>();
for(int i = begin;i<begin+end;i++){
carItems.add(itemsList.get(i));
}
return carItems;
}
/**
* 购物车商品分页管理
*/
public Page<CarItem> pageManageCartItems(int pageNo, int pageSize) {
Page<CarItem> page = new Page<>();
page.setPageNo(pageNo);
page.setPageSize(pageSize);
int totalRow = itemsList.size();
page.setTotalRow(totalRow);
int totalPage = (int)Math.ceil(totalRow * 1.0 / pageSize);
page.setTotalPage(totalPage);
int begin = pageSize*(pageNo-1);
page.setItems(listItem(begin, (totalRow-begin)/pageSize>0?pageSize:totalRow%pageSize));
page.setUrl("url");
return page;
}
/**
* 更新商品的数量
*/
public void updateItem(Integer id,Integer count){
if(id == null || count == null){
return;
}
CarItem originalCarItem = items.get(id);
if(originalCarItem == null){
return;
}
if(count == 0){
deleteItem(id);
return;
}
originalCarItem.setCount(count);
originalCarItem.setSingleTotalPrice(new BigDecimal(originalCarItem.getCount()).multiply(originalCarItem.getSinglePrice()));
}
/**
* 删除某个商品
*/
public void deleteItem(Integer id){
CarItem originalCarItem = items.get(id);
if(originalCarItem == null){
return;
}
for (CarItem carItem : itemsList) {
if(carItem.getId().equals(id)){
itemsList.remove(carItem);
items.remove(id);
return;
}
}
}
/**
* 清空购物车
*/
public void deleteCart(){
items = new HashMap<>();
itemsList = new ArrayList<>();
}
}
2.购物车显示页面
<c:forEach items="${requestScope.pages.items}" var="cartItem">
<tr>
<td class="product-thumbnail">
<a><img class="img-responsive ml-3" src="${cartItem.imgPath}" alt=""/></a>
</td>
<td class="product-name"><a>${cartItem.name}</a></td>
<td class="product-price-cart"><span class="amount">¥${cartItem.singlePrice}</span></td>
<td class="product-quantity">
<div class="cart-plus-minus">
<input readonly="true" ItemId="${cartItem.id}" class="cart-plus-minus-box" id="count" type="text" name="qtybutton" value=${cartItem.count} >
</div>
</td>
<td class="product-subtotal">¥${cartItem.singleTotalPrice}</td>
<td class="product-remove">
<a id="cleanCartItem" href="cart/cartServlet?action=deleteCartItem&id=${cartItem.id}&pageNo=${requestScope.pages.pageNo}"><i class="icon-close"></i></a>
</td>
</tr>
</c:forEach>
<div class="cart-shiping-update-wrapper">
<c:if test="${sessionScope.cart.getCartItemsCount() != 0}">
<h4>共${sessionScope.cart.getCartItemsCount()}件商品 总价${sessionScope.cart.getCartItemsPrice()}元</h4>
</c:if>
<h4></h4><h4></h4><h4></h4><h4></h4><h4></h4>
<div class="cart-shiping-update">
<a href="order/orderServlet?action=generateOrder">购 物 车 - 生 成 订 单</a>
</div>
<div class="cart-clear">
<button type="submit">继 续 购 物</button>
<a href="cart/cartServlet?action=deleteCart" id="cleanCart">清 空 购 物 车</a>
</div>
</div>
3.对购物车的增删操作
/**
* 清空购物车
*/
protected void deleteCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
Object cart = session.getAttribute("cart");
if(cart != null){
((Cart)cart).deleteCart();
}
Page<CarItem> pages = ((Cart)cart).pageManageCartItems(1,4);
request.setAttribute("pages",pages);
request.getRequestDispatcher("/views/cart/cart.jsp").forward(request,response);
}
/**
* 在购物车里删除某个商品
*/
protected void deleteCartItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pageNo = request.getParameter("pageNo");
String id = request.getParameter("id");
HttpSession session = request.getSession();
Object originalCart = session.getAttribute("cart");
Cart cart = null;
if(originalCart == null){
cart = new Cart();
}else{
cart = (Cart) originalCart;
}
if(pageNo == null){
pageNo = "1";
}
cart.deleteItem(Integer.parseInt(id));
Page<CarItem> pages = cart.pageManageCartItems(Integer.parseInt(pageNo),4);
request.setAttribute("pages",pages);
request.getRequestDispatcher("/views/cart/cart.jsp").forward(request,response);
}
/**
* 分页展示商品
*/
protected void showCartPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pageNo = request.getParameter("pageNo");
HttpSession session = request.getSession();
Object originalCart = session.getAttribute("cart");
Cart cart = null;
if(originalCart == null){
cart = new Cart();
}else{
cart = (Cart) originalCart;
}
if(pageNo == null){
pageNo = "1";
}
Page<CarItem> pages = cart.pageManageCartItems(Integer.parseInt(pageNo),4);
request.setAttribute("pages",pages);
request.getRequestDispatcher("/views/cart/cart.jsp").forward(request,response);
}
/**
* 更新商品数据(+ - 商品数量)
*/
protected void updateCartPage(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pageNo = request.getParameter("pageNo");
String id = request.getParameter("id");
String count = request.getParameter("count");
HttpSession session = request.getSession();
Object originalCart = session.getAttribute("cart");
Cart cart = null;
if(originalCart == null){
cart = new Cart();
}else{
cart = (Cart) originalCart;
}
if(pageNo == null){
pageNo = "1";
}
cart.updateItem(Integer.parseInt(id),Integer.parseInt(count));
Page<CarItem> pages = cart.pageManageCartItems(Integer.parseInt(pageNo),4);
request.setAttribute("pages",pages);
request.getRequestDispatcher("/views/cart/cart.jsp").forward(request,response);
}
首页家居功能
1.首页页面效果
<c:forEach items="${requestScope.get('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 Prodect -->
<div class="product">
<div class="thumb">
<a href="#" class="image">
<img src=${furn.imgPath} alt="Product"/>
<img class="hover-image" src="assets/images/product-image/5.jpg" alt="Product"/>
</a>
<span class="badges"> <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>
<form action="#" method="get">
<input name="pageNo" type="hidden" value=${requestScope.get('page').pageNo}>
<input name="action" type="hidden" value="addItem">
<input name="id" type="hidden" value="${furn.id}">
<input name="name" type="hidden" value="${furn.name}">
<input name="singlePrice" type="hidden" value="${furn.price}">
<input name="count" type="hidden" value="1">
<input name="name" type="hidden" value="${furn.name}">
<input name="singleTotalPrice" type="hidden" value="${furn.price}">
<input name="imgPath" type="hidden" value="${furn.imgPath}">
<!-- 根据存货判断是否能添加到购物车-->
<c:if test="${furn.inventory == 0}">
<button title="Add To Cart" furnInventory="0" class="add-to-cart" type="button">
Add To Cart【暂时缺货】
</button>
</c:if>
<c:if test="${furn.inventory > 0}">
<button furnId="${furn.id}" furnInventory="${furn.inventory}" title="Add To Cart" class="add-to-cart" type="button">
Add To Cart
</button>
</c:if>
</form>
</div>
<div class="content">
<h5 class="title">
<a href="shop-left-sidebar.html">${furn.name} </a></h5>
<span class="price">
<span class="new">家居: ${furn.name}</span>
</span>
<span class="price">
<span class="new">厂商: ${furn.vendor}</span>
</span>
<span class="price">
<span class="new">价格: ¥${furn.price}</span>
</span>
<span class="price">
<span class="new">销量: ${furn.sales}</span>
</span>
<span class="price">
<span class="new">库存: ${furn.inventory}</span>
</span>
</div>
</div>
</div>
</c:forEach>
protected void showPageDisplayByName(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 页号
String pageNo = request.getParameter("pageNo");
// 搜索框内容:家居名
String name = request.getParameter("name");
if(name == null){
name = "";
}
if(pageNo == null){
pageNo = "1";
}
// 分页(根据家具名来分页搜索)
Page<Furn> pages = furns.pageManageByName(Integer.parseInt(pageNo), Page.PAGE_SIZE,name);
StringBuffer url = new StringBuffer("manage/furnServlet?action=showPageDisplayByName");
if(!"".equals(name)){
url.append("&name=").append(name);
}else{
url.append("&name=").append("");
}
pages.setUrl(url.toString());
request.setAttribute("page",pages);
request.setAttribute("name",name);
request.getRequestDispatcher("/views/manage/furn_manage.jsp").forward(request,response);
}
2.点击首页商品加入购物车(Ajax请求)
/**
* 通过Ajax请求来添加商品
*/
protected void AddItemByAjax(HttpServletRequest request, HttpServletResponse response) throws InvocationTargetException, IllegalAccessException, IOException {
HttpSession session = request.getSession();
Object originalCart = session.getAttribute("cart");
Cart cart = null;
if(originalCart == null){
cart = new Cart();
}else{
cart = (Cart) originalCart;
}
// 添加的商品数据生成商品对象
int id = Integer.parseInt(request.getParameter("id"));
CarItem carItem = new CarItem();
Furn furn = furnService.queryFurnById(id);
carItem.setId(furn.getId());
carItem.setName(furn.getName());
carItem.setCount(1);
carItem.setImgPath(furn.getImgPath());
carItem.setSinglePrice(furn.getPrice());
carItem.setSingleTotalPrice(furn.getPrice());
cart.addItem(carItem);
session.setAttribute("cart",cart);
Gson gson = new Gson();
HashMap<String, Object> map = new HashMap<>();
map.put("carItemNum",cart.getCartItemsCount());
response.getWriter().write(gson.toJson(map));
}
订单管理
1.设计订单表以及细节订单
# 订单表
CREATE TABLE `order`(
`id` VARCHAR(32) NOT NULL UNIQUE, # 订单号
`create_time` DATETIME NOT NULL, # 订单日期
`price` DECIMAL(11,2) NOT NULL, # 价格
`status` VARCHAR(16) NOT NULL, # 订单状态
`member_id`INT NOT NULL # 关联用户id
)CHARSET utf8 ENGINE INNODB;
INSERT INTO `order`(`id`,`create_time`,`price`,`status`,`member_id`)
VALUES('12',NOW(),203,'未发货',3);
SELECT `id`,`create_time` AS createTime,`price`,`status`,`member_id` AS memberId FROM `order`;
SELECT *FROM `order`;
# 订单细节表(属于订单表)
CREATE TABLE `order_detail`(
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, #id
`name` VARCHAR(32) NOT NULL, # 家居名
`price` DECIMAL(11,2) NOT NULL, # 价格
`count` INT UNSIGNED NOT NULL, # 数量
`total_price` DECIMAL(11,2) NOT NULL, # 金额
`order_id` VARCHAR(32) NOT NULL # 订单号
)CHARSET utf8 ENGINE INNODB;
INSERT INTO `order_detail`(`name`,`price`,`count`,`total_price`,`order_id`) VALUES('123',123.0,12,456,"456789");
SELECT *FROM `order_detail` WHERE `order_id` = 456789;
2.Dao对数据库的操作
public interface OrderDao {
/**
* 添加订单
*/
public boolean saveOrder(Order order);
/**
* 查询所有订单
*/
public List<Order> listOrder();
}
public interface OrderItemDao {
/**
* 存订单详细信息
*/
public boolean saveOrderItem(OrderItem orderItem);
/**
* 生成对应订单的商品列表哦
*/
public List<OrderItem> listOrderItem(String orderId);
}
3.Service对业务的操作
public interface OrderService {
/**
* 保存订单数据
*
* @param cart 购物车
* @param memberId 登录用户id
* @return 订单号
*/
public String saveOrder(Cart cart,int memberId);
/**
* 订单列表
*/
public List<Order> listOrder();
/**
* 商品列表
*/
public List<OrderItem> listOrderItem(String orderId);
}
4.订单页面以及订单细节页面
订单页面
<c:forEach items="${requestScope.orders}" var="order">
<tr>
<td class="product-name">${order.id}</td>
<td class="product-name">${order.createTime}</td>
<td class="product-price-cart"><span class="amount">${order.price}</span></td>
<td class="product-name"><a href="#">${order.status}</a></td>
<td class="product-remove">
<a href="order/orderServlet?action=listOrderItem&orderId=${order.id}" ><i class="icon-eye"></i></a>
</td>
</tr>
</c:forEach>
订单细节页面
c:forEach items="${requestScope.orderItems}" var="orderItem">
<tbody>
<tr>
<td class="product-name"><a>${orderItem.name}</a></td>
<td class="product-price-cart"><span class="amount">$${orderItem.price}</span></td>
<td class="product-quantity">${orderItem.count}</td>
<td class="product-subtotal">$${orderItem.totalPrice}</td>
</tr>
</tbody>
</c:forEach>
<div class="col-lg-12">
<div class="cart-shiping-update-wrapper">
<h4>共${requestScope.totalCount}件商品 总价${requestScope.totalPrice}元</h4>
<div class="cart-clear">
<a href="#">继 续 购 物</a>
</div>
</div>
</div>
5.控制层对订单的数据操作
@WebServlet(name = "OrderServlet",urlPatterns = {"/order/orderServlet"})
public class OrderServlet extends BasicServlet {
OrderService orderService = new OrderServiceImpl();
MemberService memberServlet = new MemberServiceImpl();
FurnService furnService = new FurnServiceImpl();
/**
* 显示订单
*/
protected void listOrder(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Order> orders = orderService.listOrder();
request.setAttribute("orders",orders);
request.getRequestDispatcher("/views/order/order.jsp").forward(request,response);
}
/**
* 显示订单细节
*/
protected void listOrderItem (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String orderId = request.getParameter("orderId");
List<OrderItem> orderItems = orderService.listOrderItem(orderId);
int totalCount = 0;
BigDecimal totalPrice = new BigDecimal(0.0);
for (OrderItem orderItem : orderItems) {
totalCount += orderItem.getCount();
totalPrice = totalPrice.add(orderItem.getTotalPrice());
}
request.setAttribute("orderItems",orderItems);
request.setAttribute("totalCount",totalCount);
request.setAttribute("totalPrice",totalPrice);
request.getRequestDispatcher("/views/order/order_detail.jsp").forward(request,response);
}
/**
* 生成一个订单
*/
protected void generateOrder(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
HttpSession session = request.getSession();
Object username = session.getAttribute("username");
Object password = session.getAttribute("password");
if(username == null){
System.out.println("用户未登录!");
response.sendRedirect(getServletContext().getContextPath() + "/views/member/login.jsp");
return;
}
Object originalCart = session.getAttribute("cart");
if(originalCart == null){
System.out.println("购物车为空!");
response.sendRedirect(request.getHeader("Referer"));
return;
}
Cart cart = null;
cart = (Cart)originalCart;
// 遍历购物车的商品数量有没有超过该商品的库存
List<CarItem> itemsList = cart.getItemsList();
for (CarItem carItem : itemsList) {
if(carItem.getCount() > furnService.queryFurnById(carItem.getId()).getInventory()){
System.out.println("有商品已经超出库存限制!");
response.sendRedirect(request.getHeader("Referer"));
return;
}
}
// 这里通过用户名和密码查找用户并不是登录(Service层名字设计有问题)
Member member = memberServlet.login((String) username, (String) password);
String orderId = orderService.saveOrder(cart, member.getId());
session.setAttribute("cart",null);
session.setAttribute("orderId",orderId);
response.sendRedirect(getServletContext().getContextPath() + "/views/order/checkout.jsp");
}
}