背景
文件存储是各个系统中必不可少的一部分,比如在电商系统中的商品图片存储,基本需求是大容量存储、快速访问、缩略图自动生成,现在有很多云服务如阿里的oss可以满足,在这里我们分享基于minio自建图片存储方案
minIO简介:
Minio 是个基于 Golang 编写的开源对象存储套件,虽然轻量,却拥有着不错的性能。
部署:
创建文件
1 mkdir -p /opt/data/minIO/datadocker
启动命令
1 docker run -d -p 9000:9000 \ 2 --name minio \ 3 -v /opt/data/minIO/data:/data \ 4 -e "MINIO_ACCESS_KEY=root" \ 5 -e "MINIO_SECRET_KEY=root1234" \ 6 minio/minio server /data
1 用户名:MINIO_ACCESS_KEY (用户名最低3位) 2 密码: MINIO_SECRET_KEY (密码最低8位)
详细见 : min.io官网
代码展示:
javashop商城为了更好的对接更多的平台,所以文件上传功能实现了插件化,降低代码耦合,也方便二次开发,
文件上传接口类:
1 /** 2 * 存储方案参数接口 3 */ 4 public interface Uploader { 5 6 /** 7 * 配置各个存储方案的参数 8 * @return 参数列表 9 */ 10 List<ConfigItem> definitionConfigItem(); 11 12 /** 13 * 上传文件 14 * @param input 上传对象 15 * @param scene 业务场景 16 * @param config 配置信息 17 * @return 18 */ 19 FileVO upload(FileDTO input, String scene, Map config); 20 21 /** 22 * 删除文件 23 * @param filePath 文件地址 24 * @param config 配置信息 25 */ 26 void deleteFile(String filePath, Map config); 27 28 /** 29 * 获取插件ID 30 * @return 插件beanId 31 */ 32 String getPluginId(); 33 34 /** 35 * 生成缩略图路径 36 * @param url 原图片全路径 37 * @param width 需要生成图片尺寸的宽 38 * @param height 需要生成图片尺寸的高 39 * @return 生成的缩略图路径 40 */ 41 String getThumbnailUrl(String url, Integer width, Integer height); 42 43 /** 44 * 存储方案是否开启 45 * @return 0 不开启 1 开启 46 */ 47 Integer getIsOpen(); 48 49 /** 50 * 获取插件名称 51 * @return 插件名称 52 */ 53 String getPluginName(); 54 }
minIO的插件类
javashop商城 实现 minIO的插件类:
1 @Component 2 public class MinIOPlugin implements Uploader { 3 /** 4 * 获取配置参数 5 */ 6 @Override 7 public List<ConfigItem> definitionConfigItem() { 8 List<ConfigItem> list = new ArrayList(); 9 10 ConfigItem endpoint = new ConfigItem(); 11 endpoint.setType("text"); 12 endpoint.setName("endpoint"); 13 endpoint.setText("minIO服务地址"); 14 15 ConfigItem accessKey = new ConfigItem(); 16 accessKey.setType("text"); 17 accessKey.setName("accessKey"); 18 accessKey.setText("用户名"); 19 20 ConfigItem SecretKey = new ConfigItem(); 21 SecretKey.setType("text"); 22 SecretKey.setName("secretKey"); 23 SecretKey.setText("密钥"); 24 25 list.add(serviceUrl); 26 list.add(accessKey); 27 list.add(SecretKey); 28 29 return list; 30 } 31 32 /** 33 * 获取连接 34 */ 35 public MinioClient getClient(Map config, String scene) throws InvalidPortException, InvalidEndpointException, IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InternalException, NoResponseException, InvalidBucketNameException, XmlPullParserException, ErrorResponseException, RegionConflictException, InvalidObjectPrefixException { 36 String accessKeyId = StringUtil.toString(config.get("accessKey")); 37 String accessKeySecret = StringUtil.toString(config.get("secretKey")); 38 String endpoint = StringUtil.toString(config.get("endpoint")); 39 //进行连接 40 MinioClient minioClient = new MinioClient(endpoint, accessKeyId, accessKeySecret); 41 //如果桶为空 42 if (!minioClient.bucketExists(scene)) { 43 //创建桶 44 minioClient.makeBucket(scene); 45 //创建桶的文件为全部可读 46 minioClient.setBucketPolicy(scene, "*", PolicyType.READ_ONLY); 47 } 48 return minioClient; 49 } 50 51 @Override 52 public FileVO upload(FileDTO input, String scene, Map config) { 53 // 获取文件后缀 54 String ext = input.getExt(); 55 //生成随机图片名 56 String picName = UUID.randomUUID().toString().toUpperCase().replace("-", "") + "." + ext; 57 58 MinioClient minioClient = null; 59 try { 60 //获取连接 61 minioClient = this.getClient(config, scene); 62 //文件上传 63 minioClient.putObject(scene, picName, input.getStream(), input.getStream().available(), "image/" + ext); 64 65 } catch (Exception e) { 66 throw new ServiceException(SystemErrorCode.E802.code(), "上传图片失败"); 67 } 68 String serviceUrl = StringUtil.toString(config.get("serviceUrl")); 69 FileVO file = new FileVO(); 70 file.setName(picName); 71 file.setExt(ext); 72 file.setUrl(serviceUrl + "/" + scene + "/" + picName); 73 return file; 74 } 75 76 @Override 77 public void deleteFile(String filePath, Map config) { 78 MinioClient minioClient = null; 79 String[] split = filePath.split("/"); 80 String bucketName = ""; 81 String objectName = ""; 82 for (int i = 0; i < split.length; i++) { 83 if (i == 3) { 84 bucketName = split[i]; 85 } 86 } 87 if (bucketName.length() > 3) { 88 objectName = filePath.substring(filePath.indexOf(bucketName) + bucketName.length() + 1); 89 } else { 90 throw new ServiceException(SystemErrorCode.E803.code(), "删除失败,无法解析路径"); 91 } 92 try { 93 minioClient = this.getClient(config, bucketName); 94 minioClient.removeObject(bucketName, objectName); 95 } catch (Exception e) { 96 throw new ServiceException(SystemErrorCode.E903.code(),"删除图片失败"); 97 } 98 99 } 100 101 @Override 102 public String getPluginId() { 103 return "minIOPlugin"; 104 } 105 106 @Override 107 public String getThumbnailUrl(String url, Integer width, Integer height) { 108 // 缩略图全路径 109 String thumbnailPah = url + "_" + width + "x" + height; 110 // 返回缩略图全路径 111 return thumbnailPah; 112 } 113 114 @Override 115 public Integer getIsOpen() { 116 return 0; 117 } 118 119 @Override 120 public String getPluginName() { 121 return "MinIO存储"; 122 } 123 }
文件上传入口方法:
1 /** 2 * 文件上传<br> 3 * 接受POST请求<br> 4 * 同时支持多择文件上传和截图上传 5 * @param upfile 文件流 6 * @return 7 * @throws JSONException 8 * @throws IOException 9 */ 10 @PostMapping(value = "/") 11 @ApiOperation(value = "ueditor文件/图片上传") 12 public Map upload( MultipartFile upfile) throws JSONException, IOException { 13 Map result = new HashMap(16); 14 if (upfile != null && upfile.getOriginalFilename() != null) { 15 //文件类型 16 String contentType= upfile.getContentType(); 17 //获取文件名称后缀 18 String ext = contentType.substring(contentType.lastIndexOf("/") + 1, contentType.length()); 19 20 if(!FileUtil.isAllowUpImg(ext)){ 21 result.put("state","不允许上传的文件格式,请上传gif,jpg,png,jpeg,mp4格式文件。"); 22 return result; 23 } 24 FileDTO input = new FileDTO(); 25 input.setName(upfile.getOriginalFilename()); 26 input.setStream(upfile.getInputStream()); 27 input.setExt(ext); 28 FileVO file = this.fileManager.upload(input, "ueditor"); 29 String url = file.getUrl(); 30 String title = file.getName(); 31 String original = file.getName(); 32 result.put("state","SUCCESS"); 33 result.put("url", url); 34 result.put("title", title); 35 result.put("name", title); 36 result.put("original", original); 37 result.put("type","."+file.getExt()); 38 return result; 39 }else{ 40 result.put("state","没有读取要上传的文件"); 41 return result; 42 } 43 } 44 }