瀏覽代碼

fix: avoid ssr api fetch headers issue

main
leiyun 1 月之前
父節點
當前提交
7d6db61177
共有 5 個文件被更改,包括 97 次插入6 次删除
  1. +35
    -3
      app/composables/useApi.js
  2. +3
    -1
      ecosystem.config.cjs
  3. +19
    -0
      nuxt.config.js
  4. +21
    -0
      restart.sh
  5. +19
    -2
      server/api/[...].js

+ 35
- 3
app/composables/useApi.js 查看文件

@@ -84,6 +84,31 @@ export function useApi() {
return { timeStr, nonce }
}

async function fetchJson(url, options = {}) {
const response = await fetch(url, options)
const text = await response.text()
let result = null

if (text) {
try {
result = JSON.parse(text)
} catch {
const err = new Error(text || `请求失败 (${response.status})`)
err.code = response.status
throw err
}
}

if (!response.ok) {
const err = new Error(result?.msg || result?.message || `请求失败 (${response.status})`)
err.code = result?.code || response.status
err.response = result
throw err
}

return result || {}
}

/**
* 发起请求
*/
@@ -95,7 +120,10 @@ export function useApi() {
* 服务端(SSR)直接请求后端,避免内部再走一次 Nitro 代理时
* form 请求体丢失导致后端报「client必填」。
*/
const apiBase = useRuntimeConfig().public.apiBase
const runtimeConfig = useRuntimeConfig()
const apiBase = import.meta.server
? (runtimeConfig.apiServerBase || runtimeConfig.public.apiBase)
: runtimeConfig.public.apiBase
const url = import.meta.server ? apiBase + path : '/api' + path

const timestamp = Math.floor(Date.now() / 1000)
@@ -143,7 +171,9 @@ export function useApi() {
fetchOptions.query = allParams
// GET 请求通过 server proxy 时,参数在 query 里
const finalUrl = url + (queryStr ? '?' + queryStr : '')
const result = await $fetch(finalUrl, fetchOptions)
const result = import.meta.server
? await fetchJson(finalUrl, fetchOptions)
: await $fetch(finalUrl, fetchOptions)
return handleResult(result)
} else {
// POST: body 为 x-www-form-urlencoded
@@ -154,7 +184,9 @@ export function useApi() {
})
fetchOptions.body = new URLSearchParams(postBody).toString()

const result = await $fetch(url, fetchOptions)
const result = import.meta.server
? await fetchJson(url, fetchOptions)
: await $fetch(url, fetchOptions)
return handleResult(result)
}
}


+ 3
- 1
ecosystem.config.cjs 查看文件

@@ -2,13 +2,15 @@ const appName = process.env.APP_NAME || 'pc_nuxt'
const host = process.env.HOST || '0.0.0.0'
const port = process.env.PORT || '6888'
const apiBase = process.env.NUXT_PUBLIC_API_BASE || 'https://api.aionline.cc'
const apiServerBase = process.env.NUXT_API_SERVER_BASE || apiBase
const env = {
NODE_ENV: 'production',
HOST: host,
PORT: port,
NITRO_HOST: host,
NITRO_PORT: port,
NUXT_PUBLIC_API_BASE: apiBase
NUXT_PUBLIC_API_BASE: apiBase,
NUXT_API_SERVER_BASE: apiServerBase
}

module.exports = {


+ 19
- 0
nuxt.config.js 查看文件

@@ -36,11 +36,28 @@ function resolveApiBase() {
return value
}

function resolveServerApiBase() {
const value = String(process.env.NUXT_API_SERVER_BASE || apiBase).trim().replace(/\/+$/, '')
let url
try {
url = new URL(value)
} catch {
throw new Error(`[pc_nuxt] NUXT_API_SERVER_BASE 不是有效 URL:${value || '(empty)'}`)
}

if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error(`[pc_nuxt] NUXT_API_SERVER_BASE 仅支持 http/https:${value}`)
}

return value
}

if (process.env.API_BASE && !process.env.NUXT_PUBLIC_API_BASE) {
console.warn('[pc_nuxt] API_BASE 已废弃,请改用 NUXT_PUBLIC_API_BASE')
}

const apiBase = resolveApiBase()
const apiServerBase = resolveServerApiBase()

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
@@ -72,6 +89,8 @@ export default defineNuxtConfig({

// 运行时公开配置(客户端可访问)
runtimeConfig: {
// 服务端 SSR / Nitro 代理专用后端地址。可设为 http://127.0.0.1:16888,避免服务端回打公网域名。
apiServerBase,
public: {
// ThinkJS ai_api 地址,按环境覆盖
apiBase,


+ 21
- 0
restart.sh 查看文件

@@ -9,9 +9,11 @@ set -Eeuo pipefail
APP_NAME="${APP_NAME:-pc_nuxt}"
BRANCH="${BRANCH:-}"
NUXT_PUBLIC_API_BASE="${NUXT_PUBLIC_API_BASE:-https://api.aionline.cc}"
NUXT_API_SERVER_BASE="${NUXT_API_SERVER_BASE:-$NUXT_PUBLIC_API_BASE}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

export NUXT_PUBLIC_API_BASE
export NUXT_API_SERVER_BASE

cd "$SCRIPT_DIR"

@@ -53,6 +55,23 @@ if (!['http:', 'https:'].includes(url.protocol) || isPrivate) {
NODE
}

validate_server_api_base() {
node - "$NUXT_API_SERVER_BASE" <<'NODE'
const value = process.argv[2]
let url
try {
url = new URL(value)
} catch {
console.error(`[pc_nuxt] Invalid NUXT_API_SERVER_BASE: ${value || '(empty)'}`)
process.exit(1)
}
if (!['http:', 'https:'].includes(url.protocol)) {
console.error(`[pc_nuxt] NUXT_API_SERVER_BASE must be http(s): ${value}`)
process.exit(1)
}
NODE
}

validate_tls_security() {
if [ "${NODE_TLS_REJECT_UNAUTHORIZED:-}" = "0" ]; then
log "Refusing deployment: NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS certificate verification."
@@ -110,6 +129,7 @@ main() {
require_command pm2
validate_tls_security
validate_api_base
validate_server_api_base
resolve_branch

log "Deploy start: $APP_NAME"
@@ -117,6 +137,7 @@ main() {
log "pnpm: $(pnpm -v)"
log "pm2: $(pm2 -v)"
log "API: $NUXT_PUBLIC_API_BASE"
log "Server API: $NUXT_API_SERVER_BASE"

sync_latest_code
install_dependencies


+ 19
- 2
server/api/[...].js 查看文件

@@ -12,9 +12,25 @@
*/
import { proxyRequest, sendWebResponse } from 'h3'

async function parseUpstreamResponse(response) {
const contentType = response.headers.get('content-type') || ''
const text = await response.text()

if (contentType.includes('application/json')) {
return text ? JSON.parse(text) : {}
}

if (!text) return {}
try {
return JSON.parse(text)
} catch {
return text
}
}

export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event)
const apiBase = String(config.public.apiBase || '').replace(/\/+$/, '')
const apiBase = String(config.apiServerBase || config.public.apiBase || '').replace(/\/+$/, '')
if (!apiBase) {
throw createError({
statusCode: 500,
@@ -83,7 +99,8 @@ export default defineEventHandler(async (event) => {
const upstreamResponse = await fetch(fullUrl, fetchOptions)
return sendWebResponse(event, upstreamResponse)
}
return await $fetch(fullUrl, fetchOptions)
const upstreamResponse = await fetch(fullUrl, fetchOptions)
return await parseUpstreamResponse(upstreamResponse)
} catch (error) {
console.error('[Server Proxy Error]', url, error.message)
return {


Loading…
取消
儲存