Sfoglia il codice sorgente

fix: align production API and in-page login

main
leiyun 1 mese fa
parent
commit
13c838bad7
8 ha cambiato i file con 133 aggiunte e 11 eliminazioni
  1. +5
    -0
      .env.example
  2. +13
    -3
      README.md
  3. +13
    -2
      app/pages/draw/anime.vue
  4. +8
    -1
      app/pages/writing/naming.vue
  5. +2
    -2
      ecosystem.config.cjs
  6. +43
    -1
      nuxt.config.js
  7. +40
    -0
      restart.sh
  8. +9
    -2
      server/api/[...].js

+ 5
- 0
.env.example Vedi File

@@ -0,0 +1,5 @@
# Local development API. Copy this file to .env when a different address is needed.
NUXT_PUBLIC_API_BASE=http://192.168.31.168:16888

# Public site origin used for canonical URLs when configured.
NUXT_PUBLIC_SITE_URL=http://localhost:6888

+ 13
- 3
README.md Vedi File

@@ -96,17 +96,27 @@ const res = await api.post('/user/login', { username, password })
| --- | --- |
| `0` | 成功,数据在 `data` / `list` 字段 |
| `1000` | 业务异常,错误信息在 `msg` |
| `1009` | 登录失效,客户端自动跳转 `/login` |
| `1009` | 登录失效,客户端清除登录态并打开当前页面的登录弹窗 |

### 配置后端地址

```bash
# 环境变量(优先级最高)
API_BASE=http://192.168.1.100:8360 pnpm run dev
NUXT_PUBLIC_API_BASE=http://192.168.1.100:8360 pnpm run dev

# 或直接修改 nuxt.config.js 中的 runtimeConfig.public.apiBase
# 未设置时,开发环境默认使用 http://192.168.31.168:16888
# 也可以复制 .env.example 为 .env 后修改;不要直接修改 nuxt.config.js
```

生产环境默认使用 `https://api.aionline.cc`。如需覆盖,必须使用公开的 HTTP(S) 地址:

```bash
NUXT_PUBLIC_API_BASE=https://api.aionline.cc ./restart.sh
```

`restart.sh` 会在拉取和构建前校验该地址,拒绝 localhost、环回地址和局域网地址;同一变量会同时传入 Nuxt 构建、Nitro 运行时和 PM2,避免 SSR 与浏览器代理使用不同后端。
发布脚本也会拒绝 `NODE_TLS_REJECT_UNAUTHORIZED=0`,生产机应保持 TLS 证书校验开启。

### 服务端代理

前端调 `/api/**` 会自动代理到 `ai_api`,路径示例:


+ 13
- 2
app/pages/draw/anime.vue Vedi File

@@ -407,7 +407,11 @@ const strengthCopy = computed(() => '推荐 0.6 - 0.8,保留人脸特征')

// ==================== Upload ====================
function triggerUpload() {
if (!user.isLogin.value) { showToast('请先登录'); router.push('/login'); return }
if (!user.isLogin.value) {
showToast('请先登录')
user.showLogin.value = true
return
}
fileInput.value?.click()
}
function onFileChange(e) {
@@ -518,6 +522,9 @@ onMounted(() => {
loadDemo()
if (user.isLogin.value) loadBalance()
})
watch(() => user.isLogin.value, (loggedIn) => {
if (loggedIn) loadBalance()
})
onUnmounted(() => clearPoll())

// ==================== 历史记录 / 详情 ====================
@@ -661,7 +668,11 @@ async function startPoll() {
}

async function handleSubmit() {
if (!user.isLogin.value) { showToast('请先登录'); router.push('/login'); return }
if (!user.isLogin.value) {
showToast('请先登录')
user.showLogin.value = true
return
}
if (uploading.value) { showToast('图片正在上传'); return }
if (!form.imgs.length) { showToast('请上传参考图'); return }
if (submitting.value) return


+ 8
- 1
app/pages/writing/naming.vue Vedi File

@@ -855,6 +855,9 @@ async function loadBalance() {
user.refreshBalance()
}
onMounted(() => { loadBilling(); if (user.isLogin.value) loadBalance() })
watch(() => user.isLogin.value, (loggedIn) => {
if (loggedIn) loadBalance()
})

const submitting = ref(false)
const questionTitle = ref('')
@@ -880,7 +883,11 @@ const streamContent = ref('')
const streamPreviewRef = ref(null)

async function handleSubmit() {
if (!user.isLogin.value) { showToast('请先登录'); return router.push('/login') }
if (!user.isLogin.value) {
showToast('请先登录')
user.showLogin.value = true
return
}
if (!form.surname) { showToast('请输入姓氏'); return }
if (submitting.value) return
syncBirthFields()


+ 2
- 2
ecosystem.config.cjs Vedi File

@@ -1,14 +1,14 @@
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.API_BASE || 'https://api.aionline.cc'
const apiBase = process.env.NUXT_PUBLIC_API_BASE || 'https://api.aionline.cc'
const env = {
NODE_ENV: 'production',
HOST: host,
PORT: port,
NITRO_HOST: host,
NITRO_PORT: port,
API_BASE: apiBase
NUXT_PUBLIC_API_BASE: apiBase
}

module.exports = {


+ 43
- 1
nuxt.config.js Vedi File

@@ -1,5 +1,47 @@
import { fileURLToPath } from 'node:url'

const isProduction = process.env.NODE_ENV === 'production'
const defaultApiBase = isProduction
? 'https://api.aionline.cc'
: 'http://192.168.31.168:16888'

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

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

const hostname = url.hostname.toLowerCase()
const isPrivateHost = hostname === 'localhost'
|| hostname === '0.0.0.0'
|| /^127\./.test(hostname)
|| hostname === '[::1]'
|| hostname.endsWith('.local')
|| /^10\./.test(hostname)
|| /^192\.168\./.test(hostname)
|| /^169\.254\./.test(hostname)
|| /^172\.(1[6-9]|2\d|3[01])\./.test(hostname)

if (isProduction && isPrivateHost) {
throw new Error(`[pc_nuxt] 生产环境禁止使用本地/局域网 API:${value},请设置 NUXT_PUBLIC_API_BASE`)
}

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()

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
@@ -32,7 +74,7 @@ export default defineNuxtConfig({
runtimeConfig: {
public: {
// ThinkJS ai_api 地址,按环境覆盖
apiBase: process.env.API_BASE || 'http://192.168.31.168:16888',
apiBase,
// 邀请链接正式域名;未配置时自动使用当前访问域名。
siteUrl: process.env.NUXT_PUBLIC_SITE_URL || '',
},


+ 40
- 0
restart.sh Vedi File

@@ -4,8 +4,11 @@ set -Eeuo pipefail

APP_NAME="${APP_NAME:-pc_nuxt}"
BRANCH="${BRANCH:-}"
NUXT_PUBLIC_API_BASE="${NUXT_PUBLIC_API_BASE:-https://api.aionline.cc}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

export NUXT_PUBLIC_API_BASE

cd "$SCRIPT_DIR"

log() {
@@ -19,6 +22,40 @@ require_command() {
fi
}

validate_api_base() {
node - "$NUXT_PUBLIC_API_BASE" <<'NODE'
const value = process.argv[2]
let url
try {
url = new URL(value)
} catch {
console.error(`[pc_nuxt] Invalid NUXT_PUBLIC_API_BASE: ${value || '(empty)'}`)
process.exit(1)
}
const hostname = url.hostname.toLowerCase()
const isPrivate = hostname === 'localhost'
|| hostname === '0.0.0.0'
|| /^127\./.test(hostname)
|| hostname === '[::1]'
|| hostname.endsWith('.local')
|| /^10\./.test(hostname)
|| /^192\.168\./.test(hostname)
|| /^169\.254\./.test(hostname)
|| /^172\.(1[6-9]|2\d|3[01])\./.test(hostname)
if (!['http:', 'https:'].includes(url.protocol) || isPrivate) {
console.error(`[pc_nuxt] Production API must be a public http(s) URL: ${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."
exit 1
fi
}

ensure_clean_worktree() {
if [ -n "$(git status --porcelain)" ]; then
log "Working tree is not clean. Commit/stash/clean changes before deploy."
@@ -71,12 +108,15 @@ main() {
require_command node
require_command pnpm
require_command pm2
validate_tls_security
validate_api_base
resolve_branch

log "Deploy start: $APP_NAME"
log "Node: $(node -v 2>/dev/null || echo 'unknown')"
log "pnpm: $(pnpm -v)"
log "pm2: $(pm2 -v)"
log "API: $NUXT_PUBLIC_API_BASE"

pull_latest_code
install_dependencies


+ 9
- 2
server/api/[...].js Vedi File

@@ -8,12 +8,19 @@
* 1. 避免浏览器跨域
* 2. 服务端转发自动携带 cookie,保持登录态
*
* 环境变量 API_BASE 可覆盖目标地址(生产环境指向 https://api.aionline.cc)
* 环境变量 NUXT_PUBLIC_API_BASE 可覆盖目标地址(生产环境指向 https://api.aionline.cc)
*/
import { proxyRequest, sendWebResponse } from 'h3'

export default defineEventHandler(async (event) => {
const apiBase = process.env.API_BASE || 'http://192.168.31.168:16888'
const config = useRuntimeConfig(event)
const apiBase = String(config.public.apiBase || '').replace(/\/+$/, '')
if (!apiBase) {
throw createError({
statusCode: 500,
statusMessage: 'NUXT_PUBLIC_API_BASE is not configured',
})
}

// 去掉 /api 前缀,得到真实后端路径
let path = event.path.replace(/^\/api/, '') || '/'


Caricamento…
Annulla
Salva