家居網購項目--項目總結 家居網購項目總結 本項目是基於java的前後端項目,使用原生的Servlet + jsp 開發。 主要的技術點: 1.登錄註冊功能:使用kaptcha去生成驗證碼,使用郵件完成註冊 2.使用攔截器攔截用戶請求,限制用戶訪問許可權 3.使用ThreadLocal 確保是同一線程 ...
家居網購項目--項目總結
家居網購項目總結
本項目是基於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項目對我幫助很大,在之後學習完框架之後很多的底層很多細節會被隱藏起來,不再讓我們看到,所以我覺得做完這個項目一定對之後學習框架有所幫助。