上一篇文章连接:模仿天猫实战【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学习资料