springBoot版OSS上传功能【可检查魔数】

java

浏览数:87

2019-1-7

片段 1片段 2片段 3片段 4片段 5片段 6


OssAliyunUtil(阿里云上传工具类)

/**
 * @author: Peter
 * @date: 2018-4-11
 */
@Component
public class OssAliyunUtil {

    @Resource(name="defaultOssAliyunField")
    private OssAliyunField defaultOssAliyunField;
    /**
     * 上传文件(选择默认的OSS配置)
     *
     * @param file
     * @return
     */
    public String upload(MultipartFile file) {
        String url = null;
        try {
            url = upload(defaultOssAliyunField, getKey(defaultOssAliyunField.getPrefix(), FileUtils.getSuffix(file)),
                    file.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return url;
    }

    /**
     * 上传文件
     *
     * @param ossAliyunField 配置类,不同的配置类上传的配置就不一样
     * @param file
     * @return
     */
    public String upload(OssAliyunField ossAliyunField, MultipartFile file) {
        String url = null;
        try {
            url = upload(ossAliyunField, getKey(ossAliyunField.getPrefix(), FileUtils.getSuffix(file)),
                    file.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return url;
    }

    /**
     * 上传文件<>基础方法</>
     *
     * @param accessKeyId     授权 ID
     * @param accessKeySecret 授权密钥
     * @param bucketName      桶名
     * @param endpoint        节点名
     * @param styleName       样式名
     * @param key             文件名
     * @param inputStream     文件流
     * @return 访问路径 ,结果为null时说明上传失败
     */
    public String upload(String accessKeyId, String accessKeySecret, String bucketName, String endpoint, String
            styleName, String key, InputStream inputStream) {
        if (inputStream == null) {
            return null;
        }
        String url = null;
        OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
        try {
            // 带进度条的上传
            ossClient.putObject(new PutObjectRequest(bucketName, key, inputStream));
        } catch (OSSException oe) {
            oe.printStackTrace();
            key = null;
        } catch (ClientException ce) {
            ce.printStackTrace();
            key = null;
        } catch (Exception e) {
            e.printStackTrace();
            key = null;
        } finally {
            ossClient.shutdown();
        }
        if (key != null) {
            // 拼接文件访问路径。由于拼接的字符串大多为String对象,而不是""的形式,所以直接用+拼接的方式没有优势
            StringBuffer sb = new StringBuffer();
            sb.append("http://").append(bucketName).append(".").append(endpoint).append("/").append(key);
            if (StringUtils.isNotBlank(styleName)) {
                sb.append("/").append(styleName);
            }
            url = sb.toString();
        }
        return url;
    }

    /**
     * 上传文件
     *
     * @param field       配置对象 此值从OssAliyunConfig当中选择的
     * @param key         文件名
     * @param inputStream 文件流
     * @return
     */
    public String upload(OssAliyunField field, String key, InputStream inputStream) {
        return upload(field.getAccessKeyId(), field.getAccessKeySecret(), field.getBucketName(), field
                .getEndPoint(), field.getStyleName(), key, inputStream);
    }

    /**
     * 删除单个文件<>基础方法</>
     *
     * @param accessKeyId     授权 ID
     * @param accessKeySecret 授权密钥
     * @param bucketName      桶名
     * @param endpoint        节点名
     * @param key             文件名
     */
    public void delete(final String accessKeyId, final String accessKeySecret, final String bucketName,
                              final String endpoint, final String key) {
        // 创建OSSClient实例
        OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
        // 删除Object
        ossClient.deleteObject(bucketName, key);
        // 关闭client
        ossClient.shutdown();
    }

    /**
     * 删除单个文件
     *
     * @param field OSS相关配置
     * @param key   文件名
     */
    public void delete(OssAliyunField field, String key) {
        delete(field.getAccessKeyId(), field.getAccessKeySecret(), field.getBucketName(), field
                .getEndPoint(), key);
    }

    /**
     * 删除多个文件<>基础方法</>
     *
     * @param accessKeyId     授权 ID
     * @param accessKeySecret 授权密钥
     * @param bucketName      桶名
     * @param endpoint        节点名
     * @param keys            多个文件名的集合
     */
    public void delete(final String accessKeyId, final String accessKeySecret, final String bucketName,
                              final String endpoint, final List<String> keys) {
        // 创建OSSClient实例
        OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
        // 删除Objects
        DeleteObjectsResult deleteObjectsResult = ossClient.deleteObjects(new DeleteObjectsRequest(bucketName)
                .withKeys(keys));
        List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
        // 关闭client
        ossClient.shutdown();
    }

    /**
     * 删除多个文件
     *
     * @param field OSS相关配置
     * @param keys  多个文件名的集合
     */
    public void delete(OssAliyunField field, List<String> keys) {
        delete(field.getAccessKeyId(), field.getAccessKeySecret(), field.getBucketName(), field
                .getEndPoint(), keys);
    }

    /**
     * 获取文件名(bucket里的唯一key)
     * 上传和删除时除了需要bucketName外还需要此值
     *
     * @param prefix 前缀(非必传),可以用于区分是哪个模块或子项目上传的文件
     * @param suffix 后缀(非必传), 可以是 png jpg 等
     * @return
     */
    public String getKey(final String prefix, final String suffix) {
        //生成uuid,替换 - 的目的是因为后期可能会用 - 将key进行split,然后进行分类统计
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        //文件路径
        String path = DateUtils.format(new Date(), "yyyyMMdd") + "-" + uuid;

        if (StringUtils.isNotBlank(prefix)) {
            path = prefix + "-" + path;
        }
        if (suffix != null) {
            if (suffix.startsWith(".")) {
                path = path + suffix;
            } else {
                path = path + "." + suffix;
            }
        }
        return path;
    }

}


OSS配置属性类

/**
 * 阿里云OSS上传配置类
 *
 * @author: Peter
 * @date: 2018-4-11
 */
public class OssAliyunField {

    private String accessKeyId;

    private String accessKeySecret;

    private String bucketName;

    private String endPoint;

    private String styleName;

    private String prefix;

    public String getAccessKeyId() {
        return accessKeyId;
    }

    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }

    public String getAccessKeySecret() {
        return accessKeySecret;
    }

    public void setAccessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
    }

    public String getBucketName() {
        return bucketName;
    }

    public void setBucketName(String bucketName) {
        this.bucketName = bucketName;
    }

    public String getEndPoint() {
        return endPoint;
    }

    public void setEndPoint(String endPoint) {
        this.endPoint = endPoint;
    }

    public String getStyleName() {
        return styleName;
    }

    public void setStyleName(String styleName) {
        this.styleName = styleName;
    }

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
}


springboot方式配置注入类

/**
 * 阿里云配置类
 * @author: Peter
 * @date: 2018-4-11
 */
@Configuration
public class OssAliyunConfig {

    @Bean(value="defaultOssAliyunField")
    @ConfigurationProperties("oss.aliyun.defalut")
    public OssAliyunField defaultOssAliyunField() {
        return new OssAliyunField();
    }

    @Bean(value="firstOssAliyuField")
    @ConfigurationProperties("oss.aliyun.first")
    public OssAliyunField firstOssAliyuField() {
        return new OssAliyunField();
    }

    @Bean(value = "secondOssAliyuField")
    @ConfigurationProperties("oss.aliyun.second")
    public OssAliyunField secondOssAliyuField() {
        return new OssAliyunField();
    }
}


application-dev.xml配置文件

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        druid:
            first:  #数据源1
                url: jdbc:mysql://localhost:3306/renren?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
                username: root
                password: root
            second:  #数据源2
                url: jdbc:mysql://10.10.168.18:3306/renren_security?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
                username: renren
                password: 123456
            initial-size: 10
            max-active: 100
            min-idle: 10
            max-wait: 60000
            pool-prepared-statements: true
            max-pool-prepared-statement-per-connection-size: 20
            time-between-eviction-runs-millis: 60000
            min-evictable-idle-time-millis: 300000
            validation-query: SELECT 1 FROM DUAL
            test-while-idle: true
            test-on-borrow: false
            test-on-return: false
            stat-view-servlet:
                enabled: true
                url-pattern: /druid/*
                #login-username: admin
                #login-password: admin,
            filter:
                stat:
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true
oss:
  type: 1 # 1:阿里云,2:七牛,3:腾讯云
  aliyun:
    first:
      accessKeyId: 省略... # 授权ID
      accessKeySecret: 省略... # 授权秘钥
      bucketName: peterjava # 指定桶名
      endPoint: oss-cn-beijing.aliyuncs.com # 访问域名,也指文件存储在哪个区域节点,如华北1
      styleName:   # 指定访问文件样式,可对图片精选模糊压缩等改变<非必传项>。此值同样是在阿里云设置的
      prefix: admin
    second:
      accessKeyId: 省略... # 授权ID
      accessKeySecret: 省略... # 授权秘钥
      bucketName: peterjava
      endPoint: oss-cn-beijing.aliyuncs.com
      styleName:
      prefix: user
    domain: http://oss.znphjf.com # 域名,暂时不知道干嘛用的
    defalut:
      accessKeyId: 省略... # 授权ID
      accessKeySecret: 省略... # 授权秘钥
      bucketName: peterjava # 指定桶名
      endPoint: oss-cn-beijing.aliyuncs.com # 访问域名,也指文件存储在哪个区域节点,如华北1
      styleName:   # 指定访问文件样式,可对图片精选模糊压缩等改变<非必传项>。此值同样是在阿里云设置的
      prefix: default


文件工具类(检查魔数)

/** 文件工具类
 * @author Peter
 * @date 2018-4-11
 */
public class FileUtils {
    /**
     * 判断文件类型是否合法<>通过魔数判断</>
     * @param file
     * @return
     */
    public static boolean checkFileMagicNum(final MultipartFile file) {
        if(file == null){
            return false;
        }
        // 获取文件上传时带的后缀
        String fileName = file.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf(".")+1);
        // 由文件后缀得到的魔数,由于后缀可以伪造,所以该魔数不一定是文件的真实魔数
        String magicNum =  FileTypeMap.map.get(suffix.toLowerCase());
        // 取不到魔数,说明该文件类型不能上传
        if(magicNum == null){
            return false;
        }
        // 取到魔数之后,判断与文件的是否相同
        byte[] b = new byte[30];
        try {
            InputStream in = file.getInputStream();
            // 读取上传文件的前30个字节
            in.read(b,0,30);
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String realMagicNum = bytesToHex(b);
        // 判断文件开始的一段内容是否是匹配的魔数
        if(realMagicNum.toUpperCase().startsWith(magicNum)){
            return true;
        }
        return false;
    }

    /**
     * 将字节数组转换成16进制字符串
     * @param src
     * @return
     */
    public static String bytesToHex(final byte[] src){
        StringBuilder stringBuilder = new StringBuilder("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    public static String getSuffix(final MultipartFile file){
        if(file == null || file.getSize() == 0){
            return null;
        }
        String fileName = file.getOriginalFilename();
        return fileName.substring(fileName.lastIndexOf(".")+1);
    }

}


允许上传的文件类型 及对应的魔数

/**
 * 文件类型<p>允许上传的类型<p/>
 * @author Peter
 * @date 2018-4-11
 */
public class FileTypeMap {

    public static final Map<String, String> map = new HashMap<String, String>(9);
    static {
        map.put("jpeg","FFD8FF");
        map.put("jpg","FFD8FFE0");
        map.put("png","89504E47");
        map.put("wav","57415645");
        map.put("avi","41564920");
        map.put("mp4","00000020667479706D70");
        map.put("mp3","49443303000000002176");
    }}