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

调用Zoho Creator API获取图片时出现Error 401问题求助

解决React+Zoho Creator API图片访问401错误

问题根源

  • Zoho Creator的图片资源受OAuth2保护,前端直接访问图片URL时无法携带授权头,导致返回401 Unauthorized
  • 现有Express代码存在语法错误(app.get未完整定义路由)
  • Access Token仅在服务启动时获取一次,过期后无法自动刷新,会导致后续请求失效

修复方案

1. 修正Express后端代码

实现自动刷新Access Token、正确的API数据接口、图片代理路由(由后端携带授权头请求Zoho图片,再返回给前端)

const express = require("express");
const app = express();
const fetch = require("node-fetch");
const bodyParser = require("body-parser");
const cors = require("cors");
const PORT = process.env.PORT || 4000;

// CORS配置
const whitelist = ['https://zine-hosteleria-frontend12012023.onrender.com'];
const corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1 || !origin) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
};

app.use(bodyParser.json());
app.use(cors(corsOptions));

// Zoho配置
const ZOHO_REFRESH_TOKEN = "1000.3dbdad6937dc0800c4dcc662cd14d173.86efb18e337989bebb3ff4c05582c94c";
const ZOHO_CLIENT_ID = "1000.NQL17JHK3Y62Y178TO0E3FQC6MBQJV";
const ZOHO_CLIENT_SECRET = "5d04ad135862e7313377484af55efa1f41c1f49a39";
const ZOHO_TOKEN_URL = `https://accounts.zoho.eu/oauth/v2/token?refresh_token=${ZOHO_REFRESH_TOKEN}&client_id=${ZOHO_CLIENT_ID}&client_secret=${ZOHO_CLIENT_SECRET}&grant_type=refresh_token`;
const ZOHO_PRODUCTS_URL = "https://creator.zoho.eu/api/v2/hostienda1/Product-Catalog/report/Product_Details";

// 存储当前有效的Access Token和过期时间
let currentAccessToken = null;
let tokenExpiryTime = 0;

// 获取/刷新Access Token的函数
async function getAccessToken() {
  const now = Date.now();
  // 如果token未过期,直接返回
  if (currentAccessToken && now < tokenExpiryTime) {
    return currentAccessToken;
  }

  // 刷新token
  const response = await fetch(ZOHO_TOKEN_URL, { method: "POST" });
  const data = await response.json();
  currentAccessToken = data.access_token;
  // 设置过期时间(提前30秒刷新,避免刚好过期)
  tokenExpiryTime = now + (data.expires_in - 30) * 1000;
  return currentAccessToken;
}

// 获取产品数据的接口
app.get("/api/products", async (req, res) => {
  try {
    const accessToken = await getAccessToken();
    const response = await fetch(ZOHO_PRODUCTS_URL, {
      method: "GET",
      headers: {
        'Authorization': `Zoho-oauthtoken ${accessToken}`
      }
    });
    const result = await response.json();
    const products = result.data;

    // 修改图片路径为后端代理路由,而不是直接的Zoho URL
    products.forEach(product => {
      if (product.Product_Images && product.Product_Images.length > 0) {
        // 把Zoho的相对路径转为后端代理路径
        product.Product_Images[0].proxy_url = `/api/image?path=${encodeURIComponent(product.Product_Images[0].display_value)}`;
      }
    });

    res.json(products);
  } catch (error) {
    console.error("获取产品数据失败:", error);
    res.status(500).json({ error: "Failed to fetch products" });
  }
});

// 图片代理路由:后端携带授权头请求Zoho图片,返回给前端
app.get("/api/image", async (req, res) => {
  try {
    const imagePath = decodeURIComponent(req.query.path);
    const accessToken = await getAccessToken();
    const imageUrl = `https://creator.zoho.eu${imagePath}`;

    const response = await fetch(imageUrl, {
      headers: {
        'Authorization': `Zoho-oauthtoken ${accessToken}`
      }
    });

    // 把图片流转发给前端
    res.setHeader("Content-Type", response.headers.get("Content-Type"));
    response.body.pipe(res);
  } catch (error) {
    console.error("获取图片失败:", error);
    res.status(401).json({ error: "Failed to fetch image" });
  }
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

2. React前端修改

在React组件中,使用后端提供的proxy_url来渲染图片,而不是直接使用Zoho的URL:

import { useEffect, useState } from 'react';

function ProductList() {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    fetch('https://your-express-backend-url/api/products')
      .then(res => res.json())
      .then(data => setProducts(data))
      .catch(err => console.error(err));
  }, []);

  return (
    <div className="product-list">
      {products.map(product => (
        <div key={product.id} className="product">
          <h3>{product.Product_Name}</h3>
          {product.Product_Images && product.Product_Images[0] && (
            <img 
              src={product.Product_Images[0].proxy_url} 
              alt={product.Product_Name} 
              style={{ width: '200px' }}
            />
          )}
        </div>
      ))}
    </div>
  );
}

export default ProductList;

关键说明

  • 后端代理图片请求时,会自动携带有效的Zoho OAuth2授权头,避免前端直接访问的401问题
  • Access Token会自动刷新,避免过期导致的请求失败
  • 前端只需要和后端API交互,不需要处理Zoho的授权逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 06:45:21