1. 程式人生 > >電商專案day07(基於fastDFS的圖片上傳&商家錄入擴充套件屬性&規格)

電商專案day07(基於fastDFS的圖片上傳&商家錄入擴充套件屬性&規格)

今日目標:

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張表之間的關係