模仿天猫实战【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 : 在同一包下编写实现类
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 文件的时候能明显感觉不会很迷茫,这是比较好的一点
  • 发现自己阅读代码量很少,这种感觉体现在很多地方,一是写代码时感觉到自己思想的局限性,二是觉得自己写的代码有很多的相似性,虽然这个项目是自己突发奇想的想要去做的,但是有很多细节的地方,是自己没有去注意到的,比如类型要求、边界判断、事务处理等等等...

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个会写诗的程序员的博客

Spring Boot 集成 WebFlux 开发 Reactive Web 应用Spring Boot 集成 WebFlux 开发 Reactive Web 应用

IBM的研究称,整个人类文明所获得的全部数据中,有90%是过去两年内产生的。在此背景下,包括NoSQL,Hadoop, Spark, Storm, Kylin在...

782
来自专栏WindCoder

微信小程序踩坑记-Java基于SSM下的post请求

最近在持续踩微信小程序的坑,canvas和WebSocket的暂时还没找到相关的解决方案,暂时先将post请求无法获取data参数的坑填上。直接附上解决方案,已...

811
来自专栏kl的专栏

Apollo应用之动态调整线上数据源(DataSource)

博主之前写过使用apollo的配置动态推送能力来动态修改线上环境的日志输出级别,具体可见《spring boot动态调整线上日志级别》,今天来实现一个类似的应用...

3987
来自专栏Golang语言社区

使用JWT做RESTful API的身份验证-Go语言实现

在 使用Golang和MongoDB构建 RESTful API已经实现了一个简单的 RESTful API应用,但是对于有些API接口需要授权之后才能访问,在...

1161
来自专栏开发技术

spring-boot-2.0.3不一样系列之国际化

       针对spring boot,网上已有很多优质的系列教程,我就不再班门弄斧了(实际上是担心没别人写的好,哈哈哈!)。但是还是想蹭蹭spring bo...

1253
来自专栏Java架构师历程

ssh工作原理

1.通过Configuration().configure();读取并解析hibernate.cfg.xml配置文件

873
来自专栏Java架构解析

像SpringMVC一样在Android上做Web开发

一部分Android开发者看到这个标题时可能有点疑惑,SpringMVC不是用来做JavaWeb开发的吗?难道被移植到Android上来了?答案是否定的,因为S...

1273
来自专栏码神联盟

碎片化 | 第四阶段-45-session为空问题解决-视频

如清晰度低,可转PC网页观看高清版本: http://v.qq.com/x/page/k0567a1nipx.html 版权声明:本视频、课件属本公众号作者所...

3386
来自专栏Spark学习技巧

面试的角度诠释Java工程师(二)

892
来自专栏程序员宝库

Spring MVC工作原理

Dispatcher Servlet分发器 Handler Mapping 处理器映射 Controller 控制器 ModelAndView 模型和视图对象 ...

3438

扫码关注云+社区