专用图片表的设计与七牛云的使用
业务开发的过程中,各模块会有图片上传的功能,最初会在各个业务表中维护各自的图片字段,如此一来,图片字段会比较分散,
分散的图片字段有如下的缺点:
1. 统计系统中有多少图片时,得去各个表中查找,如果图片字段命名还千奇百怪,这种是一种比较耗时的工作。
2. 业务表中的记录进行删除(从后台或者直接从数据库进行)时,图片记录也随之消息了,导致图片本身和数据库之间就失去了联系。
集中的图片图片有如下的好处:
1. 图片的集中管理,在图片所在的主表被删除后,此图片记录还存在,可以根据图片记录来清除图片,达到节省磁盘空间的目的。
我设计的图片表:
对应的bean ImageAndFile 如下:
package com.soccer.model;
import com.soccer.util.QiNiuKit;
import javax.persistence.*;
import java.util.Date;
import static javax.persistence.GenerationType.IDENTITY;
/**
*@Author 丰建立
*@Date 2017/11/2 15:43
*/
@Entity
@Table(name="image_and_file")
public class ImageAndFile implements java.io.Serializable{
private Long id;
private String fileKey;//七牛文件key ,可能是多个, 以","分隔
private String tableName;//文件所在 表名
private String fieldName;//文件所在字段名
private Long recId;//文件所在记录id
private String fileExt;//文件类型
private Date opTime;//
public ImageAndFile() {
}
public ImageAndFile(String fileKey, String tableName, String fieldName, Long recId, String fileExt) {
this.fileKey = fileKey;
this.tableName = tableName;
this.fieldName = fieldName;
this.recId = recId;
this.fileExt = fileExt;
this.opTime = new Date();
}
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(name = "fileKey")
public String getFileKey() {
return fileKey;
}
public void setFileKey(String fileKey) {
this.fileKey = fileKey;
}
@Column(name = "tableName")
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
@Column(name = "fieldName")
public String getFieldName() {
return fieldName;
}
public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
@Column(name = "recId")
public Long getRecId() {
return recId;
}
public void setRecId(Long recId) {
this.recId = recId;
}
@Column(name = "fileExt")
public String getFileExt() {
return fileExt;
}
public void setFileExt(String fileExt) {
this.fileExt = fileExt;
}
@Column(name = "opTime")
public Date getOpTime() {
return opTime;
}
public void setOpTime(Date opTime) {
this.opTime = opTime;
}
@Transient
public String getUrl() {
return QiNiuKit.DOMAIN+"/"+this.fileKey+"?v="+(this.getOpTime()==null?(new Date().getTime()):this.getOpTime().getTime());
}
}
图片需要由本地上传至七牛云,而图片的 key 由 tableName/fileldName/recId.jpg组成。这样同一张图片反复上传时,会对之前的图片进行覆盖,达到节省空间的目的。 每次上传图片时,会更新图片表的时间戳,访问此图片时也会带上此时间戳,如此就可以解决 相同图片名,在客户端被缓存的问题。(如果每次访问同一张图片使用随机的时间戳,又会有流量浪费的毛病)。
下面为保存图片时代码片段:
@Transactional
public void saveOrUpdate(Logo one) throws Exception {
super.saveOrUpdate(one);
qiniuApiService.uploadImageToYun(one.getUrl(),"logo","url",one.getId());
}
需要先获表主表的id, 然后再调用图片上传方法。 因此先需要保存主表。
将本地图片上传七牛云时,有三点注意:
- 如果图片url为空,表明用户删除了图片,需要将之前存的七牛云图片删除,再删除iamge_and_file记录。
- 如果之前图片记录为空,上传图片至七牛云,并保存图片记录
- 如果之前图片记录非空,上传图片至七牛云,并更新图片记录的时间戳。
public void uploadImageToYun(String localImagePath, String tableName, String fieldName, Long recId, boolean zipImg) throws Exception{
if(StringUtils.isEmpty(tableName)){
throw new Exception("tableName is empty");
}
if(StringUtils.isEmpty(fieldName)){
throw new Exception("fieldName is empty");
}
if(StringUtils.isEmpty(recId)){
throw new Exception("recId is empty");
}
if(StringUtils.isEmpty(localImagePath)){
ImageAndFile image = imageAndFileDao.getImage(tableName,fieldName,recId);
if(image!=null){
imageAndFileDao.delete(image);
QiNiuKit.delete(image.getFileKey());
}
return;
}
String path = localImagePath;
String fileExt = "jpg";
if(localImagePath.startsWith("{")){
try {
JSONObject json = JSONObject.parseObject(localImagePath);
path = json.getString("3");
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
if(!StringUtils.isEmpty(path)&& !path.startsWith(QiNiuKit.DOMAIN)){
String absImgPath = InitServlet.APPLICATION_URL + "/" + path;
File img = new File(absImgPath);
if(img.exists() && img.isFile()){
if(zipImg){
// ImageUtil.zipImage(absImgPath, CommonServiceImpl.LARGE_DIMENS[0], CommonServiceImpl.LARGE_DIMENS[1]);
}
fileExt = path.substring(path.lastIndexOf(".")+1);
String key = QiNiuKit.upload(img, tableName+"/"+fieldName+"/"+recId+"."+fileExt);
if(key!=null){
ImageAndFile image = imageAndFileDao.getImage(tableName,fieldName,recId);
if(image == null){
imageAndFileDao.saveOrUpdate(key,tableName,fieldName,recId,fileExt);
}else{
//刷新缓存
imageAndFileDao.excuteSqlUpdate("update image_and_file u set u.opTime = now() where u.id= ?",image.getId());
QiNiuKit.refresh(image.getUrl());
}
}
}
}
}
我使用的持久层技术为hibernate .使用 小技巧使 ImageAndFile这个实体对客户端透明:
JoinColumnsOrFormulas 是 hibernate 的标签,用得好也是蛮强大,像是为我的功能量身定做一样。
如此一来,将图片集中管理起来了,而且不用对前端代码作过多修改,即可兼容。
@Column(name = "url", length = 200)
public String getUrl() {
if(this.getIcon()!=null){
return this.getIcon().getUrl();
}
return this.url;
}
private ImageAndFile icon;
@OneToOne(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
@JoinColumnsOrFormulas({
@JoinColumnOrFormula(column=@JoinColumn(name ="id", referencedColumnName ="recId", nullable = true, insertable =false, updatable = false)),
@JoinColumnOrFormula(formula=@JoinFormula(value="'logo'", referencedColumnName = "tableName")),
@JoinColumnOrFormula(formula=@JoinFormula(value="'url'", referencedColumnName = "fieldName"))
})
private ImageAndFile getIcon() {
return icon;
}
public void setIcon(ImageAndFile icon) {
this.icon = icon;
}