背景
文件存儲是各個系統中必不可少的一部分,比如在電商系統中的商品圖片存儲,基本需求是大容量存儲、快速訪問、縮略圖自動生成,現在有很多雲服務如阿里的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 }