【J2EE】模仿天貓商城(後臺篇)
之前學習了使用J2EE開發一個模仿天貓商城整站的專案,期間學習到了不少知識。但是隔了一段時間再回看程式碼,居然有點生疏了~所以寫下這篇部落格,方便日後回顧,溫故而知新,也可以和大家交流學習。
本篇介紹專案的後臺管理開發,模組主要分為:分類管理,使用者管理,訂單管理,分類下的產品管理,分類屬性管理,產品屬性管理和產品圖片管理等等。。。
涉及到的知識
前端:html+css+JavaScript+jQuery+Bootstrap
後端:j2ee
資料庫:mysql
資料庫表關係圖
模組開發
這裡使用的是MVC開發模式,jsp作為顯示的層面,servlet充當控制層,bean和dao作為模型
如果一個功能對應一個servlet,那麼一個專案裡面就有很多很多的servlet,web.xml也要配置很多次。這裡的解決方案是使用Filter結合servlet。假設訪問路徑是 http://127.0.0.1:8080/tmall/admin_category_list,首先設定一個過濾器BackServletFilter,對所有請求進行攔截,判斷訪問的地址是否以/admin_開頭,如果是,那麼做如下操作
1.取出兩個下劃線之間的值 category
2.取出最後一個下劃線之後的值 list
3. 然後根據這個值,服務端跳轉到categoryServlet,並且把list這個值傳遞過去
package tmall.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; public class BackServletFilter implements Filter { public void destroy() { } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; String contextPath=request.getServletContext().getContextPath(); String uri = request.getRequestURI(); uri =StringUtils.remove(uri, contextPath); if(uri.startsWith("/admin_")){ String servletPath = StringUtils.substringBetween(uri,"_", "_") + "Servlet"; String method = StringUtils.substringAfterLast(uri,"_" ); request.setAttribute("method", method); req.getRequestDispatcher("/" + servletPath).forward(request, response); return; } chain.doFilter(request, response); } public void init(FilterConfig arg0) throws ServletException { } }
緊接著,定義一個BaseBackServlet進行抽取,原理是利用反射技術。讓categoryServlet 繼承BaseBackServlet,重寫BaseBackServlet中的service函式。在呼叫categoryServlet中的dopost或者doget方法前,BaseBackServlet會被呼叫,根據filter傳遞過來method的值,藉助反射,呼叫categoryServlet中對用的方法,最後根據servlet中返回的欄位,選擇進行伺服器端的跳轉或者是客戶端的跳轉。
package tmall.servlet; import java.io.InputStream; import java.lang.reflect.Method; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import tmall.dao.CategoryDAO; import tmall.dao.OrderDAO; import tmall.dao.OrderItemDAO; import tmall.dao.ProductDAO; import tmall.dao.ProductImageDAO; import tmall.dao.PropertyDAO; import tmall.dao.PropertyValueDAO; import tmall.dao.ReviewDAO; import tmall.dao.UserDAO; import tmall.util.Page; public abstract class BaseBackServlet extends HttpServlet { public abstract String add(HttpServletRequest request, HttpServletResponse response, Page page) ; public abstract String delete(HttpServletRequest request, HttpServletResponse response, Page page) ; public abstract String edit(HttpServletRequest request, HttpServletResponse response, Page page) ; public abstract String update(HttpServletRequest request, HttpServletResponse response, Page page) ; public abstract String list(HttpServletRequest request, HttpServletResponse response, Page page) ; protected CategoryDAO categoryDAO = new CategoryDAO(); protected OrderDAO orderDAO = new OrderDAO(); protected OrderItemDAO orderItemDAO = new OrderItemDAO(); protected ProductDAO productDAO = new ProductDAO(); protected ProductImageDAO productImageDAO = new ProductImageDAO(); protected PropertyDAO propertyDAO = new PropertyDAO(); protected PropertyValueDAO propertyValueDAO = new PropertyValueDAO(); protected ReviewDAO reviewDAO = new ReviewDAO(); protected UserDAO userDAO = new UserDAO(); public void service(HttpServletRequest request, HttpServletResponse response) { try { /*獲取分頁資訊*/ int start= 0; int count = 5; try { start = Integer.parseInt(request.getParameter("page.start")); } catch (Exception e) { } try { count = Integer.parseInt(request.getParameter("page.count")); } catch (Exception e) { } Page page = new Page(start,count); /*藉助反射,呼叫對應的方法*/ String method = (String) request.getAttribute("method"); Method m = this.getClass().getMethod(method, javax.servlet.http.HttpServletRequest.class, javax.servlet.http.HttpServletResponse.class,Page.class); String redirect = m.invoke(this,request, response,page).toString(); /*根據方法的返回值,進行相應的客戶端跳轉,服務端跳轉,或者僅僅是輸出字串*/ if(redirect.startsWith("@")) response.sendRedirect(redirect.substring(1)); else if(redirect.startsWith("%")) response.getWriter().print(redirect.substring(1)); else request.getRequestDispatcher(redirect).forward(request, response); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); throw new RuntimeException(e); } }
1.分類管理
分類管理是後臺開發的起始,是產品管理的父級,這裡實現了分類管理增刪查改的基本操作,同時提供分頁查詢,分類屬性的增刪查改,分類下產品管理入口(在跳轉時傳遞分類id查詢分類下的產品)。下面是分類下分頁查詢程式碼,分頁查詢在其他功能開發中都會用到,在page中設定start和count作為引數傳入categoryDao中,查詢出條件下的分類集合,用request傳到jsp頁面中去
public String list(HttpServletRequest request, HttpServletResponse response, Page page) {
List<Category> cs = categoryDAO.list(page.getStart(),page.getCount());
int total = categoryDAO.getTotal();
page.setTotal(total);
request.setAttribute("thecs", cs);
request.setAttribute("page", page);
return "admin/listCategory.jsp";
}
2.分類屬性管理
每個分類都有對應屬性,例如電視機,會有產地,尺寸,顏色等屬性,所以我們得對它的這些屬性進行管理。例如做一個增加操作,提交資料是在istProperty.jsp頁面中的,除了提交屬性名稱,還會提交cid,在PropertyServlet中根據獲取到的cid,name引數,建立新的Property物件,並插入到資料庫,客戶端跳轉到admin_property_list,並帶上引數cid。
listProperty.jsp程式碼片段
<form method="post" id="addForm" action="admin_property_add">
<table class="addTable">
<tr>
<td>屬性名稱</td>
<td><input id="name" name="name" type="text"
class="form-control"></td>
</tr>
<tr class="submitTR">
<td colspan="2" align="center">
<input type="hidden" name="cid" value="${c.id}">
<button type="submit" class="btn btn-success">提 交</button>
</td>
</tr>
</table>
</form>
propertyServlet程式碼片段
public String add(HttpServletRequest request, HttpServletResponse response, Page page) {
int cid = Integer.parseInt(request.getParameter("cid"));
Category c = categoryDAO.get(cid);
String name= request.getParameter("name");
Property p = new Property();
p.setCategory(c);
p.setName(name);
propertyDAO.add(p);
return "@admin_property_list?cid="+cid;
}
3.產品管理
產品管理這裡也是關於增刪查改的基本操作,有不同的是page下必須多夾帶一個引數,這個引數就是產品父級-分類的id,在產品的分頁查詢需要用到這個引數,查詢訪問的是ProductServlet的list()方法,首先獲取分類 cid,基於cid,獲取當前分類下的產品集合, 獲取當前分類下的產品總數,並且設定給分頁page物件,拼接字串"&cid="+c.getId(),設定給page物件的Param值。 因為產品分頁都是基於當前分類下的分頁,所以分頁的時候需要傳遞這個cid,封裝好資料,服務端跳轉到admin/listProduct.jsp頁面, 在listProduct.jsp頁面上使用c:forEach 遍歷ps集合,並顯示
public String list(HttpServletRequest request, HttpServletResponse response, Page page) {
int cid = Integer.parseInt(request.getParameter("cid"));
Category c = categoryDAO.get(cid);
List<Product> ps = productDAO.list(cid, page.getStart(),page.getCount());
int total = productDAO.getTotal(cid);
page.setTotal(total);
page.setParam("&cid="+c.getId());
request.setAttribute("ps", ps);
request.setAttribute("c", c);
request.setAttribute("page", page);
return "admin/listProduct.jsp";
}
listProduct.jsp程式碼片段
<c:forEach items="${ps}" var="p">
<tr>
<td>${p.id}</td>
<td>
<c:if test="${!empty p.firstProductImage}">
<img width="40px" src="img/productSingle/${p.firstProductImage.id}.jpg">
</c:if>
</td>
<td>${p.name}</td>
<td>${p.subTitle}</td>
<td>${p.orignalPrice}</td>
<td>${p.promotePrice}</td>
<td>${p.stock}</td>
<td><a href="admin_productImage_list?pid=${p.id}"><span
class="glyphicon glyphicon-picture"></span></a></td>
<td><a href="admin_product_editPropertyValue?id=${p.id}"><span
class="glyphicon glyphicon-th-list"></span></a></td>
<td><a href="admin_product_edit?id=${p.id}"><span
class="glyphicon glyphicon-edit"></span></a></td>
<td><a deleteLink="true"
href="admin_product_delete?id=${p.id}"><span
class=" glyphicon glyphicon-trash"></span></a></td>
</tr>
</c:forEach>
4.產品圖片管理
每個產品的圖片有分為單張圖片和詳細資訊圖片,單張圖片是為了展示產品外觀,而詳細資訊圖片是為了展示產品詳細資訊。parseUpload 獲取上傳檔案的輸入流,parseUpload 方法會修改params 引數,並且把瀏覽器提交的type,pid資訊放在其中,從params 中取出type,pid資訊,並根據這個type,pid,藉助productImageDAO,向資料庫中插入資料,根據request.getSession().getServletContext().getRealPath( "img/productSingle"),定位到存放分類圖片的目錄,除了productSingle,還有productSingle_middle和productSingle_small。 因為每上傳一張圖片,都會有對應的正常,中等和小的三種大小圖片,並且放在3個不同的目錄下,檔案命名以儲存到資料庫的分類物件的id+".jpg"的格式命名,根據步驟1獲取的輸入流,把瀏覽器提交的檔案,複製到目標檔案,再借助ImageUtil.resizeImage把正常大小的圖片,改變大小之後,分別複製到productSingle_middle和productSingle_small目錄下, 處理完畢之後,客戶端條跳轉到admin_productImage_list?pid=,並帶上pid。
productImageServlet程式碼片段
public String add(HttpServletRequest request, HttpServletResponse response, Page page) {
//上傳檔案的輸入流
InputStream is = null;
//提交上傳檔案時的其他引數
Map<String,String> params = new HashMap<>();
//解析上傳
is = parseUpload(request, params);
//根據上傳的引數生成productImage物件
String type= params.get("type");
int pid = Integer.parseInt(params.get("pid"));
Product p =productDAO.get(pid);
ProductImage pi = new ProductImage();
pi.setType(type);
pi.setProduct(p);
productImageDAO.add(pi);
//生成檔案
String fileName = pi.getId()+ ".jpg";
String imageFolder;
String imageFolder_small=null;
String imageFolder_middle=null;
if(ProductImageDAO.type_single.equals(pi.getType())){
imageFolder= request.getSession().getServletContext().getRealPath("img/productSingle");
imageFolder_small= request.getSession().getServletContext().getRealPath("img/productSingle_small");
imageFolder_middle= request.getSession().getServletContext().getRealPath("img/productSingle_middle");
}
else
imageFolder= request.getSession().getServletContext().getRealPath("img/productDetail");
File f = new File(imageFolder, fileName);
f.getParentFile().mkdirs();
// 複製檔案
try {
if(null!=is && 0!=is.available()){
try(FileOutputStream fos = new FileOutputStream(f)){
byte b[] = new byte[1024 * 1024];
int length = 0;
while (-1 != (length = is.read(b))) {
fos.write(b, 0, length);
}
fos.flush();
//通過如下程式碼,把檔案儲存為jpg格式
BufferedImage img = ImageUtil.change2jpg(f);
ImageIO.write(img, "jpg", f);
if(ProductImageDAO.type_single.equals(pi.getType())){
File f_small = new File(imageFolder_small, fileName);
File f_middle = new File(imageFolder_middle, fileName);
ImageUtil.resizeImage(f, 56, 56, f_small);
ImageUtil.resizeImage(f, 217, 190, f_middle);
}
}
catch(Exception e){
e.printStackTrace();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "@admin_productImage_list?pid="+p.getId();
}
5.產品屬性管理
每個產品都有各自的屬性,比如電視機的顏色是紅色。所以對產品屬性的管理也無非是增刪查改,這裡用修改舉例,修改時候使用了post提交ajax非同步處理。使用監聽輸入框上的keyup事件,獲取輸入框裡的,還有定義的pvid,藉助JQuery的ajax函式 $.post,把id和值,提交到admin_product_updatePropertyValue,admin_product_updatePropertyValue導致ProductServlet的updatePropertyValue方法被呼叫, BaseBackServlet根據返回值"%success",直接輸出字串"success" 到瀏覽器, 瀏覽器判斷如果返回值是"success",那麼就把邊框設定為綠色,表示修改成功,否則設定為紅色,表示修改失敗。
editProperty.jsp程式碼片段
$("input.pvValue").keyup(function(){
var value = $(this).val();
var page = "admin_product_updatePropertyValue";
var pvid = $(this).attr("pvid");
var parentSpan = $(this).parent("span");
parentSpan.css("border","1px solid yellow");
$.post(
page,
{"value":value,"pvid":pvid},
function(result){
if("success"==result)
parentSpan.css("border","1px solid green");
else
parentSpan.css("border","1px solid red");
}
);
});
6.使用者管理
這裡就只是從資料庫取出來,分頁顯示~
7.訂單管理
後臺訂單管理只提供發貨和查詢的方法,下單和取消訂單是由前臺使用者自己進行的。當訂單狀態是waitDelivery的時候,就會出現發貨按鈕,發貨按鈕連結跳轉到admin_order_delivery,OrderServlet.delivery()方法被呼叫,獲取物件id,修改發貨時間,設定發貨狀態,更新資料庫,跳轉,完成了
orderServlet片段
public String delivery(HttpServletRequest request, HttpServletResponse response, Page page) {
int id = Integer.parseInt(request.getParameter("id"));
Order o = orderDAO.get(id);
o.setDeliveryDate(new Date());
o.setStatus(OrderDAO.waitConfirm);
orderDAO.update(o);
return "@admin_order_list";
}
總結下來,j2ee開發增刪查改第一步先是獲取到傳遞過來的資料,在根據實際業務對資料進行操作,到這裡後臺部分就寫完了