Bläddra i källkod

fix: avoid ssr api fetch headers issue

main
leiyun 1 månad sedan
förälder
incheckning
7d6db61177
5 ändrade filer med 97 tillägg och 6 borttagningar
  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 Visa fil

@@ -84,6 +84,31 @@ export function useApi() {
return { timeStr, nonce } 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 代理时 * 服务端(SSR)直接请求后端,避免内部再走一次 Nitro 代理时
* form 请求体丢失导致后端报「client必填」。 * 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 url = import.meta.server ? apiBase + path : '/api' + path


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


+ 3
- 1
ecosystem.config.cjs Visa fil

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


module.exports = { module.exports = {


+ 19
- 0
nuxt.config.js Visa fil

@@ -36,11 +36,28 @@ function resolveApiBase() {
return value 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) { if (process.env.API_BASE && !process.env.NUXT_PUBLIC_API_BASE) {
console.warn('[pc_nuxt] API_BASE 已废弃,请改用 NUXT_PUBLIC_API_BASE') console.warn('[pc_nuxt] API_BASE 已废弃,请改用 NUXT_PUBLIC_API_BASE')
} }


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


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


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


+ 21
- 0
restart.sh Visa fil

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


export NUXT_PUBLIC_API_BASE export NUXT_PUBLIC_API_BASE
export NUXT_API_SERVER_BASE


cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"


@@ -53,6 +55,23 @@ if (!['http:', 'https:'].includes(url.protocol) || isPrivate) {
NODE 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() { validate_tls_security() {
if [ "${NODE_TLS_REJECT_UNAUTHORIZED:-}" = "0" ]; then if [ "${NODE_TLS_REJECT_UNAUTHORIZED:-}" = "0" ]; then
log "Refusing deployment: NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS certificate verification." log "Refusing deployment: NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS certificate verification."
@@ -110,6 +129,7 @@ main() {
require_command pm2 require_command pm2
validate_tls_security validate_tls_security
validate_api_base validate_api_base
validate_server_api_base
resolve_branch resolve_branch


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


sync_latest_code sync_latest_code
install_dependencies install_dependencies


+ 19
- 2
server/api/[...].js Visa fil

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


Laddar…
Avbryt
Spara