import {
defineEventHandler,
getRequestIP,
getQuery,
readBody,
createError,
readMultipartFormData,
} from "h3";
import { useRuntimeConfig } from "#imports";
import { checkRateLimit } from "~/server/limit/limiter";
import {
ALIBABA_MODE_BASE_URL,
ALIBABA_APP_BASE_URL,
ALIBABA_UPLOAD_BASE_URL,
ALIBABA_IMAGE_BASE_URL,
Alibaba,
} from "~/server/constant";
import { ApiPath } from "~/utils/constant";
export default defineEventHandler(async (event) => {
console.log(11111111111111);
if (event.node.req.method === "OPTIONS") {
event.node.res.statusCode = 200;
event.node.res.setHeader("Content-Type", "application/json");
event.node.res.end(JSON.stringify({ body: "OK" }));
return;
}
try {
const ip = getRequestIP(event);
// 验证请求频率
if (!(await checkRateLimit(ip))) {
throw createError({
statusCode: 429,
message: "请求过于频繁,请稍后再试",
});
}
const authHeader = event.node.req.headers.authorization;
const cookieHeader = event.node.req.headers.cookie;
const cookies = typeof cookieHeader === "string" ? cookieHeader : "";
let token = null;
if (authHeader && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
} else if (cookies) {
const tokenCookie = cookies
.split(";")
.find((c) => c.trim().startsWith("auth._token.local="));
if (tokenCookie) {
token = decodeURIComponent(tokenCookie.split("=")[1]);
}
}
if (!token) {
throw createError({
statusCode: 401,
message: "未授权的访问",
});
}
const config = useRuntimeConfig();
const apiKey = config.alibabaApiKey;
if (!apiKey) {
throw createError({
statusCode: 500,
message: "API 密钥未配置",
});
}
// 处理文件上传请求
const query = getQuery(event);
if (event.node.req.method === "POST" && query.action === "fileExtract") {
const formData = await readMultipartFormData(event);
if (!formData) {
throw createError({
statusCode: 400,
message: "无效的表单数据",
});
}
const fileData = formData.find((part) => part.name === "file");
if (!fileData) {
throw createError({
statusCode: 400,
message: "未找到文件数据",
});
}
// 检查文件类型和扩展名
const fileName = fileData.filename || "";
const fileExt = fileName.split(".").pop()?.toLowerCase();
const supportedExtensions = [
"txt",
"doc",
"docx",
"pdf",
"xlsx",
"epub",
"mobi",
"md",
"csv",
];
if (!supportedExtensions.includes(fileExt)) {
throw createError({
statusCode: 400,
message:
"不支持的文件类型,仅支持 txt、doc、docx、pdf、xlsx、epub、mobi、md、csv 格式",
});
}
// 检查文件大小(150MB = 150 * 1024 * 1024 bytes)
if (fileData.data.length > 150 * 1024 * 1024) {
throw createError({
statusCode: 400,
message: "文件大小不能超过 150MB",
});
}
// 创建新的 FormData 对象用于转发
const forwardFormData = new FormData();
forwardFormData.append(
"file",
new Blob([fileData.data], { type: fileData.type }),
fileData.filename
);
forwardFormData.append("purpose", "file-extract");
try {
// const response = await fetch(`${ALIBABA_MODE_BASE_URL}v1/files`, {
// method: "POST",
// headers: {
// Authorization: `Bearer ${apiKey}`,
// },
// body: forwardFormData,
// });
// if (!response.ok) {
// // 修改错误处理逻辑,避免尝试解析可能不是 JSON 格式的响应
// const contentType = response.headers.get("content-type");
// let errorMessage = `文件上传失败 (${response.status})`;
// if (contentType && contentType.includes("application/json")) {
// try {
// const errorData = await response.json();
// errorMessage = errorData.error?.message || errorMessage;
// } catch (parseError) {
// console.error("解析错误响应失败:", parseError);
// }
// } else {
// // 如果不是 JSON 格式,尝试获取文本内容
// try {
// await response.text();
// } catch (textError) {
// // 读取错误响应文本失败
// }
// }
// throw createError({
// statusCode: response.status,
// message: errorMessage,
// });
// }
// // 确保响应是 JSON 格式
// const contentType = response.headers.get("content-type");
// if (!contentType || !contentType.includes("application/json")) {
// const textResponse = await response.text();
// // 尝试手动解析响应
// try {
// return JSON.parse(textResponse);
// } catch (parseError) {
// throw createError({
// statusCode: 500,
// message: "服务器返回了非 JSON 格式的响应",
// });
// }
// }
// return await response.json();
} catch (error) {
throw createError({
statusCode: 500,
message: error.message || "文件上传失败",
});
}
}
// 处理获取上传策略的请求
if (event.node.req.method === "GET" && query.action === "getPolicy") {
const modelName = query.model || "qwen-vl-plus";
// 转发请求到阿里云 DashScope
const dashscopeUrl = `${ALIBABA_UPLOAD_BASE_URL}?action=getPolicy&model=${modelName}`;
try {
const response = await fetch(dashscopeUrl, {
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
throw createError({
statusCode: response.status,
message:
errorData.message || `获取上传策略失败 (${response.status})`,
});
}
const policyData = await response.json();
return policyData;
} catch (error) {
throw createError({
statusCode: 500,
message: error.message || "获取上传策略失败",
});
}
}
// 删除文件
if (event.node.req.method === "DELETE" && query.action === "deleteFile") {
const fileId = query.fileId;
if (!fileId) {
throw createError({
statusCode: 400,
message: "缺少文件 ID 参数",
});
}
try {
const response = await fetch(
`${ALIBABA_MODE_BASE_URL}v1/files/${fileId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
const errorData = await response.json();
throw createError({
statusCode: response.status,
message: errorData.error?.message || `删除文件失败 (${response.status})`,
});
}
const result = await response.json();
return {
success: true,
...result
};
} catch (error) {
throw createError({
statusCode: error.statusCode || 500,
message: error.message || "删除文件失败",
});
}
}
const body = await readBody(event);
// ============== 新增文生图处理逻辑 ==============
if (event.node.req.method === "POST" && query.action === "textToImage") {
try {
// 验证必要参数
if (!body.prompt) {
throw createError({
statusCode: 400,
message: "缺少 prompt 参数",
});
}
// 构造请求参数
const requestBody = {
model: body.model || "wanx-lite",
input: {
prompt: body.prompt,
},
parameters: {
size: body.size || "1024x1024",
n: body.n || 1,
},
prompt_extend: true,
watermark: false
};
try {
const response = await fetch(`${ALIBABA_IMAGE_BASE_URL}${Alibaba.TextToImagePath}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
"X-DashScope-Async": "enable"
},
body: JSON.stringify(requestBody),
});
// 读取原始响应文本
// const responseText = await response.text();
// console.log("Raw API response:", responseText); // 调试日志
// // 处理空响应
// if (!responseText) {
// throw createError({
// statusCode: 502,
// message: "收到空响应"
// });
// }
// // 尝试解析 JSON
// let result;
// try {
// result = JSON.parse(responseText);
// } catch (e) {
// throw createError({
// statusCode: 502,
// message: `无效的JSON响应: ${e.message}\n响应内容: ${responseText}`
// });
// }
// if (!response.ok) {
// throw createError({
// statusCode: response.status,
// message: result.error?.message || result.message || `文生图请求失败 (${response.status})`,
// });
// }
// // 检查是否有任务ID
// if (!result.output?.task_id) {
// throw createError({
// statusCode: 500,
// message: '未获取到任务ID'
// });
// }
return response;
} catch (error) {
throw createError({
statusCode: error.statusCode || 500,
message: error.message || "文生图处理失败",
});
}
} catch (error) {
throw createError({
statusCode: error.statusCode || 500,
message: error.message || "文生图处理失败",
});
}
}
// -------------------文生图
let headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
};
// 检查是否包含图片 URL
if (body.messages) {
let hasImageUrl = false;
for (let i = 0; i < body.messages.length; i++) {
const msg = body.messages[i];
if (msg.content && Array.isArray(msg.content)) {
for (const item of msg.content) {
if (item.type === "image_url" && item.image_url) {
hasImageUrl = true;
}
}
} else if (
msg.content &&
typeof msg.content === "object" &&
msg.content.imageUrl
) {
// 处理直接包含imageUrl的消息对象
// 转换消息格式以符合API要求
const role = msg.role || "user";
const text = msg.content.text || "";
const imageUrl = msg.content.imageUrl;
// 替换原始消息为正确格式
body.messages[i] = {
role: role,
content: [
{ type: "text", text: text },
{ type: "image_url", image_url: { url: imageUrl } },
],
};
hasImageUrl = true;
}
}
if (hasImageUrl) {
// 确保添加OSS资源解析头
headers["X-DashScope-OssResourceResolve"] = "enable";
// 如果检测到图片URL,强制使用视觉模型
if (!body.useApp && (!body.model || body.model !== "qwen-vl-plus")) {
body.model = "qwen-vl-plus";
}
}
}
let fetchUrl = "";
let requestBody = {};
// 判断是否为应用调用
if (body.useApp && body.appId) {
fetchUrl = `${ALIBABA_APP_BASE_URL}${body.appId}${Alibaba.AppCompletionPath}`;
// 改进应用调用的消息处理
let prompt = "";
if (body.prompt) {
prompt = body.prompt;
} else if (body.messages && body.messages.length > 0) {
const lastMessage = body.messages[body.messages.length - 1];
if (typeof lastMessage.content === "string") {
prompt = lastMessage.content;
} else if (typeof lastMessage.content === "object" && lastMessage.content.text) {
prompt = lastMessage.content.text;
}
}
requestBody = {
input: {
prompt: prompt || "你好,请问有什么可以帮助你的?"
},
parameters: {
incremental_output: "true",
},
debug: {},
};
// 如果是流式输出,添加相应的头部
if (body.stream === true) {
headers["X-DashScope-SSE"] = "enable";
}
} else {
// 原有的模型调用逻辑
let path = `${event.node.req.url}`.replaceAll(ApiPath.Alibaba, "");
let baseUrl = config.alibabaUrl || ALIBABA_MODE_BASE_URL;
if (!baseUrl.startsWith("http")) {
baseUrl = `https://${baseUrl}`;
}
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}
fetchUrl = `${baseUrl}${path}${Alibaba.ModeChatPath}`;
requestBody = body;
}
// 发送请求到阿里云 DashScope API
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);
const response = await fetch(fetchUrl, {
method: "POST",
headers,
body: JSON.stringify(requestBody),
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
const errorData = await response.json();
throw createError({
statusCode: response.status,
message:
errorData.error?.message ||
errorData.message ||
`请求失败 (${response.status})`,
});
}
// 检查响应是否为流式
if (body.stream === true) {
// 设置流式响应头
event.node.res.setHeader("Content-Type", "text/event-stream");
event.node.res.setHeader("Cache-Control", "no-cache");
event.node.res.setHeader("Connection", "keep-alive");
if (!response.body) {
throw new Error("响应体为空");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
if (event.node.res.writableEnded) {
break;
}
if (body.useApp && body.appId) {
// 处理应用返回的 SSE 格式数据
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data:") && line.trim() !== "data: [DONE]") {
try {
const data = JSON.parse(line.substring(5).trim());
if (data.output && data.output.text) {
// 转换为与模型调用相同的格式
const formattedChunk = `data: ${JSON.stringify({
choices: [
{
delta: {
content: data.output.text,
},
},
],
})}\n\n`;
event.node.res.write(formattedChunk);
}
} catch (e) {
event.node.res.write(line + "\n");
}
} else if (line.trim() === "data: [DONE]") {
event.node.res.write(line + "\n");
}
}
} else {
// 原有模型调用的响应直接转发
event.node.res.write(chunk);
}
}
} catch (streamError) {
event.node.res.end(
`data: [错误] 流处理中断:${streamError.message}\n\n`
);
return;
} finally {
reader.cancel();
event.node.res.end();
}
return;
} else {
// 非流式响应
const responseData = await response.json();
// 如果是应用调用,转换响应格式以匹配原有的格式
if (body.useApp && body.appId && responseData.output) {
return {
choices: [
{
message: {
content: responseData.output.text,
},
},
],
};
}
return responseData;
}
} catch (error) {
throw createError({
statusCode: error.statusCode || 500,
message: error.message || "服务器内部错误",
});
}
});
为什么配置了文生图端点,但是请求一直报{
"error": true,
"url": "http://192.168.101.199:2000/api/alibaba?action=textToImage",
"statusCode": 500,
"statusMessage": "Server Error",
"message": "Unexpected end of JSON input"
},是需要配置什么吗,还是写法有问题,postman里面调用没问题
最新发布