Skip to content

商家后台-商品管理【商品列表】

需求分析

在商家后台,显示该商家的商品列表信息

显示商品列表

查询商家商品列表-后端

功能代码参考之前【运营商后台-商品分类功能】

注意:因为页面需要显示一级、二级、三级分类名称,需要查询分类表,返回分页的对象PageResult<map<String, Object>>

  • 修改pinyougou-shop-web工程控制GoodsController,增加分页查询的search方法
java
/**
 * 多条件分页查询SPU商品表中的数据
 *
 * @param goods 商品查询条件对象
 * @param page  当前页记录
 * @param rows  每页显示大小
 * @return 封装分页数据
 */
@GetMapping("/search")
public PageResult findByPage(Goods goods,
                             @RequestParam("page") Integer page,
                             @RequestParam("rows") Integer rows) {
    try {
        // 获取当前登录商家的id
        String sellerId = SecurityContextHolder.getContext().getAuthentication().getName();
        // 查询条件封装在goods对象中,还需要封装当前用户,只查询当前用户商品
        goods.setSellerId(sellerId);
        // 调用服务多条件分页查询方法
        return goodsService.findGoodsByPage(goods, page, rows);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
  • 服务层实现类
java
/**
 * 多条件分页查询SPU商品表中的数据
 *
 * @param goods 商品查询条件对象
 * @param page  当前页记录
 * @param rows  每页显示大小
 * @return 封装分页数据
 */
PageResult findGoodsByPage(Goods goods, Integer page, Integer rows);

@Override
public PageResult findGoodsByPage(Goods goods, Integer page, Integer rows) {
    try {
        // 使用分页助手开启分页,获取pageInfo对象
        PageInfo<Map<String, Object>> pageInfo = PageHelper.startPage(page, rows)
                .doSelectPageInfo(new ISelect() {
                    @Override
                    public void doSelect() {
                        // 使用自定义动态条件查询方法
                        goodsMapper.findByWhere(goods);
                    }
                });

        // 获取查询的结果集合
        List<Map<String, Object>> resultList = pageInfo.getList();
        // 迭代遍历分页信息对象的map集合,将1~3分类名称封装到map集合中
        for (Map<String, Object> map : resultList) {
            // 查询商品的分类的名称(一级分类名称),设置到map集合中
            ItemCat itemCat1 = itemCatMapper.selectByPrimaryKey(map.get("category1Id"));
            map.put("category1Name", itemCat1 != null ? itemCat1.getName() : "");
            // 查询商品的分类的名称(二级分类名称),设置到map集合中
            ItemCat itemCat2 = itemCatMapper.selectByPrimaryKey(map.get("category2Id"));
            map.put("category2Name", itemCat2 != null ? itemCat2.getName() : "");
            // 查询商品的分类的名称(三级分类名称),设置到map集合中
            ItemCat itemCat3 = itemCatMapper.selectByPrimaryKey(map.get("category3Id"));
            map.put("category3Name", itemCat3 != null ? itemCat3.getName() : "");
        }

        // 返回分页结果
        return new PageResult(pageInfo.getTotal(), resultList);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • 商品GoodsMapper接口与映射文件,查询条件注意要包含是否被删除(is_delete字段),只需要查询页面需要显示的字段即可
java
/**
 * 多条件查询SPU商品表中的数据
 *
 * @param goods 商品查询条件对象
 * @return map格式的结果集合
 */
List<Map<String, Object>> findByWhere(@Param("goods") Goods goods);
xml
<!-- 多条件查询SPU商品表中的数据 -->
<select id="findByWhere" resultType="map">
    SELECT
        id,price,
        goods_name as goodsName,
        category1_id as category1Id,
        category2_id as category2Id,
        category3_id as category3Id,
        audit_status as auditStatus
    FROM
        `tb_goods`
    <where>
        is_delete is null
        <if test="goods != null">
            <!-- 商家的id -->
            <if test="goods.sellerId != null and goods.sellerId != ''">
                and seller_id = #{goods.sellerId}
            </if>
            <!-- 商品名称 -->
            <if test="goods.goodsName != null and goods.goodsName != ''">
                and goods_name like CONCAT('%', #{goods.goodsName}, '%')
            </if>
            <!-- 审核状态 -->
            <if test="goods.auditStatus != null and goods.auditStatus != ''">
                and audit_status = #{goods.auditStatus}
            </if>
        </if>
    </where>
    ORDER BY id DESC
</select>

查询商家商品列表-前端

  • 参考其他模块引入相关的js文件,添加引入指令,导入分页组件。
  • 修改pinyougou-shop-web工程goodsController层,定义分页搜索的方法;定义状态码数组,用于页面转换数据字典
js
/* 定义封装查询条件对象 */
$scope.searchEntity = {};
/* 定义分页查询的方法 */
$scope.search = function (page, rows) {
    /* 调用服务层,送异步请求查询分页数据 */
    baseService.findByPage('/goods/search', page, rows, $scope.searchEntity)
        .then(function (response) {
            // 请求成功回调函数.获取响应数据,赋值给dataList
            $scope.dataList = response.data.rows;
            // 更新分页组件中的总记录数
            $scope.paginationConf.totalItems = response.data.total;
        }, function (response) {
            alert("获取商品列表数据失败!");
        });
};

/* 定义状态码数组 */
$scope.status = ['未审核', '已审核', '审核未通过', '关闭'];
  • goods.html循环显示商品列表
html
<tr ng-repeat="entity in dataList">
	<td><input type="checkbox"></td>
	<td>&#123;&#123; entity.id &#125;&#125;</td>
	<td>&#123;&#123; entity.goodsName &#125;&#125;</td>
	<td>&#123;&#123; entity.price &#125;&#125;</td>
	<td>&#123;&#123; entity.category1Name &#125;&#125;</td>
	<td>&#123;&#123; entity.category2Name &#125;&#125;</td>
	<td>&#123;&#123; entity.category3Name &#125;&#125;</td>
	<td>&#123;&#123; status[entity.auditStatus] &#125;&#125;</td>
	<td class="text-center">
		<button type="button" class="btn bg-olive btn-xs">修改</button>
	</td>
</tr>
  • 绑定查询条件输入框,按钮绑定点击事件
html
<div class="has-feedback">
	状态:<select ng-model="searchEntity.auditStatus">
		<option value="">全部</option>
		<option value="0">未申请</option>
		<option value="1">申请中</option>
		<option value="2">审核通过</option>
		<option value="3">已驳回</option>
	</select>
    商品名称:<input type="text" ng-model="searchEntity.goodsName">
	<button class="btn btn-default" ng-click="reload();">查询</button>
</div>

商家后台-商品管理【商品修改】

需求分析

在商品列表页面点击修改,进入商品编辑页面,并传递参数商品ID,商品编辑页面接受该参数后从数据库中读取商品信息,用户修改后保存信息。

基本信息读取

首选读取商品分类、商品名称、品牌,副标题,价格等信息

查询单个商品信息-后端部分

  • 修改pinyougou-shop-web工程控制层GoodsController,增加根据id查询商品方法
java
/**
 * 根据goods表的主键id查询商品数据
 *
 * @param id 商品id
 * @return 商品对象
 */
@GetMapping("/findOne")
public Goods findOne(@RequestParam("id") Long id) {
    try {
        // 调用服务层根据id查询的方法
        return goodsService.findOne(id);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
  • 商品服务层GoodsService与GoodsServiceImpl增加根据id查询商品的方法
java
/**
 * 根据goods表的主键id查询商品数据
 *
 * @param id 商品id
 * @return 商品对象
 */
Goods findOne(Long id);

@Override
public Goods findOne(Long id) {
    try {
        // 1.调用商品数据访问层根据id查询
        Goods goods = goodsMapper.selectByPrimaryKey(id);

        // 2.根据商品id,调用商品描述数据访问层查询商品描述数据
        goods.setGoodsDesc(goodsDescMapper.selectByPrimaryKey(id));

        return goods;
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

查询单个商品信息-前端部分

  • 修改goods_edit.html页面加入base标签与配置位置提供者
html
// 在<head>标签中引入
<!-- 加入base标签  -->
<base href="/"/>

// ==============================

<script type="text/javascript">
    /* 配置位置提供者 */
    app.config(function ($locationProvider) {
        $locationProvider.html5Mode(true);
    });
    ......
</script>
  • 在goodsController中引入$location服务。并新增查询单个商品的方法
js
/** 定义控制器层 */
app.controller('goodsController', function ($scope, $controller,
        $location, baseService, uploadService) {
    ......

    /* 根据主键id查询单个商品 */
    $scope.findOne = function () {
        // 通过location服务获取请求参数
        var id = $location.search().id;

        if (id == null) {
            return;
        }

        // 调用业务层查询方法
        baseService.findOne('/goods/findOne', id).then(function (response) {
            $scope.goods = response.data;
        });
    };
}
  • goods_edit.html页面上添加初始化指令
html
<body class="hold-transition skin-red sidebar-mini"
      ng-app="pinyougou" ng-controller="goodsController"
      ng-init="findItemCatByParentId(0, 'itemCatList1');findOne();">

地址栏输入(测试):http://shop.moon.com/admin/goods_edit.html?id=149187842868008

读取商品介绍(富文本编辑器)

因为数据库保存的富文本字符串,需要将字符串设置到富文本编辑器的html元素。修改goodsController中findOne方法增加富文本处理

js
$scope.findOne = function () {
    // 通过location服务获取请求参数
    var id = $location.search().id;

    if (id == null) {
        return;
    }

    // 调用业务层查询方法
    baseService.findOne('/goods/findOne', id).then(function (response) {
        $scope.goods = response.data;
        // 富文本编辑器添加商品介绍
        editor.html($scope.goods.goodsDesc.introduction);
    });
};

显示商品图片列表

修改goodsController,在findOne方法添加代码,将图片列表由字符串转换为json集合对象。

js
baseService.findOne('/goods/findOne', id).then(function (response) {
    ......

    // 把商品图片json格式的字符串转化成json数组
    $scope.goods.goodsDesc.itemImages =
        JSON.parse($scope.goods.goodsDesc.itemImages);
});

读取商品扩展属性

  • 修改goodsController.js中的findOne方法,添加扩展属性json转换
js
baseService.findOne('/goods/findOne', id).then(function (response) {
    ......

    // 把扩展属性json格式的字符串转化成json数组
    $scope.goods.goodsDesc.customAttributeItems =
        JSON.parse($scope.goods.goodsDesc.customAttributeItems);
});
  • 经过测试,发现扩展属性值并没有读取出来,这是因为与下列代码发生冲突
js
/* 用$watch监听 goods.typeTemplateId 变量发生改变(查询类型模版对象) */
$scope.$watch('goods.typeTemplateId', function (newVal, oldVal) {
    ......

    // 当改变的新值不为空时
    if (newVal) {
        /* 根据类型模版主键id查询一个类型模版对象 */
        baseService.findOne('/typeTemplate/findOne', newVal).then(function (response) {
            if (response.data) {
                ......
                // 把类型模版对象中的扩展属性json格式字符串转化成json数组
                $scope.goods.goodsDesc.customAttributeItems =
                    JSON.parse($scope.typeTemplate.customAttributeItems);
            }
        });
        ......
    }
});
  • 读取出来的值被覆盖了,需要改写代码,添加判断,当用户没有传递id参数时再执行此逻辑;还有之前做商品分类改变后,会清空扩展属性的操作,此部分也加上判断是否传递id
js
/* 用$watch监听 goods.typeTemplateId 变量发生改变(查询类型模版对象) */
$scope.$watch('goods.typeTemplateId', function (newVal, oldVal) {
    /* 判断是否是修改商品,判断id是否为空,空则为false */
    if (!$location.search().id) {
        // 1. 如果模版id变化,初始化模版对象与清空绑定到goods对象的brandId
        $scope.typeTemplate = {};
        $scope.goods.brandId = null;
        // 2. 清空扩展属性和绑定的值
        $scope.goods.goodsDesc.customAttributeItems = null;
        // 3. 清空规格选项数据与绑定勾选的规格选项
        $scope.specList = {};
        $scope.goods.goodsDesc.specificationItems = [];
        // 4. 清空已经生成SKU商品信息数组
        $scope.goods.items = null;
    }

    // 当改变的新值不为空时
    if (newVal) {
        /* 根据类型模版主键id查询一个类型模版对象 */
        baseService.findOne('/typeTemplate/findOne', newVal).then(function (response) {
            if (response.data) {
                ......
               /* 判断是否是修改商品,判断id是否为空,空则为false */
                if (!$location.search().id) {
                    // 把类型模版对象中的扩展属性json格式字符串转化成json数组
                    $scope.goods.goodsDesc.customAttributeItems =
                        JSON.parse($scope.typeTemplate.customAttributeItems);
                }
            }
        });
        ......
    }
});

读取商品规格属性

  • 修改goodsController,添加规格属性json转换
js
baseService.findOne('/goods/findOne', id).then(function (response) {
    ......

    // 把规格选项json格式的字符串转化成json数组
    $scope.goods.goodsDesc.specificationItems =
        JSON.parse($scope.goods.goodsDesc.specificationItems);
});
  • 增加根据规格名称和选项名称返回是否被勾选的方法
js
/* 根据规格名称和选项名称返回是否被勾选 */
$scope.checkAttributeValue = function (specName, optionName) {
    /*
     * 返回的数据格式:
     *  [{"attributeValue":["移动4G","联通4G","电信4G"],"attributeName":"网络"},
     *      {"attributeValue":["128G","64G"],"attributeName":"机身内存"}]
     */
    // 定义规格选项数组
    var specItems = $scope.goods.goodsDesc.specificationItems;
    /*
     * 调用工具方法,根据key从规格选项json数组中搜索一个json对象
     *  格式:{"attributeValue":["移动4G","联通4G","电信4G"],"attributeName":"网络"}
     */
    var obj = $scope.searchJsonByKey(specItems, 'attributeName', specName);

    if (obj) {
        // 判断attributeValue数组是否包含optionName
        return (obj.attributeValue.indexOf(optionName) >= 0);
    }
    // 返回对象为空
    return false;
};
  • 修改页面上规格面板的复选框,运用ng-checked指令控制复选框的勾选状态
html
<div ng-repeat="spec in specList">
	<div class="col-md-2 title">&#123;&#123; spec.text &#125;&#125;</div>
	<div class="col-md-10 data">
		<span ng-repeat="option in spec.options">
            <input type="checkbox"
                   ng-click="updateSpecAttr($event, spec.text, option.optionName);createItems();"
                   ng-checked="checkAttributeValue(spec.text, option.optionName);">
            &#123;&#123; option.optionName &#125;&#125;
		</span>
	</div>
</div>

读取SKU数据

显示SKU商品列表,并自动读取价格、库存等数据加载到列表中

SKU数据生成

读取SKU数据-后端部分

修改GoodsServiceImpl的findOne方法中增加查询SKU商品数据

java
@Override
public Goods findOne(Long id) {
    try {
        // 1.调用商品数据访问层根据id查询
        Goods goods = goodsMapper.selectByPrimaryKey(id);

        // 2.根据商品id,调用商品描述数据访问层查询商品描述数据
        goods.setGoodsDesc(goodsDescMapper.selectByPrimaryKey(id));

        // 3.调用SKU数据访问层,根据SPU的id查询SKU具体的商品数据
        Item item = new Item();
        // 封装查询的条件
        item.setGoodsId(id);
        goods.setItems(itemMapper.select(item));
        return goods;
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

读取SKU数据-前端部分

修改控制层goodsController的findOne方法,增加SKU列表规格json字符串转换对象

js
baseService.findOne('/goods/findOne', id).then(function (response) {
    ......

    // SKU列表规格json字符串转换对象
    for (let i = 0; i < $scope.goods.items.length; i++) {
        $scope.goods.items[i].spec =
            JSON.parse($scope.goods.items[i].spec);
    }
});

保存数据

商品修改保存数据-后端部分

  • 在pinyougou-shop-web工程的控制层GoodsController增加修改商品的方法
java
/**
 * 修改商品数据
 *
 * @param goods 商品对象
 * @return 成功/失败标识
 */
@PostMapping("/update")
public boolean update(@RequestBody Goods goods) {
    try {
        // 调用服务层修改方法
        goodsService.updateGoods(goods);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 服务层GoodsService增加修改方法
java
/**
 * 修改商品数据
 *
 * @param goods 商品对象
 */
void updateGoods(Goods goods);
  • 修改GoodsServiceImpl实现类,将之前保存商品方法中的SKU列表插入的代码提取出来,封装到私有方法
java
/**
 * 保存SKU具体商品表中的数据
 *
 * @param goods 商品对象
 */
private void saveItems(Goods goods) {
    // 判断是否开启规格
    if ("1".equals(goods.getIsEnableSpec())) {
        // 5 迭代所有的SKU具体商品集合,往SKU表商品表(tb_item)表添加数据
        for (Item item : goods.getItems()) {
            ......
            // 5.3 调用item数据访问层接口保存商品
            itemMapper.insertSelective(item);
        }
    } else {
        ......
        // 调用item数据访问层接口保存商品
        itemMapper.insertSelective(item);
    }
}
  • 在saveGoods方法中调用此方法,修改如下
java
@Override
public void saveGoods(Goods goods) {
    try {
        // 1.设置审核状态(未审核)
        goods.setAuditStatus("0");
        // 2.调用商品mapper保存方法,往SPU表(tb_goods)添加数据
        goodsMapper.insertSelective(goods);

        // 3.设置商品描述id,与商品的id一对一。**因为是持久态,goods保存后已经有id**
        goods.getGoodsDesc().setGoodsId(goods.getId());
        // 4.调用商品描述mapper保存,往商品描述表(tb_goods_desc)添加数据
        goodsDescMapper.insertSelective(goods.getGoodsDesc());

        // 保存SKU具体商品表中的数据
        saveItems(goods);

    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • 在updateGoods方法中实现修改
java
@Override
public void updateGoods(Goods goods) {
    // 设置商品审批状态:未审核状态
    goods.setAuditStatus("0");
    // 1. 调用通用mapper方法,修改SPU表(tb_goods)中的数据
    goodsMapper.updateByPrimaryKeySelective(goods);

    // 2. 修改商品描述表(tb_goods_desc)中的数据
    goodsDescMapper.updateByPrimaryKeySelective(goods.getGoodsDesc());

    // 修改SKU表(tb_item)中的数据(先删除SKU表中的数据、重新添加新的SKU数据)
    Item item = new Item();
    item.setGoodsId(goods.getId());
    // 3.1 调用通用mapper,根据条件删除SKU表中的数据(删除条件goodsId)
    itemMapper.delete(item);

    // 3.2 调用抽取方法,重新添加新的SKU数据
    saveItems(goods);
}

商品修改保存数据-前端部分

修改前端控制器goodsController,在saveOrUpdate方法中增加url请求的判断

js
/** 添加商品 */
$scope.saveOrUpdate = function () {
    // 定义默认url
    var url = 'save';

    // 判断是新增还是修改
    if ($scope.goods.id) {
        // 如果发送到后端的对象id属性不为空,则是修改操作
        url = 'update';
    }

    // 获取kindeditor富文本编辑器的内容
    $scope.goods.goodsDesc.introduction = editor.html();

    /** 发送post请求 */
    baseService.sendPost("/goods/" + url, $scope.goods).then(function (response) {
        if (response.data) {
            alert("添加成功!");
            /** 成功增加后清空表单goods对象 */
            $scope.goods = {};
            // 清空富文本编辑器内容
            editor.html('');
            // 清空后台返回的规格选项数据
            $scope.specList = {};
        } else {
            alert("添加失败!");
        }
    });
};

页面跳转

  1. 由商品列表页跳转到商品编辑页,修改goods.html表格行的修改按钮
html
<a href="/admin/goods_edit.html?id=&#123;&#123;entity.id&#125;&#125;"
   class="btn bg-olive btn-xs">修改</a>
  1. 由商品编辑页跳转到商品列表,修改goods_edit.html的返回列表按钮
html
<a href="javascript:location.href='/admin/goods.html'"
   class="btn btn-default">返回列表</a>
  1. 保存成功后返回列表页面。修改控制器goodsController,在saveOrUpdate方法中
js
$scope.saveOrUpdate = function () {
    ......
    /** 发送post请求 */
    baseService.sendPost("/goods/" + url, $scope.goods).then(function (response) {
        if (response.data) {
            ......
            /* 跳转到商品列表页 */
            location.href = "/admin/goods.html";
        } else {
            alert("添加失败!");
        }
    });
};

运营商后台-商品管理【商品审核】

待审核商品列表

需求:参照商家后台显示商品列表的代码

  • 在pinyougou-manager-web的前端控制器goodsController添加查询列表的方法
js
/* 定义状态码数组 */
$scope.status = ['未审核', '已审核', '审核未通过', '关闭'];

/* 定义封装查询条件对象 */
$scope.searchEntity = {};
/* 定义分页查询的方法 */
$scope.search = function (page, rows) {
    /* 调用服务层,送异步请求查询分页数据 */
    baseService.findByPage('/goods/search', page, rows, $scope.searchEntity)
        .then(function (response) {
            // 请求成功回调函数.获取响应数据,赋值给dataList
            $scope.dataList = response.data.rows;
            // 更新分页组件中的总记录数
            $scope.paginationConf.totalItems = response.data.total;
        }, function (response) {
            alert("获取商品列表数据失败!");
        });
};
  • 在pinyougou-manager-web的控制层GoodsController添加查询后端列表的方法
java
@RestController
@RequestMapping("/goods")
public class GoodsController {

    @Reference
    private GoodsService goodsService;

    /**
     * 多条件分页查询SPU商品表中的数据
     *
     * @param goods 商品查询条件对象
     * @param page  当前页记录
     * @param rows  每页显示大小
     * @return 封装分页数据
     */
    @GetMapping("/search")
    public PageResult findByPage(Goods goods,
                                 @RequestParam("page") Integer page,
                                 @RequestParam("rows") Integer rows) {
        try {
            // 运营商只需要显示待审核的商品,设置审状态码
            goods.setAuditStatus("0");
            // 调用服务多条件分页查询方法
            return goodsService.findGoodsByPage(goods, page, rows);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 修改pinyougou-manager-web工程的goods.html引入js
html
<script src="/plugins/angularjs/angular.min.js"></script>

<script src="/plugins/angularjs/pagination.js"></script>
<link rel="stylesheet" href="/plugins/angularjs/pagination.css" />

<script src="/js/base-pagination.js"></script>
<script src="/js/service/baseService.js"></script>
<script src="/js/controller/baseController.js"></script>
<script src="/js/controller/goodsController.js"></script>
  • 指令,完成初始化
html
<body class="hold-transition skin-red sidebar-mini"
      ng-app="pinyougou" ng-controller="goodsController">
  • 分页控件
html
<!-- 分页组件 -->
<tm-pagination conf="paginationConf"></tm-pagination>
  • 循环列表
html
<tr ng-repeat="entity in dataList">
    <td><input type="checkbox"></td>
    <td>&#123;&#123; entity.id &#125;&#125;</td>
    <td>&#123;&#123; entity.goodsName &#125;&#125;</td>
    <td>&#123;&#123; entity.price &#125;&#125;</td>
    <td>&#123;&#123; entity.category1Name &#125;&#125;</td>
    <td>&#123;&#123; entity.category2Name &#125;&#125;</td>
    <td>&#123;&#123; entity.category3Name &#125;&#125;</td>
    <td>&#123;&#123; status[entity.auditStatus] &#125;&#125;</td>
</tr>
  • 绑定分页查询条件
html
<div class="has-feedback">
	商品名称:<input type="text" ng-model="searchEntity.goodsName">
	<button class="btn btn-default"
            ng-click="reload();">查询</button>
</div>

商品审核与驳回

需求分析

商品审核的状态值为1,驳回的状态值为2,用户在列表中选中ID后,点击审核或驳回,修改商品状态,并刷新列表。

商品审核-后端部分

  • pinyougou-manager-web的控制层GoodsController,新增更新商品审核状态方法
java
/**
 * 商品审核(批量修改SPU商品的状态)
 *
 * @param ids    商品id数组
 * @param status 审核状态码
 * @return 成功/失败标识
 */
@GetMapping("/updateStatus")
public boolean updateStatus(@RequestParam("ids") Long[] ids,
                            @RequestParam("status") String status) {
    try {
        // 调用服务层批量修改方法
        goodsService.updateStatus(ids, status);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 服务层GoodsService与GoodsServiceImpl实现批量审核方法
java
/**
 * 商品审核(批量修改SPU商品的状态)
 *
 * @param ids    商品id数组
 * @param status 审核状态码
 */
void updateStatus(Long[] ids, String status);

@Override
public void updateStatus(Long[] ids, String status) {
    try {
        // 调用mapper批量修改状态的方法
        goodsMapper.updateStatus(ids, status);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • GoodsMapper接口与配置文件增加修改的方法
java
/**
 * 批量修改SPU商品的状态
 *
 * @param ids    商品id数组
 * @param status 修改的商品状态码
 */
void updateStatus(@Param("ids") Long[] ids, @Param("status") String status);
xml
<!-- 修改SPU商品的状态 -->
<update id="updateStatus">
    UPDATE `tb_goods` SET audit_status = #{status} WHERE `id` in
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</update>

商品审核-前端部分

  • 修改pinyougou-manager-web的控制器goodsController增加更新商品状态的方法
js
/* 定义商品审核方法 */
$scope.updateStatus = function (status) {
    // 判断是否有勾选
    if ($scope.ids.length > 0) {
        baseService.sendGet('/goods/updateStatus', 'ids=' + $scope.ids + '&status=' + status)
            .then(function (response) {
                if (response.data) {
                    // 成功响应后,重新加载数据
                    $scope.reload();
                    // 清空勾选的id
                    $scope.ids = [];
                } else {
                    alert("操作失败!");
                }
            });
    } else {
        alert("请选择要审核的商品!");
    }
}
  • pinyougou-manager-web的goods.html页面,为复选框绑定事件指令
html
<td><input type="checkbox"
           ng-click="updateSelection($event, entity.id);"/>
</td>
  • 修改页面上的审核通过和驳回按钮,绑定点击状态值
html
<button type="button" class="btn btn-default"
        title="审核通过" ng-click="updateStatus('1')">
	<i class="fa fa-check"></i> 审核通过
</button>
<button type="button" class="btn btn-default"
        title="驳回" ng-click="updateStatus('2')">
	<i class="fa fa-ban"></i> 驳回
</button>

运营商后台-商品管理【商品删除】

需求分析

为商品管理提供商品删除功能,用户选中部分商品,点击删除按钮即可实现商品删除。注意,这里的删除并非是物理删除,而是修改tb_goods表的is_delete字段为1 ,称之为“逻辑删除”。

逻辑删除-后端部分

  • 修改pinyougou-manager-web的控制层GoodsController,增加批量删除商品方法
java
/**
 * 批量删除商品(修改SPU商品的删除状态)
 *
 * @param ids 商品id数组
 * @return 成功/失败标识
 */
@GetMapping("/delete")
public boolean deleteGoods(@RequestParam("ids") Long[] ids) {
    try {
        // 调用服务层批量删除方法
        goodsService.deleteGoods(ids);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 修改服务层GoodsService与GoodsServiceImpl,增加批量删除方法
java
/**
 * 批量删除商品(修改SPU商品的删除状态)
 *
 * @param ids 商品id数组
 */
void deleteGoods(Long[] ids);

@Override
public void deleteGoods(Long[] ids) {
    try {
        // 调用mapper批量修改SPU商品的删除状态方法
        goodsMapper.updateDeleteStatus(ids, "1");
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • 修改GoodsMapper与配置文件,增加删除的sql语句
java
/**
 * 批量修改商品的删除状态
 *
 * @param ids      商品id数组
 * @param isDelete 删除的状态码
 */
void updateDeleteStatus(@Param("ids") Long[] ids, @Param("isDelete") String isDelete);
xml
<!-- 修改SPU商品的删除状态 -->
<update id="updateDeleteStatus">
    UPDATE `tb_goods` SET is_delete = #{isDelete} WHERE `id` IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</update>

逻辑删除-前端部分

  • 修改pinyougou-manager-web前端控制器goodsController,增加批量删除商品方法
java
/** 批量删除(修改商品的删除状态) */
$scope.delete = function () {
    if ($scope.ids.length > 0) {
        baseService.deleteById("/goods/delete", $scope.ids)
            .then(function (response) {
                if (response.data) {
                    // 清空勾选的id
                    $scope.ids = [];
                    // 成功响应后,重新加载数据
                    $scope.reload();
                } else {
                    alert("删除失败!");
                }
            });
    }
};
  • 修改pinyougou-manager-web的goods.html页面上的删除按钮,绑定点击事件
html
<button type="button" class="btn btn-default"
        title="删除" ng-click="delete();">
	<i class="fa fa-trash-o"></i> 删除
</button>

商品显示列表排除已删除记录

修改(检查)GoodsMapper.xml的findByWhere查询语句,是否有查询条件is_delete is null

商家后台-【商品上下架】

需求分析

什么是商品上下架?其实上下架也是商品的一个状态,但是不同于审核状态。审核状态的控制权在运营商手中,而上下架的控制权在商户手中。商户可以随时将一个已审核状态的商品上架或下架。上架表示正常销售,而下架则表示暂停销售。

实现逻辑:商品的上下架就是对上下架状态的修改。字段为tb_goods表的is_marketable字段。1表示上架、0表示下架。

商品上下架状态修改-后端部分

  • 修改pinyougou-shop-web工程的控制层GoodsController,增加批量修改上下架状态方法
java
/**
 * 修改商品的上下架状态
 *
 * @param ids    商品id数组
 * @param status 商品上下架状态码
 * @return 成功/失败标识
 */
@GetMapping("/updateStatus")
public boolean updateStatus(@RequestParam("ids") Long[] ids,
                            @RequestParam("status") String status) {
    try {
        // 调用服务层批量修改方法
        goodsService.updateMarketableStatus(ids, status);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}
  • 修改服务层GoodsService与GoodsServiceImpl,增加批量修改状态方法
java
/**
 * 修改商品上下架状态
 *
 * @param ids    商品id数组
 * @param status 商品上下架状态码
 */
void updateMarketableStatus(Long[] ids, String status);

@Override
public void updateMarketableStatus(Long[] ids, String status) {
    try {
        // 调用mapper批量修改上下架状态的方法
        goodsMapper.updateMarketableStatus(ids, status);
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}
  • 修改GoodsMapper与配置文件,增加修改上下架状态(is_marketable字段)的sql语句
java
/**
 * 批量修改商品上下架状态
 *
 * @param ids    商品id数组
 * @param status 上下架的状态码
 */
void updateMarketableStatus(@Param("ids") Long[] ids, @Param("status") String status);
xml
<!-- 修改SPU商品的上下架状态 -->
<update id="updateMarketableStatus">
    UPDATE `tb_goods` SET is_marketable = #{status} WHERE `id` in
    <foreach collection="ids" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</update>

商品上下架状态修改-前端部分

  • 修改pinyougou-shop-web前端控制器goodsController,增加批量修改商品上下架状态方法
js
/* 批量修改商品上下架状态 */
$scope.updateMarketableStatus = function (status) {
    var tip = status === '0' ? '你确认要下架吗?' : '你确认要上架吗?';
    // 提示框
    if (!confirm(tip)) {
        // 取消不请求后端
        return;
    }

    // 判断是否有勾选
    if ($scope.ids.length > 0) {
        baseService.sendGet('/goods/updateStatus', 'ids=' + $scope.ids + '&status=' + status)
            .then(function (response) {
                if (response.data) {
                    alert("操作成功!");
                    // 清空勾选的id
                    $scope.ids = [];
                    // 成功响应后,重新加载数据
                    $scope.reload();
                } else {
                    alert("操作失败!");
                }
            });
    } else {
        alert("请选择要处理的商品!");
    }
}
  • 修改pinyougou-shop-web的goods.html页面的复选框,绑定勾选事件指令
html
<td><input type="checkbox"
           ng-click="updateSelection($event, entity.id);">
</td>
  • 修改goods.html页面上的上架和下架按钮,绑定点击状态值
html
<button type="button" class="btn btn-default"
        title="上架" ng-click="updateMarketableStatus('1')">
	<i class="fa fa-check"></i> 上架
</button>
<button type="button" class="btn btn-default"
        title="下架" ng-click="updateMarketableStatus('0')">
	<i class="fa fa-ban"></i> 下架
</button>

注解式事务配置

事务异常测试

修改服务层GoodsServiceImpl的saveGoods方法,在里面增加异常

java
@Override
public void saveGoods(Goods goods) {
    try {
        // 1.设置审核状态(未审核)
        goods.setAuditStatus("0");
        // 2.调用商品mapper保存方法,往SPU表(tb_goods)添加数据
        goodsMapper.insertSelective(goods);

        // 制造异常
        System.out.println(1 / 0);

        // 3.设置商品描述id,与商品的id一对一。**因为是持久态,goods保存后已经有id**
        goods.getGoodsDesc().setGoodsId(goods.getId());
        // 4.调用商品描述mapper保存,往商品描述表(tb_goods_desc)添加数据
        goodsDescMapper.insertSelective(goods.getGoodsDesc());

        // 保存SKU具体商品表中的数据
        saveItems(goods);

    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(e);
    }
}

在插入商品表后,人为制造一个异常。运行程序后,新增商品数据,观察运行结果。

通过观察发现,程序发生异常,商品表仍然会存储记录,这是不符合我们要求的。这是因为我们目前的系统还没有配置事务。

注解式事务解决方案

配置文件

修改pinyougou-mapper工程的applicationContext-mapper.xml

xml
<!-- 配置数据源事务管理器(DataSourceTransactionManager) -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置开启事务注解驱动,由于dubbo用@service注解暴露服务,
    业务层需要加事务会产生代理对象,需设置proxy-target-class为true采用cglib产生代理 -->
<tx:annotation-driven transaction-manager="transactionManager"
                      proxy-target-class="true"/>

在类上添加注解

java
@Service(interfaceName = "com.pinyougou.sellergoods.service.GoodsService")
@Transactional(readOnly = false, rollbackFor = RuntimeException.class)
public class GoodsServiceImpl implements GoodsService {
    ......
}

说明:经过测试,我们发现,系统发生异常,商品表不会新增记录,事务配置成功。

  • readOnly=false:该服务类中所有方法CRUD都可以做
  • readOnly=true:该服务类中所有方法只能做查询
  • rollbackFor=RuntimeException.class:该服务类中所有方法出现RuntimeException异常时事务才有效

Nginx静态资源服务器

  • 存在问题:商品图片访问不了

http://image.pinyougou.com/jd/wKgMg1qzyiiAIACHAAN_6S46sOo724.jpg

  • Nginx静态资源服务器,项目中所有的静态资源(css、js、images、html),不需要经过tomcat服务器,用Nginx直接进行正向处理:
  • 操作步骤:
    1. 拷贝【资料\商品图片.zip】到E盘并解压
    2. 访问域名:http://image.pinyougou.com
    3. 配置hosts:127.0.0.1 image.pinyougou.com
    4. 配置Nginx反向代理:
json
server {
    listen       80;
    server_name  image.pinyougou.com;

    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header Host $host;

    location / {
        root E:\pyg;
    }
}

I know where dry desert ends, green grass grows · MooN