家居网购项目--项目总结

家居网购项目--项目总结

家居网购项目总结

本项目是基于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项目对我帮助很大,在之后学习完框架之后很多的底层很多细节会被隐藏起来,不再让我们看到,所以我觉得做完这个项目一定对之后学习框架有所帮助。

热门相关:万界点名册   豪门退婚妻:宝贝,再嫁我一次!   魅王毒后   驭房之术   别吃那个鬼