diff --git a/.env.development b/.env.development index 1925bf9..b6c0d4d 100644 --- a/.env.development +++ b/.env.development @@ -1,8 +1,9 @@ # VITE_API_BASE_URL='http://localhost:5173/' -# 本地开发统一走线上 Admin API(与 /rbac、/order 等 Vite 代理一致) -VITE_OMS_ADMIN_URL=http://api.nanafa.com/admin -VITE_API_ADMIN_URL=http://api.nanafa.com/admin +# 本地开发统一走测试环境 API(与 /rbac、/order、/api 等 Vite 代理一致) +VITE_API_ORIGIN=https://api.test.ideship.com +VITE_OMS_ADMIN_URL=https://api.test.ideship.com/admin +VITE_API_ADMIN_URL=https://api.test.ideship.com/admin # 智谱 AI(接口映射脚本生成) # 在 https://open.bigmodel.cn 申请 API Key diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 6356efe..da12e8b 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -1,12 +1,11 @@ # 复制到 itfx-front 仓库 .gitea/workflows/deploy.yml -# 前置: kaniko-git + kaniko-docker-config;act_runner(k3s-host) 只负责 kubectl -# 构建在集群 Kaniko Job(role=build),不跑宿主机 Docker +# push master → prod;push test → test +# 构建始终用 prod kubeconfig(Kaniko);仅 deploy 切集群 name: Build and Deploy Front on: push: - branches: [master, main] - workflow_dispatch: + branches: [master, test] env: IMAGE_NAME: itfx-front @@ -20,13 +19,45 @@ jobs: - name: Build and deploy run: | set -euxo pipefail - export KUBECONFIG=/etc/rancher/k3s/k3s.yaml - TAG="${{ gitea.sha }}" - IMG="${{ env.TCR_REGISTRY }}/${{ env.TCR_NAMESPACE }}/${{ env.IMAGE_NAME }}:${TAG}" + REF="${{ gitea.ref_name }}" + case "$REF" in + test) TARGET=test ;; + master) TARGET=prod ;; + *) echo "unsupported branch: $REF"; exit 1 ;; + esac + + SHA="${{ gitea.sha }}" REPO="${{ gitea.repository }}" - REPO="${REPO:-${{ github.repository }}}" test -n "$REPO" && [[ "$REPO" == */* ]] - echo "REPO=$REPO TAG=$TAG host=$(hostname)" + + case "$TARGET" in + test) TAG="test-${SHA}" ;; + *) TAG="${SHA}" ;; + esac + + IMG="${{ env.TCR_REGISTRY }}/${{ env.TCR_NAMESPACE }}/${{ env.IMAGE_NAME }}:${TAG}" + echo "TARGET=$TARGET REF=$REF REPO=$REPO TAG=$TAG host=$(hostname)" + + export KUBECONFIG=/etc/rancher/k3s/k3s.yaml + unset NAMESPACE cd /data/k3s - ./scripts/ci-publish-front.sh "$REPO" "${{ env.IMAGE_NAME }}" "$TAG" "$TAG" + ./scripts/ci-publish-front.sh "$REPO" "${{ env.IMAGE_NAME }}" "$TAG" "$SHA" + + case "$TARGET" in + test) + export KUBECONFIG=/etc/rancher/k3s/k3s-test.yaml + export NAMESPACE=test + export MANIFEST_ROOT=/data/k3s-test + ;; + *) + export KUBECONFIG=/etc/rancher/k3s/k3s.yaml + export NAMESPACE=prod + export MANIFEST_ROOT=/data/k3s + ;; + esac + if [[ ! -f "$KUBECONFIG" ]]; then + echo "FAIL: missing KUBECONFIG=$KUBECONFIG host=$(hostname) uid=$(id -u)" + ls -la /etc/rancher/k3s/ /root/.kube/ 2>/dev/null || true + exit 1 + fi ./scripts/ci-deploy-front.sh "$IMG" diff --git a/Dockerfile.k3s b/Dockerfile.k3s index b1fcfdc..0a93a1b 100644 --- a/Dockerfile.k3s +++ b/Dockerfile.k3s @@ -1,5 +1,6 @@ -# k3s / Kaniko:静态 SPA(方案 A)。API 直连 api.ideship.com,镜像内 nginx 不做反代 -# 基础镜像走 DaoCloud,避免 Kaniko 直连 docker.io 超时 +# k3s / Kaniko:静态 SPA(方案 A)。API 由浏览器按站点域名直连 +# app.test.ideship.com → api.test.ideship.com;其余 → api.ideship.com +# 镜像内 nginx 不做反代;也可用构建期 VITE_API_ORIGIN / VITE_API_ADMIN_URL 覆盖 FROM docker.m.daocloud.io/library/node:20-alpine AS build WORKDIR /app RUN npm config set registry https://registry.npmmirror.com \ diff --git a/nginx-k3s.conf b/nginx-k3s.conf index 9ef5d41..351ea47 100644 --- a/nginx-k3s.conf +++ b/nginx-k3s.conf @@ -1,4 +1,4 @@ -# k3s 方案 A:仅静态 SPA。API 由浏览器直连 api.ideship.com(本文件不做反代)。 +# k3s 方案 A:仅静态 SPA。API 由浏览器按域名直连(prod: api.ideship.com / test: api.test.ideship.com)。 server { listen 80; server_name _; diff --git a/src/components/ApiTest/index.vue b/src/components/ApiTest/index.vue index 22c2f5d..ad7bdc3 100644 --- a/src/components/ApiTest/index.vue +++ b/src/components/ApiTest/index.vue @@ -15,6 +15,7 @@ import { getMethodText } from '@/views/api/upstreamInterface/utils'; import { useTabRowCounts } from '@/utils/useTabRowCounts'; import RequestLog from '@/views/api/requestLog/index.vue'; + import { prodApiOrigin } from '@/utils/rewriteBrowserApiPath'; const { t } = useI18n(); @@ -1336,21 +1337,29 @@ /** * 开发态:把已知外域 URL 改写到 Vite 代理,避免浏览器 CORS。 - * - api.nanafa.com → /nanafa/... + * - api.nanafa.com / api(.test).ideship.com → /nanafa/... * - 旧 itfx IP → /api/... * - lsp.globex.cn → /external/... - * 生产:nanafa 旧址改写到 api.ideship.com(方案 A) + * 生产/测试构建:nanafa 旧址改写到当前环境 API 根(方案 A) */ function rewriteDebuggerUrlForProxy(rawUrl: string): string { const url = String(rawUrl || '').trim(); if (!url) return url; + const apiOrigin = prodApiOrigin(); - // http(s)://api.nanafa.com/... - if (/^https?:\/\/api\.nanafa\.com(?::\d+)?(\/|$)/i.test(url)) { + // http(s)://api.nanafa.com/... 或 api(.test).ideship.com/... + if (/^https?:\/\/api\.(?:nanafa|ideship)\.com(?::\d+)?(\/|$)/i.test(url) + || /^https?:\/\/api\.test\.ideship\.com(?::\d+)?(\/|$)/i.test(url)) { if (import.meta.env.DEV) { - return url.replace(/^https?:\/\/api\.nanafa\.com(?::\d+)?/i, '/nanafa'); + return url + .replace(/^https?:\/\/api\.nanafa\.com(?::\d+)?/i, '/nanafa') + .replace(/^https?:\/\/api\.test\.ideship\.com(?::\d+)?/i, '/nanafa') + .replace(/^https?:\/\/api\.ideship\.com(?::\d+)?/i, '/nanafa'); } - return url.replace(/^https?:\/\/api\.nanafa\.com(?::\d+)?/i, 'https://api.ideship.com'); + return url + .replace(/^https?:\/\/api\.nanafa\.com(?::\d+)?/i, apiOrigin) + .replace(/^https?:\/\/api\.test\.ideship\.com(?::\d+)?/i, apiOrigin) + .replace(/^https?:\/\/api\.ideship\.com(?::\d+)?/i, apiOrigin); } // 旧 itfx 直连 IP @@ -1358,7 +1367,7 @@ if (import.meta.env.DEV) { return url.replace('http://43.139.210.35:8888', '/api'); } - return url.replace('http://43.139.210.35:8888', 'https://api.ideship.com/itfx'); + return url.replace('http://43.139.210.35:8888', `${apiOrigin}/itfx`); } // 示例外部服务商 diff --git a/src/utils/adminApi.ts b/src/utils/adminApi.ts index 379d4db..93eb045 100644 --- a/src/utils/adminApi.ts +++ b/src/utils/adminApi.ts @@ -1,9 +1,11 @@ /** * Admin API 地址(方案 A) * - 开发:相对路径,Vite 代理到 Admin - * - 生产:直连 https://api.ideship.com/admin(可用 VITE_API_ADMIN_URL 覆盖) + * - 生产/测试:{apiOrigin}/admin(可用 VITE_API_ADMIN_URL 覆盖) */ +import { prodApiOrigin } from './rewriteBrowserApiPath' + function normalizeBase(base: string): string { return base.trim().replace(/\/$/, '') } @@ -12,12 +14,10 @@ function ensureLeadingSlash(path: string): string { return path.startsWith('/') ? path : `/${path}` } -const DEFAULT_PROD_ADMIN = 'https://api.ideship.com/admin' - function prodAdminBase(): string { const override = import.meta.env.VITE_API_ADMIN_URL as string | undefined if (override?.trim()) return normalizeBase(override) - return DEFAULT_PROD_ADMIN + return `${prodApiOrigin()}/admin` } /** 浏览器请求 Admin API(OpenAPI.BASE 或 omsUrl/orderUrl 等) */ @@ -43,13 +43,13 @@ export function resolveDirectAdminApiUrl(): string { return normalizeBase(override) } if (import.meta.env.DEV) { - return 'http://api.nanafa.com/admin' + return 'https://api.test.ideship.com/admin' } - return DEFAULT_PROD_ADMIN + return prodAdminBase() } /** 静态资源(uploads)在 Admin wwwroot,须带 `/admin`。 - * 生产默认 api 域名;开发空串走 Vite `/uploads` 代理。 + * 生产/测试默认 api 域名;开发空串走 Vite `/uploads` 代理。 */ export function resolveAdminAssetOrigin(): string { const override = import.meta.env.VITE_API_ASSET_ORIGIN as string | undefined @@ -57,7 +57,7 @@ export function resolveAdminAssetOrigin(): string { return normalizeBase(override) } if (!import.meta.env.DEV) { - return DEFAULT_PROD_ADMIN + return prodAdminBase() } return '' } diff --git a/src/utils/rewriteBrowserApiPath.ts b/src/utils/rewriteBrowserApiPath.ts index c165f98..d3998b7 100644 --- a/src/utils/rewriteBrowserApiPath.ts +++ b/src/utils/rewriteBrowserApiPath.ts @@ -1,5 +1,8 @@ /** - * 方案 A:生产直连 api.ideship.com PATHBASE;开发仍走 Vite 代理旧路径。 + * 方案 A:浏览器直连 API 网关 PATHBASE;开发仍走 Vite 代理旧路径。 + * - prod:https://api.ideship.com + * - test(app.test.ideship.com):https://api.test.ideship.com + * - 可用 VITE_API_ORIGIN 覆盖 */ const SPA_API_PREFIXES = [ @@ -16,10 +19,19 @@ const SPA_API_PREFIXES = [ ] as const const DEFAULT_PROD_API = 'https://api.ideship.com' +const DEFAULT_TEST_API = 'https://api.test.ideship.com' -function prodApiOrigin(): string { +/** 生产/测试 API 根(无尾斜杠) */ +export function prodApiOrigin(): string { const override = (import.meta.env.VITE_API_ORIGIN as string | undefined)?.trim() - return (override || DEFAULT_PROD_API).replace(/\/$/, '') + if (override) return override.replace(/\/$/, '') + if (typeof window !== 'undefined') { + const h = window.location.hostname + if (h === 'app.test.ideship.com' || h.endsWith('.test.ideship.com')) { + return DEFAULT_TEST_API + } + } + return DEFAULT_PROD_API } function isItfxSpaPath(afterApi: string): boolean { diff --git a/vite.config.ts b/vite.config.ts index 99c1cfa..33d0245 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -14,9 +14,12 @@ import configCompressPlugin from './config/plugin/compress'; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), ''); - // Admin API 目标:由 VITE_OMS_ADMIN_URL 决定(开发默认线上) + // 开发默认打测试环境;可用 VITE_API_ORIGIN / VITE_OMS_ADMIN_URL 覆盖 + const apiOrigin = ( + env.VITE_API_ORIGIN || 'https://api.test.ideship.com' + ).replace(/\/$/, ''); const omsAdminTarget = ( - env.VITE_OMS_ADMIN_URL || 'http://api.nanafa.com/admin' + env.VITE_OMS_ADMIN_URL || `${apiOrigin}/admin` ).replace(/\/$/, ''); return { @@ -34,16 +37,17 @@ export default defineConfig(({ mode }) => { changeOrigin: true, secure: false, }, - // 上传静态资源:/api/upload/xxx -> http://api.nanafa.com/upload/xxx + // 上传静态资源:/api/upload/xxx -> {api}/upload/xxx // 必须放在通用 /api 规则前,避免被转发到 /itfx/upload 导致 404 '/api/upload': { - target: 'http://api.nanafa.com', + target: apiOrigin, changeOrigin: true, + secure: false, rewrite: (path) => path.replace(/^\/api/, ''), timeout: 30000, proxyTimeout: 30000, }, - // Admin API:目标由 VITE_OMS_ADMIN_URL 决定(默认 http://api.nanafa.com/admin) + // Admin API:目标由 VITE_OMS_ADMIN_URL 决定(默认 https://api.test.ideship.com/admin) '/rbac': { target: omsAdminTarget, changeOrigin: true, @@ -281,10 +285,11 @@ export default defineConfig(({ mode }) => { secure: false, rewrite: (path) => path.replace(/^\/api/, ''), }, - // 轨迹(track)43.139.210.35:8889 -> http://api.nanafa.com/track,路径与 rbac、api 一致:/Track/xxx + // 轨迹(track):/Track/xxx → {api}/track/Track/xxx '/Track': { - target: 'http://api.nanafa.com', + target: apiOrigin, changeOrigin: true, + secure: false, rewrite: (path) => '/track' + path, configure: (proxy) => { proxy.on('error', (err, req, res) => { @@ -292,11 +297,12 @@ export default defineConfig(({ mode }) => { }); }, }, - // API(itfx)43.139.210.35:8888 -> http://api.nanafa.com/itfx + // API(itfx):/api/... → {api}/itfx/... // 使用正则表达式匹配,排除前端路由路径 '^/api/(?!upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface|downstreamInterface/newPort|appList|appList/createApplication|requestLog|callStatistics|apiDebugger|trackApi)': { - target: 'http://api.nanafa.com/itfx', + target: `${apiOrigin}/itfx`, changeOrigin: true, + secure: false, rewrite: (path) => path.replace(/^\/api/, ''), timeout: 30000, proxyTimeout: 30000, @@ -321,9 +327,9 @@ export default defineConfig(({ mode }) => { timeout: 30000, proxyTimeout: 30000, }, - // api.nanafa.com 整站代理(ApiTest 调试):/nanafa/itfx/xxx → http://api.nanafa.com/itfx/xxx + // API 网关整站代理(ApiTest 调试,路径名历史遗留):/nanafa/itfx/xxx → {api}/itfx/xxx '/nanafa': { - target: 'http://api.nanafa.com', + target: apiOrigin, changeOrigin: true, secure: false, rewrite: (path) => path.replace(/^\/nanafa/, ''), diff --git a/xhs-agent-desktop/app/config_store.py b/xhs-agent-desktop/app/config_store.py index 0f4f097..d33bc34 100644 --- a/xhs-agent-desktop/app/config_store.py +++ b/xhs-agent-desktop/app/config_store.py @@ -9,7 +9,7 @@ from pathlib import Path @dataclass class AgentSettings: - oms_url: str = "http://api.nanafa.com/admin" + oms_url: str = "https://api.test.ideship.com/admin" token: str = "" device_name: str = "" bridge_port: int = 9333 diff --git a/xhs-agent-desktop/engine/xhs_bridge/config.py b/xhs-agent-desktop/engine/xhs_bridge/config.py index c21872f..8457d09 100644 --- a/xhs-agent-desktop/engine/xhs_bridge/config.py +++ b/xhs-agent-desktop/engine/xhs_bridge/config.py @@ -110,7 +110,7 @@ class LicenseConfig: class OmsConfig: """OMS 物流查价 API(POST /order/price-check)。""" - api_base_url: str = "http://api.nanafa.com/admin" + api_base_url: str = "https://api.test.ideship.com/admin" timeout_sec: int = 30 auth_token: str = "" contact_name: str = ""