You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jira插件向自定义后端上传文件时form-data缺失问题

Jira Forge插件文件上传form-data丢失问题解决

问题背景

开发基于Forge和自定义UI的Jira文件上传插件,自有后端采用S3+Multer处理文件上传逻辑。Postman测试后端接口完全正常,但插件内发起请求时,请求未携带form-data;而插件外的独立React组件测试相同上传逻辑可正常工作。

代码示例

import React, { useState, useRef } from 'react';
import { Button } from 'react-bootstrap';
import Plus from '../../../../../assets/icons/Plus';
import { invokeApiRemote } from '../../../../../services/invokeApi';
import { createAttachment } from '../../../../../services/apiConfig';

const FileUpload = ({ onUpload, enableDragAndDrop, entityType, entityId }) => {
  const [isDragging, setIsDragging] = useState(false);
  const [error, setError] = useState(null);
  const fileInputRef = useRef(null);

  const handleDragOver = (e) => {
    e.preventDefault();
    setIsDragging(true);
  };

  const handleDragLeave = () => {
    setIsDragging(false);
  };

  const handleDrop = (e) => {
    e.preventDefault();
    setIsDragging(false);
    const files = Array.from(e.dataTransfer.files);
    handleFiles(files);
  };

  const handleFiles = async (files) => {
    console.log(files)
    const formData = new FormData();
    console.log('Files to upload:', files);

    files && files.map((file) => {
      formData.append('files', file);
    });

    formData.append('attachableType', entityType);
    formData.append('attachableId', entityId);
    console.log(formData)

    try {
      const response = await invokeApiRemote({
        config: createAttachment(),
        body: formData,
        headers: {
          'X-Atlassian-Token': 'no-check',
        },
      });

      if (response) {
        console.log('Attachment created successfully:', response);
        // onUpload && onUpload(response);
      }
    } catch (error) {
      console.error('Error creating attachment:', error);
      setError(error.message);
    }
  };

  const handleFileChange = (e) => {
    const files = Array.from(e.target.files);
    handleFiles(files);
  };

  const handleChooseFiles = () => {
    fileInputRef.current.click();
  };

  return (
    <div>
      {enableDragAndDrop ? (
        <div
          onDragOver={handleDragOver}
          onDragLeave={handleDragLeave}
          onDrop={handleDrop}
          onClick={handleChooseFiles}
          style={{
            border: isDragging ? '2px dashed #007bff' : '2px dashed #cccccc',
            padding: '20px',
            textAlign: 'center',
            marginBottom: '20px',
            cursor: 'pointer',
            backgroundColor: isDragging ? '#f8f9fa' : 'transparent',
          }}>
          <p>Drag & drop or click to select files</p>
        </div>
      ) : (
        <Button
          size="sm"
          variant=""
          className="me-2"
          onClick={handleChooseFiles}>
          <Plus /> Attach files
        </Button>
      )}
      <input
        type="file"
        multiple
        onChange={handleFileChange}
        style={{ display: 'none' }}
        ref={fileInputRef}
      />
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
};

export default FileUpload;

问题原因

核心问题出在Forge的API调用工具invokeApiRemote的处理逻辑上:

  1. Forge的网络请求框架可能会自动对请求body进行JSON序列化,导致FormData被转换为无效格式或丢失。
  2. 若手动设置Content-Type: multipart/form-data,会覆盖浏览器自动生成的带boundary的正确头部,导致后端无法解析form-data。
  3. 可能存在manifest权限配置缺失,导致请求被拦截或篡改。

解决方案

1. 避免手动设置Content-Type

移除请求中手动添加的Content-Type头部,让浏览器自动生成包含boundary的正确multipart/form-data头部:

// 修改handleFiles中的请求部分
try {
  const response = await invokeApiRemote({
    config: createAttachment(),
    body: formData,
    headers: {
      'X-Atlassian-Token': 'no-check',
      // 不要添加 'Content-Type': 'multipart/form-data'
    },
  });

2. 检查invokeApiRemote实现

确保invokeApiRemote内部直接传递FormData,不对其进行JSON序列化。如果使用Forge原生request方法,需配置正确:

// 示例invokeApiRemote正确实现
export async function invokeApiRemote({ config, body, headers }) {
  return await request(config.url, {
    method: config.method,
    body: body, // 直接传递FormData,不要用JSON.stringify
    headers: {
      ...headers,
      // 禁止在这里强制设置Content-Type
    },
  });
}

3. 验证Manifest权限配置

在manifest.yml中确保已配置外部后端的请求权限:

permissions:
  external:
    fetch:
      - 'https://your-backend-domain.com/**'

验证

修改后重新打包部署插件,测试文件上传功能,检查请求是否正确携带form-data,后端是否能正常接收并处理文件。

内容的提问来源于stack exchange,提问作者Adesh Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 11:05:18