SpringBoot三十七:整合阿里云OSS

    技术2022-07-12  74

    什么是对象存储OSS

    阿里云对象存储OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高可靠的云存储服务。其数据设计持久性不低于99.9999999999%(12个9),服务可用性(或业务连续性)不低于99.995%。

    OSS具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

    您可以使用阿里云提供的API、SDK接口或者OSS迁移工具轻松地将海量数据移入或移出阿里云OSS。数据存储到阿里云OSS以后,您可以选择标准存储(Standard)作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低、存储期限更长的低频访问存储(Infrequent Access)、归档存储(Archive)作为不经常访问数据的存储方式。

    官方文档

    集成OSS

    pom.xml

    <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.10.2</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>

    application.yml

    oss: endpoint: https://oss-cn-hangzhou.aliyuncs.com #Endpoint以杭州为例,其它Region请按实际情况填写 accessKeyId: yourAccessKeyId secretAccessKey: yourAccessKeySecret bucketName: yourBucketName urlPrefix: 你的文件访问地址 #bucketName+endpoint

    OSS客户端配置类,并注入到Spring中

    @Data @Configuration @ConfigurationProperties(prefix = "oss") //@PropertySource(value = {"classpath:application-oss.properties"}) public class OssConfig { private String endpoint; private String accessKeyId; private String secretAccessKey; private String bucketName; private String urlPrefix; @Bean public OSS oSSClient() { return new OSSClientBuilder().build(endpoint, accessKeyId, secretAccessKey); } }

    文件上传、删除、下载、图像压缩、图像添加水印

    测试

    @RestController public class OssController { @Autowired private OssUtil ossUtil; /** * 文件上传到oss * * @param files * @return */ @PostMapping("/oss/upload") public String upload(@RequestParam("file") MultipartFile file) { return ossUtil.putObject(file); } /** * 删除文件 * * @param fileName * @return */ @GetMapping("oss/delete") public String delete(@RequestParam("fileName") String fileName) { Boolean flag = ossUtil.deleteObject(fileName); if (flag) { return "删除成功"; } return "删除失败"; } /** * 查询文件列表 * * @return */ @GetMapping("oss/list") public List<OSSObjectSummary> list() { List<OSSObjectSummary> fileList = ossUtil.listObjects(); return fileList; } /** * 下载文件 * * @param fileName * @param response * @throws IOException */ @GetMapping("oss/download") public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) throws IOException { // 通知浏览器以附件形式下载 response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(), "ISO-8859-1")); ossUtil.download(response.getOutputStream(), fileName); } /** * 上传图片 * * @param file * @return */ @PostMapping("/oss/uploadPic") public String uploadPic(@RequestParam("file") MultipartFile file) { boolean flag = ossUtil.isImage(file); if (flag) { return ossUtil.uploadPic(file); } return "上传失败"; } }

    OSS工具类

    /** * 阿里云OSS服务器工具类 * */ @Component public class OssUtil { @Autowired private OSS ossClient; @Autowired private OssConfig ossConfig; private static final long EXPIRATION = 3600l * 1000 * 24 * 7;// 七天 // 允许上传的格式 private static final String[] IMAGE_TYPE = new String[] { ".bmp", ".jpg", ".jpeg", ".gif", ".png" }; private String PATH = "javakf/"; /** * 单文件上传 * * @param file * @return */ public String uploadFile(MultipartFile file) { String fileUrl = putObject(file); return fileUrl; } /** * 多文件上传 * * @param fileList * @return */ public String multiUpload(List<MultipartFile> fileList) { String url = null; String fileUrl = null; for (int i = 0; i < fileList.size(); i++) { url = putObject(fileList.get(i)); if (i == 0) { fileUrl = url; } else { fileUrl += ";" + url; } } return fileUrl.trim(); } /** * 上传文件-支持单文件,多文件 -返回文件访问路径,多文件以分号‘;’分隔 * * @param muFiles * @return */ public String uploadFiles(MultipartFile... file) { if (file.length < 1) { throw new RuntimeException("上传文件为空!"); } StringBuilder str = new StringBuilder(); for (MultipartFile muFile : file) { str.append(putObject(muFile)); str.append(";"); } return str.deleteCharAt(str.length() - 1).toString(); } /** * 上传文件 * * @param file * @return */ public String putObject(MultipartFile file) { try { String fileName = getFilePathName(file, true); // 上传到阿里云 ossClient.putObject(ossConfig.getBucketName(), fileName, new ByteArrayInputStream(file.getBytes())); return this.ossConfig.getUrlPrefix() + fileName; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("上传文件失败!"); } } /** * 上传图片 * * @param file * @return */ public String uploadPic(MultipartFile file) { try { String fileName = getFilePathName(file, true); InputStream inputStream = file.getInputStream(); // 如果是图片文件就进行压缩并添加水印 if (ImageUtil.isImage(file.getOriginalFilename())) { inputStream = ImageUtil.getInputStream( ImageUtil.setWatermark(ImageUtil.compress(ImageIO.read(inputStream))), ImageUtil.getFileExtention(file.getOriginalFilename())); } // 上传到阿里云 ossClient.putObject(ossConfig.getBucketName(), fileName, inputStream); return this.ossConfig.getUrlPrefix() + fileName; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("上传文件失败!"); } } /** * 校验图片格式 * * @param file * @return */ public boolean isImage(MultipartFile file) { boolean flag = false; for (String type : IMAGE_TYPE) { if (StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), type)) { flag = true; break; } } return flag; } /** * 删除文件 * * @param fileName * @return */ public Boolean deleteObject(String fileName) { try { ossClient.deleteObject(ossConfig.getBucketName(), fileName); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 查询文件列表(默认100条) * * @return */ public List<OSSObjectSummary> listObjects() { ObjectListing listObjects = ossClient.listObjects(ossConfig.getBucketName()); List<OSSObjectSummary> fileList = listObjects.getObjectSummaries(); return fileList; } /** * 查询文件列表(默认值100条,最大值1000条) * * @param maxKeys 要查询的条数 * @return */ public List<OSSObjectSummary> listObjects(Integer maxKeys) { if (maxKeys == null) { maxKeys = 100; } ObjectListing listObjects = ossClient .listObjects(new ListObjectsRequest(ossConfig.getBucketName()).withMaxKeys(maxKeys)); List<OSSObjectSummary> fileList = listObjects.getObjectSummaries(); return fileList; } /** * 查询指定前缀的文件列表 * * @param prefix 前缀 * @return */ public List<OSSObjectSummary> listObjects(String prefix) { ObjectListing listObjects = ossClient.listObjects(ossConfig.getBucketName(), prefix); List<OSSObjectSummary> fileList = listObjects.getObjectSummaries(); return fileList; } /** * 下载文件 * * @param outputStream * @param fileName */ public void download(OutputStream outputStream, String fileName) { try { OSSObject ossObject = ossClient.getObject(ossConfig.getBucketName(), fileName); BufferedInputStream in = new BufferedInputStream(ossObject.getObjectContent()); BufferedOutputStream out = new BufferedOutputStream(outputStream); byte[] buffer = new byte[1024]; int lenght = 0; while ((lenght = in.read(buffer)) != -1) { out.write(buffer, 0, lenght); } if (out != null) { out.flush(); out.close(); } if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } /** * 生成下载链接 * * @param fileName * @return */ public String getUrl(String fileName) { if (!StringUtils.isEmpty(fileName)) { // 设置URL过期时间为10年 3600l*1000*24*365*10 Date expiration = new Date(new Date().getTime() + EXPIRATION); // 生成URL URL url = ossClient.generatePresignedUrl(ossConfig.getBucketName(), fileName, expiration); if (url != null) { return url.toString(); } } return null; } /** * 上传文件 * * @param inputStream * @param fileName * @return */ public String putObject(InputStream inputStream, String fileName) { try { // 文件扩展名 String fileSuffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase(); // 新的文件名 fileName = PATH + new Random().nextInt(10000) + System.currentTimeMillis() + fileSuffix; // 创建上传Object的Metadata ObjectMetadata objectMetadata = new ObjectMetadata(); // 上传的文件的长度 objectMetadata.setContentLength(inputStream.available()); // 指定该Object被下载时的网页的缓存行为 objectMetadata.setCacheControl("no-cache"); // 指定该Object下设置Header objectMetadata.setHeader("Pragma", "no-cache"); // 指定该Object被下载时的内容编码格式 objectMetadata.setContentEncoding("utf-8"); // 文件的MIME,定义文件的类型及网页编码,决定浏览器将以什么形式、什么编码读取文件。如果用户没有指定则根据Key或文件名的扩展名生成, // 如果没有扩展名则填默认值application/octet-stream objectMetadata.setContentType(getcontentType(fileSuffix)); // 指定该Object被下载时的名称(指示MINME用户代理如何显示附加的文件,打开或下载,及文件名称) objectMetadata.setContentDisposition("inline;filename=" + fileName); // 上传文件 ossClient.putObject(ossConfig.getBucketName(), fileName, inputStream, objectMetadata); } catch (IOException e) { e.printStackTrace(); } finally { try { if (inputStream != null) { inputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } return this.ossConfig.getUrlPrefix() + fileName; } /** * 获取文件名 * * @param muFile 文件 * @param isRetain 是否保留源文件名 * @return 返回文件名,以当前年月日作为前缀路径 */ private String getFilePathName(MultipartFile file, boolean isRetain) { String fileName = file.getOriginalFilename(); String name = fileName; String fileSuffix = ""; if (fileName.indexOf('.') != -1) { name = fileName.substring(0, fileName.indexOf('.')); fileSuffix = fileName.substring(fileName.lastIndexOf(".")); } LocalDate date = LocalDate.now(); StringBuilder filePathName = new StringBuilder(PATH); filePathName.append(date.getYear()); filePathName.append("/"); filePathName.append(date.getMonthValue()); filePathName.append("/"); filePathName.append(date.getDayOfMonth()); filePathName.append("/"); // 添加随机后缀 Random r = new Random(); int pix = r.ints(1, (100 + 1)).findFirst().getAsInt(); filePathName.append(System.currentTimeMillis()); filePathName.append("" + pix); // 文件名超过32字符则截取 if (isRetain) { filePathName.append("_"); if (name.length() >= 32) { name = name.substring(0, 32); } filePathName.append(name); } filePathName.append(fileSuffix); return filePathName.toString(); } /** * 处理文件扩展名 * * @param fileSuffix * @return */ public String getcontentType(String fileSuffix) { if (fileSuffix.equalsIgnoreCase(".bmp")) { return "image/bmp"; } if (fileSuffix.equalsIgnoreCase(".gif")) { return "image/gif"; } if (fileSuffix.equalsIgnoreCase(".jpeg") || fileSuffix.equalsIgnoreCase(".jpg") || fileSuffix.equalsIgnoreCase(".png")) { return "image/jpeg"; } if (fileSuffix.equalsIgnoreCase(".html")) { return "text/html"; } if (fileSuffix.equalsIgnoreCase(".txt")) { return "text/plain"; } if (fileSuffix.equalsIgnoreCase(".vsd")) { return "application/vnd.visio"; } if (fileSuffix.equalsIgnoreCase(".pptx") || fileSuffix.equalsIgnoreCase(".ppt")) { return "application/vnd.ms-powerpoint"; } if (fileSuffix.equalsIgnoreCase(".docx") || fileSuffix.equalsIgnoreCase(".doc")) { return "application/msword"; } if (fileSuffix.equalsIgnoreCase(".xml")) { return "text/xml"; } // 默认返回类型 return "image/jpeg"; } /** * 创建存储空间 * * @param bucketName * @return */ public boolean createBucket(String bucketName) { try { boolean flag = ossClient.doesBucketExist(bucketName); if (!flag) { ossClient.createBucket(bucketName); // 设置存储空间的权限为公共读,默认是私有。 ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead); return true; } } catch (Exception e) { e.printStackTrace(); } return false; } /** * 删除存储空间 * * @param bucketName * @return */ public boolean deleteBucket(String bucketName) { try { boolean flag = ossClient.doesBucketExist(bucketName); if (flag) { ossClient.deleteBucket(bucketName); return true; } } catch (Exception e) { e.printStackTrace(); } return false; } /** * 列举所有的存储空间(名称) * * @return */ public List<String> listBuckets() { List<Bucket> buckets = ossClient.listBuckets(); List<String> bucketNames = new ArrayList<>(); for (Bucket bucket : buckets) { bucketNames.add(bucket.getName()); } return bucketNames; } }

    图像工具类

    需要添加依赖

    <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.11</version> </dependency> /** * 图像工具类 */ public class ImageUtil { // 压缩率 private static final transient float IMAGE_RATIO = 0.1f; // 压缩最大宽度 private static final transient int IMAGE_WIDTH = 800; // 水印透明度 private static float alpha = 0.3f; // 水印文字字体 private static Font font = new Font("PingFang SC Regular", Font.PLAIN, 36); // 水印文字颜色 private static Color color = new Color(111, 111, 111); // 水印文字内容 private static final String text = "这是一个水印文本"; // 水印之间的间隔 private static final int XMOVE = 80; // 水印之间的间隔 private static final int YMOVE = 80; /** * 压缩图像 * * @param image * @return * @throws IOException */ public static BufferedImage compress(BufferedImage image) throws IOException { Thumbnails.Builder<BufferedImage> imageBuilder = Thumbnails.of(image).outputQuality(IMAGE_RATIO); if (image.getWidth() > IMAGE_WIDTH) { return imageBuilder.width(IMAGE_WIDTH).asBufferedImage(); } else { return imageBuilder.scale(1).asBufferedImage(); } } /** * 图像添加水印 * * @param * @return */ public static BufferedImage setWatermark(BufferedImage image) throws IOException { return Thumbnails.of(image).outputQuality(IMAGE_RATIO).scale(1) .watermark(Positions.BOTTOM_RIGHT, createWatermark(text, image.getWidth(), image.getHeight()), alpha) .asBufferedImage(); } /** * 根据文件扩展名判断文件是否图片格式 * * @return */ public static boolean isImage(String fileName) { String[] imageExtension = new String[] { "jpeg", "jpg", "gif", "bmp", "png" }; for (String e : imageExtension) if (getFileExtention(fileName).toLowerCase().equals(e)) return true; return false; } /** * 获取文件后缀名称 * * @param fileName * @return */ public static String getFileExtention(String fileName) { String extension = fileName.substring(fileName.lastIndexOf(".") + 1); return extension; } /** * 根据图片对象获取对应InputStream * * @param image * @param readImageFormat * @return * @throws IOException */ public static InputStream getInputStream(BufferedImage image, String readImageFormat) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); ImageIO.write(image, readImageFormat, os); InputStream is = new ByteArrayInputStream(os.toByteArray()); os.close(); return is; } /** * 创建水印图片 * * @param text 水印文字 * @param width 图片宽 * @param height 图片高 * @return */ public static BufferedImage createWatermark(String text, int width, int height) { BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // 2.获取图片画笔 Graphics2D g = image.createGraphics(); // ---------- 增加下面的代码使得背景透明 ----------------- image = g.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT); g.dispose(); g = image.createGraphics(); // ---------- 背景透明代码结束 ----------------- // 6、处理文字 AttributedString ats = new AttributedString(text); ats.addAttribute(TextAttribute.FONT, font, 0, text.length()); // 7、设置对线段的锯齿状边缘处理 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 8、设置水印旋转 g.rotate(Math.toRadians(-30)); // 9、设置水印文字颜色 g.setColor(color); // 10、设置水印文字Font g.setFont(font); // 11、设置水印文字透明度 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha)); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); /** * 水印铺满图片 计算水印位置 */ int x = -width / 2; int y = -height / 2; int[] arr = getWidthAndHeight(text, font); int markWidth = arr[0];// 字体长度 int markHeight = arr[1];// 字体高度 // 循环添加水印 while (x < width * 1.5) { y = -height / 2; while (y < height * 1.5) { g.drawString(text, x, y); y += markHeight + YMOVE; } x += markWidth + XMOVE; } // 13、释放资源 g.dispose(); return image; } /** * 计算字体宽度及高度 * * @param text * @param font * @return */ private static int[] getWidthAndHeight(String text, Font font) { Rectangle2D r = font.getStringBounds(text, new FontRenderContext(AffineTransform.getScaleInstance(1, 1), false, false)); int unitHeight = (int) Math.floor(r.getHeight());// // 获取整个str用了font样式的宽度这里用四舍五入后+1保证宽度绝对能容纳这个字符串作为图片的宽度 int width = (int) Math.round(r.getWidth()) + 1; // 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度 int height = unitHeight + 3; return new int[] { width, height }; } }

    代码托管:springboot_oss

    Processed: 0.011, SQL: 10