n8n-nodes-cos-uploadfile
Version:
A custom n8n node to upload files to Tencent Cloud Object Storage (COS).
274 lines (255 loc) • 9.46 kB
text/typescript
import {
INodeType,
INodeTypeDescription,
IExecuteFunctions,
INodeExecutionData,
NodePropertyTypes,
IDataObject,
} from 'n8n-workflow';
// 导入腾讯云 COS SDK
import COS from 'cos-nodejs-sdk-v5';
// 导入 Node.js 的文件系统模块 (使用 promises API 更方便处理异步)
import * as fs from 'fs/promises'; // Changed to fs/promises for async readFile
import * as path from 'path'; // Useful for path manipulation
export class CosUpload implements INodeType {
description: INodeTypeDescription = {
displayName: '腾讯云 COS 文件上传',
name: 'cosUpload',
icon: 'fa:cloud-arrow-up', // 使用一个更贴切的图标
group: ['storage'], // 分组为存储类
version: 1,
description: '上传本地文件到腾讯云对象存储 COS',
defaults: {
name: 'COS 文件上传',
},
// 修复 inputs 和 outputs 的类型错误
inputs: ['main'] as any, // 使用 any 绕过严格的类型检查
outputs: ['main'] as any, // 使用 any 绕过严格的类型检查
// 定义节点所需的凭证类型
credentials: [
{
name: 'tencentCosApi', // 凭证的内部名称,后续会创建
required: true,
},
],
properties: [
// 上传模式选择
{
displayName: '上传模式',
name: 'uploadMode',
type: 'options' as NodePropertyTypes,
options: [
{ name: '本地文件上传', value: 'localFile' },
{ name: 'HTML内容上传', value: 'htmlContent' },
],
default: 'localFile',
description: '选择上传模式:本地文件或HTML内容',
required: true,
},
// 文件路径
{
displayName: '本地文件路径',
name: 'filePath',
type: 'string' as NodePropertyTypes,
default: '',
placeholder: '/path/to/your/file.txt',
description: '要上传的本地文件的绝对路径',
required: true,
displayOptions: {
show: {
uploadMode: ['localFile'],
},
},
},
// HTML内容
{
displayName: 'HTML内容',
name: 'htmlContent',
type: 'string' as NodePropertyTypes,
typeOptions: {
rows: 10,
},
default: '',
placeholder: '<html><body><h1>Hello World</h1></body></html>',
description: '要上传的HTML内容',
required: true,
displayOptions: {
show: {
uploadMode: ['htmlContent'],
},
},
},
// HTML文件名
{
displayName: 'HTML文件名',
name: 'htmlFileName',
type: 'string' as NodePropertyTypes,
default: 'index.html',
placeholder: 'index.html',
description: '生成的HTML文件名',
required: true,
displayOptions: {
show: {
uploadMode: ['htmlContent'],
},
},
},
// 存储桶名称
{
displayName: '存储桶名称 (Bucket)',
name: 'bucket',
type: 'string' as NodePropertyTypes,
default: '',
placeholder: 'your-bucket-name-125xxxxxxxx',
description: '您的腾讯云 COS 存储桶名称 (包含 APPID)',
required: true,
},
// 存储桶地域
{
displayName: '存储桶地域 (Region)',
name: 'region',
type: 'options' as NodePropertyTypes, // 使用选项类型
options: [
{ name: '北京 (ap-beijing)', value: 'ap-beijing' },
{ name: '南京 (ap-nanjing)', value: 'ap-nanjing' },
{ name: '上海 (ap-shanghai)', value: 'ap-shanghai' },
{ name: '广州 (ap-guangzhou)', value: 'ap-guangzhou' },
{ name: '成都 (ap-chengdu)', value: 'ap-chengdu' },
{ name: '重庆 (ap-chongqing)', value: 'ap-chongqing' },
{ name: '香港 (ap-hongkong)', value: 'ap-hongkong' },
{ name: '新加坡 (ap-singapore)', value: 'ap-singapore' },
{ name: '硅谷 (na-siliconvalley)', value: 'na-siliconvalley' },
// ... 更多地域可以根据需要添加
],
default: 'ap-guangzhou',
description: '存储桶所在的地域',
required: true,
},
// 存储在桶里的对象键
{
displayName: '对象键 (Key)',
name: 'key',
type: 'string' as NodePropertyTypes,
default: '',
placeholder: 'images/my-photo.jpg',
description: '文件在 COS 中的路径和名称',
required: true,
},
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData(); // 获取输入数据 (如果需要从上一个节点获取文件路径等)
const returnData: INodeExecutionData[] = [];
// 获取凭证
const credentials = (await this.getCredentials('tencentCosApi')) as unknown as IDataObject;
const secretId = credentials.secretId as string;
const secretKey = credentials.secretKey as string;
// 获取节点参数
const uploadMode = this.getNodeParameter('uploadMode', 0) as string;
const bucket = this.getNodeParameter('bucket', 0) as string;
const region = this.getNodeParameter('region', 0) as string;
const key = this.getNodeParameter('key', 0) as string;
// 根据上传模式获取相应参数
let filePath = '';
let htmlContent = '';
let htmlFileName = '';
if (uploadMode === 'localFile') {
filePath = this.getNodeParameter('filePath', 0) as string;
} else if (uploadMode === 'htmlContent') {
htmlContent = this.getNodeParameter('htmlContent', 0) as string;
htmlFileName = this.getNodeParameter('htmlFileName', 0) as string;
}
// 确保 SecretId 和 SecretKey 存在
if (!secretId || !secretKey) {
throw new Error('腾讯云 COS 凭证 (SecretId 和 SecretKey) 未配置或不完整。');
}
// 初始化 COS 实例
const cos = new COS({
SecretId: secretId,
SecretKey: secretKey,
});
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
if (uploadMode === 'localFile') { // 本地文件上传
// 执行文件上传
const uploadResult = await new Promise<COS.PutObjectResult>((resolve, reject) => {
cos.uploadFile(
{
Bucket: bucket,
Region: region,
Key: key,
FilePath: filePath, // 本地文件路径
},
function (err, data) {
if (err) {
return reject(err);
}
resolve(data);
},
);
});
// 将上传结果添加到输出数据
returnData.push({
json: {
success: true,
message: `文件 '${filePath}' 成功上传到 COS`,
cosResponse: uploadResult as unknown as IDataObject,
},
pairedItem: {
item: itemIndex,
},
});
} else if (uploadMode === 'htmlContent') {
// HTML内容上传
const htmlFilePath = path.join(process.cwd(), htmlFileName);
// 先写入HTML内容到临时文件
await fs.writeFile(htmlFilePath, htmlContent);
// 执行文件上传
const uploadResult = await new Promise<COS.PutObjectResult>((resolve, reject) => {
cos.uploadFile(
{
Bucket: bucket,
Region: region,
Key: key,
FilePath: htmlFilePath, // 使用刚创建的临时文件路径
},
function (err, data) {
if (err) {
return reject(err);
}
resolve(data);
},
);
});
// 将上传结果添加到输出数据
returnData.push({
json: {
success: true,
message: `HTML内容成功上传到 COS`,
cosResponse: uploadResult as unknown as IDataObject,
},
pairedItem: {
item: itemIndex,
},
});
// 清理临时文件
await fs.unlink(htmlFilePath);
}
} catch (error: unknown) {
const errorMessage = (error instanceof Error) ? error.message : String(error);
const files = await fs.readdir('/home/node/.cache/n8n/public');
returnData.push({
json: {
success: false,
message: `文件上传失败 ${JSON.stringify(files)}`,
error: errorMessage,
},
pairedItem: {
item: itemIndex,
},
});
}
}
return this.prepareOutputData(returnData);
}
}