移植文件上传:七牛云换腾讯云

This commit is contained in:
max
2024-04-12 16:16:37 +08:00
parent 5928151853
commit fdd6f1836f
17 changed files with 747 additions and 573 deletions

View File

@@ -251,7 +251,7 @@ class UploadRoomAlbumDialogFragment : BottomSheetDialogFragment() {
}
if (requestCode == 200) {
PhotoProvider.getResultPathListAsync(data) {
it?.let { paths ->
it?.mapNotNull { it.path }?.let { paths ->
compressPhotos(paths.toMutableList())
}
}

View File

@@ -207,7 +207,7 @@ class PhotoDialog : BaseDialogFragment<PhotoDialogBinding>(), EasyPermissions.Pe
REQUEST_CODE_OPEN_CAMERA_PROVIDER -> {
if (mOnResultCallBack == null || data == null) return
PhotoProvider.getResultPathListAsync(data) { paths ->
val list = paths?.toMutableList() ?: ArrayList()
val list = paths?.mapNotNull { it.path }?.toMutableList() ?: ArrayList()
val path = list[0]
if (!TextUtils.isEmpty(path)) {
mJob?.cancel()
@@ -233,7 +233,7 @@ class PhotoDialog : BaseDialogFragment<PhotoDialogBinding>(), EasyPermissions.Pe
REQUEST_CODE_OPEN_PHOTO_PROVIDER -> {
if (mOnResultCallBack == null || data == null) return
PhotoProvider.getResultPathListAsync(data) { list ->
val paths = list?.toMutableList() ?: ArrayList()
val paths = list?.mapNotNull { it.path }?.toMutableList() ?: ArrayList()
if (paths.isEmpty()) {
mOnResultCallBack?.choicePhotoCallBack(paths)
} else {

View File

@@ -130,7 +130,7 @@ object PhotoProvider {
}
@JvmStatic
fun getResultPathListAsync(data: Intent?, resultListener: ((List<String>?) -> Unit)) {
fun getResultPathListAsync(data: Intent?, resultListener: ((List<Photo>?) -> Unit)) {
cancelJop()
mPhotoJob = MainScope().launch {
val list: List<Photo>? = data?.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS)
@@ -150,22 +150,21 @@ object PhotoProvider {
* 1. 项目使用到BitmapFactory.decodeFile(imgPath, options)之类的方法该方法在android Q直接使用外部path测试中发现获取图片信息失败
*
*/
private fun copyToInternalCache(photos: List<Photo>?): List<String>? {
private fun copyToInternalCache(photos: List<Photo>?): List<Photo>? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val foldPath = getInternalPath() + File.separator
val newPaths = ArrayList<String>()
photos?.forEach {
if (it.uri != null && !it.name.isNullOrEmpty()) {
val path = "$foldPath${it.name}"
if (FileHelper.copyFileFromUri(it.uri, path, true)) {
newPaths.add(path)
it.path = path
LibLogger.debug(TAG, "path: ${it.path} , displayName: ${it.name} , newPath: $path ")
}
}
}
newPaths
photos
} else {
photos?.takeIf { it.isNotEmpty() }?.map { it.path }
photos
}
}

View File

@@ -22,6 +22,22 @@ public class ObjectTypeHelper {
return resultList;
}
public static List<DynamicMedia> customToMediaList(List<CustomItem> paramsList) {
List<DynamicMedia> resultList = new ArrayList<>();
if (paramsList == null) {
return resultList;
}
for (CustomItem item : paramsList) {
DynamicMedia media = new DynamicMedia();
media.setLocalFilePath(item.getPath());
media.setWidth(item.getWidth());
media.setHeight(item.getHeight());
media.setFormat(item.getFormat());
resultList.add(media);
}
return resultList;
}
public static List<CustomItem> stringToCustomList(List<String> paramsList) {
List<CustomItem> resultList = new ArrayList<>();
if (paramsList == null) {

View File

@@ -36,6 +36,10 @@ public class CustomItem implements Parcelable, Serializable {
private String format;
private int width;
private int height;
public String getPath(){
return path;
}
@@ -66,7 +70,7 @@ public class CustomItem implements Parcelable, Serializable {
}
public CustomItem(String path, int fileType) {
this(path, fileType, "jpeg");
this(path, fileType, "jpeg",0,0);
}
public CustomItem() {
@@ -82,12 +86,16 @@ public class CustomItem implements Parcelable, Serializable {
dest.writeString(this.path);
dest.writeInt(this.fileType);
dest.writeString(this.format);
dest.writeInt(this.width);
dest.writeInt(this.height);
}
protected CustomItem(Parcel in) {
this.path = in.readString();
this.fileType = in.readInt();
this.format = in.readString();
this.width = in.readInt();
this.height = in.readInt();
}
public static final Creator<CustomItem> CREATOR = new Creator<CustomItem>() {

View File

@@ -2,24 +2,23 @@ package com.chwl.core.file;
import android.text.TextUtils;
import com.netease.nim.uikit.common.util.log.LogUtil;
import com.qiniu.android.common.FixedZone;
import com.qiniu.android.storage.Configuration;
import com.qiniu.android.storage.UploadManager;
import com.chwk.core.file.cos.CosClient;
import com.chwk.core.file.cos.CosToken;
import com.chwl.core.utils.net.RxHelper;
import com.example.lib_utils.AppUtils;
import com.example.lib_utils.PathUtils;
import com.chwl.core.R;
import com.chwl.core.base.BaseModel;
import com.chwl.core.bean.response.ServiceResult;
import com.chwl.core.community.bean.DynamicMedia;
import com.chwl.core.exception.ErrorThrowable;
import com.chwl.core.utils.net.RxHelper;
import com.chwl.library.net.rxnet.RxNet;
import com.chwl.library.utils.ResUtil;
import org.json.JSONObject;
import java.io.File;
import java.util.UUID;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import retrofit2.http.GET;
public class FileModel extends BaseModel implements IFileModel {
@@ -29,120 +28,51 @@ public class FileModel extends BaseModel implements IFileModel {
}
private FileModel() {
Configuration config = new Configuration.Builder()
.zone(FixedZone.zoneAs0) // 设置区域不指定会自动选择。指定不同区域的上传域名、备用域名、备用IP。
.build();
uploadManager = new UploadManager(config);
}
public static FileModel get() {
return Helper.INSTANCE;
}
private UploadManager uploadManager;
private final Api api = RxNet.create(Api.class);
private CosToken cosToken;
private Single<CosToken> getCosToken() {
if (cosToken != null && cosToken.isValid()) {
return Single.just(cosToken);
} else {
return api.getCosToken().compose(RxHelper.handleSchedulers())
.compose(RxHelper.handleBeanData()).map(cosToken -> {
FileModel.get().cosToken = cosToken;
return cosToken;
});
}
}
@Override
public Single<String> uploadFile(String path) {
File file;
if (TextUtils.isEmpty(path) || !((file = new File(path)).exists())) {
return Single.error(new ErrorThrowable(path + ResUtil.getString(R.string.xchat_android_core_file_filemodel_01)));
}
File finalFile = file;
return api.getUploadToken()
.compose(RxHelper.handleSchedulers())
.compose(RxHelper.handleBeanData())
.flatMap(uploadToken -> Single.create(singleEmitter ->
uploadManager.put(finalFile, uploadToken.getKey(), uploadToken.getToken() ,
(key, info, response) -> {
if (info.isOK()) {
try {
String imgUrl = response.getString("path");
singleEmitter.onSuccess(imgUrl);
} catch (Exception e) {
singleEmitter.onError(e);
String outName = UUID.randomUUID().toString() + PathUtils.INSTANCE.getSuffixType(finalFile.getName());
return getCosToken().flatMap(token -> CosClient.INSTANCE.upload(AppUtils.getApp(), finalFile, outName, token).map(cosXmlResult -> cosXmlResult.accessUrl))
.observeOn(AndroidSchedulers.mainThread());
}
} else {
singleEmitter.onError(new Throwable(info.error));
}
}, null)
));
}
@Override
public Single<DynamicMedia> uploadFileReturnImageInfo(String path) {
return uploadFileReturnImageInfo(path, null);
}
@Override
public Single<DynamicMedia> uploadFileReturnImageInfo(String path, String qiniuName) {
File file;
if (TextUtils.isEmpty(path) || !((file = new File(path)).exists())) {
return Single.error(new ErrorThrowable(path + ResUtil.getString(R.string.xchat_android_core_file_filemodel_02)));
}
File finalFile = file;
return api.getUploadToken()
.compose(RxHelper.handleSchedulers())
.compose(RxHelper.handleBeanData())
.flatMap(uploadToken -> Single.create(singleEmitter ->
uploadManager.put(finalFile, uploadToken.getKey(), uploadToken.getToken(),
(key, info, response) -> {
if (info.isOK()) {
try {
LogUtil.print(ResUtil.getString(R.string.xchat_android_core_file_filemodel_03));
LogUtil.print(response);
DynamicMedia media = responseToMeia(response);
if (media != null) {
singleEmitter.onSuccess(media);
return;
}
} catch (Exception e) {
e.printStackTrace();
}
singleEmitter.onError(new Throwable("qiniu json error"));
} else {
singleEmitter.onError(new Throwable(info.error));
}
}, null)));
}
@Override
public Single<String> downloadFile(String url) {
return null;
}
private DynamicMedia responseToMeia(JSONObject response) {
DynamicMedia media = new DynamicMedia();
try {
String imgNamePath = response.getString("path");
media.setResUrl(imgNamePath);
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
try {
media.setFormat(response.getString("format"));
media.setWidth(response.getInt("w"));
media.setHeight(response.getInt("h"));
} catch (Exception ex) {
ex.printStackTrace();
}
LogUtil.print(ResUtil.getString(R.string.xchat_android_core_file_filemodel_04), media);
return media;
}
interface Api {
/**
* 上传文件
*
* @return
*/
@GET("/qiniu/upload/getUploadToken")
Single<ServiceResult<UploadToken>> getUploadToken();
@GET("/tencent/cos/getToken")
Single<ServiceResult<CosToken>> getCosToken();
}
}

View File

@@ -22,9 +22,4 @@ public interface IFileModel extends IModel {
* @return
*/
Single<String> downloadFile(String url);
Single<DynamicMedia> uploadFileReturnImageInfo(String path);
Single<DynamicMedia> uploadFileReturnImageInfo(String path, String qiniuName);
}

View File

@@ -1,11 +0,0 @@
package com.chwl.core.file;
import io.realm.internal.Keep;
import lombok.Data;
@Keep
@Data
public class UploadToken {
private String key;
private String token;
}

View File

@@ -0,0 +1,128 @@
package com.chwk.core.file.cos
import android.content.Context
import android.net.Uri
import android.util.Log
import com.chwl.library.common.application.Env
import com.tencent.cos.xml.CosXmlService
import com.tencent.cos.xml.CosXmlServiceConfig
import com.tencent.cos.xml.exception.CosXmlClientException
import com.tencent.cos.xml.exception.CosXmlServiceException
import com.tencent.cos.xml.listener.CosXmlResultListener
import com.tencent.cos.xml.model.CosXmlRequest
import com.tencent.cos.xml.model.CosXmlResult
import com.tencent.cos.xml.transfer.COSXMLUploadTask
import com.tencent.cos.xml.transfer.TransferConfig
import com.tencent.cos.xml.transfer.TransferManager
import io.reactivex.Single
import java.io.File
object CosClient {
private var cosXmlClient: CosXmlService? = null
private var credentialProvider: CosCredentialProvider? = null
private fun getCosXmlClient(context: Context, token: CosToken): CosXmlService {
var client = this.cosXmlClient
if (client != null && credentialProvider != null) {
credentialProvider?.updateCredentials(token.toCredential())
return client
}
// 创建 CosXmlServiceConfig 对象,根据需要修改默认的配置参数
val serviceConfig: CosXmlServiceConfig = CosXmlServiceConfig.Builder()
.setRegion(token.region)
.isHttps(true) // 使用 HTTPS 请求, 默认为 HTTP 请求
.builder()
val credentials = token.toCredential()
credentialProvider = CosCredentialProvider(credentials)
// 初始化 COS Service获取实例
client = CosXmlService(
context,
serviceConfig, credentialProvider
)
cosXmlClient = client
return client
}
/**
* 上传文件
* @param outName 远端文件名
*/
fun upload(
context: Context,
file: File,
outName: String,
token: CosToken
): Single<CosXmlResult> {
if (Env.isDebug()) {
Log.e("CosClient", "upload file:${file.absolutePath} outName:${outName}")
}
return Single.create {
uploadFile(context, file, outName, token).apply {
setCosXmlResultListener(object : CosXmlResultListener {
override fun onSuccess(request: CosXmlRequest?, result: CosXmlResult) {
transformDomain(result, token)
if (Env.isDebug()) {
Log.e("CosClient", "upload onSuccess result:${result.accessUrl}")
}
it.onSuccess(result)
}
override fun onFail(
request: CosXmlRequest?,
clientException: CosXmlClientException?,
serviceException: CosXmlServiceException?
) {
if (Env.isDebug()) {
Log.e("CosClient", "upload onFail clientException:$clientException")
Log.e("CosClient", "upload onFail serviceException:$serviceException")
}
it.onError(CosException(clientException, serviceException))
}
})
}
}
}
private fun transformDomain(result: CosXmlResult, token: CosToken) {
try {
if (result.accessUrl.isNullOrEmpty()) {
return
}
if (token.customDomain.isNullOrEmpty()) {
return
}
val newUri = Uri.parse(token.customDomain)
result.accessUrl = Uri.parse(result.accessUrl).buildUpon().scheme(newUri.scheme)
.authority(newUri.authority).build().toString()
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 上传文件
* @param outName 远端文件名
*/
private fun uploadFile(
context: Context,
file: File,
outName: String,
token: CosToken
): COSXMLUploadTask {
val cosXmlService = getCosXmlClient(context, token)
// 初始化 TransferConfig这里使用默认配置如果需要定制请参考 SDK 接口文档
val transferConfig = TransferConfig.Builder().build()
// 初始化 TransferManager
val transferManager = TransferManager(
cosXmlService,
transferConfig
)
return transferManager.upload(
token.bucket, outName,
file.absolutePath, null
)
}
}

View File

@@ -0,0 +1,16 @@
package com.chwk.core.file.cos
import com.tencent.qcloud.core.auth.BasicLifecycleCredentialProvider
import com.tencent.qcloud.core.auth.QCloudLifecycleCredentials
class CosCredentialProvider(private var credentials: QCloudLifecycleCredentials) :
BasicLifecycleCredentialProvider() {
fun updateCredentials(credentials: QCloudLifecycleCredentials) {
this.credentials = credentials
}
override fun fetchNewCredentials(): QCloudLifecycleCredentials {
return credentials
}
}

View File

@@ -0,0 +1,18 @@
package com.chwk.core.file.cos
import com.tencent.cos.xml.exception.CosXmlClientException
import com.tencent.cos.xml.exception.CosXmlServiceException
class CosException : Exception {
var clientException: CosXmlClientException? = null
var serviceException: CosXmlServiceException? = null
constructor(
clientException: CosXmlClientException?,
serviceException: CosXmlServiceException?
) : super(clientException ?: serviceException) {
this.clientException = clientException
this.serviceException = serviceException
}
}

View File

@@ -0,0 +1,41 @@
package com.chwk.core.file.cos
import com.chwl.core.utils.CurrentTimeUtils
import com.tencent.qcloud.core.auth.QCloudLifecycleCredentials
import com.tencent.qcloud.core.auth.SessionQCloudCredentials
data class CosToken(
val secretId: String?,
val secretKey: String?,
val sessionToken: String?,
val bucket: String?,
val region: String?,
val startTime: Long?,
val expireTime: Long?,
val customDomain: String?
) {
/**
* 是否有效
*/
fun isValid(): Boolean {
if (expireTime == null) {
return false
}
val currentTime = CurrentTimeUtils.getCurrentTime() / 1000
// 预留一点安全时长
var safeTime = 30
if (safeTime >= (expireTime - (startTime ?: 0))) {
safeTime = 0
}
return currentTime <= (expireTime - safeTime)
}
fun toCredential(): QCloudLifecycleCredentials {
return SessionQCloudCredentials(
secretId ?: "", secretKey ?: "",
sessionToken ?: "", startTime ?: 0, expireTime ?: 0
)
}
}

View File

@@ -2,6 +2,7 @@ package com.chwl.core.community.bean;
import android.text.TextUtils;
import kotlin.jvm.Transient;
import lombok.Data;
/**
@@ -26,6 +27,15 @@ public class DynamicMedia {
private String format;
private int width;
private int height;
@Transient
private String localFilePath;
public String getLocalFilePath() {
return localFilePath;
}
public void setLocalFilePath(String localFilePath) {
this.localFilePath = localFilePath;
}
/**
* 是否是网络图片

View File

@@ -57,7 +57,6 @@ android {
}
dependencies {
def qiniu = "8.4.4"
def SmartRefreshLayoutVersion = "1.0.3"
implementation fileTree(dir: 'libs', include: ['*.jar'])
@@ -87,8 +86,6 @@ dependencies {
api "com.orhanobut:logger:2.2.0"
api "com.qiniu:qiniu-android-sdk:${qiniu}"
api "org.greenrobot:eventbus:3.3.1"
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
@@ -120,6 +117,8 @@ dependencies {
api project(':libs:lib_utils')
api project(':libs:lib_core')
api project(':libs:lib_encipher')
api 'com.qcloud.cos:cos-android:5.9.25'
}
repositories {
mavenCentral()

View File

@@ -693,7 +693,7 @@ public class JXImageUtils {
}
public static String compressImagePxAndQuality(String inPath, File outDir, String outFileName, int expectWidth, long expectSize) {
public static CompressResult compressImagePxAndQuality(String inPath, File outDir, String outFileName, int expectWidth, long expectSize) {
try {
if (outDir == null) {
return null;
@@ -748,13 +748,15 @@ public class JXImageUtils {
fos.close();
if (thumbnailFile.exists()) {
String compressPath = thumbnailFile.getPath();
if (orientation != null) {
exif = new ExifInterface(compressPath);
if (orientation != null) {
exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientation);
exif.saveAttributes();
}
LogUtil.print(ResUtil.getString(R.string.utils_image_jximageutils_06) + getPicRotate(compressPath));
return compressPath;
int width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 0);
int height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 0);
return new CompressResult(compressPath, width, height, "image/jpeg");
}
} catch (Exception ex) {
Log.e("mouse_debug", ResUtil.getString(R.string.utils_image_jximageutils_07));
@@ -816,5 +818,33 @@ public class JXImageUtils {
return degree;
}
public static class CompressResult {
private String path;
private int width;
private int height;
private String format;
public CompressResult(String path, int width, int height, String format) {
this.path = path;
this.width = width;
this.height = height;
this.format = format;
}
public String getPath() {
return path;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public String getFormat() {
return format;
}
}
}

View File

@@ -5,7 +5,6 @@ import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import com.qiniu.android.utils.StringUtils;
import com.chwl.library.common.application.BaseApp;
import com.chwl.library.common.util.LibLogger;
import com.chwl.library.utils.FP;
@@ -877,41 +876,4 @@ public class FileHelper {
}
return content;
}
/**
* 文件转换成字符串
*
* @param filePath 文件路径
* @return 字符串内容
*/
public static String getTxtFileContent(String filePath) {
String content = "";
if (!StringUtils.isNullOrEmpty(filePath)) {
File file = new File(filePath);
if (file.isFile()) {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(file);
String line;
StringBuilder sb = new StringBuilder();
BufferedReader buffReader = new BufferedReader(new InputStreamReader(inputStream));
while ((line = buffReader.readLine()) != null) {
sb.append(line).append("\n");
}
content = sb.toString();
} catch (Exception e) {
LibLogger.error(TAG, "getTxtFileContent read fail, e = " + e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception ignore) {
}
}
}
}
}
return content;
}
}

View File

@@ -0,0 +1,33 @@
package com.example.lib_utils
object PathUtils {
/**
* 获取后缀名称
* @param path 路径
* @return 后缀格式 .mp4 .gif 等
*/
fun getSuffixType(path: String): String? {
if (path.isEmpty()) {
return null
}
val dotIndex = path.indexOfLast {
'.' == it
}
val separatorIndex = path.indexOfLast {
'/' == it
}
if (dotIndex >= 0 && dotIndex > separatorIndex) {
val suffix = path.substring(dotIndex)
val askIndex = suffix.indexOfLast {
'?' == it
}
return if (askIndex >= 0) {
suffix.substring(0, askIndex)
} else {
suffix
}
}
return null
}
}