xugenyuan

ref |> UAT调通阿里OSS签名直传,意见反馈已添加带图片上传

Signed-off-by: xugenyuan <xugenyuan@wondertek.com.cn>
... ... @@ -14,3 +14,4 @@ export { HostEnum, HostManager } from "./src/main/ets/http/HttpHostManager"
export { CacheData } from "./src/main/ets/utils/CacheData"
export { HttpParams } from "./src/main/ets/http/HttpCommonParams"
\ No newline at end of file
... ...
... ... @@ -895,4 +895,13 @@ export class HttpUrlUtils {
return url
}
static getOSSTokenUrl() {
let url = HttpUrlUtils.getHost() + HttpUrlUtils.STS_TOKEN_PATH
return url
}
static getOSSConfigInfoUrl () {
let url = HttpUrlUtils.getHost() + HttpUrlUtils.OSS_PARAMS_PATH
return url
}
}
... ...
... ... @@ -180,6 +180,9 @@ export { TopicDetailData,GroupItem } from './src/main/ets/bean/content/TopicDeta
export { FeedbackTypeBean } from './src/main/ets/bean/detail/FeedbackTypeBean';
export { FeedBackParams } from './src/main/ets/bean/content/FeedBackParams';
export { NetLayerOSSToken } from './src/main/ets/bean/oss/NetLayerOSSToken';
export { NetLayerOSSConfigInfoModel } from './src/main/ets/bean/oss/NetLayerOSSConfigInfoModel';
export { NetLayerVoiceRecoginizerToken } from './src/main/ets/bean/voicerecoginizer/NetLayerVoiceRecoginizerToken';
... ...
... ... @@ -5,4 +5,5 @@ export interface FeedBackParams {
userName:string
appVersion:string
appDevice:string
imageUrls:Array<string>
}
\ No newline at end of file
... ...
export interface NetLayerOSSConfigInfoModel {
bucketName: string; //bucket
type: string; //对应的场景类型
endPoint: string; //图片region
uploadPath: string; //文件存储的路径
domain: string; //图片对应桶CDN地址
}
\ No newline at end of file
... ...
/*
{
securityToken = "CAIS8wF1q6Ft5B2yfSjIr5eAc9eNoJRZ7ayTe3HFj3U8fMVdhvf9pDz2IH9KfnJoBu8esvQ+nWBY5/oalqNJQppiXlf+as99tj6zAowDO9ivgde8yJBZor/HcDHhJnyW9cvWZPqDP7G5U/yxalfCuzZuyL/hD1uLVECkNpv74vwOLK5gPG+CYCFBGc1dKyZ7tcYeLgGxD/u2NQPwiWeiZygB+CgE0Dojufzhm5LEt0GG0gCmm9V4/dqhfsKWCOB3J4p6XtuP2+h7S7HMyiY46WIRq/ks1/Qbpm+b7oDEUwkAv02cUfDd99p0NxV+YqUqxkWsVW8QeJcagAFKgKk8XPz8DfhO47575fFMjkr46tLwuZaaCvORfAdBZS2c/htVmIckl9LBjVeWP4JP4/PNLxmLgKRjyTJ87JPMDY1Uler3FG9dgLaUPU7IESUqyc7lqtroKdaXZhCOdj+yawEow11k6pgF1CjUf8ty/U8Q22//CZ/vGhkYaY0N+A==";
accessKeySecret = "Ar5Pre9EBACKsKoyGHotM7VwPefqhfCARMYnEbgNWuuS";
accessKeyId = "STS.NT58m9MKxZnxyWtkuipJqi2VF";
expiration = "2022-11-25 11:48:36";
currentTime = "2022-11-25 10:48:36";
};
* */
export interface NetLayerOSSToken {
securityToken: string;
accessKeySecret: string;
accessKeyId: string;
expiration: string; // "2024-08-15 20:27:29"
// timeStamp: string;
currentTime: string; // "2024-08-15 19:27:29"
type: string;
}
\ No newline at end of file
... ...
... ... @@ -14,6 +14,8 @@ import { ProcessUtils } from 'wdRouter/Index';
import { TrackConstants, TrackingButton, TrackingPageBrowse } from 'wdTracking/Index';
import inputMethod from '@ohos.inputMethod';
import { photoPickerUtils } from '../utils/PhotoPickerUtils';
import { OSSConfigSceneType, OSSUploadManager,OSSFileType, UploadResourceParams } from 'wdHwAbility'
import { it } from '@ohos/hypium';
const TAG = 'FeedBackActivity'
... ... @@ -45,6 +47,8 @@ export struct FeedBackActivity {
@State bottomSafeHeight: number = AppStorage.get<number>('bottomSafeHeight') || 0
@Provide topSafeHeight: number = AppStorage.get<number>('topSafeHeight') || 0
@State showLoading: boolean = false
dialogToast: CustomDialogController = new CustomDialogController({
builder: CustomToast({
bgColor:$r("app.color.color_B3000000"),
... ... @@ -160,61 +164,61 @@ export struct FeedBackActivity {
columns:5,
}) {
// ForEach(this.pics, (feedbackImageItem: PhotoListBean, index: number) => {
// GridCol({
// }) {
// if(1 == feedbackImageItem.itemType){
// Image($r('app.media.feekback_add'))
// .width(60)
// .height(60)
// .onClick(async (event: ClickEvent) => {
// if(await FastClickUtil.isMinDelayTime()){
// return
// }
// this.callFilePickerSelectImage();
// })
// }else{
// Stack({alignContent: Alignment.TopEnd}) {
// Image(feedbackImageItem.picPath)
// .width(60)
// .height(60)
// .borderRadius($r('app.float.margin_1'))
// .onClick(async (event: ClickEvent) => {
// if(await FastClickUtil.isMinDelayTime()){
// return
// }
// //查看图片 fixme 去除添加按钮
// ProcessUtils.gotoMultiPictureListPage(this.pics, index)
// })
// Image($r('app.media.icon_feekback_delete'))
// .width(24)
// .height(24)
// .borderRadius($r('app.float.margin_1'))
// .onClick(async (event: ClickEvent) => {
// if(await FastClickUtil.isMinDelayTime()){
// return
// }
// let temp: PhotoListBean[] = [] as PhotoListBean[]
// temp.length = this.pics.length - 1;
// let tempIndex = 0;
// for (let index = 0; index < this.pics.length; index++) {
// const element = this.pics[index];
// if(!StringUtils.isEmpty(element.picPath) && element.id != feedbackImageItem.id){
// temp[tempIndex] = element;
// tempIndex = tempIndex+1
// }
// }
// if(tempIndex < 3){
// temp[tempIndex] = this.addPic
// }
// this.pics = temp
// })
// }
// .width(60)
// .height(60)
// }
// }
// })
ForEach(this.pics, (feedbackImageItem: PhotoListBean, index: number) => {
GridCol({
}) {
if(1 == feedbackImageItem.itemType){
Image($r('app.media.feekback_add'))
.width(60)
.height(60)
.onClick(async (event: ClickEvent) => {
if(await FastClickUtil.isMinDelayTime()){
return
}
this.callFilePickerSelectImage();
})
}else{
Stack({alignContent: Alignment.TopEnd}) {
Image(feedbackImageItem.picPath)
.width(60)
.height(60)
.borderRadius($r('app.float.margin_1'))
.onClick(async (event: ClickEvent) => {
if(await FastClickUtil.isMinDelayTime()){
return
}
//查看图片 fixme 去除添加按钮
ProcessUtils.gotoMultiPictureListPage(this.pics, index)
})
Image($r('app.media.icon_feekback_delete'))
.width(24)
.height(24)
.borderRadius($r('app.float.margin_1'))
.onClick(async (event: ClickEvent) => {
if(await FastClickUtil.isMinDelayTime()){
return
}
let temp: PhotoListBean[] = [] as PhotoListBean[]
temp.length = this.pics.length - 1;
let tempIndex = 0;
for (let index = 0; index < this.pics.length; index++) {
const element = this.pics[index];
if(!StringUtils.isEmpty(element.picPath) && element.id != feedbackImageItem.id){
temp[tempIndex] = element;
tempIndex = tempIndex+1
}
}
if(tempIndex < 3){
temp[tempIndex] = this.addPic
}
this.pics = temp
})
}
.width(60)
.height(60)
}
}
})
}
.margin({bottom: $r('app.float.vp_12'), right: $r('app.float.vp_12'),left: $r('app.float.vp_12')})
Text(){
... ... @@ -426,6 +430,7 @@ export struct FeedBackActivity {
return
}
this.showLoading = true
try {
let feedBackParams: FeedBackParams = {
//反馈内容
... ... @@ -445,15 +450,36 @@ export struct FeedBackActivity {
feedBackParams.userName = UserDataLocal.getUserName()
}
// //投诉图片
// if (imageUrl.size() > 0) {
// String[] str = imageUrl.toArray(new String[imageUrl.size()]);
// map.set("imageUrls", str);
// }
//投诉图片
if (this.pics.length > 0) {
let inputs = this.pics.filter((item) => {
return item.picPath && item.picPath.length > 0
}).map((item) => {
let i = new UploadResourceParams(item.picPath)
i.scene = OSSConfigSceneType.feedback
i.fileType = OSSFileType.image
return i
})
let results = await OSSUploadManager.sharedManager().uploadFiles(inputs)
if (results.length !== inputs.length) {
this.showLoading = false
this.showToastTip('附件上传失败')
return
}
let files = results.map((item) => {
return item.ossFile ?? ""
})
feedBackParams.imageUrls = files
}
await MultiPictureDetailViewModel.feedBackCommit(feedBackParams)
this.showLoading = false
router.back();
} catch (exception) {
console.log('请求失败',JSON.stringify(exception))
this.showLoading = false
}
}
... ...
... ... @@ -6,3 +6,6 @@ export { HWLocationUtils } from './src/main/ets/location/HWLocationUtils'
export { GetuiPush } from "./src/main/ets/getuiPush/GetuiPush"
export {VoiceRecoginizer} from './src/main/ets/voiceRecognizer/VoiceRecoginizer'
export { OSSUploadManager,OSSConfigSceneType,OSSFileType,OSSUploadResult,UploadResourceParams } from "./src/main/ets/aliOSS/OSSUploadManager"
... ...
... ... @@ -15,6 +15,7 @@
"wdBean": "file:../../features/wdBean",
"wdRouter": "file:../../commons/wdRouter",
"wdTracking": "file:../../features/wdTracking",
"wdNetwork": "file:../../commons/wdNetwork"
"wdNetwork": "file:../../commons/wdNetwork",
"@ohos/xml_js": "^1.0.2"
}
}
... ...
export class OSSConfig {
stsToken: string = ''
bucket: string = ''
fileName: string = '' // 自定义
accessKeyId: string = ''
accessKeySecret: string = ''
customHeaders: Record<string, string | number>
serverUrl: string = ''
}
... ...
import { NetLayerOSSConfigInfoModel, NetLayerOSSToken } from 'wdBean'
import { HostEnum, HttpParams, HttpUrlUtils, ResponseDTO } from 'wdNetwork'
import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest'
import { data } from '@kit.TelephonyKit'
import { DateTimeUtils, DeviceUtil, Logger } from 'wdKit'
import { MultipartUpload } from './multipartUpload'
import { OSSConfig } from './OSSConfig'
import { http } from '@kit.NetworkKit'
import { it } from '@ohos/hypium'
import { putObject } from './upload'
export enum OSSConfigSceneType {
feedback = "feedback",
}
export enum OSSFileType {
image = 0,
video = 1,
}
export class OSSUploadResult {
ossFile?: string
}
export class UploadResourceParams {
fileUri: string = ''
scene: OSSConfigSceneType = OSSConfigSceneType.feedback
fileType: OSSFileType = OSSFileType.image
constructor(fileUri: string) {
this.fileUri = fileUri
}
}
const TAG = "OSSUploadManager"
export class OSSUploadManager {
private ossToken?: NetLayerOSSToken
private configInfos?: Array<NetLayerOSSConfigInfoModel>
private constructor() { }
private static manager: OSSUploadManager
static sharedManager(): OSSUploadManager {
if (!OSSUploadManager.manager) {
OSSUploadManager.manager = new OSSUploadManager()
}
return OSSUploadManager.manager
}
uploadFiles(inputs: Array<UploadResourceParams>) : Promise<Array<OSSUploadResult>> {
return new Promise(async (resolve, fail) => {
await this.getOssToken()
await this.getOssConfigInfo()
Promise.allSettled(inputs.map((item): Promise<OSSUploadResult> => {
return this.uploadFile(item.fileUri, item.scene, item.fileType)
})).then(res => {
const failedJobs = res.filter(v => v.status === 'rejected');
if (failedJobs.length > 0) {
console.info('Failed objects: ' + failedJobs.length);
resolve([])
} else {
console.info('All the objects upload success');
let results: Array<OSSUploadResult> = [] as Array<OSSUploadResult>
res.forEach((item) => {
console.info('>>>>>' + JSON.stringify(item));
if (item.status === 'fulfilled') {
results.push(item.value)
}
})
resolve(results)
}
})
})
}
uploadFile(fileUri: string, scene: OSSConfigSceneType, fileType: OSSFileType) : Promise<OSSUploadResult> {
return new Promise(async (success, fail) => {
try {
let tokenModel = await this.getOssToken()
let config = await this.getOssConfigInfo()
let configModuel = config?.filter((c) => {
return c.type == scene
}).pop()
if (!configModuel) {
Logger.warn(TAG, "配置为空")
return
}
//格式样例 zhbj/img/social/2024080215/BA6690B9CF084B3FAEEFA58F100F8D3E.jpg,这里的格式目前和iOS 是保持一致的
let objectName = configModuel.uploadPath + DateTimeUtils.getCurDate('yyyyMMddHH') + "/" + DeviceUtil.getRandomUUIDForTraceID() + (fileType == OSSFileType.image ? ".jpg" : ".mp4")
Logger.debug(TAG, `==>> ${fileUri} `)
Logger.debug(TAG, `==>> endPoint: ${configModuel.endPoint} `)
Logger.debug(TAG, `==>> bucketName: ${configModuel.bucketName} `)
Logger.debug(TAG, `==>> objectName: ` + objectName)
Logger.debug(TAG, JSON.stringify(tokenModel))
Logger.debug(TAG, JSON.stringify(configModuel))
let result = await this.upload(fileUri, configModuel.endPoint, configModuel.bucketName, objectName)
if (result) {
let r = {} as OSSUploadResult
r.ossFile = objectName
let url = configModuel.domain + "/" + objectName
console.log("result url: " + url)
success(r)
return
}
Logger.error(TAG, "上传失败")
fail("上传失败")
} catch (e) {
Logger.error(TAG, "上传失败" + JSON.stringify(e))
fail("上传失败")
}
})
}
private upload(fileUri: string, endpoint:string, bucketName:string, objectName:string) : Promise<boolean> {
return new Promise(async (reslove, fail) => {
if (!this.ossToken || !this.ossToken.securityToken) {
fail("missing accessKeyId or accessKeySecret or sessionToken")
return
}
let config: OSSConfig = {
stsToken: this.ossToken.securityToken,
bucket: bucketName,
fileName: objectName,
accessKeyId: this.ossToken.accessKeyId,
accessKeySecret: this.ossToken.accessKeySecret,
customHeaders: HttpParams.buildHeaders(),
serverUrl: this.getServerHost() + "/api/harmonyoss/get_sign_url"
}
let success = false
try {
const multipartUpload = new MultipartUpload(config, fileUri);
let result: http.HttpResponse = await multipartUpload.multipartUpload();
Logger.debug(TAG, "表单上传完成 + " + JSON.stringify(result))
if (result.responseCode == 200) {
success = true
}
// await putObject(fileUri, config)
//
// success = true
} catch (e) {
Logger.error(TAG, JSON.stringify(e))
} finally {
reslove(success)
}
})
}
private getOssToken():Promise<NetLayerOSSToken | undefined> {
return new Promise<NetLayerOSSToken | undefined>((success, fail) => {
if (this.ossToken && this.ossToken.securityToken.length > 0) {
// this.ossToken.expiration
let dateNumber = DateTimeUtils.parseDate(this.ossToken.expiration, DateTimeUtils.PATTERN_DATE_TIME_HYPHEN)
if (Date.now() < (dateNumber - 60 * 1000)) {
success(this.ossToken)
return
}
}
HttpRequest.get<ResponseDTO<NetLayerOSSToken>>(
HttpUrlUtils.getOSSTokenUrl(),
).then((res: ResponseDTO<NetLayerOSSToken>) => {
if (res.code != 0) {
fail(res.message)
return
}
this.ossToken = res.data
success(res.data)
}, (error: Error) => {
fail(error.message)
})
})
}
private getOssConfigInfo() {
let isAbroad = 0
return new Promise<Array<NetLayerOSSConfigInfoModel> | undefined>((success, fail) => {
if (this.configInfos && this.configInfos.length > 0) {
success(this.configInfos)
return
}
HttpRequest.get<ResponseDTO<Array<NetLayerOSSConfigInfoModel>>> (
HttpUrlUtils.getOSSConfigInfoUrl()+ '?type=0'
).then((res: ResponseDTO<Array<NetLayerOSSConfigInfoModel>>) => {
if (res.code != 0) {
fail(res.message)
return
}
this.configInfos = res.data
success(res.data)
})
})
}
private getServerHost() {
return HttpUrlUtils.getHost()
// switch (HttpUrlUtils.getHost()) {
// case HostEnum.HOST_UAT: {
// return "https://pd-people-uat.pdnews.cn"
// }
// case HostEnum.HOST_DEV: {
// return "https://pd-people-dev.pdnews.cn"
// }
// case HostEnum.HOST_SIT: {
// return "https://pd-people-sit.pdnews.cn"
// }
// }
// return "https://www.peopleapp.com"
}
}
\ No newline at end of file
... ...
import { http } from '@kit.NetworkKit';
import fs from '@ohos.file.fs';
import { getSignUrl } from './upload';
import { request } from './request';
import { xmlToObj } from './xml';
import { OSSConfig } from './OSSConfig';
type TPart = {
partNum: number;
etag: string;
};
type TTodoPart = {
partLength: number;
partNum: number;
}
/**
* InitiateMultipartUpload
* @param fileName 文件名
*/
const initiateMultipartUpload = async (fileName: string, config: OSSConfig) => {
console.info('in initiateMultipartUpload');
// 获取InitiateMultipartUpload签名URL
const signUrlResult = await getSignUrl(config.fileName.length > 0 ? config.fileName: fileName, {
url: config.serverUrl,
method: 'POST',
headers: config.customHeaders,
queries: {
uploads: null,
bucket: config.bucket,
stsToken: config.stsToken,
accessKeyId: config.accessKeyId,
accessKeySecret: config.accessKeySecret,
}
});
try {
// 通过InitiateMultipartUpload接口来通知OSS初始化一个Multipart Upload事件
console.info('url: ' + signUrlResult.url);
const response = await request(signUrlResult.url, {
method: http.RequestMethod.POST,
expectDataType: http.HttpDataType.STRING
}, 200);
const result = response.result as string;
console.info('success initiateMultipartUpload');
const res = xmlToObj(result) as {
InitiateMultipartUploadResult: {
Bucket: string;
Key: string;
UploadId: string;
EncodingType?: string;
}
};
return res.InitiateMultipartUploadResult;
} catch (err) {
console.info('initiateMultipartUpload request error: ' + JSON.stringify(err));
throw err;
}
};
/**
* UploadPart
* @param uploadId 分片上传的uploadId
* @param partNum 分片上传的partNumber
* @param file 上传的文件
* @param length 分片大小
* @param offset 文件读取位置
*/
const uploadPart = async (uploadId: string, config: OSSConfig, partNum: number, file: fs.File, length: number, offset: number = 0) => {
console.info('in uploadPart');
// 获取UploadPart签名URL
const signUrlResult = await getSignUrl(config.fileName.length > 0 ? config.fileName: file.name, {
url: config.serverUrl,
method: 'PUT',
headers: config.customHeaders,
extHeaders: {
'Content-Length': length
},
queries: {
uploadId,
partNumber: partNum.toString(),
bucket: config.bucket,
stsToken: config.stsToken,
accessKeyId: config.accessKeyId,
accessKeySecret: config.accessKeySecret,
},
additionalHeaders: ['Content-Length']
});
const data = new ArrayBuffer(length);
await fs.read(file.fd, data, {
length,
offset
});
try {
const response = await request(signUrlResult.url, {
method: http.RequestMethod.PUT,
header: {
'Content-Length': length,
'Content-Type': signUrlResult.contentType
},
extraData: data
}, 200);
console.info('success uploadPart');
return response.header['etag'] as string;
} catch (err) {
console.info('uploadPart request error: ' + JSON.stringify(err));
throw err;
}
};
/**
* CompleteMultipartUpload
* @param fileName 文件名
* @param uploadId 分片上传的uploadId
* @param completeAll 指定是否列举当前UploadId已上传的所有Part
* @param [parts] CompleteMultipartUpload所需的Part列表
*/
const completeMultipartUpload = async (fileName: string, config: OSSConfig, uploadId: string, completeAll: boolean = false, parts?: TPart[]) => {
console.info('in completeMultipartUpload');
if (!completeAll && !parts) {
throw new Error('completeMultipartUpload needs to pass in parameter parts.');
}
const signUrlResult = await getSignUrl(config.fileName.length > 0 ? config.fileName: fileName, {
url: config.serverUrl,
method: 'POST',
headers: config.customHeaders,
extHeaders: completeAll ? {
'x-oss-complete-all': 'yes'
} : {
'Content-Type': 'application/xml'
},
queries: {
uploadId,
bucket: config.bucket,
stsToken: config.stsToken,
accessKeyId: config.accessKeyId,
accessKeySecret: config.accessKeySecret,
}
});
let xml: string;
if (!completeAll) {
const completeParts = parts.concat().sort((a, b) => a.partNum - b.partNum)
.filter((item, index, arr) => !index || item.partNum !== arr[index - 1].partNum);
xml = '<?xml version="1.0" encoding="UTF-8"?>\n<CompleteMultipartUpload>\n';
completeParts.forEach(item => {
xml += `<Part>\n<PartNumber>${item.partNum}</PartNumber>\n<ETag>${item.etag}</ETag>\n</Part>\n`
});
xml += '</CompleteMultipartUpload>';
}
try {
const result = await request(signUrlResult.url, {
method: http.RequestMethod.POST,
header: completeAll ? {
'x-oss-complete-all': 'yes'
} : {
'Content-Type': 'application/xml'
},
extraData: !completeAll ? xml : undefined
}, 200);
console.info('success completeMultipartUpload');
return result;
} catch (err) {
console.info('completeMultipartUpload request error: ' + JSON.stringify(err));
throw err;
}
};
/**
* 分片上传信息
*/
interface ICheckpoint {
/** 分片上传的uploadId */
uploadId: string;
/** 文件URI */
fileUri: string;
/** 分片大小 */
partSize: number;
/** 已经上传完成的分片 */
doneParts: TPart[];
}
/**
* 分片上传
*/
export class MultipartUpload {
/** 分片上传信息 */
private checkpoint: ICheckpoint;
/** 上传文件 */
private file: fs.File;
/** 文件详细属性信息 */
private fileStat: fs.Stat;
/** 取消上传标识 */
private cancelFlag = true;
/** 并发上传数 */
private parallel = 5;
/** 上传队列 */
private uploadQueue: TTodoPart[] = [];
/** 当前正在上传数 */
private uploadingCount = 0;
/** 上传失败分片信息 */
private uploadErrors: {
partNum: number;
uploadError: Error;
}[] = [];
private config: OSSConfig
/**
* 创建MultipartUpload实例
* @param [fileUri] 文件URI
* @param [checkpoint] 分片上传信息
*/
constructor(config:OSSConfig, fileUri?: string, checkpoint?: ICheckpoint) {
this.config = config
if (checkpoint) {
this.checkpoint = checkpoint;
this.file = fs.openSync(checkpoint.fileUri, fs.OpenMode.READ_ONLY);
} else {
if (!fileUri) {
throw Error('MultipartUpload need fileUri or checkpoint.');
}
this.file = fs.openSync(fileUri, fs.OpenMode.READ_ONLY);
this.checkpoint = {
uploadId: '',
fileUri,
partSize: 2 ** 20,
doneParts: []
};
}
this.fileStat = fs.statSync(this.file.fd);
}
private async uploadPart(part: TTodoPart, resolve: () => void) {
this.uploadingCount++;
const {
partLength,
partNum
} = part;
try {
const result = await uploadPart(this.checkpoint.uploadId, this.config, partNum, this.file, partLength, (partNum - 1) * this.checkpoint.partSize);
this.checkpoint.doneParts.push({
partNum: partNum,
etag: result
});
this.uploadingCount--;
if(this.uploadErrors.length < 1) {
if (this.uploadQueue.length < 1 && this.uploadingCount < 1) {
resolve();
} else {
this.next(resolve);
}
}
} catch (e) {
this.uploadingCount--;
this.uploadErrors.push({
partNum: partNum,
uploadError: e
});
resolve();
}
}
private next(resolve: () => void) {
if (this.cancelFlag) {
resolve();
}
if (this.uploadQueue.length > 0 && this.uploadingCount < this.parallel && this.uploadErrors.length < 1) {
this.uploadPart(this.uploadQueue.shift(), resolve);
}
}
/**
* 执行分片上传
*/
async multipartUpload() {
this.cancelFlag = false;
this.uploadQueue = [];
this.uploadErrors = [];
if (this.checkpoint.uploadId === '') {
const initResult = await initiateMultipartUpload(this.file.name, this.config);
this.checkpoint.uploadId = initResult.UploadId;
}
const partsSum = Math.ceil(this.fileStat.size / this.checkpoint.partSize);
for (let i = 0; i < partsSum; i++) {
if (this.checkpoint.doneParts.findIndex(v => v.partNum === i + 1) === -1) {
this.uploadQueue.push({
partLength: i + 1 === partsSum ? this.fileStat.size % this.checkpoint.partSize : this.checkpoint.partSize,
partNum: i + 1
});
}
}
const tempCount = Math.min(this.parallel, this.uploadQueue.length);
await new Promise<void>((resolve) => {
for (let i = 0; i < tempCount; i++) {
this.next(resolve);
}
});
if (this.cancelFlag) {
throw new Error('MultipartUpload cancel');
}
if (this.uploadErrors.length) {
throw new Error('Upload failed parts: ' + this.uploadErrors.map(i => i.partNum).join(','));
}
return await completeMultipartUpload(this.file.name, this.config, this.checkpoint.uploadId, false, this.checkpoint.doneParts);
}
cancel() {
this.cancelFlag = true;
}
};
export {
initiateMultipartUpload,
uploadPart,
completeMultipartUpload
};
... ...
import { http } from '@kit.NetworkKit';
const request = async (url: string, options: http.HttpRequestOptions, successCode: number[] | number) => {
const httpRequest = http.createHttp();
try {
console.info('request url: ' + url);
console.info('request req: ' + JSON.stringify(options));
const httpResponse = await httpRequest.request(url, {
...options,
priority: 1,
connectTimeout: 60000,
readTimeout: 60000,
usingProtocol: http.HttpProtocol.HTTP1_1
});
console.info('request res: ' + JSON.stringify(httpResponse));
if ((Array.isArray(successCode) && successCode.includes(httpResponse.responseCode)) || httpResponse.responseCode === successCode) {
const requestID = httpResponse.header['x-oss-request-id'];
console.info(`request success${requestID ? ', oss request ID: ' + requestID : ''}`);
return httpResponse;
} else {
throw {
code: httpResponse.responseCode,
result: httpResponse.result.toString(),
requestID: httpResponse.header['x-oss-request-id']
};
}
} catch (err) {
console.info('request error: ' + JSON.stringify(err));
throw err;
} finally {
httpRequest.destroy();
}
};
export {
request
};
... ...
import { http } from '@kit.NetworkKit';
import fs from '@ohos.file.fs';
import { request } from './request';
import { OSSConfig } from './OSSConfig';
import { JSON } from '@kit.ArkTS';
// const serverUrl = 'http://x.x.x.x:3000/get_sign_url'; // 获取签名URL的服务器URL
// const serverUrl = 'https://pd-people-uat.pdnews.cn/api/harmonyoss/get_sign_url'; // 获取签名URL的服务器URL
/**
* getSignUrl返回数据
*/
export interface ISignUrlResult {
/** 签名URL */
url: string;
/** content-type */
contentType?: string;
}
/**
* 获取签名URL
* @param fileName 文件名称
* @param req 用于生成V4签名URL的请求信息
* @param req.method 请求方式
* @param [req.headers] 请求头
* @param [req.queries] 请求查询参数
* @param [req.additionalHeaders] 加签的请求头
*/
const getSignUrl = async (fileName: string, req: {
url: string;
method: 'GET' | 'POST' | 'PUT';
headers?: Record<string, string | number>;
extHeaders?: Record<string, string | number>;
queries?: Record<string, string>;
additionalHeaders?: string[];
}): Promise<ISignUrlResult> => {
console.info('in getSignUrl form url: ' + req.url);
try {
let h = req.headers ?? {}
h['Content-Type'] = 'application/json'
const response = await request(req.url, {
method: http.RequestMethod.POST,
header: h,
extraData: {
fileName,
method: req.method,
headers: req.extHeaders,
queries: req.queries,
additionalHeaders: req.additionalHeaders
},
expectDataType: http.HttpDataType.OBJECT
}, 200);
const result = response.result as ISignUrlResult;
console.info('success getSignUrl ' + JSON.stringify(result));
return result;
} catch (err) {
console.info('getSignUrl request error: ' + JSON.stringify(err));
throw err;
}
};
/**
* PutObject
* @param fileUri 文件URI
*/
const putObject = async (fileUri: string, config: OSSConfig): Promise<void> => {
console.info('in putObject');
const fileInfo = await fs.open(fileUri, fs.OpenMode.READ_ONLY);
const fileStat = await fs.stat(fileInfo.fd);
let signUrlResult: ISignUrlResult;
console.info('file name: ', fileInfo.name);
try {
// 获取PutObject的签名URL
signUrlResult = await getSignUrl(config.fileName.length > 0 ? config.fileName: fileInfo.name, {
url: config.serverUrl,
method: 'PUT',
headers: config.customHeaders,
extHeaders: {
'Content-Length': fileStat.size
},
queries: {
stsToken: config.stsToken,
bucket: config.bucket,
accessKeyId: config.accessKeyId,
accessKeySecret: config.accessKeySecret,
},
additionalHeaders: ['Content-Length']
});
} catch (e) {
await fs.close(fileInfo.fd);
throw e;
}
const data = new ArrayBuffer(fileStat.size);
await fs.read(fileInfo.fd, data);
await fs.close(fileInfo.fd);
try {
// 使用PutObject方法上传文件
await request(signUrlResult.url, {
method: http.RequestMethod.PUT,
header: {
'Content-Length': fileStat.size,
'Content-Type': signUrlResult.contentType
},
extraData: data
}, 200);
console.info('success putObject');
} catch (err) {
console.info('putObject request error: ' + JSON.stringify(err));
throw err;
}
};
export {
getSignUrl,
putObject
};
... ...
import convert from '@ohos/xml_js'
const removeTextProperty = (node) => {
if (node && typeof node === 'object') {
Object.keys(node).forEach(key => {
if (node[key] && typeof node[key] === 'object') {
if ('_text' in node[key]) {
node[key] = node[key]._text;
} else {
removeTextProperty(node[key]);
}
}
});
}
return node;
}
const xmlToObj = (xml: string): object => {
const result = convert.xml2js(xml, {
compact: true,
ignoreDeclaration: true,
ignoreAttributes: true,
ignoreComment: true,
ignoreCdata: true
});
return removeTextProperty(result);
}
export {
xmlToObj
};
... ...