|
|
|
@ -0,0 +1,523 @@
|
|
|
|
|
package cn.estsh.i3plus.ext.mes.apiservice.utils;
|
|
|
|
|
|
|
|
|
|
import com.obs.services.ObsClient;
|
|
|
|
|
import com.obs.services.model.*;
|
|
|
|
|
import groovy.util.logging.Slf4j;
|
|
|
|
|
import org.apache.commons.lang.StringUtils;
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
|
|
|
|
|
import java.io.*;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @Description : ObsUtil
|
|
|
|
|
* @Author :gsz
|
|
|
|
|
* @Date 2024/6/11 14:55
|
|
|
|
|
* @Modify
|
|
|
|
|
**/
|
|
|
|
|
@Slf4j
|
|
|
|
|
public class HuaWeiOBSUtil {
|
|
|
|
|
public static final Logger LOGGER = LoggerFactory.getLogger(HuaWeiOBSUtil.class);
|
|
|
|
|
//
|
|
|
|
|
// @Autowired
|
|
|
|
|
// HuaWeiCloudObsConfig obsConfig;
|
|
|
|
|
/**
|
|
|
|
|
* 文件外链过期时间,7天
|
|
|
|
|
*/
|
|
|
|
|
private static long expire = 7 * 24 * 60 * 60;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 文件外链访问端口
|
|
|
|
|
*/
|
|
|
|
|
private static String port = ":443";
|
|
|
|
|
|
|
|
|
|
private static String bucketName;
|
|
|
|
|
private static String ak;
|
|
|
|
|
private static String sk;
|
|
|
|
|
private static String endPoint;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* OBS操作客户端
|
|
|
|
|
*/
|
|
|
|
|
private static ObsClient obsClient = null;
|
|
|
|
|
|
|
|
|
|
private static final String SEPARATOR = "/";
|
|
|
|
|
|
|
|
|
|
public HuaWeiOBSUtil(String bucketName, String ak, String sk, String endPoint) {
|
|
|
|
|
HuaWeiOBSUtil.bucketName = bucketName;
|
|
|
|
|
HuaWeiOBSUtil.ak = ak;
|
|
|
|
|
HuaWeiOBSUtil.sk = sk;
|
|
|
|
|
HuaWeiOBSUtil.endPoint = endPoint;
|
|
|
|
|
createObsClientInstance();
|
|
|
|
|
}
|
|
|
|
|
// public HuaWeiOBSUtil(HuaWeiCloudObsConfig obsConfig) {
|
|
|
|
|
// HuaWeiOBSUtil.bucketName = obsConfig.getBucketName();
|
|
|
|
|
// HuaWeiOBSUtil.ak = obsConfig.getAk();
|
|
|
|
|
// HuaWeiOBSUtil.sk = obsConfig.getSk();
|
|
|
|
|
// HuaWeiOBSUtil.endPoint = obsConfig.getEndPoint();
|
|
|
|
|
// createObsClientInstance();
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
public static String getBucketName() {
|
|
|
|
|
return bucketName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static String getAk() {
|
|
|
|
|
return ak;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static String getSk() {
|
|
|
|
|
return sk;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static String getEndPoint() {
|
|
|
|
|
return endPoint;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取OBS操作客户端
|
|
|
|
|
*
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
private static void createObsClientInstance() {
|
|
|
|
|
try {
|
|
|
|
|
if (obsClient == null) {
|
|
|
|
|
synchronized (ObsClient.class) {
|
|
|
|
|
if (obsClient == null) {
|
|
|
|
|
obsClient = new ObsClient(ak, sk, endPoint);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
createBucket(bucketName, endPoint);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
LOGGER.error("连接华为云存储服务器异常:" + e.getMessage(), e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取上传文件的基础路径
|
|
|
|
|
*
|
|
|
|
|
* @return url
|
|
|
|
|
*/
|
|
|
|
|
public static String getBasisUrl() {
|
|
|
|
|
//实示例:http协议 + 存储桶名称 + . + endPoint + port + /
|
|
|
|
|
return getHttpProtocol(endPoint) + "://" + bucketName + "." + endPoint.replace(getHttpProtocol(endPoint) + "://", "") + port + SEPARATOR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取上传文件的基础路径
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String getBasisUrl(String bucketName) {
|
|
|
|
|
//实示例:http协议 + 存储桶名称 + . + endPoint + port + /
|
|
|
|
|
return getHttpProtocol(endPoint) + "://" + bucketName + "." + endPoint.replace(getHttpProtocol(endPoint) + "://", "") + port + SEPARATOR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取区域
|
|
|
|
|
*
|
|
|
|
|
* @param endPoint
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String getRegion(String endPoint) {
|
|
|
|
|
String substring = endPoint.substring(endPoint.indexOf(".") + 1);
|
|
|
|
|
return substring.substring(0, substring.indexOf("."));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取http协议
|
|
|
|
|
*
|
|
|
|
|
* @param endPoint
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String getHttpProtocol(String endPoint) {
|
|
|
|
|
return endPoint.substring(0, endPoint.indexOf(":"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建存储桶
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static void createBucket(String bucketName, String endPoint) {
|
|
|
|
|
if (!headBucket(bucketName)) {
|
|
|
|
|
CreateBucketRequest request = new CreateBucketRequest();
|
|
|
|
|
// 设置存储桶名称
|
|
|
|
|
request.setBucketName(bucketName);
|
|
|
|
|
// 设置桶区域位置,从endPoint中截取,如果Location设置的区域与endPoint中的区域不是同一个,则创建会报错
|
|
|
|
|
request.setLocation(getRegion(endPoint));
|
|
|
|
|
// 创建桶成功
|
|
|
|
|
obsClient.createBucket(request);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除存储桶
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static HeaderResponse deleteBucket(String bucketName) {
|
|
|
|
|
return obsClient.deleteBucket(bucketName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断存储桶是否存在
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static boolean headBucket(String bucketName) {
|
|
|
|
|
return obsClient.headBucket(bucketName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 上传字符
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @param content
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static PutObjectResult putObjectByStr(String bucketName, String objectName, String content) {
|
|
|
|
|
if (StringUtils.isBlank(content)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
return obsClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 上传输入流
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @param inputStream
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static PutObjectResult putObjectByInput(String bucketName, String objectName, InputStream inputStream) {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
return obsClient.putObject(bucketName, objectName, inputStream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 上传文件输入流
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @param fileInputStream
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static PutObjectResult putObjectByFileInput(String bucketName, String objectName, FileInputStream fileInputStream) {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
return obsClient.putObject(bucketName, objectName, fileInputStream);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 通过MultipartFile,上传文件
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @param media
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static PutObjectResult putObjectByMultipartFile(String bucketName, String objectName, MultipartFile media) throws IOException {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
return obsClient.putObject(bucketName, objectName, media.getInputStream());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 上传本地文件
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @param file
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static PutObjectResult putObjectByFile(String bucketName, String objectName, File file) {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
return obsClient.putObject(bucketName, objectName, file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 下载文件到本地
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @param filePath
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static boolean downloadObject(String bucketName, String objectName, String filePath) throws Exception {
|
|
|
|
|
if (StringUtils.isBlank(filePath)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
filePath = filePath.replace("\\", SEPARATOR);
|
|
|
|
|
|
|
|
|
|
InputStream input = null;
|
|
|
|
|
FileOutputStream fileOutputStream = null;
|
|
|
|
|
try {
|
|
|
|
|
// 获取对象
|
|
|
|
|
ObsObject obsObject = obsClient.getObject(bucketName, objectName);
|
|
|
|
|
// 读取对象内容
|
|
|
|
|
input = obsObject.getObjectContent();
|
|
|
|
|
|
|
|
|
|
if (input == null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//获取文件夹路径
|
|
|
|
|
if (filePath.contains(SEPARATOR)) {
|
|
|
|
|
String dir = filePath.substring(0, filePath.lastIndexOf(SEPARATOR));
|
|
|
|
|
File difFile = new File(dir);
|
|
|
|
|
if (!difFile.exists()) {
|
|
|
|
|
//创建文件夹
|
|
|
|
|
boolean mkdirs = difFile.mkdirs();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
File file = new File(filePath);
|
|
|
|
|
fileOutputStream = new FileOutputStream(file);
|
|
|
|
|
|
|
|
|
|
byte[] b = new byte[1024];
|
|
|
|
|
int len;
|
|
|
|
|
while ((len = input.read(b)) != -1) {
|
|
|
|
|
fileOutputStream.write(b, 0, len);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
} finally {
|
|
|
|
|
if (fileOutputStream != null) {
|
|
|
|
|
fileOutputStream.close();
|
|
|
|
|
}
|
|
|
|
|
if (input != null) {
|
|
|
|
|
input.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取文件内容
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String getObjectContent(String bucketName, String objectName) throws IOException {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
InputStream input = null;
|
|
|
|
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
|
|
|
try {
|
|
|
|
|
ObsObject obsObject = obsClient.getObject(bucketName, objectName);
|
|
|
|
|
// 读取对象内容
|
|
|
|
|
input = obsObject.getObjectContent();
|
|
|
|
|
|
|
|
|
|
byte[] b = new byte[1024];
|
|
|
|
|
int len;
|
|
|
|
|
while ((len = input.read(b)) != -1) {
|
|
|
|
|
bos.write(b, 0, len);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new String(bos.toByteArray());
|
|
|
|
|
} finally {
|
|
|
|
|
bos.close();
|
|
|
|
|
if (input != null) {
|
|
|
|
|
input.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取文件输入流
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static InputStream getObject(String bucketName, String objectName) {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
return obsClient.getObject(bucketName, objectName).getObjectContent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 指定数目和前缀列举
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param prefix
|
|
|
|
|
* @param maxKeys
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static List<ObsObject> listObjects(String bucketName, String prefix, Integer maxKeys) {
|
|
|
|
|
prefix = prefix.startsWith("/") ? prefix.substring(1) : prefix;
|
|
|
|
|
|
|
|
|
|
ListObjectsRequest request = new ListObjectsRequest(bucketName);
|
|
|
|
|
// 设置列举的对象个数
|
|
|
|
|
request.setMaxKeys(maxKeys);
|
|
|
|
|
// 设置列举的对象需要带有指定前缀
|
|
|
|
|
request.setPrefix(prefix);
|
|
|
|
|
ObjectListing result = obsClient.listObjects(request);
|
|
|
|
|
|
|
|
|
|
return result.getObjects();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 列举指定前缀的全部对象
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param prefix
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static List<ObsObject> listAllObjects(String bucketName, String prefix) {
|
|
|
|
|
prefix = prefix.startsWith("/") ? prefix.substring(1) : prefix;
|
|
|
|
|
|
|
|
|
|
List<ObsObject> list = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
ListObjectsRequest request = new ListObjectsRequest(bucketName);
|
|
|
|
|
// 设置列举的对象个数
|
|
|
|
|
request.setMaxKeys(1000);
|
|
|
|
|
// 设置列举的对象需要带有指定前缀
|
|
|
|
|
request.setPrefix(prefix);
|
|
|
|
|
|
|
|
|
|
ObjectListing result;
|
|
|
|
|
do {
|
|
|
|
|
result = obsClient.listObjects(request);
|
|
|
|
|
request.setMarker(result.getNextMarker());
|
|
|
|
|
|
|
|
|
|
list.addAll(result.getObjects());
|
|
|
|
|
} while (result.isTruncated());
|
|
|
|
|
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除单个对象
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static DeleteObjectResult deleteObject(String bucketName, String objectName) {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
return obsClient.deleteObject(bucketName, objectName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 复制对象
|
|
|
|
|
*
|
|
|
|
|
* @param sourceBucketName
|
|
|
|
|
* @param sourceObjectName
|
|
|
|
|
* @param destBucketName
|
|
|
|
|
* @param destObjectName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static CopyObjectResult copyObject(String sourceBucketName, String sourceObjectName,
|
|
|
|
|
String destBucketName, String destObjectName) {
|
|
|
|
|
return obsClient.copyObject(sourceBucketName, sourceObjectName, destBucketName, destObjectName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断对象是否存在
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static boolean doesObjectExist(String bucketName, String objectName) {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
return obsClient.doesObjectExist(bucketName, objectName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取文件外链
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @param expires 单位:秒(s)
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String getSignedUrl(String bucketName, String objectName, Long expires) {
|
|
|
|
|
//重新构建objectName
|
|
|
|
|
objectName = buildObjectName(objectName);
|
|
|
|
|
|
|
|
|
|
TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expires);
|
|
|
|
|
request.setBucketName(bucketName);
|
|
|
|
|
request.setObjectKey(objectName);
|
|
|
|
|
TemporarySignatureResponse response = obsClient.createTemporarySignature(request);
|
|
|
|
|
return response.getSignedUrl();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取文件外链-url有效时间默认7天
|
|
|
|
|
*
|
|
|
|
|
* @param bucketName
|
|
|
|
|
* @param objectName
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String getSignedUrl(String bucketName, String objectName) {
|
|
|
|
|
return getSignedUrl(bucketName, objectName, expire);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 重新构建objectName
|
|
|
|
|
*
|
|
|
|
|
* @param objectName
|
|
|
|
|
*/
|
|
|
|
|
private static String buildObjectName(String objectName) {
|
|
|
|
|
if (StringUtils.isBlank(objectName)) {
|
|
|
|
|
return objectName;
|
|
|
|
|
}
|
|
|
|
|
//去除开头的/
|
|
|
|
|
objectName = objectName.startsWith("/") ? objectName.substring(1) : objectName;
|
|
|
|
|
//去除?后边的参数
|
|
|
|
|
objectName = objectName.contains("?") ? objectName.substring(0, objectName.indexOf("?")) : objectName;
|
|
|
|
|
|
|
|
|
|
return objectName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 传入文件访问外链,返回objectName
|
|
|
|
|
*
|
|
|
|
|
* @param url
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
public static String getObjectNameByUrl(String url) {
|
|
|
|
|
if (StringUtils.isBlank(url)) {
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (url.contains(getBasisUrl())) {
|
|
|
|
|
// 去除minio基础路径
|
|
|
|
|
url = url.replace(getBasisUrl(), "");
|
|
|
|
|
// 去除?后边的参数
|
|
|
|
|
url = url.contains("?") ? url.substring(0, url.indexOf("?")) : url;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return url;
|
|
|
|
|
}
|
|
|
|
|
}
|