模仿天猫实战【SSM版】——后台开发
上一篇文章鏈接:模仿天貓實戰【SSM版】——項目起步
后臺需求分析
在開始碼代碼之前,還是需要先清楚自己要做什么事情,后臺具體需要實現哪些功能:
- 注意: 訂單、用戶、訂單、推薦鏈接均不提供增刪的功能。
后臺界面設計
不像前端那樣有原型直接照搬就可以了,后臺的設計還真的有難到我...畢竟我是一個對美有一定要求的人,一方面想盡量的簡潔、簡單,另一方面又不想要太難看,那怎么辦呢?
那當然是找模板了,找到一個順眼的下載下來就開始改,
這個模板的原地址在這里:戳這里
順便安利一下 FireFox ,真是開發神器,配合著修改,棒棒噠:
經過一番折騰...
摁,就這風格了,而且我還發現右上角的【Search】框是下載的模板用 js 實現的...對于管理來說更加方便了....而且居然還實現了分頁....
一個邪惡的想法又誕生了...
一些規定
- 為了降低項目的難度,我們做了很多的精簡,現在我們作出如下的規定:
- 全站沒有商家,只有一家 Tmall ,后臺沒有驗證,可以直接進入
- 前臺的路徑就是默認路徑,后臺的路徑需要加上 “/admin” 后綴,如訪問后臺則為:localhost/admin (默認為分類管理頁)
- 管理路徑統一為:admin/listXxxxx,如分類管理路徑為:admin/listCategory,用戶管理路徑為:admin/listUser,諸如此類
- 編輯路徑統一為:admin/editXxxxx,如編輯分類路徑為:admin/editCategory,產品編輯頁為:admin/editProduct,諸如此類
- 刪除路徑統一為:admin/deleteXxxxx
- 更新路徑統一為:admin/updateXxxxx
- 關于頁面路徑的一些規定:
- 前端頁面統一在【WEB-INF/views】下,后端頁面統一在【WEB-INF/views/admin】下
分類管理
正式開始編寫我們的代碼,以 Category 為例。
編寫 Service 層
我們需要在這一層上考慮需要完成的功能,對應我們上面畫的后臺功能圖,分類管理也就是完成分類的查詢還有修改的工作:
package cn.wmyskxz.service;import cn.wmyskxz.pojo.Category;import java.util.List;public interface CategoryService {/*** 返回分類列表* @return*/List<Category> list();/*** 通過id獲取對應的數據* @param id* @return*/Category get(Integer id);/*** 更新分類* @param category* @return*/void update(Category category); }- 編寫 CategoryServiceImpl :
在同一包下編寫實現類
編寫 CategoryController
根據業務需求可以很容易的編寫出來:
package cn.wmyskxz.controller;import cn.wmyskxz.pojo.Category; import cn.wmyskxz.service.CategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping;import java.util.List;/*** Category 的控制類** @author: @我沒有三顆心臟* @create: 2018-04-27-下午 16:37*/ @Controller @RequestMapping("/admin") public class CategoryController {@AutowiredCategoryService categoryService;@RequestMapping("/listCategory")public String list(Model model) {List<Category> categories = categoryService.list();model.addAttribute("categories", categories);return "admin/listCategory";}@RequestMapping("/editCategory")public String edit(Category category,Model model) {model.addAttribute("category", category);return "admin/editCategory";}@RequestMapping("/updateCategory")public String update(Category category) {categoryService.update(category);return "redirect:listCategory";} }JSP 相關文件編寫
自己研究了一會兒這個模板,感覺還是挺好改的,然后就給改成了大概以下這個樣子(自己在數據庫中加入了 16 條數據):
- 分類管理頁
- 分類編輯頁
模板下載下來之后文件目錄是這樣的:
我們直接整個拷貝【assets】文件夾放在【webapp】目錄下,然后根據模板里面的代碼就可以開始修改了,修改下來的兩個文件源碼如下:
- listCategory.jsp
- editCategory.jsp
這樣就完成了 Category 的后臺管理模塊
其他模塊的思路跟 Category 如出一轍,就比較偏向于體力勞動了...
- 注意: 所有本類的 id 屬性均為 id ,所有外鍵的 id 都是 屬性名_id 這樣的格式,保持統一!
Example 條件查詢
MyBatis 逆向工程自動生成文件的時候自動生成了 Example 條件查詢類,我們到底應該怎么使用它呢,這里簡要的說明一下。
不得不說這個東西還挺神奇,也很方便,比如我們需要查詢 category_id 對應下的屬性表,我們可以這樣寫:
public List<Property> list(Integer category_id) {PropertyExample example = new PropertyExample();example.or().andCategory_idEqualTo(category_id);List<Property> properties = propertyMapper.selectByExample(example);return properties; }通過方法名其實也很容易看懂這些是什么意思,我們首先創建了一個 PropertyExample 實例對象,然后通過 .or() 方法開啟條件查詢,.andCategory_idEqualTo() 匹配對應的 category_id ,自動生成的 sql 語句就像這樣:
更多詳情戳這里 - 引用其他博客的詳細說明
IDEA 快速重構
當我編寫好了 PropertyService 、PropertyServiceImpl、 PropertyController 之后再想要去編寫 Product 的這一系列文件的時候,發現其實很多代碼都是重復的,只是很少一部分的代碼需要改動,暫時不考慮設計模式的話,我們可以使用 IDEA 來完成快速重構:
- 直接復制 PropertyController 的代碼到 ProductController 中,然后【Ctrl + F】搜索 Property :
我們可以發現所有的 Property 都高亮了,然后我們怎么批量修改呢?
然后繼續瘋狂碼代碼...
開發過程中遇到的一些問題
PropertyValue 遇到的麻煩
PropertyValue 屬性值表,這個表關聯了兩個外鍵,一個指向 Product ,另一個指向 Property ,當我按照之前的設計把 listProduct.jsp 設計成下面這個樣子的時候,點擊【編輯屬性】,Property 的信息應該怎么傳遞?
- 也就是說,如何處理從 listProduct 跳轉到 listPropertyValue 頁面時憑空跳出來的 Property 的相關信息?
解決方案:
在 PropertyValueServiceImpl 中增加:
@Autowired PropertyService propertyService;我們現在有 category_id 和 product_id ,我們可以利用 Property 和 Category 之間的聯系,通過 category_id 查詢出所有對應的 Property ,然后再篩選出同時匹配 property_id 和 product_id 的 PropertyValue:
public List<PropertyValue> list(Integer product_id, Integer category_id) {PropertyValueExample example = new PropertyValueExample();List<PropertyValue> propertyValues = new ArrayList<PropertyValue>();List<Property> properties = propertyService.list(category_id);for (Property property : properties) {// 篩選出同時匹配 property_id 和 product_id 的值example.or().andProperti_idEqualTo(property.getId()).andProduct_idEqualTo(product_id);propertyValues.addAll(propertyValueMapper.selectByExample(example));}return propertyValues; }emmm...這樣的思路出來之后,對應的 Controller 就清晰了:
@RequestMapping("/listPropertyValue") public String list(Model model, Integer product_id, Integer category_id) {List<PropertyValue> propertyValues = propertyValueService.list(product_id, category_id);model.addAttribute("propertyValues", propertyValues);Product product = productService.get(product_id);model.addAttribute("product", product);return "admin/listPropertyValue"; }加入一條數據測試:
- bingo!
另一個問題是添加屬性值:
添加的屬性值必須是當前 Category 下有的屬性值,所以我們可以在 Controller 上自動注入一個 PropertyService 通過 category_id 查詢到當前分類下所有的 Property 然后傳遞給 listPropertyValue :
@Autowired PropertyService propertyService;@RequestMapping("/listPropertyValue") public String list(Model model, Integer product_id, Integer category_id) {List<PropertyValue> propertyValues = propertyValueService.list(product_id, category_id);model.addAttribute("propertyValues", propertyValues);Product product = productService.get(product_id);model.addAttribute("product", product);List<Property> properties = propertyService.list(category_id);model.addAttribute("properties", properties);return "admin/listPropertyValue"; }期間發現一個 BUG,PropertyValue 表里的 property_id 居然寫成了 properti_id,嚇得我趕緊檢查了一下所有表的字段,其他的沒問題,重新生成一下逆向工程
然后獲取屬性名稱:
- 完善之后大概是這樣:
產品圖片管理
產品圖片的管理需要涉及到文件的上傳操作,我們需要先提供必要的 jar 包依賴:
- commons-fileupload
- commons-io
同樣的搜索 maven 庫添加依賴到 pom.xml中:
<!-- 上傳文件fileupload --> <dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.3</version> </dependency> <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version> </dependency>產品圖片如何管理?
- 規定一:
所有的產品圖片均保存在【img/product/】對應的 product_id 目錄下,并且默認的文件名為 1,2,3,4,5 ,例如 product_id 為 1 的產品的產品圖片 1 保存于:【img/product/1/1.jpg】 - 規定二:
每一個產品對應五張圖片,文件名分別為 1.jpg ,2.jpg 以此類推,不能少也不能多,刪除也只是將對應目錄下的圖片刪除,id 并不改變 - 規定三:
默認產品打開的大圖即為該產品圖片目錄中的 1.jpg
- 界面大概設計成了這樣:
- 莫名其妙一個 BUG:
我把表單設計成了這樣,隱藏了兩個屬性,一個 product_id,一個 id:
為了方便操作,我想要直接申明兩個參數用來接收上面的兩個屬性,大概是這樣:
但是上面兩種方法都不行,我還查了一些資料在 @RequestParam 注解里設置了 required 屬性,仍然獲取不到,但是我改成用 ProductImage 來接收就好了..Why?
后來寫著寫著,又必須要使用上面兩種方法了....
- 根據我們的規定來完成代碼
ProductImageService 層還是跟之前的沒有多大的區別,但是值得注意的是,根據我們的規定,我們的刪除需要做一些改動(根據 product_id 批量刪除):
package cn.wmyskxz.service;import cn.wmyskxz.mapper.PropertyValueMapper; import cn.wmyskxz.pojo.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service;import java.util.ArrayList; import java.util.List;/*** cn.wmyskxz.pojo.PropertyValueValueService 實現類** @author: @我沒有三顆心臟* @create: 2018-04-28-上午 7:47*/ @Service public class PropertyValueServiceImpl implements PropertyValueService {@AutowiredPropertyValueMapper propertyValueMapper;@AutowiredPropertyService propertyService;@AutowiredProductService productService;public void add(PropertyValue propertyValue) {propertyValueMapper.insert(propertyValue);}public void delete(Integer id) {propertyValueMapper.deleteByPrimaryKey(id);}public void deleteByProductId(Integer product_id) {// 按條件查詢出需要刪除的列表PropertyValueExample example = new PropertyValueExample();example.or().andProduct_idEqualTo(product_id);Integer category_id = productService.get(product_id).getCategory_id();List<PropertyValue> propertyValues = list(product_id, category_id);// 循環刪除for (int i = 0; i < propertyValues.size(); i++) {propertyValueMapper.deleteByPrimaryKey(propertyValues.get(i).getId());}}public void update(PropertyValue propertyValue) {propertyValueMapper.updateByPrimaryKey(propertyValue);}public List<PropertyValue> list(Integer product_id, Integer category_id) {PropertyValueExample example = new PropertyValueExample();List<PropertyValue> propertyValues = new ArrayList<PropertyValue>();List<Property> properties = propertyService.list(category_id);for (Property property : properties) {// 篩選出同時匹配 property_id 和 product_id 的值example.or().andProperty_idEqualTo(property.getId()).andProduct_idEqualTo(product_id);propertyValues.addAll(propertyValueMapper.selectByExample(example));}return propertyValues;}public PropertyValue get(Integer id) {return propertyValueMapper.selectByPrimaryKey(id);} }- 首先在 ProductController 中 add 和 delete 方法中增加以下代碼:
然后編寫我們的 ProductImageController :
package cn.wmyskxz.controller;import cn.wmyskxz.pojo.Product; import cn.wmyskxz.pojo.ProductImage; import cn.wmyskxz.service.ProductImageService; import cn.wmyskxz.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.multipart.MultipartFile;import javax.servlet.http.HttpServletRequest; import java.io.File; import java.util.List;/*** ProductImage 的控制器** @author: @我沒有三顆心臟* @create: 2018-04-28-下午 14:10*/ @Controller @RequestMapping("/admin") public class ProductImageController {@AutowiredProductImageService productImageService;@AutowiredProductService productService;@RequestMapping("/editProductImage")public String edit(Model model, Integer product_id) {List<ProductImage> productImages = productImageService.list(product_id);model.addAttribute("productImages", productImages);Product product = productService.get(product_id);model.addAttribute("product", product);return "admin/editProductImage";}@RequestMapping(value = "/updateProductImage", method = RequestMethod.POST)public String update(HttpServletRequest request, // @RequestParam("productImage") ProductImage productImage,Integer product_id, Integer id,@RequestParam("picture") MultipartFile picture) {// 上傳文件到指定位置String filePath = request.getSession().getServletContext().getRealPath("img/product/" + product_id);// 因為 id 是自增長鍵,所以需要 % 5 來作為文件名String fileName = (id % 5 == 0 ? 5 : id % 5) + ".jpg";File uploadPicture = new File(filePath, fileName);if (!uploadPicture.exists()) {uploadPicture.mkdirs();}// 保存try {picture.transferTo(uploadPicture);} catch (Exception e) {e.printStackTrace();}return "redirect:editProductImage?product_id=" + product_id;}@RequestMapping("/deleteProductImage")public String delete(Integer id, Integer product_id, HttpServletRequest request) {// 不刪除表中的數據(在 ProductController 中統一刪除),刪除對應文件String filePath = request.getSession().getServletContext().getRealPath("img/product/" + product_id);String fileName = id + ".jpg";new File(filePath, fileName).delete();return "redirect:editProductImage?product_id=" + product_id;} }- 再優化一下界面的東西,增加沒有圖片顯示的 error 圖片,大概就是這個樣子:
這里就只貼一下 table 的代碼吧:
<c:forEach items="${productImages}" var="pi"><tr><td>${pi.product_id}</td><td>${pi.id}</td><td><img class="col-md-8"src="../img/product/${pi.product_id}/${pi.id%5==0?5:pi.id%5}.jpg"onerror="this.src='../img/product/error.png'"></td><td class="col-md-5"><form action="updateProductImage" method="post"enctype="multipart/form-data"><input type="hidden" name="id" value="${pi.id}"><input type="hidden" name="product_id"value="${pi.product_id}"><input type="file" name="picture" class="pull-left"><input type="submit" class="btn btn-primary pull-right" value="上傳"></form></td><td><a href="deleteProductImage?product_id=${pi.product_id}&id=${pi.id}"><spanclass="glyphicon glyphicon-trash"></span></a></td></tr> </c:forEach>在寫圖片管理的時候又遇到一個坑
在刪除頂層數據庫數據的時候,要注意刪除其下的有外鍵關聯的數據,特別是 product_id 這個東西,是很多表的外鍵,刪除 product 之前需要先清空有關聯的其他表的數據....
總之坑是很多啦..不過項目在進展總歸是好事...耐心耐心...
接著碼代碼....
還剩下一些體力活的東西,就先結博文啦...(心累.jpg)
有一些催更的朋友,希望能別催啦...每天都在碼啦,而且本身也是很low的東西,寫完之后我會上傳 github 的。
總結
當我給自己埋了一個大坑說要模仿天貓,并且陷進去的時候,一方面痛苦著一方面也察覺了自己很多不足的地方,就覺得還是很值得,現在來做一下簡短的總結。
- 進度比想象中慢了很多,雖然一步一步按照之前的分析圖來編寫代碼總體是順暢的,但是有那種寫著寫著突然發現之前的設計有問題的感覺,中途也改了幾次,發現自己分析問題不夠全面。
- 項目中有許多類似的代碼,并且在 Controller 和 Impl 中不斷有其他的東西加入,總覺得是糟糕的代碼,但是又不知道應該進一步如何改進。
- 方向永遠比努力重要,在行動之前思考清楚,我一直覺得是很重要的一點,我覺得通過對項目的分析,對我項目的進展有一個整體的構思,各個模塊該有什么功能都比較清晰,特別在編寫 JSP 文件的時候能明顯感覺不會很迷茫,這是比較好的一點
- 發現自己閱讀代碼量很少,這種感覺體現在很多地方,一是寫代碼時感覺到自己思想的局限性,二是覺得自己寫的代碼有很多的相似性,雖然這個項目是自己突發奇想的想要去做的,但是有很多細節的地方,是自己沒有去注意到的,比如類型要求、邊界判斷、事務處理等等等...
歡迎轉載,轉載請注明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關注公眾微信號:wmyskxz_javaweb
分享自己的Java Web學習之路以及各種Java學習資料
轉載于:https://www.cnblogs.com/wmyskxz/p/8969842.html
總結
以上是生活随笔為你收集整理的模仿天猫实战【SSM版】——后台开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【mac/windows】Microso
- 下一篇: 利用discord创建一个自己的disc