1、阿里云对象存储功能测试通过

2、腾讯云对象存储未测试,有需求再测
3、天翼云对象存储删除跟判断文件是否存在 未测试
This commit is contained in:
wkj 2024-07-15 12:36:52 +08:00
parent 345e597623
commit db5b28f751
57 changed files with 2148 additions and 502 deletions

9
es/ALiYunSDK.d.ts vendored Normal file
View File

@ -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 };

20
es/ALiYunSDK.js Normal file
View File

@ -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 };

View File

@ -1,6 +1,6 @@
import { CTYunInstance } from './service/ctyun/CTYun'; import { CTYunInstance } from './service/ctyun/CTYun';
class CTYunSDK { class CTYunSDK {
ctyunMap; ctyunMap; //oss
constructor() { constructor() {
this.ctyunMap = {}; this.ctyunMap = {};
} }

9
es/TencentYunSDK.d.ts vendored Normal file
View File

@ -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 };

20
es/TencentYunSDK.js Normal file
View File

@ -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 };

6
es/index.d.ts vendored
View File

@ -2,7 +2,9 @@ import WechatSDK, { WechatMpInstance, WechatPublicInstance, WechatWebInstance }
import AmapSDK from './AmapSDK'; import AmapSDK from './AmapSDK';
import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK'; import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK';
import SmsSdk, { TencentSmsInstance, AliSmsInstance, CTYunSmsInstance } from './SmsSdk'; 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 * 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'; export * from './types';

View File

@ -2,7 +2,9 @@ import WechatSDK, { WechatMpInstance, WechatPublicInstance, WechatWebInstance }
import AmapSDK from './AmapSDK'; import AmapSDK from './AmapSDK';
import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK'; import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK';
import SmsSdk, { TencentSmsInstance, AliSmsInstance, CTYunSmsInstance, } from './SmsSdk'; 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 * 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'; export * from './types';

View File

@ -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>;
}

View File

@ -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;
}
}
}

View File

@ -1,6 +1,3 @@
/// <reference types="node" />
/// <reference types="node" />
import { BinaryToTextEncoding } from 'crypto';
import { CTYunZone, ReqOptionProps } from '../../types/CTYun'; import { CTYunZone, ReqOptionProps } from '../../types/CTYun';
export declare class CTYunInstance { export declare class CTYunInstance {
private accessKey; private accessKey;
@ -22,16 +19,10 @@ export declare class CTYunInstance {
private hmacSha1; private hmacSha1;
private urlSafeBase64Encode; private urlSafeBase64Encode;
removeFile(bucket: string, zone: CTYunZone, key: string): Promise<void>; removeFile(bucket: string, zone: CTYunZone, key: string): Promise<void>;
isExistObject(srcBucket: string, zone: CTYunZone, srcKey: string): Promise<boolean>;
getAuthorization(reqOptions: ReqOptionProps, zone: CTYunZone): string; getAuthorization(reqOptions: ReqOptionProps, zone: CTYunZone): string;
signatureFn(reqOptions: ReqOptionProps, zone: CTYunZone): string | Buffer; private getSignatureKey;
stringToSign(reqOptions: ReqOptionProps, zone: CTYunZone): string; private buildCanonicalRequest;
signedHeaders(headers: Record<string, string>): string; private buildCanonicalQueryString;
canonicalString(path: string, method: string, headers: Record<string, string>): string; private calculatePayloadHash;
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;
} }

View File

@ -1,6 +1,6 @@
// import AWS from 'aws-sdk'; // import AWS from 'aws-sdk';
import crypto from 'crypto'; 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 = { const CTYun_ENDPOINT_LIST = {
hazz: { hazz: {
ul: 'oos-hazz.ctyunapi.cn', ul: 'oos-hazz.ctyunapi.cn',
@ -44,8 +44,16 @@ const CTYun_ENDPOINT_LIST = {
}; };
const serviceName = 's3'; const serviceName = 's3';
const v4Identifier = 'aws4_request'; const v4Identifier = 'aws4_request';
const expiresHeader = "presigned-expires"; const expiresHeader = 'presigned-expires';
const unsignableHeaders = ["authorization", "x-ctyun-data-location", "content-length", "user-agent", expiresHeader, "expect", "x-amzn-trace-id"]; const unsignableHeaders = [
'authorization',
'x-ctyun-data-location',
'content-length',
'user-agent',
expiresHeader,
'expect',
'x-amzn-trace-id',
];
export class CTYunInstance { export class CTYunInstance {
accessKey; accessKey;
secretKey; secretKey;
@ -55,7 +63,6 @@ export class CTYunInstance {
} }
getUploadInfo(bucket, zone, key) { getUploadInfo(bucket, zone, key) {
try { try {
// const uploadToken = this.getToken(zone, bucket, actions);
const signInfo = this.getSignInfo(bucket); const signInfo = this.getSignInfo(bucket);
return { return {
key, key,
@ -73,30 +80,30 @@ export class CTYunInstance {
getSignInfo(bucket) { getSignInfo(bucket) {
// 对于policy里的expiration我在天翼云的文档里没有找到具体的说明但是这个字段不填入就会请求失败 // 对于policy里的expiration我在天翼云的文档里没有找到具体的说明但是这个字段不填入就会请求失败
// 设置一个明天过期的时间 // 设置一个明天过期的时间
const now = new Date(); const expiration = new Date();
now.setDate(now.getDate() + 1); expiration.setDate(expiration.getDate() + 1);
const tomorrow = now.toISOString();
const policy = { const policy = {
Version: "2012-10-17", expiration: expiration.toISOString(),
Statement: [{ conditions: [
Effect: "Allow", {
Action: ["oos:*"],
Resource: `arn:ctyun:oos:::${bucket} /*`
}],
expiration: tomorrow,
conditions: [{
bucket: bucket, bucket: bucket,
}, [ },
"starts-with", ['starts-with', '$key', 'extraFile'],
"$key", ],
"extraFile", Version: '2012-10-17',
]] Statement: [
{
Effect: 'Allow',
Action: ['oos:*'],
Resource: `arn:ctyun:oos:::${bucket} /*`,
},
],
}; };
const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy)); const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy));
const signature = this.hmacSha1(encodePolicy, this.secretKey); const signature = this.hmacSha1(encodePolicy, this.secretKey);
return { return {
encodePolicy, encodePolicy,
signature signature,
}; };
} }
base64ToUrlSafe(v) { base64ToUrlSafe(v) {
@ -111,146 +118,195 @@ export class CTYunInstance {
const encoded = Buffer.from(jsonFlags).toString('base64'); const encoded = Buffer.from(jsonFlags).toString('base64');
return this.base64ToUrlSafe(encoded); return this.base64ToUrlSafe(encoded);
} }
// 当初就不应该封装天翼云,文档不全,找技术要签名代码还得自己去看源码,恶心
// 下面的代码是根据天翼云生成签名源码改动得来 oos-js-sdk-6.2.js
async removeFile(bucket, zone, key) { async removeFile(bucket, zone, key) {
const path = `/${bucket}/${key}`; const path = `/${key}`;
const host = `${CTYun_ENDPOINT_LIST[zone].ul}`; const host = `${bucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
const url = `https://${host}${path}`; 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 = { const headers = {
"Content-Type": "application/octet-stream; charset=UTF-8", 'Content-Type': 'application/xml; charset=utf-8',
"Host": "oos-hbwh.ctyunapi.cn", Host: host,
"X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", 'x-amz-content-sha256': this.calculatePayloadHash(payload),
"X-Amz-User-Agent": "aws-sdk-js/2.296.0 callback" 'x-amz-date': date,
}; };
headers["X-Amz-Date"] = date;
const reqOptions = { const reqOptions = {
path,
host,
date,
headers, headers,
method: "DELETE", method: method,
payload: payload,
host,
path,
queryParameters: {},
service,
date,
}; };
const authorization = this.getAuthorization(reqOptions, zone); const authorization = this.getAuthorization(reqOptions, zone);
headers['Authorization'] = authorization; let response;
try { try {
await fetch(url, { response = await fetch(url, {
method: 'DELETE', method,
headers, headers: {
...headers,
Authorization: authorization,
},
}); });
} }
catch (err) { catch (err) {
throw new OakNetworkException(); 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) { getAuthorization(reqOptions, zone) {
const { headers, date } = reqOptions; const { headers, host, path, method, payload, queryParameters, service, date, } = reqOptions;
const parts = []; const payloadHash = this.calculatePayloadHash(payload);
const credString = [date.substring(0, 8), zone, serviceName, v4Identifier].join("/"); // Step 2: Build canonical request
parts.push("AWS4-HMAC-SHA256" + " Credential=" + this.accessKey + "/" + credString); const { canonicalRequest, signedHeaders } = this.buildCanonicalRequest(method, path, queryParameters, headers, payloadHash);
parts.push("SignedHeaders=" + this.signedHeaders(headers)); // Step 3: Calculate canonical request hash
parts.push("Signature=" + this.signatureFn(reqOptions, zone)); const canonicalRequestHash = crypto
return parts.join(", "); .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) { getSignatureKey(dateStamp, zone, service) {
const { date } = reqOptions; const kDate = crypto
var signingKey = this.getSigningKey(date.substring(0, 8), zone); .createHmac('sha256', `AWS4${this.secretKey}`)
return this.hmacSha256(signingKey, this.stringToSign(reqOptions, zone), "hex"); .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) { buildCanonicalRequest(method, path, queryParameters, headers, payloadHash) {
const { date, path, method, headers } = reqOptions; const canonicalQuerystring = this.buildCanonicalQueryString(queryParameters);
var parts = []; const canonicalHeaders = Object.keys(headers)
parts.push("AWS4-HMAC-SHA256"); .sort()
parts.push(date); .map((key) => `${key.toLowerCase()}:${headers[key].trim()}`)
parts.push([date.substring(0, 8), zone, serviceName, v4Identifier].join("/")); .join('\n');
const canonicalStr = this.canonicalString(path, method, headers); const signedHeaders = Object.keys(headers)
const buffer = Buffer.from(canonicalStr); .sort()
const encodeStr = crypto.createHash('sha256').update(buffer).digest('hex'); .map((key) => key.toLowerCase())
parts.push(encodeStr); .join(';');
return parts.join("\n"); const canonicalRequest = [
method,
path,
canonicalQuerystring,
canonicalHeaders,
'',
signedHeaders,
payloadHash,
].join('\n');
return {
canonicalRequest,
signedHeaders,
};
} }
signedHeaders(headers) { buildCanonicalQueryString(queryParameters) {
const keys = []; const keys = Object.keys(queryParameters).sort();
this.each(headers, (key) => { const canonicalQueryString = keys
key = key.toLowerCase(); .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParameters[key])}`)
if (this.isSignableHeader(key)) .join('&');
keys.push(key); return canonicalQueryString;
});
return keys.sort().join(";");
} }
canonicalString(path, method, headers) { calculatePayloadHash(payload) {
const parts = []; const hash = crypto.createHash('sha256');
parts.push(method); hash.update(payload);
parts.push(path); return hash.digest('hex');
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;
} }
} }

20
es/service/tencent/Tencent.d.ts vendored Normal file
View File

@ -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>;
}

View File

@ -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;
}
}
}

View File

@ -187,7 +187,7 @@ export class WechatMpInstance {
const { type, media, filetype, filename } = options; const { type, media, filetype, filename } = options;
const formData = new FormData(); const formData = new FormData();
formData.append('media', media, { formData.append('media', media, {
contentType: filetype, contentType: filetype, // 微信识别需要
filename: filename, // 微信识别需要 filename: filename, // 微信识别需要
}); });
const getLength = () => { const getLength = () => {
@ -332,7 +332,7 @@ export class WechatMpInstance {
body: JSON.stringify({ body: JSON.stringify({
jump_wxa: jump_wxa, jump_wxa: jump_wxa,
is_expire: true, is_expire: true,
expire_type: expireType, expire_type: expireType, //默认是零,到期失效的 scheme 码失效类型失效时间类型0失效间隔天数类型1
expire_time: expiresAt, expire_time: expiresAt,
expire_interval: expireInterval, expire_interval: expireInterval,
}), }),

View File

@ -511,8 +511,8 @@ export class WechatPublicInstance {
const { type, media, description, filetype, filename, fileLength } = options; const { type, media, description, filetype, filename, fileLength } = options;
const formData = new FormData(); const formData = new FormData();
formData.append('media', media, { formData.append('media', media, {
contentType: filetype, contentType: filetype, // 微信识别需要
filename: filename, filename: filename, // 微信识别需要
knownLength: fileLength, knownLength: fileLength,
}); });
if (type === 'video') { if (type === 'video') {
@ -535,8 +535,8 @@ export class WechatPublicInstance {
const { media, filetype, filename, fileLength } = options; const { media, filetype, filename, fileLength } = options;
const formData = new FormData(); const formData = new FormData();
formData.append('media', media, { formData.append('media', media, {
contentType: filetype, contentType: filetype, // 微信识别需要
filename: filename, filename: filename, // 微信识别需要
knownLength: fileLength, knownLength: fileLength,
}); });
const headers = formData.getHeaders(); const headers = formData.getHeaders();
@ -556,8 +556,8 @@ export class WechatPublicInstance {
const { type, media, filetype, filename, fileLength } = options; const { type, media, filetype, filename, fileLength } = options;
const formData = new FormData(); const formData = new FormData();
formData.append('media', media, { formData.append('media', media, {
contentType: filetype, contentType: filetype, // 微信识别需要
filename: filename, filename: filename, // 微信识别需要
knownLength: fileLength, knownLength: fileLength,
}); });
const headers = formData.getHeaders(); const headers = formData.getHeaders();

1
es/types/ALiYun.d.ts vendored Normal file
View File

@ -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';

1
es/types/ALiYun.js Normal file
View File

@ -0,0 +1 @@
export {};

7
es/types/CTYun.d.ts vendored
View File

@ -4,10 +4,13 @@
*/ */
export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket'; export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket';
export type ReqOptionProps = { export type ReqOptionProps = {
payload: string;
path: string; path: string;
host: string; host: string;
date: string;
headers: any; 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'; export type CTYunZone = 'hazz' | 'lnsy' | 'sccd' | 'xjwlmq' | 'gslz' | 'sdqd' | 'gzgy' | 'hbwh' | 'xzls' | 'ahwh' | 'gdsz' | 'jssz' | 'sh2';

1
es/types/TencentYun.d.ts vendored Normal file
View File

@ -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';

1
es/types/TencentYun.js Normal file
View File

@ -0,0 +1 @@
export {};

2
es/types/index.d.ts vendored
View File

@ -1,3 +1,5 @@
export * from './Wechat'; export * from './Wechat';
export * from './Qiniu'; export * from './Qiniu';
export * from './CTYun'; export * from './CTYun';
export * from './ALiYun';
export * from './TencentYun';

View File

@ -1,3 +1,5 @@
export * from './Wechat'; export * from './Wechat';
export * from './Qiniu'; export * from './Qiniu';
export * from './CTYun'; export * from './CTYun';
export * from './ALiYun';
export * from './TencentYun';

9
lib/ALiYunSDK.d.ts vendored Normal file
View File

@ -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 };

23
lib/ALiYunSDK.js Normal file
View File

@ -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;

View File

@ -4,7 +4,7 @@ exports.CTYunInstance = void 0;
const CTYun_1 = require("./service/ctyun/CTYun"); const CTYun_1 = require("./service/ctyun/CTYun");
Object.defineProperty(exports, "CTYunInstance", { enumerable: true, get: function () { return CTYun_1.CTYunInstance; } }); Object.defineProperty(exports, "CTYunInstance", { enumerable: true, get: function () { return CTYun_1.CTYunInstance; } });
class CTYunSDK { class CTYunSDK {
ctyunMap; ctyunMap; //oss
constructor() { constructor() {
this.ctyunMap = {}; this.ctyunMap = {};
} }

9
lib/TencentYunSDK.d.ts vendored Normal file
View File

@ -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 };

23
lib/TencentYunSDK.js Normal file
View File

@ -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;

6
lib/index.d.ts vendored
View File

@ -2,7 +2,9 @@ import WechatSDK, { WechatMpInstance, WechatPublicInstance, WechatWebInstance }
import AmapSDK from './AmapSDK'; import AmapSDK from './AmapSDK';
import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK'; import QiniuSDK, { QiniuCloudInstance } from './QiniuSDK';
import SmsSdk, { TencentSmsInstance, AliSmsInstance, CTYunSmsInstance } from './SmsSdk'; 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 * 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'; export * from './types';

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); 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 tslib_1 = require("tslib");
const WechatSDK_1 = tslib_1.__importStar(require("./WechatSDK")); const WechatSDK_1 = tslib_1.__importStar(require("./WechatSDK"));
exports.WechatSDK = WechatSDK_1.default; 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, "AliSmsInstance", { enumerable: true, get: function () { return SmsSdk_1.AliSmsInstance; } });
Object.defineProperty(exports, "CTYunSmsInstance", { enumerable: true, get: function () { return SmsSdk_1.CTYunSmsInstance; } }); Object.defineProperty(exports, "CTYunSmsInstance", { enumerable: true, get: function () { return SmsSdk_1.CTYunSmsInstance; } });
const CTYunSDK_1 = tslib_1.__importStar(require("./CTYunSDK")); 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; } }); 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("./service/amap/Amap"), exports);
tslib_1.__exportStar(require("./types"), exports); tslib_1.__exportStar(require("./types"), exports);

View File

@ -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>;
}

View File

@ -1 +1,188 @@
"use strict"; "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;

View File

@ -1,6 +1,3 @@
/// <reference types="node" />
/// <reference types="node" />
import { BinaryToTextEncoding } from 'crypto';
import { CTYunZone, ReqOptionProps } from '../../types/CTYun'; import { CTYunZone, ReqOptionProps } from '../../types/CTYun';
export declare class CTYunInstance { export declare class CTYunInstance {
private accessKey; private accessKey;
@ -22,16 +19,10 @@ export declare class CTYunInstance {
private hmacSha1; private hmacSha1;
private urlSafeBase64Encode; private urlSafeBase64Encode;
removeFile(bucket: string, zone: CTYunZone, key: string): Promise<void>; removeFile(bucket: string, zone: CTYunZone, key: string): Promise<void>;
isExistObject(srcBucket: string, zone: CTYunZone, srcKey: string): Promise<boolean>;
getAuthorization(reqOptions: ReqOptionProps, zone: CTYunZone): string; getAuthorization(reqOptions: ReqOptionProps, zone: CTYunZone): string;
signatureFn(reqOptions: ReqOptionProps, zone: CTYunZone): string | Buffer; private getSignatureKey;
stringToSign(reqOptions: ReqOptionProps, zone: CTYunZone): string; private buildCanonicalRequest;
signedHeaders(headers: Record<string, string>): string; private buildCanonicalQueryString;
canonicalString(path: string, method: string, headers: Record<string, string>): string; private calculatePayloadHash;
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;
} }

View File

@ -48,8 +48,16 @@ const CTYun_ENDPOINT_LIST = {
}; };
const serviceName = 's3'; const serviceName = 's3';
const v4Identifier = 'aws4_request'; const v4Identifier = 'aws4_request';
const expiresHeader = "presigned-expires"; const expiresHeader = 'presigned-expires';
const unsignableHeaders = ["authorization", "x-ctyun-data-location", "content-length", "user-agent", expiresHeader, "expect", "x-amzn-trace-id"]; const unsignableHeaders = [
'authorization',
'x-ctyun-data-location',
'content-length',
'user-agent',
expiresHeader,
'expect',
'x-amzn-trace-id',
];
class CTYunInstance { class CTYunInstance {
accessKey; accessKey;
secretKey; secretKey;
@ -59,7 +67,6 @@ class CTYunInstance {
} }
getUploadInfo(bucket, zone, key) { getUploadInfo(bucket, zone, key) {
try { try {
// const uploadToken = this.getToken(zone, bucket, actions);
const signInfo = this.getSignInfo(bucket); const signInfo = this.getSignInfo(bucket);
return { return {
key, key,
@ -77,30 +84,30 @@ class CTYunInstance {
getSignInfo(bucket) { getSignInfo(bucket) {
// 对于policy里的expiration我在天翼云的文档里没有找到具体的说明但是这个字段不填入就会请求失败 // 对于policy里的expiration我在天翼云的文档里没有找到具体的说明但是这个字段不填入就会请求失败
// 设置一个明天过期的时间 // 设置一个明天过期的时间
const now = new Date(); const expiration = new Date();
now.setDate(now.getDate() + 1); expiration.setDate(expiration.getDate() + 1);
const tomorrow = now.toISOString();
const policy = { const policy = {
Version: "2012-10-17", expiration: expiration.toISOString(),
Statement: [{ conditions: [
Effect: "Allow", {
Action: ["oos:*"],
Resource: `arn:ctyun:oos:::${bucket} /*`
}],
expiration: tomorrow,
conditions: [{
bucket: bucket, bucket: bucket,
}, [ },
"starts-with", ['starts-with', '$key', 'extraFile'],
"$key", ],
"extraFile", Version: '2012-10-17',
]] Statement: [
{
Effect: 'Allow',
Action: ['oos:*'],
Resource: `arn:ctyun:oos:::${bucket} /*`,
},
],
}; };
const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy)); const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy));
const signature = this.hmacSha1(encodePolicy, this.secretKey); const signature = this.hmacSha1(encodePolicy, this.secretKey);
return { return {
encodePolicy, encodePolicy,
signature signature,
}; };
} }
base64ToUrlSafe(v) { base64ToUrlSafe(v) {
@ -115,147 +122,196 @@ class CTYunInstance {
const encoded = Buffer.from(jsonFlags).toString('base64'); const encoded = Buffer.from(jsonFlags).toString('base64');
return this.base64ToUrlSafe(encoded); return this.base64ToUrlSafe(encoded);
} }
// 当初就不应该封装天翼云,文档不全,找技术要签名代码还得自己去看源码,恶心
// 下面的代码是根据天翼云生成签名源码改动得来 oos-js-sdk-6.2.js
async removeFile(bucket, zone, key) { async removeFile(bucket, zone, key) {
const path = `/${bucket}/${key}`; const path = `/${key}`;
const host = `${CTYun_ENDPOINT_LIST[zone].ul}`; const host = `${bucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
const url = `https://${host}${path}`; 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 = { const headers = {
"Content-Type": "application/octet-stream; charset=UTF-8", 'Content-Type': 'application/xml; charset=utf-8',
"Host": "oos-hbwh.ctyunapi.cn", Host: host,
"X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", 'x-amz-content-sha256': this.calculatePayloadHash(payload),
"X-Amz-User-Agent": "aws-sdk-js/2.296.0 callback" 'x-amz-date': date,
}; };
headers["X-Amz-Date"] = date;
const reqOptions = { const reqOptions = {
path,
host,
date,
headers, headers,
method: "DELETE", method: method,
payload: payload,
host,
path,
queryParameters: {},
service,
date,
}; };
const authorization = this.getAuthorization(reqOptions, zone); const authorization = this.getAuthorization(reqOptions, zone);
headers['Authorization'] = authorization; let response;
try { try {
await fetch(url, { response = await fetch(url, {
method: 'DELETE', method,
headers, headers: {
...headers,
Authorization: authorization,
},
}); });
} }
catch (err) { catch (err) {
throw new Exception_1.OakNetworkException(); 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) { getAuthorization(reqOptions, zone) {
const { headers, date } = reqOptions; const { headers, host, path, method, payload, queryParameters, service, date, } = reqOptions;
const parts = []; const payloadHash = this.calculatePayloadHash(payload);
const credString = [date.substring(0, 8), zone, serviceName, v4Identifier].join("/"); // Step 2: Build canonical request
parts.push("AWS4-HMAC-SHA256" + " Credential=" + this.accessKey + "/" + credString); const { canonicalRequest, signedHeaders } = this.buildCanonicalRequest(method, path, queryParameters, headers, payloadHash);
parts.push("SignedHeaders=" + this.signedHeaders(headers)); // Step 3: Calculate canonical request hash
parts.push("Signature=" + this.signatureFn(reqOptions, zone)); const canonicalRequestHash = crypto_1.default
return parts.join(", "); .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) { getSignatureKey(dateStamp, zone, service) {
const { date } = reqOptions; const kDate = crypto_1.default
var signingKey = this.getSigningKey(date.substring(0, 8), zone); .createHmac('sha256', `AWS4${this.secretKey}`)
return this.hmacSha256(signingKey, this.stringToSign(reqOptions, zone), "hex"); .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) { buildCanonicalRequest(method, path, queryParameters, headers, payloadHash) {
const { date, path, method, headers } = reqOptions; const canonicalQuerystring = this.buildCanonicalQueryString(queryParameters);
var parts = []; const canonicalHeaders = Object.keys(headers)
parts.push("AWS4-HMAC-SHA256"); .sort()
parts.push(date); .map((key) => `${key.toLowerCase()}:${headers[key].trim()}`)
parts.push([date.substring(0, 8), zone, serviceName, v4Identifier].join("/")); .join('\n');
const canonicalStr = this.canonicalString(path, method, headers); const signedHeaders = Object.keys(headers)
const buffer = Buffer.from(canonicalStr); .sort()
const encodeStr = crypto_1.default.createHash('sha256').update(buffer).digest('hex'); .map((key) => key.toLowerCase())
parts.push(encodeStr); .join(';');
return parts.join("\n"); const canonicalRequest = [
method,
path,
canonicalQuerystring,
canonicalHeaders,
'',
signedHeaders,
payloadHash,
].join('\n');
return {
canonicalRequest,
signedHeaders,
};
} }
signedHeaders(headers) { buildCanonicalQueryString(queryParameters) {
const keys = []; const keys = Object.keys(queryParameters).sort();
this.each(headers, (key) => { const canonicalQueryString = keys
key = key.toLowerCase(); .map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParameters[key])}`)
if (this.isSignableHeader(key)) .join('&');
keys.push(key); return canonicalQueryString;
});
return keys.sort().join(";");
} }
canonicalString(path, method, headers) { calculatePayloadHash(payload) {
const parts = []; const hash = crypto_1.default.createHash('sha256');
parts.push(method); hash.update(payload);
parts.push(path); return hash.digest('hex');
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;
} }
} }
exports.CTYunInstance = CTYunInstance; exports.CTYunInstance = CTYunInstance;

20
lib/service/tencent/Tencent.d.ts vendored Normal file
View File

@ -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>;
}

View File

@ -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;

View File

@ -191,7 +191,7 @@ class WechatMpInstance {
const { type, media, filetype, filename } = options; const { type, media, filetype, filename } = options;
const formData = new form_data_1.default(); const formData = new form_data_1.default();
formData.append('media', media, { formData.append('media', media, {
contentType: filetype, contentType: filetype, // 微信识别需要
filename: filename, // 微信识别需要 filename: filename, // 微信识别需要
}); });
const getLength = () => { const getLength = () => {
@ -336,7 +336,7 @@ class WechatMpInstance {
body: JSON.stringify({ body: JSON.stringify({
jump_wxa: jump_wxa, jump_wxa: jump_wxa,
is_expire: true, is_expire: true,
expire_type: expireType, expire_type: expireType, //默认是零,到期失效的 scheme 码失效类型失效时间类型0失效间隔天数类型1
expire_time: expiresAt, expire_time: expiresAt,
expire_interval: expireInterval, expire_interval: expireInterval,
}), }),

View File

@ -515,8 +515,8 @@ class WechatPublicInstance {
const { type, media, description, filetype, filename, fileLength } = options; const { type, media, description, filetype, filename, fileLength } = options;
const formData = new form_data_1.default(); const formData = new form_data_1.default();
formData.append('media', media, { formData.append('media', media, {
contentType: filetype, contentType: filetype, // 微信识别需要
filename: filename, filename: filename, // 微信识别需要
knownLength: fileLength, knownLength: fileLength,
}); });
if (type === 'video') { if (type === 'video') {
@ -539,8 +539,8 @@ class WechatPublicInstance {
const { media, filetype, filename, fileLength } = options; const { media, filetype, filename, fileLength } = options;
const formData = new form_data_1.default(); const formData = new form_data_1.default();
formData.append('media', media, { formData.append('media', media, {
contentType: filetype, contentType: filetype, // 微信识别需要
filename: filename, filename: filename, // 微信识别需要
knownLength: fileLength, knownLength: fileLength,
}); });
const headers = formData.getHeaders(); const headers = formData.getHeaders();
@ -560,8 +560,8 @@ class WechatPublicInstance {
const { type, media, filetype, filename, fileLength } = options; const { type, media, filetype, filename, fileLength } = options;
const formData = new form_data_1.default(); const formData = new form_data_1.default();
formData.append('media', media, { formData.append('media', media, {
contentType: filetype, contentType: filetype, // 微信识别需要
filename: filename, filename: filename, // 微信识别需要
knownLength: fileLength, knownLength: fileLength,
}); });
const headers = formData.getHeaders(); const headers = formData.getHeaders();

1
lib/types/ALiYun.d.ts vendored Normal file
View File

@ -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';

2
lib/types/ALiYun.js Normal file
View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -4,10 +4,13 @@
*/ */
export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket'; export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket';
export type ReqOptionProps = { export type ReqOptionProps = {
payload: string;
path: string; path: string;
host: string; host: string;
date: string;
headers: any; 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'; export type CTYunZone = 'hazz' | 'lnsy' | 'sccd' | 'xjwlmq' | 'gslz' | 'sdqd' | 'gzgy' | 'hbwh' | 'xzls' | 'ahwh' | 'gdsz' | 'jssz' | 'sh2';

1
lib/types/TencentYun.d.ts vendored Normal file
View File

@ -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';

2
lib/types/TencentYun.js Normal file
View File

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@ -1,3 +1,5 @@
export * from './Wechat'; export * from './Wechat';
export * from './Qiniu'; export * from './Qiniu';
export * from './CTYun'; export * from './CTYun';
export * from './ALiYun';
export * from './TencentYun';

View File

@ -4,3 +4,5 @@ const tslib_1 = require("tslib");
tslib_1.__exportStar(require("./Wechat"), exports); tslib_1.__exportStar(require("./Wechat"), exports);
tslib_1.__exportStar(require("./Qiniu"), exports); tslib_1.__exportStar(require("./Qiniu"), exports);
tslib_1.__exportStar(require("./CTYun"), exports); tslib_1.__exportStar(require("./CTYun"), exports);
tslib_1.__exportStar(require("./ALiYun"), exports);
tslib_1.__exportStar(require("./TencentYun"), exports);

View File

@ -19,6 +19,7 @@
}, },
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/ali-oss": "^6.16.11",
"@types/node": "^20.6.1", "@types/node": "^20.6.1",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"tslib": "^2.4.0", "tslib": "^2.4.0",
@ -27,8 +28,10 @@
"dependencies": { "dependencies": {
"@alicloud/dysmsapi20170525": "^2.0.24", "@alicloud/dysmsapi20170525": "^2.0.24",
"@alicloud/pop-core": "^1.7.12", "@alicloud/pop-core": "^1.7.12",
"ali-oss": "^6.20.0",
"aws-sdk": "^2.1499.0", "aws-sdk": "^2.1499.0",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"cos-wx-sdk-v5": "^1.7.1",
"isomorphic-fetch": "^3.0.0", "isomorphic-fetch": "^3.0.0",
"oak-domain": "file:../oak-domain", "oak-domain": "file:../oak-domain",
"tencentcloud-sdk-nodejs": "^4.0.746", "tencentcloud-sdk-nodejs": "^4.0.746",

26
src/ALiYunSDK.ts Normal file
View File

@ -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 };

View File

@ -1,16 +1,13 @@
import { CTYunInstance } from './service/ctyun/CTYun'; import { CTYunInstance } from './service/ctyun/CTYun';
class CTYunSDK { class CTYunSDK {
ctyunMap: Record<string, CTYunInstance>; ctyunMap: Record<string, CTYunInstance>; //oss
constructor() { constructor() {
this.ctyunMap = {}; this.ctyunMap = {};
} }
getInstance( getInstance(accessKey: string, accessSecret: string) {
accessKey: string,
accessSecret: string,
) {
if (this.ctyunMap[accessKey]) { if (this.ctyunMap[accessKey]) {
return this.ctyunMap[accessKey]; return this.ctyunMap[accessKey];
} }

26
src/TencentYunSDK.ts Normal file
View File

@ -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 };

View File

@ -6,19 +6,26 @@ import SmsSdk, {
AliSmsInstance, AliSmsInstance,
CTYunSmsInstance, CTYunSmsInstance,
} from './SmsSdk'; } 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 * from './service/amap/Amap';
export { export {
AmapSDK, AmapSDK,
QiniuSDK, QiniuSDK,
WechatSDK, WechatSDK,
CTYunSDk, CTYunSDK,
ALiYunSDK,
TencentYunSDK,
CTYunInstance, CTYunInstance,
WechatMpInstance, WechatMpInstance,
WechatPublicInstance, WechatPublicInstance,
WechatWebInstance, WechatWebInstance,
QiniuCloudInstance, QiniuCloudInstance,
ALiYunInstance,
TencentYunInstance,
SmsSdk, SmsSdk,
TencentSmsInstance, TencentSmsInstance,
AliSmsInstance, AliSmsInstance,

View File

@ -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;
}
}
}

View File

@ -5,7 +5,8 @@ import {
OakExternalException, OakExternalException,
OakNetworkException, OakNetworkException,
} from 'oak-domain/lib/types/Exception'; } from 'oak-domain/lib/types/Exception';
const CTYun_ENDPOINT_LIST = {
const CTYun_ENDPOINT_LIST: Record<CTYunZone, { ul: string }> = {
hazz: { hazz: {
ul: 'oos-hazz.ctyunapi.cn', ul: 'oos-hazz.ctyunapi.cn',
}, },
@ -47,24 +48,30 @@ const CTYun_ENDPOINT_LIST = {
}, },
}; };
const serviceName = 's3'; const serviceName = 's3';
const v4Identifier = 'aws4_request'; const v4Identifier = 'aws4_request';
const expiresHeader = "presigned-expires"; const expiresHeader = 'presigned-expires';
const unsignableHeaders = ["authorization", "x-ctyun-data-location", "content-length", "user-agent", expiresHeader, "expect", "x-amzn-trace-id"]; const unsignableHeaders = [
'authorization',
'x-ctyun-data-location',
'content-length',
'user-agent',
expiresHeader,
'expect',
'x-amzn-trace-id',
];
export class CTYunInstance { export class CTYunInstance {
private accessKey: string; private accessKey: string;
private secretKey: string; private secretKey: string;
constructor(accessKey: string, secretKey: string,) { constructor(accessKey: string, secretKey: string) {
this.accessKey = accessKey; this.accessKey = accessKey;
this.secretKey = secretKey; this.secretKey = secretKey;
} }
getUploadInfo(bucket: string, zone: CTYunZone, key?: string) { getUploadInfo(bucket: string, zone: CTYunZone, key?: string) {
try { try {
// const uploadToken = this.getToken(zone, bucket, actions);
const signInfo = this.getSignInfo(bucket); const signInfo = this.getSignInfo(bucket);
return { return {
key, key,
@ -82,30 +89,30 @@ export class CTYunInstance {
getSignInfo(bucket: string) { getSignInfo(bucket: string) {
// 对于policy里的expiration我在天翼云的文档里没有找到具体的说明但是这个字段不填入就会请求失败 // 对于policy里的expiration我在天翼云的文档里没有找到具体的说明但是这个字段不填入就会请求失败
// 设置一个明天过期的时间 // 设置一个明天过期的时间
const now = new Date(); const expiration = new Date();
now.setDate(now.getDate() + 1); expiration.setDate(expiration.getDate() + 1);
const tomorrow = now.toISOString();
const policy = { const policy = {
Version: "2012-10-17", expiration: expiration.toISOString(),
Statement: [{ conditions: [
Effect: "Allow", {
Action: ["oos:*"], bucket: bucket,
Resource: `arn:ctyun:oos:::${bucket} /*` },
}], ['starts-with', '$key', 'extraFile'],
expiration: tomorrow, ],
conditions: [{ Version: '2012-10-17',
bucket: bucket, Statement: [
}, [ {
"starts-with", Effect: 'Allow',
"$key", Action: ['oos:*'],
"extraFile", Resource: `arn:ctyun:oos:::${bucket} /*`,
]] },
} ],
};
const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy)); const encodePolicy = this.urlSafeBase64Encode(JSON.stringify(policy));
const signature = this.hmacSha1(encodePolicy, this.secretKey); const signature = this.hmacSha1(encodePolicy, this.secretKey);
return { return {
encodePolicy, encodePolicy,
signature signature,
}; };
} }
@ -124,152 +131,252 @@ export class CTYunInstance {
return this.base64ToUrlSafe(encoded); return this.base64ToUrlSafe(encoded);
} }
// 当初就不应该封装天翼云,文档不全,找技术要签名代码还得自己去看源码,恶心
// 下面的代码是根据天翼云生成签名源码改动得来 oos-js-sdk-6.2.js
async removeFile(bucket: string, zone: CTYunZone, key: string) { async removeFile(bucket: string, zone: CTYunZone, key: string) {
const path = `/${bucket}/${key}`; const path = `/${key}`;
const host = `${CTYun_ENDPOINT_LIST[zone].ul}`; const host = `${bucket}.${CTYun_ENDPOINT_LIST[zone].ul}`;
const url = `https://${host}${path}`; 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> = { const headers: Record<string, string> = {
"Content-Type": "application/octet-stream; charset=UTF-8", 'Content-Type': 'application/xml; charset=utf-8',
"Host": "oos-hbwh.ctyunapi.cn", Host: host,
"X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD", 'x-amz-content-sha256': this.calculatePayloadHash(payload),
"X-Amz-User-Agent": "aws-sdk-js/2.296.0 callback" 'x-amz-date': date,
} };
headers["X-Amz-Date"] = date;
const reqOptions: ReqOptionProps = { const reqOptions: ReqOptionProps = {
path,
host,
date,
headers, headers,
method: "DELETE", method: method,
} payload: payload,
host,
path,
queryParameters: {},
service,
date,
};
const authorization = this.getAuthorization(reqOptions, zone); const authorization = this.getAuthorization(reqOptions, zone);
headers['Authorization'] = authorization;
let response: Response;
try { try {
await fetch(url, { response = await fetch(url, {
method: 'DELETE', method,
headers, headers: {
}) ...headers,
Authorization: authorization,
},
});
} catch (err) { } catch (err) {
throw new OakNetworkException(); 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) { getAuthorization(reqOptions: ReqOptionProps, zone: CTYunZone) {
const { headers, date } = reqOptions; const {
const parts = []; headers,
const credString = [date.substring(0, 8), zone, serviceName, v4Identifier].join("/"); host,
parts.push("AWS4-HMAC-SHA256" + " Credential=" + this.accessKey + "/" + credString); path,
parts.push("SignedHeaders=" + this.signedHeaders(headers)); method,
parts.push("Signature=" + this.signatureFn(reqOptions, zone)); payload,
return parts.join(", "); 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) { private getSignatureKey(dateStamp: string, zone: string, service: string) {
const { date } = reqOptions; const kDate = crypto
var signingKey = this.getSigningKey(date.substring(0, 8), zone); .createHmac('sha256', `AWS4${this.secretKey}`)
return this.hmacSha256(signingKey, this.stringToSign(reqOptions, zone), "hex"); .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) { private buildCanonicalRequest(
const { date, path, method, headers } = reqOptions; method: ReqOptionProps['method'],
var parts = []; path: string,
parts.push("AWS4-HMAC-SHA256"); queryParameters: Record<string, string>,
parts.push(date); headers: Record<string, string>,
parts.push([date.substring(0, 8), zone, serviceName, v4Identifier].join("/")); payloadHash: string
const canonicalStr = this.canonicalString(path, method, headers); ) {
const buffer = Buffer.from(canonicalStr); const canonicalQuerystring =
const encodeStr = crypto.createHash('sha256').update(buffer).digest('hex'); this.buildCanonicalQueryString(queryParameters);
parts.push(encodeStr); const canonicalHeaders = Object.keys(headers)
return parts.join("\n"); .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>) { private buildCanonicalQueryString(queryParameters: Record<string, string>) {
const keys: string[] = []; const keys = Object.keys(queryParameters).sort();
this.each(headers, (key) => { const canonicalQueryString = keys
key = key.toLowerCase(); .map(
if (this.isSignableHeader(key)) keys.push(key); (key) =>
}); `${encodeURIComponent(key)}=${encodeURIComponent(
return keys.sort().join(";"); queryParameters[key]
)}`
)
.join('&');
return canonicalQueryString;
} }
canonicalString(path: string, method: string, headers: Record<string, string>) { private calculatePayloadHash(payload: string) {
const parts = []; const hash = crypto.createHash('sha256');
parts.push(method); hash.update(payload);
parts.push(path); return hash.digest('hex');
parts.push("");
parts.push(this.canonicalHeaders(headers) + "\n");
parts.push(this.signedHeaders(headers));
parts.push("UNSIGNED-PAYLOAD");
return parts.join("\n");
} }
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;
}
} }

View File

@ -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;
}
}
}

35
src/types/ALiYun.ts Normal file
View File

@ -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';

View File

@ -6,11 +6,14 @@
export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket'; export type Action = '*' | 'PutObject' | 'GetObject' | 'DeleteObject' | 'ListBucket';
export type ReqOptionProps = { export type ReqOptionProps = {
payload: string;
path: string; path: string;
host: string; host: string;
headers: any;
method: 'DELETE' | 'GET' | 'POST' | 'PUT' | 'HEAD';
queryParameters: Record<string, any>;
service: string;
date: 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'; export type CTYunZone = 'hazz' | 'lnsy' | 'sccd' | 'xjwlmq' | 'gslz' | 'sdqd' | 'gzgy' | 'hbwh' | 'xzls' | 'ahwh' | 'gdsz' | 'jssz' | 'sh2';

13
src/types/TencentYun.ts Normal file
View File

@ -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';

View File

@ -1,3 +1,5 @@
export * from './Wechat'; export * from './Wechat';
export * from './Qiniu'; export * from './Qiniu';
export * from './CTYun'; export * from './CTYun';
export * from './ALiYun';
export * from './TencentYun';

63
test/testCos.ts Normal file
View File

@ -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();