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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>>(),
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user