上一篇文章連接:模仿天貓實戰【SSM版】——項目起步css
在開始碼代碼以前,仍是須要先清楚本身要作什麼事情,後臺具體須要實現哪些功能:html
不像前端那樣有原型直接照搬就能夠了,後臺的設計還真的有難到我...畢竟我是一個對美有必定要求的人,一方面想盡可能的簡潔、簡單,另外一方面又不想要太難看,那怎麼辦呢?前端
那固然是找模板了,找到一個順眼的下載下來就開始改,java
這個模板的原地址在這裏:戳這裏jquery
順便安利一下 FireFox ,真是開發神器,配合着修改,棒棒噠:git
摁,就這風格了,並且我還發現右上角的【Search】框是下載的模板用 js 實現的...對於管理來講更加方便了....並且竟然還實現了分頁....github
一個邪惡的想法又誕生了...web
- 爲了下降項目的難度,咱們作了不少的精簡,如今咱們做出以下的規定:
- 關於頁面路徑的一些規定:
正式開始編寫咱們的代碼,以 Category 爲例。spring
咱們須要在這一層上考慮須要完成的功能,對應咱們上面畫的後臺功能圖,分類管理也就是完成分類的查詢還有修改的工做:sql
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); }
package cn.wmyskxz.service; import cn.wmyskxz.mapper.CategoryMapper; import cn.wmyskxz.pojo.Category; import cn.wmyskxz.pojo.CategoryExample; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * CategoryService 的實現類 * * @author: @我沒有三顆心臟 * @create: 2018-04-27-下午 16:35 */ @Service public class CategoryServiceImpl implements CategoryService { @Autowired CategoryMapper categoryMapper; public List<Category> list() { CategoryExample example = new CategoryExample(); List<Category> categories = categoryMapper.selectByExample(example); return categories; } public Category get(Integer id) { return categoryMapper.selectByPrimaryKey(id); } public void update(Category category) { categoryMapper.updateByPrimaryKey(category); } }
根據業務需求能夠很容易的編寫出來:
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 { @Autowired CategoryService 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"; } }
本身研究了一下子這個模板,感受仍是挺好改的,而後就給改爲了大概如下這個樣子(本身在數據庫中加入了 16 條數據):
模板下載下來以後文件目錄是這樣的:
咱們直接整個拷貝【assets】文件夾放在【webapp】目錄下,而後根據模板裏面的代碼就能夠開始修改了,修改下來的兩個文件源碼以下:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>模仿天貓-後臺</title> <!-- Bootstrap Styles--> <link href="../assets/css/bootstrap.css" rel="stylesheet" /> <!-- FontAwesome Styles--> <link href="../assets/css/font-awesome.css" rel="stylesheet" /> <!-- Morris Chart Styles--> <!-- Custom Styles--> <link href="../assets/css/custom-styles.css" rel="stylesheet" /> <!-- Google Fonts--> <link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css' /> <!-- TABLE STYLES--> <link href="../assets/js/dataTables/dataTables.bootstrap.css" rel="stylesheet" /> </head> <body> <div id="wrapper"> <nav class="navbar navbar-default top-navbar" role="navigation"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="listCategory">Tmall</a> </div> </nav> <!--/. NAV TOP --> <nav class="navbar-default navbar-side" role="navigation"> <div class="sidebar-collapse"> <ul class="nav" id="main-menu"> <li> <a class="active-menu" href="listCategory"><i class="fa fa-bars"></i> 分類管理</a> </li> <li> <a href="listUser"><i class="fa fa-user"></i> 用戶管理</a> </li> <li> <a href="listOrder"><i class="fa fa-list-alt"></i> 訂單管理</a> </li> <li> <a href="listProduct"><i class="fa fa-th-list"></i> 產品管理</a> </li> <li> <a href="listLink"><i class="fa fa-link"></i> 推薦連接管理</a> </li> </ul> </div> </nav> <!-- /. NAV SIDE --> <div id="page-wrapper"> <div id="page-inner"> <div class="row"> <div class="col-md-12"> <h1 class="page-header"> 分類管理 <small></small> </h1> </div> </div> <div class="row"> <div class="col-md-12"> <!-- Advanced Tables --> <div class="panel panel-default"> <div class="panel-heading"> 分類管理表 </div> <div class="panel-body"> <div class="table-responsive"> <table class="table table-striped table-bordered table-hover" id="dataTables-example"> <thead> <tr> <th>分類id</th> <th>分類名稱</th> <th>編輯分類</th> <th>產品管理</th> <th>屬性管理</th> </tr> </thead> <tbody> <c:forEach items="${categories}" var="c"> <tr> <td>${c.id}</td> <td>${c.name}</td> <td><a href="editCategory?id=${c.id}&name=${c.name}"><span class="glyphicon glyphicon-th-list"></span></a></td> <td><a href="listProduct?category_id=${c.id}"><span class="glyphicon glyphicon-shopping-cart"></span></a></td> <td><a href="listProperty?category_id=${c.id}"><span class="glyphicon glyphicon-edit"></span></a></td> </tr> </c:forEach> </tbody> </table> </div> </div> </div> <!--End Advanced Tables --> </div> </div> </div> </div> <!-- /. PAGE WRAPPER --> </div> <!-- /. WRAPPER --> <!-- JS Scripts--> <!-- jQuery Js --> <script src="../assets/js/jquery-1.10.2.js"></script> <!-- Bootstrap Js --> <script src="../assets/js/bootstrap.min.js"></script> <!-- Metis Menu Js --> <script src="../assets/js/jquery.metisMenu.js"></script> <!-- DATA TABLE SCRIPTS --> <script src="../assets/js/dataTables/jquery.dataTables.js"></script> <script src="../assets/js/dataTables/dataTables.bootstrap.js"></script> <script> $(document).ready(function () { $('#dataTables-example').dataTable(); }); </script> <!-- Custom Js --> <script src="../assets/js/custom-scripts.js"></script> </body> </html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <html> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <title>模仿天貓-後臺</title> <!-- Bootstrap Styles--> <link href="../assets/css/bootstrap.css" rel="stylesheet"/> <!-- FontAwesome Styles--> <link href="../assets/css/font-awesome.css" rel="stylesheet"/> <!-- Morris Chart Styles--> <!-- Custom Styles--> <link href="../assets/css/custom-styles.css" rel="stylesheet"/> <!-- Google Fonts--> <link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'/> </head> <body> <div id="wrapper"> <nav class="navbar navbar-default top-navbar" role="navigation"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="listCategory">Tmall</a> </div> </nav> <!--/. NAV TOP --> <nav class="navbar-default navbar-side" role="navigation"> <div class="sidebar-collapse"> <ul class="nav" id="main-menu"> <li> <a class="active-menu" href="listCategory"><i class="fa fa-bars"></i> 分類管理</a> </li> <li> <a href="listUser"><i class="fa fa-user"></i> 用戶管理</a> </li> <li> <a href="listOrder"><i class="fa fa-list-alt"></i> 訂單管理</a> </li> <li> <a href="listProduct"><i class="fa fa-th-list"></i> 產品管理</a> </li> <li> <a href="listLink"><i class="fa fa-link"></i> 推薦連接管理</a> </li> </ul> </div> </nav> <!-- /. NAV SIDE --> <div id="page-wrapper"> <div id="page-inner"> <div class="row"> <div class="col-md-12"> <h1 class="page-header"> 分類管理 <small> - id:${category.id} </small> </h1> </div> </div> <div class="row"> <div class="col-md-6"> <!-- Advanced Tables --> <div class="panel panel-default"> <div class="panel-heading"> 編輯分類 </div> <div class="panel-body"> <div class="row col-lg-12"> <form action="updateCategory" role="form"> <div class="form-group"> <%-- 隱藏id屬性,一併提交 --%> <input type="hidden" name="id" value="${category.id}"> <label>分類名稱:</label> <input name="name" class="form-control" value="${category.name}"> <br/> <div class="pull-right"> <input type="submit" class="btn btn-default"> </div> </div> </form> </div> </div> </div> <!--End Advanced Tables --> </div> </div> </div> </div> <!-- /. PAGE WRAPPER --> </div> <!-- /. WRAPPER --> <!-- JS Scripts--> <!-- jQuery Js --> <script src="../assets/js/jquery-1.10.2.js"></script> <!-- Bootstrap Js --> <script src="../assets/js/bootstrap.min.js"></script> </body> </html>
其餘模塊的思路跟 Category 一模一樣,就比較偏向於體力勞動了...
id
,全部外鍵的 id 都是 屬性名_id
這樣的格式,保持統一!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 語句就像這樣:
當我編寫好了 PropertyService 、PropertyServiceImpl、 PropertyController 以後再想要去編寫 Product 的這一系列文件的時候,發現其實不少代碼都是重複的,只是不多一部分的代碼須要改動,暫時不考慮設計模式的話,咱們可使用 IDEA 來完成快速重構:
咱們能夠發現全部的 Property 都高亮了,而後咱們怎麼批量修改呢?
而後繼續瘋狂碼代碼...
PropertyValue 屬性值表,這個表關聯了兩個外鍵,一個指向 Product ,另外一個指向 Property ,當我按照以前的設計把 listProduct.jsp 設計成下面這個樣子的時候,點擊【編輯屬性】,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"; }
加入一條數據測試:
另外一個問題是添加屬性值:
添加的屬性值必須是當前 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 包依賴:
一樣的搜索 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>
產品圖片如何管理?
- 界面大概設計成了這樣:
- 莫名其妙一個 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 { @Autowired PropertyValueMapper propertyValueMapper; @Autowired PropertyService propertyService; @Autowired ProductService 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); } }
@Autowired ProductImageService productImageService; @RequestMapping("/addProduct") public String add(Product product) { productService.add(product); // 建立新的 Product 時默認建立 5 個對應的 ProductImage 數據 ProductImage productImage = new ProductImage(); productImage.setProduct_id(product.getId()); for (int i = 1; i <= 5; i++) { productImage.setId(i); productImageService.add(productImage); } return "redirect:listProduct?category_id=" + product.getCategory_id(); } @RequestMapping("/deleteProduct") public String delete(Integer id, HttpServletRequest request) { // 在刪除產品的時候將對應的 5 個 ProductImage 數據也刪除了 productImageService.deleteByProductId(id); // 同時刪除目錄下的相關文件 String path = request.getSession().getServletContext().getRealPath("" + id); deleteDir(new File(path)); // 刪除外鍵對應的數據 propertyValueService.deleteByProductId(id); int category_id = productService.get(id).getCategory_id(); productService.delete(id); return "redirect:listProduct?category_id=" + category_id; } /** * 遞歸刪除目錄下的全部文件及子目錄下全部文件 * * @param dir 將要刪除的文件目錄 * @return boolean Returns "true" if all deletions were successful. * If a deletion fails, the method stops attempting to * delete and returns "false". */ private static boolean deleteDir(File dir) { if (dir.isDirectory()) { String[] children = dir.list(); //遞歸刪除目錄中的子目錄下 for (int i = 0; i < children.length; i++) { boolean success = deleteDir(new File(dir, children[i])); if (!success) { return false; } } } // 目錄此時爲空,能夠刪除 return dir.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 { @Autowired ProductImageService productImageService; @Autowired ProductService 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}"><span class="glyphicon glyphicon-trash"></span></a></td> </tr> </c:forEach>
在刪除頂層數據庫數據的時候,要注意刪除其下的有外鍵關聯的數據,特別是 product_id 這個東西,是不少表的外鍵,刪除 product 以前須要先清空有關聯的其餘表的數據....
總之坑是不少啦..不過項目在進展總歸是好事...耐心耐心...
還剩下一些體力活的東西,就先結博文啦...(心累.jpg)
有一些催更的朋友,但願能別催啦...天天都在碼啦,並且自己也是很low的東西,寫完以後我會上傳 github 的。
當我給本身埋了一個大坑說要模仿天貓,而且陷進去的時候,一方面痛苦着一方面也察覺了本身不少不足的地方,就以爲仍是很值得,如今來作一下簡短的總結。
歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz_javaweb 分享本身的Java Web學習之路以及各類Java學習資料