文件组件

业务需求

业务场景中经常会涉及到和文件的交互,需要能够对文件资源进行有效的操作和管理。但传统的单机文件系统在互联网应用场景下往往效率不高、稳定性不佳、扩展困难,无法达到高并发、高性能的业务需求,所以需要有一种高效稳定的方式来提供文件资源管理。

解决方案

iuap的文件组件提供了对文件资源的通用操作,使用分布式文件系统FastDFS高效存储海量文件,通过集群模式水平扩展容量和数据冗余存储,同时支持阿里云OSS对象存储服务,使用OSS直接上传文件可以节省带宽流量。在对不同的文件系统进行适配时,尽量保持接口参数相同,保证用户使用简便。

功能说明

  1. 支持文件的上传、下载、删除操作;
  2. 同时支持FastDFS和阿里云OSS服务;
  3. 支持获取文件下载URL;
  4. 支持阿里云OSS的文件直传和回调;
  5. 支持阿里云OSS不同bucket权限的文件URL获取;
  6. 支持对阿里云OSS请求的安全校验;

整体设计

依赖环境

组件采用Maven进行编译和打包发布,依赖FastDFS和阿里云OSS的客户端SDK,其对外提供的依赖方式如下:

<dependency>
  <groupId>com.yonyou.iuap</groupId>
  <artifactId>iuap-file</artifactId>
  <version>${iuap.modules.version}</version>
</dependency>

${iuap.modules.version} 为平台在maven私服上发布的组件的version。

功能结构

组件封装了三种文件系统,并暴露出了常用的文件操作接口,用户通过这些接口,去操作组件所管理的对应文件系统。

使用说明

配置

文件组件目前适配了三种文件系统,本地文件存储、阿里云、FastDfs

文件组件支持三套文件系统,在使用不同的系统时接口保持统一,通过FileManager类下的静态方法,提供对文件上传下载删除的服务。

1:工程中引入对iuap-file组件的依赖

<dependency>
    <groupId>com.yonyou.iuap</groupId>
    <artifactId>iuap-file</artifactId>
    <version>${iuap.modules.version}</version>
</dependency>

${iuap.modules.version} 为在pom.xml中定义的需要引入组件的version。

2:在application.properties文件中配置工程所使用的文件存储系统

#使用哪种文件存储系统(AliOss阿里云,Local本地文件存储,FastDfs)
storeType=AliOss
#storeType=Local
#storeType=FastDfs

#默认存储Bucket(private权限)
defaultBucket=your bucket
#默认存储Bucket(read权限)
defaultBucketRead=your bucket

#使用本地文件系统时的存储路径
storeDir=/etc/filetest

#使用FastDfs文件系统时Fdfs系统的配置
connect_timeout = 10
network_timeout = 30
charset = ISO8859-1
tracker_server = 20.12.6.91:22122
#处理FastDfs文件系统的nginx服务器ip(文件url获取以及图片缩放功能)
fdfsread_server = 172.20.7.24


#使用阿里云文件系统
#阿里云endpoint、accessKeyId、accessKeySecret
endpoint=oss-cn-beijing.aliyuncs.com
accessKeyId=your accessKeyId
accessKeySecret=your accessKeySecret
#接受阿里云上传回调的主机ip和端口,该主机需要外网能直接访问(直传功能)
callbackTarget=http://localhost
#处理上传回调的servlet截取的地址,直传成功后阿里云oss将会对上面配置的主机地址发送url为改配置url的post请求(直传功能)
callbackUrl=/oss
#需要上传回调所带的参数,上面配置的的请求将会携带callbackBody配置的参数(直传功能)
callbackBody=filename=${object}&bucket=${bucket}&size=${size}

在工程上配置属性文件时,可以根据使用的存储类型,配置需要的部分。

功能调用

1.阿里云oss

非直传

组件提供的API,操作阿里云oss,所有的api都通过FileManager类静态调用,例子:FileManager.uploadFile("yourbucket","test.txt",content)。

//上传,参数(bucket名,文件名,上传文件字节数组),返回(上传文件的文件名)
String uploadFile(String bucketName, String fileName,byte[] fileContent)

 //下载,参数(bucket名,文件名),返回(下载文件的字节数组)
 byte[] downLoadFile(String bucketName,String fileName) ;

//删除,参数(bucket名,文件名),返回(删除是否成功标志)
boolean deleteFile(String bucketName,String fileName)

//获取文件url,参数(bucket名,文件名,过期时间),返回(上传文件的url)目前expired参数只支持阿里云oss私有bucket,其他系统可置为0
String getUrl(String bucketName, String fileName,int expired)

//获取图片url,参数(bucket名,文件名,过期时间),返回(图片的url)与getUrl的区别是文件名可以使用类似example.jpg@100h_100w这种格式产生略缩图,)目前expired参数只支持阿里云oss私有bucket,其他系统可置为0
String getImgUrl(String bucketName,String fileName,int expired)

example测试类

/**
 * 阿里云上传下载删除测试
 * 测试前请设置application.properties的storeType=AliOss
 * 参数"your bucket"为阿里云oss的bucket名,请更换为您的bucket名
 * @throws Exception
 */
@Test
public void testAliUpload() throws Exception {
    String filename;
    //借用文件流生成文件的二进制数组
    LocalClient client =LocalClient.getInstance();
    //参数为测试文件路径;        
    byte[] content =client.download("/etc/filetest/test.txt");
    //==================准备工作完毕=============================

    //阿里云上传
    filename=FileManager.uploadFile("your bucket","test.txt",content);
    //阿里云下载
    byte[] downloadContent=FileManager.downLoadFile("your bucket",filename);
    //获取文件url
    String url = FileManager.getUrl("your bucket", filename, 60);
    //获取图片url(将上传文件路径改为图片)
    //String imgurl = FileManager.getImgUrl(Private, filename, 60);
    //删除
    boolean flag=FileManager.deleteFile("your bucket",filename);

    System.out.println(filename);
    System.out.println(url);
    //System.out.println(imgurl);
    System.out.println("删除状态"+flag);
    //Assert.isTrue(flag);
}    

直传

直传功能目前只支持阿里云oss,该功能使文件上传请求和流量不再经过应用服务器而直接交给oss处理,直传功能由阿里云官方文档推荐的框架搭建(相关连接:web端直传)

流程

用户请求->Servlet获取签名->js装配获得的签名并上传文件->oss上传文件后向Servlet发送回调->Servlet对oss回调请求处理后向oss发送处理结果->oss向用户返回上传结果。

首先建立一个Servlet类继承CallbackServer,通过该Servlet装配oss直传需要的签名数据,并处理oss只直传完毕后的回调

package com.yonyou.iuap.file;

import java.io.IOException;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(asyncSupported = true,name = "Oss",urlPatterns = { "/oss" })
public class OssDirectServlet extends CallbackServer {

    private static final long serialVersionUID = 1L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String ossCallbackBody = GetPostBody(request.getInputStream(), Integer.parseInt(request.getHeader("content-length")));//获取回调
        boolean ret = VerifyOSSCallbackRequest(request, ossCallbackBody);//验证回调

        if (ret)
        {
            //验证通过进行操作
            Map<String, Object> map = getUrlParams(ossCallbackBody);
            String filename=(String) map.get("filename");//获取存储的文件名
            String bucket=(String) map.get("bucket");//获取存储的bucket
            System.out.println(filename);
            System.out.println(bucket);

            response(request, response, "{\"Status\":\"OK\"}", HttpServletResponse.SC_OK);

        }
        else
        {
            response(request, response, "{\"Status\":\"verdify not ok\"}", HttpServletResponse.SC_BAD_REQUEST);
        }
    }

}

通过js进行上传

function init(){

//提交
//获取oss签名参数
var ret = get_signature()
    //装配发送到oss文件上传url   
var formData = new FormData(); 
var file=$("#file").prop('files');

var filepath=$("#file").val();
var arr=filepath.split('\\');
var filename=arr[arr.length-1];//===================获得本地文件名(需要你提供)

if (ret == true)
{
    //装配oss签名参数
    formData.append("name",filename);
    formData.append("key", key);
    formData.append("policy", policyBase64);
    formData.append("OSSAccessKeyId", accessid);
    formData.append("success_action_status", '200');
    formData.append("callback", callbackbody);
    formData.append("signature", signature);
    formData.append("file",document.getElementById('file').files[0])//===================获得文件数据(需要你提供)
}
//发送文件上传请求   
    $.ajax({  
        url: host,  
        type: 'POST',  
        data: formData,  
        async: false,  
        contentType: false, //必须
        processData: false, //必须
        success: function (returndata) {  
                alert(returndata);  
        },  
           error: function (returndata) {  
            alert(returndata);  
        }  
    });  
  }

//发送到应用服务器 获取oss签名参数
function send_request(){
    var obj
    $.ajax({
        type : 'GET',
        async : false,
        url :'http://localhost/example_iuap_file/oss?bucketname='+document.getElementById('bucketname').value,//==========租户id(需要你提供)
        success : function(data){
        obj=$.parseJSON(data);
        } 
    });
    return obj;
}

//获取到签名参数后将参数填入变量待用
function get_signature()
{
    //可以判断当前expire是否超过了当前时间,如果超过了当前时间,就重新取一下.3s 做为缓冲
    expire = 0
    now = timestamp = Date.parse(new Date()) / 1000; 
    console.log('get_signature ...');
    console.log('expire:' + expire.toString());
    console.log('now:', + now.toString())
    if (expire < now + 3){
        console.log('get new sign')
        var obj = send_request(obj)
        policyBase64 = obj['policy']
           accessid = obj['accessid']
        signature = obj['signature']
        expire = parseInt(obj['expire'])
        callbackbody = obj['callback'] 
        host =obj['host']
        key = obj['perfix']+'${filename}'
        return true;
    }
    return false;
};

$("#submit").attr("onclick","init();");

发送上传请求后会在上文中的Servlet类dopost方法处理oss回调,最终oss将结果发送回前台js的ajax回调。

临时文件url与永久文件url

oss文件系统能够通过getUrl方法返回文件的url,返回的url有临时和永久两种。该接口如下

public static String getUrl(String bucketName,String fileName,int expired)

三个参数为ossbucket的权限,文件名,过期时间。其中bucketName参数对应了配置文件application.properties中所配置的defaultBucket和defaultBucketRead的bucket名称,如果该bucket名称没有配置则默认该bucket的权限为private。如果需要临时url的话bucketName对应的bucket权限应该为private,并且传入expired值为过期时长。如果需要永久的文件url前提是上传文件bucket需要为read或full权限(不推荐使用),设置bucket权限可以在oss控制台(相关连接:建立新的bucket),选择需要修改权限的bucket,Bucket属性标签有读写权限修改。在geturl获取永久链接时expired可以为0。

略缩图功能

首先在oss控制台,选择存储略缩图文件的bucket,在左边的菜单中选择图片处理,选择开通图片处理。 然后通过下面的接口可以返回略缩图url:

public static String getImgUrl(String bucketName,String fileName,int expired)

略缩图也是分为临时与永久url,方法与上文相同。通过该方法获得url以后附加类似@100h的参数产生略缩图。

例子:

将图缩略成高度为100,宽度按比例处理。

http://image-demo.img-cn-hangzhou.aliyuncs.com/example.jpg@100h

将图缩略成宽度为100,高度按比例处理。

http://image-demo.img-cn-hangzhou.aliyuncs.com/example.jpg@100w

2.FastDFS

组件提供API,操作FastDFS

FastDfs API与使用oss系统时是相同的,所有的api都通过FileManager类静态调用,例子:FileManager.uploadFile("private","test.txt",content)。

在使用FastDfs模式时,bucketName参数代表文件在操作FastDFS系统存储的权限(private、read、full)该权限将存入FastDFS文件的metadata中,目前组件还没有对不同权限的文件进行访问限制处理,请等待以后的更新。FastDfs模式下,bucketName目前为三个固定值PRIVATE、READ、FULL,代表三种权限。

//上传,参数(bucket名,文件名,上传文件字节数组),返回(上传文件的文件名)
String uploadFile(String bucketName, String fileName,byte[] fileContent)

 //下载,参数(bucket名,文件名),返回(下载文件的字节数组)
 byte[] downLoadFile(String bucketName,String fileName) ;

//删除,参数(bucket名,文件名),返回(删除是否成功标志)
boolean deleteFile(String bucketName,String fileName)

//获取文件url,参数(bucket名,文件名,过期时间),返回(上传文件的url)目前expired参数只支持阿里云oss私有bucket,其他系统可置为0
String getUrl(String bucketName, String fileName,int expired)

//获取图片url,参数(bucket名,文件名,过期时间),返回(图片的url)与getUrl的区别是文件名可以使用类似example.jpg@100h_100w这种格式产生略缩图,)目前expired参数只支持阿里云oss私有bucket,其他系统可置为0
String getImgUrl(String bucketName,String fileName,int expired)

example测试类

/**
 * fastdfs上传下载删除测试
 * 测试前请设置application.properties的storeType=FastDfs
 * @throws Exception
 */
@Test
public void testFdfsUpload() throws Exception {
    String filename;
    //借用文件流生成文件的二进制数组
    LocalClient client =LocalClient.getInstance();        
    //参数为测试文件路径
    byte[] content =client.download("/etc/filetest/test.txt");
    //==================准备工作完毕=============================

    //fastdfs上传
    filename=FileManager.uploadFile(Private, "test.txt",content);
    //fastdfs下载
    byte[] downloadContent=FileManager.downLoadFile(Private,filename);
    //获取文件url
    String url = FileManager.getUrl(Private, filename, 0);
    //获取图片url(将上传文件路径改为图片)
    //String imgurl = FileManager.getImgUrl(Private, filename, 0);
    //删除
    boolean flag=FileManager.deleteFile(Private,filename);    


    System.out.println(filename);
    System.out.println(url);
    //System.out.println(imgurl);
    System.out.println("删除状态"+flag);
    Assert.isTrue(flag);
}

略缩图功能

fdfs使用略缩图功能时 需要搭建nginx提供文件访问服务

安装nginx

1:安装前准备

yum -y install zlib zlib-devel openssl openssl-devel pcre-devel gcc gcc-c++ autoconf automake gd-devel

groupadd -r nginx

useradd -s /sbin/nologin -g nginx -r nginx

mkdir /var/tmp/nginx/client -pv

touch /var/lock/nginx.lock

2:nginx配置和安装关键步骤示例(仅供参考)

cd /usr/local

wget http://nginx.org/download/nginx-1.8.0.tar.gz

tar -zxvf nginx-1.8.0.tar.gz

cd nginx-1.8.0


./configure \
--prefix=/usr \
--sbin-path=/usr/sbin/nginx \
--conf-path=/etc/nginx/nginx.conf \
--error-log-path=/media/disk1/nginx/logs/error.log \
--pid-path=/var/run/nginx/nginx.pid \
--lock-path=/var/lock/nginx.lock \
--user=nginx \
--group=nginx \
--with-pcre=/usr/local/src/pcre-8.35 \
--with-http_ssl_module \
--with-http_flv_module \
--with-http_gzip_static_module \
--http-log-path=/media/disk1/nginx/logs/access.log \
--http-client-body-temp-path=/media/disk1/nginx/client \
--http-proxy-temp-path=/media/disk1/nginx/proxy \
--http-fastcgi-temp-path=/media/disk1/nginx/fcgi \
--with-http_stub_status_module \
--with-poll_module \
--with-http_realip_module \
--add-module=/usr/local/src/ngx_cache_purge-2.3 \
--add-module=/usr/local/src/fastdfs-nginx-module/src \
--with-http_image_filter_module \
--with-cc-opt=-Wno-error 

nginx configure时需要注意带上fastdfs-nginx-module、http_image_filter_module等模块

3:nginx安装检查

检查是否启动
ps -ef |grep nginx

启动
nginx -c /etc/nginx/nginx.conf

查看80端口是否被监听
netstat -tulnp |grep :80

4: nginx 服务配置

将nginx文件复制到/etc/init.d下并修改权限

chmod +x nginx

5:配置nginx.conf

location /g1/M00 {
    root /data/fdfs;
    ngx_fastdfs_module;
}

location  ~* /g1/M00/(.*)@(.*)$ {
    set $img_name $1;
    set $img_param $2;

    if ( $img_param ~* "([\d]+)h_([\d]+)w" ){
    set $img_height $1;
        set $img_width $2;
    }

    if ( $img_param ~* "([\d]+)h" ){
    set $img_height $1;
        set $img_width -;
    }

    if ( $img_param ~* "([\d]+)w" ){
    set $img_height -;
        set $img_width $1;
    }

    root /data/fdfs;
    ngx_fastdfs_module;

    rewrite /g1/M00/(.*)@(.*)$ /g1/M00/$1 break;

    image_filter resize $img_width $img_height;
    image_filter_buffer 5M;
    image_filter_jpeg_quality 80;
    image_filter_transparency on;

    proxy_cache content_image;
    proxy_cache_key $request_uri$is_args$args;
    proxy_cache_valid 200 304 12h;
    expires 30d;
}

5:服务启动

服务方式启动
service nginx start

停止
service nginx stop

重新加载
service nginx reload

接口调用

然后通过下面的接口可以返回略缩图url:

public static String getImgUrl(String bucketName,String fileName,int expired)

fileName后面附加类似@100h的参数产生略缩图。

例子:

将图缩略成高度为100,宽度按比例处理。

example.jpg@100h

将图缩略成宽度为100,高度按比例处理。

example.jpg@100w

将图缩略成高度为100,宽度为100。 example.jpg@100h_100w

3.本地文件系统

本地文件系统一般用于开发测试,不建议部署为生产系统

组件提供的API,操作本地文件系统

//上传,参数(bucket名传null,文件名,上传文件字节数组),返回(上传文件的文件名)
filename=FileManager.uploadFile(null,"test.txt", content);
//下载,参数(bucket名传null,文件名),返回(下载文件的字节数组)
byte[] content =client.download("/etc/filetest/test.txt");
//删除,参数(bucket名传null,文件名),返回(删除是否成功标志)
boolean flag=FileManager.deleteFile(null,filename);

example测试类

/**
 * 文件系统上传下载删除测试
 * 测试前请设置application.properties的storeType=Local
 * @throws Exception
 */
@Test
public void testFSUpload() throws Exception {
    String filename;
    //下载并将文件转换为二进制数组
    //借用文件流生成文件的二进制数组
    LocalClient client =LocalClient.getInstance();        
    byte[] content =client.download("/etc/filetest/test.txt");

    //上传    
    filename=FileManager.uploadFile(null,"test.txt", content);
    //删除
    boolean flag=FileManager.deleteFile(null,filename);

    System.out.println(filename);
    System.out.println("删除状态"+flag);
    Assert.isTrue(flag);
}

常用接口

文件组件api接口介绍

组件接口类FileManager

FastDfs模式下,bucketName为三个固定值PRIVATE、READ、FULL,代表三种权限。

方法名 参数 返回值 功能说明
uploadFile 1. String bucketName(bucket名)
2. String fileName(上传文件名)
3. byte[] fileContent(上传文件字节数组)
String(上传后的文件名) 上传文件
downLoadFile 1. String bucketName(下载文件所在bucket名)
2. String fileName(下载文件名)
byte[](下载文件的二进制数组) 下载文件
deleteFile 1. String bucketName(bucket名)
2. String fileName(要删除的文件名)
boolean(删除文件是否成功) 删除文件
getUrl 1. String bucketName(bucket名)
2. String fileName(获取url的文件名
3. int expired(单位 秒 ,连接过期时间,目前该参数只支持阿里云oss私有bucket)
String(文件的url) 返回文件url
getImgUrl 1. String bucketName(bucket名)
2. String fileName(获取url的文件名)
3. int expired(单位 秒 ,连接过期时间,目前该参数只支持阿里云oss私有bucket)
String(图片的url) 返回图片url
使用阿里云oss时在fileName加入类似@100h的参数能生成略缩图
使用fdfs时在fileName加入类似@100h的参数能生成略缩图(需要布置nginx支持)

更多详细的使用方法,请参考示例工程(DevTool/examples/example_iuap_file)