以本產品的上傳商品圖片為例,它包含了”上傳圖片后的預覽”、”清空預覽圖片”、”保存圖片"三個部分。上傳圖片后,先將圖片文件臨時存放到session中,如果需要保存圖片,再從session中獲取到文件並保存到服務器。
1、 上傳圖片預覽
上傳圖片預覽實現了上傳圖片,並且在頁面顯示圖片,此時還沒有在服務器完成修改操作:
(1) 上傳圖片HTML代碼。
<div class="commodityPicture layui-upload">
<div class="layui-upload-list">
<p class='explain'>支持100k大小的<br />jpg、png、jpeg格<br />式圖片</p>
<img class="imgStyle" id="commImage">
</div>
<button type="button" class="layui-btn layui-btn-sm" id="uploadImage">上傳商品圖片</button>
<button type="button" class="layui-btn layui-btn-sm" id="cleanImage">清空商品圖片</button>
</div>
(2) 上傳圖片js代碼。
使用到了Layui框架的upload模塊。upload.render函數設置請求后端的url、允許的圖片類型、圖片大小等信息:
//上傳商品圖片
var uploadCommImage = upload.render({
elem: '#uploadImage',
url: commPictureUpload_url,
accept: 'images',
acceptMime: 'image/jpg, image/jpeg, image/png',
exts: 'jpg|jpeg|png',
size: 100,
before: function (obj) {
//預讀本地文件示例,不支持ie8
obj.preview(function (index, file, result) {
$('#commImage').attr('src', result); //圖片鏈接(base64
});
},
done: function (res) {
bDesertLastOperation = false;
$("input[name='lastOperationToPicture']").val(LAST_OPERATION_TO_PICTURE_Upload);
if (res.ERROR == "EC_NoError") { //上傳成功
var msg = res.msg == "" ? "上傳圖片成功" : res.msg;
return layer.msg(msg);
} else { //上傳失敗
var msg = res.msg == "" ? "上傳圖片失敗" : res.msg;
return layer.msg(msg);
}
},
error: function (index, upload) {
//演示失敗狀態,並實現重傳
var demoText = $('#hintText');
demoText.html('<span style="color: #FF5722;">上傳失敗</span><a class="layui-btn layui-btn-xs demo-reload">重試</a>');
demoText.find('.demo-reload').on('click', function () {
uploadCommImage.upload();
});
}
});
(3) 后端接收前端傳遞的圖片。
使用了Spring的MultipartFile來接收上傳的圖片文件:
/** 上傳圖片到D:/nbr/pic/private_db/companyX/tempXXXXXX.jpg(png/jpeg),臨時保存之。
* 此時,還未確定圖片的名字,所以命名帶有temp字眼。這個臨時文件的路徑和MultipartFile對象被保存在會話中,給后面的操作使用
* 之后,用戶可能創建、修改、刪除商品,這個臨時文件將會被處理,比如重命名為138.jpg,對應商品F_ID=138 */
@RequestMapping(value = "/uploadPictureEx", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> uploadPictureEx(@ModelAttribute("SpringWeb") Commodity commodity, ModelMap model, HttpServletRequest req, @RequestParam("file") MultipartFile file) {
if (!canCallCurrentAction(req.getSession(), BaseAction.EnumUserScope.STAFF.getIndex())) {
logger.debug("無權訪問本Action");
return null;
}
(4) 清空session中的商品圖片信息。
上傳的圖片文件還有它應存放的位置信息我們是把它們存放在session當中的,所以如果之前有上傳過,需要先清空:
// 清空會話,確保session不受污染
req.getSession().removeAttribute(EnumSession.SESSION_CommodityPictureDestination.getName());
req.getSession().removeAttribute(EnumSession.SESSION_PictureFILE.getName());
(5) 檢查文件的類型、大小。
String fileName = file.getOriginalFilename();
String picType = "image/" + fileName.substring(fileName.lastIndexOf(".") + 1);
if (!picType.equals(JPEGType) && !picType.equals(PNGType) && !picType.equals(JPGType)) {
logger.error("商品圖片的類型不對。當前類型為:" + picType);
map.put(BaseAction.JSON_ERROR_KEY, EnumErrorCode.EC_OtherError.toString());
map.put(KEY_HTMLTable_Parameter_msg, "商品圖片的類型必須是.jpg/.jpeg/.png格式");
break;
}
Long size = file.getSize();
ErrorInfo ecOut = new ErrorInfo();
ConfigGeneral commodityLogoVolumeMax = (ConfigGeneral) CacheManager.getCache(company.getDbName(), EnumCacheType.ECT_ConfigGeneral).read1(BaseCache.CommodityLogoVolumeMax, getStaffFromSession(req.getSession()).getID(), ecOut,
company.getDbName());
if (size <= 0 || size > Long.parseLong(commodityLogoVolumeMax.getValue())) {
logger.error("商品圖片大小不能超過100K。當前size=" + size);
map.put(KEY_HTMLTable_Parameter_msg, "商品圖片大小不能超過100K");
(6) 檢查磁盤的空間大小。
/** 檢查磁盤空間是否足夠和存儲路徑是否存在 */
public static boolean checkDiskSpaceAndCreateFolder(Map<String, Object> params, String path) {
File diskPath = new File(Disk);
if (!diskPath.exists()) {
logger.error("該路徑不存在:" + diskPath);
params.put(BaseAction.KEY_HTMLTable_Parameter_msg, "服務器錯誤");
params.put(BaseAction.JSON_ERROR_KEY, EnumErrorCode.EC_OtherError.toString());
return false;
}
ErrorInfo ecOut = new ErrorInfo();
BxConfigGeneral extraDiskSpaceSize = (BxConfigGeneral) CacheManager.getCache(BaseAction.DBName_Public, EnumCacheType.ECT_BXConfigGeneral).read1(BaseCache.EXTRA_DISK_SPACE_SIZE, BaseBO.SYSTEM, ecOut, BaseAction.DBName_Public);
long minUsablePatitionSpace = 1;
logger.info("磁盤可用空間為:" + diskPath.getUsableSpace() / (1024 * 1024 * 1024) + "GB");
if (diskPath.getUsableSpace() - Integer.valueOf(extraDiskSpaceSize.getValue()) < minUsablePatitionSpace) {
logger.error("磁盤空間不足!");
params.put(BaseAction.KEY_HTMLTable_Parameter_msg, "服務器錯誤");
params.put(BaseAction.JSON_ERROR_KEY, EnumErrorCode.EC_OtherError.toString());
return false;
}
// 判斷文件夾是否存在,如果不存在,創建相應的文件夾,String1傳回來DBName
File uploadDir = new File(path);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
return true;
}
(7) 設置圖片的存放位置。
String path = BaseAction.CommodityPictureDir;
logger.info("商品圖片上傳路徑:" + path);
(8) 定義圖片的臨時名稱。
根據文件的類型定義文件的臨時名稱pictureName,如temp12345.jpg:
// 判斷是jpg還是png類型,上傳名稱。如果多個線程在這里寫,會存在隱患:出現相同的臨時圖片文件,不同的商品引用相同的圖片
String pictureName = "";
if (picType.contains(JPGType)) {
pictureName = "temp" + System.currentTimeMillis() % 1000000 + ".jpg";
} else if (picType.contains(PNGType)) {
pictureName = "temp" + System.currentTimeMillis() % 1000000 + ".png";
} else {
pictureName = "temp" + System.currentTimeMillis() % 1000000 + ".jpeg";
}
(9) 根據存放位置構建File對象。
File commodityPictureDestination = new File(path + "/" + company.getDbName() + "/" + pictureName);
(10) 將圖片信息保存在session中。
req.getSession().setAttribute(EnumSession.SESSION_CommodityPictureDestination.getName(), commodityPictureDestination);
req.getSession().setAttribute(EnumSession.SESSION_PictureFILE.getName(), file);
(11) 前端效果圖。
2、 保存圖片
(1) 更新數據庫圖片位置信息。
從session中獲取圖片文件位置信息,設置商品對象圖片位置,最后更新數據庫對應商品的圖片位置字段:
Commodity commodity = (Commodity) baseModel;
// ... 截取參數和handleCreateEx()中類似。
File dest = (File) req.getSession().getAttribute(EnumSession.SESSION_CommodityPictureDestination.getName());
// 處理商品圖片字段
int lastOperationToPicture = commodity.getLastOperationToPicture();
switch (lastOperationToPicture) {
case LAST_OPERATION_TO_PICTURE_Upload:
if (dest != null) {
StringBuilder filePath = new StringBuilder(dest.getPath().replaceAll("\\\\", "/"));// filePath一定形如d:/nbr/pic/nbr_MYJ/18.jpg
for (int i = 0; i < DIR.length(); i++) {
filePath.setCharAt(i, Character.toLowerCase(filePath.charAt(i)));// 確保下面的替換真正替換到東西
}
logger.debug("filePath=" + filePath.toString());
String commPictureDir = filePath.toString().replaceAll(DIR, "/p");
commodity.setPicture(commPictureDir);
}
break;
case LAST_OPERATION_TO_PICTURE_None:
commodity.setPicture(FLAG_WillNotUpdatePictureInSP);
break;
case LAST_OPERATION_TO_PICTURE_Clear:
(2) 從session中拿到MultipartFile file文件對象。
// 上傳圖片(如果有)
MultipartFile file = (MultipartFile) req.getSession().getAttribute(EnumSession.SESSION_PictureFILE.getName());
if (dest != null && file != null) {
renameCommodityPicture(req, dbName, bmFromDB, dest, file, ec);
}
(3) 判斷磁盤是否已經有該圖片文件,如果有則刪除。
// 現在傳入的文件是否已經存在,如果有刪除該文件
do {
File file5 = new File(BaseAction.CommodityPictureDir + dbName + "/" + bm.getID() + ".jpg");
if (file5.exists()) {
file5.delete();
break;
}
file5 = new File(BaseAction.CommodityPictureDir + dbName + "/" + bm.getID() + ".jpeg");
if (file5.exists()) {
file5.delete();
break;
}
file5 = new File(BaseAction.CommodityPictureDir + dbName + "/" + bm.getID() + ".png");
if (file5.exists()) {
file5.delete();
break;
}
} while (false);
(4) 將上傳的圖片保存到磁盤中。
file.transferTo(dest);
(5) 重命名文件名稱。
此時的文件名還是臨時文件名temp12345.jpg,根據商品ID將臨時文件重命名:
String commodityPicturePath = BaseAction.CommodityPictureDir + dbName + "/" + bm.getID() + commodityPictureType;
File companyBusinessLicensePictureDestination1 = new File(commodityPicturePath);
dest.renameTo(companyBusinessLicensePictureDestination1);
(6) 清空session中的圖片信息。
最后清空掉session中的圖片信息,釋放內存:
// 上傳圖片成功刪除對應session
req.getSession().removeAttribute(EnumSession.SESSION_CommodityPictureDestination.getName());
req.getSession().removeAttribute(EnumSession.SESSION_PictureFILE.getName());
3、清空圖片
(1) 清空圖片js代碼。
//清除商品圖片
$("#cleanImage").click(function () {
$.ajax({
url: commPictureDelete_url,
type: method_post,
async: true,
dataType: "json",
data: {},
success: function succFunction (data) { //此接口后端不會返回錯誤碼
$('.explain').show();
$('#commImage').attr('src', ' ');
bDesertLastOperation = false;
$("input[name='lastOperationToPicture']").val(LAST_OPERATION_TO_PICTURE_Clear);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
layer.msg(XMLHttpRequest.status + ":" + XMLHttpRequest.statusText);
layer.msg("清空商品圖片失敗");
}
})
})
(2) 清空圖片后端代碼,清空session中的圖片信息。
logger.info("清空商品圖片的session");
session.removeAttribute(EnumSession.SESSION_CommodityPictureDestination.getName());
session.removeAttribute(EnumSession.SESSION_PictureFILE.getName());
(3) 更新數據庫圖片信息。
清空圖片后,如果要保存修改,后端就將商品圖片設置為空,並更新數據庫。
case LAST_OPERATION_TO_PICTURE_Clear:
case LAST_OPERATION_TO_PICTURE_None:
default:
commodity.setPicture("");
break;
}
List<List<BaseModel>> bmFromDB = null;
DataSourceContextHolder.setDbName(dbName);
bmFromDB = commodityBO.createObjectEx(getStaffFromSession(req.getSession()).getID(), iUseCaseID, baseModel);
(4) 配置虛擬映射路徑。
如果在tomcat服務器上部署,並且想把代碼和上傳的圖片分離開,那么需要配置tomcat虛擬映射路徑才能訪問到圖片。在pom.xml文件中配置了tomcat虛擬映射路徑:
<!-- tomcat虛擬映射路徑 -->
<!-- 更改公司的營業執照路徑時需要同時更改config.sql中的CompanyBusinessLicensePictureDir的路徑 -->
<!-- 更改商品的圖片路徑時需要同時更改config.sql中的CommodityLogoDir的路徑 -->
<staticContextPath>/p</staticContextPath>
<staticContextDocbase>D:/nbr/pic</staticContextDocbase>
<contextReloadable>false</contextReloadable>
<useTestClasspath>true</useTestClasspath>
新建p.xml,配置映射,path 為虛擬目錄 docBase 為實際目錄:
<Context crossContext="true" reloadable="true" debug="0" workDir="D:/nbr/pic/" docBase="D:/nbr/pic/" path="/p"/>