圖片上傳-下載-刪除等圖片管理的若干經驗總結3-單一業務場景的完整解決方案

小雷FansUnion發表於2015-10-27
這次完整地介紹圖片上傳的完整解決方案,如有bug,後續再補充。


一、圖片表
CREATE TABLE `photo` (
  `id` bigint(10) unsigned NOT NULL AUTO_INCREMENT,
  `bizid` bigint(11) NOT NULL DEFAULT '-1' COMMENT '業務id,比如專案的id',
  `cover` int(11) DEFAULT '0' COMMENT '1:是,0:不是',
  `sort` int(11) DEFAULT '0' COMMENT '越小越靠前',
  `url` varchar(200) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL COMMENT '圖片的原檔名',
  `remark` varchar(255) DEFAULT NULL COMMENT '圖片備註',
  `status` int(11) DEFAULT '0' COMMENT '0:正常,1:已刪除,2:臨時的',
  `type` int(11) DEFAULT '1' COMMENT '1:專案資料 2:待續',
  `addtime` datetime DEFAULT NULL,
  `uptime` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=102 DEFAULT CHARSET=utf8 COMMENT='使用者上傳的圖片';




關鍵欄位
id:資料庫主鍵
bizid:相關業務的id,比如某個專案project的主鍵id
type:相關業務的型別,比如type=1表名這個圖片是某個專案的
status:這個圖片的狀態,0:正常狀態,1:已刪除(現在沒有什麼用,因為圖片是物理刪除的),2:臨時的


圖片物理刪除,是考慮到磁碟空間容易不足。
不應該物理刪除的理由是,今後可能會還原或者其它業務需要。


如果是邏輯刪除,要注意“邏輯刪除”和“臨時上傳的垃圾圖片”物理刪除是需要分開考慮的。
而如果是物理刪除,則可以合併考慮。


二、圖片上傳後端程式碼
@RequestMapping("uploadImg")
	public void uploadImg(MultipartHttpServletRequest request,
			HttpServletResponse response, Long bizid,Integer type) {
		MultipartFile file = request.getFile("file");
		MultipartFileValidator validator = new MultipartFileValidator();
		validator.setAllowedContentTypes(new String[] { "image/jpeg",
				"image/pjpeg", "image/png", "image/x-png" });
		String path = null;
		String name = null;
		try {
			if (null != file && !file.isEmpty()) {
				name = file.getOriginalFilename();
				validator.validate(file);
				path = storeFile(file);
				storeSlaveFile(file, path);
			} else {
				throw new Exception("file is empty");
			}
		} catch (Exception e) {
			e.printStackTrace();
			super.jsonFail(response, "圖片格式不正確或大小超過1M");
			return;
		}


		Long id = null;
		if (bizid != null) {
			// 臨時圖片存起來
			Photo photo = new Photo();
			photo.setName(name);
			photo.setUrl(path);
			photo.setBizid(bizid);
			photo.setStatus(2);
			photo.setType(type);
			id = photoService.add(photo);
		}
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("code", ErrorCode.OK.getCode());
		jsonObject.put("path", path);
		jsonObject.put("name", name);
		if (id != null) {
			jsonObject.put("id", id);
		}
		super.returnJsonObject(response, jsonObject);
	}


 1.儲存圖片
 2.儲存photo資訊
 3.返回新增圖片的id、路徑等資訊給前端
 
 三、前端圖片上傳
 
function bindSaveEvent(){
		console.log("bindSaveEvent");	
		$("#save").on("click", function() {
			var trs=$(".tr");
			var photos = new Array();
			$.each(trs,function(i,n){
				var tr = $(trs[i]);
				var photo ={};
				//newid是資料庫中的id
				var id=tr.attr("id");
				var newid=tr.attr("newid");
				photo.id=newid;
				photo.cover=$("#"+id+"-cover").val();
				photo.sort=$("#"+id+"-sort").val();
				photo.remark=$("#"+id+"-remark").val();
				photo.url=$("#"+id+"-img").attr("path");
				photo.name=$("#"+id+"-name").val();
				console.log("id="+id);
				photos[i]=photo;
			});
			var json=JSON.stringify(photos);
			console.log("id=${photoVo.project.id}"+",photos="+json);
			$.ajax( {    
			    url:'project/savePhoto',
			    data:{    
			    	id : ${photoVo.project.id},
			    	photos: json
			    },    
			    type:'post',    
			    cache:false, 
			    async: true,
			    dataType:'json',    
			    success:function(data) {
			    	//alert(data.itemName);
			    	//nameStr = data.itemName;
			    	console.log("data="+data);
			    },    
			     error : function() {    
			         alert("儲存圖片失敗!");   
			    }    
			});  
		});
	}


1.採用非同步上傳,把一條圖片資訊的id、name等資訊,傳給後端儲存。
2.一次性傳入多個圖
 存在“增加”和“刪除”按鈕。
 
function bindAddEvent(){
		console.log("bindAddEvent");
		$("#add").on("click", function() {
			var strTemplate=
				'<tr id="{photo.id}" newid="" class="tr">'+
			'<td><input id="{photo.id}-name" type="text" value="" /></td>'+
			'<td><select id="{photo.id}-sort" class="sort">'+					
					'<option value="1">1</option>'+
					'<option value="2">2</option>'+
					'<option value="3">3</option>'+
					'<option value="4">4</option>'+
					'<option value="5">5</option>'+
					'<option value="6">6</option>'+
					'<option value="7">7</option>'+
					'<option value="8">8</option>'+
					'<option value="9">9</option>'+
					'<option value="10">10</option>'+
					'<option value="0">0</option>'+
			'</select></td>'+
			'<td><select id="{photo.id}-cover" owner="{photo.id}"'+
				'class="cover">'+				
					'<option value="0">否</option>'+
					'<option value="1">是</option>'+
			'</select></td>'+
			'<td><input type="text" id="{photo.id}-remark" value="" /></td>'+
			'<td><input type="file" id="{photo.id}-file" name="file"'+
				'style="width: 264px" onchange="uploadImg({photo.id});"><img'+
				' id="{photo.id}-img" src="" path=""'+
				' width="80%"></td>'+
			'<td><a id="{photo.id}-del" owner="{photo.id}"'+
				'href="javascript:;" class="del">刪除</a></td>'+
		'</tr>';
			var photoId = new Date().getTime();
			var html = strTemplate.replace(/{photo.id}/g,photoId);
			var tbody = $("#tbody");
			tbody.append(html);
			console.log(html);
			//必須為新生成的物件,重新繫結事件
			bindDelEvent();
			bindCoverChangeEvent();
		});
	
	}


點選增加按鈕,就多生成1個上傳圖片的控制元件。
點選刪除按鈕,就根據id刪除某個圖片上傳空間。

四、圖片儲存
 
public void savePhoto(HttpServletResponse response, @RequestParam Long id,String photos) {
		List<Photo> list=JSONArray.parseArray(photos, Photo.class);
		projectService.savePhoto(id,list);
		super.jsonSucceed(response);
	}


id是所屬專案的id,photos是前端所有圖片的資訊(json格式)

儲存過程:
public void savePhoto(Long id, List<Photo> list) {
		if (CollectionUtils.isEmpty(list)) {
			logger.error("The photo list is empty~");
			return;
		}


		// 這個專案資料庫中的圖片,包括所有的狀態
		List<Photo> dbList = photoDao.getPhotoListByProjectIdAllStatus(id);
		PhotoBean photoBean = handlePhoto(list, dbList);


		//理論上,不再存在add doAdd(id, photoBean.toAdd);
		doUpdate(id, photoBean.toUpdate);
		doDelete(id, photoBean.toDelete);
	}
	
	doUpdate和doDelete批量更新和批量刪除方法,很清晰,不再贅述。
	
	//PhotoBean的結構
	class PhotoBean {
	//將要刪除的,通常是資料庫中的
	public List<Photo> toDelete;
	//將要更新的,都在資料庫中,部分最新內容來源於web前端
	public List<Photo> toUpdate;
	//將要增加的,由於上傳圖片的時候都已經插入了,這個時候可以忽略了
	//public List<Photo> toAdd;
	}
	
	 //根據前端photo集合和資料庫photo集合,得到需要更新和需要刪除的photo集合,不存在需要增加的photo集合
	private PhotoBean handlePhoto(List<Photo> list, List<Photo> dbList) {
		PhotoBean bean = new PhotoBean();
		// 全部刪除,什麼圖片都沒有上傳
		if (CollectionUtils.isEmpty(list)) {
			bean.toDelete = dbList;
		}
		// 全部增加,一般在第1次
	/*	if (CollectionUtils.isEmpty(dbList)) {
			bean.toAdd = list;
		}*/
		// 都不為null
		if (list != null && dbList != null) {
			// 交集,肯定不為null,最多是empty,id相同就是共同存在
			List<Photo> commonList = ListUtils.retainAll(list, dbList);


			// 2者交集
			List<Photo> toUpdate = commonList;
            //資料庫中的臨時圖片,狀態需要改為“正常”
			if(CollectionUtils.isNotEmpty(toUpdate)){
				for(Photo p:toUpdate){
					p.setStatus(0);
				}
			}
			
			// 在list,不再dbList中的
	/*		List<Photo> toAdd = new ArrayList<Photo>();
			for (Photo p : list) {
				if (!dbList.contains(p)) {
					toAdd.add(p);
				}
			}*/
			// 在dbList,不在list中的
			List<Photo> toDelete = new ArrayList<Photo>();
			for (Photo p : dbList) {
				if (!list.contains(p)) {
					toDelete.add(p);
				}
			}
			//bean.toAdd=toAdd;
			bean.toUpdate=toUpdate;
			bean.toDelete=toDelete;
		}
		return bean;


	}



重寫Photo的equals方法,id相等則相等。

五、寫在最後
    1.由於個人喜歡在本地儲存完整的文章,不喜歡圖片,另外CSDN的相簿也不怎麼好用。
      我寫的文章很少出現圖片,大家湊合著看。
    2.由於圖片管理是完整專案的一部分,不方便上傳完整程式碼。
 先記下來,最近抽空,單獨開一個工程,演示圖片上傳。
3.多圖分開上傳,是因為專案中的圖片可以有 備註remark、排序sort等很多欄位。
如果只需要url等少量欄位的話,可以採用百度的WebUploader多圖上傳元件。
4.程式碼中,存在前端jQuery、後端Java程式碼。
5.圖片的物理儲存,可以存到本地,也可以用Fastdfs。
6.過幾天單獨建立多圖演示專案的時候,我打算簡化點,儲存圖片不用Fastdfs。
再單獨搞個專案,演示Fastdfs的用法。
7.有需要的人士,自己參考整合多圖上傳到Fastdfs。
8.多圖上傳,真不是一個簡單的問題,至少花了3個完整的工作日。

相關文章