品优购_day05

品优购_day05

1. 面包屑导航

效果如下图:

点击查询下级时,会根据当前分类的id(即下级分类的parentId)查询下级分类,第一级分类的parentId为0。设置级别是为了控制面包屑导航的显示。

1.1 item_cat.html

绑定面包屑:

<a ng-click="grade=1;findAll({id:0})" >顶级分类</a> 
<a ng-click="grade=2;findAll(entity2)" >{{entity2.name}}</a>
<a ng-click="grade=3;findAll(entity3)" >{{entity3.name}}</a>

数据显示及按钮绑定:

<tr ng-repeat="entity in list">
	<td><input ng-click="updateSelection($event,entity.id)" type="checkbox" ></td>			                              
	<td>{{entity.id}}</td>
    <td>{{entity.name}}</td>									    
	<td>{{entity.typeId}}</td>									      
	<td class="text-center">	
<span ng-if="grade!=3">                                     
    <button type="button" class="btn bg-olive btn-xs" ng-	click="setGrade(grade+1);findAll(entity)" >查询下级</button> 
</span>			                                     
	</td>
</tr>

页面初始化时,查询parentId为0的分类,ng-init="findAll({id:0})。

之所以查询下级时传递的是entity,而不是entity.id,是因为要把entity赋值给面包屑实体,面包屑可以获取分类名称。

使用ng-if="grade!=3",当级别不等于3时,才显示查询下级按钮。

1.2 itemController.js

	//设置面包屑级别
	$scope.grade=1;//默认值第一级
	$scope.setGrade=function(grade){
		$scope.grade=grade;
	}
	//查询时记录上级parentId,默认值为0
	$scope.pId=0;
	
    //根据parentId查询数据 
	$scope.findAll=function(entity){
		//查询时记录上级parentId,方便新增分类时使用
		$scope.pId=entity.id;
		if($scope.grade==1){
			//如果为第一级,则entity2和3为null
			$scope.entity2=null;
			$scope.entity3=null;
		}
		if($scope.grade==2){
			//如果为二级,则entity2为entity,3为null
			$scope.entity2=entity;
			$scope.entity3=null;
		}
		if($scope.grade==3){
			//如果为第三级,则entity3为entity,entity2不变
			$scope.entity3=entity;
		}
		itemCatService.findAll(entity.id).success(
			function(response){
				$scope.list=response;
			}			
		);
	}    

1.3 新增分类

​ 当前显示的是哪一分类的列表,我们就将这个商品分类新增到这个分类下。

​ 实现思路:我们需要itemController.js设置一个变量去记住parentId,在保存的时候再根据parentId来新增分类:在执行itemCatService.add( $scope.entity )之前设置新增分类的parentId,$scope.entity.parentId=$scope.pId。

1.4 修改分类时类型模板不显示

​ 绑定修改按钮:

<button type="button" class="btn bg-olive btn-xs" ng-click="findOne(entity.id)" data-toggle="modal" data-target="#editModal" >修改</button>                                     

执行查询分类信息方法,虽说修改分类信息要将全部类型模板查出,但只执行findOne(entity.id)时当前类型模板也不显示,alert时也明明查到了typeId,所以应该是html页面的问题。

再加上findTypeTemplateList()才显示要修改的分类的类型模板。

<button type="button" class="btn bg-olive btn-xs" ng-click="findOne(entity.id);findTypeTemplateList()" data-toggle="modal" data-target="#editModal" >修改</button>       

2. 电商概念SPU与SKU

SPU = Standard Product Unit (标准产品单位)
SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

例如: iphone7就是一个SPU,与商家,与颜色、款式、套餐都无关。

SKU=stock keeping unit(****库存量单位)
SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。

例如:纺织品中一个SKU通常表示:规格、颜色、款式。

3. 商品录入:商品名称、副标题、价格、包装列表、售后服务

3.1 组合实体类

public class GoodsAndGoodsDesc implements Serializable {
	private TbGoods goods;//商品SPU
	private TbGoodsDesc goodsDesc;//商品扩展信息
	private List<TbItem> itemList;//SKU列表
    //...getXXX/setXXX
}

3.2 数据访问层

由于我们需要在商品表添加数据后可以得到自增的id,所以我们需要在TbGoodsMapper.xml中的insert配置中添加如下配置:

<selectKey resultType="java.lang.Long" order="AFTER" keyProperty="id">
    SELECT LAST_INSERT_ID() AS id
</selectKey>

3.3 服务层

@Override
public void add(GoodsAndGoodsDesc gd) {
    gd.getGoods().setAuditStatus("0");//设置商品为未申请状态
    //先往Tb_goods中插入数据
    goodsMapper.insert(gd.getGoods());
    //设置商品id,根据往Tb_goods中插入数据获取的id,再往Tb_goods_Desc中插入数据
    gd.getGoodsDesc().setGoodsId(gd.getGoods().getId());
    goodsDescMapper.insert(gd.getGoodsDesc());//插入商品扩展数据
}

3.4 控制层

GoodsController:

public Result add(@RequestBody GoodsAndGoodsDesc gd){
		//设置商家id,商家id即为商家后台登录者的用户名
		String sellerId=SecurityContextHolder.getContext().getAuthentication().getName();
		gd.getGoods().setSellerId(sellerId);
		......
	}

4. 商品录入:商品介绍

实现商品介绍的录入,要求使用富文本编辑器。富文本编辑器,Rich Text Editor, 简称 RTE, 它提供类似于 Microsoft Word 的编辑功能。常用的富文本编辑器:KindEditor, UEditor, CKEditor。

4.1 初始化kindeditor编辑器

在商品录入页面中添加js代码,用于初始化kindeditor:

<script type="text/javascript">
	var editor;
	KindEditor.ready(function(K) {
		editor = K.create('textarea[name="content"]', {
			allowFileManager : true
            //allowFileManager:是否允许浏览服务器已上传文件,默认值是false 
		});
	});
</script>

name="context"对应录入框name值:

<div class="col-md-2 title editer">商品介绍</div>
<div class="col-md-10 data editer">
<textarea name="content" ng-model="entity.goodsDesc.introduction" style="width:800px;height:400px;visibility:hidden;" ></textarea>
</div>

4.2 操作kindeditor编辑器的内容

  • 提取kindeditor编辑器的内容

    在goodsController.js中的add()方法中添加:

$scope.entity.goodsDesc.introduction=editor.html();
  • 清空kindeditor编辑器的内容
editor.html('');

5. 分布式文件服务器FastDFS

5.1 什么是FastDFS

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 服务器上,Storage server 没有实现自己的文件系统而是利用操作系统 的文件系统来管理文件。可以将storage称为存储服务器。

服务端两个角色:

Tracker:管理集群,tracker 也可以实现集群,每个 tracker 节点地位平等,收集 Storage 集群的状态。

Storage:实际保存文件 Storage 分为多个组,每个组之间保存的文件是不同的,每个组内部可以有多个成员,组成员内部保存的内容是一样的,组成员的地位是一致的,没有主从的概念。

5.2 文件上传及下载的流程

  • 文件上传流程

客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。

文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

如:group1/M00/00/00/wKgZhV69FTuAByuRADyOX_epkOA75.jpeg

  1. 组名(group1)

    文件上传后所在的 storage 组名称,在文件上传成功后由 storage 服务器返回,需要客户端自行保存。

  2. 虚拟磁盘路径(M00)

    storage 配置的虚拟路径,与磁盘选项 store_path对应。如果配置了store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。

  3. 数据两级目录(00/00)

    storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。

  4. 文件名(wKgZhV69FTuAByuRADyOX_epkOA75.jpeg)

    与文件上传时不同,是由存储服务器根据特定信息生成,文件名包含:源存储服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

  • 文件下载流程

5.3 最简单的FastDFS架构

5.4 FastDFS安装

FastDFS 安装步骤非常繁琐,我们在课程中不做要求,提供单独的《FastDFS安装部署文档》供课后阅读。为了能够快速的搭建FastDFS环境进行代码开发,我们这里提供了安装好的镜像。

解压“资源/Linux镜像/fastDFS/pinyougou-image-server.zip”,双击vmx文件,然后启动。

注意:遇到下列提示选择“我已移动该虚拟机”!

IP地址已经固定为192.168.25.133 ,请设置你的仅主机网段为25。

登录名为root,密码为itcast。

5.5 FastDFS入门demo

需求:将本地图片上传至图片服务器,再控制台打印url。

  1. 创建Maven工程fastDFSdemo

    由于FastDFS客户端jar包并没有在中央仓库中,所以需要使用命令手动安装jar包到Maven本地仓库,课程配套的本地仓库已经有此jar包,此步可省略。

mvn install:install-file -DgroupId=org.csource.fastdfs -DartifactId=fastdfs  -Dversion=1.2 -Dpackaging=jar -Dfile=d:\setup\fastdfs_client_v1.20.jar

​ pom.xml引入:

<dependency>
	    <groupId>org.csource.fastdfs</groupId>
	    <artifactId>fastdfs</artifactId>
	    <version>1.2</version>
	</dependency>
  1. 添加配置文件fdfs_client.conf ,服务器地址设为192.168.25.133
tracker_server=192.168.25.133:22122
  1. 创建fastDFSdemo.java
public class fastDFSdemo {
	public static void main(String[] args) throws FileNotFoundException, IOException, Exception {
		//1.加载fastDFS配置文件,配置文件的内容为tracker服务的地址
ClientGlobal.init("D:\\eclipse.workspace\\18_1fastDFSdemo\\src\\main\\resources\\fastDFS_client_conf");//物理地址
		//为了避免但斜杠被当作转义符,可换为双斜杠
		
		//2.创建一个TrackerClient对象
		TrackerClient trackerClient=new TrackerClient();
		//3.使用TrackerClient对象创建连接,获取一个TrackerServer对象
		TrackerServer trackerServer=trackerClient.getConnection();
		//4.创建一个StorageServer的引用,值为null
		StorageServer storageServer=null;
		//5.创建一个StorageClient对象,需要两个参数:TrackerServer,StorageServer的引用
		StorageClient storageClient=new StorageClient(trackerServer,storageServer);
		//6.使用StorageClient对象上传图片
		String local_filename="D:\\360chromeDownloads\\1.jpg";//本地文件名
		String file_ext_name="jpg";//文件扩展名
		NameValuePair[] meta_list=null;//文件扩展信息
		String[] strs=storageClient.upload_file(local_filename, file_ext_name, meta_list);
		//返回的是文件索引信息
		
		//7.输出上传结果
		for(String str:strs) {
			System.out.println(str);
			/*
			 * group1
			   M00/00/00/wKgZhV63WX2AQ5RkABe8kPS3KPU774.jpg
			   可通过下面地址访问到该图片:
http://192.168.25.133/group1/M00/00/00/wKgZhV63WX2AQ5RkABe8kPS3KPU774.jpg
			 */
		}
	}
}

6. 商品录入:图片

6.1 工具类

将“资源/fastDFS/工具类”的FastDFSClient.java 拷贝到pinyougou-common工程,pinyougou-common工程pom.xml引入依赖:

<dependency>
    <groupId>org.csource.fastdfs</groupId>
    <artifactId>fastdfs</artifactId>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
</dependency>	

6.2 配置文件

  1. 将“资源/fastDFS/配置文件”文件夹中的 fdfs_client.conf 拷贝到pinyougou-shop-web工程config文件夹

  2. 在pinyougou-shop-web工程application.properties添加配置

 FILE_SERVER_URL=http://192.168.25.133/  

此配置用于UploadController中注入fileServerUrl:

@Value("${FILE_SERVER_URL}")
private String fileServerUrl;
  1. 在pinyougou-shop-web工程springmvc.xml添加配置:
<!-- 配置多媒体解析器 -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.
CommonsMultipartResolver">
    <property name="defaultEncoding" value="UTF-8"></property>
    <!-- 设定文件上传的最大值5MB,5*1024*1024 -->
    <property name="maxUploadSize" value="5242880"></property>
</bean>

6.3 控制层

@RestController
public class UploadController {
	@Value("${FILE_SERVER_URL}")
	private String fileServerUrl;//文件服务器地址

	@RequestMapping("/upload")
	public Result upload(MultipartFile file) {
		//1.获取文件全名称
		String originalFileName=file.getOriginalFilename();
		//2.获取文件扩展名(不包含·)
		String extName=originalFileName.substring(originalFileName.lastIndexOf(".")+1);
		try {
			//3.创建FastDFSClient
			FastDFSClient fastDFSClient= new FastDFSClient("classpath:config/fdfs_client.conf");
			//4.执行上传
			String fileId=fastDFSClient.uploadFile(file.getBytes(), extName);
			//5.拼接FileServerUrl和fileId,用于回显图片
			String url=fileServerUrl+fileId;
			return new Result(true,url);
		} catch (Exception e) {
			e.printStackTrace();
			return new Result(false,"上传失败");
		}	
	}
}

6.4 前端

6.4.1 uploadService.js

app.service("uploadService",function($http){
	this.upload=function(){
		var formData=new FormData();//表单数据,html5新增的类,用于文件上传
		formData.append('file',file.files[0]);
		/*添加file属性;file:文件上传框的name,页面可能有很多上传框,
		 * 都叫file,由于该页面只有一个上传框,所以取第一个即可。
		 */
		return $http({
			url:'../upload.do',
			method:'POST',
			data:formData,
			headers:{'Content-Type':undefined},
			transformRequest:angular.identity
			//固定写法,对表达数据进行二进制序列化
		});
	}
});

anjularjs对于post和get请求默认的Content-Type header 是application/json。通过设置‘Content-Type’: undefined,这样浏览器会帮我们把Content-Type 设置为 multipart/form-data。

通过设置 transformRequest: angular.identity ,anjularjs transformRequest function 将序列化我们的formdata object。

(将uploadService服务注入到goodsController 中,在goods_edit.html引入js)

6.4.2 goods_edit.html

绑定参数,修改新建按钮,每次新建时清空上次遗留的数据:ng-click="img_entity={}"。

6.4.2 goodsController.js

  • 上传图片
$scope.upload=function(){
    uploadService.upload().success(
        function(response){
            if(response.flag){
                $scope.img_entity.url=response.message;
            }else{
                alert(response.message);
            }
        });
}
  • 添加图片到列表
$scope.entity={goods:{},goodsDesc:{itemImages:[]}};//定义页面实体结构
//增加图片数据到列表
$scope.addImg_entity=function(){
    $scope.entity.goodsDesc.itemImages.push($scope.img_entity);
}

​ 修改goods_edit.html上传窗口的保存按钮:

​ ng-click="addImg_entity()"

  • 移除图片

    只是从列表从删除了图片,并没有从服务器中真正地将图片删除。

$scope.delImg_entity=function(index){
    $scope.entity.goodsDesc.itemImages.splice(index,1);
}

​ 修改goods_edit.html上传窗口的删除图片按钮:

​ ng-click="delImg_entity($index)"

posted @ 2020-05-16 21:42  ALiWang1123  阅读(211)  评论(0编辑  收藏  举报