# 多基址 API 回退：401 必须换基址，不能「拿到 JSON 就 resolve」

工作台页面的接口基址是数组：`[公网 Tunnel api.biorisales.site:8443, 本地 http://127.0.0.1:8765]`，按序探测。

## 跗坑（真实发生过）

```js
// ❌ 错：公网端 token 无效 → 401 + JSON 体 → 被当成「成功响应」直接 resolve
fetch(base + path).then(function(r){ return r.json(); }).then(function(d){ resolve(d); });
```
结果：**永远不会试本地端口**，整页显示「登录已过期」，功能全废，但换个浏览器/本地打开就正常——根因难定位。

## 正确写法

```js
fetch(base + path, {method: o.method||'GET', headers: headers, body: o.body})
  .then(function(r){
    // 鉴权失败 / 服务端错误 → throw，交给 catch 换下一个基址
    if (r.status === 401 || r.status === 403 || r.status >= 500) {
      var e = new Error('http ' + r.status); e.status = r.status; throw e;
    }
    return r.json();
  })
  .then(function(d){ API_BASE = base; resolve(d); })
  .catch(function(e){
    if (e && (e.status === 401 || e.status === 403)) lastAuthErr = {ok:false, error:'登录已过期，请重新登录'};
    next();
  });
```

两条必须同时满足：

1. **401/403/5xx 一律换基址**；只有 2xx 才 resolve。
2. **全部基址都失败时返回 `lastAuthErr`**（而不是直接 reject）——否则用户看到的是「接口不可用」，而不是本该看到的「请重新登录」，错误归因会跑偏。

## 同源优先

页面若由本地服务提供（`location.origin` 匹配 `127.0.0.1|localhost`），**把 origin 放到基址数组第一位**——本地实例就能自洽工作，不依赖 Tunnel：

```js
var API_BASES = [];
if (/^https?:\/\/(127\.0\.0\.1|localhost)(:\d+)?$/.test(location.origin)) API_BASES.push(location.origin);
API_BASES.push('https://api.biorisales.site:8443', 'http://127.0.0.1:8765');
```

## 上传接口同一坑

multipart 的 `fetch` 也要 401/5xx 换基址。注意：**FormData 被 fetch 消费后不能重用**，重试必须在循环体内 **重新构造 FormData**：

```js
function tryNext(){
  if (i >= bases.length) { /* 提示失败 */ return; }
  var base = bases[i++];
  var fd = new FormData();                       // ← 每次重试重建
  arr.forEach(function(f){ fd.append('file', f, f.name); });
  fetch(base + '/api/upload', {method:'POST', headers: headers, body: fd})
    .then(function(r){ if (r.status===401||r.status===403||r.status>=500) throw new Error('http '+r.status); return r.json(); })
    .then(onOk).catch(function(){ tryNext(); });
}
```

## 自检

- 本地端口打开页面时，Network 里应看到请求先打到 `127.0.0.1:…`，而不是先去公网绕一圈。
- 把 token 改错后刷新，应看到「登录已过期，请重新登录」，而不是「接口不可用」。
