feat(dianziqian): url保存附件支持json格式 (#1517)
Co-authored-by: sealday <zhanglin@daoyoucloud.com> Co-authored-by: TomyJan <tomyjan6@gmail.com> Reviewed-on: daoyoucloud/tachybase#1517 Co-authored-by: wanggang <1366376228@qq.com> Co-committed-by: wanggang <1366376228@qq.com>
This commit is contained in:
		
							parent
							
								
									dd38db675d
								
							
						
					
					
						commit
						3df751063d
					
				@ -41,12 +41,30 @@ export default class extends Instruction {
 | 
				
			|||||||
        placeholder: 'https://www.tachybase.com',
 | 
					        placeholder: 'https://www.tachybase.com',
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    //是否携带Authorization在请求头中
 | 
				
			||||||
 | 
					    needAuthorization: {
 | 
				
			||||||
 | 
					      type: 'string',
 | 
				
			||||||
 | 
					      required: true,
 | 
				
			||||||
 | 
					      title: `{{t("needAuthorization", { ns: "${NAMESPACE}" })}}`,
 | 
				
			||||||
 | 
					      'x-decorator': 'FormItem',
 | 
				
			||||||
 | 
					      'x-component': 'Select',
 | 
				
			||||||
 | 
					      'x-component-props': {
 | 
				
			||||||
 | 
					        showSearch: false,
 | 
				
			||||||
 | 
					        allowClear: false,
 | 
				
			||||||
 | 
					        className: 'auto-width',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      enum: [
 | 
				
			||||||
 | 
					        { label: '是', value: true },
 | 
				
			||||||
 | 
					        { label: '否', value: false },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      default: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    headers: {
 | 
					    headers: {
 | 
				
			||||||
      type: 'array',
 | 
					      type: 'array',
 | 
				
			||||||
      'x-component': 'ArrayItems',
 | 
					      'x-component': 'ArrayItems',
 | 
				
			||||||
      'x-decorator': 'FormItem',
 | 
					      'x-decorator': 'FormItem',
 | 
				
			||||||
      title: `{{t("Headers", { ns: "${NAMESPACE}" })}}`,
 | 
					      title: `{{t("Headers", { ns: "${NAMESPACE}" })}}`,
 | 
				
			||||||
      description: `{{t('"Content-Type" only support "application/json", and no need to specify', { ns: "${NAMESPACE}" })}}`,
 | 
					      description: `{{t('"Content-Type" only support "application/json and mutipart/form-data" ', { ns: "${NAMESPACE}" })}}`,
 | 
				
			||||||
      items: {
 | 
					      items: {
 | 
				
			||||||
        type: 'object',
 | 
					        type: 'object',
 | 
				
			||||||
        properties: {
 | 
					        properties: {
 | 
				
			||||||
 | 
				
			|||||||
@ -273,13 +273,14 @@
 | 
				
			|||||||
  "Withdrawn": "Withdrawn",
 | 
					  "Withdrawn": "Withdrawn",
 | 
				
			||||||
  "Workflow todos": "Workflow todos",
 | 
					  "Workflow todos": "Workflow todos",
 | 
				
			||||||
  "Workflow": "Workflow",
 | 
					  "Workflow": "Workflow",
 | 
				
			||||||
  "\"Content-Type\" only support \"application/json\", and no need to specify": "\"Content-Type\" only support \"application/json\", and no need to specify",
 | 
					  "\"Content-Type\" only support \"application/json\" and \"multipart/form-data\" ": "\"Content-Type\" only support \"application/json\" and \"multipart/form-data\" ",
 | 
				
			||||||
  "concat": "concat",
 | 
					  "concat": "concat",
 | 
				
			||||||
  "ms": "ms",
 | 
					  "ms": "ms",
 | 
				
			||||||
  "Resubmit": "Resubmit",
 | 
					  "Resubmit": "Resubmit",
 | 
				
			||||||
  "Are you sure you want to resubmit it?": "Are you sure you want to resubmit it?",
 | 
					  "Are you sure you want to resubmit it?": "Are you sure you want to resubmit it?",
 | 
				
			||||||
  "MyLaunch":"MyLaunch",
 | 
					  "MyLaunch": "MyLaunch",
 | 
				
			||||||
  "CarbonCopy":"CarbonCopy",
 | 
					  "CarbonCopy": "CarbonCopy",
 | 
				
			||||||
  "ApprovalCarbonCopy":"Approval:CarbonCopy",
 | 
					  "ApprovalCarbonCopy": "Approval:CarbonCopy",
 | 
				
			||||||
  "Approval Launch":"Approval Launch"
 | 
					  "Approval Launch": "Approval Launch",
 | 
				
			||||||
 | 
					  "needAuthorization": "needAuthorization"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -355,13 +355,14 @@
 | 
				
			|||||||
  "Workflow will be triggered before or after submitting succeeded based on workflow type.": "工作流会基于其类型在提交成功之前或之后触发。",
 | 
					  "Workflow will be triggered before or after submitting succeeded based on workflow type.": "工作流会基于其类型在提交成功之前或之后触发。",
 | 
				
			||||||
  "Workflow will be triggered directly once the button clicked, without data saving.": "按钮点击后直接触发工作流,但不会保存数据。",
 | 
					  "Workflow will be triggered directly once the button clicked, without data saving.": "按钮点击后直接触发工作流,但不会保存数据。",
 | 
				
			||||||
  "Workflow": "工作流",
 | 
					  "Workflow": "工作流",
 | 
				
			||||||
  "\"Content-Type\" only support \"application/json\", and no need to specify": "\"Content-Type\" 请求头仅支持 \"application/json\",无需填写",
 | 
					  "\"Content-Type\" only support \"application/json\" and \"multipart/form-data\" ": "\"Content-Type\" 请求头仅支持 \"application/json\" 和 \"multipart/form-data \" ",
 | 
				
			||||||
  "concat": "连接",
 | 
					  "concat": "连接",
 | 
				
			||||||
  "ms": "毫秒",
 | 
					  "ms": "毫秒",
 | 
				
			||||||
  "Resubmit": "重新发起",
 | 
					  "Resubmit": "重新发起",
 | 
				
			||||||
  "Are you sure you want to resubmit it?": "是否重新复制一份?",
 | 
					  "Are you sure you want to resubmit it?": "是否重新复制一份?",
 | 
				
			||||||
  "MyLaunch":"我的发起",
 | 
					  "MyLaunch": "我的发起",
 | 
				
			||||||
  "CarbonCopy":"抄送",
 | 
					  "CarbonCopy": "抄送",
 | 
				
			||||||
  "ApprovalCarbonCopy":"审批:抄送",
 | 
					  "ApprovalCarbonCopy": "审批:抄送",
 | 
				
			||||||
  "Approval Launch":"审批发起"
 | 
					  "Approval Launch": "审批发起",
 | 
				
			||||||
 | 
					  "needAuthorization": "是否携带权限校验"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,7 +1,13 @@
 | 
				
			|||||||
 | 
					import fs from 'fs';
 | 
				
			||||||
 | 
					import os from 'os';
 | 
				
			||||||
 | 
					import path from 'path';
 | 
				
			||||||
import { Gateway } from '@tachybase/server';
 | 
					import { Gateway } from '@tachybase/server';
 | 
				
			||||||
 | 
					import { uid } from '@tachybase/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import axios, { AxiosRequestConfig } from 'axios';
 | 
					import axios, { AxiosRequestConfig } from 'axios';
 | 
				
			||||||
 | 
					import FormData from 'form-data';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
 | 
					import mime from 'mime-types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { FlowNodeModel, Instruction, JOB_STATUS, Processor } from '../..';
 | 
					import { FlowNodeModel, Instruction, JOB_STATUS, Processor } from '../..';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,19 +20,54 @@ export type RequestConfig = Pick<AxiosRequestConfig, 'url' | 'method' | 'params'
 | 
				
			|||||||
  headers: Array<Header>;
 | 
					  headers: Array<Header>;
 | 
				
			||||||
  ignoreFail: boolean;
 | 
					  ignoreFail: boolean;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					async function downloadToStream(resourceUrl, Headers, param, body) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    // 发起 GET 请求,设置响应类型为流
 | 
				
			||||||
 | 
					    const response = await axios({
 | 
				
			||||||
 | 
					      method: 'GET',
 | 
				
			||||||
 | 
					      url: resourceUrl,
 | 
				
			||||||
 | 
					      headers: Headers,
 | 
				
			||||||
 | 
					      params: param,
 | 
				
			||||||
 | 
					      data: body,
 | 
				
			||||||
 | 
					      responseType: 'stream',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    // 根据 MIME 类型获取文件扩展名
 | 
				
			||||||
 | 
					    const ext = mime.extension(Headers['Content-Type']);
 | 
				
			||||||
 | 
					    let tempFileName;
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					      tempFileName = `${uid()}.${ext}`;
 | 
				
			||||||
 | 
					    } while (fs.existsSync(tempFileName));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const tempFilePath = path.join(os.tmpdir(), tempFileName);
 | 
				
			||||||
 | 
					    const writeStream = fs.createWriteStream(tempFilePath);
 | 
				
			||||||
 | 
					    // 将响应流写入临时文件
 | 
				
			||||||
 | 
					    response.data.pipe(writeStream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					      writeStream.on('finish', () => {
 | 
				
			||||||
 | 
					        // 完成写入后,返回一个读取流
 | 
				
			||||||
 | 
					        resolve(fs.createReadStream(tempFilePath));
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      writeStream.on('error', (error) => {
 | 
				
			||||||
 | 
					        fs.unlinkSync(tempFilePath);
 | 
				
			||||||
 | 
					        reject(error);
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    console.error('下载文件时出错:', error);
 | 
				
			||||||
 | 
					    throw error;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function request(config, context) {
 | 
					async function request(config, context) {
 | 
				
			||||||
  // default headers
 | 
					  // default headers
 | 
				
			||||||
  const { token, origin } = context;
 | 
					  const { token, origin } = context;
 | 
				
			||||||
  const { method = 'POST', data, timeout = 5000 } = config;
 | 
					  const { method = 'POST', needAuthorization = true, data, timeout = 5000 } = config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const originUrl = config.url?.trim() || '';
 | 
					  const originUrl = config.url?.trim() || '';
 | 
				
			||||||
  const url = originUrl.startsWith('http') ? originUrl : `${origin}${originUrl}`;
 | 
					  const url = originUrl.startsWith('http') ? originUrl : `${origin}${originUrl}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let headers = (config.headers ?? []).reduce((result, header) => {
 | 
					  let headers = (config.headers ?? []).reduce((result, header) => {
 | 
				
			||||||
    if (header.name.toLowerCase() === 'content-type') {
 | 
					 | 
				
			||||||
      return result;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return Object.assign(result, { [header.name]: header.value });
 | 
					    return Object.assign(result, { [header.name]: header.value });
 | 
				
			||||||
  }, {});
 | 
					  }, {});
 | 
				
			||||||
  const params = (config.params ?? []).reduce(
 | 
					  const params = (config.params ?? []).reduce(
 | 
				
			||||||
@ -34,22 +75,52 @@ async function request(config, context) {
 | 
				
			|||||||
    {},
 | 
					    {},
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO(feat): only support JSON type for now, should support others in future
 | 
					  const requestParams: AxiosRequestConfig = {
 | 
				
			||||||
  headers['Content-Type'] = 'application/json';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // let temp = context.get('headers');
 | 
					 | 
				
			||||||
  headers = {
 | 
					 | 
				
			||||||
    Authorization: 'Bearer ' + token,
 | 
					 | 
				
			||||||
    ...headers,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
  return axios.request({
 | 
					 | 
				
			||||||
    url,
 | 
					    url,
 | 
				
			||||||
    method,
 | 
					    method,
 | 
				
			||||||
    headers,
 | 
					    headers,
 | 
				
			||||||
    params,
 | 
					    params,
 | 
				
			||||||
    data,
 | 
					    data,
 | 
				
			||||||
    timeout,
 | 
					    timeout,
 | 
				
			||||||
  });
 | 
					  };
 | 
				
			||||||
 | 
					  // let temp = context.get('headers');
 | 
				
			||||||
 | 
					  if (needAuthorization) {
 | 
				
			||||||
 | 
					    requestParams.headers = {
 | 
				
			||||||
 | 
					      Authorization: 'Bearer ' + token,
 | 
				
			||||||
 | 
					      ...headers,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (headers['Content-Type'] === 'multipart/form-data') {
 | 
				
			||||||
 | 
					    //workflow  contentType类型
 | 
				
			||||||
 | 
					    const formData = new FormData();
 | 
				
			||||||
 | 
					    for (const [key, value] of Object.entries(data)) {
 | 
				
			||||||
 | 
					      if (key === 'file') {
 | 
				
			||||||
 | 
					        const { resourceUrl, params: Params, headers: Header, body: Body } = data[key];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (Header['Content-Type'] === 'multipart/form-data' && Body) {
 | 
				
			||||||
 | 
					          //resource  contentType类型
 | 
				
			||||||
 | 
					          const formData = new FormData();
 | 
				
			||||||
 | 
					          Object.entries(Body).forEach(([key, value]) => {
 | 
				
			||||||
 | 
					            formData.append(key, value);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          config.data = formData;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          config.data = Body;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const stream = await downloadToStream(resourceUrl, Header, Params, Body);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formData.append('file', stream);
 | 
				
			||||||
 | 
					        headers = { ...formData.getHeaders() };
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        formData.append(key, data[key]);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    requestParams.data = formData;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    headers['Content-Type'] = 'application/json';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return axios.request(requestParams);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class extends Instruction {
 | 
					export default class extends Instruction {
 | 
				
			||||||
@ -61,11 +132,7 @@ export default class extends Instruction {
 | 
				
			|||||||
    const context = { token, origin };
 | 
					    const context = { token, origin };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const config = processor.getParsedValue(node.config, node.id) as RequestConfig;
 | 
					    const config = processor.getParsedValue(node.config, node.id) as RequestConfig;
 | 
				
			||||||
    // delete user token if outer http
 | 
					
 | 
				
			||||||
    if (config.url?.startsWith('http')) {
 | 
					 | 
				
			||||||
      delete context.token;
 | 
					 | 
				
			||||||
      delete context.origin;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const { workflow } = processor.execution;
 | 
					    const { workflow } = processor.execution;
 | 
				
			||||||
    const sync = this.workflow.isWorkflowSync(workflow);
 | 
					    const sync = this.workflow.isWorkflowSync(workflow);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { parseCollectionName } from '@tachybase/data-source-manager';
 | 
				
			|||||||
import { Gateway } from '@tachybase/server';
 | 
					import { Gateway } from '@tachybase/server';
 | 
				
			||||||
import { uid } from '@tachybase/utils';
 | 
					import { uid } from '@tachybase/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import axios from 'axios';
 | 
					import axios, { AxiosRequestConfig } from 'axios';
 | 
				
			||||||
import FormData from 'form-data';
 | 
					import FormData from 'form-data';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
import mime from 'mime-types';
 | 
					import mime from 'mime-types';
 | 
				
			||||||
@ -33,11 +33,24 @@ export class CreateInstruction extends Instruction {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const userId = _.get(processor.getScope(node.id), '$context.user.id', '');
 | 
					    const userId = _.get(processor.getScope(node.id), '$context.user.id', '');
 | 
				
			||||||
    const token = this.workflow.app.authManager.jwt.sign({ userId });
 | 
					    const token = this.workflow.app.authManager.jwt.sign({ userId });
 | 
				
			||||||
 | 
					    const isJSON = (str) => {
 | 
				
			||||||
    const handleUrl = async (url) => {
 | 
					      try {
 | 
				
			||||||
 | 
					        return JSON.parse(str);
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    //目前可处理url,json对象,base64
 | 
				
			||||||
 | 
					    const handleResource = async (resource) => {
 | 
				
			||||||
 | 
					      const parseRes = isJSON(resource);
 | 
				
			||||||
 | 
					      const config: AxiosRequestConfig<any> = {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: resource,
 | 
				
			||||||
 | 
					        responseType: 'stream',
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
      const form = new FormData();
 | 
					      const form = new FormData();
 | 
				
			||||||
      if (url.startsWith('data:')) {
 | 
					      if (resource.startsWith('data:')) {
 | 
				
			||||||
        const matches = url.match(/^data:(.+);base64,(.+)$/);
 | 
					        const matches = resource.match(/^data:(.+);base64,(.+)$/);
 | 
				
			||||||
        if (matches) {
 | 
					        if (matches) {
 | 
				
			||||||
          const contentType = matches[1];
 | 
					          const contentType = matches[1];
 | 
				
			||||||
          const base64Data = matches[2];
 | 
					          const base64Data = matches[2];
 | 
				
			||||||
@ -52,13 +65,33 @@ export class CreateInstruction extends Instruction {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          throw new Error('Invalid data URL format');
 | 
					          throw new Error('Invalid data URL format');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } else if (parseRes) {
 | 
				
			||||||
 | 
					        const { resourceUrl, params, headers, body } = parseRes;
 | 
				
			||||||
 | 
					        config.url = resourceUrl;
 | 
				
			||||||
 | 
					        config.params = params;
 | 
				
			||||||
 | 
					        config.headers = headers;
 | 
				
			||||||
 | 
					        if (headers['content-type'] === 'multipart/form-data') {
 | 
				
			||||||
 | 
					          const formData = new FormData();
 | 
				
			||||||
 | 
					          Object.entries(body).forEach(([key, value]) => {
 | 
				
			||||||
 | 
					            formData.append(key, value);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          config.data = formData;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          config.data = body;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const response = await axios(config);
 | 
				
			||||||
 | 
					        const contentType = response.headers['content-type'];
 | 
				
			||||||
 | 
					        // 根据 MIME 类型获取文件扩展名
 | 
				
			||||||
 | 
					        const ext = mime.extension(contentType);
 | 
				
			||||||
 | 
					        const filename = `${uid()}.${ext}`;
 | 
				
			||||||
 | 
					        // 创建 FormData 实例
 | 
				
			||||||
 | 
					        form.append('file', response.data, {
 | 
				
			||||||
 | 
					          filename,
 | 
				
			||||||
 | 
					          contentType: response.headers['content-type'],
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // 下载指定 URL 的内容
 | 
					        // 下载指定 URL 的内容
 | 
				
			||||||
        const response = await axios({
 | 
					        const response = await axios(config);
 | 
				
			||||||
          method: 'get',
 | 
					 | 
				
			||||||
          url,
 | 
					 | 
				
			||||||
          responseType: 'stream',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        // 获取文件的 MIME 类型
 | 
					        // 获取文件的 MIME 类型
 | 
				
			||||||
        const contentType = response.headers['content-type'];
 | 
					        const contentType = response.headers['content-type'];
 | 
				
			||||||
        // 根据 MIME 类型获取文件扩展名
 | 
					        // 根据 MIME 类型获取文件扩展名
 | 
				
			||||||
@ -90,11 +123,11 @@ export class CreateInstruction extends Instruction {
 | 
				
			|||||||
        const urls = options.values[attachmentField.options.name];
 | 
					        const urls = options.values[attachmentField.options.name];
 | 
				
			||||||
        if (Array.isArray(urls)) {
 | 
					        if (Array.isArray(urls)) {
 | 
				
			||||||
          for (const i in urls) {
 | 
					          for (const i in urls) {
 | 
				
			||||||
            urls[i] = await handleUrl(urls[i]);
 | 
					            urls[i] = await handleResource(urls[i]);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          const url = options.values[attachmentField.options.name];
 | 
					          const url = options.values[attachmentField.options.name];
 | 
				
			||||||
          options.values[attachmentField.options.name] = await handleUrl(url);
 | 
					          options.values[attachmentField.options.name] = await handleResource(url);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,10 @@
 | 
				
			|||||||
 | 
					import fs from 'fs';
 | 
				
			||||||
 | 
					import { Readable } from 'stream';
 | 
				
			||||||
import { parseCollectionName } from '@tachybase/data-source-manager';
 | 
					import { parseCollectionName } from '@tachybase/data-source-manager';
 | 
				
			||||||
import { Gateway } from '@tachybase/server';
 | 
					import { Gateway } from '@tachybase/server';
 | 
				
			||||||
import { uid } from '@tachybase/utils';
 | 
					import { uid } from '@tachybase/utils';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import axios from 'axios';
 | 
					import axios, { AxiosRequestConfig } from 'axios';
 | 
				
			||||||
import FormData from 'form-data';
 | 
					import FormData from 'form-data';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
import mime from 'mime-types';
 | 
					import mime from 'mime-types';
 | 
				
			||||||
@ -33,10 +35,24 @@ export class UpdateInstruction extends Instruction {
 | 
				
			|||||||
    const userId = _.get(processor.getScope(node.id), '$context.user.id', '');
 | 
					    const userId = _.get(processor.getScope(node.id), '$context.user.id', '');
 | 
				
			||||||
    const token = this.workflow.app.authManager.jwt.sign({ userId });
 | 
					    const token = this.workflow.app.authManager.jwt.sign({ userId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const handleUrl = async (url) => {
 | 
					    const isJSON = (str) => {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        return JSON.parse(str);
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    //目前可处理url,json对象,base64
 | 
				
			||||||
 | 
					    const handleResource = async (resource) => {
 | 
				
			||||||
 | 
					      const parseRes = isJSON(resource);
 | 
				
			||||||
 | 
					      const config: AxiosRequestConfig<any> = {
 | 
				
			||||||
 | 
					        method: 'get',
 | 
				
			||||||
 | 
					        url: resource,
 | 
				
			||||||
 | 
					        responseType: 'stream',
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
      const form = new FormData();
 | 
					      const form = new FormData();
 | 
				
			||||||
      if (url.startsWith('data:')) {
 | 
					      if (resource.startsWith('data:')) {
 | 
				
			||||||
        const matches = url.match(/^data:(.+);base64,(.+)$/);
 | 
					        const matches = resource.match(/^data:(.+);base64,(.+)$/);
 | 
				
			||||||
        if (matches) {
 | 
					        if (matches) {
 | 
				
			||||||
          const contentType = matches[1];
 | 
					          const contentType = matches[1];
 | 
				
			||||||
          const base64Data = matches[2];
 | 
					          const base64Data = matches[2];
 | 
				
			||||||
@ -51,13 +67,33 @@ export class UpdateInstruction extends Instruction {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          throw new Error('Invalid data URL format');
 | 
					          throw new Error('Invalid data URL format');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      } else if (parseRes) {
 | 
				
			||||||
 | 
					        const { resourceUrl, params, headers, body } = parseRes;
 | 
				
			||||||
 | 
					        config.url = resourceUrl;
 | 
				
			||||||
 | 
					        config.params = params;
 | 
				
			||||||
 | 
					        config.headers = headers;
 | 
				
			||||||
 | 
					        if (headers['content-type'] === 'multipart/form-data') {
 | 
				
			||||||
 | 
					          const formData = new FormData();
 | 
				
			||||||
 | 
					          Object.entries(body).forEach(([key, value]) => {
 | 
				
			||||||
 | 
					            formData.append(key, value);
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          config.data = formData;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          config.data = body;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const response = await axios(config);
 | 
				
			||||||
 | 
					        const contentType = response.headers['content-type'];
 | 
				
			||||||
 | 
					        // 根据 MIME 类型获取文件扩展名
 | 
				
			||||||
 | 
					        const ext = mime.extension(contentType);
 | 
				
			||||||
 | 
					        const filename = `${uid()}.${ext}`;
 | 
				
			||||||
 | 
					        // 创建 FormData 实例
 | 
				
			||||||
 | 
					        form.append('file', response.data, {
 | 
				
			||||||
 | 
					          filename,
 | 
				
			||||||
 | 
					          contentType: response.headers['content-type'],
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // 下载指定 URL 的内容
 | 
					        // 下载指定 URL 的内容
 | 
				
			||||||
        const response = await axios({
 | 
					        const response = await axios(config);
 | 
				
			||||||
          method: 'get',
 | 
					 | 
				
			||||||
          url,
 | 
					 | 
				
			||||||
          responseType: 'stream',
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        // 获取文件的 MIME 类型
 | 
					        // 获取文件的 MIME 类型
 | 
				
			||||||
        const contentType = response.headers['content-type'];
 | 
					        const contentType = response.headers['content-type'];
 | 
				
			||||||
        // 根据 MIME 类型获取文件扩展名
 | 
					        // 根据 MIME 类型获取文件扩展名
 | 
				
			||||||
@ -89,11 +125,11 @@ export class UpdateInstruction extends Instruction {
 | 
				
			|||||||
        const urls = options.values[attachmentField.options.name];
 | 
					        const urls = options.values[attachmentField.options.name];
 | 
				
			||||||
        if (Array.isArray(urls)) {
 | 
					        if (Array.isArray(urls)) {
 | 
				
			||||||
          for (const i in urls) {
 | 
					          for (const i in urls) {
 | 
				
			||||||
            urls[i] = await handleUrl(urls[i]);
 | 
					            urls[i] = await handleResource(urls[i]);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
          const url = options.values[attachmentField.options.name];
 | 
					          const url = options.values[attachmentField.options.name];
 | 
				
			||||||
          options.values[attachmentField.options.name] = [await handleUrl(url)];
 | 
					          options.values[attachmentField.options.name] = [await handleResource(url)];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user