第一次使用bootstrap fileinput碰到了許多坑,做下記錄
需求
- 本次使用bootstrap fileinput文件上傳組件,主要用來上傳和預覽圖片。作為一個后台管理功能,為某個表的某個字段,設置1對n的圖片記錄
- 網上搜索相關文章大多是一個簡單的上傳功能,對圖片文件預覽顯示,前后端交互並沒多少詳細描述
實現功能
- 后台界面例子
2. 在新增和編輯里,需要添加圖片上傳顯示需求,在這里我設置的字段名以_img結尾的圖片都會在編輯新增里顯示bootstrap fileinput組件
3. 點擊選擇,選擇文件后會變成一下情況
多出個上傳按鈕,圖片也會多幾個按鈕,我選擇了刪除和放大圖片的按鈕,還可以顯示圖片單獨上傳按鈕,這里我把它去掉了,統一在下方點擊上傳時,全部上傳。這里重點說下,我選擇的異步上傳方式,選擇多個圖片,上傳后台組件采用的是多個圖片輪詢一張一張上傳
4. 點擊放大按鈕
5. 點擊刪除按鈕,會調用刪除方法,點擊上傳按鈕,顯示如下:
6. 若本來就保存了圖片,在點擊修改的時候,我需要回顯圖片,顯示如下
在沒有繼續上傳圖片的時候,它不會顯示上傳按鈕,圖片左下角小圖標會有所變化
7. 點擊保存后
8. 以上為需要實現的具體需求,下面分析代碼
問題和解決方案
前端
- 此處只列出關鍵代碼
html:
<#elseif modifyField["field_name"]?ends_with("_img")?string == "true">
<div class="form-group">
<label class="control-label">${modifyField["field_description"]}:</label>
<!-- 用來作為form表單提交上傳圖片返回id的集合,這里我采用`,`隔開的字符串形式保存 -->
<input type="text" hidden id="${modifyField["field_name"]}" name="${modifyField["field_name"]}" value=""/>
<!-- 用來初始化上傳組件 -->
<input type="file" name="file" id="${modifyField["field_name"]}_uploadfile" multiple="multiple" class="file-loading" />
</div>
<#else>
js:
/**
* 銷毀圖片上傳組件
* @param initUrl
*/
function destroyUploadImg(){
//這里我用jquery找到我約定的上傳組件,使用_uploadfile結尾,並遍歷銷毀
$("#dlg input[id$='_uploadfile']").each(function(index,html){
var upfile = $(html);
upfile.fileinput("destroy");
});
}
/**
* 初始化fileinput組件
*/
var getFileInput = function(){
//初始化方法,同樣找到約定組件遍歷
$("#dlg input[id$='_uploadfile']").each(function(index,html){
var upfile = $(html);
var imgId = upfile.attr("id").substring(0,upfile.attr("id").lastIndexOf("_"))
var val = $("#"+imgId).val();
var preImageList = val.split(",");
var initialPreview = [];
var initialPreviewConfig = [];
for (var i = 0; i < preImageList.length; i++) {
if(preImageList[i] === "") {
preImageList.splice(i,1);
continue
}
initialPreview.push("${request.contextPath}/showPic?image_id=" + preImageList[i] + "&count=" + Math.random())
var previewConfig = new Object();
previewConfig.url = "${request.contextPath}/deleteImg.do";
previewConfig.key = preImageList[i];
var imageId = preImageList[i];
previewConfig.extra = {"image_id": imageId,"pRow":JSON.stringify(pRow),"currPageId":${curr_page_id},"updateCol":imgId};//上傳的額外參數,會作為post請求參數提交
initialPreviewConfig.push(previewConfig);
}
if(upfile.length > 0) {
//元素存在時執行的代碼
upfile.fileinput({
theme: 'fa',
allowedFileExtensions: ['jpg', 'png', 'gif'],//接收的文件后綴
// maxFileSize:0,
language: 'zh', //設置語言
uploadUrl: "${request.contextPath}/uploadImg.do", //上傳的地址
uploadAsync: true, //默認異步上傳
uploadExtraData:{"currPageId":${curr_page_id},"pRow":JSON.stringify(pRow),"colName":imgId},//上傳額外的post請求參數
// showUpload: false, //是否顯示上傳按鈕
showRemove : false, //顯示移除按鈕
showPreview : true, //是否顯示預覽
showCaption: false,//是否顯示標題
browseClass: "btn btn-primary", //按鈕樣式
dropZoneEnabled: true,//是否顯示拖拽區域
maxFileCount: 10, //表示允許同時上傳的最大文件個數
enctype: 'multipart/form-data',
validateInitialCount:true,
overwriteInitial: false,//是否在上傳下一個文件的時候覆蓋前一個
initialPreviewAsData: true,//實現初始化預覽
removeFromPreviewOnError:true,//碰到上傳錯誤的文件,不顯示在框內
layoutTemplates:{
actionUpload:'' //設置為空可去掉上傳按鈕
//actionDelete:'' //設置為空可去掉刪除按鈕
},
initialPreview: initialPreview,//初始化預覽文件的地址集合
initialPreviewConfig:initialPreviewConfig//初始化預覽文件配置
}).on('filepreupload', function(event, data, previewId, index) { //上傳中
var form = data.form, files = data.files, extra = data.extra,
response = data.response, reader = data.reader;
console.log('文件正在上傳');
}).on("fileuploaded", function (event, data, previewId, index) { //一個文件上傳成功
console.log('文件上傳成功!');
if(data.response['result'] === "success"){
var imageId = data.response['imageId'];
preImageList.push(imageId);
var preImageStr = preImageList.join(",");
$("#"+imgId).val(preImageStr);
}else{
return false;
}
}).on('fileerror', function(event, data, msg) { //一個文件上傳失敗
console.log('文件上傳失敗!');
}).on('filesuccessremove', function(event, id) {
console.log('文件刪除');
}).on('filedeleteerror', function(event, id) {
console.log('文件刪除錯誤');
}).on('filedeleted', function(event, key,data) {
//刪除返回結果
$("#"+imgId).val(data.responseJSON["imageIdStr"]);
$('#dg').datagrid('reload');
});
}
});
};
明白怎么回事,使用起來還是蠻簡單的,就簡單的兩個創建和銷毀方法,注釋寫的也蠻詳細了,除了業務邏輯,組件的必要注釋都在了
2. 重點的地方
- 文件上傳只要填寫上傳地址和額外參數
- 在fileuploaded方法中做上傳完畢的業務邏輯
- 文件刪除只需要在預覽配置里加上刪除的地址和額外參數,新增的不管有沒有上傳的文件,刪除的僅僅是前端
- 在filedeleted方法中做刪除完畢的業務邏輯
后端
- 先上代碼段
/**
* 顯示圖片
* @param request
* @return
* @throws Exception
*/
@RequestMapping(value = {"/showPic.do", "showPic"})
public ResponseEntity<byte[]> showPic(HttpServletRequest request) throws Exception {
HashMap<String, Object> hashMap = this.getHashMap(request);
String imageId = (String) hashMap.get("image_id");
Map<String, Object> imgConditions = new HashMap<>();
imgConditions.put("image_id", imageId);
Map<String, Object> imgInfo = tableService.getRecord(Arrays.asList("image_path", "uuid", "image_type"), TableConstant.TB_IMG, imgConditions);
String imageType = (String) imgInfo.get("image_type");
String fullPath = imgInfo.get("image_path") + (String) imgInfo.get("uuid") + "." + imageType;
FileInputStream inputStream = new FileInputStream(fullPath);
int available = inputStream.available();
byte[] data = new byte[available];
inputStream.read(data);
inputStream.close();
HttpHeaders he = new HttpHeaders();
he.setContentType(MediaType.valueOf("image/" + imageType));
log.info("imageId:" + imageId + "讀取");
return new ResponseEntity<>(data, he, HttpStatus.OK);
}
/**
* 上傳圖片
* @param file
* @return
*/
@RequestMapping(value = "/uploadImg.do")
public @ResponseBody
Map<String, Object> upload(HttpServletRequest request,@RequestParam("file") MultipartFile file) {
HashMap<String, Object> hashMap = this.getHashMap(request);
String originalFilename = file.getOriginalFilename();
Map<String, Object> pRowMap = JSON.parseObject(hashMap.get("pRow").toString(), Map.class);
String colName = (String) hashMap.get("colName");
Map<String, Object> result = new HashMap<>();
result.put("result", "fail");
if (StringUtils.isNotEmpty(originalFilename)) {
String[] splitFileName = originalFilename.split("\\.");
String uuid = UUIDUtil.getUUID();
Map<String, Object> rowMap = new HashMap<>();
if (!StringUtils.isEmpty(originalFilename)) {
splitFileName = originalFilename.split("\\.");
String refPageId = (String) hashMap.get("currPageId");
Map<String, Object> pageCondition2 = new HashMap<>();
pageCondition2.put("page_id", refPageId);
pageCondition2.put("del_flag", 0);
Map<String, Object> refPageInfo = tableService.getRecord(Arrays.asList("tb_name", "primary_key"), TableConstant.TB_PAGE, pageCondition2);
String tbName = (String) refPageInfo.get("tb_name");
String[] tbNameSpl = tbName.split("_");
String middlePath = tbNameSpl[tbNameSpl.length - 1];
String filePath = sysconfig.getProperties().get("image.path") +
"/" + middlePath +
"/" + DateUtil.format2str("yyyyMMdd") +
"/";
rowMap.put("image_type", splitFileName[splitFileName.length - 1]);
rowMap.put("image_path", filePath);
rowMap.put("uuid", uuid);
rowMap.put("image_name", originalFilename);
rowMap.put("page_id", refPageId);
Long incr = redisService.incr(sysconfig.getProperties().get("sys.id") + "_primary_key_" + refPageInfo.get("tb_name") + "_" + refPageInfo.get("primary_key"), 1);
rowMap.put("image_id", incr);
rowMap.put("col_name", colName);
String primaryKey = (String) refPageInfo.get("primary_key");
String pk = String.valueOf(pRowMap.get(primaryKey));
rowMap.put("ref_value", pk);
int ret = 0;
try {
ret = tableService.addRecord(TableConstant.TB_IMG, rowMap);
} catch (Exception e) {
String message = e.getMessage();
if (message.contains("Duplicate entry")) {
log.info("主鍵沖突,redis刷新主鍵");
List<Map<String, Object>> recordsBySQL = tableService.getRecordsBySQL(String.format("select %s from %s order by %s desc limit 1", "image_id", TableConstant.TB_IMG, "image_id"));
for (Map<String, Object> objectMap : recordsBySQL) {
redisService.set(sysconfig.getProperties().get("sys.id") + "_primary_key_" + TableConstant.TB_IMG + "_" + "image_id", String.valueOf(Integer.parseInt(objectMap.get("image_id").toString())));
}
incr = redisService.incr(sysconfig.getProperties().get("sys.id") + "_primary_key_" + TableConstant.TB_IMG + "_" + "image_id", 1);
rowMap.put("image_id", incr);
ret = tableService.addRecord(TableConstant.TB_IMG, rowMap);
}
}
if (ret > 0) {
log.info(String.format("插入圖片記錄imageId:%s成功", incr));
try {
boolean b = FileUtil.uploadFile(file.getBytes(), filePath, uuid + "." + splitFileName[1]);
if (b) {
log.info(String.format("插入圖片imageId:%s成功", incr));
result.put("result", "success");
result.put("imageId", incr);
} else {
log.info(String.format("插入圖片imageId:%s失敗", incr));
}
} catch (Exception e) {
e.printStackTrace();
}
} else {
log.info(String.format("插入圖片記錄imageId:%s失敗", incr));
}
}
return result;
}
return result;
}
/**
* 圖片刪除
*/
@RequestMapping(value = "deleteImg.do")
@ResponseBody
public Map<String, Object> deleteImg(HttpServletRequest request) {
Map<String, Object> data = new HashMap<>();
HashMap<String, Object> hashMap = this.getHashMap(request);
String imageId = (String) hashMap.get("image_id");
String pageId = (String) hashMap.get("currPageId");
String updateCol = (String) hashMap.get("updateCol");
Map<String, Object> pRowMap = JSON.parseObject(hashMap.get("pRow").toString(), Map.class);
List<Map<String, Object>> records = tableService.getRecordsBySQL(String.format("select image_id,image_path,uuid,image_type from t_uls_image where image_id=%s", imageId));
String imageIdStr = "";
for (Map<String, Object> record : records) {
String imagePath = (String) record.get("image_path");
String uuid = (String) record.get("uuid");
String imageType = (String) record.get("image_type");
String path = imagePath + "/" + uuid + "." + imageType;
File file = new File(path);
if (file.exists()) {
boolean delete = file.delete();
if (delete) {
log.info(String.format("圖片imageId:%s刪除成功", imageId));
// 查找哪張表
Map<String, Object> tableCondition = new HashMap<>();
tableCondition.put("page_id", pageId);
Map<String, Object> pageInfo = tableService.getRecord(Arrays.asList("tb_name", "primary_key"), TableConstant.TB_PAGE, tableCondition);
String tbName = (String) pageInfo.get("tb_name");
String primaryKey = (String) pageInfo.get("primary_key");
String pk = String.valueOf(pRowMap.get(primaryKey));
List<Map<String, Object>> recordsBySQL = tableService.getRecordsBySQL(String.format("select %s from %s where %s='%s'", updateCol, tbName, primaryKey, pk));
for (Map<String, Object> objectMap : recordsBySQL) {
String o = String.valueOf(objectMap.get(updateCol));
String[] split = o.split(",");
List<String> strings = new ArrayList<>(Arrays.asList(split));
if(strings.contains(imageId)){
strings.remove(imageId);
}
imageIdStr = StringUtils.join(strings, ",");
}
tableService.updateRecordsBySQL(String.format("update %s set %s='%s' where %s='%s'", tbName, updateCol, imageIdStr, primaryKey, pk));
} else {
log.info(String.format("圖片imageId:%s刪除失敗", imageId));
}
}
}
data.put("data", "success");
data.put("imageIdStr", imageIdStr);
return data;
}
- 后端和代碼架構相關了,大致參考下,遇到的問題有
- 已上傳的圖片有多張的情況,顯示不正確,也就是同時請求后端顯示圖片的時候可能報各種異常,包括不知道哪來的NULLPointException
- 之后發現,還是代碼架構的問題,繼承類把HttpServletRequest request封裝成了全局變量,並從中取出了hashmap參數封裝。導致在並發的時候,hashmap被覆蓋
- 后端主要為框架設計的邏輯,和業務結合,根據實際情況編寫,主要三大塊,上傳,讀取,刪除,問題不大
注意事項
- 上傳和刪除操作,后端返回的一定要是json數據,否則會解析錯誤,就算后台上傳成功,前台也顯示失敗
補充
- 后續需要實現上傳圖片后沒提交表單,立即刪除。組件只提供了刪除回調函數。但我需要將上傳文件綁定刪除地址以便調用后台刪除方法
- 參考