电商项目day07(基于fastDFS的图片上传&商家录入扩展属性&规格)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangwei_620/article/details/84973299

今日目标:

1、掌握fastDFS分布式上传文件的原理

2、项目中商品图片的上传

3、扩展属性的完成

4、规格属性的完成

一、基于fastDFS的文件上传原理

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

2.角色的作用

Tracker:管理集群,它自己也可以实现集群,每个tarcker地位平等,收集Storager的集群状态。

Storager:实际的把保存文件,Storager分为多个组,每个组保存的文件是不同的,每个组内可以有多个成员,组内的成员保存的内容是一致的,没有主从概念。

3.文件上传流程

         客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。
        组名:文件上传后所在的 storage 组名称,在文件上传成功后有 storage 服务器返回,需要客户端自行保存。
虚拟磁盘路径:storage 配置的虚拟路径,与磁盘选项 store_path*对应。如果配置了store_path0 则是 M00,如果配置了 store_path1 则是 M01,以此类推。
      数据两级目录:storage 服务器在每个虚拟磁盘路径下创建的两级目录,用于存储数据文件。
       文件名:与文件上传时不同。是由存储服务器根据特定信息生成,文件名包含:源存储服务器 IP 地址、文件创建时间戳、文件大小、随机数和文件拓展名等信息。

4、文件的下载流程

5.入门案列

         1.解压搭建的分布式的fastDFS的服务器,选择我已移动,然后导包,编写配置配置文件fdfs_client.conf

         2.编写Java类

          一共六步

public class FastDFS {
    public static void main(String[] args) throws Exception {
//        1、加载配置文件,配置文件中的内容就是 tracker 服务的地址。ClientGlobal加载
        ClientGlobal.init("D:\\IdeaProjects\\projectAll\\fastDFS_Test\\src\\main\\resources\\fdfs_client.conf");
//        2、创建一个 TrackerClient 对象。直接 new 一个
        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[] strings = storageClient.upload_file("图片的存储路径","jpg",null);
//        7、返回数组。包含组名和图片的路径
        for (String string : strings) {
            System.out.println(string);
        }
    }
}

二、新增商品的基本信息录入

1.图片上传

分析:通过分布式文件系统,实现上传,通过angularjs实现异步上传,局部刷新页面,

后端实现:导入jar包     fastDFS     commons-fileupdaload   

 编写配置文件   

记得在web.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>

  通过工具类获得

编写controller的UploadConroller

@RestController
@RequestMappering("/upload")
public class UploadController {
    /**
     * 文件上传controller
     */
    @Value("${FILE_SERVER_URL}")
    private String FILE_SERVER_URL;//文件服务器的地址

    @RequestMapping("/uploadFile")
    public Result uploadFile(MultipartFile file){

        //1.获取文件的扩展名
        String originalFilename = file.getOriginalFilename();
        //2.通过字符串截取的方法进行获得
        String extName = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        //3.创建一个fastDFS的客户端
        try {
            FastDFSClient fastDFSClient = new FastDFSClient("classpath:config/fdfs_client.conf");
            //4.执行上传的处理
            String path = fastDFSClient.uploadFile(file.getBytes(), extName);
            //5.拼接返回的url和ip地址,拼接成完整的url
            String url = FILE_SERVER_URL+path;
            //6.返回结果数据
            return new Result(true,url);
        } catch (Exception e) {
            e.printStackTrace();
            return new Result(false,"上传失败");
        }
    }

}

前台分析:

      1.文件上传三要素  file post enctype
       2.本项目中 基于angularjs和html5实现对象文件上传

因为没有form表单提交,我们采用angularjs和html结合实现文件上传

      3.编写server层  和contrller层

service层代码

//服务层
app.service('uploadService',function($http){
	    	

	//基于angularjs结合H5实现文件上传
	this.uploadFile=function () {
		//创建H5的表单对象
		var formData = new FormData();
		//参数一:表单提交值的名称,与后端接受的参数名是一致的
		//参数二:要提交的文件对象 file.files[0] 第一个file指的是<input type="file" id="file"/> 标签的id值
		formData.append("file",file.files[0]);
		return $http({
			//提交方式
			method:"post",
			//提交路径
			url:"../upload/uploadFile.do",
			//数据就是formData
			data:formData,
			//浏览器会帮我们把 Content-Type 设置为 multipart/form-data.
            headers: {'Content-Type':undefined},
			//anjularjs transformRequest function 将序列化我们的formdata object.
            transformRequest: angular.identity
		})
    }

});

contrller层代码

//上传图片
	$scope.uploadFile=function () {
		uploadService.uploadFile().success(function (response) {
			//如果上传成功获得url
			if (response.success){
				//设置文件地址
				$scope.image_entity.url=response.message;
			}else{
				alert(response.message);
			}
        }).error(function () {
			alert("上传发生错误");
        })
    }

页面展示:

在颜色中绑定

ng-model="image_entity.color"    
上传添加点击事件: 
ng-click="uploadFile()"

src属性赋值

src="{{image_entity.url}}"

上传页面我们如何封装数据?

[{"color":"红色","url":"http://192.168.25.133/group1/M00/00/01/wKgZhVmHINKADo__AAjlKdWCzvg874.jpg"},{"color":"黑色","url":"http://192.168.25.133/group1/M00/00/01/wKgZhVmHINyAQAXHAAgawLS1G5Y136.jpg"}]

由这个我们分析出,是封装了一个实体类进行存储的也就是在image_entity

新建和删除功能实现

//初始化entity
	$scope.entity={tbGoods:{},tbGoodsDesc:{itemImages:[],specificationItems:[]},items:[]}
	//添加上传图片到商品列表中
	$scope.addImageEntity=function () {
        $scope.entity.tbGoodsDesc.itemImages.push($scope.image_entity);
    }
    //从列表中删除
    $scope.deleImageEntity=function (index) {
        $scope.entity.tbGoodsDesc.itemImages.splice(index,1)
    }

页面实现:

首先点击新建初始化:

ng-click="image_entity={}"
<tr ng-repeat="image in entity.tbGoodsDesc.itemImages">
									            <td>
									            	{{image.color}}
									            </td>
									            <td>
									           		<img alt="" src="{{image.url}}" width="100px" height="100px">
									            </td>
												 <td> <button type="button" ng-click="deleImageEntity($index)" class="btn btn-default" title="删除" ><i class="fa fa-trash-o"></i> 删除</button></td>
					                      </tr>

2、扩展属性

分析:通过模板添加自定义属性,custom_attribute_items,注意在我们监控typeTemplete下做

$scope.entity.tbGoodsDesc.customAttributeItems = JSON.parse(response.customAttributeItems);

3.规格功能的完成

通过模板id关联的规格,找到规格然后通过id找到关联的规格选项

模板表中关联规格列表数据:[{"id":27,"text":"网络"},{"id":32,"text":"机身内存"}]
    数据结构:
        [
            {
              "id":27,
              "text":"网络"
              "options":[ //规格选项列表
                {}//规格选项对象
              ]
            }
        ]

所以我们要通过这个方法查询我们需要的这写些数据,有两个方法封装数据,一种是在写一个实体类,里面有我们需要的这些属性,但是,很麻烦,我们只能通过List<Map>   这个格式来封装数据并返回

后台实现:

//通过模板的id值获得我们规格列表数据

	@RequestMapping("/findSpecList")
	public List<Map> findSpecList(Long id){
	 return typeTemplateService.findSpecList(id);
	}
service层
@Override
	public List<Map> findSpecList(Long id) {
		////通过模板的id值获得我们规格列表数据
		TbTypeTemplate tbTypeTemplate = typeTemplateMapper.selectByPrimaryKey(id);
		//获取模板关联的规格列表
		/*[{"id":27,"text":"网络"},{"id":32,"text":"机身内存"}]*/
		String specIds = tbTypeTemplate.getSpecIds();
		//将json的字符串转化为lsit列表
		List<Map> specList = JSON.parseArray(specIds, Map.class);
		for (Map map : specList) {
			//根据id查询关联的规格选项
			//获取id  通过加""转化字符串
			Long specId = Long.parseLong(map.get("id")+"");
			TbSpecificationOptionExample example = new TbSpecificationOptionExample();
			TbSpecificationOptionExample.Criteria criteria = example.createCriteria();
			criteria.andSpecIdEqualTo(specId);
			List<TbSpecificationOption> options = specificationOptionMapper.selectByExample(example);
			map.put("options",options);
		}
		return specList;
	}

前台的展示:

goodsController层
//初始化entity
	$scope.entity={tbGoods:{},tbGoodsDesc:{itemImages:[],specificationItems:[]},items:[]}
	//添加上传图片到商品列表中
	$scope.addImageEntity=function () {
        $scope.entity.tbGoodsDesc.itemImages.push($scope.image_entity);
    }
    //从列表中删除
    $scope.deleImageEntity=function (index) {
        $scope.entity.tbGoodsDesc.itemImages.splice(index,1)
    }
页面
<div class="row data-type">
										<!--[{"id":27,"text":"网络"},{"id":32,"text":"机身内存"}]-->
										<!--获取所有的规格-->
										<div ng-repeat="spec in specList">
											<div class="col-md-2 title">{{spec.text}}</div>
											<div class="col-md-10 data">
												<!--编写当前规格下的所有规格选项-->
					                            <span ng-repeat="option in spec.options">
					                            	<input  type="checkbox" ng-click="updateSpecAttribute($event,spec.text,option.optionName)" >{{option.optionName}}
					                            </span>

											</div>
										</div>
									</div>

4.规格和规格选项数据的组装到tb_goodsDesc下的specification_item集合中,方便我们进行sku的列表动态展示

分析:首先我们分析,通过点击勾选和取消勾选来,动态组装数据,

[{"attributeName":"网络","attributeValue":["移动3G","移动4G"]},    {"attributeName":"机身内存","attributeValue":["16G"]}]

业务分析:[ ]                   页面一加载应该是一个空数组,

首先判断规格名称是否存在勾选的规格列表中

             如果不存在       

                          创建对象

              如果存在添加

                        判断是勾选选项还是取消勾选规格选项

                                   如果是勾选选项

                                                 在原有的规格选项数组中,添加勾选的规格选项

                                   如果是取消勾选

                                                在原有的规格选项数组中,移除取消的规格选项

                                  如果取消该规格对应的所有规格选项数组,则从规格列表中移除该规格数据

前台代码实现:

考虑到是这个勾选和取消勾选是一个通用的方法,我们把他写在baseController中,

//基于数组中对象的值  获取该对象返回
    //[{"attributeName":"网络","attributeValue":["移动3G","移动4G"]}]
	//list是传入的列表数组   key是attributeName  是根据传入值决定的    value 是对应的值,比如网络
	$scope.getObjectByKey = function (list,key,value) {
		for (var i=0;i<list.length;i++){
			//存在对象时
			if (list[i][key]==value){
				return list[i];
			}
		}
		//不存在则返回空值
		return null;
    }

goodsController中,注意一定要注意变量初始化的问题

 //组装商品录入勾选的的规格列表属性
	$scope.updateSpecAttribute=function ($event,specName,specOption) {
		//判断规格名称是否存在于勾选的列表中
        var specObject=  $scope.getObjectByKey($scope.entity.tbGoodsDesc.specificationItems,"attributeName",specName);
		if (specObject!=null){
			//如果存在
			//判断是勾选还是取消勾选规格选项
			if($event.target.checked){
				//勾选,在原有的规格选项数组中,添加勾选的规格选项名称
				specObject.attributeValue.push(specOption);
			}else{
				//取消勾选,在原有的规格选项数组中移除,取消勾选的规格选项名称
				var index  = specObject.attributeValue.indexOf(specOption);
				specObject.attributeValue.splice(index,1);
				//如果取消规格对应的所有规格选项,则从这个规格列表中移除该规格数据
				if(specObject.attributeValue.length<=0){
					var index1  = $scope.entity.tbGoodsDesc.specificationItems.indexOf(specObject);
					$scope.entity.tbGoodsDesc.specificationItems.splice(index1,1);
				}
			}

		}else{
            //如果不存在
            $scope.entity.tbGoodsDesc.specificationItems.push({"attributeName":specName,"attributeValue":[specOption]});
		}
    }

页面通过绑定点击事件,

<!--[{"id":27,"text":"网络"},{"id":32,"text":"机身内存"}]-->
<!--获取所有的规格-->
<div ng-repeat="spec in specList">
   <div class="col-md-2 title">{{spec.text}}</div>
   <div class="col-md-10 data">
      <!--编写当前规格下的所有规格选项-->
                       <span ng-repeat="option in spec.options">
                           <input  type="checkbox" ng-click="updateSpecAttribute($event,spec.text,option.optionName)" >{{option.optionName}}
                       </span>

   </div>
</div>

        

三、错误分析

没有初始化变量,导致页面无法显示

四、总结

1.基于fastDFS的分布式文件上传

2.规格列表的动态展示,注意一定要弄清8张表之间的关系

猜你喜欢

转载自blog.csdn.net/wangwei_620/article/details/84973299