feat: 添加镜像配置管理功能及测试页面

This commit is contained in:
2025-09-13 17:06:57 +08:00
parent 6c7a0226fd
commit c8380ddb90
9 changed files with 674 additions and 1 deletions

Binary file not shown.

View File

@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "1.0.1",
"version": "1.0.2",
"main": "dist-electron/main.js",
"scripts": {
"dev": "concurrently \"vite\" \"yarn watch:main\" \"yarn electron-dev\"",

View File

@@ -12,6 +12,9 @@ import 'dayjs/locale/zh-cn'
// 导入日志系统
import { logger } from '@/utils/logger'
// 导入镜像管理器
import { mirrorManager } from '@/utils/mirrorManager'
// 配置dayjs中文本地化
dayjs.locale('zh-cn')
@@ -24,6 +27,13 @@ OpenAPI.BASE = API_ENDPOINTS.local
logger.info('前端应用开始初始化')
logger.info(`API基础URL: ${OpenAPI.BASE}`)
// 初始化镜像管理器(异步)
mirrorManager.initialize().then(() => {
logger.info('镜像管理器初始化完成')
}).catch((error) => {
logger.error('镜像管理器初始化失败:', error)
})
// 创建应用实例
const app = createApp(App)

View File

@@ -94,6 +94,12 @@ const routes: RouteRecordRaw[] = [
component: () => import('../views/Logs.vue'),
meta: { title: '日志查看' },
},
{
path: '/mirror-test',
name: 'MirrorTest',
component: () => import('../views/MirrorTest.vue'),
meta: { title: '镜像配置测试' },
},
]
const router = createRouter({

View File

@@ -0,0 +1,187 @@
/**
* 云端配置管理器
* 负责从云端拉取最新的镜像站配置,如果失败则使用本地兜底配置
*/
import type { MirrorCategory, MirrorConfig } from '@/config/mirrors'
export interface CloudMirrorConfig {
version: string
lastUpdated: string
mirrors: MirrorCategory
apiEndpoints: Record<string, string>
downloadLinks: Record<string, string>
}
export class CloudConfigManager {
private static instance: CloudConfigManager
private cloudConfigUrl = 'https://download.auto-mas.top/d/AUTO_MAS/Server/mirrors.json'
private fallbackConfig: CloudMirrorConfig | null = null
private currentConfig: CloudMirrorConfig | null = null
private fetchTimeout = 3000 // 3秒超时
private constructor() {}
static getInstance(): CloudConfigManager {
if (!CloudConfigManager.instance) {
CloudConfigManager.instance = new CloudConfigManager()
}
return CloudConfigManager.instance
}
/**
* 设置兜底配置(本地配置)
*/
setFallbackConfig(config: CloudMirrorConfig): void {
this.fallbackConfig = config
}
/**
* 从云端拉取最新配置
*/
async fetchCloudConfig(): Promise<CloudMirrorConfig | null> {
try {
console.log('正在从云端拉取镜像站配置...')
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), this.fetchTimeout)
const response = await fetch(this.cloudConfigUrl, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Cache-Control': 'no-cache'
},
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const config: CloudMirrorConfig = await response.json()
// 验证配置格式
if (!this.validateConfig(config)) {
throw new Error('云端配置格式不正确')
}
this.currentConfig = config
console.log('云端配置拉取成功:', config.version)
return config
} catch (error) {
console.warn('云端配置拉取失败:', error)
return null
}
}
/**
* 验证配置格式是否正确
*/
private validateConfig(config: any): config is CloudMirrorConfig {
if (!config || typeof config !== 'object') {
return false
}
// 检查必需字段
if (!config.version || !config.mirrors || !config.apiEndpoints || !config.downloadLinks) {
return false
}
// 检查mirrors结构
if (typeof config.mirrors !== 'object') {
return false
}
// 检查每个镜像类型的配置
for (const [type, mirrors] of Object.entries(config.mirrors)) {
if (!Array.isArray(mirrors)) {
return false
}
// 检查每个镜像配置
for (const mirror of mirrors as any[]) {
if (!mirror.key || !mirror.name || !mirror.url || !mirror.type) {
return false
}
}
}
return true
}
/**
* 获取当前有效配置(优先云端,兜底本地)
*/
getCurrentConfig(): CloudMirrorConfig | null {
return this.currentConfig || this.fallbackConfig
}
/**
* 初始化配置(启动时调用)
*/
async initializeConfig(fallbackConfig: CloudMirrorConfig): Promise<CloudMirrorConfig> {
this.setFallbackConfig(fallbackConfig)
// 尝试拉取云端配置
const cloudConfig = await this.fetchCloudConfig()
if (cloudConfig) {
console.log('使用云端配置')
return cloudConfig
} else {
console.log('使用本地兜底配置')
return fallbackConfig
}
}
/**
* 手动刷新配置
*/
async refreshConfig(): Promise<{ success: boolean; config?: CloudMirrorConfig; error?: string }> {
try {
const cloudConfig = await this.fetchCloudConfig()
if (cloudConfig) {
return { success: true, config: cloudConfig }
} else {
return {
success: false,
error: '无法获取云端配置,继续使用当前配置',
config: this.getCurrentConfig() || undefined
}
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : '未知错误',
config: this.getCurrentConfig() || undefined
}
}
}
/**
* 获取配置状态信息
*/
getConfigStatus(): {
isUsingCloudConfig: boolean
version?: string
lastUpdated?: string
source: 'cloud' | 'fallback'
} {
const config = this.getCurrentConfig()
return {
isUsingCloudConfig: this.currentConfig !== null,
version: config?.version,
lastUpdated: config?.lastUpdated,
source: this.currentConfig ? 'cloud' : 'fallback'
}
}
}
// 导出单例实例
export const cloudConfigManager = CloudConfigManager.getInstance()

View File

@@ -14,6 +14,7 @@ import {
sortMirrorsBySpeed,
getFastestMirror,
} from '@/config/mirrors'
import { cloudConfigManager, type CloudMirrorConfig } from './cloudConfigManager'
/**
* 镜像源管理器类
@@ -23,6 +24,7 @@ export class MirrorManager {
private mirrorConfigs: MirrorCategory = { ...ALL_MIRRORS }
private apiEndpoints = { ...API_ENDPOINTS }
private downloadLinks = { ...DOWNLOAD_LINKS }
private isInitialized = false
private constructor() {}
@@ -36,6 +38,73 @@ export class MirrorManager {
return MirrorManager.instance
}
/**
* 初始化镜像管理器(从云端拉取配置)
*/
async initialize(): Promise<void> {
if (this.isInitialized) {
return
}
try {
// 准备兜底配置
const fallbackConfig: CloudMirrorConfig = {
version: '1.0.0-local',
lastUpdated: new Date().toISOString(),
mirrors: { ...ALL_MIRRORS },
apiEndpoints: { ...API_ENDPOINTS },
downloadLinks: { ...DOWNLOAD_LINKS }
}
// 从云端初始化配置
const config = await cloudConfigManager.initializeConfig(fallbackConfig)
// 更新本地配置
this.mirrorConfigs = config.mirrors
this.apiEndpoints = config.apiEndpoints
this.downloadLinks = config.downloadLinks
this.isInitialized = true
console.log('镜像管理器初始化完成')
} catch (error) {
console.error('镜像管理器初始化失败:', error)
// 使用默认配置
this.isInitialized = true
}
}
/**
* 手动刷新云端配置
*/
async refreshCloudConfig(): Promise<{ success: boolean; error?: string }> {
try {
const result = await cloudConfigManager.refreshConfig()
if (result.success && result.config) {
// 更新本地配置
this.mirrorConfigs = result.config.mirrors
this.apiEndpoints = result.config.apiEndpoints
this.downloadLinks = result.config.downloadLinks
return { success: true }
} else {
return { success: false, error: result.error }
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : '刷新配置失败'
}
}
}
/**
* 获取配置状态
*/
getConfigStatus() {
return cloudConfigManager.getConfigStatus()
}
/**
* 获取指定类型的镜像源列表
*/

View File

@@ -34,6 +34,7 @@ import AdminCheck from '@/components/initialization/AdminCheck.vue'
import AutoMode from '@/components/initialization/AutoMode.vue'
import ManualMode from '@/components/initialization/ManualMode.vue'
import type { DownloadProgress } from '@/types/initialization'
import { mirrorManager } from '@/utils/mirrorManager'
const router = useRouter()
@@ -48,6 +49,12 @@ const backendExists = ref(false)
const dependenciesInstalled = ref(false)
const serviceStarted = ref(false)
// 镜像配置状态
const mirrorConfigStatus = ref({
source: 'fallback' as 'cloud' | 'fallback',
version: ''
})
// 组件引用
const manualModeRef = ref()
@@ -230,6 +237,14 @@ function handleProgressUpdate(progress: DownloadProgress) {
onMounted(async () => {
console.log('初始化页面 onMounted 开始')
// 更新镜像配置状态
const status = mirrorManager.getConfigStatus()
mirrorConfigStatus.value = {
source: status.source,
version: status.version || ''
}
console.log('镜像配置状态:', mirrorConfigStatus.value)
// 测试配置系统
try {
console.log('测试配置系统...')

View File

@@ -0,0 +1,256 @@
<template>
<div class="mirror-test-container">
<div class="test-header">
<h2>镜像配置测试页面</h2>
<p>用于测试云端镜像配置拉取功能</p>
</div>
<div class="test-content">
<a-card title="配置状态" style="margin-bottom: 16px;">
<a-descriptions :column="1" bordered>
<a-descriptions-item label="配置来源">
<a-tag :color="configStatus.source === 'cloud' ? 'green' : 'orange'">
{{ configStatus.source === 'cloud' ? '云端配置' : '本地兜底配置' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="配置版本" v-if="configStatus.version">
{{ configStatus.version }}
</a-descriptions-item>
<a-descriptions-item label="最后更新" v-if="configStatus.lastUpdated">
{{ new Date(configStatus.lastUpdated).toLocaleString() }}
</a-descriptions-item>
</a-descriptions>
</a-card>
<a-card title="操作" style="margin-bottom: 16px;">
<a-space>
<a-button type="primary" @click="refreshConfig" :loading="refreshing">
刷新云端配置
</a-button>
<a-button @click="updateStatus">
更新状态
</a-button>
<a-button @click="testCloudUrl">
测试云端URL
</a-button>
</a-space>
</a-card>
<a-card title="当前镜像配置" v-if="currentConfig">
<a-tabs>
<a-tab-pane key="git" tab="Git镜像">
<a-table
:dataSource="currentConfig.mirrors.git"
:columns="mirrorColumns"
:pagination="false"
size="small"
/>
</a-tab-pane>
<a-tab-pane key="python" tab="Python镜像">
<a-table
:dataSource="currentConfig.mirrors.python"
:columns="mirrorColumns"
:pagination="false"
size="small"
/>
</a-tab-pane>
<a-tab-pane key="pip" tab="PIP镜像">
<a-table
:dataSource="currentConfig.mirrors.pip"
:columns="mirrorColumns"
:pagination="false"
size="small"
/>
</a-tab-pane>
</a-tabs>
</a-card>
<a-card title="测试日志" v-if="testLogs.length > 0">
<div class="test-logs">
<div
v-for="(log, index) in testLogs"
:key="index"
:class="['log-item', log.type]"
>
<span class="log-time">{{ log.time }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
</a-card>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import { mirrorManager } from '@/utils/mirrorManager'
import { cloudConfigManager, type CloudMirrorConfig } from '@/utils/cloudConfigManager'
interface TestLog {
time: string
message: string
type: 'info' | 'success' | 'error' | 'warning'
}
const configStatus = ref({
isUsingCloudConfig: false,
version: '',
lastUpdated: '',
source: 'fallback' as 'cloud' | 'fallback'
})
const currentConfig = ref<CloudMirrorConfig | null>(null)
const refreshing = ref(false)
const testLogs = ref<TestLog[]>([])
const mirrorColumns = [
{
title: 'Key',
dataIndex: 'key',
key: 'key',
width: 120
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: 150
},
{
title: 'URL',
dataIndex: 'url',
key: 'url',
ellipsis: true
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
width: 80
},
{
title: '连通性',
dataIndex: 'chinaConnectivity',
key: 'chinaConnectivity',
width: 100
}
]
const addLog = (message: string, type: TestLog['type'] = 'info') => {
testLogs.value.unshift({
time: new Date().toLocaleTimeString(),
message,
type
})
// 限制日志数量
if (testLogs.value.length > 50) {
testLogs.value = testLogs.value.slice(0, 50)
}
}
const updateStatus = () => {
const status = mirrorManager.getConfigStatus()
configStatus.value = status
currentConfig.value = cloudConfigManager.getCurrentConfig()
addLog('状态已更新', 'info')
}
const refreshConfig = async () => {
refreshing.value = true
addLog('开始刷新云端配置...', 'info')
try {
const result = await mirrorManager.refreshCloudConfig()
if (result.success) {
message.success('配置刷新成功')
addLog('云端配置刷新成功', 'success')
updateStatus()
} else {
message.warning(result.error || '刷新失败')
addLog(`刷新失败: ${result.error}`, 'warning')
}
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '未知错误'
message.error('刷新配置失败')
addLog(`刷新配置失败: ${errorMsg}`, 'error')
} finally {
refreshing.value = false
}
}
const testCloudUrl = async () => {
addLog('测试云端URL连通性...', 'info')
try {
const response = await fetch('https://download.auto-mas.top/d/AUTO_MAS/Server/mirrors.json', {
method: 'HEAD',
mode: 'no-cors'
})
addLog('云端URL连通性测试完成', 'success')
} catch (error) {
const errorMsg = error instanceof Error ? error.message : '连接失败'
addLog(`云端URL连通性测试失败: ${errorMsg}`, 'error')
}
}
onMounted(() => {
updateStatus()
addLog('镜像配置测试页面已加载', 'info')
})
</script>
<style scoped>
.mirror-test-container {
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.test-header {
text-align: center;
margin-bottom: 24px;
}
.test-header h2 {
margin-bottom: 8px;
}
.test-logs {
max-height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.log-item {
display: flex;
margin-bottom: 4px;
padding: 4px 8px;
border-radius: 4px;
}
.log-item.success {
color: #52c41a;
}
.log-item.error {
color: #ff4d4f;
}
.log-item.warning {
color: #faad14;
}
.log-time {
margin-right: 8px;
min-width: 80px;
}
.log-message {
flex: 1;
}
</style>

View File

@@ -15,6 +15,7 @@ import type { SelectValue } from 'ant-design-vue/es/select'
import type { SettingsData } from '../types/settings'
import { Service, type VersionOut } from '@/api'
import UpdateModal from '@/components/UpdateModal.vue'
import { mirrorManager } from '@/utils/mirrorManager'
const updateData = ref<Record<string, string[]>>({})
@@ -30,6 +31,15 @@ const activeKey = ref('basic')
const backendUpdateInfo = ref<VersionOut | null>(null)
// 镜像配置相关状态
const mirrorConfigStatus = ref({
isUsingCloudConfig: false,
version: '',
lastUpdated: '',
source: 'fallback' as 'cloud' | 'fallback'
})
const refreshingConfig = ref(false)
const settings = reactive<SettingsData>({
UI: {
IfShowTray: false,
@@ -239,6 +249,34 @@ const checkUpdate = async () => {
}
}
// 镜像配置相关方法
const updateMirrorConfigStatus = () => {
const status = mirrorManager.getConfigStatus()
mirrorConfigStatus.value = status
}
const refreshMirrorConfig = async () => {
refreshingConfig.value = true
try {
const result = await mirrorManager.refreshCloudConfig()
if (result.success) {
message.success('镜像配置刷新成功')
updateMirrorConfigStatus()
} else {
message.warning(result.error || '刷新失败,继续使用当前配置')
}
} catch (error) {
console.error('刷新镜像配置失败:', error)
message.error('刷新镜像配置失败')
} finally {
refreshingConfig.value = false
}
}
const goToMirrorTest = () => {
router.push('/mirror-test')
}
// 确认回调
const onUpdateConfirmed = () => {
updateVisible.value = false
@@ -247,6 +285,7 @@ const onUpdateConfirmed = () => {
onMounted(() => {
loadSettings()
getBackendVersion()
updateMirrorConfigStatus()
})
</script>
@@ -1168,6 +1207,97 @@ onMounted(() => {
</div>
</a-tab-pane>
<!-- 镜像配置 -->
<a-tab-pane key="mirrors" tab="镜像配置">
<div class="tab-content">
<div class="form-section">
<div class="section-header">
<h3>镜像站配置</h3>
<p class="section-description">
管理下载站和加速站配置支持从云端自动更新最新的镜像站列表
</p>
</div>
<a-row :gutter="24">
<a-col :span="24">
<div class="form-item-vertical">
<div class="form-label-wrapper">
<span class="form-label">配置状态</span>
</div>
<a-descriptions :column="1" bordered size="small">
<a-descriptions-item label="配置来源">
<a-tag :color="mirrorConfigStatus.source === 'cloud' ? 'green' : 'orange'">
{{ mirrorConfigStatus.source === 'cloud' ? '云端配置' : '本地兜底配置' }}
</a-tag>
</a-descriptions-item>
<a-descriptions-item label="配置版本" v-if="mirrorConfigStatus.version">
{{ mirrorConfigStatus.version }}
</a-descriptions-item>
<a-descriptions-item label="最后更新" v-if="mirrorConfigStatus.lastUpdated">
{{ new Date(mirrorConfigStatus.lastUpdated).toLocaleString() }}
</a-descriptions-item>
</a-descriptions>
</div>
</a-col>
</a-row>
<a-row :gutter="24" style="margin-top: 24px;">
<a-col :span="24">
<div class="form-item-vertical">
<div class="form-label-wrapper">
<span class="form-label">配置管理</span>
</div>
<a-space size="large">
<a-button
type="primary"
@click="refreshMirrorConfig"
:loading="refreshingConfig"
size="large"
>
刷新云端配置
</a-button>
<a-button
@click="updateMirrorConfigStatus"
size="large"
>
更新状态
</a-button>
<a-button
@click="goToMirrorTest"
size="large"
>
测试页面
</a-button>
</a-space>
</div>
</a-col>
</a-row>
<a-row :gutter="24" style="margin-top: 24px;">
<a-col :span="24">
<div class="form-item-vertical">
<div class="form-label-wrapper">
<span class="form-label">说明</span>
</div>
<a-alert
message="镜像配置说明"
type="info"
show-icon
>
<template #description>
<ul style="margin: 8px 0; padding-left: 20px;">
<li>应用启动时会自动尝试从云端拉取最新的镜像站配置</li>
<li>可以手动点击"刷新云端配置"按钮获取最新配置</li>
</ul>
</template>
</a-alert>
</div>
</a-col>
</a-row>
</div>
</div>
</a-tab-pane>
<!-- 其他 -->
<a-tab-pane key="others" tab="其他">
<div class="tab-content">