fix(admin+server): 酷狗 baseUrl 默认值移除 127.0.0.1 + 新增后台配置入口与推荐快捷地址

问题:容器内 SystemSettings.kugouApiBaseUrl 默认 http://127.0.0.1:3000 指向自己,sidecar 不可达。

修复:1) schema + migration 移除 kugouApiBaseUrl 默认值;kugouService + status API 兜底改空串(不再假设有 localhost),优先级 env(KUGOU_API_BASE) > DB。

2) 新增 POST /api/admin/kugou/settings:ADMIN+/SUPER 即时写 kugouApiBaseUrl 入库+刷新 CacheService。

3) KuGouImporter.client.vue 左栏新增 baseUrl 输入框(回车即存)+ 保存并测试按钮 + 两个推荐快捷按钮(容器宿主 http://172.19.0.1:3000、公网中继 https://bywmusic.bzw-hheb.top/kugou)+ 当前生效地址显式提示。

4) UI/Icon.vue 新增 server / globe / save 三个 SVG 图标与上述入口配套。
This commit is contained in:
2026-09-19 13:40:59 +08:00
parent 6b3a83ebeb
commit 381f3debf1
8 changed files with 143 additions and 6 deletions
+79 -1
View File
@@ -70,7 +70,55 @@
<LoadingState :height="72" />
</div>
<div class="mt-5 space-y-3">
<div class="mt-5 space-y-4">
<!-- baseUrl 配置 -->
<div class="rounded-xl border border-zinc-200 bg-zinc-50 p-4 space-y-3">
<div class="flex items-center justify-between">
<label class="text-[11px] font-black uppercase tracking-wider text-zinc-600">
KuGouMusicApi 地址
</label>
<span
v-if="baseUrlSaving"
class="text-[11px] text-zinc-500"
>保存中...</span>
<span
v-else-if="baseUrlSavedTick"
class="text-[11px] text-emerald-600 font-bold"
>已保存</span>
</div>
<input
v-model="baseUrlInput"
type="url"
placeholder="例:http://172.19.0.1:3000 或 https://bywmusic.bzw-hheb.top/kugou,留空则走环境变量 KUGOU_API_BASE"
class="w-full h-[42px] px-3 rounded-xl border border-zinc-200 bg-white text-[12px] text-zinc-900 placeholder:text-zinc-400 focus:outline-none focus:ring-2 focus:ring-indigo-500/40 focus:border-indigo-400"
@keydown.enter="saveBaseUrl"
/>
<div class="flex flex-wrap gap-2">
<button
class="h-[34px] px-3 rounded-lg text-[11px] font-bold bg-white hover:bg-zinc-100 border border-zinc-200 text-zinc-700 transition-colors inline-flex items-center gap-1"
@click="baseUrlInput = 'http://172.19.0.1:3000'"
>
<Icon name="server" :size="13" /> 容器直连宿主 http://172.19.0.1:3000
</button>
<button
class="h-[34px] px-3 rounded-lg text-[11px] font-bold bg-white hover:bg-zinc-100 border border-zinc-200 text-zinc-700 transition-colors inline-flex items-center gap-1"
@click="baseUrlInput = 'https://bywmusic.bzw-hheb.top/kugou'"
>
<Icon name="globe" :size="13" /> 中继 https://bywmusic.bzw-hheb.top/kugou
</button>
<button
class="h-[34px] px-3 rounded-lg text-[11px] font-bold bg-zinc-900 hover:bg-zinc-800 text-white transition-colors inline-flex items-center gap-1"
@click="saveBaseUrl"
:disabled="baseUrlSaving"
>
<Icon name="save" :size="13" /> 保存并测试
</button>
</div>
<p v-if="status?.service?.baseUrl" class="text-[11px] text-zinc-500 leading-relaxed">
当前生效地址:<code class="px-1.5 py-0.5 rounded-md bg-white border border-zinc-200 text-zinc-700 font-mono">{{ status.service.baseUrl }}</code>
</p>
</div>
<div class="flex gap-2">
<button
class="flex-1 h-[44px] px-3 rounded-xl text-[12px] font-bold bg-zinc-100 hover:bg-zinc-200 text-zinc-700 transition-colors flex items-center justify-center gap-1.5"
@@ -379,6 +427,9 @@ const qrContext = ref<any>(null)
const qrHint = ref('')
const qrState = ref<number>(0)
const qrPollTimer = ref<any>(null)
const baseUrlInput = ref('')
const baseUrlSaving = ref(false)
const baseUrlSavedTick = ref(0)
const totalLoadedSongs = computed(
() => Object.values(schedulesByDate.value).reduce((n, arr) => n + arr.length, 0),
@@ -436,11 +487,38 @@ async function reloadStatus() {
try {
const r = await $fetch('/api/admin/kugou/status', { ...auth.getAuthConfig() })
status.value = r
if (r?.service?.baseUrl && !baseUrlInput.value) {
baseUrlInput.value = r.service.baseUrl
}
} catch (e: any) {
toast.show?.(e?.message || '状态刷新失败', 'error')
}
}
async function saveBaseUrl() {
const value = (baseUrlInput.value || '').trim()
if (value && !/^https?:\/\//i.test(value)) {
toast.show?.('请填写合法的 http(s) URL,或留空走环境变量 KUGOU_API_BASE', 'error')
return
}
baseUrlSaving.value = true
baseUrlSavedTick.value = 0
try {
await $fetch('/api/admin/kugou/settings', {
method: 'POST',
...auth.getAuthConfig(),
body: { kugouApiBaseUrl: value || null },
})
toast.show?.('地址已保存', 'success')
baseUrlSavedTick.value = Date.now()
await reloadStatus()
} catch (e: any) {
toast.show?.(e?.message || '保存失败', 'error')
} finally {
baseUrlSaving.value = false
}
}
async function startQrLogin() {
qrLoading.value = true
qrOpen.value = true
+22
View File
@@ -377,6 +377,28 @@
/>
</g>
<!-- 服务器图标 -->
<g v-else-if="name === 'server'">
<rect x="2" y="2" width="20" height="8" rx="2" />
<rect x="2" y="14" width="20" height="8" rx="2" />
<line x1="6" x2="6.01" y1="6" y2="6" />
<line x1="6" x2="6.01" y1="18" y2="18" />
</g>
<!-- 地球图标 (globe) -->
<g v-else-if="name === 'globe'">
<circle cx="12" cy="12" r="10" />
<path d="M2 12h20" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</g>
<!-- 保存图标 -->
<g v-else-if="name === 'save'">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
<polyline points="17 21 17 13 7 13 7 21" />
<polyline points="7 3 7 8 15 8" />
</g>
<!-- 搜索图标 -->
<g v-else-if="name === 'search'">
<circle cx="11" cy="11" r="8" />
@@ -1,6 +1,6 @@
-- 酷狗导入:管理员账号凭证 + 按日歌单同步记录表
ALTER TABLE "SystemSettings"
ADD COLUMN IF NOT EXISTS "kugouApiBaseUrl" text DEFAULT 'http://127.0.0.1:3000',
ADD COLUMN IF NOT EXISTS "kugouApiBaseUrl" text,
ADD COLUMN IF NOT EXISTS "kugouAdminCookie" text,
ADD COLUMN IF NOT EXISTS "kugouAdminToken" text,
ADD COLUMN IF NOT EXISTS "kugouAdminUser" jsonb,
+1 -1
View File
@@ -354,7 +354,7 @@ export const systemSettings = pgTable('SystemSettings', {
enabledThemes: text('enabledThemes').default('[\"System\",\"ClassicDark\",\"ClassicLight\",\"ModernLight\"]').notNull(),
enabledPlatforms: text('enabledPlatforms'),
platformOrder: text('platformOrder'),
kugouApiBaseUrl: text('kugouApiBaseUrl').default('http://127.0.0.1:3000'),
kugouApiBaseUrl: text('kugouApiBaseUrl'),
kugouAdminCookie: text('kugouAdminCookie'),
kugouAdminToken: text('kugouAdminToken'),
kugouAdminUser: jsonb('kugouAdminUser').$type<Record<string, any>>(),
+37
View File
@@ -0,0 +1,37 @@
import { eq } from 'drizzle-orm'
import { db } from '~/drizzle/db'
import { systemSettings } from '~/drizzle/schema'
import { CacheService } from '~~/server/services/cacheService'
export default defineEventHandler(async (event) => {
const user = event.context.user
if (!user) throw createError({ statusCode: 401, message: '未登录' })
if (!['ADMIN', 'SUPER_ADMIN'].includes(user.role))
throw createError({ statusCode: 403, message: '仅管理员可修改酷狗配置' })
const body = await readBody(event)
const baseUrl = String(body?.kugouApiBaseUrl || '').trim()
if (baseUrl && !/^https?:\/\//i.test(baseUrl)) {
throw createError({ statusCode: 400, message: 'kugouApiBaseUrl 需要是合法的 http(s) URL,留空表示禁用 sidecar' })
}
const rows = await db.select().from(systemSettings).limit(1)
const patch: Record<string, any> = {
kugouApiBaseUrl: baseUrl || null,
updatedAt: new Date(),
}
if (!rows[0]) {
await db.insert(systemSettings).values(patch)
} else {
await db.update(systemSettings).set(patch).where(eq(systemSettings.id, rows[0].id))
}
try {
const cs = CacheService.getInstance()
await cs.setSystemSettings?.({ ...(rows[0] || {}), ...patch })
} catch {}
return { ok: true, kugouApiBaseUrl: baseUrl || null }
})
+1 -1
View File
@@ -17,7 +17,7 @@ export default defineEventHandler(async (event) => {
const rows = await db.select().from(systemSettings).limit(1)
const settings = rows[0] as any
const usedBase = base || settings?.kugouApiBaseUrl || process.env.KUGOU_API_BASE || 'http://127.0.0.1:3000'
const usedBase = base || settings?.kugouApiBaseUrl || process.env.KUGOU_API_BASE || ''
let serviceOk: boolean
let serviceMessage: string
+1 -1
View File
@@ -23,6 +23,6 @@ export default defineEventHandler(async (event) => {
try {
const cs = CacheService.getInstance()
if (rows[0]) await cs.setSystemSettings?.({ ...rows[0], ...patch })
} catch (_) {}
} catch {}
return { ok: true }
})
+1 -1
View File
@@ -59,7 +59,7 @@ export async function kuGouFetch<T = any>(
options?: KuGouCallOptions,
): Promise<T> {
const settings = (await readAdminSettings()) as any
const base = (options?.forceToken ? null : settings?.kugouApiBaseUrl) || process.env.KUGOU_API_BASE || 'http://127.0.0.1:3000'
const base = (options?.forceToken ? null : settings?.kugouApiBaseUrl) || process.env.KUGOU_API_BASE || ''
const headers: Record<string, string> = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128 Safari/537.36',