fix(cors): 修复H5环境下的CORS上传问题

- 在config/dev.ts中添加代理配置,将/api/*请求代理到外部API服务器
- 在bowongAISDK.ts中添加平台检测逻辑,H5环境使用fetch避免CORS
- 新增H5专用的文件上传和图像生成方法
- 保持小程序环境使用原有的Taro.uploadFile方法
- 确保跨平台兼容性,解决XMLHttpRequest凭据模式与通配符CORS头冲突

修复错误:
- Access-Control-Allow-Origin头在凭据模式下不能使用通配符*
- H5环境下uploadFile自动设置withCredentials导致的CORS阻止
This commit is contained in:
imeepos 2025-09-26 23:03:52 +08:00
parent 6c4247c8ea
commit 595e56378c
2 changed files with 184 additions and 3 deletions

View File

@ -7,6 +7,24 @@ export default {
devServer: {
fs: {
allow: ['..']
},
proxy: {
'/api': {
target: 'https://bowongai-test--text-video-agent-fastapi-app.modal.run',
changeOrigin: true,
secure: true,
configure: (proxy, _options) => {
proxy.on('error', (err, _req, _res) => {
console.log('proxy error', err);
});
proxy.on('proxyReq', (proxyReq, req, _res) => {
console.log('Sending Request to the Target:', req.method, req.url);
});
proxy.on('proxyRes', (proxyRes, req, _res) => {
console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
});
},
}
}
}
}

View File

@ -62,6 +62,152 @@ export class BowongAISDK {
/** API 服务基础地址 */
private readonly baseUrl = 'https://bowongai-test--text-video-agent-fastapi-app.modal.run';
/**
* H5平台
* @private
*/
private _isH5Platform(): boolean {
return process.env.TARO_ENV === 'h5';
}
/**
* H5平台专用的文件上传方法
* 使Taro.request避免CORS问题
* @private
*/
private async _uploadFileForH5(params: UploadParams): Promise<string> {
const { filePath, name, type, onProgress } = params;
const url = `${this._isH5Platform() ? '' : this.baseUrl}/api/file/upload/s3`;
try {
// 获取文件信息
let fileInfo;
try {
fileInfo = await Taro.getFileInfo({ filePath });
if (!isGetFileInfoSuccessCallbackResult(fileInfo)) {
console.warn('获取文件信息失败,使用默认值');
fileInfo = { size: 0 };
}
} catch (getFileInfoError) {
console.warn('getFileInfo调用失败使用默认值:', getFileInfoError);
fileInfo = { size: 0 };
}
// 自动生成文件名(如果未提供)
const fileName = name || `upload_${Date.now()}.${this._getFileExtension(filePath)}`;
// 自动判断文件类型(如果未提供)
const fileType = type || this._getMimeType(filePath);
console.log('开始上传文件 (H5模式):', {
fileName,
fileSize: fileInfo.size,
fileType
});
// 创建FormData
const formData = new FormData();
// 在H5环境中需要先将filePath转换为File对象
const response = await fetch(filePath);
const blob = await response.blob();
const file = new File([blob], fileName, { type: fileType });
formData.append('file', file);
formData.append('filename', fileName);
formData.append('type', fileType);
// 使用fetch进行上传
const uploadResponse = await fetch(url, {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json'
}
});
if (!uploadResponse.ok) {
throw new Error(`HTTP ${uploadResponse.status}: 文件上传失败`);
}
const result: ApiResponse<string> = await uploadResponse.json();
if (!result.status) {
throw new Error(result.msg || '文件上传失败');
}
console.log('文件上传成功 (H5模式):', result.data);
return result.data;
} catch (error) {
console.error('H5文件上传失败:', error);
throw error;
}
}
/**
* H5平台专用的图像生成方法
* @private
*/
private async _generateImageForH5(params: GenerateImageParams): Promise<string> {
const {
prompt,
model_name = 'gemini-2.5-flash-image-preview',
aspect_ratio = '9:16',
mode = 'turbo',
webhook_flag = false,
img_file
} = params;
const url = `${this._isH5Platform() ? '' : this.baseUrl}/api/custom/image/submit/task`;
try {
if (img_file) {
// 创建FormData
const formData = new FormData();
// 在H5环境中需要先将filePath转换为File对象
const response = await fetch(img_file);
const blob = await response.blob();
const fileName = `generate_${Date.now()}.${this._getFileExtension(img_file)}`;
const file = new File([blob], fileName, { type: this._getMimeType(img_file) });
formData.append('img_file', file);
formData.append('prompt', prompt);
formData.append('model_name', model_name);
formData.append('aspect_ratio', aspect_ratio);
formData.append('mode', mode.toString());
formData.append('webhook_flag', webhook_flag.toString());
formData.append('img_url', '');
// 使用fetch进行上传
const uploadResponse = await fetch(url, {
method: 'POST',
body: formData,
headers: {
'accept': 'application/json'
}
});
if (!uploadResponse.ok) {
throw new Error(`HTTP ${uploadResponse.status}: 图像生成请求失败`);
}
const result: ApiResponse<string> = await uploadResponse.json();
if (!result.status) {
throw new Error(result.msg || '图像生成请求失败');
}
console.log('图像生成任务提交成功 (H5模式):', result.data);
return result.data;
}
throw new Error(`请选择图片`)
} catch (error) {
console.error('H5图像生成失败:', error);
throw error;
}
}
/**
* S3
@ -70,14 +216,26 @@ export class BowongAISDK {
* @returns Promise<string>
*/
async upload(params: UploadParams): Promise<string> {
// 如果是H5平台使用专用的上传方法
if (this._isH5Platform()) {
return this._uploadFileForH5(params);
}
const { filePath, name, type, onProgress } = params;
const url = `${this.baseUrl}/api/file/upload/s3`;
try {
// 获取文件信息
const fileInfo = await Taro.getFileInfo({ filePath });
let fileInfo;
try {
fileInfo = await Taro.getFileInfo({ filePath });
if (!isGetFileInfoSuccessCallbackResult(fileInfo)) {
throw new Error(fileInfo.errMsg)
console.warn('获取文件信息失败,使用默认值');
fileInfo = { size: 0 };
}
} catch (getFileInfoError) {
console.warn('getFileInfo调用失败使用默认值:', getFileInfoError);
fileInfo = { size: 0 };
}
// 自动生成文件名(如果未提供)
const fileName = name || `upload_${Date.now()}.${this._getFileExtension(filePath)}`;
@ -401,6 +559,11 @@ export class BowongAISDK {
* -F 'img_file='
*/
async generateImage(params: GenerateImageParams): Promise<string> {
// 如果是H5平台使用专用的生成方法
if (this._isH5Platform()) {
return this._generateImageForH5(params);
}
const {
prompt,
model_name = 'gemini-2.5-flash-image-preview',