一,实现图片的预览。
1, 标签预留图片上传的位置。
<img id="imagePreview" style="width: 220px;height: 90px;background-size: contain;background-image: url('/file/attachment/${(entity.picture)!}/0');">
2,选择的input,可以选择图片文件。
<input id="file" type="file" style="margin-top: 3px">
3,设置隐藏的input 可以把图片数据提交到后台。
<input id="recommendPicture" type="hidden">
4,jquery 的 change事件判断有没有选择图片文件。取出文件名。
验证上传的是否是图片文件。photoValid:检查文件名是以JPG,JEPG,PNG结尾。
5,使用jQuery获取图片文件的内容。
6,processImageFile:处理图片的内容。传入图片文件的内容,把内容读成数据,读到内存,读到img标签,读到Java代码。
7,通过css把图片数据显示出来。把图片数据设置到隐藏的 input。
$("#file").change(function (evt) {
let f = $('#file').val()
if (_os.photoValid(f)){
let file = $("#file").get(0).files[0]
_os.processImageFile(file,function (r) {
$("#imagePreview").css('background-image','url('+r.image+')');
$("#imagePreview").show();
$("#recommendPicture").val(r.data)
})
}
})
8,提交 form 表单,因为图片的数据量大,form表单中使用post提交。
<form id="recommendForm" class="panel-body" action="${base}/mergeRecommend" method="post">
$("#recommendForm").submit()
二,完成图片资源的入库。
recommend picture 栏的数字= attachment的id,可以获取到对应的attachment数据
获取到attachment的 resourceId,获取到resource,获取到resource的content。
1,controller中接收前端提交的图片数据,判断前端有没有提交图片数据。有提交图片的数据:就调用方法,创建图片的资源表,附件表。方法中传入图片的数据,和哪个单元上传的图片来源名称,返回图片附件表的id。将 id 设置到对应的添加或修改的对象。
if (StringUtils.isNotBlank(recommendPicture)){
Long atId = attachmentService.createAttachment("recommendPicture",recommendPicture);
entity.setPicture(atId.toString());
}
2,在事务中创建图片的资源表,附件表。
HashUtils 获取图片的Hash值。(每一张图片的数据都有独一无二的hash值。) getByHash 查询数据库的图片资源表,判断图片资源是否已存在于数据库。不存在就创建图片资源表的数据。
@Transactional
public Long createAttachment(String name,String recommendPicture){
String hash = HashUtil.sha256(recommendPicture);
Resource r = resourceDao.getByHash(hash);
if (null == r){
r = new Resource();
r.setId(IdUtil.nextId());
r.setEncoding("Base64");
r.setHash(hash);
r.setContent(recommendPicture);
r.setCreateAt(new Date());
r.setUpdateAt(new Date());
this.resourceDao.create(r);
}
使用domain创建数据库表的数据。创建图片的资源表resource表的数据:
图片资源的id使用 IdUtils 自动生成。
编码格式使用Base64编码。
设置图片资源的hash值。
前端传过来的图片数据作为图片内容。
设置当前时间,作为创建和更新图片资源的时间。
create方法创建图片资源表数据。
3,创建图片资源的附件表。
由于前端提交过来的图片数据都是经过编码的,所以要对图片的数据进行解码,返回到char数组中,使用base64对图片数据解码。
ImageUtils 读取解码后的图片数据。图片的宽高,大小数据,返回到 ImageBean 中。
使用domain创建数据库表的数据。创建图片资源附件表attachment表。设置attachment表字段的值。create方法创建图片附件表的数据,返回attachment的id。
byte[] data = Base64.getDecoder().decode(recommendPicture);
ImageBean bean = ImageUtil.readImage(data);
Attachment a = new Attachment();
a.setResourceId(r.getId());
a.setUserId(0L);
a.setName(name);
a.setHeight(bean.height);
a.setWidth(bean.width);
a.setMime(bean.mime);
a.setSize(bean.size);
a.setCreateAt(new Date());
a.setUpdateAt(new Date());
this.entityDao.create(a);
return a.getId();
}
ImageBean封装图片宽高大小格式的数据。
public class ImageBean {
public BufferedImage image;
public int width;
public int height;
public int size;
public String mime;
}
4,在controller中判断没有图片数据传过来时,为什么不把图片的数据设为0?
@RequestMapping("/mergeRecommend")
public ModelAndView mergeRecommend(Recommend entity,String recommendPicture){
//修改以后重定向到课程推荐页面
ModelAndView mv = new ModelAndView("redirect:/manage_recommend");
//处理图片的数据
if (StringUtils.isNotBlank(recommendPicture)){
Long atId = attachmentService.createAttachment("recommendPicture",recommendPicture);
entity.setPicture(atId.toString());
}
//保存修改或添加的数据到数据库
if(null == entity.getId()){
if (StringUtils.isBlank(recommendPicture)){
entity.setPicture("0");
this.recommendService.create(entity);
}
}else {
this.recommendService.update(entity);
}
return mv;
}
因为修改时也可以不修改图片的数据,也没有图片的数据传过来,这样设置会把原先上传好的图片直接改掉,判断创建时不允许picture为空就可以了。
5,两种方式可以实现图片的加载,需不需要 ${base} 容易搞混
<img id="imagePreview" src="${base}/file/attachment/${item.picture!}/m" />
<div style="background-image: url('/file/attachment/${item.picture!}/m');background-size: contain"></div>
三,定义一个公共的,加载项目所有图片的controller,FileController。
1,FileController介绍:
id 的正则表达式:对传入的 picture 数字作限制。[0-9] 表示传入的内容必须是数字,{1~17} 表示有1 至17位数字。
通过get请求,请求(“/file/attachment/” + ID + “/0”)地址,可以直接请求到图片,在相应的位置上写上对应的url地址,就可以把图片显示出来。
但是在修改时获取图片内容,要做 if 判断,判断图片数据是否存在且不等于0。因为创建时不允许picture为空,如果用户没有上传图片,图片数据会设置成0,添加到数据库。或者如果没有图片数据,就展示一个默认的图片。
char类型 size的含义:0原图,m最小图,s小图(有些网站会对图片数据做压缩)
通过responseIO流,把图片输出出来,有输出流要抛 IOE异常。
直接拷贝使用。
@Controller
public class FileController {
protected static final String ID = "{id:[0-9]{1,17}}";
@Autowired
AttachmentService attachmentService;
//原图
@GetMapping("/file/attachment/" + ID + "/0")
public void process0(@PathVariable("id")long id, HttpServletResponse response) throws IOException {
process(id,'0', response);
}
void process(long id, char size, HttpServletResponse response) throws IOException{
DownloadBean bean = attachmentService.downloadAttachment(id,size);
response.setContentType(bean.mime);
response.setContentLength(bean.data.length);
response.setHeader("Cache-Control", "max-age=" + 3600*24*30);
ServletOutputStream output = response.getOutputStream();
output.write(bean.data);
output.flush();
}
}
在process中,调用获取图片加载数据和格式的方法。设置输出的内容格式,内容长度,对内容做缓存,减少对服务器流量的影响。通过 outputStream 输出图片的数据,刷新,实现图片的加载。
2,如何获取图片加载的数据和格式
downloadBean 封装图片加载的数据和格式。
public class DownloadBean {
public final String mime;
public final byte[] data;
public DownloadBean(String mime, byte[] data) {
this.mime = mime;
this.data = data;
}
DownloadBean bean = attachmentService.downloadAttachment(id,size);
通过请求过来的 picture 数字,也就是attachment的id,可以获取到对应的 attachment 数据。
根据 attachment 的 resourceId,获取到 resource,获取到resource的content,对resource的content做解码。
获取的过程中可能会出现读取图片的异常,用自定义的异常类,APIException,使用 ApiErro 检测用户上传的是不是图片内容。
获取到attachment中图片的格式,和解码的图片资源数据,返回一个封装的downloadBean。
public DownloadBean downloadAttachment(Long id,char size){
Attachment a = entityDao.getById(id);
if (null == a){
throw new ApiException(ApiError.PARAMETER_INVALID,"id","resource is not found");
}
Resource r = resourceDao.getById(a.getResourceId());
if (null == r){
throw new ApiException(ApiError.PARAMETER_INVALID,"id","resource is not found");
}
byte[] data = Base64.getDecoder().decode(r.getContent());
if(size == '0'){
return new DownloadBean(a.getMime(),data);
}