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',
|
||||
},
|
||||
},
|
||||
//是否携带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: {
|
||||
type: 'array',
|
||||
'x-component': 'ArrayItems',
|
||||
'x-decorator': 'FormItem',
|
||||
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: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
@ -273,13 +273,14 @@
|
||||
"Withdrawn": "Withdrawn",
|
||||
"Workflow todos": "Workflow todos",
|
||||
"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",
|
||||
"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":"Approval:CarbonCopy",
|
||||
"Approval Launch":"Approval Launch"
|
||||
}
|
||||
"MyLaunch": "MyLaunch",
|
||||
"CarbonCopy": "CarbonCopy",
|
||||
"ApprovalCarbonCopy": "Approval:CarbonCopy",
|
||||
"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 directly once the button clicked, without data saving.": "按钮点击后直接触发工作流,但不会保存数据。",
|
||||
"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": "连接",
|
||||
"ms": "毫秒",
|
||||
"Resubmit": "重新发起",
|
||||
"Are you sure you want to resubmit it?": "是否重新复制一份?",
|
||||
"MyLaunch":"我的发起",
|
||||
"CarbonCopy":"抄送",
|
||||
"ApprovalCarbonCopy":"审批:抄送",
|
||||
"Approval Launch":"审批发起"
|
||||
}
|
||||
"MyLaunch": "我的发起",
|
||||
"CarbonCopy": "抄送",
|
||||
"ApprovalCarbonCopy": "审批:抄送",
|
||||
"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 { uid } from '@tachybase/utils';
|
||||
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import FormData from 'form-data';
|
||||
import _ from 'lodash';
|
||||
import mime from 'mime-types';
|
||||
|
||||
import { FlowNodeModel, Instruction, JOB_STATUS, Processor } from '../..';
|
||||
|
||||
@ -14,19 +20,54 @@ export type RequestConfig = Pick<AxiosRequestConfig, 'url' | 'method' | 'params'
|
||||
headers: Array<Header>;
|
||||
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) {
|
||||
// default headers
|
||||
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 url = originUrl.startsWith('http') ? originUrl : `${origin}${originUrl}`;
|
||||
|
||||
let headers = (config.headers ?? []).reduce((result, header) => {
|
||||
if (header.name.toLowerCase() === 'content-type') {
|
||||
return result;
|
||||
}
|
||||
return Object.assign(result, { [header.name]: header.value });
|
||||
}, {});
|
||||
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
|
||||
headers['Content-Type'] = 'application/json';
|
||||
|
||||
// let temp = context.get('headers');
|
||||
headers = {
|
||||
Authorization: 'Bearer ' + token,
|
||||
...headers,
|
||||
};
|
||||
return axios.request({
|
||||
const requestParams: AxiosRequestConfig = {
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
params,
|
||||
data,
|
||||
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 {
|
||||
@ -61,11 +132,7 @@ export default class extends Instruction {
|
||||
const context = { token, origin };
|
||||
|
||||
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 sync = this.workflow.isWorkflowSync(workflow);
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { parseCollectionName } from '@tachybase/data-source-manager';
|
||||
import { Gateway } from '@tachybase/server';
|
||||
import { uid } from '@tachybase/utils';
|
||||
|
||||
import axios from 'axios';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import FormData from 'form-data';
|
||||
import _ from 'lodash';
|
||||
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 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();
|
||||
if (url.startsWith('data:')) {
|
||||
const matches = url.match(/^data:(.+);base64,(.+)$/);
|
||||
if (resource.startsWith('data:')) {
|
||||
const matches = resource.match(/^data:(.+);base64,(.+)$/);
|
||||
if (matches) {
|
||||
const contentType = matches[1];
|
||||
const base64Data = matches[2];
|
||||
@ -52,13 +65,33 @@ export class CreateInstruction extends Instruction {
|
||||
} else {
|
||||
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 {
|
||||
// 下载指定 URL 的内容
|
||||
const response = await axios({
|
||||
method: 'get',
|
||||
url,
|
||||
responseType: 'stream',
|
||||
});
|
||||
const response = await axios(config);
|
||||
// 获取文件的 MIME 类型
|
||||
const contentType = response.headers['content-type'];
|
||||
// 根据 MIME 类型获取文件扩展名
|
||||
@ -90,11 +123,11 @@ export class CreateInstruction extends Instruction {
|
||||
const urls = options.values[attachmentField.options.name];
|
||||
if (Array.isArray(urls)) {
|
||||
for (const i in urls) {
|
||||
urls[i] = await handleUrl(urls[i]);
|
||||
urls[i] = await handleResource(urls[i]);
|
||||
}
|
||||
} else {
|
||||
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 { Gateway } from '@tachybase/server';
|
||||
import { uid } from '@tachybase/utils';
|
||||
|
||||
import axios from 'axios';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import FormData from 'form-data';
|
||||
import _ from 'lodash';
|
||||
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 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();
|
||||
if (url.startsWith('data:')) {
|
||||
const matches = url.match(/^data:(.+);base64,(.+)$/);
|
||||
if (resource.startsWith('data:')) {
|
||||
const matches = resource.match(/^data:(.+);base64,(.+)$/);
|
||||
if (matches) {
|
||||
const contentType = matches[1];
|
||||
const base64Data = matches[2];
|
||||
@ -51,13 +67,33 @@ export class UpdateInstruction extends Instruction {
|
||||
} else {
|
||||
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 {
|
||||
// 下载指定 URL 的内容
|
||||
const response = await axios({
|
||||
method: 'get',
|
||||
url,
|
||||
responseType: 'stream',
|
||||
});
|
||||
const response = await axios(config);
|
||||
// 获取文件的 MIME 类型
|
||||
const contentType = response.headers['content-type'];
|
||||
// 根据 MIME 类型获取文件扩展名
|
||||
@ -89,11 +125,11 @@ export class UpdateInstruction extends Instruction {
|
||||
const urls = options.values[attachmentField.options.name];
|
||||
if (Array.isArray(urls)) {
|
||||
for (const i in urls) {
|
||||
urls[i] = await handleUrl(urls[i]);
|
||||
urls[i] = await handleResource(urls[i]);
|
||||
}
|
||||
} else {
|
||||
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