目标
- 完成商品分类功能
- 了解电商概念SPU和SKU
- 掌握富文本编辑器的使用
- 掌握上传服务器FastDFS
- 掌握angularJS图片上传
1. 商品分类
1. 需求分析
实现三级商品分类列表查询功能
首先显示一级分类;点击列表行的查询下级按钮,进入下级分类列表,同时更新面包屑导航
最后三级分类,不再显示查询下级按钮,同时更新面包屑导航
点击面包屑导航,可以返回操作。
2. 后端实现
1. 修改sellergoods-interface的ItemCatService接口
新增方法
// 根据上级ID查询商品分类列表
public List<TbItemCat> findByParentId(Long parentId);
2. 修改ItemCatServiceImpl
@Override
public List<TbItemCat> findByParentId(Long parentId) {
TbItemCatExample example = new TbItemCatExample();
Criteria criteria = example.createCriteria();
criteria.andParentIdEqualTo(parentId);
return itemCatMapper.selectByExample(example);
}
3. 修改ItemCatController.java
// 根据上级ID查询商品分类列表
@RequestMapping("/findByParentId")
public List<TbItemCat> findByParentId(Long parentId) {
return itemCatService.findByParentId(parentId);
}
4. 测试
3. 前端实现
1. 修改itemCatService.js
// 根据上级id查询商品分类信息
this.findByParentId = function (parentId) {
return $http.get('../itemCat/findByParentId.do?parentId='+parentId);
}
2. 修改itemCatController.js
// 根据上级ID查询列表
$scope.findByParentId = function (parentId) {
itemCatService.findByParentId(parentId).success(
function (response) {
$scope.list=response;
}
);
}
3. 修改item_cat.html
引入js
<script src="../plugins/angularjs/angular.min.js"></script>
<script type="text/javascript" src="../js/base.js"></script>
<script type="text/javascript" src="../js/service/itemCatService.js"></script>
<script type="text/javascript" src="../js/controller/itemCatController.js"></script>
<script type="text/javascript" src="../js/controller/baseController.js"></script>
指令定义
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="itemCatController" ng-init="findByParentId(0)">
循环列表
<tbody>
<tr ng-repeat="entity in list">
<td><input type="checkbox" ></td>
<td>{{entity.id}}</td>
<td>{{entity.name}}</td>
<td>
{{entity.typeId}}
</td>
<td class="text-center">
<button type="button" class="btn bg-olive btn-xs" ng-click="findByParentId(entity.id)">查询下级</button>
<button type="button" class="btn bg-olive btn-xs" data-toggle="modal" data-target="#editModal" >修改</button>
</td>
</tr>
</tbody>
4. 面包屑
1. 分析思路:
面包屑分为三级列表,其中第一个为顶级分类列表,第二个为具体单个一级分类entity_1,第三个为具体单个二级分类entity_2.
按照级别,
当前级别grade=1时,entity_1=null; entity_2=null;
当前级别grade=2时,entity_1=entity(所点击的实体); entity_2=null;
当前级别grade=3时,entity_2=entity;
2. 实现
修改itemCatController.js
$scope.grade = 1;//默认级别
//设置级别
$scope.setGrade = function(value){
$scope.grade = value;
};
// 读取列表
$scope.selectList = function (p_entity) {
if($scope.grade==1){//如果1级,上级为顶级
$scope.entity_1=null;
$scope.entity_2=null;
}
if($scope.grade==2){//如果2级,上级为1级分类
$scope.entity_1=p_entity;
$scope.entity_2=null;
}
if($scope.grade==3){
$scope.entity_2=p_entity;
}
$scope.findByParentId(p_entity.id);//查询此级下级列表
}
修改列表的查询下级按钮,设定级别值后,显示列表
<button type="button" class="btn bg-olive btn-xs" ng-click="setGrade(grade+1);selectList(entity)">查询下级</button>
使用ng-if指令,级别不等于3时,显示此按钮
<span ng-if="grade!=3">
<button type="button" class="btn bg-olive btn-xs" ng-click="setGrade(grade+1);selectList(entity)">查询下级</button>
</span>
绑定面包屑
<ol class="breadcrumb">
<li>
<a href="#" ng-click="grade=1;selectList({id:0})">顶级分类列表</a>
</li>
<li>
<a href="#" ng-click="grade=2;selectList(entity_1)" >{{entity_1.name}}</a>
</li>
<li>
<a href="#" ng-click="grade=3;selectList(entity_2)">{{entity_2.name}}</a>
</li>
</ol>
5. 新增商品分类
当前显示哪一级,商品分类新增到该分类下
实现思路:需要一个变量记住上级id,保存时根据这个id来新增分类
下拉框的实现:类型模板需要获取其name
下拉框的其他前端后端代码都做好了,但是下拉框做不下去,因为分类的上一级只有一个模板id,不能下拉。
<table class="table table-bordered table-striped" width="800px">
<tr>
<td>上级商品分类</td>
<td>
{{entity_1.name}} >> {{entity_2.name}}
</td>
</tr>
<tr>
<td>商品分类名称</td>
<td><input class="form-control" ng-model="entity.name" placeholder="商品分类名称"> </td>
</tr>
<tr>
<td>类型模板</td>
<td>
<input ng-model="entity.typeId" placeholder="商品类型模板" class="form-control" type="text" />
</td>
</tr>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-success" data-dismiss="modal" aria-hidden="true" ng-click="save()">保存</button>
controller的save方法
//保存
$scope.save=function(){
var serviceObject;//服务层对象
if($scope.entity.id!=null){//如果有ID
serviceObject=itemCatService.update( $scope.entity ); //修改
}else{
$scope.entity.parentId = $scope.parentId;// 赋予上级id
serviceObject=itemCatService.add( $scope.entity );//增加
}
serviceObject.success(
function(response){
if(response.success){
//重新查询
$scope.findByParentId($scope.parentId);//重新加载
}else{
alert(response.message);
}
}
);
}
6. 修改商品分类
<button type="button" class="btn bg-olive btn-xs" data-toggle="modal" data-target="#editModal" ng-click="findOne(entity.id)">修改</button>
7. 删除商品分类
第三级分类才可以删除,其余的不能删
<span ng-if="grade==3">
<button type="button" class="btn btn-default" title="删除" ng-click="dele(entity.parentId)"><i class="fa fa-trash-o"></i> 删除</button>
</span>
传递parentId给controller
//批量删除
$scope.dele=function(parentId){
//获取选中的复选框
itemCatService.dele( $scope.selectIds ).success(
function(response){
if(response.success){
$scope.findByParentId(parentId);//刷新列表
}
}
);
}
2. 电商概念和表结构分析
1. SPU和SKU
1. SPU
SPU=Standard Product Unit(标准产品单位)
是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的聚合。该信息描述了一个产品的特性。
通俗点:属性值、特性相同的商品就可以称为一个SPU
例如:iPhone7就是一个SPU,与商家,与颜色、款式、套餐都无关。
2. SKU
SKU=stock keeping unit(库存量单位)
即库存进出计量的单位,可以是以件、盒、托盘等为单位
SKU是物理上不可分割的最小存货单元。在使用时根据不同业态、不同管理模式来处理。
在服装、鞋类商品使用最多最普遍。
如iPhone7的金色、移动、128G就是一个SKU
2. 表结构
Tb_goods商品表,就是SPU
Tb_goods_desc desc详细描述,介绍、图片、包装、售后等大文本,一般检索用不着,优化
3. 商家后台-商品录入
1. 需求分析
商家后台实现商品录入功能,包括商品名称、副标题、价格、包装列表、售后服务。
2. 后端
1. dao
在添加细节表数据时需要获取商品表自增的id,需要在TbGoodsMapper.xml中的insert配置中添加
<selectKey resultType="java.lang.Long" order="AFTER" keyProperty="id">
SELECT LAST_INSERT_ID() AS id
</selectKey>
2. pojo
创建组合实体类goods
public class Goods implements Serializable {
private TbGoods goods;//商品SPU
private TbGoodsDesc goodsDesc;//商品细节
private List<TbItem> itemList;//商品SKU列表
//...
3. sellergoods-interface
修改
public void add(Goods goods);
4. sellergoods-service
修改
@Autowired
private TbGoodsDescMapper goodsDescMapper;
/**
* 增加
*/
@Override
public void add(Goods goods) {
goods.getGoods().setAuditStatus("0");//设置未申请状态
goodsMapper.insert(goods.getGoods());
goods.getGoodsDesc().setGoodsId(goods.getGoods().getId());//设置id
goodsDescMapper.insert(goods.getGoodsDesc());//插入商品细节数据
}
5. shop-web 的GoodsController
添加商家的登录名
@RequestMapping("/add")
public Result add(@RequestBody Goods goods){
// 获取商家的登录名
String sellerId = SecurityContextHolder.getContext().getAuthentication().getName();
goods.getGoods().setSellerId(sellerId);//设置商家id
try {
goodsService.add(goods);
return new Result(true, "增加成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false, "增加失败");
}
}
注意:需要把manager-web的GoodsController的add方法删掉,运营商不用添加商品
3. 前端
1. 控制层
goodsController.js在增加成功后弹出提示,并清空实体(因为编译页面无列表)
//新增商品
$scope.add=function(){
$scope.entity.goodsDesc.introduction = editor.html();
goodsService.add($scope.entity).success(
function(response){
if(response.success){
alert("保存成功");
$scope.entity={};
editor.html("");// 清空富文本编辑器
}else{
alert(response.message);
}
}
);
}
2. goods_edit.html
引入js
<script src="../plugins/angularjs/angular.min.js"></script>
<script type="text/javascript" src="../js/base.js"></script>
<script type="text/javascript" src="../js/service/goodsService.js"></script>
<script type="text/javascript" src="../js/controller/goodsController.js"></script>
<script type="text/javascript" src="../js/controller/baseController.js"></script>
定义控制器
<body class="hold-transition skin-red sidebar-mini" ng-app="pinyougou" ng-controller="goodsController">
表单部分代码
<div class="col-md-2 title">商品名称</div>
<div class="col-md-10 data">
<input type="text" class="form-control" ng-model="entity.goods.goodsName" placeholder="商品名称" value="">
</div>
...
<div class="col-md-2 title rowHeight2x">包装列表</div>
<div class="col-md-10 data rowHeight2x">
<textarea rows="4" class="form-control" ng-model="entity.goodsDesc.packageList" placeholder="包装列表"></textarea>
</div>
保存按钮
<button class="btn btn-primary" ng-click="add()"><i class="fa fa-save" ></i>保存</button>
4. 商品介绍的录入
1. 需求分析
需要使用富文本编辑器
2. 介绍
富文本编辑器,Rich Text Editor,简称 RTE, 它提供类似于 Microsoft Word 的编辑功能。常用的富文本编辑器:
KindEditor http://kindeditor.net/
UEditor http://ueditor.baidu.com/website/
CKEditor http://ckeditor.com/
3. 使用kindeditor录入
初始化编辑器
页面添加js代码,初始化kindeditor
<!-- 正文区域 /-->
<script type="text/javascript">
var editor;
KindEditor.ready(function(K) {
editor = K.create('textarea[name="content"]', {
allowFileManager : true
});
});
</script>
allowFileManager 【是否允许浏览器服务器已上传文件】 默认值false
提取kindeditor编辑器的内容
在goodsController中的add()方法中添加
$scope.entity.goodsDesc.introduction = editor.html();
3. 清空kindeditor编辑器的内容
修改add方法
添加
editor.html("");//清空富文本编辑器
4. 分布式文件服务器FastDFS
1. 什么是FastDFS
是用c语言编写的一款开源的分布式文件系统。 FastDFS 为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用 FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。
Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到
Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。
Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storageserver
没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将storage称为存储服务器。
服务端两个角色:
tracker:管理集群,tracker 也可以实现集群。每个tracker节点低位平等,收集storage集群的状态
storage:实际保存文件。 storage分为多个组,每个组之间保存的文件是不同的。每个组每部可以有多个成员,内容一样,地位也一样。
2. 上传和下载的流程
1. 文件上传流程
客户端上传文件后存储服务器将文件ID返回给客户端,此文件ID用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
如group1 /M00 /02/44 wKgDrE34E8wAAAAAAAAGkEIYJK42378.sh
组名:文件上传后所在的storage组名称。在文件上传成功后有storage服务器返回,需要客户端自行保存。
虚拟磁盘路径:storage配置的虚拟路径。与磁盘选项storage_page*
对应。如果配置了store_path0
则是M00,如果配置store_path1
则是M01,以此类推。
数据两级目录:storage服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。
文件名:与文件上传时不同,是由存储服务器根据特定信息生成,文件名包含:源存储服务器IP地址,文件创建时间戳,文件大小、随机数和文件拓展名等信息。
2. 文件下载流程
3. 最简单的FastDFS架构
3. FastDFS的安装
安装很繁琐,直接提供安装好的镜像。
IP地址固定为192.168.25.133
,设置网段为25
登录名和密码都是itcast
4. 入门demo
需求:将本地图片上传到图片服务器,在控制台打印url
192.168.25.133
拼接图片url,查询浏览器
如http://192.168.25.133/group1/M00/00/00/wKgZhVzVR2mAQhv2ABoTXxOFCe8479.jpg
1. 创建maven工程fastDFSdemo
jar包没有在中央仓库,需要先导到本地,代码
mvn install:install-file -DgroupId=org.csource.fastdfs -DartifactId=fastdfs -Dversion=1.2 -Dpackaging=jar -Dfile=e:\fastdfs-1.2.jar
引入pom文件
<dependencies>
<dependency>
<groupId>org.csource.fastdfs</groupId>
<artifactId>fastdfs</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
添加配置文件fdfs_client.conf,将服务器地址设置为192.168.25.133
创建Java类
public class Test {
public static void main(String[] args) throws Exception{
// 1. 加载配置文件
ClientGlobal.init("D:\\java_work\\fastDFSdemo\\src\\main\\resources\\fdfs_client.conf");
// 2. 构建一个管理者客户端
TrackerClient client = new TrackerClient();
// 3. 连接管理者服务端
TrackerServer trackerServer = client.getConnection();
// 4. 声明存储服务端
StorageServer storageServer = null;
// 5. 获取存储服务器的客户端对象
StorageClient storageClient = new StorageClient(trackerServer,storageServer);
// 6.上传文件
String[] strings = storageClient.upload_file("e:\\a.jpg", "jpg", null);
// 7. 显示上传结果file_id
for (String string : strings) {
System.out.println(string);
}
}
}
5. 商家后台-商品图片上传
1. 需求分析
录入界面实现多图片上传
用户点击新建按钮,弹出上传窗口。
2. 后端代码
1. pinyougou-common工程pom.xml文件引入依赖
<dependency>
<groupId>org.csource.fastdfs</groupId>
<artifactId>fastdfs</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
2. 配置文件
将fdfs_client.conf拷贝到shop-web工程config文件夹
在shop-web工程application.properities添加配置
FILE_SERVER_URL=http://192.168.25.133/
在springmvc.xml中添加配置多媒体解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<!--设置文件上传的最大值5MB-->
<property name="maxUploadSize" value="5242880"/>
</bean>
3. 控制层
在pinyougou-shop-web新建UploadController.java
@RestController
public class UploadController {
@Value("${FILE_SERVER_URL}")
private String FILE_SERVER_URL;//文件服务器地址
@RequestMapping("/upload")
public Result upload(MultipartFile file){
// 获取文件的扩展名
String originalFilename = file.getOriginalFilename();
String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
try {
// 2. 创建一个FastDFS的客户端
FastDFSClient fastDFSClient = new FastDFSClient("classpath:config/fdfs_client.conf");
// 3. 执行上传处理
String path = fastDFSClient.uploadFile(file.getBytes(),extName);
// 4. 拼接返回的url和ip地址,拼装成完整的url
String url = FILE_SERVER_URL+path;
return new Result(true,url);
} catch (Exception e) {
e.printStackTrace();
return new Result(false,"上传失败");
}
}
}
3. 前端代码
1. 服务层
pingyougou-shop-web工程创建unloadService.js
// 文件上传的服务层
app.service("uploadService",function ($http) {
this.uploadFile = function () {
var formData = new FormData();
// <form enctype="multipart/form-data">
// <input name="file" type="file">
formData.append("file",file.files[0]);
return $http({
url:"../upload.do",
method:'POST',
data:formData,
headers:{'Content-Type':undefined},
transformRequest: angular.identity
});
}
})
angularJS对于post和get请求默认的Content-Type header是application/json。通过设置Content-Type:undefined
,这样浏览器会帮我们把Content-Type设置为multipart/form-data
,通过设置transformRequest:angular.identity
,将序列化formData object。
2. 将uploadService服务注入goodsController中
app.controller('goodsController' ,function($scope,$controller ,goodsService,uploadService){}
3. 在goods_edit.html引入js
<script src="../plugins/angularjs/angular.min.js"></script>
<script type="text/javascript" src="../js/base.js"></script>
<script type="text/javascript" src="../js/service/goodsService.js"></script>
<script type="text/javascript" src="../js/service/uploadService.js"></script>
<script type="text/javascript" src="../js/controller/goodsController.js"></script>
<script type="text/javascript" src="../js/controller/baseController.js"></script>
4. 上传图片
1. goodsController编写代码
// 上传图片
$scope.uploadFile = function () {
uploadService.uploadFile().success(
function (response) {
if(response.success){
// 上传成功,取出url
$scope.image_entity.url = response.message;// 设置文件地址
}else{
alert(response.message);
}
}
);
}
2. 修改图片上传窗口,调用上传方法,回显上传图片
<tr>
<td>颜色</td>
<td><input class="form-control" placeholder="颜色" ng-model="image_entity.color"> </td>
</tr>
<td>
<input type="file" id="file" />
<button class="btn btn-primary" type="button" ng-click="uploadFile()">
上传
</button>
</td>
<td>
<img src="{{image_entity.url}}" width="200px" height="200px">
</td>
3. 修改新建按钮
<button type="button" class="btn btn-default" title="新建" data-target="#uploadModal" data-toggle="modal" ng-click="image_entity={}"><i class="fa fa-file-o"></i> 新建</button>
5. 图片列表
1. 在goodsController.js增加方法
// 定义页面实体结构
$scope.entity={goods:{},goodsDesc:{itemImages:[]}};
// 将当前上传的图片实体存入图片列表
$scope.add_image_entity=function () {
$scope.entity.goodsDesc.itemImages.push($scope.image_entity);
}
2. 修改上传窗口的保存按钮
<button class="btn btn-success" ng-click="add_image_entity()" data-dismiss="modal" aria-hidden="true">保存</button>
3. 遍历图片列表
<table class="table table-bordered table-striped table-hover dataTable">
<thead>
<tr>
<th class="sorting">颜色</th>
<th class="sorting">图片</th>
<th class="sorting">操作</th>
</thead>
<tbody>
<tr ng-repeat="pojo in entity.goodsDesc.itemImages">
<td>
{{pojo.color}}
</td>
<td>
<img alt="" src="{{pojo.url}}" width="100px" height="100px">
</td>
<td> <button type="button" class="btn btn-default" title="删除" ng-click="remove_image_entity($index)"><i class="fa fa-trash-o"></i> 删除</button></td>
</tr>
</tbody>
</table>
4. 移除图片
在goodsController.js增加代码
// 移除图片
$scope.remove_image_entity = function (index) {
$scope.entity.goodsDesc.itemImages.splice(index,1);
}
修改列表的删除按钮
代码见上上个代码