使用JavaScript fetch调用Zendesk工单API无法获取响应的问题
解决方案:跨域问题处理 + 获取Zendesk工单ID并上传附件
核心问题分析
- CORS跨域拦截:你遇到的
Cross-Origin Request Blocked是因为浏览器同源策略限制,Zendesk API默认不会为前端域名添加Access-Control-Allow-Origin响应头。即使工单创建成功(返回201状态),浏览器也会阻止前端读取响应内容,导致无法获取工单ID。 - fetch调用错误:你的代码中第一个
then仅把Response对象赋值给id,未解析响应体;且createTicket是异步函数,直接returnid会得到undefined——因为fetch请求还未完成。
步骤1:通过后端代理解决CORS问题
前端不能直接调用Zendesk API,必须通过你的后端服务器转发请求。后端与Zendesk API通信不受同源策略限制,再将响应返回给前端。
后端代理示例(Node.js/Express)
const express = require('express'); const fetch = require('node-fetch'); const multer = require('multer'); // 用于处理文件上传 const upload = multer(); const app = express(); app.use(express.json()); // 后端存储Zendesk认证信息(禁止暴露给前端) const ZENDESK_DOMAIN = 'https://easywalkersupport.zendesk.com'; const ZENDESK_AUTH = 'Basic ' + Buffer.from('你的Zendesk邮箱/token:你的API令牌').toString('base64'); // 创建工单的代理接口 app.post('/api/create-zendesk-ticket', async (req, res) => { try { const response = await fetch(`${ZENDESK_DOMAIN}/api/v2/tickets`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': ZENDESK_AUTH }, body: JSON.stringify(req.body) }); const data = await response.json(); res.status(response.status).json(data); } catch (error) { res.status(500).json({ error: error.message }); } }); // 上传附件并关联工单的代理接口 app.post('/api/upload-zendesk-attachment/:ticketId', upload.single('file'), async (req, res) => { try { // 第一步:上传文件到Zendesk获取上传令牌 const formData = new FormData(); formData.append('file', req.file.buffer, req.file.originalname); const uploadResponse = await fetch(`${ZENDESK_DOMAIN}/api/v2/uploads.json?filename=${req.file.originalname}`, { method: 'POST', headers: { 'Authorization': ZENDESK_AUTH }, body: formData }); const uploadData = await uploadResponse.json(); // 第二步:将附件关联到目标工单 await fetch(`${ZENDESK_DOMAIN}/api/v2/tickets/${req.params.ticketId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': ZENDESK_AUTH }, body: JSON.stringify({ ticket: { comment: { body: '附上保修相关文件', uploads: [uploadData.upload.token] } } }) }); res.status(200).json({ success: true }); } catch (error) { res.status(500).json({ error: error.message }); } }); app.listen(3000, () => console.log('代理服务器运行在3000端口'));
步骤2:修改前端代码,获取工单ID并上传文件
更新前端函数,使用async/await处理异步逻辑,调用后端代理接口完成工单创建与文件上传:
async function createTicket() { const easyWalker = $("#choose_product option:selected").html(); const batchNumber = document.getElementById("batch_number").value; const color = document.getElementById("color").value; const firstName = document.getElementById("first_name").value; const lastName = document.getElementById("last_name").value; const email = document.getElementById("email").value; const phone = document.getElementById("phone").value; console.log("创建工单中"); const ticketData = { "ticket": { "subject": `New Easywalker Warranty Registry from ${firstName} ${lastName}`, "description": `Product: ${easyWalker}\n` + `Batch Number: ${batchNumber}\n` + `Color: ${color}\n` + `Name: ${firstName} ${lastName}\n` + `Email: ${email}\n` + `Phone Number: ${phone}`, "requester": { "name": `${firstName} ${lastName}`, "email": email } } }; try { // 调用后端接口创建工单 const ticketResponse = await fetch('/api/create-zendesk-ticket', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(ticketData) }); const ticketResult = await ticketResponse.json(); const ticketId = ticketResult.ticket.id; // 提取工单ID console.log('已创建工单ID:', ticketId); // 上传文件到工单(假设文件输入框id为warranty_file) const fileInput = document.getElementById('warranty_file'); if (fileInput.files.length > 0) { const file = fileInput.files[0]; const formData = new FormData(); formData.append('file', file); await fetch(`/api/upload-zendesk-attachment/${ticketId}`, { method: 'POST', body: formData }); console.log('文件上传成功'); } return ticketId; } catch (error) { console.log('错误:', error); throw error; } } // 调用示例: // createTicket().then(id => console.log('最终工单ID:', id)).catch(err => console.error(err));
关键说明
- 安全提示:绝对不能在前端暴露Zendesk的API令牌或认证信息,必须存储在后端,否则会导致账号泄露。
- Zendesk响应结构:创建工单成功后,响应体中的
ticket.id即为所需工单ID,通过result.ticket.id直接提取。 - 文件上传逻辑:Zendesk要求先上传文件获取令牌,再将令牌关联到工单评论中,这两步必须通过后端完成。
内容的提问来源于stack exchange,提问作者Ivan
相关产品推荐
相关产品推荐

