GridFS 文件操作

优质
小牛编辑
128浏览
2023-12-01

BuguFS类

bugu-mongo通过BuguFS类来操作GridFS文件系统,能够实现对文件的保存、获取、删除、重命名、移动等操作。

创建BuguFS对象

bugu-mongo框架提供了工厂类BuguFSFactory,用于创建BuguFS对象:

public BuguFS create()

public BuguFS create(String bucket)

public BuguFS create(long chunkSize)

public BuguFS create(String bucket, long chunkSize)

public BuguFS create(String connection, String bucket, long chunkSize)

例如:

BuguFS fs = BuguFSFactory.getInstance().create();

缺省情况下,GridFS把文件保存在名称为fs的bucket中,缺省的文件块大小(chunkSize)是256K。

BuguFS操作文件

BuguFS提供了如下操作:

/* 保存文件。*/
/* 返回的是MongoDB生成的Id */
public String save(File file)

public String save(File file, String filename)

public String save(File file, String filename, Map<String, Object> params)

public String save(InputStream is, String filename)

public String save(InputStream is, String filename, Map<String, Object> params)

public String save(byte[] data, String filename)

public String save(byte[] data, String filename, Map<String, Object> params)

/* 获取一个文件 */
public GridFSDBFile findOneById(String id)

public GridFSDBFile findOne(String filename)

public GridFSDBFile findOne(DBObject query)

/* 获取文件列表 */
public List<GridFSDBFile> find(DBObject query)

public List<GridFSDBFile> find(DBObject query, int pageNum, int pageSize)

public List<GridFSDBFile> find(DBObject query, String orderBy)

public List<GridFSDBFile> find(DBObject query, String orderBy, int pageNum, int pageSize)

/* 文件重命名 */
public void renameById(String id, String newName)

public void rename(String oldName, String newName)

public void rename(GridFSDBFile file, String newName)

/* 删除文件 */
public void removeById(String id)

public void remove(String filename)

public void remove(DBObject query)

与BuguDao中的方法类似,上面的find方法支持使用形如"{level:1}"、"{level:1,price:-1}"这样的字符串进行排序。

此外,BuguFS中还有几个常用的静态常量:

public final static String BUCKET = "bucket";  //文件存放空间
public final static String FILENAME = "filename";  //文件名
public final static String LENGTH = "length";  //文件大小,以byte为单位
public final static String UPLOADDATE = "uploadDate";  //上传时间

对于GridFS中每一个文件(GridFSDBFile),都会有filename、length、uploadDate这三个属性。

一个代码例子如下:

BuguFS fs = BuguFSFactory.getInstance().create();

//保存
File file = ...
fs.save(file);   //filename使用file.getName()

String filename = ...  //提供一个文件名
fs.save(file, filename);

Byte[] data = ...
String filename = ...
fs.save(data, filename);

Map params = new HashMap();
params.put("author", "张三");
params.put("group", "技术");
fs.save(data, filename, params);

//获取
GridFSDBFile dbFile = fs.findOne(filename);
InputStream is = dbFile.getInputStream();

DBObject query = ...
List<GridFSDBFile> list = fs.find(query);

//删除
fs.remove(filename);
fs.remove(query);

辅助类

除了BuguFS外,bugu-mongo还提供了另外几个辅助类,来简化GridFS文件的上传和获取,而且功能更加强大。这几个类分别是:

上传:Uploader、ImageUploader、Watermark

读取:HttpFileGetter、UploadedFileServlet、AccessRestrictedServlet

需要注意的是,这些辅助类,都是以BuguFS为基础的,只是做了进一步的封装和简化,方便使用。

文件上传类Uploader

Uploader类有以下几个构造函数:

public Uploader(File file, String originalName)  //默认rename = false

public Uploader(File file, String originalName, boolean rename)

public Uploader(InputStream input, String originalName)

public Uploader(InputStream input, String originalName, boolean rename)

public Uploader(byte[] data, String originalName)

public Uploader(byte[] data, String originalName, boolean rename)

上面这些构造函数,如果rename = false,则以提供的originalName作为文件名来保存文件。如果rename = true,则会对文件进行重命名,由系统生成一个形如“581b4a28-9b5a-4c7c-8a14-6b8695852ba5.doc”的文件名,这是一个UUID字符串。保存文件后,可以通过uploader.getFilename()来获取重命名后的文件名。

这里,以一个简单的新闻系统为例。

发布一篇新闻的时候,同时上传新闻的附件(比如.doc文件),那么,在Struts2的Action中,代码这样写:

public class CreateNewsAction extends ActionSupport{

    private File file;
    private String fileFileName;
    private News news;
    private NewsDao newsDao;

    public String execute(){
        Uploader uploader = new Uploader(file, fileFileName, true);
        uploader.save();
        news.setAttachment(uploader.getFilename());
        newsDao.save(news);
        return SUCCESS;
    }
    ...
}

上面的代码中,因为rename = true,因此,uploader.getFilename(),返回的是,文件经过保存以后,系统自动生成的文件名(含扩展名),格式如:581b4a28-9b5a-4c7c-8a14-6b8695852ba5.doc。news实体中attachment值,就是这个字符串。

如果是使用SpringMVC,那么Controller的代码示例如下:

@Controller    
public class FileUploadController {

    @RequestMapping(value="/upload", method=RequestMethod.POST)  
    public String handleUpload(@RequestParam("file") MultipartFile file) throws IOException{
        InputStream is = file.getInputStream();
        String originalName = file.getOriginalFilename();
        Uploader uploader = new Uploader(is, originalName, true);
        uploader.save();
        ...
    }

}

如果需要把文件上传到指定的bucket中,或者要设置文件块的大小,代码可以这样写:

Uploader uploader = new Uploader(file, originalName);  //不重命名,使用原文件名
uploader.setBucket("attach");  //相应的Collection为:attach.files 和 attach.chunks
uploader.setChunkSize(64L * 1024L);  //64K
uploader.save();

还可以为上传的文件设置其它属性。例如,设置文件的作者:

Uploader uploader = new Uploader(file, fileFileName, true);
uploader.setAttribute("author", "Frank");
uploader.save();

可以把文件保存到不同的数据库中,只要指定数据库连接即可:

Uploader uploader = new Uploader(file, fileFileName, true);
uploader.setConnection("conn_2");
uploader.save();

文件保存到GridFS后,MongoDB会自动给每个文件生成一个"_id",如果你需要这个ID值,如下:

String fileID = uploader.save();

图片文件的上传ImageUploader

图片上传使用类ImageUploader,它继承自Uploader,除了具备上面讲的Uploader的功能以外,ImageUploader还能实现图片加水印、图片压缩。

图片加水印

图片上传的时候,可以自动给图片加上水印。可以是图片水印,也可以是文字水印。

上传图片的时候,给其加上图片水印的代码如下:

public class CreateProductAction extends ActionSupport{

    private File img;
    private String imgFileName;
    private Product product;
    private ProductDao productDao;

    public String execute(){
        Watermark watermark = new Watermark();
        watermark.setImagePath("/root/website/images/watermark.png");
        ImageUploader uploader = new ImageUploader(img, imgFileName, true);
        uploader.setBucket("images");
        uploader.save(watermark);
        product.setPicture(uploader.getFilename());
        productDao.save(product);
        return SUCCESS;
    }
    ...
}

watermark.setImagePath(imagePath)设置水印文件的路径,该路径是操作系统的绝对路径。

还可以给图片加上文字水印,代码示例如下:

ImageUploader uploader = new ImageUploader(file, fileFileName, true);
Watermark watermark = new Watermark();
watermark.setText("www.mongodb.com");
uploader.save(watermark);

Watermark类还有许多其它参数,它们的定义和初始值如下,这些属性值都可以通过watermark.setXXX()来进行设置:

public final static int CENTER = 1;  //水印位于图片中央
public final static int BOTTOM_RIGHT = 2;  //水印位于图片右下角
public final static int BOTTOM_LEFT = 3;  //水印位于图片左下角

private String imagePath;  //水印图片的绝对路径
private String text;  //水印文本内容

private String fontName = "Arial";  //字体
private int fontStyle = Font.PLAIN;  //文本样式
private Color color = Color.GRAY;  //文本颜色
private int fontSize = 30;  //字号
private float alpha = 0.5f;  //透明度
private int align = CENTER;  //水印位置
private int right = 20;  //水印右侧边距(如果位于右下角)
private int bottom = 20;  //水印底部边距(如果位于右下角)

图片压缩

图片上传的时候,经常还需要将该图片压缩、保存成若干份,代码例子如下:

public class CreateProductAction extends ActionSupport{

    private File img;
    private String imgFileName;
    private Product product;
    private ProductDao productDao;

    public String execute(){
        ImageUploader uploader = new ImageUploader(img, imgFileName, true);
        uploader.save();
        uploader.compress("medium", 300, 300);
        uploader.compress("small", 100, 100);
        product.setPicture(uploader.getFilename());
        productDao.save(product);
        return SUCCESS;
    }
    ...
}

public void compress(String dimension, int maxWidth, int maxHeight) 方法中,dimension是指图片尺寸的类型,用来区分同一图片的不同尺寸的拷贝。该方法压缩后的图片,保持原有的比例。

如果不想保持原有的图片比例,而是填充整个指定的尺寸,则使用public void compress(String dimension, int maxWidth, int maxHeight, boolean filling)方法,将filling参数设为true。

经过compress方法压缩保存后的图片文件,和原图有相同的filename。

获取图片尺寸

通过ImageUploader上传的图片,经过保存后,还可以取得图片的宽度和高度,代码示例如下:

ImageUploader uploader = new ImageUploader(file, fileFileName, true);
uploader.save();
int[] size = uploader.getSize();
int width = size[0];   //宽度
int height = size[1];  //高度

通过HTTP获取文件

为方便Web项目的开发,bugu-mongo提供了2种HTTP获取文件的方式:HttpFileGetter和UploadedFileServlet,开发者可以根据需要选择其中一种。

HttpFileGetter

最基本的用法如下:

@RequestMapping(value = "/getFile/{filename}", method = RequestMethod.GET)
public void getFile(HttpServletRequest request, HttpServletResponse response, @PathVariable String filename){
    HttpFileGetter getter = new HttpFileGetter(request, response);
    getter.response(filename);  //向HTTP客户端返回文件
}

也可以指定匹配自定义属性:

@RequestMapping(value = "/getFile/{author}/{filename}", method = RequestMethod.GET)
public void getFile(HttpServletRequest request, HttpServletResponse response, 
        @PathVariable String author, @PathVariable String filename){
    HttpFileGetter getter = new HttpFileGetter(request, response);
    getter.setAttribute("author", author); //author是上传文件时保存到GridFS中的自定义属性
    getter.response(filename);
}

UploadedFileServlet

除了HttpFileGetter以外,也可以通过UploadedFileServlet来获取文件,在web.xml中配置如下:

<servlet>
    <servlet-name>UploadedFile</servlet-name>
    <servlet-class>com.bugull.mongo.fs.UploadedFileServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UploadedFile</servlet-name>
    <url-pattern>/UploadedFile/*</url-pattern>
</servlet-mapping>

然后,可以通过如下的URL链接来获取文件:

http://www.domain.com/UploadedFile/xxxx.jpg

或者:

http://www.domain.com/UploadedFile/key/value/xxxx.jpg

http://www.domain.com/UploadedFile/key1/value1/key2/value2/xxxx.jpg

其中:

xxxx.jpg——表示文件名,必须提供。

key、value——表示属性名称、属性值。

例如:

http://www.domain.com/UploadedFile/581b4a28-9b5a-4c7c-8a14-6b8695852ba5.jpg

http://www.domain.com/UploadedFile/bucket/images/581b4a28-9b5a-4c7c-8a14-6b8695852ba5.jpg

http://www.domain.com/UploadedFile/dimension/small/581b4a28-9b5a-4c7c-8a14-6b8695852ba5.jpg

http://www.domain.com/UploadedFile/author/Frank/dimension/small/581b4a28-9b5a-4c7c-8a14-6b8695852ba5.jpg

有2点需要特别注意的是:

(1)如果保存文件的时候设置了bucket的名称,则获取文件的时候,需要指定bucket,如:

http://www.domain.com/UploadedFile/bucket/images/581b4a28-9b5a-4c7c-8a14-6b8695852ba5.jpg

(2)经过ImageUploader.compress()压缩后的图片,默认有一个尺寸属性,属性名称是"dimension",属性值就是compress()函数中的参数值。如:

http://www.domain.com/UploadedFile/dimension/small/581b4a28-9b5a-4c7c-8a14-6b8695852ba5.jpg

文件的Content-MD5

对于一些大文件,为了校验传输的过程中是否出错,经常需要返回文件内容的Content-MD5。默认情况下,HttpFileGetter和UploadedFileServlet都是不计算Content-MD5的,可以通过如下方式启用它:

HttpFileGetter:

@RequestMapping(value = "/getFile/{filename}", method = RequestMethod.GET)
public void getFile(HttpServletRequest request, HttpServletResponse response, @PathVariable String filename){
    HttpFileGetter getter = new HttpFileGetter(request, response);
    getter.setContentMD5(true);
    getter.response(filename);
}

UploadedFileServlet:

<servlet>
    <servlet-name>UploadedFile</servlet-name>
    <servlet-class>com.bugull.mongo.fs.UploadedFileServlet</servlet-class>
    <init-param>
        <param-name>contentMD5</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>

注意:如果没有必要,请勿启用文件的MD5,因为它需要大量的CPU运算。

指定数据库连接

在实际的项目中,经常会使用一个独立的MongoDB数据库,专门用于存放文件,而不是和业务数据混在一起。

保存(上传)的时候,使用前面的Uploader类,通过setConnection("conn_2")方法指定数据库连接。获取文件的时候,同样需要指定数据库连接。

HttpFileGetter:

@RequestMapping(value = "/getFile/{filename}", method = RequestMethod.GET)
public void getFile(HttpServletRequest request, HttpServletResponse response, @PathVariable String filename){
    HttpFileGetter getter = new HttpFileGetter(request, response);
    getter.setConnection("conn_2");
    getter.response(filename);
}

UploadedFileServlet:

<servlet>
    <servlet-name>UploadedFile</servlet-name>
    <servlet-class>com.bugull.mongo.fs.UploadedFileServlet</servlet-class>
    <init-param>
        <param-name>connection</param-name>
        <param-value>conn_2</param-value>
    </init-param>
</servlet>

其它特性

HttpFileGetter和UploadedFileServlet还有如下特性:

(1)对于"jpg", "jpeg", "png", "gif", "bmp"等格式的图片文件,浏览器能对它进行缓存。其它类型的文件,则不会进行缓存。

(2)支持断点续传和多线程下载(通过在HTTP请求头中指定Range)。

AccessRestrictedServlet

该类扩展自UploadedFileServlet,用于限制对某些资源文件的并发访问数量。例如:

<servlet>
    <servlet-name>AccessRestrictedServlet</servlet-name>
    <servlet-class>com.bugull.mongo.fs.AccessRestrictedServlet</servlet-class>
    <init-param>
        <param-name>allowBucket</param-name>
        <param-value>videos</param-value>
    </init-param>
    <init-param>
        <param-name>resourceName</param-name>
        <param-value>videos</param-value>
    </init-param>
    <init-param>
        <param-name>maxAccess</param-name>
        <param-value>100</param-value>
    </init-param>
    <init-param>
        <param-name>redirectTo</param-name>
        <param-value>/too_many_access.html</param-value>
    </init-param>
</servlet>

该配置表示:只允许通过该Servlet访问videos这个bucket中的文件,并且最多只允许100个并发访问,当超过该数目时,不返回文件,而是跳转至/too_many_access.html这个网页。

如果不配置redirectTo参数,则访问请求不会被跳转,而是会进入等待队列。

如果你的应用中只有一个AccessRestrictedServlet,那么resourceName可以不配置,其默认值为bugu。resourceName的作用是:你可以通过它来获取当前还剩下多少个空闲的访问名额,以及等待队列中有多少个元素。如:

AccessCount ac = AccessCount.getInstance();
int available = ac.getAvailablePermits("videos");  //如果没有配置resourceName,则为ac.getAvailablePermits("bugu");
int waiting = ac.getQueueLength("videos");