1、阿里云对象存储功能测试通过
2、腾讯云对象存储未测试,有需求再测 3、天翼云对象存储删除跟判断文件是否存在 未测试
This commit is contained in:
parent
345e597623
commit
db5b28f751
|
|
@ -0,0 +1,9 @@
|
|||
import { ALiYunInstance } from './service/ali/Ali';
|
||||
declare class ALiYunSDK {
|
||||
aliMap: Record<string, ALiYunInstance>;
|
||||
constructor();
|
||||
getInstance(accessKey: string, accessSecret: string): ALiYunInstance;
|
||||
}
|
||||
declare const SDK: ALiYunSDK;
|
||||
export default SDK;
|
||||
export { ALiYunInstance };
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { ALiYunInstance } from './service/ali/Ali';
|
||||
class ALiYunSDK {
|
||||
aliMap; //oss
|
||||
constructor() {
|
||||
this.aliMap = {};
|
||||
}
|
||||
getInstance(accessKey, accessSecret) {
|
||||
if (this.aliMap[accessKey]) {
|
||||
return this.aliMap[accessKey];
|
||||
}
|
||||
const instance = new ALiYunInstance(accessKey, accessSecret);
|
||||
Object.assign(this.aliMap, {
|
||||
[accessKey]: instance,
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
const SDK = new ALiYunSDK();
|
||||
export default SDK;
|
||||
export { ALiYunInstance };
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { CTYunInstance } from './service/ctyun/CTYun';
|
||||
class CTYunSDK {
|
||||
ctyunMap;
|
||||
ctyunMap; //oss
|
||||
constructor() {
|
||||
this.ctyunMap = {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import { TencentYunInstance } from './service/tencent/Tencent';
|
||||
declare class TencentYunSDK {
|
||||
tencentMap: Record<string, TencentYunInstance>;
|
||||
constructor();
|
||||
getInstance(accessKey: string, accessSecret: string): TencentYunInstance;
|
||||
}
|
||||
declare const SDK: TencentYunSDK;
|
||||
export default SDK;
|
||||
export { TencentYunInstance };
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { TencentYunInstance } from './service/tencent/Tencent';
|
||||
class TencentYunSDK {
|
||||
tencentMap; //oss
|
||||
constructor() {
|
||||
this.tencentMap = {};
|
||||
}
|
||||
getInstance(accessKey, accessSecret) {
|
||||
if (this.tencentMap[accessKey]) {
|
||||
return this.tencentMap[accessKey];
|
||||
}
|
||||
const instance = new TencentYunInstance(accessKey, accessSecret);
|
||||
Object.assign(this.tencentMap, {
|
||||
[accessKey]: instance,
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
const SDK = new TencentYunSDK();
|
||||
export default SDK;
|
||||
export { TencentYunInstance };
|
||||
|
|
@ -2,7 +2,9 @@ import WechatSDK, { WechatMpInstance, WechatPublicInstance, WechatWebInstance }
|
|||
import AmapSDK from './AmapSDK';
|
||||
import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK';
|
||||
import SmsSdk, { TencentSmsInstance, AliSmsInstance, CTYunSmsInstance } from './SmsSdk';
|
||||
import CTYunSDk, { CTYunInstance } from './CTYunSDK';
|
||||
import CTYunSDK, { CTYunInstance } from './CTYunSDK';
|
||||
import ALiYunSDK, { ALiYunInstance } from './ALiYunSDK';
|
||||
import TencentYunSDK, { TencentYunInstance } from './TencentYunSDK';
|
||||
export * from './service/amap/Amap';
|
||||
export { AmapSDK, QiniuSDK, WechatSDK, CTYunSDk, CTYunInstance, WechatMpInstance, WechatPublicInstance, WechatWebInstance, QiniuCloudInstance, SmsSdk, TencentSmsInstance, AliSmsInstance, CTYunSmsInstance, };
|
||||
export { AmapSDK, QiniuSDK, WechatSDK, CTYunSDK, ALiYunSDK, TencentYunSDK, CTYunInstance, WechatMpInstance, WechatPublicInstance, WechatWebInstance, QiniuCloudInstance, ALiYunInstance, TencentYunInstance, SmsSdk, TencentSmsInstance, AliSmsInstance, CTYunSmsInstance, };
|
||||
export * from './types';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import WechatSDK, { WechatMpInstance, WechatPublicInstance, WechatWebInstance }
|
|||
import AmapSDK from './AmapSDK';
|
||||
import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK';
|
||||
import SmsSdk, { TencentSmsInstance, AliSmsInstance, CTYunSmsInstance, } from './SmsSdk';
|
||||
import CTYunSDk, { CTYunInstance } from './CTYunSDK';
|
||||
import CTYunSDK, { CTYunInstance } from './CTYunSDK';
|
||||
import ALiYunSDK, { ALiYunInstance } from './ALiYunSDK';
|
||||
import TencentYunSDK, { TencentYunInstance } from './TencentYunSDK';
|
||||
export * from './service/amap/Amap';
|
||||
export { AmapSDK, QiniuSDK, WechatSDK, CTYunSDk, CTYunInstance, WechatMpInstance, WechatPublicInstance, WechatWebInstance, QiniuCloudInstance, SmsSdk, TencentSmsInstance, AliSmsInstance, CTYunSmsInstance, };
|
||||
export { AmapSDK, QiniuSDK, WechatSDK, CTYunSDK, ALiYunSDK, TencentYunSDK, CTYunInstance, WechatMpInstance, WechatPublicInstance, WechatWebInstance, QiniuCloudInstance, ALiYunInstance, TencentYunInstance, SmsSdk, TencentSmsInstance, AliSmsInstance, CTYunSmsInstance, };
|
||||
export * from './types';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { ALiYunZone } from '../../types/ALiYun';
|
||||
import OSS from 'ali-oss';
|
||||
export declare class ALiYunInstance {
|
||||
private accessKey;
|
||||
private secretKey;
|
||||
constructor(accessKey: string, secretKey: string);
|
||||
getUploadInfo(bucket: string, zone: ALiYunZone, key?: string): {
|
||||
key: string | undefined;
|
||||
accessKey: string;
|
||||
policy: string;
|
||||
signature: string;
|
||||
uploadHost: string;
|
||||
bucket: string;
|
||||
};
|
||||
private getSignInfo;
|
||||
removeFile(srcBucket: string, zone: ALiYunZone, srcKey: string): Promise<OSS.DeleteResult>;
|
||||
isExistObject(srcBucket: string, zone: ALiYunZone, srcKey: string): Promise<boolean>;
|
||||
}
|
||||
|
|
@ -1 +1,183 @@
|
|||
"use strict";
|
||||
import OSS from 'ali-oss';
|
||||
function getEndpoint(RegionID) {
|
||||
return `oss-${RegionID}.aliyuncs.com`;
|
||||
}
|
||||
const ALiYun_ENDPOINT_LIST = {
|
||||
'cn-hangzhou': {
|
||||
ul: getEndpoint('cn-hangzhou'),
|
||||
},
|
||||
'cn-shanghai': {
|
||||
ul: getEndpoint('cn-shanghai'),
|
||||
},
|
||||
'cn-nanjing': {
|
||||
ul: getEndpoint('cn-nanjing'),
|
||||
},
|
||||
'cn-fuzhou': {
|
||||
ul: getEndpoint('cn-fuzhou'),
|
||||
},
|
||||
'cn-wuhan': {
|
||||
ul: getEndpoint('cn-wuhan'),
|
||||
},
|
||||
'cn-qingdao': {
|
||||
ul: getEndpoint('cn-qingdao'),
|
||||
},
|
||||
'cn-beijing': {
|
||||
ul: getEndpoint('cn-beijing'),
|
||||
},
|
||||
'cn-zhangjiakou': {
|
||||
ul: getEndpoint('cn-zhangjiakou'),
|
||||
},
|
||||
'cn-huhehaote': {
|
||||
ul: getEndpoint('cn-huhehaote'),
|
||||
},
|
||||
'cn-wulanchabu': {
|
||||
ul: getEndpoint('cn-wulanchabu'),
|
||||
},
|
||||
'cn-shenzhen': {
|
||||
ul: getEndpoint('cn-shenzhen'),
|
||||
},
|
||||
'cn-heyuan': {
|
||||
ul: getEndpoint('cn-heyuan'),
|
||||
},
|
||||
'cn-guangzhou': {
|
||||
ul: getEndpoint('cn-guangzhou'),
|
||||
},
|
||||
'cn-chengdu': {
|
||||
ul: getEndpoint('cn-chengdu'),
|
||||
},
|
||||
'cn-hongkong': {
|
||||
ul: getEndpoint('cn-hongkong'),
|
||||
},
|
||||
'us-west-1': {
|
||||
ul: getEndpoint('us-west-1'),
|
||||
},
|
||||
'us-east-1': {
|
||||
ul: getEndpoint('us-east-1'),
|
||||
},
|
||||
'ap-northeast-1': {
|
||||
ul: getEndpoint('ap-northeast-1'),
|
||||
},
|
||||
'ap-northeast-2': {
|
||||
ul: getEndpoint('ap-northeast-2'),
|
||||
},
|
||||
'ap-southeast-1': {
|
||||
ul: getEndpoint('ap-southeast-1'),
|
||||
},
|
||||
'ap-southeast-2': {
|
||||
ul: getEndpoint('ap-southeast-2'),
|
||||
},
|
||||
'ap-southeast-3': {
|
||||
ul: getEndpoint('ap-southeast-3'),
|
||||
},
|
||||
'ap-southeast-5': {
|
||||
ul: getEndpoint('ap-southeast-5'),
|
||||
},
|
||||
'ap-southeast-6': {
|
||||
ul: getEndpoint('ap-southeast-6'),
|
||||
},
|
||||
'ap-southeast-7': {
|
||||
ul: getEndpoint('ap-southeast-7'),
|
||||
},
|
||||
'ap-south-1': {
|
||||
ul: getEndpoint('ap-south-1'),
|
||||
},
|
||||
'eu-central-1': {
|
||||
ul: getEndpoint('eu-central-1'),
|
||||
},
|
||||
'eu-west-1': {
|
||||
ul: getEndpoint('eu-west-1'),
|
||||
},
|
||||
'me-east-1': {
|
||||
ul: getEndpoint('me-east-1'),
|
||||
},
|
||||
'rg-china-mainland': {
|
||||
ul: getEndpoint('rg-china-mainland'),
|
||||
},
|
||||
};
|
||||
export class ALiYunInstance {
|
||||
accessKey;
|
||||
secretKey;
|
||||
constructor(accessKey, secretKey) {
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
getUploadInfo(bucket, zone, key) {
|
||||
try {
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
accessKey: this.accessKey,
|
||||
policy: signInfo.policy,
|
||||
signature: signInfo.signature,
|
||||
uploadHost: `https://${bucket}.${ALiYun_ENDPOINT_LIST[zone].ul}`,
|
||||
bucket,
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
//https://help.aliyun.com/zh/oss/user-guide/form-upload?spm=a2c4g.11186623.0.0.22147f6b1UaGxF#6ee9b6b0be6on
|
||||
getSignInfo(bucket) {
|
||||
const client = new OSS({
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
bucket: bucket,
|
||||
});
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1);
|
||||
const policy = {
|
||||
expiration: now.toISOString(),
|
||||
conditions: [
|
||||
// 设置上传文件的大小限制。
|
||||
['content-length-range', 0, 1048576000],
|
||||
// 限制可上传的Bucket。
|
||||
{ bucket: bucket },
|
||||
],
|
||||
};
|
||||
const data = client.calculatePostSignature(policy);
|
||||
return {
|
||||
policy: data.policy,
|
||||
signature: data.Signature,
|
||||
ossAccessKeyId: data.OSSAccessKeyId,
|
||||
};
|
||||
}
|
||||
async removeFile(srcBucket, zone, srcKey) {
|
||||
const client = new OSS({
|
||||
// oss-cn-hangzhou填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||||
region: `oss-${zone}`,
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
// 填写Bucket名称。
|
||||
bucket: srcBucket,
|
||||
});
|
||||
try {
|
||||
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
|
||||
const result = await client.delete(srcKey);
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async isExistObject(srcBucket, zone, srcKey) {
|
||||
const client = new OSS({
|
||||
// yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||||
region: `oss-${zone}`,
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
bucket: srcBucket,
|
||||
});
|
||||
let result;
|
||||
try {
|
||||
result = await client.head(srcKey);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { BinaryToTextEncoding } from 'crypto';
|
||||
import { CTYunZone, ReqOptionProps } from '../../types/CTYun';
|
||||
export declare class CTYunInstance {
|
||||
private accessKey;
|
||||
|
|
@ -22,16 +19,10 @@ export declare class CTYunInstance {
|
|||
private hmacSha1;
|
||||
private urlSafeBase64Encode;
|
||||
removeFile(bucket: string, zone: CTYunZone, key: string): Promise<void>;
|
||||
isExistObject(srcBucket: string, zone: CTYunZone, srcKey: string): Promise<boolean>;
|
||||
getAuthorization(reqOptions: ReqOptionProps, zone: CTYunZone): string;
|
||||
signatureFn(reqOptions: ReqOptionProps, zone: CTYunZone): string | Buffer;
|
||||
stringToSign(reqOptions: ReqOptionProps, zone: CTYunZone): string;
|
||||
signedHeaders(headers: Record<string, string>): string;
|
||||
canonicalString(path: string, method: string, headers: Record<string, string>): string;
|
||||
canonicalHeaders(headers: Record<string, string>): string;
|
||||
canonicalHeaderValues(values: string): string;
|
||||
getSigningKey(date: string, zone: CTYunZone): string | Buffer;
|
||||
hmacSha256(key: string | Buffer, content: string | Buffer, digest?: BinaryToTextEncoding, fn?: string): string | Buffer;
|
||||
each(object: any, iterFunction: (key: any, item: any) => any): void;
|
||||
arrayEach(array: any, iterFunction: (item: any, key: any) => any): void;
|
||||
isSignableHeader(key: string): boolean;
|
||||
private getSignatureKey;
|
||||
private buildCanonicalRequest;
|
||||
private buildCanonicalQueryString;
|
||||
private calculatePayloadHash;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// import AWS from 'aws-sdk';
|
||||
import crypto from 'crypto';
|
||||
import { OakNetworkException, } from 'oak-domain/lib/types/Exception';
|
||||
import { OakExternalException, OakNetworkException, } from 'oak-domain/lib/types/Exception';
|
||||
const CTYun_ENDPOINT_LIST = {
|
||||
hazz: {
|
||||
ul: 'oos-hazz.ctyunapi.cn',
|
||||
|
|
@ -44,8 +44,16 @@ const CTYun_ENDPOINT_LIST = {
|
|||
};
|
||||
const serviceName = 's3';
|
||||
const v4Identifier = 'aws4_request';
|
||||
const expiresHeader = "presigned-expires";
|
||||
const unsignableHeaders = ["authorization", "x-ctyun-data-location", "content-length", "user-agent", expiresHeader, "expect", "x-amzn-trace-id"];
|
||||
const expiresHeader = 'presigned-expires';
|
||||
const unsignableHeaders = [
|
||||
'authorization',
|
||||
'x-ctyun-data-location',
|
||||
'content-length',
|
||||
'user-agent',
|
||||
expiresHeader,
|
||||
'expect',
|
||||
'x-amzn-trace-id',
|
||||
];
|
||||
export class CTYunInstance {
|
||||
accessKey;
|
||||
secretKey;
|
||||
|
|
@ -55,7 +63,6 @@ export class CTYunInstance {
|
|||
}
|
||||
getUploadInfo(bucket, zone, key) {
|
||||
try {
|
||||
// const uploadToken = this.getToken(zone, bucket, actions);
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
|
|
@ -73,30 +80,30 @@ export class CTYunInstance {
|
|||
getSignInfo(bucket) {
|
||||
// 对于policy里的expiration,我在天翼云的文档里没有找到具体的说明,但是这个字段不填入就会请求失败
|
||||
// 设置一个明天过期的时间
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1);
|
||||
const tomorrow = now.toISOString();
|
||||
const expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 1);
|
||||
const policy = {
|
||||
Version: "2012-10-17",
|
||||
Statement: [{
|
||||
Effect: "Allow",
|
||||
Action: ["oos:*"],
|
||||
Resource: `arn:ctyun:oos:::${bucket} /*`
|
||||
}],
|
||||
expiration: tomorrow,
|
||||
conditions: [{
|
||||
expiration: expiration.toISOString(),
|
||||
conditions: [
|
||||
{
|
||||
bucket: bucket,
|
||||
}, [
|
||||
"starts-with",
|
||||
"$key",
|
||||
"extraFile",
|
||||
]]
|
||||
},
|
||||
['starts-with', '$key', 'extraFile'],
|
||||
],
|
||||
Version: '2012-10-17',
|
||||
Statement: [
|
||||
{
|
||||
Effect: 'Allow',
|
||||
Action: ['oos:*'],
|
||||
Resource: `arn:ctyun:oos:::${bucket} /*`,
|
||||
},
|
||||
],
|
||||
};
|
||||
const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy));
|
||||
const signature = this.hmacSha1(encodePolicy, this.secretKey);
|
||||
return {
|
||||
encodePolicy,
|
||||
signature
|
||||
signature,
|
||||
};
|
||||
}
|
||||
base64ToUrlSafe(v) {
|
||||
|
|
@ -111,146 +118,195 @@ export class CTYunInstance {
|
|||
const encoded = Buffer.from(jsonFlags).toString('base64');
|
||||
return this.base64ToUrlSafe(encoded);
|
||||
}
|
||||
// 当初就不应该封装天翼云,文档不全,找技术要签名代码还得自己去看源码,恶心
|
||||
// 下面的代码是根据天翼云生成签名源码改动得来 oos-js-sdk-6.2.js
|
||||
async removeFile(bucket, zone, key) {
|
||||
const path = `/${bucket}/${key}`;
|
||||
const host = `${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const path = `/${key}`;
|
||||
const host = `${bucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const url = `https://${host}${path}`;
|
||||
const date = new Date().toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:\-]|\.\d{3}/g, "");
|
||||
const payload = '';
|
||||
const method = 'DELETE';
|
||||
const service = 's3';
|
||||
const date = new Date()
|
||||
.toISOString()
|
||||
.replace(/\.\d{3}Z$/, 'Z')
|
||||
.replace(/[:\-]|\.\d{3}/g, '');
|
||||
const headers = {
|
||||
"Content-Type": "application/octet-stream; charset=UTF-8",
|
||||
"Host": "oos-hbwh.ctyunapi.cn",
|
||||
"X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD",
|
||||
"X-Amz-User-Agent": "aws-sdk-js/2.296.0 callback"
|
||||
'Content-Type': 'application/xml; charset=utf-8',
|
||||
Host: host,
|
||||
'x-amz-content-sha256': this.calculatePayloadHash(payload),
|
||||
'x-amz-date': date,
|
||||
};
|
||||
headers["X-Amz-Date"] = date;
|
||||
const reqOptions = {
|
||||
path,
|
||||
host,
|
||||
date,
|
||||
headers,
|
||||
method: "DELETE",
|
||||
method: method,
|
||||
payload: payload,
|
||||
host,
|
||||
path,
|
||||
queryParameters: {},
|
||||
service,
|
||||
date,
|
||||
};
|
||||
const authorization = this.getAuthorization(reqOptions, zone);
|
||||
headers['Authorization'] = authorization;
|
||||
let response;
|
||||
try {
|
||||
await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
throw new OakNetworkException();
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return;
|
||||
}
|
||||
const text = await response.text();
|
||||
throw new OakExternalException('ctyun', response.status.toString(), text, {
|
||||
status: response.status,
|
||||
});
|
||||
}
|
||||
async isExistObject(srcBucket, zone, srcKey) {
|
||||
const path = `/${srcKey}`;
|
||||
const host = `${srcBucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const url = `https://${host}${path}`;
|
||||
const payload = '';
|
||||
const method = 'GET';
|
||||
const service = 's3';
|
||||
const date = new Date()
|
||||
.toISOString()
|
||||
.replace(/\.\d{3}Z$/, 'Z')
|
||||
.replace(/[:\-]|\.\d{3}/g, '');
|
||||
const headers = {
|
||||
Host: host,
|
||||
'x-amz-content-sha256': this.calculatePayloadHash(payload),
|
||||
'x-amz-date': date,
|
||||
};
|
||||
const reqOptions = {
|
||||
headers,
|
||||
method: method,
|
||||
payload: payload,
|
||||
host,
|
||||
path,
|
||||
queryParameters: {},
|
||||
service,
|
||||
date,
|
||||
};
|
||||
const authorization = this.getAuthorization(reqOptions, zone);
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
throw new OakNetworkException();
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return true;
|
||||
}
|
||||
const text = await response.text();
|
||||
if (response.status === 404 && text.includes('NoSuchKey')) {
|
||||
return false;
|
||||
}
|
||||
throw new OakExternalException('ctyun', response.status.toString(), text, {
|
||||
status: response.status,
|
||||
});
|
||||
}
|
||||
getAuthorization(reqOptions, zone) {
|
||||
const { headers, date } = reqOptions;
|
||||
const parts = [];
|
||||
const credString = [date.substring(0, 8), zone, serviceName, v4Identifier].join("/");
|
||||
parts.push("AWS4-HMAC-SHA256" + " Credential=" + this.accessKey + "/" + credString);
|
||||
parts.push("SignedHeaders=" + this.signedHeaders(headers));
|
||||
parts.push("Signature=" + this.signatureFn(reqOptions, zone));
|
||||
return parts.join(", ");
|
||||
const { headers, host, path, method, payload, queryParameters, service, date, } = reqOptions;
|
||||
const payloadHash = this.calculatePayloadHash(payload);
|
||||
// Step 2: Build canonical request
|
||||
const { canonicalRequest, signedHeaders } = this.buildCanonicalRequest(method, path, queryParameters, headers, payloadHash);
|
||||
// Step 3: Calculate canonical request hash
|
||||
const canonicalRequestHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(canonicalRequest)
|
||||
.digest('hex');
|
||||
// Step 4: Build string to sign
|
||||
const dateStamp = date.substr(0, 8);
|
||||
const credentialScope = `${dateStamp}/${zone}/${service}/aws4_request`;
|
||||
const stringToSign = [
|
||||
'AWS4-HMAC-SHA256',
|
||||
date,
|
||||
credentialScope,
|
||||
canonicalRequestHash,
|
||||
].join('\n');
|
||||
// Step 5: Get signing key
|
||||
const signingKey = this.getSignatureKey(dateStamp, zone, service);
|
||||
// Step 6: Calculate signature
|
||||
const signature = crypto
|
||||
.createHmac('sha256', signingKey)
|
||||
.update(stringToSign)
|
||||
.digest('hex');
|
||||
const authorizationHeader = [
|
||||
'AWS4-HMAC-SHA256 Credential=' +
|
||||
this.accessKey +
|
||||
'/' +
|
||||
credentialScope,
|
||||
'SignedHeaders=' + signedHeaders,
|
||||
'Signature=' + signature,
|
||||
].join(', ');
|
||||
return authorizationHeader;
|
||||
}
|
||||
signatureFn(reqOptions, zone) {
|
||||
const { date } = reqOptions;
|
||||
var signingKey = this.getSigningKey(date.substring(0, 8), zone);
|
||||
return this.hmacSha256(signingKey, this.stringToSign(reqOptions, zone), "hex");
|
||||
getSignatureKey(dateStamp, zone, service) {
|
||||
const kDate = crypto
|
||||
.createHmac('sha256', `AWS4${this.secretKey}`)
|
||||
.update(dateStamp)
|
||||
.digest();
|
||||
const kRegion = crypto
|
||||
.createHmac('sha256', kDate)
|
||||
.update(zone)
|
||||
.digest();
|
||||
const kService = crypto
|
||||
.createHmac('sha256', kRegion)
|
||||
.update(service)
|
||||
.digest();
|
||||
const kSigning = crypto
|
||||
.createHmac('sha256', kService)
|
||||
.update('aws4_request')
|
||||
.digest();
|
||||
return kSigning;
|
||||
}
|
||||
stringToSign(reqOptions, zone) {
|
||||
const { date, path, method, headers } = reqOptions;
|
||||
var parts = [];
|
||||
parts.push("AWS4-HMAC-SHA256");
|
||||
parts.push(date);
|
||||
parts.push([date.substring(0, 8), zone, serviceName, v4Identifier].join("/"));
|
||||
const canonicalStr = this.canonicalString(path, method, headers);
|
||||
const buffer = Buffer.from(canonicalStr);
|
||||
const encodeStr = crypto.createHash('sha256').update(buffer).digest('hex');
|
||||
parts.push(encodeStr);
|
||||
return parts.join("\n");
|
||||
buildCanonicalRequest(method, path, queryParameters, headers, payloadHash) {
|
||||
const canonicalQuerystring = this.buildCanonicalQueryString(queryParameters);
|
||||
const canonicalHeaders = Object.keys(headers)
|
||||
.sort()
|
||||
.map((key) => `${key.toLowerCase()}:${headers[key].trim()}`)
|
||||
.join('\n');
|
||||
const signedHeaders = Object.keys(headers)
|
||||
.sort()
|
||||
.map((key) => key.toLowerCase())
|
||||
.join(';');
|
||||
const canonicalRequest = [
|
||||
method,
|
||||
path,
|
||||
canonicalQuerystring,
|
||||
canonicalHeaders,
|
||||
'',
|
||||
signedHeaders,
|
||||
payloadHash,
|
||||
].join('\n');
|
||||
return {
|
||||
canonicalRequest,
|
||||
signedHeaders,
|
||||
};
|
||||
}
|
||||
signedHeaders(headers) {
|
||||
const keys = [];
|
||||
this.each(headers, (key) => {
|
||||
key = key.toLowerCase();
|
||||
if (this.isSignableHeader(key))
|
||||
keys.push(key);
|
||||
});
|
||||
return keys.sort().join(";");
|
||||
buildCanonicalQueryString(queryParameters) {
|
||||
const keys = Object.keys(queryParameters).sort();
|
||||
const canonicalQueryString = keys
|
||||
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParameters[key])}`)
|
||||
.join('&');
|
||||
return canonicalQueryString;
|
||||
}
|
||||
canonicalString(path, method, headers) {
|
||||
const parts = [];
|
||||
parts.push(method);
|
||||
parts.push(path);
|
||||
parts.push("");
|
||||
parts.push(this.canonicalHeaders(headers) + "\n");
|
||||
parts.push(this.signedHeaders(headers));
|
||||
parts.push("UNSIGNED-PAYLOAD");
|
||||
return parts.join("\n");
|
||||
}
|
||||
canonicalHeaders(headers) {
|
||||
const headerarr = [];
|
||||
this.each(headers, function (key, item) {
|
||||
headerarr.push([key, item]);
|
||||
});
|
||||
headerarr.sort(function (a, b) {
|
||||
return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1;
|
||||
});
|
||||
const parts = [];
|
||||
this.arrayEach(headerarr, (item) => {
|
||||
let key = item[0].toLowerCase();
|
||||
if (this.isSignableHeader(key)) {
|
||||
var value = item[1];
|
||||
if (typeof value === "undefined" || value === null || typeof value.toString !== "function") {
|
||||
throw new Error("Header " + key + " contains invalid value");
|
||||
}
|
||||
parts.push(key + ":" + this.canonicalHeaderValues(value.toString()));
|
||||
}
|
||||
});
|
||||
return parts.join("\n");
|
||||
}
|
||||
canonicalHeaderValues(values) {
|
||||
return values.replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
|
||||
}
|
||||
getSigningKey(date, zone) {
|
||||
const kDate = this.hmacSha256("AWS4" + this.secretKey, date);
|
||||
const kRegion = this.hmacSha256(kDate, zone);
|
||||
const kService = this.hmacSha256(kRegion, serviceName);
|
||||
var signingKey = this.hmacSha256(kService, v4Identifier);
|
||||
return signingKey;
|
||||
}
|
||||
hmacSha256(key, content, digest, fn) {
|
||||
if (!fn)
|
||||
fn = "sha256";
|
||||
if (typeof content === "string")
|
||||
content = Buffer.from(content);
|
||||
if (!digest) {
|
||||
return crypto.createHmac(fn, key).update(content).digest();
|
||||
}
|
||||
return crypto.createHmac(fn, key).update(content).digest(digest);
|
||||
}
|
||||
each(object, iterFunction) {
|
||||
for (let key in object) {
|
||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||
const ret = iterFunction.call(this, key, object[key]);
|
||||
if (Object.keys(ret).length === 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
arrayEach(array, iterFunction) {
|
||||
for (let idx in array) {
|
||||
if (Object.prototype.hasOwnProperty.call(array, idx)) {
|
||||
const ret = iterFunction.call(this, array[idx], parseInt(idx, 10));
|
||||
if (Object.keys(ret).length === 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
isSignableHeader(key) {
|
||||
if (key.toLowerCase().indexOf("x-amz-") === 0)
|
||||
return true;
|
||||
return unsignableHeaders.indexOf(key) < 0;
|
||||
calculatePayloadHash(payload) {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(payload);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { TencentYunZone } from '../../types/TencentYun';
|
||||
export declare class TencentYunInstance {
|
||||
private accessKey;
|
||||
private secretKey;
|
||||
private COS;
|
||||
constructor(accessKey: string, secretKey: string);
|
||||
getUploadInfo(bucket: string, zone: TencentYunZone, key?: string): {
|
||||
key: string | undefined;
|
||||
accessKey: string;
|
||||
policy: string;
|
||||
signature: string;
|
||||
uploadHost: string;
|
||||
bucket: string;
|
||||
keyTime: string;
|
||||
algorithm: string;
|
||||
};
|
||||
private getSignInfo;
|
||||
removeFile(srcBucket: string, zone: TencentYunZone, srcKey: string): Promise<any>;
|
||||
isExistObject(srcBucket: string, zone: TencentYunZone, srcKey: string): Promise<boolean>;
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
import crypto from 'crypto';
|
||||
function getEndpoint(RegionID) {
|
||||
return `cos-${RegionID}.myqcloud.com`;
|
||||
}
|
||||
const Tencent_ENDPOINT_LIST = {
|
||||
'ap-beijing': {
|
||||
ul: getEndpoint('ap-nanjing'),
|
||||
},
|
||||
'ap-nanjing': {
|
||||
ul: getEndpoint('ap-nanjing'),
|
||||
},
|
||||
'ap-shanghai': {
|
||||
ul: getEndpoint('ap-shanghai'),
|
||||
},
|
||||
'ap-guangzhou': {
|
||||
ul: getEndpoint('ap-guangzhou'),
|
||||
},
|
||||
'ap-chengdu': {
|
||||
ul: getEndpoint('ap-chengdu'),
|
||||
},
|
||||
'ap-chongqing': {
|
||||
ul: getEndpoint('ap-chongqing'),
|
||||
},
|
||||
'ap-shenzhen-fsi': {
|
||||
ul: getEndpoint('ap-shenzhen-fsi'),
|
||||
},
|
||||
'ap-shanghai-fsi': {
|
||||
ul: getEndpoint('ap-shanghai-fsi'),
|
||||
},
|
||||
'ap-beijing-fsi': {
|
||||
ul: getEndpoint('ap-beijing-fsi'),
|
||||
},
|
||||
'ap-hongkong': {
|
||||
ul: getEndpoint('ap-hongkong'),
|
||||
},
|
||||
'ap-singapore': {
|
||||
ul: getEndpoint('ap-singapore'),
|
||||
},
|
||||
};
|
||||
// TODO 腾讯云代码未验证
|
||||
export class TencentYunInstance {
|
||||
accessKey;
|
||||
secretKey;
|
||||
COS;
|
||||
constructor(accessKey, secretKey) {
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
this.COS = require('cos-wx-sdk-v5');
|
||||
}
|
||||
getUploadInfo(bucket, zone, key) {
|
||||
try {
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
accessKey: this.accessKey,
|
||||
policy: signInfo.policy,
|
||||
signature: signInfo.signature,
|
||||
uploadHost: `https://${bucket}.${Tencent_ENDPOINT_LIST[zone].ul}`,
|
||||
bucket,
|
||||
keyTime: signInfo.keyTime,
|
||||
algorithm: signInfo.algorithm,
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
//https://cloud.tencent.com/document/product/436/7778
|
||||
getSignInfo(bucket) {
|
||||
const expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 1);
|
||||
const keyTime = `${Math.floor(Date.now() / 1000)};${Math.floor(expiration.getTime() / 1000)}`;
|
||||
const algorithm = 'sha1';
|
||||
const policy = {
|
||||
expiration: expiration.toISOString(),
|
||||
conditions: [
|
||||
// 限制可上传的Bucket。
|
||||
{ bucket: bucket },
|
||||
['eq', '$x-cos-server-side-encryption', 'AES256'],
|
||||
{ 'q-sign-algorithm': algorithm },
|
||||
{ 'q-ak': this.accessKey },
|
||||
{ 'q-sign-time': keyTime },
|
||||
],
|
||||
};
|
||||
const encodedPolicy = Buffer.from(JSON.stringify(policy)).toString('base64');
|
||||
const signKey = crypto
|
||||
.createHmac('sha1', this.secretKey)
|
||||
.update(keyTime)
|
||||
.digest();
|
||||
const stringToSign = crypto
|
||||
.createHash('sha1')
|
||||
.update(encodedPolicy)
|
||||
.digest('hex');
|
||||
const signature = crypto
|
||||
.createHmac('sha1', signKey)
|
||||
.update(stringToSign)
|
||||
.digest('hex');
|
||||
return {
|
||||
policy: encodedPolicy,
|
||||
signature,
|
||||
keyTime,
|
||||
algorithm,
|
||||
};
|
||||
}
|
||||
async removeFile(srcBucket, zone, srcKey) {
|
||||
const client = new this.COS({
|
||||
SecretId: this.accessKey,
|
||||
SecretKey: this.secretKey,
|
||||
});
|
||||
try {
|
||||
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
|
||||
const result = await client.deleteObject({
|
||||
Bucket: srcBucket,
|
||||
Region: zone,
|
||||
Key: srcKey,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async isExistObject(srcBucket, zone, srcKey) {
|
||||
const client = new this.COS({
|
||||
SecretId: this.accessKey,
|
||||
SecretKey: this.secretKey,
|
||||
});
|
||||
let result;
|
||||
try {
|
||||
result = await client.headObject({
|
||||
Bucket: srcBucket,
|
||||
Region: zone,
|
||||
Key: srcKey,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ export class WechatMpInstance {
|
|||
const { type, media, filetype, filename } = options;
|
||||
const formData = new FormData();
|
||||
formData.append('media', media, {
|
||||
contentType: filetype,
|
||||
contentType: filetype, // 微信识别需要
|
||||
filename: filename, // 微信识别需要
|
||||
});
|
||||
const getLength = () => {
|
||||
|
|
@ -332,7 +332,7 @@ export class WechatMpInstance {
|
|||
body: JSON.stringify({
|
||||
jump_wxa: jump_wxa,
|
||||
is_expire: true,
|
||||
expire_type: expireType,
|
||||
expire_type: expireType, //默认是零,到期失效的 scheme 码失效类型,失效时间类型:0,失效间隔天数类型:1
|
||||
expire_time: expiresAt,
|
||||
expire_interval: expireInterval,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -511,8 +511,8 @@ export class WechatPublicInstance {
|
|||
const { type, media, description, filetype, filename, fileLength } = options;
|
||||
const formData = new FormData();
|
||||
formData.append('media', media, {
|
||||
contentType: filetype,
|
||||
filename: filename,
|
||||
contentType: filetype, // 微信识别需要
|
||||
filename: filename, // 微信识别需要
|
||||
knownLength: fileLength,
|
||||
});
|
||||
if (type === 'video') {
|
||||
|
|
@ -535,8 +535,8 @@ export class WechatPublicInstance {
|
|||
const { media, filetype, filename, fileLength } = options;
|
||||
const formData = new FormData();
|
||||
formData.append('media', media, {
|
||||
contentType: filetype,
|
||||
filename: filename,
|
||||
contentType: filetype, // 微信识别需要
|
||||
filename: filename, // 微信识别需要
|
||||
knownLength: fileLength,
|
||||
});
|
||||
const headers = formData.getHeaders();
|
||||
|
|
@ -556,8 +556,8 @@ export class WechatPublicInstance {
|
|||
const { type, media, filetype, filename, fileLength } = options;
|
||||
const formData = new FormData();
|
||||
formData.append('media', media, {
|
||||
contentType: filetype,
|
||||
filename: filename,
|
||||
contentType: filetype, // 微信识别需要
|
||||
filename: filename, // 微信识别需要
|
||||
knownLength: fileLength,
|
||||
});
|
||||
const headers = formData.getHeaders();
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export type ALiYunZone = 'cn-hangzhou' | 'cn-shanghai' | 'cn-nanjing' | 'cn-fuzhou' | 'cn-wuhan' | 'cn-qingdao' | 'cn-beijing' | 'cn-zhangjiakou' | 'cn-huhehaote' | 'cn-wulanchabu' | 'cn-shenzhen' | 'cn-heyuan' | 'cn-guangzhou' | 'cn-chengdu' | 'cn-hongkong' | 'us-west-1' | 'us-east-1' | 'ap-northeast-1' | 'ap-northeast-2' | 'ap-southeast-1' | 'ap-southeast-2' | 'ap-southeast-3' | 'ap-southeast-5' | 'ap-southeast-6' | 'ap-southeast-7' | 'ap-south-1' | 'eu-central-1' | 'eu-west-1' | 'me-east-1' | 'rg-china-mainland';
|
||||
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
|
|
@ -4,10 +4,13 @@
|
|||
*/
|
||||
export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket';
|
||||
export type ReqOptionProps = {
|
||||
payload: string;
|
||||
path: string;
|
||||
host: string;
|
||||
date: string;
|
||||
headers: any;
|
||||
method: 'DELETE' | "GET" | "POST" | "PUT";
|
||||
method: 'DELETE' | 'GET' | 'POST' | 'PUT' | 'HEAD';
|
||||
queryParameters: Record<string, any>;
|
||||
service: string;
|
||||
date: string;
|
||||
};
|
||||
export type CTYunZone = 'hazz' | 'lnsy' | 'sccd' | 'xjwlmq' | 'gslz' | 'sdqd' | 'gzgy' | 'hbwh' | 'xzls' | 'ahwh' | 'gdsz' | 'jssz' | 'sh2';
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export type TencentYunZone = 'ap-beijing' | 'ap-nanjing' | 'ap-shanghai' | 'ap-guangzhou' | 'ap-chengdu' | 'ap-chongqing' | 'ap-shenzhen-fsi' | 'ap-shanghai-fsi' | 'ap-beijing-fsi' | 'ap-hongkong' | 'ap-singapore';
|
||||
|
|
@ -0,0 +1 @@
|
|||
export {};
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
export * from './Wechat';
|
||||
export * from './Qiniu';
|
||||
export * from './CTYun';
|
||||
export * from './ALiYun';
|
||||
export * from './TencentYun';
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export * from './Wechat';
|
||||
export * from './Qiniu';
|
||||
export * from './CTYun';
|
||||
export * from './ALiYun';
|
||||
export * from './TencentYun';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import { ALiYunInstance } from './service/ali/Ali';
|
||||
declare class ALiYunSDK {
|
||||
aliMap: Record<string, ALiYunInstance>;
|
||||
constructor();
|
||||
getInstance(accessKey: string, accessSecret: string): ALiYunInstance;
|
||||
}
|
||||
declare const SDK: ALiYunSDK;
|
||||
export default SDK;
|
||||
export { ALiYunInstance };
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ALiYunInstance = void 0;
|
||||
const Ali_1 = require("./service/ali/Ali");
|
||||
Object.defineProperty(exports, "ALiYunInstance", { enumerable: true, get: function () { return Ali_1.ALiYunInstance; } });
|
||||
class ALiYunSDK {
|
||||
aliMap; //oss
|
||||
constructor() {
|
||||
this.aliMap = {};
|
||||
}
|
||||
getInstance(accessKey, accessSecret) {
|
||||
if (this.aliMap[accessKey]) {
|
||||
return this.aliMap[accessKey];
|
||||
}
|
||||
const instance = new Ali_1.ALiYunInstance(accessKey, accessSecret);
|
||||
Object.assign(this.aliMap, {
|
||||
[accessKey]: instance,
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
const SDK = new ALiYunSDK();
|
||||
exports.default = SDK;
|
||||
|
|
@ -4,7 +4,7 @@ exports.CTYunInstance = void 0;
|
|||
const CTYun_1 = require("./service/ctyun/CTYun");
|
||||
Object.defineProperty(exports, "CTYunInstance", { enumerable: true, get: function () { return CTYun_1.CTYunInstance; } });
|
||||
class CTYunSDK {
|
||||
ctyunMap;
|
||||
ctyunMap; //oss
|
||||
constructor() {
|
||||
this.ctyunMap = {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import { TencentYunInstance } from './service/tencent/Tencent';
|
||||
declare class TencentYunSDK {
|
||||
tencentMap: Record<string, TencentYunInstance>;
|
||||
constructor();
|
||||
getInstance(accessKey: string, accessSecret: string): TencentYunInstance;
|
||||
}
|
||||
declare const SDK: TencentYunSDK;
|
||||
export default SDK;
|
||||
export { TencentYunInstance };
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TencentYunInstance = void 0;
|
||||
const Tencent_1 = require("./service/tencent/Tencent");
|
||||
Object.defineProperty(exports, "TencentYunInstance", { enumerable: true, get: function () { return Tencent_1.TencentYunInstance; } });
|
||||
class TencentYunSDK {
|
||||
tencentMap; //oss
|
||||
constructor() {
|
||||
this.tencentMap = {};
|
||||
}
|
||||
getInstance(accessKey, accessSecret) {
|
||||
if (this.tencentMap[accessKey]) {
|
||||
return this.tencentMap[accessKey];
|
||||
}
|
||||
const instance = new Tencent_1.TencentYunInstance(accessKey, accessSecret);
|
||||
Object.assign(this.tencentMap, {
|
||||
[accessKey]: instance,
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
const SDK = new TencentYunSDK();
|
||||
exports.default = SDK;
|
||||
|
|
@ -2,7 +2,9 @@ import WechatSDK, { WechatMpInstance, WechatPublicInstance, WechatWebInstance }
|
|||
import AmapSDK from './AmapSDK';
|
||||
import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK';
|
||||
import SmsSdk, { TencentSmsInstance, AliSmsInstance, CTYunSmsInstance } from './SmsSdk';
|
||||
import CTYunSDk, { CTYunInstance } from './CTYunSDK';
|
||||
import CTYunSDK, { CTYunInstance } from './CTYunSDK';
|
||||
import ALiYunSDK, { ALiYunInstance } from './ALiYunSDK';
|
||||
import TencentYunSDK, { TencentYunInstance } from './TencentYunSDK';
|
||||
export * from './service/amap/Amap';
|
||||
export { AmapSDK, QiniuSDK, WechatSDK, CTYunSDk, CTYunInstance, WechatMpInstance, WechatPublicInstance, WechatWebInstance, QiniuCloudInstance, SmsSdk, TencentSmsInstance, AliSmsInstance, CTYunSmsInstance, };
|
||||
export { AmapSDK, QiniuSDK, WechatSDK, CTYunSDK, ALiYunSDK, TencentYunSDK, CTYunInstance, WechatMpInstance, WechatPublicInstance, WechatWebInstance, QiniuCloudInstance, ALiYunInstance, TencentYunInstance, SmsSdk, TencentSmsInstance, AliSmsInstance, CTYunSmsInstance, };
|
||||
export * from './types';
|
||||
|
|
|
|||
10
lib/index.js
10
lib/index.js
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CTYunSmsInstance = exports.AliSmsInstance = exports.TencentSmsInstance = exports.SmsSdk = exports.QiniuCloudInstance = exports.WechatWebInstance = exports.WechatPublicInstance = exports.WechatMpInstance = exports.CTYunInstance = exports.CTYunSDk = exports.WechatSDK = exports.QiniuSDK = exports.AmapSDK = void 0;
|
||||
exports.CTYunSmsInstance = exports.AliSmsInstance = exports.TencentSmsInstance = exports.SmsSdk = exports.TencentYunInstance = exports.ALiYunInstance = exports.QiniuCloudInstance = exports.WechatWebInstance = exports.WechatPublicInstance = exports.WechatMpInstance = exports.CTYunInstance = exports.TencentYunSDK = exports.ALiYunSDK = exports.CTYunSDK = exports.WechatSDK = exports.QiniuSDK = exports.AmapSDK = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const WechatSDK_1 = tslib_1.__importStar(require("./WechatSDK"));
|
||||
exports.WechatSDK = WechatSDK_1.default;
|
||||
|
|
@ -18,7 +18,13 @@ Object.defineProperty(exports, "TencentSmsInstance", { enumerable: true, get: fu
|
|||
Object.defineProperty(exports, "AliSmsInstance", { enumerable: true, get: function () { return SmsSdk_1.AliSmsInstance; } });
|
||||
Object.defineProperty(exports, "CTYunSmsInstance", { enumerable: true, get: function () { return SmsSdk_1.CTYunSmsInstance; } });
|
||||
const CTYunSDK_1 = tslib_1.__importStar(require("./CTYunSDK"));
|
||||
exports.CTYunSDk = CTYunSDK_1.default;
|
||||
exports.CTYunSDK = CTYunSDK_1.default;
|
||||
Object.defineProperty(exports, "CTYunInstance", { enumerable: true, get: function () { return CTYunSDK_1.CTYunInstance; } });
|
||||
const ALiYunSDK_1 = tslib_1.__importStar(require("./ALiYunSDK"));
|
||||
exports.ALiYunSDK = ALiYunSDK_1.default;
|
||||
Object.defineProperty(exports, "ALiYunInstance", { enumerable: true, get: function () { return ALiYunSDK_1.ALiYunInstance; } });
|
||||
const TencentYunSDK_1 = tslib_1.__importStar(require("./TencentYunSDK"));
|
||||
exports.TencentYunSDK = TencentYunSDK_1.default;
|
||||
Object.defineProperty(exports, "TencentYunInstance", { enumerable: true, get: function () { return TencentYunSDK_1.TencentYunInstance; } });
|
||||
tslib_1.__exportStar(require("./service/amap/Amap"), exports);
|
||||
tslib_1.__exportStar(require("./types"), exports);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { ALiYunZone } from '../../types/ALiYun';
|
||||
import OSS from 'ali-oss';
|
||||
export declare class ALiYunInstance {
|
||||
private accessKey;
|
||||
private secretKey;
|
||||
constructor(accessKey: string, secretKey: string);
|
||||
getUploadInfo(bucket: string, zone: ALiYunZone, key?: string): {
|
||||
key: string | undefined;
|
||||
accessKey: string;
|
||||
policy: string;
|
||||
signature: string;
|
||||
uploadHost: string;
|
||||
bucket: string;
|
||||
};
|
||||
private getSignInfo;
|
||||
removeFile(srcBucket: string, zone: ALiYunZone, srcKey: string): Promise<OSS.DeleteResult>;
|
||||
isExistObject(srcBucket: string, zone: ALiYunZone, srcKey: string): Promise<boolean>;
|
||||
}
|
||||
|
|
@ -1 +1,188 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ALiYunInstance = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const ali_oss_1 = tslib_1.__importDefault(require("ali-oss"));
|
||||
function getEndpoint(RegionID) {
|
||||
return `oss-${RegionID}.aliyuncs.com`;
|
||||
}
|
||||
const ALiYun_ENDPOINT_LIST = {
|
||||
'cn-hangzhou': {
|
||||
ul: getEndpoint('cn-hangzhou'),
|
||||
},
|
||||
'cn-shanghai': {
|
||||
ul: getEndpoint('cn-shanghai'),
|
||||
},
|
||||
'cn-nanjing': {
|
||||
ul: getEndpoint('cn-nanjing'),
|
||||
},
|
||||
'cn-fuzhou': {
|
||||
ul: getEndpoint('cn-fuzhou'),
|
||||
},
|
||||
'cn-wuhan': {
|
||||
ul: getEndpoint('cn-wuhan'),
|
||||
},
|
||||
'cn-qingdao': {
|
||||
ul: getEndpoint('cn-qingdao'),
|
||||
},
|
||||
'cn-beijing': {
|
||||
ul: getEndpoint('cn-beijing'),
|
||||
},
|
||||
'cn-zhangjiakou': {
|
||||
ul: getEndpoint('cn-zhangjiakou'),
|
||||
},
|
||||
'cn-huhehaote': {
|
||||
ul: getEndpoint('cn-huhehaote'),
|
||||
},
|
||||
'cn-wulanchabu': {
|
||||
ul: getEndpoint('cn-wulanchabu'),
|
||||
},
|
||||
'cn-shenzhen': {
|
||||
ul: getEndpoint('cn-shenzhen'),
|
||||
},
|
||||
'cn-heyuan': {
|
||||
ul: getEndpoint('cn-heyuan'),
|
||||
},
|
||||
'cn-guangzhou': {
|
||||
ul: getEndpoint('cn-guangzhou'),
|
||||
},
|
||||
'cn-chengdu': {
|
||||
ul: getEndpoint('cn-chengdu'),
|
||||
},
|
||||
'cn-hongkong': {
|
||||
ul: getEndpoint('cn-hongkong'),
|
||||
},
|
||||
'us-west-1': {
|
||||
ul: getEndpoint('us-west-1'),
|
||||
},
|
||||
'us-east-1': {
|
||||
ul: getEndpoint('us-east-1'),
|
||||
},
|
||||
'ap-northeast-1': {
|
||||
ul: getEndpoint('ap-northeast-1'),
|
||||
},
|
||||
'ap-northeast-2': {
|
||||
ul: getEndpoint('ap-northeast-2'),
|
||||
},
|
||||
'ap-southeast-1': {
|
||||
ul: getEndpoint('ap-southeast-1'),
|
||||
},
|
||||
'ap-southeast-2': {
|
||||
ul: getEndpoint('ap-southeast-2'),
|
||||
},
|
||||
'ap-southeast-3': {
|
||||
ul: getEndpoint('ap-southeast-3'),
|
||||
},
|
||||
'ap-southeast-5': {
|
||||
ul: getEndpoint('ap-southeast-5'),
|
||||
},
|
||||
'ap-southeast-6': {
|
||||
ul: getEndpoint('ap-southeast-6'),
|
||||
},
|
||||
'ap-southeast-7': {
|
||||
ul: getEndpoint('ap-southeast-7'),
|
||||
},
|
||||
'ap-south-1': {
|
||||
ul: getEndpoint('ap-south-1'),
|
||||
},
|
||||
'eu-central-1': {
|
||||
ul: getEndpoint('eu-central-1'),
|
||||
},
|
||||
'eu-west-1': {
|
||||
ul: getEndpoint('eu-west-1'),
|
||||
},
|
||||
'me-east-1': {
|
||||
ul: getEndpoint('me-east-1'),
|
||||
},
|
||||
'rg-china-mainland': {
|
||||
ul: getEndpoint('rg-china-mainland'),
|
||||
},
|
||||
};
|
||||
class ALiYunInstance {
|
||||
accessKey;
|
||||
secretKey;
|
||||
constructor(accessKey, secretKey) {
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
getUploadInfo(bucket, zone, key) {
|
||||
try {
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
accessKey: this.accessKey,
|
||||
policy: signInfo.policy,
|
||||
signature: signInfo.signature,
|
||||
uploadHost: `https://${bucket}.${ALiYun_ENDPOINT_LIST[zone].ul}`,
|
||||
bucket,
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
//https://help.aliyun.com/zh/oss/user-guide/form-upload?spm=a2c4g.11186623.0.0.22147f6b1UaGxF#6ee9b6b0be6on
|
||||
getSignInfo(bucket) {
|
||||
const client = new ali_oss_1.default({
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
bucket: bucket,
|
||||
});
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1);
|
||||
const policy = {
|
||||
expiration: now.toISOString(),
|
||||
conditions: [
|
||||
// 设置上传文件的大小限制。
|
||||
['content-length-range', 0, 1048576000],
|
||||
// 限制可上传的Bucket。
|
||||
{ bucket: bucket },
|
||||
],
|
||||
};
|
||||
const data = client.calculatePostSignature(policy);
|
||||
return {
|
||||
policy: data.policy,
|
||||
signature: data.Signature,
|
||||
ossAccessKeyId: data.OSSAccessKeyId,
|
||||
};
|
||||
}
|
||||
async removeFile(srcBucket, zone, srcKey) {
|
||||
const client = new ali_oss_1.default({
|
||||
// oss-cn-hangzhou填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||||
region: `oss-${zone}`,
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
// 填写Bucket名称。
|
||||
bucket: srcBucket,
|
||||
});
|
||||
try {
|
||||
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
|
||||
const result = await client.delete(srcKey);
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async isExistObject(srcBucket, zone, srcKey) {
|
||||
const client = new ali_oss_1.default({
|
||||
// yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||||
region: `oss-${zone}`,
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
bucket: srcBucket,
|
||||
});
|
||||
let result;
|
||||
try {
|
||||
result = await client.head(srcKey);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.ALiYunInstance = ALiYunInstance;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
/// <reference types="node" />
|
||||
/// <reference types="node" />
|
||||
import { BinaryToTextEncoding } from 'crypto';
|
||||
import { CTYunZone, ReqOptionProps } from '../../types/CTYun';
|
||||
export declare class CTYunInstance {
|
||||
private accessKey;
|
||||
|
|
@ -22,16 +19,10 @@ export declare class CTYunInstance {
|
|||
private hmacSha1;
|
||||
private urlSafeBase64Encode;
|
||||
removeFile(bucket: string, zone: CTYunZone, key: string): Promise<void>;
|
||||
isExistObject(srcBucket: string, zone: CTYunZone, srcKey: string): Promise<boolean>;
|
||||
getAuthorization(reqOptions: ReqOptionProps, zone: CTYunZone): string;
|
||||
signatureFn(reqOptions: ReqOptionProps, zone: CTYunZone): string | Buffer;
|
||||
stringToSign(reqOptions: ReqOptionProps, zone: CTYunZone): string;
|
||||
signedHeaders(headers: Record<string, string>): string;
|
||||
canonicalString(path: string, method: string, headers: Record<string, string>): string;
|
||||
canonicalHeaders(headers: Record<string, string>): string;
|
||||
canonicalHeaderValues(values: string): string;
|
||||
getSigningKey(date: string, zone: CTYunZone): string | Buffer;
|
||||
hmacSha256(key: string | Buffer, content: string | Buffer, digest?: BinaryToTextEncoding, fn?: string): string | Buffer;
|
||||
each(object: any, iterFunction: (key: any, item: any) => any): void;
|
||||
arrayEach(array: any, iterFunction: (item: any, key: any) => any): void;
|
||||
isSignableHeader(key: string): boolean;
|
||||
private getSignatureKey;
|
||||
private buildCanonicalRequest;
|
||||
private buildCanonicalQueryString;
|
||||
private calculatePayloadHash;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,16 @@ const CTYun_ENDPOINT_LIST = {
|
|||
};
|
||||
const serviceName = 's3';
|
||||
const v4Identifier = 'aws4_request';
|
||||
const expiresHeader = "presigned-expires";
|
||||
const unsignableHeaders = ["authorization", "x-ctyun-data-location", "content-length", "user-agent", expiresHeader, "expect", "x-amzn-trace-id"];
|
||||
const expiresHeader = 'presigned-expires';
|
||||
const unsignableHeaders = [
|
||||
'authorization',
|
||||
'x-ctyun-data-location',
|
||||
'content-length',
|
||||
'user-agent',
|
||||
expiresHeader,
|
||||
'expect',
|
||||
'x-amzn-trace-id',
|
||||
];
|
||||
class CTYunInstance {
|
||||
accessKey;
|
||||
secretKey;
|
||||
|
|
@ -59,7 +67,6 @@ class CTYunInstance {
|
|||
}
|
||||
getUploadInfo(bucket, zone, key) {
|
||||
try {
|
||||
// const uploadToken = this.getToken(zone, bucket, actions);
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
|
|
@ -77,30 +84,30 @@ class CTYunInstance {
|
|||
getSignInfo(bucket) {
|
||||
// 对于policy里的expiration,我在天翼云的文档里没有找到具体的说明,但是这个字段不填入就会请求失败
|
||||
// 设置一个明天过期的时间
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1);
|
||||
const tomorrow = now.toISOString();
|
||||
const expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 1);
|
||||
const policy = {
|
||||
Version: "2012-10-17",
|
||||
Statement: [{
|
||||
Effect: "Allow",
|
||||
Action: ["oos:*"],
|
||||
Resource: `arn:ctyun:oos:::${bucket} /*`
|
||||
}],
|
||||
expiration: tomorrow,
|
||||
conditions: [{
|
||||
expiration: expiration.toISOString(),
|
||||
conditions: [
|
||||
{
|
||||
bucket: bucket,
|
||||
}, [
|
||||
"starts-with",
|
||||
"$key",
|
||||
"extraFile",
|
||||
]]
|
||||
},
|
||||
['starts-with', '$key', 'extraFile'],
|
||||
],
|
||||
Version: '2012-10-17',
|
||||
Statement: [
|
||||
{
|
||||
Effect: 'Allow',
|
||||
Action: ['oos:*'],
|
||||
Resource: `arn:ctyun:oos:::${bucket} /*`,
|
||||
},
|
||||
],
|
||||
};
|
||||
const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy));
|
||||
const signature = this.hmacSha1(encodePolicy, this.secretKey);
|
||||
return {
|
||||
encodePolicy,
|
||||
signature
|
||||
signature,
|
||||
};
|
||||
}
|
||||
base64ToUrlSafe(v) {
|
||||
|
|
@ -115,147 +122,196 @@ class CTYunInstance {
|
|||
const encoded = Buffer.from(jsonFlags).toString('base64');
|
||||
return this.base64ToUrlSafe(encoded);
|
||||
}
|
||||
// 当初就不应该封装天翼云,文档不全,找技术要签名代码还得自己去看源码,恶心
|
||||
// 下面的代码是根据天翼云生成签名源码改动得来 oos-js-sdk-6.2.js
|
||||
async removeFile(bucket, zone, key) {
|
||||
const path = `/${bucket}/${key}`;
|
||||
const host = `${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const path = `/${key}`;
|
||||
const host = `${bucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const url = `https://${host}${path}`;
|
||||
const date = new Date().toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:\-]|\.\d{3}/g, "");
|
||||
const payload = '';
|
||||
const method = 'DELETE';
|
||||
const service = 's3';
|
||||
const date = new Date()
|
||||
.toISOString()
|
||||
.replace(/\.\d{3}Z$/, 'Z')
|
||||
.replace(/[:\-]|\.\d{3}/g, '');
|
||||
const headers = {
|
||||
"Content-Type": "application/octet-stream; charset=UTF-8",
|
||||
"Host": "oos-hbwh.ctyunapi.cn",
|
||||
"X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD",
|
||||
"X-Amz-User-Agent": "aws-sdk-js/2.296.0 callback"
|
||||
'Content-Type': 'application/xml; charset=utf-8',
|
||||
Host: host,
|
||||
'x-amz-content-sha256': this.calculatePayloadHash(payload),
|
||||
'x-amz-date': date,
|
||||
};
|
||||
headers["X-Amz-Date"] = date;
|
||||
const reqOptions = {
|
||||
path,
|
||||
host,
|
||||
date,
|
||||
headers,
|
||||
method: "DELETE",
|
||||
method: method,
|
||||
payload: payload,
|
||||
host,
|
||||
path,
|
||||
queryParameters: {},
|
||||
service,
|
||||
date,
|
||||
};
|
||||
const authorization = this.getAuthorization(reqOptions, zone);
|
||||
headers['Authorization'] = authorization;
|
||||
let response;
|
||||
try {
|
||||
await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
throw new Exception_1.OakNetworkException();
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return;
|
||||
}
|
||||
const text = await response.text();
|
||||
throw new Exception_1.OakExternalException('ctyun', response.status.toString(), text, {
|
||||
status: response.status,
|
||||
});
|
||||
}
|
||||
async isExistObject(srcBucket, zone, srcKey) {
|
||||
const path = `/${srcKey}`;
|
||||
const host = `${srcBucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const url = `https://${host}${path}`;
|
||||
const payload = '';
|
||||
const method = 'GET';
|
||||
const service = 's3';
|
||||
const date = new Date()
|
||||
.toISOString()
|
||||
.replace(/\.\d{3}Z$/, 'Z')
|
||||
.replace(/[:\-]|\.\d{3}/g, '');
|
||||
const headers = {
|
||||
Host: host,
|
||||
'x-amz-content-sha256': this.calculatePayloadHash(payload),
|
||||
'x-amz-date': date,
|
||||
};
|
||||
const reqOptions = {
|
||||
headers,
|
||||
method: method,
|
||||
payload: payload,
|
||||
host,
|
||||
path,
|
||||
queryParameters: {},
|
||||
service,
|
||||
date,
|
||||
};
|
||||
const authorization = this.getAuthorization(reqOptions, zone);
|
||||
let response;
|
||||
try {
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
throw new Exception_1.OakNetworkException();
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return true;
|
||||
}
|
||||
const text = await response.text();
|
||||
if (response.status === 404 && text.includes('NoSuchKey')) {
|
||||
return false;
|
||||
}
|
||||
throw new Exception_1.OakExternalException('ctyun', response.status.toString(), text, {
|
||||
status: response.status,
|
||||
});
|
||||
}
|
||||
getAuthorization(reqOptions, zone) {
|
||||
const { headers, date } = reqOptions;
|
||||
const parts = [];
|
||||
const credString = [date.substring(0, 8), zone, serviceName, v4Identifier].join("/");
|
||||
parts.push("AWS4-HMAC-SHA256" + " Credential=" + this.accessKey + "/" + credString);
|
||||
parts.push("SignedHeaders=" + this.signedHeaders(headers));
|
||||
parts.push("Signature=" + this.signatureFn(reqOptions, zone));
|
||||
return parts.join(", ");
|
||||
const { headers, host, path, method, payload, queryParameters, service, date, } = reqOptions;
|
||||
const payloadHash = this.calculatePayloadHash(payload);
|
||||
// Step 2: Build canonical request
|
||||
const { canonicalRequest, signedHeaders } = this.buildCanonicalRequest(method, path, queryParameters, headers, payloadHash);
|
||||
// Step 3: Calculate canonical request hash
|
||||
const canonicalRequestHash = crypto_1.default
|
||||
.createHash('sha256')
|
||||
.update(canonicalRequest)
|
||||
.digest('hex');
|
||||
// Step 4: Build string to sign
|
||||
const dateStamp = date.substr(0, 8);
|
||||
const credentialScope = `${dateStamp}/${zone}/${service}/aws4_request`;
|
||||
const stringToSign = [
|
||||
'AWS4-HMAC-SHA256',
|
||||
date,
|
||||
credentialScope,
|
||||
canonicalRequestHash,
|
||||
].join('\n');
|
||||
// Step 5: Get signing key
|
||||
const signingKey = this.getSignatureKey(dateStamp, zone, service);
|
||||
// Step 6: Calculate signature
|
||||
const signature = crypto_1.default
|
||||
.createHmac('sha256', signingKey)
|
||||
.update(stringToSign)
|
||||
.digest('hex');
|
||||
const authorizationHeader = [
|
||||
'AWS4-HMAC-SHA256 Credential=' +
|
||||
this.accessKey +
|
||||
'/' +
|
||||
credentialScope,
|
||||
'SignedHeaders=' + signedHeaders,
|
||||
'Signature=' + signature,
|
||||
].join(', ');
|
||||
return authorizationHeader;
|
||||
}
|
||||
signatureFn(reqOptions, zone) {
|
||||
const { date } = reqOptions;
|
||||
var signingKey = this.getSigningKey(date.substring(0, 8), zone);
|
||||
return this.hmacSha256(signingKey, this.stringToSign(reqOptions, zone), "hex");
|
||||
getSignatureKey(dateStamp, zone, service) {
|
||||
const kDate = crypto_1.default
|
||||
.createHmac('sha256', `AWS4${this.secretKey}`)
|
||||
.update(dateStamp)
|
||||
.digest();
|
||||
const kRegion = crypto_1.default
|
||||
.createHmac('sha256', kDate)
|
||||
.update(zone)
|
||||
.digest();
|
||||
const kService = crypto_1.default
|
||||
.createHmac('sha256', kRegion)
|
||||
.update(service)
|
||||
.digest();
|
||||
const kSigning = crypto_1.default
|
||||
.createHmac('sha256', kService)
|
||||
.update('aws4_request')
|
||||
.digest();
|
||||
return kSigning;
|
||||
}
|
||||
stringToSign(reqOptions, zone) {
|
||||
const { date, path, method, headers } = reqOptions;
|
||||
var parts = [];
|
||||
parts.push("AWS4-HMAC-SHA256");
|
||||
parts.push(date);
|
||||
parts.push([date.substring(0, 8), zone, serviceName, v4Identifier].join("/"));
|
||||
const canonicalStr = this.canonicalString(path, method, headers);
|
||||
const buffer = Buffer.from(canonicalStr);
|
||||
const encodeStr = crypto_1.default.createHash('sha256').update(buffer).digest('hex');
|
||||
parts.push(encodeStr);
|
||||
return parts.join("\n");
|
||||
buildCanonicalRequest(method, path, queryParameters, headers, payloadHash) {
|
||||
const canonicalQuerystring = this.buildCanonicalQueryString(queryParameters);
|
||||
const canonicalHeaders = Object.keys(headers)
|
||||
.sort()
|
||||
.map((key) => `${key.toLowerCase()}:${headers[key].trim()}`)
|
||||
.join('\n');
|
||||
const signedHeaders = Object.keys(headers)
|
||||
.sort()
|
||||
.map((key) => key.toLowerCase())
|
||||
.join(';');
|
||||
const canonicalRequest = [
|
||||
method,
|
||||
path,
|
||||
canonicalQuerystring,
|
||||
canonicalHeaders,
|
||||
'',
|
||||
signedHeaders,
|
||||
payloadHash,
|
||||
].join('\n');
|
||||
return {
|
||||
canonicalRequest,
|
||||
signedHeaders,
|
||||
};
|
||||
}
|
||||
signedHeaders(headers) {
|
||||
const keys = [];
|
||||
this.each(headers, (key) => {
|
||||
key = key.toLowerCase();
|
||||
if (this.isSignableHeader(key))
|
||||
keys.push(key);
|
||||
});
|
||||
return keys.sort().join(";");
|
||||
buildCanonicalQueryString(queryParameters) {
|
||||
const keys = Object.keys(queryParameters).sort();
|
||||
const canonicalQueryString = keys
|
||||
.map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParameters[key])}`)
|
||||
.join('&');
|
||||
return canonicalQueryString;
|
||||
}
|
||||
canonicalString(path, method, headers) {
|
||||
const parts = [];
|
||||
parts.push(method);
|
||||
parts.push(path);
|
||||
parts.push("");
|
||||
parts.push(this.canonicalHeaders(headers) + "\n");
|
||||
parts.push(this.signedHeaders(headers));
|
||||
parts.push("UNSIGNED-PAYLOAD");
|
||||
return parts.join("\n");
|
||||
}
|
||||
canonicalHeaders(headers) {
|
||||
const headerarr = [];
|
||||
this.each(headers, function (key, item) {
|
||||
headerarr.push([key, item]);
|
||||
});
|
||||
headerarr.sort(function (a, b) {
|
||||
return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1;
|
||||
});
|
||||
const parts = [];
|
||||
this.arrayEach(headerarr, (item) => {
|
||||
let key = item[0].toLowerCase();
|
||||
if (this.isSignableHeader(key)) {
|
||||
var value = item[1];
|
||||
if (typeof value === "undefined" || value === null || typeof value.toString !== "function") {
|
||||
throw new Error("Header " + key + " contains invalid value");
|
||||
}
|
||||
parts.push(key + ":" + this.canonicalHeaderValues(value.toString()));
|
||||
}
|
||||
});
|
||||
return parts.join("\n");
|
||||
}
|
||||
canonicalHeaderValues(values) {
|
||||
return values.replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
|
||||
}
|
||||
getSigningKey(date, zone) {
|
||||
const kDate = this.hmacSha256("AWS4" + this.secretKey, date);
|
||||
const kRegion = this.hmacSha256(kDate, zone);
|
||||
const kService = this.hmacSha256(kRegion, serviceName);
|
||||
var signingKey = this.hmacSha256(kService, v4Identifier);
|
||||
return signingKey;
|
||||
}
|
||||
hmacSha256(key, content, digest, fn) {
|
||||
if (!fn)
|
||||
fn = "sha256";
|
||||
if (typeof content === "string")
|
||||
content = Buffer.from(content);
|
||||
if (!digest) {
|
||||
return crypto_1.default.createHmac(fn, key).update(content).digest();
|
||||
}
|
||||
return crypto_1.default.createHmac(fn, key).update(content).digest(digest);
|
||||
}
|
||||
each(object, iterFunction) {
|
||||
for (let key in object) {
|
||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||
const ret = iterFunction.call(this, key, object[key]);
|
||||
if (Object.keys(ret).length === 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
arrayEach(array, iterFunction) {
|
||||
for (let idx in array) {
|
||||
if (Object.prototype.hasOwnProperty.call(array, idx)) {
|
||||
const ret = iterFunction.call(this, array[idx], parseInt(idx, 10));
|
||||
if (Object.keys(ret).length === 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
isSignableHeader(key) {
|
||||
if (key.toLowerCase().indexOf("x-amz-") === 0)
|
||||
return true;
|
||||
return unsignableHeaders.indexOf(key) < 0;
|
||||
calculatePayloadHash(payload) {
|
||||
const hash = crypto_1.default.createHash('sha256');
|
||||
hash.update(payload);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
}
|
||||
exports.CTYunInstance = CTYunInstance;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { TencentYunZone } from '../../types/TencentYun';
|
||||
export declare class TencentYunInstance {
|
||||
private accessKey;
|
||||
private secretKey;
|
||||
private COS;
|
||||
constructor(accessKey: string, secretKey: string);
|
||||
getUploadInfo(bucket: string, zone: TencentYunZone, key?: string): {
|
||||
key: string | undefined;
|
||||
accessKey: string;
|
||||
policy: string;
|
||||
signature: string;
|
||||
uploadHost: string;
|
||||
bucket: string;
|
||||
keyTime: string;
|
||||
algorithm: string;
|
||||
};
|
||||
private getSignInfo;
|
||||
removeFile(srcBucket: string, zone: TencentYunZone, srcKey: string): Promise<any>;
|
||||
isExistObject(srcBucket: string, zone: TencentYunZone, srcKey: string): Promise<boolean>;
|
||||
}
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TencentYunInstance = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const crypto_1 = tslib_1.__importDefault(require("crypto"));
|
||||
function getEndpoint(RegionID) {
|
||||
return `cos-${RegionID}.myqcloud.com`;
|
||||
}
|
||||
const Tencent_ENDPOINT_LIST = {
|
||||
'ap-beijing': {
|
||||
ul: getEndpoint('ap-nanjing'),
|
||||
},
|
||||
'ap-nanjing': {
|
||||
ul: getEndpoint('ap-nanjing'),
|
||||
},
|
||||
'ap-shanghai': {
|
||||
ul: getEndpoint('ap-shanghai'),
|
||||
},
|
||||
'ap-guangzhou': {
|
||||
ul: getEndpoint('ap-guangzhou'),
|
||||
},
|
||||
'ap-chengdu': {
|
||||
ul: getEndpoint('ap-chengdu'),
|
||||
},
|
||||
'ap-chongqing': {
|
||||
ul: getEndpoint('ap-chongqing'),
|
||||
},
|
||||
'ap-shenzhen-fsi': {
|
||||
ul: getEndpoint('ap-shenzhen-fsi'),
|
||||
},
|
||||
'ap-shanghai-fsi': {
|
||||
ul: getEndpoint('ap-shanghai-fsi'),
|
||||
},
|
||||
'ap-beijing-fsi': {
|
||||
ul: getEndpoint('ap-beijing-fsi'),
|
||||
},
|
||||
'ap-hongkong': {
|
||||
ul: getEndpoint('ap-hongkong'),
|
||||
},
|
||||
'ap-singapore': {
|
||||
ul: getEndpoint('ap-singapore'),
|
||||
},
|
||||
};
|
||||
// TODO 腾讯云代码未验证
|
||||
class TencentYunInstance {
|
||||
accessKey;
|
||||
secretKey;
|
||||
COS;
|
||||
constructor(accessKey, secretKey) {
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
this.COS = require('cos-wx-sdk-v5');
|
||||
}
|
||||
getUploadInfo(bucket, zone, key) {
|
||||
try {
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
accessKey: this.accessKey,
|
||||
policy: signInfo.policy,
|
||||
signature: signInfo.signature,
|
||||
uploadHost: `https://${bucket}.${Tencent_ENDPOINT_LIST[zone].ul}`,
|
||||
bucket,
|
||||
keyTime: signInfo.keyTime,
|
||||
algorithm: signInfo.algorithm,
|
||||
};
|
||||
}
|
||||
catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
//https://cloud.tencent.com/document/product/436/7778
|
||||
getSignInfo(bucket) {
|
||||
const expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 1);
|
||||
const keyTime = `${Math.floor(Date.now() / 1000)};${Math.floor(expiration.getTime() / 1000)}`;
|
||||
const algorithm = 'sha1';
|
||||
const policy = {
|
||||
expiration: expiration.toISOString(),
|
||||
conditions: [
|
||||
// 限制可上传的Bucket。
|
||||
{ bucket: bucket },
|
||||
['eq', '$x-cos-server-side-encryption', 'AES256'],
|
||||
{ 'q-sign-algorithm': algorithm },
|
||||
{ 'q-ak': this.accessKey },
|
||||
{ 'q-sign-time': keyTime },
|
||||
],
|
||||
};
|
||||
const encodedPolicy = Buffer.from(JSON.stringify(policy)).toString('base64');
|
||||
const signKey = crypto_1.default
|
||||
.createHmac('sha1', this.secretKey)
|
||||
.update(keyTime)
|
||||
.digest();
|
||||
const stringToSign = crypto_1.default
|
||||
.createHash('sha1')
|
||||
.update(encodedPolicy)
|
||||
.digest('hex');
|
||||
const signature = crypto_1.default
|
||||
.createHmac('sha1', signKey)
|
||||
.update(stringToSign)
|
||||
.digest('hex');
|
||||
return {
|
||||
policy: encodedPolicy,
|
||||
signature,
|
||||
keyTime,
|
||||
algorithm,
|
||||
};
|
||||
}
|
||||
async removeFile(srcBucket, zone, srcKey) {
|
||||
const client = new this.COS({
|
||||
SecretId: this.accessKey,
|
||||
SecretKey: this.secretKey,
|
||||
});
|
||||
try {
|
||||
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
|
||||
const result = await client.deleteObject({
|
||||
Bucket: srcBucket,
|
||||
Region: zone,
|
||||
Key: srcKey,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async isExistObject(srcBucket, zone, srcKey) {
|
||||
const client = new this.COS({
|
||||
SecretId: this.accessKey,
|
||||
SecretKey: this.secretKey,
|
||||
});
|
||||
let result;
|
||||
try {
|
||||
result = await client.headObject({
|
||||
Bucket: srcBucket,
|
||||
Region: zone,
|
||||
Key: srcKey,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.TencentYunInstance = TencentYunInstance;
|
||||
|
|
@ -191,7 +191,7 @@ class WechatMpInstance {
|
|||
const { type, media, filetype, filename } = options;
|
||||
const formData = new form_data_1.default();
|
||||
formData.append('media', media, {
|
||||
contentType: filetype,
|
||||
contentType: filetype, // 微信识别需要
|
||||
filename: filename, // 微信识别需要
|
||||
});
|
||||
const getLength = () => {
|
||||
|
|
@ -336,7 +336,7 @@ class WechatMpInstance {
|
|||
body: JSON.stringify({
|
||||
jump_wxa: jump_wxa,
|
||||
is_expire: true,
|
||||
expire_type: expireType,
|
||||
expire_type: expireType, //默认是零,到期失效的 scheme 码失效类型,失效时间类型:0,失效间隔天数类型:1
|
||||
expire_time: expiresAt,
|
||||
expire_interval: expireInterval,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -515,8 +515,8 @@ class WechatPublicInstance {
|
|||
const { type, media, description, filetype, filename, fileLength } = options;
|
||||
const formData = new form_data_1.default();
|
||||
formData.append('media', media, {
|
||||
contentType: filetype,
|
||||
filename: filename,
|
||||
contentType: filetype, // 微信识别需要
|
||||
filename: filename, // 微信识别需要
|
||||
knownLength: fileLength,
|
||||
});
|
||||
if (type === 'video') {
|
||||
|
|
@ -539,8 +539,8 @@ class WechatPublicInstance {
|
|||
const { media, filetype, filename, fileLength } = options;
|
||||
const formData = new form_data_1.default();
|
||||
formData.append('media', media, {
|
||||
contentType: filetype,
|
||||
filename: filename,
|
||||
contentType: filetype, // 微信识别需要
|
||||
filename: filename, // 微信识别需要
|
||||
knownLength: fileLength,
|
||||
});
|
||||
const headers = formData.getHeaders();
|
||||
|
|
@ -560,8 +560,8 @@ class WechatPublicInstance {
|
|||
const { type, media, filetype, filename, fileLength } = options;
|
||||
const formData = new form_data_1.default();
|
||||
formData.append('media', media, {
|
||||
contentType: filetype,
|
||||
filename: filename,
|
||||
contentType: filetype, // 微信识别需要
|
||||
filename: filename, // 微信识别需要
|
||||
knownLength: fileLength,
|
||||
});
|
||||
const headers = formData.getHeaders();
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export type ALiYunZone = 'cn-hangzhou' | 'cn-shanghai' | 'cn-nanjing' | 'cn-fuzhou' | 'cn-wuhan' | 'cn-qingdao' | 'cn-beijing' | 'cn-zhangjiakou' | 'cn-huhehaote' | 'cn-wulanchabu' | 'cn-shenzhen' | 'cn-heyuan' | 'cn-guangzhou' | 'cn-chengdu' | 'cn-hongkong' | 'us-west-1' | 'us-east-1' | 'ap-northeast-1' | 'ap-northeast-2' | 'ap-southeast-1' | 'ap-southeast-2' | 'ap-southeast-3' | 'ap-southeast-5' | 'ap-southeast-6' | 'ap-southeast-7' | 'ap-south-1' | 'eu-central-1' | 'eu-west-1' | 'me-east-1' | 'rg-china-mainland';
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
|
@ -4,10 +4,13 @@
|
|||
*/
|
||||
export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket';
|
||||
export type ReqOptionProps = {
|
||||
payload: string;
|
||||
path: string;
|
||||
host: string;
|
||||
date: string;
|
||||
headers: any;
|
||||
method: 'DELETE' | "GET" | "POST" | "PUT";
|
||||
method: 'DELETE' | 'GET' | 'POST' | 'PUT' | 'HEAD';
|
||||
queryParameters: Record<string, any>;
|
||||
service: string;
|
||||
date: string;
|
||||
};
|
||||
export type CTYunZone = 'hazz' | 'lnsy' | 'sccd' | 'xjwlmq' | 'gslz' | 'sdqd' | 'gzgy' | 'hbwh' | 'xzls' | 'ahwh' | 'gdsz' | 'jssz' | 'sh2';
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export type TencentYunZone = 'ap-beijing' | 'ap-nanjing' | 'ap-shanghai' | 'ap-guangzhou' | 'ap-chengdu' | 'ap-chongqing' | 'ap-shenzhen-fsi' | 'ap-shanghai-fsi' | 'ap-beijing-fsi' | 'ap-hongkong' | 'ap-singapore';
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
export * from './Wechat';
|
||||
export * from './Qiniu';
|
||||
export * from './CTYun';
|
||||
export * from './ALiYun';
|
||||
export * from './TencentYun';
|
||||
|
|
|
|||
|
|
@ -4,3 +4,5 @@ const tslib_1 = require("tslib");
|
|||
tslib_1.__exportStar(require("./Wechat"), exports);
|
||||
tslib_1.__exportStar(require("./Qiniu"), exports);
|
||||
tslib_1.__exportStar(require("./CTYun"), exports);
|
||||
tslib_1.__exportStar(require("./ALiYun"), exports);
|
||||
tslib_1.__exportStar(require("./TencentYun"), exports);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
},
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/ali-oss": "^6.16.11",
|
||||
"@types/node": "^20.6.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.4.0",
|
||||
|
|
@ -27,8 +28,10 @@
|
|||
"dependencies": {
|
||||
"@alicloud/dysmsapi20170525": "^2.0.24",
|
||||
"@alicloud/pop-core": "^1.7.12",
|
||||
"ali-oss": "^6.20.0",
|
||||
"aws-sdk": "^2.1499.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"cos-wx-sdk-v5": "^1.7.1",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"oak-domain": "file:../oak-domain",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.746",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { ALiYunInstance } from './service/ali/Ali';
|
||||
|
||||
class ALiYunSDK {
|
||||
aliMap: Record<string, ALiYunInstance>; //oss
|
||||
|
||||
constructor() {
|
||||
this.aliMap = {};
|
||||
}
|
||||
|
||||
getInstance(accessKey: string, accessSecret: string) {
|
||||
if (this.aliMap[accessKey]) {
|
||||
return this.aliMap[accessKey];
|
||||
}
|
||||
const instance = new ALiYunInstance(accessKey, accessSecret);
|
||||
Object.assign(this.aliMap, {
|
||||
[accessKey]: instance,
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
const SDK = new ALiYunSDK();
|
||||
export default SDK;
|
||||
|
||||
export { ALiYunInstance };
|
||||
|
||||
|
|
@ -1,16 +1,13 @@
|
|||
import { CTYunInstance } from './service/ctyun/CTYun';
|
||||
|
||||
class CTYunSDK {
|
||||
ctyunMap: Record<string, CTYunInstance>;
|
||||
ctyunMap: Record<string, CTYunInstance>; //oss
|
||||
|
||||
constructor() {
|
||||
this.ctyunMap = {};
|
||||
}
|
||||
|
||||
getInstance(
|
||||
accessKey: string,
|
||||
accessSecret: string,
|
||||
) {
|
||||
getInstance(accessKey: string, accessSecret: string) {
|
||||
if (this.ctyunMap[accessKey]) {
|
||||
return this.ctyunMap[accessKey];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { TencentYunInstance } from './service/tencent/Tencent';
|
||||
|
||||
class TencentYunSDK {
|
||||
tencentMap: Record<string, TencentYunInstance>; //oss
|
||||
|
||||
constructor() {
|
||||
this.tencentMap = {};
|
||||
}
|
||||
|
||||
getInstance(accessKey: string, accessSecret: string) {
|
||||
if (this.tencentMap[accessKey]) {
|
||||
return this.tencentMap[accessKey];
|
||||
}
|
||||
const instance = new TencentYunInstance(accessKey, accessSecret);
|
||||
Object.assign(this.tencentMap, {
|
||||
[accessKey]: instance,
|
||||
});
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
const SDK = new TencentYunSDK();
|
||||
export default SDK;
|
||||
|
||||
export { TencentYunInstance };
|
||||
|
||||
11
src/index.ts
11
src/index.ts
|
|
@ -6,19 +6,26 @@ import SmsSdk, {
|
|||
AliSmsInstance,
|
||||
CTYunSmsInstance,
|
||||
} from './SmsSdk';
|
||||
import CTYunSDk, { CTYunInstance } from './CTYunSDK';
|
||||
import CTYunSDK, { CTYunInstance } from './CTYunSDK';
|
||||
import ALiYunSDK, { ALiYunInstance } from './ALiYunSDK';
|
||||
import TencentYunSDK, { TencentYunInstance } from './TencentYunSDK';
|
||||
|
||||
export * from './service/amap/Amap';
|
||||
export {
|
||||
AmapSDK,
|
||||
QiniuSDK,
|
||||
WechatSDK,
|
||||
CTYunSDk,
|
||||
CTYunSDK,
|
||||
ALiYunSDK,
|
||||
TencentYunSDK,
|
||||
CTYunInstance,
|
||||
WechatMpInstance,
|
||||
WechatPublicInstance,
|
||||
WechatWebInstance,
|
||||
QiniuCloudInstance,
|
||||
ALiYunInstance,
|
||||
TencentYunInstance,
|
||||
|
||||
SmsSdk,
|
||||
TencentSmsInstance,
|
||||
AliSmsInstance,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,198 @@
|
|||
|
||||
import { ALiYunZone } from '../../types/ALiYun';
|
||||
import OSS from 'ali-oss';
|
||||
import {
|
||||
OakExternalException,
|
||||
OakNetworkException,
|
||||
} from 'oak-domain/lib/types/Exception';
|
||||
|
||||
function getEndpoint(RegionID: ALiYunZone) {
|
||||
return `oss-${RegionID}.aliyuncs.com`;
|
||||
}
|
||||
|
||||
const ALiYun_ENDPOINT_LIST: Record<ALiYunZone, { ul: string }> = {
|
||||
'cn-hangzhou': {
|
||||
ul: getEndpoint('cn-hangzhou'),
|
||||
},
|
||||
'cn-shanghai': {
|
||||
ul: getEndpoint('cn-shanghai'),
|
||||
},
|
||||
'cn-nanjing': {
|
||||
ul: getEndpoint('cn-nanjing'),
|
||||
},
|
||||
'cn-fuzhou': {
|
||||
ul: getEndpoint('cn-fuzhou'),
|
||||
},
|
||||
'cn-wuhan': {
|
||||
ul: getEndpoint('cn-wuhan'),
|
||||
},
|
||||
'cn-qingdao': {
|
||||
ul: getEndpoint('cn-qingdao'),
|
||||
},
|
||||
'cn-beijing': {
|
||||
ul: getEndpoint('cn-beijing'),
|
||||
},
|
||||
'cn-zhangjiakou': {
|
||||
ul: getEndpoint('cn-zhangjiakou'),
|
||||
},
|
||||
'cn-huhehaote': {
|
||||
ul: getEndpoint('cn-huhehaote'),
|
||||
},
|
||||
'cn-wulanchabu': {
|
||||
ul: getEndpoint('cn-wulanchabu'),
|
||||
},
|
||||
'cn-shenzhen': {
|
||||
ul: getEndpoint('cn-shenzhen'),
|
||||
},
|
||||
'cn-heyuan': {
|
||||
ul: getEndpoint('cn-heyuan'),
|
||||
},
|
||||
'cn-guangzhou': {
|
||||
ul: getEndpoint('cn-guangzhou'),
|
||||
},
|
||||
'cn-chengdu': {
|
||||
ul: getEndpoint('cn-chengdu'),
|
||||
},
|
||||
'cn-hongkong': {
|
||||
ul: getEndpoint('cn-hongkong'),
|
||||
},
|
||||
|
||||
'us-west-1': {
|
||||
ul: getEndpoint('us-west-1'),
|
||||
},
|
||||
'us-east-1': {
|
||||
ul: getEndpoint('us-east-1'),
|
||||
},
|
||||
'ap-northeast-1': {
|
||||
ul: getEndpoint('ap-northeast-1'),
|
||||
},
|
||||
'ap-northeast-2': {
|
||||
ul: getEndpoint('ap-northeast-2'),
|
||||
},
|
||||
'ap-southeast-1': {
|
||||
ul: getEndpoint('ap-southeast-1'),
|
||||
},
|
||||
'ap-southeast-2': {
|
||||
ul: getEndpoint('ap-southeast-2'),
|
||||
},
|
||||
'ap-southeast-3': {
|
||||
ul: getEndpoint('ap-southeast-3'),
|
||||
},
|
||||
'ap-southeast-5': {
|
||||
ul: getEndpoint('ap-southeast-5'),
|
||||
},
|
||||
'ap-southeast-6': {
|
||||
ul: getEndpoint('ap-southeast-6'),
|
||||
},
|
||||
'ap-southeast-7': {
|
||||
ul: getEndpoint('ap-southeast-7'),
|
||||
},
|
||||
'ap-south-1': {
|
||||
ul: getEndpoint('ap-south-1'),
|
||||
},
|
||||
'eu-central-1': {
|
||||
ul: getEndpoint('eu-central-1'),
|
||||
},
|
||||
'eu-west-1': {
|
||||
ul: getEndpoint('eu-west-1'),
|
||||
},
|
||||
'me-east-1': {
|
||||
ul: getEndpoint('me-east-1'),
|
||||
},
|
||||
'rg-china-mainland': {
|
||||
ul: getEndpoint('rg-china-mainland'),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
export class ALiYunInstance {
|
||||
private accessKey: string;
|
||||
private secretKey: string;
|
||||
|
||||
constructor(accessKey: string, secretKey: string) {
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
getUploadInfo(bucket: string, zone: ALiYunZone, key?: string) {
|
||||
try {
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
accessKey: this.accessKey,
|
||||
policy: signInfo.policy,
|
||||
signature: signInfo.signature,
|
||||
uploadHost: `https://${bucket}.${ALiYun_ENDPOINT_LIST[zone].ul}`,
|
||||
bucket,
|
||||
};
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
//https://help.aliyun.com/zh/oss/user-guide/form-upload?spm=a2c4g.11186623.0.0.22147f6b1UaGxF#6ee9b6b0be6on
|
||||
private getSignInfo(bucket: string) {
|
||||
const client = new OSS({
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
bucket: bucket,
|
||||
});
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1);
|
||||
const policy = {
|
||||
expiration: now.toISOString(),
|
||||
conditions: [
|
||||
// 设置上传文件的大小限制。
|
||||
['content-length-range', 0, 1048576000],
|
||||
// 限制可上传的Bucket。
|
||||
{ bucket: bucket },
|
||||
],
|
||||
};
|
||||
const data = client.calculatePostSignature(policy);
|
||||
|
||||
return {
|
||||
policy: data.policy,
|
||||
signature: data.Signature,
|
||||
ossAccessKeyId: data.OSSAccessKeyId,
|
||||
};
|
||||
}
|
||||
|
||||
async removeFile(srcBucket: string, zone: ALiYunZone, srcKey: string) {
|
||||
const client = new OSS({
|
||||
// oss-cn-hangzhou填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||||
region: `oss-${zone}`,
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
// 填写Bucket名称。
|
||||
bucket: srcBucket,
|
||||
});
|
||||
|
||||
try {
|
||||
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
|
||||
const result = await client.delete(srcKey);
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async isExistObject(srcBucket: string, zone: ALiYunZone, srcKey: string) {
|
||||
const client = new OSS({
|
||||
// yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
|
||||
region: `oss-${zone}`,
|
||||
accessKeyId: this.accessKey,
|
||||
accessKeySecret: this.secretKey,
|
||||
bucket: srcBucket,
|
||||
});
|
||||
let result: OSS.HeadObjectResult;
|
||||
try {
|
||||
result = await client.head(srcKey);
|
||||
return true
|
||||
} catch (error: any) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,8 @@ import {
|
|||
OakExternalException,
|
||||
OakNetworkException,
|
||||
} from 'oak-domain/lib/types/Exception';
|
||||
const CTYun_ENDPOINT_LIST = {
|
||||
|
||||
const CTYun_ENDPOINT_LIST: Record<CTYunZone, { ul: string }> = {
|
||||
hazz: {
|
||||
ul: 'oos-hazz.ctyunapi.cn',
|
||||
},
|
||||
|
|
@ -47,24 +48,30 @@ const CTYun_ENDPOINT_LIST = {
|
|||
},
|
||||
};
|
||||
|
||||
|
||||
const serviceName = 's3';
|
||||
const v4Identifier = 'aws4_request';
|
||||
const expiresHeader = "presigned-expires";
|
||||
const unsignableHeaders = ["authorization", "x-ctyun-data-location", "content-length", "user-agent", expiresHeader, "expect", "x-amzn-trace-id"];
|
||||
const expiresHeader = 'presigned-expires';
|
||||
const unsignableHeaders = [
|
||||
'authorization',
|
||||
'x-ctyun-data-location',
|
||||
'content-length',
|
||||
'user-agent',
|
||||
expiresHeader,
|
||||
'expect',
|
||||
'x-amzn-trace-id',
|
||||
];
|
||||
|
||||
export class CTYunInstance {
|
||||
private accessKey: string;
|
||||
private secretKey: string;
|
||||
|
||||
constructor(accessKey: string, secretKey: string,) {
|
||||
constructor(accessKey: string, secretKey: string) {
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
}
|
||||
|
||||
getUploadInfo(bucket: string, zone: CTYunZone, key?: string) {
|
||||
try {
|
||||
// const uploadToken = this.getToken(zone, bucket, actions);
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
|
|
@ -82,30 +89,30 @@ export class CTYunInstance {
|
|||
getSignInfo(bucket: string) {
|
||||
// 对于policy里的expiration,我在天翼云的文档里没有找到具体的说明,但是这个字段不填入就会请求失败
|
||||
// 设置一个明天过期的时间
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1);
|
||||
const tomorrow = now.toISOString();
|
||||
const expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 1);
|
||||
const policy = {
|
||||
Version: "2012-10-17",
|
||||
Statement: [{
|
||||
Effect: "Allow",
|
||||
Action: ["oos:*"],
|
||||
Resource: `arn:ctyun:oos:::${bucket} /*`
|
||||
}],
|
||||
expiration: tomorrow,
|
||||
conditions: [{
|
||||
bucket: bucket,
|
||||
}, [
|
||||
"starts-with",
|
||||
"$key",
|
||||
"extraFile",
|
||||
]]
|
||||
}
|
||||
expiration: expiration.toISOString(),
|
||||
conditions: [
|
||||
{
|
||||
bucket: bucket,
|
||||
},
|
||||
['starts-with', '$key', 'extraFile'],
|
||||
],
|
||||
Version: '2012-10-17',
|
||||
Statement: [
|
||||
{
|
||||
Effect: 'Allow',
|
||||
Action: ['oos:*'],
|
||||
Resource: `arn:ctyun:oos:::${bucket} /*`,
|
||||
},
|
||||
],
|
||||
};
|
||||
const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy));
|
||||
const signature = this.hmacSha1(encodePolicy, this.secretKey);
|
||||
return {
|
||||
encodePolicy,
|
||||
signature
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -124,152 +131,252 @@ export class CTYunInstance {
|
|||
return this.base64ToUrlSafe(encoded);
|
||||
}
|
||||
|
||||
|
||||
// 当初就不应该封装天翼云,文档不全,找技术要签名代码还得自己去看源码,恶心
|
||||
// 下面的代码是根据天翼云生成签名源码改动得来 oos-js-sdk-6.2.js
|
||||
async removeFile(bucket: string, zone: CTYunZone, key: string) {
|
||||
const path = `/${bucket}/${key}`;
|
||||
const host = `${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const path = `/${key}`;
|
||||
const host = `${bucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const url = `https://${host}${path}`;
|
||||
const date = new Date().toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[:\-]|\.\d{3}/g, "");
|
||||
const payload = '';
|
||||
const method = 'DELETE';
|
||||
const service = 's3';
|
||||
const date = new Date()
|
||||
.toISOString()
|
||||
.replace(/\.\d{3}Z$/, 'Z')
|
||||
.replace(/[:\-]|\.\d{3}/g, '');
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/octet-stream; charset=UTF-8",
|
||||
"Host": "oos-hbwh.ctyunapi.cn",
|
||||
"X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD",
|
||||
"X-Amz-User-Agent": "aws-sdk-js/2.296.0 callback"
|
||||
}
|
||||
headers["X-Amz-Date"] = date;
|
||||
'Content-Type': 'application/xml; charset=utf-8',
|
||||
Host: host,
|
||||
'x-amz-content-sha256': this.calculatePayloadHash(payload),
|
||||
'x-amz-date': date,
|
||||
};
|
||||
|
||||
const reqOptions: ReqOptionProps = {
|
||||
path,
|
||||
host,
|
||||
date,
|
||||
headers,
|
||||
method: "DELETE",
|
||||
}
|
||||
method: method,
|
||||
payload: payload,
|
||||
host,
|
||||
path,
|
||||
queryParameters: {},
|
||||
service,
|
||||
date,
|
||||
};
|
||||
|
||||
const authorization = this.getAuthorization(reqOptions, zone);
|
||||
headers['Authorization'] = authorization;
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
await fetch(url, {
|
||||
method: 'DELETE',
|
||||
headers,
|
||||
})
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OakNetworkException();
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return;
|
||||
}
|
||||
const text = await response.text();
|
||||
throw new OakExternalException(
|
||||
'ctyun',
|
||||
response.status.toString(),
|
||||
text,
|
||||
{
|
||||
status: response.status,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async isExistObject(srcBucket: string, zone: CTYunZone, srcKey: string) {
|
||||
const path = `/${srcKey}`;
|
||||
const host = `${srcBucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
|
||||
const url = `https://${host}${path}`;
|
||||
const payload = '';
|
||||
const method = 'GET';
|
||||
const service = 's3';
|
||||
const date = new Date()
|
||||
.toISOString()
|
||||
.replace(/\.\d{3}Z$/, 'Z')
|
||||
.replace(/[:\-]|\.\d{3}/g, '');
|
||||
const headers: Record<string, string> = {
|
||||
Host: host,
|
||||
'x-amz-content-sha256': this.calculatePayloadHash(payload),
|
||||
'x-amz-date': date,
|
||||
};
|
||||
|
||||
const reqOptions: ReqOptionProps = {
|
||||
headers,
|
||||
method: method,
|
||||
payload: payload,
|
||||
host,
|
||||
path,
|
||||
queryParameters: {},
|
||||
service,
|
||||
date,
|
||||
};
|
||||
|
||||
const authorization = this.getAuthorization(reqOptions, zone);
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: authorization,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
throw new OakNetworkException();
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return true;
|
||||
}
|
||||
const text = await response.text();
|
||||
if (response.status === 404 && text.includes('NoSuchKey')) {
|
||||
return false;
|
||||
}
|
||||
throw new OakExternalException(
|
||||
'ctyun',
|
||||
response.status.toString(),
|
||||
text,
|
||||
{
|
||||
status: response.status,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getAuthorization(reqOptions: ReqOptionProps, zone: CTYunZone) {
|
||||
const { headers, date } = reqOptions;
|
||||
const parts = [];
|
||||
const credString = [date.substring(0, 8), zone, serviceName, v4Identifier].join("/");
|
||||
parts.push("AWS4-HMAC-SHA256" + " Credential=" + this.accessKey + "/" + credString);
|
||||
parts.push("SignedHeaders=" + this.signedHeaders(headers));
|
||||
parts.push("Signature=" + this.signatureFn(reqOptions, zone));
|
||||
return parts.join(", ");
|
||||
const {
|
||||
headers,
|
||||
host,
|
||||
path,
|
||||
method,
|
||||
payload,
|
||||
queryParameters,
|
||||
service,
|
||||
date,
|
||||
} = reqOptions;
|
||||
|
||||
const payloadHash = this.calculatePayloadHash(payload);
|
||||
|
||||
// Step 2: Build canonical request
|
||||
const { canonicalRequest, signedHeaders } = this.buildCanonicalRequest(
|
||||
method,
|
||||
path,
|
||||
queryParameters,
|
||||
headers,
|
||||
payloadHash
|
||||
);
|
||||
|
||||
// Step 3: Calculate canonical request hash
|
||||
const canonicalRequestHash = crypto
|
||||
.createHash('sha256')
|
||||
.update(canonicalRequest)
|
||||
.digest('hex');
|
||||
|
||||
// Step 4: Build string to sign
|
||||
|
||||
const dateStamp = date.substr(0, 8);
|
||||
const credentialScope = `${dateStamp}/${zone}/${service}/aws4_request`;
|
||||
const stringToSign = [
|
||||
'AWS4-HMAC-SHA256',
|
||||
date,
|
||||
credentialScope,
|
||||
canonicalRequestHash,
|
||||
].join('\n');
|
||||
|
||||
// Step 5: Get signing key
|
||||
const signingKey = this.getSignatureKey(dateStamp, zone, service);
|
||||
|
||||
// Step 6: Calculate signature
|
||||
const signature = crypto
|
||||
.createHmac('sha256', signingKey)
|
||||
.update(stringToSign)
|
||||
.digest('hex');
|
||||
|
||||
const authorizationHeader = [
|
||||
'AWS4-HMAC-SHA256 Credential=' +
|
||||
this.accessKey +
|
||||
'/' +
|
||||
credentialScope,
|
||||
'SignedHeaders=' + signedHeaders,
|
||||
'Signature=' + signature,
|
||||
].join(', ');
|
||||
|
||||
return authorizationHeader;
|
||||
}
|
||||
|
||||
signatureFn(reqOptions: ReqOptionProps, zone: CTYunZone) {
|
||||
const { date } = reqOptions;
|
||||
var signingKey = this.getSigningKey(date.substring(0, 8), zone);
|
||||
return this.hmacSha256(signingKey, this.stringToSign(reqOptions, zone), "hex");
|
||||
private getSignatureKey(dateStamp: string, zone: string, service: string) {
|
||||
const kDate = crypto
|
||||
.createHmac('sha256', `AWS4${this.secretKey}`)
|
||||
.update(dateStamp)
|
||||
.digest();
|
||||
const kRegion = crypto
|
||||
.createHmac('sha256', kDate)
|
||||
.update(zone)
|
||||
.digest();
|
||||
const kService = crypto
|
||||
.createHmac('sha256', kRegion)
|
||||
.update(service)
|
||||
.digest();
|
||||
const kSigning = crypto
|
||||
.createHmac('sha256', kService)
|
||||
.update('aws4_request')
|
||||
.digest();
|
||||
return kSigning;
|
||||
}
|
||||
|
||||
stringToSign(reqOptions: ReqOptionProps, zone: CTYunZone) {
|
||||
const { date, path, method, headers } = reqOptions;
|
||||
var parts = [];
|
||||
parts.push("AWS4-HMAC-SHA256");
|
||||
parts.push(date);
|
||||
parts.push([date.substring(0, 8), zone, serviceName, v4Identifier].join("/"));
|
||||
const canonicalStr = this.canonicalString(path, method, headers);
|
||||
const buffer = Buffer.from(canonicalStr);
|
||||
const encodeStr = crypto.createHash('sha256').update(buffer).digest('hex');
|
||||
parts.push(encodeStr);
|
||||
return parts.join("\n");
|
||||
private buildCanonicalRequest(
|
||||
method: ReqOptionProps['method'],
|
||||
path: string,
|
||||
queryParameters: Record<string, string>,
|
||||
headers: Record<string, string>,
|
||||
payloadHash: string
|
||||
) {
|
||||
const canonicalQuerystring =
|
||||
this.buildCanonicalQueryString(queryParameters);
|
||||
const canonicalHeaders = Object.keys(headers)
|
||||
.sort()
|
||||
.map((key) => `${key.toLowerCase()}:${headers[key].trim()}`)
|
||||
.join('\n');
|
||||
const signedHeaders = Object.keys(headers)
|
||||
.sort()
|
||||
.map((key) => key.toLowerCase())
|
||||
.join(';');
|
||||
|
||||
const canonicalRequest = [
|
||||
method,
|
||||
path,
|
||||
canonicalQuerystring,
|
||||
canonicalHeaders,
|
||||
'',
|
||||
signedHeaders,
|
||||
payloadHash,
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
canonicalRequest,
|
||||
signedHeaders,
|
||||
};
|
||||
}
|
||||
|
||||
signedHeaders(headers: Record<string, string>) {
|
||||
const keys: string[] = [];
|
||||
this.each(headers, (key) => {
|
||||
key = key.toLowerCase();
|
||||
if (this.isSignableHeader(key)) keys.push(key);
|
||||
});
|
||||
return keys.sort().join(";");
|
||||
private buildCanonicalQueryString(queryParameters: Record<string, string>) {
|
||||
const keys = Object.keys(queryParameters).sort();
|
||||
const canonicalQueryString = keys
|
||||
.map(
|
||||
(key) =>
|
||||
`${encodeURIComponent(key)}=${encodeURIComponent(
|
||||
queryParameters[key]
|
||||
)}`
|
||||
)
|
||||
.join('&');
|
||||
return canonicalQueryString;
|
||||
}
|
||||
|
||||
canonicalString(path: string, method: string, headers: Record<string, string>) {
|
||||
const parts = [];
|
||||
parts.push(method);
|
||||
parts.push(path);
|
||||
parts.push("");
|
||||
parts.push(this.canonicalHeaders(headers) + "\n");
|
||||
parts.push(this.signedHeaders(headers));
|
||||
parts.push("UNSIGNED-PAYLOAD");
|
||||
return parts.join("\n");
|
||||
private calculatePayloadHash(payload: string) {
|
||||
const hash = crypto.createHash('sha256');
|
||||
hash.update(payload);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
canonicalHeaders(headers: Record<string, string>) {
|
||||
const headerarr:string[][] = [];
|
||||
this.each(headers, function (key, item) {
|
||||
headerarr.push([key, item]);
|
||||
});
|
||||
headerarr.sort(function (a, b) {
|
||||
return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1;
|
||||
});
|
||||
const parts: string[] = [];
|
||||
this.arrayEach(headerarr, (item) => {
|
||||
let key = item[0].toLowerCase();
|
||||
if (this.isSignableHeader(key)) {
|
||||
var value = item[1];
|
||||
if (typeof value === "undefined" || value === null || typeof value.toString !== "function") {
|
||||
throw new Error("Header " + key + " contains invalid value");
|
||||
}
|
||||
parts.push(key + ":" + this.canonicalHeaderValues(value.toString()));
|
||||
}
|
||||
});
|
||||
return parts.join("\n");
|
||||
}
|
||||
canonicalHeaderValues(values: string) {
|
||||
return values.replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
|
||||
}
|
||||
|
||||
getSigningKey(date: string, zone: CTYunZone) {
|
||||
const kDate = this.hmacSha256("AWS4" + this.secretKey, date);
|
||||
const kRegion = this.hmacSha256(kDate, zone);
|
||||
const kService = this.hmacSha256(kRegion, serviceName);
|
||||
var signingKey = this.hmacSha256(kService, v4Identifier);
|
||||
return signingKey;
|
||||
}
|
||||
|
||||
hmacSha256(key: string | Buffer, content: string | Buffer, digest?: BinaryToTextEncoding, fn?: string) {
|
||||
if (!fn) fn = "sha256";
|
||||
if (typeof content === "string") content = Buffer.from(content);
|
||||
if (!digest) {
|
||||
return crypto.createHmac(fn, key).update(content).digest();
|
||||
}
|
||||
return crypto.createHmac(fn, key).update(content).digest(digest);
|
||||
}
|
||||
|
||||
each(object: any, iterFunction: (key: any, item: any) => any) {
|
||||
for (let key in object) {
|
||||
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
||||
const ret = iterFunction.call(this, key, object[key]);
|
||||
if (Object.keys(ret).length === 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
arrayEach(array: any, iterFunction: (item: any, key: any) => any) {
|
||||
for (let idx in array) {
|
||||
if (Object.prototype.hasOwnProperty.call(array, idx)) {
|
||||
const ret = iterFunction.call(this, array[idx], parseInt(idx, 10));
|
||||
if (Object.keys(ret).length === 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isSignableHeader(key: string) {
|
||||
if (key.toLowerCase().indexOf("x-amz-") === 0) return true;
|
||||
return unsignableHeaders.indexOf(key) < 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
|
||||
import crypto, { BinaryToTextEncoding } from 'crypto';
|
||||
|
||||
import { TencentYunZone } from '../../types/TencentYun';
|
||||
import {
|
||||
OakExternalException,
|
||||
OakNetworkException,
|
||||
} from 'oak-domain/lib/types/Exception';
|
||||
|
||||
|
||||
|
||||
|
||||
function getEndpoint(RegionID: TencentYunZone) {
|
||||
return `cos-${RegionID}.myqcloud.com`;
|
||||
}
|
||||
|
||||
const Tencent_ENDPOINT_LIST: Record<TencentYunZone, { ul: string }> = {
|
||||
'ap-beijing': {
|
||||
ul: getEndpoint('ap-nanjing'),
|
||||
},
|
||||
'ap-nanjing': {
|
||||
ul: getEndpoint('ap-nanjing'),
|
||||
},
|
||||
'ap-shanghai': {
|
||||
ul: getEndpoint('ap-shanghai'),
|
||||
},
|
||||
'ap-guangzhou': {
|
||||
ul: getEndpoint('ap-guangzhou'),
|
||||
},
|
||||
'ap-chengdu': {
|
||||
ul: getEndpoint('ap-chengdu'),
|
||||
},
|
||||
'ap-chongqing': {
|
||||
ul: getEndpoint('ap-chongqing'),
|
||||
},
|
||||
'ap-shenzhen-fsi': {
|
||||
ul: getEndpoint('ap-shenzhen-fsi'),
|
||||
},
|
||||
'ap-shanghai-fsi': {
|
||||
ul: getEndpoint('ap-shanghai-fsi'),
|
||||
},
|
||||
'ap-beijing-fsi': {
|
||||
ul: getEndpoint('ap-beijing-fsi'),
|
||||
},
|
||||
'ap-hongkong': {
|
||||
ul: getEndpoint('ap-hongkong'),
|
||||
},
|
||||
'ap-singapore': {
|
||||
ul: getEndpoint('ap-singapore'),
|
||||
},
|
||||
};
|
||||
|
||||
// TODO 腾讯云代码未验证
|
||||
export class TencentYunInstance {
|
||||
private accessKey: string;
|
||||
private secretKey: string;
|
||||
|
||||
private COS;
|
||||
|
||||
constructor(accessKey: string, secretKey: string) {
|
||||
this.accessKey = accessKey;
|
||||
this.secretKey = secretKey;
|
||||
|
||||
this.COS = require('cos-wx-sdk-v5');
|
||||
}
|
||||
|
||||
getUploadInfo(bucket: string, zone: TencentYunZone, key?: string) {
|
||||
try {
|
||||
const signInfo = this.getSignInfo(bucket);
|
||||
return {
|
||||
key,
|
||||
accessKey: this.accessKey,
|
||||
policy: signInfo.policy,
|
||||
signature: signInfo.signature,
|
||||
uploadHost: `https://${bucket}.${Tencent_ENDPOINT_LIST[zone].ul}`,
|
||||
bucket,
|
||||
keyTime: signInfo.keyTime,
|
||||
algorithm: signInfo.algorithm,
|
||||
};
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
//https://cloud.tencent.com/document/product/436/7778
|
||||
private getSignInfo(bucket: string) {
|
||||
const expiration = new Date();
|
||||
expiration.setDate(expiration.getDate() + 1);
|
||||
|
||||
const keyTime = `${Math.floor(Date.now() / 1000)};${Math.floor(
|
||||
expiration.getTime() / 1000
|
||||
)}`;
|
||||
const algorithm = 'sha1';
|
||||
|
||||
const policy = {
|
||||
expiration: expiration.toISOString(),
|
||||
conditions: [
|
||||
// 限制可上传的Bucket。
|
||||
{ bucket: bucket },
|
||||
['eq', '$x-cos-server-side-encryption', 'AES256'],
|
||||
{ 'q-sign-algorithm': algorithm },
|
||||
{ 'q-ak': this.accessKey },
|
||||
{ 'q-sign-time': keyTime },
|
||||
],
|
||||
};
|
||||
|
||||
const encodedPolicy = Buffer.from(JSON.stringify(policy)).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
const signKey = crypto
|
||||
.createHmac('sha1', this.secretKey)
|
||||
.update(keyTime)
|
||||
.digest();
|
||||
const stringToSign = crypto
|
||||
.createHash('sha1')
|
||||
.update(encodedPolicy)
|
||||
.digest('hex');
|
||||
|
||||
const signature = crypto
|
||||
.createHmac('sha1', signKey)
|
||||
.update(stringToSign)
|
||||
.digest('hex');
|
||||
|
||||
return {
|
||||
policy: encodedPolicy,
|
||||
signature,
|
||||
keyTime,
|
||||
algorithm,
|
||||
};
|
||||
}
|
||||
|
||||
async removeFile(srcBucket: string, zone: TencentYunZone, srcKey: string) {
|
||||
const client = new this.COS({
|
||||
SecretId: this.accessKey,
|
||||
SecretKey: this.secretKey,
|
||||
});
|
||||
|
||||
try {
|
||||
// 填写Object完整路径。Object完整路径中不能包含Bucket名称。
|
||||
const result = await client.deleteObject({
|
||||
Bucket: srcBucket,
|
||||
Region: zone,
|
||||
Key: srcKey,
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async isExistObject(
|
||||
srcBucket: string,
|
||||
zone: TencentYunZone,
|
||||
srcKey: string
|
||||
) {
|
||||
const client = new this.COS({
|
||||
SecretId: this.accessKey,
|
||||
SecretKey: this.secretKey,
|
||||
});
|
||||
let result: any;
|
||||
try {
|
||||
result = await client.headObject({
|
||||
Bucket: srcBucket,
|
||||
Region: zone,
|
||||
Key: srcKey,
|
||||
});
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
if (error.code === 'NoSuchKey') {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
|
||||
|
||||
|
||||
export type ALiYunZone =
|
||||
| 'cn-hangzhou'
|
||||
| 'cn-shanghai'
|
||||
| 'cn-nanjing'
|
||||
| 'cn-fuzhou'
|
||||
| 'cn-wuhan'
|
||||
| 'cn-qingdao'
|
||||
| 'cn-beijing'
|
||||
| 'cn-zhangjiakou'
|
||||
| 'cn-huhehaote'
|
||||
| 'cn-wulanchabu'
|
||||
| 'cn-shenzhen'
|
||||
| 'cn-heyuan'
|
||||
| 'cn-guangzhou'
|
||||
| 'cn-chengdu'
|
||||
| 'cn-hongkong'
|
||||
| 'us-west-1'
|
||||
| 'us-east-1'
|
||||
| 'ap-northeast-1'
|
||||
| 'ap-northeast-2'
|
||||
| 'ap-southeast-1'
|
||||
| 'ap-southeast-2'
|
||||
| 'ap-southeast-3'
|
||||
| 'ap-southeast-5'
|
||||
| 'ap-southeast-6'
|
||||
| 'ap-southeast-7'
|
||||
| 'ap-south-1'
|
||||
| 'eu-central-1'
|
||||
| 'eu-west-1'
|
||||
| 'me-east-1'
|
||||
| 'rg-china-mainland';
|
||||
|
|
@ -6,11 +6,14 @@
|
|||
export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket';
|
||||
|
||||
export type ReqOptionProps = {
|
||||
payload: string;
|
||||
path: string;
|
||||
host: string;
|
||||
headers: any;
|
||||
method: 'DELETE' | 'GET' | 'POST' | 'PUT' | 'HEAD';
|
||||
queryParameters: Record<string, any>;
|
||||
service: string;
|
||||
date: string;
|
||||
headers: any
|
||||
method: 'DELETE' | "GET" | "POST" | "PUT",
|
||||
}
|
||||
};
|
||||
|
||||
export type CTYunZone = 'hazz' | 'lnsy' | 'sccd' | 'xjwlmq' | 'gslz' | 'sdqd' | 'gzgy' | 'hbwh' | 'xzls' | 'ahwh' | 'gdsz' | 'jssz' | 'sh2';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
export type TencentYunZone =
|
||||
| 'ap-beijing'
|
||||
| 'ap-nanjing'
|
||||
| 'ap-shanghai'
|
||||
| 'ap-guangzhou'
|
||||
| 'ap-chengdu'
|
||||
| 'ap-chongqing'
|
||||
| 'ap-shenzhen-fsi'
|
||||
| 'ap-shanghai-fsi'
|
||||
| 'ap-beijing-fsi'
|
||||
| 'ap-hongkong'
|
||||
| 'ap-singapore';
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
export * from './Wechat';
|
||||
export * from './Qiniu';
|
||||
export * from './CTYun';
|
||||
export * from './CTYun';
|
||||
export * from './ALiYun';
|
||||
export * from './TencentYun';
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { ALiYunInstance, ALiYunSDK, CTYunSDK } from '../src/index';
|
||||
|
||||
const accessKey = '';
|
||||
const accessSecret = '';
|
||||
|
||||
|
||||
//测试阿里云文件是否存在
|
||||
async function isExistFile() {
|
||||
const instance = ALiYunSDK.getInstance(accessKey, accessSecret);
|
||||
|
||||
const result = await instance.isExistObject(
|
||||
'bmzs-release',
|
||||
'cn-hangzhou',
|
||||
'extraFile/11ef425f-8c96-e3eb-8828-db68e1a0c102.png'
|
||||
);
|
||||
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
async function removeFile() {
|
||||
const instance = ALiYunSDK.getInstance(accessKey, accessSecret);
|
||||
|
||||
const result = await instance.removeFile(
|
||||
'bmzs-release',
|
||||
'cn-hangzhou',
|
||||
'extraFile/11ef425f-8c96-e3eb-8828-db68e1a0c102.png'
|
||||
);
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
// isExistFile();
|
||||
removeFile();
|
||||
|
||||
const accessKey2 = '';
|
||||
const accessSecret2 = '';
|
||||
|
||||
async function removeCTYunFile() {
|
||||
const instance = CTYunSDK.getInstance(accessKey2, accessSecret2);
|
||||
|
||||
const result = await instance.removeFile(
|
||||
'jiupai-oss-dev',
|
||||
'hbwh',
|
||||
'fff.jpg'
|
||||
);
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
async function isExistCTYunFile() {
|
||||
const instance = CTYunSDK.getInstance(accessKey2, accessSecret2);
|
||||
|
||||
const result = await instance.isExistObject(
|
||||
'jiupai-oss-dev',
|
||||
'hbwh',
|
||||
'fff.jpg'
|
||||
);
|
||||
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
// removeCTYunFile();
|
||||
// isExistCTYunFile();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue