Java中OSS存储使用

编程入门 行业动态 更新时间:2024-10-13 00:30:39

<a href=https://www.elefans.com/category/jswz/34/1770091.html style=Java中OSS存储使用"/>

Java中OSS存储使用

本篇文章希望你先看完官方的API了解一下OSS基本语法和概念再来应该比较容易懂。

  • 这里给出官方的OSS API:OSS API
  • 官方SDK:点击打开链接
  • 官方帮助文档:点击打开链接
  • OSS GitHub地址:点击打开链接
  • OSS java 依赖jar包地址:点击下载
  • 参考文章:

2018-02-03 更新,优化web直传代码,加入注释,新增生成文件夹、移动端临时上传下载、生成签名url

2017-12-21 更新,修复上传图片OSS报错,仍会返回url的BUG,操作是修改uploadFile()和uploadWebFile()方法,在catch打印日志后return null。

2017-11-29 更新,加入web直传需要的 StsServiceUtil,获取文件名的改为截取accessUrl部分,配置变量加入示例,测试类加入baseUrl,更简洁。

一、上传规范

主要用到了aliyun-sdk-oss,aliyun-java-sdk-core,aliyun-java-sdk-sts,joda-time等包。

1.上传图片主要分为本地文件上传(文件流方式)和网络文件上传(如获取微信头像)上传

1)上传本地文件需要参数有InputStraem流、directory目录和fileName文件名,目录是在项目底下的目录,由于不建议直接在项目oss文件夹直接上传图片,所以目录是一定要传的。当传入目录为空时,会直接取文件名,一般是更新和替换的时候传null。

上传自定义格式本地文件用upload(),png格式图片用uploadImage()方法,mp4格式视频用uploadVedio()方法文件名已封装,如果想自己生成文件名直接调用底层uploadFile()方法

2)上传网络图片需要参数有fileUrl文件地址、directory目录和fileName文件名,png格式图片用uploadWebImage()方法,文件名已封装,如果想自定义文件名直接调用底层uploadWebFile()方法。

2.如果想更新文件有两种方式,一种是直接更新文件,只更新内容,不更新文件名和文件地址。另一种是替换文件,删除原文件并上传新文件,文件名和地址同时替换。

参数传入InputStream流和fileUrl原文件名。

1)直接更新文件,这个可能存在浏览器原数据缓存,调用updateFile()方法。

2)删除替换文件,由于地址更好,解决缓存问题 ,调用replaceFile()方法。建议更新图片用这种方式。

3.查询文件是否存在

参数传入fileUrl文件地址,通过调用doesObjectExist方法判断返回值。

4.删除文件,分为单个删除和批量删除

参数传入fileUrl文件地址或fileUrls文件地址集合

1.单个上传,调用deleteObject方法。

2.批量上传,调用deleteObjects()方法和deleteBatchObject()方法,deleteObjects()方法主要是用在同一个endpoint和bucketName ,底层是调用OSS的deleteObjects()方法,速度较快

而deleteBatchObject()还适用于不同的endPoint和bucketName ,底层调用的是我们封装的deleteObject()方法,由于还是一个个去删,速度较慢

5.Web直传获取签名

注意:为了浏览安全,必须为bucket设置CORS。

主要用于网页端上传时,网页端向服务端请求签名,然后直接上传,不用对服务端产生压力。而且安全可靠。但是这个例子有一个特点,就是用户上传了多少文件,用户上传了什么文件,用户后端程序并不能马上知道,如果想实时知晓用户上传了什么文件,可以采用上传回调。本例子无法实现分片与断点。

详情:.html

http://172.16.1.98:83/wordpress/2017/08/30/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E9%85%8D%E5%90%88web%E7%AB%AF%E4%B8%8A%E4%BC%A0%E8%A7%86%E9%A2%91/

http://172.16.1.98:83/wordpress/2017/08/23/%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%AD%BE%E5%90%8D%E5%90%8E%E4%B8%8A%E4%BC%A0%E6%96%87%E4%BB%B6%E5%88%B0oss/

二、对文件的存储操作

注意需要修改配置!!!

注意需要修改配置!!!

注意需要修改配置!!!

package com.lemo.face.util.oss;import com.googlemon.base.Preconditions;
import com.googlemon.collect.Lists;
import com.googlemon.collect.Maps;import com.aliyun.oss.OSSClient;
import com.aliyun.ossmon.utils.BinaryUtil;
import com.aliyun.ossmon.utils.IOUtils;
import com.aliyun.oss.model.DeleteObjectsRequest;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.ListObjectsRequest;
import com.aliyun.oss.model.MatchMode;
import com.aliyun.oss.model.OSSObjectSummary;
import com.aliyun.oss.model.ObjectListing;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PolicyConditions;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;import org.apachemons.collections.CollectionUtils;
import org.apachemons.lang3.RandomStringUtils;
import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;import java.io.File;
import java.io.InputStream;
import java.URL;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;import lombok.extern.slf4j.Slf4j;/*** @author 杨小华* @desc 阿里文件云存储管理类* @create 2017/11/27 14:25**/
@Slf4j
public class OSSClientUtil {static final String ACCESS_KEY_ID = "";static final String ACCESS_KEY_SECRET = "";/*** 用户的存储空间(bucket)名称*/static final String BUCKET_NAME = "";/*** 对应的映射域名*/private static final String ACCESS_URL = "/";/*** 用户的存储空间所在数据中心的访问域名*/private static final String ENDPOINT = "oss-cn-hangzhou.aliyuncs";/*** 指定项目文件夹*/private static final String PIC_LOCATION = "face/";/*** 加密密钥*/private static final String KEY = "biaoqing";private static OSSClient ossClient;/*** @desc 静态初始化ossClient* @author 杨小华* @create 2017/11/25 12:44**/static {ossClient = new OSSClient(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);}/*** @desc 上传自定义格式* @author 杨小华* @create 2017/11/24 11:09**/public static String upload(InputStream is, String directory, String fileType) {String fileName = getFileName() + "." + fileType;return uploadFile(is, directory, fileName);}/*** @desc 上传视频指定mp4格式* @author 杨小华* @create 2017/11/22 15:00**/public static String uploadVideo(InputStream is, String directory) {String fileName = getFileName() + ".mp4";return uploadFile(is, directory, fileName);}/*** @desc 上传图片指定png格式* @author 杨小华* @create 2017/11/22 14:59**/public static String uploadImage(InputStream is, String directory) {String fileName = getFileName() + ".png";return uploadFile(is, directory, fileName);}/*** @desc 上传网络图片指定png格式* @author 杨小华* @create 2017/11/25 13:37**/public static String uploadWebImage(String fileUrl, String directory) {String fileName = getFileName() + ".png";return uploadWebFile(fileUrl, directory, fileName);}/*** @desc 上传本地文件(文件流上传)* @author 杨小华* @create 2017/11/23 19:57**/public static String uploadFile(InputStream is, String directory, String fileName) {String key = PIC_LOCATION + directory + "/" + fileName;if (Objects.isNull(directory)) {key = fileName;}try {ObjectMetadata objectMetadata = getObjectMetadata(is.available());ossClient.putObject(BUCKET_NAME, key, is, objectMetadata);} catch (Exception e) {log.error(e.getMessage());return null;} finally {IOUtils.safeClose(is);}return ACCESS_URL + key;}/*** @desc 上传网络图片* @author 杨小华* @create 2017/11/24 15:43**/public static String uploadWebFile(String fileUrl, String directory, String fileName) {String key = PIC_LOCATION + directory + "/" + fileName;InputStream is = null;try {Integer length = new URL(fileUrl).openConnection().getContentLength();is = new URL(fileUrl).openStream();ObjectMetadata objectMetadata = getObjectMetadata(length);ossClient.putObject(BUCKET_NAME, key, is, objectMetadata);} catch (Exception e) {log.error(e.getMessage());return null;} finally {IOUtils.safeClose(is);}return ACCESS_URL + key;}/*** @desc 更新文件:只更新内容,不更新文件名和文件地址。 (因为地址没变,可能存在浏览器原数据缓存,不能及时加载新数据,例如图片更新,请注意)* @author 杨小华* @create 2017/11/23 20:40**/public static String updateFile(InputStream is, String fileUrl) {String key = getFileNameByUrl(fileUrl);return uploadFile(is, null, key);}/*** @desc 替换文件:删除原文件并上传新文件,文件名和地址同时替换 解决原数据缓存问题,只要更新了地址,就能重新加载数据)* @author 杨小华* @create 2017/11/24 14:19**/public static String replaceFile(InputStream is, String fileUrl) {boolean flag = deleteObject(fileUrl);String fileName = getFileNameByUrl(fileUrl);if (!flag) {return null;}return uploadFile(is, null, fileName);}/*** @desc 查询文件是否存在* @author 杨小华* @create 2017/11/24 10:09**/public static boolean doesObjectExist(String key) {boolean result = false;try {//如果带http,提取key值if (key.indexOf("http") != -1) {key = getFileNameByUrl(key);}result = ossClient.doesObjectExist(BUCKET_NAME, key);} catch (Exception e) {log.error(e.getMessage());}return result;}/*** @desc 删除Object。 注意:以下所有删除如果文件不存在返回的是true,如果需要先判断是否存在先调用doesObjectExist()方法* @author 杨小华* @create 2017/11/24 10:06**/public static boolean deleteObject(String fileUrl) {try {String key = getFileNameByUrl(fileUrl);ossClient.deleteObject(BUCKET_NAME, key);} catch (Exception e) {log.error(e.getMessage());return false;}return true;}/*** @desc 批量删除object(适用于相同的endpoint和bucketName)* @author 杨小华* @create 2017/11/24 10:02**/public static int deleteObjects(List<String> fileUrls) {int count = 0;List<String> keys = getFileNamesByUrl(fileUrls);try {// 删除ObjectsDeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(BUCKET_NAME);deleteRequest.withKeys(keys);count = ossClient.deleteObjects(deleteRequest).getDeletedObjects().size();} catch (Exception e) {log.error(e.getMessage());}return count;}/*** @desc 批量文件删除(较慢, 适用于不同endpoint和bucketName)* @author 杨小华* @create 2017/11/24 13:03**/public static int deleteBatchObject(List<String> fileUrls) {int count = 0;for (String url : fileUrls) {if (deleteObject(url)) {count++;}}return count;}/*** @desc web直传获取签名* @author 杨小华* @create 2017/9/11 13:01**/public static Map<String, Object> getWebSign(String callbackUrl, int seconds) {Map<String, Object> data = Maps.newHashMap();// 存储目录String dir = PIC_LOCATION + "web/" + getDirectory();//回调内容Map<String, String> callback = Maps.newHashMap();callback.put("callbackUrl", callbackUrl);callback.put("callbackBody", "filename=${object}&size=${size}&mimeType=${mimeType}&height" +"=${imageInfo.height}&width=${imageInfo.width}");callback.put("callbackBodyType", "application/x-www-form-urlencoded");//签名有效期30秒过期Date expiration = DateTime.now().plusSeconds(seconds).toDate();// 提交节点String host = "http://" + BUCKET_NAME + "." + ENDPOINT;try {PolicyConditions policyConds = new PolicyConditions();// 设置上传文件的大小限制policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0,1048576000);//指定此次上传的文件名必须是dir变量的值开头policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);//policyString policy = BinaryUtil.toBase64String(postPolicy.getBytes("utf-8"));//签名String signature = ossClient.calculatePostSignature(postPolicy);//回调String callbackData = BinaryUtil.toBase64String(callback.toString().getBytes("utf-8"));data.put("policy", policy);data.put("signature", signature);data.put("callback", callbackData);data.put("dir", dir);data.put("accessKeyId", ACCESS_KEY_ID);data.put("accessUrl", ACCESS_URL);data.put("host", host);data.put("expire", expiration);} catch (Exception e) {log.error(e.getMessage(), e);return null;}return data;}/*** 移动端临时授权上传** @return java.util.Map<java.lang.String,java.lang.Object>* @author 杨小华* @date 2018/1/29 14:11* @since 1.0.0*/public static Map<String, Object> getSTSWrite() {Map<String, Object> data = Maps.newHashMap();// 设置文件目录String folder = PIC_LOCATION + "app/" + getDirectory();//权限验证AssumeRoleResponse.Credentials credentials = STSUtil.createSTSForPutObject(folder);Preconditions.checkState(Objects.nonNull(credentials), "权限获取失败,请重试");AssumeRoleResponse.Credentials encryptCredentials = DESUtil.encrypt(credentials, KEY);//完整文件名data.put("oss", encryptCredentials);data.put("folder", folder);data.put("endpoint", ENDPOINT);data.put("buckName", BUCKET_NAME);data.put("accessUrl", ACCESS_URL);return data;}/*** 移动端临时授权下载** @return java.util.Map<java.lang.String,java.lang.Object>* @author 杨小华* @date 2018/1/31 15:33* @since 1.0.0*/public static Map<String, Object> getSTSRead() {Map<String, Object> data = Maps.newHashMap();//权限验证AssumeRoleResponse.Credentials credentials = STSUtil.createSTSForReadOnly();Preconditions.checkState(Objects.nonNull(credentials), "权限获取失败,请重试");AssumeRoleResponse.Credentials encryptCredentials = DESUtil.encrypt(credentials, KEY);//完整文件名data.put("oss", encryptCredentials);data.put("endpoint", ENDPOINT);data.put("buckName", BUCKET_NAME);return data;}/*** 私有型bucket,生成签名URL** @param key     表情地址,如face/zip/test.zip* @param seconds 多少秒后过期* @return java.lang.String* @author 杨小华* @date 2018/2/2 14:32* @since 1.0.0*/public static String getSignUrl(String key, int seconds) {//如果key值不存在Preconditions.checkArgument(doesObjectExist(key), "key值不存在");Date expire = DateTime.now().plusSeconds(seconds).toDate();GeneratePresignedUrlRequest generatePresignedUrlRequest =new GeneratePresignedUrlRequest(BUCKET_NAME, key);generatePresignedUrlRequest.setExpiration(expire);URL url = ossClient.generatePresignedUrl(generatePresignedUrlRequest);return url.toString();}/*** 单个下载文件** @param key     文件key值* @param fileUrl 目标文件路径名称* @return boolean* @author 杨小华* @date 2018/2/2 18:08* @since 1.0.0*/public static boolean getObject(String key, String fileUrl) {ObjectMetadata object = ossClient.getObject(new GetObjectRequest(BUCKET_NAME, key), newFile(fileUrl));if (Objects.nonNull(object)) {return true;}return false;}/*** 批量下载文件** @param preFix 下载某个文件夹中的所有* @param dir    目标目录* @return java.lang.String* @author 杨小华* @date 2018/2/2 17:33* @since 1.0.0*/public static String listObject(String preFix, String dir) {// 构造ListObjectsRequest请求ListObjectsRequest listObjectsRequest = new ListObjectsRequest(BUCKET_NAME);//Delimiter 设置为 “/” 时,罗列该文件夹下的文件listObjectsRequest.setDelimiter("/");//Prefix 设为某个文件夹名,罗列以此 Prefix 开头的文件listObjectsRequest.setPrefix(preFix);ObjectListing listing = ossClient.listObjects(listObjectsRequest);//如果改目录下没有文件返回nullif (CollectionUtils.isEmpty(listing.getObjectSummaries())) {return null;}// 取第一个目录的keyFile file = new File(dir + listing.getObjectSummaries().get(0).getKey());//判断文件所在本地路径是否存在,若无,新建目录File fileParent = file.getParentFile();if (!fileParent.exists()) {fileParent.mkdirs();}// 遍历所有Object:目录下的文件for (OSSObjectSummary objectSummary : listing.getObjectSummaries()) {//Bucket中存储文件的路径String key = objectSummary.getKey();//下载object到文件ossClient.getObject(new GetObjectRequest(BUCKET_NAME, key), file);}return file.getParent();}/*** @desc 生成文件名* @author 杨小华* @create 2017/11/24 11:15**/public static String getFileName() {return LocalDateTime.now().toString("yyyyMMddHHmmssSSS_") + RandomStringUtils.randomNumeric(6);}/*** 生成目录** @return java.lang.String* @author 杨小华* @date 2018/1/29 19:00* @since 1.0.0*/public static String getDirectory() {return LocalDateTime.now().toString("yyyy-MM-dd");}/*** @desc 根据url获取fileName* @author 杨小华* @create 2017/11/23 20:40**/private static String getFileNameByUrl(String fileUrl) {int beginIndex = fileUrl.indexOf(ACCESS_URL);//针对单个图片处理的图片int endIndex = fileUrl.indexOf("?");//针对使用模板图片处理的图片int endIndex2 = fileUrl.indexOf("@!");if (beginIndex == -1) {return null;}if (endIndex != -1) {return fileUrl.substring(beginIndex + ACCESS_URL.length(), endIndex);}if (endIndex2 != -1) {return fileUrl.substring(beginIndex + ACCESS_URL.length(), endIndex2);}return fileUrl.substring(beginIndex + ACCESS_URL.length());}/*** @desc 根据url获取fileNames集合* @author 杨小华* @create 2017/11/23 20:42**/private static List<String> getFileNamesByUrl(List<String> fileUrls) {List<String> fileNames = Lists.newArrayList();for (String url : fileUrls) {fileNames.add(getFileNameByUrl(url));}return fileNames;}/*** @desc ObjectMetaData是用户对该object的描述,* 由一系列name-value对组成;其中ContentLength是必须设置的,以便SDK可以正确识别上传Object的大小* @author 杨小华* @create 2017/11/23 20:12**/private static ObjectMetadata getObjectMetadata(long length) {ObjectMetadata objectMetadata = new ObjectMetadata();objectMetadata.setContentLength(length);// 被下载时网页的缓存行为objectMetadata.setCacheControl("no-cache");objectMetadata.setHeader("Pragma", "no-cache");return objectMetadata;}
}

临时授权:

http://172.16.1.98:83/wordpress/2018/02/01/oss%E7%9A%84%E4%B8%B4%E6%97%B6%E6%8E%88%E6%9D%83%E8%AE%B0%E5%BD%95/

三、Junit4单元测试代码

package com.lemo.erp.base;import com.googlemon.collect.Lists;
import com.lemo.erp.plugins.oss.OSSClientUtil;
import org.junit.Assert;
import org.junit.Test;import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Map;/*** @author 杨小华* @desc Oss测试类* @create 2017/11/27 8:56**/
public class OSSClientUtilTest {private String baseUrl = "/";/*** @desc 测试上传任意格式的文件(如xml)* @author 杨小华* @create 2017/11/25 12:49**/@Testpublic void testUpload() throws Exception {File file = new File("C:\\Users\\leimo\\Desktop\\test\\test.xml");InputStream is = new FileInputStream(file);String result = OSSClientUtil.upload(is, "test", "xml");Assert.assertTrue(result.contains("xml"));}/*** @desc 测试上传视频* @author 杨小华* @create 2017/11/25 13:28**/@Testpublic void testUploadVideo() throws Exception {File file = new File("C:\\Users\\leimo\\Desktop\\test\\test.mp4");InputStream is = new FileInputStream(file);String result = OSSClientUtil.uploadVideo(is, "test");Assert.assertTrue(result.contains("mp4"));}/*** @desc 测试上传图片* @author 杨小华* @create 2017/11/25 13:25**/@Testpublic void testUploadImage() throws Exception {File file = new File("C:\\Users\\leimo\\Desktop\\test\\test.jpg");InputStream is = new FileInputStream(file);String result = OSSClientUtil.uploadImage(is, "test");Assert.assertTrue(result.contains("png"));}/*** @desc 测试上传网络图片* @author 杨小华* @create 2017/11/24 16:01**/@Testpublic void testUploadWebImage() throws Exception {String headImg = ".png";String result = OSSClientUtil.uploadWebImage(headImg, "test");Assert.assertTrue(result.contains("png"));}/*** @desc 测试上传网络图片指定名称abc.png* @author 杨小华* @create 2017/11/25 13:41**/@Testpublic void testUploadWebFile() throws Exception {String headImg = ".png";String result = OSSClientUtil.uploadWebFile(headImg, "test", "abc.png");Assert.assertTrue(result.contains("test/abc.png"));}/*** @desc 用来测试批量删除的图片123.png* @author 杨小华* @create 2017/11/25 15:20**/@Testpublic void testUploadWebFile2() throws Exception {String headImg = ".png";String result = OSSClientUtil.uploadWebFile(headImg, "test", "123.png");Assert.assertTrue(result.contains("test/123.png"));}/*** @desc 测试更新图片* @author 杨小华* @create 2017/11/24 16:03**/@Testpublic void testUpdate() throws Exception {String url = baseUrl + "test/abc.png";File file = new File("C:\\Users\\leimo\\Desktop\\test\\test.jpg");InputStream is = new FileInputStream(file);String result = OSSClientUtil.updateFile(is, url);Assert.assertEquals(url, result);}/*** @desc 测试替换图片* @author 杨小华* @create 2017/11/24 16:22**/@Testpublic void testReplaceFile() throws Exception {String url = baseUrl + "test/abc.png";File file = new File("C:\\Users\\leimo\\Desktop\\test\\test.jpg");InputStream is = new FileInputStream(file);String result = OSSClientUtil.replaceFile(is, url);Assert.assertEquals(url, result);}/*** @desc 测试查询* @author 杨小华* @create 2017/11/24 17:31**/@Testpublic void testDoesObjectExist() throws Exception {String url = baseUrl + "test/abc.png?200";boolean result = OSSClientUtil.doesObjectExist(url);Assert.assertTrue(result);}/*** @desc 测试单个删除* @author 杨小华* @create 2017/11/24 17:33**/@Testpublic void testDeleteObject() throws Exception {String url = baseUrl + "test/abc.png";boolean result = OSSClientUtil.deleteObject(url);System.out.println(result);Assert.assertTrue(result);}/*** @desc 测试批量删除(同一endpoint和buckName)* @author 杨小华* @create 2017/11/25 13:45**/@Testpublic void testDeleteObjects() throws Exception {List<String> fileUrls = Lists.newArrayList();fileUrls.add(baseUrl + "test/abc.png");fileUrls.add(baseUrl + "test/123.png");int count = OSSClientUtil.deleteObjects(fileUrls);Assert.assertEquals(fileUrls.size(), count);}/*** @desc 测试批量删除(适用不同endpoint和buckName)* @author 杨小华* @create 2017/11/25 15:18**/@Testpublic void testDeleteBatchObject() {List<String> fileUrls = Lists.newArrayList();fileUrls.add(baseUrl + "test/abc.png");fileUrls.add(baseUrl + "test/123.png");int count = OSSClientUtil.deleteBatchObject(fileUrls);Assert.assertEquals(fileUrls.size(), count);}/*** @desc 测试获取web直传签名* @author 杨小华* @create 2017/11/27 9:38**/@Testpublic void testGetSign() {Map<String, Object> sign = OSSClientUtil.getSign();Assert.assertTrue(sign.containsKey("endpoint"));}}

四、图片处理

1.单张不同的图片格式处理可采用直接在后面拼接或用代码去请求,如:

// 缩放
String style = "image/resize,m_fixed,w_100,h_100";  
GetObjectRequest request = new GetObjectRequest(bucketName, key);
request.setProcess(style);ossClient.getObject(request, new File("example-resize.jpg"));// 裁剪
style = "image/crop,w_100,h_100,x_100,y_100,r_1"; 
request = new GetObjectRequest(bucketName, key);
request.setProcess(style);ossClient.getObject(request, new File("example-crop.jpg"));

2.多张图片共用同一种格式,采用模板的方式,如用户头像框,可能有多个用户上传,他们的图片格式是一样的。

.png?image/crop,w_100,h_100,x_100,y_100,r_1 让凌霄配置这种格式的模板名称如@!crop,使用的时候在图片后面加上这个模板名称如 .png@!crop 以下是示例图片:

 

更多推荐

Java中OSS存储使用

本文发布于:2024-03-05 23:50:51,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1713786.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:Java   OSS

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!