模仿天貓實(shí)戰(zhàn)【SSM版】——后臺(tái)開(kāi)發(fā)

上一篇文章鏈接:模仿天貓實(shí)戰(zhàn)【SSM版】——項(xiàng)目起步

后臺(tái)需求分析

在開(kāi)始碼代碼之前,還是需要先清楚自己要做什么事情甜奄,后臺(tái)具體需要實(shí)現(xiàn)哪些功能:

  • 注意: 訂單柠横、用戶、訂單课兄、推薦鏈接均不提供增刪的功能牍氛。

后臺(tái)界面設(shè)計(jì)

不像前端那樣有原型直接照搬就可以了,后臺(tái)的設(shè)計(jì)還真的有難到我...畢竟我是一個(gè)對(duì)美有一定要求的人烟阐,一方面想盡量的簡(jiǎn)潔搬俊、簡(jiǎn)單,另一方面又不想要太難看蜒茄,那怎么辦呢唉擂?

那當(dāng)然是找模板了,找到一個(gè)順眼的下載下來(lái)就開(kāi)始改檀葛,

這個(gè)模板的原地址在這里:戳這里

順便安利一下 FireFox 玩祟,真是開(kāi)發(fā)神器,配合著修改屿聋,棒棒噠:

經(jīng)過(guò)一番折騰...

摁空扎,就這風(fēng)格了,而且我還發(fā)現(xiàn)右上角的【Search】框是下載的模板用 js 實(shí)現(xiàn)的...對(duì)于管理來(lái)說(shuō)更加方便了....而且居然還實(shí)現(xiàn)了分頁(yè)....

一個(gè)邪惡的想法又誕生了...

一些規(guī)定

  • 為了降低項(xiàng)目的難度润讥,我們做了很多的精簡(jiǎn)转锈,現(xiàn)在我們作出如下的規(guī)定:
  • 全站沒(méi)有商家,只有一家 Tmall 楚殿,后臺(tái)沒(méi)有驗(yàn)證撮慨,可以直接進(jìn)入
  • 前臺(tái)的路徑就是默認(rèn)路徑,后臺(tái)的路徑需要加上 “/admin” 后綴勒魔,如訪問(wèn)后臺(tái)則為:localhost/admin (默認(rèn)為分類管理頁(yè)
  • 管理路徑統(tǒng)一為:admin/listXxxxx甫煞,如分類管理路徑為:admin/listCategory,用戶管理路徑為:admin/listUser冠绢,諸如此類
  • 編輯路徑統(tǒng)一為:admin/editXxxxx抚吠,如編輯分類路徑為:admin/editCategory,產(chǎn)品編輯頁(yè)為:admin/editProduct弟胀,諸如此類
  • 刪除路徑統(tǒng)一為:admin/deleteXxxxx
  • 更新路徑統(tǒng)一為:admin/updateXxxxx
  • 關(guān)于頁(yè)面路徑的一些規(guī)定:
  • 前端頁(yè)面統(tǒng)一在【W(wǎng)EB-INF/views】下楷力,后端頁(yè)面統(tǒng)一在【W(wǎng)EB-INF/views/admin】下

分類管理

正式開(kāi)始編寫(xiě)我們的代碼喊式,以 Category 為例。

編寫(xiě) Service 層

我們需要在這一層上考慮需要完成的功能萧朝,對(duì)應(yīng)我們上面畫(huà)的后臺(tái)功能圖岔留,分類管理也就是完成分類的查詢還有修改的工作:

package cn.wmyskxz.service;

import cn.wmyskxz.pojo.Category;

import java.util.List;

public interface CategoryService {

    /**
     * 返回分類列表
     * @return
     */
    List<Category> list();

    /**
     * 通過(guò)id獲取對(duì)應(yīng)的數(shù)據(jù)
     * @param id
     * @return
     */
    Category get(Integer id);

    /**
     * 更新分類
     * @param category
     * @return
     */
    void update(Category category);
}
  • 編寫(xiě) CategoryServiceImpl :
    在同一包下編寫(xiě)實(shí)現(xiàn)類
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 的實(shí)現(xiàn)類
 *
 * @author: @我沒(méi)有三顆心臟
 * @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);
    }
}

編寫(xiě) CategoryController

根據(jù)業(yè)務(wù)需求可以很容易的編寫(xiě)出來(lái):

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: @我沒(méi)有三顆心臟
 * @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 相關(guān)文件編寫(xiě)

自己研究了一會(huì)兒這個(gè)模板,感覺(jué)還是挺好改的检柬,然后就給改成了大概以下這個(gè)樣子(自己在數(shù)據(jù)庫(kù)中加入了 16 條數(shù)據(jù)):

  • 分類管理頁(yè)
  • 分類編輯頁(yè)

模板下載下來(lái)之后文件目錄是這樣的:

我們直接整個(gè)拷貝【assets】文件夾放在【webapp】目錄下献联,然后根據(jù)模板里面的代碼就可以開(kāi)始修改了,修改下來(lái)的兩個(gè)文件源碼如下:

  • 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>模仿天貓-后臺(tái)</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  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> 產(chǎn)品管理</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>產(chǎn)品管理</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>模仿天貓-后臺(tái)</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  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> 產(chǎn)品管理</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 的后臺(tái)管理模塊

其他模塊的思路跟 Category 如出一轍里逆,就比較偏向于體力勞動(dòng)了...

  • 注意: 所有本類的 id 屬性均為 id ,所有外鍵的 id 都是 屬性名_id 這樣的格式用爪,保持統(tǒng)一原押!

Example 條件查詢

MyBatis 逆向工程自動(dòng)生成文件的時(shí)候自動(dòng)生成了 Example 條件查詢類,我們到底應(yīng)該怎么使用它呢偎血,這里簡(jiǎn)要的說(shuō)明一下诸衔。

不得不說(shuō)這個(gè)東西還挺神奇,也很方便颇玷,比如我們需要查詢 category_id 對(duì)應(yīng)下的屬性表笨农,我們可以這樣寫(xiě):

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;
}

通過(guò)方法名其實(shí)也很容易看懂這些是什么意思,我們首先創(chuàng)建了一個(gè) PropertyExample 實(shí)例對(duì)象亚隙,然后通過(guò) .or() 方法開(kāi)啟條件查詢磁餐,.andCategory_idEqualTo() 匹配對(duì)應(yīng)的 category_id ,自動(dòng)生成的 sql 語(yǔ)句就像這樣:

更多詳情戳這里 - 引用其他博客的詳細(xì)說(shuō)明


IDEA 快速重構(gòu)

當(dāng)我編寫(xiě)好了 PropertyService 阿弃、PropertyServiceImpl、 PropertyController 之后再想要去編寫(xiě) Product 的這一系列文件的時(shí)候羞延,發(fā)現(xiàn)其實(shí)很多代碼都是重復(fù)的人柿,只是很少一部分的代碼需要改動(dòng)挟憔,暫時(shí)不考慮設(shè)計(jì)模式的話,我們可以使用 IDEA 來(lái)完成快速重構(gòu):

  • 直接復(fù)制 PropertyController 的代碼到 ProductController 中,然后【Ctrl + F】搜索 Property :

我們可以發(fā)現(xiàn)所有的 Property 都高亮了狰右,然后我們?cè)趺磁啃薷哪兀?/p>

然后繼續(xù)瘋狂碼代碼...


開(kāi)發(fā)過(guò)程中遇到的一些問(wèn)題

PropertyValue 遇到的麻煩

PropertyValue 屬性值表,這個(gè)表關(guān)聯(lián)了兩個(gè)外鍵篙程,一個(gè)指向 Product 藏畅,另一個(gè)指向 Property ,當(dāng)我按照之前的設(shè)計(jì)把 listProduct.jsp 設(shè)計(jì)成下面這個(gè)樣子的時(shí)候巩步,點(diǎn)擊【編輯屬性】旁赊,Property 的信息應(yīng)該怎么傳遞?

  • 也就是說(shuō)椅野,如何處理從 listProduct 跳轉(zhuǎn)到 listPropertyValue 頁(yè)面時(shí)憑空跳出來(lái)的 Property 的相關(guān)信息终畅?

解決方案:

在 PropertyValueServiceImpl 中增加:

@Autowired
PropertyService propertyService;

我們現(xiàn)在有 category_id 和 product_id 籍胯,我們可以利用 Property 和 Category 之間的聯(lián)系,通過(guò) category_id 查詢出所有對(duì)應(yīng)的 Property 离福,然后再篩選出同時(shí)匹配 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) {
        // 篩選出同時(shí)匹配 property_id 和 product_id 的值
        example.or().andProperti_idEqualTo(property.getId()).andProduct_idEqualTo(product_id);
        propertyValues.addAll(propertyValueMapper.selectByExample(example));
    }
    return propertyValues;
}

emmm...這樣的思路出來(lái)之后杖狼,對(duì)應(yīng)的 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";
}

加入一條數(shù)據(jù)測(cè)試:

  • bingo!

另一個(gè)問(wèn)題是添加屬性值:

添加的屬性值必須是當(dāng)前 Category 下有的屬性值妖爷,所以我們可以在 Controller 上自動(dòng)注入一個(gè) PropertyService 通過(guò) category_id 查詢到當(dāng)前分類下所有的 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";
}

期間發(fā)現(xiàn)一個(gè) BUG蝶涩,PropertyValue 表里的 property_id 居然寫(xiě)成了 properti_id,嚇得我趕緊檢查了一下所有表的字段絮识,其他的沒(méi)問(wèn)題绿聘,重新生成一下逆向工程

然后獲取屬性名稱:

  • 完善之后大概是這樣:

產(chǎn)品圖片管理

產(chǎn)品圖片的管理需要涉及到文件的上傳操作,我們需要先提供必要的 jar 包依賴:

  • commons-fileupload
  • commons-io

同樣的搜索 maven 庫(kù)添加依賴到 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>

產(chǎn)品圖片如何管理笋除?

  • 規(guī)定一:
    所有的產(chǎn)品圖片均保存在【img/product/】對(duì)應(yīng)的 product_id 目錄下斜友,并且默認(rèn)的文件名為 1,2垃它,3鲜屏,4,5 国拇,例如 product_id 為 1 的產(chǎn)品的產(chǎn)品圖片 1 保存于:【img/product/1/1.jpg】
  • 規(guī)定二:
    每一個(gè)產(chǎn)品對(duì)應(yīng)五張圖片洛史,文件名分別為 1.jpg ,2.jpg 以此類推酱吝,不能少也不能多也殖,刪除也只是將對(duì)應(yīng)目錄下的圖片刪除,id 并不改變
  • 規(guī)定三:
    默認(rèn)產(chǎn)品打開(kāi)的大圖即為該產(chǎn)品圖片目錄中的 1.jpg
  • 界面大概設(shè)計(jì)成了這樣:
  • 莫名其妙一個(gè) BUG:

我把表單設(shè)計(jì)成了這樣务热,隱藏了兩個(gè)屬性忆嗜,一個(gè) product_id,一個(gè) id:

為了方便操作崎岂,我想要直接申明兩個(gè)參數(shù)用來(lái)接收上面的兩個(gè)屬性捆毫,大概是這樣:

但是上面兩種方法都不行,我還查了一些資料在 @RequestParam 注解里設(shè)置了 required 屬性冲甘,仍然獲取不到绩卤,但是我改成用 ProductImage 來(lái)接收就好了..Why?

后來(lái)寫(xiě)著寫(xiě)著,又必須要使用上面兩種方法了....

  • 根據(jù)我們的規(guī)定來(lái)完成代碼

ProductImageService 層還是跟之前的沒(méi)有多大的區(qū)別江醇,但是值得注意的是濒憋,根據(jù)我們的規(guī)定,我們的刪除需要做一些改動(dòng)(根據(jù) 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 實(shí)現(xiàn)類
 *
 * @author: @我沒(méi)有三顆心臟
 * @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) {
        // 按條件查詢出需要?jiǎng)h除的列表
        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);
        // 循環(huán)刪除
        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) {
            // 篩選出同時(shí)匹配 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);

    // 創(chuàng)建新的 Product 時(shí)默認(rèn)創(chuàng)建 5 個(gè)對(duì)應(yīng)的 ProductImage 數(shù)據(jù)
    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) {

    // 在刪除產(chǎn)品的時(shí)候?qū)?duì)應(yīng)的 5 個(gè) ProductImage 數(shù)據(jù)也刪除了
    productImageService.deleteByProductId(id);
    // 同時(shí)刪除目錄下的相關(guān)文件
    String path = request.getSession().getServletContext().getRealPath("" + id);
    deleteDir(new File(path));

    // 刪除外鍵對(duì)應(yīng)的數(shù)據(jù)
    propertyValueService.deleteByProductId(id);

    int category_id = productService.get(id).getCategory_id();
    productService.delete(id);

    return "redirect:listProduct?category_id=" + category_id;
}

/**
 * 遞歸刪除目錄下的所有文件及子目錄下所有文件
 *
 * @param dir 將要?jiǎng)h除的文件目錄
 * @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;
            }
        }
    }
    // 目錄此時(shí)為空陶夜,可以刪除
    return dir.delete();
}

然后編寫(xiě)我們的 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: @我沒(méi)有三顆心臟
 * @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);
        // 因?yàn)?id 是自增長(zhǎng)鍵凛驮,所以需要 % 5 來(lái)作為文件名
        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) {
        // 不刪除表中的數(shù)據(jù)(在 ProductController 中統(tǒng)一刪除),刪除對(duì)應(yīng)文件
        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;
    }
}

  • 再優(yōu)化一下界面的東西律适,增加沒(méi)有圖片顯示的 error 圖片辐烂,大概就是這個(gè)樣子:

這里就只貼一下 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>

在寫(xiě)圖片管理的時(shí)候又遇到一個(gè)坑

在刪除頂層數(shù)據(jù)庫(kù)數(shù)據(jù)的時(shí)候遏插,要注意刪除其下的有外鍵關(guān)聯(lián)的數(shù)據(jù),特別是 product_id 這個(gè)東西纠修,是很多表的外鍵胳嘲,刪除 product 之前需要先清空有關(guān)聯(lián)的其他表的數(shù)據(jù)....

總之坑是很多啦..不過(guò)項(xiàng)目在進(jìn)展總歸是好事...耐心耐心...

接著碼代碼....

還剩下一些體力活的東西,就先結(jié)博文啦...(心累.jpg)

有一些催更的朋友扣草,希望能別催啦...每天都在碼啦了牛,而且本身也是很low的東西,寫(xiě)完之后我會(huì)上傳 github 的辰妙。


總結(jié)

當(dāng)我給自己埋了一個(gè)大坑說(shuō)要模仿天貓鹰祸,并且陷進(jìn)去的時(shí)候,一方面痛苦著一方面也察覺(jué)了自己很多不足的地方密浑,就覺(jué)得還是很值得蛙婴,現(xiàn)在來(lái)做一下簡(jiǎn)短的總結(jié)。

  • 進(jìn)度比想象中慢了很多尔破,雖然一步一步按照之前的分析圖來(lái)編寫(xiě)代碼總體是順暢的街图,但是有那種寫(xiě)著寫(xiě)著突然發(fā)現(xiàn)之前的設(shè)計(jì)有問(wèn)題的感覺(jué),中途也改了幾次懒构,發(fā)現(xiàn)自己分析問(wèn)題不夠全面餐济。
  • 項(xiàng)目中有許多類似的代碼,并且在 Controller 和 Impl 中不斷有其他的東西加入胆剧,總覺(jué)得是糟糕的代碼絮姆,但是又不知道應(yīng)該進(jìn)一步如何改進(jìn)。
  • 方向永遠(yuǎn)比努力重要秩霍,在行動(dòng)之前思考清楚篙悯,我一直覺(jué)得是很重要的一點(diǎn),我覺(jué)得通過(guò)對(duì)項(xiàng)目的分析铃绒,對(duì)我項(xiàng)目的進(jìn)展有一個(gè)整體的構(gòu)思辕近,各個(gè)模塊該有什么功能都比較清晰,特別在編寫(xiě) JSP 文件的時(shí)候能明顯感覺(jué)不會(huì)很迷茫匿垄,這是比較好的一點(diǎn)
  • 發(fā)現(xiàn)自己閱讀代碼量很少,這種感覺(jué)體現(xiàn)在很多地方归粉,一是寫(xiě)代碼時(shí)感覺(jué)到自己思想的局限性椿疗,二是覺(jué)得自己寫(xiě)的代碼有很多的相似性,雖然這個(gè)項(xiàng)目是自己突發(fā)奇想的想要去做的糠悼,但是有很多細(xì)節(jié)的地方届榄,是自己沒(méi)有去注意到的,比如類型要求倔喂、邊界判斷铝条、事務(wù)處理等等等...

歡迎轉(zhuǎn)載靖苇,轉(zhuǎn)載請(qǐng)注明出處!
簡(jiǎn)書(shū)ID:@我沒(méi)有三顆心臟
github:wmyskxz
歡迎關(guān)注公眾微信號(hào):wmyskxz_javaweb
分享自己的Java Web學(xué)習(xí)之路以及各種Java學(xué)習(xí)資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末班缰,一起剝皮案震驚了整個(gè)濱河市贤壁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌埠忘,老刑警劉巖脾拆,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異莹妒,居然都是意外死亡名船,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)旨怠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)渠驼,“玉大人,你說(shuō)我怎么就攤上這事鉴腻∶陨龋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵拘哨,是天一觀的道長(zhǎng)谋梭。 經(jīng)常有香客問(wèn)我,道長(zhǎng)倦青,這世上最難降的妖魔是什么瓮床? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮产镐,結(jié)果婚禮上隘庄,老公的妹妹穿的比我還像新娘。我一直安慰自己癣亚,他們只是感情好丑掺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著述雾,像睡著了一般街州。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上玻孟,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天唆缴,我揣著相機(jī)與錄音,去河邊找鬼黍翎。 笑死面徽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播趟紊,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼氮双,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了霎匈?” 一聲冷哼從身側(cè)響起戴差,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唧躲,沒(méi)想到半個(gè)月后造挽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弄痹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年饭入,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肛真。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谐丢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚓让,到底是詐尸還是另有隱情乾忱,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布历极,位于F島的核電站窄瘟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏趟卸。R本人自食惡果不足惜蹄葱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锄列。 院中可真熱鬧图云,春花似錦、人聲如沸邻邮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)筒严。三九已至丹泉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸭蛙,已是汗流浹背嘀掸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留规惰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓泉蝌,卻偏偏與公主長(zhǎng)得像歇万,于是被迫代替她去往敵國(guó)和親揩晴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理贪磺,服務(wù)發(fā)現(xiàn)硫兰,斷路器,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139
  • 文章分類 后臺(tái)文章分類列表頁(yè)模板導(dǎo)的詳細(xì)步驟建立數(shù)據(jù)表blog_category寒锚,并添加相應(yīng)的文章字段使用php ...
    JoyceZhao閱讀 1,706評(píng)論 0 12
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,385評(píng)論 8 265
  • OC語(yǔ)言基礎(chǔ) 1.類與對(duì)象 類方法 OC的類方法只有2種:靜態(tài)方法和實(shí)例方法兩種 在OC中劫映,只要方法聲明在@int...
    奇異果好補(bǔ)閱讀 4,271評(píng)論 0 11
  • 寬容是人類生活中至高無(wú)尚的美德 因?yàn)閷捜莅说男撵` 因?yàn)閷捜菘梢猿揭磺?因?yàn)閷捜菪枰活w博大的心 因?yàn)閷捜菔?..
    千層云林閱讀 768評(píng)論 0 18