今天真高兴,通过自定义render实现了数据库中图片的显示

真正在Jfinal的学习上进了一步。不过理解还不够透彻,对render的理解还需再深入研究。

最初并没有考虑通过render来实现,但在controller中实现这个功能时,图片也能显示,但就是在后台总有错误,具体为:java.lang.IllegalStateException: ......

在网上找了很多资料,说是与jsp页面显示时自动执行的out.getWriter()重复调用导致。但实际我是用的beetl模板,这样看来beetl模板执行了类似前面说的out.getWriter(),从而造成冲突。

后来想到在jfinal的验证码和二维码中实现了类似的功能,于是参考实现了以下图片显示类。

--------

应波总鼓励,代码贴出来,请波总和大家指导一下:(ImageRender类的实现主要参考了JFinal验证码和二维码的实现)

页面的代码:

<img src="/pijianattach/showAttImage/${imgId}">


Cotroller中的方法:

/**
 * 从数据库中读取二进制内容
 */
public void showAttImage() {
    int id=getParaToInt(0);
    PijianAttachModel att = PijianAttachModel.dao.findById(id);
    String sExt=att.getStr("附件扩展名");
    render(new ImageRender(att.getBytes("附件内容"),sExt.replaceAll("\\.", "")));
}


ImageRender类:

package com.base.controller;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ServletOutputStream;
import com.jfinal.kit.LogKit;
import com.jfinal.render.Render;
import com.jfinal.render.RenderException;
/**
 * 图片的输出类
 * @author Administrator
 *
 */
public class ImageRender extends Render {
    protected InputStream inputStream;
    protected String sFileExt;
    
    @Override
    public void render() {
        //response.setHeader("Pragma","no-cache");
        //response.setHeader("Cache-Control","no-cache");
        //response.setDateHeader("Expires", 0);
        response.setContentType("image/"+this.sFileExt);
        byte buf[] = new byte[1024]; //定义缓存大小
        ServletOutputStream sos = null;
        try {
            sos = response.getOutputStream();
            while (inputStream.read(buf) != -1) {
                sos.write(buf);
            }
        } catch (IOException e) {
            if (getDevMode()) {
                throw new RenderException(e);
            }
        } catch (Exception e) {
                throw new RenderException(e);
        } finally {
                if (sos != null) {
                try {sos.close();} catch (IOException e) {LogKit.logNothing(e);}
        }
        }
    }
    /**
     * 构造函数。
     * @param is 输出流
     * @param sExt 文件扩展名,或者叫文件类型。此处不带“.”。如jpg
     */
    public ImageRender(byte[] bytes,String sExt){
        this.inputStream=new ByteArrayInputStream(bytes);
        this.sFileExt=sExt;
    }
}


为方便大家参考,下面是附件的入库操作:

附件上传对应的Controller中的方法:

//文件上传
public void uploadPijianAtt(){
    // TODO Auto-generated method stub  
    try {
        // 获取上传的文件
        UploadFile uf=getFile();
        //验证扩展名
        String enableExt=".doc,.docx,.jpg,.jpeg,.png,.zip,.rar,";
        String fileExt=FileUtils.getFileType(uf.getFileName()).toLowerCase();
        //
        if(enableExt.contains(fileExt+",")) {
            //另存为
            //String saveAsName="";
            //saveAsName=FileUtils.uploadfile(uf, "upload/tempfiles");
            //读取文件,写入数据库
            FileInputStream fis = new FileInputStream(uf.getFile());
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            //附件表的模型
            PijianAttachModel pjatt=new PijianAttachModel();
            //获取文件内容
            byte[] read = new byte[1024];
            int len = 0;
            while((len = fis.read(read))!= -1){
                bos.write(read,0,len);
            }
            
            //
            pjatt.set("附件内容", bos.toByteArray());
            pjatt.set("附件大小", bos.toByteArray().length);//此处是否有更好的方法?
            pjatt.set("附件名称", uf.getFileName());
            pjatt.set("附件扩展名", fileExt);
            fis.close();
            bos.close();
            //
            pjatt.save();
            //删除上传的临时文件
	    FileUtils.delDirOrFile(uf.getUploadPath()+"/"+uf.getFileName());
            //
            renderJson(0,pjatt.get("编号"));
        }else {
            renderJson(1,"文件格式不正确,请上传"+enableExt+"格式的文件。");
        }
    } catch (Exception e) {
        // TODO: handle exception
        e.printStackTrace();
        renderJson(1,"上传出错。");
    }
}


文件操作工具类中的相关处理方法:

/**
 * 获取文件的类型
 * @param fileName
 * @return 文件类型扩展名,如:.jpg
 */
public static String getFileType(String fileName){
    String suffix = fileName.substring(fileName.lastIndexOf("."), fileName.length());// 后缀
    return suffix;
}

/**
* 删除文件夹或文件
* 
* @param path  要删除的路径或文件,文件应带路径
* @return
*/
public static void delDirOrFile(String path) {
    File f = new File(path);// 定义文件路径
    //判断路径是否存在
    if(f.exists()){
	//判断是文件还是文件夹
	if(f.isDirectory()){
	    //目录
	    // 若有则把文件放进数组,并判断是否有下级目录
	    File delFile[] = f.listFiles();
	    int i = f.listFiles().length;
		for (int j = 0; j < i; j++) {
		    if (delFile[j].isDirectory()) {
			delDirOrFile(delFile[j].getAbsolutePath());// 递归调用del方法并取得子目录路径
		    }
		    delFile[j].delete();// 删除文件
		}				
	}else{
	    //文件
	    f.delete();
	}
    }
}


在界面上,使用了ajaxfileupload的js组件,上传后,将附件id返回界面,用于主记录保存。

扩展:

一般用户上传的附件可能会很大,尤其在现在手机像素越来越高的情况下,拍照的图片文件会达到10M以上,而对于库中保存不需要这么大的文件,清晰度够用就行。因此,可以考虑将图片进行压缩处理,如像素降低后再入库,这样更省空间。

评论区

JFinal

2019-12-26 20:18

代码没贴出来呢? 修改这个分享,可以追加代码进来

三晋一枝花

2019-12-26 20:47

@JFinal 波总,代码帖出来了,请指导。不知道我的原因分析是否正确,以及render()方法是如何执行的,因为并没有主动调它。

HingLo

2019-12-26 21:03

可以看一些源码,从JFinalFilter 看起走,源码还是比较容易理解。重点是ActionMapping 类与ActionHandler 看完你就晓得render是什么时候执行的了。

三晋一枝花

2019-12-26 21:17

jfinal009

2019-12-26 21:39

@三晋一枝花 上传图片存入数据库,可以提供样例代码参考吗

三晋一枝花

2019-12-26 21:41

@jfinal009 这个应该简单吧,我找见了发一份。说实话,还真没有现成的,现在是在改造一个旧系统了,所以需要显示数据库中的图片。

JFinal

2019-12-26 22:21

@三晋一枝花 InputStream istream 这个变量可以放到 ImageRender 内部去使用,封装更好

三晋一枝花

2019-12-27 09:07

@JFinal 是的,已调整。

亚枫

2019-12-31 17:28

图片存数据库,是用base64来存吗?对于大的图片存进数据库,会不会导致查询时变慢?

三晋一枝花

2020-01-01 10:44

@亚枫 在数据库中不是用base64,目前使用的数据库是SQL Server,字段类型是Image,使用byte[]保存在数据库中。
从查询性能上看,数据查询没有任何影响,因为数据库的性能机制保障了其查询速度。目前库中的数据条数在10万级别,数据库的大小是30G以上,查询可以说是秒开,我们以前也做过类似的测试,数据库的查询速度应该比文件系统更快,尤其是在文件非常多的情况下。
但随着系统运行,会使库的大小变的非常大,这个就需要从多个方面权衡了。
之所以使用数据库保存二进制文件,是因为如果将文件保存在系统的文件目录,以后的备份是一个问题;而SQL Server的自动备份机制非常好,可以很好的解决备份问题。
这样,我们可以看到,维护人员只需要保障系统的正常运行,并做好数据库的备份工作,就可以了。简化了运维工作。

三晋一枝花

2020-01-01 10:50

@亚枫
对了,还有两个问题请参考:
一是备份工作,采用每月一次完整备份,每天一次增量备份;这样对存储要求低一些。
二是对附件使用单独的数据库,这样不至于影响主库的备份和迁移工作。

三晋一枝花

2020-01-01 11:11

@jfinal009 上传处理代码已更新到文章中了,请参考。

jfinal009

2020-02-26 14:06

已经应用,效果很好,方便提供二维码,给您打个小赏,表达谢意。

热门分享

扫码入社