上一篇文章链接:模仿天猫实战【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 :

    在同一包下编写实现类
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);
}
}

编写 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 { @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";
}
}

JSP 相关文件编写

自己研究了一会儿这个模板,感觉还是挺好改的,然后就给改成了大概以下这个样子(自己在数据库中加入了 16 条数据):

  • 分类管理页

  • 分类编辑页

模板下载下来之后文件目录是这样的:

我们直接整个拷贝【assets】文件夹放在【webapp】目录下,然后根据模板里面的代码就可以开始修改了,修改下来的两个文件源码如下:

  • listCategory.jsp
<%@ 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>
  • editCategory.jsp
<%@ 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 的后台管理模块

其他模块的思路跟 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 { @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);
}
}
  • 首先在 ProductController 中 add 和 delete 方法中增加以下代码:
@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 的。


总结

当我给自己埋了一个大坑说要模仿天猫,并且陷进去的时候,一方面痛苦着一方面也察觉了自己很多不足的地方,就觉得还是很值得,现在来做一下简短的总结。

  • 进度比想象中慢了很多,虽然一步一步按照之前的分析图来编写代码总体是顺畅的,但是有那种写着写着突然发现之前的设计有问题的感觉,中途也改了几次,发现自己分析问题不够全面。
  • 项目中有许多类似的代码,并且在 Controller 和 Impl 中不断有其他的东西加入,总觉得是糟糕的代码,但是又不知道应该进一步如何改进。
  • 方向永远比努力重要,在行动之前思考清楚,我一直觉得是很重要的一点,我觉得通过对项目的分析,对我项目的进展有一个整体的构思,各个模块该有什么功能都比较清晰,特别在编写 JSP 文件的时候能明显感觉不会很迷茫,这是比较好的一点
  • 发现自己阅读代码量很少,这种感觉体现在很多地方,一是写代码时感觉到自己思想的局限性,二是觉得自己写的代码有很多的相似性,虽然这个项目是自己突发奇想的想要去做的,但是有很多细节的地方,是自己没有去注意到的,比如类型要求、边界判断、事务处理等等等...

欢迎转载,转载请注明出处!

简书ID:@我没有三颗心脏

github:wmyskxz

欢迎关注公众微信号:wmyskxz_javaweb

分享自己的Java Web学习之路以及各种Java学习资料

模仿天猫实战【SSM版】——后台开发的更多相关文章

  1. 模仿天猫实战【SSM】——总结

    第一篇文章链接:模仿天猫实战[SSM版]--项目起步 第二篇文章链接:模仿天猫实战[SSM版]--后台开发 总结:项目从4-27号开始写,到今天5-7号才算真正的完工,有许多粗糙的地方,但总算完成了, ...

  2. 模仿天猫实战【SSM版】——项目起步

    前言:现在自己的学习似乎遇到了瓶颈,感觉学习了 SSM 之后有一些迷茫,不知道接下来该往哪里去努力了,我觉得这是个很不好的状态,为了度过这段时期,我准备把天猫模仿下来(给自己找点事做)之后开始去巩固 ...

  3. 第二篇 :微信公众平台开发实战Java版之开启开发者模式,接入微信公众平台开发

    第一部分:微信公众号对接的基本介绍 一.填写服务器配置信息的介绍 登录微信公众平台官网后,进入到公众平台后台管理页面. 选择 公众号基本设置->基本配置 ,点击“修改配置”按钮,填写服务器地址( ...

  4. 第九篇 :微信公众平台开发实战Java版之如何实现自定义分享内容

    第一部分:微信JS-SDK介绍 微信JS-SDK是微信公众平台面向网页开发者提供的基于微信内的网页开发工具包. 通过使用微信JS-SDK,网页开发者可借助微信高效地使用拍照.选图.语音.位置等手机系统 ...

  5. 第八篇 :微信公众平台开发实战Java版之如何网页授权获取用户基本信息

    第一部分:微信授权获取基本信息的介绍 我们首先来看看官方的文档怎么说: 如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑. 关于网页授权回调域 ...

  6. 第七篇 :微信公众平台开发实战Java版之如何获取微信用户基本信息

    在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据O ...

  7. 第六篇 :微信公众平台开发实战Java版之如何自定义微信公众号菜单

    我们来了解一下 自定义菜单创建接口: http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_to ...

  8. 第五篇 :微信公众平台开发实战Java版之如何获取公众号的access_token以及缓存access_token

    一.access_token简介 为了使第三方开发者能够为用户提供更多更有价值的个性化服务,微信公众平台 开放了许多接口,包括自定义菜单接口.客服接口.获取用户信息接口.用户分组接口.群发接口等, 开 ...

  9. 第四篇 :微信公众平台开发实战Java版之完成消息接受与相应以及消息的处理

    温馨提示: 这篇文章是依赖前几篇的文章的. 第一篇:微信公众平台开发实战之了解微信公众平台基础知识以及资料准备 第二篇 :微信公众平台开发实战之开启开发者模式,接入微信公众平台开发 第三篇 :微信公众 ...

随机推荐

  1. [LuoguP1113] 杂物 - 拓扑排序

    其实只是纪念下第一篇洛谷题解? Description John的农场在给奶牛挤奶前有很多杂务要完成,每一项杂务都需要一定的时间来完成它.比如:他们要将奶牛集合起来,将他们赶进牛棚,为奶牛清洗乳房以及 ...

  2. Django知识总结

    一.什么是web框架? 框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以帮你快速开发特定的系统,简单地说,就是你用别人搭建好的舞台来做表演. web应 ...

  3. 0415关于通过FILEBEAT,LOGSTASH,ES,KIBNA实现数据的采集

    如何通过FILEBEAT,LOGSTASH,ES,KIBNA实现数据的采集总体参考网址:https://www.olinux.org.cn/elk/1157.html官方网址:https://www. ...

  4. 解决MySQL在修改列时因为外键依赖出错的问题

    因为 favorite_food 中的 person_id 对 person 表中的 person_id 有外键依赖关系,所以在执行 ALTER TABLE person MODIFY person_ ...

  5. java同时连接db2和mysql的程序

    db2的jar包安装参见:http://www.cnblogs.com/zifeiy/p/7918554.html 代码: package com.anbank.datafetch; import j ...

  6. Java入门1

    一.eclipse的简单使用 1.新建项目 在package explorer的空白处点击右键,新建一个项目(new->Java Project)或者点击菜单栏的File->JavaPro ...

  7. Java之GC

    Java之GC GC:GC 是JVM的垃圾回收器.与C/C++不同,java程序员无需考虑太多内存分配的位置,更不用考虑内存释放的机制,java对象内存的申请和释放都有JVM托管.JVM的内存释放机制 ...

  8. Temple Build~dp(01背包的变形)

    The Dwarves of Middle Earth are renowned for their delving and smithy ability, but they are also mas ...

  9. spring源码阅读(2)核心类介绍

    (1).BeanFactory作为一个主接口不继承任何接口,暂且称为一级接口. (2).有3个子接口继承了它,进行功能上的增强.这3个子接口称为二级接口. (3).ConfigurableBeanFa ...

  10. 计蒜客NOIP模拟赛D2T2 直线的交点

    伦伦刚刚在高中学习了解析几何,学会了计算两条直线的交点.这天,老师给她布置了一道作业.在平面上有 nnn 条直线,他们之间有若干交点.给定一对平板(两条平行的直线),问这有多少对直线,他们的交点在这一 ...