家居网购项目--项目总结
家居网购项目--项目总结
家居网购项目总结
本项目是基于java的前后端项目,使用原生的Servlet + jsp 开发。
主要的技术点:
1.登录注册功能:使用kaptcha去生成验证码,使用邮件完成注册
2.使用拦截器拦截用户请求,限制用户访问权限
3.使用ThreadLocal 确保是同一线程来完成事务的提交和回滚
4.使用MVC模式使用DAO-Serice-Servlet进行分层
5.使用数据模型entity Cart.java 将数据存入session中来进行购物车的存储
6.使用ajax对页面进行局部刷新
7.使用json格式进行前后端数据的交换
8.前端使用jsp进行服务器渲染
9.数据库使用Mysql
项目准备
1.一些工作环境的配置,IDE使用IntellijIDEA,jdk1.8,下面介绍一些关于此项目的主要模块后端代码的实现部分
2.搭建项目的框架
注册与登录功能的实现
注册和登录功能是每个网站最基本的功能,实现的主要难点我觉得在于正则表达式的编写。
//完成对用户名的校验
var usernamePattern = /^\w{6,10}$/;
//验证邮箱
var emailVal = $("#email").val();
var emailPattern = /^[\w-]+@([a-zA-Z]+\.)+[a-zA-Z]+$/
if (!emailPattern.test(emailVal)) {
$("span[class= 'errorMsg']").text("电子邮箱格式不正确");
return false;
}
//...
用户表实现
密码MD5加密
为了保证安全,密码不能明文的在网络中进行传输,也不能以明文的形式存到数据库中。
存在数据库的密码 = MD5( 密码 ) 防止密码泄露
Kaptcha 生成验证码
导入 Kaptcha.jar 后,服务器会生成的验证码并保存在session中,我们需要通过 com.google.code.kaptcha下的 Constants.java类 的 属性 拿到对应的验证码
public static final String KAPTCHA_SESSION_KEY = "KAPTCHA_SESSION_KEY";//静态属性
//获取验证码
String token = (String) request.getSession().getAttribute(KAPTCHA_SESSION_KEY);
并且为了防止验证码被重复使用需要立即删除验证码
//立即删除验证码防止验证码被重复使用
request.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
过滤器的使用
本项目有两个过滤器,分别是:
1.用于用户权限验证的 AuthFilter.java
2.用于配合JDBC提交和回滚事务的 TransactionFilter.java
使用过滤器需要在web.xml文件中配置相关参数,并且过滤器一般配置的优先级较高(高于Servlet的配置)
<!--AuthFilter 过滤器-->
<filter>
<filter-name>AuthFilter</filter-name>
<filter-class>com.code_study.furns.filter.AuthFilter</filter-class>
<init-param>
<!--3. 对于要拦截的目录的某些要放行的资源,通过配置参数指定-->
<param-name>excludedUrls</param-name>
<param-value>/views/manage/manage_login.jsp,/views/member/login.jsp</param-value>
</init-param>
<init-param>
<!--3. 拦截的url-->
<param-name>interceptUrls</param-name>
<param-value>
/views/cart/*
,/views/manage/*
,/views/member/*
,/views/order/*
,/cartServlet
,/manage/furnServlet
,/orderServlet
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<!--配置要拦截的url-->
<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>
</filter-mapping>
使用拦截器AuthFilter来拦截所有的用户请求,判断请求中的session是否存在有效的member或者admin,如果都没有就请求转发到登陆页面,如果有,根据member或者admin对应权限判断 放行的资源是否包含 请求的url。如果不包含配置的放行url 就走验证
<param-name>excludedUrls</param-name>
<param-value>/views/manage/manage_login.jsp,/views/member/login.jsp</param-value>
Servlet合并
将Servlet合并成一个BasicServlet——利用了模板设计模式+动态绑定+反射
//解决接收到的数据中文乱码问题
request.setCharacterEncoding("utf-8");
//获取action不同的值
String action = request.getParameter("action");
try {
//反射获取servlet对应的方法的对象
Method declaredMethod = this.getClass().getDeclaredMethod(action, HttpServletRequest.class, HttpServletResponse.class);
declaredMethod.invoke(this, request, response);
} catch (Exception e) {
//将发生的异常继续throw
throw new RuntimeException(e);
}
分页显示
将分页显示抽象成一个数据模型entity Page.java
//每页显示多少条记录其他地方也可以使用
public static final Integer PAGE_SIZE = 4;//表示 每页显示几条记录
private Integer pageNo;//表示 第几页
private Integer totalRow;//表示 多少条记录
private Integer pageTotalCount;//表示 共有多少页 -> totalRow / pageSize
private List<T> items;//当前页显示的数据
private String url;//分页导航的字符串
private Integer pageSize = PAGE_SIZE;//每页显示几条记录
进行分页的方法page
public Page<Furn> page(int pageNo, int pageSize) {
Page<Furn> page = new Page<>();
page.setPageNo(pageNo);
page.setPageSize(pageSize);
int totalRow = furnDAO.getTotalRow();
page.setTotalRow(totalRow);
//pageTotalCount 计算得到
int pageTotalCount = totalRow / pageSize;
if (totalRow % pageSize > 0) {
pageTotalCount += 1;
}
page.setPageTotalCount(pageTotalCount);
//begin计算得到 ->int pageNo, int pageSize
int begin = (pageNo - 1) * pageSize;
List<Furn> pageItems = furnDAO.getPageItems(begin, pageSize);
page.setItems(pageItems);
String url = "/manage/furnServlet?action=page&pageNo=" + pageNo;
page.setUrl(url);
return page;
}
从而在前端可以进行分页导航栏的展示
<li><a href="customerFurnServlet?action=pageByName&pageNo=1">首页</a></li>
<c:if test="${requestScope.page.pageNo > 1}">
<li><a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo-1}">上一页</a></li>
</c:if>
<c:choose>
<c:when test="${requestScope.page.pageTotalCount<=5}">
<c:set var="begin" value="1"/>
<c:set var="end" value="${requestScope.page.pageTotalCount}"/>
</c:when>
<c:when test="${requestScope.page.pageTotalCount > 5}">
<c:choose>
<c:when test="${requestScope.page.pageNo<=3}">
<c:set var="begin" value="1"/>
<c:set var="end" value="5"/>
</c:when>
<c:when test="${requestScope.page.pageNo>requestScope.page.pageTotalCount-3}">
<c:set var="begin" value="${requestScope.page.pageTotalCount - 4}"/>
<c:set var="end" value="${requestScope.page.pageTotalCount}"/>
</c:when>
<c:otherwise>
<c:set var="begin" value="${requestScope.page.pageNo - 2}"/>
<c:set var="end" value="${requestScope.page.pageNo +2 }"/>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
<c:forEach begin="${begin}" end="${end}" var="i">
<c:if test="${i == requestScope.page.pageNo}">
<li><a class="active" href="${requestScope.page.url}&pageNo=${i}">${i}</a></li>
</c:if>
<c:if test="${i != requestScope.page.pageNo}">
<li><a href="${requestScope.page.url}&pageNo=${i}">${i}</a></li>
</c:if>
</c:forEach>
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotalCount}">
<li><a href="${requestScope.page.url}&pageNo=${requestScope.page.pageNo+1}">下一页</a></li>
</c:if>
<li><a href="customerFurnServlet?action=pageByName&pageNo=${requestScope.page.pageTotalCount}">末页</a></li>
<li><a>共${requestScope.get("page").pageTotalCount}页</a></li>
<li><a>共${requestScope.get("page").totalRow}条</a></li>
</ul>
搜索功能
在首页我们需要根据用户提供的家居名称进行查询相关家居信息
使用Mysql中的模糊查询
public int getTotalRowByName(String name) {
String sql = "SELECT COUNT(*) FROM furn WHERE`name` LIKE ?";
return ((Number)queryScalar(sql,"%"+name+"%")).intValue();//模糊查询
}
使用Ajax局部刷新
ajax实现异步请求有三种常用方法1) $.ajax 2)$.get和 $.post 3)$.getJson
这里满足发送的请求方式是get请求因此使用方便的$.getJson
$("button.add-to-cart").click(function () {
//获取到点击的furn.id
var furnId = $(this).attr("furnId");
//发出一个请求添加家居
$.getJSON(
"cartServlet", {
"action": "addItemByAjax",
"id": furnId
},
function (data) {
if (data.url == undefined) {//没有返回url 已经登录过
//局部刷新
$("span.header-action-num").text(data.cartTotalCount);
} else {
// 说明当前服务器返回url 要求定位
location.href = data.url;
}
}
)
})
文件上传
文件上传是 前端的form 表单里的enctype 需要修改成multipart/form-data 提交的才可以是文件和文本
为了让文件可以按照年月日分类进行存放 我们可以提供一个工具方法
public static String getYearMonthDay() {
int year = LocalDateTime.now().getYear();
int month = LocalDateTime.now().getMonthValue();
int day = LocalDateTime.now().getDayOfMonth();
return year + "/" + month + "/" + day;
}
并且在创建目录时拼接到 fileRealPath中
File fileRealPathDirectory = new File(fileRealPath + "/" + yearMonthDay);
需要更新图片的路径
//更新图片路径
furn.setImgPath(WebUtils.FURN_IMG_DIRECTORY + "/" +yearMonthDay+ "/"+ name);
以下是部分代码
//1. 判断是不是文件表单(enctype="multipart/form-data")
if (ServletFileUpload.isMultipartContent(request)) {
//2. 创建 DiskFileItemFactory 对象, 用于构建一个解析上传数据的工具对象
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
//3. 创建一个解析上传数据的工具对象
ServletFileUpload servletFileUpload =
new ServletFileUpload(diskFileItemFactory);
//解决接收到文件名是中文乱码问题
servletFileUpload.setHeaderEncoding("utf-8");
try {
List<FileItem> list = servletFileUpload.parseRequest(request);
for (FileItem fileItem : list) {
if (fileItem.isFormField()) {//如果是true就是文本 input text
if ("name".equals(fileItem.getFieldName())) {
furn.setName(fileItem.getString("utf-8"));
} else if ("maker".equals(fileItem.getFieldName())) {
furn.setMaker(fileItem.getString("utf-8"));
} else if ("price".equals(fileItem.getFieldName())) {
furn.setPrice(new BigDecimal(fileItem.getString()));
} else if ("sales".equals(fileItem.getFieldName())) {
furn.setSales(new Integer(fileItem.getString()));
} else if ("stock".equals(fileItem.getFieldName())) {
furn.setStock(new Integer(fileItem.getString()));
}
} else {
//用一个方法
//获取上传的文件的名字
String name = fileItem.getName();
System.out.println("上传的文件名=" + name);
if (!"".equals(name)) {
//把这个上传到 服务器的 temp下的文件保存到你指定的目录
//1.指定一个目录 , 就是我们网站工作目录下
String filePath = "/" + WebUtils.FURN_IMG_DIRECTORY ;
//
//2. 获取到完整目录
String fileRealPath =
request.getServletContext().getRealPath(filePath);
//3. 创建这个上传的目录=> 创建目录
String yearMonthDay = WebUtils.getYearMonthDay();
File fileRealPathDirectory = new File(fileRealPath + "/" + yearMonthDay);
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();
//5. 提示信息
response.setContentType("text/html;charset=utf-8");
response.getWriter().write("上传成功~");
//todo
//删除原先的图片
//1.获取原先的图片路径
String imgPath = furnService.queryFurnById(id).getImgPath();
//2.判断是否存在
String path = "out/artifacts/jiaju_mall_war_exploded/"+imgPath;
File file = new File(path);
if (file.exists()){
file.delete();
System.out.println("图片删除成功");
}else{
System.out.println("图片并不存在,无需删除");
}
path = "web/"+imgPath;
System.out.println("path= "+path);
file = new File(path);
if (file.exists()){
file.delete();
System.out.println("图片删除成功");
}else{
System.out.println("图片并不存在,无需删除");
}
//更新图片路径
furn.setImgPath(WebUtils.FURN_IMG_DIRECTORY + "/" +yearMonthDay+ "/"+ name);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
System.out.println("不是文件表单...");
}
//更新furn
furnService.updateFurnById(furn);
request.getRequestDispatcher("/views/manage/update_ok.jsp").forward(request, response);
}
结语
以上只是对本项目的相对比较重要的功能进行了总结,当然了这个项目还有一些其他的功能,比如订单生成/管理,购物车的生成/管理,统一错误404,500的页面显示等。总的来说,原生的Web项目对我帮助很大,在之后学习完框架之后很多的底层很多细节会被隐藏起来,不再让我们看到,所以我觉得做完这个项目一定对之后学习框架有所帮助。
热门相关:万界点名册 豪门退婚妻:宝贝,再嫁我一次! 魅王毒后 驭房之术 别吃那个鬼