ref |> UAT调通阿里OSS签名直传,意见反馈已添加带图片上传
Signed-off-by: xugenyuan <xugenyuan@wondertek.com.cn>
Showing
15 changed files
with
933 additions
and
61 deletions
| @@ -14,3 +14,4 @@ export { HostEnum, HostManager } from "./src/main/ets/http/HttpHostManager" | @@ -14,3 +14,4 @@ export { HostEnum, HostManager } from "./src/main/ets/http/HttpHostManager" | ||
| 14 | 14 | ||
| 15 | export { CacheData } from "./src/main/ets/utils/CacheData" | 15 | export { CacheData } from "./src/main/ets/utils/CacheData" |
| 16 | 16 | ||
| 17 | +export { HttpParams } from "./src/main/ets/http/HttpCommonParams" |
| @@ -895,4 +895,13 @@ export class HttpUrlUtils { | @@ -895,4 +895,13 @@ export class HttpUrlUtils { | ||
| 895 | return url | 895 | return url |
| 896 | } | 896 | } |
| 897 | 897 | ||
| 898 | + static getOSSTokenUrl() { | ||
| 899 | + let url = HttpUrlUtils.getHost() + HttpUrlUtils.STS_TOKEN_PATH | ||
| 900 | + return url | ||
| 901 | + } | ||
| 902 | + | ||
| 903 | + static getOSSConfigInfoUrl () { | ||
| 904 | + let url = HttpUrlUtils.getHost() + HttpUrlUtils.OSS_PARAMS_PATH | ||
| 905 | + return url | ||
| 906 | + } | ||
| 898 | } | 907 | } |
| @@ -180,6 +180,9 @@ export { TopicDetailData,GroupItem } from './src/main/ets/bean/content/TopicDeta | @@ -180,6 +180,9 @@ export { TopicDetailData,GroupItem } from './src/main/ets/bean/content/TopicDeta | ||
| 180 | 180 | ||
| 181 | export { FeedbackTypeBean } from './src/main/ets/bean/detail/FeedbackTypeBean'; | 181 | export { FeedbackTypeBean } from './src/main/ets/bean/detail/FeedbackTypeBean'; |
| 182 | export { FeedBackParams } from './src/main/ets/bean/content/FeedBackParams'; | 182 | export { FeedBackParams } from './src/main/ets/bean/content/FeedBackParams'; |
| 183 | +export { NetLayerOSSToken } from './src/main/ets/bean/oss/NetLayerOSSToken'; | ||
| 184 | +export { NetLayerOSSConfigInfoModel } from './src/main/ets/bean/oss/NetLayerOSSConfigInfoModel'; | ||
| 185 | + | ||
| 183 | 186 | ||
| 184 | export { NetLayerVoiceRecoginizerToken } from './src/main/ets/bean/voicerecoginizer/NetLayerVoiceRecoginizerToken'; | 187 | export { NetLayerVoiceRecoginizerToken } from './src/main/ets/bean/voicerecoginizer/NetLayerVoiceRecoginizerToken'; |
| 185 | 188 |
| 1 | +/* | ||
| 2 | +{ | ||
| 3 | + securityToken = "CAIS8wF1q6Ft5B2yfSjIr5eAc9eNoJRZ7ayTe3HFj3U8fMVdhvf9pDz2IH9KfnJoBu8esvQ+nWBY5/oalqNJQppiXlf+as99tj6zAowDO9ivgde8yJBZor/HcDHhJnyW9cvWZPqDP7G5U/yxalfCuzZuyL/hD1uLVECkNpv74vwOLK5gPG+CYCFBGc1dKyZ7tcYeLgGxD/u2NQPwiWeiZygB+CgE0Dojufzhm5LEt0GG0gCmm9V4/dqhfsKWCOB3J4p6XtuP2+h7S7HMyiY46WIRq/ks1/Qbpm+b7oDEUwkAv02cUfDd99p0NxV+YqUqxkWsVW8QeJcagAFKgKk8XPz8DfhO47575fFMjkr46tLwuZaaCvORfAdBZS2c/htVmIckl9LBjVeWP4JP4/PNLxmLgKRjyTJ87JPMDY1Uler3FG9dgLaUPU7IESUqyc7lqtroKdaXZhCOdj+yawEow11k6pgF1CjUf8ty/U8Q22//CZ/vGhkYaY0N+A=="; | ||
| 4 | + accessKeySecret = "Ar5Pre9EBACKsKoyGHotM7VwPefqhfCARMYnEbgNWuuS"; | ||
| 5 | + accessKeyId = "STS.NT58m9MKxZnxyWtkuipJqi2VF"; | ||
| 6 | + expiration = "2022-11-25 11:48:36"; | ||
| 7 | + currentTime = "2022-11-25 10:48:36"; | ||
| 8 | + }; | ||
| 9 | + * */ | ||
| 10 | +export interface NetLayerOSSToken { | ||
| 11 | + securityToken: string; | ||
| 12 | + accessKeySecret: string; | ||
| 13 | + accessKeyId: string; | ||
| 14 | + expiration: string; // "2024-08-15 20:27:29" | ||
| 15 | + // timeStamp: string; | ||
| 16 | + currentTime: string; // "2024-08-15 19:27:29" | ||
| 17 | + type: string; | ||
| 18 | +} |
| @@ -14,6 +14,8 @@ import { ProcessUtils } from 'wdRouter/Index'; | @@ -14,6 +14,8 @@ import { ProcessUtils } from 'wdRouter/Index'; | ||
| 14 | import { TrackConstants, TrackingButton, TrackingPageBrowse } from 'wdTracking/Index'; | 14 | import { TrackConstants, TrackingButton, TrackingPageBrowse } from 'wdTracking/Index'; |
| 15 | import inputMethod from '@ohos.inputMethod'; | 15 | import inputMethod from '@ohos.inputMethod'; |
| 16 | import { photoPickerUtils } from '../utils/PhotoPickerUtils'; | 16 | import { photoPickerUtils } from '../utils/PhotoPickerUtils'; |
| 17 | +import { OSSConfigSceneType, OSSUploadManager,OSSFileType, UploadResourceParams } from 'wdHwAbility' | ||
| 18 | +import { it } from '@ohos/hypium'; | ||
| 17 | 19 | ||
| 18 | const TAG = 'FeedBackActivity' | 20 | const TAG = 'FeedBackActivity' |
| 19 | 21 | ||
| @@ -45,6 +47,8 @@ export struct FeedBackActivity { | @@ -45,6 +47,8 @@ export struct FeedBackActivity { | ||
| 45 | @State bottomSafeHeight: number = AppStorage.get<number>('bottomSafeHeight') || 0 | 47 | @State bottomSafeHeight: number = AppStorage.get<number>('bottomSafeHeight') || 0 |
| 46 | @Provide topSafeHeight: number = AppStorage.get<number>('topSafeHeight') || 0 | 48 | @Provide topSafeHeight: number = AppStorage.get<number>('topSafeHeight') || 0 |
| 47 | 49 | ||
| 50 | + @State showLoading: boolean = false | ||
| 51 | + | ||
| 48 | dialogToast: CustomDialogController = new CustomDialogController({ | 52 | dialogToast: CustomDialogController = new CustomDialogController({ |
| 49 | builder: CustomToast({ | 53 | builder: CustomToast({ |
| 50 | bgColor:$r("app.color.color_B3000000"), | 54 | bgColor:$r("app.color.color_B3000000"), |
| @@ -160,61 +164,61 @@ export struct FeedBackActivity { | @@ -160,61 +164,61 @@ export struct FeedBackActivity { | ||
| 160 | columns:5, | 164 | columns:5, |
| 161 | }) { | 165 | }) { |
| 162 | 166 | ||
| 163 | - // ForEach(this.pics, (feedbackImageItem: PhotoListBean, index: number) => { | ||
| 164 | - // GridCol({ | ||
| 165 | - // }) { | ||
| 166 | - // if(1 == feedbackImageItem.itemType){ | ||
| 167 | - // Image($r('app.media.feekback_add')) | ||
| 168 | - // .width(60) | ||
| 169 | - // .height(60) | ||
| 170 | - // .onClick(async (event: ClickEvent) => { | ||
| 171 | - // if(await FastClickUtil.isMinDelayTime()){ | ||
| 172 | - // return | ||
| 173 | - // } | ||
| 174 | - // this.callFilePickerSelectImage(); | ||
| 175 | - // }) | ||
| 176 | - // }else{ | ||
| 177 | - // Stack({alignContent: Alignment.TopEnd}) { | ||
| 178 | - // Image(feedbackImageItem.picPath) | ||
| 179 | - // .width(60) | ||
| 180 | - // .height(60) | ||
| 181 | - // .borderRadius($r('app.float.margin_1')) | ||
| 182 | - // .onClick(async (event: ClickEvent) => { | ||
| 183 | - // if(await FastClickUtil.isMinDelayTime()){ | ||
| 184 | - // return | ||
| 185 | - // } | ||
| 186 | - // //查看图片 fixme 去除添加按钮 | ||
| 187 | - // ProcessUtils.gotoMultiPictureListPage(this.pics, index) | ||
| 188 | - // }) | ||
| 189 | - // Image($r('app.media.icon_feekback_delete')) | ||
| 190 | - // .width(24) | ||
| 191 | - // .height(24) | ||
| 192 | - // .borderRadius($r('app.float.margin_1')) | ||
| 193 | - // .onClick(async (event: ClickEvent) => { | ||
| 194 | - // if(await FastClickUtil.isMinDelayTime()){ | ||
| 195 | - // return | ||
| 196 | - // } | ||
| 197 | - // let temp: PhotoListBean[] = [] as PhotoListBean[] | ||
| 198 | - // temp.length = this.pics.length - 1; | ||
| 199 | - // let tempIndex = 0; | ||
| 200 | - // for (let index = 0; index < this.pics.length; index++) { | ||
| 201 | - // const element = this.pics[index]; | ||
| 202 | - // if(!StringUtils.isEmpty(element.picPath) && element.id != feedbackImageItem.id){ | ||
| 203 | - // temp[tempIndex] = element; | ||
| 204 | - // tempIndex = tempIndex+1 | ||
| 205 | - // } | ||
| 206 | - // } | ||
| 207 | - // if(tempIndex < 3){ | ||
| 208 | - // temp[tempIndex] = this.addPic | ||
| 209 | - // } | ||
| 210 | - // this.pics = temp | ||
| 211 | - // }) | ||
| 212 | - // } | ||
| 213 | - // .width(60) | ||
| 214 | - // .height(60) | ||
| 215 | - // } | ||
| 216 | - // } | ||
| 217 | - // }) | 167 | + ForEach(this.pics, (feedbackImageItem: PhotoListBean, index: number) => { |
| 168 | + GridCol({ | ||
| 169 | + }) { | ||
| 170 | + if(1 == feedbackImageItem.itemType){ | ||
| 171 | + Image($r('app.media.feekback_add')) | ||
| 172 | + .width(60) | ||
| 173 | + .height(60) | ||
| 174 | + .onClick(async (event: ClickEvent) => { | ||
| 175 | + if(await FastClickUtil.isMinDelayTime()){ | ||
| 176 | + return | ||
| 177 | + } | ||
| 178 | + this.callFilePickerSelectImage(); | ||
| 179 | + }) | ||
| 180 | + }else{ | ||
| 181 | + Stack({alignContent: Alignment.TopEnd}) { | ||
| 182 | + Image(feedbackImageItem.picPath) | ||
| 183 | + .width(60) | ||
| 184 | + .height(60) | ||
| 185 | + .borderRadius($r('app.float.margin_1')) | ||
| 186 | + .onClick(async (event: ClickEvent) => { | ||
| 187 | + if(await FastClickUtil.isMinDelayTime()){ | ||
| 188 | + return | ||
| 189 | + } | ||
| 190 | + //查看图片 fixme 去除添加按钮 | ||
| 191 | + ProcessUtils.gotoMultiPictureListPage(this.pics, index) | ||
| 192 | + }) | ||
| 193 | + Image($r('app.media.icon_feekback_delete')) | ||
| 194 | + .width(24) | ||
| 195 | + .height(24) | ||
| 196 | + .borderRadius($r('app.float.margin_1')) | ||
| 197 | + .onClick(async (event: ClickEvent) => { | ||
| 198 | + if(await FastClickUtil.isMinDelayTime()){ | ||
| 199 | + return | ||
| 200 | + } | ||
| 201 | + let temp: PhotoListBean[] = [] as PhotoListBean[] | ||
| 202 | + temp.length = this.pics.length - 1; | ||
| 203 | + let tempIndex = 0; | ||
| 204 | + for (let index = 0; index < this.pics.length; index++) { | ||
| 205 | + const element = this.pics[index]; | ||
| 206 | + if(!StringUtils.isEmpty(element.picPath) && element.id != feedbackImageItem.id){ | ||
| 207 | + temp[tempIndex] = element; | ||
| 208 | + tempIndex = tempIndex+1 | ||
| 209 | + } | ||
| 210 | + } | ||
| 211 | + if(tempIndex < 3){ | ||
| 212 | + temp[tempIndex] = this.addPic | ||
| 213 | + } | ||
| 214 | + this.pics = temp | ||
| 215 | + }) | ||
| 216 | + } | ||
| 217 | + .width(60) | ||
| 218 | + .height(60) | ||
| 219 | + } | ||
| 220 | + } | ||
| 221 | + }) | ||
| 218 | } | 222 | } |
| 219 | .margin({bottom: $r('app.float.vp_12'), right: $r('app.float.vp_12'),left: $r('app.float.vp_12')}) | 223 | .margin({bottom: $r('app.float.vp_12'), right: $r('app.float.vp_12'),left: $r('app.float.vp_12')}) |
| 220 | Text(){ | 224 | Text(){ |
| @@ -426,6 +430,7 @@ export struct FeedBackActivity { | @@ -426,6 +430,7 @@ export struct FeedBackActivity { | ||
| 426 | return | 430 | return |
| 427 | } | 431 | } |
| 428 | 432 | ||
| 433 | + this.showLoading = true | ||
| 429 | try { | 434 | try { |
| 430 | let feedBackParams: FeedBackParams = { | 435 | let feedBackParams: FeedBackParams = { |
| 431 | //反馈内容 | 436 | //反馈内容 |
| @@ -445,15 +450,36 @@ export struct FeedBackActivity { | @@ -445,15 +450,36 @@ export struct FeedBackActivity { | ||
| 445 | feedBackParams.userName = UserDataLocal.getUserName() | 450 | feedBackParams.userName = UserDataLocal.getUserName() |
| 446 | } | 451 | } |
| 447 | 452 | ||
| 448 | - // //投诉图片 | ||
| 449 | - // if (imageUrl.size() > 0) { | ||
| 450 | - // String[] str = imageUrl.toArray(new String[imageUrl.size()]); | ||
| 451 | - // map.set("imageUrls", str); | ||
| 452 | - // } | 453 | + //投诉图片 |
| 454 | + if (this.pics.length > 0) { | ||
| 455 | + let inputs = this.pics.filter((item) => { | ||
| 456 | + return item.picPath && item.picPath.length > 0 | ||
| 457 | + }).map((item) => { | ||
| 458 | + let i = new UploadResourceParams(item.picPath) | ||
| 459 | + i.scene = OSSConfigSceneType.feedback | ||
| 460 | + i.fileType = OSSFileType.image | ||
| 461 | + return i | ||
| 462 | + }) | ||
| 463 | + let results = await OSSUploadManager.sharedManager().uploadFiles(inputs) | ||
| 464 | + if (results.length !== inputs.length) { | ||
| 465 | + this.showLoading = false | ||
| 466 | + this.showToastTip('附件上传失败') | ||
| 467 | + return | ||
| 468 | + } | ||
| 469 | + | ||
| 470 | + let files = results.map((item) => { | ||
| 471 | + return item.ossFile ?? "" | ||
| 472 | + }) | ||
| 473 | + feedBackParams.imageUrls = files | ||
| 474 | + } | ||
| 475 | + | ||
| 453 | await MultiPictureDetailViewModel.feedBackCommit(feedBackParams) | 476 | await MultiPictureDetailViewModel.feedBackCommit(feedBackParams) |
| 477 | + | ||
| 478 | + this.showLoading = false | ||
| 454 | router.back(); | 479 | router.back(); |
| 455 | } catch (exception) { | 480 | } catch (exception) { |
| 456 | console.log('请求失败',JSON.stringify(exception)) | 481 | console.log('请求失败',JSON.stringify(exception)) |
| 482 | + this.showLoading = false | ||
| 457 | } | 483 | } |
| 458 | } | 484 | } |
| 459 | 485 |
| @@ -6,3 +6,6 @@ export { HWLocationUtils } from './src/main/ets/location/HWLocationUtils' | @@ -6,3 +6,6 @@ export { HWLocationUtils } from './src/main/ets/location/HWLocationUtils' | ||
| 6 | export { GetuiPush } from "./src/main/ets/getuiPush/GetuiPush" | 6 | export { GetuiPush } from "./src/main/ets/getuiPush/GetuiPush" |
| 7 | 7 | ||
| 8 | export {VoiceRecoginizer} from './src/main/ets/voiceRecognizer/VoiceRecoginizer' | 8 | export {VoiceRecoginizer} from './src/main/ets/voiceRecognizer/VoiceRecoginizer' |
| 9 | + | ||
| 10 | +export { OSSUploadManager,OSSConfigSceneType,OSSFileType,OSSUploadResult,UploadResourceParams } from "./src/main/ets/aliOSS/OSSUploadManager" | ||
| 11 | + |
| @@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
| 15 | "wdBean": "file:../../features/wdBean", | 15 | "wdBean": "file:../../features/wdBean", |
| 16 | "wdRouter": "file:../../commons/wdRouter", | 16 | "wdRouter": "file:../../commons/wdRouter", |
| 17 | "wdTracking": "file:../../features/wdTracking", | 17 | "wdTracking": "file:../../features/wdTracking", |
| 18 | - "wdNetwork": "file:../../commons/wdNetwork" | 18 | + "wdNetwork": "file:../../commons/wdNetwork", |
| 19 | + "@ohos/xml_js": "^1.0.2" | ||
| 19 | } | 20 | } |
| 20 | } | 21 | } |
| 1 | +import { NetLayerOSSConfigInfoModel, NetLayerOSSToken } from 'wdBean' | ||
| 2 | +import { HostEnum, HttpParams, HttpUrlUtils, ResponseDTO } from 'wdNetwork' | ||
| 3 | +import { HttpRequest } from 'wdNetwork/src/main/ets/http/HttpRequest' | ||
| 4 | +import { data } from '@kit.TelephonyKit' | ||
| 5 | +import { DateTimeUtils, DeviceUtil, Logger } from 'wdKit' | ||
| 6 | +import { MultipartUpload } from './multipartUpload' | ||
| 7 | +import { OSSConfig } from './OSSConfig' | ||
| 8 | +import { http } from '@kit.NetworkKit' | ||
| 9 | +import { it } from '@ohos/hypium' | ||
| 10 | +import { putObject } from './upload' | ||
| 11 | + | ||
| 12 | +export enum OSSConfigSceneType { | ||
| 13 | + feedback = "feedback", | ||
| 14 | + | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +export enum OSSFileType { | ||
| 18 | + image = 0, | ||
| 19 | + video = 1, | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +export class OSSUploadResult { | ||
| 23 | + ossFile?: string | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +export class UploadResourceParams { | ||
| 27 | + fileUri: string = '' | ||
| 28 | + scene: OSSConfigSceneType = OSSConfigSceneType.feedback | ||
| 29 | + fileType: OSSFileType = OSSFileType.image | ||
| 30 | + | ||
| 31 | + constructor(fileUri: string) { | ||
| 32 | + this.fileUri = fileUri | ||
| 33 | + } | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +const TAG = "OSSUploadManager" | ||
| 37 | + | ||
| 38 | +export class OSSUploadManager { | ||
| 39 | + | ||
| 40 | + private ossToken?: NetLayerOSSToken | ||
| 41 | + private configInfos?: Array<NetLayerOSSConfigInfoModel> | ||
| 42 | + | ||
| 43 | + private constructor() { } | ||
| 44 | + private static manager: OSSUploadManager | ||
| 45 | + static sharedManager(): OSSUploadManager { | ||
| 46 | + if (!OSSUploadManager.manager) { | ||
| 47 | + OSSUploadManager.manager = new OSSUploadManager() | ||
| 48 | + } | ||
| 49 | + return OSSUploadManager.manager | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + uploadFiles(inputs: Array<UploadResourceParams>) : Promise<Array<OSSUploadResult>> { | ||
| 53 | + return new Promise(async (resolve, fail) => { | ||
| 54 | + await this.getOssToken() | ||
| 55 | + await this.getOssConfigInfo() | ||
| 56 | + | ||
| 57 | + Promise.allSettled(inputs.map((item): Promise<OSSUploadResult> => { | ||
| 58 | + return this.uploadFile(item.fileUri, item.scene, item.fileType) | ||
| 59 | + })).then(res => { | ||
| 60 | + const failedJobs = res.filter(v => v.status === 'rejected'); | ||
| 61 | + | ||
| 62 | + if (failedJobs.length > 0) { | ||
| 63 | + console.info('Failed objects: ' + failedJobs.length); | ||
| 64 | + resolve([]) | ||
| 65 | + } else { | ||
| 66 | + console.info('All the objects upload success'); | ||
| 67 | + let results: Array<OSSUploadResult> = [] as Array<OSSUploadResult> | ||
| 68 | + res.forEach((item) => { | ||
| 69 | + console.info('>>>>>' + JSON.stringify(item)); | ||
| 70 | + if (item.status === 'fulfilled') { | ||
| 71 | + results.push(item.value) | ||
| 72 | + } | ||
| 73 | + }) | ||
| 74 | + resolve(results) | ||
| 75 | + } | ||
| 76 | + }) | ||
| 77 | + }) | ||
| 78 | + } | ||
| 79 | + | ||
| 80 | + uploadFile(fileUri: string, scene: OSSConfigSceneType, fileType: OSSFileType) : Promise<OSSUploadResult> { | ||
| 81 | + return new Promise(async (success, fail) => { | ||
| 82 | + | ||
| 83 | + try { | ||
| 84 | + let tokenModel = await this.getOssToken() | ||
| 85 | + | ||
| 86 | + let config = await this.getOssConfigInfo() | ||
| 87 | + let configModuel = config?.filter((c) => { | ||
| 88 | + return c.type == scene | ||
| 89 | + }).pop() | ||
| 90 | + | ||
| 91 | + if (!configModuel) { | ||
| 92 | + Logger.warn(TAG, "配置为空") | ||
| 93 | + return | ||
| 94 | + } | ||
| 95 | + | ||
| 96 | + //格式样例 zhbj/img/social/2024080215/BA6690B9CF084B3FAEEFA58F100F8D3E.jpg,这里的格式目前和iOS 是保持一致的 | ||
| 97 | + let objectName = configModuel.uploadPath + DateTimeUtils.getCurDate('yyyyMMddHH') + "/" + DeviceUtil.getRandomUUIDForTraceID() + (fileType == OSSFileType.image ? ".jpg" : ".mp4") | ||
| 98 | + Logger.debug(TAG, `==>> ${fileUri} `) | ||
| 99 | + Logger.debug(TAG, `==>> endPoint: ${configModuel.endPoint} `) | ||
| 100 | + Logger.debug(TAG, `==>> bucketName: ${configModuel.bucketName} `) | ||
| 101 | + Logger.debug(TAG, `==>> objectName: ` + objectName) | ||
| 102 | + Logger.debug(TAG, JSON.stringify(tokenModel)) | ||
| 103 | + Logger.debug(TAG, JSON.stringify(configModuel)) | ||
| 104 | + | ||
| 105 | + let result = await this.upload(fileUri, configModuel.endPoint, configModuel.bucketName, objectName) | ||
| 106 | + | ||
| 107 | + if (result) { | ||
| 108 | + let r = {} as OSSUploadResult | ||
| 109 | + r.ossFile = objectName | ||
| 110 | + | ||
| 111 | + let url = configModuel.domain + "/" + objectName | ||
| 112 | + console.log("result url: " + url) | ||
| 113 | + success(r) | ||
| 114 | + return | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + Logger.error(TAG, "上传失败") | ||
| 118 | + fail("上传失败") | ||
| 119 | + | ||
| 120 | + } catch (e) { | ||
| 121 | + | ||
| 122 | + Logger.error(TAG, "上传失败" + JSON.stringify(e)) | ||
| 123 | + fail("上传失败") | ||
| 124 | + } | ||
| 125 | + }) | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + private upload(fileUri: string, endpoint:string, bucketName:string, objectName:string) : Promise<boolean> { | ||
| 129 | + | ||
| 130 | + return new Promise(async (reslove, fail) => { | ||
| 131 | + | ||
| 132 | + if (!this.ossToken || !this.ossToken.securityToken) { | ||
| 133 | + fail("missing accessKeyId or accessKeySecret or sessionToken") | ||
| 134 | + return | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + let config: OSSConfig = { | ||
| 138 | + stsToken: this.ossToken.securityToken, | ||
| 139 | + bucket: bucketName, | ||
| 140 | + fileName: objectName, | ||
| 141 | + accessKeyId: this.ossToken.accessKeyId, | ||
| 142 | + accessKeySecret: this.ossToken.accessKeySecret, | ||
| 143 | + customHeaders: HttpParams.buildHeaders(), | ||
| 144 | + serverUrl: this.getServerHost() + "/api/harmonyoss/get_sign_url" | ||
| 145 | + } | ||
| 146 | + | ||
| 147 | + let success = false | ||
| 148 | + try { | ||
| 149 | + const multipartUpload = new MultipartUpload(config, fileUri); | ||
| 150 | + | ||
| 151 | + let result: http.HttpResponse = await multipartUpload.multipartUpload(); | ||
| 152 | + Logger.debug(TAG, "表单上传完成 + " + JSON.stringify(result)) | ||
| 153 | + | ||
| 154 | + if (result.responseCode == 200) { | ||
| 155 | + success = true | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + // await putObject(fileUri, config) | ||
| 159 | + // | ||
| 160 | + // success = true | ||
| 161 | + | ||
| 162 | + } catch (e) { | ||
| 163 | + Logger.error(TAG, JSON.stringify(e)) | ||
| 164 | + } finally { | ||
| 165 | + reslove(success) | ||
| 166 | + } | ||
| 167 | + }) | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + private getOssToken():Promise<NetLayerOSSToken | undefined> { | ||
| 171 | + return new Promise<NetLayerOSSToken | undefined>((success, fail) => { | ||
| 172 | + | ||
| 173 | + if (this.ossToken && this.ossToken.securityToken.length > 0) { | ||
| 174 | + | ||
| 175 | + // this.ossToken.expiration | ||
| 176 | + let dateNumber = DateTimeUtils.parseDate(this.ossToken.expiration, DateTimeUtils.PATTERN_DATE_TIME_HYPHEN) | ||
| 177 | + if (Date.now() < (dateNumber - 60 * 1000)) { | ||
| 178 | + success(this.ossToken) | ||
| 179 | + return | ||
| 180 | + } | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + HttpRequest.get<ResponseDTO<NetLayerOSSToken>>( | ||
| 184 | + HttpUrlUtils.getOSSTokenUrl(), | ||
| 185 | + ).then((res: ResponseDTO<NetLayerOSSToken>) => { | ||
| 186 | + if (res.code != 0) { | ||
| 187 | + fail(res.message) | ||
| 188 | + return | ||
| 189 | + } | ||
| 190 | + | ||
| 191 | + this.ossToken = res.data | ||
| 192 | + success(res.data) | ||
| 193 | + }, (error: Error) => { | ||
| 194 | + fail(error.message) | ||
| 195 | + }) | ||
| 196 | + }) | ||
| 197 | + } | ||
| 198 | + | ||
| 199 | + private getOssConfigInfo() { | ||
| 200 | + let isAbroad = 0 | ||
| 201 | + return new Promise<Array<NetLayerOSSConfigInfoModel> | undefined>((success, fail) => { | ||
| 202 | + if (this.configInfos && this.configInfos.length > 0) { | ||
| 203 | + success(this.configInfos) | ||
| 204 | + return | ||
| 205 | + } | ||
| 206 | + HttpRequest.get<ResponseDTO<Array<NetLayerOSSConfigInfoModel>>> ( | ||
| 207 | + HttpUrlUtils.getOSSConfigInfoUrl()+ '?type=0' | ||
| 208 | + ).then((res: ResponseDTO<Array<NetLayerOSSConfigInfoModel>>) => { | ||
| 209 | + if (res.code != 0) { | ||
| 210 | + fail(res.message) | ||
| 211 | + return | ||
| 212 | + } | ||
| 213 | + this.configInfos = res.data | ||
| 214 | + success(res.data) | ||
| 215 | + }) | ||
| 216 | + }) | ||
| 217 | + } | ||
| 218 | + | ||
| 219 | + private getServerHost() { | ||
| 220 | + return HttpUrlUtils.getHost() | ||
| 221 | + // switch (HttpUrlUtils.getHost()) { | ||
| 222 | + // case HostEnum.HOST_UAT: { | ||
| 223 | + // return "https://pd-people-uat.pdnews.cn" | ||
| 224 | + // } | ||
| 225 | + // case HostEnum.HOST_DEV: { | ||
| 226 | + // return "https://pd-people-dev.pdnews.cn" | ||
| 227 | + // } | ||
| 228 | + // case HostEnum.HOST_SIT: { | ||
| 229 | + // return "https://pd-people-sit.pdnews.cn" | ||
| 230 | + // } | ||
| 231 | + // } | ||
| 232 | + // return "https://www.peopleapp.com" | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | +} |
| 1 | +import { http } from '@kit.NetworkKit'; | ||
| 2 | +import fs from '@ohos.file.fs'; | ||
| 3 | +import { getSignUrl } from './upload'; | ||
| 4 | +import { request } from './request'; | ||
| 5 | +import { xmlToObj } from './xml'; | ||
| 6 | +import { OSSConfig } from './OSSConfig'; | ||
| 7 | + | ||
| 8 | +type TPart = { | ||
| 9 | + partNum: number; | ||
| 10 | + etag: string; | ||
| 11 | +}; | ||
| 12 | + | ||
| 13 | +type TTodoPart = { | ||
| 14 | + partLength: number; | ||
| 15 | + partNum: number; | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * InitiateMultipartUpload | ||
| 20 | + * @param fileName 文件名 | ||
| 21 | + */ | ||
| 22 | +const initiateMultipartUpload = async (fileName: string, config: OSSConfig) => { | ||
| 23 | + console.info('in initiateMultipartUpload'); | ||
| 24 | + | ||
| 25 | + // 获取InitiateMultipartUpload签名URL | ||
| 26 | + const signUrlResult = await getSignUrl(config.fileName.length > 0 ? config.fileName: fileName, { | ||
| 27 | + url: config.serverUrl, | ||
| 28 | + method: 'POST', | ||
| 29 | + headers: config.customHeaders, | ||
| 30 | + queries: { | ||
| 31 | + uploads: null, | ||
| 32 | + bucket: config.bucket, | ||
| 33 | + stsToken: config.stsToken, | ||
| 34 | + accessKeyId: config.accessKeyId, | ||
| 35 | + accessKeySecret: config.accessKeySecret, | ||
| 36 | + } | ||
| 37 | + }); | ||
| 38 | + | ||
| 39 | + try { | ||
| 40 | + // 通过InitiateMultipartUpload接口来通知OSS初始化一个Multipart Upload事件 | ||
| 41 | + console.info('url: ' + signUrlResult.url); | ||
| 42 | + const response = await request(signUrlResult.url, { | ||
| 43 | + method: http.RequestMethod.POST, | ||
| 44 | + expectDataType: http.HttpDataType.STRING | ||
| 45 | + }, 200); | ||
| 46 | + const result = response.result as string; | ||
| 47 | + | ||
| 48 | + console.info('success initiateMultipartUpload'); | ||
| 49 | + | ||
| 50 | + const res = xmlToObj(result) as { | ||
| 51 | + InitiateMultipartUploadResult: { | ||
| 52 | + Bucket: string; | ||
| 53 | + Key: string; | ||
| 54 | + UploadId: string; | ||
| 55 | + EncodingType?: string; | ||
| 56 | + } | ||
| 57 | + }; | ||
| 58 | + | ||
| 59 | + return res.InitiateMultipartUploadResult; | ||
| 60 | + } catch (err) { | ||
| 61 | + console.info('initiateMultipartUpload request error: ' + JSON.stringify(err)); | ||
| 62 | + | ||
| 63 | + throw err; | ||
| 64 | + } | ||
| 65 | +}; | ||
| 66 | + | ||
| 67 | +/** | ||
| 68 | + * UploadPart | ||
| 69 | + * @param uploadId 分片上传的uploadId | ||
| 70 | + * @param partNum 分片上传的partNumber | ||
| 71 | + * @param file 上传的文件 | ||
| 72 | + * @param length 分片大小 | ||
| 73 | + * @param offset 文件读取位置 | ||
| 74 | + */ | ||
| 75 | +const uploadPart = async (uploadId: string, config: OSSConfig, partNum: number, file: fs.File, length: number, offset: number = 0) => { | ||
| 76 | + console.info('in uploadPart'); | ||
| 77 | + | ||
| 78 | + // 获取UploadPart签名URL | ||
| 79 | + const signUrlResult = await getSignUrl(config.fileName.length > 0 ? config.fileName: file.name, { | ||
| 80 | + url: config.serverUrl, | ||
| 81 | + method: 'PUT', | ||
| 82 | + headers: config.customHeaders, | ||
| 83 | + extHeaders: { | ||
| 84 | + 'Content-Length': length | ||
| 85 | + }, | ||
| 86 | + queries: { | ||
| 87 | + uploadId, | ||
| 88 | + partNumber: partNum.toString(), | ||
| 89 | + bucket: config.bucket, | ||
| 90 | + stsToken: config.stsToken, | ||
| 91 | + accessKeyId: config.accessKeyId, | ||
| 92 | + accessKeySecret: config.accessKeySecret, | ||
| 93 | + }, | ||
| 94 | + additionalHeaders: ['Content-Length'] | ||
| 95 | + }); | ||
| 96 | + | ||
| 97 | + const data = new ArrayBuffer(length); | ||
| 98 | + | ||
| 99 | + await fs.read(file.fd, data, { | ||
| 100 | + length, | ||
| 101 | + offset | ||
| 102 | + }); | ||
| 103 | + | ||
| 104 | + try { | ||
| 105 | + const response = await request(signUrlResult.url, { | ||
| 106 | + method: http.RequestMethod.PUT, | ||
| 107 | + header: { | ||
| 108 | + 'Content-Length': length, | ||
| 109 | + 'Content-Type': signUrlResult.contentType | ||
| 110 | + }, | ||
| 111 | + extraData: data | ||
| 112 | + }, 200); | ||
| 113 | + | ||
| 114 | + console.info('success uploadPart'); | ||
| 115 | + | ||
| 116 | + return response.header['etag'] as string; | ||
| 117 | + } catch (err) { | ||
| 118 | + console.info('uploadPart request error: ' + JSON.stringify(err)); | ||
| 119 | + | ||
| 120 | + throw err; | ||
| 121 | + } | ||
| 122 | +}; | ||
| 123 | + | ||
| 124 | +/** | ||
| 125 | + * CompleteMultipartUpload | ||
| 126 | + * @param fileName 文件名 | ||
| 127 | + * @param uploadId 分片上传的uploadId | ||
| 128 | + * @param completeAll 指定是否列举当前UploadId已上传的所有Part | ||
| 129 | + * @param [parts] CompleteMultipartUpload所需的Part列表 | ||
| 130 | + */ | ||
| 131 | +const completeMultipartUpload = async (fileName: string, config: OSSConfig, uploadId: string, completeAll: boolean = false, parts?: TPart[]) => { | ||
| 132 | + console.info('in completeMultipartUpload'); | ||
| 133 | + | ||
| 134 | + if (!completeAll && !parts) { | ||
| 135 | + throw new Error('completeMultipartUpload needs to pass in parameter parts.'); | ||
| 136 | + } | ||
| 137 | + | ||
| 138 | + const signUrlResult = await getSignUrl(config.fileName.length > 0 ? config.fileName: fileName, { | ||
| 139 | + url: config.serverUrl, | ||
| 140 | + method: 'POST', | ||
| 141 | + headers: config.customHeaders, | ||
| 142 | + extHeaders: completeAll ? { | ||
| 143 | + 'x-oss-complete-all': 'yes' | ||
| 144 | + } : { | ||
| 145 | + 'Content-Type': 'application/xml' | ||
| 146 | + }, | ||
| 147 | + queries: { | ||
| 148 | + uploadId, | ||
| 149 | + bucket: config.bucket, | ||
| 150 | + stsToken: config.stsToken, | ||
| 151 | + accessKeyId: config.accessKeyId, | ||
| 152 | + accessKeySecret: config.accessKeySecret, | ||
| 153 | + } | ||
| 154 | + }); | ||
| 155 | + | ||
| 156 | + let xml: string; | ||
| 157 | + | ||
| 158 | + if (!completeAll) { | ||
| 159 | + const completeParts = parts.concat().sort((a, b) => a.partNum - b.partNum) | ||
| 160 | + .filter((item, index, arr) => !index || item.partNum !== arr[index - 1].partNum); | ||
| 161 | + xml = '<?xml version="1.0" encoding="UTF-8"?>\n<CompleteMultipartUpload>\n'; | ||
| 162 | + | ||
| 163 | + completeParts.forEach(item => { | ||
| 164 | + xml += `<Part>\n<PartNumber>${item.partNum}</PartNumber>\n<ETag>${item.etag}</ETag>\n</Part>\n` | ||
| 165 | + }); | ||
| 166 | + xml += '</CompleteMultipartUpload>'; | ||
| 167 | + } | ||
| 168 | + | ||
| 169 | + try { | ||
| 170 | + const result = await request(signUrlResult.url, { | ||
| 171 | + method: http.RequestMethod.POST, | ||
| 172 | + header: completeAll ? { | ||
| 173 | + 'x-oss-complete-all': 'yes' | ||
| 174 | + } : { | ||
| 175 | + 'Content-Type': 'application/xml' | ||
| 176 | + }, | ||
| 177 | + extraData: !completeAll ? xml : undefined | ||
| 178 | + }, 200); | ||
| 179 | + console.info('success completeMultipartUpload'); | ||
| 180 | + | ||
| 181 | + return result; | ||
| 182 | + } catch (err) { | ||
| 183 | + console.info('completeMultipartUpload request error: ' + JSON.stringify(err)); | ||
| 184 | + | ||
| 185 | + throw err; | ||
| 186 | + } | ||
| 187 | +}; | ||
| 188 | + | ||
| 189 | +/** | ||
| 190 | + * 分片上传信息 | ||
| 191 | + */ | ||
| 192 | +interface ICheckpoint { | ||
| 193 | + /** 分片上传的uploadId */ | ||
| 194 | + uploadId: string; | ||
| 195 | + /** 文件URI */ | ||
| 196 | + fileUri: string; | ||
| 197 | + /** 分片大小 */ | ||
| 198 | + partSize: number; | ||
| 199 | + /** 已经上传完成的分片 */ | ||
| 200 | + doneParts: TPart[]; | ||
| 201 | +} | ||
| 202 | + | ||
| 203 | +/** | ||
| 204 | + * 分片上传 | ||
| 205 | + */ | ||
| 206 | +export class MultipartUpload { | ||
| 207 | + /** 分片上传信息 */ | ||
| 208 | + private checkpoint: ICheckpoint; | ||
| 209 | + /** 上传文件 */ | ||
| 210 | + private file: fs.File; | ||
| 211 | + /** 文件详细属性信息 */ | ||
| 212 | + private fileStat: fs.Stat; | ||
| 213 | + /** 取消上传标识 */ | ||
| 214 | + private cancelFlag = true; | ||
| 215 | + /** 并发上传数 */ | ||
| 216 | + private parallel = 5; | ||
| 217 | + /** 上传队列 */ | ||
| 218 | + private uploadQueue: TTodoPart[] = []; | ||
| 219 | + /** 当前正在上传数 */ | ||
| 220 | + private uploadingCount = 0; | ||
| 221 | + /** 上传失败分片信息 */ | ||
| 222 | + private uploadErrors: { | ||
| 223 | + partNum: number; | ||
| 224 | + uploadError: Error; | ||
| 225 | + }[] = []; | ||
| 226 | + private config: OSSConfig | ||
| 227 | + | ||
| 228 | + /** | ||
| 229 | + * 创建MultipartUpload实例 | ||
| 230 | + * @param [fileUri] 文件URI | ||
| 231 | + * @param [checkpoint] 分片上传信息 | ||
| 232 | + */ | ||
| 233 | + constructor(config:OSSConfig, fileUri?: string, checkpoint?: ICheckpoint) { | ||
| 234 | + this.config = config | ||
| 235 | + if (checkpoint) { | ||
| 236 | + this.checkpoint = checkpoint; | ||
| 237 | + this.file = fs.openSync(checkpoint.fileUri, fs.OpenMode.READ_ONLY); | ||
| 238 | + } else { | ||
| 239 | + if (!fileUri) { | ||
| 240 | + throw Error('MultipartUpload need fileUri or checkpoint.'); | ||
| 241 | + } | ||
| 242 | + | ||
| 243 | + this.file = fs.openSync(fileUri, fs.OpenMode.READ_ONLY); | ||
| 244 | + this.checkpoint = { | ||
| 245 | + uploadId: '', | ||
| 246 | + fileUri, | ||
| 247 | + partSize: 2 ** 20, | ||
| 248 | + doneParts: [] | ||
| 249 | + }; | ||
| 250 | + } | ||
| 251 | + | ||
| 252 | + this.fileStat = fs.statSync(this.file.fd); | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + private async uploadPart(part: TTodoPart, resolve: () => void) { | ||
| 256 | + this.uploadingCount++; | ||
| 257 | + | ||
| 258 | + const { | ||
| 259 | + partLength, | ||
| 260 | + partNum | ||
| 261 | + } = part; | ||
| 262 | + | ||
| 263 | + try { | ||
| 264 | + const result = await uploadPart(this.checkpoint.uploadId, this.config, partNum, this.file, partLength, (partNum - 1) * this.checkpoint.partSize); | ||
| 265 | + | ||
| 266 | + this.checkpoint.doneParts.push({ | ||
| 267 | + partNum: partNum, | ||
| 268 | + etag: result | ||
| 269 | + }); | ||
| 270 | + this.uploadingCount--; | ||
| 271 | + | ||
| 272 | + if(this.uploadErrors.length < 1) { | ||
| 273 | + if (this.uploadQueue.length < 1 && this.uploadingCount < 1) { | ||
| 274 | + resolve(); | ||
| 275 | + } else { | ||
| 276 | + this.next(resolve); | ||
| 277 | + } | ||
| 278 | + } | ||
| 279 | + } catch (e) { | ||
| 280 | + this.uploadingCount--; | ||
| 281 | + this.uploadErrors.push({ | ||
| 282 | + partNum: partNum, | ||
| 283 | + uploadError: e | ||
| 284 | + }); | ||
| 285 | + resolve(); | ||
| 286 | + } | ||
| 287 | + } | ||
| 288 | + | ||
| 289 | + private next(resolve: () => void) { | ||
| 290 | + if (this.cancelFlag) { | ||
| 291 | + resolve(); | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + if (this.uploadQueue.length > 0 && this.uploadingCount < this.parallel && this.uploadErrors.length < 1) { | ||
| 295 | + this.uploadPart(this.uploadQueue.shift(), resolve); | ||
| 296 | + } | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + /** | ||
| 300 | + * 执行分片上传 | ||
| 301 | + */ | ||
| 302 | + async multipartUpload() { | ||
| 303 | + this.cancelFlag = false; | ||
| 304 | + this.uploadQueue = []; | ||
| 305 | + this.uploadErrors = []; | ||
| 306 | + | ||
| 307 | + if (this.checkpoint.uploadId === '') { | ||
| 308 | + const initResult = await initiateMultipartUpload(this.file.name, this.config); | ||
| 309 | + | ||
| 310 | + this.checkpoint.uploadId = initResult.UploadId; | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + const partsSum = Math.ceil(this.fileStat.size / this.checkpoint.partSize); | ||
| 314 | + | ||
| 315 | + for (let i = 0; i < partsSum; i++) { | ||
| 316 | + if (this.checkpoint.doneParts.findIndex(v => v.partNum === i + 1) === -1) { | ||
| 317 | + this.uploadQueue.push({ | ||
| 318 | + partLength: i + 1 === partsSum ? this.fileStat.size % this.checkpoint.partSize : this.checkpoint.partSize, | ||
| 319 | + partNum: i + 1 | ||
| 320 | + }); | ||
| 321 | + } | ||
| 322 | + } | ||
| 323 | + | ||
| 324 | + const tempCount = Math.min(this.parallel, this.uploadQueue.length); | ||
| 325 | + | ||
| 326 | + await new Promise<void>((resolve) => { | ||
| 327 | + for (let i = 0; i < tempCount; i++) { | ||
| 328 | + this.next(resolve); | ||
| 329 | + } | ||
| 330 | + }); | ||
| 331 | + | ||
| 332 | + if (this.cancelFlag) { | ||
| 333 | + throw new Error('MultipartUpload cancel'); | ||
| 334 | + } | ||
| 335 | + | ||
| 336 | + if (this.uploadErrors.length) { | ||
| 337 | + throw new Error('Upload failed parts: ' + this.uploadErrors.map(i => i.partNum).join(',')); | ||
| 338 | + } | ||
| 339 | + | ||
| 340 | + return await completeMultipartUpload(this.file.name, this.config, this.checkpoint.uploadId, false, this.checkpoint.doneParts); | ||
| 341 | + } | ||
| 342 | + | ||
| 343 | + cancel() { | ||
| 344 | + this.cancelFlag = true; | ||
| 345 | + } | ||
| 346 | +}; | ||
| 347 | + | ||
| 348 | +export { | ||
| 349 | + initiateMultipartUpload, | ||
| 350 | + uploadPart, | ||
| 351 | + completeMultipartUpload | ||
| 352 | +}; |
| 1 | +import { http } from '@kit.NetworkKit'; | ||
| 2 | + | ||
| 3 | +const request = async (url: string, options: http.HttpRequestOptions, successCode: number[] | number) => { | ||
| 4 | + const httpRequest = http.createHttp(); | ||
| 5 | + | ||
| 6 | + try { | ||
| 7 | + console.info('request url: ' + url); | ||
| 8 | + console.info('request req: ' + JSON.stringify(options)); | ||
| 9 | + const httpResponse = await httpRequest.request(url, { | ||
| 10 | + ...options, | ||
| 11 | + priority: 1, | ||
| 12 | + connectTimeout: 60000, | ||
| 13 | + readTimeout: 60000, | ||
| 14 | + usingProtocol: http.HttpProtocol.HTTP1_1 | ||
| 15 | + }); | ||
| 16 | + console.info('request res: ' + JSON.stringify(httpResponse)); | ||
| 17 | + | ||
| 18 | + if ((Array.isArray(successCode) && successCode.includes(httpResponse.responseCode)) || httpResponse.responseCode === successCode) { | ||
| 19 | + const requestID = httpResponse.header['x-oss-request-id']; | ||
| 20 | + | ||
| 21 | + console.info(`request success${requestID ? ', oss request ID: ' + requestID : ''}`); | ||
| 22 | + | ||
| 23 | + return httpResponse; | ||
| 24 | + } else { | ||
| 25 | + throw { | ||
| 26 | + code: httpResponse.responseCode, | ||
| 27 | + result: httpResponse.result.toString(), | ||
| 28 | + requestID: httpResponse.header['x-oss-request-id'] | ||
| 29 | + }; | ||
| 30 | + } | ||
| 31 | + } catch (err) { | ||
| 32 | + console.info('request error: ' + JSON.stringify(err)); | ||
| 33 | + | ||
| 34 | + throw err; | ||
| 35 | + } finally { | ||
| 36 | + httpRequest.destroy(); | ||
| 37 | + } | ||
| 38 | +}; | ||
| 39 | + | ||
| 40 | +export { | ||
| 41 | + request | ||
| 42 | +}; |
| 1 | +import { http } from '@kit.NetworkKit'; | ||
| 2 | +import fs from '@ohos.file.fs'; | ||
| 3 | +import { request } from './request'; | ||
| 4 | +import { OSSConfig } from './OSSConfig'; | ||
| 5 | +import { JSON } from '@kit.ArkTS'; | ||
| 6 | + | ||
| 7 | +// const serverUrl = 'http://x.x.x.x:3000/get_sign_url'; // 获取签名URL的服务器URL | ||
| 8 | +// const serverUrl = 'https://pd-people-uat.pdnews.cn/api/harmonyoss/get_sign_url'; // 获取签名URL的服务器URL | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * getSignUrl返回数据 | ||
| 12 | + */ | ||
| 13 | +export interface ISignUrlResult { | ||
| 14 | + /** 签名URL */ | ||
| 15 | + url: string; | ||
| 16 | + /** content-type */ | ||
| 17 | + contentType?: string; | ||
| 18 | +} | ||
| 19 | + | ||
| 20 | +/** | ||
| 21 | + * 获取签名URL | ||
| 22 | + * @param fileName 文件名称 | ||
| 23 | + * @param req 用于生成V4签名URL的请求信息 | ||
| 24 | + * @param req.method 请求方式 | ||
| 25 | + * @param [req.headers] 请求头 | ||
| 26 | + * @param [req.queries] 请求查询参数 | ||
| 27 | + * @param [req.additionalHeaders] 加签的请求头 | ||
| 28 | + */ | ||
| 29 | +const getSignUrl = async (fileName: string, req: { | ||
| 30 | + url: string; | ||
| 31 | + method: 'GET' | 'POST' | 'PUT'; | ||
| 32 | + headers?: Record<string, string | number>; | ||
| 33 | + extHeaders?: Record<string, string | number>; | ||
| 34 | + queries?: Record<string, string>; | ||
| 35 | + additionalHeaders?: string[]; | ||
| 36 | +}): Promise<ISignUrlResult> => { | ||
| 37 | + console.info('in getSignUrl form url: ' + req.url); | ||
| 38 | + | ||
| 39 | + try { | ||
| 40 | + let h = req.headers ?? {} | ||
| 41 | + h['Content-Type'] = 'application/json' | ||
| 42 | + const response = await request(req.url, { | ||
| 43 | + method: http.RequestMethod.POST, | ||
| 44 | + header: h, | ||
| 45 | + extraData: { | ||
| 46 | + fileName, | ||
| 47 | + method: req.method, | ||
| 48 | + headers: req.extHeaders, | ||
| 49 | + queries: req.queries, | ||
| 50 | + additionalHeaders: req.additionalHeaders | ||
| 51 | + }, | ||
| 52 | + expectDataType: http.HttpDataType.OBJECT | ||
| 53 | + }, 200); | ||
| 54 | + const result = response.result as ISignUrlResult; | ||
| 55 | + | ||
| 56 | + console.info('success getSignUrl ' + JSON.stringify(result)); | ||
| 57 | + | ||
| 58 | + return result; | ||
| 59 | + } catch (err) { | ||
| 60 | + console.info('getSignUrl request error: ' + JSON.stringify(err)); | ||
| 61 | + | ||
| 62 | + throw err; | ||
| 63 | + } | ||
| 64 | +}; | ||
| 65 | + | ||
| 66 | +/** | ||
| 67 | + * PutObject | ||
| 68 | + * @param fileUri 文件URI | ||
| 69 | + */ | ||
| 70 | +const putObject = async (fileUri: string, config: OSSConfig): Promise<void> => { | ||
| 71 | + console.info('in putObject'); | ||
| 72 | + | ||
| 73 | + const fileInfo = await fs.open(fileUri, fs.OpenMode.READ_ONLY); | ||
| 74 | + const fileStat = await fs.stat(fileInfo.fd); | ||
| 75 | + let signUrlResult: ISignUrlResult; | ||
| 76 | + | ||
| 77 | + console.info('file name: ', fileInfo.name); | ||
| 78 | + | ||
| 79 | + try { | ||
| 80 | + // 获取PutObject的签名URL | ||
| 81 | + signUrlResult = await getSignUrl(config.fileName.length > 0 ? config.fileName: fileInfo.name, { | ||
| 82 | + url: config.serverUrl, | ||
| 83 | + method: 'PUT', | ||
| 84 | + headers: config.customHeaders, | ||
| 85 | + extHeaders: { | ||
| 86 | + 'Content-Length': fileStat.size | ||
| 87 | + }, | ||
| 88 | + queries: { | ||
| 89 | + stsToken: config.stsToken, | ||
| 90 | + bucket: config.bucket, | ||
| 91 | + accessKeyId: config.accessKeyId, | ||
| 92 | + accessKeySecret: config.accessKeySecret, | ||
| 93 | + }, | ||
| 94 | + additionalHeaders: ['Content-Length'] | ||
| 95 | + }); | ||
| 96 | + } catch (e) { | ||
| 97 | + await fs.close(fileInfo.fd); | ||
| 98 | + | ||
| 99 | + throw e; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + const data = new ArrayBuffer(fileStat.size); | ||
| 103 | + | ||
| 104 | + await fs.read(fileInfo.fd, data); | ||
| 105 | + await fs.close(fileInfo.fd); | ||
| 106 | + | ||
| 107 | + try { | ||
| 108 | + // 使用PutObject方法上传文件 | ||
| 109 | + await request(signUrlResult.url, { | ||
| 110 | + method: http.RequestMethod.PUT, | ||
| 111 | + header: { | ||
| 112 | + 'Content-Length': fileStat.size, | ||
| 113 | + 'Content-Type': signUrlResult.contentType | ||
| 114 | + }, | ||
| 115 | + extraData: data | ||
| 116 | + }, 200); | ||
| 117 | + | ||
| 118 | + console.info('success putObject'); | ||
| 119 | + } catch (err) { | ||
| 120 | + console.info('putObject request error: ' + JSON.stringify(err)); | ||
| 121 | + | ||
| 122 | + throw err; | ||
| 123 | + } | ||
| 124 | +}; | ||
| 125 | + | ||
| 126 | +export { | ||
| 127 | + getSignUrl, | ||
| 128 | + putObject | ||
| 129 | +}; |
| 1 | +import convert from '@ohos/xml_js' | ||
| 2 | + | ||
| 3 | +const removeTextProperty = (node) => { | ||
| 4 | + if (node && typeof node === 'object') { | ||
| 5 | + Object.keys(node).forEach(key => { | ||
| 6 | + if (node[key] && typeof node[key] === 'object') { | ||
| 7 | + if ('_text' in node[key]) { | ||
| 8 | + node[key] = node[key]._text; | ||
| 9 | + } else { | ||
| 10 | + removeTextProperty(node[key]); | ||
| 11 | + } | ||
| 12 | + } | ||
| 13 | + }); | ||
| 14 | + } | ||
| 15 | + | ||
| 16 | + return node; | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +const xmlToObj = (xml: string): object => { | ||
| 20 | + const result = convert.xml2js(xml, { | ||
| 21 | + compact: true, | ||
| 22 | + ignoreDeclaration: true, | ||
| 23 | + ignoreAttributes: true, | ||
| 24 | + ignoreComment: true, | ||
| 25 | + ignoreCdata: true | ||
| 26 | + }); | ||
| 27 | + | ||
| 28 | + return removeTextProperty(result); | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +export { | ||
| 32 | + xmlToObj | ||
| 33 | +}; |
-
Please register or login to post a comment