以本产品的上传商品图片为例,它包含了”上传图片后的预览”、”清空预览图片”、”保存图片"三个部分。上传图片后,先将图片文件临时存放到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"/>